diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 5523449..3d655e0 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -35,6 +35,12 @@ android { // TODO: Add your own signing config for the release build. // Signing with the debug keys for now, so `flutter run --release` works. signingConfig = signingConfigs.getByName("debug") + isMinifyEnabled = false + isShrinkResources = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) } } } diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro new file mode 100644 index 0000000..b5b1f0f --- /dev/null +++ b/android/app/proguard-rules.pro @@ -0,0 +1,10 @@ +-keep class com.sun.jna.* { *; } +-keepclassmembers class * extends com.sun.jna.* { public *; } + +-dontwarn java.awt.** +-dontwarn javax.swing.** +-dontwarn java.beans.** +-dontwarn sun.misc.** + +-keep class org.vosk.** { *; } +-keep class net.java.dev.jna.** { *; } diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 87ae0f5..d7b881b 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,6 +1,11 @@ + + + + + + + + + + + diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png index db77bb4..e8f051e 100644 Binary files a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png index 17987b7..1e6c45d 100644 Binary files a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png index 09d4391..aad9ce4 100644 Binary files a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png index d5f1c8d..f7dfd99 100644 Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png index 4d6372e..717a301 100644 Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/android/build.gradle.kts b/android/build.gradle.kts index dbee657..2c0b9ad 100644 --- a/android/build.gradle.kts +++ b/android/build.gradle.kts @@ -1,5 +1,7 @@ allprojects { repositories { + maven { url = uri("https://maven.aliyun.com/repository/google") } + maven { url = uri("https://maven.aliyun.com/repository/public") } google() mavenCentral() } diff --git a/android/build/reports/problems/problems-report.html b/android/build/reports/problems/problems-report.html new file mode 100644 index 0000000..e85474d --- /dev/null +++ b/android/build/reports/problems/problems-report.html @@ -0,0 +1,663 @@ + + + + + + + + + + + + + Gradle Configuration Cache + + + +
+ +
+ Loading... +
+ + + + + + diff --git a/android/settings.gradle.kts b/android/settings.gradle.kts index ca7fe06..d813785 100644 --- a/android/settings.gradle.kts +++ b/android/settings.gradle.kts @@ -11,6 +11,9 @@ pluginManagement { includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") repositories { + maven { url = uri("https://maven.aliyun.com/repository/google") } + maven { url = uri("https://maven.aliyun.com/repository/public") } + maven { url = uri("https://maven.aliyun.com/repository/gradle-plugin") } google() mavenCentral() gradlePluginPortal() diff --git a/assets/logo.png b/assets/logo.png new file mode 100644 index 0000000..b2525d3 Binary files /dev/null and b/assets/logo.png differ diff --git a/assets/models/vosk-model-small-cn-0.22.zip b/assets/models/vosk-model-small-cn-0.22.zip new file mode 100644 index 0000000..b465498 Binary files /dev/null and b/assets/models/vosk-model-small-cn-0.22.zip differ diff --git a/ios/Flutter/AppFrameworkInfo.plist b/ios/Flutter/AppFrameworkInfo.plist index 1dc6cf7..391a902 100644 --- a/ios/Flutter/AppFrameworkInfo.plist +++ b/ios/Flutter/AppFrameworkInfo.plist @@ -20,7 +20,5 @@ ???? CFBundleVersion 1.0 - MinimumOSVersion - 13.0 diff --git a/ios/Podfile b/ios/Podfile index 620e46e..c9c25a1 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -39,5 +39,16 @@ end post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_ios_build_settings(target) + + target.build_configurations.each do |config| + config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [ + '$(inherited)', + 'PERMISSION_CAMERA=1', + 'PERMISSION_MICROPHONE=1', + 'PERMISSION_SPEECH_RECOGNIZER=1', + 'PERMISSION_PHOTOS=1', + 'PERMISSION_PHOTOS_ADD_ONLY=1', + ] + end end end diff --git a/ios/Podfile.lock b/ios/Podfile.lock new file mode 100644 index 0000000..3ea8660 --- /dev/null +++ b/ios/Podfile.lock @@ -0,0 +1,161 @@ +PODS: + - CwlCatchException (2.2.1): + - CwlCatchExceptionSupport (~> 2.2.1) + - CwlCatchExceptionSupport (2.2.1) + - Flutter (1.0.0) + - GoogleDataTransport (9.4.1): + - GoogleUtilities/Environment (~> 7.7) + - nanopb (< 2.30911.0, >= 2.30908.0) + - PromisesObjC (< 3.0, >= 1.2) + - GoogleMLKit/BarcodeScanning (4.0.0): + - GoogleMLKit/MLKitCore + - MLKitBarcodeScanning (~> 3.0.0) + - GoogleMLKit/MLKitCore (4.0.0): + - MLKitCommon (~> 9.0.0) + - GoogleToolboxForMac/DebugUtils (2.3.2): + - GoogleToolboxForMac/Defines (= 2.3.2) + - GoogleToolboxForMac/Defines (2.3.2) + - GoogleToolboxForMac/Logger (2.3.2): + - GoogleToolboxForMac/Defines (= 2.3.2) + - "GoogleToolboxForMac/NSData+zlib (2.3.2)": + - GoogleToolboxForMac/Defines (= 2.3.2) + - "GoogleToolboxForMac/NSDictionary+URLArguments (2.3.2)": + - GoogleToolboxForMac/DebugUtils (= 2.3.2) + - GoogleToolboxForMac/Defines (= 2.3.2) + - "GoogleToolboxForMac/NSString+URLArguments (= 2.3.2)" + - "GoogleToolboxForMac/NSString+URLArguments (2.3.2)" + - GoogleUtilities/Environment (7.13.3): + - GoogleUtilities/Privacy + - PromisesObjC (< 3.0, >= 1.2) + - GoogleUtilities/Logger (7.13.3): + - GoogleUtilities/Environment + - GoogleUtilities/Privacy + - GoogleUtilities/Privacy (7.13.3) + - GoogleUtilities/UserDefaults (7.13.3): + - GoogleUtilities/Logger + - GoogleUtilities/Privacy + - GoogleUtilitiesComponents (1.1.0): + - GoogleUtilities/Logger + - GTMSessionFetcher/Core (2.3.0) + - image_picker_ios (0.0.1): + - Flutter + - MLImage (1.0.0-beta4) + - MLKitBarcodeScanning (3.0.0): + - MLKitCommon (~> 9.0) + - MLKitVision (~> 5.0) + - MLKitCommon (9.0.0): + - GoogleDataTransport (~> 9.0) + - GoogleToolboxForMac/Logger (~> 2.1) + - "GoogleToolboxForMac/NSData+zlib (~> 2.1)" + - "GoogleToolboxForMac/NSDictionary+URLArguments (~> 2.1)" + - GoogleUtilities/UserDefaults (~> 7.0) + - GoogleUtilitiesComponents (~> 1.0) + - GTMSessionFetcher/Core (< 3.0, >= 1.1) + - MLKitVision (5.0.0): + - GoogleToolboxForMac/Logger (~> 2.1) + - "GoogleToolboxForMac/NSData+zlib (~> 2.1)" + - GTMSessionFetcher/Core (< 3.0, >= 1.1) + - MLImage (= 1.0.0-beta4) + - MLKitCommon (~> 9.0) + - mobile_scanner (3.5.6): + - Flutter + - GoogleMLKit/BarcodeScanning (~> 4.0.0) + - nanopb (2.30910.0): + - nanopb/decode (= 2.30910.0) + - nanopb/encode (= 2.30910.0) + - nanopb/decode (2.30910.0) + - nanopb/encode (2.30910.0) + - permission_handler_apple (9.3.0): + - Flutter + - PromisesObjC (2.4.0) + - shared_preferences_foundation (0.0.1): + - Flutter + - FlutterMacOS + - speech_to_text (7.2.0): + - CwlCatchException + - Flutter + - FlutterMacOS + - sqflite_darwin (0.0.4): + - Flutter + - FlutterMacOS + - url_launcher_ios (0.0.1): + - Flutter + - vosk_flutter_service (0.1.1): + - Flutter + +DEPENDENCIES: + - Flutter (from `Flutter`) + - image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`) + - mobile_scanner (from `.symlinks/plugins/mobile_scanner/ios`) + - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`) + - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) + - speech_to_text (from `.symlinks/plugins/speech_to_text/darwin`) + - sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`) + - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) + - vosk_flutter_service (from `.symlinks/plugins/vosk_flutter_service/ios`) + +SPEC REPOS: + trunk: + - CwlCatchException + - CwlCatchExceptionSupport + - GoogleDataTransport + - GoogleMLKit + - GoogleToolboxForMac + - GoogleUtilities + - GoogleUtilitiesComponents + - GTMSessionFetcher + - MLImage + - MLKitBarcodeScanning + - MLKitCommon + - MLKitVision + - nanopb + - PromisesObjC + +EXTERNAL SOURCES: + Flutter: + :path: Flutter + image_picker_ios: + :path: ".symlinks/plugins/image_picker_ios/ios" + mobile_scanner: + :path: ".symlinks/plugins/mobile_scanner/ios" + permission_handler_apple: + :path: ".symlinks/plugins/permission_handler_apple/ios" + shared_preferences_foundation: + :path: ".symlinks/plugins/shared_preferences_foundation/darwin" + speech_to_text: + :path: ".symlinks/plugins/speech_to_text/darwin" + sqflite_darwin: + :path: ".symlinks/plugins/sqflite_darwin/darwin" + url_launcher_ios: + :path: ".symlinks/plugins/url_launcher_ios/ios" + vosk_flutter_service: + :path: ".symlinks/plugins/vosk_flutter_service/ios" + +SPEC CHECKSUMS: + CwlCatchException: 7acc161b299a6de7f0a46a6ed741eae2c8b4d75a + CwlCatchExceptionSupport: 54ccab8d8c78907b57f99717fb19d4cc3bce02dc + Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 + GoogleDataTransport: 6c09b596d841063d76d4288cc2d2f42cc36e1e2a + GoogleMLKit: 2bd0dc6253c4d4f227aad460f69215a504b2980e + GoogleToolboxForMac: 8bef7c7c5cf7291c687cf5354f39f9db6399ad34 + GoogleUtilities: ea963c370a38a8069cc5f7ba4ca849a60b6d7d15 + GoogleUtilitiesComponents: 679b2c881db3b615a2777504623df6122dd20afe + GTMSessionFetcher: 3a63d75eecd6aa32c2fc79f578064e1214dfdec2 + image_picker_ios: 4f2f91b01abdb52842a8e277617df877e40f905b + MLImage: 7bb7c4264164ade9bf64f679b40fb29c8f33ee9b + MLKitBarcodeScanning: 04e264482c5f3810cb89ebc134ef6b61e67db505 + MLKitCommon: c1b791c3e667091918d91bda4bba69a91011e390 + MLKitVision: 8baa5f46ee3352614169b85250574fde38c36f49 + mobile_scanner: 38dcd8a49d7d485f632b7de65e4900010187aef2 + nanopb: 438bc412db1928dac798aa6fd75726007be04262 + permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2 + PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 + shared_preferences_foundation: 5086985c1d43c5ba4d5e69a4e8083a389e2909e6 + speech_to_text: 87bf9298952e8d9073be1b6aade6d5758db5170c + sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d + url_launcher_ios: bb13df5870e8c4234ca12609d04010a21be43dfa + vosk_flutter_service: f2468d1e2a71ef740fa2b011072c0db5c9db92b4 + +PODFILE CHECKSUM: 6ee64a990764a8fa4675fa771bfc90cb436e0452 + +COCOAPODS: 1.16.2 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 1e8fdf4..fb50889 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -8,12 +8,14 @@ /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 2E04EE7A2058FEF3933A2B95 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A9F3E80106CE7ED79A62A6BB /* Pods_RunnerTests.framework */; }; 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + C99BD58100E5E11901401CCA /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4139977D07D97FB62585E8CD /* Pods_Runner.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -40,11 +42,18 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 096CC58A6C3E67BF6E963D43 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 31AD78A16558003E9B63C41B /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 4139977D07D97FB62585E8CD /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 551B51F14108C6827DE32591 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + 6790C7D1B29C11DB6730CF5A /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 6B31EAF52AE7B4040C25C94C /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 6E0D99486EF4607C76F1D798 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; @@ -55,13 +64,23 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + A9F3E80106CE7ED79A62A6BB /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 80B8D16098254EFE83FFC01E /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 2E04EE7A2058FEF3933A2B95 /* Pods_RunnerTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 97C146EB1CF9000F007C117D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + C99BD58100E5E11901401CCA /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -76,6 +95,15 @@ path = RunnerTests; sourceTree = ""; }; + 75F4588978CEB3D7883B557E /* Frameworks */ = { + isa = PBXGroup; + children = ( + 4139977D07D97FB62585E8CD /* Pods_Runner.framework */, + A9F3E80106CE7ED79A62A6BB /* Pods_RunnerTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( @@ -94,6 +122,8 @@ 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, 331C8082294A63A400263BE5 /* RunnerTests */, + FB4377A0B6990F16B3684000 /* Pods */, + 75F4588978CEB3D7883B557E /* Frameworks */, ); sourceTree = ""; }; @@ -121,6 +151,20 @@ path = Runner; sourceTree = ""; }; + FB4377A0B6990F16B3684000 /* Pods */ = { + isa = PBXGroup; + children = ( + 6B31EAF52AE7B4040C25C94C /* Pods-Runner.debug.xcconfig */, + 6790C7D1B29C11DB6730CF5A /* Pods-Runner.release.xcconfig */, + 096CC58A6C3E67BF6E963D43 /* Pods-Runner.profile.xcconfig */, + 551B51F14108C6827DE32591 /* Pods-RunnerTests.debug.xcconfig */, + 6E0D99486EF4607C76F1D798 /* Pods-RunnerTests.release.xcconfig */, + 31AD78A16558003E9B63C41B /* Pods-RunnerTests.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -128,8 +172,10 @@ isa = PBXNativeTarget; buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( + A7EFE6B4A06F92680E95C9C5 /* [CP] Check Pods Manifest.lock */, 331C807D294A63A400263BE5 /* Sources */, 331C807F294A63A400263BE5 /* Resources */, + 80B8D16098254EFE83FFC01E /* Frameworks */, ); buildRules = ( ); @@ -145,12 +191,15 @@ isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( + 248A8C3A0B504B1E7E60B456 /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + 567F914051B59E065686899B /* [CP] Embed Pods Frameworks */, + EB3656BCB985502D283A7F0D /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -222,6 +271,28 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ + 248A8C3A0B504B1E7E60B456 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -238,6 +309,23 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; + 567F914051B59E065686899B /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -253,6 +341,45 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; + A7EFE6B4A06F92680E95C9C5 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + EB3656BCB985502D283A7F0D /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Copy Pods Resources"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -369,7 +496,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.zhinianManage; + PRODUCT_BUNDLE_IDENTIFIER = com.lishaohua.zhinianManage.dev; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; @@ -379,13 +506,14 @@ }; 331C8088294A63A400263BE5 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 551B51F14108C6827DE32591 /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.example.zhinianManage.RunnerTests; + PRODUCT_BUNDLE_IDENTIFIER = com.lishaohua.zhinianManage.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -396,13 +524,14 @@ }; 331C8089294A63A400263BE5 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 6E0D99486EF4607C76F1D798 /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.example.zhinianManage.RunnerTests; + PRODUCT_BUNDLE_IDENTIFIER = com.lishaohua.zhinianManage.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; @@ -411,13 +540,14 @@ }; 331C808A294A63A400263BE5 /* Profile */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 31AD78A16558003E9B63C41B /* Pods-RunnerTests.profile.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.example.zhinianManage.RunnerTests; + PRODUCT_BUNDLE_IDENTIFIER = com.lishaohua.zhinianManage.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; @@ -549,7 +679,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.zhinianManage; + PRODUCT_BUNDLE_IDENTIFIER = com.lishaohua.zhinianManage.dev; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -572,7 +702,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.zhinianManage; + PRODUCT_BUNDLE_IDENTIFIER = com.lishaohua.zhinianManage.dev; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; diff --git a/ios/Runner.xcodeproj/project.pbxproj.bak b/ios/Runner.xcodeproj/project.pbxproj.bak new file mode 100644 index 0000000..758bc44 --- /dev/null +++ b/ios/Runner.xcodeproj/project.pbxproj.bak @@ -0,0 +1,731 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 2E04EE7A2058FEF3933A2B95 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A9F3E80106CE7ED79A62A6BB /* Pods_RunnerTests.framework */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + C99BD58100E5E11901401CCA /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4139977D07D97FB62585E8CD /* Pods_Runner.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 096CC58A6C3E67BF6E963D43 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 31AD78A16558003E9B63C41B /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 4139977D07D97FB62585E8CD /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 551B51F14108C6827DE32591 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + 6790C7D1B29C11DB6730CF5A /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 6B31EAF52AE7B4040C25C94C /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 6E0D99486EF4607C76F1D798 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + A9F3E80106CE7ED79A62A6BB /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 80B8D16098254EFE83FFC01E /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 2E04EE7A2058FEF3933A2B95 /* Pods_RunnerTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + C99BD58100E5E11901401CCA /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 75F4588978CEB3D7883B557E /* Frameworks */ = { + isa = PBXGroup; + children = ( + 4139977D07D97FB62585E8CD /* Pods_Runner.framework */, + A9F3E80106CE7ED79A62A6BB /* Pods_RunnerTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 331C8082294A63A400263BE5 /* RunnerTests */, + FB4377A0B6990F16B3684000 /* Pods */, + 75F4588978CEB3D7883B557E /* Frameworks */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; + FB4377A0B6990F16B3684000 /* Pods */ = { + isa = PBXGroup; + children = ( + 6B31EAF52AE7B4040C25C94C /* Pods-Runner.debug.xcconfig */, + 6790C7D1B29C11DB6730CF5A /* Pods-Runner.release.xcconfig */, + 096CC58A6C3E67BF6E963D43 /* Pods-Runner.profile.xcconfig */, + 551B51F14108C6827DE32591 /* Pods-RunnerTests.debug.xcconfig */, + 6E0D99486EF4607C76F1D798 /* Pods-RunnerTests.release.xcconfig */, + 31AD78A16558003E9B63C41B /* Pods-RunnerTests.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + A7EFE6B4A06F92680E95C9C5 /* [CP] Check Pods Manifest.lock */, + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + 80B8D16098254EFE83FFC01E /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 248A8C3A0B504B1E7E60B456 /* [CP] Check Pods Manifest.lock */, + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + 567F914051B59E065686899B /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 248A8C3A0B504B1E7E60B456 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 567F914051B59E065686899B /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; + A7EFE6B4A06F92680E95C9C5 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = UBDH3YTVFW; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.zhinianManage; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 551B51F14108C6827DE32591 /* Pods-RunnerTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.zhinianManage.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 6E0D99486EF4607C76F1D798 /* Pods-RunnerTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.zhinianManage.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 31AD78A16558003E9B63C41B /* Pods-RunnerTests.profile.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.zhinianManage.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = UBDH3YTVFW; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.zhinianManage; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = UBDH3YTVFW; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.zhinianManage; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/ios/Runner.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcworkspace/contents.xcworkspacedata index 1d526a1..21a3cc1 100644 --- a/ios/Runner.xcworkspace/contents.xcworkspacedata +++ b/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -4,4 +4,7 @@ + + diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index 6266644..c30b367 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -2,12 +2,15 @@ import Flutter import UIKit @main -@objc class AppDelegate: FlutterAppDelegate { +@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate { override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { - GeneratedPluginRegistrant.register(with: self) return super.application(application, didFinishLaunchingWithOptions: launchOptions) } + + func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) { + GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry) + } } diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png index dc9ada4..5106c57 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png index 7353c41..a4359e7 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png index 797d452..a8e2dcf 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png index 6ed2d93..b9c29cb 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png index 4cd7b00..2dd4ad1 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png index fe73094..aae486c 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png index 321773c..417f87c 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png index 797d452..a8e2dcf 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png index 502f463..fbdece0 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png index 0ec3034..943ec8b 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png index 0ec3034..943ec8b 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png index e9f5fea..b6da22d 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png index 84ac32a..909fb7a 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png index 8953cba..c81ad00 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png index 0467bf1..7619153 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 860d891..9d9cc12 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -2,10 +2,22 @@ + CADisableMinimumFrameDurationOnPhone + + NSCameraUsageDescription + 需要使用相机扫描客户出示的核销二维码 + NSPhotoLibraryUsageDescription + 需要访问相册以选择活动封面、订单凭证等图片 + NSPhotoLibraryAddUsageDescription + 需要将凭证或活动二维码保存到相册 + NSMicrophoneUsageDescription + 需要使用麦克风进行语音输入 + NSSpeechRecognitionUsageDescription + 需要使用语音识别将语音转写为文字 CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName - Zhinian Manage + 智念商家端 CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier @@ -24,6 +36,29 @@ $(FLUTTER_BUILD_NUMBER) LSRequiresIPhoneOS + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneClassName + UIWindowScene + UISceneConfigurationName + flutter + UISceneDelegateClassName + FlutterSceneDelegate + UISceneStoryboardFile + Main + + + + + UIApplicationSupportsIndirectInputEvents + UILaunchStoryboardName LaunchScreen UIMainStoryboardFile @@ -41,9 +76,5 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight - CADisableMinimumFrameDurationOnPhone - - UIApplicationSupportsIndirectInputEvents - diff --git a/lib/app.dart b/lib/app.dart index 221af74..a46265a 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -14,14 +14,27 @@ class ZhinianApp extends ConsumerWidget { final router = ref.watch(routerProvider); final settings = ref.watch(settingsProvider); + final platformBrightness = MediaQuery.platformBrightnessOf(context); + final isDark = switch (settings.themeMode) { + AppThemeMode.light => false, + AppThemeMode.dark => true, + AppThemeMode.system => platformBrightness == Brightness.dark, + }; + AppColors.applyMode(isDark); + return MaterialApp.router( - title: '智念管理', + title: '智念商家端', debugShowCheckedModeBanner: false, - theme: AppTheme.lightTheme, - darkTheme: AppTheme.darkTheme, - themeMode: settings.flutterThemeMode, + theme: AppTheme.theme, routerConfig: router, locale: settings.locale, + // Pages read AppColors.* statics directly; without a key, Router won't + // rebuild descendants on a pure theme toggle. Keying by mode forces the + // whole subtree to remount so every page picks up the new colors. + builder: (context, child) => KeyedSubtree( + key: ValueKey(isDark), + child: child ?? const SizedBox.shrink(), + ), localizationsDelegates: const [ AppLocalizations.delegate, GlobalMaterialLocalizations.delegate, diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 7cac705..bdb7537 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -1,6 +1,6 @@ { "@@locale": "en", - "appTitle": "Zhinian Manage", + "appTitle": "Zhinian Merchant", "appSubtitle": "Hotel & Scenic Smart Management", "welcomeLogin": "Welcome", "enterAccountInfo": "Please enter your account info", @@ -71,7 +71,7 @@ "unknownLocation": "Unknown Location", "workOrderId": "Work Order ID", "creator": "Creator", - "assignee": "Assignee", + "assignee": "Accepting Dept", "notAssigned": "Not Assigned", "category": "Category", "priority": "Priority", @@ -129,8 +129,8 @@ "employeeDesc": "Add and manage employee accounts", "dataReport": "Data Reports", "reportDesc": "View operation analytics", - "appMarket": "App Market", - "marketDesc": "Extend more feature modules", + "appMarket": "App Plugins", + "marketDesc": "More business extension plugins", "helpCenter": "Help Center", "helpDesc": "User guide & FAQ", "aboutUs": "About Us", @@ -193,7 +193,7 @@ "contactTeam": "Contact our support team for more help", "contactService": "Contact Support", "productIntro": "Product Introduction", - "productDesc": "Zhinian Manage is a smart management app for hotels and scenic spots. With AI assistant, event publishing, order management, and QR verification, it helps businesses operate efficiently.", + "productDesc": "Zhinian Merchant is a smart management app for hotels and scenic spots. With AI assistant, event publishing, order management, and QR verification, it helps businesses operate efficiently.", "officialWebsite": "Official Website", "servicePhone": "Service Phone", "techSupport": "Tech Support", @@ -238,11 +238,11 @@ "spaPackage": "SPA Package", "orderUnit": " orders", "latestVersion": "Latest Version", - "agreementTitle": "Zhinian Manage User Agreement", - "privacyTitle": "Zhinian Manage Privacy Policy", + "agreementTitle": "Zhinian Merchant User Agreement", + "privacyTitle": "Zhinian Merchant Privacy Policy", "lastUpdated": "Last updated: December 2024", "agreementSection1": "1. Scope", - "agreementPara1": "This agreement is between you and Zhinian Tech regarding the use of Zhinian Manage services. Services include but are not limited to event publishing, order management, work order processing, QR verification, etc.", + "agreementPara1": "This agreement is between you and Zhinian Tech regarding the use of Zhinian Merchant services. Services include but are not limited to event publishing, order management, work order processing, QR verification, etc.", "agreementSection2": "2. Account Registration", "agreementPara2": "You need to register an account to use our services. Provide a valid phone number and set a secure password. You are responsible for all actions under your account.", "agreementSection3": "3. Service Usage Rules", @@ -268,5 +268,28 @@ "privacySection6": "6. Cookies & Local Storage", "privacyPara6": "We use local storage to save login status and app settings for a better experience. You can clear this data in settings.", "privacySection7": "7. Privacy Policy Updates", - "privacyPara7": "We may update this privacy policy from time to time. Updates will be notified in the app, and continued use constitutes acceptance." + "privacyPara7": "We may update this privacy policy from time to time. Updates will be notified in the app, and continued use constitutes acceptance.", + + "acceptOrder": "Accept", + "transferOrder": "Transfer", + "completeOrder": "Complete", + "selectTransferDept": "Select Department", + "acceptSuccess": "Order accepted", + "acceptFailed": "Failed to accept, please retry", + "transferSuccess": "Order transferred", + "transferFailed": "Failed to transfer, please retry", + "completeSuccess": "Order completed", + "completeFailed": "Operation failed, please retry", + "deptMaintenance": "Maintenance", + "deptCleaning": "Cleaning", + "deptFrontDesk": "Front Desk", + "deptSecurity": "Security", + "deptAdmin": "Administration", + "deptKitchen": "Kitchen", + "transferDept": "Transfer Dept", + "voiceListening": "Listening...", + "voiceUnavailable": "Speech recognition unavailable", + "voicePermissionDenied": "Microphone permission denied", + "voiceError": "Speech recognition error", + "voiceModelLoading": "Loading speech model, please wait..." } \ No newline at end of file diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 32d1202..9bafe64 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -103,7 +103,7 @@ abstract class AppLocalizations { /// No description provided for @appTitle. /// /// In zh, this message translates to: - /// **'智念管理'** + /// **'智念商家端'** String get appTitle; /// No description provided for @appSubtitle. @@ -439,7 +439,7 @@ abstract class AppLocalizations { /// No description provided for @pending. /// /// In zh, this message translates to: - /// **'待处理'** + /// **'待接单'** String get pending; /// No description provided for @processing. @@ -529,7 +529,7 @@ abstract class AppLocalizations { /// No description provided for @assignee. /// /// In zh, this message translates to: - /// **'指派给'** + /// **'接单部门'** String get assignee; /// No description provided for @notAssigned. @@ -901,13 +901,13 @@ abstract class AppLocalizations { /// No description provided for @appMarket. /// /// In zh, this message translates to: - /// **'应用市场'** + /// **'应用插件'** String get appMarket; /// No description provided for @marketDesc. /// /// In zh, this message translates to: - /// **'扩展更多功能模块'** + /// **'更多业务扩展插件'** String get marketDesc; /// No description provided for @helpCenter. @@ -1195,7 +1195,7 @@ abstract class AppLocalizations { /// No description provided for @faq3A. /// /// In zh, this message translates to: - /// **'工单共有三种状态:待处理(刚创建未分配)、处理中(已分配给相关人员)、已完成(问题已解决)。可以在「订单工单」页面的工单Tab中按状态筛选。'** + /// **'工单共有三种状态:待接单(刚创建未分配)、处理中(已分配给相关人员)、已完成(问题已解决)。可以在「订单工单」页面的工单Tab中按状态筛选。'** String get faq3A; /// No description provided for @faq4Q. @@ -1489,13 +1489,13 @@ abstract class AppLocalizations { /// No description provided for @agreementTitle. /// /// In zh, this message translates to: - /// **'智念管理用户协议'** + /// **'智念商家端用户协议'** String get agreementTitle; /// No description provided for @privacyTitle. /// /// In zh, this message translates to: - /// **'智念管理隐私政策'** + /// **'智念商家端隐私政策'** String get privacyTitle; /// No description provided for @lastUpdated. @@ -1513,7 +1513,7 @@ abstract class AppLocalizations { /// No description provided for @agreementPara1. /// /// In zh, this message translates to: - /// **'本协议是您与智念科技之间关于使用智念管理服务所订立的协议。智念管理服务包括但不限于事件发布、订单管理、工单处理、扫码核销等功能。'** + /// **'本协议是您与智念科技之间关于使用智念商家端服务所订立的协议。智念商家端服务包括但不限于事件发布、订单管理、工单处理、扫码核销等功能。'** String get agreementPara1; /// No description provided for @agreementSection2. @@ -1681,7 +1681,7 @@ abstract class AppLocalizations { /// No description provided for @productDesc. /// /// In zh, this message translates to: - /// **'智念管理是一款专为酒店、景区等文旅行业打造的智能管理应用。通过AI助手、事件发布、订单工单管理、扫码核销等功能,帮助商家高效运营、提升服务质量。'** + /// **'智念商家端是一款专为酒店、景区等文旅行业打造的智能管理应用。通过AI助手、事件发布、订单工单管理、扫码核销等功能,帮助商家高效运营、提升服务质量。'** String get productDesc; /// No description provided for @officialWebsite. @@ -1719,6 +1719,138 @@ abstract class AppLocalizations { /// In zh, this message translates to: /// **'All Rights Reserved'** String get allRightsReserved; + + /// No description provided for @acceptOrder. + /// + /// In zh, this message translates to: + /// **'接单'** + String get acceptOrder; + + /// No description provided for @transferOrder. + /// + /// In zh, this message translates to: + /// **'转单'** + String get transferOrder; + + /// No description provided for @completeOrder. + /// + /// In zh, this message translates to: + /// **'完成工单'** + String get completeOrder; + + /// No description provided for @selectTransferDept. + /// + /// In zh, this message translates to: + /// **'选择转单部门'** + String get selectTransferDept; + + /// No description provided for @acceptSuccess. + /// + /// In zh, this message translates to: + /// **'接单成功'** + String get acceptSuccess; + + /// No description provided for @acceptFailed. + /// + /// In zh, this message translates to: + /// **'接单失败,请重试'** + String get acceptFailed; + + /// No description provided for @transferSuccess. + /// + /// In zh, this message translates to: + /// **'转单成功'** + String get transferSuccess; + + /// No description provided for @transferFailed. + /// + /// In zh, this message translates to: + /// **'转单失败,请重试'** + String get transferFailed; + + /// No description provided for @completeSuccess. + /// + /// In zh, this message translates to: + /// **'工单已完成'** + String get completeSuccess; + + /// No description provided for @completeFailed. + /// + /// In zh, this message translates to: + /// **'操作失败,请重试'** + String get completeFailed; + + /// No description provided for @deptMaintenance. + /// + /// In zh, this message translates to: + /// **'维修部'** + String get deptMaintenance; + + /// No description provided for @deptCleaning. + /// + /// In zh, this message translates to: + /// **'保洁部'** + String get deptCleaning; + + /// No description provided for @deptFrontDesk. + /// + /// In zh, this message translates to: + /// **'前台部'** + String get deptFrontDesk; + + /// No description provided for @deptSecurity. + /// + /// In zh, this message translates to: + /// **'安保部'** + String get deptSecurity; + + /// No description provided for @deptAdmin. + /// + /// In zh, this message translates to: + /// **'行政部'** + String get deptAdmin; + + /// No description provided for @deptKitchen. + /// + /// In zh, this message translates to: + /// **'厨房'** + String get deptKitchen; + + /// No description provided for @transferDept. + /// + /// In zh, this message translates to: + /// **'转单部门'** + String get transferDept; + + /// No description provided for @voiceListening. + /// + /// In zh, this message translates to: + /// **'正在聆听...'** + String get voiceListening; + + /// No description provided for @voiceUnavailable. + /// + /// In zh, this message translates to: + /// **'当前设备不支持语音识别'** + String get voiceUnavailable; + + /// No description provided for @voicePermissionDenied. + /// + /// In zh, this message translates to: + /// **'麦克风权限被拒绝'** + String get voicePermissionDenied; + + /// No description provided for @voiceError. + /// + /// In zh, this message translates to: + /// **'语音识别出错'** + String get voiceError; + + /// No description provided for @voiceModelLoading. + /// + /// In zh, this message translates to: + /// **'正在加载语音模型,请稍候...'** + String get voiceModelLoading; } class _AppLocalizationsDelegate diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index a1b895e..246f5b2 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -9,7 +9,7 @@ class AppLocalizationsEn extends AppLocalizations { AppLocalizationsEn([String locale = 'en']) : super(locale); @override - String get appTitle => 'Zhinian Manage'; + String get appTitle => 'Zhinian Merchant'; @override String get appSubtitle => 'Hotel & Scenic Smart Management'; @@ -224,7 +224,7 @@ class AppLocalizationsEn extends AppLocalizations { String get creator => 'Creator'; @override - String get assignee => 'Assignee'; + String get assignee => 'Accepting Dept'; @override String get notAssigned => 'Not Assigned'; @@ -410,10 +410,10 @@ class AppLocalizationsEn extends AppLocalizations { String get reportDesc => 'View operation analytics'; @override - String get appMarket => 'App Market'; + String get appMarket => 'App Plugins'; @override - String get marketDesc => 'Extend more feature modules'; + String get marketDesc => 'More business extension plugins'; @override String get helpCenter => 'Help Center'; @@ -712,10 +712,10 @@ class AppLocalizationsEn extends AppLocalizations { String get latestVersion => 'Latest Version'; @override - String get agreementTitle => 'Zhinian Manage User Agreement'; + String get agreementTitle => 'Zhinian Merchant User Agreement'; @override - String get privacyTitle => 'Zhinian Manage Privacy Policy'; + String get privacyTitle => 'Zhinian Merchant Privacy Policy'; @override String get lastUpdated => 'Last updated: December 2024'; @@ -725,7 +725,7 @@ class AppLocalizationsEn extends AppLocalizations { @override String get agreementPara1 => - 'This agreement is between you and Zhinian Tech regarding the use of Zhinian Manage services. Services include but are not limited to event publishing, order management, work order processing, QR verification, etc.'; + 'This agreement is between you and Zhinian Tech regarding the use of Zhinian Merchant services. Services include but are not limited to event publishing, order management, work order processing, QR verification, etc.'; @override String get agreementSection2 => '2. Account Registration'; @@ -823,7 +823,7 @@ class AppLocalizationsEn extends AppLocalizations { @override String get productDesc => - 'Zhinian Manage is a smart management app for hotels and scenic spots. With AI assistant, event publishing, order management, and QR verification, it helps businesses operate efficiently.'; + 'Zhinian Merchant is a smart management app for hotels and scenic spots. With AI assistant, event publishing, order management, and QR verification, it helps businesses operate efficiently.'; @override String get officialWebsite => 'Official Website'; @@ -842,4 +842,70 @@ class AppLocalizationsEn extends AppLocalizations { @override String get allRightsReserved => 'All Rights Reserved'; + + @override + String get acceptOrder => 'Accept'; + + @override + String get transferOrder => 'Transfer'; + + @override + String get completeOrder => 'Complete'; + + @override + String get selectTransferDept => 'Select Department'; + + @override + String get acceptSuccess => 'Order accepted'; + + @override + String get acceptFailed => 'Failed to accept, please retry'; + + @override + String get transferSuccess => 'Order transferred'; + + @override + String get transferFailed => 'Failed to transfer, please retry'; + + @override + String get completeSuccess => 'Order completed'; + + @override + String get completeFailed => 'Operation failed, please retry'; + + @override + String get deptMaintenance => 'Maintenance'; + + @override + String get deptCleaning => 'Cleaning'; + + @override + String get deptFrontDesk => 'Front Desk'; + + @override + String get deptSecurity => 'Security'; + + @override + String get deptAdmin => 'Administration'; + + @override + String get deptKitchen => 'Kitchen'; + + @override + String get transferDept => 'Transfer Dept'; + + @override + String get voiceListening => 'Listening...'; + + @override + String get voiceUnavailable => 'Speech recognition unavailable'; + + @override + String get voicePermissionDenied => 'Microphone permission denied'; + + @override + String get voiceError => 'Speech recognition error'; + + @override + String get voiceModelLoading => 'Loading speech model, please wait...'; } diff --git a/lib/l10n/app_localizations_th.dart b/lib/l10n/app_localizations_th.dart index 23b6c84..a5c9b55 100644 --- a/lib/l10n/app_localizations_th.dart +++ b/lib/l10n/app_localizations_th.dart @@ -9,7 +9,7 @@ class AppLocalizationsTh extends AppLocalizations { AppLocalizationsTh([String locale = 'th']) : super(locale); @override - String get appTitle => 'จื้อเหนียน จัดการ'; + String get appTitle => 'จื้อเหนียน ผู้ขาย'; @override String get appSubtitle => 'ระบบจัดการโรงแรมและสถานที่ท่องเที่ยวอัจฉริยะ'; @@ -222,7 +222,7 @@ class AppLocalizationsTh extends AppLocalizations { String get creator => 'ผู้สร้าง'; @override - String get assignee => 'ผู้รับมอบหมาย'; + String get assignee => 'แผนกที่รับงาน'; @override String get notAssigned => 'ยังไม่ได้มอบหมาย'; @@ -408,10 +408,10 @@ class AppLocalizationsTh extends AppLocalizations { String get reportDesc => 'ดูการวิเคราะห์การดำเนินงาน'; @override - String get appMarket => 'ตลาดแอป'; + String get appMarket => 'ปลั๊กอินแอป'; @override - String get marketDesc => 'ขยายโมดูลฟีเจอร์เพิ่มเติม'; + String get marketDesc => 'ปลั๊กอินส่วนขยายธุรกิจเพิ่มเติม'; @override String get helpCenter => 'ศูนย์ช่วยเหลือ'; @@ -711,10 +711,10 @@ class AppLocalizationsTh extends AppLocalizations { String get latestVersion => 'เวอร์ชันล่าสุด'; @override - String get agreementTitle => 'ข้อตกลงผู้ใช้ จื้อเหนียน จัดการ'; + String get agreementTitle => 'ข้อตกลงผู้ใช้ จื้อเหนียน ผู้ขาย'; @override - String get privacyTitle => 'นโยบายความเป็นส่วนตัว จื้อเหนียน จัดการ'; + String get privacyTitle => 'นโยบายความเป็นส่วนตัว จื้อเหนียน ผู้ขาย'; @override String get lastUpdated => 'อัปเดตล่าสุด: ธันวาคม 2024'; @@ -724,7 +724,7 @@ class AppLocalizationsTh extends AppLocalizations { @override String get agreementPara1 => - 'ข้อตกลงนี้เป็นข้อตกลงระหว่างคุณกับ จื้อเหนียน เทค เกี่ยวกับการใช้บริการ จื้อเหนียน จัดการ บริการรวมถึงแต่ไม่จำกัดเพียง การเผยแพร่เหตุการณ์ การจัดการคำสั่งซื้อ การประมวลผลใบงาน การตรวจสอบ QR ฯลฯ'; + 'ข้อตกลงนี้เป็นข้อตกลงระหว่างคุณกับ จื้อเหนียน เทค เกี่ยวกับการใช้บริการ จื้อเหนียน ผู้ขาย บริการรวมถึงแต่ไม่จำกัดเพียง การเผยแพร่เหตุการณ์ การจัดการคำสั่งซื้อ การประมวลผลใบงาน การตรวจสอบ QR ฯลฯ'; @override String get agreementSection2 => '2. การลงทะเบียนบัญชี'; @@ -822,7 +822,7 @@ class AppLocalizationsTh extends AppLocalizations { @override String get productDesc => - 'จื้อเหนียน จัดการเป็นแอปจัดการอัจฉริยะสำหรับโรงแรมและสถานที่ท่องเที่ยว ด้วยผู้ช่วย AI การเผยแพร่เหตุการณ์ การจัดการคำสั่งซื้อ และการตรวจสอบ QR ช่วยให้ธุรกิจดำเนินงานได้อย่างมีประสิทธิภาพ'; + 'จื้อเหนียน ผู้ขายเป็นแอปจัดการอัจฉริยะสำหรับโรงแรมและสถานที่ท่องเที่ยว ด้วยผู้ช่วย AI การเผยแพร่เหตุการณ์ การจัดการคำสั่งซื้อ และการตรวจสอบ QR ช่วยให้ธุรกิจดำเนินงานได้อย่างมีประสิทธิภาพ'; @override String get officialWebsite => 'เว็บไซต์ทางการ'; @@ -841,4 +841,70 @@ class AppLocalizationsTh extends AppLocalizations { @override String get allRightsReserved => 'สงวนลิขสิทธิ์ทั้งหมด'; + + @override + String get acceptOrder => 'รับงาน'; + + @override + String get transferOrder => 'โอนงาน'; + + @override + String get completeOrder => 'เสร็จสิ้นงาน'; + + @override + String get selectTransferDept => 'เลือกแผนกโอนงาน'; + + @override + String get acceptSuccess => 'รับงานสำเร็จ'; + + @override + String get acceptFailed => 'รับงานล้มเหลว โปรดลองอีกครั้ง'; + + @override + String get transferSuccess => 'โอนงานสำเร็จ'; + + @override + String get transferFailed => 'โอนงานล้มเหลว โปรดลองอีกครั้ง'; + + @override + String get completeSuccess => 'งานเสร็จสิ้น'; + + @override + String get completeFailed => 'การดำเนินการล้มเหลว โปรดลองอีกครั้ง'; + + @override + String get deptMaintenance => 'ฝ่ายซ่อมบำรุง'; + + @override + String get deptCleaning => 'ฝ่ายทำความสะอาด'; + + @override + String get deptFrontDesk => 'ฝ่ายต้อนรับ'; + + @override + String get deptSecurity => 'ฝ่ายรักษาความปลอดภัย'; + + @override + String get deptAdmin => 'ฝ่ายธุรการ'; + + @override + String get deptKitchen => 'ฝ่ายครัว'; + + @override + String get transferDept => 'แผนกโอนงาน'; + + @override + String get voiceListening => 'กำลังฟัง...'; + + @override + String get voiceUnavailable => 'ไม่รองรับการรู้จำเสียง'; + + @override + String get voicePermissionDenied => 'ถูกปฏิเสธสิทธิ์ไมโครโฟน'; + + @override + String get voiceError => 'เกิดข้อผิดพลาดในการรู้จำเสียง'; + + @override + String get voiceModelLoading => 'กำลังโหลดโมเดลเสียง โปรดรอ...'; } diff --git a/lib/l10n/app_localizations_zh.dart b/lib/l10n/app_localizations_zh.dart index 6d777ba..d9051de 100644 --- a/lib/l10n/app_localizations_zh.dart +++ b/lib/l10n/app_localizations_zh.dart @@ -9,7 +9,7 @@ class AppLocalizationsZh extends AppLocalizations { AppLocalizationsZh([String locale = 'zh']) : super(locale); @override - String get appTitle => '智念管理'; + String get appTitle => '智念商家端'; @override String get appSubtitle => '酒店景区智能管理平台'; @@ -177,7 +177,7 @@ class AppLocalizationsZh extends AppLocalizations { String get all => '全部'; @override - String get pending => '待处理'; + String get pending => '待接单'; @override String get processing => '处理中'; @@ -222,7 +222,7 @@ class AppLocalizationsZh extends AppLocalizations { String get creator => '创建人'; @override - String get assignee => '指派给'; + String get assignee => '接单部门'; @override String get notAssigned => '未指派'; @@ -408,10 +408,10 @@ class AppLocalizationsZh extends AppLocalizations { String get reportDesc => '查看运营数据分析'; @override - String get appMarket => '应用市场'; + String get appMarket => '应用插件'; @override - String get marketDesc => '扩展更多功能模块'; + String get marketDesc => '更多业务扩展插件'; @override String get helpCenter => '帮助中心'; @@ -560,7 +560,7 @@ class AppLocalizationsZh extends AppLocalizations { @override String get faq3A => - '工单共有三种状态:待处理(刚创建未分配)、处理中(已分配给相关人员)、已完成(问题已解决)。可以在「订单工单」页面的工单Tab中按状态筛选。'; + '工单共有三种状态:待接单(刚创建未分配)、处理中(已分配给相关人员)、已完成(问题已解决)。可以在「订单工单」页面的工单Tab中按状态筛选。'; @override String get faq4Q => '老板和员工角色有什么区别?'; @@ -708,10 +708,10 @@ class AppLocalizationsZh extends AppLocalizations { String get latestVersion => '最新版本'; @override - String get agreementTitle => '智念管理用户协议'; + String get agreementTitle => '智念商家端用户协议'; @override - String get privacyTitle => '智念管理隐私政策'; + String get privacyTitle => '智念商家端隐私政策'; @override String get lastUpdated => '最后更新日期:2024年12月'; @@ -721,7 +721,7 @@ class AppLocalizationsZh extends AppLocalizations { @override String get agreementPara1 => - '本协议是您与智念科技之间关于使用智念管理服务所订立的协议。智念管理服务包括但不限于事件发布、订单管理、工单处理、扫码核销等功能。'; + '本协议是您与智念科技之间关于使用智念商家端服务所订立的协议。智念商家端服务包括但不限于事件发布、订单管理、工单处理、扫码核销等功能。'; @override String get agreementSection2 => '二、账号注册'; @@ -815,7 +815,7 @@ class AppLocalizationsZh extends AppLocalizations { @override String get productDesc => - '智念管理是一款专为酒店、景区等文旅行业打造的智能管理应用。通过AI助手、事件发布、订单工单管理、扫码核销等功能,帮助商家高效运营、提升服务质量。'; + '智念商家端是一款专为酒店、景区等文旅行业打造的智能管理应用。通过AI助手、事件发布、订单工单管理、扫码核销等功能,帮助商家高效运营、提升服务质量。'; @override String get officialWebsite => '官方网站'; @@ -834,4 +834,70 @@ class AppLocalizationsZh extends AppLocalizations { @override String get allRightsReserved => 'All Rights Reserved'; + + @override + String get acceptOrder => '接单'; + + @override + String get transferOrder => '转单'; + + @override + String get completeOrder => '完成工单'; + + @override + String get selectTransferDept => '选择转单部门'; + + @override + String get acceptSuccess => '接单成功'; + + @override + String get acceptFailed => '接单失败,请重试'; + + @override + String get transferSuccess => '转单成功'; + + @override + String get transferFailed => '转单失败,请重试'; + + @override + String get completeSuccess => '工单已完成'; + + @override + String get completeFailed => '操作失败,请重试'; + + @override + String get deptMaintenance => '维修部'; + + @override + String get deptCleaning => '保洁部'; + + @override + String get deptFrontDesk => '前台部'; + + @override + String get deptSecurity => '安保部'; + + @override + String get deptAdmin => '行政部'; + + @override + String get deptKitchen => '厨房'; + + @override + String get transferDept => '转单部门'; + + @override + String get voiceListening => '正在聆听...'; + + @override + String get voiceUnavailable => '当前设备不支持语音识别'; + + @override + String get voicePermissionDenied => '麦克风权限被拒绝'; + + @override + String get voiceError => '语音识别出错'; + + @override + String get voiceModelLoading => '正在加载语音模型,请稍候...'; } diff --git a/lib/l10n/app_th.arb b/lib/l10n/app_th.arb index 71a702d..dfd498b 100644 --- a/lib/l10n/app_th.arb +++ b/lib/l10n/app_th.arb @@ -1,6 +1,6 @@ { "@@locale": "th", - "appTitle": "จื้อเหนียน จัดการ", + "appTitle": "จื้อเหนียน ผู้ขาย", "appSubtitle": "ระบบจัดการโรงแรมและสถานที่ท่องเที่ยวอัจฉริยะ", "welcomeLogin": "ยินดีต้อนรับ", "enterAccountInfo": "กรุณากรอกข้อมูลบัญชีของคุณ", @@ -71,7 +71,7 @@ "unknownLocation": "ไม่ทราบตำแหน่ง", "workOrderId": "รหัสใบงาน", "creator": "ผู้สร้าง", - "assignee": "ผู้รับมอบหมาย", + "assignee": "แผนกที่รับงาน", "notAssigned": "ยังไม่ได้มอบหมาย", "category": "หมวดหมู่", "priority": "ลำดับความสำคัญ", @@ -129,8 +129,8 @@ "employeeDesc": "เพิ่มและจัดการบัญชีพนักงาน", "dataReport": "รายงานข้อมูล", "reportDesc": "ดูการวิเคราะห์การดำเนินงาน", - "appMarket": "ตลาดแอป", - "marketDesc": "ขยายโมดูลฟีเจอร์เพิ่มเติม", + "appMarket": "ปลั๊กอินแอป", + "marketDesc": "ปลั๊กอินส่วนขยายธุรกิจเพิ่มเติม", "helpCenter": "ศูนย์ช่วยเหลือ", "helpDesc": "คู่มือใช้งานและคำถามที่พบบ่อย", "aboutUs": "เกี่ยวกับเรา", @@ -193,7 +193,7 @@ "contactTeam": "ติดต่อทีมสนับสนุนของเราเพื่อขอความช่วยเหลือเพิ่มเติม", "contactService": "ติดต่อฝ่ายสนับสนุน", "productIntro": "แนะนำผลิตภัณฑ์", - "productDesc": "จื้อเหนียน จัดการเป็นแอปจัดการอัจฉริยะสำหรับโรงแรมและสถานที่ท่องเที่ยว ด้วยผู้ช่วย AI การเผยแพร่เหตุการณ์ การจัดการคำสั่งซื้อ และการตรวจสอบ QR ช่วยให้ธุรกิจดำเนินงานได้อย่างมีประสิทธิภาพ", + "productDesc": "จื้อเหนียน ผู้ขายเป็นแอปจัดการอัจฉริยะสำหรับโรงแรมและสถานที่ท่องเที่ยว ด้วยผู้ช่วย AI การเผยแพร่เหตุการณ์ การจัดการคำสั่งซื้อ และการตรวจสอบ QR ช่วยให้ธุรกิจดำเนินงานได้อย่างมีประสิทธิภาพ", "officialWebsite": "เว็บไซต์ทางการ", "servicePhone": "โทรบริการ", "techSupport": "ฝ่ายสนับสนุนทางเทคนิค", @@ -238,11 +238,11 @@ "spaPackage": "แพ็คเกจสปา", "orderUnit": " รายการ", "latestVersion": "เวอร์ชันล่าสุด", - "agreementTitle": "ข้อตกลงผู้ใช้ จื้อเหนียน จัดการ", - "privacyTitle": "นโยบายความเป็นส่วนตัว จื้อเหนียน จัดการ", + "agreementTitle": "ข้อตกลงผู้ใช้ จื้อเหนียน ผู้ขาย", + "privacyTitle": "นโยบายความเป็นส่วนตัว จื้อเหนียน ผู้ขาย", "lastUpdated": "อัปเดตล่าสุด: ธันวาคม 2024", "agreementSection1": "1. ขอบเขตข้อตกลง", - "agreementPara1": "ข้อตกลงนี้เป็นข้อตกลงระหว่างคุณกับ จื้อเหนียน เทค เกี่ยวกับการใช้บริการ จื้อเหนียน จัดการ บริการรวมถึงแต่ไม่จำกัดเพียง การเผยแพร่เหตุการณ์ การจัดการคำสั่งซื้อ การประมวลผลใบงาน การตรวจสอบ QR ฯลฯ", + "agreementPara1": "ข้อตกลงนี้เป็นข้อตกลงระหว่างคุณกับ จื้อเหนียน เทค เกี่ยวกับการใช้บริการ จื้อเหนียน ผู้ขาย บริการรวมถึงแต่ไม่จำกัดเพียง การเผยแพร่เหตุการณ์ การจัดการคำสั่งซื้อ การประมวลผลใบงาน การตรวจสอบ QR ฯลฯ", "agreementSection2": "2. การลงทะเบียนบัญชี", "agreementPara2": "คุณต้องลงทะเบียนบัญชีเพื่อใช้บริการของเรา กรุณากรอกหมายเลขโทรศัพท์ที่ถูกต้องและตั้งรหัสผ่านที่ปลอดภัย คุณรับผิดชอบต่อการกระทำทั้งหมดภายใต้บัญชีของคุณ", "agreementSection3": "3. กฎการใช้บริการ", @@ -268,5 +268,28 @@ "privacySection6": "6. Cookies และการจัดเก็บข้อมูลในเครื่อง", "privacyPara6": "เราใช้การจัดเก็บข้อมูลในเครื่องเพื่อบันทึกสถานะการเข้าสู่ระบบและการตั้งค่าแอป เพื่อประสบการณ์ที่ดีขึ้น คุณสามารถล้างข้อมูลเหล่านี้ได้ในการตั้งค่า", "privacySection7": "7. การอัปเดตนโยบายความเป็นส่วนตัว", - "privacyPara7": "เราอาจอัปเดตนโยบายความเป็นส่วนตัวเป็นครั้งคราว การอัปเดตจะแจ้งในแอป และการใช้งานต่อถือว่ายอมรับ" + "privacyPara7": "เราอาจอัปเดตนโยบายความเป็นส่วนตัวเป็นครั้งคราว การอัปเดตจะแจ้งในแอป และการใช้งานต่อถือว่ายอมรับ", + + "acceptOrder": "รับงาน", + "transferOrder": "โอนงาน", + "completeOrder": "เสร็จสิ้นงาน", + "selectTransferDept": "เลือกแผนกโอนงาน", + "acceptSuccess": "รับงานสำเร็จ", + "acceptFailed": "รับงานล้มเหลว โปรดลองอีกครั้ง", + "transferSuccess": "โอนงานสำเร็จ", + "transferFailed": "โอนงานล้มเหลว โปรดลองอีกครั้ง", + "completeSuccess": "งานเสร็จสิ้น", + "completeFailed": "การดำเนินการล้มเหลว โปรดลองอีกครั้ง", + "deptMaintenance": "ฝ่ายซ่อมบำรุง", + "deptCleaning": "ฝ่ายทำความสะอาด", + "deptFrontDesk": "ฝ่ายต้อนรับ", + "deptSecurity": "ฝ่ายรักษาความปลอดภัย", + "deptAdmin": "ฝ่ายธุรการ", + "deptKitchen": "ฝ่ายครัว", + "transferDept": "แผนกโอนงาน", + "voiceListening": "กำลังฟัง...", + "voiceUnavailable": "ไม่รองรับการรู้จำเสียง", + "voicePermissionDenied": "ถูกปฏิเสธสิทธิ์ไมโครโฟน", + "voiceError": "เกิดข้อผิดพลาดในการรู้จำเสียง", + "voiceModelLoading": "กำลังโหลดโมเดลเสียง โปรดรอ..." } \ No newline at end of file diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index 5e7e961..e6c75f0 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -1,6 +1,6 @@ { "@@locale": "zh", - "appTitle": "智念管理", + "appTitle": "智念商家端", "appSubtitle": "酒店景区智能管理平台", "welcomeLogin": "欢迎登录", "enterAccountInfo": "请输入您的账号信息", @@ -59,7 +59,7 @@ "workOrder": "工单", "order": "订单", "all": "全部", - "pending": "待处理", + "pending": "待接单", "processing": "处理中", "completed": "已完成", "pendingPayment": "待支付", @@ -75,7 +75,7 @@ "unknownLocation": "未知位置", "workOrderId": "工单编号", "creator": "创建人", - "assignee": "指派给", + "assignee": "接单部门", "notAssigned": "未指派", "category": "分类", "priority": "优先级", @@ -140,8 +140,8 @@ "employeeDesc": "添加和管理员工账号", "dataReport": "数据报表", "reportDesc": "查看运营数据分析", - "appMarket": "应用市场", - "marketDesc": "扩展更多功能模块", + "appMarket": "应用插件", + "marketDesc": "更多业务扩展插件", "helpCenter": "帮助中心", "helpDesc": "使用指南与常见问题", "aboutUs": "关于我们", @@ -195,7 +195,7 @@ "faq2Q": "如何核销订单?", "faq2A": "在首页快捷菜单中点击「核销」,使用手机摄像头扫描客户出示的二维码,识别成功后会跳转到订单详情页,点击底部「核销」按钮并确认即可完成。", "faq3Q": "工单状态有哪些?", - "faq3A": "工单共有三种状态:待处理(刚创建未分配)、处理中(已分配给相关人员)、已完成(问题已解决)。可以在「订单工单」页面的工单Tab中按状态筛选。", + "faq3A": "工单共有三种状态:待接单(刚创建未分配)、处理中(已分配给相关人员)、已完成(问题已解决)。可以在「订单工单」页面的工单Tab中按状态筛选。", "faq4Q": "老板和员工角色有什么区别?", "faq4A": "老板角色拥有完整的操作权限,包括员工管理、数据报表等管理功能;员工角色只能处理日常业务,如发布事件、处理工单、核销订单等。", "faq5Q": "如何切换主题和语言?", @@ -251,11 +251,11 @@ "latestVersion": "最新版本", - "agreementTitle": "智念管理用户协议", - "privacyTitle": "智念管理隐私政策", + "agreementTitle": "智念商家端用户协议", + "privacyTitle": "智念商家端隐私政策", "lastUpdated": "最后更新日期:2024年12月", "agreementSection1": "一、协议范围", - "agreementPara1": "本协议是您与智念科技之间关于使用智念管理服务所订立的协议。智念管理服务包括但不限于事件发布、订单管理、工单处理、扫码核销等功能。", + "agreementPara1": "本协议是您与智念科技之间关于使用智念商家端服务所订立的协议。智念商家端服务包括但不限于事件发布、订单管理、工单处理、扫码核销等功能。", "agreementSection2": "二、账号注册", "agreementPara2": "您需要注册一个账号才能使用我们的服务。注册时需提供真实有效的手机号码,并设置安全密码。您应对账号下的所有行为负责。", "agreementSection3": "三、服务使用规范", @@ -284,11 +284,35 @@ "privacyPara7": "我们可能会不时更新本隐私政策。更新后的政策将在应用内提示,继续使用即视为接受更新。", "productIntro": "产品简介", - "productDesc": "智念管理是一款专为酒店、景区等文旅行业打造的智能管理应用。通过AI助手、事件发布、订单工单管理、扫码核销等功能,帮助商家高效运营、提升服务质量。", + "productDesc": "智念商家端是一款专为酒店、景区等文旅行业打造的智能管理应用。通过AI助手、事件发布、订单工单管理、扫码核销等功能,帮助商家高效运营、提升服务质量。", "officialWebsite": "官方网站", "servicePhone": "客服电话", "techSupport": "技术支持", "companyAddress": "公司地址", "copyright": "Copyright 2024 智念科技", - "allRightsReserved": "All Rights Reserved" + "allRightsReserved": "All Rights Reserved", + + "acceptOrder": "接单", + "transferOrder": "转单", + "completeOrder": "完成工单", + "selectTransferDept": "选择转单部门", + "acceptSuccess": "接单成功", + "acceptFailed": "接单失败,请重试", + "transferSuccess": "转单成功", + "transferFailed": "转单失败,请重试", + "completeSuccess": "工单已完成", + "completeFailed": "操作失败,请重试", + "deptMaintenance": "维修部", + "deptCleaning": "保洁部", + "deptFrontDesk": "前台部", + "deptSecurity": "安保部", + "deptAdmin": "行政部", + "deptKitchen": "厨房", + "transferDept": "转单部门", + + "voiceListening": "正在聆听...", + "voiceUnavailable": "当前设备不支持语音识别", + "voicePermissionDenied": "麦克风权限被拒绝", + "voiceError": "语音识别出错", + "voiceModelLoading": "正在加载语音模型,请稍候..." } diff --git a/lib/models/work_order.dart b/lib/models/work_order.dart index c9a1b49..867b689 100644 --- a/lib/models/work_order.dart +++ b/lib/models/work_order.dart @@ -34,7 +34,7 @@ class WorkOrder { String get statusText { switch (status) { case WorkOrderStatus.pending: - return '待处理'; + return '待接单'; case WorkOrderStatus.processing: return '处理中'; case WorkOrderStatus.completed: diff --git a/lib/pages/about_page.dart b/lib/pages/about_page.dart index 6f027ea..6d03979 100644 --- a/lib/pages/about_page.dart +++ b/lib/pages/about_page.dart @@ -21,15 +21,15 @@ class AboutPage extends StatelessWidget { width: 100, height: 100, decoration: BoxDecoration( - gradient: AppGradients.primary, borderRadius: BorderRadius.circular(24), ), - child: const Icon(Icons.auto_awesome, color: Colors.white, size: 48), + clipBehavior: Clip.antiAlias, + child: Image.asset('assets/logo.png', fit: BoxFit.cover), ), const SizedBox(height: 20), Text( l10n.appTitle, - style: const TextStyle( + style: TextStyle( fontSize: 24, fontWeight: FontWeight.bold, color: AppColors.textPrimary, @@ -38,7 +38,7 @@ class AboutPage extends StatelessWidget { const SizedBox(height: 4), Text( '${l10n.version} 1.0.0', - style: const TextStyle(fontSize: 14, color: AppColors.textSecondary), + style: TextStyle(fontSize: 14, color: AppColors.textSecondary), ), const SizedBox(height: 8), Container( @@ -68,7 +68,7 @@ class AboutPage extends StatelessWidget { children: [ Text( l10n.productIntro, - style: const TextStyle( + style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: AppColors.textPrimary, @@ -77,7 +77,7 @@ class AboutPage extends StatelessWidget { const SizedBox(height: 12), Text( l10n.productDesc, - style: const TextStyle( + style: TextStyle( fontSize: 14, color: AppColors.textSecondary, height: 1.7, @@ -115,14 +115,14 @@ class AboutPage extends StatelessWidget { ListTile( leading: const Icon(Icons.description_outlined, color: AppColors.primary), title: Text(l10n.userAgreement), - trailing: const Icon(Icons.chevron_right, color: AppColors.textTertiary), + trailing: Icon(Icons.chevron_right, color: AppColors.textTertiary), onTap: () => context.push('/settings/policy?type=agreement'), ), const Divider(height: 1, indent: 72), ListTile( leading: const Icon(Icons.privacy_tip_outlined, color: AppColors.primary), title: Text(l10n.privacyPolicy), - trailing: const Icon(Icons.chevron_right, color: AppColors.textTertiary), + trailing: Icon(Icons.chevron_right, color: AppColors.textTertiary), onTap: () => context.push('/settings/policy?type=privacy'), ), ], @@ -131,12 +131,12 @@ class AboutPage extends StatelessWidget { const SizedBox(height: 32), Text( l10n.copyright, - style: const TextStyle(fontSize: 12, color: AppColors.textTertiary), + style: TextStyle(fontSize: 12, color: AppColors.textTertiary), ), const SizedBox(height: 8), Text( l10n.allRightsReserved, - style: const TextStyle(fontSize: 12, color: AppColors.textTertiary), + style: TextStyle(fontSize: 12, color: AppColors.textTertiary), ), ], ), @@ -151,12 +151,12 @@ class AboutPage extends StatelessWidget { children: [ Text( label, - style: const TextStyle(fontSize: 14, color: AppColors.textSecondary), + style: TextStyle(fontSize: 14, color: AppColors.textSecondary), ), const Spacer(), Text( value, - style: const TextStyle( + style: TextStyle( fontSize: 14, fontWeight: FontWeight.w500, color: AppColors.textPrimary, diff --git a/lib/pages/app_market_page.dart b/lib/pages/app_market_page.dart index 5912f41..7c925cd 100644 --- a/lib/pages/app_market_page.dart +++ b/lib/pages/app_market_page.dart @@ -1,204 +1,142 @@ import 'package:flutter/material.dart'; +import 'package:flutter_animate/flutter_animate.dart'; import '../theme.dart'; import '../l10n/app_localizations.dart'; -class AppMarketPage extends StatefulWidget { +class AppMarketPage extends StatelessWidget { const AppMarketPage({super.key}); - @override - State createState() => _AppMarketPageState(); -} - -class _AppMarketPageState extends State { - final List> _apps = [ - { - 'name': '会员管理', - 'desc': '会员积分、等级、权益管理', - 'icon': Icons.card_membership, - 'color': const Color(0xFF1A56DB), - 'installed': true, - }, - { - 'name': '财务对账', - 'desc': '收支明细、财务报表、对账单', - 'icon': Icons.account_balance_wallet, - 'color': const Color(0xFF10B981), - 'installed': true, - }, - { - 'name': '库存管理', - 'desc': '商品库存、出入库记录', - 'icon': Icons.inventory_2, - 'color': const Color(0xFFF59E0B), - 'installed': false, - }, - { - 'name': '智能排班', - 'desc': '员工排班、考勤统计', - 'icon': Icons.calendar_month, - 'color': const Color(0xFF8B5CF6), - 'installed': false, - }, - { - 'name': '客户评价', - 'desc': '收集和分析客户反馈', - 'icon': Icons.reviews, - 'color': const Color(0xFFEC4899), - 'installed': false, - }, - { - 'name': '营销工具', - 'desc': '优惠券、拼团、秒杀活动', - 'icon': Icons.campaign, - 'color': const Color(0xFFEF4444), - 'installed': true, - }, - ]; - - void _installApp(int index) { - final l10n = AppLocalizations.of(context)!; - setState(() { - _apps[index]['installed'] = true; - }); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('${_apps[index]['name']} ${l10n.installSuccess}'), - backgroundColor: AppColors.success, - behavior: SnackBarBehavior.floating, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), - ), - ); - } - - void _openApp(int index) { - final l10n = AppLocalizations.of(context)!; - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('${l10n.opening} ${_apps[index]['name']}...'), - behavior: SnackBarBehavior.floating, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), - ), - ); - } - @override Widget build(BuildContext context) { final l10n = AppLocalizations.of(context)!; + final plugins = <_Plugin>[ + _Plugin( + name: '开关房', + desc: '管理房态、入住与退房', + icon: Icons.meeting_room_outlined, + color: const Color(0xFF3B82F6), + ), + _Plugin( + name: '下载报表', + desc: '导出经营数据与报表文件', + icon: Icons.file_download_outlined, + color: const Color(0xFFF59E0B), + ), + _Plugin( + name: '评价管理', + desc: '查看与回复客户评价', + icon: Icons.rate_review_outlined, + color: const Color(0xFFEC4899), + ), + ]; + return Scaffold( backgroundColor: AppColors.background, - appBar: AppBar(title: Text(l10n.appMarket)), + appBar: AppBar( + title: Text(l10n.appMarket), + backgroundColor: AppColors.surface, + elevation: 0, + scrolledUnderElevation: 0, + surfaceTintColor: Colors.transparent, + ), body: ListView.builder( - padding: const EdgeInsets.all(16), - itemCount: _apps.length, + padding: const EdgeInsets.fromLTRB(16, 16, 16, 24), + itemCount: plugins.length, itemBuilder: (context, index) { - final app = _apps[index]; - return Container( - margin: const EdgeInsets.only(bottom: 12), - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( + final p = plugins[index]; + return Padding( + padding: const EdgeInsets.only(bottom: 12), + child: Material( color: AppColors.surface, borderRadius: BorderRadius.circular(16), - boxShadow: [ - BoxShadow( - color: Colors.black.withValues(alpha: 0.03), - blurRadius: 10, - offset: const Offset(0, 2), - ), - ], - ), - child: Row( - children: [ - Container( - width: 52, - height: 52, + child: InkWell( + borderRadius: BorderRadius.circular(16), + onTap: () => _openPlugin(context, p.name), + child: Container( + padding: const EdgeInsets.all(16), decoration: BoxDecoration( - color: (app['color'] as Color).withValues(alpha: 0.1), - borderRadius: BorderRadius.circular(14), + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.03), + blurRadius: 10, + offset: const Offset(0, 2), + ), + ], ), - child: Icon(app['icon'] as IconData, - color: app['color'] as Color, size: 26), - ), - const SizedBox(width: 16), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + child: Row( children: [ - Row( - children: [ - Text( - app['name'], - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w600, - color: AppColors.textPrimary, - ), - ), - if (app['installed'] == true) ...[ - const SizedBox(width: 8), - Container( - padding: const EdgeInsets.symmetric( - horizontal: 8, vertical: 2), - decoration: BoxDecoration( - color: AppColors.success.withValues(alpha: 0.1), - borderRadius: BorderRadius.circular(4), + Container( + width: 48, + height: 48, + decoration: BoxDecoration( + color: p.color.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(12), + ), + child: Icon(p.icon, color: p.color, size: 24), + ), + const SizedBox(width: 14), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + p.name, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: AppColors.textPrimary, ), - child: Text( - l10n.installed, - style: const TextStyle( - fontSize: 10, - color: AppColors.success, - fontWeight: FontWeight.w500, - ), + ), + const SizedBox(height: 4), + Text( + p.desc, + style: TextStyle( + fontSize: 13, + color: AppColors.textSecondary, ), ), ], - ], - ), - const SizedBox(height: 6), - Text( - app['desc'], - style: const TextStyle( - fontSize: 13, - color: AppColors.textSecondary, ), ), + Icon( + Icons.chevron_right, + color: AppColors.textTertiary, + size: 20, + ), ], ), ), - const SizedBox(width: 8), - if (app['installed'] == true) - OutlinedButton( - onPressed: () => _openApp(index), - style: OutlinedButton.styleFrom( - padding: const EdgeInsets.symmetric( - horizontal: 16, vertical: 8), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), - ), - child: Text(l10n.open, - style: const TextStyle(fontSize: 12)), - ) - else - ElevatedButton( - onPressed: () => _installApp(index), - style: ElevatedButton.styleFrom( - backgroundColor: AppColors.primary, - foregroundColor: Colors.white, - padding: const EdgeInsets.symmetric( - horizontal: 16, vertical: 8), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), - ), - child: Text(l10n.install, - style: const TextStyle(fontSize: 12)), - ), - ], + ), ), - ); + ).animate().fadeIn(duration: 300.ms, delay: (index * 60).ms).slideY(begin: 0.1, end: 0, duration: 300.ms); }, ), ); } + + void _openPlugin(BuildContext context, String name) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('$name 即将上线'), + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + duration: const Duration(seconds: 2), + ), + ); + } +} + +class _Plugin { + final String name; + final String desc; + final IconData icon; + final Color color; + + const _Plugin({ + required this.name, + required this.desc, + required this.icon, + required this.color, + }); } diff --git a/lib/pages/employee_page.dart b/lib/pages/employee_page.dart index 06d12f8..13acb00 100644 --- a/lib/pages/employee_page.dart +++ b/lib/pages/employee_page.dart @@ -66,7 +66,7 @@ class _EmployeePageState extends ConsumerState { const SizedBox(height: 20), Text( l10n.addEmployee, - style: const TextStyle( + style: TextStyle( fontSize: 20, fontWeight: FontWeight.bold, color: AppColors.textPrimary, @@ -208,7 +208,7 @@ class _EmployeePageState extends ConsumerState { const SizedBox(height: 20), Text( emp['name'], - style: const TextStyle( + style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: AppColors.textPrimary, @@ -217,7 +217,7 @@ class _EmployeePageState extends ConsumerState { const SizedBox(height: 4), Text( '${emp['role']} | ${emp['phone']}', - style: const TextStyle( + style: TextStyle( fontSize: 14, color: AppColors.textSecondary, ), @@ -319,7 +319,7 @@ class _EmployeePageState extends ConsumerState { size: 64, color: AppColors.textTertiary.withValues(alpha: 0.5)), const SizedBox(height: 16), Text(l10n.noEmployees, - style: const TextStyle( + style: TextStyle( fontSize: 16, color: AppColors.textSecondary)), ], ), @@ -367,7 +367,7 @@ class _EmployeePageState extends ConsumerState { children: [ Text( emp['name'], - style: const TextStyle( + style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: AppColors.textPrimary, @@ -399,7 +399,7 @@ class _EmployeePageState extends ConsumerState { const SizedBox(height: 6), Text( '${emp['role']} | ${emp['phone']}', - style: const TextStyle( + style: TextStyle( fontSize: 13, color: AppColors.textSecondary, ), @@ -409,7 +409,7 @@ class _EmployeePageState extends ConsumerState { ), if (isBoss) IconButton( - icon: const Icon(Icons.more_vert, + icon: Icon(Icons.more_vert, color: AppColors.textTertiary), onPressed: () => _showEmployeeActions(index), ), diff --git a/lib/pages/event_list_page.dart b/lib/pages/event_list_page.dart index 0374dca..baef5b0 100644 --- a/lib/pages/event_list_page.dart +++ b/lib/pages/event_list_page.dart @@ -4,6 +4,7 @@ import 'package:flutter_animate/flutter_animate.dart'; import '../providers/event_provider.dart'; import '../theme.dart'; import '../l10n/app_localizations.dart'; +import '../widgets/skeleton.dart'; class EventListPage extends ConsumerStatefulWidget { const EventListPage({super.key}); @@ -62,7 +63,10 @@ class _EventListPageState extends ConsumerState { title: Text(l10n.eventRecords), ), body: eventState.isLoading - ? const Center(child: CircularProgressIndicator()) + ? SkeletonList( + count: 5, + itemBuilder: (_) => const EventCardSkeleton(), + ) : eventState.events.isEmpty ? _buildEmptyState(l10n) : ListView.builder( @@ -89,7 +93,7 @@ class _EventListPageState extends ConsumerState { const SizedBox(height: 16), Text( l10n.noEvents, - style: const TextStyle(fontSize: 16, color: AppColors.textSecondary), + style: TextStyle(fontSize: 16, color: AppColors.textSecondary), ), ], ), @@ -137,7 +141,7 @@ class _EventListPageState extends ConsumerState { Expanded( child: Text( event.entityName, - style: const TextStyle( + style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: AppColors.textPrimary, @@ -166,7 +170,7 @@ class _EventListPageState extends ConsumerState { event.description, maxLines: 2, overflow: TextOverflow.ellipsis, - style: const TextStyle( + style: TextStyle( fontSize: 14, color: AppColors.textSecondary, height: 1.4, @@ -187,7 +191,7 @@ class _EventListPageState extends ConsumerState { const SizedBox(width: 4), Text( '${l10n.publishTime}: ${_formatDate(event.publishTime)}', - style: const TextStyle(fontSize: 12, color: AppColors.textTertiary), + style: TextStyle(fontSize: 12, color: AppColors.textTertiary), ), if (event.popupReminder) ...[ const SizedBox(width: 12), @@ -197,7 +201,7 @@ class _EventListPageState extends ConsumerState { if (event.creatorName != null) Text( event.creatorName!, - style: const TextStyle(fontSize: 12, color: AppColors.textTertiary), + style: TextStyle(fontSize: 12, color: AppColors.textTertiary), ), ], ), diff --git a/lib/pages/event_publish_page.dart b/lib/pages/event_publish_page.dart index 43d8dc8..28d3433 100644 --- a/lib/pages/event_publish_page.dart +++ b/lib/pages/event_publish_page.dart @@ -192,7 +192,6 @@ class _EventPublishPageState extends ConsumerState { controller: _descController, label: l10n.eventDesc, hint: l10n.eventDescHint, - icon: Icons.description_outlined, maxLines: 4, validator: (v) => v == null || v.isEmpty ? l10n.eventDesc : null, ), @@ -274,7 +273,7 @@ class _EventPublishPageState extends ConsumerState { Widget _buildSectionTitle(String title) { return Text( title, - style: const TextStyle( + style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: AppColors.textPrimary, @@ -286,7 +285,7 @@ class _EventPublishPageState extends ConsumerState { required TextEditingController controller, required String label, required String hint, - required IconData icon, + IconData? icon, int maxLines = 1, String? Function(String?)? validator, }) { @@ -295,7 +294,7 @@ class _EventPublishPageState extends ConsumerState { children: [ Text( label, - style: const TextStyle( + style: TextStyle( fontSize: 14, fontWeight: FontWeight.w500, color: AppColors.textPrimary, @@ -308,7 +307,9 @@ class _EventPublishPageState extends ConsumerState { validator: validator, decoration: InputDecoration( hintText: hint, - prefixIcon: Icon(icon, color: AppColors.textTertiary, size: 20), + prefixIcon: icon != null + ? Icon(icon, color: AppColors.textTertiary, size: 20) + : null, filled: true, fillColor: AppColors.surface, border: OutlineInputBorder( @@ -382,9 +383,9 @@ class _EventPublishPageState extends ConsumerState { child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - const Icon(Icons.add_photo_alternate_outlined, color: AppColors.textTertiary, size: 28), + Icon(Icons.add_photo_alternate_outlined, color: AppColors.textTertiary, size: 28), const SizedBox(height: 4), - Text(l10n.addImage, style: const TextStyle(fontSize: 12, color: AppColors.textTertiary)), + Text(l10n.addImage, style: TextStyle(fontSize: 12, color: AppColors.textTertiary)), ], ), ), @@ -418,7 +419,7 @@ class _EventPublishPageState extends ConsumerState { children: [ Text( label, - style: const TextStyle(fontSize: 12, color: AppColors.textSecondary), + style: TextStyle(fontSize: 12, color: AppColors.textSecondary), ), const SizedBox(height: 2), Text( @@ -434,7 +435,7 @@ class _EventPublishPageState extends ConsumerState { ], ), ), - const Icon(Icons.chevron_right, color: AppColors.textTertiary), + Icon(Icons.chevron_right, color: AppColors.textTertiary), ], ), ), @@ -465,11 +466,11 @@ class _EventPublishPageState extends ConsumerState { children: [ Text( l10n.popupReminder, - style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w500, color: AppColors.textPrimary), + style: TextStyle(fontSize: 15, fontWeight: FontWeight.w500, color: AppColors.textPrimary), ), Text( l10n.popupReminderDesc, - style: const TextStyle(fontSize: 12, color: AppColors.textSecondary), + style: TextStyle(fontSize: 12, color: AppColors.textSecondary), ), ], ), diff --git a/lib/pages/help_page.dart b/lib/pages/help_page.dart index 0e6cc1d..9c1924c 100644 --- a/lib/pages/help_page.dart +++ b/lib/pages/help_page.dart @@ -78,7 +78,7 @@ class HelpPage extends StatelessWidget { const SizedBox(height: 20), Text( l10n.faq, - style: const TextStyle( + style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: AppColors.textPrimary, @@ -105,7 +105,7 @@ class HelpPage extends StatelessWidget { const SizedBox(height: 12), Text( l10n.stillQuestions, - style: const TextStyle( + style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: AppColors.textPrimary, @@ -114,7 +114,7 @@ class HelpPage extends StatelessWidget { const SizedBox(height: 8), Text( l10n.contactTeam, - style: const TextStyle(fontSize: 14, color: AppColors.textSecondary), + style: TextStyle(fontSize: 14, color: AppColors.textSecondary), ), const SizedBox(height: 16), SizedBox( @@ -190,7 +190,7 @@ class _FaqItemState extends State<_FaqItem> { ), title: Text( widget.question, - style: const TextStyle( + style: TextStyle( fontSize: 14, fontWeight: FontWeight.w500, color: AppColors.textPrimary, @@ -199,7 +199,7 @@ class _FaqItemState extends State<_FaqItem> { trailing: AnimatedRotation( turns: _expanded ? 0.5 : 0, duration: const Duration(milliseconds: 200), - child: const Icon(Icons.keyboard_arrow_down, color: AppColors.textTertiary), + child: Icon(Icons.keyboard_arrow_down, color: AppColors.textTertiary), ), onTap: () => setState(() => _expanded = !_expanded), ), @@ -209,7 +209,7 @@ class _FaqItemState extends State<_FaqItem> { padding: const EdgeInsets.fromLTRB(72, 0, 16, 16), child: Text( widget.answer, - style: const TextStyle( + style: TextStyle( fontSize: 14, color: AppColors.textSecondary, height: 1.6, diff --git a/lib/pages/home_page.dart b/lib/pages/home_page.dart index d40c95d..9e5cf94 100644 --- a/lib/pages/home_page.dart +++ b/lib/pages/home_page.dart @@ -4,11 +4,19 @@ import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:go_router/go_router.dart'; import 'package:mobile_scanner/mobile_scanner.dart'; import 'package:flutter_animate/flutter_animate.dart'; +import 'dart:async'; +import 'dart:io' show Platform; +import 'package:speech_to_text/speech_to_text.dart' as stt; +import 'package:speech_to_text/speech_recognition_error.dart'; +import 'package:permission_handler/permission_handler.dart'; import '../providers/auth_provider.dart'; import '../providers/chat_provider.dart'; import '../models/message.dart'; +import '../services/vosk_voice_service.dart'; import '../theme.dart'; import '../l10n/app_localizations.dart'; +import '../widgets/chart_block.dart'; +import '../widgets/markdown_message.dart'; class HomePage extends ConsumerStatefulWidget { const HomePage({super.key}); @@ -20,22 +28,307 @@ class HomePage extends ConsumerStatefulWidget { class _HomePageState extends ConsumerState { final _messageController = TextEditingController(); final _scrollController = ScrollController(); + final stt.SpeechToText _speech = stt.SpeechToText(); + bool _speechAvailable = false; + bool _isListening = false; + bool _voskInitializing = false; + String _voiceBaseText = ''; + StreamSubscription? _voskPartialSub; + StreamSubscription? _voskResultSub; + StreamSubscription? _voskErrorSub; @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { _scrollToBottom(); + if (Platform.isAndroid) { + _speechAvailable = true; + } }); } @override void dispose() { + _speech.cancel(); + _voskPartialSub?.cancel(); + _voskResultSub?.cancel(); + _voskErrorSub?.cancel(); + if (Platform.isAndroid) { + VoskVoiceService.instance.stop(); + } _messageController.dispose(); _scrollController.dispose(); super.dispose(); } + Future _initSpeech() async { + try { + final available = await _speech.initialize( + onStatus: (status) { + if (!mounted) return; + if (status == 'notListening' || status == 'done') { + setState(() => _isListening = false); + } + }, + onError: (SpeechRecognitionError err) { + if (!mounted) return; + debugPrint('[iOS-STT] error: ${err.errorMsg} permanent=${err.permanent}'); + setState(() => _isListening = false); + }, + debugLogging: true, + finalTimeout: const Duration(seconds: 5), + ); + debugPrint('[iOS-STT] initialize -> $available'); + if (mounted) setState(() => _speechAvailable = available); + } catch (e) { + debugPrint('[iOS-STT] initialize threw $e'); + if (mounted) setState(() => _speechAvailable = false); + } + } + + String _resolveSpeechLocaleId() { + final lang = Localizations.localeOf(context).languageCode; + switch (lang) { + case 'zh': + return 'zh_CN'; + case 'th': + return 'th_TH'; + default: + return 'en_US'; + } + } + + Future _toggleListening() async { + final l10n = AppLocalizations.of(context)!; + if (Platform.isAndroid) { + await _toggleListeningVosk(l10n); + return; + } + await _toggleListeningIOS(l10n); + } + + Future _toggleListeningVosk(AppLocalizations l10n) async { + if (_isListening) { + await VoskVoiceService.instance.stop(); + if (mounted) setState(() => _isListening = false); + return; + } + final micStatus = await Permission.microphone.status; + var granted = micStatus.isGranted; + if (!granted && !micStatus.isPermanentlyDenied) { + final r = await Permission.microphone.request(); + granted = r.isGranted; + if (!granted && r.isPermanentlyDenied) { + _showPermissionSettingsSnack(l10n); + return; + } + } else if (micStatus.isPermanentlyDenied) { + _showPermissionSettingsSnack(l10n); + return; + } + if (!granted) { + _showVoiceSnack(l10n.voicePermissionDenied); + return; + } + if (_voskInitializing) return; + if (!VoskVoiceService.instance.isReady) { + setState(() => _voskInitializing = true); + _showVoiceLoadingSnack(l10n); + final ok = await VoskVoiceService.instance.ensureReady(); + if (mounted) setState(() => _voskInitializing = false); + if (!ok) { + if (mounted) { + ScaffoldMessenger.of(context).hideCurrentSnackBar(); + _showVoiceSnack(l10n.voiceError); + } + return; + } + if (mounted) ScaffoldMessenger.of(context).hideCurrentSnackBar(); + } + _voiceBaseText = _messageController.text; + _voskPartialSub ??= VoskVoiceService.instance.partialStream.listen(_onVoskText); + _voskResultSub ??= VoskVoiceService.instance.resultStream.listen(_onVoskText); + _voskErrorSub ??= VoskVoiceService.instance.errorStream.listen((_) { + if (!mounted) return; + setState(() => _isListening = false); + _showVoiceSnack(l10n.voiceError); + }); + final started = await VoskVoiceService.instance.start(); + if (!started) { + if (mounted) _showVoiceSnack(l10n.voiceError); + return; + } + if (mounted) setState(() => _isListening = true); + } + + void _onVoskText(String text) { + if (!mounted || text.isEmpty) return; + final cleaned = text.replaceAll(' ', ''); + final next = _voiceBaseText.isEmpty ? cleaned : '$_voiceBaseText$cleaned'; + _messageController.value = TextEditingValue( + text: next, + selection: TextSelection.collapsed(offset: next.length), + ); + } + + Future _toggleListeningIOS(AppLocalizations l10n) async { + if (_isListening) { + await _speech.stop(); + if (mounted) setState(() => _isListening = false); + return; + } + final micStatus = await Permission.microphone.status; + debugPrint('[iOS-STT] mic status before request: $micStatus'); + var micGranted = micStatus.isGranted; + if (!micGranted && !micStatus.isPermanentlyDenied) { + final r = await Permission.microphone.request(); + debugPrint('[iOS-STT] mic status after request: $r'); + micGranted = r.isGranted; + if (!micGranted && r.isPermanentlyDenied) { + _showPermissionSettingsSnack(l10n); + return; + } + } else if (micStatus.isPermanentlyDenied) { + _showPermissionSettingsSnack(l10n); + return; + } + if (!micGranted) { + _showVoiceSnack(l10n.voicePermissionDenied); + return; + } + if (!_speechAvailable) { + await _initSpeech(); + if (!_speechAvailable) { + _showVoiceSnack(l10n.voiceUnavailable); + return; + } + } + _voiceBaseText = _messageController.text; + setState(() => _isListening = true); + try { + await _speech.listen( + onResult: (result) { + final words = result.recognizedWords; + if (!mounted) return; + final next = _voiceBaseText.isEmpty + ? words + : (words.isEmpty ? _voiceBaseText : '$_voiceBaseText $words'); + _messageController.value = TextEditingValue( + text: next, + selection: TextSelection.collapsed(offset: next.length), + ); + }, + listenFor: const Duration(seconds: 30), + pauseFor: const Duration(seconds: 3), + localeId: _resolveSpeechLocaleId(), + listenOptions: stt.SpeechListenOptions( + partialResults: true, + cancelOnError: true, + listenMode: stt.ListenMode.dictation, + ), + ); + } catch (e) { + debugPrint('[iOS-STT] listen threw $e'); + if (mounted) { + setState(() => _isListening = false); + _showVoiceSnack(l10n.voiceError); + } + } + } + + void _showVoiceLoadingSnack(AppLocalizations l10n) { + if (!mounted) return; + ScaffoldMessenger.of(context).hideCurrentSnackBar(); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + behavior: SnackBarBehavior.floating, + backgroundColor: AppColors.textPrimary.withOpacity(0.9), + margin: const EdgeInsets.fromLTRB(16, 0, 16, 90), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + duration: const Duration(seconds: 10), + content: Row( + children: [ + const SizedBox( + width: 16, + height: 16, + child: CircularProgressIndicator( + strokeWidth: 2, + valueColor: AlwaysStoppedAnimation(Colors.white), + ), + ), + const SizedBox(width: 12), + Expanded( + child: Text( + l10n.voiceModelLoading, + style: const TextStyle(color: Colors.white, fontSize: 13), + ), + ), + ], + ), + ), + ); + } + + void _showPermissionSettingsSnack(AppLocalizations l10n) { + if (!mounted) return; + ScaffoldMessenger.of(context).hideCurrentSnackBar(); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + behavior: SnackBarBehavior.floating, + backgroundColor: AppColors.textPrimary.withOpacity(0.92), + margin: const EdgeInsets.fromLTRB(16, 0, 16, 90), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + duration: const Duration(seconds: 4), + content: Row( + children: [ + const Icon(Icons.mic_off, color: Colors.white, size: 18), + const SizedBox(width: 8), + Expanded( + child: Text( + l10n.voicePermissionDenied, + style: const TextStyle(color: Colors.white, fontSize: 13), + ), + ), + ], + ), + action: SnackBarAction( + label: l10n.settings, + textColor: AppColors.primaryLight, + onPressed: () { + openAppSettings(); + }, + ), + ), + ); + } + + void _showVoiceSnack(String message) { + if (!mounted) return; + ScaffoldMessenger.of(context).hideCurrentSnackBar(); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + behavior: SnackBarBehavior.floating, + backgroundColor: AppColors.textPrimary.withOpacity(0.9), + margin: const EdgeInsets.fromLTRB(16, 0, 16, 90), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + duration: const Duration(seconds: 2), + content: Row( + children: [ + const Icon(Icons.mic_off, color: Colors.white, size: 18), + const SizedBox(width: 8), + Expanded( + child: Text( + message, + style: const TextStyle(color: Colors.white, fontSize: 13), + ), + ), + ], + ), + ), + ); + } + void _scrollToBottom() { if (_scrollController.hasClients) { _scrollController.animateTo( @@ -50,6 +343,7 @@ class _HomePageState extends ConsumerState { final text = _messageController.text.trim(); if (text.isEmpty) return; _messageController.clear(); + FocusScope.of(context).unfocus(); ref.read(chatProvider.notifier).sendMessage(text).then((_) { Future.delayed(const Duration(milliseconds: 100), _scrollToBottom); }); @@ -64,6 +358,268 @@ class _HomePageState extends ConsumerState { ); } + List<_NotificationItem> _buildNotificationItems(BuildContext context) { + return [ + _NotificationItem( + icon: Icons.assignment_outlined, + color: const Color(0xFFF59E0B), + title: '新工单', + subtitle: '待接单工单提醒', + count: 3, + onTap: () => context.push('/order-work'), + ), + _NotificationItem( + icon: Icons.receipt_long_outlined, + color: const Color(0xFF3B82F6), + title: '新订单', + subtitle: '待核销订单提醒', + count: 2, + onTap: () => context.push('/order-work?tab=order'), + ), + _NotificationItem( + icon: Icons.campaign_outlined, + color: const Color(0xFF8B5CF6), + title: '系统通知', + subtitle: '系统公告与活动消息', + count: 1, + onTap: () => context.push('/messages'), + ), + ]; + } + + Widget _buildNotificationButton(BuildContext context) { + final items = _buildNotificationItems(context); + final total = items.fold(0, (sum, e) => sum + e.count); + return Stack( + clipBehavior: Clip.none, + alignment: Alignment.center, + children: [ + IconButton( + onPressed: () => _showNotifications(context, items), + icon: Icon( + Icons.notifications_none_outlined, + size: 22, + color: AppColors.textSecondary, + ), + ), + if (total > 0) + Positioned( + right: 6, + top: 6, + child: IgnorePointer( + child: Stack( + clipBehavior: Clip.none, + alignment: Alignment.center, + children: [ + Positioned.fill( + child: Container( + decoration: BoxDecoration( + color: AppColors.error, + borderRadius: BorderRadius.circular(9), + ), + ) + .animate(onPlay: (c) => c.repeat()) + .scaleXY( + begin: 1.0, + end: 2.1, + duration: 1400.ms, + curve: Curves.easeOut, + ) + .fade( + begin: 0.55, + end: 0, + duration: 1400.ms, + curve: Curves.easeOut, + ), + ), + Container( + padding: const EdgeInsets.symmetric(horizontal: 4), + constraints: const BoxConstraints(minWidth: 17, minHeight: 17), + decoration: BoxDecoration( + color: AppColors.error, + borderRadius: BorderRadius.circular(9), + border: Border.all(color: AppColors.surface, width: 1.5), + boxShadow: [ + BoxShadow( + color: AppColors.error.withOpacity(0.35), + blurRadius: 4, + offset: const Offset(0, 1), + ), + ], + ), + child: Center( + child: Text( + total > 99 ? '99+' : '$total', + style: const TextStyle( + color: Colors.white, + fontSize: 10, + fontWeight: FontWeight.w700, + height: 1.1, + ), + ), + ), + ) + .animate(onPlay: (c) => c.repeat(reverse: true)) + .scaleXY( + begin: 1.0, + end: 1.12, + duration: 1400.ms, + curve: Curves.easeInOut, + ), + ], + ), + ), + ), + ], + ); + } + + void _showNotifications(BuildContext context, List<_NotificationItem> items) { + showModalBottomSheet( + context: context, + backgroundColor: Colors.transparent, + isScrollControlled: true, + builder: (ctx) { + return Container( + decoration: BoxDecoration( + color: AppColors.surface, + borderRadius: const BorderRadius.vertical(top: Radius.circular(24)), + ), + child: SafeArea( + top: false, + child: Padding( + padding: const EdgeInsets.fromLTRB(20, 12, 20, 16), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Center( + child: Container( + width: 40, + height: 4, + decoration: BoxDecoration( + color: AppColors.divider, + borderRadius: BorderRadius.circular(2), + ), + ), + ), + const SizedBox(height: 18), + Row( + children: [ + Text( + '消息中心', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: AppColors.textPrimary, + ), + ), + const SizedBox(width: 8), + Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), + decoration: BoxDecoration( + color: AppColors.primary.withOpacity(0.1), + borderRadius: BorderRadius.circular(10), + ), + child: Text( + '${items.fold(0, (s, e) => s + e.count)} 条未读', + style: TextStyle( + fontSize: 11, + fontWeight: FontWeight.w600, + color: AppColors.primary, + ), + ), + ), + ], + ), + const SizedBox(height: 16), + ...items.map( + (item) => Padding( + padding: const EdgeInsets.only(bottom: 10), + child: Material( + color: AppColors.background, + borderRadius: BorderRadius.circular(14), + child: InkWell( + borderRadius: BorderRadius.circular(14), + onTap: () { + Navigator.of(ctx).pop(); + item.onTap(); + }, + child: Padding( + padding: const EdgeInsets.all(14), + child: Row( + children: [ + Container( + width: 44, + height: 44, + decoration: BoxDecoration( + color: item.color.withOpacity(0.12), + borderRadius: BorderRadius.circular(12), + ), + child: Icon(item.icon, color: item.color, size: 22), + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + item.title, + style: TextStyle( + fontSize: 15, + fontWeight: FontWeight.w600, + color: AppColors.textPrimary, + ), + ), + const SizedBox(height: 2), + Text( + item.subtitle, + style: TextStyle( + fontSize: 12.5, + color: AppColors.textSecondary, + ), + ), + ], + ), + ), + if (item.count > 0) + Container( + margin: const EdgeInsets.only(left: 8), + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3), + decoration: BoxDecoration( + color: AppColors.error, + borderRadius: BorderRadius.circular(10), + ), + child: Text( + '${item.count}', + style: const TextStyle( + color: Colors.white, + fontSize: 11, + fontWeight: FontWeight.w700, + ), + ), + ), + Icon( + Icons.chevron_right, + color: AppColors.textTertiary, + size: 20, + ), + ], + ), + ), + ), + ), + ), + ), + ], + ), + ), + ), + ); + }, + ); + } + @override Widget build(BuildContext context) { final l10n = AppLocalizations.of(context)!; @@ -89,10 +645,10 @@ class _HomePageState extends ConsumerState { width: 36, height: 36, decoration: BoxDecoration( - gradient: AppGradients.primary, borderRadius: BorderRadius.circular(10), ), - child: const Icon(Icons.auto_awesome, color: Colors.white, size: 20), + clipBehavior: Clip.antiAlias, + child: Image.asset('assets/logo.png', fit: BoxFit.cover), ), const SizedBox(width: 10), Column( @@ -119,7 +675,7 @@ class _HomePageState extends ConsumerState { const SizedBox(width: 4), Text( l10n.online, - style: const TextStyle(fontSize: 11, color: AppColors.textSecondary), + style: TextStyle(fontSize: 11, color: AppColors.textSecondary), ), ], ), @@ -128,93 +684,18 @@ class _HomePageState extends ConsumerState { ], ), actions: [ - if (user != null) - PopupMenuButton( - offset: const Offset(0, 40), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), + if (user != null) ...[ + _buildNotificationButton(context), + IconButton( + onPressed: () => context.push('/settings'), + icon: Icon( + Icons.settings_outlined, + size: 22, + color: AppColors.textSecondary, ), - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: Row( - children: [ - CircleAvatar( - radius: 14, - backgroundColor: AppColors.primary.withOpacity(0.1), - child: Text( - user.name.substring(0, 1), - style: const TextStyle( - fontSize: 12, - fontWeight: FontWeight.bold, - color: AppColors.primary, - ), - ), - ), - const SizedBox(width: 6), - Container( - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3), - decoration: BoxDecoration( - color: user.isBoss - ? const Color(0xFFFFF7ED) - : const Color(0xFFEFF6FF), - borderRadius: BorderRadius.circular(20), - ), - child: Text( - user.isBoss ? l10n.boss : l10n.employee, - style: TextStyle( - fontSize: 11, - fontWeight: FontWeight.w500, - color: user.isBoss - ? const Color(0xFFEA580C) - : const Color(0xFF2563EB), - ), - ), - ), - const Icon(Icons.keyboard_arrow_down, size: 18), - ], - ), - ), - onSelected: (value) { - if (value == 'settings') { - context.push('/settings'); - } else if (value == 'logout') { - ref.read(authProvider.notifier).logout(); - } - }, - itemBuilder: (context) => [ - PopupMenuItem( - value: 'profile', - child: Row( - children: [ - const Icon(Icons.person_outline, size: 18), - const SizedBox(width: 10), - Text('${user.name} (${user.phone})'), - ], - ), - ), - const PopupMenuDivider(), - PopupMenuItem( - value: 'settings', - child: Row( - children: [ - const Icon(Icons.settings_outlined, size: 18), - const SizedBox(width: 10), - Text(l10n.settings), - ], - ), - ), - PopupMenuItem( - value: 'logout', - child: Row( - children: [ - const Icon(Icons.logout, size: 18, color: AppColors.error), - const SizedBox(width: 10), - Text(l10n.logout, style: const TextStyle(color: AppColors.error)), - ], - ), - ), - ], ), + const SizedBox(width: 4), + ], ], ), body: Column( @@ -230,8 +711,24 @@ class _HomePageState extends ConsumerState { }, ), ), - _buildQuickActions(l10n), - _buildInputArea(l10n), + Container( + decoration: BoxDecoration( + color: AppColors.surface, + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.04), + blurRadius: 12, + offset: const Offset(0, -4), + ), + ], + ), + child: Column( + children: [ + _buildQuickActions(l10n), + _buildInputArea(l10n), + ], + ), + ), ], ), ); @@ -308,10 +805,11 @@ class _HomePageState extends ConsumerState { height: 1.5, ), ) - : MarkdownBody( + : MarkdownMessage( data: message.content, + builders: {'code': ChartCodeBuilder()}, styleSheet: MarkdownStyleSheet( - p: const TextStyle( + p: TextStyle( fontSize: 15, color: AppColors.textPrimary, height: 1.6, @@ -322,10 +820,10 @@ class _HomePageState extends ConsumerState { backgroundColor: AppColors.primary.withOpacity(0.08), fontFamily: 'monospace', ), - codeblockDecoration: BoxDecoration( - color: AppColors.primary.withOpacity(0.05), - borderRadius: BorderRadius.circular(8), + codeblockDecoration: const BoxDecoration( + color: Colors.transparent, ), + codeblockPadding: EdgeInsets.zero, blockquote: TextStyle( fontSize: 14, color: AppColors.textSecondary, @@ -339,11 +837,11 @@ class _HomePageState extends ConsumerState { ), ), ), - tableHead: const TextStyle( + tableHead: TextStyle( fontWeight: FontWeight.w600, color: AppColors.textPrimary, ), - tableBody: const TextStyle( + tableBody: TextStyle( color: AppColors.textSecondary, ), tableBorder: TableBorder.all( @@ -351,17 +849,17 @@ class _HomePageState extends ConsumerState { width: 1, ), tableCellsPadding: const EdgeInsets.all(8), - h1: const TextStyle( + h1: TextStyle( fontSize: 20, fontWeight: FontWeight.bold, color: AppColors.textPrimary, ), - h2: const TextStyle( + h2: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: AppColors.textPrimary, ), - h3: const TextStyle( + h3: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: AppColors.textPrimary, @@ -388,46 +886,33 @@ class _HomePageState extends ConsumerState { } Widget _buildQuickActions(AppLocalizations l10n) { - return Container( - margin: const EdgeInsets.fromLTRB(16, 0, 16, 8), - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: AppColors.surface, - borderRadius: BorderRadius.circular(16), - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.06), - blurRadius: 16, - offset: const Offset(0, 4), - ), - ], - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + return Padding( + padding: const EdgeInsets.fromLTRB(8, 8, 8, 2), + child: Row( children: [ - Wrap( - spacing: 12, - runSpacing: 12, - children: [ - _ActionChip( - icon: Icons.event_note_outlined, - label: l10n.eventPublish, - color: const Color(0xFF8B5CF6), - onTap: () => context.push('/event/publish'), - ), - _ActionChip( - icon: Icons.assignment_outlined, - label: l10n.orderWork, - color: const Color(0xFFF59E0B), - onTap: () => context.push('/order-work'), - ), - _ActionChip( - icon: Icons.qr_code_scanner, - label: l10n.verify, - color: const Color(0xFF10B981), - onTap: () => _openScanner(), - ), - ], + Expanded( + child: _QuickAction( + icon: Icons.event_note_outlined, + label: l10n.eventPublish, + color: const Color(0xFF8B5CF6), + onTap: () => context.push('/event/publish'), + ), + ), + Expanded( + child: _QuickAction( + icon: Icons.assignment_outlined, + label: l10n.orderWork, + color: const Color(0xFFF59E0B), + onTap: () => context.push('/order-work'), + ), + ), + Expanded( + child: _QuickAction( + icon: Icons.qr_code_scanner, + label: l10n.verify, + color: const Color(0xFF10B981), + onTap: () => _openScanner(), + ), ), ], ), @@ -435,63 +920,73 @@ class _HomePageState extends ConsumerState { } Widget _buildInputArea(AppLocalizations l10n) { - return Container( - padding: const EdgeInsets.fromLTRB(16, 8, 16, 24), - decoration: BoxDecoration( - color: AppColors.surface, - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.04), - blurRadius: 12, - offset: const Offset(0, -4), - ), - ], - ), + final isAndroid = Theme.of(context).platform == TargetPlatform.android; + return Padding( + padding: EdgeInsets.fromLTRB(16, 2, 16, isAndroid ? 16 : 4), child: SafeArea( - child: Row( + top: false, + child: Column( + mainAxisSize: MainAxisSize.min, children: [ - Expanded( - child: TextField( - controller: _messageController, - maxLines: null, - textInputAction: TextInputAction.send, - onSubmitted: (_) => _sendMessage(), - decoration: InputDecoration( - hintText: l10n.enterMessage, - hintStyle: TextStyle( - fontSize: 14, - color: AppColors.textTertiary, + if (_isListening) _buildListeningHint(l10n), + Row( + children: [ + Expanded( + child: TextField( + controller: _messageController, + maxLines: null, + textInputAction: TextInputAction.send, + onSubmitted: (_) => _sendMessage(), + decoration: InputDecoration( + hintText: _isListening ? l10n.voiceListening : l10n.enterMessage, + hintStyle: TextStyle( + fontSize: 14, + color: _isListening + ? AppColors.error.withOpacity(0.85) + : AppColors.textTertiary, + ), + filled: true, + fillColor: AppColors.background, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(28), + borderSide: BorderSide.none, + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(28), + borderSide: BorderSide.none, + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(28), + borderSide: BorderSide.none, + ), + contentPadding: const EdgeInsets.fromLTRB(18, 10, 8, 10), + isDense: true, + suffixIcon: _buildMicButton(l10n), + suffixIconConstraints: const BoxConstraints( + minWidth: 36, + minHeight: 36, + ), + ), ), - filled: true, - fillColor: AppColors.background, - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(20), - borderSide: BorderSide.none, + ), + const SizedBox(width: 8), + GestureDetector( + onTap: _sendMessage, + child: Container( + width: 36, + height: 36, + decoration: BoxDecoration( + gradient: AppGradients.primary, + shape: BoxShape.circle, + ), + child: const Icon( + Icons.send, + color: Colors.white, + size: 17, + ), ), - contentPadding: const EdgeInsets.symmetric( - horizontal: 16, - vertical: 10, - ), - isDense: true, ), - ), - ), - const SizedBox(width: 10), - GestureDetector( - onTap: _sendMessage, - child: Container( - width: 40, - height: 40, - decoration: const BoxDecoration( - gradient: AppGradients.primary, - shape: BoxShape.circle, - ), - child: const Icon( - Icons.send, - color: Colors.white, - size: 18, - ), - ), + ], ), ], ), @@ -499,6 +994,80 @@ class _HomePageState extends ConsumerState { ); } + Widget _buildMicButton(AppLocalizations l10n) { + final listening = _isListening; + final tooltip = listening + ? l10n.voiceListening + : (_speechAvailable ? l10n.aiAssistant : l10n.voiceUnavailable); + final icon = Icon( + listening ? Icons.mic : Icons.mic_none, + color: listening ? AppColors.error : AppColors.textSecondary, + size: 22, + ); + final button = InkWell( + onTap: _toggleListening, + customBorder: const CircleBorder(), + child: Padding( + padding: const EdgeInsets.all(7), + child: listening + ? icon + .animate(onPlay: (c) => c.repeat(reverse: true)) + .scaleXY( + begin: 1.0, + end: 1.15, + duration: 700.ms, + curve: Curves.easeInOut, + ) + : icon, + ), + ); + return Tooltip(message: tooltip, child: button); + } + + Widget _buildListeningHint(AppLocalizations l10n) { + return Padding( + padding: const EdgeInsets.only(bottom: 8), + child: Row( + children: [ + Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), + decoration: BoxDecoration( + color: AppColors.error.withOpacity(0.1), + borderRadius: BorderRadius.circular(16), + border: Border.all(color: AppColors.error.withOpacity(0.25), width: 1), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 8, + height: 8, + decoration: const BoxDecoration( + color: AppColors.error, + shape: BoxShape.circle, + ), + ) + .animate(onPlay: (c) => c.repeat(reverse: true)) + .fadeIn(begin: 0.35, duration: 600.ms, curve: Curves.easeInOut) + .scaleXY(begin: 0.8, end: 1.1, duration: 600.ms), + const SizedBox(width: 8), + Text( + l10n.voiceListening, + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w500, + color: AppColors.error, + ), + ), + ], + ), + ), + const Spacer(), + ], + ), + ); + } + String _formatTime(DateTime time) { final now = DateTime.now(); if (time.year == now.year && time.month == now.month && time.day == now.day) { @@ -508,13 +1077,13 @@ class _HomePageState extends ConsumerState { } } -class _ActionChip extends StatelessWidget { +class _QuickAction extends StatelessWidget { final IconData icon; final String label; final Color color; final VoidCallback onTap; - const _ActionChip({ + const _QuickAction({ required this.icon, required this.label, required this.color, @@ -523,26 +1092,33 @@ class _ActionChip extends StatelessWidget { @override Widget build(BuildContext context) { - return GestureDetector( + return InkWell( onTap: onTap, - child: Container( - padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10), - decoration: BoxDecoration( - color: color.withOpacity(0.08), - borderRadius: BorderRadius.circular(12), - ), - child: Row( + borderRadius: BorderRadius.circular(14), + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 4), + child: Column( mainAxisSize: MainAxisSize.min, children: [ - Icon(icon, size: 18, color: color), - const SizedBox(width: 6), + Container( + width: 38, + height: 38, + decoration: BoxDecoration( + color: color.withOpacity(0.1), + borderRadius: BorderRadius.circular(12), + ), + child: Icon(icon, color: color, size: 20), + ), + const SizedBox(height: 4), Text( label, style: TextStyle( - fontSize: 13, + fontSize: 11.5, fontWeight: FontWeight.w500, - color: color, + color: AppColors.textSecondary, ), + maxLines: 1, + overflow: TextOverflow.ellipsis, ), ], ), @@ -551,6 +1127,24 @@ class _ActionChip extends StatelessWidget { } } +class _NotificationItem { + final IconData icon; + final Color color; + final String title; + final String subtitle; + final int count; + final VoidCallback onTap; + + const _NotificationItem({ + required this.icon, + required this.color, + required this.title, + required this.subtitle, + required this.count, + required this.onTap, + }); +} + class ScannerPage extends StatefulWidget { const ScannerPage({super.key}); diff --git a/lib/pages/login_page.dart b/lib/pages/login_page.dart index eee7d9b..40fca96 100644 --- a/lib/pages/login_page.dart +++ b/lib/pages/login_page.dart @@ -1,6 +1,9 @@ +import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_animate/flutter_animate.dart'; +import 'package:go_router/go_router.dart'; import '../providers/auth_provider.dart'; import '../theme.dart'; import '../l10n/app_localizations.dart'; @@ -12,30 +15,29 @@ class LoginPage extends ConsumerStatefulWidget { ConsumerState createState() => _LoginPageState(); } -class _LoginPageState extends ConsumerState - with SingleTickerProviderStateMixin { +class _LoginPageState extends ConsumerState { final _phoneController = TextEditingController(); final _passwordController = TextEditingController(); final _formKey = GlobalKey(); + final _agreementTap = TapGestureRecognizer(); + final _privacyTap = TapGestureRecognizer(); bool _obscurePassword = true; bool _isAgreed = true; - late AnimationController _animController; @override void initState() { super.initState(); - _animController = AnimationController( - vsync: this, - duration: const Duration(milliseconds: 1200), - ); - _animController.forward(); + _agreementTap.onTap = + () => context.push('/settings/policy?type=agreement'); + _privacyTap.onTap = () => context.push('/settings/policy?type=privacy'); } @override void dispose() { _phoneController.dispose(); _passwordController.dispose(); - _animController.dispose(); + _agreementTap.dispose(); + _privacyTap.dispose(); super.dispose(); } @@ -43,7 +45,10 @@ class _LoginPageState extends ConsumerState if (!_formKey.currentState!.validate()) return; if (!_isAgreed) { ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(AppLocalizations.of(context)!.pleaseComplete)), + SnackBar( + content: Text(AppLocalizations.of(context)!.pleaseComplete), + behavior: SnackBarBehavior.floating, + ), ); return; } @@ -65,297 +70,44 @@ class _LoginPageState extends ConsumerState content: Text(next.error!), backgroundColor: AppColors.error, behavior: SnackBarBehavior.floating, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), ), ); } }); return Scaffold( - body: Stack( - children: [ - Container( - decoration: const BoxDecoration( - gradient: LinearGradient( - colors: [Color(0xFF1A56DB), Color(0xFF00C9A7)], - begin: Alignment.topLeft, - end: Alignment.bottomRight, - ), - ), - ), - Positioned( - top: -100, - right: -100, - child: Container( - width: 300, - height: 300, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: Colors.white.withOpacity(0.05), - ), - ), - ), - Positioned( - bottom: -80, - left: -80, - child: Container( - width: 250, - height: 250, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: Colors.white.withOpacity(0.05), - ), - ), - ), - SafeArea( - child: SingleChildScrollView( - padding: const EdgeInsets.all(24), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const SizedBox(height: 60), - Center(child: _buildLogo(l10n)) - .animate(controller: _animController) - .fadeIn(duration: 600.ms, delay: 200.ms) - .slideY(begin: -0.3, end: 0, duration: 600.ms, delay: 200.ms), - const SizedBox(height: 48), - _buildLoginCard(authState, l10n) - .animate(controller: _animController) - .fadeIn(duration: 800.ms, delay: 400.ms) - .slideY(begin: 0.3, end: 0, duration: 800.ms, delay: 400.ms), - ], - ), - ), - ), - ], - ), - ); - } - - Widget _buildLogo(AppLocalizations l10n) { - return Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Container( - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: Colors.white.withOpacity(0.15), - borderRadius: BorderRadius.circular(20), - ), - child: const Icon( - Icons.auto_awesome, - size: 40, - color: Colors.white, - ), - ), - const SizedBox(height: 24), - Text( - l10n.appTitle, - style: const TextStyle( - fontSize: 36, - fontWeight: FontWeight.bold, - color: Colors.white, - letterSpacing: 2, - ), - ), - const SizedBox(height: 8), - Text( - l10n.appSubtitle, - style: TextStyle( - fontSize: 16, - color: Colors.white.withOpacity(0.8), - letterSpacing: 1, - ), - ), - ], - ); - } - - Widget _buildLoginCard(AuthState authState, AppLocalizations l10n) { - return Container( - padding: const EdgeInsets.all(28), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(24), - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.1), - blurRadius: 40, - offset: const Offset(0, 10), - ), - ], - ), - child: Form( - key: _formKey, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + resizeToAvoidBottomInset: false, + body: GestureDetector( + onTap: () => FocusScope.of(context).unfocus(), + behavior: HitTestBehavior.opaque, + child: Stack( children: [ - Text( - l10n.welcomeLogin, - style: const TextStyle( - fontSize: 24, - fontWeight: FontWeight.bold, - color: AppColors.textPrimary, - ), - ), - const SizedBox(height: 4), - Text( - l10n.enterAccountInfo, - style: TextStyle( - fontSize: 14, - color: AppColors.textSecondary, - ), - ), - const SizedBox(height: 28), - _buildTextField( - controller: _phoneController, - label: l10n.phoneNumber, - hint: l10n.enterPhone, - prefixIcon: Icons.phone_outlined, - keyboardType: TextInputType.phone, - validator: (v) { - if (v == null || v.isEmpty) return l10n.enterPhone; - if (v.length != 11) return '手机号格式不正确'; - return null; - }, - ), - const SizedBox(height: 16), - _buildTextField( - controller: _passwordController, - label: l10n.password, - hint: l10n.enterPassword, - prefixIcon: Icons.lock_outline, - obscureText: _obscurePassword, - suffixIcon: IconButton( - icon: Icon( - _obscurePassword ? Icons.visibility_off : Icons.visibility, - color: AppColors.textTertiary, - ), - onPressed: () { - setState(() { - _obscurePassword = !_obscurePassword; - }); - }, - ), - validator: (v) { - if (v == null || v.isEmpty) return l10n.enterPassword; - if (v.length < 6) return '密码不能少于6位'; - return null; - }, - ), - const SizedBox(height: 8), - Align( - alignment: Alignment.centerRight, - child: TextButton( - onPressed: () {}, - child: Text(l10n.forgotPassword), - ), - ), - const SizedBox(height: 8), - SizedBox( - width: double.infinity, - height: 52, - child: ElevatedButton( - onPressed: authState.isLoading ? null : _login, - style: ElevatedButton.styleFrom( - backgroundColor: AppColors.primary, - foregroundColor: Colors.white, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(14), - ), - elevation: 0, - ), - child: authState.isLoading - ? const SizedBox( - width: 24, - height: 24, - child: CircularProgressIndicator( - strokeWidth: 2.5, - valueColor: AlwaysStoppedAnimation(Colors.white), - ), - ) - : Text( - l10n.login, - style: const TextStyle( - fontSize: 17, - fontWeight: FontWeight.w600, - ), - ), - ), - ), - const SizedBox(height: 20), - Row( - children: [ - SizedBox( - width: 20, - height: 20, - child: Checkbox( - value: _isAgreed, - onChanged: (v) => setState(() => _isAgreed = v ?? true), - activeColor: AppColors.primary, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(4), - ), - ), - ), - const SizedBox(width: 8), - Expanded( - child: Text.rich( - TextSpan( - text: l10n.agreePrefix, - style: const TextStyle( - fontSize: 12, - color: AppColors.textSecondary, - ), - children: [ - TextSpan( - text: l10n.userAgreement, - style: const TextStyle( - color: AppColors.primary, - fontWeight: FontWeight.w500, - ), - ), - const TextSpan(text: '和'), - TextSpan( - text: l10n.privacyPolicy, - style: const TextStyle( - color: AppColors.primary, - fontWeight: FontWeight.w500, - ), - ), - ], - ), - ), - ), - ], - ), - const SizedBox(height: 16), Container( - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: AppColors.background, - borderRadius: BorderRadius.circular(12), + width: double.infinity, + height: double.infinity, + decoration: const BoxDecoration( + gradient: LinearGradient( + colors: [Color(0xFF1A56DB), Color(0xFF00C9A7)], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), ), - child: Row( - children: [ - Icon( - Icons.info_outline, - size: 16, - color: AppColors.primary.withOpacity(0.7), + ), + _buildFloatingBlobs(), + SafeArea( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 32), + child: Form( + key: _formKey, + child: Column( + children: [ + Expanded(flex: 4, child: _buildHeader(l10n)), + Expanded(flex: 6, child: _buildForm(authState, l10n)), + _buildAgreement(l10n), + const SizedBox(height: 12), + ], ), - const SizedBox(width: 8), - Expanded( - child: Text( - l10n.roleHint, - style: TextStyle( - fontSize: 12, - color: AppColors.textSecondary, - ), - ), - ), - ], + ), ), ), ], @@ -364,60 +116,422 @@ class _LoginPageState extends ConsumerState ); } - Widget _buildTextField({ - required TextEditingController controller, - required String label, - required String hint, - required IconData prefixIcon, - TextInputType? keyboardType, - bool obscureText = false, - Widget? suffixIcon, - String? Function(String?)? validator, - }) { + Widget _buildFloatingBlobs() { + return IgnorePointer( + child: Stack( + children: [ + Positioned( + top: -60, + left: -50, + child: _Blob(size: 220, color: Colors.white.withOpacity(0.10)) + .animate(onPlay: (c) => c.repeat(reverse: true)) + .moveY( + begin: 0, + end: 30, + duration: 4200.ms, + curve: Curves.easeInOut, + ) + .moveX( + begin: 0, + end: 18, + duration: 5200.ms, + curve: Curves.easeInOut, + ), + ), + Positioned( + top: 140, + right: -80, + child: _Blob(size: 180, color: Colors.white.withOpacity(0.08)) + .animate(onPlay: (c) => c.repeat(reverse: true)) + .moveY( + begin: 0, + end: -28, + duration: 4800.ms, + curve: Curves.easeInOut, + ) + .moveX( + begin: 0, + end: -22, + duration: 5800.ms, + curve: Curves.easeInOut, + ), + ), + Positioned( + bottom: 80, + left: -40, + child: _Blob(size: 200, color: Colors.white.withOpacity(0.07)) + .animate(onPlay: (c) => c.repeat(reverse: true)) + .moveY( + begin: 0, + end: 32, + duration: 5400.ms, + curve: Curves.easeInOut, + ) + .moveX( + begin: 0, + end: 24, + duration: 6200.ms, + curve: Curves.easeInOut, + ), + ), + Positioned( + bottom: -40, + right: -30, + child: _Blob(size: 160, color: Colors.white.withOpacity(0.06)) + .animate(onPlay: (c) => c.repeat(reverse: true)) + .moveY( + begin: 0, + end: -20, + duration: 5000.ms, + curve: Curves.easeInOut, + ), + ), + ], + ), + ); + } + + Widget _buildHeader(AppLocalizations l10n) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Stack( + alignment: Alignment.center, + children: [ + Container( + width: 140, + height: 140, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Colors.white.withOpacity(0.15), + ), + ) + .animate(onPlay: (c) => c.repeat(reverse: true)) + .scaleXY( + begin: 1.0, + end: 1.18, + duration: 2200.ms, + curve: Curves.easeInOut, + ) + .fade( + begin: 0.7, + end: 0.3, + duration: 2200.ms, + curve: Curves.easeInOut, + ), + Container( + width: 88, + height: 88, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(24), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.15), + blurRadius: 18, + offset: const Offset(0, 6), + ), + ], + ), + clipBehavior: Clip.antiAlias, + child: Image.asset('assets/logo.png', fit: BoxFit.cover), + ) + .animate(onPlay: (c) => c.repeat(reverse: true)) + .scaleXY( + begin: 1.0, + end: 1.04, + duration: 2400.ms, + curve: Curves.easeInOut, + ), + ], + ) + .animate() + .fadeIn(duration: 700.ms) + .scale( + begin: const Offset(0.85, 0.85), + end: const Offset(1, 1), + duration: 700.ms, + curve: Curves.easeOutBack, + ), + const SizedBox(height: 28), + Text( + l10n.appTitle, + style: const TextStyle( + fontSize: 32, + fontWeight: FontWeight.w700, + color: Colors.white, + letterSpacing: 4, + ), + ) + .animate(delay: 200.ms) + .fadeIn(duration: 600.ms) + .slideY(begin: 0.3, end: 0, duration: 600.ms, curve: Curves.easeOut), + const SizedBox(height: 10), + Text( + l10n.appSubtitle, + style: TextStyle( + fontSize: 14, + color: Colors.white.withOpacity(0.85), + letterSpacing: 2, + ), + ) + .animate(delay: 350.ms) + .fadeIn(duration: 600.ms) + .slideY(begin: 0.3, end: 0, duration: 600.ms, curve: Curves.easeOut), + ], + ), + ); + } + + Widget _buildForm(AuthState authState, AppLocalizations l10n) { return Column( - crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, children: [ - Text( - label, - style: const TextStyle( - fontSize: 14, - fontWeight: FontWeight.w500, - color: AppColors.textPrimary, + _input( + controller: _phoneController, + hint: l10n.enterPhone, + icon: Icons.phone_outlined, + keyboardType: TextInputType.number, + maxLength: 11, + inputFormatters: [FilteringTextInputFormatter.digitsOnly], + validator: (v) { + if (v == null || v.isEmpty) return l10n.enterPhone; + if (!RegExp(r'^1[3-9]\d{9}$').hasMatch(v)) return '手机号格式不正确'; + return null; + }, + ) + .animate(delay: 500.ms) + .fadeIn(duration: 500.ms) + .slideX(begin: -0.15, end: 0, duration: 500.ms, curve: Curves.easeOut), + const SizedBox(height: 16), + _input( + controller: _passwordController, + hint: l10n.enterPassword, + icon: Icons.lock_outline, + obscure: _obscurePassword, + suffix: IconButton( + icon: Icon( + _obscurePassword + ? Icons.visibility_off_outlined + : Icons.visibility_outlined, + color: Colors.white.withOpacity(0.7), + size: 20, + ), + onPressed: () => + setState(() => _obscurePassword = !_obscurePassword), ), - ), - const SizedBox(height: 8), - TextFormField( - controller: controller, - keyboardType: keyboardType, - obscureText: obscureText, - validator: validator, - decoration: InputDecoration( - hintText: hint, - prefixIcon: Icon(prefixIcon, color: AppColors.textTertiary, size: 20), - suffixIcon: suffixIcon, - filled: true, - fillColor: AppColors.background, - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), - borderSide: BorderSide.none, + validator: (v) { + if (v == null || v.isEmpty) return l10n.enterPassword; + if (v.length < 6) return '密码不能少于6位'; + return null; + }, + ) + .animate(delay: 620.ms) + .fadeIn(duration: 500.ms) + .slideX(begin: -0.15, end: 0, duration: 500.ms, curve: Curves.easeOut), + const SizedBox(height: 4), + Align( + alignment: Alignment.centerRight, + child: TextButton( + onPressed: () {}, + style: TextButton.styleFrom( + foregroundColor: Colors.white.withOpacity(0.9), + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + minimumSize: const Size(0, 32), + tapTargetSize: MaterialTapTargetSize.shrinkWrap, ), - enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), - borderSide: BorderSide.none, + child: Text( + l10n.forgotPassword, + style: const TextStyle(fontSize: 13), ), - focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), - borderSide: const BorderSide(color: AppColors.primary, width: 1.5), - ), - errorBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), - borderSide: const BorderSide(color: AppColors.error, width: 1), - ), - contentPadding: - const EdgeInsets.symmetric(horizontal: 16, vertical: 14), ), - ), + ) + .animate(delay: 720.ms) + .fadeIn(duration: 500.ms), + const SizedBox(height: 24), + SizedBox( + width: double.infinity, + height: 54, + child: ElevatedButton( + onPressed: authState.isLoading ? null : _login, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.white, + foregroundColor: AppColors.primary, + disabledBackgroundColor: Colors.white.withOpacity(0.6), + elevation: 0, + shadowColor: Colors.black.withOpacity(0.2), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + ), + child: authState.isLoading + ? const SizedBox( + width: 22, + height: 22, + child: CircularProgressIndicator( + strokeWidth: 2.5, + valueColor: AlwaysStoppedAnimation(AppColors.primary), + ), + ) + : Text( + l10n.login, + style: const TextStyle( + fontSize: 17, + fontWeight: FontWeight.w700, + letterSpacing: 4, + ), + ), + ), + ) + .animate(delay: 820.ms) + .fadeIn(duration: 500.ms) + .slideY(begin: 0.3, end: 0, duration: 500.ms, curve: Curves.easeOut) + .then(delay: 200.ms) + .shimmer( + duration: 2200.ms, + color: AppColors.primary.withOpacity(0.18), + ), ], ); } + + Widget _buildAgreement(AppLocalizations l10n) { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox( + width: 20, + height: 20, + child: Checkbox( + value: _isAgreed, + onChanged: (v) => setState(() => _isAgreed = v ?? true), + activeColor: Colors.white, + checkColor: AppColors.primary, + side: BorderSide(color: Colors.white.withOpacity(0.6), width: 1.5), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(4), + ), + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + ), + ), + const SizedBox(width: 8), + Flexible( + child: Text.rich( + TextSpan( + text: l10n.agreePrefix, + style: TextStyle( + fontSize: 12, + color: Colors.white.withOpacity(0.75), + ), + children: [ + TextSpan( + text: l10n.userAgreement, + style: const TextStyle( + color: Colors.white, + fontWeight: FontWeight.w500, + ), + recognizer: _agreementTap, + ), + const TextSpan(text: '和'), + TextSpan( + text: l10n.privacyPolicy, + style: const TextStyle( + color: Colors.white, + fontWeight: FontWeight.w500, + ), + recognizer: _privacyTap, + ), + ], + ), + ), + ), + ], + ) + .animate(delay: 950.ms) + .fadeIn(duration: 500.ms); + } + + Widget _input({ + required TextEditingController controller, + required String hint, + required IconData icon, + TextInputType? keyboardType, + bool obscure = false, + Widget? suffix, + String? Function(String?)? validator, + int? maxLength, + List? inputFormatters, + }) { + return TextFormField( + controller: controller, + keyboardType: keyboardType, + obscureText: obscure, + validator: validator, + maxLength: maxLength, + inputFormatters: inputFormatters, + style: const TextStyle(fontSize: 15, color: Colors.white), + cursorColor: Colors.white, + decoration: InputDecoration( + hintText: hint, + counterText: '', + hintStyle: TextStyle( + color: Colors.white.withOpacity(0.55), + fontSize: 14, + ), + prefixIcon: Icon( + icon, + color: Colors.white.withOpacity(0.75), + size: 20, + ), + suffixIcon: suffix, + filled: true, + fillColor: Colors.white.withOpacity(0.12), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(16), + borderSide: BorderSide.none, + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(16), + borderSide: BorderSide( + color: Colors.white.withOpacity(0.18), + width: 1, + ), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(16), + borderSide: const BorderSide(color: Colors.white, width: 1.5), + ), + errorBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(16), + borderSide: BorderSide(color: Colors.red.shade100, width: 1), + ), + focusedErrorBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(16), + borderSide: BorderSide(color: Colors.red.shade100, width: 1.5), + ), + errorStyle: TextStyle(color: Colors.red.shade100, fontSize: 12), + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 16, + ), + ), + ); + } +} + +class _Blob extends StatelessWidget { + final double size; + final Color color; + + const _Blob({required this.size, required this.color}); + + @override + Widget build(BuildContext context) { + return Container( + width: size, + height: size, + decoration: BoxDecoration(color: color, shape: BoxShape.circle), + ); + } } diff --git a/lib/pages/order_detail_page.dart b/lib/pages/order_detail_page.dart index 14888a4..8f6e702 100644 --- a/lib/pages/order_detail_page.dart +++ b/lib/pages/order_detail_page.dart @@ -4,6 +4,7 @@ import 'package:go_router/go_router.dart'; import '../providers/order_provider.dart'; import '../theme.dart'; import '../l10n/app_localizations.dart'; +import '../widgets/skeleton.dart'; class OrderDetailPage extends ConsumerStatefulWidget { final String orderId; @@ -29,8 +30,10 @@ class _OrderDetailPageState extends ConsumerState { final order = state.currentOrder; if (order == null) { - return const Scaffold( - body: Center(child: CircularProgressIndicator()), + return Scaffold( + backgroundColor: AppColors.background, + appBar: AppBar(title: Text(l10n.orderDetail)), + body: const DetailPageSkeleton(), ); } @@ -129,7 +132,7 @@ class _OrderDetailPageState extends ConsumerState { children: [ Text( l10n.productInfo, - style: const TextStyle( + style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: AppColors.textPrimary, @@ -154,7 +157,7 @@ class _OrderDetailPageState extends ConsumerState { children: [ Text( order.productName, - style: const TextStyle( + style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: AppColors.textPrimary, @@ -163,7 +166,7 @@ class _OrderDetailPageState extends ConsumerState { const SizedBox(height: 6), Text( '${l10n.quantity}: x${order.quantity}', - style: const TextStyle( + style: TextStyle( fontSize: 14, color: AppColors.textSecondary, ), @@ -171,7 +174,7 @@ class _OrderDetailPageState extends ConsumerState { const SizedBox(height: 4), Text( '${l10n.unitPrice}: ¥${(order.amount / order.quantity).toStringAsFixed(2)}', - style: const TextStyle( + style: TextStyle( fontSize: 13, color: AppColors.textTertiary, ), @@ -219,7 +222,7 @@ class _OrderDetailPageState extends ConsumerState { children: [ Text( l10n.paymentInfo, - style: const TextStyle( + style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: AppColors.textPrimary, @@ -277,7 +280,7 @@ class _OrderDetailPageState extends ConsumerState { children: [ Text( l10n.orderInfo, - style: const TextStyle( + style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: AppColors.textPrimary, @@ -319,12 +322,12 @@ class _OrderDetailPageState extends ConsumerState { children: [ Text( label, - style: const TextStyle(fontSize: 12, color: AppColors.textTertiary), + style: TextStyle(fontSize: 12, color: AppColors.textTertiary), ), const SizedBox(height: 2), Text( value, - style: const TextStyle( + style: TextStyle( fontSize: 15, fontWeight: FontWeight.w500, color: AppColors.textPrimary, diff --git a/lib/pages/order_work_page.dart b/lib/pages/order_work_page.dart index 90cc978..df3ba2a 100644 --- a/lib/pages/order_work_page.dart +++ b/lib/pages/order_work_page.dart @@ -8,6 +8,7 @@ import '../models/work_order.dart'; import '../models/order.dart'; import '../theme.dart'; import '../l10n/app_localizations.dart'; +import '../widgets/skeleton.dart'; class OrderWorkPage extends ConsumerStatefulWidget { final String initialTab; @@ -57,21 +58,50 @@ class _OrderWorkPageState extends ConsumerState backgroundColor: AppColors.background, appBar: AppBar( title: Text(l10n.ordersAndWorkOrders), - bottom: TabBar( - controller: _tabController, - tabs: [ - Tab(text: l10n.workOrder), - Tab(text: l10n.order), - ], - indicator: BoxDecoration( - borderRadius: BorderRadius.circular(8), - color: AppColors.primary.withOpacity(0.1), + backgroundColor: AppColors.surface, + elevation: 0, + scrolledUnderElevation: 0, + surfaceTintColor: Colors.transparent, + bottom: PreferredSize( + preferredSize: const Size.fromHeight(52), + child: Container( + color: AppColors.surface, + padding: const EdgeInsets.fromLTRB(16, 0, 16, 12), + child: Container( + height: 38, + decoration: BoxDecoration( + color: AppColors.background, + borderRadius: BorderRadius.circular(10), + ), + child: TabBar( + controller: _tabController, + tabs: [ + Tab(text: l10n.workOrder), + Tab(text: l10n.order), + ], + indicator: BoxDecoration( + borderRadius: BorderRadius.circular(8), + color: AppColors.surface, + ), + indicatorSize: TabBarIndicatorSize.tab, + indicatorPadding: const EdgeInsets.all(3), + labelColor: AppColors.textPrimary, + unselectedLabelColor: AppColors.textSecondary, + labelStyle: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + ), + unselectedLabelStyle: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, + ), + dividerColor: Colors.transparent, + dividerHeight: 0, + splashFactory: NoSplash.splashFactory, + overlayColor: WidgetStateProperty.all(Colors.transparent), + ), + ), ), - indicatorSize: TabBarIndicatorSize.tab, - indicatorPadding: const EdgeInsets.symmetric(horizontal: 32, vertical: 6), - labelColor: AppColors.primary, - unselectedLabelColor: AppColors.textSecondary, - labelStyle: const TextStyle(fontWeight: FontWeight.w600), ), ), body: TabBarView( @@ -113,7 +143,10 @@ class _WorkOrderTab extends ConsumerWidget { _buildWorkFilterChips(context, l10n), Expanded( child: state.isLoading - ? const Center(child: CircularProgressIndicator()) + ? SkeletonList( + count: 5, + itemBuilder: (_) => const WorkOrderCardSkeleton(), + ) : state.orders.isEmpty ? _buildEmptyState(l10n.noWorkOrders) : ListView.builder( @@ -198,7 +231,7 @@ class _WorkOrderTab extends ConsumerWidget { Expanded( child: Text( order.title, - style: const TextStyle( + style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: AppColors.textPrimary, @@ -227,7 +260,7 @@ class _WorkOrderTab extends ConsumerWidget { order.description, maxLines: 2, overflow: TextOverflow.ellipsis, - style: const TextStyle( + style: TextStyle( fontSize: 14, color: AppColors.textSecondary, height: 1.4, @@ -240,15 +273,15 @@ class _WorkOrderTab extends ConsumerWidget { const SizedBox(width: 4), Text( '${l10n.creator}: ${order.creatorName}', - style: const TextStyle(fontSize: 12, color: AppColors.textTertiary), + style: TextStyle(fontSize: 12, color: AppColors.textTertiary), ), if (order.assigneeName != null) ...[ const SizedBox(width: 12), Icon(Icons.assignment_ind_outlined, size: 14, color: AppColors.textTertiary), const SizedBox(width: 4), Text( - '${l10n.assignee}: ${order.assigneeName}', - style: const TextStyle(fontSize: 12, color: AppColors.textTertiary), + '${order.status == WorkOrderStatus.pending ? l10n.transferDept : l10n.assignee}: ${order.assigneeName}', + style: TextStyle(fontSize: 12, color: AppColors.textTertiary), ), ], const Spacer(), @@ -305,7 +338,7 @@ class _WorkOrderTab extends ConsumerWidget { children: [ Icon(Icons.inbox_outlined, size: 64, color: AppColors.textTertiary.withOpacity(0.5)), const SizedBox(height: 16), - Text(text, style: const TextStyle(fontSize: 16, color: AppColors.textSecondary)), + Text(text, style: TextStyle(fontSize: 16, color: AppColors.textSecondary)), ], ), ); @@ -328,7 +361,10 @@ class _OrderTab extends ConsumerWidget { _buildOrderFilterChips(context, l10n), Expanded( child: state.isLoading - ? const Center(child: CircularProgressIndicator()) + ? SkeletonList( + count: 5, + itemBuilder: (_) => const OrderCardSkeleton(), + ) : state.orders.isEmpty ? _buildEmptyState(l10n.noOrders) : ListView.builder( @@ -415,7 +451,7 @@ class _OrderTab extends ConsumerWidget { Expanded( child: Text( order.orderNo, - style: const TextStyle( + style: TextStyle( fontSize: 14, fontWeight: FontWeight.w600, color: AppColors.textPrimary, @@ -442,7 +478,7 @@ class _OrderTab extends ConsumerWidget { const SizedBox(height: 12), Text( order.productName, - style: const TextStyle( + style: TextStyle( fontSize: 15, fontWeight: FontWeight.w500, color: AppColors.textPrimary, @@ -455,14 +491,14 @@ class _OrderTab extends ConsumerWidget { const SizedBox(width: 4), Text( order.customerName, - style: const TextStyle(fontSize: 13, color: AppColors.textSecondary), + style: TextStyle(fontSize: 13, color: AppColors.textSecondary), ), const SizedBox(width: 16), Icon(Icons.confirmation_number_outlined, size: 14, color: AppColors.textTertiary), const SizedBox(width: 4), Text( 'x${order.quantity}', - style: const TextStyle(fontSize: 13, color: AppColors.textSecondary), + style: TextStyle(fontSize: 13, color: AppColors.textSecondary), ), ], ), @@ -487,7 +523,7 @@ class _OrderTab extends ConsumerWidget { ), child: Text( '${l10n.verifyCode}: ${order.verifyCode}', - style: const TextStyle( + style: TextStyle( fontSize: 12, fontFamily: 'monospace', color: AppColors.textSecondary, @@ -509,7 +545,7 @@ class _OrderTab extends ConsumerWidget { children: [ Icon(Icons.receipt_long_outlined, size: 64, color: AppColors.textTertiary.withOpacity(0.5)), const SizedBox(height: 16), - Text(text, style: const TextStyle(fontSize: 16, color: AppColors.textSecondary)), + Text(text, style: TextStyle(fontSize: 16, color: AppColors.textSecondary)), ], ), ); diff --git a/lib/pages/policy_page.dart b/lib/pages/policy_page.dart index 030170a..8e6db93 100644 --- a/lib/pages/policy_page.dart +++ b/lib/pages/policy_page.dart @@ -29,7 +29,7 @@ class PolicyPage extends StatelessWidget { children: [ Text( isAgreement ? l10n.agreementTitle : l10n.privacyTitle, - style: const TextStyle( + style: TextStyle( fontSize: 20, fontWeight: FontWeight.bold, color: AppColors.textPrimary, @@ -38,7 +38,7 @@ class PolicyPage extends StatelessWidget { const SizedBox(height: 8), Text( l10n.lastUpdated, - style: const TextStyle(fontSize: 12, color: AppColors.textTertiary), + style: TextStyle(fontSize: 12, color: AppColors.textTertiary), ), const SizedBox(height: 24), if (isAgreement) ..._buildAgreementContent(context, l10n) else ..._buildPrivacyContent(context, l10n), @@ -92,7 +92,7 @@ class PolicyPage extends StatelessWidget { padding: const EdgeInsets.only(top: 20, bottom: 8), child: Text( title, - style: const TextStyle( + style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: AppColors.textPrimary, @@ -104,7 +104,7 @@ class PolicyPage extends StatelessWidget { Widget _paragraph(String text) { return Text( text, - style: const TextStyle( + style: TextStyle( fontSize: 14, color: AppColors.textSecondary, height: 1.7, diff --git a/lib/pages/report_page.dart b/lib/pages/report_page.dart index 9523031..d515f52 100644 --- a/lib/pages/report_page.dart +++ b/lib/pages/report_page.dart @@ -73,7 +73,7 @@ class ReportPage extends StatelessWidget { children: [ Text( item.$1, - style: const TextStyle(fontSize: 13, color: AppColors.textSecondary), + style: TextStyle(fontSize: 13, color: AppColors.textSecondary), ), const SizedBox(height: 8), Text( @@ -108,7 +108,7 @@ class ReportPage extends StatelessWidget { children: [ Text( title, - style: const TextStyle( + style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: AppColors.textPrimary, @@ -128,7 +128,7 @@ class ReportPage extends StatelessWidget { children: [ SizedBox( width: 40, - child: Text(label, style: const TextStyle(fontSize: 12, color: AppColors.textSecondary)), + child: Text(label, style: TextStyle(fontSize: 12, color: AppColors.textSecondary)), ), Expanded( child: Container( @@ -150,7 +150,7 @@ class ReportPage extends StatelessWidget { ), ), const SizedBox(width: 10), - Text('${(value * 100).toInt()}%', style: const TextStyle(fontSize: 12, color: AppColors.textSecondary)), + Text('${(value * 100).toInt()}%', style: TextStyle(fontSize: 12, color: AppColors.textSecondary)), ], ), ); @@ -168,9 +168,9 @@ class ReportPage extends StatelessWidget { ), const SizedBox(width: 10), Expanded( - child: Text(label, style: const TextStyle(fontSize: 14, color: AppColors.textPrimary)), + child: Text(label, style: TextStyle(fontSize: 14, color: AppColors.textPrimary)), ), - Text('${(value * 100).toInt()}%', style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600, color: AppColors.textPrimary)), + Text('${(value * 100).toInt()}%', style: TextStyle(fontSize: 14, fontWeight: FontWeight.w600, color: AppColors.textPrimary)), ], ), ); @@ -195,7 +195,7 @@ class ReportPage extends StatelessWidget { children: [ Text( l10n.topSales, - style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: AppColors.textPrimary), + style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: AppColors.textPrimary), ), const SizedBox(height: 16), ...products.asMap().entries.map((entry) { @@ -227,7 +227,7 @@ class ReportPage extends StatelessWidget { Expanded( child: Text( product.$1, - style: const TextStyle(fontSize: 14, color: AppColors.textPrimary), + style: TextStyle(fontSize: 14, color: AppColors.textPrimary), ), ), Text( @@ -237,7 +237,7 @@ class ReportPage extends StatelessWidget { const SizedBox(width: 12), Text( product.$3, - style: const TextStyle(fontSize: 12, color: AppColors.textTertiary), + style: TextStyle(fontSize: 12, color: AppColors.textTertiary), ), ], ), diff --git a/lib/pages/scan_result_page.dart b/lib/pages/scan_result_page.dart index 924bb1d..aa5d413 100644 --- a/lib/pages/scan_result_page.dart +++ b/lib/pages/scan_result_page.dart @@ -71,7 +71,7 @@ class _ScanResultPageState extends ConsumerState { const SizedBox(height: 20), Text( l10n.confirmVerify, - style: const TextStyle( + style: TextStyle( fontSize: 20, fontWeight: FontWeight.bold, color: AppColors.textPrimary, @@ -80,12 +80,12 @@ class _ScanResultPageState extends ConsumerState { const SizedBox(height: 8), Text( '${l10n.orderNo}: ${order.orderNo}', - style: const TextStyle(fontSize: 14, color: AppColors.textSecondary), + style: TextStyle(fontSize: 14, color: AppColors.textSecondary), ), const SizedBox(height: 4), Text( '${l10n.customerName}: ${order.customerName}', - style: const TextStyle(fontSize: 14, color: AppColors.textSecondary), + style: TextStyle(fontSize: 14, color: AppColors.textSecondary), ), const SizedBox(height: 4), Text( @@ -107,7 +107,7 @@ class _ScanResultPageState extends ConsumerState { shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), - side: const BorderSide(color: AppColors.divider), + side: BorderSide(color: AppColors.divider), ), child: Text(l10n.cancel), ), @@ -187,7 +187,7 @@ class _ScanResultPageState extends ConsumerState { children: [ const CircularProgressIndicator(), const SizedBox(height: 16), - Text(l10n.scanning, style: const TextStyle(color: AppColors.textSecondary)), + Text(l10n.scanning, style: TextStyle(color: AppColors.textSecondary)), ], ), ), @@ -295,7 +295,7 @@ class _ScanResultPageState extends ConsumerState { children: [ Text( l10n.orderInfo, - style: const TextStyle( + style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: AppColors.textPrimary, @@ -328,7 +328,7 @@ class _ScanResultPageState extends ConsumerState { children: [ Text( l10n.customerInfo, - style: const TextStyle( + style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: AppColors.textPrimary, @@ -354,12 +354,12 @@ class _ScanResultPageState extends ConsumerState { children: [ Text( label, - style: const TextStyle(fontSize: 12, color: AppColors.textTertiary), + style: TextStyle(fontSize: 12, color: AppColors.textTertiary), ), const SizedBox(height: 2), Text( value, - style: const TextStyle( + style: TextStyle( fontSize: 15, fontWeight: FontWeight.w500, color: AppColors.textPrimary, diff --git a/lib/pages/settings_page.dart b/lib/pages/settings_page.dart index 9122f1b..7fbc0a7 100644 --- a/lib/pages/settings_page.dart +++ b/lib/pages/settings_page.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import '../providers/auth_provider.dart'; @@ -6,10 +7,40 @@ import '../providers/settings_provider.dart'; import '../theme.dart'; import '../l10n/app_localizations.dart'; -class SettingsPage extends ConsumerWidget { +class SettingsPage extends ConsumerStatefulWidget { const SettingsPage({super.key}); - void _showThemePicker(BuildContext context, WidgetRef ref) { + @override + ConsumerState createState() => _SettingsPageState(); +} + +class _SettingsPageState extends ConsumerState { + final ScrollController _scrollController = ScrollController(); + bool _scrolled = false; + + static const double _scrollThreshold = 120; + + @override + void initState() { + super.initState(); + _scrollController.addListener(_onScroll); + } + + @override + void dispose() { + _scrollController.removeListener(_onScroll); + _scrollController.dispose(); + super.dispose(); + } + + void _onScroll() { + final shouldBe = _scrollController.offset > _scrollThreshold; + if (shouldBe != _scrolled) { + setState(() => _scrolled = shouldBe); + } + } + + void _showThemePicker(BuildContext context) { final current = ref.read(settingsProvider).themeMode; showModalBottomSheet( context: context, @@ -18,9 +49,9 @@ class SettingsPage extends ConsumerWidget { final l10n = AppLocalizations.of(ctx)!; return Container( padding: const EdgeInsets.all(20), - decoration: const BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.vertical(top: Radius.circular(24)), + decoration: BoxDecoration( + color: AppColors.surface, + borderRadius: const BorderRadius.vertical(top: Radius.circular(24)), ), child: SafeArea( child: Column( @@ -28,11 +59,11 @@ class SettingsPage extends ConsumerWidget { children: [ Container(width: 40, height: 4, decoration: BoxDecoration(color: AppColors.divider, borderRadius: BorderRadius.circular(2))), const SizedBox(height: 20), - Text(l10n.themeSettings, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: AppColors.textPrimary)), + Text(l10n.themeSettings, style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: AppColors.textPrimary)), const SizedBox(height: 20), - _themeOption(ctx, ref, AppThemeMode.light, l10n.lightMode, Icons.wb_sunny_outlined, current), - _themeOption(ctx, ref, AppThemeMode.dark, l10n.darkMode, Icons.nights_stay_outlined, current), - _themeOption(ctx, ref, AppThemeMode.system, l10n.followSystem, Icons.settings_suggest_outlined, current), + _themeOption(ctx, AppThemeMode.light, l10n.lightMode, Icons.wb_sunny_outlined, current), + _themeOption(ctx, AppThemeMode.dark, l10n.darkMode, Icons.nights_stay_outlined, current), + _themeOption(ctx, AppThemeMode.system, l10n.followSystem, Icons.settings_suggest_outlined, current), ], ), ), @@ -41,7 +72,7 @@ class SettingsPage extends ConsumerWidget { ); } - Widget _themeOption(BuildContext ctx, WidgetRef ref, AppThemeMode mode, String label, IconData icon, AppThemeMode current) { + Widget _themeOption(BuildContext ctx, AppThemeMode mode, String label, IconData icon, AppThemeMode current) { final selected = current == mode; return ListTile( leading: Icon(icon, color: selected ? AppColors.primary : AppColors.textTertiary), @@ -55,7 +86,7 @@ class SettingsPage extends ConsumerWidget { ); } - void _showLanguagePicker(BuildContext context, WidgetRef ref) { + void _showLanguagePicker(BuildContext context) { final current = ref.read(settingsProvider).locale; showModalBottomSheet( context: context, @@ -64,9 +95,9 @@ class SettingsPage extends ConsumerWidget { final l10n = AppLocalizations.of(ctx)!; return Container( padding: const EdgeInsets.all(20), - decoration: const BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.vertical(top: Radius.circular(24)), + decoration: BoxDecoration( + color: AppColors.surface, + borderRadius: const BorderRadius.vertical(top: Radius.circular(24)), ), child: SafeArea( child: Column( @@ -74,11 +105,11 @@ class SettingsPage extends ConsumerWidget { children: [ Container(width: 40, height: 4, decoration: BoxDecoration(color: AppColors.divider, borderRadius: BorderRadius.circular(2))), const SizedBox(height: 20), - Text(l10n.languageSettings, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: AppColors.textPrimary)), + Text(l10n.languageSettings, style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: AppColors.textPrimary)), const SizedBox(height: 20), - _languageOption(ctx, ref, const Locale('zh', 'CN'), l10n.simplifiedChinese, current), - _languageOption(ctx, ref, const Locale('en', 'US'), l10n.english, current), - _languageOption(ctx, ref, const Locale('th', 'TH'), l10n.thai, current), + _languageOption(ctx, const Locale('zh', 'CN'), l10n.simplifiedChinese, current), + _languageOption(ctx, const Locale('en', 'US'), l10n.english, current), + _languageOption(ctx, const Locale('th', 'TH'), l10n.thai, current), ], ), ), @@ -87,7 +118,7 @@ class SettingsPage extends ConsumerWidget { ); } - Widget _languageOption(BuildContext ctx, WidgetRef ref, Locale locale, String label, Locale current) { + Widget _languageOption(BuildContext ctx, Locale locale, String label, Locale current) { final selected = current.languageCode == locale.languageCode; return ListTile( title: Text(label, style: TextStyle(fontWeight: selected ? FontWeight.w600 : FontWeight.w400, color: AppColors.textPrimary)), @@ -101,7 +132,7 @@ class SettingsPage extends ConsumerWidget { } @override - Widget build(BuildContext context, WidgetRef ref) { + Widget build(BuildContext context) { final l10n = AppLocalizations.of(context)!; final authState = ref.watch(authProvider); final settings = ref.watch(settingsProvider); @@ -121,222 +152,274 @@ class SettingsPage extends ConsumerWidget { _ => l10n.simplifiedChinese, }; - return Scaffold( - backgroundColor: AppColors.background, - appBar: AppBar(title: Text(l10n.settings)), - body: ListView( - padding: const EdgeInsets.all(20), - children: [ - _buildProfileCard(user, l10n), - const SizedBox(height: 24), - _buildSectionTitle(l10n.appConfiguration), - const SizedBox(height: 12), - _buildSettingsCard([ - _SettingItem( - icon: Icons.notifications_outlined, - iconColor: const Color(0xFF8B5CF6), - title: l10n.pushNotification, - subtitle: settings.pushNotification ? l10n.enabled : l10n.disabled, - trailing: Switch( - value: settings.pushNotification, - onChanged: (v) => ref.read(settingsProvider.notifier).setPushNotification(v), - activeTrackColor: AppColors.primary, - ), - onTap: () => ref.read(settingsProvider.notifier).setPushNotification(!settings.pushNotification), - ), - _SettingItem( - icon: Icons.volume_up_outlined, - iconColor: const Color(0xFFEC4899), - title: l10n.soundNotification, - subtitle: settings.soundNotification ? l10n.enabled : l10n.disabled, - trailing: Switch( - value: settings.soundNotification, - onChanged: (v) => ref.read(settingsProvider.notifier).setSoundNotification(v), - activeTrackColor: AppColors.primary, - ), - onTap: () => ref.read(settingsProvider.notifier).setSoundNotification(!settings.soundNotification), - ), - _SettingItem( - icon: Icons.palette_outlined, - iconColor: const Color(0xFFEC4899), - title: l10n.themeSettings, - subtitle: themeLabel, - onTap: () => _showThemePicker(context, ref), - ), - _SettingItem( - icon: Icons.language_outlined, - iconColor: const Color(0xFF3B82F6), - title: l10n.languageSettings, - subtitle: localeLabel, - onTap: () => _showLanguagePicker(context, ref), - ), - ]), - const SizedBox(height: 24), - _buildSectionTitle(l10n.businessExtension), - const SizedBox(height: 12), - _buildSettingsCard([ - _SettingItem( - icon: Icons.store_outlined, - iconColor: const Color(0xFFF59E0B), - title: l10n.storeManagement, - subtitle: l10n.storeDesc, - onTap: () => context.push('/settings/store'), - ), - _SettingItem( - icon: Icons.people_outline, - iconColor: const Color(0xFF10B981), - title: l10n.employeeManagement, - subtitle: l10n.employeeDesc, - showBadge: isBoss, - onTap: () => context.push('/settings/employees'), - ), - _SettingItem( - icon: Icons.analytics_outlined, - iconColor: const Color(0xFFEF4444), - title: l10n.dataReport, - subtitle: l10n.reportDesc, - showBadge: isBoss, - onTap: () => context.push('/settings/report'), - ), - _SettingItem( - icon: Icons.extension_outlined, - iconColor: const Color(0xFF6366F1), - title: l10n.appMarket, - subtitle: l10n.marketDesc, - onTap: () => context.push('/settings/app-market'), - ), - ]), - const SizedBox(height: 24), - _buildSectionTitle(l10n.system), - const SizedBox(height: 12), - _buildSettingsCard([ - _SettingItem( - icon: Icons.help_outline, - iconColor: const Color(0xFF64748B), - title: l10n.helpCenter, - subtitle: l10n.helpDesc, - onTap: () => context.push('/settings/help'), - ), - _SettingItem( - icon: Icons.info_outline, - iconColor: const Color(0xFF64748B), - title: l10n.aboutUs, - subtitle: '${l10n.version} 1.0.0', - onTap: () => context.push('/settings/about'), - ), - ]), - const SizedBox(height: 32), - SizedBox( - width: double.infinity, - height: 52, - child: ElevatedButton( - onPressed: () { - ref.read(authProvider.notifier).logout(); - context.go('/login'); - }, - style: ElevatedButton.styleFrom( - backgroundColor: AppColors.error.withValues(alpha: 0.1), - foregroundColor: AppColors.error, - elevation: 0, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(14), - ), - ), - child: Text( - l10n.logout, - style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600), - ), - ), - ), - const SizedBox(height: 32), - ], - ), - ); - } - - Widget _buildProfileCard(user, AppLocalizations l10n) { - return Container( - padding: const EdgeInsets.all(24), - decoration: BoxDecoration( - gradient: AppGradients.primary, - borderRadius: BorderRadius.circular(20), - ), - child: Row( - children: [ - Container( - width: 64, - height: 64, - decoration: BoxDecoration( - color: Colors.white.withValues(alpha: 0.2), - shape: BoxShape.circle, - ), - child: Center( - child: Text( - user?.name.substring(0, 1) ?? l10n.user, - style: const TextStyle( - fontSize: 24, - fontWeight: FontWeight.bold, - color: Colors.white, - ), - ), - ), - ), - const SizedBox(width: 16), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + return AnnotatedRegion( + value: _scrolled + ? (AppColors.isDark ? SystemUiOverlayStyle.light : SystemUiOverlayStyle.dark) + : SystemUiOverlayStyle.light, + child: Scaffold( + backgroundColor: AppColors.background, + body: Stack( + children: [ + ListView( + controller: _scrollController, + padding: EdgeInsets.zero, children: [ - Text( - user?.name ?? l10n.user, - style: const TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold, - color: Colors.white, - ), - ), - const SizedBox(height: 4), - Text( - user?.phone ?? '', - style: TextStyle( - fontSize: 14, - color: Colors.white.withValues(alpha: 0.8), - ), - ), - const SizedBox(height: 8), - Container( - padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), - decoration: BoxDecoration( - color: Colors.white.withValues(alpha: 0.2), - borderRadius: BorderRadius.circular(20), - ), - child: Text( - user?.isBoss == true ? l10n.boss : l10n.employee, - style: const TextStyle( - fontSize: 12, - fontWeight: FontWeight.w500, - color: Colors.white, - ), + _buildHeader(context, user, l10n, isBoss), + Padding( + padding: const EdgeInsets.fromLTRB(20, 28, 20, 32), + child: Column( + children: [ + Align( + alignment: Alignment.centerLeft, + child: _buildSectionTitle(l10n.appConfiguration), + ), + const SizedBox(height: 14), + _buildSettingsCard([ + _SettingItem( + icon: Icons.notifications_outlined, + iconColor: const Color(0xFF8B5CF6), + title: l10n.pushNotification, + subtitle: settings.pushNotification ? l10n.enabled : l10n.disabled, + trailing: Switch( + value: settings.pushNotification, + onChanged: (v) => ref.read(settingsProvider.notifier).setPushNotification(v), + activeTrackColor: AppColors.primary, + ), + onTap: () => ref.read(settingsProvider.notifier).setPushNotification(!settings.pushNotification), + ), + _SettingItem( + icon: Icons.volume_up_outlined, + iconColor: const Color(0xFFEC4899), + title: l10n.soundNotification, + subtitle: settings.soundNotification ? l10n.enabled : l10n.disabled, + trailing: Switch( + value: settings.soundNotification, + onChanged: (v) => ref.read(settingsProvider.notifier).setSoundNotification(v), + activeTrackColor: AppColors.primary, + ), + onTap: () => ref.read(settingsProvider.notifier).setSoundNotification(!settings.soundNotification), + ), + _SettingItem( + icon: Icons.palette_outlined, + iconColor: const Color(0xFFEC4899), + title: l10n.themeSettings, + subtitle: themeLabel, + onTap: () => _showThemePicker(context), + ), + _SettingItem( + icon: Icons.language_outlined, + iconColor: const Color(0xFF3B82F6), + title: l10n.languageSettings, + subtitle: localeLabel, + onTap: () => _showLanguagePicker(context), + ), + ]), + const SizedBox(height: 28), + Align( + alignment: Alignment.centerLeft, + child: _buildSectionTitle(l10n.businessExtension), + ), + const SizedBox(height: 14), + _buildSettingsCard([ + _SettingItem( + icon: Icons.people_outline, + iconColor: const Color(0xFF10B981), + title: l10n.employeeManagement, + subtitle: l10n.employeeDesc, + showBadge: isBoss, + onTap: () => context.push('/settings/employees'), + ), + _SettingItem( + icon: Icons.analytics_outlined, + iconColor: const Color(0xFFEF4444), + title: l10n.dataReport, + subtitle: l10n.reportDesc, + showBadge: isBoss, + onTap: () => context.push('/settings/report'), + ), + _SettingItem( + icon: Icons.extension_outlined, + iconColor: const Color(0xFF6366F1), + title: l10n.appMarket, + subtitle: l10n.marketDesc, + onTap: () => context.push('/settings/app-market'), + ), + ]), + const SizedBox(height: 28), + Align( + alignment: Alignment.centerLeft, + child: _buildSectionTitle(l10n.system), + ), + const SizedBox(height: 14), + _buildSettingsCard([ + _SettingItem( + icon: Icons.help_outline, + iconColor: const Color(0xFF64748B), + title: l10n.helpCenter, + subtitle: l10n.helpDesc, + onTap: () => context.push('/settings/help'), + ), + _SettingItem( + icon: Icons.info_outline, + iconColor: const Color(0xFF64748B), + title: l10n.aboutUs, + subtitle: '${l10n.version} 1.0.0', + onTap: () => context.push('/settings/about'), + ), + ]), + const SizedBox(height: 36), + SizedBox( + width: double.infinity, + height: 52, + child: ElevatedButton( + onPressed: () { + ref.read(authProvider.notifier).logout(); + context.go('/login'); + }, + style: ElevatedButton.styleFrom( + backgroundColor: AppColors.error.withValues(alpha: 0.1), + foregroundColor: AppColors.error, + elevation: 0, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(14), + ), + ), + child: Text( + l10n.logout, + style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600), + ), + ), + ), + ], ), ), ], ), + Positioned( + top: 0, + left: 0, + right: 0, + child: SafeArea( + bottom: false, + child: SizedBox( + height: 44, + child: Align( + alignment: Alignment.centerLeft, + child: IconButton( + icon: Icon( + Icons.arrow_back_ios_new, + color: _scrolled ? AppColors.textPrimary : Colors.white, + size: 20, + ), + onPressed: () { + if (Navigator.of(context).canPop()) { + Navigator.of(context).pop(); + } else { + context.go('/home'); + } + }, + ), + ), + ), + ), + ), + ], + ), + ), + ); + } + + Widget _buildHeader(BuildContext context, user, AppLocalizations l10n, bool isBoss) { + final initial = (user?.name is String && (user!.name as String).isNotEmpty) + ? (user.name as String).substring(0, 1) + : l10n.user.substring(0, 1); + return Container( + decoration: BoxDecoration( + gradient: AppGradients.primary, + borderRadius: const BorderRadius.vertical(bottom: Radius.circular(28)), + ), + child: SafeArea( + bottom: false, + child: Padding( + padding: const EdgeInsets.fromLTRB(12, 60, 12, 36), + child: Column( + children: [ + Container( + width: 96, + height: 96, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Colors.white.withValues(alpha: 0.16), + border: Border.all( + color: Colors.white.withValues(alpha: 0.32), + width: 1.5, + ), + ), + child: Center( + child: Text( + initial, + style: const TextStyle( + fontSize: 36, + fontWeight: FontWeight.w700, + color: Colors.white, + ), + ), + ), + ), + const SizedBox(height: 18), + Text( + user?.name ?? l10n.user, + style: const TextStyle( + fontSize: 24, + fontWeight: FontWeight.w700, + color: Colors.white, + letterSpacing: 1.5, + ), + ), + const SizedBox(height: 8), + Text( + user?.phone ?? '', + style: TextStyle( + fontSize: 14, + color: Colors.white.withValues(alpha: 0.82), + letterSpacing: 1, + ), + ), + const SizedBox(height: 18), + Container( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 6), + decoration: BoxDecoration( + color: Colors.white.withValues(alpha: 0.22), + borderRadius: BorderRadius.circular(22), + ), + child: Text( + isBoss ? l10n.boss : l10n.employee, + style: const TextStyle( + fontSize: 13, + color: Colors.white, + fontWeight: FontWeight.w500, + letterSpacing: 1.5, + ), + ), + ), + ], ), - Icon( - Icons.chevron_right, - color: Colors.white.withValues(alpha: 0.6), - ), - ], + ), ), ); } Widget _buildSectionTitle(String title) { - return Text( - title, - style: const TextStyle( - fontSize: 14, - fontWeight: FontWeight.w600, - color: AppColors.textSecondary, + return Padding( + padding: const EdgeInsets.only(left: 4), + child: Text( + title, + style: TextStyle( + fontSize: 13, + fontWeight: FontWeight.w600, + color: AppColors.textSecondary, + letterSpacing: 1.2, + ), ), ); } @@ -374,7 +457,7 @@ class SettingsPage extends ConsumerWidget { children: [ Text( item.title, - style: const TextStyle( + style: TextStyle( fontSize: 15, fontWeight: FontWeight.w500, color: AppColors.textPrimary, @@ -395,12 +478,12 @@ class SettingsPage extends ConsumerWidget { ), subtitle: Text( item.subtitle, - style: const TextStyle( + style: TextStyle( fontSize: 13, color: AppColors.textTertiary, ), ), - trailing: item.trailing ?? const Icon(Icons.chevron_right, color: AppColors.textTertiary, size: 20), + trailing: item.trailing ?? Icon(Icons.chevron_right, color: AppColors.textTertiary, size: 20), onTap: item.onTap, ), if (!isLast) diff --git a/lib/pages/store_page.dart b/lib/pages/store_page.dart index 575df01..7859549 100644 --- a/lib/pages/store_page.dart +++ b/lib/pages/store_page.dart @@ -91,7 +91,7 @@ class StorePage extends StatelessWidget { children: [ Text( name, - style: const TextStyle( + style: TextStyle( fontSize: 17, fontWeight: FontWeight.w600, color: AppColors.textPrimary, @@ -124,7 +124,7 @@ class StorePage extends StatelessWidget { ), child: Text( type, - style: const TextStyle( + style: TextStyle( fontSize: 11, color: AppColors.textSecondary, ), @@ -135,7 +135,7 @@ class StorePage extends StatelessWidget { ], ), ), - const Icon(Icons.chevron_right, color: AppColors.textTertiary), + Icon(Icons.chevron_right, color: AppColors.textTertiary), ], ), const Divider(height: 24), @@ -155,7 +155,7 @@ class StorePage extends StatelessWidget { Expanded( child: Text( text, - style: const TextStyle(fontSize: 13, color: AppColors.textSecondary), + style: TextStyle(fontSize: 13, color: AppColors.textSecondary), ), ), ], diff --git a/lib/pages/system_messages_page.dart b/lib/pages/system_messages_page.dart new file mode 100644 index 0000000..13bc030 --- /dev/null +++ b/lib/pages/system_messages_page.dart @@ -0,0 +1,159 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_animate/flutter_animate.dart'; +import '../theme.dart'; + +class SystemMessagesPage extends StatelessWidget { + const SystemMessagesPage({super.key}); + + @override + Widget build(BuildContext context) { + final messages = <_SystemMessage>[ + _SystemMessage( + title: '版本更新', + content: '智念商家端 v1.0.1 已发布,新增应用插件中心,优化首页和消息中心交互体验。', + time: '今天 09:30', + type: '版本更新', + typeColor: const Color(0xFF3B82F6), + ), + _SystemMessage( + title: '春节运营公告', + content: '春节期间平台手续费立减 30%,详情可在「活动发布」中查看活动模板。', + time: '昨天 18:20', + type: '运营公告', + typeColor: const Color(0xFF10B981), + ), + _SystemMessage( + title: '安全提醒', + content: '检测到近期有钓鱼链接冒用商家端信息,请勿在陌生短信中点击链接登录。', + time: '3 天前', + type: '安全提醒', + typeColor: const Color(0xFFF59E0B), + ), + ]; + + return Scaffold( + backgroundColor: AppColors.background, + appBar: AppBar( + title: const Text('系统通知'), + backgroundColor: AppColors.surface, + elevation: 0, + scrolledUnderElevation: 0, + surfaceTintColor: Colors.transparent, + ), + body: messages.isEmpty + ? _buildEmpty() + : ListView.builder( + padding: const EdgeInsets.fromLTRB(16, 16, 16, 24), + itemCount: messages.length, + itemBuilder: (context, index) { + final m = messages[index]; + return _buildMessageCard(m, index); + }, + ), + ); + } + + Widget _buildMessageCard(_SystemMessage m, int index) { + return Container( + margin: const EdgeInsets.only(bottom: 12), + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: AppColors.surface, + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.03), + blurRadius: 10, + offset: const Offset(0, 2), + ), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3), + decoration: BoxDecoration( + color: m.typeColor.withOpacity(0.1), + borderRadius: BorderRadius.circular(6), + ), + child: Text( + m.type, + style: TextStyle( + fontSize: 11, + color: m.typeColor, + fontWeight: FontWeight.w600, + ), + ), + ), + const Spacer(), + Text( + m.time, + style: TextStyle( + fontSize: 12, + color: AppColors.textTertiary, + ), + ), + ], + ), + const SizedBox(height: 12), + Text( + m.title, + style: TextStyle( + fontSize: 15, + fontWeight: FontWeight.w600, + color: AppColors.textPrimary, + ), + ), + const SizedBox(height: 6), + Text( + m.content, + style: TextStyle( + fontSize: 13, + color: AppColors.textSecondary, + height: 1.55, + ), + ), + ], + ), + ).animate().fadeIn(duration: 300.ms, delay: (index * 60).ms).slideY(begin: 0.1, end: 0, duration: 300.ms); + } + + Widget _buildEmpty() { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.notifications_off_outlined, + size: 64, + color: AppColors.textTertiary.withOpacity(0.5), + ), + const SizedBox(height: 16), + Text( + '暂无系统通知', + style: TextStyle(fontSize: 16, color: AppColors.textSecondary), + ), + ], + ), + ); + } +} + +class _SystemMessage { + final String title; + final String content; + final String time; + final String type; + final Color typeColor; + + const _SystemMessage({ + required this.title, + required this.content, + required this.time, + required this.type, + required this.typeColor, + }); +} diff --git a/lib/pages/work_order_detail_page.dart b/lib/pages/work_order_detail_page.dart index 7d8c1cf..65e51d7 100644 --- a/lib/pages/work_order_detail_page.dart +++ b/lib/pages/work_order_detail_page.dart @@ -1,9 +1,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:cached_network_image/cached_network_image.dart'; import '../models/work_order.dart'; import '../providers/work_order_provider.dart'; import '../theme.dart'; import '../l10n/app_localizations.dart'; +import '../widgets/skeleton.dart'; class WorkOrderDetailPage extends ConsumerStatefulWidget { final String orderId; @@ -14,6 +16,8 @@ class WorkOrderDetailPage extends ConsumerStatefulWidget { } class _WorkOrderDetailPageState extends ConsumerState { + bool _actionInProgress = false; + @override void initState() { super.initState(); @@ -26,6 +30,16 @@ class _WorkOrderDetailPageState extends ConsumerState { Widget build(BuildContext context) { final l10n = AppLocalizations.of(context)!; final state = ref.watch(workOrderProvider); + final hasMatch = state.orders.any((o) => o.id == widget.orderId); + + if (state.isLoading && !hasMatch) { + return Scaffold( + backgroundColor: AppColors.background, + appBar: AppBar(title: Text(l10n.workOrderDetail)), + body: const DetailPageSkeleton(), + ); + } + final order = state.orders.firstWhere( (o) => o.id == widget.orderId, orElse: () => state.orders.isNotEmpty ? state.orders.first : _mockOrder(context, l10n), @@ -41,21 +55,117 @@ class _WorkOrderDetailPageState extends ConsumerState { children: [ _buildHeader(context, order, l10n), const SizedBox(height: 20), - _buildInfoCard(context, order, l10n), - const SizedBox(height: 20), _buildDescriptionCard(context, order, l10n), - if (order.images.isNotEmpty) ...[ - const SizedBox(height: 20), - _buildImagesCard(context, order, l10n), - ], const SizedBox(height: 20), - _buildTimeline(context, order, l10n), + _buildInfoCard(context, order, l10n), + if (order.status != WorkOrderStatus.pending) ...[ + const SizedBox(height: 20), + _buildTimeline(context, order, l10n), + ], ], ), ), + bottomNavigationBar: _buildBottomBar(context, order, l10n), ); } + Widget? _buildBottomBar(BuildContext context, WorkOrder order, AppLocalizations l10n) { + if (order.status == WorkOrderStatus.pending) { + return _ActionBar( + children: [ + Expanded( + child: _SecondaryButton( + label: l10n.transferOrder, + icon: Icons.swap_horiz, + onPressed: _actionInProgress ? null : () => _onTransfer(order, l10n), + ), + ), + const SizedBox(width: 12), + Expanded( + flex: 2, + child: _PrimaryButton( + label: l10n.acceptOrder, + icon: Icons.check_circle_outline, + loading: _actionInProgress, + onPressed: _actionInProgress ? null : () => _onAccept(order, l10n), + ), + ), + ], + ); + } + if (order.status == WorkOrderStatus.processing) { + return _ActionBar( + children: [ + Expanded( + child: _PrimaryButton( + label: l10n.completeOrder, + icon: Icons.task_alt, + loading: _actionInProgress, + onPressed: _actionInProgress ? null : () => _onComplete(order, l10n), + ), + ), + ], + ); + } + return null; + } + + Future _onAccept(WorkOrder order, AppLocalizations l10n) async { + setState(() => _actionInProgress = true); + final ok = await ref.read(workOrderProvider.notifier).acceptOrder(order.id); + if (!mounted) return; + setState(() => _actionInProgress = false); + _showToast(ok ? l10n.acceptSuccess : l10n.acceptFailed, success: ok); + } + + Future _onComplete(WorkOrder order, AppLocalizations l10n) async { + setState(() => _actionInProgress = true); + final ok = await ref.read(workOrderProvider.notifier).completeOrder(order.id); + if (!mounted) return; + setState(() => _actionInProgress = false); + _showToast(ok ? l10n.completeSuccess : l10n.completeFailed, success: ok); + } + + Future _onTransfer(WorkOrder order, AppLocalizations l10n) async { + final department = await showModalBottomSheet( + context: context, + backgroundColor: Colors.transparent, + isScrollControlled: true, + builder: (_) => _DepartmentPicker(currentAssignee: order.assigneeName), + ); + if (department == null || !mounted) return; + setState(() => _actionInProgress = true); + final ok = await ref.read(workOrderProvider.notifier).transferOrder(order.id, department); + if (!mounted) return; + setState(() => _actionInProgress = false); + _showToast(ok ? l10n.transferSuccess : l10n.transferFailed, success: ok); + } + + void _showToast(String message, {required bool success}) { + ScaffoldMessenger.of(context) + ..hideCurrentSnackBar() + ..showSnackBar( + SnackBar( + content: Row( + children: [ + Icon( + success ? Icons.check_circle : Icons.error_outline, + color: Colors.white, + size: 20, + ), + const SizedBox(width: 10), + Expanded(child: Text(message)), + ], + ), + backgroundColor: success ? const Color(0xFF10B981) : const Color(0xFFEF4444), + behavior: SnackBarBehavior.floating, + margin: const EdgeInsets.fromLTRB(16, 0, 16, 16), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + duration: const Duration(seconds: 2), + ), + ); + } + Widget _buildHeader(BuildContext context, WorkOrder order, AppLocalizations l10n) { return Container( padding: const EdgeInsets.all(24), @@ -122,9 +232,11 @@ class _WorkOrderDetailPageState extends ConsumerState { const Divider(height: 24), _buildInfoRow(l10n.creator, order.creatorName, Icons.person_outline), const Divider(height: 24), - _buildInfoRow(l10n.assignee, order.assigneeName ?? l10n.notAssigned, Icons.assignment_ind_outlined), - const Divider(height: 24), - _buildInfoRow(l10n.category, order.category, Icons.folder_outlined), + _buildInfoRow( + order.status == WorkOrderStatus.pending ? l10n.transferDept : l10n.assignee, + order.assigneeName ?? l10n.notAssigned, + Icons.assignment_ind_outlined, + ), const Divider(height: 24), _buildInfoRow(l10n.priority, _getPriorityText(context, order.priority), Icons.flag_outlined), const Divider(height: 24), @@ -149,12 +261,12 @@ class _WorkOrderDetailPageState extends ConsumerState { children: [ Text( label, - style: const TextStyle(fontSize: 12, color: AppColors.textTertiary), + style: TextStyle(fontSize: 12, color: AppColors.textTertiary), ), const SizedBox(height: 2), Text( value, - style: const TextStyle( + style: TextStyle( fontSize: 15, fontWeight: FontWeight.w500, color: AppColors.textPrimary, @@ -168,6 +280,11 @@ class _WorkOrderDetailPageState extends ConsumerState { } Widget _buildDescriptionCard(BuildContext context, WorkOrder order, AppLocalizations l10n) { + final imageUrl = order.images.isNotEmpty + ? order.images.first + : 'https://picsum.photos/seed/wo${order.id}/800/450'; + final heroTag = 'workOrderImage_${order.id}'; + return Container( width: double.infinity, padding: const EdgeInsets.all(20), @@ -184,7 +301,7 @@ class _WorkOrderDetailPageState extends ConsumerState { const SizedBox(width: 8), Text( l10n.problemDesc, - style: const TextStyle( + style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: AppColors.textPrimary, @@ -195,59 +312,65 @@ class _WorkOrderDetailPageState extends ConsumerState { const SizedBox(height: 12), Text( order.description, - style: const TextStyle( + style: TextStyle( fontSize: 15, color: AppColors.textSecondary, height: 1.6, ), ), + const SizedBox(height: 16), + GestureDetector( + onTap: () => _openImageViewer(context, imageUrl, heroTag), + child: ClipRRect( + borderRadius: BorderRadius.circular(12), + child: Hero( + tag: heroTag, + child: AspectRatio( + aspectRatio: 16 / 9, + child: CachedNetworkImage( + imageUrl: imageUrl, + fit: BoxFit.cover, + placeholder: (_, __) => Container( + color: AppColors.background, + alignment: Alignment.center, + child: SizedBox( + width: 24, + height: 24, + child: CircularProgressIndicator( + strokeWidth: 2, + valueColor: AlwaysStoppedAnimation( + AppColors.primary.withOpacity(0.5), + ), + ), + ), + ), + errorWidget: (_, __, ___) => Container( + color: AppColors.background, + alignment: Alignment.center, + child: Icon( + Icons.broken_image_outlined, + color: AppColors.textTertiary, + size: 32, + ), + ), + ), + ), + ), + ), + ), ], ), ); } - Widget _buildImagesCard(BuildContext context, WorkOrder order, AppLocalizations l10n) { - return Container( - width: double.infinity, - padding: const EdgeInsets.all(20), - decoration: BoxDecoration( - color: AppColors.surface, - borderRadius: BorderRadius.circular(16), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - const Icon(Icons.image_outlined, size: 18, color: AppColors.primary), - const SizedBox(width: 8), - Text( - l10n.attachments, - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w600, - color: AppColors.textPrimary, - ), - ), - ], - ), - const SizedBox(height: 12), - Wrap( - spacing: 12, - runSpacing: 12, - children: order.images.map((img) { - return Container( - width: 100, - height: 100, - decoration: BoxDecoration( - color: AppColors.background, - borderRadius: BorderRadius.circular(12), - ), - child: const Icon(Icons.image, color: AppColors.textTertiary), - ); - }).toList(), - ), - ], + void _openImageViewer(BuildContext context, String url, String heroTag) { + Navigator.of(context).push( + PageRouteBuilder( + opaque: false, + barrierColor: Colors.black87, + transitionDuration: const Duration(milliseconds: 280), + reverseTransitionDuration: const Duration(milliseconds: 220), + pageBuilder: (_, __, ___) => _ImageViewer(url: url, heroTag: heroTag), ), ); } @@ -269,7 +392,7 @@ class _WorkOrderDetailPageState extends ConsumerState { const SizedBox(width: 8), Text( l10n.progress, - style: const TextStyle( + style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: AppColors.textPrimary, @@ -419,3 +542,324 @@ class _TimelineItem extends StatelessWidget { ); } } + +class _ImageViewer extends StatelessWidget { + final String url; + final String heroTag; + + const _ImageViewer({required this.url, required this.heroTag}); + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.transparent, + body: Stack( + children: [ + GestureDetector( + onTap: () => Navigator.of(context).pop(), + behavior: HitTestBehavior.opaque, + child: Center( + child: Hero( + tag: heroTag, + child: InteractiveViewer( + minScale: 1.0, + maxScale: 4.0, + child: CachedNetworkImage( + imageUrl: url, + fit: BoxFit.contain, + placeholder: (_, __) => const Center( + child: SizedBox( + width: 32, + height: 32, + child: CircularProgressIndicator( + strokeWidth: 2.5, + valueColor: AlwaysStoppedAnimation(Colors.white), + ), + ), + ), + errorWidget: (_, __, ___) => const Icon( + Icons.broken_image_outlined, + color: Colors.white54, + size: 48, + ), + ), + ), + ), + ), + ), + Positioned( + top: MediaQuery.of(context).padding.top + 8, + right: 12, + child: Material( + color: Colors.black38, + shape: const CircleBorder(), + child: IconButton( + icon: const Icon(Icons.close, color: Colors.white, size: 22), + onPressed: () => Navigator.of(context).pop(), + ), + ), + ), + ], + ), + ); + } +} + +class _ActionBar extends StatelessWidget { + final List children; + const _ActionBar({required this.children}); + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + color: AppColors.surface, + border: Border(top: BorderSide(color: AppColors.divider, width: 0.5)), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.04), + blurRadius: 12, + offset: const Offset(0, -2), + ), + ], + ), + child: SafeArea( + top: false, + child: Padding( + padding: const EdgeInsets.fromLTRB(16, 12, 16, 12), + child: Row(children: children), + ), + ), + ); + } +} + +class _PrimaryButton extends StatelessWidget { + final String label; + final IconData icon; + final bool loading; + final VoidCallback? onPressed; + + const _PrimaryButton({ + required this.label, + required this.icon, + this.loading = false, + required this.onPressed, + }); + + @override + Widget build(BuildContext context) { + final disabled = onPressed == null; + return Material( + borderRadius: BorderRadius.circular(14), + color: Colors.transparent, + child: Ink( + decoration: BoxDecoration( + color: disabled ? AppColors.divider : AppColors.primary, + borderRadius: BorderRadius.circular(14), + ), + child: InkWell( + borderRadius: BorderRadius.circular(14), + onTap: onPressed, + child: Container( + height: 48, + alignment: Alignment.center, + child: loading + ? const SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator( + strokeWidth: 2.2, + valueColor: AlwaysStoppedAnimation(Colors.white), + ), + ) + : Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(icon, color: Colors.white, size: 18), + const SizedBox(width: 6), + Text( + label, + style: const TextStyle( + color: Colors.white, + fontSize: 15, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ), + ), + ), + ); + } +} + +class _SecondaryButton extends StatelessWidget { + final String label; + final IconData icon; + final VoidCallback? onPressed; + + const _SecondaryButton({ + required this.label, + required this.icon, + required this.onPressed, + }); + + @override + Widget build(BuildContext context) { + final disabled = onPressed == null; + return Material( + color: AppColors.background, + borderRadius: BorderRadius.circular(14), + child: InkWell( + borderRadius: BorderRadius.circular(14), + onTap: onPressed, + child: Container( + height: 48, + alignment: Alignment.center, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(14), + border: Border.all(color: AppColors.divider), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + icon, + color: disabled ? AppColors.textTertiary : AppColors.primary, + size: 18, + ), + const SizedBox(width: 6), + Text( + label, + style: TextStyle( + color: disabled ? AppColors.textTertiary : AppColors.primary, + fontSize: 15, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ), + ), + ); + } +} + +class _DepartmentPicker extends StatelessWidget { + final String? currentAssignee; + const _DepartmentPicker({this.currentAssignee}); + + @override + Widget build(BuildContext context) { + final l10n = AppLocalizations.of(context)!; + final departments = <_DeptOption>[ + _DeptOption(label: l10n.deptMaintenance, icon: Icons.build_outlined), + _DeptOption(label: l10n.deptCleaning, icon: Icons.cleaning_services_outlined), + _DeptOption(label: l10n.deptFrontDesk, icon: Icons.support_agent_outlined), + _DeptOption(label: l10n.deptSecurity, icon: Icons.shield_outlined), + _DeptOption(label: l10n.deptAdmin, icon: Icons.badge_outlined), + _DeptOption(label: l10n.deptKitchen, icon: Icons.restaurant_outlined), + ]; + + return Container( + decoration: BoxDecoration( + color: AppColors.surface, + borderRadius: const BorderRadius.vertical(top: Radius.circular(24)), + ), + child: SafeArea( + top: false, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox(height: 8), + Container( + width: 40, + height: 4, + decoration: BoxDecoration( + color: AppColors.divider, + borderRadius: BorderRadius.circular(2), + ), + ), + const SizedBox(height: 12), + Padding( + padding: const EdgeInsets.fromLTRB(20, 4, 20, 8), + child: Row( + children: [ + Text( + l10n.selectTransferDept, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: AppColors.textPrimary, + ), + ), + const Spacer(), + IconButton( + icon: Icon(Icons.close, color: AppColors.textTertiary, size: 20), + onPressed: () => Navigator.of(context).pop(), + visualDensity: VisualDensity.compact, + ), + ], + ), + ), + Divider(height: 1, color: AppColors.divider), + ListView.separated( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + padding: const EdgeInsets.symmetric(vertical: 4), + itemCount: departments.length, + separatorBuilder: (_, __) => + Divider(height: 1, color: AppColors.divider.withOpacity(0.5), indent: 60), + itemBuilder: (_, i) { + final dept = departments[i]; + final selected = dept.label == currentAssignee; + return InkWell( + onTap: () => Navigator.of(context).pop(dept.label), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 14), + child: Row( + children: [ + Container( + width: 32, + height: 32, + decoration: BoxDecoration( + color: AppColors.primary.withOpacity(0.1), + borderRadius: BorderRadius.circular(10), + ), + child: Icon(dept.icon, color: AppColors.primary, size: 18), + ), + const SizedBox(width: 12), + Expanded( + child: Text( + dept.label, + style: TextStyle( + fontSize: 15, + color: AppColors.textPrimary, + fontWeight: + selected ? FontWeight.w600 : FontWeight.w500, + ), + ), + ), + if (selected) + Icon(Icons.check, color: AppColors.primary, size: 20), + ], + ), + ), + ); + }, + ), + const SizedBox(height: 8), + ], + ), + ), + ); + } +} + +class _DeptOption { + final String label; + final IconData icon; + const _DeptOption({required this.label, required this.icon}); +} diff --git a/lib/providers/work_order_provider.dart b/lib/providers/work_order_provider.dart index 08bf2d8..23417ab 100644 --- a/lib/providers/work_order_provider.dart +++ b/lib/providers/work_order_provider.dart @@ -33,11 +33,51 @@ class WorkOrderNotifier extends StateNotifier { Future getDetail(String id) async { try { - return await apiService.getWorkOrderDetail(id); + final order = await apiService.getWorkOrderDetail(id); + _upsertOrder(order); + return order; } catch (_) { return null; } } + + Future acceptOrder(String id) async { + try { + final updated = await apiService.acceptWorkOrder(id); + _upsertOrder(updated); + return true; + } catch (_) { + return false; + } + } + + Future transferOrder(String id, String department) async { + try { + final updated = await apiService.transferWorkOrder(id, department); + _upsertOrder(updated); + return true; + } catch (_) { + return false; + } + } + + Future completeOrder(String id) async { + try { + final updated = await apiService.completeWorkOrder(id); + _upsertOrder(updated); + return true; + } catch (_) { + return false; + } + } + + void _upsertOrder(WorkOrder order) { + final exists = state.orders.any((o) => o.id == order.id); + final newOrders = exists + ? state.orders.map((o) => o.id == order.id ? order : o).toList() + : [...state.orders, order]; + state = state.copyWith(orders: newOrders); + } } final workOrderProvider = StateNotifierProvider((ref) { diff --git a/lib/router.dart b/lib/router.dart index 570a979..e1d9b16 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -18,6 +18,7 @@ import 'pages/app_market_page.dart'; import 'pages/help_page.dart'; import 'pages/about_page.dart'; import 'pages/policy_page.dart'; +import 'pages/system_messages_page.dart'; final routerProvider = Provider((ref) { final authState = ref.watch(authProvider); @@ -26,12 +27,13 @@ final routerProvider = Provider((ref) { initialLocation: '/login', redirect: (context, state) { final isLoggedIn = authState.isLoggedIn; - final isLoginRoute = state.matchedLocation == '/login'; + final loc = state.matchedLocation; + final isPublicRoute = loc == '/login' || loc == '/settings/policy'; - if (!isLoggedIn && !isLoginRoute) { + if (!isLoggedIn && !isPublicRoute) { return '/login'; } - if (isLoggedIn && isLoginRoute) { + if (isLoggedIn && loc == '/login') { return '/home'; } return null; @@ -81,6 +83,10 @@ final routerProvider = Provider((ref) { return ScanResultPage(scanCode: code); }, ), + GoRoute( + path: '/messages', + builder: (context, state) => const SystemMessagesPage(), + ), GoRoute( path: '/settings', builder: (context, state) => const SettingsPage(), diff --git a/lib/services/api_service.dart b/lib/services/api_service.dart index a6add35..4e0ecbd 100644 --- a/lib/services/api_service.dart +++ b/lib/services/api_service.dart @@ -54,6 +54,39 @@ class ApiService { Future sendMessage(String content) async { await Future.delayed(const Duration(milliseconds: 1200)); + final tableTrigger = RegExp(r'表格|列表|清单|明细'); + if (tableTrigger.hasMatch(content)) { + return ChatMessage( + id: 'msg_${DateTime.now().millisecondsSinceEpoch}', + content: _tableReport(), + type: MessageType.markdown, + sender: MessageSender.ai, + timestamp: DateTime.now(), + ); + } + + final imageTrigger = RegExp(r'图片|照片|图像|实景|环境照|相册'); + if (imageTrigger.hasMatch(content)) { + return ChatMessage( + id: 'msg_${DateTime.now().millisecondsSinceEpoch}', + content: _imageGallery(), + type: MessageType.markdown, + sender: MessageSender.ai, + timestamp: DateTime.now(), + ); + } + + final chartTrigger = RegExp(r'报表|数据|营收|趋势|占比|图表|统计|分析'); + if (chartTrigger.hasMatch(content)) { + return ChatMessage( + id: 'msg_${DateTime.now().millisecondsSinceEpoch}', + content: _chartReport(), + type: MessageType.markdown, + sender: MessageSender.ai, + timestamp: DateTime.now(), + ); + } + final responses = [ '收到您的消息,我已记录并会尽快处理。\n\n**当前状态**:已受理\n**预计处理时间**:2小时内', '好的,我来帮您查询一下。\n\n| 项目 | 状态 | 时间 |\n|------|------|------|\n| 订单核销 | 已完成 | 10:30 |\n| 工单处理 | 进行中 | 11:00 |', @@ -71,6 +104,75 @@ class ApiService { ); } + String _imageGallery() { + return '''### 智念度假酒店 · 环境实景 + +**大堂吧** — 全息投影 + 智能引导 +![大堂](https://picsum.photos/seed/zhinian-lobby/640/360) + +**无边泳池** — 山景一线,270° 全景视野 +![泳池](https://picsum.photos/seed/zhinian-pool/640/360) + +**SPA 养生** — 古法中医结合现代理疗 +![SPA](https://picsum.photos/seed/zhinian-spa/640/360) + +> 提示:可继续询问"客房实景""餐厅照片"查看更多。'''; + } + + String _tableReport() { + return '''### 今日订单明细 + +共 **5 条** 待跟进订单: + +| 订单号 | 客户 | 商品 | 金额 | 状态 | +|---|---|---|---|---| +| ZN20240507001 | 王先生 | 风景区成人票 ×2 | ¥128 | 🟡 待核销 | +| ZN20240507002 | 李女士 | 豪华套房 ×1 | ¥888 | ⚪ 待支付 | +| ZN20240507003 | 张 family | 亲子套票 ×3 | ¥388 | 🟢 已核销 | +| ZN20240507004 | 陈先生 | SPA 养生套餐 | ¥598 | 🔴 退款中 | +| ZN20240507005 | 赵女士 | 景区+酒店套餐 | ¥1588 | ⚫ 已退款 | + +**汇总** + +| 维度 | 数值 | +|---|---| +| 订单总数 | 5 | +| 应收金额 | ¥3,590 | +| 已退款 | ¥1,588 | +| 净收入 | ¥2,002 | + +> 提示:点击订单行可跳转至详情,或继续询问"图表"查看可视化分析。'''; + } + + String _chartReport() { + return '''### 今日数据报表 + +总订单 **128 笔**,核销率 **75%**,环比上周 **+12.4%**。 + +```chart +type: bar +title: 订单状态分布(笔) +labels: 已核销,待处理,待支付,退款 +data: 96,32,18,2 +``` + +```chart +type: line +title: 近7日订单趋势 +labels: 周一,周二,周三,周四,周五,周六,周日 +data: 88,92,105,98,120,135,128 +``` + +```chart +type: pie +title: 营收构成占比 +labels: 客房,餐饮,SPA,门票 +data: 58,22,12,8 +``` + +> 提示:可继续询问"工单趋势""营收对比"等查看更多分析。'''; + } + Future> getEvents() async { await Future.delayed(const Duration(milliseconds: 600)); return List.generate(8, (index) { @@ -121,7 +223,7 @@ class ApiService { description: '308房间空调制冷效果不佳,需要安排维修人员检查。', status: WorkOrderStatus.pending, creatorName: '前台小王', - assigneeName: '维修老张', + assigneeName: '维修部', createdAt: DateTime.now().subtract(const Duration(hours: 2)), priority: 'high', category: '设备维修', @@ -134,7 +236,7 @@ class ApiService { description: '后花园草坪需要定期修剪,预计耗时2小时。', status: WorkOrderStatus.processing, creatorName: '张老板', - assigneeName: '园丁老李', + assigneeName: '保洁部', createdAt: DateTime.now().subtract(const Duration(hours: 5)), priority: 'normal', category: '环境维护', @@ -147,7 +249,7 @@ class ApiService { description: '厨房排风系统需要深度清洁。', status: WorkOrderStatus.completed, creatorName: '厨师长', - assigneeName: '清洁团队', + assigneeName: '保洁部', createdAt: DateTime.now().subtract(const Duration(days: 1)), completedAt: DateTime.now().subtract(const Duration(hours: 3)), priority: 'normal', @@ -173,7 +275,7 @@ class ApiService { description: '下午有重要会议,需要提前调试投影设备。', status: WorkOrderStatus.processing, creatorName: '行政小刘', - assigneeName: 'IT小王', + assigneeName: '行政部', createdAt: DateTime.now().subtract(const Duration(hours: 1)), priority: 'urgent', category: '设备调试', @@ -192,6 +294,39 @@ class ApiService { return orders.firstWhere((o) => o.id == id, orElse: () => orders.first); } + Future acceptWorkOrder(String id) async { + await Future.delayed(const Duration(milliseconds: 700)); + if (_random.nextInt(20) == 0) { + throw Exception('网络异常,请稍后重试'); + } + final order = await getWorkOrderDetail(id); + return order.copyWith( + status: WorkOrderStatus.processing, + assigneeName: order.assigneeName ?? '维修部', + ); + } + + Future transferWorkOrder(String id, String department) async { + await Future.delayed(const Duration(milliseconds: 700)); + if (_random.nextInt(20) == 0) { + throw Exception('网络异常,请稍后重试'); + } + final order = await getWorkOrderDetail(id); + return order.copyWith(assigneeName: department); + } + + Future completeWorkOrder(String id) async { + await Future.delayed(const Duration(milliseconds: 700)); + if (_random.nextInt(20) == 0) { + throw Exception('网络异常,请稍后重试'); + } + final order = await getWorkOrderDetail(id); + return order.copyWith( + status: WorkOrderStatus.completed, + completedAt: DateTime.now(), + ); + } + Future> getOrders(OrderStatus status) async { await Future.delayed(const Duration(milliseconds: 600)); final allOrders = [ diff --git a/lib/services/vosk_voice_service.dart b/lib/services/vosk_voice_service.dart new file mode 100644 index 0000000..34f4960 --- /dev/null +++ b/lib/services/vosk_voice_service.dart @@ -0,0 +1,111 @@ +import 'dart:async'; +import 'dart:convert'; + +import 'package:vosk_flutter_service/vosk_flutter.dart'; + +class VoskVoiceService { + VoskVoiceService._(); + static final VoskVoiceService instance = VoskVoiceService._(); + + static const String _modelAsset = 'assets/models/vosk-model-small-cn-0.22.zip'; + static const int _sampleRate = 16000; + + final VoskFlutterPlugin _vosk = VoskFlutterPlugin.instance(); + Model? _model; + Recognizer? _recognizer; + SpeechService? _speechService; + + bool _initializing = false; + bool _listening = false; + + final StreamController _partialController = StreamController.broadcast(); + final StreamController _resultController = StreamController.broadcast(); + final StreamController _errorController = StreamController.broadcast(); + + StreamSubscription? _partialSub; + StreamSubscription? _resultSub; + + Stream get partialStream => _partialController.stream; + Stream get resultStream => _resultController.stream; + Stream get errorStream => _errorController.stream; + bool get isListening => _listening; + bool get isReady => _speechService != null; + + Future ensureReady() async { + if (_speechService != null) return true; + if (_initializing) { + while (_initializing) { + await Future.delayed(const Duration(milliseconds: 80)); + } + return _speechService != null; + } + _initializing = true; + try { + final modelPath = await ModelLoader().loadFromAssets(_modelAsset); + _model = await _vosk.createModel(modelPath); + _recognizer = await _vosk.createRecognizer( + model: _model!, + sampleRate: _sampleRate, + ); + _speechService = await _vosk.initSpeechService(_recognizer!); + _partialSub = _speechService!.onPartial().listen((raw) { + final text = _extractField(raw, 'partial'); + if (text != null) _partialController.add(text); + }); + _resultSub = _speechService!.onResult().listen((raw) { + final text = _extractField(raw, 'text'); + if (text != null) _resultController.add(text); + }); + return true; + } catch (e) { + _errorController.add(e.toString()); + return false; + } finally { + _initializing = false; + } + } + + Future start() async { + if (!await ensureReady()) return false; + if (_listening) return true; + final ok = await _speechService!.start( + onRecognitionError: (err) => _errorController.add(err.toString()), + ); + _listening = ok ?? false; + return _listening; + } + + Future stop() async { + if (!_listening || _speechService == null) return; + await _speechService!.stop(); + _listening = false; + } + + Future cancel() async { + if (_speechService == null) return; + await _speechService!.cancel(); + _listening = false; + } + + Future dispose() async { + await _partialSub?.cancel(); + await _resultSub?.cancel(); + await _speechService?.dispose(); + await _recognizer?.dispose(); + _speechService = null; + _recognizer = null; + _model = null; + _listening = false; + } + + String? _extractField(String raw, String key) { + try { + final map = jsonDecode(raw); + if (map is Map && map[key] is String) { + final v = (map[key] as String).trim(); + return v; + } + } catch (_) {} + return null; + } +} diff --git a/lib/theme.dart b/lib/theme.dart index e2fb9b6..6c81b82 100644 --- a/lib/theme.dart +++ b/lib/theme.dart @@ -4,45 +4,106 @@ import 'package:google_fonts/google_fonts.dart'; import 'package:flutter_animate/flutter_animate.dart'; class AppColors { + // Brand colors (stable across light/dark) static const primary = Color(0xFF1A56DB); static const primaryLight = Color(0xFF3B82F6); static const primaryDark = Color(0xFF1E40AF); static const accent = Color(0xFF00C9A7); static const accentLight = Color(0xFF6EE7B7); - static const background = Color(0xFFF8FAFC); - static const surface = Colors.white; static const error = Color(0xFFEF4444); static const warning = Color(0xFFF59E0B); static const success = Color(0xFF10B981); - static const textPrimary = Color(0xFF1E293B); - static const textSecondary = Color(0xFF64748B); - static const textTertiary = Color(0xFF94A3B8); - static const divider = Color(0xFFE2E8F0); - static const cardShadow = Color(0x1A000000); static const gradientStart = Color(0xFF1A56DB); static const gradientEnd = Color(0xFF00C9A7); static const chatUserBubble = Color(0xFF1A56DB); - static const chatAiBubble = Color(0xFFF1F5F9); static const scanOverlay = Color(0x801A56DB); + + // Light palette (raw values) + static const _lightBackground = Color(0xFFF8FAFC); + static const _lightSurface = Color(0xFFFFFFFF); + static const _lightTextPrimary = Color(0xFF1E293B); + static const _lightTextSecondary = Color(0xFF64748B); + static const _lightTextTertiary = Color(0xFF94A3B8); + static const _lightDivider = Color(0xFFE2E8F0); + static const _lightCardShadow = Color(0x1A000000); + static const _lightChatAiBubble = Color(0xFFF1F5F9); + + // Dark palette (raw values) + static const _darkBackground = Color(0xFF0F172A); + static const _darkSurface = Color(0xFF1E293B); + static const _darkTextPrimary = Color(0xFFE2E8F0); + static const _darkTextSecondary = Color(0xFF94A3B8); + static const _darkTextTertiary = Color(0xFF64748B); + static const _darkDivider = Color(0xFF334155); + static const _darkCardShadow = Color(0x40000000); + static const _darkChatAiBubble = Color(0xFF1E293B); + + // Mutable references — flipped at runtime via applyMode. + static Color background = _lightBackground; + static Color surface = _lightSurface; + static Color textPrimary = _lightTextPrimary; + static Color textSecondary = _lightTextSecondary; + static Color textTertiary = _lightTextTertiary; + static Color divider = _lightDivider; + static Color cardShadow = _lightCardShadow; + static Color chatAiBubble = _lightChatAiBubble; + static bool isDark = false; + + static void applyMode(bool dark) { + isDark = dark; + if (dark) { + background = _darkBackground; + surface = _darkSurface; + textPrimary = _darkTextPrimary; + textSecondary = _darkTextSecondary; + textTertiary = _darkTextTertiary; + divider = _darkDivider; + cardShadow = _darkCardShadow; + chatAiBubble = _darkChatAiBubble; + } else { + background = _lightBackground; + surface = _lightSurface; + textPrimary = _lightTextPrimary; + textSecondary = _lightTextSecondary; + textTertiary = _lightTextTertiary; + divider = _lightDivider; + cardShadow = _lightCardShadow; + chatAiBubble = _lightChatAiBubble; + } + } } class AppTheme { - static ThemeData get lightTheme { + static ThemeData get theme { + final dark = AppColors.isDark; return ThemeData( useMaterial3: true, - brightness: Brightness.light, - colorScheme: const ColorScheme.light( - primary: AppColors.primary, - onPrimary: Colors.white, - secondary: AppColors.accent, - onSecondary: Colors.white, - surface: AppColors.surface, - onSurface: AppColors.textPrimary, - error: AppColors.error, - onError: Colors.white, - background: AppColors.background, - onBackground: AppColors.textPrimary, - ), + brightness: dark ? Brightness.dark : Brightness.light, + colorScheme: dark + ? ColorScheme.dark( + primary: AppColors.primaryLight, + onPrimary: Colors.white, + secondary: AppColors.accent, + onSecondary: Colors.white, + surface: AppColors.surface, + onSurface: AppColors.textPrimary, + error: AppColors.error, + onError: Colors.white, + background: AppColors.background, + onBackground: AppColors.textPrimary, + ) + : ColorScheme.light( + primary: AppColors.primary, + onPrimary: Colors.white, + secondary: AppColors.accent, + onSecondary: Colors.white, + surface: AppColors.surface, + onSurface: AppColors.textPrimary, + error: AppColors.error, + onError: Colors.white, + background: AppColors.background, + onBackground: AppColors.textPrimary, + ), scaffoldBackgroundColor: AppColors.background, textTheme: GoogleFonts.notoSansScTextTheme().copyWith( displayLarge: GoogleFonts.notoSansSc( @@ -72,7 +133,8 @@ class AppTheme { centerTitle: true, backgroundColor: AppColors.surface, foregroundColor: AppColors.textPrimary, - systemOverlayStyle: SystemUiOverlayStyle.dark, + systemOverlayStyle: + dark ? SystemUiOverlayStyle.light : SystemUiOverlayStyle.dark, titleTextStyle: GoogleFonts.notoSansSc( fontSize: 18, fontWeight: FontWeight.w600, color: AppColors.textPrimary, ), @@ -148,7 +210,7 @@ class AppTheme { foregroundColor: Colors.white, elevation: 4, ), - bottomNavigationBarTheme: const BottomNavigationBarThemeData( + bottomNavigationBarTheme: BottomNavigationBarThemeData( backgroundColor: AppColors.surface, selectedItemColor: AppColors.primary, unselectedItemColor: AppColors.textTertiary, @@ -163,7 +225,7 @@ class AppTheme { borderRadius: BorderRadius.circular(8), ), ), - dividerTheme: const DividerThemeData( + dividerTheme: DividerThemeData( color: AppColors.divider, thickness: 1, space: 1, @@ -181,33 +243,22 @@ class AppTheme { ), ); } - - static ThemeData get darkTheme { - return lightTheme.copyWith( - brightness: Brightness.dark, - colorScheme: const ColorScheme.dark( - primary: AppColors.primaryLight, - onPrimary: Colors.white, - secondary: AppColors.accent, - onSecondary: Colors.white, - surface: Color(0xFF1E293B), - onSurface: Colors.white, - error: AppColors.error, - onError: Colors.white, - background: Color(0xFF0F172A), - onBackground: Colors.white, - ), - scaffoldBackgroundColor: const Color(0xFF0F172A), - ); - } } class AppGradients { - static const primary = LinearGradient( - colors: [AppColors.gradientStart, AppColors.gradientEnd], - begin: Alignment.topLeft, - end: Alignment.bottomRight, - ); + // Light: bright brand blue → teal. Dark: deeper, more subdued. + static const _lightStart = Color(0xFF1A56DB); + static const _lightEnd = Color(0xFF00C9A7); + static const _darkStart = Color(0xFF1E3A8A); + static const _darkEnd = Color(0xFF0F766E); + + static LinearGradient get primary => LinearGradient( + colors: AppColors.isDark + ? const [_darkStart, _darkEnd] + : const [_lightStart, _lightEnd], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ); static const card = LinearGradient( colors: [Color(0xFF1A56DB), Color(0xFF2563EB)], diff --git a/lib/widgets/chart_block.dart b/lib/widgets/chart_block.dart new file mode 100644 index 0000000..1a092b9 --- /dev/null +++ b/lib/widgets/chart_block.dart @@ -0,0 +1,331 @@ +import 'package:flutter/material.dart'; +import 'package:fl_chart/fl_chart.dart'; +import 'package:flutter_markdown/flutter_markdown.dart'; +import 'package:markdown/markdown.dart' as md; +import '../theme.dart'; + +class ChartSpec { + final String type; + final String? title; + final List labels; + final List data; + + const ChartSpec({ + required this.type, + required this.labels, + required this.data, + this.title, + }); + + static ChartSpec? tryParse(String raw) { + final lines = raw.split('\n').where((l) => l.trim().isNotEmpty); + final map = {}; + for (final line in lines) { + final colon = line.indexOf(':'); + if (colon < 0) continue; + map[line.substring(0, colon).trim().toLowerCase()] = + line.substring(colon + 1).trim(); + } + final type = (map['type'] ?? 'bar').toLowerCase(); + final labels = (map['labels'] ?? '') + .split(',') + .map((s) => s.trim()) + .where((s) => s.isNotEmpty) + .toList(); + final data = (map['data'] ?? '') + .split(',') + .map((s) => double.tryParse(s.trim())) + .whereType() + .toList(); + if (labels.isEmpty || data.isEmpty || labels.length != data.length) { + return null; + } + return ChartSpec(type: type, title: map['title'], labels: labels, data: data); + } +} + +class ChartBlock extends StatelessWidget { + final ChartSpec spec; + const ChartBlock({super.key, required this.spec}); + + static const _palette = [ + AppColors.primaryLight, + AppColors.accent, + AppColors.warning, + AppColors.error, + AppColors.success, + AppColors.primaryDark, + AppColors.accentLight, + ]; + + @override + Widget build(BuildContext context) { + return Container( + margin: const EdgeInsets.symmetric(vertical: 8), + padding: const EdgeInsets.fromLTRB(4, 8, 4, 4), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (spec.title != null && spec.title!.isNotEmpty) ...[ + Text( + spec.title!, + style: TextStyle( + fontSize: 13, + fontWeight: FontWeight.w600, + color: AppColors.textPrimary, + ), + ), + const SizedBox(height: 8), + ], + SizedBox(height: 180, child: _buildChart()), + if (spec.type == 'pie') ...[ + const SizedBox(height: 8), + _buildLegend(), + ], + ], + ), + ); + } + + Widget _buildChart() { + switch (spec.type) { + case 'line': + return _scrollable(_buildLine()); + case 'pie': + return _buildPie(); + case 'bar': + default: + return _scrollable(_buildBar()); + } + } + + Widget _scrollable(Widget chart) { + const perPoint = 56.0; + final intrinsicWidth = spec.data.length * perPoint; + return LayoutBuilder( + builder: (context, constraints) { + final width = intrinsicWidth > constraints.maxWidth + ? intrinsicWidth + : constraints.maxWidth; + return SingleChildScrollView( + scrollDirection: Axis.horizontal, + physics: const BouncingScrollPhysics(), + child: SizedBox(width: width, child: chart), + ); + }, + ); + } + + Widget _buildBar() { + final maxY = spec.data.reduce((a, b) => a > b ? a : b); + return BarChart( + BarChartData( + alignment: BarChartAlignment.spaceAround, + maxY: maxY * 1.2, + barTouchData: BarTouchData(enabled: false), + gridData: FlGridData( + show: true, + drawVerticalLine: false, + getDrawingHorizontalLine: (_) => + FlLine(color: AppColors.divider, strokeWidth: 0.5), + ), + borderData: FlBorderData(show: false), + titlesData: FlTitlesData( + topTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)), + rightTitles: + const AxisTitles(sideTitles: SideTitles(showTitles: false)), + leftTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + reservedSize: 32, + getTitlesWidget: (v, _) => Text( + v.toInt().toString(), + style: TextStyle(fontSize: 10, color: AppColors.textTertiary), + ), + ), + ), + bottomTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + reservedSize: 22, + getTitlesWidget: (v, _) { + final idx = v.toInt(); + if (idx < 0 || idx >= spec.labels.length) { + return const SizedBox(); + } + return Padding( + padding: const EdgeInsets.only(top: 4), + child: Text( + spec.labels[idx], + style: + TextStyle(fontSize: 10, color: AppColors.textSecondary), + ), + ); + }, + ), + ), + ), + barGroups: [ + for (var i = 0; i < spec.data.length; i++) + BarChartGroupData( + x: i, + barRods: [ + BarChartRodData( + toY: spec.data[i], + color: _palette[i % _palette.length], + width: 18, + borderRadius: const BorderRadius.vertical( + top: Radius.circular(4), + ), + ), + ], + ), + ], + ), + ); + } + + Widget _buildLine() { + final maxY = spec.data.reduce((a, b) => a > b ? a : b); + final minY = spec.data.reduce((a, b) => a < b ? a : b); + final pad = (maxY - minY).abs() * 0.2 + 1; + return LineChart( + LineChartData( + minY: minY - pad, + maxY: maxY + pad, + gridData: FlGridData( + show: true, + drawVerticalLine: false, + getDrawingHorizontalLine: (_) => + FlLine(color: AppColors.divider, strokeWidth: 0.5), + ), + borderData: FlBorderData(show: false), + titlesData: FlTitlesData( + topTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)), + rightTitles: + const AxisTitles(sideTitles: SideTitles(showTitles: false)), + leftTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + reservedSize: 32, + getTitlesWidget: (v, _) => Text( + v.toInt().toString(), + style: TextStyle(fontSize: 10, color: AppColors.textTertiary), + ), + ), + ), + bottomTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + reservedSize: 22, + getTitlesWidget: (v, _) { + final idx = v.toInt(); + if (idx < 0 || idx >= spec.labels.length) { + return const SizedBox(); + } + return Padding( + padding: const EdgeInsets.only(top: 4), + child: Text( + spec.labels[idx], + style: + TextStyle(fontSize: 10, color: AppColors.textSecondary), + ), + ); + }, + ), + ), + ), + lineBarsData: [ + LineChartBarData( + spots: [ + for (var i = 0; i < spec.data.length; i++) + FlSpot(i.toDouble(), spec.data[i]), + ], + isCurved: true, + color: AppColors.primaryLight, + barWidth: 2.5, + dotData: FlDotData( + show: true, + getDotPainter: (spot, _, __, ___) => FlDotCirclePainter( + radius: 3, + color: AppColors.primaryLight, + strokeWidth: 1.5, + strokeColor: AppColors.surface, + ), + ), + belowBarData: BarAreaData( + show: true, + color: AppColors.primaryLight.withOpacity(0.15), + ), + ), + ], + ), + ); + } + + Widget _buildPie() { + final total = spec.data.fold(0, (a, b) => a + b); + return PieChart( + PieChartData( + sectionsSpace: 2, + centerSpaceRadius: 28, + sections: [ + for (var i = 0; i < spec.data.length; i++) + PieChartSectionData( + value: spec.data[i], + color: _palette[i % _palette.length], + radius: 50, + title: total == 0 + ? '' + : '${(spec.data[i] / total * 100).toStringAsFixed(0)}%', + titleStyle: const TextStyle( + color: Colors.white, + fontSize: 10, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ); + } + + Widget _buildLegend() { + return Wrap( + spacing: 12, + runSpacing: 4, + children: [ + for (var i = 0; i < spec.labels.length; i++) + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 10, + height: 10, + decoration: BoxDecoration( + color: _palette[i % _palette.length], + borderRadius: BorderRadius.circular(2), + ), + ), + const SizedBox(width: 4), + Text( + '${spec.labels[i]} ${spec.data[i].toInt()}', + style: TextStyle(fontSize: 11, color: AppColors.textSecondary), + ), + ], + ), + ], + ); + } +} + +class ChartCodeBuilder extends MarkdownElementBuilder { + @override + Widget? visitElementAfter(md.Element element, TextStyle? preferredStyle) { + final lang = element.attributes['class'] ?? ''; + if (!lang.contains('language-chart')) return null; + final raw = element.textContent; + final spec = ChartSpec.tryParse(raw); + if (spec == null) return null; + return ChartBlock(spec: spec); + } +} diff --git a/lib/widgets/markdown_message.dart b/lib/widgets/markdown_message.dart new file mode 100644 index 0000000..25625ec --- /dev/null +++ b/lib/widgets/markdown_message.dart @@ -0,0 +1,308 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_markdown/flutter_markdown.dart'; +import '../theme.dart'; + +class MarkdownMessage extends StatelessWidget { + final String data; + final MarkdownStyleSheet styleSheet; + final Map builders; + + const MarkdownMessage({ + super.key, + required this.data, + required this.styleSheet, + this.builders = const {}, + }); + + @override + Widget build(BuildContext context) { + final segments = _split(data); + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + for (final seg in segments) + if (seg.isTable) + _ScrollableTable(rows: seg.rows!, styleSheet: styleSheet) + else + MarkdownBody( + data: seg.text, + styleSheet: styleSheet, + builders: builders, + imageBuilder: (uri, title, alt) => + _MarkdownImage(uri: uri, alt: alt ?? title), + shrinkWrap: true, + ), + ], + ); + } + + static final _tableSep = RegExp(r'^\s*\|?\s*:?-+:?\s*(\|\s*:?-+:?\s*)+\|?\s*$'); + static final _tableRow = RegExp(r'^\s*\|.*\|\s*$'); + + List<_Segment> _split(String src) { + final lines = src.split('\n'); + final result = <_Segment>[]; + final buffer = []; + + void flushText() { + if (buffer.isEmpty) return; + final text = buffer.join('\n').trim(); + if (text.isNotEmpty) result.add(_Segment.text(text)); + buffer.clear(); + } + + for (var i = 0; i < lines.length; i++) { + final line = lines[i]; + final isHeader = _tableRow.hasMatch(line); + final next = i + 1 < lines.length ? lines[i + 1] : ''; + if (isHeader && _tableSep.hasMatch(next)) { + flushText(); + final rows = >[]; + rows.add(_cells(line)); + i += 2; + while (i < lines.length && _tableRow.hasMatch(lines[i])) { + rows.add(_cells(lines[i])); + i++; + } + i--; + result.add(_Segment.table(rows)); + } else { + buffer.add(line); + } + } + flushText(); + return result; + } + + List _cells(String line) { + var trimmed = line.trim(); + if (trimmed.startsWith('|')) trimmed = trimmed.substring(1); + if (trimmed.endsWith('|')) trimmed = trimmed.substring(0, trimmed.length - 1); + return trimmed.split('|').map((c) => c.trim()).toList(); + } +} + +class _Segment { + final String text; + final List>? rows; + bool get isTable => rows != null; + + _Segment.text(this.text) : rows = null; + _Segment.table(this.rows) : text = ''; +} + +class _MarkdownImage extends StatelessWidget { + final Uri uri; + final String? alt; + + const _MarkdownImage({required this.uri, this.alt}); + + @override + Widget build(BuildContext context) { + final url = uri.toString(); + final tag = 'md-img:$url'; + return Padding( + padding: const EdgeInsets.symmetric(vertical: 6), + child: GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + FocusManager.instance.primaryFocus?.unfocus(); + Navigator.of(context, rootNavigator: true).push( + PageRouteBuilder( + opaque: false, + barrierColor: Colors.black, + transitionDuration: const Duration(milliseconds: 240), + pageBuilder: (_, __, ___) => + _ImageViewer(url: url, heroTag: tag, alt: alt), + ), + ); + }, + child: Hero( + tag: tag, + child: ClipRRect( + borderRadius: BorderRadius.circular(10), + child: CachedNetworkImage( + imageUrl: url, + fit: BoxFit.cover, + width: double.infinity, + placeholder: (context, _) => AspectRatio( + aspectRatio: 16 / 9, + child: Container( + color: AppColors.divider.withOpacity(0.3), + alignment: Alignment.center, + child: const SizedBox( + width: 22, + height: 22, + child: CircularProgressIndicator(strokeWidth: 2), + ), + ), + ), + errorWidget: (context, _, __) => AspectRatio( + aspectRatio: 16 / 9, + child: Container( + color: AppColors.divider.withOpacity(0.3), + alignment: Alignment.center, + child: Icon( + Icons.broken_image_outlined, + color: AppColors.textTertiary, + size: 32, + ), + ), + ), + ), + ), + ), + ), + ); + } +} + +class _ImageViewer extends StatelessWidget { + final String url; + final String heroTag; + final String? alt; + + const _ImageViewer({required this.url, required this.heroTag, this.alt}); + + void _close(BuildContext context) { + FocusManager.instance.primaryFocus?.unfocus(); + Navigator.of(context).maybePop(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.black, + body: GestureDetector( + onTap: () => _close(context), + behavior: HitTestBehavior.opaque, + child: SafeArea( + child: Stack( + children: [ + Center( + child: Hero( + tag: heroTag, + child: InteractiveViewer( + minScale: 1, + maxScale: 4, + child: CachedNetworkImage( + imageUrl: url, + fit: BoxFit.contain, + placeholder: (context, _) => const Center( + child: CircularProgressIndicator( + strokeWidth: 2, + valueColor: + AlwaysStoppedAnimation(Colors.white), + ), + ), + errorWidget: (context, _, __) => const Icon( + Icons.broken_image_outlined, + color: Colors.white54, + size: 64, + ), + ), + ), + ), + ), + Positioned( + top: 8, + right: 8, + child: IconButton( + icon: const Icon(Icons.close, color: Colors.white), + onPressed: () => _close(context), + ), + ), + if (alt != null && alt!.isNotEmpty) + Positioned( + left: 16, + right: 16, + bottom: 16, + child: Text( + alt!, + textAlign: TextAlign.center, + style: const TextStyle( + color: Colors.white70, + fontSize: 13, + ), + ), + ), + ], + ), + ), + ), + ); + } +} + +class _ScrollableTable extends StatelessWidget { + final List> rows; + final MarkdownStyleSheet styleSheet; + + const _ScrollableTable({required this.rows, required this.styleSheet}); + + @override + Widget build(BuildContext context) { + if (rows.isEmpty) return const SizedBox.shrink(); + final header = rows.first; + final body = rows.skip(1).toList(); + final cols = header.length; + + final padding = styleSheet.tableCellsPadding ?? const EdgeInsets.all(8); + final headStyle = styleSheet.tableHead ?? + const TextStyle(fontWeight: FontWeight.w600); + final bodyStyle = styleSheet.tableBody ?? const TextStyle(); + final border = styleSheet.tableBorder ?? + TableBorder.all(color: AppColors.divider, width: 1); + + return Padding( + padding: const EdgeInsets.symmetric(vertical: 8), + child: LayoutBuilder( + builder: (context, constraints) { + return SingleChildScrollView( + scrollDirection: Axis.horizontal, + physics: const BouncingScrollPhysics(), + child: ConstrainedBox( + constraints: BoxConstraints(minWidth: constraints.maxWidth), + child: Table( + defaultColumnWidth: const IntrinsicColumnWidth(), + border: border, + children: [ + TableRow( + decoration: BoxDecoration( + color: AppColors.primary.withOpacity(0.06), + ), + children: [ + for (var c = 0; c < cols; c++) + Padding( + padding: padding, + child: Text( + c < header.length ? header[c] : '', + style: headStyle, + ), + ), + ], + ), + for (final row in body) + TableRow( + children: [ + for (var c = 0; c < cols; c++) + Padding( + padding: padding, + child: Text( + c < row.length ? row[c] : '', + style: bodyStyle, + ), + ), + ], + ), + ], + ), + ), + ); + }, + ), + ); + } +} diff --git a/lib/widgets/skeleton.dart b/lib/widgets/skeleton.dart new file mode 100644 index 0000000..257065f --- /dev/null +++ b/lib/widgets/skeleton.dart @@ -0,0 +1,310 @@ +import 'package:flutter/material.dart'; +import 'package:shimmer/shimmer.dart'; +import '../theme.dart'; + +class SkeletonBox extends StatelessWidget { + final double? width; + final double height; + final double radius; + + const SkeletonBox({ + super.key, + this.width, + required this.height, + this.radius = 6, + }); + + @override + Widget build(BuildContext context) { + return Container( + width: width, + height: height, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(radius), + ), + ); + } +} + +class SkeletonShimmer extends StatelessWidget { + final Widget child; + + const SkeletonShimmer({super.key, required this.child}); + + @override + Widget build(BuildContext context) { + final base = AppColors.isDark + ? const Color(0xFF334155) + : const Color(0xFFE2E8F0); + final highlight = AppColors.isDark + ? const Color(0xFF475569) + : const Color(0xFFF1F5F9); + return Shimmer.fromColors( + baseColor: base, + highlightColor: highlight, + period: const Duration(milliseconds: 1400), + child: child, + ); + } +} + +class EventCardSkeleton extends StatelessWidget { + const EventCardSkeleton({super.key}); + + @override + Widget build(BuildContext context) { + return Container( + margin: const EdgeInsets.only(bottom: 12), + decoration: BoxDecoration( + color: AppColors.surface, + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.03), + blurRadius: 10, + offset: const Offset(0, 2), + ), + ], + ), + child: SkeletonShimmer( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.all(16), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SkeletonBox(width: 44, height: 44, radius: 12), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: const [ + Expanded(child: SkeletonBox(height: 16, radius: 6)), + SizedBox(width: 8), + SkeletonBox(width: 48, height: 18, radius: 10), + ], + ), + const SizedBox(height: 10), + const SkeletonBox(height: 12, radius: 4), + const SizedBox(height: 6), + const SkeletonBox(width: 180, height: 12, radius: 4), + ], + ), + ), + ], + ), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + child: Row( + children: const [ + SkeletonBox(width: 160, height: 12, radius: 4), + ], + ), + ), + ], + ), + ), + ); + } +} + +class WorkOrderCardSkeleton extends StatelessWidget { + const WorkOrderCardSkeleton({super.key}); + + @override + Widget build(BuildContext context) { + return Container( + margin: const EdgeInsets.only(bottom: 12), + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: AppColors.surface, + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.03), + blurRadius: 10, + offset: const Offset(0, 2), + ), + ], + ), + child: SkeletonShimmer( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: const [ + Expanded(child: SkeletonBox(height: 16, radius: 6)), + SizedBox(width: 8), + SkeletonBox(width: 56, height: 18, radius: 10), + ], + ), + const SizedBox(height: 12), + const SkeletonBox(height: 12, radius: 4), + const SizedBox(height: 6), + const SkeletonBox(width: 220, height: 12, radius: 4), + const SizedBox(height: 14), + Row( + children: const [ + SkeletonBox(width: 80, height: 12, radius: 4), + SizedBox(width: 12), + SkeletonBox(width: 100, height: 12, radius: 4), + Spacer(), + SkeletonBox(width: 36, height: 16, radius: 4), + ], + ), + ], + ), + ), + ); + } +} + +class OrderCardSkeleton extends StatelessWidget { + const OrderCardSkeleton({super.key}); + + @override + Widget build(BuildContext context) { + return Container( + margin: const EdgeInsets.only(bottom: 12), + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: AppColors.surface, + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.03), + blurRadius: 10, + offset: const Offset(0, 2), + ), + ], + ), + child: SkeletonShimmer( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: const [ + SkeletonBox(width: 140, height: 14, radius: 4), + Spacer(), + SkeletonBox(width: 56, height: 18, radius: 10), + ], + ), + const SizedBox(height: 14), + const SkeletonBox(height: 15, width: 200, radius: 4), + const SizedBox(height: 10), + Row( + children: const [ + SkeletonBox(width: 90, height: 12, radius: 4), + SizedBox(width: 16), + SkeletonBox(width: 50, height: 12, radius: 4), + ], + ), + const SizedBox(height: 14), + Row( + children: const [ + SkeletonBox(width: 100, height: 12, radius: 4), + Spacer(), + SkeletonBox(width: 70, height: 16, radius: 4), + ], + ), + ], + ), + ), + ); + } +} + +class DetailPageSkeleton extends StatelessWidget { + const DetailPageSkeleton({super.key}); + + @override + Widget build(BuildContext context) { + return SkeletonShimmer( + child: SingleChildScrollView( + padding: const EdgeInsets.all(20), + physics: const NeverScrollableScrollPhysics(), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: AppColors.surface, + borderRadius: BorderRadius.circular(16), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: const [ + SkeletonBox(width: 100, height: 14, radius: 4), + SizedBox(height: 12), + SkeletonBox(height: 22, radius: 6), + SizedBox(height: 10), + SkeletonBox(width: 220, height: 14, radius: 4), + ], + ), + ), + const SizedBox(height: 20), + _block(height: 130), + const SizedBox(height: 20), + _block(height: 130), + const SizedBox(height: 20), + _block(height: 100), + ], + ), + ), + ); + } + + Widget _block({required double height}) { + return Container( + width: double.infinity, + height: height, + decoration: BoxDecoration( + color: AppColors.surface, + borderRadius: BorderRadius.circular(16), + ), + child: Padding( + padding: const EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: const [ + SkeletonBox(width: 80, height: 14, radius: 4), + SizedBox(height: 14), + SkeletonBox(height: 14, radius: 4), + SizedBox(height: 8), + SkeletonBox(width: 200, height: 14, radius: 4), + ], + ), + ), + ); + } +} + +class SkeletonList extends StatelessWidget { + final WidgetBuilder itemBuilder; + final int count; + final EdgeInsetsGeometry padding; + + const SkeletonList({ + super.key, + required this.itemBuilder, + this.count = 5, + this.padding = const EdgeInsets.all(16), + }); + + @override + Widget build(BuildContext context) { + return ListView.builder( + padding: padding, + itemCount: count, + physics: const NeverScrollableScrollPhysics(), + itemBuilder: (context, _) => itemBuilder(context), + ); + } +} diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 250040f..4557e79 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -8,13 +8,17 @@ import Foundation import file_selector_macos import mobile_scanner import shared_preferences_foundation +import speech_to_text import sqflite_darwin import url_launcher_macos +import vosk_flutter_service func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) MobileScannerPlugin.register(with: registry.registrar(forPlugin: "MobileScannerPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) + SpeechToTextPlugin.register(with: registry.registrar(forPlugin: "SpeechToTextPlugin")) SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) + VoskFlutterPlugin.register(with: registry.registrar(forPlugin: "VoskFlutterPlugin")) } diff --git a/pubspec.lock b/pubspec.lock index f126f70..c65721e 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -6,7 +6,7 @@ packages: description: name: archive sha256: a96e8b390886ee8abb49b7bd3ac8df6f451c621619f52a26e815fdcf568959ff - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "4.0.9" args: @@ -14,7 +14,7 @@ packages: description: name: args sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.7.0" async: @@ -22,7 +22,7 @@ packages: description: name: async sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.13.1" badges: @@ -30,15 +30,23 @@ packages: description: name: badges sha256: cf1c88fb3777df69ccd630b80de5267f54efa4a39381b0404a7c03d56cb7c041 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.2.0" + build_cli_annotations: + dependency: transitive + description: + name: build_cli_annotations + sha256: e563c2e01de8974566a1998410d3f6f03521788160a02503b0b1f1a46c7b3d95 + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.1" cached_network_image: dependency: "direct main" description: name: cached_network_image sha256: "7c1183e361e5c8b0a0f21a28401eecdbde252441106a9816400dd4c2b2424916" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.4.1" cached_network_image_platform_interface: @@ -46,7 +54,7 @@ packages: description: name: cached_network_image_platform_interface sha256: "35814b016e37fbdc91f7ae18c8caf49ba5c88501813f73ce8a07027a395e2829" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "4.1.1" cached_network_image_web: @@ -54,23 +62,31 @@ packages: description: name: cached_network_image_web sha256: "980842f4e8e2535b8dbd3d5ca0b1f0ba66bf61d14cc3a17a9b4788a3685ba062" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.3.1" characters: dependency: transitive description: name: characters - sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 - url: "https://pub.dev" + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b + url: "https://pub.flutter-io.cn" source: hosted - version: "1.4.0" + version: "1.4.1" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.0.4" clock: dependency: transitive description: name: clock sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.1.2" code_assets: @@ -78,7 +94,7 @@ packages: description: name: code_assets sha256: "83ccdaa064c980b5596c35dd64a8d3ecc68620174ab9b90b6343b753aa721687" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.0.0" collection: @@ -86,7 +102,7 @@ packages: description: name: collection sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.19.1" cross_file: @@ -94,7 +110,7 @@ packages: description: name: cross_file sha256: "28bb3ae56f117b5aec029d702a90f57d285cd975c3c5c281eaca38dbc47c5937" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.3.5+2" crypto: @@ -102,7 +118,7 @@ packages: description: name: crypto sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.0.7" dio: @@ -110,7 +126,7 @@ packages: description: name: dio sha256: aff32c08f92787a557dd5c0145ac91536481831a01b4648136373cddb0e64f8c - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "5.9.2" dio_web_adapter: @@ -118,15 +134,23 @@ packages: description: name: dio_web_adapter sha256: "2f9e64323a7c3c7ef69567d5c800424a11f8337b8b228bad02524c9fb3c1f340" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.2" + equatable: + dependency: transitive + description: + name: equatable + sha256: "3e0141505477fd8ad55d6eb4e7776d3fe8430be8e497ccb1521370c3f21a3e2b" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.0.8" ffi: dependency: transitive description: name: ffi sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.2.0" file: @@ -134,7 +158,7 @@ packages: description: name: file sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "7.0.1" file_selector_linux: @@ -142,7 +166,7 @@ packages: description: name: file_selector_linux sha256: "2567f398e06ac72dcf2e98a0c95df2a9edd03c2c2e0cacd4780f20cdf56263a0" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.9.4" file_selector_macos: @@ -150,7 +174,7 @@ packages: description: name: file_selector_macos sha256: "5e0bbe9c312416f1787a68259ea1505b52f258c587f12920422671807c4d618a" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.9.5" file_selector_platform_interface: @@ -158,7 +182,7 @@ packages: description: name: file_selector_platform_interface sha256: "35e0bd61ebcdb91a3505813b055b09b79dfdc7d0aee9c09a7ba59ae4bb13dc85" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.7.0" file_selector_windows: @@ -166,7 +190,7 @@ packages: description: name: file_selector_windows sha256: "62197474ae75893a62df75939c777763d39c2bc5f73ce5b88497208bc269abfd" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.9.3+5" fixnum: @@ -174,9 +198,17 @@ packages: description: name: fixnum sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.1.1" + fl_chart: + dependency: "direct main" + description: + name: fl_chart + sha256: d0f0d49112f2f4b192481c16d05b6418bd7820e021e265a3c22db98acf7ed7fb + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.68.0" flutter: dependency: "direct main" description: flutter @@ -187,7 +219,7 @@ packages: description: name: flutter_animate sha256: "7befe2d3252728afb77aecaaea1dec88a89d35b9b1d2eea6d04479e8af9117b5" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "4.5.2" flutter_cache_manager: @@ -195,7 +227,7 @@ packages: description: name: flutter_cache_manager sha256: "400b6592f16a4409a7f2bb929a9a7e38c72cceb8ffb99ee57bbf2cb2cecf8386" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.4.1" flutter_hooks: @@ -203,7 +235,7 @@ packages: description: name: flutter_hooks sha256: cde36b12f7188c85286fba9b38cc5a902e7279f36dd676967106c041dc9dde70 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.20.5" flutter_localizations: @@ -216,7 +248,7 @@ packages: description: name: flutter_markdown sha256: "04c4722cc36ec5af38acc38ece70d22d3c2123c61305d555750a091517bbe504" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.6.23" flutter_plugin_android_lifecycle: @@ -224,7 +256,7 @@ packages: description: name: flutter_plugin_android_lifecycle sha256: "38d1c268de9097ff59cf0e844ac38759fc78f76836d37edad06fa21e182055a0" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.0.34" flutter_riverpod: @@ -232,7 +264,7 @@ packages: description: name: flutter_riverpod sha256: "9532ee6db4a943a1ed8383072a2e3eeda041db5657cdf6d2acecf3c21ecbe7e1" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.6.1" flutter_shaders: @@ -240,7 +272,7 @@ packages: description: name: flutter_shaders sha256: "34794acadd8275d971e02df03afee3dee0f98dbfb8c4837082ad0034f612a3e2" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.1.3" flutter_slidable: @@ -248,7 +280,7 @@ packages: description: name: flutter_slidable sha256: a857de7ea701f276fd6a6c4c67ae885b60729a3449e42766bb0e655171042801 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.1.2" flutter_web_plugins: @@ -261,7 +293,7 @@ packages: description: name: freezed_annotation sha256: c2e2d632dd9b8a2b7751117abcfc2b4888ecfe181bd9fca7170d9ef02e595fe2 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.4.4" glob: @@ -269,7 +301,7 @@ packages: description: name: glob sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.3" go_router: @@ -277,7 +309,7 @@ packages: description: name: go_router sha256: b465e99ce64ba75e61c8c0ce3d87b66d8ac07f0b35d0a7e0263fcfc10f99e836 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "13.2.5" google_fonts: @@ -285,7 +317,7 @@ packages: description: name: google_fonts sha256: ba03d03bcaa2f6cb7bd920e3b5027181db75ab524f8891c8bc3aa603885b8055 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "6.3.3" hive: @@ -293,7 +325,7 @@ packages: description: name: hive sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.2.3" hive_flutter: @@ -301,7 +333,7 @@ packages: description: name: hive_flutter sha256: dca1da446b1d808a51689fb5d0c6c9510c0a2ba01e22805d492c73b68e33eecc - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.1.0" hooks: @@ -309,7 +341,7 @@ packages: description: name: hooks sha256: "025f060e86d2d4c3c47b56e33caf7f93bf9283340f26d23424ebcfccf34f621e" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.0.3" hooks_riverpod: @@ -317,7 +349,7 @@ packages: description: name: hooks_riverpod sha256: "70bba33cfc5670c84b796e6929c54b8bc5be7d0fe15bb28c2560500b9ad06966" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.6.1" http: @@ -325,7 +357,7 @@ packages: description: name: http sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.6.0" http_parser: @@ -333,7 +365,7 @@ packages: description: name: http_parser sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "4.1.2" image_picker: @@ -341,7 +373,7 @@ packages: description: name: image_picker sha256: "91c025426c2881c551100bce834e201c835a170151545f58d17da5180ca7d9ac" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.2.2" image_picker_android: @@ -349,7 +381,7 @@ packages: description: name: image_picker_android sha256: d5b3e1774af29c9ab00103afb0d4614070f924d2e0057ac867ec98800114793f - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.8.13+17" image_picker_for_web: @@ -357,7 +389,7 @@ packages: description: name: image_picker_for_web sha256: "66257a3191ab360d23a55c8241c91a6e329d31e94efa7be9cf7a212e65850214" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.1.1" image_picker_ios: @@ -365,7 +397,7 @@ packages: description: name: image_picker_ios sha256: b9c4a438a9ff4f60808c9cf0039b93a42bb6c2211ef6ebb647394b2b3fa84588 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.8.13+6" image_picker_linux: @@ -373,7 +405,7 @@ packages: description: name: image_picker_linux sha256: "1f81c5f2046b9ab724f85523e4af65be1d47b038160a8c8deed909762c308ed4" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.2.2" image_picker_macos: @@ -381,7 +413,7 @@ packages: description: name: image_picker_macos sha256: "86f0f15a309de7e1a552c12df9ce5b59fe927e71385329355aec4776c6a8ec91" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.2.2+1" image_picker_platform_interface: @@ -389,7 +421,7 @@ packages: description: name: image_picker_platform_interface sha256: "567e056716333a1647c64bb6bd873cff7622233a5c3f694be28a583d4715690c" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.11.1" image_picker_windows: @@ -397,7 +429,7 @@ packages: description: name: image_picker_windows sha256: d248c86554a72b5495a31c56f060cf73a41c7ff541689327b1a7dbccc33adfae - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.2.2" intl: @@ -405,7 +437,7 @@ packages: description: name: intl sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.20.2" jni: @@ -413,7 +445,7 @@ packages: description: name: jni sha256: c2230682d5bc2362c1c9e8d3c7f406d9cbba23ab3f2e203a025dd47e0fb2e68f - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.0.0" jni_flutter: @@ -421,7 +453,7 @@ packages: description: name: jni_flutter sha256: "8b59e590786050b1cd866677dddaf76b1ade5e7bc751abe04b86e84d379d3ba6" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.0.1" js: @@ -429,23 +461,23 @@ packages: description: name: js sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.7.2" json_annotation: dependency: "direct main" description: name: json_annotation - sha256: cb09e7dac6210041fad964ed7fbee004f14258b4eca4040f72d1234062ace4c8 - url: "https://pub.dev" + sha256: "2a743920d81b7910627f68ee2c9ac1fc0bfee32b9fc3403587d7c6791ca12f80" + url: "https://pub.flutter-io.cn" source: hosted - version: "4.11.0" + version: "4.12.0" logging: dependency: transitive description: name: logging sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.3.0" lottie: @@ -453,7 +485,7 @@ packages: description: name: lottie sha256: "8b6359a7422167014aa73ce763fa133fb832065dcc0ac4d1dec1f603a5cef7d0" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.3.3" markdown: @@ -461,23 +493,23 @@ packages: description: name: markdown sha256: ee85086ad7698b42522c6ad42fe195f1b9898e4d974a1af4576c1a3a176cada9 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "7.3.1" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec - url: "https://pub.dev" + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" + url: "https://pub.flutter-io.cn" source: hosted - version: "0.11.1" + version: "0.13.0" meta: dependency: transitive description: name: meta sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.17.0" mime: @@ -485,7 +517,7 @@ packages: description: name: mime sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.0.0" mobile_scanner: @@ -493,7 +525,7 @@ packages: description: name: mobile_scanner sha256: "1b60b8f9d4ce0cb0e7d7bc223c955d083a0737bee66fa1fcfe5de48225e0d5b3" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.5.7" native_toolchain_c: @@ -501,7 +533,7 @@ packages: description: name: native_toolchain_c sha256: "6ba77bb18063eebe9de401f5e6437e95e1438af0a87a3a39084fbd37c90df572" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.17.6" objective_c: @@ -509,7 +541,7 @@ packages: description: name: objective_c sha256: "100a1c87616ab6ed41ec263b083c0ef3261ee6cd1dc3b0f35f8ddfa4f996fe52" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "9.3.0" octo_image: @@ -517,7 +549,7 @@ packages: description: name: octo_image sha256: "34faa6639a78c7e3cbe79be6f9f96535867e879748ade7d17c9b1ae7536293bd" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.0" package_config: @@ -525,7 +557,7 @@ packages: description: name: package_config sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.2.0" path: @@ -533,7 +565,7 @@ packages: description: name: path sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.9.1" path_provider: @@ -541,7 +573,7 @@ packages: description: name: path_provider sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.5" path_provider_android: @@ -549,7 +581,7 @@ packages: description: name: path_provider_android sha256: "69cbd515a62b94d32a7944f086b2f82b4ac40a1d45bebfc00813a430ab2dabcd" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.3.1" path_provider_foundation: @@ -557,7 +589,7 @@ packages: description: name: path_provider_foundation sha256: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.6.0" path_provider_linux: @@ -565,7 +597,7 @@ packages: description: name: path_provider_linux sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.2.1" path_provider_platform_interface: @@ -573,7 +605,7 @@ packages: description: name: path_provider_platform_interface sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.2" path_provider_windows: @@ -581,15 +613,63 @@ packages: description: name: path_provider_windows sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.3.0" + permission_handler: + dependency: "direct main" + description: + name: permission_handler + sha256: bc917da36261b00137bbc8896bf1482169cd76f866282368948f032c8c1caae1 + url: "https://pub.flutter-io.cn" + source: hosted + version: "12.0.1" + permission_handler_android: + dependency: transitive + description: + name: permission_handler_android + sha256: "1e3bc410ca1bf84662104b100eb126e066cb55791b7451307f9708d4007350e6" + url: "https://pub.flutter-io.cn" + source: hosted + version: "13.0.1" + permission_handler_apple: + dependency: transitive + description: + name: permission_handler_apple + sha256: f000131e755c54cf4d84a5d8bd6e4149e262cc31c5a8b1d698de1ac85fa41023 + url: "https://pub.flutter-io.cn" + source: hosted + version: "9.4.7" + permission_handler_html: + dependency: transitive + description: + name: permission_handler_html + sha256: "38f000e83355abb3392140f6bc3030660cfaef189e1f87824facb76300b4ff24" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.1.3+5" + permission_handler_platform_interface: + dependency: transitive + description: + name: permission_handler_platform_interface + sha256: eb99b295153abce5d683cac8c02e22faab63e50679b937fa1bf67d58bb282878 + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.3.0" + permission_handler_windows: + dependency: transitive + description: + name: permission_handler_windows + sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.2.1" platform: dependency: transitive description: name: platform sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.1.6" plugin_platform_interface: @@ -597,7 +677,7 @@ packages: description: name: plugin_platform_interface sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.8" posix: @@ -605,7 +685,7 @@ packages: description: name: posix sha256: "185ef7606574f789b40f289c233efa52e96dead518aed988e040a10737febb07" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "6.5.0" pub_semver: @@ -613,15 +693,23 @@ packages: description: name: pub_semver sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.2.0" + pubspec_parse: + dependency: transitive + description: + name: pubspec_parse + sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.5.0" pull_to_refresh_flutter3: dependency: "direct main" description: name: pull_to_refresh_flutter3 sha256: "37a88d901cca9a46dbdd46523de8e7b35a3e58634a0e775b1a5904981f69b353" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.0.2" record_use: @@ -629,7 +717,7 @@ packages: description: name: record_use sha256: "2551bd8eecfe95d14ae75f6021ad0248be5c27f138c2ec12fcb52b500b3ba1ed" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.6.0" riverpod: @@ -637,7 +725,7 @@ packages: description: name: riverpod sha256: "59062512288d3056b2321804332a13ffdd1bf16df70dcc8e506e411280a72959" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.6.1" rxdart: @@ -645,7 +733,7 @@ packages: description: name: rxdart sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.28.0" shared_preferences: @@ -653,7 +741,7 @@ packages: description: name: shared_preferences sha256: c3025c5534b01739267eb7d76959bbc25a6d10f6988e1c2a3036940133dd10bf - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.5.5" shared_preferences_android: @@ -661,7 +749,7 @@ packages: description: name: shared_preferences_android sha256: e8d4762b1e2e8578fc4d0fd548cebf24afd24f49719c08974df92834565e2c53 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.4.23" shared_preferences_foundation: @@ -669,7 +757,7 @@ packages: description: name: shared_preferences_foundation sha256: "4e7eaffc2b17ba398759f1151415869a34771ba11ebbccd1b0145472a619a64f" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.5.6" shared_preferences_linux: @@ -677,7 +765,7 @@ packages: description: name: shared_preferences_linux sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.4.1" shared_preferences_platform_interface: @@ -685,7 +773,7 @@ packages: description: name: shared_preferences_platform_interface sha256: "649dc798a33931919ea356c4305c2d1f81619ea6e92244070b520187b5140ef9" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.4.2" shared_preferences_web: @@ -693,7 +781,7 @@ packages: description: name: shared_preferences_web sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.4.3" shared_preferences_windows: @@ -701,7 +789,7 @@ packages: description: name: shared_preferences_windows sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.4.1" shimmer: @@ -709,7 +797,7 @@ packages: description: name: shimmer sha256: "5f88c883a22e9f9f299e5ba0e4f7e6054857224976a5d9f839d4ebdc94a14ac9" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.0.0" sky_engine: @@ -722,15 +810,39 @@ packages: description: name: source_span sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.10.2" + speech_to_text: + dependency: "direct main" + description: + name: speech_to_text + sha256: "75587f7400f485fdf166beacd471549d98fe5d58e634f708916bb65dec05d6a4" + url: "https://pub.flutter-io.cn" + source: hosted + version: "7.4.0" + speech_to_text_platform_interface: + dependency: transitive + description: + name: speech_to_text_platform_interface + sha256: a7e16e02853853ed7534ac2bde9a1c4f39c8879970a7974ac6ff832d4bdaa4b0 + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.4.0" + speech_to_text_windows: + dependency: transitive + description: + name: speech_to_text_windows + sha256: "2d1d10565b23262386b453b33656299608dc7a66784453735d6c1318f13f44d7" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.1" sqflite: dependency: transitive description: name: sqflite sha256: "564cfed0746fe53140c23b70b308e045c3b31f17778f2f326ccb7d804ea0250a" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.4.2+1" sqflite_android: @@ -738,7 +850,7 @@ packages: description: name: sqflite_android sha256: "881e28efdcc9950fd8e9bb42713dcf1103e62a2e7168f23c9338d82db13dec40" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.4.2+3" sqflite_common: @@ -746,7 +858,7 @@ packages: description: name: sqflite_common sha256: f8a08a13fb8f0f8c590df89d745000bed44a673ed94bac846739e1a016875c21 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.5.7" sqflite_darwin: @@ -754,7 +866,7 @@ packages: description: name: sqflite_darwin sha256: "279832e5cde3fe99e8571879498c9211f3ca6391b0d818df4e17d9fff5c6ccb3" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.4.2" sqflite_platform_interface: @@ -762,7 +874,7 @@ packages: description: name: sqflite_platform_interface sha256: "8dd4515c7bdcae0a785b0062859336de775e8c65db81ae33dd5445f35be61920" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.4.0" stack_trace: @@ -770,7 +882,7 @@ packages: description: name: stack_trace sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.12.1" state_notifier: @@ -778,7 +890,7 @@ packages: description: name: state_notifier sha256: b8677376aa54f2d7c58280d5a007f9e8774f1968d1fb1c096adcb4792fba29bb - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.0.0" string_scanner: @@ -786,23 +898,23 @@ packages: description: name: string_scanner sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.4.1" synchronized: dependency: transitive description: name: synchronized - sha256: c254ade258ec8282947a0acbbc90b9575b4f19673533ee46f2f6e9b3aeefd7c0 - url: "https://pub.dev" + sha256: "63896c27e81b28f8cb4e69ead0d3e8f03f1d1e5fc531a3e579cabed6a2c7c9e5" + url: "https://pub.flutter-io.cn" source: hosted - version: "3.4.0" + version: "3.4.0+1" term_glyph: dependency: transitive description: name: term_glyph sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.2.2" typed_data: @@ -810,7 +922,7 @@ packages: description: name: typed_data sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.4.0" url_launcher: @@ -818,7 +930,7 @@ packages: description: name: url_launcher sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "6.3.2" url_launcher_android: @@ -826,7 +938,7 @@ packages: description: name: url_launcher_android sha256: "3bb000251e55d4a209aa0e2e563309dc9bb2befea2295fd0cec1f51760aac572" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "6.3.29" url_launcher_ios: @@ -834,7 +946,7 @@ packages: description: name: url_launcher_ios sha256: "580fe5dfb51671ae38191d316e027f6b76272b026370708c2d898799750a02b0" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "6.4.1" url_launcher_linux: @@ -842,7 +954,7 @@ packages: description: name: url_launcher_linux sha256: d5e14138b3bc193a0f63c10a53c94b91d399df0512b1f29b94a043db7482384a - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.2.2" url_launcher_macos: @@ -850,7 +962,7 @@ packages: description: name: url_launcher_macos sha256: "368adf46f71ad3c21b8f06614adb38346f193f3a59ba8fe9a2fd74133070ba18" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.2.5" url_launcher_platform_interface: @@ -858,7 +970,7 @@ packages: description: name: url_launcher_platform_interface sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.3.2" url_launcher_web: @@ -866,7 +978,7 @@ packages: description: name: url_launcher_web sha256: "85c81589622fbc87c1c683aaea164d3604a7777495a79d91e39ffcdec39ddb34" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.4.3" url_launcher_windows: @@ -874,7 +986,7 @@ packages: description: name: url_launcher_windows sha256: "712c70ab1b99744ff066053cbe3e80c73332b38d46e5e945c98689b2e66fc15f" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.1.5" uuid: @@ -882,7 +994,7 @@ packages: description: name: uuid sha256: "1fef9e8e11e2991bb773070d4656b7bd5d850967a2456cfc83cf47925ba79489" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "4.5.3" vector_math: @@ -890,15 +1002,23 @@ packages: description: name: vector_math sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.2.0" + vosk_flutter_service: + dependency: "direct main" + description: + name: vosk_flutter_service + sha256: "3a33da4ad222bd62c180adc35c594d30a2ae7115c7a7276a5f3a1e5c19dbcf59" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.1.1" web: dependency: transitive description: name: web sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.1.1" xdg_directories: @@ -906,7 +1026,7 @@ packages: description: name: xdg_directories sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.1.0" yaml: @@ -914,9 +1034,9 @@ packages: description: name: yaml sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.1.3" sdks: - dart: ">=3.10.3 <4.0.0" + dart: ">=3.11.0 <4.0.0" flutter: ">=3.38.4" diff --git a/pubspec.yaml b/pubspec.yaml index d07a2b6..e723efe 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -22,6 +22,7 @@ dependencies: shared_preferences: ^2.2.2 mobile_scanner: ^3.5.5 flutter_markdown: ^0.6.18 + fl_chart: ^0.68.0 image_picker: ^1.0.7 url_launcher: ^6.2.2 shimmer: ^3.0.0 @@ -35,7 +36,13 @@ dependencies: uuid: ^4.2.2 freezed_annotation: ^2.4.1 json_annotation: ^4.8.1 + speech_to_text: ^7.4.0 + permission_handler: ^12.0.0 + vosk_flutter_service: ^0.1.1 flutter: uses-material-design: true - generate: true \ No newline at end of file + generate: true + assets: + - assets/logo.png + - assets/models/vosk-model-small-cn-0.22.zip