Compare commits
1 Commits
master
...
glassy_sta
| Author | SHA1 | Date |
|---|---|---|
|
|
8309a2d2e2 |
|
|
@ -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
|
||||
```
|
||||
|
||||
脚本会自动打开此文件夹并显示详细信息。
|
||||
|
|
@ -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
|
||||
|
|
@ -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';
|
||||
|
||||
|
|
@ -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),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import 'dart:async';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
|
|
@ -65,20 +66,148 @@ class _BottomAnnotationSwitchJobState extends State<BottomAnnotationSwitch>
|
|||
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(
|
||||
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(
|
||||
color: Colors.black.withOpacity(0.1),
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, -2),
|
||||
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(
|
||||
flex: 7,
|
||||
|
|
@ -99,7 +228,7 @@ class _BottomAnnotationSwitchJobState extends State<BottomAnnotationSwitch>
|
|||
isPrimary: true,
|
||||
),
|
||||
),
|
||||
Container(width: 0.5.w, height: double.infinity, color: Colors.white.withOpacity(0.3)),
|
||||
_buildGlassDivider(),
|
||||
// 注解笔
|
||||
Expanded(
|
||||
child: Obx(() => _buildIconButton(
|
||||
|
|
@ -113,7 +242,7 @@ class _BottomAnnotationSwitchJobState extends State<BottomAnnotationSwitch>
|
|||
tooltip: '注解笔',
|
||||
)),
|
||||
),
|
||||
Container(width: 0.5.w, height: double.infinity, color: Colors.white.withOpacity(0.3)),
|
||||
_buildGlassDivider(),
|
||||
// 滑动试题
|
||||
Expanded(
|
||||
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(
|
||||
child: Row(
|
||||
children: [
|
||||
|
|
@ -145,7 +274,7 @@ class _BottomAnnotationSwitchJobState extends State<BottomAnnotationSwitch>
|
|||
isPrimary: false,
|
||||
),
|
||||
),
|
||||
Container(width: 0.5.w, height: double.infinity, color: Colors.white.withOpacity(0.3)),
|
||||
_buildGlassDivider(),
|
||||
// 撤销上一步
|
||||
Expanded(
|
||||
child: _buildIconButton(
|
||||
|
|
@ -157,7 +286,7 @@ class _BottomAnnotationSwitchJobState extends State<BottomAnnotationSwitch>
|
|||
tooltip: '撤销上一步',
|
||||
),
|
||||
),
|
||||
Container(width: 0.5.w, height: double.infinity, color: Colors.white.withOpacity(0.3)),
|
||||
_buildGlassDivider(),
|
||||
// 全部撤销
|
||||
Expanded(
|
||||
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(
|
||||
flex: 3,
|
||||
child: Row(
|
||||
|
|
@ -184,22 +313,22 @@ class _BottomAnnotationSwitchJobState extends State<BottomAnnotationSwitch>
|
|||
child: _buildActionButton(
|
||||
'取消',
|
||||
Icons.clear,
|
||||
() => easyThrottle(
|
||||
'homework_bottom_operation_bar_scoring_related', () => _homeworkLogic.cancelAllRatings()),
|
||||
() => 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)),
|
||||
_buildGlassDivider(),
|
||||
Expanded(
|
||||
child: Obx(() {
|
||||
final submitLoading = _homeworkLogic.state.submitLoading.value;
|
||||
return _buildActionButton(
|
||||
'提交',
|
||||
Icons.send,
|
||||
() => easyThrottle(
|
||||
'homework_bottom_operation_bar_scoring_related', () => _homeworkLogic.submit(context)),
|
||||
() => easyThrottle('homework_bottom_operation_bar_scoring_related',
|
||||
() => _homeworkLogic.submit(context)),
|
||||
isEnabled: !submitLoading,
|
||||
isPrimary: true,
|
||||
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(
|
||||
|
|
@ -230,6 +454,9 @@ class _BottomAnnotationSwitchJobState extends State<BottomAnnotationSwitch>
|
|||
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<BottomAnnotationSwitch>
|
|||
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<BottomAnnotationSwitch>
|
|||
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,
|
||||
|
|
|
|||
|
|
@ -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<HomeworkReviewLogic> {
|
|||
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<HomeworkReviewLogic> {
|
|||
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),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,7 +319,9 @@ class HomeworkReviewLogic extends GetxController with RequestToolMixin, EventBus
|
|||
questionNo: e.questionNo,
|
||||
studentScore: studentScore,
|
||||
);
|
||||
}).toList()))
|
||||
}).toList(),
|
||||
),
|
||||
)
|
||||
.then((e) async {
|
||||
state.needRefresh = true;
|
||||
var totalUnAnnotateCount = data.totalUnAnnotateCount;
|
||||
|
|
@ -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(
|
||||
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) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue