From 8309a2d2e2bffd247ddbcf80239faeadda369fa8 Mon Sep 17 00:00:00 2001 From: "DESKTOP-I3JPKHK\\wy" <1111> Date: Thu, 25 Sep 2025 13:55:51 +0800 Subject: [PATCH] 11111 --- .../README_mac_build.md | 101 ++++ .../flutter_build_apk_onmac.sh | 134 +++++ .../utils/app_upgrade/UpdateDialog.dart | 12 +- .../components/bottom_operation_bar.dart | 533 +++++++++++++----- .../components/question_paper_view.dart | 299 +++++++++- .../configuration_files/index.dart | 82 +-- 6 files changed, 985 insertions(+), 176 deletions(-) create mode 100644 making_school_asignment_app/README_mac_build.md create mode 100644 making_school_asignment_app/flutter_build_apk_onmac.sh diff --git a/making_school_asignment_app/README_mac_build.md b/making_school_asignment_app/README_mac_build.md new file mode 100644 index 0000000..5a0dbc3 --- /dev/null +++ b/making_school_asignment_app/README_mac_build.md @@ -0,0 +1,101 @@ +# macOS Flutter APK 构建脚本使用说明 + +## 概述 +`flutter_build_apk_onmac.sh` 是基于Windows版本的 `flutter_build_apk_onwindows.bat` 脚本改写的macOS版本,用于在macOS系统上构建Flutter APK。 + +## 主要功能 +1. **进程清理**: 终止相关的Java和Gradle进程 +2. **项目清理**: 执行 `flutter clean` +3. **文件清理**: 删除 `build`、`.dart_tool`、`pubspec.lock` 等文件夹 +4. **依赖管理**: 执行 `flutter pub get` +5. **代码生成**: 运行 `build_runner` +6. **APK构建**: 构建release版本的APK +7. **结果展示**: 自动打开APK所在文件夹并显示文件信息 + +## 使用方法 + +### 1. 设置执行权限 +在终端中执行以下命令为脚本添加执行权限: +```bash +chmod +x flutter_build_apk_onmac.sh +``` + +### 2. 运行脚本 +```bash +./flutter_build_apk_onmac.sh +``` + +或者直接用bash运行: +```bash +bash flutter_build_apk_onmac.sh +``` + +## 系统要求 +- macOS 系统 +- 已安装 Flutter SDK +- 已安装 Android SDK +- 项目已正确配置Android构建环境 + +## 脚本特性 + +### 错误处理 +- 使用 `set -e` 确保任何命令失败时脚本立即退出 +- 详细的错误日志输出 +- 进程清理的容错处理 + +### 日志系统 +脚本包含彩色日志输出: +- 🔵 **INFO**: 一般信息 +- 🟢 **SUCCESS**: 成功操作 +- 🟡 **WARNING**: 警告信息 +- 🔴 **ERROR**: 错误信息 + +### 强制删除功能 +- 实现了类似Windows版本的强制删除机制 +- 支持重试机制(最多3次) +- 指数退避策略 + +### 自动化功能 +- 自动打开APK所在文件夹(使用macOS的`open`命令) +- 显示生成的APK文件列表 +- 构建完成后的详细信息展示 + +## 与Windows版本的主要差异 + +| 功能 | Windows版本 | macOS版本 | +|------|------------|-----------| +| 脚本语言 | Batch (.bat) | Bash (.sh) | +| 进程终止 | `taskkill` | `pkill` | +| 文件删除 | `del`, `rmdir` | `rm -rf` | +| 打开文件夹 | `explorer` | `open` | +| 编码设置 | `chcp 65001` | UTF-8默认支持 | +| 错误处理 | `ERRORLEVEL` | 退出码 `$?` | + +## 故障排除 + +### 权限问题 +如果遇到权限错误,确保脚本有执行权限: +```bash +ls -la flutter_build_apk_onmac.sh +# 应该显示类似: -rwxr-xr-x 的权限 +``` + +### Flutter环境问题 +确保Flutter环境正确配置: +```bash +flutter doctor +``` + +### Android SDK问题 +确保Android SDK路径正确配置: +```bash +echo $ANDROID_HOME +``` + +## 输出文件位置 +构建成功后,APK文件将位于: +``` +build/app/outputs/flutter-apk/app-release.apk +``` + +脚本会自动打开此文件夹并显示详细信息。 diff --git a/making_school_asignment_app/flutter_build_apk_onmac.sh b/making_school_asignment_app/flutter_build_apk_onmac.sh new file mode 100644 index 0000000..3a539dd --- /dev/null +++ b/making_school_asignment_app/flutter_build_apk_onmac.sh @@ -0,0 +1,134 @@ +#!/bin/bash + +# macOS版本的Flutter APK构建脚本 +# 基于Windows版本的flutter_build_apk_onwindows.bat改写 + +# 设置错误处理 +set -e + +# 颜色定义 +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# 日志函数 +log_info() { + echo -e "${BLUE}[INFO]${NC} $1" +} + +log_success() { + echo -e "${GREEN}[SUCCESS]${NC} $1" +} + +log_warning() { + echo -e "${YELLOW}[WARNING]${NC} $1" +} + +log_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +# 获取项目根目录 +PROJECT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$PROJECT_DIR" + +log_info "项目目录: $PROJECT_DIR" + +# 1. 终止相关进程 +log_info "终止相关进程..." +pkill -f "java.*gradle" 2>/dev/null || true +pkill -f "gradle" 2>/dev/null || true +sleep 2 + +# 强制删除函数 +force_delete() { + local target="$1" + local max_retries=3 + local retry_delay=1 + + if [ ! -e "$target" ]; then + return 0 + fi + + for ((i=1; i<=max_retries; i++)); do + log_info "尝试删除 $target (第 $i 次,共 $max_retries 次)" + + # 尝试删除 + rm -rf "$target" 2>/dev/null || true + + # 检查是否删除成功 + if [ ! -e "$target" ]; then + log_success "成功删除 $target" + return 0 + fi + + # 如果未成功,等待后重试 + if [ $i -lt $max_retries ]; then + sleep $retry_delay + retry_delay=$((retry_delay * 2)) # 指数退避策略 + fi + done + + log_warning "警告: 无法完全删除 $target,某些文件可能正在使用中" + return 1 +} + +# 2. Flutter清理 +log_info "运行 flutter clean..." +if ! flutter clean; then + log_error "flutter clean 失败,错误码: $?" + exit 1 +fi + +# 3. 强制删除构建目录 +log_info "删除残留文件" +force_delete "build" +force_delete ".dart_tool" +force_delete "pubspec.lock" + +# 4. 获取依赖包 +log_info "运行 flutter pub get..." +if ! flutter pub get; then + log_error "flutter pub get 失败,错误码: $?" + exit 1 +fi + +# 5. 运行build_runner +log_info "运行 build_runner..." +if ! flutter packages pub run build_runner build --delete-conflicting-outputs; then + log_error "build_runner 失败,错误码: $?" + exit 1 +fi + +# 6. 构建release APK +log_info "构建 release APK..." +if ! flutter build apk --release; then + log_error "APK 构建失败,错误码: $?" + exit 1 +fi + +# 7. 打开APK所在文件夹 +log_info "打开 APK 目录" +APK_DIR="$PROJECT_DIR/build/app/outputs/flutter-apk" +if [ -d "$APK_DIR" ]; then + # macOS使用open命令打开Finder + open "$APK_DIR" +else + log_warning "APK 目录不存在: $APK_DIR" +fi + +echo +log_success "所有步骤成功完成!" +log_success "APK 文件生成位置: $APK_DIR" +echo + +# 显示生成的APK文件信息 +if [ -d "$APK_DIR" ]; then + echo "生成的APK文件:" + ls -la "$APK_DIR"/*.apk 2>/dev/null || log_warning "未找到APK文件" +fi + +sleep 3 +exit 0 diff --git a/making_school_asignment_app/lib/common/utils/app_upgrade/UpdateDialog.dart b/making_school_asignment_app/lib/common/utils/app_upgrade/UpdateDialog.dart index 8b60c11..3475a88 100644 --- a/making_school_asignment_app/lib/common/utils/app_upgrade/UpdateDialog.dart +++ b/making_school_asignment_app/lib/common/utils/app_upgrade/UpdateDialog.dart @@ -14,6 +14,8 @@ * @LastEditors: Please set LastEditors * @LastEditTime: 2021-01-12 15:08:43 */ +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_widget_from_html_core/flutter_widget_from_html_core.dart'; import 'package:get/get.dart'; import 'package:making_school_asignment_app/common/utils/app_upgrade/DownloadApk.dart'; @@ -22,8 +24,6 @@ import 'package:making_school_asignment_app/common/utils/app_upgrade/upgradeLogi import 'package:making_school_asignment_app/common/utils/utils.dart'; import 'package:making_school_asignment_app/page/global_widget/my_text.dart'; import 'package:percent_indicator/linear_percent_indicator.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher_string.dart'; @@ -61,7 +61,7 @@ class UpdateDialog extends Dialog { children: [ Container( alignment: Alignment.center, - margin: EdgeInsets.only(top:isPad()? 136.h : 128.h, bottom: 10.h), + margin: EdgeInsets.only(top: isPad() ? 136.h : 128.h, bottom: 10.h), child: quickText( updateAppEvent.title, size: 18.sp, @@ -184,7 +184,8 @@ class DownloadButton extends StatelessWidget { gradient: LinearGradient(colors: [primaryColor, primaryColor.withOpacity(0.7)]), ), child: MaterialButton( - onPressed: () => easyThrottle('DownloadButton_App_Upgrade', duration: const Duration(milliseconds: 1000), () async { + onPressed: () => + easyThrottle('DownloadButton_App_Upgrade', duration: const Duration(milliseconds: 1000), () async { if (deviceInfo == "android" && updateAppEvent.equipment == Equipment.android) { // 权限检查 判断是否有读写内存的权限 bool flag = await UpgradePermission(updateAppEvent.deviceInfo).checkPermission(context, updateAppEvent); @@ -215,7 +216,8 @@ class DownloadButton extends StatelessWidget { // await autoUpdater.setScheduledCheckInterval(0); // } }), - child: quickText(!logic.loadingApk.value ? '立即体验' : '正在下载...', size: 16.sp, color: Colors.white, fontWeight: FontWeight.w500), + child: quickText(!logic.loadingApk.value ? '立即体验' : '正在下载...', + size: 16.sp, color: Colors.white, fontWeight: FontWeight.w500), ), ); }); diff --git a/making_school_asignment_app/lib/page/home_page/children/homework_review/components/bottom_operation_bar.dart b/making_school_asignment_app/lib/page/home_page/children/homework_review/components/bottom_operation_bar.dart index 44aa0a6..e0315e6 100644 --- a/making_school_asignment_app/lib/page/home_page/children/homework_review/components/bottom_operation_bar.dart +++ b/making_school_asignment_app/lib/page/home_page/children/homework_review/components/bottom_operation_bar.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; @@ -62,156 +63,379 @@ class _BottomAnnotationSwitchJobState extends State @override Widget build(BuildContext context) { return SafeArea( - left: false, - right: false, - top: false, - child: Container( - width: double.infinity, - height: _animationController.value * 70.h, - decoration: BoxDecoration( - color: const Color.fromRGBO(83, 83, 83, 1), - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.1), - blurRadius: 8, - offset: const Offset(0, -2), + left: false, + right: false, + top: false, + child: ClipRRect( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(25.r), + topRight: Radius.circular(25.r), + ), + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: 25, sigmaY: 25), + child: Container( + width: double.infinity, + height: _animationController.value * 70.h, + decoration: BoxDecoration( + // 增强的液态玻璃背景 + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + Colors.white.withOpacity(0.12), + Colors.white.withOpacity(0.08), + Colors.white.withOpacity(0.06), + Colors.black.withOpacity(0.03), + ], + stops: const [0.0, 0.3, 0.7, 1.0], ), - ], - ), - child: Row( - children: [ - Expanded( - flex: 7, - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + // 圆润的液态边缘 + borderRadius: BorderRadius.only( + topLeft: Radius.circular(25.r), + topRight: Radius.circular(25.r), + ), + // 多层阴影增强液态质感 + boxShadow: [ + // 主阴影 - 深度感 + BoxShadow( + color: Colors.black.withOpacity(0.25), + blurRadius: 30, + offset: const Offset(0, -8), + spreadRadius: -10, + ), + // 内部高光 - 玻璃反射 + BoxShadow( + color: Colors.white.withOpacity(0.15), + blurRadius: 3, + offset: const Offset(0, 2), + ), + // 彩色光晕 - 液态折射 + BoxShadow( + color: Colors.blue.withOpacity(0.05), + blurRadius: 20, + offset: const Offset(0, -3), + spreadRadius: -5, + ), + ], + ), + child: Stack( + children: [ + // 增强的液态边缘装饰层 + Positioned( + top: 0, + left: 0, + right: 0, + height: 5.h, + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(25.r), + topRight: Radius.circular(25.r), + ), + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Colors.white.withOpacity(0.25), + Colors.white.withOpacity(0.12), + Colors.white.withOpacity(0.05), + Colors.transparent, + ], + stops: const [0.0, 0.3, 0.6, 1.0], + ), + ), + ), + ), + // 液态波纹效果 + Positioned.fill( + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(25.r), + topRight: Radius.circular(25.r), + ), + gradient: RadialGradient( + center: const Alignment(0.0, -0.8), + radius: 1.5, + colors: [ + Colors.white.withOpacity(0.08), + Colors.white.withOpacity(0.04), + Colors.transparent, + Colors.white.withOpacity(0.02), + Colors.transparent, + ], + stops: const [0.0, 0.2, 0.4, 0.7, 1.0], + ), + ), + ), + ), + // 液态流动光效 + Positioned.fill( + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(25.r), + topRight: Radius.circular(25.r), + ), + gradient: LinearGradient( + begin: const Alignment(-1.0, -1.0), + end: const Alignment(1.0, 1.0), + colors: [ + Colors.transparent, + Colors.white.withOpacity(0.06), + Colors.transparent, + Colors.white.withOpacity(0.03), + Colors.transparent, + ], + stops: const [0.0, 0.25, 0.5, 0.75, 1.0], + ), + ), + ), + ), + // 顶部液滴效果 + Positioned( + top: -2, + left: 0, + right: 0, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + _buildLiquidDroplet(width: 60.w, height: 4.h), + _buildLiquidDroplet(width: 40.w, height: 3.h), + _buildLiquidDroplet(width: 50.w, height: 3.5.h), + ], + ), + ), + // 主要内容 + Row( children: [ Expanded( - child: Row( + flex: 7, + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - // 全对按钮 Expanded( - child: _buildActionButton( - '全对', - Icons.check_circle_outline, - () => easyThrottle('homework_bottom_operation_bar_scoring_related', - () => _homeworkLogic.allPairs(context)), - isEnabled: !_homeworkLogic.state.submitLoading.value, - isPrimary: true, + child: Row( + children: [ + // 全对按钮 + Expanded( + child: _buildActionButton( + '全对', + Icons.check_circle_outline, + () => easyThrottle('homework_bottom_operation_bar_scoring_related', + () => _homeworkLogic.allPairs(context)), + isEnabled: !_homeworkLogic.state.submitLoading.value, + isPrimary: true, + ), + ), + _buildGlassDivider(), + // 注解笔 + Expanded( + child: Obx(() => _buildIconButton( + const IconData(0xe635, fontFamily: "AlibabaIcon"), + () => easyThrottle('homework_bottom_action_bar_annotations', () { + // 互斥:启用注解笔,禁用滑动 + _logicControl.gestureMove.value = false; + _logicControl.pen.value = true; + }), + isActive: _logicControl.pen.value, + tooltip: '注解笔', + )), + ), + _buildGlassDivider(), + // 滑动试题 + Expanded( + child: Obx(() => _buildIconButton( + const IconData(0xe636, fontFamily: "AlibabaIcon"), + () => easyThrottle('homework_bottom_action_bar_annotations', () { + // 互斥:启用滑动,禁用注解笔 + _logicControl.pen.value = false; + _logicControl.gestureMove.value = true; + }), + isActive: _logicControl.gestureMove.value, + tooltip: '滑动试题', + )), + ), + ], ), ), - Container(width: 0.5.w, height: double.infinity, color: Colors.white.withOpacity(0.3)), - // 注解笔 + _buildHorizontalGlassDivider(), Expanded( - child: Obx(() => _buildIconButton( - const IconData(0xe635, fontFamily: "AlibabaIcon"), - () => easyThrottle('homework_bottom_action_bar_annotations', () { - // 互斥:启用注解笔,禁用滑动 - _logicControl.gestureMove.value = false; - _logicControl.pen.value = true; - }), - isActive: _logicControl.pen.value, - tooltip: '注解笔', - )), - ), - Container(width: 0.5.w, height: double.infinity, color: Colors.white.withOpacity(0.3)), - // 滑动试题 - Expanded( - child: Obx(() => _buildIconButton( - const IconData(0xe636, fontFamily: "AlibabaIcon"), - () => easyThrottle('homework_bottom_action_bar_annotations', () { - // 互斥:启用滑动,禁用注解笔 - _logicControl.pen.value = false; - _logicControl.gestureMove.value = true; - }), - isActive: _logicControl.gestureMove.value, - tooltip: '滑动试题', - )), + child: Row( + children: [ + // 全错按钮 + Expanded( + child: _buildActionButton( + '全错', + Icons.cancel_outlined, + () => easyThrottle('homework_bottom_operation_bar_scoring_related', + () => _homeworkLogic.allWrongRating(context)), + isEnabled: !_homeworkLogic.state.submitLoading.value, + isPrimary: false, + ), + ), + _buildGlassDivider(), + // 撤销上一步 + Expanded( + child: _buildIconButton( + const IconData(0xe638, fontFamily: "AlibabaIcon"), + () => easyThrottle( + 'homework_bottom_action_bar_annotations', + () => eventFire(model: BottomOperationBar(revokeAll: false, revokePreStep: true)), + ), + tooltip: '撤销上一步', + ), + ), + _buildGlassDivider(), + // 全部撤销 + Expanded( + child: _buildIconButton( + const IconData(0xe637, fontFamily: "AlibabaIcon"), + () => easyThrottle( + 'homework_bottom_action_bar_annotations', + () => eventFire(model: BottomOperationBar(revokeAll: true, revokePreStep: false)), + ), + tooltip: '全部撤销', + ), + ), + ], + ), ), ], ), ), - Container(width: double.infinity, color: Colors.white.withOpacity(0.3), height: 0.5.h), + _buildGlassDivider(), Expanded( + flex: 3, child: Row( children: [ - // 全错按钮 Expanded( child: _buildActionButton( - '全错', - Icons.cancel_outlined, + '取消', + Icons.clear, () => easyThrottle('homework_bottom_operation_bar_scoring_related', - () => _homeworkLogic.allWrongRating(context)), - isEnabled: !_homeworkLogic.state.submitLoading.value, + () => _homeworkLogic.cancelAllRatings()), + isEnabled: true, isPrimary: false, + isSecondary: true, ), ), - Container(width: 0.5.w, height: double.infinity, color: Colors.white.withOpacity(0.3)), - // 撤销上一步 + _buildGlassDivider(), Expanded( - child: _buildIconButton( - const IconData(0xe638, fontFamily: "AlibabaIcon"), - () => easyThrottle( - 'homework_bottom_action_bar_annotations', - () => eventFire(model: BottomOperationBar(revokeAll: false, revokePreStep: true)), - ), - tooltip: '撤销上一步', - ), - ), - Container(width: 0.5.w, height: double.infinity, color: Colors.white.withOpacity(0.3)), - // 全部撤销 - Expanded( - child: _buildIconButton( - const IconData(0xe637, fontFamily: "AlibabaIcon"), - () => easyThrottle( - 'homework_bottom_action_bar_annotations', - () => eventFire(model: BottomOperationBar(revokeAll: true, revokePreStep: false)), - ), - tooltip: '全部撤销', - ), + child: Obx(() { + final submitLoading = _homeworkLogic.state.submitLoading.value; + return _buildActionButton( + '提交', + Icons.send, + () => easyThrottle('homework_bottom_operation_bar_scoring_related', + () => _homeworkLogic.submit(context)), + isEnabled: !submitLoading, + isPrimary: true, + isLoading: submitLoading, + ); + }), ), ], ), ), ], ), - ), - Container(width: 0.5.w, height: double.infinity, color: Colors.white.withOpacity(0.3)), - Expanded( - flex: 3, - child: Row( - children: [ - Expanded( - child: _buildActionButton( - '取消', - Icons.clear, - () => easyThrottle( - 'homework_bottom_operation_bar_scoring_related', () => _homeworkLogic.cancelAllRatings()), - isEnabled: true, - isPrimary: false, - isSecondary: true, - ), - ), - Container(width: 0.5.w, height: double.infinity, color: Colors.white.withOpacity(0.3)), - Expanded( - child: Obx(() { - final submitLoading = _homeworkLogic.state.submitLoading.value; - return _buildActionButton( - '提交', - Icons.send, - () => easyThrottle( - 'homework_bottom_operation_bar_scoring_related', () => _homeworkLogic.submit(context)), - isEnabled: !submitLoading, - isPrimary: true, - isLoading: submitLoading, - ); - }), - ), - ], - ), - ), - ], + ], + ), ), - )); + ), + ), + ); + } + + // 液滴效果 + Widget _buildLiquidDroplet({required double width, required double height}) { + return Container( + width: width, + height: height, + decoration: BoxDecoration( + borderRadius: BorderRadius.only( + bottomLeft: Radius.circular(width / 2), + bottomRight: Radius.circular(width / 2), + ), + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Colors.white.withOpacity(0.3), + Colors.white.withOpacity(0.15), + Colors.white.withOpacity(0.05), + Colors.transparent, + ], + stops: const [0.0, 0.3, 0.7, 1.0], + ), + boxShadow: [ + BoxShadow( + color: Colors.white.withOpacity(0.2), + blurRadius: 4, + offset: const Offset(0, 1), + ), + ], + ), + ); + } + + // 增强的垂直玻璃分隔线 + Widget _buildGlassDivider() { + return Container( + width: 1.w, + height: double.infinity, + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Colors.white.withOpacity(0.05), + Colors.white.withOpacity(0.15), + Colors.white.withOpacity(0.25), + Colors.white.withOpacity(0.15), + Colors.white.withOpacity(0.05), + ], + stops: const [0.0, 0.2, 0.5, 0.8, 1.0], + ), + boxShadow: [ + BoxShadow( + color: Colors.white.withOpacity(0.1), + blurRadius: 2, + spreadRadius: 0.5, + ), + ], + ), + ); + } + + // 增强的水平玻璃分隔线 + Widget _buildHorizontalGlassDivider() { + return Container( + width: double.infinity, + height: 1.h, + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + colors: [ + Colors.white.withOpacity(0.03), + Colors.white.withOpacity(0.08), + Colors.white.withOpacity(0.2), + Colors.white.withOpacity(0.08), + Colors.white.withOpacity(0.03), + ], + stops: const [0.0, 0.2, 0.5, 0.8, 1.0], + ), + boxShadow: [ + BoxShadow( + color: Colors.white.withOpacity(0.15), + blurRadius: 3, + spreadRadius: 0.5, + ), + ], + ), + ); } Widget _buildActionButton( @@ -230,6 +454,9 @@ class _BottomAnnotationSwitchJobState extends State color: Colors.transparent, child: InkWell( onTap: isEnabled ? onPressed : null, + borderRadius: BorderRadius.circular(8.r), + splashColor: isPrimary ? Colors.white.withOpacity(0.2) : primaryColor.withOpacity(0.2), + highlightColor: isPrimary ? Colors.white.withOpacity(0.1) : primaryColor.withOpacity(0.1), child: Semantics( label: text, hint: isLoading ? '正在处理中' : (isEnabled ? '点击执行$text操作' : '$text按钮已禁用'), @@ -239,12 +466,35 @@ class _BottomAnnotationSwitchJobState extends State width: double.infinity, height: double.infinity, decoration: BoxDecoration( - color: isPrimary - ? (isEnabled ? primaryColor : primaryColor.withOpacity(0.5)) - : isSecondary - ? Colors.transparent - : Colors.transparent, - borderRadius: BorderRadius.circular(4.r), + // 液态玻璃背景 + gradient: isPrimary + ? LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: isEnabled + ? [ + primaryColor.withOpacity(0.8), + primaryColor.withOpacity(0.6), + ] + : [ + primaryColor.withOpacity(0.3), + primaryColor.withOpacity(0.2), + ], + ) + : LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + Colors.white.withOpacity(0.08), + Colors.white.withOpacity(0.03), + ], + ), + borderRadius: BorderRadius.circular(8.r), + // 液态玻璃边框 + border: Border.all( + color: isPrimary ? Colors.white.withOpacity(0.2) : Colors.white.withOpacity(0.1), + width: 0.5, + ), ), child: Column( mainAxisAlignment: MainAxisAlignment.center, @@ -300,14 +550,39 @@ class _BottomAnnotationSwitchJobState extends State color: Colors.transparent, child: InkWell( onTap: onPressed, + borderRadius: BorderRadius.circular(8.r), + splashColor: primaryColor.withOpacity(0.2), + highlightColor: primaryColor.withOpacity(0.1), child: Tooltip( message: tooltip ?? '', child: Container( width: double.infinity, height: double.infinity, decoration: BoxDecoration( - color: isActive ? primaryColor.withOpacity(0.2) : Colors.transparent, - borderRadius: BorderRadius.circular(4.r), + // 液态玻璃背景 + gradient: isActive + ? LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + primaryColor.withOpacity(0.15), + primaryColor.withOpacity(0.08), + ], + ) + : LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + Colors.white.withOpacity(0.05), + Colors.white.withOpacity(0.02), + ], + ), + borderRadius: BorderRadius.circular(8.r), + // 液态玻璃边框 + border: Border.all( + color: isActive ? primaryColor.withOpacity(0.3) : Colors.white.withOpacity(0.08), + width: 0.5, + ), ), child: Column( mainAxisAlignment: MainAxisAlignment.center, diff --git a/making_school_asignment_app/lib/page/home_page/children/homework_review/components/question_paper_view.dart b/making_school_asignment_app/lib/page/home_page/children/homework_review/components/question_paper_view.dart index a4c22fa..8490d06 100644 --- a/making_school_asignment_app/lib/page/home_page/children/homework_review/components/question_paper_view.dart +++ b/making_school_asignment_app/lib/page/home_page/children/homework_review/components/question_paper_view.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:ui'; import 'package:flutter/cupertino.dart' hide TransformationController; import 'package:flutter/material.dart'; @@ -80,12 +81,9 @@ class QuestionPaperView extends GetView { child: Obx(() { LastPage? lastPageVal = sateData.data.value?.lastPage; if (lastPageVal == null) return const SizedBox(); - return FloatingActionButton( + return GlassmorphicButton( heroTag: '点击前往上一题', tooltip: '点击前往上一题', - focusColor: Theme.of(context).primaryColor, - backgroundColor: const Color.fromRGBO(24, 32, 32, 0.05), - elevation: 10.r, onPressed: () => easyThrottle('TestQuestionSwitch', () { var param = sateData.param.value; param.studentId = lastPageVal.studentId; @@ -104,11 +102,9 @@ class QuestionPaperView extends GetView { NextPage? nextPageVal = sateData.data.value?.nextPage; if (nextPageVal == null) return const SizedBox(); - return FloatingActionButton( + return GlassmorphicButton( heroTag: '点击前往下一题', tooltip: '点击前往下一题', - elevation: 10.r, - backgroundColor: const Color.fromRGBO(24, 32, 32, 0.05), onPressed: () => easyThrottle('TestQuestionSwitch', () { var param = sateData.param.value; param.studentId = nextPageVal.studentId; @@ -683,3 +679,292 @@ class DrawingPainter extends CustomPainter { @override bool shouldRebuildSemantics(CustomPainter oldDelegate) => false; } + +// iOS风格的液态玻璃态按钮 +class GlassmorphicButton extends StatelessWidget { + final Widget child; + final VoidCallback? onPressed; + final String? heroTag; + final String? tooltip; + final double size; + final Color? backgroundColor; + final Color? borderColor; + + const GlassmorphicButton({ + super.key, + required this.child, + this.onPressed, + this.heroTag, + this.tooltip, + this.size = 56.0, + this.backgroundColor, + this.borderColor, + }); + + @override + Widget build(BuildContext context) { + return SizedBox( + width: size, + height: size, + child: ClipRRect( + borderRadius: BorderRadius.circular(size / 2), + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: 3, sigmaY: 3), + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(size / 2), + // 几乎不可见的背景 + gradient: RadialGradient( + center: Alignment.topLeft, + radius: 1.2, + colors: [ + Colors.white.withOpacity(0.002), + Colors.white.withOpacity(0.004), + Colors.white.withOpacity(0.001), + ], + stops: const [0.0, 0.4, 1.0], + ), + // 液态玻璃边缘 - 多层渐变边框效果 + border: Border.all( + color: Colors.transparent, + width: 0, + ), + // 几乎不可见的阴影 + boxShadow: [ + // 微弱阴影 + BoxShadow( + color: Colors.black.withOpacity(0.005), + blurRadius: 4, + offset: const Offset(0, 1), + spreadRadius: -1, + ), + // 极微弱高光 + BoxShadow( + color: Colors.white.withOpacity(0.008), + blurRadius: 0.5, + offset: const Offset(0, -0.3), + ), + ], + ), + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(size / 2), + // 几乎不可见的流动 + gradient: SweepGradient( + center: Alignment.center, + startAngle: 0, + endAngle: 6.28, + colors: [ + Colors.transparent, + Colors.white.withOpacity(0.0008), + Colors.transparent, + Colors.white.withOpacity(0.0015), + Colors.transparent, + Colors.white.withOpacity(0.0008), + Colors.transparent, + ], + stops: const [0.0, 0.15, 0.3, 0.5, 0.65, 0.85, 1.0], + ), + ), + child: Material( + color: Colors.transparent, + child: InkWell( + borderRadius: BorderRadius.circular(size / 2), + onTap: onPressed, + splashColor: Colors.white.withOpacity(0.008), + highlightColor: Colors.white.withOpacity(0.003), + child: Container( + width: size, + height: size, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(size / 2), + // 几乎不存在的表面 + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Colors.white.withOpacity(0.001), + Colors.transparent, + Colors.transparent, + Colors.white.withOpacity(0.0005), + ], + stops: const [0.0, 0.2, 0.8, 1.0], + ), + ), + child: Stack( + children: [ + // 液态边缘效果 - 表面张力 + Positioned.fill( + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(size / 2), + // 边缘渐变 - 模拟液体表面张力 + gradient: RadialGradient( + center: Alignment.center, + radius: 0.5, + colors: [ + Colors.transparent, + Colors.transparent, + Colors.white.withOpacity(0.008), + Colors.white.withOpacity(0.015), + Colors.white.withOpacity(0.025), + ], + stops: const [0.0, 0.7, 0.85, 0.93, 1.0], + ), + ), + ), + ), + // 液态边缘流动效果 + Positioned.fill( + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(size / 2), + // 环形边缘高光 + gradient: SweepGradient( + center: Alignment.center, + colors: [ + Colors.white.withOpacity(0.012), + Colors.transparent, + Colors.white.withOpacity(0.008), + Colors.transparent, + Colors.white.withOpacity(0.015), + Colors.transparent, + Colors.white.withOpacity(0.01), + Colors.transparent, + Colors.white.withOpacity(0.012), + ], + stops: const [0.0, 0.125, 0.25, 0.375, 0.5, 0.625, 0.75, 0.875, 1.0], + ), + ), + ), + ), + // 液态玻璃流动光效 + Positioned.fill( + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(size / 2), + gradient: LinearGradient( + begin: const Alignment(-0.8, -0.8), + end: const Alignment(0.8, 0.8), + colors: [ + Colors.white.withOpacity(0.0008), + Colors.transparent, + Colors.transparent, + Colors.white.withOpacity(0.0003), + ], + stops: const [0.0, 0.3, 0.7, 1.0], + ), + ), + ), + ), + // 液体高光点 - 模拟液体表面反射 + Positioned( + top: size * 0.2, + left: size * 0.3, + child: Container( + width: size * 0.08, + height: size * 0.08, + decoration: BoxDecoration( + shape: BoxShape.circle, + gradient: RadialGradient( + colors: [ + Colors.white.withOpacity(0.0015), + Colors.transparent, + ], + ), + ), + ), + ), + // 另一个液体反光点 + Positioned( + bottom: size * 0.3, + right: size * 0.25, + child: Container( + width: size * 0.05, + height: size * 0.05, + decoration: BoxDecoration( + shape: BoxShape.circle, + gradient: RadialGradient( + colors: [ + Colors.white.withOpacity(0.001), + Colors.transparent, + ], + ), + ), + ), + ), + // 液体流动线条 + Positioned( + top: size * 0.4, + left: size * 0.15, + child: Container( + width: size * 0.7, + height: 0.8, + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + Colors.transparent, + Colors.white.withOpacity(0.0012), + Colors.transparent, + ], + ), + borderRadius: BorderRadius.circular(0.5), + ), + ), + ), + // 边缘液滴效果 - 顶部 + Positioned( + top: -2, + left: size * 0.35, + child: Container( + width: size * 0.3, + height: 4, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(2), + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Colors.white.withOpacity(0.02), + Colors.white.withOpacity(0.008), + Colors.transparent, + ], + ), + ), + ), + ), + // 边缘液滴效果 - 底部 + Positioned( + bottom: -2, + left: size * 0.4, + child: Container( + width: size * 0.2, + height: 3, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(1.5), + gradient: LinearGradient( + begin: Alignment.bottomCenter, + end: Alignment.topCenter, + colors: [ + Colors.white.withOpacity(0.015), + Colors.white.withOpacity(0.005), + Colors.transparent, + ], + ), + ), + ), + ), + // 按钮图标 + Center(child: child), + ], + ), + ), + ), + ), + ), + ), + ), + ), + ); + } +} diff --git a/making_school_asignment_app/lib/page/home_page/children/homework_review/configuration_files/index.dart b/making_school_asignment_app/lib/page/home_page/children/homework_review/configuration_files/index.dart index d82baea..bc56268 100644 --- a/making_school_asignment_app/lib/page/home_page/children/homework_review/configuration_files/index.dart +++ b/making_school_asignment_app/lib/page/home_page/children/homework_review/configuration_files/index.dart @@ -131,8 +131,9 @@ class HomeworkReviewLogic extends GetxController with RequestToolMixin, EventBus void onReady() { Future.delayed(Duration.zero, () { DeviceInfoPlugin().androidInfo.then((androidInfo) { - Permission storagePermission = - androidInfo.version.sdkInt >= 33 ? Permission.manageExternalStorage : Permission.storage; + Permission storagePermission = androidInfo.version.sdkInt >= 33 + ? Permission.manageExternalStorage + : Permission.storage; PermissionDescribeUtil.instance.toLaunchPermissionRequest( Get.context!, title: '储存权限请求', @@ -229,8 +230,11 @@ class HomeworkReviewLogic extends GetxController with RequestToolMixin, EventBus if (data == null) return null; // 获取OSS 图片url - String imgKey = UploadOssImgUtils.getInstance() - .setImgKey(param.homeworkId, data.studentId.toString(), data.templateId.toString()); + String imgKey = UploadOssImgUtils.getInstance().setImgKey( + param.homeworkId, + data.studentId.toString(), + data.templateId.toString(), + ); var resUrl = await getClient().getOssPresignedUri(imgKey); if (resUrl == null) return null; @@ -302,7 +306,8 @@ class HomeworkReviewLogic extends GetxController with RequestToolMixin, EventBus if (newzgtAnnotate != null) zgtAnnotate = newzgtAnnotate; await getClient() - .reviewSubmission(ReviewSubmissionParams( + .reviewSubmission( + ReviewSubmissionParams( homeworkId: state.param.value.homeworkId, templateId: data.templateId, studentId: data.studentId, @@ -314,31 +319,33 @@ class HomeworkReviewLogic extends GetxController with RequestToolMixin, EventBus questionNo: e.questionNo, studentScore: studentScore, ); - }).toList())) + }).toList(), + ), + ) .then((e) async { - state.needRefresh = true; - var totalUnAnnotateCount = data.totalUnAnnotateCount; - if (data.needAnnotate) totalUnAnnotateCount -= 1; // 是否需要批阅 + state.needRefresh = true; + var totalUnAnnotateCount = data.totalUnAnnotateCount; + if (data.needAnnotate) totalUnAnnotateCount -= 1; // 是否需要批阅 - if (totalUnAnnotateCount <= 0) { - // 批阅完成 - if (!state.lastQuestionPrompt) { - await _showCompletionDialog(context); - } else { - ToastUtils.showSuccess("批阅已提交", duration: const Duration(milliseconds: 800)); - } - return; - } + if (totalUnAnnotateCount <= 0) { + // 批阅完成 + if (!state.lastQuestionPrompt) { + await _showCompletionDialog(context); + } else { + ToastUtils.showSuccess("批阅已提交", duration: const Duration(milliseconds: 800)); + } + return; + } - var newParams = DoPaperDetailsParam.fromJson(state.param.value.toJson()); - if (totalUnAnnotateCount > 0) { - // 当前批阅任务完成 重复提交后 停留在当前数据位置 - newParams.templateId = null; - newParams.studentId = null; - } - state.param.value = newParams; - ToastUtils.showSuccess("批阅已提交"); - }); + var newParams = DoPaperDetailsParam.fromJson(state.param.value.toJson()); + if (totalUnAnnotateCount > 0) { + // 当前批阅任务完成 重复提交后 停留在当前数据位置 + newParams.templateId = null; + newParams.studentId = null; + } + state.param.value = newParams; + ToastUtils.showSuccess("批阅已提交"); + }); } catch (e) { print("批阅提交报错 $e"); ToastUtils.showError('提交失败,请检查网络连接后重试'); @@ -358,7 +365,10 @@ class HomeworkReviewLogic extends GetxController with RequestToolMixin, EventBus children: [ Icon(Icons.check_circle, color: Colors.green, size: 24.sp), SizedBox(width: 8.w), - Text('批阅已完成', style: TextStyle(fontSize: 18.sp, fontWeight: FontWeight.w600)), + Text( + '批阅已完成', + style: TextStyle(fontSize: 18.sp, fontWeight: FontWeight.w600), + ), ], ), content: Text('暂无更多批阅项,是否继续?', style: TextStyle(fontSize: 14.sp)), @@ -387,13 +397,15 @@ class HomeworkReviewLogic extends GetxController with RequestToolMixin, EventBus try { var data = state.data.value!; var param = state.param.value; - await getClient().toFavStudent(FavorParam( - homeworkId: param.homeworkId, - studentId: data.studentId, - templateId: data.templateId, - questionNo: data.studentQuestions[0].questionNo, - isFav: !data.isFav, - )); + await getClient().toFavStudent( + FavorParam( + homeworkId: param.homeworkId, + studentId: data.studentId, + templateId: data.templateId, + questionNo: data.studentQuestions[0].questionNo, + isFav: !data.isFav, + ), + ); state.favorite.value = !data.isFav; data.isFav = state.favorite.value; } catch (e) {