This commit is contained in:
DESKTOP-I3JPKHK\wy 2025-09-25 13:55:51 +08:00
parent bac6a7410a
commit 8309a2d2e2
6 changed files with 985 additions and 176 deletions

View File

@ -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
```
脚本会自动打开此文件夹并显示详细信息。

View File

@ -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

View File

@ -14,6 +14,8 @@
* @LastEditors: Please set LastEditors * @LastEditors: Please set LastEditors
* @LastEditTime: 2021-01-12 15:08:43 * @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:flutter_widget_from_html_core/flutter_widget_from_html_core.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:making_school_asignment_app/common/utils/app_upgrade/DownloadApk.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/common/utils/utils.dart';
import 'package:making_school_asignment_app/page/global_widget/my_text.dart'; import 'package:making_school_asignment_app/page/global_widget/my_text.dart';
import 'package:percent_indicator/linear_percent_indicator.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.dart';
import 'package:url_launcher/url_launcher_string.dart'; import 'package:url_launcher/url_launcher_string.dart';
@ -61,7 +61,7 @@ class UpdateDialog extends Dialog {
children: [ children: [
Container( Container(
alignment: Alignment.center, 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( child: quickText(
updateAppEvent.title, updateAppEvent.title,
size: 18.sp, size: 18.sp,
@ -184,7 +184,8 @@ class DownloadButton extends StatelessWidget {
gradient: LinearGradient(colors: [primaryColor, primaryColor.withOpacity(0.7)]), gradient: LinearGradient(colors: [primaryColor, primaryColor.withOpacity(0.7)]),
), ),
child: MaterialButton( 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) { if (deviceInfo == "android" && updateAppEvent.equipment == Equipment.android) {
// //
bool flag = await UpgradePermission(updateAppEvent.deviceInfo).checkPermission(context, updateAppEvent); bool flag = await UpgradePermission(updateAppEvent.deviceInfo).checkPermission(context, updateAppEvent);
@ -215,7 +216,8 @@ class DownloadButton extends StatelessWidget {
// await autoUpdater.setScheduledCheckInterval(0); // 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),
), ),
); );
}); });

View File

@ -1,4 +1,5 @@
import 'dart:async'; import 'dart:async';
import 'dart:ui';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
@ -65,20 +66,148 @@ class _BottomAnnotationSwitchJobState extends State<BottomAnnotationSwitch>
left: false, left: false,
right: false, right: false,
top: 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( child: Container(
width: double.infinity, width: double.infinity,
height: _animationController.value * 70.h, height: _animationController.value * 70.h,
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color.fromRGBO(83, 83, 83, 1), //
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],
),
//
borderRadius: BorderRadius.only(
topLeft: Radius.circular(25.r),
topRight: Radius.circular(25.r),
),
//
boxShadow: [ boxShadow: [
// -
BoxShadow( BoxShadow(
color: Colors.black.withOpacity(0.1), color: Colors.black.withOpacity(0.25),
blurRadius: 8, blurRadius: 30,
offset: const Offset(0, -2), 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( 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: [ children: [
Expanded( Expanded(
flex: 7, flex: 7,
@ -99,7 +228,7 @@ class _BottomAnnotationSwitchJobState extends State<BottomAnnotationSwitch>
isPrimary: true, isPrimary: true,
), ),
), ),
Container(width: 0.5.w, height: double.infinity, color: Colors.white.withOpacity(0.3)), _buildGlassDivider(),
// //
Expanded( Expanded(
child: Obx(() => _buildIconButton( child: Obx(() => _buildIconButton(
@ -113,7 +242,7 @@ class _BottomAnnotationSwitchJobState extends State<BottomAnnotationSwitch>
tooltip: '注解笔', tooltip: '注解笔',
)), )),
), ),
Container(width: 0.5.w, height: double.infinity, color: Colors.white.withOpacity(0.3)), _buildGlassDivider(),
// //
Expanded( Expanded(
child: Obx(() => _buildIconButton( child: Obx(() => _buildIconButton(
@ -130,7 +259,7 @@ class _BottomAnnotationSwitchJobState extends State<BottomAnnotationSwitch>
], ],
), ),
), ),
Container(width: double.infinity, color: Colors.white.withOpacity(0.3), height: 0.5.h), _buildHorizontalGlassDivider(),
Expanded( Expanded(
child: Row( child: Row(
children: [ children: [
@ -145,7 +274,7 @@ class _BottomAnnotationSwitchJobState extends State<BottomAnnotationSwitch>
isPrimary: false, isPrimary: false,
), ),
), ),
Container(width: 0.5.w, height: double.infinity, color: Colors.white.withOpacity(0.3)), _buildGlassDivider(),
// //
Expanded( Expanded(
child: _buildIconButton( child: _buildIconButton(
@ -157,7 +286,7 @@ class _BottomAnnotationSwitchJobState extends State<BottomAnnotationSwitch>
tooltip: '撤销上一步', tooltip: '撤销上一步',
), ),
), ),
Container(width: 0.5.w, height: double.infinity, color: Colors.white.withOpacity(0.3)), _buildGlassDivider(),
// //
Expanded( Expanded(
child: _buildIconButton( child: _buildIconButton(
@ -175,7 +304,7 @@ class _BottomAnnotationSwitchJobState extends State<BottomAnnotationSwitch>
], ],
), ),
), ),
Container(width: 0.5.w, height: double.infinity, color: Colors.white.withOpacity(0.3)), _buildGlassDivider(),
Expanded( Expanded(
flex: 3, flex: 3,
child: Row( child: Row(
@ -184,22 +313,22 @@ class _BottomAnnotationSwitchJobState extends State<BottomAnnotationSwitch>
child: _buildActionButton( child: _buildActionButton(
'取消', '取消',
Icons.clear, Icons.clear,
() => easyThrottle( () => easyThrottle('homework_bottom_operation_bar_scoring_related',
'homework_bottom_operation_bar_scoring_related', () => _homeworkLogic.cancelAllRatings()), () => _homeworkLogic.cancelAllRatings()),
isEnabled: true, isEnabled: true,
isPrimary: false, isPrimary: false,
isSecondary: true, isSecondary: true,
), ),
), ),
Container(width: 0.5.w, height: double.infinity, color: Colors.white.withOpacity(0.3)), _buildGlassDivider(),
Expanded( Expanded(
child: Obx(() { child: Obx(() {
final submitLoading = _homeworkLogic.state.submitLoading.value; final submitLoading = _homeworkLogic.state.submitLoading.value;
return _buildActionButton( return _buildActionButton(
'提交', '提交',
Icons.send, Icons.send,
() => easyThrottle( () => easyThrottle('homework_bottom_operation_bar_scoring_related',
'homework_bottom_operation_bar_scoring_related', () => _homeworkLogic.submit(context)), () => _homeworkLogic.submit(context)),
isEnabled: !submitLoading, isEnabled: !submitLoading,
isPrimary: true, isPrimary: true,
isLoading: submitLoading, isLoading: submitLoading,
@ -211,7 +340,102 @@ class _BottomAnnotationSwitchJobState extends State<BottomAnnotationSwitch>
), ),
], ],
), ),
)); ],
),
),
),
),
);
}
//
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( Widget _buildActionButton(
@ -230,6 +454,9 @@ class _BottomAnnotationSwitchJobState extends State<BottomAnnotationSwitch>
color: Colors.transparent, color: Colors.transparent,
child: InkWell( child: InkWell(
onTap: isEnabled ? onPressed : null, 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( child: Semantics(
label: text, label: text,
hint: isLoading ? '正在处理中' : (isEnabled ? '点击执行$text操作' : '$text按钮已禁用'), hint: isLoading ? '正在处理中' : (isEnabled ? '点击执行$text操作' : '$text按钮已禁用'),
@ -239,12 +466,35 @@ class _BottomAnnotationSwitchJobState extends State<BottomAnnotationSwitch>
width: double.infinity, width: double.infinity,
height: double.infinity, height: double.infinity,
decoration: BoxDecoration( decoration: BoxDecoration(
color: isPrimary //
? (isEnabled ? primaryColor : primaryColor.withOpacity(0.5)) gradient: isPrimary
: isSecondary ? LinearGradient(
? Colors.transparent begin: Alignment.topLeft,
: Colors.transparent, end: Alignment.bottomRight,
borderRadius: BorderRadius.circular(4.r), 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( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
@ -300,14 +550,39 @@ class _BottomAnnotationSwitchJobState extends State<BottomAnnotationSwitch>
color: Colors.transparent, color: Colors.transparent,
child: InkWell( child: InkWell(
onTap: onPressed, onTap: onPressed,
borderRadius: BorderRadius.circular(8.r),
splashColor: primaryColor.withOpacity(0.2),
highlightColor: primaryColor.withOpacity(0.1),
child: Tooltip( child: Tooltip(
message: tooltip ?? '', message: tooltip ?? '',
child: Container( child: Container(
width: double.infinity, width: double.infinity,
height: double.infinity, height: double.infinity,
decoration: BoxDecoration( 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( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,

View File

@ -1,4 +1,5 @@
import 'dart:async'; import 'dart:async';
import 'dart:ui';
import 'package:flutter/cupertino.dart' hide TransformationController; import 'package:flutter/cupertino.dart' hide TransformationController;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -80,12 +81,9 @@ class QuestionPaperView extends GetView<HomeworkReviewLogic> {
child: Obx(() { child: Obx(() {
LastPage? lastPageVal = sateData.data.value?.lastPage; LastPage? lastPageVal = sateData.data.value?.lastPage;
if (lastPageVal == null) return const SizedBox(); if (lastPageVal == null) return const SizedBox();
return FloatingActionButton( return GlassmorphicButton(
heroTag: '点击前往上一题', heroTag: '点击前往上一题',
tooltip: '点击前往上一题', tooltip: '点击前往上一题',
focusColor: Theme.of(context).primaryColor,
backgroundColor: const Color.fromRGBO(24, 32, 32, 0.05),
elevation: 10.r,
onPressed: () => easyThrottle('TestQuestionSwitch', () { onPressed: () => easyThrottle('TestQuestionSwitch', () {
var param = sateData.param.value; var param = sateData.param.value;
param.studentId = lastPageVal.studentId; param.studentId = lastPageVal.studentId;
@ -104,11 +102,9 @@ class QuestionPaperView extends GetView<HomeworkReviewLogic> {
NextPage? nextPageVal = sateData.data.value?.nextPage; NextPage? nextPageVal = sateData.data.value?.nextPage;
if (nextPageVal == null) return const SizedBox(); if (nextPageVal == null) return const SizedBox();
return FloatingActionButton( return GlassmorphicButton(
heroTag: '点击前往下一题', heroTag: '点击前往下一题',
tooltip: '点击前往下一题', tooltip: '点击前往下一题',
elevation: 10.r,
backgroundColor: const Color.fromRGBO(24, 32, 32, 0.05),
onPressed: () => easyThrottle('TestQuestionSwitch', () { onPressed: () => easyThrottle('TestQuestionSwitch', () {
var param = sateData.param.value; var param = sateData.param.value;
param.studentId = nextPageVal.studentId; param.studentId = nextPageVal.studentId;
@ -683,3 +679,292 @@ class DrawingPainter extends CustomPainter {
@override @override
bool shouldRebuildSemantics(CustomPainter oldDelegate) => false; 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),
],
),
),
),
),
),
),
),
),
);
}
}

View File

@ -131,8 +131,9 @@ class HomeworkReviewLogic extends GetxController with RequestToolMixin, EventBus
void onReady() { void onReady() {
Future.delayed(Duration.zero, () { Future.delayed(Duration.zero, () {
DeviceInfoPlugin().androidInfo.then((androidInfo) { DeviceInfoPlugin().androidInfo.then((androidInfo) {
Permission storagePermission = Permission storagePermission = androidInfo.version.sdkInt >= 33
androidInfo.version.sdkInt >= 33 ? Permission.manageExternalStorage : Permission.storage; ? Permission.manageExternalStorage
: Permission.storage;
PermissionDescribeUtil.instance.toLaunchPermissionRequest( PermissionDescribeUtil.instance.toLaunchPermissionRequest(
Get.context!, Get.context!,
title: '储存权限请求', title: '储存权限请求',
@ -229,8 +230,11 @@ class HomeworkReviewLogic extends GetxController with RequestToolMixin, EventBus
if (data == null) return null; if (data == null) return null;
// OSS url // OSS url
String imgKey = UploadOssImgUtils.getInstance() String imgKey = UploadOssImgUtils.getInstance().setImgKey(
.setImgKey(param.homeworkId, data.studentId.toString(), data.templateId.toString()); param.homeworkId,
data.studentId.toString(),
data.templateId.toString(),
);
var resUrl = await getClient().getOssPresignedUri(imgKey); var resUrl = await getClient().getOssPresignedUri(imgKey);
if (resUrl == null) return null; if (resUrl == null) return null;
@ -302,7 +306,8 @@ class HomeworkReviewLogic extends GetxController with RequestToolMixin, EventBus
if (newzgtAnnotate != null) zgtAnnotate = newzgtAnnotate; if (newzgtAnnotate != null) zgtAnnotate = newzgtAnnotate;
await getClient() await getClient()
.reviewSubmission(ReviewSubmissionParams( .reviewSubmission(
ReviewSubmissionParams(
homeworkId: state.param.value.homeworkId, homeworkId: state.param.value.homeworkId,
templateId: data.templateId, templateId: data.templateId,
studentId: data.studentId, studentId: data.studentId,
@ -314,7 +319,9 @@ class HomeworkReviewLogic extends GetxController with RequestToolMixin, EventBus
questionNo: e.questionNo, questionNo: e.questionNo,
studentScore: studentScore, studentScore: studentScore,
); );
}).toList())) }).toList(),
),
)
.then((e) async { .then((e) async {
state.needRefresh = true; state.needRefresh = true;
var totalUnAnnotateCount = data.totalUnAnnotateCount; var totalUnAnnotateCount = data.totalUnAnnotateCount;
@ -358,7 +365,10 @@ class HomeworkReviewLogic extends GetxController with RequestToolMixin, EventBus
children: [ children: [
Icon(Icons.check_circle, color: Colors.green, size: 24.sp), Icon(Icons.check_circle, color: Colors.green, size: 24.sp),
SizedBox(width: 8.w), 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)), content: Text('暂无更多批阅项,是否继续?', style: TextStyle(fontSize: 14.sp)),
@ -387,13 +397,15 @@ class HomeworkReviewLogic extends GetxController with RequestToolMixin, EventBus
try { try {
var data = state.data.value!; var data = state.data.value!;
var param = state.param.value; var param = state.param.value;
await getClient().toFavStudent(FavorParam( await getClient().toFavStudent(
FavorParam(
homeworkId: param.homeworkId, homeworkId: param.homeworkId,
studentId: data.studentId, studentId: data.studentId,
templateId: data.templateId, templateId: data.templateId,
questionNo: data.studentQuestions[0].questionNo, questionNo: data.studentQuestions[0].questionNo,
isFav: !data.isFav, isFav: !data.isFav,
)); ),
);
state.favorite.value = !data.isFav; state.favorite.value = !data.isFav;
data.isFav = state.favorite.value; data.isFav = state.favorite.value;
} catch (e) { } catch (e) {