Compare commits
1 Commits
master
...
the_window
| Author | SHA1 | Date |
|---|---|---|
|
|
a0131636a5 |
|
|
@ -5,11 +5,9 @@
|
||||||
*.swp
|
*.swp
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.atom/
|
.atom/
|
||||||
.build/
|
|
||||||
.buildlog/
|
.buildlog/
|
||||||
.history
|
.history
|
||||||
.svn/
|
.svn/
|
||||||
.swiftpm/
|
|
||||||
migrate_working_dir/
|
migrate_working_dir/
|
||||||
|
|
||||||
# IntelliJ related
|
# IntelliJ related
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
{
|
{
|
||||||
"dart.flutterSdkPath": ".fvm/versions/3.32.0"
|
"dart.flutterSdkPath": ".fvm/versions/3.32.0",
|
||||||
|
"java.configuration.updateBuildConfiguration": "interactive"
|
||||||
}
|
}
|
||||||
|
|
@ -35,7 +35,6 @@ android {
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||||
targetCompatibility = JavaVersion.VERSION_1_8
|
targetCompatibility = JavaVersion.VERSION_1_8
|
||||||
coreLibraryDesugaringEnabled true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
|
|
@ -85,14 +84,8 @@ android {
|
||||||
shrinkResources false //删除无用资源
|
shrinkResources false //删除无用资源
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
flutter {
|
flutter {
|
||||||
source = "../.."
|
source = "../.."
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
|
||||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4' // 核心库反糖依赖
|
|
||||||
}
|
|
||||||
|
|
@ -40,7 +40,7 @@
|
||||||
android:name="flutterEmbedding"
|
android:name="flutterEmbedding"
|
||||||
android:value="2"/>
|
android:value="2"/>
|
||||||
<!-- Provider -->
|
<!-- Provider -->
|
||||||
<!-- <provider
|
<provider
|
||||||
android:name="androidx.core.content.FileProvider"
|
android:name="androidx.core.content.FileProvider"
|
||||||
android:authorities="${applicationId}.fileProvider"
|
android:authorities="${applicationId}.fileProvider"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
|
|
@ -48,16 +48,6 @@
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||||
android:resource="@xml/file_paths"/>
|
android:resource="@xml/file_paths"/>
|
||||||
</provider> -->
|
|
||||||
<!-- ✅ FileProvider 配置 - 这是必需的! -->
|
|
||||||
<provider
|
|
||||||
android:name="androidx.core.content.FileProvider"
|
|
||||||
android:authorities="${applicationId}.fileprovider"
|
|
||||||
android:exported="false"
|
|
||||||
android:grantUriPermissions="true">
|
|
||||||
<meta-data
|
|
||||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
|
||||||
android:resource="@xml/file_paths" />
|
|
||||||
</provider>
|
</provider>
|
||||||
</application>
|
</application>
|
||||||
<!-- 访问电话状态 -->
|
<!-- 访问电话状态 -->
|
||||||
|
|
@ -87,21 +77,25 @@
|
||||||
<!-- 屏幕常亮权限 -->
|
<!-- 屏幕常亮权限 -->
|
||||||
<uses-permission
|
<uses-permission
|
||||||
android:name="android.permission.WAKE_LOCK"/>
|
android:name="android.permission.WAKE_LOCK"/>
|
||||||
<!-- <uses-permission
|
<uses-permission
|
||||||
android:name="android.permission.CAMERA"/> -->
|
android:name="android.permission.CAMERA"/>
|
||||||
<!-- Permissions options for the `access notification policy` group -->
|
<!-- Permissions options for the `access notification policy` group -->
|
||||||
<uses-permission
|
<uses-permission
|
||||||
android:name="android.permission.ACCESS_NOTIFICATION_POLICY"/>
|
android:name="android.permission.ACCESS_NOTIFICATION_POLICY"/>
|
||||||
<!-- Permissions options for the `notification` group -->
|
<!-- Permissions options for the `notification` group -->
|
||||||
<uses-permission
|
<uses-permission
|
||||||
android:name="android.permission.POST_NOTIFICATIONS"/>
|
android:name="android.permission.POST_NOTIFICATIONS"/>
|
||||||
<!-- 注释掉文本处理权限,避免不必要的剪切板访问 -->
|
<!-- Required to query activities that can process text, see:
|
||||||
<!-- <queries>
|
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>
|
<intent>
|
||||||
<action
|
<action
|
||||||
android:name="android.intent.action.PROCESS_TEXT"/>
|
android:name="android.intent.action.PROCESS_TEXT"/>
|
||||||
<data
|
<data
|
||||||
android:mimeType="text/plain"/>
|
android:mimeType="text/plain"/>
|
||||||
</intent>
|
</intent>
|
||||||
</queries> -->
|
</queries>
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|
@ -1,17 +1,5 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<paths xmlns:android="http://schemas.android.com/apk/res/android">
|
<paths>
|
||||||
<!-- 外部存储 -->
|
|
||||||
<external-path name="external_files" path="." />
|
|
||||||
<!-- 应用内部存储 -->
|
|
||||||
<files-path name="files" path="." />
|
|
||||||
<!-- 缓存目录 -->
|
|
||||||
<cache-path name="cache" path="." />
|
|
||||||
<!-- 外部缓存 -->
|
|
||||||
<external-cache-path name="external_cache" path="." />
|
|
||||||
<!-- 下载目录 -->
|
|
||||||
<external-path name="downloads" path="Download/" />
|
|
||||||
|
|
||||||
<!-- 如果你已有其他配置,请保留它们 -->
|
|
||||||
<external-path path="Android/data/com.yuanxuan.making_school_asignment_app/" name="files_root" />
|
<external-path path="Android/data/com.yuanxuan.making_school_asignment_app/" name="files_root" />
|
||||||
<external-path path="." name="external_storage_root" />
|
<external-path path="." name="external_storage_root" />
|
||||||
</paths>
|
</paths>
|
||||||
|
|
@ -30,8 +30,8 @@
|
||||||
<!-- 屏幕常亮权限 -->
|
<!-- 屏幕常亮权限 -->
|
||||||
<uses-permission
|
<uses-permission
|
||||||
android:name="android.permission.WAKE_LOCK"/>
|
android:name="android.permission.WAKE_LOCK"/>
|
||||||
<!-- <uses-permission
|
<uses-permission
|
||||||
android:name="android.permission.CAMERA"/> -->
|
android:name="android.permission.CAMERA"/>
|
||||||
<!-- Permissions options for the `access notification policy` group -->
|
<!-- Permissions options for the `access notification policy` group -->
|
||||||
<uses-permission
|
<uses-permission
|
||||||
android:name="android.permission.ACCESS_NOTIFICATION_POLICY"/>
|
android:name="android.permission.ACCESS_NOTIFICATION_POLICY"/>
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@
|
||||||
type="application/octet-stream" />
|
type="application/octet-stream" />
|
||||||
</item> -->
|
</item> -->
|
||||||
<item>
|
<item>
|
||||||
<title>Version 1.0.7+8</title>
|
<title>Version 1.0.3+4</title>
|
||||||
#发行说明-读取html方式(2选1)
|
#发行说明-读取html方式(2选1)
|
||||||
<!-- <sparkle:releaseNotesLink>
|
<!-- <sparkle:releaseNotesLink>
|
||||||
https://your_domain/your_path/release_notes.html
|
https://your_domain/your_path/release_notes.html
|
||||||
|
|
@ -39,15 +39,16 @@
|
||||||
<![CDATA[
|
<![CDATA[
|
||||||
<ul>
|
<ul>
|
||||||
<li>1、修复已知BUG</li>
|
<li>1、修复已知BUG</li>
|
||||||
<li>2、添加缩放布局和批阅书写</li>
|
<li>2、优化操作布局</li>
|
||||||
</ul>
|
</ul>
|
||||||
]]>
|
]]>
|
||||||
</description>
|
</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"
|
<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="MEUCIQCwWC4fKrn/dfdH6uzX4Sssv7G6XVgvwjQLklrTM6+cBQIgZDY4INHcwuxrsIREuPQeTvQL4j0mvF9qPQfppjRSgsg=" length="0"
|
sparkle:dsaSignature="MEYCIQCuU0BodcdWrF+WoJrWRpY8P1pfK+dKkvrl3ZJ5KxnDdAIhAIWqp7VBNO9IaKFj2ypQ+s7DWurBUSaf6MTvexMmuvX+"
|
||||||
sparkle:version="1.0.7+8"
|
length="0"
|
||||||
|
sparkle:version="1.0.3+4"
|
||||||
sparkle:os="windows"
|
sparkle:os="windows"
|
||||||
type="application/octet-stream" />
|
type="application/octet-stream" />
|
||||||
</item>
|
</item>
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -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>
|
<key>CFBundleVersion</key>
|
||||||
<string>1.0</string>
|
<string>1.0</string>
|
||||||
<key>MinimumOSVersion</key>
|
<key>MinimumOSVersion</key>
|
||||||
<string>13.0</string>
|
<string>12.0</string>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
# Uncomment this line to define a global platform for your project
|
# 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.
|
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
||||||
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||||
|
|
|
||||||
|
|
@ -477,10 +477,9 @@
|
||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||||
MTL_ENABLE_DEBUG_INFO = NO;
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
|
||||||
SUPPORTED_PLATFORMS = iphoneos;
|
SUPPORTED_PLATFORMS = iphoneos;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
VALIDATE_PRODUCT = YES;
|
VALIDATE_PRODUCT = YES;
|
||||||
|
|
@ -496,7 +495,7 @@
|
||||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
CURRENT_PROJECT_VERSION = 7;
|
||||||
DEVELOPMENT_TEAM = Z778GC45N8;
|
DEVELOPMENT_TEAM = Z778GC45N8;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
|
|
@ -504,7 +503,7 @@
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = "$(FLUTTER_BUILD_NAME)";
|
MARKETING_VERSION = 4;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "com.yuanxuan.makingS--buneng--choolAsignmentApp";
|
PRODUCT_BUNDLE_IDENTIFIER = "com.yuanxuan.makingS--buneng--choolAsignmentApp";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
|
|
@ -525,7 +524,7 @@
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.yuanxuan.makingSchoolAsignmentApp.RunnerTests;
|
PRODUCT_BUNDLE_IDENTIFIER = com.yuanxuan.makingSchoolAsignmentApp.RunnerTests;
|
||||||
|
|
@ -543,7 +542,7 @@
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEVELOPMENT_TEAM = Z778GC45N8;
|
DEVELOPMENT_TEAM = Z778GC45N8;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
|
|
@ -560,7 +559,7 @@
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.yuanxuan.makingSchoolAsignmentApp.RunnerTests;
|
PRODUCT_BUNDLE_IDENTIFIER = com.yuanxuan.makingSchoolAsignmentApp.RunnerTests;
|
||||||
|
|
@ -620,11 +619,10 @@
|
||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||||
MTL_ENABLE_DEBUG_INFO = YES;
|
MTL_ENABLE_DEBUG_INFO = YES;
|
||||||
ONLY_ACTIVE_ARCH = YES;
|
ONLY_ACTIVE_ARCH = YES;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
};
|
};
|
||||||
name = Debug;
|
name = Debug;
|
||||||
|
|
@ -673,10 +671,9 @@
|
||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||||
MTL_ENABLE_DEBUG_INFO = NO;
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
|
||||||
SUPPORTED_PLATFORMS = iphoneos;
|
SUPPORTED_PLATFORMS = iphoneos;
|
||||||
SWIFT_COMPILATION_MODE = wholemodule;
|
SWIFT_COMPILATION_MODE = wholemodule;
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||||
|
|
@ -692,7 +689,7 @@
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
CURRENT_PROJECT_VERSION = 7;
|
||||||
DEVELOPMENT_TEAM = Z778GC45N8;
|
DEVELOPMENT_TEAM = Z778GC45N8;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
|
|
@ -700,7 +697,7 @@
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = "$(FLUTTER_BUILD_NAME)";
|
MARKETING_VERSION = 4;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.yuanxuan.makingSchoolAsignmentApp;
|
PRODUCT_BUNDLE_IDENTIFIER = com.yuanxuan.makingSchoolAsignmentApp;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||||
|
|
@ -722,7 +719,7 @@
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_ENTITLEMENTS = Runner/RunnerRelease.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Runner/RunnerRelease.entitlements;
|
||||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
CURRENT_PROJECT_VERSION = 7;
|
||||||
DEVELOPMENT_TEAM = Z778GC45N8;
|
DEVELOPMENT_TEAM = Z778GC45N8;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
|
|
@ -730,7 +727,7 @@
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = "$(FLUTTER_BUILD_NAME)";
|
MARKETING_VERSION = 4;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.yuanxuan.makingSchoolAsignmentApp;
|
PRODUCT_BUNDLE_IDENTIFIER = com.yuanxuan.makingSchoolAsignmentApp;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,6 @@
|
||||||
buildConfiguration = "Debug"
|
buildConfiguration = "Debug"
|
||||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
|
|
||||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||||
<MacroExpansion>
|
<MacroExpansion>
|
||||||
<BuildableReference
|
<BuildableReference
|
||||||
|
|
@ -55,13 +54,11 @@
|
||||||
buildConfiguration = "Debug"
|
buildConfiguration = "Debug"
|
||||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
|
|
||||||
launchStyle = "0"
|
launchStyle = "0"
|
||||||
useCustomWorkingDirectory = "NO"
|
useCustomWorkingDirectory = "NO"
|
||||||
ignoresPersistentStateOnLaunch = "NO"
|
ignoresPersistentStateOnLaunch = "NO"
|
||||||
debugDocumentVersioning = "YES"
|
debugDocumentVersioning = "YES"
|
||||||
debugServiceExtension = "internal"
|
debugServiceExtension = "internal"
|
||||||
enableGPUValidationMode = "1"
|
|
||||||
allowLocationSimulation = "YES">
|
allowLocationSimulation = "YES">
|
||||||
<BuildableProductRunnable
|
<BuildableProductRunnable
|
||||||
runnableDebuggingMode = "0">
|
runnableDebuggingMode = "0">
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import Flutter
|
import Flutter
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
@main
|
@UIApplicationMain
|
||||||
@objc class AppDelegate: FlutterAppDelegate {
|
@objc class AppDelegate: FlutterAppDelegate {
|
||||||
override func application(
|
override func application(
|
||||||
_ application: UIApplication,
|
_ application: UIApplication,
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,7 @@ class AppStateController extends GetxController with WidgetsBindingObserver {
|
||||||
final appLifecycleState = AppLifecycleState.resumed.obs;
|
final appLifecycleState = AppLifecycleState.resumed.obs;
|
||||||
|
|
||||||
// 便捷方法:判断应用是否在前台
|
// 便捷方法:判断应用是否在前台
|
||||||
bool get isAppInForeground =>
|
bool get isAppInForeground => appLifecycleState.value == AppLifecycleState.resumed;
|
||||||
appLifecycleState.value == AppLifecycleState.resumed;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
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,223 @@
|
||||||
|
/*
|
||||||
|
* @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_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:flutter/material.dart';
|
||||||
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
import 'package:url_launcher/url_launcher_string.dart';
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
|
class PlatformUtils {
|
||||||
|
static bool get isDesktop =>
|
||||||
|
defaultTargetPlatform == TargetPlatform.windows ||
|
||||||
|
defaultTargetPlatform == TargetPlatform.linux ||
|
||||||
|
defaultTargetPlatform == TargetPlatform.macOS;
|
||||||
|
|
||||||
|
static bool get isMobile =>
|
||||||
|
defaultTargetPlatform == TargetPlatform.android || defaultTargetPlatform == TargetPlatform.iOS;
|
||||||
|
}
|
||||||
|
|
@ -11,18 +11,18 @@ import 'package:get/get.dart';
|
||||||
import 'package:making_school_asignment_app/common/config/app_config.dart';
|
import 'package:making_school_asignment_app/common/config/app_config.dart';
|
||||||
import 'package:making_school_asignment_app/common/config/colorUtils.dart';
|
import 'package:making_school_asignment_app/common/config/colorUtils.dart';
|
||||||
import 'package:making_school_asignment_app/common/store/user_store.dart';
|
import 'package:making_school_asignment_app/common/store/user_store.dart';
|
||||||
|
import 'package:making_school_asignment_app/common/utils/platform_utils.dart';
|
||||||
import 'package:making_school_asignment_app/common/utils/storage.dart';
|
import 'package:making_school_asignment_app/common/utils/storage.dart';
|
||||||
import 'package:making_school_asignment_app/common/utils/utils.dart';
|
import 'package:making_school_asignment_app/common/utils/utils.dart';
|
||||||
import 'package:making_school_asignment_app/routes/app_pages.dart';
|
import 'package:making_school_asignment_app/routes/app_pages.dart';
|
||||||
|
|
||||||
import 'common/config/request_config.dart';
|
import 'common/config/request_config.dart';
|
||||||
import 'common/controllers/upgrade_logic.dart';
|
import 'common/utils/app_upgrade/upgradeLogic.dart';
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
// 在测试模式下运行Get
|
// 在测试模式下运行Get
|
||||||
Get.testMode = true;
|
Get.testMode = true;
|
||||||
FlutterNativeSplash.preserve(
|
FlutterNativeSplash.preserve(widgetsBinding: WidgetsFlutterBinding.ensureInitialized());
|
||||||
widgetsBinding: WidgetsFlutterBinding.ensureInitialized());
|
|
||||||
|
|
||||||
/// 初始化本地存储
|
/// 初始化本地存储
|
||||||
await Get.putAsync<StorageService>(() => StorageService.init());
|
await Get.putAsync<StorageService>(() => StorageService.init());
|
||||||
|
|
@ -32,8 +32,7 @@ void main() async {
|
||||||
|
|
||||||
// Windows
|
// Windows
|
||||||
if (Platform.isWindows) {
|
if (Platform.isWindows) {
|
||||||
String feedURL =
|
String feedURL = '${RequestConfig.imgUrl}infra-app/making_school_asignment_app/3/appcast.xml';
|
||||||
'${RequestConfig.imgUrl}infra-app/making_school_asignment_app/3/appcast.xml';
|
|
||||||
await autoUpdater.setFeedURL(feedURL);
|
await autoUpdater.setFeedURL(feedURL);
|
||||||
await autoUpdater.checkForUpdates();
|
await autoUpdater.checkForUpdates();
|
||||||
await autoUpdater.setScheduledCheckInterval(3600);
|
await autoUpdater.setScheduledCheckInterval(3600);
|
||||||
|
|
@ -43,14 +42,14 @@ void main() async {
|
||||||
statusBarColor: Colors.transparent, //状态栏背景颜色
|
statusBarColor: Colors.transparent, //状态栏背景颜色
|
||||||
statusBarIconBrightness: Brightness.light // dark:一般显示黑色 light:一般显示白色
|
statusBarIconBrightness: Brightness.light // dark:一般显示黑色 light:一般显示白色
|
||||||
));
|
));
|
||||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky,
|
if (PlatformUtils.isMobile) {
|
||||||
overlays: [SystemUiOverlay.top]); // 屏幕刘海
|
SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky, overlays: [SystemUiOverlay.top]); // 屏幕刘海
|
||||||
await SystemChrome.setPreferredOrientations(
|
await SystemChrome.setPreferredOrientations(
|
||||||
[DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]); // 屏幕强制竖屏
|
[DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]); // 屏幕强制竖屏
|
||||||
|
}
|
||||||
runApp(const MyApp());
|
runApp(const MyApp());
|
||||||
|
|
||||||
Future.delayed(
|
Future.delayed(const Duration(seconds: 3), () => FlutterNativeSplash.remove());
|
||||||
const Duration(seconds: 3), () => FlutterNativeSplash.remove());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class MyApp extends StatelessWidget {
|
class MyApp extends StatelessWidget {
|
||||||
|
|
@ -62,7 +61,8 @@ class MyApp extends StatelessWidget {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
// String? oldRouter;
|
// String? oldRouter;
|
||||||
return ScreenUtilInit(
|
return ScreenUtilInit(
|
||||||
designSize: const Size(AppConfig.UI_WIDTH, AppConfig.UI_HEIGHT),
|
designSize:
|
||||||
|
PlatformUtils.isDesktop ? const Size(1920, 1080) : const Size(AppConfig.UI_WIDTH, AppConfig.UI_HEIGHT),
|
||||||
/* minTextAdapt: true,
|
/* minTextAdapt: true,
|
||||||
splitScreenMode: true,*/
|
splitScreenMode: true,*/
|
||||||
builder: () => GetMaterialApp(
|
builder: () => GetMaterialApp(
|
||||||
|
|
@ -132,11 +132,9 @@ class MyApp extends StatelessWidget {
|
||||||
builder: (context, child) {
|
builder: (context, child) {
|
||||||
return MediaQuery(
|
return MediaQuery(
|
||||||
//Setting font does not change with system font size
|
//Setting font does not change with system font size
|
||||||
data: MediaQuery.of(context)
|
data: MediaQuery.of(context).copyWith(textScaler: const TextScaler.linear(1.0)),
|
||||||
.copyWith(textScaler: const TextScaler.linear(1.0)),
|
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
body: GestureDetector(
|
body: GestureDetector(onTap: () => Utils.hideKeyboard(), child: child),
|
||||||
onTap: () => Utils.hideKeyboard(), child: child),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,125 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
|
|
||||||
|
class DesktopPageTemplate extends StatelessWidget {
|
||||||
|
final String title;
|
||||||
|
final Widget? child;
|
||||||
|
final List<Widget>? actions;
|
||||||
|
final String? subtitle;
|
||||||
|
|
||||||
|
const DesktopPageTemplate({
|
||||||
|
super.key,
|
||||||
|
required this.title,
|
||||||
|
this.child,
|
||||||
|
this.actions,
|
||||||
|
this.subtitle,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: const Color(0xFFF0F2F5),
|
||||||
|
body: Column(
|
||||||
|
children: [
|
||||||
|
_buildHeader(context),
|
||||||
|
Expanded(
|
||||||
|
child: child ?? _buildPlaceholder(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildHeader(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
padding: EdgeInsets.all(24.r),
|
||||||
|
color: Colors.white,
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
title,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 24.sp,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: const Color(0xFF333333),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (subtitle != null) ...[
|
||||||
|
SizedBox(height: 4.h),
|
||||||
|
Text(
|
||||||
|
subtitle!,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14.sp,
|
||||||
|
color: Colors.grey[600],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (actions != null)
|
||||||
|
Row(children: actions!)
|
||||||
|
else
|
||||||
|
IconButton(
|
||||||
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
icon: Icon(Icons.close, size: 24.sp, color: Colors.grey[600]),
|
||||||
|
tooltip: '关闭',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildPlaceholder() {
|
||||||
|
return Center(
|
||||||
|
child: Card(
|
||||||
|
elevation: 2,
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12.r)),
|
||||||
|
child: Container(
|
||||||
|
padding: EdgeInsets.all(48.r),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.desktop_windows_outlined,
|
||||||
|
size: 64.r,
|
||||||
|
color: Colors.grey[400],
|
||||||
|
),
|
||||||
|
SizedBox(height: 16.h),
|
||||||
|
Text(
|
||||||
|
'桌面版 $title',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 20.sp,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: const Color(0xFF333333),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 8.h),
|
||||||
|
Text(
|
||||||
|
'此页面已针对 Windows 桌面环境进行优化',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14.sp,
|
||||||
|
color: Colors.grey[600],
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
SizedBox(height: 24.h),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {},
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 32.w, vertical: 12.h),
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8.r)),
|
||||||
|
),
|
||||||
|
child: const Text('返回'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,100 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
|
import 'package:making_school_asignment_app/page/home_page/children/my_info.dart';
|
||||||
|
|
||||||
|
import '../home_page/home_view.dart';
|
||||||
|
|
||||||
|
class DesktopScaffold extends StatefulWidget {
|
||||||
|
const DesktopScaffold({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<DesktopScaffold> createState() => _DesktopScaffoldState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DesktopScaffoldState extends State<DesktopScaffold> {
|
||||||
|
int _selectedIndex = 0;
|
||||||
|
final List<Widget> _pages = [const HomePage(), const MyInfo()];
|
||||||
|
bool _isExpanded = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: const Color(0xFFF0F2F5),
|
||||||
|
body: Row(
|
||||||
|
children: <Widget>[
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black.withOpacity(0.05),
|
||||||
|
blurRadius: 10,
|
||||||
|
offset: const Offset(2, 0),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: NavigationRail(
|
||||||
|
selectedIndex: _selectedIndex,
|
||||||
|
onDestinationSelected: (int index) {
|
||||||
|
setState(() {
|
||||||
|
_selectedIndex = index;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
extended: _isExpanded,
|
||||||
|
minWidth: 72.w,
|
||||||
|
minExtendedWidth: 200.w,
|
||||||
|
labelType: _isExpanded ? NavigationRailLabelType.none : NavigationRailLabelType.selected,
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
selectedIconTheme: IconThemeData(
|
||||||
|
color: Theme.of(context).primaryColor,
|
||||||
|
size: 28.r,
|
||||||
|
),
|
||||||
|
unselectedIconTheme: IconThemeData(
|
||||||
|
color: Colors.grey[600],
|
||||||
|
size: 24.r,
|
||||||
|
),
|
||||||
|
selectedLabelTextStyle: TextStyle(
|
||||||
|
color: Theme.of(context).primaryColor,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
fontSize: 12.sp,
|
||||||
|
),
|
||||||
|
unselectedLabelTextStyle: TextStyle(
|
||||||
|
color: Colors.grey[600],
|
||||||
|
fontSize: 12.sp,
|
||||||
|
),
|
||||||
|
leading: Padding(
|
||||||
|
padding: EdgeInsets.symmetric(vertical: 16.h),
|
||||||
|
child: FloatingActionButton.small(
|
||||||
|
onPressed: () {
|
||||||
|
setState(() {
|
||||||
|
_isExpanded = !_isExpanded;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
backgroundColor: Colors.grey[100],
|
||||||
|
foregroundColor: Colors.grey[700],
|
||||||
|
elevation: 0,
|
||||||
|
child: Icon(_isExpanded ? Icons.chevron_left : Icons.menu, size: 20.r),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
destinations: const <NavigationRailDestination>[
|
||||||
|
NavigationRailDestination(
|
||||||
|
icon: Icon(Icons.assignment_outlined),
|
||||||
|
selectedIcon: Icon(Icons.assignment),
|
||||||
|
label: Text('作业管理'),
|
||||||
|
),
|
||||||
|
NavigationRailDestination(
|
||||||
|
icon: Icon(Icons.person_outline),
|
||||||
|
selectedIcon: Icon(Icons.person),
|
||||||
|
label: Text('个人信息'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: _pages[_selectedIndex],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,159 @@
|
||||||
|
import 'package:flutter/material.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/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/storage.dart';
|
||||||
|
import 'package:making_school_asignment_app/routes/app_pages.dart';
|
||||||
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
|
|
||||||
|
class OtherDesktopPage extends StatefulWidget {
|
||||||
|
const OtherDesktopPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<OtherDesktopPage> createState() => _OtherDesktopPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _OtherDesktopPageState extends State<OtherDesktopPage> with RequestToolMixin {
|
||||||
|
late Rx<UserInfoDetail?> userInfo = UserStore.to.userDetailInfo;
|
||||||
|
RxString localVersion = ''.obs;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
getPageInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
getPageInfo() async {
|
||||||
|
PackageInfo packageInfo = await PackageInfo.fromPlatform();
|
||||||
|
localVersion.value = packageInfo.version;
|
||||||
|
}
|
||||||
|
|
||||||
|
_showLogoutDialog(BuildContext context) async {
|
||||||
|
return showDialog<bool>(
|
||||||
|
context: context,
|
||||||
|
barrierDismissible: false,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: const Text('账户注销'),
|
||||||
|
content: const Text('账户注销无法恢复,您确定要注销账户吗?'),
|
||||||
|
actions: <Widget>[
|
||||||
|
TextButton(
|
||||||
|
child: const Text('取消'),
|
||||||
|
onPressed: () => Navigator.of(context).pop(false),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
child: const Text('确定'),
|
||||||
|
onPressed: () async {
|
||||||
|
await getClient().toUserLogout(userInfo.value!.id);
|
||||||
|
try {
|
||||||
|
UserStore.to.erase();
|
||||||
|
await StorageService.to.erase();
|
||||||
|
Navigator.of(context).pop(true);
|
||||||
|
Get.offAllNamed(Routes.login);
|
||||||
|
} catch (e) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: const Color(0xFFF0F2F5),
|
||||||
|
body: SingleChildScrollView(
|
||||||
|
padding: EdgeInsets.all(24.r),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
_buildHeader(),
|
||||||
|
SizedBox(height: 24.h),
|
||||||
|
_buildSettingsCards(),
|
||||||
|
SizedBox(height: 24.h),
|
||||||
|
_buildVersionInfo(),
|
||||||
|
SizedBox(height: 24.h),
|
||||||
|
_buildLogoutSection(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildHeader() {
|
||||||
|
return Text(
|
||||||
|
'其他设置',
|
||||||
|
style: TextStyle(fontSize: 24.sp, fontWeight: FontWeight.bold, color: const Color(0xFF333333)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildSettingsCards() {
|
||||||
|
return Card(
|
||||||
|
elevation: 2,
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12.r)),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
ListTile(
|
||||||
|
leading: Icon(Icons.privacy_tip_outlined, color: Colors.grey[600]),
|
||||||
|
title: const Text('用户隐私协议'),
|
||||||
|
trailing: const Icon(Icons.arrow_forward_ios, size: 16),
|
||||||
|
onTap: () {
|
||||||
|
Get.toNamed(Routes.agreementPage, arguments: {"type": AGREEMENT_KEY.PRIVACY_GREEMENT.name});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const Divider(height: 1),
|
||||||
|
ListTile(
|
||||||
|
leading: Icon(Icons.description_outlined, color: Colors.grey[600]),
|
||||||
|
title: const Text('用户协议'),
|
||||||
|
trailing: const Icon(Icons.arrow_forward_ios, size: 16),
|
||||||
|
onTap: () {
|
||||||
|
Get.toNamed(Routes.agreementPage, arguments: {"type": AGREEMENT_KEY.USER_AGREEMENT.name});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildVersionInfo() {
|
||||||
|
return Card(
|
||||||
|
elevation: 2,
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12.r)),
|
||||||
|
child: ListTile(
|
||||||
|
leading: Icon(Icons.info_outline, color: Colors.grey[600]),
|
||||||
|
title: const Text('APP版本'),
|
||||||
|
trailing: Obx(() => Text(localVersion.value, style: TextStyle(color: Colors.grey[600]))),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildLogoutSection() {
|
||||||
|
return Card(
|
||||||
|
elevation: 2,
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12.r)),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
ListTile(
|
||||||
|
leading: Icon(Icons.logout, color: Colors.red[600]),
|
||||||
|
title: Text('账户注销', style: TextStyle(color: Colors.red[600])),
|
||||||
|
trailing: Icon(Icons.arrow_forward_ios, size: 16, color: Colors.red[600]),
|
||||||
|
onTap: () => _showLogoutDialog(context),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.all(16.r),
|
||||||
|
child: Text(
|
||||||
|
'APP备案号: 渝ICP备17007225号-4A',
|
||||||
|
style: TextStyle(fontSize: 12.sp, color: Colors.grey[500]),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,189 @@
|
||||||
|
import 'package:flutter/material.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/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/storage.dart';
|
||||||
|
import 'package:making_school_asignment_app/page/global_widget/my_text.dart';
|
||||||
|
import 'package:making_school_asignment_app/routes/app_pages.dart';
|
||||||
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
|
|
||||||
|
class OtherMobilePage extends StatefulWidget {
|
||||||
|
const OtherMobilePage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<OtherMobilePage> createState() => _OtherMobilePageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _OtherMobilePageState extends State<OtherMobilePage> with RequestToolMixin {
|
||||||
|
late Rx<UserInfoDetail?> userInfo = UserStore.to.userDetailInfo;
|
||||||
|
RxString localVersion = ''.obs;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
getPageInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取当前版本号
|
||||||
|
getPageInfo() async {
|
||||||
|
PackageInfo packageInfo = await PackageInfo.fromPlatform();
|
||||||
|
localVersion.value = packageInfo.version;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确认对话框
|
||||||
|
_showAlertDialog(context1) async {
|
||||||
|
await showDialog(
|
||||||
|
barrierDismissible: false,
|
||||||
|
context: context1,
|
||||||
|
builder: (context) {
|
||||||
|
return AlertDialog(title: quickText("提示信息"), content: quickText("账户注销无法恢复,您确定要注销账户吗?"), actions: <Widget>[
|
||||||
|
TextButton(
|
||||||
|
child: quickText("取消"),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.pop(context, 'Cancle');
|
||||||
|
},
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
child: quickText("确定"),
|
||||||
|
onPressed: () async {
|
||||||
|
await getClient().toUserLogout(userInfo.value!.id);
|
||||||
|
try {
|
||||||
|
UserStore.to.erase();
|
||||||
|
var msg = await StorageService.to.erase();
|
||||||
|
print(msg);
|
||||||
|
Navigator.pop(context, "Ok");
|
||||||
|
Get.offAllNamed(Routes.login);
|
||||||
|
} catch (e) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final personalInfoTitleStly = TextStyle(
|
||||||
|
color: const Color.fromRGBO(80, 87, 103, 1),
|
||||||
|
fontSize: 16.sp,
|
||||||
|
);
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: const Color.fromRGBO(248, 248, 248, 1),
|
||||||
|
appBar: AppBar(
|
||||||
|
backgroundColor: Theme.of(context).primaryColor,
|
||||||
|
title: quickText('其他', color: Colors.white),
|
||||||
|
),
|
||||||
|
body: Stack(
|
||||||
|
alignment: const FractionalOffset(0.5, 0.98),
|
||||||
|
children: [
|
||||||
|
ListView(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
margin: EdgeInsets.symmetric(vertical: 22.h, horizontal: 16.w),
|
||||||
|
padding: EdgeInsets.symmetric(vertical: 22.h, horizontal: 16.w),
|
||||||
|
height: 130.h,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(6.w)),
|
||||||
|
color: Colors.white,
|
||||||
|
boxShadow: const [
|
||||||
|
BoxShadow(
|
||||||
|
color: Color.fromRGBO(46, 91, 255, 0.1),
|
||||||
|
offset: Offset.zero,
|
||||||
|
blurRadius: 20,
|
||||||
|
spreadRadius: 10,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
InkWell(
|
||||||
|
onTap: () {
|
||||||
|
Get.toNamed(Routes.agreementPage, arguments: {"type": AGREEMENT_KEY.PRIVACY_GREEMENT.name});
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
padding: EdgeInsets.only(bottom: 4.h),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text('用户隐私协议', style: personalInfoTitleStly),
|
||||||
|
Icon(
|
||||||
|
Icons.arrow_forward_ios,
|
||||||
|
color: const Color.fromRGBO(80, 87, 103, 1),
|
||||||
|
size: 16.sp,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
height: 1.w,
|
||||||
|
color: const Color.fromRGBO(240, 243, 255, 1),
|
||||||
|
),
|
||||||
|
SizedBox(height: 8.h),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text('APP版本', style: personalInfoTitleStly),
|
||||||
|
Obx(() {
|
||||||
|
return quickText(localVersion);
|
||||||
|
})
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
bottom: 50.h,
|
||||||
|
child: InkWell(
|
||||||
|
child: Container(
|
||||||
|
height: 46.h,
|
||||||
|
width: ScreenUtil().screenWidth - 60.w,
|
||||||
|
padding: EdgeInsets.symmetric(vertical: 14.h),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(6.w)),
|
||||||
|
color: Colors.white,
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: const Color.fromRGBO(46, 91, 255, 0.2),
|
||||||
|
offset: Offset(2.w, 2.h),
|
||||||
|
blurRadius: 14,
|
||||||
|
spreadRadius: 0.5,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.exit_to_app_outlined,
|
||||||
|
size: 13.sp,
|
||||||
|
color: const Color.fromRGBO(148, 163, 182, 1),
|
||||||
|
),
|
||||||
|
Container(width: 6.w),
|
||||||
|
Text(
|
||||||
|
'账户注销',
|
||||||
|
style: TextStyle(color: const Color.fromRGBO(148, 163, 182, 1), fontSize: 13.sp),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onTap: () => _showAlertDialog(context),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [Text('APP备案号: ', style: personalInfoTitleStly), quickText('渝ICP备17007225号-4A', size: 14.sp)],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,199 +1,13 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:making_school_asignment_app/common/utils/platform_utils.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:making_school_asignment_app/page/global_widget/other_desktop_page.dart';
|
||||||
import 'package:making_school_asignment_app/common/const_text.dart';
|
import 'package:making_school_asignment_app/page/global_widget/other_mobile_page.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/storage.dart';
|
|
||||||
import 'package:making_school_asignment_app/routes/app_pages.dart';
|
|
||||||
import 'package:package_info_plus/package_info_plus.dart';
|
|
||||||
import 'package:making_school_asignment_app/page/global_widget/my_text.dart';
|
|
||||||
// 其他页面
|
|
||||||
|
|
||||||
class OhterPage extends StatefulWidget {
|
class OhterPage extends StatelessWidget {
|
||||||
const OhterPage({super.key});
|
const OhterPage({super.key});
|
||||||
|
|
||||||
@override
|
|
||||||
State<OhterPage> createState() => _OhterPageState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _OhterPageState extends State<OhterPage> with RequestToolMixin {
|
|
||||||
late Rx<UserInfoDetail?> userInfo = UserStore.to.userDetailInfo;
|
|
||||||
RxString localVersion = ''.obs;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
getPageInfo();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取当前版本号
|
|
||||||
getPageInfo() async {
|
|
||||||
PackageInfo packageInfo = await PackageInfo.fromPlatform();
|
|
||||||
localVersion.value = packageInfo.version;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 确认对话框
|
|
||||||
_showAlertDialog(context1) async {
|
|
||||||
await showDialog(
|
|
||||||
// 表示点击灰色背景的时候是否消失弹出框
|
|
||||||
barrierDismissible: false,
|
|
||||||
context: context1,
|
|
||||||
builder: (context) {
|
|
||||||
return AlertDialog(title: quickText("提示信息"), content: quickText("账户注销无法恢复,您确定要注销账户吗?"), actions: <Widget>[
|
|
||||||
TextButton(
|
|
||||||
child: quickText("取消"),
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.pop(context, 'Cancle');
|
|
||||||
},
|
|
||||||
),
|
|
||||||
TextButton(
|
|
||||||
child: quickText("确定"),
|
|
||||||
onPressed: () async {
|
|
||||||
/* ref.read(markingKeyboardProvider.notifier).clean();
|
|
||||||
ref.read(markingSubtopicSwitchingProvider.notifier).clean();
|
|
||||||
ref.read(userTokenProvider.notifier).clean();
|
|
||||||
ref.read(userProvider.notifier).clean();*/
|
|
||||||
await getClient().toUserLogout(userInfo.value!.id);
|
|
||||||
try {
|
|
||||||
UserStore.to.erase();
|
|
||||||
var msg = await StorageService.to.erase();
|
|
||||||
print(msg);
|
|
||||||
Navigator.pop(context, "Ok");
|
|
||||||
Get.offAllNamed(Routes.login);
|
|
||||||
} catch (e) {
|
|
||||||
print(e);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final personalInfoTitleStly = TextStyle(
|
return PlatformUtils.isDesktop ? const OtherDesktopPage() : const OtherMobilePage();
|
||||||
color: const Color.fromRGBO(80, 87, 103, 1),
|
|
||||||
fontSize: 16.sp,
|
|
||||||
);
|
|
||||||
|
|
||||||
return Scaffold(
|
|
||||||
backgroundColor: const Color.fromRGBO(248, 248, 248, 1),
|
|
||||||
appBar: AppBar(
|
|
||||||
backgroundColor: Theme.of(context).primaryColor,
|
|
||||||
title: quickText('其他', color: Colors.white),
|
|
||||||
),
|
|
||||||
body: Stack(
|
|
||||||
alignment: const FractionalOffset(0.5, 0.98),
|
|
||||||
children: [
|
|
||||||
ListView(
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
margin: EdgeInsets.symmetric(vertical: 22.h, horizontal: 16.w),
|
|
||||||
padding: EdgeInsets.symmetric(vertical: 22.h, horizontal: 16.w),
|
|
||||||
height: 130.h,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
borderRadius: BorderRadius.all(Radius.circular(6.w)),
|
|
||||||
color: Colors.white,
|
|
||||||
boxShadow: const [
|
|
||||||
BoxShadow(
|
|
||||||
color: Color.fromRGBO(46, 91, 255, 0.1),
|
|
||||||
offset: Offset.zero, //阴影y轴偏移量
|
|
||||||
blurRadius: 20, //阴影模糊程度
|
|
||||||
spreadRadius: 10, //阴影扩散程度
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
InkWell(
|
|
||||||
onTap: () {
|
|
||||||
Get.toNamed(Routes.agreementPage, arguments: {"type": AGREEMENT_KEY.PRIVACY_GREEMENT.name});
|
|
||||||
},
|
|
||||||
child: Container(
|
|
||||||
padding: EdgeInsets.only(bottom: 4.h),
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Text('用户隐私协议', style: personalInfoTitleStly),
|
|
||||||
Icon(
|
|
||||||
Icons.arrow_forward_ios,
|
|
||||||
color: const Color.fromRGBO(80, 87, 103, 1),
|
|
||||||
size: 16.sp,
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Container(
|
|
||||||
height: 1.w,
|
|
||||||
color: const Color.fromRGBO(240, 243, 255, 1),
|
|
||||||
),
|
|
||||||
SizedBox(height: 8.h),
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Text('APP版本', style: personalInfoTitleStly),
|
|
||||||
Obx(() {
|
|
||||||
return quickText(localVersion);
|
|
||||||
})
|
|
||||||
],
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Positioned(
|
|
||||||
bottom: 50.h,
|
|
||||||
child: InkWell(
|
|
||||||
child: Container(
|
|
||||||
height: 46.h,
|
|
||||||
width: ScreenUtil().screenWidth - 60.w,
|
|
||||||
padding: EdgeInsets.symmetric(vertical: 14.h),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
borderRadius: BorderRadius.all(
|
|
||||||
Radius.circular(6.w),
|
|
||||||
),
|
|
||||||
color: Colors.white,
|
|
||||||
boxShadow: [
|
|
||||||
BoxShadow(
|
|
||||||
color: const Color.fromRGBO(46, 91, 255, 0.2),
|
|
||||||
offset: Offset(2.w, 2.h), //阴影y轴偏移量
|
|
||||||
blurRadius: 14, //阴影模糊程度
|
|
||||||
spreadRadius: 0.5, //阴影扩散程度
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Icon(
|
|
||||||
Icons.exit_to_app_outlined,
|
|
||||||
size: 13.sp,
|
|
||||||
color: const Color.fromRGBO(148, 163, 182, 1),
|
|
||||||
),
|
|
||||||
Container(
|
|
||||||
width: 6.w,
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
'账户注销',
|
|
||||||
style: TextStyle(color: const Color.fromRGBO(148, 163, 182, 1), fontSize: 13.sp),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
onTap: () => _showAlertDialog(context),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [Text('APP备案号: ', style: personalInfoTitleStly), quickText('渝ICP备17007225号-4A', size: 14.sp)],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,11 @@ 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/job/user_info_detail.dart';
|
||||||
import 'package:making_school_asignment_app/common/mixins/request_tool_mixin.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/store/user_store.dart';
|
||||||
|
import 'package:making_school_asignment_app/common/utils/app_upgrade/upgradeLogic.dart';
|
||||||
|
import 'package:making_school_asignment_app/common/utils/platform_utils.dart';
|
||||||
import 'package:making_school_asignment_app/common/utils/storage.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/toast_utils.dart';
|
||||||
|
import 'package:making_school_asignment_app/page/global_widget/desktop_scaffold.dart';
|
||||||
import 'package:making_school_asignment_app/page/home_page/children/my_info.dart';
|
import 'package:making_school_asignment_app/page/home_page/children/my_info.dart';
|
||||||
import 'package:making_school_asignment_app/page/home_page/home_view.dart';
|
import 'package:making_school_asignment_app/page/home_page/home_view.dart';
|
||||||
import 'package:making_school_asignment_app/routes/app_pages.dart';
|
import 'package:making_school_asignment_app/routes/app_pages.dart';
|
||||||
|
|
@ -23,8 +26,11 @@ class StartPage extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _StartPageState extends State<StartPage> with RequestToolMixin {
|
class _StartPageState extends State<StartPage> with RequestToolMixin {
|
||||||
|
Timer? _timer;
|
||||||
|
Timer? _timerPermission;
|
||||||
DateTime? lastPopTime;
|
DateTime? lastPopTime;
|
||||||
final _pageController = Get.find<PageIndexController>();
|
final _pageController = Get.find<PageIndexController>();
|
||||||
|
final _upgradeLogic = Get.find<UpgradeLogic>();
|
||||||
|
|
||||||
late final List<Widget> _bodyList;
|
late final List<Widget> _bodyList;
|
||||||
|
|
||||||
|
|
@ -34,12 +40,26 @@ class _StartPageState extends State<StartPage> with RequestToolMixin {
|
||||||
|
|
||||||
// const WorkPage(),
|
// const WorkPage(),
|
||||||
_bodyList = [const HomePage(), const MyInfo()];
|
_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;
|
String? token = UserStore.to.token;
|
||||||
UserInfo? userInfo = UserStore.to.userInfo.value;
|
UserInfo? userInfo = UserStore.to.userInfo.value;
|
||||||
UserInfoDetail? userInfoDetail = UserStore.to.userDetailInfo.value;
|
UserInfoDetail? userInfoDetail = UserStore.to.userDetailInfo.value;
|
||||||
|
|
||||||
if ((token?.isNotEmpty ?? false) && userInfo != null) {
|
if ((token?.isNotEmpty ?? false) && userInfo != null) {
|
||||||
|
// Future.delayed(const Duration(milliseconds: 200)).then((e) {
|
||||||
|
// Get.toNamed(Routes.home);
|
||||||
|
// });
|
||||||
// 更新用户信息
|
// 更新用户信息
|
||||||
if (userInfoDetail == null) UserStore.to.updateUserInfo();
|
if (userInfoDetail == null) UserStore.to.updateUserInfo();
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -54,6 +74,8 @@ class _StartPageState extends State<StartPage> with RequestToolMixin {
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
Get.delete<PageIndexController>();
|
Get.delete<PageIndexController>();
|
||||||
|
_timer?.cancel();
|
||||||
|
_timerPermission?.cancel();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -69,75 +91,75 @@ class _StartPageState extends State<StartPage> with RequestToolMixin {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return PopScope(
|
return PlatformUtils.isDesktop
|
||||||
canPop: false,
|
? const DesktopScaffold()
|
||||||
child: Scaffold(
|
: PopScope(
|
||||||
body: PageView(
|
canPop: false,
|
||||||
controller: _pageController._pageIndexState.pageController,
|
child: Scaffold(
|
||||||
physics: const BouncingScrollPhysics(),
|
body: PageView(
|
||||||
onPageChanged: (index) {
|
controller: _pageController._pageIndexState.pageController,
|
||||||
_pageController._pageIndexState.pageIndex.value = index;
|
physics: const BouncingScrollPhysics(),
|
||||||
// if (index == 2) {
|
onPageChanged: (index) {
|
||||||
// SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle(statusBarIconBrightness: Brightness.light));
|
_pageController._pageIndexState.pageIndex.value = index;
|
||||||
// } else {
|
// if (index == 2) {
|
||||||
// SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle(statusBarIconBrightness: Brightness.dark));
|
// SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle(statusBarIconBrightness: Brightness.light));
|
||||||
// }
|
// } else {
|
||||||
},
|
// SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle(statusBarIconBrightness: Brightness.dark));
|
||||||
children: _bodyList,
|
// }
|
||||||
),
|
},
|
||||||
bottomNavigationBar: Obx(() {
|
children: _bodyList,
|
||||||
return BottomNavigationBar(
|
|
||||||
backgroundColor: Colors.white,
|
|
||||||
items: <BottomNavigationBarItem>[
|
|
||||||
BottomNavigationBarItem(
|
|
||||||
label: '作业',
|
|
||||||
icon: getItemIcon('assets/images/ic_home_normal.png'),
|
|
||||||
activeIcon: getItemIcon('assets/images/ic_home_active.png'),
|
|
||||||
),
|
),
|
||||||
// BottomNavigationBarItem(
|
bottomNavigationBar: Obx(() {
|
||||||
// label: '考试',
|
return BottomNavigationBar(
|
||||||
// icon: getItemIcon('assets/images/ic_work_normal.png'),
|
backgroundColor: Colors.white,
|
||||||
// activeIcon: getItemIcon('assets/images/ic_work_active.png'),
|
items: <BottomNavigationBarItem>[
|
||||||
// ),
|
BottomNavigationBarItem(
|
||||||
BottomNavigationBarItem(
|
label: '作业',
|
||||||
label: '我的',
|
icon: getItemIcon('assets/images/ic_home_normal.png'),
|
||||||
icon: getItemIcon('assets/images/ic_mine_normal.png'),
|
activeIcon: getItemIcon('assets/images/ic_home_active.png'),
|
||||||
activeIcon: getItemIcon('assets/images/ic_mine_active.png'),
|
),
|
||||||
),
|
// BottomNavigationBarItem(
|
||||||
],
|
// label: '考试',
|
||||||
//设置显示的模式
|
// icon: getItemIcon('assets/images/ic_work_normal.png'),
|
||||||
type: BottomNavigationBarType.fixed,
|
// activeIcon: getItemIcon('assets/images/ic_work_active.png'),
|
||||||
fixedColor: Theme.of(context).primaryColor,
|
// ),
|
||||||
unselectedItemColor: Colors.grey,
|
BottomNavigationBarItem(
|
||||||
// unselectedItemColor: const Color.fromRGBO(80, 87, 103, 1),
|
label: '我的',
|
||||||
// backgroundColor: Colors.white,
|
icon: getItemIcon('assets/images/ic_mine_normal.png'),
|
||||||
//设置当前的索引
|
activeIcon: getItemIcon('assets/images/ic_mine_active.png'),
|
||||||
currentIndex: _pageController._pageIndexState.pageIndex.value,
|
),
|
||||||
//tabBottom的点击监听
|
],
|
||||||
onTap: (index) {
|
//设置显示的模式
|
||||||
_pageController._pageIndexState.pageController.jumpToPage(index);
|
type: BottomNavigationBarType.fixed,
|
||||||
|
fixedColor: Theme.of(context).primaryColor,
|
||||||
|
unselectedItemColor: Colors.grey,
|
||||||
|
// unselectedItemColor: const Color.fromRGBO(80, 87, 103, 1),
|
||||||
|
// backgroundColor: Colors.white,
|
||||||
|
//设置当前的索引
|
||||||
|
currentIndex: _pageController._pageIndexState.pageIndex.value,
|
||||||
|
//tabBottom的点击监听
|
||||||
|
onTap: (index) {
|
||||||
|
_pageController._pageIndexState.pageController.jumpToPage(index);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
onPopInvokedWithResult: (bool didPop, dynamic result) async {
|
||||||
|
if (lastPopTime == null || DateTime.now().difference(lastPopTime!) > const Duration(seconds: 1)) {
|
||||||
|
lastPopTime = DateTime.now();
|
||||||
|
ToastUtils.getFluttertoast(
|
||||||
|
context: context,
|
||||||
|
msg: '连续两次返回就退出',
|
||||||
|
gravity: ToastGravity.CENTER,
|
||||||
|
fontSize: 18,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
lastPopTime = DateTime.now();
|
||||||
|
// 退出app
|
||||||
|
SystemNavigator.pop();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}),
|
|
||||||
),
|
|
||||||
onPopInvokedWithResult: (bool didPop, dynamic result) async {
|
|
||||||
if (lastPopTime == null ||
|
|
||||||
DateTime.now().difference(lastPopTime!) >
|
|
||||||
const Duration(seconds: 1)) {
|
|
||||||
lastPopTime = DateTime.now();
|
|
||||||
ToastUtils.getFluttertoast(
|
|
||||||
context: context,
|
|
||||||
msg: '连续两次返回就退出',
|
|
||||||
gravity: ToastGravity.CENTER,
|
|
||||||
fontSize: 18,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
lastPopTime = DateTime.now();
|
|
||||||
// 退出app
|
|
||||||
SystemNavigator.pop();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -159,23 +181,6 @@ class PageIndexController extends GetxController with RequestToolMixin {
|
||||||
_pageIndexState = PageIndexState(pageController: PageController());
|
_pageIndexState = PageIndexState(pageController: PageController());
|
||||||
super.onInit();
|
super.onInit();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// TODO 升级插件 暂时不使用
|
|
||||||
// @override
|
|
||||||
// void onReady() {
|
|
||||||
// AppUpgradeSimple.instance.checkUpdate(
|
|
||||||
// context: Get.context!,
|
|
||||||
// url: 'https://dpc-teacher-api.23544.com/api/infra/AppVersion/Get',
|
|
||||||
// params: {
|
|
||||||
// 'appName': 'making_school_asignment_app',
|
|
||||||
// 'ftuType': 1,
|
|
||||||
// },
|
|
||||||
// showNoUpdateToast: true,
|
|
||||||
// autoDownload: false,
|
|
||||||
// autoInstall: true,
|
|
||||||
// );
|
|
||||||
// super.onReady();
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class StartPageIndexBinding extends Bindings {
|
class StartPageIndexBinding extends Bindings {
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ import 'package:making_school_asignment_app/common/job/annotated_class.dart';
|
||||||
import 'package:making_school_asignment_app/common/utils/utils.dart';
|
import 'package:making_school_asignment_app/common/utils/utils.dart';
|
||||||
import 'package:making_school_asignment_app/page/global_widget/ReturnToHomepage.dart';
|
import 'package:making_school_asignment_app/page/global_widget/ReturnToHomepage.dart';
|
||||||
import 'package:making_school_asignment_app/page/home_page/children/annotate_class/widget/annotate_item.dart';
|
import 'package:making_school_asignment_app/page/home_page/children/annotate_class/widget/annotate_item.dart';
|
||||||
|
|
||||||
import 'annotate_class_logic.dart';
|
import 'annotate_class_logic.dart';
|
||||||
import 'widget/completed_annotate_item.dart';
|
import 'widget/completed_annotate_item.dart';
|
||||||
|
|
||||||
|
|
@ -28,21 +27,23 @@ class _AnnotateClassPageState extends State<AnnotateClassPage> {
|
||||||
String homeworkId = state.homeworkId.value;
|
String homeworkId = state.homeworkId.value;
|
||||||
return AnnotatedRegion(
|
return AnnotatedRegion(
|
||||||
value: const SystemUiOverlayStyle(
|
value: const SystemUiOverlayStyle(
|
||||||
systemNavigationBarColor: Colors.transparent,
|
systemNavigationBarColor:Colors.transparent,
|
||||||
systemNavigationBarDividerColor: null,
|
systemNavigationBarDividerColor: null,
|
||||||
statusBarColor: Colors.transparent,
|
statusBarColor: Colors.transparent,
|
||||||
systemNavigationBarIconBrightness: Brightness.light,
|
systemNavigationBarIconBrightness: Brightness.light,
|
||||||
statusBarIconBrightness: Brightness.light,
|
statusBarIconBrightness: Brightness.light,
|
||||||
statusBarBrightness: Brightness.light,
|
statusBarBrightness: Brightness.light,
|
||||||
),
|
),
|
||||||
child: OrientationBuilder(builder: (BuildContext context, Orientation orientation) {
|
child: OrientationBuilder(
|
||||||
|
builder: (BuildContext context, Orientation orientation) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: const Color.fromRGBO(245, 245, 245, 1),
|
backgroundColor: const Color.fromRGBO(245, 245, 245, 1),
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
backgroundColor: Colors.white,
|
backgroundColor: Colors.white,
|
||||||
elevation: 0.3,
|
|
||||||
title: Obx(() {
|
title: Obx(() {
|
||||||
return Text(state.name.value, style: TextStyle(fontSize: 14.sp, color: const Color(0xFF333333)));
|
return Text(state.name.value,
|
||||||
|
style:
|
||||||
|
TextStyle(fontSize: 14.sp, color: const Color(0xFF333333)));
|
||||||
}),
|
}),
|
||||||
centerTitle: true,
|
centerTitle: true,
|
||||||
leading: IconButton(
|
leading: IconButton(
|
||||||
|
|
@ -70,12 +71,13 @@ class _AnnotateClassPageState extends State<AnnotateClassPage> {
|
||||||
child: state.completed.value
|
child: state.completed.value
|
||||||
? Utils.isPad()
|
? Utils.isPad()
|
||||||
? GridView(
|
? GridView(
|
||||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
gridDelegate:
|
||||||
crossAxisCount: 2, //横轴三个子widget
|
SliverGridDelegateWithFixedCrossAxisCount(
|
||||||
mainAxisSpacing: 10.h,
|
crossAxisCount: 2, //横轴三个子widget
|
||||||
crossAxisSpacing: 6.w,
|
mainAxisSpacing: 10.h,
|
||||||
childAspectRatio: 1.48 //宽高比为1时,子widget
|
crossAxisSpacing: 6.w,
|
||||||
),
|
childAspectRatio: 1.48 //宽高比为1时,子widget
|
||||||
|
),
|
||||||
children: state.classList.map((taskItem) {
|
children: state.classList.map((taskItem) {
|
||||||
return CompletedAnnotateItem(
|
return CompletedAnnotateItem(
|
||||||
taskItem: taskItem,
|
taskItem: taskItem,
|
||||||
|
|
|
||||||
|
|
@ -94,7 +94,7 @@ class _AnnotateItemState extends State<AnnotateItem> {
|
||||||
context: context,
|
context: context,
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
return ShowStudentList(
|
return ShowStudentList(
|
||||||
title: '${widget.item.className} ${submitted ? '已提交' : '未提交'}作业学生',
|
title: '${widget.item.className ?? ''} ${submitted ? '已提交' : '未提交'}作业学生',
|
||||||
studentList: students,
|
studentList: students,
|
||||||
homeworkId: widget.homeworkId,
|
homeworkId: widget.homeworkId,
|
||||||
subject: widget.logic.state.subject,
|
subject: widget.logic.state.subject,
|
||||||
|
|
@ -133,7 +133,7 @@ class _AnnotateItemState extends State<AnnotateItem> {
|
||||||
},
|
},
|
||||||
child: Text(
|
child: Text(
|
||||||
'已交:${widget.item.commitStudentCount}',
|
'已交:${widget.item.commitStudentCount}',
|
||||||
style: TextStyle(fontSize: widget.font - 2.sp, color: Theme.of(context).primaryColor),
|
style: TextStyle(fontSize: widget.font - 2.sp, color: const Color(0xFF4CC793)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
|
|
@ -256,7 +256,12 @@ class _AnnotateItemState extends State<AnnotateItem> {
|
||||||
linearGradient: LinearGradient(
|
linearGradient: LinearGradient(
|
||||||
tileMode: TileMode.mirror,
|
tileMode: TileMode.mirror,
|
||||||
stops: const [0.0, 1.0],
|
stops: const [0.0, 1.0],
|
||||||
colors: [Theme.of(context).primaryColor.withOpacity(0.1), Theme.of(context).primaryColor],
|
colors: widget.item.annotateRate / 100 != 1
|
||||||
|
? [Theme.of(context).primaryColor.withOpacity(0.1), Theme.of(context).primaryColor]
|
||||||
|
: [
|
||||||
|
const Color.fromRGBO(144, 224, 190, 1).withOpacity(0.1),
|
||||||
|
const Color.fromRGBO(144, 224, 190, 1),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
// linearStrokeCap: LinearStrokeCap.butt,
|
// linearStrokeCap: LinearStrokeCap.butt,
|
||||||
// progressColor: Theme.of(context).primaryColor,
|
// progressColor: Theme.of(context).primaryColor,
|
||||||
|
|
@ -266,21 +271,21 @@ class _AnnotateItemState extends State<AnnotateItem> {
|
||||||
),
|
),
|
||||||
ProgressBar(
|
ProgressBar(
|
||||||
title: '客观题正确率:',
|
title: '客观题正确率:',
|
||||||
color: Theme.of(context).primaryColor,
|
color: const Color(0xFFADDCA5),
|
||||||
percent: widget.item.kgtCorrectRate / 100,
|
percent: widget.item.kgtCorrectRate / 100,
|
||||||
marginEdg: EdgeInsets.zero,
|
marginEdg: EdgeInsets.zero,
|
||||||
padingEdg: EdgeInsets.only(top: 8.h, left: 14.r, right: 14.r),
|
padingEdg: EdgeInsets.only(top: 8.h, left: 14.r, right: 14.r),
|
||||||
fontSize: widget.font - 2.sp),
|
fontSize: widget.font - 2.sp),
|
||||||
ProgressBar(
|
ProgressBar(
|
||||||
title: '主观题正确率:',
|
title: '主观题正确率:',
|
||||||
color: Theme.of(context).primaryColor,
|
color: const Color(0xFFADDCA5),
|
||||||
percent: widget.item.zgtCorrectRate / 100,
|
percent: widget.item.zgtCorrectRate / 100,
|
||||||
padingEdg: EdgeInsets.symmetric(horizontal: 10.r),
|
padingEdg: EdgeInsets.symmetric(horizontal: 10.r),
|
||||||
marginEdg: EdgeInsets.only(top: 8.h),
|
marginEdg: EdgeInsets.only(top: 8.h),
|
||||||
fontSize: widget.font - 2.sp),
|
fontSize: widget.font - 2.sp),
|
||||||
ProgressBar(
|
ProgressBar(
|
||||||
title: '总正确率:',
|
title: '总正确率:',
|
||||||
color: Theme.of(context).primaryColor,
|
color: const Color(0xFFADDCA5),
|
||||||
percent: widget.item.correctRate / 100,
|
percent: widget.item.correctRate / 100,
|
||||||
padingEdg: EdgeInsets.symmetric(horizontal: 10.r),
|
padingEdg: EdgeInsets.symmetric(horizontal: 10.r),
|
||||||
marginEdg: EdgeInsets.only(top: 8.h),
|
marginEdg: EdgeInsets.only(top: 8.h),
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,183 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_easyloading/flutter_easyloading.dart';
|
||||||
|
import 'package:flutter_easyrefresh/easy_refresh.dart';
|
||||||
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:making_school_asignment_app/common/job/student_item.dart';
|
||||||
|
import 'package:making_school_asignment_app/page/global_widget/MyEmptyWidget.dart';
|
||||||
|
import 'package:making_school_asignment_app/routes/app_pages.dart';
|
||||||
|
|
||||||
|
import 'class_student_logic.dart';
|
||||||
|
|
||||||
|
class ClassStudentDesktopPage extends StatefulWidget {
|
||||||
|
const ClassStudentDesktopPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ClassStudentDesktopPage> createState() => _ClassStudentDesktopPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ClassStudentDesktopPageState extends State<ClassStudentDesktopPage> {
|
||||||
|
final logic = Get.find<ClassStudentLogic>();
|
||||||
|
final state = Get.find<ClassStudentLogic>().state;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: const Color(0xFFF0F2F5),
|
||||||
|
body: Column(
|
||||||
|
children: [
|
||||||
|
_buildHeader(),
|
||||||
|
Expanded(
|
||||||
|
child: Container(
|
||||||
|
padding: EdgeInsets.all(24.r),
|
||||||
|
child: Card(
|
||||||
|
elevation: 2,
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12.r)),
|
||||||
|
child: Obx(() {
|
||||||
|
return EasyRefresh(
|
||||||
|
controller: logic.refreshController,
|
||||||
|
header: MaterialHeader(),
|
||||||
|
onRefresh: () async {
|
||||||
|
logic.getList();
|
||||||
|
},
|
||||||
|
child: state.studentList.isNotEmpty ? _buildStudentGrid() : const MyEmptyWidget(),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildHeader() {
|
||||||
|
return Container(
|
||||||
|
padding: EdgeInsets.all(24.r),
|
||||||
|
color: Colors.white,
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Obx(() => Text(
|
||||||
|
state.title.value,
|
||||||
|
style: TextStyle(fontSize: 24.sp, fontWeight: FontWeight.bold, color: const Color(0xFF333333)),
|
||||||
|
)),
|
||||||
|
IconButton(
|
||||||
|
onPressed: () => Get.back(),
|
||||||
|
icon: Icon(Icons.close, size: 24.sp, color: Colors.grey[600]),
|
||||||
|
tooltip: '关闭',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildStudentGrid() {
|
||||||
|
return GridView.builder(
|
||||||
|
padding: EdgeInsets.all(16.r),
|
||||||
|
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||||
|
crossAxisCount: 3,
|
||||||
|
crossAxisSpacing: 16.r,
|
||||||
|
mainAxisSpacing: 16.r,
|
||||||
|
childAspectRatio: 2.5,
|
||||||
|
),
|
||||||
|
itemCount: state.studentList.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
StudentItem item = state.studentList[index];
|
||||||
|
return _buildStudentCard(item);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildStudentCard(StudentItem item) {
|
||||||
|
return Card(
|
||||||
|
elevation: 2,
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8.r)),
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () {
|
||||||
|
Get.toNamed(Routes.studentWorkDetailPage, arguments: {
|
||||||
|
'studentName': item.name,
|
||||||
|
'studentId': item.id,
|
||||||
|
'subject': state.subject,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
borderRadius: BorderRadius.circular(8.r),
|
||||||
|
child: Container(
|
||||||
|
padding: EdgeInsets.all(16.r),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
item.name,
|
||||||
|
style: TextStyle(fontSize: 14.sp, color: const Color(0xFF333333), fontWeight: FontWeight.w500),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
_buildActionButton(item),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildActionButton(StudentItem item) {
|
||||||
|
if (state.page == 'answerTrajectory') {
|
||||||
|
return Container(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 6.h),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(color: const Color(0xFFB2DA93)),
|
||||||
|
borderRadius: BorderRadius.circular(16.r),
|
||||||
|
),
|
||||||
|
child: Text('详情', style: TextStyle(fontSize: 12.sp, color: const Color(0xFFB2DA93))),
|
||||||
|
);
|
||||||
|
} else if (state.page == 'history') {
|
||||||
|
return Container(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 6.h),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context).primaryColor,
|
||||||
|
borderRadius: BorderRadius.circular(16.r),
|
||||||
|
),
|
||||||
|
child: Text('历史作业', style: TextStyle(fontSize: 12.sp, color: Colors.white)),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return InkWell(
|
||||||
|
onTap: () {
|
||||||
|
logic.setJobReadLevel(item.id, !item.priorityAnnotate);
|
||||||
|
EasyLoading.show(status: 'loading...');
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 6.h),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: item.priorityAnnotate ? const Color(0xFFEBE4FF) : const Color(0xFFE1E1E1),
|
||||||
|
borderRadius: BorderRadius.circular(16.r),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
item.priorityAnnotate ? Icons.star : Icons.star_border,
|
||||||
|
size: 14.r,
|
||||||
|
color: item.priorityAnnotate ? Theme.of(context).primaryColor : Colors.grey,
|
||||||
|
),
|
||||||
|
SizedBox(width: 4.w),
|
||||||
|
Text(
|
||||||
|
'优先批阅',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12.sp,
|
||||||
|
color: item.priorityAnnotate ? Theme.of(context).primaryColor : Colors.grey,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
Get.delete<ClassStudentLogic>();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,341 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_easyloading/flutter_easyloading.dart';
|
||||||
|
import 'package:flutter_easyrefresh/easy_refresh.dart';
|
||||||
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:making_school_asignment_app/common/job/student_item.dart';
|
||||||
|
import 'package:making_school_asignment_app/common/utils/utils.dart';
|
||||||
|
import 'package:making_school_asignment_app/page/global_widget/MyEmptyWidget.dart';
|
||||||
|
import 'package:making_school_asignment_app/page/global_widget/ReturnToHomepage.dart';
|
||||||
|
import 'package:making_school_asignment_app/routes/app_pages.dart';
|
||||||
|
|
||||||
|
import 'class_student_logic.dart';
|
||||||
|
|
||||||
|
class ClassStudentMobilePage extends StatefulWidget {
|
||||||
|
const ClassStudentMobilePage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ClassStudentMobilePage> createState() => _ClassStudentMobilePageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ClassStudentMobilePageState extends State<ClassStudentMobilePage> {
|
||||||
|
final logic = Get.find<ClassStudentLogic>();
|
||||||
|
final state = Get.find<ClassStudentLogic>().state;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return OrientationBuilder(builder: (BuildContext context, Orientation orientation) {
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: const Color.fromRGBO(245, 245, 245, 1),
|
||||||
|
appBar: AppBar(
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
title: Obx(() {
|
||||||
|
return Text(
|
||||||
|
state.title.value,
|
||||||
|
style: TextStyle(fontSize: 14.sp, color: const Color(0xFF333333)),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
centerTitle: true,
|
||||||
|
leading: IconButton(
|
||||||
|
icon: const Icon(Icons.arrow_back_ios, color: Colors.black),
|
||||||
|
onPressed: () => Get.back(),
|
||||||
|
),
|
||||||
|
actions: const [
|
||||||
|
ReturnToHomepage(),
|
||||||
|
],
|
||||||
|
elevation: 0,
|
||||||
|
),
|
||||||
|
body: Obx(() {
|
||||||
|
return EasyRefresh(
|
||||||
|
firstRefresh: false,
|
||||||
|
taskIndependence: true,
|
||||||
|
controller: logic.refreshController,
|
||||||
|
header: MaterialHeader(),
|
||||||
|
footer: TaurusFooter(),
|
||||||
|
onRefresh: () async {
|
||||||
|
logic.getList();
|
||||||
|
},
|
||||||
|
child: state.studentList.isNotEmpty
|
||||||
|
? Utils.isPad()
|
||||||
|
? GridView(
|
||||||
|
shrinkWrap: true,
|
||||||
|
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||||
|
crossAxisCount: 2,
|
||||||
|
mainAxisSpacing: 0.r,
|
||||||
|
crossAxisSpacing: 0.r,
|
||||||
|
childAspectRatio: 556 / 90,
|
||||||
|
),
|
||||||
|
children: List.generate(state.studentList.length, (index) {
|
||||||
|
StudentItem item = state.studentList[index];
|
||||||
|
return InkWell(
|
||||||
|
onTap: () {
|
||||||
|
Get.toNamed(Routes.studentWorkDetailPage, arguments: {
|
||||||
|
'studentName': item.name,
|
||||||
|
'studentId': item.id,
|
||||||
|
'subject': state.subject,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 10.r),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(0.r)),
|
||||||
|
color: Colors.transparent,
|
||||||
|
border: Border(
|
||||||
|
left: BorderSide(
|
||||||
|
width: (index + 1) % 2 == 0 ? 1.r : 0, color: const Color(0xFFA5A5A5)),
|
||||||
|
bottom: BorderSide(width: 1.r, color: const Color(0xFFA5A5A5)))),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
item.name,
|
||||||
|
style: TextStyle(fontSize: 12.sp, color: Theme.of(context).primaryColor),
|
||||||
|
)),
|
||||||
|
state.page == 'answerTrajectory'
|
||||||
|
? Container(
|
||||||
|
height: 20.r,
|
||||||
|
width: 70.r,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(width: 1.r, color: const Color(0xFFB2DA93)),
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(20.r)),
|
||||||
|
),
|
||||||
|
child: Center(
|
||||||
|
child: Text('详情',
|
||||||
|
style: TextStyle(fontSize: 10.r, color: const Color(0xFFB2DA93))),
|
||||||
|
))
|
||||||
|
: state.page == 'history'
|
||||||
|
? Container(
|
||||||
|
height: 20.r,
|
||||||
|
width: 70.r,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context).primaryColor,
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(20.r))),
|
||||||
|
child: Center(
|
||||||
|
child: Text(
|
||||||
|
'历史作业',
|
||||||
|
style: TextStyle(fontSize: 10.r, color: Colors.white),
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
: item.priorityAnnotate
|
||||||
|
? InkWell(
|
||||||
|
onTap: () {
|
||||||
|
logic.setJobReadLevel(item.id, false);
|
||||||
|
EasyLoading.show(status: 'loading...');
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
height: 20.r,
|
||||||
|
width: 80.r,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(4.r)),
|
||||||
|
color: const Color(0xFFEBE4FF),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.only(left: 3.r),
|
||||||
|
child: Image.asset(
|
||||||
|
'assets/images/youx_icon_active.png',
|
||||||
|
width: 14.r,
|
||||||
|
height: 14.r,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.only(top: 2.r, left: 4.r),
|
||||||
|
child: Text(
|
||||||
|
'优先批阅',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 10.sp, color: Theme.of(context).primaryColor),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: InkWell(
|
||||||
|
onTap: () {
|
||||||
|
logic.setJobReadLevel(item.id, true);
|
||||||
|
EasyLoading.show(status: 'loading...');
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
height: 20.r,
|
||||||
|
width: 80.r,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(4.r)),
|
||||||
|
color: const Color(0xFFE1E1E1),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.only(left: 3.r),
|
||||||
|
child: Image.asset(
|
||||||
|
'assets/images/youx_icon_default.png',
|
||||||
|
width: 14.r,
|
||||||
|
height: 14.r,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.only(top: 2.r, left: 4.r),
|
||||||
|
child: Text(
|
||||||
|
'优先批阅',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 10.sp, color: const Color(0xFF8A9691)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
: Padding(
|
||||||
|
padding: EdgeInsets.symmetric(vertical: 14.r, horizontal: 14.r),
|
||||||
|
child: ListView.builder(
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
StudentItem item = state.studentList[index];
|
||||||
|
return InkWell(
|
||||||
|
onTap: () {
|
||||||
|
Get.toNamed(Routes.studentWorkDetailPage, arguments: {
|
||||||
|
'studentName': item.name,
|
||||||
|
'studentId': item.id,
|
||||||
|
'subject': state.subject,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
padding: EdgeInsets.symmetric(vertical: 20.r, horizontal: 15.r),
|
||||||
|
margin: EdgeInsets.only(bottom: 15.r),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(10.r)),
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
item.name,
|
||||||
|
style: TextStyle(fontSize: 12.sp, color: const Color(0xFF4CC793)),
|
||||||
|
)),
|
||||||
|
state.page == 'answerTrajectory'
|
||||||
|
? Container(
|
||||||
|
height: 24.r,
|
||||||
|
width: 72.r,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(width: 1.r, color: const Color(0xFFB2DA93)),
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(20.r)),
|
||||||
|
),
|
||||||
|
child: Center(
|
||||||
|
child: Text('详情',
|
||||||
|
style: TextStyle(fontSize: 10.r, color: const Color(0xFFB2DA93))),
|
||||||
|
))
|
||||||
|
: state.page == 'history'
|
||||||
|
? Container(
|
||||||
|
height: 24.r,
|
||||||
|
width: 82.r,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: const Color(0xFF4CC793),
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(20.r))),
|
||||||
|
child: Center(
|
||||||
|
child: Text(
|
||||||
|
'历史作业',
|
||||||
|
style: TextStyle(fontSize: 10.r, color: Colors.white),
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
: item.priorityAnnotate
|
||||||
|
? InkWell(
|
||||||
|
onTap: () {
|
||||||
|
logic.setJobReadLevel(item.id, false);
|
||||||
|
EasyLoading.show(status: 'loading...');
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
height: 24.r,
|
||||||
|
width: 82.r,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(4.r)),
|
||||||
|
color: const Color(0xFFB7FFE0),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.only(left: 3.r),
|
||||||
|
child: Image.asset(
|
||||||
|
'assets/images/youx_icon_active.png',
|
||||||
|
width: 14.r,
|
||||||
|
height: 14.r,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.only(top: 5.r, left: 4.r),
|
||||||
|
child: Text(
|
||||||
|
'优先批阅',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 10.sp, color: const Color(0xFF4CC793)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: InkWell(
|
||||||
|
onTap: () {
|
||||||
|
logic.setJobReadLevel(item.id, true);
|
||||||
|
EasyLoading.show(status: 'loading...');
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
height: 24.r,
|
||||||
|
width: 82.r,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(4.r)),
|
||||||
|
color: const Color(0xFFE1E1E1),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.only(left: 3.r),
|
||||||
|
child: Image.asset(
|
||||||
|
'assets/images/youx_icon_default.png',
|
||||||
|
width: 14.r,
|
||||||
|
height: 14.r,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.only(top: 5.r, left: 4.r),
|
||||||
|
child: Text(
|
||||||
|
'优先批阅',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 10.sp, color: const Color(0xFF8A9691)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
itemCount: state.studentList.length,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: const MyEmptyWidget(),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
Get.delete<ClassStudentLogic>();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,348 +1,13 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_easyloading/flutter_easyloading.dart';
|
import 'package:making_school_asignment_app/common/utils/platform_utils.dart';
|
||||||
import 'package:flutter_easyrefresh/easy_refresh.dart';
|
import 'package:making_school_asignment_app/page/home_page/children/class_student/class_student_desktop_view.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:making_school_asignment_app/page/home_page/children/class_student/class_student_mobile_view.dart';
|
||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:making_school_asignment_app/common/job/student_item.dart';
|
|
||||||
import 'package:making_school_asignment_app/common/utils/utils.dart';
|
|
||||||
import 'package:making_school_asignment_app/page/global_widget/MyEmptyWidget.dart';
|
|
||||||
import 'package:making_school_asignment_app/page/global_widget/ReturnToHomepage.dart';
|
|
||||||
import 'package:making_school_asignment_app/routes/app_pages.dart';
|
|
||||||
|
|
||||||
import 'class_student_logic.dart';
|
class ClassStudentPage extends StatelessWidget {
|
||||||
|
|
||||||
class ClassStudentPage extends StatefulWidget {
|
|
||||||
const ClassStudentPage({super.key});
|
const ClassStudentPage({super.key});
|
||||||
|
|
||||||
@override
|
|
||||||
State<ClassStudentPage> createState() => _ClassStudentPageState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _ClassStudentPageState extends State<ClassStudentPage> {
|
|
||||||
final logic = Get.find<ClassStudentLogic>();
|
|
||||||
final state = Get.find<ClassStudentLogic>().state;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return OrientationBuilder(builder: (BuildContext context, Orientation orientation) {
|
return PlatformUtils.isDesktop ? const ClassStudentDesktopPage() : const ClassStudentMobilePage();
|
||||||
return Scaffold(
|
|
||||||
backgroundColor: const Color.fromRGBO(245, 245, 245, 1),
|
|
||||||
appBar: AppBar(
|
|
||||||
backgroundColor: Colors.white,
|
|
||||||
title: Obx(() {
|
|
||||||
return Text(
|
|
||||||
state.title.value,
|
|
||||||
style: TextStyle(fontSize: 14.sp, color: const Color(0xFF333333)),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
centerTitle: true,
|
|
||||||
leading: IconButton(
|
|
||||||
icon: const Icon(Icons.arrow_back_ios, color: Colors.black),
|
|
||||||
onPressed: () => Get.back(),
|
|
||||||
),
|
|
||||||
actions: const [
|
|
||||||
ReturnToHomepage(),
|
|
||||||
],
|
|
||||||
elevation: 0,
|
|
||||||
),
|
|
||||||
body: Obx(() {
|
|
||||||
return EasyRefresh(
|
|
||||||
firstRefresh: false,
|
|
||||||
taskIndependence: true,
|
|
||||||
controller: logic.refreshController,
|
|
||||||
header: MaterialHeader(),
|
|
||||||
footer: TaurusFooter(),
|
|
||||||
onRefresh: () async {
|
|
||||||
logic.getList();
|
|
||||||
},
|
|
||||||
child: state.studentList.isNotEmpty
|
|
||||||
? Utils.isPad()
|
|
||||||
? GridView(
|
|
||||||
shrinkWrap: true,
|
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
|
||||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
|
||||||
crossAxisCount: 2,
|
|
||||||
mainAxisSpacing: 0.r,
|
|
||||||
crossAxisSpacing: 0.r,
|
|
||||||
childAspectRatio: 556 / 90,
|
|
||||||
),
|
|
||||||
children: List.generate(state.studentList.length, (index) {
|
|
||||||
StudentItem item = state.studentList[index];
|
|
||||||
return InkWell(
|
|
||||||
onTap: () {
|
|
||||||
// RouterManager.router.navigateTo(context,
|
|
||||||
// '${RouterManager.jobPersonalDetailPath}?studentId=${item.studentId}&studentName=${Uri.encodeComponent(item.studentName)}');
|
|
||||||
Get.toNamed(Routes.studentWorkDetailPage, arguments: {
|
|
||||||
'studentName': item.name,
|
|
||||||
'studentId': item.id,
|
|
||||||
'subject': state.subject,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
child: Container(
|
|
||||||
padding: EdgeInsets.symmetric(horizontal: 10.r),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
borderRadius: BorderRadius.all(Radius.circular(0.r)),
|
|
||||||
color: Colors.transparent,
|
|
||||||
border: Border(
|
|
||||||
left: BorderSide(
|
|
||||||
width: (index + 1) % 2 == 0 ? 1.r : 0, color: const Color(0xFFA5A5A5)),
|
|
||||||
bottom: BorderSide(width: 1.r, color: const Color(0xFFA5A5A5)))),
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: Text(
|
|
||||||
item.name,
|
|
||||||
style: TextStyle(fontSize: 12.sp, color: Theme.of(context).primaryColor),
|
|
||||||
)),
|
|
||||||
state.page == 'answerTrajectory'
|
|
||||||
? Container(
|
|
||||||
height: 20.r,
|
|
||||||
width: 70.r,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
border: Border.all(width: 1.r, color: const Color(0xFFB2DA93)),
|
|
||||||
borderRadius: BorderRadius.all(Radius.circular(20.r)),
|
|
||||||
),
|
|
||||||
child: Center(
|
|
||||||
child: Text('详情',
|
|
||||||
style: TextStyle(fontSize: 10.r, color: const Color(0xFFB2DA93))),
|
|
||||||
))
|
|
||||||
: state.page == 'history'
|
|
||||||
? Container(
|
|
||||||
height: 20.r,
|
|
||||||
width: 70.r,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Theme.of(context).primaryColor,
|
|
||||||
borderRadius: BorderRadius.all(Radius.circular(20.r))),
|
|
||||||
child: Center(
|
|
||||||
child: Text(
|
|
||||||
'历史作业',
|
|
||||||
style: TextStyle(fontSize: 10.r, color: Colors.white),
|
|
||||||
)),
|
|
||||||
)
|
|
||||||
: item.priorityAnnotate
|
|
||||||
? InkWell(
|
|
||||||
onTap: () {
|
|
||||||
logic.setJobReadLevel(item.id, false);
|
|
||||||
EasyLoading.show(status: 'loading...');
|
|
||||||
},
|
|
||||||
child: Container(
|
|
||||||
height: 20.r,
|
|
||||||
width: 80.r,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
borderRadius: BorderRadius.all(Radius.circular(4.r)),
|
|
||||||
color: const Color(0xFFEBE4FF),
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Padding(
|
|
||||||
padding: EdgeInsets.only(left: 3.r),
|
|
||||||
child: Image.asset(
|
|
||||||
'assets/images/youx_icon_active.png',
|
|
||||||
width: 14.r,
|
|
||||||
height: 14.r,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: EdgeInsets.only(top: 2.r, left: 4.r),
|
|
||||||
child: Text(
|
|
||||||
'优先批阅',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 10.sp, color: Theme.of(context).primaryColor),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: InkWell(
|
|
||||||
onTap: () {
|
|
||||||
logic.setJobReadLevel(item.id, true);
|
|
||||||
EasyLoading.show(status: 'loading...');
|
|
||||||
},
|
|
||||||
child: Container(
|
|
||||||
height: 20.r,
|
|
||||||
width: 80.r,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
borderRadius: BorderRadius.all(Radius.circular(4.r)),
|
|
||||||
color: const Color(0xFFE1E1E1),
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Padding(
|
|
||||||
padding: EdgeInsets.only(left: 3.r),
|
|
||||||
child: Image.asset(
|
|
||||||
'assets/images/youx_icon_default.png',
|
|
||||||
width: 14.r,
|
|
||||||
height: 14.r,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: EdgeInsets.only(top: 2.r, left: 4.r),
|
|
||||||
child: Text(
|
|
||||||
'优先批阅',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 10.sp, color: const Color(0xFF8A9691)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
: Padding(
|
|
||||||
padding: EdgeInsets.symmetric(vertical: 14.r, horizontal: 14.r),
|
|
||||||
child: ListView.builder(
|
|
||||||
shrinkWrap: true,
|
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
|
||||||
itemCount: state.studentList.length,
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
StudentItem item = state.studentList[index];
|
|
||||||
return InkWell(
|
|
||||||
onTap: () {
|
|
||||||
// RouterManager.router.navigateTo(context,
|
|
||||||
// '${RouterManager.jobPersonalDetailPath}?studentId=${item.studentId}&studentName=${Uri.encodeComponent(item.studentName)}');
|
|
||||||
Get.toNamed(Routes.studentWorkDetailPage, arguments: {
|
|
||||||
'studentName': item.name,
|
|
||||||
'studentId': item.id,
|
|
||||||
'subject': state.subject,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
child: Container(
|
|
||||||
padding: EdgeInsets.symmetric(vertical: 20.r, horizontal: 15.r),
|
|
||||||
margin: EdgeInsets.only(bottom: 15.r),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
borderRadius: BorderRadius.all(Radius.circular(10.r)),
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: Text(
|
|
||||||
item.name,
|
|
||||||
style: TextStyle(fontSize: 12.sp, color: const Color(0xFF4CC793)),
|
|
||||||
)),
|
|
||||||
state.page == 'answerTrajectory'
|
|
||||||
? Container(
|
|
||||||
height: 24.r,
|
|
||||||
width: 72.r,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
border: Border.all(width: 1.r, color: const Color(0xFFB2DA93)),
|
|
||||||
borderRadius: BorderRadius.all(Radius.circular(20.r)),
|
|
||||||
),
|
|
||||||
child: Center(
|
|
||||||
child: Text('详情',
|
|
||||||
style: TextStyle(fontSize: 10.r, color: const Color(0xFFB2DA93))),
|
|
||||||
))
|
|
||||||
: state.page == 'history'
|
|
||||||
? Container(
|
|
||||||
height: 24.r,
|
|
||||||
width: 82.r,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Theme.of(context).primaryColor,
|
|
||||||
borderRadius: BorderRadius.all(Radius.circular(20.r))),
|
|
||||||
child: Center(
|
|
||||||
child: Text(
|
|
||||||
'历史作业',
|
|
||||||
style: TextStyle(fontSize: 10.r, color: Colors.white),
|
|
||||||
)),
|
|
||||||
)
|
|
||||||
: item.priorityAnnotate
|
|
||||||
? InkWell(
|
|
||||||
onTap: () {
|
|
||||||
logic.setJobReadLevel(item.id, false);
|
|
||||||
EasyLoading.show(status: 'loading...');
|
|
||||||
},
|
|
||||||
child: Container(
|
|
||||||
height: 24.r,
|
|
||||||
width: 82.r,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
borderRadius: BorderRadius.all(Radius.circular(4.r)),
|
|
||||||
color: const Color(0xFFB7FFE0),
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Padding(
|
|
||||||
padding: EdgeInsets.only(left: 3.r),
|
|
||||||
child: Image.asset(
|
|
||||||
'assets/images/youx_icon_active.png',
|
|
||||||
width: 14.r,
|
|
||||||
height: 14.r,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: EdgeInsets.only(top: 5.r, left: 4.r),
|
|
||||||
child: Text(
|
|
||||||
'优先批阅',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 10.sp, color: const Color(0xFF4CC793)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: InkWell(
|
|
||||||
onTap: () {
|
|
||||||
logic.setJobReadLevel(item.id, true);
|
|
||||||
EasyLoading.show(status: 'loading...');
|
|
||||||
},
|
|
||||||
child: Container(
|
|
||||||
height: 24.r,
|
|
||||||
width: 82.r,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
borderRadius: BorderRadius.all(Radius.circular(4.r)),
|
|
||||||
color: const Color(0xFFE1E1E1),
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Padding(
|
|
||||||
padding: EdgeInsets.only(left: 3.r),
|
|
||||||
child: Image.asset(
|
|
||||||
'assets/images/youx_icon_default.png',
|
|
||||||
width: 14.r,
|
|
||||||
height: 14.r,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: EdgeInsets.only(top: 5.r, left: 4.r),
|
|
||||||
child: Text(
|
|
||||||
'优先批阅',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 10.sp, color: const Color(0xFF8A9691)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: const MyEmptyWidget(),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
Get.delete<ClassStudentLogic>();
|
|
||||||
super.dispose();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,16 +27,11 @@ class _FavStudentPageState extends State<FavStudentPage> {
|
||||||
context: context,
|
context: context,
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
insetPadding: EdgeInsets.all(25.r),
|
insetPadding: EdgeInsets.all(25.r),
|
||||||
content: FavoriteStudentDialog(
|
content: FavoriteStudentDialog(
|
||||||
item: item,
|
item: item, group: groups, deleteFav: logic.getDelete, confirmDialog: confirmDialog),
|
||||||
group: groups,
|
contentPadding: const EdgeInsets.all(0),
|
||||||
deleteFav: logic.getDelete,
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(15.r))));
|
||||||
confirmDialog: confirmDialog,
|
|
||||||
),
|
|
||||||
contentPadding: const EdgeInsets.all(0),
|
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(15.r))),
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
import 'package:get/get_rx/get_rx.dart';
|
||||||
import 'package:making_school_asignment_app/common/config/request_config.dart';
|
import 'package:making_school_asignment_app/common/config/request_config.dart';
|
||||||
import 'package:making_school_asignment_app/common/job/annotated_class.dart';
|
import 'package:making_school_asignment_app/common/job/annotated_class.dart';
|
||||||
import 'package:making_school_asignment_app/page/global_widget/MyEmptyWidget.dart';
|
import 'package:making_school_asignment_app/page/global_widget/MyEmptyWidget.dart';
|
||||||
|
|
@ -13,8 +15,12 @@ class FavoriteStudentDialog extends StatefulWidget {
|
||||||
final Function deleteFav;
|
final Function deleteFav;
|
||||||
final Future<bool> Function() confirmDialog;
|
final Future<bool> Function() confirmDialog;
|
||||||
|
|
||||||
const FavoriteStudentDialog(
|
const FavoriteStudentDialog({Key? key,
|
||||||
{super.key, required this.item, required this.group, required this.deleteFav, required this.confirmDialog});
|
required this.item,
|
||||||
|
required this.group,
|
||||||
|
required this.deleteFav,
|
||||||
|
required this.confirmDialog})
|
||||||
|
: super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<FavoriteStudentDialog> createState() => _FavoriteStudentDialogState();
|
State<FavoriteStudentDialog> createState() => _FavoriteStudentDialogState();
|
||||||
|
|
@ -24,7 +30,7 @@ class _FavoriteStudentDialogState extends State<FavoriteStudentDialog> {
|
||||||
RxInt defaultIndex = 0.obs;
|
RxInt defaultIndex = 0.obs;
|
||||||
RxList<HomeworkFavs> imageList = RxList();
|
RxList<HomeworkFavs> imageList = RxList();
|
||||||
late PageController pageController;
|
late PageController pageController;
|
||||||
late Rx<HomeworkFavs> currentStudent = Rx(HomeworkFavs('', '', '', -1, -1, '', '', -1, '', -1, '-1', '', ''));
|
late Rx<HomeworkFavs> currentStudent = Rx(HomeworkFavs('','','',-1,-1,'','',-1,'',-1,'-1','',''));
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
|
@ -37,7 +43,8 @@ class _FavoriteStudentDialogState extends State<FavoriteStudentDialog> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
imageList.value = list;
|
imageList.value = list;
|
||||||
defaultIndex.value = list.indexWhere((element) => element.id == widget.item.id);
|
defaultIndex.value =
|
||||||
|
list.indexWhere((element) => element.id == widget.item.id);
|
||||||
|
|
||||||
pageController = PageController(initialPage: defaultIndex.value);
|
pageController = PageController(initialPage: defaultIndex.value);
|
||||||
}
|
}
|
||||||
|
|
@ -51,8 +58,14 @@ class _FavoriteStudentDialogState extends State<FavoriteStudentDialog> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Container(
|
return Container(
|
||||||
width: MediaQuery.of(context).size.width,
|
width: MediaQuery
|
||||||
height: MediaQuery.of(context).size.height,
|
.of(context)
|
||||||
|
.size
|
||||||
|
.width,
|
||||||
|
height: MediaQuery
|
||||||
|
.of(context)
|
||||||
|
.size
|
||||||
|
.height,
|
||||||
padding: EdgeInsets.symmetric(vertical: 10.r, horizontal: 14.r),
|
padding: EdgeInsets.symmetric(vertical: 10.r, horizontal: 14.r),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
|
|
@ -61,17 +74,16 @@ class _FavoriteStudentDialogState extends State<FavoriteStudentDialog> {
|
||||||
children: [
|
children: [
|
||||||
Obx(() {
|
Obx(() {
|
||||||
return Text(
|
return Text(
|
||||||
'${currentStudent.value.className} ${currentStudent.value.studentName}',
|
'${currentStudent.value.className} ${currentStudent.value
|
||||||
|
.studentName}',
|
||||||
style: TextStyle(fontSize: 12.sp, color: Theme.of(context).primaryColor),
|
style: TextStyle(fontSize: 12.sp, color: Theme.of(context).primaryColor),
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
Expanded(child: Container()),
|
Expanded(child: Container()),
|
||||||
Obx(() {
|
Text(
|
||||||
return Text(
|
'第${currentStudent.value.questionNo}页',
|
||||||
'第${currentStudent.value.questionNo}页',
|
style: TextStyle(fontSize: 12.sp, color: Color(0xFF868686)),
|
||||||
style: TextStyle(fontSize: 12.sp, color: const Color(0xFF868686)),
|
),
|
||||||
);
|
|
||||||
}),
|
|
||||||
InkWell(
|
InkWell(
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
bool confim = await widget.confirmDialog();
|
bool confim = await widget.confirmDialog();
|
||||||
|
|
@ -82,7 +94,8 @@ class _FavoriteStudentDialogState extends State<FavoriteStudentDialog> {
|
||||||
if (defaultIndex.value < imageList.length) {
|
if (defaultIndex.value < imageList.length) {
|
||||||
currentStudent.value = imageList[defaultIndex.value];
|
currentStudent.value = imageList[defaultIndex.value];
|
||||||
} else {
|
} else {
|
||||||
currentStudent.value = imageList[defaultIndex.value - 1];
|
currentStudent.value =
|
||||||
|
imageList[defaultIndex.value - 1];
|
||||||
defaultIndex = defaultIndex - 1;
|
defaultIndex = defaultIndex - 1;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -118,93 +131,114 @@ class _FavoriteStudentDialogState extends State<FavoriteStudentDialog> {
|
||||||
Obx(() {
|
Obx(() {
|
||||||
return imageList.isNotEmpty
|
return imageList.isNotEmpty
|
||||||
? Expanded(
|
? Expanded(
|
||||||
child: Container(
|
child: Container(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
width: MediaQuery.of(context).size.width,
|
width: MediaQuery
|
||||||
child: PhotoViewGallery.builder(
|
.of(context)
|
||||||
scrollPhysics: const BouncingScrollPhysics(),
|
.size
|
||||||
builder: (BuildContext context, int index) {
|
.width,
|
||||||
final HomeworkFavs item = imageList[index];
|
child: PhotoViewGallery.builder(
|
||||||
return PhotoViewGalleryPageOptions(
|
scrollPhysics: const BouncingScrollPhysics(),
|
||||||
imageProvider: NetworkImage(RequestConfig.imgUrl + item.zgtAnswer!),
|
builder: (BuildContext context, int index) {
|
||||||
heroAttributes: PhotoViewHeroAttributes(tag: item.id!),
|
final HomeworkFavs item = imageList[index];
|
||||||
);
|
return PhotoViewGalleryPageOptions(
|
||||||
},
|
imageProvider: NetworkImage(RequestConfig.imgUrl + item.zgtAnswer!),
|
||||||
itemCount: imageList.length,
|
heroAttributes: PhotoViewHeroAttributes(tag: item.id!),
|
||||||
pageController: pageController,
|
);
|
||||||
onPageChanged: (index) {
|
},
|
||||||
defaultIndex.value = index;
|
itemCount: imageList.length,
|
||||||
currentStudent.value = imageList[index];
|
pageController: pageController,
|
||||||
},
|
onPageChanged: (index) {
|
||||||
scrollDirection: Axis.horizontal,
|
defaultIndex.value = index;
|
||||||
),
|
currentStudent.value = imageList[index];
|
||||||
),
|
},
|
||||||
)
|
scrollDirection: Axis.horizontal,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
: Padding(
|
: Padding(
|
||||||
padding: EdgeInsets.only(top: MediaQuery.of(context).size.height / 2 - 200.r),
|
padding: EdgeInsets.only(
|
||||||
child: const MyEmptyWidget(),
|
top: MediaQuery
|
||||||
);
|
.of(context)
|
||||||
|
.size
|
||||||
|
.height / 2 - 200.r),
|
||||||
|
child: const MyEmptyWidget(),
|
||||||
|
);
|
||||||
}),
|
}),
|
||||||
Obx(() {
|
Obx(() {
|
||||||
return imageList.isNotEmpty
|
return imageList.isNotEmpty?Padding(
|
||||||
? Padding(
|
padding: EdgeInsets.symmetric(vertical: 15.r),
|
||||||
padding: EdgeInsets.symmetric(vertical: 15.r),
|
child: Row(
|
||||||
child: Row(
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
children: [
|
||||||
children: [
|
InkWell(
|
||||||
InkWell(
|
onTap: () {
|
||||||
onTap: () {
|
if (defaultIndex.value > 0) {
|
||||||
if (defaultIndex.value > 0) {
|
defaultIndex.value = defaultIndex.value - 1;
|
||||||
defaultIndex.value = defaultIndex.value - 1;
|
pageController.jumpToPage(defaultIndex.value);
|
||||||
pageController.jumpToPage(defaultIndex.value);
|
}
|
||||||
}
|
},
|
||||||
},
|
child: Container(
|
||||||
child: Container(
|
width:
|
||||||
width: (MediaQuery.of(context).size.width - 78.r) / 2 - 10.r,
|
(MediaQuery
|
||||||
height: 28.r,
|
.of(context)
|
||||||
decoration: BoxDecoration(
|
.size
|
||||||
borderRadius: BorderRadius.all(Radius.circular(6.r)),
|
.width - 78.r) / 2 - 10.r,
|
||||||
border: Border.all(width: 1.r, color: const Color(0xFFCACACA), style: BorderStyle.solid),
|
height: 28.r,
|
||||||
),
|
decoration: BoxDecoration(
|
||||||
child: Center(
|
borderRadius: BorderRadius.all(Radius.circular(6.r)),
|
||||||
child: Text(
|
border: Border.all(
|
||||||
|
width: 1.r,
|
||||||
|
color: Color(0xFFCACACA),
|
||||||
|
style: BorderStyle.solid),
|
||||||
|
),
|
||||||
|
child: Center(
|
||||||
|
child: Text(
|
||||||
'上一页',
|
'上一页',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 10.r,
|
fontSize: 10.r,
|
||||||
color: defaultIndex == 0 ? const Color(0xFFCACACA) : const Color(0xFF505E6E)),
|
color: defaultIndex == 0
|
||||||
|
? Color(0xFFCACACA)
|
||||||
|
: Color(0xFF505E6E)),
|
||||||
)),
|
)),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
InkWell(
|
||||||
|
onTap: () {
|
||||||
|
if (defaultIndex.value < imageList.length - 1) {
|
||||||
|
defaultIndex.value = defaultIndex.value + 1;
|
||||||
|
pageController.jumpToPage(defaultIndex.value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
width:
|
||||||
|
(MediaQuery
|
||||||
|
.of(context)
|
||||||
|
.size
|
||||||
|
.width - 78.r) / 2 - 10.r,
|
||||||
|
height: 28.r,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(6.r)),
|
||||||
|
border: Border.all(
|
||||||
|
width: 1.r,
|
||||||
|
color: Color(0xFFCACACA),
|
||||||
|
style: BorderStyle.solid),
|
||||||
),
|
),
|
||||||
InkWell(
|
child: Center(
|
||||||
onTap: () {
|
child: Text(
|
||||||
if (defaultIndex.value < imageList.length - 1) {
|
|
||||||
defaultIndex.value = defaultIndex.value + 1;
|
|
||||||
pageController.jumpToPage(defaultIndex.value);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: Container(
|
|
||||||
width: (MediaQuery.of(context).size.width - 78.r) / 2 - 10.r,
|
|
||||||
height: 28.r,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
borderRadius: BorderRadius.all(Radius.circular(6.r)),
|
|
||||||
border: Border.all(width: 1.r, color: const Color(0xFFCACACA), style: BorderStyle.solid),
|
|
||||||
),
|
|
||||||
child: Center(
|
|
||||||
child: Text(
|
|
||||||
'下一页',
|
'下一页',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 10.r,
|
fontSize: 10.r,
|
||||||
color: defaultIndex == imageList.length - 1
|
color: defaultIndex == imageList.length - 1
|
||||||
? const Color(0xFFCACACA)
|
? Color(0xFFCACACA)
|
||||||
: const Color(0xFF505E6E)),
|
: Color(0xFF505E6E)),
|
||||||
)),
|
)),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
)
|
],
|
||||||
: Container();
|
),
|
||||||
})
|
):Container();
|
||||||
|
})
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,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/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/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/anti_shake_throttling.dart';
|
||||||
|
import 'package:making_school_asignment_app/common/utils/utils.dart';
|
||||||
|
|
||||||
import '../configuration_files/index.dart';
|
import '../configuration_files/index.dart';
|
||||||
|
|
||||||
|
|
@ -32,7 +33,7 @@ class _BottomAnnotationSwitchJobState extends State<BottomAnnotationSwitch>
|
||||||
lowerBound: 0,
|
lowerBound: 0,
|
||||||
upperBound: 1,
|
upperBound: 1,
|
||||||
duration: const Duration(milliseconds: 300),
|
duration: const Duration(milliseconds: 300),
|
||||||
);
|
)..addListener(toUp);
|
||||||
|
|
||||||
_opControllisten = _logicControl.opControl.listen((e) {
|
_opControllisten = _logicControl.opControl.listen((e) {
|
||||||
if (e) {
|
if (e) {
|
||||||
|
|
@ -47,174 +48,170 @@ class _BottomAnnotationSwitchJobState extends State<BottomAnnotationSwitch>
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_animationController.dispose();
|
_animationController
|
||||||
|
..removeListener(toUp)
|
||||||
|
..dispose();
|
||||||
_opControllisten?.cancel();
|
_opControllisten?.cancel();
|
||||||
|
|
||||||
eventCancel();
|
eventCancel();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void toUp() => toUpState(setState, () {}, mounted);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final Widget barContent = Row(
|
return SafeArea(
|
||||||
children: [
|
left: false,
|
||||||
Expanded(
|
right: false,
|
||||||
flex: 7,
|
top: false,
|
||||||
child: Column(
|
child: Container(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
width: double.infinity,
|
||||||
children: [
|
height: _animationController.value * 70.h,
|
||||||
Expanded(
|
decoration: BoxDecoration(
|
||||||
child: Row(
|
color: const Color.fromRGBO(83, 83, 83, 1),
|
||||||
children: [
|
boxShadow: [
|
||||||
// 全对按钮
|
BoxShadow(
|
||||||
Expanded(
|
color: Colors.black.withOpacity(0.1),
|
||||||
child: _buildActionButton(
|
blurRadius: 8,
|
||||||
'全对',
|
offset: const Offset(0, -2),
|
||||||
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(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: _buildActionButton(
|
flex: 7,
|
||||||
'取消',
|
child: Column(
|
||||||
Icons.clear,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
() => easyThrottle(
|
children: [
|
||||||
'homework_bottom_operation_bar_scoring_related', () => _homeworkLogic.cancelAllRatings()),
|
Expanded(
|
||||||
isEnabled: true,
|
child: Row(
|
||||||
isPrimary: false,
|
children: [
|
||||||
isSecondary: true,
|
// 全对按钮
|
||||||
|
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)),
|
Container(width: 0.5.w, height: double.infinity, color: Colors.white.withOpacity(0.3)),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Obx(() {
|
flex: 3,
|
||||||
final submitLoading = _homeworkLogic.state.submitLoading.value;
|
child: Row(
|
||||||
return _buildActionButton(
|
children: [
|
||||||
'提交',
|
Expanded(
|
||||||
Icons.send,
|
child: _buildActionButton(
|
||||||
() => easyThrottle(
|
'取消',
|
||||||
'homework_bottom_operation_bar_scoring_related', () => _homeworkLogic.submit(context)),
|
Icons.clear,
|
||||||
isEnabled: !submitLoading,
|
() => easyThrottle(
|
||||||
isPrimary: true,
|
'homework_bottom_operation_bar_scoring_related', () => _homeworkLogic.cancelAllRatings()),
|
||||||
isLoading: submitLoading,
|
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(
|
|
||||||
width: double.infinity,
|
|
||||||
height: _animationController.value * 70.h,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: const Color.fromRGBO(83, 83, 83, 1),
|
|
||||||
boxShadow: [
|
|
||||||
BoxShadow(
|
|
||||||
color: Colors.black.withOpacity(0.1),
|
|
||||||
blurRadius: 8,
|
|
||||||
offset: const Offset(0, -2),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
child: barContent,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildActionButton(
|
Widget _buildActionButton(
|
||||||
|
|
|
||||||
|
|
@ -15,42 +15,8 @@ import 'original_manuscript_handwriting/answer_handwriting_view.dart';
|
||||||
|
|
||||||
part 'dropdown_switch_students_type.g.dart';
|
part 'dropdown_switch_students_type.g.dart';
|
||||||
|
|
||||||
// 常量定义
|
/// 学生和页码切换
|
||||||
class _Constants {
|
|
||||||
static const double containerHeight = 30.0;
|
|
||||||
static const double borderRadius = 4.0;
|
|
||||||
static const double iconSize = 12.0;
|
|
||||||
static const double textSize = 14.0;
|
|
||||||
static const double smallTextSize = 12.0;
|
|
||||||
static const double tinyTextSize = 4.0;
|
|
||||||
|
|
||||||
// 颜色常量
|
|
||||||
static const Color shadowColor = Color.fromRGBO(46, 91, 255, 0.2);
|
|
||||||
static const Color backgroundColor = Color.fromRGBO(244, 244, 244, 1);
|
|
||||||
static const Color textColor = Color.fromRGBO(79, 79, 79, 1);
|
|
||||||
static const Color iconColor = Color.fromRGBO(104, 103, 103, 1);
|
|
||||||
static const Color priorityIconDisabledColor = Color.fromRGBO(164, 164, 164, 1);
|
|
||||||
|
|
||||||
// 间距常量
|
|
||||||
static const double paddingSmall = 2.0;
|
|
||||||
static const double paddingMedium = 8.0;
|
|
||||||
static const double paddingLarge = 12.0;
|
|
||||||
static const double paddingXL = 13.0;
|
|
||||||
|
|
||||||
// 阴影参数
|
|
||||||
static const double shadowBlurRadius = 14.0;
|
|
||||||
static const double shadowSpreadRadius = 0.5;
|
|
||||||
static const Offset shadowOffset = Offset(2.0, 2.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 学生和页码切换组件
|
|
||||||
///
|
///
|
||||||
/// 提供作业批阅时的页码选择和学生选择功能,包含:
|
|
||||||
/// - 页码下拉选择框
|
|
||||||
/// - 学生下拉选择框
|
|
||||||
/// - 继续批阅按钮
|
|
||||||
/// - 学生历史作业按钮
|
|
||||||
/// - 学生笔迹查看按钮
|
|
||||||
|
|
||||||
class DropdownSwitchStudentsType extends StatelessWidget {
|
class DropdownSwitchStudentsType extends StatelessWidget {
|
||||||
const DropdownSwitchStudentsType({super.key});
|
const DropdownSwitchStudentsType({super.key});
|
||||||
|
|
@ -58,20 +24,19 @@ class DropdownSwitchStudentsType extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final logic = Get.find<HomeworkReviewLogic>();
|
final logic = Get.find<HomeworkReviewLogic>();
|
||||||
final stateData = logic.state.data;
|
final sateData = logic.state.data;
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
height: _Constants.containerHeight.h,
|
height: 30.h,
|
||||||
padding: EdgeInsets.only(
|
padding: EdgeInsets.only(bottom: 2.r, left: 12.r, right: 12.r),
|
||||||
bottom: _Constants.paddingSmall.r, left: _Constants.paddingLarge.r, right: _Constants.paddingLarge.r),
|
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
boxShadow: [
|
boxShadow: [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: _Constants.shadowColor,
|
color: const Color.fromRGBO(46, 91, 255, 0.2),
|
||||||
offset: Offset(_Constants.shadowOffset.dx.w, _Constants.shadowOffset.dy.h),
|
offset: Offset(2.w, 2.h), //阴影y轴偏移量
|
||||||
blurRadius: _Constants.shadowBlurRadius,
|
blurRadius: 14, //阴影模糊程度
|
||||||
spreadRadius: _Constants.shadowSpreadRadius,
|
spreadRadius: 0.5, //阴影扩散程度
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
@ -79,16 +44,151 @@ class DropdownSwitchStudentsType extends StatelessWidget {
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
flex: 2,
|
flex: 2,
|
||||||
child: _buildPageDropdown(logic, stateData),
|
child: Container(
|
||||||
|
padding: EdgeInsets.only(left: 10.w),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: const Color.fromRGBO(244, 244, 244, 1),
|
||||||
|
borderRadius: BorderRadius.circular(4.r),
|
||||||
|
),
|
||||||
|
child: Obx(() {
|
||||||
|
return DropdownButton(
|
||||||
|
isExpanded: true,
|
||||||
|
underline: Container(),
|
||||||
|
padding: EdgeInsets.only(right: 4.w),
|
||||||
|
icon: const Icon(Icons.keyboard_arrow_down_rounded),
|
||||||
|
value: sateData.value?.templateId,
|
||||||
|
hint: const Text('请选择作业页码'), // 锚点的显示文本
|
||||||
|
items: sateData.value?.templateIdKeys?.map((e) {
|
||||||
|
return DropdownMenuItem(
|
||||||
|
value: e,
|
||||||
|
child: quickText('${sateData.value!.templateIdKeyMap![e]}页',
|
||||||
|
color: const Color.fromRGBO(79, 79, 79, 1), size: 14.sp),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
onChanged: (value) {
|
||||||
|
if (logic.state.param.value.templateId == value) return;
|
||||||
|
var templateIds = logic.state.data.value?.templateIds;
|
||||||
|
|
||||||
|
if (value != null && templateIds != null) {
|
||||||
|
final templateIdKeyMap = logic.state.data.value?.templateIdKeyMap;
|
||||||
|
|
||||||
|
var answeredAlready = templateIds[value.toString()];
|
||||||
|
if (answeredAlready != null && !answeredAlready) {
|
||||||
|
final currentStudentId = sateData.value?.studentId;
|
||||||
|
final students = sateData.value?.students ?? [];
|
||||||
|
|
||||||
|
// 获取当前学生姓名
|
||||||
|
PaperStudents? currentStudent;
|
||||||
|
if (currentStudentId != null) {
|
||||||
|
try {
|
||||||
|
currentStudent = students.firstWhere((e) => e.id == currentStudentId);
|
||||||
|
} catch (e) {
|
||||||
|
currentStudent = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final studentName = currentStudent?.name ?? '当前学生';
|
||||||
|
final questionNumber = templateIdKeyMap?[value] ?? '当前选择页';
|
||||||
|
|
||||||
|
ToastUtils.showInfo("$studentName第$questionNumber页,未作答 无需批阅");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logic.state.param.value.templateId = value;
|
||||||
|
logic.state.param.value = DoPaperDetailsParam.fromJson(logic.state.param.value.toJson());
|
||||||
|
// _useSwitchStudentAndType.currentTab.value = _useSwitchStudentAndType.tabs.value.firstWhere((element) => element.pageIndex == value);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
SizedBox(width: _Constants.paddingMedium.w),
|
SizedBox(width: 8.w),
|
||||||
Expanded(
|
Expanded(
|
||||||
flex: 3,
|
flex: 3,
|
||||||
child: _buildStudentDropdown(context, logic, stateData),
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
padding: EdgeInsets.only(left: 13.w),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: const Color.fromRGBO(244, 244, 244, 1),
|
||||||
|
borderRadius: BorderRadius.circular(4.r),
|
||||||
|
),
|
||||||
|
child: Obx(() {
|
||||||
|
return DropdownButton(
|
||||||
|
padding: EdgeInsets.only(right: 4.w),
|
||||||
|
icon: const Icon(Icons.keyboard_arrow_down_rounded),
|
||||||
|
value: sateData.value?.studentId,
|
||||||
|
underline: Container(),
|
||||||
|
isExpanded: true,
|
||||||
|
items: sateData.value?.students.map((e) {
|
||||||
|
return DropdownMenuItem(
|
||||||
|
value: e.id,
|
||||||
|
child: Stack(
|
||||||
|
alignment: const FractionalOffset(0, 0.62),
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
padding: sateData.value?.studentId != e.id && e.isPriority
|
||||||
|
? EdgeInsets.only(left: 14.w)
|
||||||
|
: null,
|
||||||
|
child: quickText(
|
||||||
|
e.name,
|
||||||
|
size: 14.sp,
|
||||||
|
color: const Color.fromRGBO(79, 79, 79, 1),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (e.isPriority && sateData.value?.studentId != e.id)
|
||||||
|
Stack(
|
||||||
|
alignment: const FractionalOffset(0.52, 0.24),
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
const IconData(0xe63d, fontFamily: "AlibabaIcon"),
|
||||||
|
size: 12.sp,
|
||||||
|
color: e.isPriority
|
||||||
|
? Theme.of(context).primaryColor
|
||||||
|
: const Color.fromRGBO(164, 164, 164, 1),
|
||||||
|
),
|
||||||
|
quickText('优先', size: 4.sp, color: Colors.white),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
hint: const Text('请选择学生'), // 锚点的显示文本
|
||||||
|
onChanged: (value) {
|
||||||
|
if (logic.state.param.value.studentId == value) return;
|
||||||
|
logic.state.param.value.studentId = value;
|
||||||
|
logic.state.param.value = DoPaperDetailsParam.fromJson(logic.state.param.value.toJson());
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
left: 2.w,
|
||||||
|
child: Stack(
|
||||||
|
alignment: const FractionalOffset(0.52, 0.24),
|
||||||
|
children: [
|
||||||
|
Obx(() {
|
||||||
|
return Icon(
|
||||||
|
const IconData(0xe63d, fontFamily: "AlibabaIcon"),
|
||||||
|
size: 12.sp,
|
||||||
|
color: sateData.value?.priority ?? false
|
||||||
|
? Theme.of(context).primaryColor
|
||||||
|
: const Color.fromRGBO(164, 164, 164, 1),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
quickText('优先', size: 4.sp, color: Colors.white),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
SizedBox(width: _Constants.paddingMedium.w),
|
// const Expanded(flex: 1, child: SizedBox()),
|
||||||
|
SizedBox(width: 8.w),
|
||||||
const Expanded(
|
const Expanded(
|
||||||
flex: 6,
|
flex: 5,
|
||||||
child: Row(
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
|
|
@ -103,323 +203,137 @@ class DropdownSwitchStudentsType extends StatelessWidget {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 构建页码下拉框
|
|
||||||
Widget _buildPageDropdown(HomeworkReviewLogic logic, Rx<DoPaperDetailsResult?> stateData) {
|
|
||||||
return Container(
|
|
||||||
padding: EdgeInsets.only(left: 10.w),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: _Constants.backgroundColor,
|
|
||||||
borderRadius: BorderRadius.circular(_Constants.borderRadius.r),
|
|
||||||
),
|
|
||||||
child: Obx(() {
|
|
||||||
final data = stateData.value;
|
|
||||||
return DropdownButton(
|
|
||||||
isExpanded: true,
|
|
||||||
underline: Container(),
|
|
||||||
padding: EdgeInsets.only(right: 4.w),
|
|
||||||
icon: const Icon(Icons.keyboard_arrow_down_rounded),
|
|
||||||
value: data?.templateId,
|
|
||||||
hint: const Text('请选择作业页码'),
|
|
||||||
items: data?.templateIdKeys?.map((e) {
|
|
||||||
return DropdownMenuItem(
|
|
||||||
value: e,
|
|
||||||
child: quickText(
|
|
||||||
'${data.templateIdKeyMap![e]}页',
|
|
||||||
color: _Constants.textColor,
|
|
||||||
size: _Constants.textSize.sp,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}).toList(),
|
|
||||||
onChanged: (value) => _handlePageChange(logic, value),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 构建学生下拉框
|
|
||||||
Widget _buildStudentDropdown(BuildContext context, HomeworkReviewLogic logic, Rx<DoPaperDetailsResult?> stateData) {
|
|
||||||
return Container(
|
|
||||||
padding: EdgeInsets.only(left: _Constants.paddingXL.w),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: _Constants.backgroundColor,
|
|
||||||
borderRadius: BorderRadius.circular(_Constants.borderRadius.r),
|
|
||||||
),
|
|
||||||
child: Obx(() {
|
|
||||||
final data = stateData.value;
|
|
||||||
return DropdownButton(
|
|
||||||
padding: EdgeInsets.only(right: 4.w),
|
|
||||||
icon: const Icon(Icons.keyboard_arrow_down_rounded),
|
|
||||||
value: data?.studentId,
|
|
||||||
underline: Container(),
|
|
||||||
isExpanded: true,
|
|
||||||
items: data?.students.map((student) {
|
|
||||||
return DropdownMenuItem(
|
|
||||||
value: student.id,
|
|
||||||
child: _buildStudentItem(context, student),
|
|
||||||
);
|
|
||||||
}).toList(),
|
|
||||||
hint: const Text('请选择学生'),
|
|
||||||
onChanged: (value) => _handleStudentChange(logic, value),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 构建学生项目
|
|
||||||
Widget _buildStudentItem(BuildContext context, PaperStudents student) {
|
|
||||||
return Stack(
|
|
||||||
alignment: const FractionalOffset(0, 0.5),
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
padding: EdgeInsets.only(left: 16.w),
|
|
||||||
child: quickText(
|
|
||||||
student.name,
|
|
||||||
size: _Constants.textSize.sp,
|
|
||||||
color: _Constants.textColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Stack(
|
|
||||||
alignment: const FractionalOffset(0.52, 0.26),
|
|
||||||
children: [
|
|
||||||
Icon(
|
|
||||||
const IconData(0xe63d, fontFamily: "AlibabaIcon"),
|
|
||||||
size: _Constants.iconSize.sp,
|
|
||||||
color: student.isPriority ? Theme.of(context).primaryColor : _Constants.priorityIconDisabledColor,
|
|
||||||
),
|
|
||||||
quickText('优先', size: _Constants.tinyTextSize.sp, color: Colors.white),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 处理页码变更
|
|
||||||
void _handlePageChange(HomeworkReviewLogic logic, dynamic value) {
|
|
||||||
if (logic.state.param.value.templateId == value) return;
|
|
||||||
|
|
||||||
final templateIds = logic.state.data.value?.templateIds;
|
|
||||||
if (value == null || templateIds == null) return;
|
|
||||||
|
|
||||||
final templateIdKeyMap = logic.state.data.value?.templateIdKeyMap;
|
|
||||||
final answeredAlready = templateIds[value.toString()];
|
|
||||||
|
|
||||||
if (answeredAlready != null && !answeredAlready) {
|
|
||||||
_showUnansweredPageToast(logic.state.data.value, value, templateIdKeyMap);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
logic.state.param.value.templateId = value;
|
|
||||||
logic.state.param.value = DoPaperDetailsParam.fromJson(logic.state.param.value.toJson());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 处理学生变更
|
|
||||||
void _handleStudentChange(HomeworkReviewLogic logic, dynamic value) {
|
|
||||||
if (logic.state.param.value.studentId == value) return;
|
|
||||||
|
|
||||||
logic.state.param.value.studentId = value;
|
|
||||||
logic.state.param.value = DoPaperDetailsParam.fromJson(logic.state.param.value.toJson());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 显示未作答页面提示
|
|
||||||
void _showUnansweredPageToast(DoPaperDetailsResult? data, dynamic value, Map<dynamic, dynamic>? templateIdKeyMap) {
|
|
||||||
final currentStudentId = data?.studentId;
|
|
||||||
final students = data?.students ?? [];
|
|
||||||
|
|
||||||
// 获取当前学生姓名
|
|
||||||
PaperStudents? currentStudent;
|
|
||||||
if (currentStudentId != null) {
|
|
||||||
try {
|
|
||||||
currentStudent = students.firstWhere((e) => e.id == currentStudentId);
|
|
||||||
} catch (e) {
|
|
||||||
currentStudent = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final studentName = currentStudent?.name ?? '当前学生';
|
|
||||||
final questionNumber = templateIdKeyMap?[value] ?? '当前选择页';
|
|
||||||
|
|
||||||
ToastUtils.showInfo("$studentName第$questionNumber页,未作答 无需批阅");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@swidget
|
@swidget
|
||||||
Widget $studentHandwriting(BuildContext context) {
|
Widget $studentHandwriting(BuildContext context) {
|
||||||
final logic = Get.find<HomeworkReviewLogic>();
|
final logic = Get.find<HomeworkReviewLogic>();
|
||||||
final stateData = logic.state.data;
|
final sateData = Get.find<HomeworkReviewLogic>().state.data;
|
||||||
|
|
||||||
return InkWell(
|
return InkWell(
|
||||||
onTap: () => easyThrottle(
|
onTap: () => easyThrottle(
|
||||||
'SHOW_ANSWER_HANDWRITING',
|
'SHOW_ANSWER_HANDWRITING',
|
||||||
() async {
|
() async {
|
||||||
final data = stateData.value;
|
var homeworkId = logic.state.param.value.homeworkId;
|
||||||
final studentId = data?.studentId;
|
var studentId = sateData.value?.studentId;
|
||||||
if (studentId == null) return;
|
if (studentId == null) return;
|
||||||
|
var templateIdKeyMap = sateData.value?.templateIdKeyMap;
|
||||||
final homeworkId = logic.state.param.value.homeworkId;
|
int? pageNum;
|
||||||
final templateId = data?.templateId;
|
var templateId = sateData.value?.templateId;
|
||||||
final templateIdKeyMap = data?.templateIdKeyMap;
|
if (templateIdKeyMap != null && templateId != null) {
|
||||||
|
pageNum = templateIdKeyMap[templateId];
|
||||||
int? pageNum;
|
}
|
||||||
if (templateIdKeyMap != null && templateId != null) {
|
await showAnswerHandwriting(context,
|
||||||
pageNum = templateIdKeyMap[templateId];
|
homeworkId: homeworkId, studentId: studentId, templateId: templateId, pageNum: pageNum);
|
||||||
}
|
ToastUtils.dismiss();
|
||||||
|
},
|
||||||
await showAnswerHandwriting(
|
),
|
||||||
context,
|
child: Row(
|
||||||
homeworkId: homeworkId,
|
mainAxisSize: MainAxisSize.min,
|
||||||
studentId: studentId,
|
children: [
|
||||||
templateId: templateId,
|
Icon(Icons.edit_outlined, size: 12.sp, color: Theme.of(context).primaryColor.withOpacity(0.8)),
|
||||||
pageNum: pageNum,
|
SizedBox(width: 1.w),
|
||||||
);
|
quickText(
|
||||||
ToastUtils.dismiss();
|
'学生笔迹',
|
||||||
},
|
size: 12.sp,
|
||||||
),
|
decoration: TextDecoration.underline,
|
||||||
child: Row(
|
color: Theme.of(context).primaryColor.withOpacity(0.9),
|
||||||
mainAxisSize: MainAxisSize.min,
|
),
|
||||||
children: [
|
],
|
||||||
Icon(
|
));
|
||||||
Icons.edit_outlined,
|
|
||||||
size: _Constants.iconSize.sp,
|
|
||||||
color: Theme.of(context).primaryColor.withOpacity(0.8),
|
|
||||||
),
|
|
||||||
SizedBox(width: 1.w),
|
|
||||||
quickText(
|
|
||||||
'学生笔迹',
|
|
||||||
size: _Constants.smallTextSize.sp,
|
|
||||||
decoration: TextDecoration.underline,
|
|
||||||
color: Theme.of(context).primaryColor.withOpacity(0.9),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@swidget
|
@swidget
|
||||||
Widget $continueToReview(BuildContext context, {bool isFloatingAction = false}) {
|
Widget $continueToReview(BuildContext context, {bool isFloatingAction = false}) {
|
||||||
final logic = Get.find<HomeworkReviewLogic>();
|
final logic = Get.find<HomeworkReviewLogic>();
|
||||||
final stateData = logic.state.data;
|
final sateData = Get.find<HomeworkReviewLogic>().state.data;
|
||||||
|
|
||||||
return Obx(() {
|
return Obx(() {
|
||||||
final data = stateData.value;
|
var data = sateData.value;
|
||||||
|
var param = logic.state.param.value;
|
||||||
// 检查是否需要显示继续批阅按钮
|
int? submitCount = data?.submitCount; // 提交数量
|
||||||
if (!_shouldShowContinueReview(data)) {
|
int? annotatedCount = data?.annotatedCount; // 批阅数量
|
||||||
return const SizedBox();
|
// || (submitCount == annotatedCount || (param.templateId == null && param.studentId == null))
|
||||||
}
|
// if (data == null || (data.needAnnotate ? true : data.totalUnAnnotateCount <= 0) ) return const SizedBox();
|
||||||
|
if (data == null ||
|
||||||
void onPressed() => easyThrottle(
|
(data.needAnnotate
|
||||||
|
? (data.continuePage == null ? true : data.continuePage!.templateId == data.templateId)
|
||||||
|
: data.totalUnAnnotateCount <= 0)) return const SizedBox();
|
||||||
|
callFun() => easyThrottle(
|
||||||
'DO_PAPERS_JOB_CONTINUE_TO_REVIEW',
|
'DO_PAPERS_JOB_CONTINUE_TO_REVIEW',
|
||||||
() {
|
() {
|
||||||
final param = logic.state.param.value;
|
var param = logic.state.param.value;
|
||||||
param.templateId = null;
|
param.templateId = null;
|
||||||
param.studentId = null;
|
param.studentId = null;
|
||||||
logic.state.param.value = DoPaperDetailsParam.fromJson(param.toJson());
|
logic.state.param.value = DoPaperDetailsParam.fromJson(param.toJson());
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
if (isFloatingAction) {
|
if (isFloatingAction) {
|
||||||
return FloatingActionButton(
|
return FloatingActionButton(
|
||||||
elevation: 8,
|
elevation: 8,
|
||||||
tooltip: "继续批阅",
|
tooltip: "继续批阅",
|
||||||
backgroundColor: Colors.white,
|
backgroundColor: Colors.white,
|
||||||
onPressed: onPressed,
|
onPressed: callFun,
|
||||||
child: Icon(
|
child: Icon(Icons.flip_camera_android_outlined, size: 20.sp, color: Theme.of(context).primaryColor),
|
||||||
Icons.flip_camera_android_outlined,
|
|
||||||
size: 20.sp,
|
|
||||||
color: Theme.of(context).primaryColor,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
return SizedBox(
|
||||||
return InkWell(
|
child: InkWell(
|
||||||
onTap: onPressed,
|
onTap: callFun,
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Icon(
|
Icon(Icons.flip_camera_android_outlined, size: 12.sp, color: Theme.of(context).primaryColor.withOpacity(0.8)),
|
||||||
Icons.flip_camera_android_outlined,
|
|
||||||
size: _Constants.iconSize.sp,
|
|
||||||
color: Theme.of(context).primaryColor.withOpacity(0.8),
|
|
||||||
),
|
|
||||||
SizedBox(width: 1.w),
|
SizedBox(width: 1.w),
|
||||||
quickText(
|
quickText(
|
||||||
'继续批阅',
|
'继续批阅',
|
||||||
size: _Constants.smallTextSize.sp,
|
size: 12.sp,
|
||||||
decoration: TextDecoration.underline,
|
decoration: TextDecoration.underline,
|
||||||
color: Theme.of(context).primaryColor.withOpacity(0.9),
|
color: Theme.of(context).primaryColor.withOpacity(0.9),
|
||||||
),
|
),
|
||||||
SizedBox(width: 2.w),
|
SizedBox(width: 2.w),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 检查是否应该显示继续批阅按钮
|
// 学生历史作业
|
||||||
bool _shouldShowContinueReview(DoPaperDetailsResult? data) {
|
|
||||||
if (data == null) return false;
|
|
||||||
|
|
||||||
if (data.needAnnotate) {
|
|
||||||
return data.continuePage == null || data.continuePage!.templateId != data.templateId;
|
|
||||||
} else {
|
|
||||||
return data.totalUnAnnotateCount > 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 学生历史作业
|
|
||||||
@swidget
|
@swidget
|
||||||
Widget $historyHomework(BuildContext context) {
|
Widget $historyHomework(BuildContext context) {
|
||||||
final logic = Get.find<HomeworkReviewLogic>();
|
final sateData = Get.find<HomeworkReviewLogic>().state.data;
|
||||||
final stateData = logic.state.data;
|
return SizedBox(
|
||||||
|
child: InkWell(
|
||||||
return InkWell(
|
onTap: () => easyThrottle('DO_PAPERS_JOB_HISTORICAL_HOMEWORK', () {
|
||||||
onTap: () => easyThrottle(
|
int? studentId = sateData.value?.studentId;
|
||||||
'DO_PAPERS_JOB_HISTORICAL_HOMEWORK',
|
if (kDebugMode) print(studentId);
|
||||||
() => _navigateToStudentHistory(logic, stateData.value),
|
if (studentId == null || (sateData.value?.students.isEmpty ?? true)) return;
|
||||||
),
|
PaperStudents? currentStudent;
|
||||||
child: Row(
|
try {
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
currentStudent = sateData.value!.students.firstWhere((e) => e.id == studentId);
|
||||||
mainAxisSize: MainAxisSize.min,
|
} catch (e) {
|
||||||
children: [
|
return;
|
||||||
Icon(
|
}
|
||||||
Icons.location_history,
|
var theState = Get.find<HomeworkReviewLogic>().state;
|
||||||
size: _Constants.iconSize.sp,
|
Get.toNamed(Routes.studentWorkDetailPage, arguments: {
|
||||||
color: _Constants.iconColor,
|
'studentId': studentId,
|
||||||
),
|
'studentName': currentStudent.name,
|
||||||
SizedBox(width: 1.w),
|
'subject': theState.param.value.subject
|
||||||
quickText(
|
});
|
||||||
'历史作业',
|
}),
|
||||||
size: _Constants.smallTextSize.sp,
|
child: Row(
|
||||||
decoration: TextDecoration.underline,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
color: Theme.of(context).primaryColor.withOpacity(0.8),
|
mainAxisSize: MainAxisSize.min,
|
||||||
),
|
children: [
|
||||||
],
|
Icon(Icons.location_history, size: 12.sp, color: const Color.fromRGBO(104, 103, 103, 1)),
|
||||||
|
SizedBox(width: 1.w),
|
||||||
|
quickText(
|
||||||
|
'历史作业',
|
||||||
|
size: 12.sp,
|
||||||
|
decoration: TextDecoration.underline,
|
||||||
|
color: Theme.of(context).primaryColor.withOpacity(0.8),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 导航到学生历史作业页面
|
|
||||||
void _navigateToStudentHistory(HomeworkReviewLogic logic, DoPaperDetailsResult? data) {
|
|
||||||
final studentId = data?.studentId;
|
|
||||||
final students = data?.students ?? [];
|
|
||||||
|
|
||||||
if (kDebugMode) print('Student ID: $studentId');
|
|
||||||
|
|
||||||
if (studentId == null || students.isEmpty) return;
|
|
||||||
|
|
||||||
PaperStudents? currentStudent;
|
|
||||||
try {
|
|
||||||
currentStudent = students.firstWhere((e) => e.id == studentId);
|
|
||||||
} catch (e) {
|
|
||||||
if (kDebugMode) print('Student not found: $e');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Get.toNamed(Routes.studentWorkDetailPage, arguments: {
|
|
||||||
'studentId': studentId,
|
|
||||||
'studentName': currentStudent.name,
|
|
||||||
'subject': logic.state.param.value.subject,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -106,8 +106,7 @@ Widget $questionNumberScrollView({
|
||||||
/// 无法联动
|
/// 无法联动
|
||||||
// if (sateData.panQuestView == false) sateData.slide.value = scrollControllerNum.offset;
|
// if (sateData.panQuestView == false) sateData.slide.value = scrollControllerNum.offset;
|
||||||
|
|
||||||
final currentMatrix = controller.zoomController?.value;
|
final currentMatrix = controller.zoomController.value;
|
||||||
if (currentMatrix == null) return;
|
|
||||||
|
|
||||||
// 创建一个新的变换矩阵,只修改垂直平移分量
|
// 创建一个新的变换矩阵,只修改垂直平移分量
|
||||||
final newMatrix = Matrix4.copy(currentMatrix)
|
final newMatrix = Matrix4.copy(currentMatrix)
|
||||||
|
|
@ -117,7 +116,7 @@ Widget $questionNumberScrollView({
|
||||||
currentMatrix.getTranslation().z, // 保持 Z 轴不变
|
currentMatrix.getTranslation().z, // 保持 Z 轴不变
|
||||||
));
|
));
|
||||||
|
|
||||||
controller.zoomController?.value = newMatrix;
|
controller.zoomController.value = newMatrix;
|
||||||
});
|
});
|
||||||
var listenVal = sateData.slide.listen((e) {
|
var listenVal = sateData.slide.listen((e) {
|
||||||
if (sateData.panQuestView != null && sateData.panQuestView == true && e != scrollControllerNum.offset) {
|
if (sateData.panQuestView != null && sateData.panQuestView == true && e != scrollControllerNum.offset) {
|
||||||
|
|
@ -133,24 +132,30 @@ Widget $questionNumberScrollView({
|
||||||
|
|
||||||
// var actualImgHeight = useImageInfo.value?.actualImgHeight ?? 0; // 实际图片高度
|
// var actualImgHeight = useImageInfo.value?.actualImgHeight ?? 0; // 实际图片高度
|
||||||
|
|
||||||
// 移除高频日志,避免在滚动/缩放时造成不必要的 I/O 开销
|
print("图片高度:${usePiddingTop.value}");
|
||||||
|
|
||||||
return ListView.builder(
|
return SingleChildScrollView(
|
||||||
controller: scrollControllerNum,
|
controller: scrollControllerNum,
|
||||||
physics: const BouncingScrollPhysics(),
|
physics: const BouncingScrollPhysics(),
|
||||||
padding: EdgeInsets.only(top: usePiddingTop.value > 0 ? usePiddingTop.value : 0),
|
padding: EdgeInsets.only(top: usePiddingTop.value > 0 ? usePiddingTop.value : 0),
|
||||||
itemCount: studentQuestions.length,
|
scrollDirection: Axis.vertical, // 设置垂直滚动
|
||||||
itemBuilder: (context, index) {
|
child: SizedBox(
|
||||||
final e = studentQuestions[index];
|
height: (actualImgHeight.value ?? 0) * useZoom.value,
|
||||||
return $ScoringQuestionsView(
|
child: Column(
|
||||||
key: Key('${sateData.data.value?.templateId}_${sateData.data.value?.studentId}_${e.questionNo}'),
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
sateData: sateData,
|
mainAxisSize: MainAxisSize.min,
|
||||||
item: e,
|
children: studentQuestions
|
||||||
logic: controller,
|
.map((e) => $ScoringQuestionsView(
|
||||||
scaleRatio: sateZoomData.zoomFile.value!.scaleRatio,
|
key: Key('${sateData.data.value?.templateId}_${sateData.data.value?.studentId}_${e.questionNo}'),
|
||||||
initScale: useZoom.value,
|
sateData: sateData,
|
||||||
);
|
item: e,
|
||||||
},
|
logic: controller,
|
||||||
|
scaleRatio: sateZoomData.zoomFile.value!.scaleRatio,
|
||||||
|
initScale: useZoom.value,
|
||||||
|
))
|
||||||
|
.toList(),
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -177,6 +182,7 @@ Widget $scoringQuestionsView(
|
||||||
useEffect(() {
|
useEffect(() {
|
||||||
/// 学生打分数据
|
/// 学生打分数据
|
||||||
studentScoreListener() {
|
studentScoreListener() {
|
||||||
|
print(item.toJson());
|
||||||
item.studentScore = studentScore.value;
|
item.studentScore = studentScore.value;
|
||||||
try {
|
try {
|
||||||
var theVal = sateData.studentQuestions.value?.firstWhere((e) => e.questionNo == item.questionNo);
|
var theVal = sateData.studentQuestions.value?.firstWhere((e) => e.questionNo == item.questionNo);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:io';
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:flutter/cupertino.dart' hide TransformationController;
|
import 'package:flutter/cupertino.dart' hide TransformationController;
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
@ -15,7 +17,6 @@ import 'package:making_school_asignment_app/common/utils/cached_network_img.dart
|
||||||
import 'package:making_school_asignment_app/common/utils/toast_utils.dart';
|
import 'package:making_school_asignment_app/common/utils/toast_utils.dart';
|
||||||
import 'package:making_school_asignment_app/page/global_widget/my_text.dart';
|
import 'package:making_school_asignment_app/page/global_widget/my_text.dart';
|
||||||
import 'package:making_school_asignment_app/page/home_page/children/homework_review/configuration_files/index.dart';
|
import 'package:making_school_asignment_app/page/home_page/children/homework_review/configuration_files/index.dart';
|
||||||
import 'package:vector_math/vector_math_64.dart' show Vector3, Matrix4;
|
|
||||||
import 'package:zoom_widget/zoom_widget.dart';
|
import 'package:zoom_widget/zoom_widget.dart';
|
||||||
|
|
||||||
import '../configuration_files/zoom_logic.dart';
|
import '../configuration_files/zoom_logic.dart';
|
||||||
|
|
@ -46,10 +47,10 @@ class QuestionPaperView extends GetView<HomeworkReviewLogic> {
|
||||||
if (zoomFileModel == null) {
|
if (zoomFileModel == null) {
|
||||||
/// 计算高度
|
/// 计算高度
|
||||||
return LayoutBuilder(builder: (BuildContext context, BoxConstraints constraints) {
|
return LayoutBuilder(builder: (BuildContext context, BoxConstraints constraints) {
|
||||||
WidgetsBinding.instance.addPostFrameCallback(
|
WidgetsBinding.instance.addPostFrameCallback((_) => zoomState.zoomFile.value = ZoomFileModel(
|
||||||
(_) => zoomState.zoomFile.value =
|
viewWidth: constraints.maxWidth,
|
||||||
ZoomFileModel(viewWidth: constraints.maxWidth, viewHeight: constraints.maxHeight),
|
viewHeight: constraints.maxHeight,
|
||||||
);
|
));
|
||||||
return const SizedBox();
|
return const SizedBox();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -298,11 +299,25 @@ class QuestionImageView extends HookWidget with EventBusMixin<BottomOperationBar
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int _activePointers = 0;
|
||||||
|
|
||||||
|
Timer? timer;
|
||||||
// 定时器超时时间,单位为毫秒
|
// 定时器超时时间,单位为毫秒
|
||||||
static const int timeoutDuration = 300;
|
static const int timeoutDuration = 300;
|
||||||
|
|
||||||
/// 解决多指截屏问题后 无法进入手指抬起回调导致无法再次进行批注
|
/// 解决多指截屏问题后 无法进入手指抬起回调导致无法再次进行批注
|
||||||
/// 注意:实际逻辑在 build 内部基于 useRef 管理,避免可变实例字段
|
void toTimer(ValueNotifier<List<dynamic>> vnHandWritings) {
|
||||||
|
timer?.cancel();
|
||||||
|
timer = Timer(const Duration(milliseconds: timeoutDuration), () {
|
||||||
|
if (_activePointers > 2) {
|
||||||
|
_activePointers = 0;
|
||||||
|
if (vnHandWritings.value.isNotEmpty && vnHandWritings.value.last != null) {
|
||||||
|
vnHandWritings.value.add(null); // 增加空点以分隔不同的线段
|
||||||
|
sateData.handwritings = vnHandWritings.value; // 添加笔迹数据
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/// 获取最后一个点的坐标位置
|
/// 获取最后一个点的坐标位置
|
||||||
Offset? getLastDrop(List<dynamic> vals, int index) {
|
Offset? getLastDrop(List<dynamic> vals, int index) {
|
||||||
|
|
@ -323,40 +338,12 @@ class QuestionImageView extends HookWidget with EventBusMixin<BottomOperationBar
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
// 管理触点数量与定时器(避免在 HookWidget 中使用可变实例字段)
|
|
||||||
final activePointers = useRef<int>(0);
|
|
||||||
final timerRef = useRef<Timer?>(null);
|
|
||||||
|
|
||||||
void startTimerForHandwriting(ValueNotifier<List<dynamic>> vnHandWritings) {
|
|
||||||
timerRef.value?.cancel();
|
|
||||||
timerRef.value = Timer(const Duration(milliseconds: timeoutDuration), () {
|
|
||||||
if (activePointers.value > 2) {
|
|
||||||
activePointers.value = 0;
|
|
||||||
if (vnHandWritings.value.isNotEmpty && vnHandWritings.value.last != null) {
|
|
||||||
vnHandWritings.value.add(null); // 增加空点以分隔不同的线段
|
|
||||||
sateData.handwritings = vnHandWritings.value; // 添加笔迹数据
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
final theMaxHeight = useState<double>(maxHeight);
|
final theMaxHeight = useState<double>(maxHeight);
|
||||||
useValueChanged<double, void>(maxHeight, (oldValue, __) => theMaxHeight.value = maxHeight);
|
useValueChanged<double, void>(maxHeight, (oldValue, __) => theMaxHeight.value = maxHeight);
|
||||||
|
|
||||||
var zoomKey = useState<GlobalKey>(GlobalKey());
|
var zoomKey = useState<GlobalKey>(GlobalKey());
|
||||||
useValueChanged<int?, void>(zoomState.zoomFile.value?.templateId, (old, __) {
|
useValueChanged<int?, void>(zoomState.zoomFile.value?.templateId, (old, __) {
|
||||||
// 延迟重建Zoom组件,确保重置完成后再重建
|
zoomKey.value = GlobalKey();
|
||||||
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);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
var vnHandWritings = useValueNotifier<List<dynamic>>([]);
|
var vnHandWritings = useValueNotifier<List<dynamic>>([]);
|
||||||
|
|
@ -490,8 +477,7 @@ class QuestionImageView extends HookWidget with EventBusMixin<BottomOperationBar
|
||||||
});
|
});
|
||||||
|
|
||||||
return () {
|
return () {
|
||||||
activePointers.value = 0;
|
_activePointers = 0;
|
||||||
timerRef.value?.cancel();
|
|
||||||
eventCancel();
|
eventCancel();
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
@ -505,37 +491,46 @@ class QuestionImageView extends HookWidget with EventBusMixin<BottomOperationBar
|
||||||
// 判断当前是否已经有触摸点,如果有则忽略该触摸事件
|
// 判断当前是否已经有触摸点,如果有则忽略该触摸事件
|
||||||
|
|
||||||
// 处理单个触摸点按下的逻辑
|
// 处理单个触摸点按下的逻辑
|
||||||
activePointers.value = activePointers.value + 1;
|
_activePointers = _activePointers + 1;
|
||||||
|
|
||||||
/// 解决多指截屏问题后 无法进入手指抬起回调导致无法再次进行批注
|
/// 解决多指截屏问题后 无法进入手指抬起回调导致无法再次进行批注
|
||||||
startTimerForHandwriting(vnHandWritings);
|
toTimer(vnHandWritings);
|
||||||
sateData.panQuestView = true;
|
sateData.panQuestView = true;
|
||||||
},
|
},
|
||||||
onPointerUp: (PointerUpEvent details) {
|
onPointerUp: (PointerUpEvent details) {
|
||||||
// 处理单个触摸点抬起的逻辑
|
// 处理单个触摸点抬起的逻辑
|
||||||
// activePointers--;
|
// activePointers--;
|
||||||
// globalPosition = null;
|
// globalPosition = null;
|
||||||
if (activePointers.value > 0) {
|
if (_activePointers > 0) {
|
||||||
activePointers.value = activePointers.value - 1;
|
_activePointers = _activePointers - 1;
|
||||||
}
|
}
|
||||||
// 移除高频日志,避免导致掉帧
|
print("---进入:onPointerUp $_activePointers");
|
||||||
timerRef.value?.cancel();
|
timer?.cancel();
|
||||||
if (!annotationState.pen.value) return;
|
if (!annotationState.pen.value) return;
|
||||||
|
|
||||||
vnHandWritings.value.add(null); // 增加空点以分隔不同的线段
|
vnHandWritings.value.add(null); // 增加空点以分隔不同的线段
|
||||||
sateData.handwritings = vnHandWritings.value; // 添加笔迹数据
|
sateData.handwritings = vnHandWritings.value; // 添加笔迹数据
|
||||||
},
|
},
|
||||||
onPointerMove: (PointerMoveEvent event) {
|
onPointerMove: (PointerMoveEvent event) {
|
||||||
// 移除高频日志,避免导致掉帧
|
print("进入:onPointerMove $_activePointers");
|
||||||
if (activePointers.value != 1) return;
|
if (_activePointers != 1) return;
|
||||||
startTimerForHandwriting(vnHandWritings);
|
toTimer(vnHandWritings);
|
||||||
if (!annotationState.pen.value) return;
|
if (!annotationState.pen.value) return;
|
||||||
|
|
||||||
Offset localPosition = event.localPosition; // 相对
|
Offset localPosition = event.localPosition; // 相对
|
||||||
|
var zoomFile = zoomState.zoomFile.value!;
|
||||||
|
// var imageHeightOffsetStart = zoomFile.imageHeightOffsetStart??0;
|
||||||
|
var imageHeightOffsetStart = zoomState.zoomFile.value!.getZoomFileOffsetStart(zoomState.initScale.value ?? 1);
|
||||||
|
// print("位置:$localPosition; 图片所在位置:$imageHeightOffsetStart");
|
||||||
|
if (imageHeightOffsetStart == 0) return;
|
||||||
|
|
||||||
// 使用矩阵进行统一坐标反算(避免重复补偿)
|
var dy = localPosition.dy;
|
||||||
final Matrix4 matrix = logic.zoomController?.value ?? Matrix4.identity(); // 当前变换矩阵
|
// print(zoomFile.getZoomFileHeightOffsetEnd(zoomState.initScale.value ?? 1));
|
||||||
final double theScale = matrix.getMaxScaleOnAxis();
|
if (dy < imageHeightOffsetStart || dy > zoomFile.getZoomFileHeightOffsetEnd(zoomState.initScale.value ?? 1)) {
|
||||||
|
return; // 检查笔记是否超出图片范围
|
||||||
|
}
|
||||||
|
|
||||||
|
var theScale = zoomState.initScale.value ?? 1;
|
||||||
// if (theScale != 1) {
|
// if (theScale != 1) {
|
||||||
// print("PPPPPPPPPPPPPPPPPPPPPPPP ${(zoomFile.imageHeightOffsetStart ?? 0)}");
|
// print("PPPPPPPPPPPPPPPPPPPPPPPP ${(zoomFile.imageHeightOffsetStart ?? 0)}");
|
||||||
// localPosition = Offset(localPosition.dx, localPosition.dy + (zoomFile.imageHeightOffsetStart ?? 0));
|
// localPosition = Offset(localPosition.dx, localPosition.dy + (zoomFile.imageHeightOffsetStart ?? 0));
|
||||||
|
|
@ -554,51 +549,26 @@ class QuestionImageView extends HookWidget with EventBusMixin<BottomOperationBar
|
||||||
// }
|
// }
|
||||||
// (dy / theScale) - (max(0, imageHeightOffsetStart) / theScale) + ((sateData.zoomOffset?.dy.abs() ?? 0) / theScale),
|
// (dy / theScale) - (max(0, imageHeightOffsetStart) / theScale) + ((sateData.zoomOffset?.dy.abs() ?? 0) / theScale),
|
||||||
|
|
||||||
// 屏幕坐标 -> 图片坐标(通过逆矩阵)
|
var leftOffset = zoomFile.getZoomFileOffsetStartWidth(theScale);
|
||||||
// final inv = Matrix4.inverted(matrix);
|
var topOffset = max(0, imageHeightOffsetStart);
|
||||||
// final v = inv.transform3(Vector3(localPosition.dx, localPosition.dy, 0));
|
|
||||||
// localPosition = Offset(v.x, v.y);
|
|
||||||
|
|
||||||
/// 屏幕坐标 -> 图片坐标(通过逆矩阵)。当无缩放/平移时走快速路径,避免不必要求逆
|
// 优先使用变换矩阵获得当前内容平移(有符号,右/下为正,左/上为负)
|
||||||
/// 它们是 Matrix4 内部 4x4 矩阵的平移分量(列主序存储)。
|
final translation = logic.zoomController.value.getTranslation();
|
||||||
/// storage[12] 是平移的 x 分量,storage[13] 是平移的 y 分量(storage[14] 则是 z)。
|
final panDx = translation.x;
|
||||||
/// 因为 Matrix4 采用列主序,索引计算为 index = col * 4 + row,平移在第 4 列的前 3 行,所以是 12、13、14。
|
final panDy = translation.y;
|
||||||
/// 用途:当 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 在图片坐标系做边界校验(更直观):0..maxWidth、0..actualHeight
|
// 将屏幕触点换算到图片坐标:先去除图片在容器中的留白,再减去内容平移,最后按缩放反算
|
||||||
if (localPosition.dy < 0 || localPosition.dy > actualHeight) return;
|
final correctedDx = (localPosition.dx - leftOffset - panDx) / theScale;
|
||||||
if (localPosition.dx < 0 || localPosition.dx > maxWidth) return;
|
final correctedDy = (dy - topOffset - panDy) / theScale;
|
||||||
|
localPosition = Offset(correctedDx, correctedDy);
|
||||||
|
|
||||||
/// 判断当前点和上一个点的距离:
|
/// 判断当前点和上一个点的距离 判断是否是多指
|
||||||
/// - 跨平台跳变过滤(自适应缩放阈值)
|
if (Platform.isAndroid) {
|
||||||
/// - 同时加入最小采样间距,降采样以减少噪点与性能压力
|
var lastDrop = getLastDrop(vnHandWritings.value, vnHandWritings.value.length - 1);
|
||||||
var lastDrop = getLastDrop(vnHandWritings.value, vnHandWritings.value.length - 1);
|
if (lastDrop != null &&
|
||||||
if (lastDrop != null) {
|
((lastDrop.dx - localPosition.dx).abs() > 65 || (lastDrop.dy - localPosition.dy).abs() > 65)) {
|
||||||
const double baseJump = 65.0;
|
/// 当前X点和上一个x点相差 大于10判定为多个手指
|
||||||
final double jumpThreshold = baseJump / theScale;
|
return;
|
||||||
if ((lastDrop.dx - localPosition.dx).abs() > jumpThreshold ||
|
|
||||||
(lastDrop.dy - localPosition.dy).abs() > jumpThreshold) {
|
|
||||||
return; // 多指/跳变判定
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 最小采样间距(随缩放反向缩放)
|
|
||||||
if (lastDrop != null) {
|
|
||||||
const double baseDist = 1.6; // 基础距离
|
|
||||||
final double minDist = baseDist / theScale;
|
|
||||||
final double dx = localPosition.dx - lastDrop.dx;
|
|
||||||
final double dy = localPosition.dy - lastDrop.dy;
|
|
||||||
if ((dx * dx + dy * dy) < (minDist * minDist)) {
|
|
||||||
return; // 距离过近,忽略本次点
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// print("最终位置 : $localPosition");
|
// print("最终位置 : $localPosition");
|
||||||
|
|
@ -606,62 +576,77 @@ class QuestionImageView extends HookWidget with EventBusMixin<BottomOperationBar
|
||||||
sateData.handwritings = vnHandWritings.value;
|
sateData.handwritings = vnHandWritings.value;
|
||||||
},
|
},
|
||||||
child: Obx(() {
|
child: Obx(() {
|
||||||
|
var isPen = annotationState.pen.value;
|
||||||
var showZgtAnnotate = sateData.data.value?.showZgtAnnotate;
|
var showZgtAnnotate = sateData.data.value?.showZgtAnnotate;
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
height: double.infinity,
|
height: double.infinity,
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
child: IgnorePointer(
|
child: IgnorePointer(
|
||||||
// 仅当“滑动试题”启用时,让 Zoom 接收触摸;否则屏蔽以避免与批注冲突
|
// 仅当“滑动试题”启用时,让 Zoom 接收触摸;否则屏蔽以避免与批注冲突
|
||||||
ignoring: !annotationState.gestureMove.value,
|
ignoring: !annotationState.gestureMove.value,
|
||||||
child: Zoom(
|
child:
|
||||||
key: zoomKey.value,
|
// ZoomView(
|
||||||
// initTotalZoomOut: true, // 展示全部内容 初始化不产生滚动条
|
// key: zoomKey.value,
|
||||||
zoomSensibility: 0.05,
|
// viewWidth: maxWidth,
|
||||||
scrollWeight: 4.r,
|
// viewHeight: maxHeight,
|
||||||
doubleTapAnimDuration: Duration.zero,
|
// imageDisplayHeight: actualHeight,
|
||||||
maxZoomWidth: maxWidth,
|
// url: sateData.data.value!.zgtAnswer,
|
||||||
maxZoomHeight: actualHeight,
|
// onScale: logic.zoomLogic.onScaleUpdate,
|
||||||
canvasColor: Colors.transparent,
|
// // onTrans: (offset) {
|
||||||
doubleTapScaleChange: 1,
|
// // print("偏移位置:$offset");
|
||||||
initScale: 1,
|
// // },
|
||||||
backgroundColor: Colors.transparent,
|
// onContentOffset: logic.zoomLogic.onPanUpPosition,
|
||||||
// onScaleUpdate: (double scale, double zoom) => logic.zoomLogic.onScaleUpdate(zoom),
|
// ),
|
||||||
// onPositionUpdate: logic.zoomLogic.onPanUpPosition,
|
|
||||||
transformationController: logic.zoomController,
|
Obx(
|
||||||
child: Obx(() {
|
() => Zoom(
|
||||||
return Stack(
|
key: zoomKey.value,
|
||||||
children: [
|
// initTotalZoomOut: true, // 展示全部内容 初始化不产生滚动条
|
||||||
$TheCachedNetworkImage(
|
zoomSensibility: 0.05,
|
||||||
imgWidth: maxWidth,
|
scrollWeight: 4.r,
|
||||||
imageUrl: sateData.data.value!.zgtAnswer,
|
doubleTapAnimDuration: Duration.zero,
|
||||||
(_, imageProvider) => Image(image: imageProvider, fit: BoxFit.fitWidth),
|
maxZoomWidth: maxWidth,
|
||||||
),
|
maxZoomHeight: actualHeight,
|
||||||
RepaintBoundary(
|
canvasColor: Colors.transparent,
|
||||||
key: logic.pictureOverviewKey,
|
doubleTapScaleChange: 1,
|
||||||
child: CustomPaint(
|
// initPosition: initPosition.value,
|
||||||
isComplex: true,
|
// initScale: logic.zoomLogic.zoomState.initScale.value ?? 1,
|
||||||
willChange: true,
|
backgroundColor: Colors.transparent,
|
||||||
size: Size(maxWidth, actualHeight),
|
onScaleUpdate: (double scale, double zoom) => logic.zoomLogic.onScaleUpdate(zoom),
|
||||||
foregroundPainter: DrawingPainter(ctrl: vnHandWritings),
|
onPositionUpdate: logic.zoomLogic.onPanUpPosition,
|
||||||
// child: $TheCachedNetworkImage(
|
transformationController: logic.zoomController,
|
||||||
// imgWidth: maxWidth,
|
child: Stack(
|
||||||
// imageUrl: showZgtAnnotate ?? sateData.data.value!.zgtAnswer,
|
children: [
|
||||||
// (_, imageProvider) => Image(image: imageProvider, fit: BoxFit.fitWidth),
|
$TheCachedNetworkImage(
|
||||||
// ),
|
imgWidth: maxWidth,
|
||||||
child: showZgtAnnotate != null
|
imageUrl: sateData.data.value!.zgtAnswer,
|
||||||
? $TheCachedNetworkImage(
|
(_, imageProvider) => Image(image: imageProvider, fit: BoxFit.fitWidth),
|
||||||
imgWidth: maxWidth,
|
|
||||||
imageUrl: showZgtAnnotate,
|
|
||||||
(_, imageProvider) => Image(image: imageProvider, fit: BoxFit.fitWidth))
|
|
||||||
: null,
|
|
||||||
),
|
),
|
||||||
),
|
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 +659,19 @@ class DrawingPainter extends CustomPainter {
|
||||||
final Paint paintBrush = Paint()
|
final Paint paintBrush = Paint()
|
||||||
..color = Colors.red
|
..color = Colors.red
|
||||||
..strokeCap = StrokeCap.round
|
..strokeCap = StrokeCap.round
|
||||||
..strokeJoin = StrokeJoin.round
|
..strokeWidth = 0.7.sp;
|
||||||
..isAntiAlias = true
|
|
||||||
..strokeWidth = 0.7.sp
|
|
||||||
..style = PaintingStyle.stroke;
|
|
||||||
DrawingPainter({required this.ctrl}) : super(repaint: ctrl);
|
DrawingPainter({required this.ctrl}) : super(repaint: ctrl);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void paint(Canvas canvas, Size size) {
|
void paint(Canvas canvas, Size size) {
|
||||||
final List<dynamic> points = ctrl.value;
|
var points = ctrl.value;
|
||||||
if (points.isEmpty) return;
|
var pointsLength = points.length;
|
||||||
|
for (int i = 0; i < pointsLength; i++) {
|
||||||
Path path = Path();
|
Offset? offsetData = points[i];
|
||||||
Offset? previous;
|
Offset? nextOffsetData = pointsLength - 1 == i ? null : points[i + 1];
|
||||||
for (int i = 0; i < points.length; i++) {
|
if (offsetData != null && nextOffsetData != null) {
|
||||||
final Offset? current = points[i] as Offset?;
|
canvas.drawLine(offsetData, nextOffsetData, paintBrush);
|
||||||
if (current == null) {
|
|
||||||
// 提交当前子路径
|
|
||||||
if (path.computeMetrics().isNotEmpty) {
|
|
||||||
canvas.drawPath(path, paintBrush);
|
|
||||||
}
|
|
||||||
path = Path();
|
|
||||||
previous = null;
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,7 @@ class HomeworkReviewLogic extends GetxController with RequestToolMixin, EventBus
|
||||||
|
|
||||||
StreamSubscription<TestQuestionsImageInfo?>? imageScaleZoomStream;
|
StreamSubscription<TestQuestionsImageInfo?>? imageScaleZoomStream;
|
||||||
|
|
||||||
zoomWidget.TransformationController? zoomController;
|
late final zoomWidget.TransformationController zoomController;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
|
|
@ -105,6 +105,15 @@ class HomeworkReviewLogic extends GetxController with RequestToolMixin, EventBus
|
||||||
// 试题数据
|
// 试题数据
|
||||||
_dataListen = state.data.listen((e) {
|
_dataListen = state.data.listen((e) {
|
||||||
if (e == null) return;
|
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;
|
if (state.favorite.value != e.isFav) state.favorite.value = !state.favorite.value;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -138,7 +147,7 @@ class HomeworkReviewLogic extends GetxController with RequestToolMixin, EventBus
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onClose() {
|
void onClose() {
|
||||||
zoomController?.dispose();
|
zoomController.dispose();
|
||||||
// SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: [SystemUiOverlay.top]); // 屏幕刘海
|
// SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: [SystemUiOverlay.top]); // 屏幕刘海
|
||||||
eventCancel();
|
eventCancel();
|
||||||
_dataListen.cancel();
|
_dataListen.cancel();
|
||||||
|
|
@ -164,7 +173,7 @@ class HomeworkReviewLogic extends GetxController with RequestToolMixin, EventBus
|
||||||
state.studentQuestions.value = data.studentQuestions; // 赋值试题集合
|
state.studentQuestions.value = data.studentQuestions; // 赋值试题集合
|
||||||
state.data.value = data;
|
state.data.value = data;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// 移除冗余打印,使用提示替代
|
print('获取数据报错了:$e');
|
||||||
state.getDataError.value = true;
|
state.getDataError.value = true;
|
||||||
ToastUtils.showError('获取试题数据失败,请检查网络连接后重试');
|
ToastUtils.showError('获取试题数据失败,请检查网络连接后重试');
|
||||||
} finally {
|
} finally {
|
||||||
|
|
@ -247,7 +256,8 @@ class HomeworkReviewLogic extends GetxController with RequestToolMixin, EventBus
|
||||||
|
|
||||||
return imgKey;
|
return imgKey;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// 图片上传失败日志移除,统一提示
|
print('图片上传失败');
|
||||||
|
print(e.toString());
|
||||||
ToastUtils.getFluttertoast(msg: '图片上传失败', context: context);
|
ToastUtils.getFluttertoast(msg: '图片上传失败', context: context);
|
||||||
} finally {
|
} finally {
|
||||||
// ToastUtils.dismiss();
|
// ToastUtils.dismiss();
|
||||||
|
|
@ -330,7 +340,7 @@ class HomeworkReviewLogic extends GetxController with RequestToolMixin, EventBus
|
||||||
ToastUtils.showSuccess("批阅已提交");
|
ToastUtils.showSuccess("批阅已提交");
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// 提交异常日志移除,统一提示
|
print("批阅提交报错 $e");
|
||||||
ToastUtils.showError('提交失败,请检查网络连接后重试');
|
ToastUtils.showError('提交失败,请检查网络连接后重试');
|
||||||
} finally {
|
} finally {
|
||||||
state.submitLoading.value = false;
|
state.submitLoading.value = false;
|
||||||
|
|
|
||||||
|
|
@ -17,43 +17,47 @@ class ZoomLogic extends GetxController {
|
||||||
);
|
);
|
||||||
|
|
||||||
var oldTemplateId;
|
var oldTemplateId;
|
||||||
StreamSubscription? _streamHomework;
|
late StreamSubscription _streamHomework;
|
||||||
StreamSubscription? _streamZoomState;
|
late StreamSubscription _streamZoomState;
|
||||||
|
|
||||||
StreamSubscription<double?>? initScaleStream;
|
StreamSubscription<double?>? initScaleStream;
|
||||||
|
|
||||||
Map<int, ZoomFileModel> imageSizeMap = {};
|
|
||||||
|
|
||||||
HomeworkReviewLogic get homeworkReviewLogic => Get.find<HomeworkReviewLogic>();
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
oldTemplateId = zoomState.zoomFile.value?.templateId;
|
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) {
|
WidgetsBinding.instance.addPostFrameCallback((e) {
|
||||||
/// 试题加载后更新尺寸对象(只要题号没有变就不更新尺寸对象)
|
/// 试题加载后更新尺寸对象(只要题号没有变就不更新尺寸对象)
|
||||||
_streamHomework = homeworkReviewLogic.state.data.listen((e) {
|
_streamHomework = Get.find<HomeworkReviewLogic>().state.data.listen((e) {
|
||||||
var zoomFile = zoomState.zoomFile.value;
|
print("HOMEWORKSTATE 变化了");
|
||||||
if (e == null || zoomFile == null) return;
|
if (e == null || zoomState.zoomFile.value == null) return;
|
||||||
|
zoomState.zoomFile.value!.templateId = e.templateId;
|
||||||
if (zoomFile.templateId != e.templateId) {
|
print("666666 ${e.templateId}");
|
||||||
// zoom 题号判断是否有变 有变就需要清空 zoom文件
|
zoomState.zoomFile.value = ZoomFileModel.fromJson(zoomState.zoomFile.value!.toJson());
|
||||||
zoomState.initScale.value = null;
|
|
||||||
zoomFile = ZoomFileModel(
|
|
||||||
templateId: e.templateId,
|
|
||||||
viewWidth: zoomFile.viewWidth,
|
|
||||||
viewHeight: zoomFile.viewHeight,
|
|
||||||
);
|
|
||||||
|
|
||||||
/// 重置缩放和位置
|
|
||||||
_resetZoom();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 更新尺寸对象
|
|
||||||
refreshZoomFile(zoomFile);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -65,53 +69,15 @@ class ZoomLogic extends GetxController {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onClose() {
|
void onClose() {
|
||||||
_streamHomework?.cancel();
|
_streamHomework.cancel();
|
||||||
_streamZoomState?.cancel();
|
_streamZoomState.cancel();
|
||||||
super.onClose();
|
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 {
|
void onScaleUpdate(double zoom) async {
|
||||||
|
print("缩放比例:$zoom");
|
||||||
|
|
||||||
/// 防抖
|
/// 防抖
|
||||||
zoomState.initScale.value = zoom;
|
zoomState.initScale.value = zoom;
|
||||||
// anti_shake_throttling.debounce(() => zoomState.initScale.value = zoom, const Duration(milliseconds: 100))();
|
// 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 {
|
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.zoomOffset = val;
|
||||||
state.slide.value = val.dy.abs().toInt().toDouble();
|
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 {
|
class HomeworkReviewZoomBinding extends Bindings {
|
||||||
|
|
@ -185,19 +143,18 @@ class ZoomFileModel extends Object {
|
||||||
this.imageHeightOffsetend,
|
this.imageHeightOffsetend,
|
||||||
this.pixelRatio,
|
this.pixelRatio,
|
||||||
}) {
|
}) {
|
||||||
/// 初始化尺寸信息
|
|
||||||
// 图片已视图宽为基准,高度自适应可滑动 图片的实际宽高都需要乘此基准值.
|
// 图片已视图宽为基准,高度自适应可滑动 图片的实际宽高都需要乘此基准值.
|
||||||
if (fileHeight != null && fileWidth != null) {
|
if (fileHeight == null || fileWidth == null) return;
|
||||||
scaleRatio = viewWidth / fileWidth!;
|
|
||||||
actualWidth = fileWidth! * scaleRatio;
|
|
||||||
actualHeight = fileHeight! * scaleRatio;
|
|
||||||
|
|
||||||
pixelRatio = fileWidth! / viewWidth; // 图片在此设备的像素比例
|
scaleRatio = viewWidth / fileWidth!;
|
||||||
|
actualWidth = fileWidth! * scaleRatio;
|
||||||
|
actualHeight = fileHeight! * scaleRatio;
|
||||||
|
|
||||||
remainingHeight = viewHeight - actualHeight!;
|
pixelRatio = fileWidth! / viewWidth; // 图片在此设备的像素比例
|
||||||
imageHeightOffsetStart = remainingHeight! / 2;
|
|
||||||
imageHeightOffsetend = imageHeightOffsetStart! + actualHeight!;
|
remainingHeight = viewHeight - actualHeight!;
|
||||||
}
|
imageHeightOffsetStart = remainingHeight! / 2;
|
||||||
|
imageHeightOffsetend = imageHeightOffsetStart! + actualHeight!;
|
||||||
}
|
}
|
||||||
|
|
||||||
factory ZoomFileModel.fromJson(Map<String, dynamic> srcJson) => _$ZoomFileModelFromJson(srcJson);
|
factory ZoomFileModel.fromJson(Map<String, dynamic> srcJson) => _$ZoomFileModelFromJson(srcJson);
|
||||||
|
|
|
||||||
|
|
@ -1,247 +1,13 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:making_school_asignment_app/common/utils/platform_utils.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:making_school_asignment_app/page/home_page/children/my_info_desktop.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:making_school_asignment_app/page/home_page/children/my_info_mobile.dart';
|
||||||
import 'package:making_school_asignment_app/common/job/user_info_detail.dart';
|
|
||||||
import 'package:making_school_asignment_app/common/store/app_storage_key.dart';
|
|
||||||
import 'package:making_school_asignment_app/common/store/user_store.dart';
|
|
||||||
import 'package:making_school_asignment_app/common/utils/storage.dart';
|
|
||||||
import 'package:making_school_asignment_app/page/global_widget/my_text.dart';
|
|
||||||
import 'package:making_school_asignment_app/routes/app_pages.dart';
|
|
||||||
|
|
||||||
class MyInfo extends StatefulWidget {
|
class MyInfo extends StatelessWidget {
|
||||||
const MyInfo({super.key});
|
const MyInfo({super.key});
|
||||||
|
|
||||||
@override
|
|
||||||
State<MyInfo> createState() => _MyInfoState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _MyInfoState extends State<MyInfo> with AutomaticKeepAliveClientMixin {
|
|
||||||
late Rx<UserInfoDetail?> userInfo = UserStore.to.userDetailInfo;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle(
|
|
||||||
statusBarIconBrightness: Brightness.light,
|
|
||||||
statusBarColor: Colors.transparent, //状态栏背景颜色
|
|
||||||
// systemStatusBarContrastEnforced: false,
|
|
||||||
));
|
|
||||||
super.initState();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 确认对话框
|
|
||||||
_showAlertDialog(context1) async {
|
|
||||||
await showDialog(
|
|
||||||
// 表示点击灰色背景的时候是否消失弹出框
|
|
||||||
barrierDismissible: false,
|
|
||||||
context: context1,
|
|
||||||
builder: (context) {
|
|
||||||
return AlertDialog(title: quickText("提示信息"), content: quickText("您确定要退出登录吗?"), actions: <Widget>[
|
|
||||||
TextButton(
|
|
||||||
child: quickText("取消"),
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.pop(context, 'Cancle');
|
|
||||||
},
|
|
||||||
),
|
|
||||||
TextButton(
|
|
||||||
child: quickText("确定"),
|
|
||||||
onPressed: () async {
|
|
||||||
try {
|
|
||||||
var pwd = StorageService.to.read(AppStorageKey.pwd.value);
|
|
||||||
var account = StorageService.to.read(AppStorageKey.account.value);
|
|
||||||
|
|
||||||
UserStore.to.erase();
|
|
||||||
await StorageService.to.erase();
|
|
||||||
StorageService.to.write(AppStorageKey.privacyAgreement.value, true);
|
|
||||||
StorageService.to.write(AppStorageKey.account.value, account);
|
|
||||||
StorageService.to.write(AppStorageKey.pwd.value, pwd);
|
|
||||||
Navigator.pop(context, "Ok");
|
|
||||||
Get.offAllNamed(Routes.login);
|
|
||||||
} catch (e) {
|
|
||||||
print(e);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool get wantKeepAlive => true;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
super.build(context);
|
return PlatformUtils.isDesktop ? const MyInfoDesktop() : const MyInfoMobile();
|
||||||
|
|
||||||
final personalInfoTitleStly = TextStyle(
|
|
||||||
color: const Color.fromRGBO(80, 87, 103, 1),
|
|
||||||
fontSize: 13.sp,
|
|
||||||
);
|
|
||||||
final personalInfoValStly = TextStyle(
|
|
||||||
color: const Color.fromRGBO(148, 163, 182, 1),
|
|
||||||
fontSize: 13.sp,
|
|
||||||
);
|
|
||||||
|
|
||||||
return Stack(
|
|
||||||
children: [
|
|
||||||
SizedBox(
|
|
||||||
height: double.infinity,
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
height: 220.h,
|
|
||||||
width: double.infinity,
|
|
||||||
decoration: const BoxDecoration(
|
|
||||||
image: DecorationImage(
|
|
||||||
image: AssetImage('assets/images/personal_bg.png'),
|
|
||||||
fit: BoxFit.cover,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: Container(
|
|
||||||
color: const Color.fromRGBO(248, 248, 248, 1),
|
|
||||||
))
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SafeArea(
|
|
||||||
child: Scaffold(
|
|
||||||
backgroundColor: Colors.transparent,
|
|
||||||
body: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
height: 150.h,
|
|
||||||
padding: EdgeInsets.only(left: 20.r, right: 20.r),
|
|
||||||
// alignment: Alignment.center,
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Image.asset(
|
|
||||||
'assets/images/default_user_dead.png',
|
|
||||||
),
|
|
||||||
SizedBox(
|
|
||||||
width: 5.w,
|
|
||||||
),
|
|
||||||
InkWell(
|
|
||||||
onTap: () {
|
|
||||||
/*if (tokenState == '' || userState.id == '') {
|
|
||||||
toLoginPage(context);
|
|
||||||
}*/
|
|
||||||
},
|
|
||||||
child: Container(
|
|
||||||
margin: EdgeInsets.only(top: 0.h),
|
|
||||||
child: Text(
|
|
||||||
userInfo.value?.name ?? '请前往登录',
|
|
||||||
style:
|
|
||||||
TextStyle(fontSize: 16.sp, color: const Color(0xFF332A2A), fontWeight: FontWeight.w500),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const Spacer(),
|
|
||||||
InkWell(
|
|
||||||
onTap: () {
|
|
||||||
_showAlertDialog(context);
|
|
||||||
},
|
|
||||||
child: Container(
|
|
||||||
padding: EdgeInsets.all(2.r),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: const Color.fromRGBO(255, 255, 255, 1),
|
|
||||||
borderRadius: BorderRadius.circular(50.r),
|
|
||||||
),
|
|
||||||
child: Image.asset('assets/images/out_icon.png', fit: BoxFit.cover),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(height: 14.h),
|
|
||||||
Container(
|
|
||||||
margin: EdgeInsets.symmetric(vertical: 5.h, horizontal: 16.w),
|
|
||||||
padding: EdgeInsets.symmetric(vertical: 15.h, horizontal: 16.w),
|
|
||||||
alignment: Alignment.center,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
borderRadius: BorderRadius.all(Radius.circular(6.w)),
|
|
||||||
color: Colors.white,
|
|
||||||
boxShadow: const [
|
|
||||||
BoxShadow(
|
|
||||||
color: Color.fromRGBO(46, 91, 255, 0.1),
|
|
||||||
offset: Offset.zero, //阴影y轴偏移量
|
|
||||||
blurRadius: 20, //阴影模糊程度
|
|
||||||
spreadRadius: 10, //阴影扩散程度
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Text('账号', style: personalInfoTitleStly),
|
|
||||||
Text(userInfo.value?.name ?? '请前往登录', style: personalInfoValStly)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Container(
|
|
||||||
margin: EdgeInsets.symmetric(vertical: 5.h, horizontal: 16.w),
|
|
||||||
padding: EdgeInsets.symmetric(vertical: 15.h, horizontal: 16.w),
|
|
||||||
alignment: Alignment.center,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
borderRadius: BorderRadius.all(Radius.circular(6.w)),
|
|
||||||
color: Colors.white,
|
|
||||||
boxShadow: const [
|
|
||||||
BoxShadow(
|
|
||||||
color: Color.fromRGBO(46, 91, 255, 0.1),
|
|
||||||
offset: Offset.zero, //阴影y轴偏移量
|
|
||||||
blurRadius: 20, //阴影模糊程度
|
|
||||||
spreadRadius: 10, //阴影扩散程度
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Text('所在学校', style: personalInfoTitleStly),
|
|
||||||
Text(userInfo.value?.schoolName ?? '', style: personalInfoValStly)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Container(
|
|
||||||
margin: EdgeInsets.symmetric(vertical: 5.h, horizontal: 16.w),
|
|
||||||
padding: EdgeInsets.symmetric(vertical: 15.h, horizontal: 16.w),
|
|
||||||
alignment: Alignment.center,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
borderRadius: BorderRadius.all(Radius.circular(6.w)),
|
|
||||||
color: Colors.white,
|
|
||||||
boxShadow: const [
|
|
||||||
BoxShadow(
|
|
||||||
color: Color.fromRGBO(46, 91, 255, 0.1),
|
|
||||||
offset: Offset.zero, //阴影y轴偏移量
|
|
||||||
blurRadius: 20, //阴影模糊程度
|
|
||||||
spreadRadius: 10, //阴影扩散程度
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
child: InkWell(
|
|
||||||
onTap: () {
|
|
||||||
Get.toNamed(Routes.otherPage);
|
|
||||||
},
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Text('其他', style: personalInfoTitleStly),
|
|
||||||
Icon(
|
|
||||||
Icons.arrow_forward_ios,
|
|
||||||
color: const Color.fromRGBO(80, 87, 103, 1),
|
|
||||||
size: 13.sp,
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,229 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:making_school_asignment_app/common/job/user_info_detail.dart';
|
||||||
|
import 'package:making_school_asignment_app/common/store/app_storage_key.dart';
|
||||||
|
import 'package:making_school_asignment_app/common/store/user_store.dart';
|
||||||
|
import 'package:making_school_asignment_app/common/utils/storage.dart';
|
||||||
|
import 'package:making_school_asignment_app/routes/app_pages.dart';
|
||||||
|
|
||||||
|
class MyInfoDesktop extends StatefulWidget {
|
||||||
|
const MyInfoDesktop({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<MyInfoDesktop> createState() => _MyInfoDesktopState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MyInfoDesktopState extends State<MyInfoDesktop> with AutomaticKeepAliveClientMixin {
|
||||||
|
late Rx<UserInfoDetail?> userInfo = UserStore.to.userDetailInfo;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get wantKeepAlive => true;
|
||||||
|
|
||||||
|
// 确认对话框
|
||||||
|
_showLogoutDialog(BuildContext context) async {
|
||||||
|
return showDialog<bool>(
|
||||||
|
context: context,
|
||||||
|
barrierDismissible: false,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: const Text('退出登录'),
|
||||||
|
content: const Text('您确定要退出登录吗?'),
|
||||||
|
actions: <Widget>[
|
||||||
|
TextButton(
|
||||||
|
child: const Text('取消'),
|
||||||
|
onPressed: () => Navigator.of(context).pop(false),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
child: const Text('确定'),
|
||||||
|
onPressed: () async {
|
||||||
|
try {
|
||||||
|
var pwd = StorageService.to.read(AppStorageKey.pwd.value);
|
||||||
|
var account = StorageService.to.read(AppStorageKey.account.value);
|
||||||
|
|
||||||
|
UserStore.to.erase();
|
||||||
|
await StorageService.to.erase();
|
||||||
|
StorageService.to.write(AppStorageKey.privacyAgreement.value, true);
|
||||||
|
StorageService.to.write(AppStorageKey.account.value, account);
|
||||||
|
StorageService.to.write(AppStorageKey.pwd.value, pwd);
|
||||||
|
Navigator.of(context).pop(true);
|
||||||
|
Get.offAllNamed(Routes.login);
|
||||||
|
} catch (e) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
super.build(context);
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: const Color(0xFFF0F2F5),
|
||||||
|
body: SingleChildScrollView(
|
||||||
|
padding: EdgeInsets.all(24.r),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
_buildHeader(),
|
||||||
|
SizedBox(height: 24.h),
|
||||||
|
_buildUserCard(),
|
||||||
|
SizedBox(height: 16.h),
|
||||||
|
_buildInfoCards(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildHeader() {
|
||||||
|
return Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'个人信息',
|
||||||
|
style: TextStyle(fontSize: 24.sp, fontWeight: FontWeight.bold, color: const Color(0xFF333333)),
|
||||||
|
),
|
||||||
|
ElevatedButton.icon(
|
||||||
|
onPressed: () => _showLogoutDialog(context),
|
||||||
|
icon: const Icon(Icons.logout, size: 18),
|
||||||
|
label: const Text('退出登录'),
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Colors.red[50],
|
||||||
|
foregroundColor: Colors.red[700],
|
||||||
|
elevation: 0,
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8.r)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildUserCard() {
|
||||||
|
return Card(
|
||||||
|
elevation: 2,
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12.r)),
|
||||||
|
child: Container(
|
||||||
|
padding: EdgeInsets.all(24.r),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
width: 80.r,
|
||||||
|
height: 80.r,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(40.r),
|
||||||
|
color: Colors.grey[200],
|
||||||
|
),
|
||||||
|
child: Icon(Icons.person, size: 40.r, color: Colors.grey[600]),
|
||||||
|
),
|
||||||
|
SizedBox(width: 24.w),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
userInfo.value?.name ?? '未登录用户',
|
||||||
|
style: TextStyle(fontSize: 20.sp, fontWeight: FontWeight.bold, color: const Color(0xFF333333)),
|
||||||
|
),
|
||||||
|
SizedBox(height: 8.h),
|
||||||
|
Text(
|
||||||
|
userInfo.value?.schoolName ?? '暂无学校信息',
|
||||||
|
style: TextStyle(fontSize: 14.sp, color: Colors.grey[600]),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildInfoCards() {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
_buildInfoCard('账号信息', userInfo.value?.name ?? '请前往登录', Icons.account_circle),
|
||||||
|
SizedBox(height: 12.h),
|
||||||
|
_buildInfoCard('所在学校', userInfo.value?.schoolName ?? '暂无', Icons.school),
|
||||||
|
SizedBox(height: 12.h),
|
||||||
|
_buildClickableInfoCard('其他设置', '点击查看更多选项', Icons.settings, () {
|
||||||
|
Get.toNamed(Routes.otherPage);
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildInfoCard(String title, String value, IconData icon) {
|
||||||
|
return Card(
|
||||||
|
elevation: 1,
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8.r)),
|
||||||
|
child: Container(
|
||||||
|
padding: EdgeInsets.all(16.r),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Icon(icon, size: 24.r, color: Colors.grey[600]),
|
||||||
|
SizedBox(width: 16.w),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
title,
|
||||||
|
style: TextStyle(fontSize: 14.sp, fontWeight: FontWeight.w500, color: const Color(0xFF333333)),
|
||||||
|
),
|
||||||
|
SizedBox(height: 4.h),
|
||||||
|
Text(
|
||||||
|
value,
|
||||||
|
style: TextStyle(fontSize: 13.sp, color: Colors.grey[600]),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildClickableInfoCard(String title, String subtitle, IconData icon, VoidCallback onTap) {
|
||||||
|
return Card(
|
||||||
|
elevation: 1,
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8.r)),
|
||||||
|
child: InkWell(
|
||||||
|
onTap: onTap,
|
||||||
|
borderRadius: BorderRadius.circular(8.r),
|
||||||
|
child: Container(
|
||||||
|
padding: EdgeInsets.all(16.r),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Icon(icon, size: 24.r, color: Colors.grey[600]),
|
||||||
|
SizedBox(width: 16.w),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
title,
|
||||||
|
style: TextStyle(fontSize: 14.sp, fontWeight: FontWeight.w500, color: const Color(0xFF333333)),
|
||||||
|
),
|
||||||
|
SizedBox(height: 4.h),
|
||||||
|
Text(
|
||||||
|
subtitle,
|
||||||
|
style: TextStyle(fontSize: 13.sp, color: Colors.grey[600]),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Icon(Icons.arrow_forward_ios, size: 16.r, color: Colors.grey[400]),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,247 @@
|
||||||
|
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/job/user_info_detail.dart';
|
||||||
|
import 'package:making_school_asignment_app/common/store/app_storage_key.dart';
|
||||||
|
import 'package:making_school_asignment_app/common/store/user_store.dart';
|
||||||
|
import 'package:making_school_asignment_app/common/utils/storage.dart';
|
||||||
|
import 'package:making_school_asignment_app/page/global_widget/my_text.dart';
|
||||||
|
import 'package:making_school_asignment_app/routes/app_pages.dart';
|
||||||
|
|
||||||
|
class MyInfoMobile extends StatefulWidget {
|
||||||
|
const MyInfoMobile({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<MyInfoMobile> createState() => _MyInfoMobileState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MyInfoMobileState extends State<MyInfoMobile> with AutomaticKeepAliveClientMixin {
|
||||||
|
late Rx<UserInfoDetail?> userInfo = UserStore.to.userDetailInfo;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle(
|
||||||
|
statusBarIconBrightness: Brightness.light,
|
||||||
|
statusBarColor: Colors.transparent, //状态栏背景颜色
|
||||||
|
// systemStatusBarContrastEnforced: false,
|
||||||
|
));
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确认对话框
|
||||||
|
_showAlertDialog(context1) async {
|
||||||
|
await showDialog(
|
||||||
|
// 表示点击灰色背景的时候是否消失弹出框
|
||||||
|
barrierDismissible: false,
|
||||||
|
context: context1,
|
||||||
|
builder: (context) {
|
||||||
|
return AlertDialog(title: quickText("提示信息"), content: quickText("您确定要退出登录吗?"), actions: <Widget>[
|
||||||
|
TextButton(
|
||||||
|
child: quickText("取消"),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.pop(context, 'Cancle');
|
||||||
|
},
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
child: quickText("确定"),
|
||||||
|
onPressed: () async {
|
||||||
|
try {
|
||||||
|
var pwd = StorageService.to.read(AppStorageKey.pwd.value);
|
||||||
|
var account = StorageService.to.read(AppStorageKey.account.value);
|
||||||
|
|
||||||
|
UserStore.to.erase();
|
||||||
|
await StorageService.to.erase();
|
||||||
|
StorageService.to.write(AppStorageKey.privacyAgreement.value, true);
|
||||||
|
StorageService.to.write(AppStorageKey.account.value, account);
|
||||||
|
StorageService.to.write(AppStorageKey.pwd.value, pwd);
|
||||||
|
Navigator.pop(context, "Ok");
|
||||||
|
Get.offAllNamed(Routes.login);
|
||||||
|
} catch (e) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get wantKeepAlive => true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
super.build(context);
|
||||||
|
|
||||||
|
final personalInfoTitleStly = TextStyle(
|
||||||
|
color: const Color.fromRGBO(80, 87, 103, 1),
|
||||||
|
fontSize: 13.sp,
|
||||||
|
);
|
||||||
|
final personalInfoValStly = TextStyle(
|
||||||
|
color: const Color.fromRGBO(148, 163, 182, 1),
|
||||||
|
fontSize: 13.sp,
|
||||||
|
);
|
||||||
|
|
||||||
|
return Stack(
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
height: double.infinity,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
height: 220.h,
|
||||||
|
width: double.infinity,
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
image: DecorationImage(
|
||||||
|
image: AssetImage('assets/images/personal_bg.png'),
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Container(
|
||||||
|
color: const Color.fromRGBO(248, 248, 248, 1),
|
||||||
|
))
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SafeArea(
|
||||||
|
child: Scaffold(
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
body: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
height: 150.h,
|
||||||
|
padding: EdgeInsets.only(left: 20.r, right: 20.r),
|
||||||
|
// alignment: Alignment.center,
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Image.asset(
|
||||||
|
'assets/images/default_user_dead.png',
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
width: 5.w,
|
||||||
|
),
|
||||||
|
InkWell(
|
||||||
|
onTap: () {
|
||||||
|
/*if (tokenState == '' || userState.id == '') {
|
||||||
|
toLoginPage(context);
|
||||||
|
}*/
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
margin: EdgeInsets.only(top: 0.h),
|
||||||
|
child: Text(
|
||||||
|
userInfo.value?.name ?? '请前往登录',
|
||||||
|
style:
|
||||||
|
TextStyle(fontSize: 16.sp, color: const Color(0xFF332A2A), fontWeight: FontWeight.w500),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
InkWell(
|
||||||
|
onTap: () {
|
||||||
|
_showAlertDialog(context);
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
padding: EdgeInsets.all(2.r),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: const Color.fromRGBO(255, 255, 255, 1),
|
||||||
|
borderRadius: BorderRadius.circular(50.r),
|
||||||
|
),
|
||||||
|
child: Image.asset('assets/images/out_icon.png', fit: BoxFit.cover),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 14.h),
|
||||||
|
Container(
|
||||||
|
margin: EdgeInsets.symmetric(vertical: 5.h, horizontal: 16.w),
|
||||||
|
padding: EdgeInsets.symmetric(vertical: 15.h, horizontal: 16.w),
|
||||||
|
alignment: Alignment.center,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(6.w)),
|
||||||
|
color: Colors.white,
|
||||||
|
boxShadow: const [
|
||||||
|
BoxShadow(
|
||||||
|
color: Color.fromRGBO(46, 91, 255, 0.1),
|
||||||
|
offset: Offset.zero, //阴影y轴偏移量
|
||||||
|
blurRadius: 20, //阴影模糊程度
|
||||||
|
spreadRadius: 10, //阴影扩散程度
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text('账号', style: personalInfoTitleStly),
|
||||||
|
Text(userInfo.value?.name ?? '请前往登录', style: personalInfoValStly)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
margin: EdgeInsets.symmetric(vertical: 5.h, horizontal: 16.w),
|
||||||
|
padding: EdgeInsets.symmetric(vertical: 15.h, horizontal: 16.w),
|
||||||
|
alignment: Alignment.center,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(6.w)),
|
||||||
|
color: Colors.white,
|
||||||
|
boxShadow: const [
|
||||||
|
BoxShadow(
|
||||||
|
color: Color.fromRGBO(46, 91, 255, 0.1),
|
||||||
|
offset: Offset.zero, //阴影y轴偏移量
|
||||||
|
blurRadius: 20, //阴影模糊程度
|
||||||
|
spreadRadius: 10, //阴影扩散程度
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text('所在学校', style: personalInfoTitleStly),
|
||||||
|
Text(userInfo.value?.schoolName ?? '', style: personalInfoValStly)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
margin: EdgeInsets.symmetric(vertical: 5.h, horizontal: 16.w),
|
||||||
|
padding: EdgeInsets.symmetric(vertical: 15.h, horizontal: 16.w),
|
||||||
|
alignment: Alignment.center,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(6.w)),
|
||||||
|
color: Colors.white,
|
||||||
|
boxShadow: const [
|
||||||
|
BoxShadow(
|
||||||
|
color: Color.fromRGBO(46, 91, 255, 0.1),
|
||||||
|
offset: Offset.zero, //阴影y轴偏移量
|
||||||
|
blurRadius: 20, //阴影模糊程度
|
||||||
|
spreadRadius: 10, //阴影扩散程度
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () {
|
||||||
|
Get.toNamed(Routes.otherPage);
|
||||||
|
},
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text('其他', style: personalInfoTitleStly),
|
||||||
|
Icon(
|
||||||
|
Icons.arrow_forward_ios,
|
||||||
|
color: const Color.fromRGBO(80, 87, 103, 1),
|
||||||
|
size: 13.sp,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,175 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:making_school_asignment_app/common/mixins/getx_keepalive_widget.dart';
|
||||||
|
import 'package:making_school_asignment_app/page/global_widget/my_text.dart';
|
||||||
|
import 'package:making_school_asignment_app/page/home_page/children/read_over/widget/annotate_list.dart';
|
||||||
|
import 'package:making_school_asignment_app/routes/app_pages.dart';
|
||||||
|
|
||||||
|
import 'read_over_logic.dart';
|
||||||
|
|
||||||
|
class ReadOverDesktopPage extends GetxKeepAliveWidget<ReadOverLogic> {
|
||||||
|
const ReadOverDesktopPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget buildContent(BuildContext context, ReadOverLogic controller) {
|
||||||
|
final state = controller.state;
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: const Color(0xFFF0F2F5),
|
||||||
|
body: Column(
|
||||||
|
children: [
|
||||||
|
_buildHeader(context, controller, state),
|
||||||
|
Expanded(
|
||||||
|
child: Container(
|
||||||
|
padding: EdgeInsets.all(24.r),
|
||||||
|
child: Card(
|
||||||
|
elevation: 2,
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12.r)),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
_buildTabBar(context, controller, state),
|
||||||
|
Expanded(
|
||||||
|
child: Obx(() {
|
||||||
|
return AnnotateList(
|
||||||
|
tabIndex: state.tabIndex.value,
|
||||||
|
assessType: 0,
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildHeader(BuildContext context, ReadOverLogic controller, dynamic state) {
|
||||||
|
return Container(
|
||||||
|
padding: EdgeInsets.all(24.r),
|
||||||
|
color: Colors.white,
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'作业批阅',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 24.sp,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: const Color(0xFF333333),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
Get.toNamed(Routes.studentHistoryWorkPage, arguments: {'page': 'set'});
|
||||||
|
},
|
||||||
|
icon: Icon(
|
||||||
|
const IconData(0xe63e, fontFamily: "AlibabaIcon"),
|
||||||
|
color: const Color.fromRGBO(44, 48, 63, 1),
|
||||||
|
size: 24.sp,
|
||||||
|
),
|
||||||
|
tooltip: '设置',
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
onPressed: () => Get.back(),
|
||||||
|
icon: Icon(Icons.close, size: 24.sp, color: Colors.grey[600]),
|
||||||
|
tooltip: '关闭',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildTabBar(BuildContext context, ReadOverLogic controller, dynamic state) {
|
||||||
|
return Container(
|
||||||
|
padding: EdgeInsets.all(16.r),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: const Color.fromRGBO(243, 243, 243, 1),
|
||||||
|
borderRadius: BorderRadius.circular(8.r),
|
||||||
|
),
|
||||||
|
child: TabBar(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
indicatorPadding: EdgeInsets.zero,
|
||||||
|
indicatorWeight: 0,
|
||||||
|
labelPadding: EdgeInsets.symmetric(horizontal: 2.w),
|
||||||
|
controller: controller.tabController,
|
||||||
|
unselectedLabelStyle: TextStyle(
|
||||||
|
fontSize: 14.sp,
|
||||||
|
color: const Color.fromRGBO(69, 83, 100, 1),
|
||||||
|
),
|
||||||
|
labelStyle: TextStyle(
|
||||||
|
fontSize: 14.sp,
|
||||||
|
color: const Color(0xFF8C68FF),
|
||||||
|
),
|
||||||
|
indicator: const UnderlineTabIndicator(
|
||||||
|
borderSide: BorderSide(width: 0, color: Colors.transparent),
|
||||||
|
),
|
||||||
|
onTap: (index) {
|
||||||
|
state.tabIndex.value = index;
|
||||||
|
if (index == 1 && state.completedToRefresh) {
|
||||||
|
state.completedToRefresh = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tabs: <Widget>[
|
||||||
|
Tab(
|
||||||
|
iconMargin: EdgeInsets.zero,
|
||||||
|
height: 34.h,
|
||||||
|
child: Obx(() {
|
||||||
|
return Container(
|
||||||
|
width: 140.w,
|
||||||
|
alignment: Alignment.center,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: state.tabIndex.value == 0 ? const Color.fromRGBO(255, 255, 255, 1) : null,
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(8.r)),
|
||||||
|
),
|
||||||
|
child: quickText(
|
||||||
|
'待批阅',
|
||||||
|
size: 14.sp,
|
||||||
|
color: state.tabIndex.value == 0
|
||||||
|
? Theme.of(context).primaryColor
|
||||||
|
: const Color.fromRGBO(80, 94, 110, 1),
|
||||||
|
fontWeight: state.tabIndex.value == 0 ? FontWeight.bold : null,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
Tab(
|
||||||
|
iconMargin: EdgeInsets.zero,
|
||||||
|
height: 34.h,
|
||||||
|
child: Obx(() {
|
||||||
|
return Container(
|
||||||
|
width: 140.w,
|
||||||
|
alignment: Alignment.center,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: state.tabIndex.value == 1 ? const Color.fromRGBO(255, 255, 255, 1) : null,
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(8.r)),
|
||||||
|
),
|
||||||
|
child: quickText(
|
||||||
|
'已批阅',
|
||||||
|
size: 14.sp,
|
||||||
|
color: state.tabIndex.value == 1
|
||||||
|
? Theme.of(context).primaryColor
|
||||||
|
: const Color.fromRGBO(80, 94, 110, 1),
|
||||||
|
fontWeight: state.tabIndex.value == 1 ? FontWeight.bold : null,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,174 @@
|
||||||
|
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/mixins/getx_keepalive_widget.dart';
|
||||||
|
import 'package:making_school_asignment_app/page/global_widget/my_text.dart';
|
||||||
|
import 'package:making_school_asignment_app/page/home_page/children/read_over/widget/annotate_list.dart';
|
||||||
|
import 'package:making_school_asignment_app/routes/app_pages.dart';
|
||||||
|
|
||||||
|
import 'read_over_logic.dart';
|
||||||
|
|
||||||
|
class ReadOverMobilePage extends GetxKeepAliveWidget<ReadOverLogic> {
|
||||||
|
const ReadOverMobilePage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle(
|
||||||
|
statusBarColor: Colors.transparent,
|
||||||
|
statusBarIconBrightness: Brightness.dark,
|
||||||
|
systemStatusBarContrastEnforced: false,
|
||||||
|
));
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget buildContent(BuildContext context, ReadOverLogic controller) {
|
||||||
|
final state = controller.state;
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
body: SafeArea(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
height: 60.h,
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 10.w),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
flex: 1,
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () => Get.back(),
|
||||||
|
child: Container(
|
||||||
|
height: 30.h,
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
padding: EdgeInsets.only(left: 10.w),
|
||||||
|
child: Icon(Icons.arrow_back_ios_sharp, size: 16.sp),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
flex: 4,
|
||||||
|
child: Container(
|
||||||
|
padding: EdgeInsets.symmetric(vertical: 2.h),
|
||||||
|
alignment: Alignment.center,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: const Color.fromRGBO(243, 243, 243, 1),
|
||||||
|
borderRadius: BorderRadius.circular(8.r),
|
||||||
|
),
|
||||||
|
child: TabBar(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
indicatorPadding: EdgeInsets.zero,
|
||||||
|
indicatorWeight: 0,
|
||||||
|
labelPadding: EdgeInsets.symmetric(horizontal: 2.w),
|
||||||
|
controller: controller.tabController,
|
||||||
|
unselectedLabelStyle: TextStyle(
|
||||||
|
fontSize: 14.sp,
|
||||||
|
color: const Color.fromRGBO(69, 83, 100, 1),
|
||||||
|
),
|
||||||
|
labelStyle: TextStyle(
|
||||||
|
fontSize: 14.sp,
|
||||||
|
color: const Color(0xFF8C68FF),
|
||||||
|
),
|
||||||
|
indicator: const UnderlineTabIndicator(
|
||||||
|
borderSide: BorderSide(width: 0, color: Colors.transparent),
|
||||||
|
),
|
||||||
|
onTap: (index) {
|
||||||
|
state.tabIndex.value = index;
|
||||||
|
if (index == 1 && state.completedToRefresh) {
|
||||||
|
state.completedToRefresh = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tabs: <Widget>[
|
||||||
|
Tab(
|
||||||
|
iconMargin: EdgeInsets.zero,
|
||||||
|
height: 34.h,
|
||||||
|
child: Obx(() {
|
||||||
|
return Container(
|
||||||
|
width: 140.w,
|
||||||
|
alignment: Alignment.center,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: state.tabIndex.value == 0 ? const Color.fromRGBO(255, 255, 255, 1) : null,
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(8.r)),
|
||||||
|
),
|
||||||
|
child: quickText(
|
||||||
|
'待批阅',
|
||||||
|
size: 14.sp,
|
||||||
|
color: state.tabIndex.value == 0
|
||||||
|
? Theme.of(context).primaryColor
|
||||||
|
: const Color.fromRGBO(80, 94, 110, 1),
|
||||||
|
fontWeight: state.tabIndex.value == 0 ? FontWeight.bold : null,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
Tab(
|
||||||
|
iconMargin: EdgeInsets.zero,
|
||||||
|
height: 34.h,
|
||||||
|
child: Obx(() {
|
||||||
|
return Container(
|
||||||
|
width: 140.w,
|
||||||
|
alignment: Alignment.center,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: state.tabIndex.value == 1 ? const Color.fromRGBO(255, 255, 255, 1) : null,
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(8.r)),
|
||||||
|
),
|
||||||
|
child: quickText(
|
||||||
|
'已批阅',
|
||||||
|
size: 14.sp,
|
||||||
|
color: state.tabIndex.value == 1
|
||||||
|
? Theme.of(context).primaryColor
|
||||||
|
: const Color.fromRGBO(80, 94, 110, 1),
|
||||||
|
fontWeight: state.tabIndex.value == 1 ? FontWeight.bold : null,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
flex: 1,
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () {
|
||||||
|
Get.toNamed(Routes.studentHistoryWorkPage, arguments: {'page': 'set'});
|
||||||
|
},
|
||||||
|
child: Icon(
|
||||||
|
const IconData(0xe63e, fontFamily: "AlibabaIcon"),
|
||||||
|
color: const Color.fromRGBO(44, 48, 63, 1),
|
||||||
|
size: 24.sp,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Container(
|
||||||
|
color: const Color.fromRGBO(245, 245, 245, 1),
|
||||||
|
child: Obx(() {
|
||||||
|
return AnnotateList(
|
||||||
|
tabIndex: state.tabIndex.value,
|
||||||
|
assessType: 0,
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
Get.delete<ReadOverLogic>();
|
||||||
|
SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle(
|
||||||
|
statusBarColor: Colors.transparent,
|
||||||
|
statusBarIconBrightness: Brightness.light,
|
||||||
|
systemStatusBarContrastEnforced: false,
|
||||||
|
));
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,197 +1,13 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:making_school_asignment_app/common/utils/platform_utils.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:making_school_asignment_app/page/home_page/children/read_over/read_over_desktop_view.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:making_school_asignment_app/page/home_page/children/read_over/read_over_mobile_view.dart';
|
||||||
import 'package:making_school_asignment_app/page/global_widget/my_text.dart';
|
|
||||||
import 'package:making_school_asignment_app/page/home_page/children/read_over/widget/annotate_list.dart';
|
|
||||||
import 'package:making_school_asignment_app/routes/app_pages.dart';
|
|
||||||
|
|
||||||
import 'read_over_logic.dart';
|
class ReadOverPage extends StatelessWidget {
|
||||||
|
|
||||||
class ReadOverPage extends StatefulWidget {
|
|
||||||
const ReadOverPage({super.key});
|
const ReadOverPage({super.key});
|
||||||
|
|
||||||
@override
|
|
||||||
State<ReadOverPage> createState() => _ReadOverPageState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _ReadOverPageState extends State<ReadOverPage> {
|
|
||||||
final logic = Get.find<ReadOverLogic>();
|
|
||||||
final state = Get.find<ReadOverLogic>().state;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return AnnotatedRegion(
|
return PlatformUtils.isDesktop ? const ReadOverDesktopPage() : const ReadOverMobilePage();
|
||||||
value: const SystemUiOverlayStyle(
|
|
||||||
/* systemNavigationBarColor: Color(0xFF000000),
|
|
||||||
systemNavigationBarDividerColor: null,*/
|
|
||||||
statusBarColor: Colors.white,
|
|
||||||
systemNavigationBarDividerColor: null,
|
|
||||||
systemNavigationBarIconBrightness: Brightness.light,
|
|
||||||
statusBarIconBrightness: Brightness.light,
|
|
||||||
statusBarBrightness: Brightness.light,
|
|
||||||
),
|
|
||||||
child: Scaffold(
|
|
||||||
backgroundColor: Colors.white,
|
|
||||||
body: OrientationBuilder(
|
|
||||||
builder: (BuildContext context, Orientation orientation) {
|
|
||||||
return Column(
|
|
||||||
children: <Widget>[
|
|
||||||
Container(
|
|
||||||
color: Colors.white,
|
|
||||||
margin: EdgeInsets.only(top: MediaQuery.of(context).padding.top),
|
|
||||||
padding: EdgeInsets.only(bottom: 9.h, top: 4.h),
|
|
||||||
child: Row(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
flex: 1,
|
|
||||||
child: InkWell(
|
|
||||||
onTap: () {
|
|
||||||
Get.back();
|
|
||||||
},
|
|
||||||
child: Container(
|
|
||||||
height: 30.h,
|
|
||||||
alignment: Alignment.centerLeft,
|
|
||||||
padding: EdgeInsets.only(left: 10.w),
|
|
||||||
child: Icon(
|
|
||||||
Icons.arrow_back_ios_sharp,
|
|
||||||
size: 16.sp,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)),
|
|
||||||
Expanded(
|
|
||||||
flex: 4,
|
|
||||||
child: Container(
|
|
||||||
padding: EdgeInsets.symmetric(vertical: 2.h),
|
|
||||||
alignment: Alignment.center,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: const Color.fromRGBO(243, 243, 243, 1),
|
|
||||||
borderRadius: BorderRadius.circular(8.r),
|
|
||||||
),
|
|
||||||
child: TabBar(
|
|
||||||
padding: EdgeInsets.zero,
|
|
||||||
indicatorPadding: EdgeInsets.zero,
|
|
||||||
indicatorWeight: 0,
|
|
||||||
labelPadding: EdgeInsets.symmetric(horizontal: 2.w),
|
|
||||||
controller: logic.tabController,
|
|
||||||
unselectedLabelStyle: TextStyle(
|
|
||||||
fontSize: 14.sp,
|
|
||||||
color: const Color.fromRGBO(69, 83, 100, 1),
|
|
||||||
),
|
|
||||||
labelStyle: TextStyle(
|
|
||||||
fontSize: 14.sp,
|
|
||||||
color: const Color(0xFF8C68FF),
|
|
||||||
),
|
|
||||||
// labelColor: const Color.fromRGBO(45, 56, 76, 1),
|
|
||||||
indicator: const UnderlineTabIndicator(
|
|
||||||
borderSide: BorderSide(
|
|
||||||
width: 0, // 设置边界宽度为0,从而去除下划线
|
|
||||||
color: Colors.transparent),
|
|
||||||
),
|
|
||||||
onTap: (index) {
|
|
||||||
state.tabIndex.value = index;
|
|
||||||
if (index == 1 && state.completedToRefresh) {
|
|
||||||
// 已阅卷
|
|
||||||
// _refreshController2.callRefresh();
|
|
||||||
state.completedToRefresh = false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
tabs: <Widget>[
|
|
||||||
Tab(
|
|
||||||
iconMargin: EdgeInsets.zero,
|
|
||||||
height: 34.h,
|
|
||||||
child: Obx(() {
|
|
||||||
return Container(
|
|
||||||
width: 140.w,
|
|
||||||
alignment: Alignment.center,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: state.tabIndex.value == 0 ? const Color.fromRGBO(255, 255, 255, 1) : null,
|
|
||||||
borderRadius: BorderRadius.all(Radius.circular(8.r)),
|
|
||||||
),
|
|
||||||
child: quickText(
|
|
||||||
'待批阅',
|
|
||||||
size: 14.sp,
|
|
||||||
color: state.tabIndex.value == 0
|
|
||||||
? Theme.of(context).primaryColor
|
|
||||||
: const Color.fromRGBO(80, 94, 110, 1),
|
|
||||||
fontWeight: state.tabIndex.value == 0 ? FontWeight.bold : null,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
Tab(
|
|
||||||
iconMargin: EdgeInsets.zero,
|
|
||||||
height: 34.h,
|
|
||||||
child: Obx(() {
|
|
||||||
return Container(
|
|
||||||
width: 140.w,
|
|
||||||
alignment: Alignment.center,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: state.tabIndex.value == 1 ? const Color.fromRGBO(255, 255, 255, 1) : null,
|
|
||||||
borderRadius: BorderRadius.all(Radius.circular(8.r)),
|
|
||||||
),
|
|
||||||
child: quickText(
|
|
||||||
'已批阅',
|
|
||||||
size: 14.sp,
|
|
||||||
color: state.tabIndex.value == 1
|
|
||||||
? Theme.of(context).primaryColor
|
|
||||||
: const Color.fromRGBO(80, 94, 110, 1),
|
|
||||||
fontWeight: state.tabIndex.value == 1 ? FontWeight.bold : null,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
flex: 1,
|
|
||||||
child: InkWell(
|
|
||||||
onTap: () {
|
|
||||||
Get.toNamed(Routes.studentHistoryWorkPage, arguments: {'page': 'set'});
|
|
||||||
},
|
|
||||||
child: Icon(const IconData(0xe63e, fontFamily: "AlibabaIcon"),
|
|
||||||
color: const Color.fromRGBO(44, 48, 63, 1), size: 24.sp),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: Container(
|
|
||||||
color: const Color.fromRGBO(245, 245, 245, 1),
|
|
||||||
child: Obx(() {
|
|
||||||
return AnnotateList(
|
|
||||||
tabIndex: state.tabIndex.value,
|
|
||||||
assessType: 0,
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
Get.delete<ReadOverLogic>();
|
|
||||||
logic.homeController.getList();
|
|
||||||
SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle(
|
|
||||||
statusBarColor: Colors.transparent, //状态栏背景颜色
|
|
||||||
statusBarIconBrightness: Brightness.light,
|
|
||||||
systemStatusBarContrastEnforced: false,
|
|
||||||
));
|
|
||||||
super.dispose();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,7 @@ class PersonalDetailTopBar extends StatefulWidget {
|
||||||
final ValueChanged<int>? onTap;
|
final ValueChanged<int>? onTap;
|
||||||
final String customTimeStr;
|
final String customTimeStr;
|
||||||
final bool? hasAll;
|
final bool? hasAll;
|
||||||
const PersonalDetailTopBar(
|
const PersonalDetailTopBar({Key? key,required this.controller, this.onTap, required this.customTimeStr,this.hasAll=false}) : super(key: key);
|
||||||
{super.key, required this.controller, this.onTap, required this.customTimeStr, this.hasAll = false});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<PersonalDetailTopBar> createState() => _PersonalDetailTopBarState();
|
State<PersonalDetailTopBar> createState() => _PersonalDetailTopBarState();
|
||||||
|
|
@ -15,7 +14,7 @@ class PersonalDetailTopBar extends StatefulWidget {
|
||||||
|
|
||||||
class _PersonalDetailTopBarState extends State<PersonalDetailTopBar> {
|
class _PersonalDetailTopBarState extends State<PersonalDetailTopBar> {
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState(){
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -23,7 +22,9 @@ class _PersonalDetailTopBarState extends State<PersonalDetailTopBar> {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Container(
|
return Container(
|
||||||
alignment: Alignment.centerLeft,
|
alignment: Alignment.centerLeft,
|
||||||
decoration: BoxDecoration(border: Border(bottom: BorderSide(width: 1.r, color: const Color(0xFFCCCCCC)))),
|
decoration: BoxDecoration(
|
||||||
|
border: Border(bottom: BorderSide(width: 1.r,color: Color(0xFFCCCCCC)))
|
||||||
|
),
|
||||||
child: TabBar(
|
child: TabBar(
|
||||||
controller: widget.controller,
|
controller: widget.controller,
|
||||||
dividerHeight: 0,
|
dividerHeight: 0,
|
||||||
|
|
@ -32,22 +33,26 @@ class _PersonalDetailTopBarState extends State<PersonalDetailTopBar> {
|
||||||
labelStyle: TextStyle(
|
labelStyle: TextStyle(
|
||||||
fontSize: 12.sp,
|
fontSize: 12.sp,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
color: Theme.of(context).primaryColor,
|
color: const Color(0xFF4CC793),
|
||||||
),
|
),
|
||||||
isScrollable: true,
|
isScrollable: true,
|
||||||
labelColor: Theme.of(context).primaryColor,
|
labelColor: const Color(0xFF4CC793),
|
||||||
unselectedLabelColor: const Color(0xFF505E6E),
|
unselectedLabelColor: const Color(0xFF505E6E),
|
||||||
padding: EdgeInsets.symmetric(horizontal: 14.r),
|
padding: EdgeInsets.symmetric(horizontal: 14.r),
|
||||||
// indicatorSize: TabBarIndicatorSize.label, // 设置指示器高度和标签一样高
|
// indicatorSize: TabBarIndicatorSize.label, // 设置指示器高度和标签一样高
|
||||||
onTap: widget.onTap,
|
onTap: widget.onTap,
|
||||||
indicator: UnderlineTabIndicator(
|
indicator: UnderlineTabIndicator(
|
||||||
borderSide: BorderSide(width: 2.r, color: Theme.of(context).primaryColor),
|
borderSide: BorderSide(
|
||||||
|
width: 2.r,
|
||||||
|
color:const Color(0xFF4CC793)
|
||||||
|
),
|
||||||
),
|
),
|
||||||
tabs: <Widget>[
|
tabs: <Widget>[
|
||||||
const Tab(text: '近一周'),
|
const Tab(text: '近一周'),
|
||||||
const Tab(text: '近一月'),
|
const Tab(text: '近一月'),
|
||||||
Tab(text: widget.customTimeStr),
|
Tab(text: widget.customTimeStr),
|
||||||
if (widget.hasAll == true) const Tab(text: '全部'),
|
if(widget.hasAll == true)
|
||||||
|
const Tab(text: '全部'),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,217 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:making_school_asignment_app/common/mixins/getx_keepalive_widget.dart';
|
||||||
|
import 'package:making_school_asignment_app/routes/app_pages.dart';
|
||||||
|
|
||||||
|
import 'home_logic.dart';
|
||||||
|
|
||||||
|
class HomeDesktopPage extends GetxKeepAliveWidget<HomeLogic> {
|
||||||
|
const HomeDesktopPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget buildContent(BuildContext context, HomeLogic controller) {
|
||||||
|
final state = controller.state;
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: const Color(0xFFF0F2F5),
|
||||||
|
body: RefreshIndicator(
|
||||||
|
onRefresh: () {
|
||||||
|
state.pageNumber = 1;
|
||||||
|
return Future.microtask(() => controller.getList());
|
||||||
|
},
|
||||||
|
child: CustomScrollView(
|
||||||
|
physics: const AlwaysScrollableScrollPhysics(),
|
||||||
|
slivers: [
|
||||||
|
SliverPadding(
|
||||||
|
padding: EdgeInsets.all(24.r),
|
||||||
|
sliver: SliverToBoxAdapter(child: _buildHeader(controller)),
|
||||||
|
),
|
||||||
|
SliverPadding(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 24.r, vertical: 0),
|
||||||
|
sliver: _buildMenuSliverGrid(state),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildHeader(HomeLogic controller) {
|
||||||
|
return Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'我的作业管理',
|
||||||
|
style: TextStyle(fontSize: 24.sp, fontWeight: FontWeight.bold, color: const Color(0xFF333333)),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
onPressed: () => controller.getList(),
|
||||||
|
icon: Icon(Icons.refresh, size: 24.sp, color: Colors.grey[600]),
|
||||||
|
tooltip: '刷新数据',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
SliverGrid _buildMenuSliverGrid(dynamic state) {
|
||||||
|
final menuItems = [
|
||||||
|
{
|
||||||
|
'bgImg': 'assets/images/home_bg_01.png',
|
||||||
|
'name': '作业批阅',
|
||||||
|
'url': Routes.readOverPage,
|
||||||
|
'isPrimary': true,
|
||||||
|
'value': state.totalCount
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'bgImg': 'assets/images/home_bg_02.png',
|
||||||
|
'name': '知识点掌握',
|
||||||
|
'url': Routes.studentHistoryWorkPage,
|
||||||
|
'page': 'points'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'bgImg': 'assets/images/home_bg_03.png',
|
||||||
|
'name': '学生历史作业',
|
||||||
|
'url': Routes.studentHistoryWorkPage,
|
||||||
|
'page': 'history'
|
||||||
|
},
|
||||||
|
{'bgImg': 'assets/images/home_bg_04.png', 'name': '答题轨迹', 'url': Routes.answerTrajectoryPage},
|
||||||
|
{'bgImg': 'assets/images/home_bg_05.png', 'name': '优先批阅设定', 'url': Routes.studentHistoryWorkPage, 'page': 'set'},
|
||||||
|
];
|
||||||
|
|
||||||
|
int crossAxisCount;
|
||||||
|
final width = Get.width;
|
||||||
|
if (width > 1200) {
|
||||||
|
crossAxisCount = 4;
|
||||||
|
} else if (width > 800) {
|
||||||
|
crossAxisCount = 3;
|
||||||
|
} else {
|
||||||
|
crossAxisCount = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
return SliverGrid(
|
||||||
|
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||||
|
crossAxisCount: crossAxisCount,
|
||||||
|
crossAxisSpacing: 16.r,
|
||||||
|
mainAxisSpacing: 16.r,
|
||||||
|
childAspectRatio: 1.5,
|
||||||
|
),
|
||||||
|
delegate: SliverChildBuilderDelegate(
|
||||||
|
(context, index) {
|
||||||
|
final item = menuItems[index];
|
||||||
|
if (item['value'] is RxInt) {
|
||||||
|
return Obx(() => _MenuItem(
|
||||||
|
bgImg: item['bgImg'] as String,
|
||||||
|
name: item['name'] as String,
|
||||||
|
url: item['url'] as String,
|
||||||
|
page: item['page'] as String?,
|
||||||
|
isPrimary: item['isPrimary'] as bool? ?? false,
|
||||||
|
value: (item['value'] as RxInt).value > 0 ? (item['value'] as RxInt).value.toString() : null,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
return _MenuItem(
|
||||||
|
bgImg: item['bgImg'] as String,
|
||||||
|
name: item['name'] as String,
|
||||||
|
url: item['url'] as String,
|
||||||
|
page: item['page'] as String?,
|
||||||
|
isPrimary: item['isPrimary'] as bool? ?? false,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
childCount: menuItems.length,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MenuItem extends StatefulWidget {
|
||||||
|
final String bgImg;
|
||||||
|
final String name;
|
||||||
|
final String? value;
|
||||||
|
final String url;
|
||||||
|
final String? page;
|
||||||
|
final bool isPrimary;
|
||||||
|
|
||||||
|
const _MenuItem({
|
||||||
|
required this.bgImg,
|
||||||
|
required this.name,
|
||||||
|
this.value,
|
||||||
|
required this.url,
|
||||||
|
this.page,
|
||||||
|
this.isPrimary = false,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
__MenuItemState createState() => __MenuItemState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class __MenuItemState extends State<_MenuItem> {
|
||||||
|
bool _isHovered = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return MouseRegion(
|
||||||
|
onEnter: (_) => setState(() => _isHovered = true),
|
||||||
|
onExit: (_) => setState(() => _isHovered = false),
|
||||||
|
cursor: SystemMouseCursors.click,
|
||||||
|
child: Card(
|
||||||
|
elevation: _isHovered ? 8.0 : 2.0,
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12.r)),
|
||||||
|
shadowColor: Colors.black.withOpacity(0.1),
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () => Get.toNamed(widget.url, arguments: {'page': widget.page ?? ''}),
|
||||||
|
borderRadius: BorderRadius.circular(12.r),
|
||||||
|
child: Container(
|
||||||
|
padding: EdgeInsets.all(16.r),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(12.r),
|
||||||
|
gradient: widget.isPrimary ? const LinearGradient(colors: [Color(0xFF667eea), Color(0xFF764ba2)]) : null,
|
||||||
|
color: widget.isPrimary ? null : Colors.white,
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
widget.name,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14.sp,
|
||||||
|
color: widget.isPrimary ? Colors.white : const Color(0xFF2C2C2C),
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (widget.value != null && widget.value!.isNotEmpty)
|
||||||
|
Container(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: widget.isPrimary ? Colors.white : const Color(0xFFFF6969),
|
||||||
|
borderRadius: BorderRadius.circular(12.r),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
widget.value!,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12.sp,
|
||||||
|
color: widget.isPrimary ? const Color(0xFFFF6969) : Colors.white,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Icon(
|
||||||
|
Icons.arrow_forward,
|
||||||
|
size: 16.sp,
|
||||||
|
color: widget.isPrimary ? Colors.white70 : Colors.grey[400],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,436 @@
|
||||||
|
import 'package:badges/badges.dart' as badges;
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:flutter_easyrefresh/easy_refresh.dart';
|
||||||
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
|
import 'package:functional_widget_annotation/functional_widget_annotation.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:making_school_asignment_app/common/mixins/getx_keepalive_widget.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';
|
||||||
|
|
||||||
|
import 'home_logic.dart';
|
||||||
|
|
||||||
|
part 'home_mobile_view.g.dart';
|
||||||
|
|
||||||
|
class HomeMobilePage extends GetxKeepAliveWidget<HomeLogic> {
|
||||||
|
const HomeMobilePage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle(
|
||||||
|
statusBarColor: Colors.transparent, //状态栏背景颜色
|
||||||
|
statusBarIconBrightness: Brightness.light,
|
||||||
|
systemStatusBarContrastEnforced: false,
|
||||||
|
));
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget buildContent(context, controller) {
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
body: OrientationBuilder(
|
||||||
|
builder: (BuildContext context, Orientation orientation) {
|
||||||
|
final state = controller.state;
|
||||||
|
return EasyRefresh(
|
||||||
|
firstRefresh: false,
|
||||||
|
taskIndependence: true,
|
||||||
|
enableControlFinishLoad: true,
|
||||||
|
controller: controller.refreshController,
|
||||||
|
header: MaterialHeader(),
|
||||||
|
onRefresh: () async {
|
||||||
|
state.pageNumber = 1;
|
||||||
|
return controller.getList();
|
||||||
|
},
|
||||||
|
child: CustomScrollView(
|
||||||
|
slivers: [
|
||||||
|
// 顶部横幅区域
|
||||||
|
SliverAppBar(
|
||||||
|
expandedHeight: 300.h,
|
||||||
|
floating: false,
|
||||||
|
pinned: false,
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
flexibleSpace: FlexibleSpaceBar(
|
||||||
|
background: Image.asset(
|
||||||
|
'assets/images/home_banner.png',
|
||||||
|
width: Get.width,
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// 主要内容区域
|
||||||
|
SliverToBoxAdapter(
|
||||||
|
child: Container(
|
||||||
|
padding: EdgeInsets.symmetric(vertical: 0, horizontal: 20.r),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.only(topLeft: Radius.circular(30.r), topRight: Radius.circular(30.r)),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
SizedBox(height: 20.r),
|
||||||
|
// 标题区域
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.assignment_outlined,
|
||||||
|
size: 24.sp,
|
||||||
|
color: const Color(0xFF4F4F4F),
|
||||||
|
),
|
||||||
|
SizedBox(width: 8.r),
|
||||||
|
Text(
|
||||||
|
'我的作业管理',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 22.sp, color: const Color(0xFF2C2C2C), fontWeight: FontWeight.w700),
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
// 刷新按钮
|
||||||
|
IconButton(
|
||||||
|
onPressed: () => controller.refreshController.callRefresh(),
|
||||||
|
icon: Icon(
|
||||||
|
Icons.refresh,
|
||||||
|
size: 20.sp,
|
||||||
|
color: const Color(0xFF4F4F4F),
|
||||||
|
),
|
||||||
|
tooltip: '刷新数据',
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
SizedBox(height: 24.r),
|
||||||
|
// 功能菜单网格
|
||||||
|
_buildMenuGrid(context, state),
|
||||||
|
SizedBox(height: 20.r),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildMenuGrid(BuildContext context, dynamic state) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
// 第一行
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Obx(() => $MenuItem(
|
||||||
|
bgImg: 'assets/images/home_bg_01.png',
|
||||||
|
name: '作业批阅',
|
||||||
|
value: state.totalCount.value > 0 ? state.totalCount.value.toString() : null,
|
||||||
|
url: Routes.readOverPage,
|
||||||
|
isPrimary: true,
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
SizedBox(width: 16.r),
|
||||||
|
const Expanded(
|
||||||
|
child: $MenuItem(
|
||||||
|
bgImg: 'assets/images/home_bg_02.png',
|
||||||
|
name: '知识点掌握',
|
||||||
|
url: Routes.studentHistoryWorkPage,
|
||||||
|
page: 'points',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SizedBox(height: 16.r),
|
||||||
|
// 第二行
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
const Expanded(
|
||||||
|
child: $MenuItem(
|
||||||
|
bgImg: 'assets/images/home_bg_03.png',
|
||||||
|
name: '学生历史作业',
|
||||||
|
url: Routes.studentHistoryWorkPage,
|
||||||
|
page: 'history',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 16.r),
|
||||||
|
const Expanded(
|
||||||
|
child: $MenuItem(bgImg: 'assets/images/home_bg_04.png', name: '答题轨迹', url: Routes.answerTrajectoryPage),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SizedBox(height: 16.r),
|
||||||
|
// 第三行
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
const Expanded(
|
||||||
|
child: $MenuItem(
|
||||||
|
bgImg: 'assets/images/home_bg_05.png',
|
||||||
|
name: '优先批阅设定',
|
||||||
|
url: Routes.studentHistoryWorkPage,
|
||||||
|
page: 'set',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 16.r),
|
||||||
|
// 占位空间
|
||||||
|
const Expanded(child: SizedBox()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@swidget
|
||||||
|
Widget $menuItem({
|
||||||
|
required String bgImg,
|
||||||
|
required String name,
|
||||||
|
String? value,
|
||||||
|
required String url,
|
||||||
|
String? page,
|
||||||
|
bool isPrimary = false,
|
||||||
|
}) {
|
||||||
|
return Material(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () {
|
||||||
|
Get.toNamed(url, arguments: {'page': page ?? ''});
|
||||||
|
},
|
||||||
|
borderRadius: BorderRadius.circular(12.r),
|
||||||
|
child: Semantics(
|
||||||
|
label: '$name${value != null ? ',待处理数量:$value' : ''}',
|
||||||
|
hint: '点击进入$name页面',
|
||||||
|
button: true,
|
||||||
|
child: Container(
|
||||||
|
width: (Get.width - 60.r) / 2,
|
||||||
|
height: (Get.width - 60.r) / 2 * 86 / 164,
|
||||||
|
padding: EdgeInsets.all(16.r),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(12.r),
|
||||||
|
gradient: isPrimary
|
||||||
|
? const LinearGradient(
|
||||||
|
begin: Alignment.topLeft,
|
||||||
|
end: Alignment.bottomRight,
|
||||||
|
colors: [
|
||||||
|
Color(0xFF667eea),
|
||||||
|
Color(0xFF764ba2),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
color: isPrimary ? null : Colors.white,
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black.withOpacity(0.08),
|
||||||
|
blurRadius: 8,
|
||||||
|
offset: const Offset(0, 2),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
image: !isPrimary
|
||||||
|
? DecorationImage(
|
||||||
|
image: AssetImage(bgImg),
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
colorFilter: ColorFilter.mode(
|
||||||
|
Colors.white.withOpacity(0.8),
|
||||||
|
BlendMode.srcATop,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
name,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14.sp,
|
||||||
|
color: isPrimary ? Colors.white : const Color(0xFF2C2C2C),
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
maxLines: 2,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (value != null && value != '')
|
||||||
|
Semantics(
|
||||||
|
label: '待处理数量:$value',
|
||||||
|
child: Container(
|
||||||
|
width: 20.r,
|
||||||
|
height: 20.r,
|
||||||
|
alignment: Alignment.center,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: isPrimary ? Colors.white : const Color(0xFFFF6969),
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(10.r)),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
value,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 10.sp,
|
||||||
|
color: isPrimary ? const Color(0xFFFF6969) : Colors.white,
|
||||||
|
fontWeight: FontWeight.w600),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
if (isPrimary)
|
||||||
|
Icon(
|
||||||
|
Icons.arrow_forward_ios,
|
||||||
|
size: 14.sp,
|
||||||
|
color: Colors.white,
|
||||||
|
)
|
||||||
|
else
|
||||||
|
Image.asset(
|
||||||
|
'assets/images/home_right_icon.png',
|
||||||
|
width: 16.r,
|
||||||
|
height: 16.r,
|
||||||
|
semanticLabel: '进入图标',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class EntranceModel extends Object {
|
||||||
|
String title;
|
||||||
|
String image;
|
||||||
|
String navigationUrl;
|
||||||
|
String? page;
|
||||||
|
|
||||||
|
EntranceModel({required this.title, required this.image, required this.navigationUrl, this.page});
|
||||||
|
}
|
||||||
|
|
||||||
|
@swidget
|
||||||
|
Widget $termRow(BuildContext context, List<EntranceModel> items, int? data) {
|
||||||
|
var leng = items.length;
|
||||||
|
Widget childWidget;
|
||||||
|
switch (leng) {
|
||||||
|
case 1:
|
||||||
|
childWidget = Row(children: [Expanded(child: $TermItem(items[0], data!))]);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
childWidget = Row(children: [
|
||||||
|
Expanded(flex: 9, child: $TermItem(items[0], data!)),
|
||||||
|
// const Expanded(flex: 1, child: SizedBox()),
|
||||||
|
SizedBox(width: ScreenUtil().screenWidth / 30),
|
||||||
|
Expanded(flex: 9, child: $TermItem(items[1], data)),
|
||||||
|
]);
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
double theHeight = ScreenUtil().screenWidth / 30 + 54.h * 2;
|
||||||
|
childWidget = Row(
|
||||||
|
children: [
|
||||||
|
Expanded(child: $TermItem(items[0], data!, theHeight: theHeight)),
|
||||||
|
SizedBox(width: ScreenUtil().screenWidth / 30),
|
||||||
|
Expanded(
|
||||||
|
child: SizedBox(
|
||||||
|
height: theHeight,
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
$TermItem(items[1], data),
|
||||||
|
$TermItem(items[2], data),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
childWidget = Container();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Container(padding: EdgeInsets.symmetric(horizontal: 14.w), child: childWidget);
|
||||||
|
}
|
||||||
|
|
||||||
|
@swidget
|
||||||
|
Widget $termItem(BuildContext context, EntranceModel e, int data, {double? theHeight}) {
|
||||||
|
bool isJob = e.title == '作业批阅';
|
||||||
|
|
||||||
|
return Material(
|
||||||
|
color: Colors.white,
|
||||||
|
elevation: 3.r,
|
||||||
|
shadowColor: const Color.fromRGBO(231, 231, 231, 1),
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(8.r)),
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () {
|
||||||
|
Get.toNamed(e.navigationUrl, arguments: {'page': e.page ?? ''});
|
||||||
|
},
|
||||||
|
|
||||||
|
// splashColor: splashColor,
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(8.r)),
|
||||||
|
child: badges.Badge(
|
||||||
|
showBadge: isJob && data > 0,
|
||||||
|
ignorePointer: false,
|
||||||
|
badgeContent: quickText(data, color: Colors.white, size: 10.sp),
|
||||||
|
badgeAnimation: const badges.BadgeAnimation.rotation(
|
||||||
|
animationDuration: Duration(seconds: 1),
|
||||||
|
colorChangeAnimationDuration: Duration(seconds: 1),
|
||||||
|
loopAnimation: false,
|
||||||
|
curve: Curves.fastOutSlowIn,
|
||||||
|
colorChangeAnimationCurve: Curves.easeInCubic,
|
||||||
|
),
|
||||||
|
badgeStyle: badges.BadgeStyle(
|
||||||
|
badgeColor: const Color.fromRGBO(255, 105, 105, 1),
|
||||||
|
shape: badges.BadgeShape.square,
|
||||||
|
borderRadius: BorderRadius.only(
|
||||||
|
topLeft: Radius.circular(10.r), topRight: Radius.circular(8.5.r), bottomRight: Radius.circular(8.5.r)),
|
||||||
|
// borderSide: BorderSide(color: Colors.white, width: 2),
|
||||||
|
elevation: 1,
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: Utils.isPad() ? 11.w : 16.w, vertical: 2.h),
|
||||||
|
),
|
||||||
|
position: badges.BadgePosition.topEnd(top: 10.r, end: 10.r),
|
||||||
|
child: Container(
|
||||||
|
height: theHeight,
|
||||||
|
padding: EdgeInsets.symmetric(vertical: 12.h),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(8.r)),
|
||||||
|
// boxShadow: [
|
||||||
|
// BoxShadow(
|
||||||
|
// color: const Color.fromRGBO(231, 231, 231, 1),
|
||||||
|
// offset: Offset(4.w, 6.h), //阴影y轴偏移量
|
||||||
|
// blurRadius: 8, //阴影模糊程度
|
||||||
|
// spreadRadius: 0.2, //阴影扩散程度
|
||||||
|
// )
|
||||||
|
// ],
|
||||||
|
// border: Border.all(width: 0.5.w, color: Color.fromARGB(255, 219, 226, 250)),
|
||||||
|
),
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: isJob
|
||||||
|
? Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Image.asset(e.image, height: 32.r, width: 32.r, fit: BoxFit.cover),
|
||||||
|
SizedBox(height: 6.r),
|
||||||
|
quickText(e.title,
|
||||||
|
size: 12.sp, color: const Color.fromRGBO(79, 79, 79, 1), fontWeight: FontWeight.w500),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Image.asset(e.image, height: 32.r, width: 32.r, fit: BoxFit.cover),
|
||||||
|
SizedBox(width: 6.r),
|
||||||
|
quickText(e.title,
|
||||||
|
size: 12.sp, color: const Color.fromRGBO(79, 79, 79, 1), fontWeight: FontWeight.w500),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1,474 +1,13 @@
|
||||||
import 'package:badges/badges.dart' as badges;
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:making_school_asignment_app/common/utils/platform_utils.dart';
|
||||||
import 'package:flutter_easyrefresh/easy_refresh.dart';
|
import 'package:making_school_asignment_app/page/home_page/home_desktop_view.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:making_school_asignment_app/page/home_page/home_mobile_view.dart';
|
||||||
import 'package:functional_widget_annotation/functional_widget_annotation.dart';
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:making_school_asignment_app/common/mixins/getx_keepalive_widget.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';
|
|
||||||
|
|
||||||
import 'home_logic.dart';
|
class HomePage extends StatelessWidget {
|
||||||
|
|
||||||
part 'home_view.g.dart';
|
|
||||||
|
|
||||||
class HomePage extends GetxKeepAliveWidget<HomeLogic> {
|
|
||||||
const HomePage({super.key});
|
const HomePage({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
Widget build(BuildContext context) {
|
||||||
SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle(
|
return PlatformUtils.isDesktop ? const HomeDesktopPage() : const HomeMobilePage();
|
||||||
statusBarColor: Colors.transparent, //状态栏背景颜色
|
|
||||||
statusBarIconBrightness: Brightness.light,
|
|
||||||
systemStatusBarContrastEnforced: false,
|
|
||||||
));
|
|
||||||
super.initState();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget buildContent(context, controller) {
|
|
||||||
return Scaffold(
|
|
||||||
backgroundColor: Colors.white,
|
|
||||||
body: OrientationBuilder(
|
|
||||||
builder: (BuildContext context, Orientation orientation) {
|
|
||||||
final state = controller.state;
|
|
||||||
return EasyRefresh(
|
|
||||||
firstRefresh: false,
|
|
||||||
taskIndependence: true,
|
|
||||||
enableControlFinishLoad: true,
|
|
||||||
controller: controller.refreshController,
|
|
||||||
header: MaterialHeader(),
|
|
||||||
onRefresh: () async {
|
|
||||||
state.pageNumber = 1;
|
|
||||||
return controller.getList();
|
|
||||||
},
|
|
||||||
child: CustomScrollView(
|
|
||||||
slivers: [
|
|
||||||
// 顶部横幅区域
|
|
||||||
SliverAppBar(
|
|
||||||
expandedHeight: 300.h,
|
|
||||||
floating: false,
|
|
||||||
pinned: false,
|
|
||||||
backgroundColor: Colors.transparent,
|
|
||||||
flexibleSpace: FlexibleSpaceBar(
|
|
||||||
background: Image.asset(
|
|
||||||
'assets/images/home_banner.png',
|
|
||||||
width: Get.width,
|
|
||||||
fit: BoxFit.cover,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
// 主要内容区域
|
|
||||||
SliverToBoxAdapter(
|
|
||||||
child: Container(
|
|
||||||
padding: EdgeInsets.symmetric(vertical: 0, horizontal: 20.r),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.white,
|
|
||||||
borderRadius: BorderRadius.only(topLeft: Radius.circular(30.r), topRight: Radius.circular(30.r)),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
SizedBox(height: 20.r),
|
|
||||||
// 标题区域
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Icon(
|
|
||||||
Icons.assignment_outlined,
|
|
||||||
size: 24.sp,
|
|
||||||
color: const Color(0xFF4F4F4F),
|
|
||||||
),
|
|
||||||
SizedBox(width: 8.r),
|
|
||||||
Text(
|
|
||||||
'我的作业管理',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 22.sp, color: const Color(0xFF2C2C2C), fontWeight: FontWeight.w700),
|
|
||||||
),
|
|
||||||
const Spacer(),
|
|
||||||
// 刷新按钮
|
|
||||||
IconButton(
|
|
||||||
onPressed: () => controller.refreshController.callRefresh(),
|
|
||||||
icon: Icon(
|
|
||||||
Icons.refresh,
|
|
||||||
size: 20.sp,
|
|
||||||
color: const Color(0xFF4F4F4F),
|
|
||||||
),
|
|
||||||
tooltip: '刷新数据',
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
|
|
||||||
SizedBox(height: 24.r),
|
|
||||||
// 功能菜单网格
|
|
||||||
_buildMenuGrid(context, state),
|
|
||||||
SizedBox(height: 20.r),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildStatCard(String title, String value, Color color, IconData icon) {
|
|
||||||
return Container(
|
|
||||||
padding: EdgeInsets.all(12.r),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.white,
|
|
||||||
borderRadius: BorderRadius.circular(8.r),
|
|
||||||
boxShadow: [
|
|
||||||
BoxShadow(
|
|
||||||
color: Colors.black.withOpacity(0.05),
|
|
||||||
blurRadius: 4,
|
|
||||||
offset: const Offset(0, 2),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
Icon(icon, color: color, size: 20.sp),
|
|
||||||
SizedBox(height: 4.r),
|
|
||||||
Text(
|
|
||||||
value,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 18.sp,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: color,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
title,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 12.sp,
|
|
||||||
color: const Color(0xFF6C757D),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildMenuGrid(BuildContext context, dynamic state) {
|
|
||||||
return Column(
|
|
||||||
children: [
|
|
||||||
// 第一行
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: Obx(() => $MenuItem(
|
|
||||||
bgImg: 'assets/images/home_bg_01.png',
|
|
||||||
name: '作业批阅',
|
|
||||||
value: state.totalCount.value > 0 ? state.totalCount.value.toString() : null,
|
|
||||||
url: Routes.readOverPage,
|
|
||||||
isPrimary: true,
|
|
||||||
)),
|
|
||||||
),
|
|
||||||
SizedBox(width: 16.r),
|
|
||||||
const Expanded(
|
|
||||||
child: $MenuItem(
|
|
||||||
bgImg: 'assets/images/home_bg_02.png',
|
|
||||||
name: '知识点掌握',
|
|
||||||
url: Routes.studentHistoryWorkPage,
|
|
||||||
page: 'points',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
SizedBox(height: 16.r),
|
|
||||||
// 第二行
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
const Expanded(
|
|
||||||
child: $MenuItem(
|
|
||||||
bgImg: 'assets/images/home_bg_03.png',
|
|
||||||
name: '学生历史作业',
|
|
||||||
url: Routes.studentHistoryWorkPage,
|
|
||||||
page: 'history',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(width: 16.r),
|
|
||||||
const Expanded(
|
|
||||||
child: $MenuItem(bgImg: 'assets/images/home_bg_04.png', name: '答题轨迹', url: Routes.answerTrajectoryPage),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
SizedBox(height: 16.r),
|
|
||||||
// 第三行
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
const Expanded(
|
|
||||||
child: $MenuItem(
|
|
||||||
bgImg: 'assets/images/home_bg_05.png',
|
|
||||||
name: '优先批阅设定',
|
|
||||||
url: Routes.studentHistoryWorkPage,
|
|
||||||
page: 'set',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(width: 16.r),
|
|
||||||
// 占位空间
|
|
||||||
const Expanded(child: SizedBox()),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@swidget
|
|
||||||
Widget $menuItem({
|
|
||||||
required String bgImg,
|
|
||||||
required String name,
|
|
||||||
String? value,
|
|
||||||
required String url,
|
|
||||||
String? page,
|
|
||||||
bool isPrimary = false,
|
|
||||||
}) {
|
|
||||||
return Material(
|
|
||||||
color: Colors.transparent,
|
|
||||||
child: InkWell(
|
|
||||||
onTap: () {
|
|
||||||
Get.toNamed(url, arguments: {'page': page ?? ''});
|
|
||||||
},
|
|
||||||
borderRadius: BorderRadius.circular(12.r),
|
|
||||||
child: Semantics(
|
|
||||||
label: '$name${value != null ? ',待处理数量:$value' : ''}',
|
|
||||||
hint: '点击进入$name页面',
|
|
||||||
button: true,
|
|
||||||
child: Container(
|
|
||||||
width: (Get.width - 60.r) / 2,
|
|
||||||
height: (Get.width - 60.r) / 2 * 86 / 164,
|
|
||||||
padding: EdgeInsets.all(16.r),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
borderRadius: BorderRadius.circular(12.r),
|
|
||||||
gradient: isPrimary
|
|
||||||
? const LinearGradient(
|
|
||||||
begin: Alignment.topLeft,
|
|
||||||
end: Alignment.bottomRight,
|
|
||||||
colors: [
|
|
||||||
Color(0xFF667eea),
|
|
||||||
Color(0xFF764ba2),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
: null,
|
|
||||||
color: isPrimary ? null : Colors.white,
|
|
||||||
boxShadow: [
|
|
||||||
BoxShadow(
|
|
||||||
color: Colors.black.withOpacity(0.08),
|
|
||||||
blurRadius: 8,
|
|
||||||
offset: const Offset(0, 2),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
image: !isPrimary
|
|
||||||
? DecorationImage(
|
|
||||||
image: AssetImage(bgImg),
|
|
||||||
fit: BoxFit.cover,
|
|
||||||
colorFilter: ColorFilter.mode(
|
|
||||||
Colors.white.withOpacity(0.8),
|
|
||||||
BlendMode.srcATop,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: null,
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: Text(
|
|
||||||
name,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 14.sp,
|
|
||||||
color: isPrimary ? Colors.white : const Color(0xFF2C2C2C),
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
maxLines: 2,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (value != null && value != '')
|
|
||||||
Semantics(
|
|
||||||
label: '待处理数量:$value',
|
|
||||||
child: Container(
|
|
||||||
width: 20.r,
|
|
||||||
height: 20.r,
|
|
||||||
alignment: Alignment.center,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: isPrimary ? Colors.white : const Color(0xFFFF6969),
|
|
||||||
borderRadius: BorderRadius.all(Radius.circular(10.r)),
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
value,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 10.sp,
|
|
||||||
color: isPrimary ? const Color(0xFFFF6969) : Colors.white,
|
|
||||||
fontWeight: FontWeight.w600),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const Spacer(),
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
if (isPrimary)
|
|
||||||
Icon(
|
|
||||||
Icons.arrow_forward_ios,
|
|
||||||
size: 14.sp,
|
|
||||||
color: Colors.white,
|
|
||||||
)
|
|
||||||
else
|
|
||||||
Image.asset(
|
|
||||||
'assets/images/home_right_icon.png',
|
|
||||||
width: 16.r,
|
|
||||||
height: 16.r,
|
|
||||||
semanticLabel: '进入图标',
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
class EntranceModel extends Object {
|
|
||||||
String title;
|
|
||||||
String image;
|
|
||||||
String navigationUrl;
|
|
||||||
String? page;
|
|
||||||
|
|
||||||
EntranceModel({required this.title, required this.image, required this.navigationUrl, this.page});
|
|
||||||
}
|
|
||||||
|
|
||||||
@swidget
|
|
||||||
Widget $termRow(BuildContext context, List<EntranceModel> items, int? data) {
|
|
||||||
var leng = items.length;
|
|
||||||
Widget childWidget;
|
|
||||||
switch (leng) {
|
|
||||||
case 1:
|
|
||||||
childWidget = Row(children: [Expanded(child: $TermItem(items[0], data!))]);
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
childWidget = Row(children: [
|
|
||||||
Expanded(flex: 9, child: $TermItem(items[0], data!)),
|
|
||||||
// const Expanded(flex: 1, child: SizedBox()),
|
|
||||||
SizedBox(width: ScreenUtil().screenWidth / 30),
|
|
||||||
Expanded(flex: 9, child: $TermItem(items[1], data)),
|
|
||||||
]);
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
double theHeight = ScreenUtil().screenWidth / 30 + 54.h * 2;
|
|
||||||
childWidget = Row(
|
|
||||||
children: [
|
|
||||||
Expanded(child: $TermItem(items[0], data!, theHeight: theHeight)),
|
|
||||||
SizedBox(width: ScreenUtil().screenWidth / 30),
|
|
||||||
Expanded(
|
|
||||||
child: SizedBox(
|
|
||||||
height: theHeight,
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
$TermItem(items[1], data),
|
|
||||||
$TermItem(items[2], data),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
childWidget = Container();
|
|
||||||
}
|
|
||||||
|
|
||||||
return Container(padding: EdgeInsets.symmetric(horizontal: 14.w), child: childWidget);
|
|
||||||
}
|
|
||||||
|
|
||||||
@swidget
|
|
||||||
Widget $termItem(BuildContext context, EntranceModel e, int data, {double? theHeight}) {
|
|
||||||
bool isJob = e.title == '作业批阅';
|
|
||||||
|
|
||||||
return Material(
|
|
||||||
color: Colors.white,
|
|
||||||
elevation: 3.r,
|
|
||||||
shadowColor: const Color.fromRGBO(231, 231, 231, 1),
|
|
||||||
borderRadius: BorderRadius.all(Radius.circular(8.r)),
|
|
||||||
child: InkWell(
|
|
||||||
onTap: () {
|
|
||||||
Get.toNamed(e.navigationUrl, arguments: {'page': e.page ?? ''});
|
|
||||||
},
|
|
||||||
|
|
||||||
// splashColor: splashColor,
|
|
||||||
borderRadius: BorderRadius.all(Radius.circular(8.r)),
|
|
||||||
child: badges.Badge(
|
|
||||||
showBadge: isJob && data > 0,
|
|
||||||
ignorePointer: false,
|
|
||||||
badgeContent: quickText(data, color: Colors.white, size: 10.sp),
|
|
||||||
badgeAnimation: const badges.BadgeAnimation.rotation(
|
|
||||||
animationDuration: Duration(seconds: 1),
|
|
||||||
colorChangeAnimationDuration: Duration(seconds: 1),
|
|
||||||
loopAnimation: false,
|
|
||||||
curve: Curves.fastOutSlowIn,
|
|
||||||
colorChangeAnimationCurve: Curves.easeInCubic,
|
|
||||||
),
|
|
||||||
badgeStyle: badges.BadgeStyle(
|
|
||||||
badgeColor: const Color.fromRGBO(255, 105, 105, 1),
|
|
||||||
shape: badges.BadgeShape.square,
|
|
||||||
borderRadius: BorderRadius.only(
|
|
||||||
topLeft: Radius.circular(10.r), topRight: Radius.circular(8.5.r), bottomRight: Radius.circular(8.5.r)),
|
|
||||||
// borderSide: BorderSide(color: Colors.white, width: 2),
|
|
||||||
elevation: 1,
|
|
||||||
padding: EdgeInsets.symmetric(horizontal: Utils.isPad() ? 11.w : 16.w, vertical: 2.h),
|
|
||||||
),
|
|
||||||
position: badges.BadgePosition.topEnd(top: 10.r, end: 10.r),
|
|
||||||
child: Container(
|
|
||||||
height: theHeight,
|
|
||||||
padding: EdgeInsets.symmetric(vertical: 12.h),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
borderRadius: BorderRadius.all(Radius.circular(8.r)),
|
|
||||||
// boxShadow: [
|
|
||||||
// BoxShadow(
|
|
||||||
// color: const Color.fromRGBO(231, 231, 231, 1),
|
|
||||||
// offset: Offset(4.w, 6.h), //阴影y轴偏移量
|
|
||||||
// blurRadius: 8, //阴影模糊程度
|
|
||||||
// spreadRadius: 0.2, //阴影扩散程度
|
|
||||||
// )
|
|
||||||
// ],
|
|
||||||
// border: Border.all(width: 0.5.w, color: Color.fromARGB(255, 219, 226, 250)),
|
|
||||||
),
|
|
||||||
alignment: Alignment.center,
|
|
||||||
child: isJob
|
|
||||||
? Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Image.asset(e.image, height: 32.r, width: 32.r, fit: BoxFit.cover),
|
|
||||||
SizedBox(height: 6.r),
|
|
||||||
quickText(e.title,
|
|
||||||
size: 12.sp, color: const Color.fromRGBO(79, 79, 79, 1), fontWeight: FontWeight.w500),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Image.asset(e.image, height: 32.r, width: 32.r, fit: BoxFit.cover),
|
|
||||||
SizedBox(width: 6.r),
|
|
||||||
quickText(e.title,
|
|
||||||
size: 12.sp, color: const Color.fromRGBO(79, 79, 79, 1), fontWeight: FontWeight.w500),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
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/const_text.dart';
|
||||||
|
|
||||||
|
class AgreementDesktopPage extends StatelessWidget {
|
||||||
|
const AgreementDesktopPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
AGREEMENT_KEY type = AGREEMENT_KEY.values.byName(Get.arguments['type']);
|
||||||
|
AgreementClass agreement = AGREEMENT_MAP[type]!;
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: const Color(0xFFF0F2F5),
|
||||||
|
body: Center(
|
||||||
|
child: Container(
|
||||||
|
width: 800.w,
|
||||||
|
height: 600.h,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(8.r),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black.withOpacity(0.1),
|
||||||
|
blurRadius: 20,
|
||||||
|
offset: const Offset(0, 10),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
AppBar(
|
||||||
|
title: Text(agreement.title),
|
||||||
|
centerTitle: true,
|
||||||
|
automaticallyImplyLeading: false,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.vertical(
|
||||||
|
top: Radius.circular(8.r),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
padding: EdgeInsets.all(24.r),
|
||||||
|
child: HtmlWidget(agreement.richText),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.all(16.r),
|
||||||
|
child: ElevatedButton(
|
||||||
|
onPressed: () => Get.back(),
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
minimumSize: Size(120.w, 50.h),
|
||||||
|
),
|
||||||
|
child: const Text('关闭'),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
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/const_text.dart';
|
||||||
|
import 'package:making_school_asignment_app/page/global_widget/my_text.dart';
|
||||||
|
|
||||||
|
// 协议富文本
|
||||||
|
class AgreementMobilePage extends StatelessWidget {
|
||||||
|
const AgreementMobilePage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
AGREEMENT_KEY type = AGREEMENT_KEY.values.byName(Get.arguments['type']);
|
||||||
|
AgreementClass agreement = AGREEMENT_MAP[type]!;
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
backgroundColor: Theme.of(context).primaryColor,
|
||||||
|
title: quickText(agreement.title, color: Colors.white),
|
||||||
|
),
|
||||||
|
body: ListView(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 14.w, vertical: 8.h),
|
||||||
|
children: [
|
||||||
|
HtmlWidget(agreement.richText, textStyle: const TextStyle(color: Colors.black)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,38 +1,13 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:making_school_asignment_app/common/utils/platform_utils.dart';
|
||||||
import 'package:flutter_widget_from_html_core/flutter_widget_from_html_core.dart';
|
import 'package:making_school_asignment_app/page/login_page/children/agreement_desktop.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:making_school_asignment_app/page/login_page/children/agreement_mobile.dart';
|
||||||
import 'package:making_school_asignment_app/common/const_text.dart';
|
|
||||||
import 'package:making_school_asignment_app/page/global_widget/my_text.dart';
|
|
||||||
|
|
||||||
// 协议富文本
|
|
||||||
class AgreementPage extends StatelessWidget {
|
class AgreementPage extends StatelessWidget {
|
||||||
const AgreementPage({super.key});
|
const AgreementPage({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
AGREEMENT_KEY type = AGREEMENT_KEY.values.byName(Get.arguments['type']);
|
return PlatformUtils.isDesktop ? const AgreementDesktopPage() : const AgreementMobilePage();
|
||||||
AgreementClass agreement = AGREEMENT_MAP[type]!;
|
|
||||||
return Scaffold(
|
|
||||||
appBar: AppBar(
|
|
||||||
backgroundColor: Theme.of(context).primaryColor,
|
|
||||||
title: quickText(agreement.title, color: Colors.white),
|
|
||||||
),
|
|
||||||
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>{},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,294 +1,13 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:making_school_asignment_app/common/utils/platform_utils.dart';
|
||||||
import 'package:flutter_easyloading/flutter_easyloading.dart';
|
import 'package:making_school_asignment_app/page/login_page/children/register_desktop.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:making_school_asignment_app/page/login_page/children/register_mobile.dart';
|
||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:making_school_asignment_app/common/const_text.dart';
|
|
||||||
import 'package:making_school_asignment_app/common/job/user_register_params.dart';
|
|
||||||
import 'package:making_school_asignment_app/common/mixins/request_tool_mixin.dart';
|
|
||||||
import 'package:making_school_asignment_app/common/utils/toast_utils.dart';
|
|
||||||
import 'package:making_school_asignment_app/page/global_widget/my_text.dart';
|
|
||||||
import 'package:making_school_asignment_app/routes/app_pages.dart';
|
|
||||||
|
|
||||||
/// 注册页面
|
class Register extends StatelessWidget {
|
||||||
class Register extends StatefulWidget {
|
|
||||||
const Register({super.key});
|
const Register({super.key});
|
||||||
|
|
||||||
@override
|
|
||||||
State<Register> createState() => _RegisterState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _RegisterState extends State<Register> with RequestToolMixin {
|
|
||||||
late final FocusNode _theFocus;
|
|
||||||
late final FocusNode _pwdFocus; // 密码
|
|
||||||
//文本输入框控制器
|
|
||||||
late final TextEditingController _userNameController;
|
|
||||||
late final TextEditingController _passwordController;
|
|
||||||
|
|
||||||
bool readAgreement = false; // 阅读协议
|
|
||||||
bool _isShowPwd = true;
|
|
||||||
bool canLogin = true;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
_userNameController = TextEditingController();
|
|
||||||
_passwordController = TextEditingController();
|
|
||||||
_pwdFocus = FocusNode();
|
|
||||||
_theFocus = FocusNode();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
super.dispose();
|
|
||||||
_userNameController.dispose();
|
|
||||||
_passwordController.dispose();
|
|
||||||
_pwdFocus.dispose();
|
|
||||||
_theFocus.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
void toRegister() async {
|
|
||||||
if (!canLogin) return;
|
|
||||||
setState(() => canLogin = false);
|
|
||||||
void toMsg(msg) {
|
|
||||||
Future.delayed(Duration.zero, () => ToastUtils.showError(msg));
|
|
||||||
setState(() => canLogin = true);
|
|
||||||
}
|
|
||||||
|
|
||||||
FocusScope.of(context).requestFocus(_theFocus);
|
|
||||||
try {
|
|
||||||
String userName = _userNameController.text.trim();
|
|
||||||
String userPwd = _passwordController.text.trim();
|
|
||||||
if (userName == '') return toMsg('请填写用户账号');
|
|
||||||
if (userPwd == '') return toMsg('请填写密码');
|
|
||||||
if (userPwd.length < 6) return toMsg('密码长度不得少于6位');
|
|
||||||
if (!readAgreement) return toMsg('请勾选我已阅读用户协议和隐私协议');
|
|
||||||
EasyLoading.show(status: 'loading...');
|
|
||||||
var resultData = await getClient().toRegister(UserRegisterParams(account: userName, password: userPwd));
|
|
||||||
print(resultData);
|
|
||||||
// if (resultData.success) return toMsg(resultData.message ?? '注册失败,请重试');
|
|
||||||
ToastUtils.showSuccess('注册成功,请登录');
|
|
||||||
// 跳转登录页
|
|
||||||
Future.delayed(Duration.zero, () => Get.back());
|
|
||||||
} catch (e) {
|
|
||||||
setState(() => canLogin = true);
|
|
||||||
} finally {
|
|
||||||
EasyLoading.dismiss();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _showPassword() {
|
|
||||||
setState(() {
|
|
||||||
_isShowPwd = !_isShowPwd;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return GestureDetector(
|
return PlatformUtils.isDesktop ? const RegisterDesktop() : const RegisterMobile();
|
||||||
behavior: HitTestBehavior.translucent,
|
|
||||||
onTap: () {
|
|
||||||
FocusScope.of(context).requestFocus(_theFocus);
|
|
||||||
},
|
|
||||||
child: AnnotatedRegion(
|
|
||||||
value: const SystemUiOverlayStyle(
|
|
||||||
statusBarColor: Colors.transparent,
|
|
||||||
systemNavigationBarIconBrightness: Brightness.light,
|
|
||||||
statusBarIconBrightness: Brightness.light,
|
|
||||||
statusBarBrightness: Brightness.dark,
|
|
||||||
),
|
|
||||||
child: Scaffold(
|
|
||||||
backgroundColor: Colors.transparent,
|
|
||||||
resizeToAvoidBottomInset: false,
|
|
||||||
body: Stack(
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
width: double.infinity,
|
|
||||||
height: double.infinity,
|
|
||||||
alignment: Alignment.center,
|
|
||||||
decoration: const BoxDecoration(
|
|
||||||
image: DecorationImage(
|
|
||||||
image: AssetImage('assets/images/register_bg.png'),
|
|
||||||
fit: BoxFit.fill, // 完全填充
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: SingleChildScrollView(
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
width: 86.w,
|
|
||||||
height: 86.w,
|
|
||||||
alignment: Alignment.center,
|
|
||||||
child: SizedBox(
|
|
||||||
height: 86.w,
|
|
||||||
width: 86.w,
|
|
||||||
child: Image.asset('assets/images/login_logo_icon.png', fit: BoxFit.cover),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Container(
|
|
||||||
margin: EdgeInsets.symmetric(horizontal: 32.w, vertical: 24.h),
|
|
||||||
padding: EdgeInsets.only(top: 34.h, bottom: 16.h, left: 22.w, right: 22.w),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.white,
|
|
||||||
border: Border.all(width: 1.w, color: Colors.white),
|
|
||||||
borderRadius: BorderRadius.all(Radius.circular(10.w)),
|
|
||||||
boxShadow: const [
|
|
||||||
BoxShadow(
|
|
||||||
color: Color.fromRGBO(46, 91, 255, 0.1),
|
|
||||||
offset: Offset.zero, //阴影y轴偏移量
|
|
||||||
blurRadius: 100, //阴影模糊程度
|
|
||||||
spreadRadius: 100, //阴影扩散程度
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
child: Column(children: [
|
|
||||||
TextField(
|
|
||||||
controller: _userNameController,
|
|
||||||
maxLines: 1,
|
|
||||||
maxLength: 20,
|
|
||||||
textInputAction: TextInputAction.next,
|
|
||||||
enableInteractiveSelection: false,
|
|
||||||
onEditingComplete: () {
|
|
||||||
FocusScope.of(context).requestFocus(_pwdFocus);
|
|
||||||
},
|
|
||||||
style: TextStyle(
|
|
||||||
color: const Color.fromRGBO(80, 87, 103, 1),
|
|
||||||
fontSize: 15.sp,
|
|
||||||
),
|
|
||||||
decoration: InputDecoration(
|
|
||||||
hintText: "请输入账号",
|
|
||||||
hintStyle: TextStyle(fontSize: 16.sp, color: const Color.fromRGBO(153, 153, 153, 1)),
|
|
||||||
labelText: "账号",
|
|
||||||
labelStyle: TextStyle(fontSize: 16.sp, color: const Color.fromRGBO(148, 163, 182, 1)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
TextField(
|
|
||||||
focusNode: _pwdFocus,
|
|
||||||
controller: _passwordController,
|
|
||||||
keyboardType: TextInputType.number,
|
|
||||||
maxLines: 1,
|
|
||||||
obscureText: _isShowPwd, //隐藏密码显示
|
|
||||||
textInputAction: TextInputAction.go,
|
|
||||||
enableInteractiveSelection: false,
|
|
||||||
onSubmitted: (_) => toRegister(),
|
|
||||||
style: TextStyle(
|
|
||||||
color: const Color.fromRGBO(80, 87, 103, 1),
|
|
||||||
fontSize: 15.sp,
|
|
||||||
),
|
|
||||||
decoration: InputDecoration(
|
|
||||||
hintText: "请输入密码",
|
|
||||||
suffix: GestureDetector(
|
|
||||||
onTap: _showPassword,
|
|
||||||
child: Icon(
|
|
||||||
Icons.remove_red_eye,
|
|
||||||
color: !_isShowPwd ? Theme.of(context).primaryColor : Colors.grey,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
hintStyle: TextStyle(fontSize: 16.sp, color: const Color.fromRGBO(153, 153, 153, 1)),
|
|
||||||
labelText: "密码",
|
|
||||||
isDense: true,
|
|
||||||
labelStyle: TextStyle(fontSize: 16.sp, color: const Color.fromRGBO(148, 163, 182, 1)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(
|
|
||||||
height: 12.h,
|
|
||||||
),
|
|
||||||
InkWell(
|
|
||||||
onTap: toRegister,
|
|
||||||
child: Container(
|
|
||||||
margin: EdgeInsets.symmetric(vertical: 10.h),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: canLogin ? Theme.of(context).primaryColor : Colors.grey,
|
|
||||||
boxShadow: [
|
|
||||||
BoxShadow(
|
|
||||||
color: const Color.fromRGBO(76, 199, 147, 0.5),
|
|
||||||
offset: Offset(4.w, 6.h), //阴影y轴偏移量
|
|
||||||
blurRadius: 10, //阴影模糊程度
|
|
||||||
spreadRadius: 0.5, //阴影扩散程度
|
|
||||||
)
|
|
||||||
],
|
|
||||||
borderRadius: BorderRadius.all(
|
|
||||||
Radius.circular(8.w),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
alignment: Alignment.center,
|
|
||||||
width: double.infinity,
|
|
||||||
height: 50.h,
|
|
||||||
child: Text(
|
|
||||||
'注 册',
|
|
||||||
style: TextStyle(fontSize: 16.sp, color: Colors.white),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
width: 30.w,
|
|
||||||
padding: EdgeInsets.only(right: 10.w),
|
|
||||||
child: Checkbox(
|
|
||||||
activeColor: Colors.deepOrangeAccent,
|
|
||||||
checkColor: Colors.white,
|
|
||||||
value: readAgreement,
|
|
||||||
onChanged: (value) {
|
|
||||||
FocusScope.of(context).requestFocus(_pwdFocus);
|
|
||||||
FocusScope.of(context).requestFocus(_theFocus);
|
|
||||||
setState(() {
|
|
||||||
readAgreement = value ?? false;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
InkWell(
|
|
||||||
onTap: () {
|
|
||||||
// RouterManager.router.navigateTo(
|
|
||||||
// context,
|
|
||||||
// '${RouterManager.agreementPath}?type=${AGREEMENT_KEY.USER_AGREEMENT.name}',
|
|
||||||
// transition: getTransition(),
|
|
||||||
// );
|
|
||||||
},
|
|
||||||
child: quickText('我已阅读', size: 11.sp),
|
|
||||||
),
|
|
||||||
InkWell(
|
|
||||||
onTap: () {
|
|
||||||
Get.toNamed(Routes.agreementPage,
|
|
||||||
arguments: {"type": AGREEMENT_KEY.USER_AGREEMENT.name});
|
|
||||||
},
|
|
||||||
child: quickText(
|
|
||||||
'《用户协议》',
|
|
||||||
size: 12.sp,
|
|
||||||
color: Theme.of(context).primaryColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
InkWell(
|
|
||||||
onTap: () {
|
|
||||||
Get.toNamed(Routes.agreementPage,
|
|
||||||
arguments: {"type": AGREEMENT_KEY.PRIVACY_GREEMENT.name});
|
|
||||||
},
|
|
||||||
child: quickText(
|
|
||||||
'《隐私协议》',
|
|
||||||
size: 12.sp,
|
|
||||||
color: Theme.of(context).primaryColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
]),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
)),
|
|
||||||
Positioned(
|
|
||||||
top: MediaQuery.of(context).padding.top + 20.r,
|
|
||||||
left: 20.r,
|
|
||||||
child: InkWell(
|
|
||||||
onTap: () => Navigator.pop(context),
|
|
||||||
child: Icon(Icons.arrow_back_ios_new_rounded, color: Colors.white, size: 24.sp),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,207 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_easyloading/flutter_easyloading.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/job/user_register_params.dart';
|
||||||
|
import 'package:making_school_asignment_app/common/mixins/request_tool_mixin.dart';
|
||||||
|
import 'package:making_school_asignment_app/common/utils/toast_utils.dart';
|
||||||
|
import 'package:making_school_asignment_app/page/global_widget/my_text.dart';
|
||||||
|
import 'package:making_school_asignment_app/routes/app_pages.dart';
|
||||||
|
|
||||||
|
class RegisterDesktop extends StatefulWidget {
|
||||||
|
const RegisterDesktop({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<RegisterDesktop> createState() => _RegisterDesktopState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _RegisterDesktopState extends State<RegisterDesktop> with RequestToolMixin {
|
||||||
|
late final TextEditingController _userNameController;
|
||||||
|
late final TextEditingController _passwordController;
|
||||||
|
late final TextEditingController _confirmPasswordController;
|
||||||
|
|
||||||
|
bool readAgreement = false;
|
||||||
|
bool _isShowPwd = true;
|
||||||
|
bool _isShowConfirmPwd = true;
|
||||||
|
bool canRegister = true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_userNameController = TextEditingController();
|
||||||
|
_passwordController = TextEditingController();
|
||||||
|
_confirmPasswordController = TextEditingController();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
super.dispose();
|
||||||
|
_userNameController.dispose();
|
||||||
|
_passwordController.dispose();
|
||||||
|
_confirmPasswordController.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void toRegister() async {
|
||||||
|
if (!canRegister) return;
|
||||||
|
setState(() => canRegister = false);
|
||||||
|
void toMsg(msg) {
|
||||||
|
Future.delayed(Duration.zero, () => ToastUtils.showError(msg));
|
||||||
|
setState(() => canRegister = true);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
String userName = _userNameController.text.trim();
|
||||||
|
String userPwd = _passwordController.text.trim();
|
||||||
|
String confirmPwd = _confirmPasswordController.text.trim();
|
||||||
|
|
||||||
|
if (userName.isEmpty) return toMsg('请填写用户账号');
|
||||||
|
if (userPwd.isEmpty) return toMsg('请填写密码');
|
||||||
|
if (userPwd.length < 6) return toMsg('密码长度不得少于6位');
|
||||||
|
if (userPwd != confirmPwd) return toMsg('两次输入的密码不一致');
|
||||||
|
if (!readAgreement) return toMsg('请勾选我已阅读用户协议和隐私协议');
|
||||||
|
|
||||||
|
EasyLoading.show(status: '注册中...');
|
||||||
|
await getClient().toRegister(UserRegisterParams(account: userName, password: userPwd));
|
||||||
|
ToastUtils.showSuccess('注册成功,请登录');
|
||||||
|
Get.back();
|
||||||
|
} catch (e) {
|
||||||
|
setState(() => canRegister = true);
|
||||||
|
} finally {
|
||||||
|
EasyLoading.dismiss();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: const Color(0xFFF0F2F5),
|
||||||
|
body: Center(
|
||||||
|
child: Container(
|
||||||
|
width: 400.w,
|
||||||
|
padding: EdgeInsets.all(32.r),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(8.r),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black.withOpacity(0.1),
|
||||||
|
blurRadius: 20,
|
||||||
|
offset: const Offset(0, 10),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'创建您的账户',
|
||||||
|
style: TextStyle(fontSize: 24.sp, fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
SizedBox(height: 24.h),
|
||||||
|
_buildTextField(controller: _userNameController, hintText: "请输入账号", prefixIcon: Icons.person_outline),
|
||||||
|
SizedBox(height: 20.h),
|
||||||
|
_buildPasswordField(
|
||||||
|
controller: _passwordController,
|
||||||
|
hintText: "请输入密码",
|
||||||
|
prefixIcon: Icons.lock_outline,
|
||||||
|
obscureText: _isShowPwd,
|
||||||
|
onToggleVisibility: () => setState(() => _isShowPwd = !_isShowPwd),
|
||||||
|
),
|
||||||
|
SizedBox(height: 20.h),
|
||||||
|
_buildPasswordField(
|
||||||
|
controller: _confirmPasswordController,
|
||||||
|
hintText: "请再次输入密码",
|
||||||
|
prefixIcon: Icons.lock_outline,
|
||||||
|
obscureText: _isShowConfirmPwd,
|
||||||
|
onToggleVisibility: () => setState(() => _isShowConfirmPwd = !_isShowConfirmPwd),
|
||||||
|
),
|
||||||
|
SizedBox(height: 20.h),
|
||||||
|
InkWell(
|
||||||
|
onTap: toRegister,
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: canRegister ? const Color(0xFF8C68FF) : const Color(0xFFdddddd),
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(8.r)),
|
||||||
|
),
|
||||||
|
alignment: Alignment.center,
|
||||||
|
width: double.infinity,
|
||||||
|
height: 50.h,
|
||||||
|
child: quickText('注 册', size: 18.sp, color: Colors.white),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 20.h),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Checkbox(
|
||||||
|
value: readAgreement,
|
||||||
|
onChanged: (value) => setState(() => readAgreement = value ?? false),
|
||||||
|
),
|
||||||
|
const Text('我已阅读并同意'),
|
||||||
|
InkWell(
|
||||||
|
onTap: () =>
|
||||||
|
Get.toNamed(Routes.agreementPage, arguments: {"type": AGREEMENT_KEY.USER_AGREEMENT.name}),
|
||||||
|
child: Text('《用户协议》', style: TextStyle(color: Theme.of(context).primaryColor)),
|
||||||
|
),
|
||||||
|
const Text('和'),
|
||||||
|
InkWell(
|
||||||
|
onTap: () =>
|
||||||
|
Get.toNamed(Routes.agreementPage, arguments: {"type": AGREEMENT_KEY.PRIVACY_GREEMENT.name}),
|
||||||
|
child: Text('《隐私协议》', style: TextStyle(color: Theme.of(context).primaryColor)),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SizedBox(height: 20.h),
|
||||||
|
InkWell(
|
||||||
|
onTap: () => Get.back(),
|
||||||
|
child: Text('已有账户?返回登录', style: TextStyle(color: Theme.of(context).primaryColor)),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildTextField(
|
||||||
|
{required TextEditingController controller, required String hintText, required IconData prefixIcon}) {
|
||||||
|
return TextField(
|
||||||
|
controller: controller,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: hintText,
|
||||||
|
prefixIcon: Icon(prefixIcon),
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8.r), borderSide: const BorderSide(color: Colors.grey)),
|
||||||
|
enabledBorder: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8.r), borderSide: BorderSide(color: Colors.grey.shade300)),
|
||||||
|
focusedBorder: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8.r), borderSide: BorderSide(color: Theme.of(context).primaryColor)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildPasswordField(
|
||||||
|
{required TextEditingController controller,
|
||||||
|
required String hintText,
|
||||||
|
required IconData prefixIcon,
|
||||||
|
required bool obscureText,
|
||||||
|
required VoidCallback onToggleVisibility}) {
|
||||||
|
return TextField(
|
||||||
|
controller: controller,
|
||||||
|
obscureText: obscureText,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: hintText,
|
||||||
|
prefixIcon: Icon(prefixIcon),
|
||||||
|
suffixIcon: IconButton(
|
||||||
|
icon: Icon(obscureText ? Icons.visibility_off : Icons.visibility), onPressed: onToggleVisibility),
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8.r), borderSide: const BorderSide(color: Colors.grey)),
|
||||||
|
enabledBorder: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8.r), borderSide: BorderSide(color: Colors.grey.shade300)),
|
||||||
|
focusedBorder: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8.r), borderSide: BorderSide(color: Theme.of(context).primaryColor)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,292 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:flutter_easyloading/flutter_easyloading.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/job/user_register_params.dart';
|
||||||
|
import 'package:making_school_asignment_app/common/mixins/request_tool_mixin.dart';
|
||||||
|
import 'package:making_school_asignment_app/common/utils/toast_utils.dart';
|
||||||
|
import 'package:making_school_asignment_app/page/global_widget/my_text.dart';
|
||||||
|
import 'package:making_school_asignment_app/routes/app_pages.dart';
|
||||||
|
|
||||||
|
/// 注册页面
|
||||||
|
class RegisterMobile extends StatefulWidget {
|
||||||
|
const RegisterMobile({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<RegisterMobile> createState() => _RegisterMobileState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _RegisterMobileState extends State<RegisterMobile> with RequestToolMixin {
|
||||||
|
late final FocusNode _theFocus;
|
||||||
|
late final FocusNode _pwdFocus; // 密码
|
||||||
|
//文本输入框控制器
|
||||||
|
late final TextEditingController _userNameController;
|
||||||
|
late final TextEditingController _passwordController;
|
||||||
|
|
||||||
|
bool readAgreement = false; // 阅读协议
|
||||||
|
bool _isShowPwd = true;
|
||||||
|
bool canLogin = true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_userNameController = TextEditingController();
|
||||||
|
_passwordController = TextEditingController();
|
||||||
|
_pwdFocus = FocusNode();
|
||||||
|
_theFocus = FocusNode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
super.dispose();
|
||||||
|
_userNameController.dispose();
|
||||||
|
_passwordController.dispose();
|
||||||
|
_pwdFocus.dispose();
|
||||||
|
_theFocus.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void toRegister() async {
|
||||||
|
if (!canLogin) return;
|
||||||
|
setState(() => canLogin = false);
|
||||||
|
void toMsg(msg) {
|
||||||
|
Future.delayed(Duration.zero, () => ToastUtils.showError(msg));
|
||||||
|
setState(() => canLogin = true);
|
||||||
|
}
|
||||||
|
|
||||||
|
FocusScope.of(context).requestFocus(_theFocus);
|
||||||
|
try {
|
||||||
|
String userName = _userNameController.text.trim();
|
||||||
|
String userPwd = _passwordController.text.trim();
|
||||||
|
if (userName == '') return toMsg('请填写用户账号');
|
||||||
|
if (userPwd == '') return toMsg('请填写密码');
|
||||||
|
if (userPwd.length < 6) return toMsg('密码长度不得少于6位');
|
||||||
|
if (!readAgreement) return toMsg('请勾选我已阅读用户协议和隐私协议');
|
||||||
|
EasyLoading.show(status: 'loading...');
|
||||||
|
var resultData = await getClient().toRegister(UserRegisterParams(account: userName, password: userPwd));
|
||||||
|
print(resultData);
|
||||||
|
// if (resultData.success) return toMsg(resultData.message ?? '注册失败,请重试');
|
||||||
|
ToastUtils.showSuccess('注册成功,请登录');
|
||||||
|
// 跳转登录页
|
||||||
|
Future.delayed(Duration.zero, () => Get.back());
|
||||||
|
} catch (e) {
|
||||||
|
setState(() => canLogin = true);
|
||||||
|
} finally {
|
||||||
|
EasyLoading.dismiss();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showPassword() {
|
||||||
|
setState(() {
|
||||||
|
_isShowPwd = !_isShowPwd;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return GestureDetector(
|
||||||
|
behavior: HitTestBehavior.translucent,
|
||||||
|
onTap: () {
|
||||||
|
FocusScope.of(context).requestFocus(_theFocus);
|
||||||
|
},
|
||||||
|
child: AnnotatedRegion(
|
||||||
|
value: const SystemUiOverlayStyle(
|
||||||
|
statusBarColor: Colors.transparent,
|
||||||
|
systemNavigationBarIconBrightness: Brightness.light,
|
||||||
|
statusBarIconBrightness: Brightness.light,
|
||||||
|
statusBarBrightness: Brightness.dark,
|
||||||
|
),
|
||||||
|
child: Scaffold(
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
resizeToAvoidBottomInset: false,
|
||||||
|
body: Stack(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
width: double.infinity,
|
||||||
|
height: double.infinity,
|
||||||
|
alignment: Alignment.center,
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
image: DecorationImage(
|
||||||
|
image: AssetImage('assets/images/register_bg.png'),
|
||||||
|
fit: BoxFit.fill, // 完全填充
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
width: 86.w,
|
||||||
|
height: 86.w,
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 86.w,
|
||||||
|
width: 86.w,
|
||||||
|
child: Image.asset('assets/images/login_logo_icon.png', fit: BoxFit.cover),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
margin: EdgeInsets.symmetric(horizontal: 32.w, vertical: 24.h),
|
||||||
|
padding: EdgeInsets.only(top: 34.h, bottom: 16.h, left: 22.w, right: 22.w),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
border: Border.all(width: 1.w, color: Colors.white),
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(10.w)),
|
||||||
|
boxShadow: const [
|
||||||
|
BoxShadow(
|
||||||
|
color: Color.fromRGBO(46, 91, 255, 0.1),
|
||||||
|
offset: Offset.zero, //阴影y轴偏移量
|
||||||
|
blurRadius: 100, //阴影模糊程度
|
||||||
|
spreadRadius: 100, //阴影扩散程度
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Column(children: [
|
||||||
|
TextField(
|
||||||
|
controller: _userNameController,
|
||||||
|
maxLines: 1,
|
||||||
|
maxLength: 20,
|
||||||
|
textInputAction: TextInputAction.next,
|
||||||
|
onEditingComplete: () {
|
||||||
|
FocusScope.of(context).requestFocus(_pwdFocus);
|
||||||
|
},
|
||||||
|
style: TextStyle(
|
||||||
|
color: const Color.fromRGBO(80, 87, 103, 1),
|
||||||
|
fontSize: 15.sp,
|
||||||
|
),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: "请输入账号",
|
||||||
|
hintStyle: TextStyle(fontSize: 16.sp, color: const Color.fromRGBO(153, 153, 153, 1)),
|
||||||
|
labelText: "账号",
|
||||||
|
labelStyle: TextStyle(fontSize: 16.sp, color: const Color.fromRGBO(148, 163, 182, 1)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextField(
|
||||||
|
focusNode: _pwdFocus,
|
||||||
|
controller: _passwordController,
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
maxLines: 1,
|
||||||
|
obscureText: _isShowPwd, //隐藏密码显示
|
||||||
|
textInputAction: TextInputAction.go,
|
||||||
|
onSubmitted: (_) => toRegister(),
|
||||||
|
style: TextStyle(
|
||||||
|
color: const Color.fromRGBO(80, 87, 103, 1),
|
||||||
|
fontSize: 15.sp,
|
||||||
|
),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: "请输入密码",
|
||||||
|
suffix: GestureDetector(
|
||||||
|
onTap: _showPassword,
|
||||||
|
child: Icon(
|
||||||
|
Icons.remove_red_eye,
|
||||||
|
color: !_isShowPwd ? Theme.of(context).primaryColor : Colors.grey,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
hintStyle: TextStyle(fontSize: 16.sp, color: const Color.fromRGBO(153, 153, 153, 1)),
|
||||||
|
labelText: "密码",
|
||||||
|
isDense: true,
|
||||||
|
labelStyle: TextStyle(fontSize: 16.sp, color: const Color.fromRGBO(148, 163, 182, 1)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: 12.h,
|
||||||
|
),
|
||||||
|
InkWell(
|
||||||
|
onTap: toRegister,
|
||||||
|
child: Container(
|
||||||
|
margin: EdgeInsets.symmetric(vertical: 10.h),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: canLogin ? Theme.of(context).primaryColor : Colors.grey,
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: const Color.fromRGBO(76, 199, 147, 0.5),
|
||||||
|
offset: Offset(4.w, 6.h), //阴影y轴偏移量
|
||||||
|
blurRadius: 10, //阴影模糊程度
|
||||||
|
spreadRadius: 0.5, //阴影扩散程度
|
||||||
|
)
|
||||||
|
],
|
||||||
|
borderRadius: BorderRadius.all(
|
||||||
|
Radius.circular(8.w),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
alignment: Alignment.center,
|
||||||
|
width: double.infinity,
|
||||||
|
height: 50.h,
|
||||||
|
child: Text(
|
||||||
|
'注 册',
|
||||||
|
style: TextStyle(fontSize: 16.sp, color: Colors.white),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
width: 30.w,
|
||||||
|
padding: EdgeInsets.only(right: 10.w),
|
||||||
|
child: Checkbox(
|
||||||
|
activeColor: Colors.deepOrangeAccent,
|
||||||
|
checkColor: Colors.white,
|
||||||
|
value: readAgreement,
|
||||||
|
onChanged: (value) {
|
||||||
|
FocusScope.of(context).requestFocus(_pwdFocus);
|
||||||
|
FocusScope.of(context).requestFocus(_theFocus);
|
||||||
|
setState(() {
|
||||||
|
readAgreement = value ?? false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
InkWell(
|
||||||
|
onTap: () {
|
||||||
|
// RouterManager.router.navigateTo(
|
||||||
|
// context,
|
||||||
|
// '${RouterManager.agreementPath}?type=${AGREEMENT_KEY.USER_AGREEMENT.name}',
|
||||||
|
// transition: getTransition(),
|
||||||
|
// );
|
||||||
|
},
|
||||||
|
child: quickText('我已阅读', size: 11.sp),
|
||||||
|
),
|
||||||
|
InkWell(
|
||||||
|
onTap: () {
|
||||||
|
Get.toNamed(Routes.agreementPage,
|
||||||
|
arguments: {"type": AGREEMENT_KEY.USER_AGREEMENT.name});
|
||||||
|
},
|
||||||
|
child: quickText(
|
||||||
|
'《用户协议》',
|
||||||
|
size: 12.sp,
|
||||||
|
color: Theme.of(context).primaryColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
InkWell(
|
||||||
|
onTap: () {
|
||||||
|
Get.toNamed(Routes.agreementPage,
|
||||||
|
arguments: {"type": AGREEMENT_KEY.PRIVACY_GREEMENT.name});
|
||||||
|
},
|
||||||
|
child: quickText(
|
||||||
|
'《隐私协议》',
|
||||||
|
size: 12.sp,
|
||||||
|
color: Theme.of(context).primaryColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
Positioned(
|
||||||
|
top: MediaQuery.of(context).padding.top + 20.r,
|
||||||
|
left: 20.r,
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () => Navigator.pop(context),
|
||||||
|
child: Icon(Icons.arrow_back_ios_new_rounded, color: Colors.white, size: 24.sp),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,231 @@
|
||||||
|
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/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';
|
||||||
|
|
||||||
|
import 'children/sys_protocol.dart';
|
||||||
|
import 'login_logic.dart';
|
||||||
|
|
||||||
|
class LoginDesktopPage extends StatefulWidget {
|
||||||
|
const LoginDesktopPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<LoginDesktopPage> createState() => _LoginDesktopPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LoginDesktopPageState extends State<LoginDesktopPage> {
|
||||||
|
final logic = Get.find<LoginLogic>();
|
||||||
|
final upgradeLogic = Get.find<UpgradeLogic>();
|
||||||
|
final state = Get.find<LoginLogic>().state;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle(
|
||||||
|
statusBarIconBrightness: Brightness.dark,
|
||||||
|
statusBarColor: Colors.transparent, //状态栏背景颜色
|
||||||
|
));
|
||||||
|
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||||
|
await sysProtocol(context);
|
||||||
|
});
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return GestureDetector(
|
||||||
|
behavior: HitTestBehavior.translucent,
|
||||||
|
onTap: () {
|
||||||
|
Utils.hideKeyboard();
|
||||||
|
},
|
||||||
|
child: Scaffold(
|
||||||
|
backgroundColor: const Color(0xFFF0F2F5),
|
||||||
|
body: Center(
|
||||||
|
child: Container(
|
||||||
|
width: 400.w,
|
||||||
|
padding: EdgeInsets.all(32.r),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(8.r),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black.withOpacity(0.1),
|
||||||
|
blurRadius: 20,
|
||||||
|
offset: const Offset(0, 10),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Image.asset('assets/images/login_logo_icon.png', height: 64.r),
|
||||||
|
SizedBox(height: 24.h),
|
||||||
|
Text(
|
||||||
|
'欢迎登录',
|
||||||
|
style: TextStyle(fontSize: 24.sp, fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
SizedBox(height: 24.h),
|
||||||
|
_buildTextField(
|
||||||
|
controller: state.userNameController,
|
||||||
|
hintText: "请输入账号",
|
||||||
|
prefixIcon: Icons.person_outline,
|
||||||
|
),
|
||||||
|
SizedBox(height: 20.h),
|
||||||
|
Obx(
|
||||||
|
() => _buildPasswordField(
|
||||||
|
controller: state.passwordController,
|
||||||
|
hintText: "请输入密码",
|
||||||
|
prefixIcon: Icons.lock_outline,
|
||||||
|
obscureText: state.isShowPwd.value,
|
||||||
|
onToggleVisibility: () {
|
||||||
|
state.isShowPwd.value = !state.isShowPwd.value;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 20.h),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Obx(() => Checkbox(
|
||||||
|
value: state.keepPwd.value,
|
||||||
|
onChanged: (value) {
|
||||||
|
state.keepPwd.value = value ?? false;
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
const Text('记住密码'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
InkWell(
|
||||||
|
onTap: () => Get.toNamed(Routes.register),
|
||||||
|
child: Text('账号注册', style: TextStyle(color: Theme.of(context).primaryColor)),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SizedBox(height: 20.h),
|
||||||
|
InkWell(
|
||||||
|
onTap: () => easyThrottle('LOGIN_EASYTHROTTLE', () async {
|
||||||
|
Utils.hideKeyboard();
|
||||||
|
await Future.delayed(Duration.zero);
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) => logic.toLogin(context, upgradeLogic));
|
||||||
|
}),
|
||||||
|
child: Obx(() {
|
||||||
|
return Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: state.canLogin.value ? const Color(0xFF8C68FF) : const Color(0xFFdddddd),
|
||||||
|
borderRadius: BorderRadius.all(
|
||||||
|
Radius.circular(8.r),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
alignment: Alignment.center,
|
||||||
|
width: double.infinity,
|
||||||
|
height: 50.h,
|
||||||
|
child: quickText('登 录', size: 18.sp, color: Colors.white));
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
SizedBox(height: 20.h),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Obx(() => Checkbox(
|
||||||
|
value: state.readAgreement.value,
|
||||||
|
onChanged: (value) {
|
||||||
|
state.readAgreement.value = value ?? false;
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
const Text('我已阅读并同意'),
|
||||||
|
InkWell(
|
||||||
|
onTap: () {
|
||||||
|
Get.toNamed(Routes.agreementPage, arguments: {"type": AGREEMENT_KEY.USER_AGREEMENT.name});
|
||||||
|
},
|
||||||
|
child: Text(
|
||||||
|
'《用户协议》',
|
||||||
|
style: TextStyle(color: Theme.of(context).primaryColor),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Text('和'),
|
||||||
|
InkWell(
|
||||||
|
onTap: () {
|
||||||
|
Get.toNamed(Routes.agreementPage, arguments: {"type": AGREEMENT_KEY.PRIVACY_GREEMENT.name});
|
||||||
|
},
|
||||||
|
child: Text(
|
||||||
|
'《隐私协议》',
|
||||||
|
style: TextStyle(color: Theme.of(context).primaryColor),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildTextField({
|
||||||
|
required TextEditingController controller,
|
||||||
|
required String hintText,
|
||||||
|
required IconData prefixIcon,
|
||||||
|
}) {
|
||||||
|
return TextField(
|
||||||
|
controller: controller,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: hintText,
|
||||||
|
prefixIcon: Icon(prefixIcon),
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8.r),
|
||||||
|
borderSide: const BorderSide(color: Colors.grey),
|
||||||
|
),
|
||||||
|
enabledBorder: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8.r),
|
||||||
|
borderSide: BorderSide(color: Colors.grey.shade300),
|
||||||
|
),
|
||||||
|
focusedBorder: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8.r),
|
||||||
|
borderSide: BorderSide(color: Theme.of(context).primaryColor),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildPasswordField({
|
||||||
|
required TextEditingController controller,
|
||||||
|
required String hintText,
|
||||||
|
required IconData prefixIcon,
|
||||||
|
required bool obscureText,
|
||||||
|
required VoidCallback onToggleVisibility,
|
||||||
|
}) {
|
||||||
|
return TextField(
|
||||||
|
controller: controller,
|
||||||
|
obscureText: obscureText,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: hintText,
|
||||||
|
prefixIcon: Icon(prefixIcon),
|
||||||
|
suffixIcon: IconButton(
|
||||||
|
icon: Icon(obscureText ? Icons.visibility_off : Icons.visibility),
|
||||||
|
onPressed: onToggleVisibility,
|
||||||
|
),
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8.r),
|
||||||
|
borderSide: const BorderSide(color: Colors.grey),
|
||||||
|
),
|
||||||
|
enabledBorder: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8.r),
|
||||||
|
borderSide: BorderSide(color: Colors.grey.shade300),
|
||||||
|
),
|
||||||
|
focusedBorder: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8.r),
|
||||||
|
borderSide: BorderSide(color: Theme.of(context).primaryColor),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,7 +4,8 @@ import 'package:flutter_easyloading/flutter_easyloading.dart';
|
||||||
import 'package:get/get.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/mixins/request_tool_mixin.dart';
|
||||||
import 'package:making_school_asignment_app/common/store/user_store.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/storage.dart';
|
||||||
import 'package:making_school_asignment_app/common/utils/toast_utils.dart';
|
import 'package:making_school_asignment_app/common/utils/toast_utils.dart';
|
||||||
import 'package:making_school_asignment_app/common/utils/utils.dart';
|
import 'package:making_school_asignment_app/common/utils/utils.dart';
|
||||||
|
|
@ -34,7 +35,7 @@ class LoginLogic extends GetxController with RequestToolMixin {
|
||||||
}
|
}
|
||||||
// state.userNameController.text = "AppleTester";
|
// state.userNameController.text = "AppleTester";
|
||||||
// state.passwordController.text = "AppleTester123!";
|
// state.passwordController.text = "AppleTester123!";
|
||||||
|
|
||||||
// state.userNameController.text = "SZJY200504252";
|
// state.userNameController.text = "SZJY200504252";
|
||||||
// state.passwordController.text = "504252";
|
// state.passwordController.text = "504252";
|
||||||
|
|
||||||
|
|
@ -66,11 +67,9 @@ class LoginLogic extends GetxController with RequestToolMixin {
|
||||||
|
|
||||||
void toMsg(msg, [bool error = true]) {
|
void toMsg(msg, [bool error = true]) {
|
||||||
if (error) {
|
if (error) {
|
||||||
WidgetsBinding.instance
|
WidgetsBinding.instance.addPostFrameCallback((_) => ToastUtils.showError(msg));
|
||||||
.addPostFrameCallback((_) => ToastUtils.showError(msg));
|
|
||||||
} else {
|
} else {
|
||||||
WidgetsBinding.instance
|
WidgetsBinding.instance.addPostFrameCallback((_) => ToastUtils.showInfo(msg));
|
||||||
.addPostFrameCallback((_) => ToastUtils.showInfo(msg));
|
|
||||||
}
|
}
|
||||||
state.canLogin.value = true;
|
state.canLogin.value = true;
|
||||||
}
|
}
|
||||||
|
|
@ -88,6 +87,15 @@ class LoginLogic extends GetxController with RequestToolMixin {
|
||||||
EasyLoading.show(status: 'loading...');
|
EasyLoading.show(status: 'loading...');
|
||||||
|
|
||||||
try {
|
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);
|
await getClient().toLogin(userName, userPwd);
|
||||||
String? nameidentifier = UserStore.to.userInfo.value?.nameidentifier;
|
String? nameidentifier = UserStore.to.userInfo.value?.nameidentifier;
|
||||||
if (nameidentifier == null) {
|
if (nameidentifier == null) {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,404 @@
|
||||||
|
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/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';
|
||||||
|
|
||||||
|
import 'children/sys_protocol.dart';
|
||||||
|
import 'login_logic.dart';
|
||||||
|
|
||||||
|
class LoginMobilePage extends StatefulWidget {
|
||||||
|
const LoginMobilePage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<LoginMobilePage> createState() => _LoginMobilePageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LoginMobilePageState extends State<LoginMobilePage> {
|
||||||
|
final logic = Get.find<LoginLogic>();
|
||||||
|
final upgradeLogic = Get.find<UpgradeLogic>();
|
||||||
|
final state = Get.find<LoginLogic>().state;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle(
|
||||||
|
statusBarIconBrightness: Brightness.light,
|
||||||
|
statusBarColor: Colors.transparent, //状态栏背景颜色
|
||||||
|
));
|
||||||
|
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||||
|
await sysProtocol(context);
|
||||||
|
|
||||||
|
/// 为了发布各平台方便审核 不能再登陆页面直接弹出审核
|
||||||
|
// await Future.delayed(Duration.zero, () => upgradeLogic.getAppUpgrade(context));
|
||||||
|
});
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return GestureDetector(
|
||||||
|
behavior: HitTestBehavior.translucent,
|
||||||
|
onTap: () {
|
||||||
|
Utils.hideKeyboard();
|
||||||
|
// FocusScope.of(context).requestFocus(state.theFocus);
|
||||||
|
},
|
||||||
|
child: AnnotatedRegion(
|
||||||
|
value: const SystemUiOverlayStyle(
|
||||||
|
statusBarColor: Colors.transparent,
|
||||||
|
systemNavigationBarIconBrightness: Brightness.light,
|
||||||
|
statusBarIconBrightness: Brightness.light,
|
||||||
|
statusBarBrightness: Brightness.dark,
|
||||||
|
),
|
||||||
|
child: Scaffold(
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
resizeToAvoidBottomInset: false,
|
||||||
|
body: Stack(
|
||||||
|
children: [
|
||||||
|
Positioned(
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
child: Image.asset(
|
||||||
|
'assets/images/logo_banner.png',
|
||||||
|
width: Get.width,
|
||||||
|
fit: BoxFit.fill,
|
||||||
|
)),
|
||||||
|
SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
height: 130.r,
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
width: 77.w,
|
||||||
|
height: 77.w,
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 77.w,
|
||||||
|
width: 77.w,
|
||||||
|
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),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius:
|
||||||
|
BorderRadius.only(topLeft: Radius.circular(30.r), topRight: Radius.circular(30.r)),
|
||||||
|
/*boxShadow: const [
|
||||||
|
BoxShadow(
|
||||||
|
color: Color.fromRGBO(46, 91, 255, 0.1),
|
||||||
|
offset: Offset.zero, //阴影y轴偏移量
|
||||||
|
blurRadius: 100, //阴影模糊程度
|
||||||
|
spreadRadius: 100, //阴影扩散程度
|
||||||
|
)
|
||||||
|
],*/
|
||||||
|
),
|
||||||
|
child: Column(children: [
|
||||||
|
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)),
|
||||||
|
),
|
||||||
|
child: TextField(
|
||||||
|
controller: state.userNameController,
|
||||||
|
/* maxLines: 1,
|
||||||
|
maxLength: 20,*/
|
||||||
|
textInputAction: TextInputAction.next,
|
||||||
|
onEditingComplete: () {
|
||||||
|
Get.focusScope?.nextFocus();
|
||||||
|
// FocusScope.of(context).requestFocus(_pwdFocus);
|
||||||
|
},
|
||||||
|
style: TextStyle(
|
||||||
|
color: const Color(0xFF434343),
|
||||||
|
fontSize: 14.sp,
|
||||||
|
),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: "请输入账号",
|
||||||
|
hintStyle: TextStyle(
|
||||||
|
fontSize: 14.sp,
|
||||||
|
color: const Color(0xFF434343),
|
||||||
|
),
|
||||||
|
// labelText: "账号",
|
||||||
|
labelStyle: TextStyle(
|
||||||
|
fontSize: 14.sp,
|
||||||
|
color: const Color(0xFF434343),
|
||||||
|
),
|
||||||
|
border: InputBorder.none,
|
||||||
|
isDense: true,
|
||||||
|
prefixIconConstraints: BoxConstraints(
|
||||||
|
minHeight: 10.w,
|
||||||
|
minWidth: 10.h,
|
||||||
|
),
|
||||||
|
prefixIcon: Padding(
|
||||||
|
padding: EdgeInsets.only(right: 5.r),
|
||||||
|
child: Image.asset(
|
||||||
|
'assets/images/login_account.png',
|
||||||
|
width: 15.r,
|
||||||
|
height: 15.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)),
|
||||||
|
),
|
||||||
|
child: TextField(
|
||||||
|
focusNode: state.pwdFocus,
|
||||||
|
controller: state.passwordController,
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
maxLines: 1,
|
||||||
|
obscureText: state.isShowPwd.value,
|
||||||
|
//隐藏密码显示
|
||||||
|
// textInputAction: state.isShowPwd.value?TextInputAction.go:TextInputAction.next,
|
||||||
|
textInputAction: TextInputAction.send,
|
||||||
|
onSubmitted: (_) => easyThrottle('LOGIN_EASYTHROTTLE', () async {
|
||||||
|
Utils.hideKeyboard();
|
||||||
|
await Future.delayed(const Duration(milliseconds: 300));
|
||||||
|
WidgetsBinding.instance
|
||||||
|
.addPostFrameCallback((_) => logic.toLogin(context, upgradeLogic));
|
||||||
|
}),
|
||||||
|
style: TextStyle(
|
||||||
|
color: const Color(0xFF434343),
|
||||||
|
fontSize: 14.sp,
|
||||||
|
),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: "请输入密码",
|
||||||
|
prefixIconConstraints: BoxConstraints(
|
||||||
|
minHeight: 10.w,
|
||||||
|
minWidth: 10.h,
|
||||||
|
),
|
||||||
|
prefixIcon: Padding(
|
||||||
|
padding: EdgeInsets.only(right: 5.r),
|
||||||
|
child: Image.asset(
|
||||||
|
'assets/images/login_pwd.png',
|
||||||
|
width: 15.r,
|
||||||
|
height: 15.r,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
suffixIconConstraints: BoxConstraints(
|
||||||
|
minHeight: 10.w,
|
||||||
|
minWidth: 10.h,
|
||||||
|
),
|
||||||
|
suffixIcon: InkWell(
|
||||||
|
onTap: () {
|
||||||
|
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',
|
||||||
|
width: 15.r,
|
||||||
|
height: 15.r,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
hintStyle: TextStyle(
|
||||||
|
fontSize: 14.sp,
|
||||||
|
color: const Color(0xFF434343),
|
||||||
|
),
|
||||||
|
border: InputBorder.none,
|
||||||
|
// labelText: "密码",
|
||||||
|
isDense: true,
|
||||||
|
labelStyle: TextStyle(
|
||||||
|
fontSize: 14.sp,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
SizedBox(height: 10.h),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
width: 25.w,
|
||||||
|
padding: EdgeInsets.only(right: 0.w),
|
||||||
|
child: Obx(() {
|
||||||
|
return Transform.scale(
|
||||||
|
scale: 1.0,
|
||||||
|
child: Checkbox(
|
||||||
|
// activeColor: Colors.transparent, //去掉勾选时背景颜色
|
||||||
|
|
||||||
|
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;
|
||||||
|
},
|
||||||
|
side: WidgetStateBorderSide.resolveWith(
|
||||||
|
(Set<WidgetState> states) {
|
||||||
|
if (states.contains(WidgetState.selected)) {
|
||||||
|
//修改勾选时边框颜色为红色
|
||||||
|
return BorderSide(
|
||||||
|
width: 1.5.r, color: Theme.of(context).primaryColor);
|
||||||
|
}
|
||||||
|
//修改默认时边框颜色为绿色
|
||||||
|
return BorderSide(width: 1.r, color: const Color(0xFF434343));
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
InkWell(
|
||||||
|
onTap: () {
|
||||||
|
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;
|
||||||
|
},
|
||||||
|
child: Text(
|
||||||
|
'记住密码',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12.sp,
|
||||||
|
color: const Color(0xFF434343),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
InkWell(
|
||||||
|
onTap: () => Get.toNamed(Routes.register),
|
||||||
|
child: quickText('账号注册', color: const Color(0xFF434343)),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
InkWell(
|
||||||
|
onTap: () => easyThrottle('LOGIN_EASYTHROTTLE', () async {
|
||||||
|
Utils.hideKeyboard();
|
||||||
|
await Future.delayed(Duration.zero);
|
||||||
|
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),
|
||||||
|
/*boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color:
|
||||||
|
const Color.fromRGBO(76, 199, 147, 0.5),
|
||||||
|
offset: Offset(6.w, 10.h), //阴影y轴偏移量
|
||||||
|
blurRadius: 14, //阴影模糊程度
|
||||||
|
spreadRadius: 0.5, //阴影扩散程度
|
||||||
|
)
|
||||||
|
],*/
|
||||||
|
borderRadius: BorderRadius.all(
|
||||||
|
Radius.circular(17.w),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
alignment: Alignment.center,
|
||||||
|
width: double.infinity,
|
||||||
|
height: 50.h,
|
||||||
|
child: quickText('登 录', size: 18.sp, color: Colors.white));
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
width: 25.w,
|
||||||
|
padding: EdgeInsets.only(right: 0.w),
|
||||||
|
child: Obx(() {
|
||||||
|
return Transform.scale(
|
||||||
|
scale: 1.0,
|
||||||
|
child: Checkbox(
|
||||||
|
activeColor: Theme.of(context).primaryColor,
|
||||||
|
checkColor: Colors.white,
|
||||||
|
value: state.readAgreement.value,
|
||||||
|
onChanged: (value) {
|
||||||
|
Utils.hideKeyboard();
|
||||||
|
/* FocusScope.of(context).requestFocus(
|
||||||
|
state.pwdFocus);
|
||||||
|
FocusScope.of(context).requestFocus(
|
||||||
|
state.theFocus);*/
|
||||||
|
state.readAgreement.value = value ?? false;
|
||||||
|
},
|
||||||
|
side: WidgetStateBorderSide.resolveWith(
|
||||||
|
(Set<WidgetState> states) {
|
||||||
|
if (states.contains(WidgetState.selected)) {
|
||||||
|
//修改勾选时边框颜色为红色
|
||||||
|
return BorderSide(width: 1.5.r, color: Theme.of(context).primaryColor);
|
||||||
|
}
|
||||||
|
//修改默认时边框颜色为绿色
|
||||||
|
return BorderSide(width: 1.r, color: const Color(0xFF434343));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
InkWell(
|
||||||
|
onTap: () {
|
||||||
|
Get.toNamed(Routes.agreementPage,
|
||||||
|
arguments: {"type": AGREEMENT_KEY.USER_AGREEMENT.name});
|
||||||
|
},
|
||||||
|
child: quickText(
|
||||||
|
'请仔细阅读',
|
||||||
|
size: 11.sp,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
InkWell(
|
||||||
|
onTap: () {
|
||||||
|
Get.toNamed(Routes.agreementPage,
|
||||||
|
arguments: {"type": AGREEMENT_KEY.USER_AGREEMENT.name});
|
||||||
|
},
|
||||||
|
child: Text(
|
||||||
|
'《用户协议》',
|
||||||
|
style: TextStyle(fontSize: 12.r, color: Theme.of(context).primaryColor),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
InkWell(
|
||||||
|
onTap: () {
|
||||||
|
Get.toNamed(Routes.agreementPage,
|
||||||
|
arguments: {"type": AGREEMENT_KEY.PRIVACY_GREEMENT.name});
|
||||||
|
},
|
||||||
|
child: quickText(
|
||||||
|
'《隐私协议》',
|
||||||
|
size: 12.sp,
|
||||||
|
color: Theme.of(context).primaryColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,447 +1,13 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:making_school_asignment_app/common/utils/platform_utils.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:making_school_asignment_app/page/login_page/login_desktop_view.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:making_school_asignment_app/page/login_page/login_mobile_view.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/utils.dart';
|
|
||||||
import 'package:making_school_asignment_app/page/global_widget/my_text.dart';
|
|
||||||
import 'package:making_school_asignment_app/routes/app_pages.dart';
|
|
||||||
|
|
||||||
import 'children/sys_protocol.dart';
|
class LoginPage extends StatelessWidget {
|
||||||
import 'login_logic.dart';
|
|
||||||
|
|
||||||
class LoginPage extends StatefulWidget {
|
|
||||||
const LoginPage({super.key});
|
const LoginPage({super.key});
|
||||||
|
|
||||||
@override
|
|
||||||
State<LoginPage> createState() => _LoginPageState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _LoginPageState extends State<LoginPage> {
|
|
||||||
final logic = Get.find<LoginLogic>();
|
|
||||||
final upgradeLogic = Get.find<UpgradeLogic>();
|
|
||||||
final state = Get.find<LoginLogic>().state;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle(
|
|
||||||
statusBarIconBrightness: Brightness.light,
|
|
||||||
statusBarColor: Colors.transparent, //状态栏背景颜色
|
|
||||||
));
|
|
||||||
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
|
||||||
await sysProtocol(context);
|
|
||||||
|
|
||||||
/// 为了发布各平台方便审核 不能再登陆页面直接弹出审核
|
|
||||||
// await Future.delayed(Duration.zero, () => upgradeLogic.getAppUpgrade(context));
|
|
||||||
});
|
|
||||||
super.initState();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return GestureDetector(
|
return PlatformUtils.isDesktop ? const LoginDesktopPage() : const LoginMobilePage();
|
||||||
behavior: HitTestBehavior.translucent,
|
|
||||||
onTap: () {
|
|
||||||
Utils.hideKeyboard();
|
|
||||||
// FocusScope.of(context).requestFocus(state.theFocus);
|
|
||||||
},
|
|
||||||
child: AnnotatedRegion(
|
|
||||||
value: const SystemUiOverlayStyle(
|
|
||||||
statusBarColor: Colors.transparent,
|
|
||||||
systemNavigationBarIconBrightness: Brightness.light,
|
|
||||||
statusBarIconBrightness: Brightness.light,
|
|
||||||
statusBarBrightness: Brightness.dark,
|
|
||||||
),
|
|
||||||
child: Scaffold(
|
|
||||||
backgroundColor: Colors.white,
|
|
||||||
resizeToAvoidBottomInset: false,
|
|
||||||
body: Stack(
|
|
||||||
children: [
|
|
||||||
Positioned(
|
|
||||||
top: 0,
|
|
||||||
left: 0,
|
|
||||||
child: Image.asset(
|
|
||||||
'assets/images/logo_banner.png',
|
|
||||||
width: Get.width,
|
|
||||||
fit: BoxFit.fill,
|
|
||||||
)),
|
|
||||||
SingleChildScrollView(
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
SizedBox(
|
|
||||||
height: 130.r,
|
|
||||||
),
|
|
||||||
Container(
|
|
||||||
width: 77.w,
|
|
||||||
height: 77.w,
|
|
||||||
alignment: Alignment.center,
|
|
||||||
child: SizedBox(
|
|
||||||
height: 77.w,
|
|
||||||
width: 77.w,
|
|
||||||
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),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.white,
|
|
||||||
borderRadius: BorderRadius.only(
|
|
||||||
topLeft: Radius.circular(30.r),
|
|
||||||
topRight: Radius.circular(30.r)),
|
|
||||||
/*boxShadow: const [
|
|
||||||
BoxShadow(
|
|
||||||
color: Color.fromRGBO(46, 91, 255, 0.1),
|
|
||||||
offset: Offset.zero, //阴影y轴偏移量
|
|
||||||
blurRadius: 100, //阴影模糊程度
|
|
||||||
spreadRadius: 100, //阴影扩散程度
|
|
||||||
)
|
|
||||||
],*/
|
|
||||||
),
|
|
||||||
child: Column(children: [
|
|
||||||
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)),
|
|
||||||
),
|
|
||||||
child: TextField(
|
|
||||||
controller: state.userNameController,
|
|
||||||
/* maxLines: 1,
|
|
||||||
maxLength: 20,*/
|
|
||||||
enableInteractiveSelection: false,
|
|
||||||
textInputAction: TextInputAction.next,
|
|
||||||
onEditingComplete: () {
|
|
||||||
Get.focusScope?.nextFocus();
|
|
||||||
// FocusScope.of(context).requestFocus(_pwdFocus);
|
|
||||||
},
|
|
||||||
style: TextStyle(
|
|
||||||
color: const Color(0xFF434343),
|
|
||||||
fontSize: 14.sp,
|
|
||||||
),
|
|
||||||
decoration: InputDecoration(
|
|
||||||
hintText: "请输入账号",
|
|
||||||
hintStyle: TextStyle(
|
|
||||||
fontSize: 14.sp,
|
|
||||||
color: const Color(0xFF434343),
|
|
||||||
),
|
|
||||||
// labelText: "账号",
|
|
||||||
labelStyle: TextStyle(
|
|
||||||
fontSize: 14.sp,
|
|
||||||
color: const Color(0xFF434343),
|
|
||||||
),
|
|
||||||
border: InputBorder.none,
|
|
||||||
isDense: true,
|
|
||||||
prefixIconConstraints: BoxConstraints(
|
|
||||||
minHeight: 10.w,
|
|
||||||
minWidth: 10.h,
|
|
||||||
),
|
|
||||||
prefixIcon: Padding(
|
|
||||||
padding: EdgeInsets.only(right: 5.r),
|
|
||||||
child: Image.asset(
|
|
||||||
'assets/images/login_account.png',
|
|
||||||
width: 15.r,
|
|
||||||
height: 15.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)),
|
|
||||||
),
|
|
||||||
child: TextField(
|
|
||||||
focusNode: state.pwdFocus,
|
|
||||||
controller: state.passwordController,
|
|
||||||
// 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 {
|
|
||||||
Utils.hideKeyboard();
|
|
||||||
await Future.delayed(
|
|
||||||
const Duration(milliseconds: 300));
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback(
|
|
||||||
(_) =>
|
|
||||||
logic.toLogin(context, upgradeLogic));
|
|
||||||
}),
|
|
||||||
style: TextStyle(
|
|
||||||
color: const Color(0xFF434343),
|
|
||||||
fontSize: 14.sp,
|
|
||||||
),
|
|
||||||
decoration: InputDecoration(
|
|
||||||
hintText: "请输入密码",
|
|
||||||
prefixIconConstraints: BoxConstraints(
|
|
||||||
minHeight: 10.w,
|
|
||||||
minWidth: 10.h,
|
|
||||||
),
|
|
||||||
prefixIcon: Padding(
|
|
||||||
padding: EdgeInsets.only(right: 5.r),
|
|
||||||
child: Image.asset(
|
|
||||||
'assets/images/login_pwd.png',
|
|
||||||
width: 15.r,
|
|
||||||
height: 15.r,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
suffixIconConstraints: BoxConstraints(
|
|
||||||
minHeight: 10.w,
|
|
||||||
minWidth: 10.h,
|
|
||||||
),
|
|
||||||
suffixIcon: InkWell(
|
|
||||||
onTap: () {
|
|
||||||
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',
|
|
||||||
width: 15.r,
|
|
||||||
height: 15.r,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
hintStyle: TextStyle(
|
|
||||||
fontSize: 14.sp,
|
|
||||||
color: const Color(0xFF434343),
|
|
||||||
),
|
|
||||||
border: InputBorder.none,
|
|
||||||
// labelText: "密码",
|
|
||||||
isDense: true,
|
|
||||||
labelStyle: TextStyle(
|
|
||||||
fontSize: 14.sp,
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
SizedBox(height: 10.h),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
width: 25.w,
|
|
||||||
padding: EdgeInsets.only(right: 0.w),
|
|
||||||
child: Obx(() {
|
|
||||||
return Transform.scale(
|
|
||||||
scale: 1.0,
|
|
||||||
child: Checkbox(
|
|
||||||
// activeColor: Colors.transparent, //去掉勾选时背景颜色
|
|
||||||
|
|
||||||
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;
|
|
||||||
},
|
|
||||||
side: WidgetStateBorderSide
|
|
||||||
.resolveWith(
|
|
||||||
(Set<WidgetState> states) {
|
|
||||||
if (states.contains(
|
|
||||||
WidgetState.selected)) {
|
|
||||||
//修改勾选时边框颜色为红色
|
|
||||||
return BorderSide(
|
|
||||||
width: 1.5.r,
|
|
||||||
color: Theme.of(context)
|
|
||||||
.primaryColor);
|
|
||||||
}
|
|
||||||
//修改默认时边框颜色为绿色
|
|
||||||
return BorderSide(
|
|
||||||
width: 1.r,
|
|
||||||
color: const Color(
|
|
||||||
0xFF434343));
|
|
||||||
},
|
|
||||||
)),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
InkWell(
|
|
||||||
onTap: () {
|
|
||||||
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;
|
|
||||||
},
|
|
||||||
child: Text(
|
|
||||||
'记住密码',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 12.sp,
|
|
||||||
color: const Color(0xFF434343),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
InkWell(
|
|
||||||
onTap: () => Get.toNamed(Routes.register),
|
|
||||||
child: quickText('账号注册',
|
|
||||||
color: const Color(0xFF434343)),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
InkWell(
|
|
||||||
onTap: () =>
|
|
||||||
easyThrottle('LOGIN_EASYTHROTTLE', () async {
|
|
||||||
Utils.hideKeyboard();
|
|
||||||
await Future.delayed(Duration.zero);
|
|
||||||
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),
|
|
||||||
/*boxShadow: [
|
|
||||||
BoxShadow(
|
|
||||||
color:
|
|
||||||
const Color.fromRGBO(76, 199, 147, 0.5),
|
|
||||||
offset: Offset(6.w, 10.h), //阴影y轴偏移量
|
|
||||||
blurRadius: 14, //阴影模糊程度
|
|
||||||
spreadRadius: 0.5, //阴影扩散程度
|
|
||||||
)
|
|
||||||
],*/
|
|
||||||
borderRadius: BorderRadius.all(
|
|
||||||
Radius.circular(17.w),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
alignment: Alignment.center,
|
|
||||||
width: double.infinity,
|
|
||||||
height: 50.h,
|
|
||||||
child: quickText('登 录',
|
|
||||||
size: 18.sp, color: Colors.white));
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
width: 25.w,
|
|
||||||
padding: EdgeInsets.only(right: 0.w),
|
|
||||||
child: Obx(() {
|
|
||||||
return Transform.scale(
|
|
||||||
scale: 1.0,
|
|
||||||
child: Checkbox(
|
|
||||||
activeColor: Theme.of(context).primaryColor,
|
|
||||||
checkColor: Colors.white,
|
|
||||||
value: state.readAgreement.value,
|
|
||||||
onChanged: (value) {
|
|
||||||
Utils.hideKeyboard();
|
|
||||||
/* FocusScope.of(context).requestFocus(
|
|
||||||
state.pwdFocus);
|
|
||||||
FocusScope.of(context).requestFocus(
|
|
||||||
state.theFocus);*/
|
|
||||||
state.readAgreement.value =
|
|
||||||
value ?? false;
|
|
||||||
},
|
|
||||||
side: WidgetStateBorderSide.resolveWith(
|
|
||||||
(Set<WidgetState> states) {
|
|
||||||
if (states
|
|
||||||
.contains(WidgetState.selected)) {
|
|
||||||
//修改勾选时边框颜色为红色
|
|
||||||
return BorderSide(
|
|
||||||
width: 1.5.r,
|
|
||||||
color: Theme.of(context)
|
|
||||||
.primaryColor);
|
|
||||||
}
|
|
||||||
//修改默认时边框颜色为绿色
|
|
||||||
return BorderSide(
|
|
||||||
width: 1.r,
|
|
||||||
color: const Color(0xFF434343));
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
InkWell(
|
|
||||||
onTap: () {
|
|
||||||
Get.toNamed(Routes.agreementPage, arguments: {
|
|
||||||
"type": AGREEMENT_KEY.USER_AGREEMENT.name
|
|
||||||
});
|
|
||||||
},
|
|
||||||
child: quickText(
|
|
||||||
'请仔细阅读',
|
|
||||||
size: 11.sp,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
InkWell(
|
|
||||||
onTap: () {
|
|
||||||
Get.toNamed(Routes.agreementPage, arguments: {
|
|
||||||
"type": AGREEMENT_KEY.USER_AGREEMENT.name
|
|
||||||
});
|
|
||||||
},
|
|
||||||
child: Text(
|
|
||||||
'《用户协议》',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 12.r,
|
|
||||||
color: Theme.of(context).primaryColor),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
InkWell(
|
|
||||||
onTap: () {
|
|
||||||
Get.toNamed(Routes.agreementPage, arguments: {
|
|
||||||
"type": AGREEMENT_KEY.PRIVACY_GREEMENT.name
|
|
||||||
});
|
|
||||||
},
|
|
||||||
child: quickText(
|
|
||||||
'《隐私协议》',
|
|
||||||
size: 12.sp,
|
|
||||||
color: Theme.of(context).primaryColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
]),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,118 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:making_school_asignment_app/common/mixins/getx_keepalive_widget.dart';
|
||||||
|
import 'package:making_school_asignment_app/page/home_page/children/read_over/widget/annotate_list.dart';
|
||||||
|
import 'package:making_school_asignment_app/routes/app_pages.dart';
|
||||||
|
|
||||||
|
import 'work_logic.dart';
|
||||||
|
|
||||||
|
class WorkDesktopPage extends GetxKeepAliveWidget<WorkLogic> {
|
||||||
|
const WorkDesktopPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget buildContent(BuildContext context, WorkLogic controller) {
|
||||||
|
final state = controller.state;
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: const Color(0xFFF0F2F5),
|
||||||
|
body: Column(
|
||||||
|
children: [
|
||||||
|
_buildHeader(context, controller, state),
|
||||||
|
Expanded(
|
||||||
|
child: Container(
|
||||||
|
padding: EdgeInsets.all(24.r),
|
||||||
|
child: Card(
|
||||||
|
elevation: 2,
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12.r)),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
_buildTabBar(context, controller, state),
|
||||||
|
Expanded(
|
||||||
|
child: Obx(() {
|
||||||
|
return AnnotateList(
|
||||||
|
tabIndex: state.tabIndex.value,
|
||||||
|
assessType: 1,
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildHeader(BuildContext context, WorkLogic controller, dynamic state) {
|
||||||
|
return Container(
|
||||||
|
padding: EdgeInsets.all(24.r),
|
||||||
|
color: Colors.white,
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'作业批阅',
|
||||||
|
style: TextStyle(fontSize: 24.sp, fontWeight: FontWeight.bold, color: const Color(0xFF333333)),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
Get.toNamed(Routes.studentHistoryWorkPage, arguments: {'page': 'set'});
|
||||||
|
},
|
||||||
|
icon: Icon(Icons.settings, size: 24.sp, color: Colors.grey[600]),
|
||||||
|
tooltip: '设置',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildTabBar(BuildContext context, WorkLogic controller, dynamic state) {
|
||||||
|
return Container(
|
||||||
|
padding: EdgeInsets.all(16.r),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Container(
|
||||||
|
height: 48.h,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.grey[100],
|
||||||
|
borderRadius: BorderRadius.circular(8.r),
|
||||||
|
),
|
||||||
|
child: TabBar(
|
||||||
|
controller: controller.tabController,
|
||||||
|
indicator: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(6.r),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black.withOpacity(0.1),
|
||||||
|
blurRadius: 4,
|
||||||
|
offset: const Offset(0, 2),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
indicatorPadding: EdgeInsets.all(4.r),
|
||||||
|
labelColor: Theme.of(context).primaryColor,
|
||||||
|
unselectedLabelColor: Colors.grey[600],
|
||||||
|
labelStyle: TextStyle(fontSize: 14.sp, fontWeight: FontWeight.w600),
|
||||||
|
unselectedLabelStyle: TextStyle(fontSize: 14.sp),
|
||||||
|
onTap: (index) {
|
||||||
|
state.tabIndex.value = index;
|
||||||
|
if (index == 1 && state.completedToRefresh) {
|
||||||
|
state.completedToRefresh = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tabs: const [
|
||||||
|
Tab(text: '待批阅'),
|
||||||
|
Tab(text: '已批阅'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,141 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:making_school_asignment_app/common/mixins/getx_keepalive_widget.dart';
|
||||||
|
import 'package:making_school_asignment_app/page/global_widget/my_text.dart';
|
||||||
|
import 'package:making_school_asignment_app/page/home_page/children/read_over/widget/annotate_list.dart';
|
||||||
|
import 'package:making_school_asignment_app/routes/app_pages.dart';
|
||||||
|
|
||||||
|
import 'work_logic.dart';
|
||||||
|
|
||||||
|
class WorkMobilePage extends GetxKeepAliveWidget<WorkLogic> {
|
||||||
|
const WorkMobilePage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget buildContent(BuildContext context, WorkLogic controller) {
|
||||||
|
final state = controller.state;
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: const Color.fromRGBO(244, 244, 244, 1),
|
||||||
|
body: Column(
|
||||||
|
children: <Widget>[
|
||||||
|
Container(
|
||||||
|
color: Colors.white,
|
||||||
|
margin: EdgeInsets.only(top: MediaQuery.of(context).padding.top),
|
||||||
|
padding: EdgeInsets.only(bottom: 9.h, top: 4.h),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
flex: 1,
|
||||||
|
child: Container(
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
padding: EdgeInsets.only(left: 10.w),
|
||||||
|
)),
|
||||||
|
Expanded(
|
||||||
|
flex: 4,
|
||||||
|
child: Container(
|
||||||
|
padding: EdgeInsets.symmetric(vertical: 2.h),
|
||||||
|
alignment: Alignment.center,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: const Color.fromRGBO(243, 243, 243, 1),
|
||||||
|
borderRadius: BorderRadius.circular(8.r),
|
||||||
|
),
|
||||||
|
child: TabBar(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
indicatorPadding: EdgeInsets.zero,
|
||||||
|
indicatorWeight: 0,
|
||||||
|
labelPadding: EdgeInsets.symmetric(horizontal: 2.w),
|
||||||
|
controller: controller.tabController,
|
||||||
|
unselectedLabelStyle: TextStyle(
|
||||||
|
fontSize: 14.sp,
|
||||||
|
color: const Color.fromRGBO(69, 83, 100, 1),
|
||||||
|
),
|
||||||
|
labelStyle: TextStyle(
|
||||||
|
fontSize: 14.sp,
|
||||||
|
color: const Color(0xFF4CC793),
|
||||||
|
),
|
||||||
|
// labelColor: const Color.fromRGBO(45, 56, 76, 1),
|
||||||
|
indicator: const BoxDecoration(),
|
||||||
|
onTap: (index) {
|
||||||
|
state.tabIndex.value = index;
|
||||||
|
if (index == 1 && state.completedToRefresh) {
|
||||||
|
// 已阅卷
|
||||||
|
// _refreshController2.callRefresh();
|
||||||
|
state.completedToRefresh = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tabs: <Widget>[
|
||||||
|
Tab(
|
||||||
|
iconMargin: EdgeInsets.zero,
|
||||||
|
height: 34.h,
|
||||||
|
child: Obx(() {
|
||||||
|
return Container(
|
||||||
|
width: 140.w,
|
||||||
|
alignment: Alignment.center,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: state.tabIndex.value == 0 ? const Color.fromRGBO(255, 255, 255, 1) : null,
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(8.r)),
|
||||||
|
),
|
||||||
|
child: quickText(
|
||||||
|
'待批阅',
|
||||||
|
size: 14.sp,
|
||||||
|
color: state.tabIndex.value == 0
|
||||||
|
? Theme.of(context).primaryColor
|
||||||
|
: const Color.fromRGBO(80, 94, 110, 1),
|
||||||
|
fontWeight: state.tabIndex.value == 0 ? FontWeight.bold : null,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
Tab(
|
||||||
|
iconMargin: EdgeInsets.zero,
|
||||||
|
height: 34.h,
|
||||||
|
child: Obx(() {
|
||||||
|
return Container(
|
||||||
|
width: 140.w,
|
||||||
|
alignment: Alignment.center,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: state.tabIndex.value == 1 ? const Color.fromRGBO(255, 255, 255, 1) : null,
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(8.r)),
|
||||||
|
),
|
||||||
|
child: quickText(
|
||||||
|
'已批阅',
|
||||||
|
size: 14.sp,
|
||||||
|
color: state.tabIndex.value == 1
|
||||||
|
? Theme.of(context).primaryColor
|
||||||
|
: const Color.fromRGBO(80, 94, 110, 1),
|
||||||
|
fontWeight: state.tabIndex.value == 1 ? FontWeight.bold : null,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
flex: 1,
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () {
|
||||||
|
Get.toNamed(Routes.studentHistoryWorkPage, arguments: {'page': 'set'});
|
||||||
|
},
|
||||||
|
child: Icon(const IconData(0xe63e, fontFamily: "AlibabaIcon"),
|
||||||
|
color: const Color.fromRGBO(44, 48, 63, 1), size: 24.sp),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Obx(() {
|
||||||
|
return AnnotateList(
|
||||||
|
tabIndex: state.tabIndex.value,
|
||||||
|
assessType: 1,
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,141 +1,13 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:making_school_asignment_app/common/utils/platform_utils.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:making_school_asignment_app/page/work_page/work_desktop_view.dart';
|
||||||
import 'package:making_school_asignment_app/common/mixins/getx_keepalive_widget.dart';
|
import 'package:making_school_asignment_app/page/work_page/work_mobile_view.dart';
|
||||||
import 'package:making_school_asignment_app/page/global_widget/my_text.dart';
|
|
||||||
import 'package:making_school_asignment_app/page/home_page/children/read_over/widget/annotate_list.dart';
|
|
||||||
import 'package:making_school_asignment_app/routes/app_pages.dart';
|
|
||||||
|
|
||||||
import 'work_logic.dart';
|
class WorkPage extends StatelessWidget {
|
||||||
|
|
||||||
class WorkPage extends GetxKeepAliveWidget<WorkLogic> {
|
|
||||||
const WorkPage({super.key});
|
const WorkPage({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget buildContent(BuildContext context, WorkLogic controller) {
|
Widget build(BuildContext context) {
|
||||||
final state = controller.state;
|
return PlatformUtils.isDesktop ? const WorkDesktopPage() : const WorkMobilePage();
|
||||||
return Scaffold(
|
|
||||||
backgroundColor: const Color.fromRGBO(244, 244, 244, 1),
|
|
||||||
body: Column(
|
|
||||||
children: <Widget>[
|
|
||||||
Container(
|
|
||||||
color: Colors.white,
|
|
||||||
margin: EdgeInsets.only(top: MediaQuery.of(context).padding.top),
|
|
||||||
padding: EdgeInsets.only(bottom: 9.h, top: 4.h),
|
|
||||||
child: Row(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
flex: 1,
|
|
||||||
child: Container(
|
|
||||||
alignment: Alignment.centerLeft,
|
|
||||||
padding: EdgeInsets.only(left: 10.w),
|
|
||||||
)),
|
|
||||||
Expanded(
|
|
||||||
flex: 4,
|
|
||||||
child: Container(
|
|
||||||
padding: EdgeInsets.symmetric(vertical: 2.h),
|
|
||||||
alignment: Alignment.center,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: const Color.fromRGBO(243, 243, 243, 1),
|
|
||||||
borderRadius: BorderRadius.circular(8.r),
|
|
||||||
),
|
|
||||||
child: TabBar(
|
|
||||||
padding: EdgeInsets.zero,
|
|
||||||
indicatorPadding: EdgeInsets.zero,
|
|
||||||
indicatorWeight: 0,
|
|
||||||
labelPadding: EdgeInsets.symmetric(horizontal: 2.w),
|
|
||||||
controller: controller.tabController,
|
|
||||||
unselectedLabelStyle: TextStyle(
|
|
||||||
fontSize: 14.sp,
|
|
||||||
color: const Color.fromRGBO(69, 83, 100, 1),
|
|
||||||
),
|
|
||||||
labelStyle: TextStyle(
|
|
||||||
fontSize: 14.sp,
|
|
||||||
color: const Color(0xFF4CC793),
|
|
||||||
),
|
|
||||||
// labelColor: const Color.fromRGBO(45, 56, 76, 1),
|
|
||||||
indicator: const BoxDecoration(),
|
|
||||||
onTap: (index) {
|
|
||||||
state.tabIndex.value = index;
|
|
||||||
if (index == 1 && state.completedToRefresh) {
|
|
||||||
// 已阅卷
|
|
||||||
// _refreshController2.callRefresh();
|
|
||||||
state.completedToRefresh = false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
tabs: <Widget>[
|
|
||||||
Tab(
|
|
||||||
iconMargin: EdgeInsets.zero,
|
|
||||||
height: 34.h,
|
|
||||||
child: Obx(() {
|
|
||||||
return Container(
|
|
||||||
width: 140.w,
|
|
||||||
alignment: Alignment.center,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: state.tabIndex.value == 0 ? const Color.fromRGBO(255, 255, 255, 1) : null,
|
|
||||||
borderRadius: BorderRadius.all(Radius.circular(8.r)),
|
|
||||||
),
|
|
||||||
child: quickText(
|
|
||||||
'待批阅',
|
|
||||||
size: 14.sp,
|
|
||||||
color: state.tabIndex.value == 0
|
|
||||||
? Theme.of(context).primaryColor
|
|
||||||
: const Color.fromRGBO(80, 94, 110, 1),
|
|
||||||
fontWeight: state.tabIndex.value == 0 ? FontWeight.bold : null,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
Tab(
|
|
||||||
iconMargin: EdgeInsets.zero,
|
|
||||||
height: 34.h,
|
|
||||||
child: Obx(() {
|
|
||||||
return Container(
|
|
||||||
width: 140.w,
|
|
||||||
alignment: Alignment.center,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: state.tabIndex.value == 1 ? const Color.fromRGBO(255, 255, 255, 1) : null,
|
|
||||||
borderRadius: BorderRadius.all(Radius.circular(8.r)),
|
|
||||||
),
|
|
||||||
child: quickText(
|
|
||||||
'已批阅',
|
|
||||||
size: 14.sp,
|
|
||||||
color: state.tabIndex.value == 1
|
|
||||||
? Theme.of(context).primaryColor
|
|
||||||
: const Color.fromRGBO(80, 94, 110, 1),
|
|
||||||
fontWeight: state.tabIndex.value == 1 ? FontWeight.bold : null,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
flex: 1,
|
|
||||||
child: InkWell(
|
|
||||||
onTap: () {
|
|
||||||
Get.toNamed(Routes.studentHistoryWorkPage, arguments: {'page': 'set'});
|
|
||||||
},
|
|
||||||
child: Icon(const IconData(0xe63e, fontFamily: "AlibabaIcon"),
|
|
||||||
color: const Color.fromRGBO(44, 48, 63, 1), size: 24.sp),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: Obx(() {
|
|
||||||
return AnnotateList(
|
|
||||||
tabIndex: state.tabIndex.value,
|
|
||||||
assessType: 1,
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
# 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
|
# 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.
|
# of the product and file versions while build-number is used as the build suffix.
|
||||||
version: 1.0.8+9
|
version: 1.0.5+6
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=3.4.1 <4.0.0'
|
sdk: '>=3.4.1 <4.0.0'
|
||||||
|
|
@ -48,14 +48,14 @@ dependencies:
|
||||||
# http请求插件
|
# http请求插件
|
||||||
dio: ^5.4.2+1
|
dio: ^5.4.2+1
|
||||||
# 网络缓存图片
|
# 网络缓存图片
|
||||||
cached_network_image: ^3.4.1
|
cached_network_image: ^3.2.1
|
||||||
# 上拉加载和下拉刷新的组件
|
# 上拉加载和下拉刷新的组件
|
||||||
flutter_easyrefresh: ^2.2.2
|
flutter_easyrefresh: ^2.2.2
|
||||||
photo_view: ^0.15.0
|
photo_view: ^0.15.0
|
||||||
# 加密验签插件,支持SHA MD5 HMAC
|
# 加密验签插件,支持SHA MD5 HMAC
|
||||||
crypto: ^3.0.2
|
crypto: ^3.0.2
|
||||||
# 获取app版本号
|
# 获取app版本号
|
||||||
package_info_plus: ^9.0.0
|
package_info_plus: ^8.0.0
|
||||||
# 网络监控
|
# 网络监控
|
||||||
connectivity_plus: ^6.0.3
|
connectivity_plus: ^6.0.3
|
||||||
# toast组件用于系统尚未初始化完成时
|
# toast组件用于系统尚未初始化完成时
|
||||||
|
|
@ -71,9 +71,10 @@ dependencies:
|
||||||
url: https://gitea.23544.com/wangyang/zoom_widget.git
|
url: https://gitea.23544.com/wangyang/zoom_widget.git
|
||||||
# url: https://github.com/semakers/zoom-widget.git
|
# url: https://github.com/semakers/zoom-widget.git
|
||||||
# ref: a35c9da6afe405c23b5897b449683d424016e9f1
|
# ref: a35c9da6afe405c23b5897b449683d424016e9f1
|
||||||
|
# start retrofit请求封装
|
||||||
retrofit: 4.6.0
|
retrofit: ^4.1.0
|
||||||
json_annotation: ^4.9.0
|
json_annotation: 4.9.0
|
||||||
|
# end retrofit请求封装
|
||||||
# 进度条
|
# 进度条
|
||||||
percent_indicator: ^4.2.3
|
percent_indicator: ^4.2.3
|
||||||
badges: ^3.1.2
|
badges: ^3.1.2
|
||||||
|
|
@ -88,15 +89,14 @@ dependencies:
|
||||||
flutter_spinkit: ^5.2.1
|
flutter_spinkit: ^5.2.1
|
||||||
event_bus: ^2.0.0
|
event_bus: ^2.0.0
|
||||||
path_provider: ^2.1.3
|
path_provider: ^2.1.3
|
||||||
|
uuid: ^3.0.7
|
||||||
flutter_echarts: ^2.4.0
|
flutter_echarts: ^2.4.0
|
||||||
# 饼图
|
# 饼图
|
||||||
flutter_echart: ^2.0.0
|
flutter_echart: ^2.0.0
|
||||||
url_launcher: ^6.1.11
|
url_launcher: ^6.1.11
|
||||||
app_installer: ^1.1.0
|
app_installer: ^1.1.0
|
||||||
# auto_updater: ^0.2.1
|
auto_updater: ^0.2.1
|
||||||
auto_updater: ^1.0.0
|
permission_handler: ^11.3.1
|
||||||
permission_handler: ^12.0.1
|
|
||||||
flutter_distributor: 0.6.5
|
flutter_distributor: 0.6.5
|
||||||
# fastforge: ^0.6.5
|
# fastforge: ^0.6.5
|
||||||
flutter_native_splash: ^2.4.6
|
flutter_native_splash: ^2.4.6
|
||||||
|
|
@ -104,15 +104,9 @@ dependencies:
|
||||||
app_settings: ^5.1.1
|
app_settings: ^5.1.1
|
||||||
device_info_plus: ^11.1.0
|
device_info_plus: ^11.1.0
|
||||||
auto_size_text: ^3.0.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:
|
dependency_overrides:
|
||||||
archive: ^4.0.2
|
archive: ^4.0.2
|
||||||
uuid: ^4.4.2
|
|
||||||
# analyzer: ^3..0
|
|
||||||
# meta: ^1.15.0
|
# meta: ^1.15.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue