Compare commits
1 Commits
master
...
glassy_sta
| Author | SHA1 | Date |
|---|---|---|
|
|
8309a2d2e2 |
|
|
@ -5,11 +5,9 @@
|
|||
*.swp
|
||||
.DS_Store
|
||||
.atom/
|
||||
.build/
|
||||
.buildlog/
|
||||
.history
|
||||
.svn/
|
||||
.swiftpm/
|
||||
migrate_working_dir/
|
||||
|
||||
# IntelliJ related
|
||||
|
|
|
|||
|
|
@ -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
|
||||
```
|
||||
|
||||
脚本会自动打开此文件夹并显示详细信息。
|
||||
|
|
@ -87,21 +87,25 @@
|
|||
<!-- 屏幕常亮权限 -->
|
||||
<uses-permission
|
||||
android:name="android.permission.WAKE_LOCK"/>
|
||||
<!-- <uses-permission
|
||||
android:name="android.permission.CAMERA"/> -->
|
||||
<uses-permission
|
||||
android:name="android.permission.CAMERA"/>
|
||||
<!-- Permissions options for the `access notification policy` group -->
|
||||
<uses-permission
|
||||
android:name="android.permission.ACCESS_NOTIFICATION_POLICY"/>
|
||||
<!-- Permissions options for the `notification` group -->
|
||||
<uses-permission
|
||||
android:name="android.permission.POST_NOTIFICATIONS"/>
|
||||
<!-- 注释掉文本处理权限,避免不必要的剪切板访问 -->
|
||||
<!-- <queries>
|
||||
<!-- Required to query activities that can process text, see:
|
||||
https://developer.android.com/training/package-visibility and
|
||||
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
|
||||
|
||||
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
|
||||
<queries>
|
||||
<intent>
|
||||
<action
|
||||
android:name="android.intent.action.PROCESS_TEXT"/>
|
||||
<data
|
||||
android:mimeType="text/plain"/>
|
||||
</intent>
|
||||
</queries> -->
|
||||
</queries>
|
||||
</manifest>
|
||||
|
|
@ -30,8 +30,8 @@
|
|||
<!-- 屏幕常亮权限 -->
|
||||
<uses-permission
|
||||
android:name="android.permission.WAKE_LOCK"/>
|
||||
<!-- <uses-permission
|
||||
android:name="android.permission.CAMERA"/> -->
|
||||
<uses-permission
|
||||
android:name="android.permission.CAMERA"/>
|
||||
<!-- Permissions options for the `access notification policy` group -->
|
||||
<uses-permission
|
||||
android:name="android.permission.ACCESS_NOTIFICATION_POLICY"/>
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@
|
|||
type="application/octet-stream" />
|
||||
</item> -->
|
||||
<item>
|
||||
<title>Version 1.0.7+8</title>
|
||||
<title>Version 1.0.3+4</title>
|
||||
#发行说明-读取html方式(2选1)
|
||||
<!-- <sparkle:releaseNotesLink>
|
||||
https://your_domain/your_path/release_notes.html
|
||||
|
|
@ -39,15 +39,16 @@
|
|||
<![CDATA[
|
||||
<ul>
|
||||
<li>1、修复已知BUG</li>
|
||||
<li>2、添加缩放布局和批阅书写</li>
|
||||
<li>2、优化操作布局</li>
|
||||
</ul>
|
||||
]]>
|
||||
</description>
|
||||
<pubDate>Tue, 30 Sep 2025 14:09:13 +0800</pubDate>
|
||||
<pubDate>Fri, 14 Mar 2025 12:00:00 +0800</pubDate>
|
||||
#你更新程序的地址
|
||||
<enclosure url="https://dpc-job-oss.23544.com/infra-app/making_school_asignment_app/1.0.7/3/making_school_asignment_app-1.0.7+8-windows-setup.exe"
|
||||
sparkle:dsaSignature="MEUCIQCwWC4fKrn/dfdH6uzX4Sssv7G6XVgvwjQLklrTM6+cBQIgZDY4INHcwuxrsIREuPQeTvQL4j0mvF9qPQfppjRSgsg=" length="0"
|
||||
sparkle:version="1.0.7+8"
|
||||
<enclosure url="https://dpc-job-oss.23544.com/infra-app/making_school_asignment_app/1.0.3/3/making_school_asignment_app-1.0.3+4-windows-setup.exe"
|
||||
sparkle:dsaSignature="MEYCIQCuU0BodcdWrF+WoJrWRpY8P1pfK+dKkvrl3ZJ5KxnDdAIhAIWqp7VBNO9IaKFj2ypQ+s7DWurBUSaf6MTvexMmuvX+"
|
||||
length="0"
|
||||
sparkle:version="1.0.3+4"
|
||||
sparkle:os="windows"
|
||||
type="application/octet-stream" />
|
||||
</item>
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -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
|
||||
|
|
@ -1,159 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
IFS=$'\n\t'
|
||||
|
||||
# Ensure UTF-8 locale for macOS terminals
|
||||
export LANG=en_US.UTF-8
|
||||
export LC_ALL=en_US.UTF-8
|
||||
|
||||
# Resolve project directory (script location)
|
||||
PROJECT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
|
||||
echo "Project dir: $PROJECT_DIR"
|
||||
|
||||
# To save credentials in macOS Keychain for non-interactive uploads, run:
|
||||
# security add-generic-password -s "TransporterCredentials" -w "you@example.com:app-specific-password" -a transporter -U
|
||||
# The script will read that item and expect the stored password string in the form: "APPLE_ID:APPLE_PASSWORD"
|
||||
|
||||
force_delete() {
|
||||
local target="$1"
|
||||
local max_retries=3
|
||||
local retry_delay=1
|
||||
|
||||
if [ ! -e "$PROJECT_DIR/$target" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
for i in $(seq 1 $max_retries); do
|
||||
echo "Attempting to delete $target (try $i of $max_retries)"
|
||||
rm -rf "$PROJECT_DIR/$target" 2>/dev/null || true
|
||||
if [ ! -e "$PROJECT_DIR/$target" ]; then
|
||||
echo "Successfully deleted $target"
|
||||
return 0
|
||||
fi
|
||||
if [ "$i" -lt "$max_retries" ]; then
|
||||
sleep $retry_delay
|
||||
retry_delay=$((retry_delay * 2))
|
||||
fi
|
||||
done
|
||||
|
||||
echo "Warning: Could not completely delete $target, some files may be in use"
|
||||
return 1
|
||||
}
|
||||
|
||||
cd "$PROJECT_DIR"
|
||||
|
||||
echo "Running flutter clean..."
|
||||
if ! flutter clean; then
|
||||
echo "flutter clean failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Removing residual files"
|
||||
force_delete build || true
|
||||
force_delete .dart_tool || true
|
||||
force_delete pubspec.lock || true
|
||||
|
||||
echo "Running flutter pub get..."
|
||||
if ! flutter pub get; then
|
||||
echo "flutter pub get failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Running build_runner (if present)..."
|
||||
if ! flutter packages pub run build_runner build --delete-conflicting-outputs; then
|
||||
echo "build_runner failed or not present — continuing"
|
||||
fi
|
||||
|
||||
echo "Building iOS IPA (verbose)..."
|
||||
if ! flutter build ipa --release --verbose; then
|
||||
echo "IPA build failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Try to find the generated .ipa (searching under build/)
|
||||
IPA_PATH="$(find "$PROJECT_DIR/build" -type f -name '*.ipa' -print -quit || true)"
|
||||
if [ -z "$IPA_PATH" ]; then
|
||||
echo "No .ipa found in build output"
|
||||
echo "Build finished but no IPA to upload. Exiting."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Found IPA: $IPA_PATH"
|
||||
|
||||
# Upload via Transporter (if installed). Prefer environment vars APPLE_ID and APPLE_PASSWORD (app-specific password).
|
||||
ITMS_TRANSPORTER="/Applications/Transporter.app/Contents/itms/bin/iTMSTransporter"
|
||||
if [ -x "$ITMS_TRANSPORTER" ]; then
|
||||
echo "Using Transporter at $ITMS_TRANSPORTER"
|
||||
|
||||
# Read credentials from env, then try macOS Keychain, otherwise prompt
|
||||
if [ -z "${APPLE_ID:-}" ] || [ -z "${APPLE_PASSWORD:-}" ]; then
|
||||
# Try to read combined credentials from Keychain (service: TransporterCredentials)
|
||||
if command -v security >/dev/null 2>&1; then
|
||||
KC_PAIR="$(security find-generic-password -s "TransporterCredentials" -w 2>/dev/null || true)"
|
||||
if [ -n "$KC_PAIR" ]; then
|
||||
# Expect stored value in form: appleid:app-specific-password
|
||||
APPLE_ID="${KC_PAIR%%:*}"
|
||||
APPLE_PASSWORD="${KC_PAIR#*:}"
|
||||
if [ -n "${APPLE_ID}" ] && [ -n "${APPLE_PASSWORD}" ]; then
|
||||
echo "Credentials loaded from Keychain (service: TransporterCredentials)."
|
||||
else
|
||||
# clear if parsing failed
|
||||
APPLE_ID=""
|
||||
APPLE_PASSWORD=""
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -z "${APPLE_ID:-}" ] || [ -z "${APPLE_PASSWORD:-}" ]; then
|
||||
echo "Provide App Store Connect credentials for upload. You can set APPLE_ID and APPLE_PASSWORD (app-specific password) in the environment or save a keychain item named 'TransporterCredentials' to avoid prompts."
|
||||
read -p "Apple ID (email): " APPLE_ID
|
||||
read -s -p "App-specific password (will not echo): " APPLE_PASSWORD
|
||||
echo
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "Uploading $IPA_PATH to App Store Connect (this may take a while)..."
|
||||
# Try transporter with a small retry loop
|
||||
UPLOAD_EXIT=0
|
||||
MAX_UPLOAD_TRIES=2
|
||||
for try in $(seq 1 $MAX_UPLOAD_TRIES); do
|
||||
echo "Transporter upload attempt $try of $MAX_UPLOAD_TRIES..."
|
||||
if "$ITMS_TRANSPORTER" -m upload -u "$APPLE_ID" -p "$APPLE_PASSWORD" -f "$IPA_PATH"; then
|
||||
echo "Upload succeeded"
|
||||
UPLOAD_EXIT=0
|
||||
break
|
||||
else
|
||||
UPLOAD_EXIT=$?
|
||||
echo "Transporter upload attempt $try failed (exit $UPLOAD_EXIT)"
|
||||
# small delay before retry
|
||||
sleep $((try * 2))
|
||||
fi
|
||||
done
|
||||
|
||||
if [ $UPLOAD_EXIT -ne 0 ]; then
|
||||
echo "Transporter upload failed after $MAX_UPLOAD_TRIES attempts (exit $UPLOAD_EXIT)."
|
||||
echo "Opening Transporter app so you can upload the IPA manually..."
|
||||
# Try to open Transporter GUI with the IPA as argument (may import the file)
|
||||
if command -v open >/dev/null 2>&1; then
|
||||
if open -a Transporter "$IPA_PATH" 2>/dev/null; then
|
||||
echo "Transporter app opened. Please complete the upload there."
|
||||
else
|
||||
echo "Failed to open Transporter with the IPA. Opening Transporter app without file..."
|
||||
open -a Transporter || true
|
||||
echo "Transporter opened. Please drag the IPA ($IPA_PATH) into the app to upload."
|
||||
fi
|
||||
else
|
||||
echo "Cannot open Transporter GUI: 'open' command not found. Please upload IPA manually: $IPA_PATH"
|
||||
fi
|
||||
# Do not exit with error here so the script can finish and user can manually complete upload
|
||||
fi
|
||||
else
|
||||
echo "Transporter not found at $ITMS_TRANSPORTER. Please install Transporter app from the Mac App Store or upload the IPA manually."
|
||||
echo "IPA path: $IPA_PATH"
|
||||
fi
|
||||
|
||||
echo "All steps completed successfully!"
|
||||
echo "IPA: $IPA_PATH"
|
||||
|
||||
exit 0
|
||||
|
|
@ -21,6 +21,6 @@
|
|||
<key>CFBundleVersion</key>
|
||||
<string>1.0</string>
|
||||
<key>MinimumOSVersion</key>
|
||||
<string>13.0</string>
|
||||
<string>12.0</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
# Uncomment this line to define a global platform for your project
|
||||
# platform :ios, '13.0'
|
||||
# platform :ios, '12.0'
|
||||
|
||||
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
||||
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||
|
|
|
|||
|
|
@ -477,10 +477,9 @@
|
|||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||
SUPPORTED_PLATFORMS = iphoneos;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
|
|
@ -496,7 +495,7 @@
|
|||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
CURRENT_PROJECT_VERSION = 7;
|
||||
DEVELOPMENT_TEAM = Z778GC45N8;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
|
|
@ -504,7 +503,7 @@
|
|||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = "$(FLUTTER_BUILD_NAME)";
|
||||
MARKETING_VERSION = 4;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.yuanxuan.makingS--buneng--choolAsignmentApp";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
|
|
@ -525,7 +524,7 @@
|
|||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.yuanxuan.makingSchoolAsignmentApp.RunnerTests;
|
||||
|
|
@ -543,7 +542,7 @@
|
|||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = Z778GC45N8;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
|
|
@ -560,7 +559,7 @@
|
|||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.yuanxuan.makingSchoolAsignmentApp.RunnerTests;
|
||||
|
|
@ -620,11 +619,10 @@
|
|||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
|
|
@ -673,10 +671,9 @@
|
|||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||
SUPPORTED_PLATFORMS = iphoneos;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
|
|
@ -692,7 +689,7 @@
|
|||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
CURRENT_PROJECT_VERSION = 7;
|
||||
DEVELOPMENT_TEAM = Z778GC45N8;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
|
|
@ -700,7 +697,7 @@
|
|||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = "$(FLUTTER_BUILD_NAME)";
|
||||
MARKETING_VERSION = 4;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.yuanxuan.makingSchoolAsignmentApp;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||
|
|
@ -722,7 +719,7 @@
|
|||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/RunnerRelease.entitlements;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
CURRENT_PROJECT_VERSION = 7;
|
||||
DEVELOPMENT_TEAM = Z778GC45N8;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
|
|
@ -730,7 +727,7 @@
|
|||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = "$(FLUTTER_BUILD_NAME)";
|
||||
MARKETING_VERSION = 4;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.yuanxuan.makingSchoolAsignmentApp;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||
|
|
|
|||
|
|
@ -26,7 +26,6 @@
|
|||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
|
|
@ -55,13 +54,11 @@
|
|||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
enableGPUValidationMode = "1"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import Flutter
|
||||
import UIKit
|
||||
|
||||
@main
|
||||
@UIApplicationMain
|
||||
@objc class AppDelegate: FlutterAppDelegate {
|
||||
override func application(
|
||||
_ application: UIApplication,
|
||||
|
|
|
|||
|
|
@ -7,8 +7,7 @@ class AppStateController extends GetxController with WidgetsBindingObserver {
|
|||
final appLifecycleState = AppLifecycleState.resumed.obs;
|
||||
|
||||
// 便捷方法:判断应用是否在前台
|
||||
bool get isAppInForeground =>
|
||||
appLifecycleState.value == AppLifecycleState.resumed;
|
||||
bool get isAppInForeground => appLifecycleState.value == AppLifecycleState.resumed;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
|
|
|
|||
|
|
@ -1,77 +0,0 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:making_school_asignment_app/common/job/app_version.dart';
|
||||
import 'package:making_school_asignment_app/common/mixins/request_tool_mixin.dart';
|
||||
|
||||
import 'package:yx_app_upgrade_flutter/yx_app_upgrade_flutter.dart';
|
||||
|
||||
class UpgradeLogic extends GetxService with RequestToolMixin {
|
||||
Timer? _timer;
|
||||
|
||||
@override
|
||||
void onReady() {
|
||||
/// 首次检查更新
|
||||
Future.delayed(const Duration(seconds: 10)).then(
|
||||
(value) => checkUpdate(Get.context!),
|
||||
);
|
||||
|
||||
/// 定时检查更新
|
||||
_timer?.cancel();
|
||||
_timer = Timer.periodic(
|
||||
const Duration(seconds: 40),
|
||||
(_) => checkUpdate(Get.context!),
|
||||
);
|
||||
super.onReady();
|
||||
}
|
||||
|
||||
@override
|
||||
void onClose() {
|
||||
_timer?.cancel();
|
||||
super.onClose();
|
||||
}
|
||||
|
||||
void checkUpdate(BuildContext context) {
|
||||
UpgradeAuxiliaryUtils.instance.initiateVersionCheck(
|
||||
context,
|
||||
future: (int upType) async {
|
||||
// upType: 1 为 Android, 2 为 iOS
|
||||
try {
|
||||
AppVersion? result = await getClient().getLastAppVersion(
|
||||
'making_school_asignment_app',
|
||||
upType,
|
||||
);
|
||||
if (result != null) return _convertToAppUpgradeVersion(result);
|
||||
} catch (e, stack) {
|
||||
debugPrint('fetchLatestUpgradeVersion error: $e\n$stack');
|
||||
}
|
||||
return null;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// 将 UpdateappResult 转换为 AppUpgradeVersion
|
||||
AppUpgradeVersion? _convertToAppUpgradeVersion(AppVersion? model) {
|
||||
if (model == null) return null;
|
||||
|
||||
// 将文件大小从 KB 转换为字节
|
||||
final filePath =
|
||||
(model.appFileUrl?.isNotEmpty ?? false) ? model.appFileUrl : null;
|
||||
|
||||
return AppUpgradeVersion(
|
||||
versionName: model.version,
|
||||
// versionBuildNumber: model.version,
|
||||
isForce: false,
|
||||
updateContent: model.description ?? '修复BUG,优化体验',
|
||||
downloadUrl: filePath,
|
||||
appStoreUrl: filePath,
|
||||
// apkSize: apkSizeBytes,
|
||||
supportedMethods: [
|
||||
AppUpgradeMethod.market,
|
||||
AppUpgradeMethod.inApp,
|
||||
AppUpgradeMethod.browser,
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,159 @@
|
|||
/*
|
||||
* @Descripttion: 下载APK
|
||||
* @version: DownloadApk
|
||||
* @Author: wy
|
||||
* @Date: 2020-07-30 15:54:40
|
||||
* @LastEditors: wangyang 1147192855@qq.com
|
||||
* @LastEditTime: 2022-08-02 15:12:21
|
||||
*/
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:making_school_asignment_app/common/utils/utils.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:app_installer/app_installer.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
import 'UpgradePermission.dart';
|
||||
import 'model/UpdateAppEvent.dart';
|
||||
import 'upgradeLogic.dart';
|
||||
|
||||
class DownloadApk {
|
||||
/// 下载安卓更新包
|
||||
static Future<File?> _downloadAndroid(
|
||||
context, UpdateAppEvent event, UpgradeLogic logic) async {
|
||||
/// 创建存储文件
|
||||
Directory? storageDir = await getExternalStorageDirectory();
|
||||
final storagePath = storageDir?.path ?? '/';
|
||||
String version = event.version.replaceAll(".", "-");
|
||||
File file = new File('$storagePath/${event.appName}v$version.apk');
|
||||
if (await file.exists()) await file.delete();
|
||||
if (!file.existsSync()) file.createSync();
|
||||
try {
|
||||
/// 发起下载请求
|
||||
Response response = await Dio().get(
|
||||
event.link,
|
||||
onReceiveProgress: (num received, num total) {
|
||||
showDownloadProgress(context, received, total, logic);
|
||||
},
|
||||
options: Options(
|
||||
responseType: ResponseType.bytes,
|
||||
followRedirects: false,
|
||||
),
|
||||
);
|
||||
file.writeAsBytesSync(response.data);
|
||||
return file;
|
||||
} catch (e) {
|
||||
print(e);
|
||||
// toPrint(val: e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// 安装apk
|
||||
static Future<bool> installApk(
|
||||
context, UpdateAppEvent event, UpgradeLogic logic) async {
|
||||
try {
|
||||
logic.loadingApk.value = true;
|
||||
File? _apkFile = await _downloadAndroid(context, event, logic);
|
||||
if (_apkFile == null) return false;
|
||||
String _apkFilePath = _apkFile.path;
|
||||
if (_apkFilePath.isEmpty) {
|
||||
debugPrint('make sure the apk file is set');
|
||||
return false;
|
||||
}
|
||||
|
||||
await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: const Text("未知应用安装权限提示",
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
)),
|
||||
content: const Text("请注意:更新时若出现需要同意“安装未知应用权限”,请同意!!!"),
|
||||
actions: [
|
||||
MaterialButton(
|
||||
color: Theme.of(context).primaryColor,
|
||||
child: const Text("我已知晓",
|
||||
style: TextStyle(
|
||||
color: Colors.white, fontWeight: FontWeight.bold)),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
await AppInstaller.installApk(_apkFilePath); // 安装APK
|
||||
// 如果2秒还没有进入安装引导页面 一律视为更新失败,弹出其他方式更新
|
||||
Utils.getInstance().setTimeOut(1, () async {
|
||||
try {
|
||||
// 其他方式下载
|
||||
// await SystemNavigator.pop(); // 退出APP
|
||||
var options = ['应用市场更新APP', '浏览器下载并安装APP'];
|
||||
var uri =
|
||||
Uri.parse('market://details?id=${event.packageName}'); // 应用市场URI
|
||||
if (!await canLaunchUrl(uri))
|
||||
options.removeAt(0); // 如果不能打开应用市场 就屏蔽掉 这个安装方式
|
||||
String? option = await UpgradePermission.showCustomModalBottomSheet(
|
||||
context, options);
|
||||
if (option == '应用市场更新APP') await launchUrl(uri);
|
||||
if (option == '浏览器下载并安装APP') await launchUrl(Uri.parse(event.link));
|
||||
} catch (_) {}
|
||||
});
|
||||
|
||||
print('安装执行完成了..............0.0');
|
||||
} catch (e) {
|
||||
print('安装进入报错....');
|
||||
print(e);
|
||||
} finally {
|
||||
logic.loadingApk.value = false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// 展示下载进度
|
||||
static void showDownloadProgress(
|
||||
context, num received, num total, UpgradeLogic logic) {
|
||||
if (total != -1) {
|
||||
double progress = double.parse((received / total).toStringAsFixed(2));
|
||||
// debugPrint('下载进度$progress');
|
||||
logic.downloadRatio.value = progress;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class RestartWidget extends StatefulWidget {
|
||||
final Widget child;
|
||||
|
||||
const RestartWidget({super.key, required this.child});
|
||||
|
||||
static restartApp(BuildContext context) {
|
||||
final _RestartWidgetState? state =
|
||||
context.findAncestorStateOfType<_RestartWidgetState>();
|
||||
state?.restartApp();
|
||||
}
|
||||
|
||||
@override
|
||||
State<RestartWidget> createState() => _RestartWidgetState();
|
||||
}
|
||||
|
||||
class _RestartWidgetState extends State<RestartWidget> {
|
||||
Key key = UniqueKey();
|
||||
|
||||
void restartApp() {
|
||||
setState(() {
|
||||
key = UniqueKey();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return KeyedSubtree(
|
||||
key: key,
|
||||
child: widget.child,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,225 @@
|
|||
/*
|
||||
* @Author: wangyang 1147192855@qq.com
|
||||
* @Date:
|
||||
* @LastEditors: wangyang 1147192855@qq.com
|
||||
* @LastEditTime: 2022-09-29 10:11:54
|
||||
* @FilePath: \marking_app\lib\utils\app_upgrade\UpdateDialog.dart
|
||||
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
|
||||
*/
|
||||
/*
|
||||
* @Descripttion: 版本更新提示弹窗
|
||||
* @version:
|
||||
* @Author: wy
|
||||
* @Date: 2020-07-30 14:03:28
|
||||
* @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';
|
||||
import 'package:making_school_asignment_app/common/utils/app_upgrade/UpgradePermission.dart';
|
||||
import 'package:making_school_asignment_app/common/utils/app_upgrade/upgradeLogic.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:percent_indicator/linear_percent_indicator.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
|
||||
import '../anti_shake_throttling.dart';
|
||||
import 'model/UpdateAppEvent.dart';
|
||||
|
||||
class UpdateDialog extends Dialog {
|
||||
final UpdateAppEvent updateAppEvent;
|
||||
final String deviceInfo;
|
||||
|
||||
const UpdateDialog({super.key, required this.updateAppEvent, required this.deviceInfo});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Center(
|
||||
child: Container(
|
||||
width: isPad() ? ScreenUtil().screenWidth * 0.62 : 319.w,
|
||||
height: 440.h,
|
||||
padding: EdgeInsets.symmetric(vertical: 8.h),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(18.sp),
|
||||
image: const DecorationImage(
|
||||
alignment: Alignment.topCenter,
|
||||
image: AssetImage("assets/images/upgrade_dialog_bgc.png"),
|
||||
fit: BoxFit.fitWidth,
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: ListView(
|
||||
physics: const BouncingScrollPhysics(),
|
||||
padding: EdgeInsets.fromLTRB(16.w, 0, 16.w, 0),
|
||||
children: [
|
||||
Container(
|
||||
alignment: Alignment.center,
|
||||
margin: EdgeInsets.only(top: isPad() ? 136.h : 128.h, bottom: 10.h),
|
||||
child: quickText(
|
||||
updateAppEvent.title,
|
||||
size: 18.sp,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: const Color.fromRGBO(58, 90, 159, 1),
|
||||
),
|
||||
),
|
||||
HtmlWidget(
|
||||
updateAppEvent.description,
|
||||
customStylesBuilder: (element) {
|
||||
return {'color': '#666666', 'font-weight': 'normal', 'text-decoration': 'none'};
|
||||
},
|
||||
onLoadingBuilder: (context, element, loadingProgress) => const CircularProgressIndicator(),
|
||||
renderMode: RenderMode.column,
|
||||
textStyle: TextStyle(fontSize: 14.sp, color: Colors.black87),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
DownloadProgress(),
|
||||
DownloadButton(updateAppEvent, deviceInfo: deviceInfo),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// 调起
|
||||
static showUpdateDialog(BuildContext context, UpdateAppEvent updateAppEvent) {
|
||||
return showDialog(
|
||||
barrierDismissible: false,
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return WillPopScope(
|
||||
onWillPop: () async => false,
|
||||
child: UpdateDialog(updateAppEvent: updateAppEvent, deviceInfo: updateAppEvent.deviceInfo),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 进度条
|
||||
class DownloadProgress extends StatelessWidget {
|
||||
DownloadProgress({super.key});
|
||||
final logic = Get.find<UpgradeLogic>();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Obx(() {
|
||||
var str = (logic.downloadRatio.value * 100).toString().split('.')[0];
|
||||
if (logic.downloadRatio.value <= 0) return Container();
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
LinearPercentIndicator(
|
||||
alignment: MainAxisAlignment.center,
|
||||
width: isPad() ? ScreenUtil().screenWidth * 0.55 : 245.w,
|
||||
animation: false,
|
||||
lineHeight: 20.0.h,
|
||||
percent: logic.downloadRatio.value,
|
||||
center: quickText(
|
||||
"$str%",
|
||||
size: 14.sp,
|
||||
align: TextAlign.center,
|
||||
color: Colors.white,
|
||||
),
|
||||
linearStrokeCap: LinearStrokeCap.roundAll,
|
||||
progressColor: Theme.of(context).primaryColor,
|
||||
),
|
||||
Container(
|
||||
height: 6.h,
|
||||
),
|
||||
quickText('更新中...', size: 12.sp, fontWeight: FontWeight.w500, color: const Color.fromRGBO(90, 90, 90, 1))
|
||||
],
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 点击下载按钮
|
||||
class DownloadButton extends StatelessWidget {
|
||||
final UpdateAppEvent updateAppEvent;
|
||||
final String deviceInfo;
|
||||
DownloadButton(this.updateAppEvent, {required this.deviceInfo, super.key});
|
||||
final logic = Get.find<UpgradeLogic>();
|
||||
|
||||
// 打开浏览器或者对应的对应市场进行下载
|
||||
Future<bool> toLaunch(UpdateAppEvent data) async {
|
||||
var uri = Uri.parse('market://details?id=${updateAppEvent.packageName}');
|
||||
if (await canLaunchUrl(uri)) {
|
||||
// 跳进对应的应用市场进行更新操作
|
||||
return await launchUrl(uri);
|
||||
}
|
||||
// 无法进入应用市场就打开浏览器进行下载
|
||||
uri = Uri.parse(data.link);
|
||||
if (await canLaunchUrl(uri)) return await launchUrl(uri);
|
||||
return false;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Obx(() {
|
||||
final count = logic.downloadRatio.value;
|
||||
if (count > 0) return Container();
|
||||
var primaryColor = Theme.of(context).primaryColor;
|
||||
|
||||
if (updateAppEvent.equipment == Equipment.windows) {
|
||||
return SizedBox(
|
||||
child: quickText('若没有弹出更新弹框,可关闭APP重新打开执行更新...', size: 16.sp),
|
||||
);
|
||||
}
|
||||
|
||||
return Container(
|
||||
height: 38.h,
|
||||
width: isPad() ? ScreenUtil().screenWidth * 0.45 : 245.w,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.all(Radius.circular(42.h)),
|
||||
gradient: LinearGradient(colors: [primaryColor, primaryColor.withOpacity(0.7)]),
|
||||
),
|
||||
child: MaterialButton(
|
||||
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);
|
||||
if (flag) {
|
||||
flag = await DownloadApk.installApk(context, updateAppEvent, logic);
|
||||
if (!flag) {
|
||||
print('执行到了重置更新按钮的地方....');
|
||||
logic.downloadRatio.value = 0.0; // 更新失败重置 更新按钮
|
||||
} else {
|
||||
print('更新成功重新打开APP..............');
|
||||
RestartWidget.restartApp(context); // 安装成功 重启APP
|
||||
}
|
||||
return;
|
||||
}
|
||||
// await FlutterClipboard.copy(updateAppEvent.link);
|
||||
// setTimeOut(1000, () => ToastUtils.showInfo('下载链接已经复制到设备,可前往浏览器下载安装'));
|
||||
} else if (deviceInfo == "ios" && updateAppEvent.equipment == Equipment.ios) {
|
||||
try {
|
||||
print(updateAppEvent.link);
|
||||
await launchUrlString(updateAppEvent.link);
|
||||
} catch (e) {
|
||||
print('进来更新报错$e');
|
||||
}
|
||||
}
|
||||
// else if (deviceInfo == 'windows' && updateAppEvent.equipment == Equipment.windows) {
|
||||
// await autoUpdater.setFeedURL(updateAppEvent.link);
|
||||
// await autoUpdater.checkForUpdates();
|
||||
// await autoUpdater.setScheduledCheckInterval(0);
|
||||
// }
|
||||
}),
|
||||
child: quickText(!logic.loadingApk.value ? '立即体验' : '正在下载...',
|
||||
size: 16.sp, color: Colors.white, fontWeight: FontWeight.w500),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,143 @@
|
|||
/*
|
||||
* @Descripttion: 获取本地权限
|
||||
* @version: UpgradePermission
|
||||
* @Author: wy
|
||||
* @Date: 2020-07-30 15:41:39
|
||||
* @LastEditors: wangyang 1147192855@qq.com
|
||||
* @LastEditTime: 2022-08-01 14:08:57
|
||||
*/
|
||||
import 'package:app_installer/app_installer.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
import 'model/UpdateAppEvent.dart';
|
||||
|
||||
class UpgradePermission {
|
||||
final String _flatform;
|
||||
const UpgradePermission(this._flatform);
|
||||
|
||||
/// 检查是否有权限,用于安卓
|
||||
/// noExecutions 执行次数
|
||||
Future<bool> checkPermission(BuildContext context, UpdateAppEvent updateAppEvent, [int? noExecutions]) async {
|
||||
noExecutions ??= 1;
|
||||
if (_flatform != 'android') return true; // 非安卓
|
||||
var status = await Permission.storage.request();
|
||||
if (status.isGranted) return true;
|
||||
|
||||
if (status.isDenied) {
|
||||
// 普通拒绝 可以再进行提示
|
||||
|
||||
await showDialog<bool>(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: const Text("权限提示"),
|
||||
content: const Text("无法获取存储权限,请同意获取设备存储权限"),
|
||||
actions: [
|
||||
MaterialButton(
|
||||
color: Theme.of(context).primaryColor,
|
||||
child: const Text("同意", style: TextStyle(color: Colors.white)),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
if (noExecutions < 2) return checkPermission(context, updateAppEvent, ++noExecutions);
|
||||
// 执行次数大于2次,就不再询问直接打开设置权限页面(防止某些机型不会弹起权限询问交互弹框)
|
||||
}
|
||||
|
||||
// 拒绝并不再提示
|
||||
bool? res = await showDialog<bool>(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: const Text("权限提示"),
|
||||
content: const Text("储存权限被永久拒绝,并且不再提示。请前往设置页面同意储存权限"),
|
||||
actions: [
|
||||
MaterialButton(
|
||||
color: Colors.green.shade900,
|
||||
child: const Text("其它方式更新", style: TextStyle(color: Colors.white)),
|
||||
onPressed: () => Navigator.of(context).pop(false),
|
||||
),
|
||||
MaterialButton(
|
||||
color: Theme.of(context).primaryColor,
|
||||
child: const Text("前往设置", style: TextStyle(color: Colors.white)),
|
||||
onPressed: () => Navigator.of(context).pop(true),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
if (res == null || !res) {
|
||||
// 其他方式下载
|
||||
// await SystemNavigator.pop(); // 退出APP
|
||||
var options = ['应用市场更新APP', '浏览器下载并安装APP'];
|
||||
var uri = Uri.parse('market://details?id=${updateAppEvent.packageName}'); // 应用市场URI
|
||||
// if (!await canLaunchUrl(uri)) options.removeAt(0); // 如果不能打开应用市场 就屏蔽掉 这个安装方式
|
||||
String? option = await showCustomModalBottomSheet(context, options);
|
||||
if (option == '应用市场更新APP') {
|
||||
if (await canLaunchUrl(uri)) {
|
||||
await launchUrl(uri);
|
||||
} else {
|
||||
await AppInstaller.goStore(updateAppEvent.packageName, 'iOSAppId');
|
||||
}
|
||||
}
|
||||
if (option == '浏览器下载并安装APP') await launchUrl(Uri.parse(updateAppEvent.link));
|
||||
} else
|
||||
await openAppSettings();
|
||||
return false;
|
||||
}
|
||||
|
||||
// 其他方式下载选择
|
||||
static Future<String?> showCustomModalBottomSheet(context, List<String> options) async {
|
||||
return showModalBottomSheet<String>(
|
||||
backgroundColor: Colors.transparent,
|
||||
isScrollControlled: true,
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return Container(
|
||||
clipBehavior: Clip.antiAlias,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: const Radius.circular(20.0),
|
||||
topRight: const Radius.circular(20.0),
|
||||
),
|
||||
),
|
||||
height: MediaQuery.of(context).size.height / 4.0,
|
||||
child: Column(children: [
|
||||
SizedBox(
|
||||
height: 50,
|
||||
child: Stack(
|
||||
textDirection: TextDirection.rtl,
|
||||
children: [
|
||||
Center(child: Text('选择其它方式更新APP', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16.0))),
|
||||
IconButton(icon: Icon(Icons.close), onPressed: () => Navigator.of(context).pop()),
|
||||
],
|
||||
),
|
||||
),
|
||||
Divider(height: 1.0),
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
itemCount: options.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
var name = options[index];
|
||||
return ListTile(
|
||||
title: Text(name),
|
||||
trailing: Icon(name.contains('浏览器') ? Icons.browser_updated_outlined : Icons.local_grocery_store_outlined),
|
||||
onTap: () => Navigator.of(context).pop(name),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
]),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* @Descripttion:
|
||||
* @version:
|
||||
* @Author: wy
|
||||
* @Date: 2020-07-30 14:10:44
|
||||
* @LastEditors: wangyang 1147192855@qq.com
|
||||
* @LastEditTime: 2022-09-28 18:20:11
|
||||
*/
|
||||
|
||||
class UpdateAppEvent {
|
||||
final String version;
|
||||
final String title;
|
||||
final String description;
|
||||
final String link;
|
||||
final bool upgrade;
|
||||
final String deviceInfo;
|
||||
final String appName;
|
||||
final String packageName;
|
||||
final Equipment equipment;
|
||||
num? v;
|
||||
num? lv;
|
||||
|
||||
UpdateAppEvent({
|
||||
required this.version,
|
||||
required this.title,
|
||||
required this.description,
|
||||
required this.link,
|
||||
required this.upgrade,
|
||||
required this.deviceInfo,
|
||||
required this.appName,
|
||||
required this.packageName,
|
||||
this.v,
|
||||
this.lv,
|
||||
int type = 0,
|
||||
}) : equipment = Equipment.values[type];
|
||||
|
||||
factory UpdateAppEvent.fromJson(Map json, String localVersion, String deviceInfo, String appName, String packageName,
|
||||
{String keyStr = "version", String typeName = "packageNameType"}) {
|
||||
String version = json[keyStr]; // 版本号
|
||||
String? descriptionStr = json["description"];
|
||||
|
||||
String newDescription = '系统有更新,请立即下载';
|
||||
bool upgrade = false;
|
||||
num? v;
|
||||
num? lv;
|
||||
// 版本号
|
||||
if (version != '') {
|
||||
v = num.parse(version.replaceAll(".", ""));
|
||||
lv = num.parse(localVersion.replaceAll(".", ""));
|
||||
|
||||
// if (lv < v) upgrade = true;
|
||||
//当前版本不等于线上版本更新为线上版本
|
||||
if (lv != v) upgrade = true;
|
||||
}
|
||||
|
||||
// 升级说明
|
||||
if (descriptionStr != null && descriptionStr.isNotEmpty) {
|
||||
newDescription = descriptionStr;
|
||||
}
|
||||
|
||||
return UpdateAppEvent(
|
||||
version: version, // 版本号
|
||||
title: json["title"] ?? "发现新版本", // title
|
||||
description: newDescription, //说明
|
||||
link: json["downloadPath"]??'', // 链接地址
|
||||
upgrade: upgrade,
|
||||
deviceInfo: deviceInfo,
|
||||
appName: appName,
|
||||
packageName: packageName,
|
||||
type: json[typeName] ?? 0,
|
||||
v: v,
|
||||
lv: lv,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return "UpdateAppEvent { version: $version, title: $title, description: $description, link: $link}";
|
||||
}
|
||||
}
|
||||
|
||||
// 设备枚举
|
||||
enum Equipment { other, android, ios, windows }
|
||||
|
|
@ -0,0 +1,156 @@
|
|||
import 'dart:io';
|
||||
import 'package:device_info_plus/device_info_plus.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:making_school_asignment_app/common/job/app_version.dart';
|
||||
import 'package:making_school_asignment_app/common/mixins/request_tool_mixin.dart';
|
||||
import 'package:making_school_asignment_app/common/store/user_store.dart';
|
||||
import 'package:making_school_asignment_app/common/utils/permission_describe_util.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
|
||||
import 'UpdateDialog.dart';
|
||||
import 'model/UpdateAppEvent.dart';
|
||||
|
||||
class UpgradeLogic extends GetxController with RequestToolMixin {
|
||||
Rx<bool> showUpgrade = false.obs; // 是否已经显示升级弹窗
|
||||
Rx<bool> loadingApk = false.obs; // 是否已经显示升级弹窗
|
||||
Rx<double> downloadRatio = 0.0.obs; // 下载比例
|
||||
|
||||
// @override
|
||||
// void onInit() {
|
||||
|
||||
// super.onInit();
|
||||
// }
|
||||
|
||||
Future<UpdateAppEvent?> getUpdateAppEvent() async {
|
||||
// 获取设备信息
|
||||
String deviceInfo;
|
||||
int deviceType;
|
||||
if (Platform.isAndroid) {
|
||||
deviceInfo = "android";
|
||||
deviceType = 1;
|
||||
} else if (Platform.isIOS) {
|
||||
deviceInfo = "ios";
|
||||
deviceType = 2;
|
||||
} else if (Platform.isWindows) {
|
||||
deviceInfo = "windows";
|
||||
deviceType = 3;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
AppVersion? result = await getClient().getLastAppVersion('making_school_asignment_app', deviceType);
|
||||
if (result == null) return null;
|
||||
|
||||
//获取当前版本
|
||||
PackageInfo packageInfo = await PackageInfo.fromPlatform();
|
||||
//获取当前版本
|
||||
String localVersion = packageInfo.version;
|
||||
String appName = packageInfo.appName; //应用名称
|
||||
String packageName = packageInfo.packageName; //包名称
|
||||
// String buildNumber = packageInfo.buildNumber; //小版本号
|
||||
|
||||
Map json = {
|
||||
'downloadPath': Platform.isWindows ? '' : result.appFileUrl,
|
||||
'version': result.version,
|
||||
'systemType': deviceType,
|
||||
'description': result.description ?? 'APP新版本更新',
|
||||
};
|
||||
return UpdateAppEvent.fromJson(json, localVersion, deviceInfo, appName, packageName, typeName: 'systemType');
|
||||
}
|
||||
|
||||
Future<void> getAppUpgrade(BuildContext context) async {
|
||||
if (!const bool.fromEnvironment('dart.vm.product')) return;
|
||||
|
||||
try {
|
||||
showUpgrade.value = true;
|
||||
|
||||
final names = [
|
||||
UserStore.to.userDetailInfo.value?.name,
|
||||
UserStore.to.userDetailInfo.value?.account,
|
||||
].whereType<String>().where((name) => name.isNotEmpty).toList();
|
||||
if (names.contains('AppleTester')) return;
|
||||
|
||||
UpdateAppEvent? updateAppEvent = await getUpdateAppEvent();
|
||||
if (updateAppEvent != null && updateAppEvent.upgrade) {
|
||||
if (Platform.isAndroid) {
|
||||
DeviceInfoPlugin deviceInfoPlugin = DeviceInfoPlugin();
|
||||
AndroidDeviceInfo androidInfo = await deviceInfoPlugin.androidInfo;
|
||||
Permission storagePermission;
|
||||
if (androidInfo.version.sdkInt >= 33) {
|
||||
storagePermission = Permission.manageExternalStorage;
|
||||
} else {
|
||||
storagePermission = Permission.storage;
|
||||
}
|
||||
await PermissionDescribeUtil.instance.toLaunchPermissionRequest(
|
||||
Get.context ?? context,
|
||||
title: '储存权限请求',
|
||||
describe: "为了提供更好的服务,需要获取到存储权限用于保存APP升级文件APK,进行升级",
|
||||
permissions: [storagePermission],
|
||||
);
|
||||
}
|
||||
|
||||
await UpdateDialog.showUpdateDialog(
|
||||
Get.context ?? context,
|
||||
updateAppEvent,
|
||||
);
|
||||
}
|
||||
/**
|
||||
// 获取设备信息
|
||||
String deviceInfo;
|
||||
int deviceType;
|
||||
if (Platform.isAndroid) {
|
||||
deviceInfo = "android";
|
||||
deviceType = 1;
|
||||
} else if (Platform.isIOS) {
|
||||
deviceInfo = "ios";
|
||||
deviceType = 2;
|
||||
} else if (Platform.isWindows) {
|
||||
deviceInfo = "windows";
|
||||
deviceType = 3;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (result != null) {
|
||||
//获取当前版本
|
||||
PackageInfo packageInfo = await PackageInfo.fromPlatform();
|
||||
//获取当前版本
|
||||
String localVersion = packageInfo.version;
|
||||
String appName = packageInfo.appName; //应用名称
|
||||
String packageName = packageInfo.packageName; //包名称
|
||||
// String buildNumber = packageInfo.buildNumber; //小版本号
|
||||
|
||||
Map json = {'downloadPath': Platform.isWindows ? '' : result.appFileUrl, 'version': result.version, 'systemType': deviceType, 'description': result.description ?? 'APP新版本更新'};
|
||||
UpdateAppEvent updateAppEvent = UpdateAppEvent.fromJson(json, localVersion, deviceInfo, appName, packageName, typeName: 'systemType');
|
||||
if (updateAppEvent.upgrade) {
|
||||
if (Platform.isAndroid) {
|
||||
DeviceInfoPlugin deviceInfoPlugin = DeviceInfoPlugin();
|
||||
AndroidDeviceInfo androidInfo = await deviceInfoPlugin.androidInfo;
|
||||
Permission storagePermission;
|
||||
if (androidInfo.version.sdkInt >= 33) {
|
||||
storagePermission = Permission.manageExternalStorage;
|
||||
} else {
|
||||
storagePermission = Permission.storage;
|
||||
}
|
||||
await PermissionDescribeUtil.instance.toLaunchPermissionRequest(
|
||||
Get.context ?? context,
|
||||
title: '储存权限请求',
|
||||
describe: "为了提供更好的服务,需要获取到存储权限用于保存APP升级文件APK,进行升级",
|
||||
permissions: [storagePermission],
|
||||
);
|
||||
}
|
||||
|
||||
await UpdateDialog.showUpdateDialog(
|
||||
Get.context ?? context,
|
||||
updateAppEvent,
|
||||
);
|
||||
}
|
||||
}
|
||||
*/
|
||||
} finally {
|
||||
showUpgrade.value = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -16,13 +16,12 @@ import 'package:making_school_asignment_app/common/utils/utils.dart';
|
|||
import 'package:making_school_asignment_app/routes/app_pages.dart';
|
||||
|
||||
import 'common/config/request_config.dart';
|
||||
import 'common/controllers/upgrade_logic.dart';
|
||||
import 'common/utils/app_upgrade/upgradeLogic.dart';
|
||||
|
||||
void main() async {
|
||||
// 在测试模式下运行Get
|
||||
Get.testMode = true;
|
||||
FlutterNativeSplash.preserve(
|
||||
widgetsBinding: WidgetsFlutterBinding.ensureInitialized());
|
||||
FlutterNativeSplash.preserve(widgetsBinding: WidgetsFlutterBinding.ensureInitialized());
|
||||
|
||||
/// 初始化本地存储
|
||||
await Get.putAsync<StorageService>(() => StorageService.init());
|
||||
|
|
@ -32,8 +31,7 @@ void main() async {
|
|||
|
||||
// Windows
|
||||
if (Platform.isWindows) {
|
||||
String feedURL =
|
||||
'${RequestConfig.imgUrl}infra-app/making_school_asignment_app/3/appcast.xml';
|
||||
String feedURL = '${RequestConfig.imgUrl}infra-app/making_school_asignment_app/3/appcast.xml';
|
||||
await autoUpdater.setFeedURL(feedURL);
|
||||
await autoUpdater.checkForUpdates();
|
||||
await autoUpdater.setScheduledCheckInterval(3600);
|
||||
|
|
@ -43,14 +41,11 @@ void main() async {
|
|||
statusBarColor: Colors.transparent, //状态栏背景颜色
|
||||
statusBarIconBrightness: Brightness.light // dark:一般显示黑色 light:一般显示白色
|
||||
));
|
||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky,
|
||||
overlays: [SystemUiOverlay.top]); // 屏幕刘海
|
||||
await SystemChrome.setPreferredOrientations(
|
||||
[DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]); // 屏幕强制竖屏
|
||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky, overlays: [SystemUiOverlay.top]); // 屏幕刘海
|
||||
await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]); // 屏幕强制竖屏
|
||||
runApp(const MyApp());
|
||||
|
||||
Future.delayed(
|
||||
const Duration(seconds: 3), () => FlutterNativeSplash.remove());
|
||||
Future.delayed(const Duration(seconds: 3), () => FlutterNativeSplash.remove());
|
||||
}
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
|
|
@ -132,11 +127,9 @@ class MyApp extends StatelessWidget {
|
|||
builder: (context, child) {
|
||||
return MediaQuery(
|
||||
//Setting font does not change with system font size
|
||||
data: MediaQuery.of(context)
|
||||
.copyWith(textScaler: const TextScaler.linear(1.0)),
|
||||
data: MediaQuery.of(context).copyWith(textScaler: const TextScaler.linear(1.0)),
|
||||
child: Scaffold(
|
||||
body: GestureDetector(
|
||||
onTap: () => Utils.hideKeyboard(), child: child),
|
||||
body: GestureDetector(onTap: () => Utils.hideKeyboard(), child: child),
|
||||
),
|
||||
);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import 'package:making_school_asignment_app/common/job/user_info.dart';
|
|||
import 'package:making_school_asignment_app/common/job/user_info_detail.dart';
|
||||
import 'package:making_school_asignment_app/common/mixins/request_tool_mixin.dart';
|
||||
import 'package:making_school_asignment_app/common/store/user_store.dart';
|
||||
import 'package:making_school_asignment_app/common/utils/app_upgrade/upgradeLogic.dart';
|
||||
import 'package:making_school_asignment_app/common/utils/storage.dart';
|
||||
import 'package:making_school_asignment_app/common/utils/toast_utils.dart';
|
||||
import 'package:making_school_asignment_app/page/home_page/children/my_info.dart';
|
||||
|
|
@ -23,8 +24,11 @@ class StartPage extends StatefulWidget {
|
|||
}
|
||||
|
||||
class _StartPageState extends State<StartPage> with RequestToolMixin {
|
||||
Timer? _timer;
|
||||
Timer? _timerPermission;
|
||||
DateTime? lastPopTime;
|
||||
final _pageController = Get.find<PageIndexController>();
|
||||
final _upgradeLogic = Get.find<UpgradeLogic>();
|
||||
|
||||
late final List<Widget> _bodyList;
|
||||
|
||||
|
|
@ -34,12 +38,26 @@ class _StartPageState extends State<StartPage> with RequestToolMixin {
|
|||
|
||||
// const WorkPage(),
|
||||
_bodyList = [const HomePage(), const MyInfo()];
|
||||
// APP 启动后就直接更新
|
||||
WidgetsBinding.instance.addPostFrameCallback((e) {
|
||||
/// 首页用户未更新不进行更新判断,没有登陆的用户在登陆页面和40秒回调中检查弹出更新APP
|
||||
if (!_upgradeLogic.showUpgrade.value && UserStore.to.userDetailInfo.value != null)
|
||||
_upgradeLogic.getAppUpgrade(context);
|
||||
});
|
||||
_timer?.cancel();
|
||||
_timer = Timer.periodic(const Duration(seconds: 40), (e) {
|
||||
if (Get.currentRoute == Routes.login) return; // 在登录页面不更新APP
|
||||
if (!_upgradeLogic.showUpgrade.value) _upgradeLogic.getAppUpgrade(context);
|
||||
});
|
||||
|
||||
String? token = UserStore.to.token;
|
||||
UserInfo? userInfo = UserStore.to.userInfo.value;
|
||||
UserInfoDetail? userInfoDetail = UserStore.to.userDetailInfo.value;
|
||||
|
||||
if ((token?.isNotEmpty ?? false) && userInfo != null) {
|
||||
// Future.delayed(const Duration(milliseconds: 200)).then((e) {
|
||||
// Get.toNamed(Routes.home);
|
||||
// });
|
||||
// 更新用户信息
|
||||
if (userInfoDetail == null) UserStore.to.updateUserInfo();
|
||||
} else {
|
||||
|
|
@ -54,6 +72,8 @@ class _StartPageState extends State<StartPage> with RequestToolMixin {
|
|||
@override
|
||||
void dispose() {
|
||||
Get.delete<PageIndexController>();
|
||||
_timer?.cancel();
|
||||
_timerPermission?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
|
@ -121,9 +141,7 @@ class _StartPageState extends State<StartPage> with RequestToolMixin {
|
|||
}),
|
||||
),
|
||||
onPopInvokedWithResult: (bool didPop, dynamic result) async {
|
||||
if (lastPopTime == null ||
|
||||
DateTime.now().difference(lastPopTime!) >
|
||||
const Duration(seconds: 1)) {
|
||||
if (lastPopTime == null || DateTime.now().difference(lastPopTime!) > const Duration(seconds: 1)) {
|
||||
lastPopTime = DateTime.now();
|
||||
ToastUtils.getFluttertoast(
|
||||
context: context,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import 'dart:async';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
|
|
@ -6,6 +7,7 @@ import 'package:get/get.dart';
|
|||
import 'package:making_school_asignment_app/common/job/marking_models/do_paper_bus.dart';
|
||||
import 'package:making_school_asignment_app/common/mixins/event_bus_mixin.dart';
|
||||
import 'package:making_school_asignment_app/common/utils/anti_shake_throttling.dart';
|
||||
import 'package:making_school_asignment_app/common/utils/utils.dart';
|
||||
|
||||
import '../configuration_files/index.dart';
|
||||
|
||||
|
|
@ -32,7 +34,7 @@ class _BottomAnnotationSwitchJobState extends State<BottomAnnotationSwitch>
|
|||
lowerBound: 0,
|
||||
upperBound: 1,
|
||||
duration: const Duration(milliseconds: 300),
|
||||
);
|
||||
)..addListener(toUp);
|
||||
|
||||
_opControllisten = _logicControl.opControl.listen((e) {
|
||||
if (e) {
|
||||
|
|
@ -47,172 +49,391 @@ class _BottomAnnotationSwitchJobState extends State<BottomAnnotationSwitch>
|
|||
|
||||
@override
|
||||
void dispose() {
|
||||
_animationController.dispose();
|
||||
_animationController
|
||||
..removeListener(toUp)
|
||||
..dispose();
|
||||
_opControllisten?.cancel();
|
||||
|
||||
eventCancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void toUp() => toUpState(setState, () {}, mounted);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final Widget barContent = Row(
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 7,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
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,
|
||||
),
|
||||
),
|
||||
Container(width: 0.5.w, height: double.infinity, color: Colors.white.withOpacity(0.3)),
|
||||
// 注解笔
|
||||
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: '滑动试题',
|
||||
)),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(width: double.infinity, color: Colors.white.withOpacity(0.3), height: 0.5.h),
|
||||
Expanded(
|
||||
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,
|
||||
),
|
||||
),
|
||||
Container(width: 0.5.w, height: double.infinity, color: Colors.white.withOpacity(0.3)),
|
||||
// 撤销上一步
|
||||
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: '全部撤销',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
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,
|
||||
);
|
||||
}),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
return SafeArea(
|
||||
left: false,
|
||||
right: false,
|
||||
top: false,
|
||||
child: AnimatedBuilder(
|
||||
animation: _animationController,
|
||||
builder: (context, _) {
|
||||
return Container(
|
||||
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: barContent,
|
||||
);
|
||||
},
|
||||
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,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
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: '滑动试题',
|
||||
)),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
_buildHorizontalGlassDivider(),
|
||||
Expanded(
|
||||
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: '全部撤销',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
_buildGlassDivider(),
|
||||
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,
|
||||
),
|
||||
),
|
||||
_buildGlassDivider(),
|
||||
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,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
@ -233,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按钮已禁用'),
|
||||
|
|
@ -242,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,
|
||||
|
|
@ -303,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,
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ class DropdownSwitchStudentsType extends StatelessWidget {
|
|||
),
|
||||
SizedBox(width: _Constants.paddingMedium.w),
|
||||
const Expanded(
|
||||
flex: 6,
|
||||
flex: 5,
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
|
|
|
|||
|
|
@ -106,8 +106,7 @@ Widget $questionNumberScrollView({
|
|||
/// 无法联动
|
||||
// if (sateData.panQuestView == false) sateData.slide.value = scrollControllerNum.offset;
|
||||
|
||||
final currentMatrix = controller.zoomController?.value;
|
||||
if (currentMatrix == null) return;
|
||||
final currentMatrix = controller.zoomController.value;
|
||||
|
||||
// 创建一个新的变换矩阵,只修改垂直平移分量
|
||||
final newMatrix = Matrix4.copy(currentMatrix)
|
||||
|
|
@ -117,7 +116,7 @@ Widget $questionNumberScrollView({
|
|||
currentMatrix.getTranslation().z, // 保持 Z 轴不变
|
||||
));
|
||||
|
||||
controller.zoomController?.value = newMatrix;
|
||||
controller.zoomController.value = newMatrix;
|
||||
});
|
||||
var listenVal = sateData.slide.listen((e) {
|
||||
if (sateData.panQuestView != null && sateData.panQuestView == true && e != scrollControllerNum.offset) {
|
||||
|
|
@ -133,24 +132,30 @@ Widget $questionNumberScrollView({
|
|||
|
||||
// var actualImgHeight = useImageInfo.value?.actualImgHeight ?? 0; // 实际图片高度
|
||||
|
||||
// 移除高频日志,避免在滚动/缩放时造成不必要的 I/O 开销
|
||||
print("图片高度:${usePiddingTop.value}");
|
||||
|
||||
return ListView.builder(
|
||||
return SingleChildScrollView(
|
||||
controller: scrollControllerNum,
|
||||
physics: const BouncingScrollPhysics(),
|
||||
padding: EdgeInsets.only(top: usePiddingTop.value > 0 ? usePiddingTop.value : 0),
|
||||
itemCount: studentQuestions.length,
|
||||
itemBuilder: (context, index) {
|
||||
final e = studentQuestions[index];
|
||||
return $ScoringQuestionsView(
|
||||
key: Key('${sateData.data.value?.templateId}_${sateData.data.value?.studentId}_${e.questionNo}'),
|
||||
sateData: sateData,
|
||||
item: e,
|
||||
logic: controller,
|
||||
scaleRatio: sateZoomData.zoomFile.value!.scaleRatio,
|
||||
initScale: useZoom.value,
|
||||
);
|
||||
},
|
||||
scrollDirection: Axis.vertical, // 设置垂直滚动
|
||||
child: SizedBox(
|
||||
height: (actualImgHeight.value ?? 0) * useZoom.value,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: studentQuestions
|
||||
.map((e) => $ScoringQuestionsView(
|
||||
key: Key('${sateData.data.value?.templateId}_${sateData.data.value?.studentId}_${e.questionNo}'),
|
||||
sateData: sateData,
|
||||
item: e,
|
||||
logic: controller,
|
||||
scaleRatio: sateZoomData.zoomFile.value!.scaleRatio,
|
||||
initScale: useZoom.value,
|
||||
))
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -177,6 +182,7 @@ Widget $scoringQuestionsView(
|
|||
useEffect(() {
|
||||
/// 学生打分数据
|
||||
studentScoreListener() {
|
||||
print(item.toJson());
|
||||
item.studentScore = studentScore.value;
|
||||
try {
|
||||
var theVal = sateData.studentQuestions.value?.firstWhere((e) => e.questionNo == item.questionNo);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import 'dart:async';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter/cupertino.dart' hide TransformationController;
|
||||
import 'package:flutter/material.dart';
|
||||
|
|
@ -46,10 +47,10 @@ class QuestionPaperView extends GetView<HomeworkReviewLogic> {
|
|||
if (zoomFileModel == null) {
|
||||
/// 计算高度
|
||||
return LayoutBuilder(builder: (BuildContext context, BoxConstraints constraints) {
|
||||
WidgetsBinding.instance.addPostFrameCallback(
|
||||
(_) => zoomState.zoomFile.value =
|
||||
ZoomFileModel(viewWidth: constraints.maxWidth, viewHeight: constraints.maxHeight),
|
||||
);
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => zoomState.zoomFile.value = ZoomFileModel(
|
||||
viewWidth: constraints.maxWidth,
|
||||
viewHeight: constraints.maxHeight,
|
||||
));
|
||||
return const SizedBox();
|
||||
});
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
@ -345,18 +341,7 @@ class QuestionImageView extends HookWidget with EventBusMixin<BottomOperationBar
|
|||
|
||||
var zoomKey = useState<GlobalKey>(GlobalKey());
|
||||
useValueChanged<int?, void>(zoomState.zoomFile.value?.templateId, (old, __) {
|
||||
// 延迟重建Zoom组件,确保重置完成后再重建
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
zoomKey.value = GlobalKey();
|
||||
|
||||
// 重建后再次设置居中矩阵,确保居中状态不被覆盖
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
final double offsetY = (zoomState.zoomFile.value?.imageHeightOffsetStart ?? 0).toDouble();
|
||||
if (offsetY > 0) {
|
||||
logic.zoomController?.value = Matrix4.identity()..setTranslationRaw(0, offsetY, 0);
|
||||
}
|
||||
});
|
||||
});
|
||||
zoomKey.value = GlobalKey();
|
||||
});
|
||||
|
||||
var vnHandWritings = useValueNotifier<List<dynamic>>([]);
|
||||
|
|
@ -518,7 +503,7 @@ class QuestionImageView extends HookWidget with EventBusMixin<BottomOperationBar
|
|||
if (activePointers.value > 0) {
|
||||
activePointers.value = activePointers.value - 1;
|
||||
}
|
||||
// 移除高频日志,避免导致掉帧
|
||||
print("---进入:onPointerUp ${activePointers.value}");
|
||||
timerRef.value?.cancel();
|
||||
if (!annotationState.pen.value) return;
|
||||
|
||||
|
|
@ -526,7 +511,7 @@ class QuestionImageView extends HookWidget with EventBusMixin<BottomOperationBar
|
|||
sateData.handwritings = vnHandWritings.value; // 添加笔迹数据
|
||||
},
|
||||
onPointerMove: (PointerMoveEvent event) {
|
||||
// 移除高频日志,避免导致掉帧
|
||||
print("进入:onPointerMove ${activePointers.value}");
|
||||
if (activePointers.value != 1) return;
|
||||
startTimerForHandwriting(vnHandWritings);
|
||||
if (!annotationState.pen.value) return;
|
||||
|
|
@ -534,7 +519,7 @@ class QuestionImageView extends HookWidget with EventBusMixin<BottomOperationBar
|
|||
Offset localPosition = event.localPosition; // 相对
|
||||
|
||||
// 使用矩阵进行统一坐标反算(避免重复补偿)
|
||||
final Matrix4 matrix = logic.zoomController?.value ?? Matrix4.identity(); // 当前变换矩阵
|
||||
final Matrix4 matrix = logic.zoomController.value; // 当前变换矩阵
|
||||
final double theScale = matrix.getMaxScaleOnAxis();
|
||||
// if (theScale != 1) {
|
||||
// print("PPPPPPPPPPPPPPPPPPPPPPPP ${(zoomFile.imageHeightOffsetStart ?? 0)}");
|
||||
|
|
@ -555,24 +540,9 @@ class QuestionImageView extends HookWidget with EventBusMixin<BottomOperationBar
|
|||
// (dy / theScale) - (max(0, imageHeightOffsetStart) / theScale) + ((sateData.zoomOffset?.dy.abs() ?? 0) / theScale),
|
||||
|
||||
// 屏幕坐标 -> 图片坐标(通过逆矩阵)
|
||||
// final inv = Matrix4.inverted(matrix);
|
||||
// final v = inv.transform3(Vector3(localPosition.dx, localPosition.dy, 0));
|
||||
// localPosition = Offset(v.x, v.y);
|
||||
|
||||
/// 屏幕坐标 -> 图片坐标(通过逆矩阵)。当无缩放/平移时走快速路径,避免不必要求逆
|
||||
/// 它们是 Matrix4 内部 4x4 矩阵的平移分量(列主序存储)。
|
||||
/// storage[12] 是平移的 x 分量,storage[13] 是平移的 y 分量(storage[14] 则是 z)。
|
||||
/// 因为 Matrix4 采用列主序,索引计算为 index = col * 4 + row,平移在第 4 列的前 3 行,所以是 12、13、14。
|
||||
/// 用途:当 theScale == 1.0 && storage[12] == 0.0 && storage[13] == 0.0 时,可判定无缩放/平移,走快速路径跳过逆矩阵。
|
||||
final double tx = matrix.storage[12];
|
||||
final double ty = matrix.storage[13];
|
||||
if (theScale == 1.0 && tx == 0.0 && ty == 0.0) {
|
||||
// identity 变换,局部坐标无需转换
|
||||
} else {
|
||||
final inv = Matrix4.inverted(matrix);
|
||||
final v = inv.transform3(Vector3(localPosition.dx, localPosition.dy, 0));
|
||||
localPosition = Offset(v.x, v.y);
|
||||
}
|
||||
final inv = Matrix4.inverted(matrix);
|
||||
final v = inv.transform3(Vector3(localPosition.dx, localPosition.dy, 0));
|
||||
localPosition = Offset(v.x, v.y);
|
||||
|
||||
// 在图片坐标系做边界校验(更直观):0..maxWidth、0..actualHeight
|
||||
if (localPosition.dy < 0 || localPosition.dy > actualHeight) return;
|
||||
|
|
@ -612,56 +582,69 @@ class QuestionImageView extends HookWidget with EventBusMixin<BottomOperationBar
|
|||
width: double.infinity,
|
||||
alignment: Alignment.center,
|
||||
child: IgnorePointer(
|
||||
// 仅当“滑动试题”启用时,让 Zoom 接收触摸;否则屏蔽以避免与批注冲突
|
||||
ignoring: !annotationState.gestureMove.value,
|
||||
child: Zoom(
|
||||
key: zoomKey.value,
|
||||
// initTotalZoomOut: true, // 展示全部内容 初始化不产生滚动条
|
||||
zoomSensibility: 0.05,
|
||||
scrollWeight: 4.r,
|
||||
doubleTapAnimDuration: Duration.zero,
|
||||
maxZoomWidth: maxWidth,
|
||||
maxZoomHeight: actualHeight,
|
||||
canvasColor: Colors.transparent,
|
||||
doubleTapScaleChange: 1,
|
||||
initScale: 1,
|
||||
backgroundColor: Colors.transparent,
|
||||
// onScaleUpdate: (double scale, double zoom) => logic.zoomLogic.onScaleUpdate(zoom),
|
||||
// onPositionUpdate: logic.zoomLogic.onPanUpPosition,
|
||||
transformationController: logic.zoomController,
|
||||
child: Obx(() {
|
||||
return Stack(
|
||||
children: [
|
||||
$TheCachedNetworkImage(
|
||||
imgWidth: maxWidth,
|
||||
imageUrl: sateData.data.value!.zgtAnswer,
|
||||
(_, imageProvider) => Image(image: imageProvider, fit: BoxFit.fitWidth),
|
||||
),
|
||||
RepaintBoundary(
|
||||
key: logic.pictureOverviewKey,
|
||||
child: CustomPaint(
|
||||
isComplex: true,
|
||||
willChange: true,
|
||||
size: Size(maxWidth, actualHeight),
|
||||
foregroundPainter: DrawingPainter(ctrl: vnHandWritings),
|
||||
// child: $TheCachedNetworkImage(
|
||||
// imgWidth: maxWidth,
|
||||
// imageUrl: showZgtAnnotate ?? sateData.data.value!.zgtAnswer,
|
||||
// (_, imageProvider) => Image(image: imageProvider, fit: BoxFit.fitWidth),
|
||||
// ),
|
||||
child: showZgtAnnotate != null
|
||||
? $TheCachedNetworkImage(
|
||||
imgWidth: maxWidth,
|
||||
imageUrl: showZgtAnnotate,
|
||||
(_, imageProvider) => Image(image: imageProvider, fit: BoxFit.fitWidth))
|
||||
: null,
|
||||
// 仅当“滑动试题”启用时,让 Zoom 接收触摸;否则屏蔽以避免与批注冲突
|
||||
ignoring: !annotationState.gestureMove.value,
|
||||
child:
|
||||
// ZoomView(
|
||||
// key: zoomKey.value,
|
||||
// viewWidth: maxWidth,
|
||||
// viewHeight: maxHeight,
|
||||
// imageDisplayHeight: actualHeight,
|
||||
// url: sateData.data.value!.zgtAnswer,
|
||||
// onScale: logic.zoomLogic.onScaleUpdate,
|
||||
// // onTrans: (offset) {
|
||||
// // print("偏移位置:$offset");
|
||||
// // },
|
||||
// onContentOffset: logic.zoomLogic.onPanUpPosition,
|
||||
// ),
|
||||
|
||||
Obx(
|
||||
() => Zoom(
|
||||
key: zoomKey.value,
|
||||
// initTotalZoomOut: true, // 展示全部内容 初始化不产生滚动条
|
||||
zoomSensibility: 0.05,
|
||||
scrollWeight: 4.r,
|
||||
doubleTapAnimDuration: Duration.zero,
|
||||
maxZoomWidth: maxWidth,
|
||||
maxZoomHeight: actualHeight,
|
||||
canvasColor: Colors.transparent,
|
||||
doubleTapScaleChange: 1,
|
||||
// initPosition: initPosition.value,
|
||||
// initScale: logic.zoomLogic.zoomState.initScale.value ?? 1,
|
||||
backgroundColor: Colors.transparent,
|
||||
onScaleUpdate: (double scale, double zoom) => logic.zoomLogic.onScaleUpdate(zoom),
|
||||
onPositionUpdate: logic.zoomLogic.onPanUpPosition,
|
||||
transformationController: logic.zoomController,
|
||||
child: Stack(
|
||||
children: [
|
||||
$TheCachedNetworkImage(
|
||||
imgWidth: maxWidth,
|
||||
imageUrl: sateData.data.value!.zgtAnswer,
|
||||
(_, imageProvider) => Image(image: imageProvider, fit: BoxFit.fitWidth),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
RepaintBoundary(
|
||||
key: logic.pictureOverviewKey,
|
||||
child: CustomPaint(
|
||||
// isComplex: true,
|
||||
size: Size(maxWidth, actualHeight),
|
||||
foregroundPainter: DrawingPainter(ctrl: vnHandWritings),
|
||||
// child: $TheCachedNetworkImage(
|
||||
// imgWidth: maxWidth,
|
||||
// imageUrl: showZgtAnnotate ?? sateData.data.value!.zgtAnswer,
|
||||
// (_, imageProvider) => Image(image: imageProvider, fit: BoxFit.fitWidth),
|
||||
// ),
|
||||
child: showZgtAnnotate != null
|
||||
? $TheCachedNetworkImage(
|
||||
imgWidth: maxWidth,
|
||||
imageUrl: showZgtAnnotate,
|
||||
(_, imageProvider) => Image(image: imageProvider, fit: BoxFit.fitWidth))
|
||||
: null,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)),
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
|
@ -674,40 +657,19 @@ class DrawingPainter extends CustomPainter {
|
|||
final Paint paintBrush = Paint()
|
||||
..color = Colors.red
|
||||
..strokeCap = StrokeCap.round
|
||||
..strokeJoin = StrokeJoin.round
|
||||
..isAntiAlias = true
|
||||
..strokeWidth = 0.7.sp
|
||||
..style = PaintingStyle.stroke;
|
||||
..strokeWidth = 0.7.sp;
|
||||
DrawingPainter({required this.ctrl}) : super(repaint: ctrl);
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final List<dynamic> points = ctrl.value;
|
||||
if (points.isEmpty) return;
|
||||
|
||||
Path path = Path();
|
||||
Offset? previous;
|
||||
for (int i = 0; i < points.length; i++) {
|
||||
final Offset? current = points[i] as Offset?;
|
||||
if (current == null) {
|
||||
// 提交当前子路径
|
||||
if (path.computeMetrics().isNotEmpty) {
|
||||
canvas.drawPath(path, paintBrush);
|
||||
}
|
||||
path = Path();
|
||||
previous = null;
|
||||
continue;
|
||||
var points = ctrl.value;
|
||||
var pointsLength = points.length;
|
||||
for (int i = 0; i < pointsLength; i++) {
|
||||
Offset? offsetData = points[i];
|
||||
Offset? nextOffsetData = pointsLength - 1 == i ? null : points[i + 1];
|
||||
if (offsetData != null && nextOffsetData != null) {
|
||||
canvas.drawLine(offsetData, nextOffsetData, paintBrush);
|
||||
}
|
||||
if (previous == null) {
|
||||
path.moveTo(current.dx, current.dy);
|
||||
} else {
|
||||
path.lineTo(current.dx, current.dy);
|
||||
}
|
||||
previous = current;
|
||||
}
|
||||
// 绘制最后的子路径
|
||||
if (path.computeMetrics().isNotEmpty) {
|
||||
canvas.drawPath(path, paintBrush);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -717,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),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ class HomeworkReviewLogic extends GetxController with RequestToolMixin, EventBus
|
|||
|
||||
StreamSubscription<TestQuestionsImageInfo?>? imageScaleZoomStream;
|
||||
|
||||
zoomWidget.TransformationController? zoomController;
|
||||
late final zoomWidget.TransformationController zoomController;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
|
|
@ -105,6 +105,15 @@ class HomeworkReviewLogic extends GetxController with RequestToolMixin, EventBus
|
|||
// 试题数据
|
||||
_dataListen = state.data.listen((e) {
|
||||
if (e == null) return;
|
||||
var zoomState = zoomLogic.zoomState;
|
||||
|
||||
final currentTemplateId = zoomState.zoomFile.value?.templateId; // 获取旧题号ID
|
||||
if (currentTemplateId != null && currentTemplateId != e.templateId) {
|
||||
// zoom 题号判断是否有变 有变就需要清空 zoom文件
|
||||
zoomState.initScale.value = null;
|
||||
zoomState.zoomFile.value!.clearZoomFile(e.templateId);
|
||||
zoomState.zoomFile.update((_) {}); // 更新的是对象 需要执行此回调
|
||||
}
|
||||
if (state.favorite.value != e.isFav) state.favorite.value = !state.favorite.value;
|
||||
});
|
||||
|
||||
|
|
@ -122,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: '储存权限请求',
|
||||
|
|
@ -138,7 +148,7 @@ class HomeworkReviewLogic extends GetxController with RequestToolMixin, EventBus
|
|||
|
||||
@override
|
||||
void onClose() {
|
||||
zoomController?.dispose();
|
||||
zoomController.dispose();
|
||||
// SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: [SystemUiOverlay.top]); // 屏幕刘海
|
||||
eventCancel();
|
||||
_dataListen.cancel();
|
||||
|
|
@ -164,7 +174,7 @@ class HomeworkReviewLogic extends GetxController with RequestToolMixin, EventBus
|
|||
state.studentQuestions.value = data.studentQuestions; // 赋值试题集合
|
||||
state.data.value = data;
|
||||
} catch (e) {
|
||||
// 移除冗余打印,使用提示替代
|
||||
print('获取数据报错了:$e');
|
||||
state.getDataError.value = true;
|
||||
ToastUtils.showError('获取试题数据失败,请检查网络连接后重试');
|
||||
} finally {
|
||||
|
|
@ -220,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;
|
||||
|
||||
|
|
@ -247,7 +260,8 @@ class HomeworkReviewLogic extends GetxController with RequestToolMixin, EventBus
|
|||
|
||||
return imgKey;
|
||||
} catch (e) {
|
||||
// 图片上传失败日志移除,统一提示
|
||||
print('图片上传失败');
|
||||
print(e.toString());
|
||||
ToastUtils.getFluttertoast(msg: '图片上传失败', context: context);
|
||||
} finally {
|
||||
// ToastUtils.dismiss();
|
||||
|
|
@ -292,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,
|
||||
|
|
@ -304,33 +319,35 @@ 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('提交失败,请检查网络连接后重试');
|
||||
} finally {
|
||||
state.submitLoading.value = false;
|
||||
|
|
@ -348,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)),
|
||||
|
|
@ -377,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) {
|
||||
|
|
|
|||
|
|
@ -17,43 +17,47 @@ class ZoomLogic extends GetxController {
|
|||
);
|
||||
|
||||
var oldTemplateId;
|
||||
StreamSubscription? _streamHomework;
|
||||
StreamSubscription? _streamZoomState;
|
||||
late StreamSubscription _streamHomework;
|
||||
late StreamSubscription _streamZoomState;
|
||||
|
||||
StreamSubscription<double?>? initScaleStream;
|
||||
|
||||
Map<int, ZoomFileModel> imageSizeMap = {};
|
||||
|
||||
HomeworkReviewLogic get homeworkReviewLogic => Get.find<HomeworkReviewLogic>();
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
oldTemplateId = zoomState.zoomFile.value?.templateId;
|
||||
|
||||
/// 根据第一次加载的试题题号 分析试题图片所占的宽高
|
||||
_streamZoomState = zoomState.zoomFile.listen((e) => refreshZoomFile(e));
|
||||
_streamZoomState = zoomState.zoomFile.listen((e) {
|
||||
var templateId = e?.templateId;
|
||||
if (templateId == null) return;
|
||||
|
||||
var homeworkData = Get.find<HomeworkReviewLogic>().state.data.value;
|
||||
var zgtAnswer = homeworkData?.zgtAnswer;
|
||||
if (zgtAnswer == null) return;
|
||||
if (oldTemplateId == templateId) return;
|
||||
|
||||
// getNetworkImageDimensions(zgtAnswer);
|
||||
|
||||
// 第三方库网络图,图片会被解码并缓存于内存
|
||||
oldTemplateId = templateId;
|
||||
CachedNetworkImageProvider(zgtAnswer).getImageSize().then((s) {
|
||||
// 提取宽度和高度
|
||||
if (s == null) return;
|
||||
var oldVal = zoomState.zoomFile.value!;
|
||||
oldVal.fileWidth = s.width;
|
||||
oldVal.fileHeight = s.height;
|
||||
zoomState.zoomFile.value = ZoomFileModel.fromJson(oldVal.toJson());
|
||||
});
|
||||
});
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((e) {
|
||||
/// 试题加载后更新尺寸对象(只要题号没有变就不更新尺寸对象)
|
||||
_streamHomework = homeworkReviewLogic.state.data.listen((e) {
|
||||
var zoomFile = zoomState.zoomFile.value;
|
||||
if (e == null || zoomFile == null) return;
|
||||
|
||||
if (zoomFile.templateId != e.templateId) {
|
||||
// zoom 题号判断是否有变 有变就需要清空 zoom文件
|
||||
zoomState.initScale.value = null;
|
||||
zoomFile = ZoomFileModel(
|
||||
templateId: e.templateId,
|
||||
viewWidth: zoomFile.viewWidth,
|
||||
viewHeight: zoomFile.viewHeight,
|
||||
);
|
||||
|
||||
/// 重置缩放和位置
|
||||
_resetZoom();
|
||||
}
|
||||
|
||||
/// 更新尺寸对象
|
||||
refreshZoomFile(zoomFile);
|
||||
_streamHomework = Get.find<HomeworkReviewLogic>().state.data.listen((e) {
|
||||
print("HOMEWORKSTATE 变化了");
|
||||
if (e == null || zoomState.zoomFile.value == null) return;
|
||||
zoomState.zoomFile.value!.templateId = e.templateId;
|
||||
print("666666 ${e.templateId}");
|
||||
zoomState.zoomFile.value = ZoomFileModel.fromJson(zoomState.zoomFile.value!.toJson());
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -65,53 +69,15 @@ class ZoomLogic extends GetxController {
|
|||
|
||||
@override
|
||||
void onClose() {
|
||||
_streamHomework?.cancel();
|
||||
_streamZoomState?.cancel();
|
||||
_streamHomework.cancel();
|
||||
_streamZoomState.cancel();
|
||||
super.onClose();
|
||||
}
|
||||
|
||||
void refreshZoomFile(ZoomFileModel? e) {
|
||||
var templateId = e?.templateId;
|
||||
if (templateId == null) return;
|
||||
|
||||
var homeworkData = homeworkReviewLogic.state.data.value;
|
||||
var zgtAnswer = homeworkData?.zgtAnswer;
|
||||
if (zgtAnswer == null || oldTemplateId == templateId) return;
|
||||
|
||||
// getNetworkImageDimensions(zgtAnswer);
|
||||
|
||||
oldTemplateId = templateId;
|
||||
final fileSize = imageSizeMap[templateId];
|
||||
// 移除冗余日志,避免频繁 I/O
|
||||
if (fileSize != null) {
|
||||
zoomState.zoomFile.value = fileSize;
|
||||
return;
|
||||
}
|
||||
|
||||
/// 第三方库网络图,图片会被解码并缓存于内存
|
||||
CachedNetworkImageProvider(zgtAnswer).getImageSize().then((s) {
|
||||
if (s == null) return;
|
||||
|
||||
// 提取宽度和高度
|
||||
var oldZoomFile = zoomState.zoomFile.value!;
|
||||
|
||||
/// 重置部分数据 避免数据被覆盖
|
||||
var newZoomFile = ZoomFileModel(
|
||||
templateId: templateId,
|
||||
viewWidth: oldZoomFile.viewWidth,
|
||||
viewHeight: oldZoomFile.viewHeight,
|
||||
fileWidth: s.width,
|
||||
fileHeight: s.height,
|
||||
);
|
||||
|
||||
/// 初始化zoom文件
|
||||
imageSizeMap[templateId] = newZoomFile;
|
||||
zoomState.zoomFile.value = newZoomFile;
|
||||
});
|
||||
}
|
||||
|
||||
// 缩放组件 ==> 缩放监听
|
||||
void onScaleUpdate(double zoom) async {
|
||||
print("缩放比例:$zoom");
|
||||
|
||||
/// 防抖
|
||||
zoomState.initScale.value = zoom;
|
||||
// anti_shake_throttling.debounce(() => zoomState.initScale.value = zoom, const Duration(milliseconds: 100))();
|
||||
|
|
@ -120,20 +86,12 @@ class ZoomLogic extends GetxController {
|
|||
// 缩放组件 ==> 位置更新
|
||||
void onPanUpPosition(Offset val) async {
|
||||
// 手指在移动 非物体移动的位置
|
||||
var state = homeworkReviewLogic.state;
|
||||
var state = Get.find<HomeworkReviewLogic>().state;
|
||||
print('**************** 正在移动位置 YYY:${val.dy}');
|
||||
print('**************** 正在移动位置 XXX:${val.dx}');
|
||||
state.zoomOffset = val;
|
||||
state.slide.value = val.dy.abs().toInt().toDouble();
|
||||
}
|
||||
|
||||
// 重置缩放和位置
|
||||
void _resetZoom() {
|
||||
// 重置缩放状态
|
||||
zoomState.initScale.value = 1.0;
|
||||
|
||||
// 重置位置偏移
|
||||
homeworkReviewLogic.state.zoomOffset = Offset.zero;
|
||||
homeworkReviewLogic.state.slide.value = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
class HomeworkReviewZoomBinding extends Bindings {
|
||||
|
|
@ -185,19 +143,18 @@ class ZoomFileModel extends Object {
|
|||
this.imageHeightOffsetend,
|
||||
this.pixelRatio,
|
||||
}) {
|
||||
/// 初始化尺寸信息
|
||||
// 图片已视图宽为基准,高度自适应可滑动 图片的实际宽高都需要乘此基准值.
|
||||
if (fileHeight != null && fileWidth != null) {
|
||||
scaleRatio = viewWidth / fileWidth!;
|
||||
actualWidth = fileWidth! * scaleRatio;
|
||||
actualHeight = fileHeight! * scaleRatio;
|
||||
if (fileHeight == null || fileWidth == null) return;
|
||||
|
||||
pixelRatio = fileWidth! / viewWidth; // 图片在此设备的像素比例
|
||||
scaleRatio = viewWidth / fileWidth!;
|
||||
actualWidth = fileWidth! * scaleRatio;
|
||||
actualHeight = fileHeight! * scaleRatio;
|
||||
|
||||
remainingHeight = viewHeight - actualHeight!;
|
||||
imageHeightOffsetStart = remainingHeight! / 2;
|
||||
imageHeightOffsetend = imageHeightOffsetStart! + actualHeight!;
|
||||
}
|
||||
pixelRatio = fileWidth! / viewWidth; // 图片在此设备的像素比例
|
||||
|
||||
remainingHeight = viewHeight - actualHeight!;
|
||||
imageHeightOffsetStart = remainingHeight! / 2;
|
||||
imageHeightOffsetend = imageHeightOffsetStart! + actualHeight!;
|
||||
}
|
||||
|
||||
factory ZoomFileModel.fromJson(Map<String, dynamic> srcJson) => _$ZoomFileModelFromJson(srcJson);
|
||||
|
|
|
|||
|
|
@ -21,16 +21,7 @@ class AgreementPage extends StatelessWidget {
|
|||
body: ListView(
|
||||
padding: EdgeInsets.symmetric(horizontal: 14.w, vertical: 8.h),
|
||||
children: [
|
||||
HtmlWidget(
|
||||
agreement.richText,
|
||||
textStyle: const TextStyle(color: Colors.black),
|
||||
// 禁用文本选择,避免触发剪切板访问
|
||||
renderMode: RenderMode.column,
|
||||
// 禁用所有交互
|
||||
onTapUrl: (url) => false,
|
||||
// 自定义样式,确保安全
|
||||
customStylesBuilder: (element) => <String, String>{},
|
||||
),
|
||||
HtmlWidget(agreement.richText, textStyle: const TextStyle(color: Colors.black)),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_easyloading/flutter_easyloading.dart';
|
||||
|
|
@ -147,7 +148,6 @@ class _RegisterState extends State<Register> with RequestToolMixin {
|
|||
maxLines: 1,
|
||||
maxLength: 20,
|
||||
textInputAction: TextInputAction.next,
|
||||
enableInteractiveSelection: false,
|
||||
onEditingComplete: () {
|
||||
FocusScope.of(context).requestFocus(_pwdFocus);
|
||||
},
|
||||
|
|
@ -169,7 +169,6 @@ class _RegisterState extends State<Register> with RequestToolMixin {
|
|||
maxLines: 1,
|
||||
obscureText: _isShowPwd, //隐藏密码显示
|
||||
textInputAction: TextInputAction.go,
|
||||
enableInteractiveSelection: false,
|
||||
onSubmitted: (_) => toRegister(),
|
||||
style: TextStyle(
|
||||
color: const Color.fromRGBO(80, 87, 103, 1),
|
||||
|
|
@ -250,8 +249,7 @@ class _RegisterState extends State<Register> with RequestToolMixin {
|
|||
),
|
||||
InkWell(
|
||||
onTap: () {
|
||||
Get.toNamed(Routes.agreementPage,
|
||||
arguments: {"type": AGREEMENT_KEY.USER_AGREEMENT.name});
|
||||
Get.toNamed(Routes.agreementPage, arguments: {"type": AGREEMENT_KEY.USER_AGREEMENT.name});
|
||||
},
|
||||
child: quickText(
|
||||
'《用户协议》',
|
||||
|
|
@ -261,13 +259,12 @@ class _RegisterState extends State<Register> with RequestToolMixin {
|
|||
),
|
||||
InkWell(
|
||||
onTap: () {
|
||||
Get.toNamed(Routes.agreementPage,
|
||||
arguments: {"type": AGREEMENT_KEY.PRIVACY_GREEMENT.name});
|
||||
Get.toNamed(Routes.agreementPage, arguments: {"type": AGREEMENT_KEY.PRIVACY_GREEMENT.name});
|
||||
},
|
||||
child: quickText(
|
||||
'《隐私协议》',
|
||||
size: 12.sp,
|
||||
color: Theme.of(context).primaryColor,
|
||||
color:Theme.of(context).primaryColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -4,7 +4,8 @@ import 'package:flutter_easyloading/flutter_easyloading.dart';
|
|||
import 'package:get/get.dart';
|
||||
import 'package:making_school_asignment_app/common/mixins/request_tool_mixin.dart';
|
||||
import 'package:making_school_asignment_app/common/store/user_store.dart';
|
||||
import 'package:making_school_asignment_app/common/controllers/upgrade_logic.dart';
|
||||
import 'package:making_school_asignment_app/common/utils/app_upgrade/model/UpdateAppEvent.dart';
|
||||
import 'package:making_school_asignment_app/common/utils/app_upgrade/upgradeLogic.dart';
|
||||
import 'package:making_school_asignment_app/common/utils/storage.dart';
|
||||
import 'package:making_school_asignment_app/common/utils/toast_utils.dart';
|
||||
import 'package:making_school_asignment_app/common/utils/utils.dart';
|
||||
|
|
@ -66,11 +67,9 @@ class LoginLogic extends GetxController with RequestToolMixin {
|
|||
|
||||
void toMsg(msg, [bool error = true]) {
|
||||
if (error) {
|
||||
WidgetsBinding.instance
|
||||
.addPostFrameCallback((_) => ToastUtils.showError(msg));
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => ToastUtils.showError(msg));
|
||||
} else {
|
||||
WidgetsBinding.instance
|
||||
.addPostFrameCallback((_) => ToastUtils.showInfo(msg));
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => ToastUtils.showInfo(msg));
|
||||
}
|
||||
state.canLogin.value = true;
|
||||
}
|
||||
|
|
@ -88,6 +87,15 @@ class LoginLogic extends GetxController with RequestToolMixin {
|
|||
EasyLoading.show(status: 'loading...');
|
||||
|
||||
try {
|
||||
/// 检查升级APP
|
||||
if (userName != 'AppleTester' && const bool.fromEnvironment('dart.vm.product')) {
|
||||
UpdateAppEvent? updateAppEvent = await upgradeLogic.getUpdateAppEvent();
|
||||
var upgrade = updateAppEvent?.upgrade ?? false;
|
||||
if (upgrade) {
|
||||
upgradeLogic.getAppUpgrade(Get.context ?? context);
|
||||
return toMsg('请在升级APP后再次登陆', false);
|
||||
}
|
||||
}
|
||||
await getClient().toLogin(userName, userPwd);
|
||||
String? nameidentifier = UserStore.to.userInfo.value?.nameidentifier;
|
||||
if (nameidentifier == null) {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import 'package:get/get.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:making_school_asignment_app/common/const_text.dart';
|
||||
import 'package:making_school_asignment_app/common/utils/anti_shake_throttling.dart';
|
||||
import 'package:making_school_asignment_app/common/controllers/upgrade_logic.dart';
|
||||
import 'package:making_school_asignment_app/common/utils/app_upgrade/upgradeLogic.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/routes/app_pages.dart';
|
||||
|
|
@ -33,7 +33,6 @@ class _LoginPageState extends State<LoginPage> {
|
|||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||
await sysProtocol(context);
|
||||
|
||||
/// 为了发布各平台方便审核 不能再登陆页面直接弹出审核
|
||||
// await Future.delayed(Duration.zero, () => upgradeLogic.getAppUpgrade(context));
|
||||
});
|
||||
|
|
@ -81,19 +80,15 @@ class _LoginPageState extends State<LoginPage> {
|
|||
child: SizedBox(
|
||||
height: 77.w,
|
||||
width: 77.w,
|
||||
child: Image.asset('assets/images/login_logo_icon.png',
|
||||
fit: BoxFit.cover),
|
||||
child: Image.asset('assets/images/login_logo_icon.png', fit: BoxFit.cover),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
margin: EdgeInsets.only(top: 90.r),
|
||||
padding: EdgeInsets.only(
|
||||
top: 50.h, bottom: 16.h, left: 40.w, right: 40.w),
|
||||
padding: EdgeInsets.only(top: 50.h, bottom: 16.h, left: 40.w, right: 40.w),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(30.r),
|
||||
topRight: Radius.circular(30.r)),
|
||||
borderRadius: BorderRadius.only(topLeft: Radius.circular(30.r), topRight: Radius.circular(30.r)),
|
||||
/*boxShadow: const [
|
||||
BoxShadow(
|
||||
color: Color.fromRGBO(46, 91, 255, 0.1),
|
||||
|
|
@ -110,16 +105,13 @@ class _LoginPageState extends State<LoginPage> {
|
|||
),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.transparent,
|
||||
border: Border.all(
|
||||
width: 1.w, color: const Color(0xFF434343)),
|
||||
borderRadius:
|
||||
BorderRadius.all(Radius.circular(17.w)),
|
||||
border: Border.all(width: 1.w, color: const Color(0xFF434343)),
|
||||
borderRadius: BorderRadius.all(Radius.circular(17.w)),
|
||||
),
|
||||
child: TextField(
|
||||
controller: state.userNameController,
|
||||
/* maxLines: 1,
|
||||
maxLength: 20,*/
|
||||
enableInteractiveSelection: false,
|
||||
textInputAction: TextInputAction.next,
|
||||
onEditingComplete: () {
|
||||
Get.focusScope?.nextFocus();
|
||||
|
|
@ -157,35 +149,30 @@ class _LoginPageState extends State<LoginPage> {
|
|||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 20.r),
|
||||
SizedBox(
|
||||
height: 20.r,
|
||||
),
|
||||
Obx(() {
|
||||
return Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 15.w),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.transparent,
|
||||
border: Border.all(
|
||||
width: 1.w, color: const Color(0xFF434343)),
|
||||
borderRadius:
|
||||
BorderRadius.all(Radius.circular(17.w)),
|
||||
border: Border.all(width: 1.w, color: const Color(0xFF434343)),
|
||||
borderRadius: BorderRadius.all(Radius.circular(17.w)),
|
||||
),
|
||||
child: TextField(
|
||||
focusNode: state.pwdFocus,
|
||||
controller: state.passwordController,
|
||||
// keyboardType: TextInputType.number,
|
||||
keyboardType: TextInputType.number,
|
||||
maxLines: 1,
|
||||
obscureText: state.isShowPwd.value,
|
||||
//隐藏密码显示
|
||||
// textInputAction: state.isShowPwd.value?TextInputAction.go:TextInputAction.next,
|
||||
textInputAction: TextInputAction.send,
|
||||
enableInteractiveSelection: false,
|
||||
onSubmitted: (_) =>
|
||||
easyThrottle('LOGIN_EASYTHROTTLE', () async {
|
||||
onSubmitted: (_) => easyThrottle('LOGIN_EASYTHROTTLE', () async{
|
||||
Utils.hideKeyboard();
|
||||
await Future.delayed(
|
||||
const Duration(milliseconds: 300));
|
||||
WidgetsBinding.instance.addPostFrameCallback(
|
||||
(_) =>
|
||||
logic.toLogin(context, upgradeLogic));
|
||||
await Future.delayed(const Duration(milliseconds: 300));
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => logic.toLogin(context,upgradeLogic));
|
||||
}),
|
||||
style: TextStyle(
|
||||
color: const Color(0xFF434343),
|
||||
|
|
@ -211,15 +198,12 @@ class _LoginPageState extends State<LoginPage> {
|
|||
),
|
||||
suffixIcon: InkWell(
|
||||
onTap: () {
|
||||
state.isShowPwd.value =
|
||||
!state.isShowPwd.value;
|
||||
state.isShowPwd.value = !state.isShowPwd.value;
|
||||
},
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(right: 5.r),
|
||||
child: Image.asset(
|
||||
state.isShowPwd.value
|
||||
? 'assets/images/eye_default.png'
|
||||
: 'assets/images/eye_active.png',
|
||||
state.isShowPwd.value ? 'assets/images/eye_default.png' : 'assets/images/eye_active.png',
|
||||
width: 15.r,
|
||||
height: 15.r,
|
||||
),
|
||||
|
|
@ -255,35 +239,23 @@ class _LoginPageState extends State<LoginPage> {
|
|||
child: Checkbox(
|
||||
// activeColor: Colors.transparent, //去掉勾选时背景颜色
|
||||
|
||||
activeColor:
|
||||
Theme.of(context).primaryColor,
|
||||
activeColor: Theme.of(context).primaryColor,
|
||||
// checkColor: Colors.white,
|
||||
value: state.keepPwd.value,
|
||||
onChanged: (value) {
|
||||
// Get.focusScope?.nextFocus();
|
||||
FocusScope.of(context)
|
||||
.requestFocus(state.pwdFocus);
|
||||
FocusScope.of(context)
|
||||
.requestFocus(state.theFocus);
|
||||
state.keepPwd.value =
|
||||
value ?? false;
|
||||
FocusScope.of(context).requestFocus(state.pwdFocus);
|
||||
FocusScope.of(context).requestFocus(state.theFocus);
|
||||
state.keepPwd.value = value ?? false;
|
||||
},
|
||||
side: WidgetStateBorderSide
|
||||
.resolveWith(
|
||||
side: WidgetStateBorderSide.resolveWith(
|
||||
(Set<WidgetState> states) {
|
||||
if (states.contains(
|
||||
WidgetState.selected)) {
|
||||
if (states.contains(WidgetState.selected)) {
|
||||
//修改勾选时边框颜色为红色
|
||||
return BorderSide(
|
||||
width: 1.5.r,
|
||||
color: Theme.of(context)
|
||||
.primaryColor);
|
||||
return BorderSide(width: 1.5.r, color: Theme.of(context).primaryColor);
|
||||
}
|
||||
//修改默认时边框颜色为绿色
|
||||
return BorderSide(
|
||||
width: 1.r,
|
||||
color: const Color(
|
||||
0xFF434343));
|
||||
return BorderSide(width: 1.r, color: const Color(0xFF434343));
|
||||
},
|
||||
)),
|
||||
);
|
||||
|
|
@ -294,12 +266,9 @@ class _LoginPageState extends State<LoginPage> {
|
|||
Utils.hideKeyboard();
|
||||
Get.focusScope?.nextFocus();
|
||||
Get.focusScope?.nextFocus();
|
||||
FocusScope.of(context)
|
||||
.requestFocus(state.pwdFocus);
|
||||
FocusScope.of(context)
|
||||
.requestFocus(state.theFocus);
|
||||
state.keepPwd.value =
|
||||
!state.keepPwd.value;
|
||||
FocusScope.of(context).requestFocus(state.pwdFocus);
|
||||
FocusScope.of(context).requestFocus(state.theFocus);
|
||||
state.keepPwd.value = !state.keepPwd.value;
|
||||
},
|
||||
child: Text(
|
||||
'记住密码',
|
||||
|
|
@ -314,26 +283,21 @@ class _LoginPageState extends State<LoginPage> {
|
|||
),
|
||||
InkWell(
|
||||
onTap: () => Get.toNamed(Routes.register),
|
||||
child: quickText('账号注册',
|
||||
color: const Color(0xFF434343)),
|
||||
child: quickText('账号注册', color: const Color(0xFF434343)),
|
||||
)
|
||||
],
|
||||
),
|
||||
InkWell(
|
||||
onTap: () =>
|
||||
easyThrottle('LOGIN_EASYTHROTTLE', () async {
|
||||
onTap: () => easyThrottle('LOGIN_EASYTHROTTLE', () async{
|
||||
Utils.hideKeyboard();
|
||||
await Future.delayed(Duration.zero);
|
||||
WidgetsBinding.instance.addPostFrameCallback(
|
||||
(_) => logic.toLogin(context, upgradeLogic));
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => logic.toLogin(context,upgradeLogic));
|
||||
}),
|
||||
child: Obx(() {
|
||||
return Container(
|
||||
margin: EdgeInsets.symmetric(vertical: 10.h),
|
||||
decoration: BoxDecoration(
|
||||
color: state.canLogin.value
|
||||
? const Color(0xFF8C68FF)
|
||||
: const Color(0xFFdddddd),
|
||||
color: state.canLogin.value ? const Color(0xFF8C68FF) : const Color(0xFFdddddd),
|
||||
/*boxShadow: [
|
||||
BoxShadow(
|
||||
color:
|
||||
|
|
@ -350,8 +314,7 @@ class _LoginPageState extends State<LoginPage> {
|
|||
alignment: Alignment.center,
|
||||
width: double.infinity,
|
||||
height: 50.h,
|
||||
child: quickText('登 录',
|
||||
size: 18.sp, color: Colors.white));
|
||||
child: quickText('登 录', size: 18.sp, color: Colors.white));
|
||||
}),
|
||||
),
|
||||
Row(
|
||||
|
|
@ -372,23 +335,16 @@ class _LoginPageState extends State<LoginPage> {
|
|||
state.pwdFocus);
|
||||
FocusScope.of(context).requestFocus(
|
||||
state.theFocus);*/
|
||||
state.readAgreement.value =
|
||||
value ?? false;
|
||||
state.readAgreement.value = value ?? false;
|
||||
},
|
||||
side: WidgetStateBorderSide.resolveWith(
|
||||
(Set<WidgetState> states) {
|
||||
if (states
|
||||
.contains(WidgetState.selected)) {
|
||||
if (states.contains(WidgetState.selected)) {
|
||||
//修改勾选时边框颜色为红色
|
||||
return BorderSide(
|
||||
width: 1.5.r,
|
||||
color: Theme.of(context)
|
||||
.primaryColor);
|
||||
return BorderSide(width: 1.5.r, color: Theme.of(context).primaryColor);
|
||||
}
|
||||
//修改默认时边框颜色为绿色
|
||||
return BorderSide(
|
||||
width: 1.r,
|
||||
color: const Color(0xFF434343));
|
||||
return BorderSide(width: 1.r, color: const Color(0xFF434343));
|
||||
},
|
||||
),
|
||||
),
|
||||
|
|
@ -397,9 +353,7 @@ class _LoginPageState extends State<LoginPage> {
|
|||
),
|
||||
InkWell(
|
||||
onTap: () {
|
||||
Get.toNamed(Routes.agreementPage, arguments: {
|
||||
"type": AGREEMENT_KEY.USER_AGREEMENT.name
|
||||
});
|
||||
Get.toNamed(Routes.agreementPage, arguments: {"type": AGREEMENT_KEY.USER_AGREEMENT.name});
|
||||
},
|
||||
child: quickText(
|
||||
'请仔细阅读',
|
||||
|
|
@ -408,22 +362,16 @@ class _LoginPageState extends State<LoginPage> {
|
|||
),
|
||||
InkWell(
|
||||
onTap: () {
|
||||
Get.toNamed(Routes.agreementPage, arguments: {
|
||||
"type": AGREEMENT_KEY.USER_AGREEMENT.name
|
||||
});
|
||||
Get.toNamed(Routes.agreementPage, arguments: {"type": AGREEMENT_KEY.USER_AGREEMENT.name});
|
||||
},
|
||||
child: Text(
|
||||
'《用户协议》',
|
||||
style: TextStyle(
|
||||
fontSize: 12.r,
|
||||
color: Theme.of(context).primaryColor),
|
||||
style: TextStyle(fontSize: 12.r, color: Theme.of(context).primaryColor),
|
||||
),
|
||||
),
|
||||
InkWell(
|
||||
onTap: () {
|
||||
Get.toNamed(Routes.agreementPage, arguments: {
|
||||
"type": AGREEMENT_KEY.PRIVACY_GREEMENT.name
|
||||
});
|
||||
Get.toNamed(Routes.agreementPage, arguments: {"type": AGREEMENT_KEY.PRIVACY_GREEMENT.name});
|
||||
},
|
||||
child: quickText(
|
||||
'《隐私协议》',
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
flutter/ephemeral
|
||||
|
|
@ -0,0 +1,145 @@
|
|||
# Project-level configuration.
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
project(runner LANGUAGES CXX)
|
||||
|
||||
# The name of the executable created for the application. Change this to change
|
||||
# the on-disk name of your application.
|
||||
set(BINARY_NAME "making_school_asignment_app")
|
||||
# The unique GTK application identifier for this application. See:
|
||||
# https://wiki.gnome.org/HowDoI/ChooseApplicationID
|
||||
set(APPLICATION_ID "com.yuanxuan.making_school_asignment_app")
|
||||
|
||||
# Explicitly opt in to modern CMake behaviors to avoid warnings with recent
|
||||
# versions of CMake.
|
||||
cmake_policy(SET CMP0063 NEW)
|
||||
|
||||
# Load bundled libraries from the lib/ directory relative to the binary.
|
||||
set(CMAKE_INSTALL_RPATH "$ORIGIN/lib")
|
||||
|
||||
# Root filesystem for cross-building.
|
||||
if(FLUTTER_TARGET_PLATFORM_SYSROOT)
|
||||
set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT})
|
||||
set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT})
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
|
||||
endif()
|
||||
|
||||
# Define build configuration options.
|
||||
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
|
||||
set(CMAKE_BUILD_TYPE "Debug" CACHE
|
||||
STRING "Flutter build mode" FORCE)
|
||||
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
|
||||
"Debug" "Profile" "Release")
|
||||
endif()
|
||||
|
||||
# Compilation settings that should be applied to most targets.
|
||||
#
|
||||
# Be cautious about adding new options here, as plugins use this function by
|
||||
# default. In most cases, you should add new options to specific targets instead
|
||||
# of modifying this function.
|
||||
function(APPLY_STANDARD_SETTINGS TARGET)
|
||||
target_compile_features(${TARGET} PUBLIC cxx_std_14)
|
||||
target_compile_options(${TARGET} PRIVATE -Wall -Werror)
|
||||
target_compile_options(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:-O3>")
|
||||
target_compile_definitions(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:NDEBUG>")
|
||||
endfunction()
|
||||
|
||||
# Flutter library and tool build rules.
|
||||
set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
|
||||
add_subdirectory(${FLUTTER_MANAGED_DIR})
|
||||
|
||||
# System-level dependencies.
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
|
||||
|
||||
add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}")
|
||||
|
||||
# Define the application target. To change its name, change BINARY_NAME above,
|
||||
# not the value here, or `flutter run` will no longer work.
|
||||
#
|
||||
# Any new source files that you add to the application should be added here.
|
||||
add_executable(${BINARY_NAME}
|
||||
"main.cc"
|
||||
"my_application.cc"
|
||||
"${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
|
||||
)
|
||||
|
||||
# Apply the standard set of build settings. This can be removed for applications
|
||||
# that need different build settings.
|
||||
apply_standard_settings(${BINARY_NAME})
|
||||
|
||||
# Add dependency libraries. Add any application-specific dependencies here.
|
||||
target_link_libraries(${BINARY_NAME} PRIVATE flutter)
|
||||
target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK)
|
||||
|
||||
# Run the Flutter tool portions of the build. This must not be removed.
|
||||
add_dependencies(${BINARY_NAME} flutter_assemble)
|
||||
|
||||
# Only the install-generated bundle's copy of the executable will launch
|
||||
# correctly, since the resources must in the right relative locations. To avoid
|
||||
# people trying to run the unbundled copy, put it in a subdirectory instead of
|
||||
# the default top-level location.
|
||||
set_target_properties(${BINARY_NAME}
|
||||
PROPERTIES
|
||||
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run"
|
||||
)
|
||||
|
||||
|
||||
# Generated plugin build rules, which manage building the plugins and adding
|
||||
# them to the application.
|
||||
include(flutter/generated_plugins.cmake)
|
||||
|
||||
|
||||
# === Installation ===
|
||||
# By default, "installing" just makes a relocatable bundle in the build
|
||||
# directory.
|
||||
set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle")
|
||||
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
|
||||
set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
|
||||
endif()
|
||||
|
||||
# Start with a clean build bundle directory every time.
|
||||
install(CODE "
|
||||
file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\")
|
||||
" COMPONENT Runtime)
|
||||
|
||||
set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
|
||||
set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib")
|
||||
|
||||
install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
|
||||
COMPONENT Runtime)
|
||||
|
||||
install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
|
||||
COMPONENT Runtime)
|
||||
|
||||
install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
|
||||
COMPONENT Runtime)
|
||||
|
||||
foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES})
|
||||
install(FILES "${bundled_library}"
|
||||
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
|
||||
COMPONENT Runtime)
|
||||
endforeach(bundled_library)
|
||||
|
||||
# Copy the native assets provided by the build.dart from all packages.
|
||||
set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/")
|
||||
install(DIRECTORY "${NATIVE_ASSETS_DIR}"
|
||||
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
|
||||
COMPONENT Runtime)
|
||||
|
||||
# Fully re-copy the assets directory on each build to avoid having stale files
|
||||
# from a previous install.
|
||||
set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
|
||||
install(CODE "
|
||||
file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
|
||||
" COMPONENT Runtime)
|
||||
install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
|
||||
DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
|
||||
|
||||
# Install the AOT library on non-Debug builds only.
|
||||
if(NOT CMAKE_BUILD_TYPE MATCHES "Debug")
|
||||
install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
|
||||
COMPONENT Runtime)
|
||||
endif()
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
# This file controls Flutter-level build steps. It should not be edited.
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
|
||||
set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
|
||||
|
||||
# Configuration provided via flutter tool.
|
||||
include(${EPHEMERAL_DIR}/generated_config.cmake)
|
||||
|
||||
# TODO: Move the rest of this into files in ephemeral. See
|
||||
# https://github.com/flutter/flutter/issues/57146.
|
||||
|
||||
# Serves the same purpose as list(TRANSFORM ... PREPEND ...),
|
||||
# which isn't available in 3.10.
|
||||
function(list_prepend LIST_NAME PREFIX)
|
||||
set(NEW_LIST "")
|
||||
foreach(element ${${LIST_NAME}})
|
||||
list(APPEND NEW_LIST "${PREFIX}${element}")
|
||||
endforeach(element)
|
||||
set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
# === Flutter Library ===
|
||||
# System-level dependencies.
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
|
||||
pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0)
|
||||
pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0)
|
||||
|
||||
set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so")
|
||||
|
||||
# Published to parent scope for install step.
|
||||
set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
|
||||
set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
|
||||
set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
|
||||
set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE)
|
||||
|
||||
list(APPEND FLUTTER_LIBRARY_HEADERS
|
||||
"fl_basic_message_channel.h"
|
||||
"fl_binary_codec.h"
|
||||
"fl_binary_messenger.h"
|
||||
"fl_dart_project.h"
|
||||
"fl_engine.h"
|
||||
"fl_json_message_codec.h"
|
||||
"fl_json_method_codec.h"
|
||||
"fl_message_codec.h"
|
||||
"fl_method_call.h"
|
||||
"fl_method_channel.h"
|
||||
"fl_method_codec.h"
|
||||
"fl_method_response.h"
|
||||
"fl_plugin_registrar.h"
|
||||
"fl_plugin_registry.h"
|
||||
"fl_standard_message_codec.h"
|
||||
"fl_standard_method_codec.h"
|
||||
"fl_string_codec.h"
|
||||
"fl_value.h"
|
||||
"fl_view.h"
|
||||
"flutter_linux.h"
|
||||
)
|
||||
list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/")
|
||||
add_library(flutter INTERFACE)
|
||||
target_include_directories(flutter INTERFACE
|
||||
"${EPHEMERAL_DIR}"
|
||||
)
|
||||
target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}")
|
||||
target_link_libraries(flutter INTERFACE
|
||||
PkgConfig::GTK
|
||||
PkgConfig::GLIB
|
||||
PkgConfig::GIO
|
||||
)
|
||||
add_dependencies(flutter flutter_assemble)
|
||||
|
||||
# === Flutter tool backend ===
|
||||
# _phony_ is a non-existent file to force this command to run every time,
|
||||
# since currently there's no way to get a full input/output list from the
|
||||
# flutter tool.
|
||||
add_custom_command(
|
||||
OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
|
||||
${CMAKE_CURRENT_BINARY_DIR}/_phony_
|
||||
COMMAND ${CMAKE_COMMAND} -E env
|
||||
${FLUTTER_TOOL_ENVIRONMENT}
|
||||
"${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh"
|
||||
${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE}
|
||||
VERBATIM
|
||||
)
|
||||
add_custom_target(flutter_assemble DEPENDS
|
||||
"${FLUTTER_LIBRARY}"
|
||||
${FLUTTER_LIBRARY_HEADERS}
|
||||
)
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
//
|
||||
// Generated file. Do not edit.
|
||||
//
|
||||
|
||||
// clang-format off
|
||||
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <url_launcher_linux/url_launcher_plugin.h>
|
||||
|
||||
void fl_register_plugins(FlPluginRegistry* registry) {
|
||||
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
|
||||
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
//
|
||||
// Generated file. Do not edit.
|
||||
//
|
||||
|
||||
// clang-format off
|
||||
|
||||
#ifndef GENERATED_PLUGIN_REGISTRANT_
|
||||
#define GENERATED_PLUGIN_REGISTRANT_
|
||||
|
||||
#include <flutter_linux/flutter_linux.h>
|
||||
|
||||
// Registers Flutter plugins.
|
||||
void fl_register_plugins(FlPluginRegistry* registry);
|
||||
|
||||
#endif // GENERATED_PLUGIN_REGISTRANT_
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
#
|
||||
# Generated file, do not edit.
|
||||
#
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
url_launcher_linux
|
||||
)
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
)
|
||||
|
||||
set(PLUGIN_BUNDLED_LIBRARIES)
|
||||
|
||||
foreach(plugin ${FLUTTER_PLUGIN_LIST})
|
||||
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin})
|
||||
target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
|
||||
list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
|
||||
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
|
||||
endforeach(plugin)
|
||||
|
||||
foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
|
||||
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin})
|
||||
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
|
||||
endforeach(ffi_plugin)
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
#include "my_application.h"
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
g_autoptr(MyApplication) app = my_application_new();
|
||||
return g_application_run(G_APPLICATION(app), argc, argv);
|
||||
}
|
||||
|
|
@ -0,0 +1,124 @@
|
|||
#include "my_application.h"
|
||||
|
||||
#include <flutter_linux/flutter_linux.h>
|
||||
#ifdef GDK_WINDOWING_X11
|
||||
#include <gdk/gdkx.h>
|
||||
#endif
|
||||
|
||||
#include "flutter/generated_plugin_registrant.h"
|
||||
|
||||
struct _MyApplication {
|
||||
GtkApplication parent_instance;
|
||||
char** dart_entrypoint_arguments;
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION)
|
||||
|
||||
// Implements GApplication::activate.
|
||||
static void my_application_activate(GApplication* application) {
|
||||
MyApplication* self = MY_APPLICATION(application);
|
||||
GtkWindow* window =
|
||||
GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application)));
|
||||
|
||||
// Use a header bar when running in GNOME as this is the common style used
|
||||
// by applications and is the setup most users will be using (e.g. Ubuntu
|
||||
// desktop).
|
||||
// If running on X and not using GNOME then just use a traditional title bar
|
||||
// in case the window manager does more exotic layout, e.g. tiling.
|
||||
// If running on Wayland assume the header bar will work (may need changing
|
||||
// if future cases occur).
|
||||
gboolean use_header_bar = TRUE;
|
||||
#ifdef GDK_WINDOWING_X11
|
||||
GdkScreen* screen = gtk_window_get_screen(window);
|
||||
if (GDK_IS_X11_SCREEN(screen)) {
|
||||
const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen);
|
||||
if (g_strcmp0(wm_name, "GNOME Shell") != 0) {
|
||||
use_header_bar = FALSE;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (use_header_bar) {
|
||||
GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new());
|
||||
gtk_widget_show(GTK_WIDGET(header_bar));
|
||||
gtk_header_bar_set_title(header_bar, "making_school_asignment_app");
|
||||
gtk_header_bar_set_show_close_button(header_bar, TRUE);
|
||||
gtk_window_set_titlebar(window, GTK_WIDGET(header_bar));
|
||||
} else {
|
||||
gtk_window_set_title(window, "making_school_asignment_app");
|
||||
}
|
||||
|
||||
gtk_window_set_default_size(window, 1280, 720);
|
||||
gtk_widget_show(GTK_WIDGET(window));
|
||||
|
||||
g_autoptr(FlDartProject) project = fl_dart_project_new();
|
||||
fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments);
|
||||
|
||||
FlView* view = fl_view_new(project);
|
||||
gtk_widget_show(GTK_WIDGET(view));
|
||||
gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view));
|
||||
|
||||
fl_register_plugins(FL_PLUGIN_REGISTRY(view));
|
||||
|
||||
gtk_widget_grab_focus(GTK_WIDGET(view));
|
||||
}
|
||||
|
||||
// Implements GApplication::local_command_line.
|
||||
static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) {
|
||||
MyApplication* self = MY_APPLICATION(application);
|
||||
// Strip out the first argument as it is the binary name.
|
||||
self->dart_entrypoint_arguments = g_strdupv(*arguments + 1);
|
||||
|
||||
g_autoptr(GError) error = nullptr;
|
||||
if (!g_application_register(application, nullptr, &error)) {
|
||||
g_warning("Failed to register: %s", error->message);
|
||||
*exit_status = 1;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
g_application_activate(application);
|
||||
*exit_status = 0;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// Implements GApplication::startup.
|
||||
static void my_application_startup(GApplication* application) {
|
||||
//MyApplication* self = MY_APPLICATION(object);
|
||||
|
||||
// Perform any actions required at application startup.
|
||||
|
||||
G_APPLICATION_CLASS(my_application_parent_class)->startup(application);
|
||||
}
|
||||
|
||||
// Implements GApplication::shutdown.
|
||||
static void my_application_shutdown(GApplication* application) {
|
||||
//MyApplication* self = MY_APPLICATION(object);
|
||||
|
||||
// Perform any actions required at application shutdown.
|
||||
|
||||
G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application);
|
||||
}
|
||||
|
||||
// Implements GObject::dispose.
|
||||
static void my_application_dispose(GObject* object) {
|
||||
MyApplication* self = MY_APPLICATION(object);
|
||||
g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev);
|
||||
G_OBJECT_CLASS(my_application_parent_class)->dispose(object);
|
||||
}
|
||||
|
||||
static void my_application_class_init(MyApplicationClass* klass) {
|
||||
G_APPLICATION_CLASS(klass)->activate = my_application_activate;
|
||||
G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line;
|
||||
G_APPLICATION_CLASS(klass)->startup = my_application_startup;
|
||||
G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown;
|
||||
G_OBJECT_CLASS(klass)->dispose = my_application_dispose;
|
||||
}
|
||||
|
||||
static void my_application_init(MyApplication* self) {}
|
||||
|
||||
MyApplication* my_application_new() {
|
||||
return MY_APPLICATION(g_object_new(my_application_get_type(),
|
||||
"application-id", APPLICATION_ID,
|
||||
"flags", G_APPLICATION_NON_UNIQUE,
|
||||
nullptr));
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
#ifndef FLUTTER_MY_APPLICATION_H_
|
||||
#define FLUTTER_MY_APPLICATION_H_
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION,
|
||||
GtkApplication)
|
||||
|
||||
/**
|
||||
* my_application_new:
|
||||
*
|
||||
* Creates a new Flutter-based application.
|
||||
*
|
||||
* Returns: a new #MyApplication.
|
||||
*/
|
||||
MyApplication* my_application_new();
|
||||
|
||||
#endif // FLUTTER_MY_APPLICATION_H_
|
||||
|
|
@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
|||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
# In Windows, build-name is used as the major, minor, and patch parts
|
||||
# of the product and file versions while build-number is used as the build suffix.
|
||||
version: 1.0.8+9
|
||||
version: 1.0.6+7
|
||||
|
||||
environment:
|
||||
sdk: '>=3.4.1 <4.0.0'
|
||||
|
|
@ -48,14 +48,14 @@ dependencies:
|
|||
# http请求插件
|
||||
dio: ^5.4.2+1
|
||||
# 网络缓存图片
|
||||
cached_network_image: ^3.4.1
|
||||
cached_network_image: ^3.2.1
|
||||
# 上拉加载和下拉刷新的组件
|
||||
flutter_easyrefresh: ^2.2.2
|
||||
photo_view: ^0.15.0
|
||||
# 加密验签插件,支持SHA MD5 HMAC
|
||||
crypto: ^3.0.2
|
||||
# 获取app版本号
|
||||
package_info_plus: ^9.0.0
|
||||
package_info_plus: ^8.0.0
|
||||
# 网络监控
|
||||
connectivity_plus: ^6.0.3
|
||||
# toast组件用于系统尚未初始化完成时
|
||||
|
|
@ -71,9 +71,15 @@ dependencies:
|
|||
url: https://gitea.23544.com/wangyang/zoom_widget.git
|
||||
# url: https://github.com/semakers/zoom-widget.git
|
||||
# ref: a35c9da6afe405c23b5897b449683d424016e9f1
|
||||
|
||||
retrofit: 4.6.0
|
||||
json_annotation: ^4.9.0
|
||||
# TODO 升级插件 暂时不使用
|
||||
# app_upgrade_plugin:
|
||||
# # path: C:\Users\wy\Desktop\my_project\app_upgrade_plugin
|
||||
# git:
|
||||
# url: https://gitea.23544.com/wangyang/app_upgrade_plugin.git
|
||||
# start retrofit请求封装
|
||||
retrofit: ^4.1.0
|
||||
json_annotation: 4.9.0
|
||||
# end retrofit请求封装
|
||||
# 进度条
|
||||
percent_indicator: ^4.2.3
|
||||
badges: ^3.1.2
|
||||
|
|
@ -88,15 +94,14 @@ dependencies:
|
|||
flutter_spinkit: ^5.2.1
|
||||
event_bus: ^2.0.0
|
||||
path_provider: ^2.1.3
|
||||
|
||||
uuid: ^3.0.7
|
||||
flutter_echarts: ^2.4.0
|
||||
# 饼图
|
||||
flutter_echart: ^2.0.0
|
||||
url_launcher: ^6.1.11
|
||||
app_installer: ^1.1.0
|
||||
# auto_updater: ^0.2.1
|
||||
auto_updater: ^1.0.0
|
||||
permission_handler: ^12.0.1
|
||||
auto_updater: ^0.2.1
|
||||
permission_handler: ^11.3.1
|
||||
flutter_distributor: 0.6.5
|
||||
# fastforge: ^0.6.5
|
||||
flutter_native_splash: ^2.4.6
|
||||
|
|
@ -104,15 +109,9 @@ dependencies:
|
|||
app_settings: ^5.1.1
|
||||
device_info_plus: ^11.1.0
|
||||
auto_size_text: ^3.0.0
|
||||
yx_app_upgrade_flutter:
|
||||
git:
|
||||
url: https://gitea.23544.com/wangyang/yx_app_upgrade_flutter.git
|
||||
ref: 2.0.4
|
||||
|
||||
dependency_overrides:
|
||||
archive: ^4.0.2
|
||||
uuid: ^4.4.2
|
||||
# analyzer: ^3..0
|
||||
# meta: ^1.15.0
|
||||
|
||||
dev_dependencies:
|
||||
|
|
|
|||
Loading…
Reference in New Issue