commit 8156e8efbf8eaed436b69f96316ce3106ebfcf25 Author: kongbeiwu Date: Fri May 15 14:41:37 2026 +0800 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3820a95 --- /dev/null +++ b/.gitignore @@ -0,0 +1,45 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.build/ +.buildlog/ +.history +.svn/ +.swiftpm/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins-dependencies +.pub-cache/ +.pub/ +/build/ +/coverage/ + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/.metadata b/.metadata new file mode 100644 index 0000000..7dbc69a --- /dev/null +++ b/.metadata @@ -0,0 +1,39 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "f6ff1529fd6d8af5f706051d9251ac9231c83407" + channel: "stable" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: f6ff1529fd6d8af5f706051d9251ac9231c83407 + base_revision: f6ff1529fd6d8af5f706051d9251ac9231c83407 + - platform: android + create_revision: f6ff1529fd6d8af5f706051d9251ac9231c83407 + base_revision: f6ff1529fd6d8af5f706051d9251ac9231c83407 + - platform: ios + create_revision: f6ff1529fd6d8af5f706051d9251ac9231c83407 + base_revision: f6ff1529fd6d8af5f706051d9251ac9231c83407 + - platform: macos + create_revision: f6ff1529fd6d8af5f706051d9251ac9231c83407 + base_revision: f6ff1529fd6d8af5f706051d9251ac9231c83407 + - platform: web + create_revision: f6ff1529fd6d8af5f706051d9251ac9231c83407 + base_revision: f6ff1529fd6d8af5f706051d9251ac9231c83407 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/README.md b/README.md new file mode 100644 index 0000000..722eb8e --- /dev/null +++ b/README.md @@ -0,0 +1,16 @@ +# zhinian_manage + +A new Flutter project. + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) + +For help getting started with Flutter development, view the +[online documentation](https://docs.flutter.dev/), which offers tutorials, +samples, guidance on mobile development, and a full API reference. diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 0000000..1cf403e --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,7 @@ +include: package:flutter_lints/flutter.yaml + +linter: + rules: + prefer_const_constructors: false + prefer_const_literals_to_create_immutables: false + avoid_print: false \ No newline at end of file diff --git a/android/.gitignore b/android/.gitignore new file mode 100644 index 0000000..be3943c --- /dev/null +++ b/android/.gitignore @@ -0,0 +1,14 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java +.cxx/ + +# Remember to never publicly share your keystore. +# See https://flutter.dev/to/reference-keystore +key.properties +**/*.keystore +**/*.jks diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts new file mode 100644 index 0000000..5523449 --- /dev/null +++ b/android/app/build.gradle.kts @@ -0,0 +1,44 @@ +plugins { + id("com.android.application") + id("kotlin-android") + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id("dev.flutter.flutter-gradle-plugin") +} + +android { + namespace = "com.example.zhinian_manage" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_17.toString() + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId = "com.example.zhinian_manage" + // You can update the following values to match your application needs. + // For more information, see: https://flutter.dev/to/review-gradle-config. + minSdk = flutter.minSdkVersion + targetSdk = flutter.targetSdkVersion + versionCode = flutter.versionCode + versionName = flutter.versionName + } + + buildTypes { + release { + // 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") + } + } +} + +flutter { + source = "../.." +} diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..87ae0f5 --- /dev/null +++ b/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/android/app/src/main/kotlin/com/example/zhinian_manage/MainActivity.kt b/android/app/src/main/kotlin/com/example/zhinian_manage/MainActivity.kt new file mode 100644 index 0000000..d011dcb --- /dev/null +++ b/android/app/src/main/kotlin/com/example/zhinian_manage/MainActivity.kt @@ -0,0 +1,5 @@ +package com.example.zhinian_manage + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity : FlutterActivity() diff --git a/android/app/src/main/res/drawable-v21/launch_background.xml b/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/android/app/src/main/res/drawable/launch_background.xml b/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..db77bb4 Binary files /dev/null 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 new file mode 100644 index 0000000..17987b7 Binary files /dev/null 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 new file mode 100644 index 0000000..09d4391 Binary files /dev/null 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 new file mode 100644 index 0000000..d5f1c8d Binary files /dev/null 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 new file mode 100644 index 0000000..4d6372e Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/values-night/styles.xml b/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..06952be --- /dev/null +++ b/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..cb1ef88 --- /dev/null +++ b/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/android/app/src/profile/AndroidManifest.xml b/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/android/build.gradle.kts b/android/build.gradle.kts new file mode 100644 index 0000000..dbee657 --- /dev/null +++ b/android/build.gradle.kts @@ -0,0 +1,24 @@ +allprojects { + repositories { + google() + mavenCentral() + } +} + +val newBuildDir: Directory = + rootProject.layout.buildDirectory + .dir("../../build") + .get() +rootProject.layout.buildDirectory.value(newBuildDir) + +subprojects { + val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) + project.layout.buildDirectory.value(newSubprojectBuildDir) +} +subprojects { + project.evaluationDependsOn(":app") +} + +tasks.register("clean") { + delete(rootProject.layout.buildDirectory) +} diff --git a/android/gradle.properties b/android/gradle.properties new file mode 100644 index 0000000..fbee1d8 --- /dev/null +++ b/android/gradle.properties @@ -0,0 +1,2 @@ +org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError +android.useAndroidX=true diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..e4ef43f --- /dev/null +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-all.zip diff --git a/android/settings.gradle.kts b/android/settings.gradle.kts new file mode 100644 index 0000000..ca7fe06 --- /dev/null +++ b/android/settings.gradle.kts @@ -0,0 +1,26 @@ +pluginManagement { + val flutterSdkPath = + run { + val properties = java.util.Properties() + file("local.properties").inputStream().use { properties.load(it) } + val flutterSdkPath = properties.getProperty("flutter.sdk") + require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } + flutterSdkPath + } + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id("dev.flutter.flutter-plugin-loader") version "1.0.0" + id("com.android.application") version "8.11.1" apply false + id("org.jetbrains.kotlin.android") version "2.2.20" apply false +} + +include(":app") diff --git a/ios/.gitignore b/ios/.gitignore new file mode 100644 index 0000000..7a7f987 --- /dev/null +++ b/ios/.gitignore @@ -0,0 +1,34 @@ +**/dgph +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/ios/Flutter/AppFrameworkInfo.plist b/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000..1dc6cf7 --- /dev/null +++ b/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 13.0 + + diff --git a/ios/Flutter/Debug.xcconfig b/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000..ec97fc6 --- /dev/null +++ b/ios/Flutter/Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "Generated.xcconfig" diff --git a/ios/Flutter/Release.xcconfig b/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000..c4855bf --- /dev/null +++ b/ios/Flutter/Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "Generated.xcconfig" diff --git a/ios/Podfile b/ios/Podfile new file mode 100644 index 0000000..620e46e --- /dev/null +++ b/ios/Podfile @@ -0,0 +1,43 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '13.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..1e8fdf4 --- /dev/null +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,619 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 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 */; }; +/* 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 */ + 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 = ""; }; + 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 = ""; }; + 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 = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + 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 */, + ); + 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 = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + ); + 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 = ( + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + ); + 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 */ + 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"; + }; + 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"; + }; +/* 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; + 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; + 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; + 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.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..e3773d4 --- /dev/null +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1d526a1 --- /dev/null +++ b/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000..6266644 --- /dev/null +++ b/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import Flutter +import UIKit + +@main +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..d36b1fa --- /dev/null +++ b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} 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 new file mode 100644 index 0000000..dc9ada4 Binary files /dev/null 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 new file mode 100644 index 0000000..7353c41 Binary files /dev/null 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 new file mode 100644 index 0000000..797d452 Binary files /dev/null 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 new file mode 100644 index 0000000..6ed2d93 Binary files /dev/null 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 new file mode 100644 index 0000000..4cd7b00 Binary files /dev/null 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 new file mode 100644 index 0000000..fe73094 Binary files /dev/null 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 new file mode 100644 index 0000000..321773c Binary files /dev/null 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 new file mode 100644 index 0000000..797d452 Binary files /dev/null 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 new file mode 100644 index 0000000..502f463 Binary files /dev/null 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 new file mode 100644 index 0000000..0ec3034 Binary files /dev/null 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 new file mode 100644 index 0000000..0ec3034 Binary files /dev/null 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 new file mode 100644 index 0000000..e9f5fea Binary files /dev/null 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 new file mode 100644 index 0000000..84ac32a Binary files /dev/null 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 new file mode 100644 index 0000000..8953cba Binary files /dev/null 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 new file mode 100644 index 0000000..0467bf1 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 0000000..0bedcf2 --- /dev/null +++ b/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000..89c2725 --- /dev/null +++ b/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/ios/Runner/Base.lproj/LaunchScreen.storyboard b/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..f2e259c --- /dev/null +++ b/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner/Base.lproj/Main.storyboard b/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000..f3c2851 --- /dev/null +++ b/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist new file mode 100644 index 0000000..860d891 --- /dev/null +++ b/ios/Runner/Info.plist @@ -0,0 +1,49 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Zhinian Manage + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + zhinian_manage + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + + + diff --git a/ios/Runner/Runner-Bridging-Header.h b/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000..308a2a5 --- /dev/null +++ b/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/ios/RunnerTests/RunnerTests.swift b/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..86a7c3b --- /dev/null +++ b/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Flutter +import UIKit +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/l10n.yaml b/l10n.yaml new file mode 100644 index 0000000..d4b27ea --- /dev/null +++ b/l10n.yaml @@ -0,0 +1,7 @@ +arb-dir: lib/l10n +template-arb-file: app_zh.arb +output-localization-file: app_localizations.dart +output-class: AppLocalizations +output-dir: lib/l10n +generate: true +synthetic-package: false diff --git a/lib/app.dart b/lib/app.dart new file mode 100644 index 0000000..221af74 --- /dev/null +++ b/lib/app.dart @@ -0,0 +1,38 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'l10n/app_localizations.dart'; +import 'router.dart'; +import 'theme.dart'; +import 'providers/settings_provider.dart'; + +class ZhinianApp extends ConsumerWidget { + const ZhinianApp({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final router = ref.watch(routerProvider); + final settings = ref.watch(settingsProvider); + + return MaterialApp.router( + title: '智念管理', + debugShowCheckedModeBanner: false, + theme: AppTheme.lightTheme, + darkTheme: AppTheme.darkTheme, + themeMode: settings.flutterThemeMode, + routerConfig: router, + locale: settings.locale, + localizationsDelegates: const [ + AppLocalizations.delegate, + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + ], + supportedLocales: const [ + Locale('zh', 'CN'), + Locale('en', 'US'), + Locale('th', 'TH'), + ], + ); + } +} diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb new file mode 100644 index 0000000..7cac705 --- /dev/null +++ b/lib/l10n/app_en.arb @@ -0,0 +1,272 @@ +{ + "@@locale": "en", + "appTitle": "Zhinian Manage", + "appSubtitle": "Hotel & Scenic Smart Management", + "welcomeLogin": "Welcome", + "enterAccountInfo": "Please enter your account info", + "phoneNumber": "Phone Number", + "enterPhone": "Enter phone number", + "password": "Password", + "enterPassword": "Enter password", + "forgotPassword": "Forgot password?", + "login": "Log In", + "agreePrefix": "I have read and agree to", + "userAgreement": "User Agreement", + "privacyPolicy": "Privacy Policy", + "roleHint": "Phone ending with 88 = Boss role, others = Employee", + "loginSuccess": "Login successful", + "loginFailed": "Login failed", + "aiAssistant": "Zhinian AI Assistant", + "online": "Online", + "boss": "Boss", + "employee": "Employee", + "settings": "Settings", + "logout": "Log Out", + "enterMessage": "Type a message...", + "eventPublish": "Publish Event", + "orderWork": "Orders & Work Orders", + "verify": "Verify", + "appExtension": "App Extensions", + "thinking": "Thinking...", + "sendFailed": "Sorry, message failed to send. Please try again later.", + "publishEvent": "Publish Event", + "eventRecords": "Event Records", + "basicInfo": "Basic Info", + "entityName": "Entity Name", + "entityNameHint": "Enter entity name, e.g. Lobby Bar, Pool", + "eventDesc": "Event Description", + "eventDescHint": "Please describe the event in detail...", + "eventImages": "Event Images", + "addImage": "Add Image", + "timeSettings": "Time Settings", + "publishTime": "Publish Time", + "effectiveTime": "Effective Time", + "endTime": "End Time", + "selectTime": "Please select", + "setDefaultTime": "Set Default Time", + "popupReminder": "Popup Reminder", + "popupReminderDesc": "Show popup notification when event takes effect", + "publish": "Publish Event", + "publishSuccess": "Event published successfully", + "noEvents": "No event records", + "published": "Published", + "draft": "Draft", + "expired": "Expired", + "ordersAndWorkOrders": "Orders & Work Orders", + "workOrder": "Work Order", + "order": "Order", + "all": "All", + "pending": "Pending", + "processing": "Processing", + "completed": "Completed", + "pendingPayment": "Pending Payment", + "pendingVerify": "Pending Verify", + "verified": "Verified", + "pendingRefund": "Pending Refund", + "refunded": "Refunded", + "noWorkOrders": "No work orders", + "noOrders": "No orders", + "workOrderDetail": "Work Order Detail", + "orderDetail": "Order Detail", + "unknownLocation": "Unknown Location", + "workOrderId": "Work Order ID", + "creator": "Creator", + "assignee": "Assignee", + "notAssigned": "Not Assigned", + "category": "Category", + "priority": "Priority", + "urgent": "Urgent", + "high": "High", + "normal": "Normal", + "createTime": "Create Time", + "problemDesc": "Problem Description", + "attachments": "Attachments", + "progress": "Progress", + "workOrderCreated": "Work Order Created", + "workOrderAssigned": "Work Order Assigned", + "startProcessing": "Start Processing", + "workOrderCompleted": "Work Order Completed", + "productInfo": "Product Info", + "quantity": "Quantity", + "unitPrice": "Unit Price", + "customerInfo": "Customer Info", + "paymentInfo": "Payment Info", + "orderAmount": "Order Amount", + "actualAmount": "Actual Amount", + "paymentTime": "Payment Time", + "orderNo": "Order No.", + "verifyTime": "Verify Time", + "refundTime": "Refund Time", + "verifyCode": "Verify Code", + "remark": "Remark", + "scanResult": "Scan Result", + "scanning": "Scanning...", + "scanSuccess": "Scan Successful", + "orderInfo": "Order Info", + "product": "Product", + "amount": "Amount", + "contactCustomer": "Contact Customer", + "confirmVerify": "Confirm Verify", + "cancel": "Cancel", + "confirm": "Confirm", + "notVerifiable": "This order cannot be verified", + "verifySuccess": "Verification successful!", + "pushNotification": "Push Notifications", + "soundNotification": "Sound Notifications", + "enabled": "Enabled", + "disabled": "Disabled", + "themeSettings": "Theme", + "lightMode": "Light", + "darkMode": "Dark", + "followSystem": "Follow System", + "languageSettings": "Language", + "simplifiedChinese": "简体中文", + "english": "English", + "thai": "ภาษาไทย", + "storeManagement": "Store Management", + "storeDesc": "Manage hotel & scenic info", + "employeeManagement": "Employee Management", + "employeeDesc": "Add and manage employee accounts", + "dataReport": "Data Reports", + "reportDesc": "View operation analytics", + "appMarket": "App Market", + "marketDesc": "Extend more feature modules", + "helpCenter": "Help Center", + "helpDesc": "User guide & FAQ", + "aboutUs": "About Us", + "version": "Version", + "logoutConfirm": "Are you sure you want to log out?", + "appConfiguration": "App Configuration", + "businessExtension": "Business Extension", + "system": "System", + "user": "User", + "employeeList": "Employee Management", + "noEmployees": "No employees", + "addEmployee": "Add Employee", + "employeeName": "Employee Name", + "enterEmployeeName": "Enter employee name", + "enterPhoneNumber": "Enter phone number", + "role": "Role", + "confirmAdd": "Confirm Add", + "call": "Call", + "setOnLeave": "Set On Leave", + "restoreActive": "Restore Active", + "deleteEmployee": "Delete Employee", + "deleteConfirm": "Are you sure you want to delete employee {name}?", + "addSuccess": "Employee added successfully", + "deleteSuccess": "Employee deleted", + "pleaseComplete": "Please complete all fields", + "storeList": "Store Management", + "hotel": "Hotel", + "scenic": "Scenic", + "spa": "SPA", + "open": "Open", + "closed": "Closed", + "totalRevenue": "Total Revenue", + "orderCount": "Orders", + "avgOrderValue": "Avg Order", + "verifyRate": "Verify Rate", + "vsLastWeek": "vs last week", + "orderTrend": "Order Trend", + "revenueComposition": "Revenue Composition", + "topSales": "Top Sales", + "installed": "Installed", + "install": "Install", + "installSuccess": "Installation successful", + "opening": "Opening...", + "needHelp": "Need Help?", + "faqSubtitle": "FAQ to quickly find answers", + "faq": "FAQ", + "faq1Q": "How to publish an event?", + "faq1A": "Tap the '+' button on the home page, select 'Publish Event', fill in entity name, description, time, etc., and tap publish. The system will send reminders automatically.", + "faq2Q": "How to verify an order?", + "faq2A": "Tap 'Verify' in the quick menu, scan the QR code with your camera. After recognition, go to order details and tap 'Verify' to confirm.", + "faq3Q": "What are the work order statuses?", + "faq3A": "Three statuses: Pending (newly created), Processing (assigned), Completed (resolved). You can filter by status in the Work Orders tab.", + "faq4Q": "What is the difference between Boss and Employee?", + "faq4A": "Boss has full permissions including employee management and data reports. Employee can only handle daily operations like publishing events and verifying orders.", + "faq5Q": "How to switch theme and language?", + "faq5A": "Go to 'Settings', tap 'Theme' to switch light/dark mode; tap 'Language' to switch between Simplified Chinese and English.", + "faq6Q": "What if scan doesn't work?", + "faq6A": "Ensure camera permission is granted, align QR code in the center, keep proper distance and lighting. You can also manually enter the order number.", + "stillQuestions": "Still have questions?", + "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.", + "officialWebsite": "Official Website", + "servicePhone": "Service Phone", + "techSupport": "Tech Support", + "companyAddress": "Address", + "copyright": "Copyright 2024 Zhinian Tech", + "allRightsReserved": "All Rights Reserved", + "customerName": "Customer Name", + "contactPhone": "Contact Phone", + "belongScenic": "Belong Scenic", + "name": "Name", + "phone": "Phone", + "createdWorkOrder": "created the work order", + "staffStartedProcessing": "Staff started processing", + "workOrderFinished": "Work order finished", + "unknownWorkOrder": "Unknown Work Order", + "workOrderLoadFailed": "Work order info failed to load", + "other": "Other", + "receptionist": "Receptionist", + "maintenance": "Maintenance", + "cleaner": "Cleaner", + "admin": "Admin", + "lifeguard": "Lifeguard", + "chef": "Chef", + "security": "Security", + "active": "Active", + "onLeave": "On Leave", + "confirmDelete": "Confirm Delete", + "delete": "Delete", + "openScenic": "Open", + "monday": "Mon", + "tuesday": "Tue", + "wednesday": "Wed", + "thursday": "Thu", + "friday": "Fri", + "saturday": "Sat", + "sunday": "Sun", + "ticket": "Ticket", + "catering": "Catering", + "adultTicket": "Adult Ticket", + "luxurySuite": "Luxury Suite", + "familyPackage": "Family Package", + "spaPackage": "SPA Package", + "orderUnit": " orders", + "latestVersion": "Latest Version", + "agreementTitle": "Zhinian Manage User Agreement", + "privacyTitle": "Zhinian Manage 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.", + "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", + "agreementPara3": "You must comply with laws and regulations when using the service. Do not engage in illegal activities or interfere with normal service operation.", + "agreementSection4": "4. Data Ownership", + "agreementPara4": "Business data generated during your use belongs to you. We may use anonymized data for service optimization but will not disclose your specific business data to third parties.", + "agreementSection5": "5. Service Changes and Termination", + "agreementPara5": "We reserve the right to adjust service content as needed. If you violate this agreement, we may suspend or terminate your account.", + "agreementSection6": "6. Disclaimer", + "agreementPara6": "We strive to ensure service stability and security, but are not liable for service interruptions or data loss caused by force majeure, network failures, or third-party reasons.", + "agreementSection7": "7. Agreement Modifications", + "agreementPara7": "We reserve the right to modify this agreement at any time. Modified agreements will be published in the app, and continued use constitutes acceptance.", + "privacySection1": "1. Information Collection", + "privacyPara1": "We collect: account registration info (phone, name), business operation data (events, orders, work orders), and device info (for service optimization).", + "privacySection2": "2. Information Use", + "privacyPara2": "We use your information to: provide and manage services, verify identity, ensure security, optimize services, and provide customer support. We do not sell your personal information.", + "privacySection3": "3. Information Sharing", + "privacyPara3": "We may share information when: we have your explicit consent, required by law, or to protect our legitimate rights. We will anonymize data before sharing.", + "privacySection4": "4. Information Protection", + "privacyPara4": "We use industry-standard security measures including data encryption, access control, and security audits. However, internet transmission cannot guarantee absolute security.", + "privacySection5": "5. Your Rights", + "privacyPara5": "You have the right to access, correct, and delete your personal information. You may also cancel your account at any time, after which we will delete your data (except as required by law).", + "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." +} \ No newline at end of file diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart new file mode 100644 index 0000000..32d1202 --- /dev/null +++ b/lib/l10n/app_localizations.dart @@ -0,0 +1,1757 @@ +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:intl/intl.dart' as intl; + +import 'app_localizations_en.dart'; +import 'app_localizations_th.dart'; +import 'app_localizations_zh.dart'; + +// ignore_for_file: type=lint + +/// Callers can lookup localized strings with an instance of AppLocalizations +/// returned by `AppLocalizations.of(context)`. +/// +/// Applications need to include `AppLocalizations.delegate()` in their app's +/// `localizationDelegates` list, and the locales they support in the app's +/// `supportedLocales` list. For example: +/// +/// ```dart +/// import 'l10n/app_localizations.dart'; +/// +/// return MaterialApp( +/// localizationsDelegates: AppLocalizations.localizationsDelegates, +/// supportedLocales: AppLocalizations.supportedLocales, +/// home: MyApplicationHome(), +/// ); +/// ``` +/// +/// ## Update pubspec.yaml +/// +/// Please make sure to update your pubspec.yaml to include the following +/// packages: +/// +/// ```yaml +/// dependencies: +/// # Internationalization support. +/// flutter_localizations: +/// sdk: flutter +/// intl: any # Use the pinned version from flutter_localizations +/// +/// # Rest of dependencies +/// ``` +/// +/// ## iOS Applications +/// +/// iOS applications define key application metadata, including supported +/// locales, in an Info.plist file that is built into the application bundle. +/// To configure the locales supported by your app, you’ll need to edit this +/// file. +/// +/// First, open your project’s ios/Runner.xcworkspace Xcode workspace file. +/// Then, in the Project Navigator, open the Info.plist file under the Runner +/// project’s Runner folder. +/// +/// Next, select the Information Property List item, select Add Item from the +/// Editor menu, then select Localizations from the pop-up menu. +/// +/// Select and expand the newly-created Localizations item then, for each +/// locale your application supports, add a new item and select the locale +/// you wish to add from the pop-up menu in the Value field. This list should +/// be consistent with the languages listed in the AppLocalizations.supportedLocales +/// property. +abstract class AppLocalizations { + AppLocalizations(String locale) + : localeName = intl.Intl.canonicalizedLocale(locale.toString()); + + final String localeName; + + static AppLocalizations? of(BuildContext context) { + return Localizations.of(context, AppLocalizations); + } + + static const LocalizationsDelegate delegate = + _AppLocalizationsDelegate(); + + /// A list of this localizations delegate along with the default localizations + /// delegates. + /// + /// Returns a list of localizations delegates containing this delegate along with + /// GlobalMaterialLocalizations.delegate, GlobalCupertinoLocalizations.delegate, + /// and GlobalWidgetsLocalizations.delegate. + /// + /// Additional delegates can be added by appending to this list in + /// MaterialApp. This list does not have to be used at all if a custom list + /// of delegates is preferred or required. + static const List> localizationsDelegates = + >[ + delegate, + GlobalMaterialLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + ]; + + /// A list of this localizations delegate's supported locales. + static const List supportedLocales = [ + Locale('en'), + Locale('th'), + Locale('zh') + ]; + + /// No description provided for @appTitle. + /// + /// In zh, this message translates to: + /// **'智念管理'** + String get appTitle; + + /// No description provided for @appSubtitle. + /// + /// In zh, this message translates to: + /// **'酒店景区智能管理平台'** + String get appSubtitle; + + /// No description provided for @welcomeLogin. + /// + /// In zh, this message translates to: + /// **'欢迎登录'** + String get welcomeLogin; + + /// No description provided for @enterAccountInfo. + /// + /// In zh, this message translates to: + /// **'请输入您的账号信息'** + String get enterAccountInfo; + + /// No description provided for @phoneNumber. + /// + /// In zh, this message translates to: + /// **'手机号'** + String get phoneNumber; + + /// No description provided for @enterPhone. + /// + /// In zh, this message translates to: + /// **'请输入手机号'** + String get enterPhone; + + /// No description provided for @password. + /// + /// In zh, this message translates to: + /// **'密码'** + String get password; + + /// No description provided for @enterPassword. + /// + /// In zh, this message translates to: + /// **'请输入密码'** + String get enterPassword; + + /// No description provided for @forgotPassword. + /// + /// In zh, this message translates to: + /// **'忘记密码?'** + String get forgotPassword; + + /// No description provided for @login. + /// + /// In zh, this message translates to: + /// **'登录'** + String get login; + + /// No description provided for @agreePrefix. + /// + /// In zh, this message translates to: + /// **'我已阅读并同意'** + String get agreePrefix; + + /// No description provided for @userAgreement. + /// + /// In zh, this message translates to: + /// **'《用户协议》'** + String get userAgreement; + + /// No description provided for @privacyPolicy. + /// + /// In zh, this message translates to: + /// **'《隐私政策》'** + String get privacyPolicy; + + /// No description provided for @roleHint. + /// + /// In zh, this message translates to: + /// **'尾号88为老板角色,其他为员工角色'** + String get roleHint; + + /// No description provided for @loginSuccess. + /// + /// In zh, this message translates to: + /// **'登录成功'** + String get loginSuccess; + + /// No description provided for @loginFailed. + /// + /// In zh, this message translates to: + /// **'登录失败'** + String get loginFailed; + + /// No description provided for @aiAssistant. + /// + /// In zh, this message translates to: + /// **'智念AI助手'** + String get aiAssistant; + + /// No description provided for @online. + /// + /// In zh, this message translates to: + /// **'在线'** + String get online; + + /// No description provided for @boss. + /// + /// In zh, this message translates to: + /// **'老板'** + String get boss; + + /// No description provided for @employee. + /// + /// In zh, this message translates to: + /// **'员工'** + String get employee; + + /// No description provided for @settings. + /// + /// In zh, this message translates to: + /// **'设置'** + String get settings; + + /// No description provided for @logout. + /// + /// In zh, this message translates to: + /// **'退出登录'** + String get logout; + + /// No description provided for @enterMessage. + /// + /// In zh, this message translates to: + /// **'输入消息...'** + String get enterMessage; + + /// No description provided for @eventPublish. + /// + /// In zh, this message translates to: + /// **'事件发布'** + String get eventPublish; + + /// No description provided for @orderWork. + /// + /// In zh, this message translates to: + /// **'订单工单'** + String get orderWork; + + /// No description provided for @verify. + /// + /// In zh, this message translates to: + /// **'核销'** + String get verify; + + /// No description provided for @appExtension. + /// + /// In zh, this message translates to: + /// **'应用扩展'** + String get appExtension; + + /// No description provided for @thinking. + /// + /// In zh, this message translates to: + /// **'思考中...'** + String get thinking; + + /// No description provided for @sendFailed. + /// + /// In zh, this message translates to: + /// **'抱歉,消息发送失败,请稍后重试。'** + String get sendFailed; + + /// No description provided for @publishEvent. + /// + /// In zh, this message translates to: + /// **'发布事件'** + String get publishEvent; + + /// No description provided for @eventRecords. + /// + /// In zh, this message translates to: + /// **'事件记录'** + String get eventRecords; + + /// No description provided for @basicInfo. + /// + /// In zh, this message translates to: + /// **'基本信息'** + String get basicInfo; + + /// No description provided for @entityName. + /// + /// In zh, this message translates to: + /// **'实体名称'** + String get entityName; + + /// No description provided for @entityNameHint. + /// + /// In zh, this message translates to: + /// **'请输入实体名称,如:大堂吧、游泳池'** + String get entityNameHint; + + /// No description provided for @eventDesc. + /// + /// In zh, this message translates to: + /// **'事件描述'** + String get eventDesc; + + /// No description provided for @eventDescHint. + /// + /// In zh, this message translates to: + /// **'请详细描述事件内容...'** + String get eventDescHint; + + /// No description provided for @eventImages. + /// + /// In zh, this message translates to: + /// **'事件图片'** + String get eventImages; + + /// No description provided for @addImage. + /// + /// In zh, this message translates to: + /// **'添加图片'** + String get addImage; + + /// No description provided for @timeSettings. + /// + /// In zh, this message translates to: + /// **'时间设置'** + String get timeSettings; + + /// No description provided for @publishTime. + /// + /// In zh, this message translates to: + /// **'发布时间'** + String get publishTime; + + /// No description provided for @effectiveTime. + /// + /// In zh, this message translates to: + /// **'生效时间'** + String get effectiveTime; + + /// No description provided for @endTime. + /// + /// In zh, this message translates to: + /// **'结束时间'** + String get endTime; + + /// No description provided for @selectTime. + /// + /// In zh, this message translates to: + /// **'请选择'** + String get selectTime; + + /// No description provided for @setDefaultTime. + /// + /// In zh, this message translates to: + /// **'设置默认时间'** + String get setDefaultTime; + + /// No description provided for @popupReminder. + /// + /// In zh, this message translates to: + /// **'弹窗提醒'** + String get popupReminder; + + /// No description provided for @popupReminderDesc. + /// + /// In zh, this message translates to: + /// **'事件生效时弹出提醒通知'** + String get popupReminderDesc; + + /// No description provided for @publish. + /// + /// In zh, this message translates to: + /// **'发布事件'** + String get publish; + + /// No description provided for @publishSuccess. + /// + /// In zh, this message translates to: + /// **'事件发布成功'** + String get publishSuccess; + + /// No description provided for @noEvents. + /// + /// In zh, this message translates to: + /// **'暂无事件记录'** + String get noEvents; + + /// No description provided for @published. + /// + /// In zh, this message translates to: + /// **'已发布'** + String get published; + + /// No description provided for @draft. + /// + /// In zh, this message translates to: + /// **'草稿'** + String get draft; + + /// No description provided for @expired. + /// + /// In zh, this message translates to: + /// **'已过期'** + String get expired; + + /// No description provided for @ordersAndWorkOrders. + /// + /// In zh, this message translates to: + /// **'订单工单'** + String get ordersAndWorkOrders; + + /// No description provided for @workOrder. + /// + /// In zh, this message translates to: + /// **'工单'** + String get workOrder; + + /// No description provided for @order. + /// + /// In zh, this message translates to: + /// **'订单'** + String get order; + + /// No description provided for @all. + /// + /// In zh, this message translates to: + /// **'全部'** + String get all; + + /// No description provided for @pending. + /// + /// In zh, this message translates to: + /// **'待处理'** + String get pending; + + /// No description provided for @processing. + /// + /// In zh, this message translates to: + /// **'处理中'** + String get processing; + + /// No description provided for @completed. + /// + /// In zh, this message translates to: + /// **'已完成'** + String get completed; + + /// No description provided for @pendingPayment. + /// + /// In zh, this message translates to: + /// **'待支付'** + String get pendingPayment; + + /// No description provided for @pendingVerify. + /// + /// In zh, this message translates to: + /// **'待核销'** + String get pendingVerify; + + /// No description provided for @verified. + /// + /// In zh, this message translates to: + /// **'已核销'** + String get verified; + + /// No description provided for @pendingRefund. + /// + /// In zh, this message translates to: + /// **'待退款'** + String get pendingRefund; + + /// No description provided for @refunded. + /// + /// In zh, this message translates to: + /// **'已退款'** + String get refunded; + + /// No description provided for @noWorkOrders. + /// + /// In zh, this message translates to: + /// **'暂无工单'** + String get noWorkOrders; + + /// No description provided for @noOrders. + /// + /// In zh, this message translates to: + /// **'暂无订单'** + String get noOrders; + + /// No description provided for @workOrderDetail. + /// + /// In zh, this message translates to: + /// **'工单详情'** + String get workOrderDetail; + + /// No description provided for @orderDetail. + /// + /// In zh, this message translates to: + /// **'订单详情'** + String get orderDetail; + + /// No description provided for @unknownLocation. + /// + /// In zh, this message translates to: + /// **'未知位置'** + String get unknownLocation; + + /// No description provided for @workOrderId. + /// + /// In zh, this message translates to: + /// **'工单编号'** + String get workOrderId; + + /// No description provided for @creator. + /// + /// In zh, this message translates to: + /// **'创建人'** + String get creator; + + /// No description provided for @assignee. + /// + /// In zh, this message translates to: + /// **'指派给'** + String get assignee; + + /// No description provided for @notAssigned. + /// + /// In zh, this message translates to: + /// **'未指派'** + String get notAssigned; + + /// No description provided for @category. + /// + /// In zh, this message translates to: + /// **'分类'** + String get category; + + /// No description provided for @priority. + /// + /// In zh, this message translates to: + /// **'优先级'** + String get priority; + + /// No description provided for @urgent. + /// + /// In zh, this message translates to: + /// **'紧急'** + String get urgent; + + /// No description provided for @high. + /// + /// In zh, this message translates to: + /// **'高'** + String get high; + + /// No description provided for @normal. + /// + /// In zh, this message translates to: + /// **'普通'** + String get normal; + + /// No description provided for @createTime. + /// + /// In zh, this message translates to: + /// **'创建时间'** + String get createTime; + + /// No description provided for @problemDesc. + /// + /// In zh, this message translates to: + /// **'问题描述'** + String get problemDesc; + + /// No description provided for @attachments. + /// + /// In zh, this message translates to: + /// **'附件图片'** + String get attachments; + + /// No description provided for @progress. + /// + /// In zh, this message translates to: + /// **'处理进度'** + String get progress; + + /// No description provided for @workOrderCreated. + /// + /// In zh, this message translates to: + /// **'工单创建'** + String get workOrderCreated; + + /// No description provided for @workOrderAssigned. + /// + /// In zh, this message translates to: + /// **'工单指派'** + String get workOrderAssigned; + + /// No description provided for @startProcessing. + /// + /// In zh, this message translates to: + /// **'开始处理'** + String get startProcessing; + + /// No description provided for @workOrderCompleted. + /// + /// In zh, this message translates to: + /// **'工单完成'** + String get workOrderCompleted; + + /// No description provided for @productInfo. + /// + /// In zh, this message translates to: + /// **'商品信息'** + String get productInfo; + + /// No description provided for @quantity. + /// + /// In zh, this message translates to: + /// **'数量'** + String get quantity; + + /// No description provided for @unitPrice. + /// + /// In zh, this message translates to: + /// **'单价'** + String get unitPrice; + + /// No description provided for @customerInfo. + /// + /// In zh, this message translates to: + /// **'客户信息'** + String get customerInfo; + + /// No description provided for @paymentInfo. + /// + /// In zh, this message translates to: + /// **'支付信息'** + String get paymentInfo; + + /// No description provided for @orderAmount. + /// + /// In zh, this message translates to: + /// **'订单金额'** + String get orderAmount; + + /// No description provided for @actualAmount. + /// + /// In zh, this message translates to: + /// **'实付金额'** + String get actualAmount; + + /// No description provided for @paymentTime. + /// + /// In zh, this message translates to: + /// **'支付时间'** + String get paymentTime; + + /// No description provided for @orderNo. + /// + /// In zh, this message translates to: + /// **'订单编号'** + String get orderNo; + + /// No description provided for @verifyTime. + /// + /// In zh, this message translates to: + /// **'核销时间'** + String get verifyTime; + + /// No description provided for @refundTime. + /// + /// In zh, this message translates to: + /// **'退款时间'** + String get refundTime; + + /// No description provided for @verifyCode. + /// + /// In zh, this message translates to: + /// **'核销码'** + String get verifyCode; + + /// No description provided for @remark. + /// + /// In zh, this message translates to: + /// **'备注'** + String get remark; + + /// No description provided for @scanResult. + /// + /// In zh, this message translates to: + /// **'扫码结果'** + String get scanResult; + + /// No description provided for @scanning. + /// + /// In zh, this message translates to: + /// **'正在识别...'** + String get scanning; + + /// No description provided for @scanSuccess. + /// + /// In zh, this message translates to: + /// **'识别成功'** + String get scanSuccess; + + /// No description provided for @orderInfo. + /// + /// In zh, this message translates to: + /// **'订单信息'** + String get orderInfo; + + /// No description provided for @product. + /// + /// In zh, this message translates to: + /// **'商品'** + String get product; + + /// No description provided for @amount. + /// + /// In zh, this message translates to: + /// **'金额'** + String get amount; + + /// No description provided for @contactCustomer. + /// + /// In zh, this message translates to: + /// **'联系客户'** + String get contactCustomer; + + /// No description provided for @confirmVerify. + /// + /// In zh, this message translates to: + /// **'确认核销'** + String get confirmVerify; + + /// No description provided for @cancel. + /// + /// In zh, this message translates to: + /// **'取消'** + String get cancel; + + /// No description provided for @confirm. + /// + /// In zh, this message translates to: + /// **'确认'** + String get confirm; + + /// No description provided for @notVerifiable. + /// + /// In zh, this message translates to: + /// **'该订单不可核销'** + String get notVerifiable; + + /// No description provided for @verifySuccess. + /// + /// In zh, this message translates to: + /// **'核销成功!'** + String get verifySuccess; + + /// No description provided for @appConfiguration. + /// + /// In zh, this message translates to: + /// **'应用配置'** + String get appConfiguration; + + /// No description provided for @businessExtension. + /// + /// In zh, this message translates to: + /// **'业务扩展'** + String get businessExtension; + + /// No description provided for @systemLabel. + /// + /// In zh, this message translates to: + /// **'系统'** + String get systemLabel; + + /// No description provided for @user. + /// + /// In zh, this message translates to: + /// **'用户'** + String get user; + + /// No description provided for @pushNotification. + /// + /// In zh, this message translates to: + /// **'消息通知'** + String get pushNotification; + + /// No description provided for @soundNotification. + /// + /// In zh, this message translates to: + /// **'声音提醒'** + String get soundNotification; + + /// No description provided for @enabled. + /// + /// In zh, this message translates to: + /// **'已开启'** + String get enabled; + + /// No description provided for @disabled. + /// + /// In zh, this message translates to: + /// **'已关闭'** + String get disabled; + + /// No description provided for @themeSettings. + /// + /// In zh, this message translates to: + /// **'主题设置'** + String get themeSettings; + + /// No description provided for @lightMode. + /// + /// In zh, this message translates to: + /// **'浅色模式'** + String get lightMode; + + /// No description provided for @darkMode. + /// + /// In zh, this message translates to: + /// **'深色模式'** + String get darkMode; + + /// No description provided for @followSystem. + /// + /// In zh, this message translates to: + /// **'跟随系统'** + String get followSystem; + + /// No description provided for @languageSettings. + /// + /// In zh, this message translates to: + /// **'语言设置'** + String get languageSettings; + + /// No description provided for @simplifiedChinese. + /// + /// In zh, this message translates to: + /// **'简体中文'** + String get simplifiedChinese; + + /// No description provided for @english. + /// + /// In zh, this message translates to: + /// **'English'** + String get english; + + /// No description provided for @thai. + /// + /// In zh, this message translates to: + /// **'ภาษาไทย'** + String get thai; + + /// No description provided for @storeManagement. + /// + /// In zh, this message translates to: + /// **'门店管理'** + String get storeManagement; + + /// No description provided for @storeDesc. + /// + /// In zh, this message translates to: + /// **'管理酒店/景区信息'** + String get storeDesc; + + /// No description provided for @employeeManagement. + /// + /// In zh, this message translates to: + /// **'员工管理'** + String get employeeManagement; + + /// No description provided for @employeeDesc. + /// + /// In zh, this message translates to: + /// **'添加和管理员工账号'** + String get employeeDesc; + + /// No description provided for @dataReport. + /// + /// In zh, this message translates to: + /// **'数据报表'** + String get dataReport; + + /// No description provided for @reportDesc. + /// + /// In zh, this message translates to: + /// **'查看运营数据分析'** + String get reportDesc; + + /// 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. + /// + /// In zh, this message translates to: + /// **'帮助中心'** + String get helpCenter; + + /// No description provided for @helpDesc. + /// + /// In zh, this message translates to: + /// **'使用指南与常见问题'** + String get helpDesc; + + /// No description provided for @aboutUs. + /// + /// In zh, this message translates to: + /// **'关于我们'** + String get aboutUs; + + /// No description provided for @version. + /// + /// In zh, this message translates to: + /// **'版本'** + String get version; + + /// No description provided for @logoutConfirm. + /// + /// In zh, this message translates to: + /// **'确定要退出登录吗?'** + String get logoutConfirm; + + /// No description provided for @employeeList. + /// + /// In zh, this message translates to: + /// **'员工管理'** + String get employeeList; + + /// No description provided for @noEmployees. + /// + /// In zh, this message translates to: + /// **'暂无员工'** + String get noEmployees; + + /// No description provided for @addEmployee. + /// + /// In zh, this message translates to: + /// **'添加员工'** + String get addEmployee; + + /// No description provided for @employeeName. + /// + /// In zh, this message translates to: + /// **'员工姓名'** + String get employeeName; + + /// No description provided for @enterEmployeeName. + /// + /// In zh, this message translates to: + /// **'请输入员工姓名'** + String get enterEmployeeName; + + /// No description provided for @enterPhoneNumber. + /// + /// In zh, this message translates to: + /// **'请输入手机号'** + String get enterPhoneNumber; + + /// No description provided for @role. + /// + /// In zh, this message translates to: + /// **'岗位'** + String get role; + + /// No description provided for @confirmAdd. + /// + /// In zh, this message translates to: + /// **'确认添加'** + String get confirmAdd; + + /// No description provided for @call. + /// + /// In zh, this message translates to: + /// **'拨打电话'** + String get call; + + /// No description provided for @setOnLeave. + /// + /// In zh, this message translates to: + /// **'设为休假'** + String get setOnLeave; + + /// No description provided for @restoreActive. + /// + /// In zh, this message translates to: + /// **'恢复在职'** + String get restoreActive; + + /// No description provided for @deleteEmployee. + /// + /// In zh, this message translates to: + /// **'删除员工'** + String get deleteEmployee; + + /// No description provided for @deleteConfirm. + /// + /// In zh, this message translates to: + /// **'确定要删除员工 {name} 吗?'** + String deleteConfirm(Object name); + + /// No description provided for @addSuccess. + /// + /// In zh, this message translates to: + /// **'员工添加成功'** + String get addSuccess; + + /// No description provided for @deleteSuccess. + /// + /// In zh, this message translates to: + /// **'员工已删除'** + String get deleteSuccess; + + /// No description provided for @pleaseComplete. + /// + /// In zh, this message translates to: + /// **'请填写完整信息'** + String get pleaseComplete; + + /// No description provided for @storeList. + /// + /// In zh, this message translates to: + /// **'门店管理'** + String get storeList; + + /// No description provided for @hotel. + /// + /// In zh, this message translates to: + /// **'酒店'** + String get hotel; + + /// No description provided for @scenic. + /// + /// In zh, this message translates to: + /// **'景区'** + String get scenic; + + /// No description provided for @spa. + /// + /// In zh, this message translates to: + /// **'SPA'** + String get spa; + + /// No description provided for @open. + /// + /// In zh, this message translates to: + /// **'打开'** + String get open; + + /// No description provided for @closed. + /// + /// In zh, this message translates to: + /// **'已关闭'** + String get closed; + + /// No description provided for @totalRevenue. + /// + /// In zh, this message translates to: + /// **'总营收'** + String get totalRevenue; + + /// No description provided for @orderCount. + /// + /// In zh, this message translates to: + /// **'订单数'** + String get orderCount; + + /// No description provided for @avgOrderValue. + /// + /// In zh, this message translates to: + /// **'客单价'** + String get avgOrderValue; + + /// No description provided for @verifyRate. + /// + /// In zh, this message translates to: + /// **'核销率'** + String get verifyRate; + + /// No description provided for @vsLastWeek. + /// + /// In zh, this message translates to: + /// **'较上周'** + String get vsLastWeek; + + /// No description provided for @orderTrend. + /// + /// In zh, this message translates to: + /// **'订单趋势'** + String get orderTrend; + + /// No description provided for @revenueComposition. + /// + /// In zh, this message translates to: + /// **'营收构成'** + String get revenueComposition; + + /// No description provided for @topSales. + /// + /// In zh, this message translates to: + /// **'热销排行'** + String get topSales; + + /// No description provided for @installed. + /// + /// In zh, this message translates to: + /// **'已安装'** + String get installed; + + /// No description provided for @install. + /// + /// In zh, this message translates to: + /// **'安装'** + String get install; + + /// No description provided for @installSuccess. + /// + /// In zh, this message translates to: + /// **'安装成功'** + String get installSuccess; + + /// No description provided for @opening. + /// + /// In zh, this message translates to: + /// **'正在打开...'** + String get opening; + + /// No description provided for @needHelp. + /// + /// In zh, this message translates to: + /// **'需要帮助?'** + String get needHelp; + + /// No description provided for @faqSubtitle. + /// + /// In zh, this message translates to: + /// **'常见问题解答,快速找到答案'** + String get faqSubtitle; + + /// No description provided for @faq. + /// + /// In zh, this message translates to: + /// **'常见问题'** + String get faq; + + /// No description provided for @faq1Q. + /// + /// In zh, this message translates to: + /// **'如何发布事件通知?'** + String get faq1Q; + + /// No description provided for @faq1A. + /// + /// In zh, this message translates to: + /// **'在首页点击底部输入框左侧的「+」按钮,选择「事件发布」,填写实体名称、事件描述、时间等信息后点击发布即可。系统会自动在设定时间推送提醒。'** + String get faq1A; + + /// No description provided for @faq2Q. + /// + /// In zh, this message translates to: + /// **'如何核销订单?'** + String get faq2Q; + + /// No description provided for @faq2A. + /// + /// In zh, this message translates to: + /// **'在首页快捷菜单中点击「核销」,使用手机摄像头扫描客户出示的二维码,识别成功后会跳转到订单详情页,点击底部「核销」按钮并确认即可完成。'** + String get faq2A; + + /// No description provided for @faq3Q. + /// + /// In zh, this message translates to: + /// **'工单状态有哪些?'** + String get faq3Q; + + /// No description provided for @faq3A. + /// + /// In zh, this message translates to: + /// **'工单共有三种状态:待处理(刚创建未分配)、处理中(已分配给相关人员)、已完成(问题已解决)。可以在「订单工单」页面的工单Tab中按状态筛选。'** + String get faq3A; + + /// No description provided for @faq4Q. + /// + /// In zh, this message translates to: + /// **'老板和员工角色有什么区别?'** + String get faq4Q; + + /// No description provided for @faq4A. + /// + /// In zh, this message translates to: + /// **'老板角色拥有完整的操作权限,包括员工管理、数据报表等管理功能;员工角色只能处理日常业务,如发布事件、处理工单、核销订单等。'** + String get faq4A; + + /// No description provided for @faq5Q. + /// + /// In zh, this message translates to: + /// **'如何切换主题和语言?'** + String get faq5Q; + + /// No description provided for @faq5A. + /// + /// In zh, this message translates to: + /// **'进入「设置」页面,点击「主题设置」可在浅色/深色模式间切换;点击「语言设置」可切换简体中文和英文界面。'** + String get faq5A; + + /// No description provided for @faq6Q. + /// + /// In zh, this message translates to: + /// **'扫码没有反应怎么办?'** + String get faq6Q; + + /// No description provided for @faq6A. + /// + /// In zh, this message translates to: + /// **'请确保摄像头权限已开启,将二维码对准扫描框中央,保持适当距离和光线。如持续无法识别,可手动输入订单号进行核销。'** + String get faq6A; + + /// No description provided for @stillQuestions. + /// + /// In zh, this message translates to: + /// **'仍有问题?'** + String get stillQuestions; + + /// No description provided for @contactTeam. + /// + /// In zh, this message translates to: + /// **'联系我们的客服团队获取更多帮助'** + String get contactTeam; + + /// No description provided for @contactService. + /// + /// In zh, this message translates to: + /// **'联系客服'** + String get contactService; + + /// No description provided for @customerName. + /// + /// In zh, this message translates to: + /// **'客户姓名'** + String get customerName; + + /// No description provided for @contactPhone. + /// + /// In zh, this message translates to: + /// **'联系电话'** + String get contactPhone; + + /// No description provided for @belongScenic. + /// + /// In zh, this message translates to: + /// **'所属景区'** + String get belongScenic; + + /// No description provided for @name. + /// + /// In zh, this message translates to: + /// **'姓名'** + String get name; + + /// No description provided for @phone. + /// + /// In zh, this message translates to: + /// **'电话'** + String get phone; + + /// No description provided for @createdWorkOrder. + /// + /// In zh, this message translates to: + /// **'创建了该工单'** + String get createdWorkOrder; + + /// No description provided for @staffStartedProcessing. + /// + /// In zh, this message translates to: + /// **'维修人员开始处理'** + String get staffStartedProcessing; + + /// No description provided for @workOrderFinished. + /// + /// In zh, this message translates to: + /// **'工单已处理完成'** + String get workOrderFinished; + + /// No description provided for @unknownWorkOrder. + /// + /// In zh, this message translates to: + /// **'未知工单'** + String get unknownWorkOrder; + + /// No description provided for @workOrderLoadFailed. + /// + /// In zh, this message translates to: + /// **'工单信息加载失败'** + String get workOrderLoadFailed; + + /// No description provided for @system. + /// + /// In zh, this message translates to: + /// **'系统'** + String get system; + + /// No description provided for @other. + /// + /// In zh, this message translates to: + /// **'其他'** + String get other; + + /// No description provided for @receptionist. + /// + /// In zh, this message translates to: + /// **'前台'** + String get receptionist; + + /// No description provided for @maintenance. + /// + /// In zh, this message translates to: + /// **'维修工'** + String get maintenance; + + /// No description provided for @cleaner. + /// + /// In zh, this message translates to: + /// **'保洁'** + String get cleaner; + + /// No description provided for @admin. + /// + /// In zh, this message translates to: + /// **'行政'** + String get admin; + + /// No description provided for @lifeguard. + /// + /// In zh, this message translates to: + /// **'救生员'** + String get lifeguard; + + /// No description provided for @chef. + /// + /// In zh, this message translates to: + /// **'厨师'** + String get chef; + + /// No description provided for @security. + /// + /// In zh, this message translates to: + /// **'保安'** + String get security; + + /// No description provided for @active. + /// + /// In zh, this message translates to: + /// **'在职'** + String get active; + + /// No description provided for @onLeave. + /// + /// In zh, this message translates to: + /// **'休假'** + String get onLeave; + + /// No description provided for @confirmDelete. + /// + /// In zh, this message translates to: + /// **'确认删除'** + String get confirmDelete; + + /// No description provided for @delete. + /// + /// In zh, this message translates to: + /// **'删除'** + String get delete; + + /// No description provided for @openScenic. + /// + /// In zh, this message translates to: + /// **'开放中'** + String get openScenic; + + /// No description provided for @monday. + /// + /// In zh, this message translates to: + /// **'周一'** + String get monday; + + /// No description provided for @tuesday. + /// + /// In zh, this message translates to: + /// **'周二'** + String get tuesday; + + /// No description provided for @wednesday. + /// + /// In zh, this message translates to: + /// **'周三'** + String get wednesday; + + /// No description provided for @thursday. + /// + /// In zh, this message translates to: + /// **'周四'** + String get thursday; + + /// No description provided for @friday. + /// + /// In zh, this message translates to: + /// **'周五'** + String get friday; + + /// No description provided for @saturday. + /// + /// In zh, this message translates to: + /// **'周六'** + String get saturday; + + /// No description provided for @sunday. + /// + /// In zh, this message translates to: + /// **'周日'** + String get sunday; + + /// No description provided for @ticket. + /// + /// In zh, this message translates to: + /// **'门票'** + String get ticket; + + /// No description provided for @catering. + /// + /// In zh, this message translates to: + /// **'餐饮'** + String get catering; + + /// No description provided for @adultTicket. + /// + /// In zh, this message translates to: + /// **'成人门票'** + String get adultTicket; + + /// No description provided for @luxurySuite. + /// + /// In zh, this message translates to: + /// **'豪华套房'** + String get luxurySuite; + + /// No description provided for @familyPackage. + /// + /// In zh, this message translates to: + /// **'亲子套票'** + String get familyPackage; + + /// No description provided for @spaPackage. + /// + /// In zh, this message translates to: + /// **'SPA套餐'** + String get spaPackage; + + /// No description provided for @orderUnit. + /// + /// In zh, this message translates to: + /// **'单'** + String get orderUnit; + + /// No description provided for @latestVersion. + /// + /// In zh, this message translates to: + /// **'最新版本'** + String get latestVersion; + + /// 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. + /// + /// In zh, this message translates to: + /// **'最后更新日期:2024年12月'** + String get lastUpdated; + + /// No description provided for @agreementSection1. + /// + /// In zh, this message translates to: + /// **'一、协议范围'** + String get agreementSection1; + + /// No description provided for @agreementPara1. + /// + /// In zh, this message translates to: + /// **'本协议是您与智念科技之间关于使用智念管理服务所订立的协议。智念管理服务包括但不限于事件发布、订单管理、工单处理、扫码核销等功能。'** + String get agreementPara1; + + /// No description provided for @agreementSection2. + /// + /// In zh, this message translates to: + /// **'二、账号注册'** + String get agreementSection2; + + /// No description provided for @agreementPara2. + /// + /// In zh, this message translates to: + /// **'您需要注册一个账号才能使用我们的服务。注册时需提供真实有效的手机号码,并设置安全密码。您应对账号下的所有行为负责。'** + String get agreementPara2; + + /// No description provided for @agreementSection3. + /// + /// In zh, this message translates to: + /// **'三、服务使用规范'** + String get agreementSection3; + + /// No description provided for @agreementPara3. + /// + /// In zh, this message translates to: + /// **'您在使用服务时应遵守法律法规,不得利用本服务从事违法违规活动。不得干扰服务的正常运行,不得侵犯他人合法权益。'** + String get agreementPara3; + + /// No description provided for @agreementSection4. + /// + /// In zh, this message translates to: + /// **'四、数据所有权'** + String get agreementSection4; + + /// No description provided for @agreementPara4. + /// + /// In zh, this message translates to: + /// **'您在使用服务过程中产生的业务数据归您所有。我们有权在必要范围内使用匿名化数据进行服务优化,但不会向第三方披露您的具体业务数据。'** + String get agreementPara4; + + /// No description provided for @agreementSection5. + /// + /// In zh, this message translates to: + /// **'五、服务变更与终止'** + String get agreementSection5; + + /// No description provided for @agreementPara5. + /// + /// In zh, this message translates to: + /// **'我们有权根据业务发展需要调整服务内容。如您违反本协议,我们有权暂停或终止您的账号使用权限。'** + String get agreementPara5; + + /// No description provided for @agreementSection6. + /// + /// In zh, this message translates to: + /// **'六、免责声明'** + String get agreementSection6; + + /// No description provided for @agreementPara6. + /// + /// In zh, this message translates to: + /// **'我们尽力保证服务的稳定性和安全性,但不对因不可抗力、网络故障、第三方原因等导致的服务中断或数据丢失承担责任。'** + String get agreementPara6; + + /// No description provided for @agreementSection7. + /// + /// In zh, this message translates to: + /// **'七、协议修改'** + String get agreementSection7; + + /// No description provided for @agreementPara7. + /// + /// In zh, this message translates to: + /// **'我们保留随时修改本协议的权利。修改后的协议将在应用内公布,继续使用即视为接受修改。'** + String get agreementPara7; + + /// No description provided for @privacySection1. + /// + /// In zh, this message translates to: + /// **'一、信息收集'** + String get privacySection1; + + /// No description provided for @privacyPara1. + /// + /// In zh, this message translates to: + /// **'我们收集的信息包括:账号注册信息(手机号、姓名)、业务操作数据(事件、订单、工单记录)、设备信息(用于服务优化)。'** + String get privacyPara1; + + /// No description provided for @privacySection2. + /// + /// In zh, this message translates to: + /// **'二、信息使用'** + String get privacySection2; + + /// No description provided for @privacyPara2. + /// + /// In zh, this message translates to: + /// **'我们使用您的信息用于:提供和管理服务、身份验证、安全保障、服务优化、客服支持。我们不会将您的个人信息出售给任何第三方。'** + String get privacyPara2; + + /// No description provided for @privacySection3. + /// + /// In zh, this message translates to: + /// **'三、信息共享'** + String get privacySection3; + + /// No description provided for @privacyPara3. + /// + /// In zh, this message translates to: + /// **'在以下情况下我们可能会共享信息:获得您的明确同意、法律法规要求、保护我们的合法权益。共享前我们会进行匿名化处理。'** + String get privacyPara3; + + /// No description provided for @privacySection4. + /// + /// In zh, this message translates to: + /// **'四、信息保护'** + String get privacySection4; + + /// No description provided for @privacyPara4. + /// + /// In zh, this message translates to: + /// **'我们采用行业标准的安全措施保护您的信息,包括数据加密、访问控制、安全审计等。但互联网传输无法保证绝对安全。'** + String get privacyPara4; + + /// No description provided for @privacySection5. + /// + /// In zh, this message translates to: + /// **'五、您的权利'** + String get privacySection5; + + /// No description provided for @privacyPara5. + /// + /// In zh, this message translates to: + /// **'您有权访问、更正、删除您的个人信息。您也可以随时注销账号,注销后我们将删除您的个人数据(法律法规要求保留的除外)。'** + String get privacyPara5; + + /// No description provided for @privacySection6. + /// + /// In zh, this message translates to: + /// **'六、Cookie 与本地存储'** + String get privacySection6; + + /// No description provided for @privacyPara6. + /// + /// In zh, this message translates to: + /// **'我们使用本地存储技术保存登录状态和应用设置,以提升使用体验。您可以在设置中清除这些数据。'** + String get privacyPara6; + + /// No description provided for @privacySection7. + /// + /// In zh, this message translates to: + /// **'七、隐私政策更新'** + String get privacySection7; + + /// No description provided for @privacyPara7. + /// + /// In zh, this message translates to: + /// **'我们可能会不时更新本隐私政策。更新后的政策将在应用内提示,继续使用即视为接受更新。'** + String get privacyPara7; + + /// No description provided for @productIntro. + /// + /// In zh, this message translates to: + /// **'产品简介'** + String get productIntro; + + /// No description provided for @productDesc. + /// + /// In zh, this message translates to: + /// **'智念管理是一款专为酒店、景区等文旅行业打造的智能管理应用。通过AI助手、事件发布、订单工单管理、扫码核销等功能,帮助商家高效运营、提升服务质量。'** + String get productDesc; + + /// No description provided for @officialWebsite. + /// + /// In zh, this message translates to: + /// **'官方网站'** + String get officialWebsite; + + /// No description provided for @servicePhone. + /// + /// In zh, this message translates to: + /// **'客服电话'** + String get servicePhone; + + /// No description provided for @techSupport. + /// + /// In zh, this message translates to: + /// **'技术支持'** + String get techSupport; + + /// No description provided for @companyAddress. + /// + /// In zh, this message translates to: + /// **'公司地址'** + String get companyAddress; + + /// No description provided for @copyright. + /// + /// In zh, this message translates to: + /// **'Copyright 2024 智念科技'** + String get copyright; + + /// No description provided for @allRightsReserved. + /// + /// In zh, this message translates to: + /// **'All Rights Reserved'** + String get allRightsReserved; +} + +class _AppLocalizationsDelegate + extends LocalizationsDelegate { + const _AppLocalizationsDelegate(); + + @override + Future load(Locale locale) { + return SynchronousFuture(lookupAppLocalizations(locale)); + } + + @override + bool isSupported(Locale locale) => + ['en', 'th', 'zh'].contains(locale.languageCode); + + @override + bool shouldReload(_AppLocalizationsDelegate old) => false; +} + +AppLocalizations lookupAppLocalizations(Locale locale) { + // Lookup logic when only language code is specified. + switch (locale.languageCode) { + case 'en': + return AppLocalizationsEn(); + case 'th': + return AppLocalizationsTh(); + case 'zh': + return AppLocalizationsZh(); + } + + throw FlutterError( + 'AppLocalizations.delegate failed to load unsupported locale "$locale". This is likely ' + 'an issue with the localizations generation tool. Please file an issue ' + 'on GitHub with a reproducible sample app and the gen-l10n configuration ' + 'that was used.'); +} diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart new file mode 100644 index 0000000..a1b895e --- /dev/null +++ b/lib/l10n/app_localizations_en.dart @@ -0,0 +1,845 @@ +// ignore: unused_import +import 'package:intl/intl.dart' as intl; +import 'app_localizations.dart'; + +// ignore_for_file: type=lint + +/// The translations for English (`en`). +class AppLocalizationsEn extends AppLocalizations { + AppLocalizationsEn([String locale = 'en']) : super(locale); + + @override + String get appTitle => 'Zhinian Manage'; + + @override + String get appSubtitle => 'Hotel & Scenic Smart Management'; + + @override + String get welcomeLogin => 'Welcome'; + + @override + String get enterAccountInfo => 'Please enter your account info'; + + @override + String get phoneNumber => 'Phone Number'; + + @override + String get enterPhone => 'Enter phone number'; + + @override + String get password => 'Password'; + + @override + String get enterPassword => 'Enter password'; + + @override + String get forgotPassword => 'Forgot password?'; + + @override + String get login => 'Log In'; + + @override + String get agreePrefix => 'I have read and agree to'; + + @override + String get userAgreement => 'User Agreement'; + + @override + String get privacyPolicy => 'Privacy Policy'; + + @override + String get roleHint => 'Phone ending with 88 = Boss role, others = Employee'; + + @override + String get loginSuccess => 'Login successful'; + + @override + String get loginFailed => 'Login failed'; + + @override + String get aiAssistant => 'Zhinian AI Assistant'; + + @override + String get online => 'Online'; + + @override + String get boss => 'Boss'; + + @override + String get employee => 'Employee'; + + @override + String get settings => 'Settings'; + + @override + String get logout => 'Log Out'; + + @override + String get enterMessage => 'Type a message...'; + + @override + String get eventPublish => 'Publish Event'; + + @override + String get orderWork => 'Orders & Work Orders'; + + @override + String get verify => 'Verify'; + + @override + String get appExtension => 'App Extensions'; + + @override + String get thinking => 'Thinking...'; + + @override + String get sendFailed => + 'Sorry, message failed to send. Please try again later.'; + + @override + String get publishEvent => 'Publish Event'; + + @override + String get eventRecords => 'Event Records'; + + @override + String get basicInfo => 'Basic Info'; + + @override + String get entityName => 'Entity Name'; + + @override + String get entityNameHint => 'Enter entity name, e.g. Lobby Bar, Pool'; + + @override + String get eventDesc => 'Event Description'; + + @override + String get eventDescHint => 'Please describe the event in detail...'; + + @override + String get eventImages => 'Event Images'; + + @override + String get addImage => 'Add Image'; + + @override + String get timeSettings => 'Time Settings'; + + @override + String get publishTime => 'Publish Time'; + + @override + String get effectiveTime => 'Effective Time'; + + @override + String get endTime => 'End Time'; + + @override + String get selectTime => 'Please select'; + + @override + String get setDefaultTime => 'Set Default Time'; + + @override + String get popupReminder => 'Popup Reminder'; + + @override + String get popupReminderDesc => + 'Show popup notification when event takes effect'; + + @override + String get publish => 'Publish Event'; + + @override + String get publishSuccess => 'Event published successfully'; + + @override + String get noEvents => 'No event records'; + + @override + String get published => 'Published'; + + @override + String get draft => 'Draft'; + + @override + String get expired => 'Expired'; + + @override + String get ordersAndWorkOrders => 'Orders & Work Orders'; + + @override + String get workOrder => 'Work Order'; + + @override + String get order => 'Order'; + + @override + String get all => 'All'; + + @override + String get pending => 'Pending'; + + @override + String get processing => 'Processing'; + + @override + String get completed => 'Completed'; + + @override + String get pendingPayment => 'Pending Payment'; + + @override + String get pendingVerify => 'Pending Verify'; + + @override + String get verified => 'Verified'; + + @override + String get pendingRefund => 'Pending Refund'; + + @override + String get refunded => 'Refunded'; + + @override + String get noWorkOrders => 'No work orders'; + + @override + String get noOrders => 'No orders'; + + @override + String get workOrderDetail => 'Work Order Detail'; + + @override + String get orderDetail => 'Order Detail'; + + @override + String get unknownLocation => 'Unknown Location'; + + @override + String get workOrderId => 'Work Order ID'; + + @override + String get creator => 'Creator'; + + @override + String get assignee => 'Assignee'; + + @override + String get notAssigned => 'Not Assigned'; + + @override + String get category => 'Category'; + + @override + String get priority => 'Priority'; + + @override + String get urgent => 'Urgent'; + + @override + String get high => 'High'; + + @override + String get normal => 'Normal'; + + @override + String get createTime => 'Create Time'; + + @override + String get problemDesc => 'Problem Description'; + + @override + String get attachments => 'Attachments'; + + @override + String get progress => 'Progress'; + + @override + String get workOrderCreated => 'Work Order Created'; + + @override + String get workOrderAssigned => 'Work Order Assigned'; + + @override + String get startProcessing => 'Start Processing'; + + @override + String get workOrderCompleted => 'Work Order Completed'; + + @override + String get productInfo => 'Product Info'; + + @override + String get quantity => 'Quantity'; + + @override + String get unitPrice => 'Unit Price'; + + @override + String get customerInfo => 'Customer Info'; + + @override + String get paymentInfo => 'Payment Info'; + + @override + String get orderAmount => 'Order Amount'; + + @override + String get actualAmount => 'Actual Amount'; + + @override + String get paymentTime => 'Payment Time'; + + @override + String get orderNo => 'Order No.'; + + @override + String get verifyTime => 'Verify Time'; + + @override + String get refundTime => 'Refund Time'; + + @override + String get verifyCode => 'Verify Code'; + + @override + String get remark => 'Remark'; + + @override + String get scanResult => 'Scan Result'; + + @override + String get scanning => 'Scanning...'; + + @override + String get scanSuccess => 'Scan Successful'; + + @override + String get orderInfo => 'Order Info'; + + @override + String get product => 'Product'; + + @override + String get amount => 'Amount'; + + @override + String get contactCustomer => 'Contact Customer'; + + @override + String get confirmVerify => 'Confirm Verify'; + + @override + String get cancel => 'Cancel'; + + @override + String get confirm => 'Confirm'; + + @override + String get notVerifiable => 'This order cannot be verified'; + + @override + String get verifySuccess => 'Verification successful!'; + + @override + String get appConfiguration => 'App Configuration'; + + @override + String get businessExtension => 'Business Extension'; + + @override + String get systemLabel => '系统'; + + @override + String get user => 'User'; + + @override + String get pushNotification => 'Push Notifications'; + + @override + String get soundNotification => 'Sound Notifications'; + + @override + String get enabled => 'Enabled'; + + @override + String get disabled => 'Disabled'; + + @override + String get themeSettings => 'Theme'; + + @override + String get lightMode => 'Light'; + + @override + String get darkMode => 'Dark'; + + @override + String get followSystem => 'Follow System'; + + @override + String get languageSettings => 'Language'; + + @override + String get simplifiedChinese => '简体中文'; + + @override + String get english => 'English'; + + @override + String get thai => 'ภาษาไทย'; + + @override + String get storeManagement => 'Store Management'; + + @override + String get storeDesc => 'Manage hotel & scenic info'; + + @override + String get employeeManagement => 'Employee Management'; + + @override + String get employeeDesc => 'Add and manage employee accounts'; + + @override + String get dataReport => 'Data Reports'; + + @override + String get reportDesc => 'View operation analytics'; + + @override + String get appMarket => 'App Market'; + + @override + String get marketDesc => 'Extend more feature modules'; + + @override + String get helpCenter => 'Help Center'; + + @override + String get helpDesc => 'User guide & FAQ'; + + @override + String get aboutUs => 'About Us'; + + @override + String get version => 'Version'; + + @override + String get logoutConfirm => 'Are you sure you want to log out?'; + + @override + String get employeeList => 'Employee Management'; + + @override + String get noEmployees => 'No employees'; + + @override + String get addEmployee => 'Add Employee'; + + @override + String get employeeName => 'Employee Name'; + + @override + String get enterEmployeeName => 'Enter employee name'; + + @override + String get enterPhoneNumber => 'Enter phone number'; + + @override + String get role => 'Role'; + + @override + String get confirmAdd => 'Confirm Add'; + + @override + String get call => 'Call'; + + @override + String get setOnLeave => 'Set On Leave'; + + @override + String get restoreActive => 'Restore Active'; + + @override + String get deleteEmployee => 'Delete Employee'; + + @override + String deleteConfirm(Object name) { + return 'Are you sure you want to delete employee $name?'; + } + + @override + String get addSuccess => 'Employee added successfully'; + + @override + String get deleteSuccess => 'Employee deleted'; + + @override + String get pleaseComplete => 'Please complete all fields'; + + @override + String get storeList => 'Store Management'; + + @override + String get hotel => 'Hotel'; + + @override + String get scenic => 'Scenic'; + + @override + String get spa => 'SPA'; + + @override + String get open => 'Open'; + + @override + String get closed => 'Closed'; + + @override + String get totalRevenue => 'Total Revenue'; + + @override + String get orderCount => 'Orders'; + + @override + String get avgOrderValue => 'Avg Order'; + + @override + String get verifyRate => 'Verify Rate'; + + @override + String get vsLastWeek => 'vs last week'; + + @override + String get orderTrend => 'Order Trend'; + + @override + String get revenueComposition => 'Revenue Composition'; + + @override + String get topSales => 'Top Sales'; + + @override + String get installed => 'Installed'; + + @override + String get install => 'Install'; + + @override + String get installSuccess => 'Installation successful'; + + @override + String get opening => 'Opening...'; + + @override + String get needHelp => 'Need Help?'; + + @override + String get faqSubtitle => 'FAQ to quickly find answers'; + + @override + String get faq => 'FAQ'; + + @override + String get faq1Q => 'How to publish an event?'; + + @override + String get faq1A => + 'Tap the \'+\' button on the home page, select \'Publish Event\', fill in entity name, description, time, etc., and tap publish. The system will send reminders automatically.'; + + @override + String get faq2Q => 'How to verify an order?'; + + @override + String get faq2A => + 'Tap \'Verify\' in the quick menu, scan the QR code with your camera. After recognition, go to order details and tap \'Verify\' to confirm.'; + + @override + String get faq3Q => 'What are the work order statuses?'; + + @override + String get faq3A => + 'Three statuses: Pending (newly created), Processing (assigned), Completed (resolved). You can filter by status in the Work Orders tab.'; + + @override + String get faq4Q => 'What is the difference between Boss and Employee?'; + + @override + String get faq4A => + 'Boss has full permissions including employee management and data reports. Employee can only handle daily operations like publishing events and verifying orders.'; + + @override + String get faq5Q => 'How to switch theme and language?'; + + @override + String get faq5A => + 'Go to \'Settings\', tap \'Theme\' to switch light/dark mode; tap \'Language\' to switch between Simplified Chinese and English.'; + + @override + String get faq6Q => 'What if scan doesn\'t work?'; + + @override + String get faq6A => + 'Ensure camera permission is granted, align QR code in the center, keep proper distance and lighting. You can also manually enter the order number.'; + + @override + String get stillQuestions => 'Still have questions?'; + + @override + String get contactTeam => 'Contact our support team for more help'; + + @override + String get contactService => 'Contact Support'; + + @override + String get customerName => 'Customer Name'; + + @override + String get contactPhone => 'Contact Phone'; + + @override + String get belongScenic => 'Belong Scenic'; + + @override + String get name => 'Name'; + + @override + String get phone => 'Phone'; + + @override + String get createdWorkOrder => 'created the work order'; + + @override + String get staffStartedProcessing => 'Staff started processing'; + + @override + String get workOrderFinished => 'Work order finished'; + + @override + String get unknownWorkOrder => 'Unknown Work Order'; + + @override + String get workOrderLoadFailed => 'Work order info failed to load'; + + @override + String get system => 'System'; + + @override + String get other => 'Other'; + + @override + String get receptionist => 'Receptionist'; + + @override + String get maintenance => 'Maintenance'; + + @override + String get cleaner => 'Cleaner'; + + @override + String get admin => 'Admin'; + + @override + String get lifeguard => 'Lifeguard'; + + @override + String get chef => 'Chef'; + + @override + String get security => 'Security'; + + @override + String get active => 'Active'; + + @override + String get onLeave => 'On Leave'; + + @override + String get confirmDelete => 'Confirm Delete'; + + @override + String get delete => 'Delete'; + + @override + String get openScenic => 'Open'; + + @override + String get monday => 'Mon'; + + @override + String get tuesday => 'Tue'; + + @override + String get wednesday => 'Wed'; + + @override + String get thursday => 'Thu'; + + @override + String get friday => 'Fri'; + + @override + String get saturday => 'Sat'; + + @override + String get sunday => 'Sun'; + + @override + String get ticket => 'Ticket'; + + @override + String get catering => 'Catering'; + + @override + String get adultTicket => 'Adult Ticket'; + + @override + String get luxurySuite => 'Luxury Suite'; + + @override + String get familyPackage => 'Family Package'; + + @override + String get spaPackage => 'SPA Package'; + + @override + String get orderUnit => ' orders'; + + @override + String get latestVersion => 'Latest Version'; + + @override + String get agreementTitle => 'Zhinian Manage User Agreement'; + + @override + String get privacyTitle => 'Zhinian Manage Privacy Policy'; + + @override + String get lastUpdated => 'Last updated: December 2024'; + + @override + String get agreementSection1 => '1. Scope'; + + @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.'; + + @override + String get agreementSection2 => '2. Account Registration'; + + @override + String get 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.'; + + @override + String get agreementSection3 => '3. Service Usage Rules'; + + @override + String get agreementPara3 => + 'You must comply with laws and regulations when using the service. Do not engage in illegal activities or interfere with normal service operation.'; + + @override + String get agreementSection4 => '4. Data Ownership'; + + @override + String get agreementPara4 => + 'Business data generated during your use belongs to you. We may use anonymized data for service optimization but will not disclose your specific business data to third parties.'; + + @override + String get agreementSection5 => '5. Service Changes and Termination'; + + @override + String get agreementPara5 => + 'We reserve the right to adjust service content as needed. If you violate this agreement, we may suspend or terminate your account.'; + + @override + String get agreementSection6 => '6. Disclaimer'; + + @override + String get agreementPara6 => + 'We strive to ensure service stability and security, but are not liable for service interruptions or data loss caused by force majeure, network failures, or third-party reasons.'; + + @override + String get agreementSection7 => '7. Agreement Modifications'; + + @override + String get agreementPara7 => + 'We reserve the right to modify this agreement at any time. Modified agreements will be published in the app, and continued use constitutes acceptance.'; + + @override + String get privacySection1 => '1. Information Collection'; + + @override + String get privacyPara1 => + 'We collect: account registration info (phone, name), business operation data (events, orders, work orders), and device info (for service optimization).'; + + @override + String get privacySection2 => '2. Information Use'; + + @override + String get privacyPara2 => + 'We use your information to: provide and manage services, verify identity, ensure security, optimize services, and provide customer support. We do not sell your personal information.'; + + @override + String get privacySection3 => '3. Information Sharing'; + + @override + String get privacyPara3 => + 'We may share information when: we have your explicit consent, required by law, or to protect our legitimate rights. We will anonymize data before sharing.'; + + @override + String get privacySection4 => '4. Information Protection'; + + @override + String get privacyPara4 => + 'We use industry-standard security measures including data encryption, access control, and security audits. However, internet transmission cannot guarantee absolute security.'; + + @override + String get privacySection5 => '5. Your Rights'; + + @override + String get privacyPara5 => + 'You have the right to access, correct, and delete your personal information. You may also cancel your account at any time, after which we will delete your data (except as required by law).'; + + @override + String get privacySection6 => '6. Cookies & Local Storage'; + + @override + String get privacyPara6 => + 'We use local storage to save login status and app settings for a better experience. You can clear this data in settings.'; + + @override + String get privacySection7 => '7. Privacy Policy Updates'; + + @override + String get privacyPara7 => + 'We may update this privacy policy from time to time. Updates will be notified in the app, and continued use constitutes acceptance.'; + + @override + String get productIntro => 'Product Introduction'; + + @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.'; + + @override + String get officialWebsite => 'Official Website'; + + @override + String get servicePhone => 'Service Phone'; + + @override + String get techSupport => 'Tech Support'; + + @override + String get companyAddress => 'Address'; + + @override + String get copyright => 'Copyright 2024 Zhinian Tech'; + + @override + String get allRightsReserved => 'All Rights Reserved'; +} diff --git a/lib/l10n/app_localizations_th.dart b/lib/l10n/app_localizations_th.dart new file mode 100644 index 0000000..23b6c84 --- /dev/null +++ b/lib/l10n/app_localizations_th.dart @@ -0,0 +1,844 @@ +// ignore: unused_import +import 'package:intl/intl.dart' as intl; +import 'app_localizations.dart'; + +// ignore_for_file: type=lint + +/// The translations for Thai (`th`). +class AppLocalizationsTh extends AppLocalizations { + AppLocalizationsTh([String locale = 'th']) : super(locale); + + @override + String get appTitle => 'จื้อเหนียน จัดการ'; + + @override + String get appSubtitle => 'ระบบจัดการโรงแรมและสถานที่ท่องเที่ยวอัจฉริยะ'; + + @override + String get welcomeLogin => 'ยินดีต้อนรับ'; + + @override + String get enterAccountInfo => 'กรุณากรอกข้อมูลบัญชีของคุณ'; + + @override + String get phoneNumber => 'หมายเลขโทรศัพท์'; + + @override + String get enterPhone => 'กรอกหมายเลขโทรศัพท์'; + + @override + String get password => 'รหัสผ่าน'; + + @override + String get enterPassword => 'กรอกรหัสผ่าน'; + + @override + String get forgotPassword => 'ลืมรหัสผ่าน?'; + + @override + String get login => 'เข้าสู่ระบบ'; + + @override + String get agreePrefix => 'ฉันได้อ่านและยอมรับ'; + + @override + String get userAgreement => 'ข้อตกลงผู้ใช้'; + + @override + String get privacyPolicy => 'นโยบายความเป็นส่วนตัว'; + + @override + String get roleHint => 'เบอร์ลงท้าย 88 = เจ้าของ อื่นๆ = พนักงาน'; + + @override + String get loginSuccess => 'เข้าสู่ระบบสำเร็จ'; + + @override + String get loginFailed => 'เข้าสู่ระบบไม่สำเร็จ'; + + @override + String get aiAssistant => 'ผู้ช่วย AI จื้อเหนียน'; + + @override + String get online => 'ออนไลน์'; + + @override + String get boss => 'เจ้าของ'; + + @override + String get employee => 'พนักงาน'; + + @override + String get settings => 'การตั้งค่า'; + + @override + String get logout => 'ออกจากระบบ'; + + @override + String get enterMessage => 'พิมพ์ข้อความ...'; + + @override + String get eventPublish => 'เผยแพร่เหตุการณ์'; + + @override + String get orderWork => 'คำสั่งซื้อและใบงาน'; + + @override + String get verify => 'ตรวจสอบ'; + + @override + String get appExtension => 'ส่วนขยายแอป'; + + @override + String get thinking => 'กำลังคิด...'; + + @override + String get sendFailed => 'ขออภัย ส่งข้อความไม่สำเร็จ กรุณาลองใหม่ภายหลัง'; + + @override + String get publishEvent => 'เผยแพร่เหตุการณ์'; + + @override + String get eventRecords => 'บันทึกเหตุการณ์'; + + @override + String get basicInfo => 'ข้อมูลพื้นฐาน'; + + @override + String get entityName => 'ชื่อหน่วยงาน'; + + @override + String get entityNameHint => 'กรอกชื่อหน่วยงาน เช่น บาร์ล็อบบี้ สระว่ายน้ำ'; + + @override + String get eventDesc => 'รายละเอียดเหตุการณ์'; + + @override + String get eventDescHint => 'โปรดอธิบายเหตุการณ์โดยละเอียด...'; + + @override + String get eventImages => 'รูปภาพเหตุการณ์'; + + @override + String get addImage => 'เพิ่มรูปภาพ'; + + @override + String get timeSettings => 'การตั้งค่าเวลา'; + + @override + String get publishTime => 'เวลาเผยแพร่'; + + @override + String get effectiveTime => 'เวลามีผล'; + + @override + String get endTime => 'เวลาสิ้นสุด'; + + @override + String get selectTime => 'กรุณาเลือก'; + + @override + String get setDefaultTime => 'ตั้งค่าเวลาเริ่มต้น'; + + @override + String get popupReminder => 'แจ้งเตือนป๊อปอัพ'; + + @override + String get popupReminderDesc => 'แสดงการแจ้งเตือนป๊อปอัพเมื่อเหตุการณ์มีผล'; + + @override + String get publish => 'เผยแพร่เหตุการณ์'; + + @override + String get publishSuccess => 'เผยแพร่เหตุการณ์สำเร็จ'; + + @override + String get noEvents => 'ไม่มีบันทึกเหตุการณ์'; + + @override + String get published => 'เผยแพร่แล้ว'; + + @override + String get draft => 'ร่าง'; + + @override + String get expired => 'หมดอายุ'; + + @override + String get ordersAndWorkOrders => 'คำสั่งซื้อและใบงาน'; + + @override + String get workOrder => 'ใบงาน'; + + @override + String get order => 'คำสั่งซื้อ'; + + @override + String get all => 'ทั้งหมด'; + + @override + String get pending => 'รอดำเนินการ'; + + @override + String get processing => 'กำลังดำเนินการ'; + + @override + String get completed => 'เสร็จสิ้น'; + + @override + String get pendingPayment => 'รอชำระเงิน'; + + @override + String get pendingVerify => 'รอตรวจสอบ'; + + @override + String get verified => 'ตรวจสอบแล้ว'; + + @override + String get pendingRefund => 'รอคืนเงิน'; + + @override + String get refunded => 'คืนเงินแล้ว'; + + @override + String get noWorkOrders => 'ไม่มีใบงาน'; + + @override + String get noOrders => 'ไม่มีคำสั่งซื้อ'; + + @override + String get workOrderDetail => 'รายละเอียดใบงาน'; + + @override + String get orderDetail => 'รายละเอียดคำสั่งซื้อ'; + + @override + String get unknownLocation => 'ไม่ทราบตำแหน่ง'; + + @override + String get workOrderId => 'รหัสใบงาน'; + + @override + String get creator => 'ผู้สร้าง'; + + @override + String get assignee => 'ผู้รับมอบหมาย'; + + @override + String get notAssigned => 'ยังไม่ได้มอบหมาย'; + + @override + String get category => 'หมวดหมู่'; + + @override + String get priority => 'ลำดับความสำคัญ'; + + @override + String get urgent => 'ด่วน'; + + @override + String get high => 'สูง'; + + @override + String get normal => 'ปกติ'; + + @override + String get createTime => 'เวลาสร้าง'; + + @override + String get problemDesc => 'รายละเอียดปัญหา'; + + @override + String get attachments => 'ไฟล์แนบ'; + + @override + String get progress => 'ความคืบหน้า'; + + @override + String get workOrderCreated => 'สร้างใบงาน'; + + @override + String get workOrderAssigned => 'มอบหมายใบงาน'; + + @override + String get startProcessing => 'เริ่มดำเนินการ'; + + @override + String get workOrderCompleted => 'ใบงานเสร็จสิ้น'; + + @override + String get productInfo => 'ข้อมูลสินค้า'; + + @override + String get quantity => 'จำนวน'; + + @override + String get unitPrice => 'ราคาต่อหน่วย'; + + @override + String get customerInfo => 'ข้อมูลลูกค้า'; + + @override + String get paymentInfo => 'ข้อมูลการชำระเงิน'; + + @override + String get orderAmount => 'ยอดคำสั่งซื้อ'; + + @override + String get actualAmount => 'ยอดชำระจริง'; + + @override + String get paymentTime => 'เวลาชำระเงิน'; + + @override + String get orderNo => 'หมายเลขคำสั่งซื้อ'; + + @override + String get verifyTime => 'เวลาตรวจสอบ'; + + @override + String get refundTime => 'เวลาคืนเงิน'; + + @override + String get verifyCode => 'รหัสตรวจสอบ'; + + @override + String get remark => 'หมายเหตุ'; + + @override + String get scanResult => 'ผลการสแกน'; + + @override + String get scanning => 'กำลังสแกน...'; + + @override + String get scanSuccess => 'สแกนสำเร็จ'; + + @override + String get orderInfo => 'ข้อมูลคำสั่งซื้อ'; + + @override + String get product => 'สินค้า'; + + @override + String get amount => 'จำนวนเงิน'; + + @override + String get contactCustomer => 'ติดต่อลูกค้า'; + + @override + String get confirmVerify => 'ยืนยันการตรวจสอบ'; + + @override + String get cancel => 'ยกเลิก'; + + @override + String get confirm => 'ยืนยัน'; + + @override + String get notVerifiable => 'คำสั่งซื้อนี้ไม่สามารถตรวจสอบได้'; + + @override + String get verifySuccess => 'ตรวจสอบสำเร็จ!'; + + @override + String get appConfiguration => 'การตั้งค่าแอป'; + + @override + String get businessExtension => 'ขยายธุรกิจ'; + + @override + String get systemLabel => '系统'; + + @override + String get user => 'ผู้ใช้'; + + @override + String get pushNotification => 'การแจ้งเตือนแบบพุช'; + + @override + String get soundNotification => 'การแจ้งเตือนด้วยเสียง'; + + @override + String get enabled => 'เปิดใช้งาน'; + + @override + String get disabled => 'ปิดใช้งาน'; + + @override + String get themeSettings => 'ธีม'; + + @override + String get lightMode => 'โหมดสว่าง'; + + @override + String get darkMode => 'โหมดมืด'; + + @override + String get followSystem => 'ตามระบบ'; + + @override + String get languageSettings => 'ภาษา'; + + @override + String get simplifiedChinese => '简体中文'; + + @override + String get english => 'English'; + + @override + String get thai => 'ภาษาไทย'; + + @override + String get storeManagement => 'การจัดการร้านค้า'; + + @override + String get storeDesc => 'จัดการข้อมูลโรงแรมและสถานที่ท่องเที่ยว'; + + @override + String get employeeManagement => 'การจัดการพนักงาน'; + + @override + String get employeeDesc => 'เพิ่มและจัดการบัญชีพนักงาน'; + + @override + String get dataReport => 'รายงานข้อมูล'; + + @override + String get reportDesc => 'ดูการวิเคราะห์การดำเนินงาน'; + + @override + String get appMarket => 'ตลาดแอป'; + + @override + String get marketDesc => 'ขยายโมดูลฟีเจอร์เพิ่มเติม'; + + @override + String get helpCenter => 'ศูนย์ช่วยเหลือ'; + + @override + String get helpDesc => 'คู่มือใช้งานและคำถามที่พบบ่อย'; + + @override + String get aboutUs => 'เกี่ยวกับเรา'; + + @override + String get version => 'เวอร์ชัน'; + + @override + String get logoutConfirm => 'คุณแน่ใจหรือไม่ว่าต้องการออกจากระบบ?'; + + @override + String get employeeList => 'การจัดการพนักงาน'; + + @override + String get noEmployees => 'ไม่มีพนักงาน'; + + @override + String get addEmployee => 'เพิ่มพนักงาน'; + + @override + String get employeeName => 'ชื่อพนักงาน'; + + @override + String get enterEmployeeName => 'กรอกชื่อพนักงาน'; + + @override + String get enterPhoneNumber => 'กรอกหมายเลขโทรศัพท์'; + + @override + String get role => 'ตำแหน่ง'; + + @override + String get confirmAdd => 'ยืนยันการเพิ่ม'; + + @override + String get call => 'โทร'; + + @override + String get setOnLeave => 'ตั้งสถานะลาออก'; + + @override + String get restoreActive => 'กู้คืนสถานะใช้งาน'; + + @override + String get deleteEmployee => 'ลบพนักงาน'; + + @override + String deleteConfirm(Object name) { + return 'คุณแน่ใจหรือไม่ว่าต้องการลบพนักงาน $name?'; + } + + @override + String get addSuccess => 'เพิ่มพนักงานสำเร็จ'; + + @override + String get deleteSuccess => 'ลบพนักงานแล้ว'; + + @override + String get pleaseComplete => 'กรุณากรอกข้อมูลให้ครบถ้วน'; + + @override + String get storeList => 'การจัดการร้านค้า'; + + @override + String get hotel => 'โรงแรม'; + + @override + String get scenic => 'สถานที่ท่องเที่ยว'; + + @override + String get spa => 'สปา'; + + @override + String get open => 'เปิด'; + + @override + String get closed => 'ปิด'; + + @override + String get totalRevenue => 'รายได้รวม'; + + @override + String get orderCount => 'จำนวนคำสั่งซื้อ'; + + @override + String get avgOrderValue => 'มูลค่าคำสั่งซื้อเฉลี่ย'; + + @override + String get verifyRate => 'อัตราการตรวจสอบ'; + + @override + String get vsLastWeek => 'เทียบกับสัปดาห์ที่แล้ว'; + + @override + String get orderTrend => 'แนวโน้มคำสั่งซื้อ'; + + @override + String get revenueComposition => 'องค์ประกอบรายได้'; + + @override + String get topSales => 'สินค้าขายดี'; + + @override + String get installed => 'ติดตั้งแล้ว'; + + @override + String get install => 'ติดตั้ง'; + + @override + String get installSuccess => 'ติดตั้งสำเร็จ'; + + @override + String get opening => 'กำลังเปิด...'; + + @override + String get needHelp => 'ต้องการความช่วยเหลือ?'; + + @override + String get faqSubtitle => 'คำถามที่พบบ่อยเพื่อค้นหาคำตอบอย่างรวดเร็ว'; + + @override + String get faq => 'คำถามที่พบบ่อย'; + + @override + String get faq1Q => 'วิธีเผยแพร่เหตุการณ์?'; + + @override + String get faq1A => + 'แตะปุ่ม \'+\' บนหน้าหลัก เลือก \'เผยแพร่เหตุการณ์\' กรอกชื่อหน่วยงาน รายละเอียด เวลา ฯลฯ แล้วแตะเผยแพร่ ระบบจะส่งการแจ้งเตือนโดยอัตโนมัติ'; + + @override + String get faq2Q => 'วิธีตรวจสอบคำสั่งซื้อ?'; + + @override + String get faq2A => + 'แตะ \'ตรวจสอบ\' ในเมนูลัด สแกน QR Code ด้วยกล้อง หลังจากจดจำได้แล้ว ไปที่รายละเอียดคำสั่งซื้อและแตะ \'ตรวจสอบ\' เพื่อยืนยัน'; + + @override + String get faq3Q => 'สถานะใบงานมีอะไรบ้าง?'; + + @override + String get faq3A => + 'สามสถานะ: รอดำเนินการ (สร้างใหม่) กำลังดำเนินการ (มอบหมายแล้ว) เสร็จสิ้น (แก้ไขแล้ว) คุณสามารถกรองตามสถานะในแท็บใบงาน'; + + @override + String get faq4Q => 'ความแตกต่างระหว่างเจ้าของและพนักงานคืออะไร?'; + + @override + String get faq4A => + 'เจ้าของมีสิทธิ์ทั้งหมดรวมถึงการจัดการพนักงานและรายงานข้อมูล พนักงานสามารถจัดการงานประจำวันเช่นเผยแพร่เหตุการณ์และตรวจสอบคำสั่งซื้อได้เท่านั้น'; + + @override + String get faq5Q => 'วิธีสลับธีมและภาษา?'; + + @override + String get faq5A => + 'ไปที่ \'การตั้งค่า\' แตะ \'ธีม\' เพื่อสลับโหมดสว่าง/มืด แตะ \'ภาษา\' เพื่อสลับระหว่างภาษาจีนตัวย่อและภาษาอังกฤษ'; + + @override + String get faq6Q => 'ถ้าสแกนไม่ได้ต้องทำอย่างไร?'; + + @override + String get faq6A => + 'ตรวจสอบให้แน่ใจว่าได้อนุญาตสิทธิ์กล้อง จัด QR Code ให้อยู่ตรงกลาง รักษาระยะห่างและแสงที่เหมาะสม คุณยังสามารถกรอกหมายเลขคำสั่งซื้อด้วยตนเองได้'; + + @override + String get stillQuestions => 'ยังมีคำถาม?'; + + @override + String get contactTeam => + 'ติดต่อทีมสนับสนุนของเราเพื่อขอความช่วยเหลือเพิ่มเติม'; + + @override + String get contactService => 'ติดต่อฝ่ายสนับสนุน'; + + @override + String get customerName => 'ชื่อลูกค้า'; + + @override + String get contactPhone => 'เบอร์ติดต่อ'; + + @override + String get belongScenic => 'สังกัดสถานที่ท่องเที่ยว'; + + @override + String get name => 'ชื่อ'; + + @override + String get phone => 'โทรศัพท์'; + + @override + String get createdWorkOrder => 'สร้างใบงานนี้'; + + @override + String get staffStartedProcessing => 'พนักงานเริ่มดำเนินการ'; + + @override + String get workOrderFinished => 'ใบงานเสร็จสิ้นแล้ว'; + + @override + String get unknownWorkOrder => 'ใบงานที่ไม่รู้จัก'; + + @override + String get workOrderLoadFailed => 'โหลดข้อมูลใบงานล้มเหลว'; + + @override + String get system => 'ระบบ'; + + @override + String get other => 'อื่นๆ'; + + @override + String get receptionist => 'แผนกต้อนรับ'; + + @override + String get maintenance => 'ช่างซ่อมบำรุง'; + + @override + String get cleaner => 'แม่บ้าน'; + + @override + String get admin => 'ธุรการ'; + + @override + String get lifeguard => 'ผู้ช่วยชีวิต'; + + @override + String get chef => 'พ่อครัว'; + + @override + String get security => 'รปภ.'; + + @override + String get active => 'ปฏิบัติงาน'; + + @override + String get onLeave => 'ลา'; + + @override + String get confirmDelete => 'ยืนยันการลบ'; + + @override + String get delete => 'ลบ'; + + @override + String get openScenic => 'เปิดให้เข้าชม'; + + @override + String get monday => 'จ.'; + + @override + String get tuesday => 'อ.'; + + @override + String get wednesday => 'พ.'; + + @override + String get thursday => 'พฤ.'; + + @override + String get friday => 'ศ.'; + + @override + String get saturday => 'ส.'; + + @override + String get sunday => 'อา.'; + + @override + String get ticket => 'ตั๋ว'; + + @override + String get catering => 'อาหารและเครื่องดื่ม'; + + @override + String get adultTicket => 'ตั๋วผู้ใหญ่'; + + @override + String get luxurySuite => 'ห้องสวีทหรู'; + + @override + String get familyPackage => 'แพ็คเกจครอบครัว'; + + @override + String get spaPackage => 'แพ็คเกจสปา'; + + @override + String get orderUnit => ' รายการ'; + + @override + String get latestVersion => 'เวอร์ชันล่าสุด'; + + @override + String get agreementTitle => 'ข้อตกลงผู้ใช้ จื้อเหนียน จัดการ'; + + @override + String get privacyTitle => 'นโยบายความเป็นส่วนตัว จื้อเหนียน จัดการ'; + + @override + String get lastUpdated => 'อัปเดตล่าสุด: ธันวาคม 2024'; + + @override + String get agreementSection1 => '1. ขอบเขตข้อตกลง'; + + @override + String get agreementPara1 => + 'ข้อตกลงนี้เป็นข้อตกลงระหว่างคุณกับ จื้อเหนียน เทค เกี่ยวกับการใช้บริการ จื้อเหนียน จัดการ บริการรวมถึงแต่ไม่จำกัดเพียง การเผยแพร่เหตุการณ์ การจัดการคำสั่งซื้อ การประมวลผลใบงาน การตรวจสอบ QR ฯลฯ'; + + @override + String get agreementSection2 => '2. การลงทะเบียนบัญชี'; + + @override + String get agreementPara2 => + 'คุณต้องลงทะเบียนบัญชีเพื่อใช้บริการของเรา กรุณากรอกหมายเลขโทรศัพท์ที่ถูกต้องและตั้งรหัสผ่านที่ปลอดภัย คุณรับผิดชอบต่อการกระทำทั้งหมดภายใต้บัญชีของคุณ'; + + @override + String get agreementSection3 => '3. กฎการใช้บริการ'; + + @override + String get agreementPara3 => + 'คุณต้องปฏิบัติตามกฎหมายและระเบียบข้อบังคับเมื่อใช้บริการ ห้ามกระทำการที่ผิดกฎหมายหรือรบกวนการทำงานปกติของบริการ'; + + @override + String get agreementSection4 => '4. กรรมสิทธิ์ข้อมูล'; + + @override + String get agreementPara4 => + 'ข้อมูลธุรกิจที่เกิดขึ้นระหว่างการใช้งานเป็นของคุณ เราอาจใช้ข้อมูลที่ไม่ระบุตัวตนเพื่อปรับปรุงบริการ แต่จะไม่เปิดเผยข้อมูลธุรกิจเฉพาะของคุณแก่บุคคลที่สาม'; + + @override + String get agreementSection5 => '5. การเปลี่ยนแปลงและยุติบริการ'; + + @override + String get agreementPara5 => + 'เราขอสงวนสิทธิ์ในการปรับเนื้อหาบริการตามความจำเป็น หากคุณละเมิดข้อตกลงนี้ เราอาจระงับหรือยุติบัญชีของคุณ'; + + @override + String get agreementSection6 => '6. ข้อจำกัดความรับผิดชอบ'; + + @override + String get agreementPara6 => + 'เราพยายามอย่างเต็มที่เพื่อความเสถียรและความปลอดภัยของบริการ แต่ไม่รับผิดชอบต่อการหยุดชะงักของบริการหรือการสูญหายของข้อมูลที่เกิดจากเหตุสุดวิสัย ความล้มเหลวของเครือข่าย หรือสาเหตุจากบุคคลที่สาม'; + + @override + String get agreementSection7 => '7. การแก้ไขข้อตกลง'; + + @override + String get agreementPara7 => + 'เราขอสงวนสิทธิ์ในการแก้ไขข้อตกลงนี้ได้ตลอดเวลา ข้อตกลงที่แก้ไขจะเผยแพร่ในแอป และการใช้งานต่อถือว่ายอมรับ'; + + @override + String get privacySection1 => '1. การเก็บรวบรวมข้อมูล'; + + @override + String get privacyPara1 => + 'เราเก็บรวบรวม: ข้อมูลการลงทะเบียนบัญชี (เบอร์โทร ชื่อ) ข้อมูลการดำเนินงานธุรกิจ (เหตุการณ์ คำสั่งซื้อ ใบงาน) และข้อมูลอุปกรณ์ (เพื่อปรับปรุงบริการ)'; + + @override + String get privacySection2 => '2. การใช้ข้อมูล'; + + @override + String get privacyPara2 => + 'เราใช้ข้อมูลของคุณเพื่อ: ให้บริการและจัดการบริการ ยืนยันตัวตน รักษาความปลอดภัย ปรับปรุงบริการ และให้การสนับสนุนลูกค้า เราจะไม่ขายข้อมูลส่วนบุคคลของคุณ'; + + @override + String get privacySection3 => '3. การแบ่งปันข้อมูล'; + + @override + String get privacyPara3 => + 'เราอาจแบ่งปันข้อมูลเมื่อ: ได้รับความยินยอมจากคุณอย่างชัดเจน กฎหมายกำหนด หรือเพื่อปกป้องสิทธิ์ที่ชอบธรรมของเรา เราจะทำให้ข้อมูลไม่ระบุตัวตนก่อนแบ่งปัน'; + + @override + String get privacySection4 => '4. การปกป้องข้อมูล'; + + @override + String get privacyPara4 => + 'เราใช้มาตรการรักษาความปลอดภัยตามมาตรฐานอุตสาหกรรม รวมถึงการเข้ารหัสข้อมูล การควบคุมการเข้าถึง และการตรวจสอบความปลอดภัย แต่การส่งผ่านอินเทอร์เน็ตไม่สามารถรับประกันความปลอดภัยได้อย่างสมบูรณ์'; + + @override + String get privacySection5 => '5. สิทธิ์ของคุณ'; + + @override + String get privacyPara5 => + 'คุณมีสิทธิ์เข้าถึง แก้ไข และลบข้อมูลส่วนบุคคลของคุณ คุณยังสามารถยกเลิกบัญชีได้ตลอดเวลา หลังจากนั้นเราจะลบข้อมูลของคุณ (ยกเว้นตามที่กฎหมายกำหนด)'; + + @override + String get privacySection6 => '6. Cookies และการจัดเก็บข้อมูลในเครื่อง'; + + @override + String get privacyPara6 => + 'เราใช้การจัดเก็บข้อมูลในเครื่องเพื่อบันทึกสถานะการเข้าสู่ระบบและการตั้งค่าแอป เพื่อประสบการณ์ที่ดีขึ้น คุณสามารถล้างข้อมูลเหล่านี้ได้ในการตั้งค่า'; + + @override + String get privacySection7 => '7. การอัปเดตนโยบายความเป็นส่วนตัว'; + + @override + String get privacyPara7 => + 'เราอาจอัปเดตนโยบายความเป็นส่วนตัวเป็นครั้งคราว การอัปเดตจะแจ้งในแอป และการใช้งานต่อถือว่ายอมรับ'; + + @override + String get productIntro => 'แนะนำผลิตภัณฑ์'; + + @override + String get productDesc => + 'จื้อเหนียน จัดการเป็นแอปจัดการอัจฉริยะสำหรับโรงแรมและสถานที่ท่องเที่ยว ด้วยผู้ช่วย AI การเผยแพร่เหตุการณ์ การจัดการคำสั่งซื้อ และการตรวจสอบ QR ช่วยให้ธุรกิจดำเนินงานได้อย่างมีประสิทธิภาพ'; + + @override + String get officialWebsite => 'เว็บไซต์ทางการ'; + + @override + String get servicePhone => 'โทรบริการ'; + + @override + String get techSupport => 'ฝ่ายสนับสนุนทางเทคนิค'; + + @override + String get companyAddress => 'ที่อยู่'; + + @override + String get copyright => 'ลิขสิทธิ์ 2024 จื้อเหนียน เทค'; + + @override + String get allRightsReserved => 'สงวนลิขสิทธิ์ทั้งหมด'; +} diff --git a/lib/l10n/app_localizations_zh.dart b/lib/l10n/app_localizations_zh.dart new file mode 100644 index 0000000..6d777ba --- /dev/null +++ b/lib/l10n/app_localizations_zh.dart @@ -0,0 +1,837 @@ +// ignore: unused_import +import 'package:intl/intl.dart' as intl; +import 'app_localizations.dart'; + +// ignore_for_file: type=lint + +/// The translations for Chinese (`zh`). +class AppLocalizationsZh extends AppLocalizations { + AppLocalizationsZh([String locale = 'zh']) : super(locale); + + @override + String get appTitle => '智念管理'; + + @override + String get appSubtitle => '酒店景区智能管理平台'; + + @override + String get welcomeLogin => '欢迎登录'; + + @override + String get enterAccountInfo => '请输入您的账号信息'; + + @override + String get phoneNumber => '手机号'; + + @override + String get enterPhone => '请输入手机号'; + + @override + String get password => '密码'; + + @override + String get enterPassword => '请输入密码'; + + @override + String get forgotPassword => '忘记密码?'; + + @override + String get login => '登录'; + + @override + String get agreePrefix => '我已阅读并同意'; + + @override + String get userAgreement => '《用户协议》'; + + @override + String get privacyPolicy => '《隐私政策》'; + + @override + String get roleHint => '尾号88为老板角色,其他为员工角色'; + + @override + String get loginSuccess => '登录成功'; + + @override + String get loginFailed => '登录失败'; + + @override + String get aiAssistant => '智念AI助手'; + + @override + String get online => '在线'; + + @override + String get boss => '老板'; + + @override + String get employee => '员工'; + + @override + String get settings => '设置'; + + @override + String get logout => '退出登录'; + + @override + String get enterMessage => '输入消息...'; + + @override + String get eventPublish => '事件发布'; + + @override + String get orderWork => '订单工单'; + + @override + String get verify => '核销'; + + @override + String get appExtension => '应用扩展'; + + @override + String get thinking => '思考中...'; + + @override + String get sendFailed => '抱歉,消息发送失败,请稍后重试。'; + + @override + String get publishEvent => '发布事件'; + + @override + String get eventRecords => '事件记录'; + + @override + String get basicInfo => '基本信息'; + + @override + String get entityName => '实体名称'; + + @override + String get entityNameHint => '请输入实体名称,如:大堂吧、游泳池'; + + @override + String get eventDesc => '事件描述'; + + @override + String get eventDescHint => '请详细描述事件内容...'; + + @override + String get eventImages => '事件图片'; + + @override + String get addImage => '添加图片'; + + @override + String get timeSettings => '时间设置'; + + @override + String get publishTime => '发布时间'; + + @override + String get effectiveTime => '生效时间'; + + @override + String get endTime => '结束时间'; + + @override + String get selectTime => '请选择'; + + @override + String get setDefaultTime => '设置默认时间'; + + @override + String get popupReminder => '弹窗提醒'; + + @override + String get popupReminderDesc => '事件生效时弹出提醒通知'; + + @override + String get publish => '发布事件'; + + @override + String get publishSuccess => '事件发布成功'; + + @override + String get noEvents => '暂无事件记录'; + + @override + String get published => '已发布'; + + @override + String get draft => '草稿'; + + @override + String get expired => '已过期'; + + @override + String get ordersAndWorkOrders => '订单工单'; + + @override + String get workOrder => '工单'; + + @override + String get order => '订单'; + + @override + String get all => '全部'; + + @override + String get pending => '待处理'; + + @override + String get processing => '处理中'; + + @override + String get completed => '已完成'; + + @override + String get pendingPayment => '待支付'; + + @override + String get pendingVerify => '待核销'; + + @override + String get verified => '已核销'; + + @override + String get pendingRefund => '待退款'; + + @override + String get refunded => '已退款'; + + @override + String get noWorkOrders => '暂无工单'; + + @override + String get noOrders => '暂无订单'; + + @override + String get workOrderDetail => '工单详情'; + + @override + String get orderDetail => '订单详情'; + + @override + String get unknownLocation => '未知位置'; + + @override + String get workOrderId => '工单编号'; + + @override + String get creator => '创建人'; + + @override + String get assignee => '指派给'; + + @override + String get notAssigned => '未指派'; + + @override + String get category => '分类'; + + @override + String get priority => '优先级'; + + @override + String get urgent => '紧急'; + + @override + String get high => '高'; + + @override + String get normal => '普通'; + + @override + String get createTime => '创建时间'; + + @override + String get problemDesc => '问题描述'; + + @override + String get attachments => '附件图片'; + + @override + String get progress => '处理进度'; + + @override + String get workOrderCreated => '工单创建'; + + @override + String get workOrderAssigned => '工单指派'; + + @override + String get startProcessing => '开始处理'; + + @override + String get workOrderCompleted => '工单完成'; + + @override + String get productInfo => '商品信息'; + + @override + String get quantity => '数量'; + + @override + String get unitPrice => '单价'; + + @override + String get customerInfo => '客户信息'; + + @override + String get paymentInfo => '支付信息'; + + @override + String get orderAmount => '订单金额'; + + @override + String get actualAmount => '实付金额'; + + @override + String get paymentTime => '支付时间'; + + @override + String get orderNo => '订单编号'; + + @override + String get verifyTime => '核销时间'; + + @override + String get refundTime => '退款时间'; + + @override + String get verifyCode => '核销码'; + + @override + String get remark => '备注'; + + @override + String get scanResult => '扫码结果'; + + @override + String get scanning => '正在识别...'; + + @override + String get scanSuccess => '识别成功'; + + @override + String get orderInfo => '订单信息'; + + @override + String get product => '商品'; + + @override + String get amount => '金额'; + + @override + String get contactCustomer => '联系客户'; + + @override + String get confirmVerify => '确认核销'; + + @override + String get cancel => '取消'; + + @override + String get confirm => '确认'; + + @override + String get notVerifiable => '该订单不可核销'; + + @override + String get verifySuccess => '核销成功!'; + + @override + String get appConfiguration => '应用配置'; + + @override + String get businessExtension => '业务扩展'; + + @override + String get systemLabel => '系统'; + + @override + String get user => '用户'; + + @override + String get pushNotification => '消息通知'; + + @override + String get soundNotification => '声音提醒'; + + @override + String get enabled => '已开启'; + + @override + String get disabled => '已关闭'; + + @override + String get themeSettings => '主题设置'; + + @override + String get lightMode => '浅色模式'; + + @override + String get darkMode => '深色模式'; + + @override + String get followSystem => '跟随系统'; + + @override + String get languageSettings => '语言设置'; + + @override + String get simplifiedChinese => '简体中文'; + + @override + String get english => 'English'; + + @override + String get thai => 'ภาษาไทย'; + + @override + String get storeManagement => '门店管理'; + + @override + String get storeDesc => '管理酒店/景区信息'; + + @override + String get employeeManagement => '员工管理'; + + @override + String get employeeDesc => '添加和管理员工账号'; + + @override + String get dataReport => '数据报表'; + + @override + String get reportDesc => '查看运营数据分析'; + + @override + String get appMarket => '应用市场'; + + @override + String get marketDesc => '扩展更多功能模块'; + + @override + String get helpCenter => '帮助中心'; + + @override + String get helpDesc => '使用指南与常见问题'; + + @override + String get aboutUs => '关于我们'; + + @override + String get version => '版本'; + + @override + String get logoutConfirm => '确定要退出登录吗?'; + + @override + String get employeeList => '员工管理'; + + @override + String get noEmployees => '暂无员工'; + + @override + String get addEmployee => '添加员工'; + + @override + String get employeeName => '员工姓名'; + + @override + String get enterEmployeeName => '请输入员工姓名'; + + @override + String get enterPhoneNumber => '请输入手机号'; + + @override + String get role => '岗位'; + + @override + String get confirmAdd => '确认添加'; + + @override + String get call => '拨打电话'; + + @override + String get setOnLeave => '设为休假'; + + @override + String get restoreActive => '恢复在职'; + + @override + String get deleteEmployee => '删除员工'; + + @override + String deleteConfirm(Object name) { + return '确定要删除员工 $name 吗?'; + } + + @override + String get addSuccess => '员工添加成功'; + + @override + String get deleteSuccess => '员工已删除'; + + @override + String get pleaseComplete => '请填写完整信息'; + + @override + String get storeList => '门店管理'; + + @override + String get hotel => '酒店'; + + @override + String get scenic => '景区'; + + @override + String get spa => 'SPA'; + + @override + String get open => '打开'; + + @override + String get closed => '已关闭'; + + @override + String get totalRevenue => '总营收'; + + @override + String get orderCount => '订单数'; + + @override + String get avgOrderValue => '客单价'; + + @override + String get verifyRate => '核销率'; + + @override + String get vsLastWeek => '较上周'; + + @override + String get orderTrend => '订单趋势'; + + @override + String get revenueComposition => '营收构成'; + + @override + String get topSales => '热销排行'; + + @override + String get installed => '已安装'; + + @override + String get install => '安装'; + + @override + String get installSuccess => '安装成功'; + + @override + String get opening => '正在打开...'; + + @override + String get needHelp => '需要帮助?'; + + @override + String get faqSubtitle => '常见问题解答,快速找到答案'; + + @override + String get faq => '常见问题'; + + @override + String get faq1Q => '如何发布事件通知?'; + + @override + String get faq1A => + '在首页点击底部输入框左侧的「+」按钮,选择「事件发布」,填写实体名称、事件描述、时间等信息后点击发布即可。系统会自动在设定时间推送提醒。'; + + @override + String get faq2Q => '如何核销订单?'; + + @override + String get faq2A => + '在首页快捷菜单中点击「核销」,使用手机摄像头扫描客户出示的二维码,识别成功后会跳转到订单详情页,点击底部「核销」按钮并确认即可完成。'; + + @override + String get faq3Q => '工单状态有哪些?'; + + @override + String get faq3A => + '工单共有三种状态:待处理(刚创建未分配)、处理中(已分配给相关人员)、已完成(问题已解决)。可以在「订单工单」页面的工单Tab中按状态筛选。'; + + @override + String get faq4Q => '老板和员工角色有什么区别?'; + + @override + String get faq4A => + '老板角色拥有完整的操作权限,包括员工管理、数据报表等管理功能;员工角色只能处理日常业务,如发布事件、处理工单、核销订单等。'; + + @override + String get faq5Q => '如何切换主题和语言?'; + + @override + String get faq5A => '进入「设置」页面,点击「主题设置」可在浅色/深色模式间切换;点击「语言设置」可切换简体中文和英文界面。'; + + @override + String get faq6Q => '扫码没有反应怎么办?'; + + @override + String get faq6A => '请确保摄像头权限已开启,将二维码对准扫描框中央,保持适当距离和光线。如持续无法识别,可手动输入订单号进行核销。'; + + @override + String get stillQuestions => '仍有问题?'; + + @override + String get contactTeam => '联系我们的客服团队获取更多帮助'; + + @override + String get contactService => '联系客服'; + + @override + String get customerName => '客户姓名'; + + @override + String get contactPhone => '联系电话'; + + @override + String get belongScenic => '所属景区'; + + @override + String get name => '姓名'; + + @override + String get phone => '电话'; + + @override + String get createdWorkOrder => '创建了该工单'; + + @override + String get staffStartedProcessing => '维修人员开始处理'; + + @override + String get workOrderFinished => '工单已处理完成'; + + @override + String get unknownWorkOrder => '未知工单'; + + @override + String get workOrderLoadFailed => '工单信息加载失败'; + + @override + String get system => '系统'; + + @override + String get other => '其他'; + + @override + String get receptionist => '前台'; + + @override + String get maintenance => '维修工'; + + @override + String get cleaner => '保洁'; + + @override + String get admin => '行政'; + + @override + String get lifeguard => '救生员'; + + @override + String get chef => '厨师'; + + @override + String get security => '保安'; + + @override + String get active => '在职'; + + @override + String get onLeave => '休假'; + + @override + String get confirmDelete => '确认删除'; + + @override + String get delete => '删除'; + + @override + String get openScenic => '开放中'; + + @override + String get monday => '周一'; + + @override + String get tuesday => '周二'; + + @override + String get wednesday => '周三'; + + @override + String get thursday => '周四'; + + @override + String get friday => '周五'; + + @override + String get saturday => '周六'; + + @override + String get sunday => '周日'; + + @override + String get ticket => '门票'; + + @override + String get catering => '餐饮'; + + @override + String get adultTicket => '成人门票'; + + @override + String get luxurySuite => '豪华套房'; + + @override + String get familyPackage => '亲子套票'; + + @override + String get spaPackage => 'SPA套餐'; + + @override + String get orderUnit => '单'; + + @override + String get latestVersion => '最新版本'; + + @override + String get agreementTitle => '智念管理用户协议'; + + @override + String get privacyTitle => '智念管理隐私政策'; + + @override + String get lastUpdated => '最后更新日期:2024年12月'; + + @override + String get agreementSection1 => '一、协议范围'; + + @override + String get agreementPara1 => + '本协议是您与智念科技之间关于使用智念管理服务所订立的协议。智念管理服务包括但不限于事件发布、订单管理、工单处理、扫码核销等功能。'; + + @override + String get agreementSection2 => '二、账号注册'; + + @override + String get agreementPara2 => + '您需要注册一个账号才能使用我们的服务。注册时需提供真实有效的手机号码,并设置安全密码。您应对账号下的所有行为负责。'; + + @override + String get agreementSection3 => '三、服务使用规范'; + + @override + String get agreementPara3 => + '您在使用服务时应遵守法律法规,不得利用本服务从事违法违规活动。不得干扰服务的正常运行,不得侵犯他人合法权益。'; + + @override + String get agreementSection4 => '四、数据所有权'; + + @override + String get agreementPara4 => + '您在使用服务过程中产生的业务数据归您所有。我们有权在必要范围内使用匿名化数据进行服务优化,但不会向第三方披露您的具体业务数据。'; + + @override + String get agreementSection5 => '五、服务变更与终止'; + + @override + String get agreementPara5 => '我们有权根据业务发展需要调整服务内容。如您违反本协议,我们有权暂停或终止您的账号使用权限。'; + + @override + String get agreementSection6 => '六、免责声明'; + + @override + String get agreementPara6 => + '我们尽力保证服务的稳定性和安全性,但不对因不可抗力、网络故障、第三方原因等导致的服务中断或数据丢失承担责任。'; + + @override + String get agreementSection7 => '七、协议修改'; + + @override + String get agreementPara7 => '我们保留随时修改本协议的权利。修改后的协议将在应用内公布,继续使用即视为接受修改。'; + + @override + String get privacySection1 => '一、信息收集'; + + @override + String get privacyPara1 => + '我们收集的信息包括:账号注册信息(手机号、姓名)、业务操作数据(事件、订单、工单记录)、设备信息(用于服务优化)。'; + + @override + String get privacySection2 => '二、信息使用'; + + @override + String get privacyPara2 => + '我们使用您的信息用于:提供和管理服务、身份验证、安全保障、服务优化、客服支持。我们不会将您的个人信息出售给任何第三方。'; + + @override + String get privacySection3 => '三、信息共享'; + + @override + String get privacyPara3 => + '在以下情况下我们可能会共享信息:获得您的明确同意、法律法规要求、保护我们的合法权益。共享前我们会进行匿名化处理。'; + + @override + String get privacySection4 => '四、信息保护'; + + @override + String get privacyPara4 => + '我们采用行业标准的安全措施保护您的信息,包括数据加密、访问控制、安全审计等。但互联网传输无法保证绝对安全。'; + + @override + String get privacySection5 => '五、您的权利'; + + @override + String get privacyPara5 => + '您有权访问、更正、删除您的个人信息。您也可以随时注销账号,注销后我们将删除您的个人数据(法律法规要求保留的除外)。'; + + @override + String get privacySection6 => '六、Cookie 与本地存储'; + + @override + String get privacyPara6 => '我们使用本地存储技术保存登录状态和应用设置,以提升使用体验。您可以在设置中清除这些数据。'; + + @override + String get privacySection7 => '七、隐私政策更新'; + + @override + String get privacyPara7 => '我们可能会不时更新本隐私政策。更新后的政策将在应用内提示,继续使用即视为接受更新。'; + + @override + String get productIntro => '产品简介'; + + @override + String get productDesc => + '智念管理是一款专为酒店、景区等文旅行业打造的智能管理应用。通过AI助手、事件发布、订单工单管理、扫码核销等功能,帮助商家高效运营、提升服务质量。'; + + @override + String get officialWebsite => '官方网站'; + + @override + String get servicePhone => '客服电话'; + + @override + String get techSupport => '技术支持'; + + @override + String get companyAddress => '公司地址'; + + @override + String get copyright => 'Copyright 2024 智念科技'; + + @override + String get allRightsReserved => 'All Rights Reserved'; +} diff --git a/lib/l10n/app_th.arb b/lib/l10n/app_th.arb new file mode 100644 index 0000000..71a702d --- /dev/null +++ b/lib/l10n/app_th.arb @@ -0,0 +1,272 @@ +{ + "@@locale": "th", + "appTitle": "จื้อเหนียน จัดการ", + "appSubtitle": "ระบบจัดการโรงแรมและสถานที่ท่องเที่ยวอัจฉริยะ", + "welcomeLogin": "ยินดีต้อนรับ", + "enterAccountInfo": "กรุณากรอกข้อมูลบัญชีของคุณ", + "phoneNumber": "หมายเลขโทรศัพท์", + "enterPhone": "กรอกหมายเลขโทรศัพท์", + "password": "รหัสผ่าน", + "enterPassword": "กรอกรหัสผ่าน", + "forgotPassword": "ลืมรหัสผ่าน?", + "login": "เข้าสู่ระบบ", + "agreePrefix": "ฉันได้อ่านและยอมรับ", + "userAgreement": "ข้อตกลงผู้ใช้", + "privacyPolicy": "นโยบายความเป็นส่วนตัว", + "roleHint": "เบอร์ลงท้าย 88 = เจ้าของ อื่นๆ = พนักงาน", + "loginSuccess": "เข้าสู่ระบบสำเร็จ", + "loginFailed": "เข้าสู่ระบบไม่สำเร็จ", + "aiAssistant": "ผู้ช่วย AI จื้อเหนียน", + "online": "ออนไลน์", + "boss": "เจ้าของ", + "employee": "พนักงาน", + "settings": "การตั้งค่า", + "logout": "ออกจากระบบ", + "enterMessage": "พิมพ์ข้อความ...", + "eventPublish": "เผยแพร่เหตุการณ์", + "orderWork": "คำสั่งซื้อและใบงาน", + "verify": "ตรวจสอบ", + "appExtension": "ส่วนขยายแอป", + "thinking": "กำลังคิด...", + "sendFailed": "ขออภัย ส่งข้อความไม่สำเร็จ กรุณาลองใหม่ภายหลัง", + "publishEvent": "เผยแพร่เหตุการณ์", + "eventRecords": "บันทึกเหตุการณ์", + "basicInfo": "ข้อมูลพื้นฐาน", + "entityName": "ชื่อหน่วยงาน", + "entityNameHint": "กรอกชื่อหน่วยงาน เช่น บาร์ล็อบบี้ สระว่ายน้ำ", + "eventDesc": "รายละเอียดเหตุการณ์", + "eventDescHint": "โปรดอธิบายเหตุการณ์โดยละเอียด...", + "eventImages": "รูปภาพเหตุการณ์", + "addImage": "เพิ่มรูปภาพ", + "timeSettings": "การตั้งค่าเวลา", + "publishTime": "เวลาเผยแพร่", + "effectiveTime": "เวลามีผล", + "endTime": "เวลาสิ้นสุด", + "selectTime": "กรุณาเลือก", + "setDefaultTime": "ตั้งค่าเวลาเริ่มต้น", + "popupReminder": "แจ้งเตือนป๊อปอัพ", + "popupReminderDesc": "แสดงการแจ้งเตือนป๊อปอัพเมื่อเหตุการณ์มีผล", + "publish": "เผยแพร่เหตุการณ์", + "publishSuccess": "เผยแพร่เหตุการณ์สำเร็จ", + "noEvents": "ไม่มีบันทึกเหตุการณ์", + "published": "เผยแพร่แล้ว", + "draft": "ร่าง", + "expired": "หมดอายุ", + "ordersAndWorkOrders": "คำสั่งซื้อและใบงาน", + "workOrder": "ใบงาน", + "order": "คำสั่งซื้อ", + "all": "ทั้งหมด", + "pending": "รอดำเนินการ", + "processing": "กำลังดำเนินการ", + "completed": "เสร็จสิ้น", + "pendingPayment": "รอชำระเงิน", + "pendingVerify": "รอตรวจสอบ", + "verified": "ตรวจสอบแล้ว", + "pendingRefund": "รอคืนเงิน", + "refunded": "คืนเงินแล้ว", + "noWorkOrders": "ไม่มีใบงาน", + "noOrders": "ไม่มีคำสั่งซื้อ", + "workOrderDetail": "รายละเอียดใบงาน", + "orderDetail": "รายละเอียดคำสั่งซื้อ", + "unknownLocation": "ไม่ทราบตำแหน่ง", + "workOrderId": "รหัสใบงาน", + "creator": "ผู้สร้าง", + "assignee": "ผู้รับมอบหมาย", + "notAssigned": "ยังไม่ได้มอบหมาย", + "category": "หมวดหมู่", + "priority": "ลำดับความสำคัญ", + "urgent": "ด่วน", + "high": "สูง", + "normal": "ปกติ", + "createTime": "เวลาสร้าง", + "problemDesc": "รายละเอียดปัญหา", + "attachments": "ไฟล์แนบ", + "progress": "ความคืบหน้า", + "workOrderCreated": "สร้างใบงาน", + "workOrderAssigned": "มอบหมายใบงาน", + "startProcessing": "เริ่มดำเนินการ", + "workOrderCompleted": "ใบงานเสร็จสิ้น", + "productInfo": "ข้อมูลสินค้า", + "quantity": "จำนวน", + "unitPrice": "ราคาต่อหน่วย", + "customerInfo": "ข้อมูลลูกค้า", + "paymentInfo": "ข้อมูลการชำระเงิน", + "orderAmount": "ยอดคำสั่งซื้อ", + "actualAmount": "ยอดชำระจริง", + "paymentTime": "เวลาชำระเงิน", + "orderNo": "หมายเลขคำสั่งซื้อ", + "verifyTime": "เวลาตรวจสอบ", + "refundTime": "เวลาคืนเงิน", + "verifyCode": "รหัสตรวจสอบ", + "remark": "หมายเหตุ", + "scanResult": "ผลการสแกน", + "scanning": "กำลังสแกน...", + "scanSuccess": "สแกนสำเร็จ", + "orderInfo": "ข้อมูลคำสั่งซื้อ", + "product": "สินค้า", + "amount": "จำนวนเงิน", + "contactCustomer": "ติดต่อลูกค้า", + "confirmVerify": "ยืนยันการตรวจสอบ", + "cancel": "ยกเลิก", + "confirm": "ยืนยัน", + "notVerifiable": "คำสั่งซื้อนี้ไม่สามารถตรวจสอบได้", + "verifySuccess": "ตรวจสอบสำเร็จ!", + "pushNotification": "การแจ้งเตือนแบบพุช", + "soundNotification": "การแจ้งเตือนด้วยเสียง", + "enabled": "เปิดใช้งาน", + "disabled": "ปิดใช้งาน", + "themeSettings": "ธีม", + "lightMode": "โหมดสว่าง", + "darkMode": "โหมดมืด", + "followSystem": "ตามระบบ", + "languageSettings": "ภาษา", + "simplifiedChinese": "简体中文", + "english": "English", + "thai": "ภาษาไทย", + "storeManagement": "การจัดการร้านค้า", + "storeDesc": "จัดการข้อมูลโรงแรมและสถานที่ท่องเที่ยว", + "employeeManagement": "การจัดการพนักงาน", + "employeeDesc": "เพิ่มและจัดการบัญชีพนักงาน", + "dataReport": "รายงานข้อมูล", + "reportDesc": "ดูการวิเคราะห์การดำเนินงาน", + "appMarket": "ตลาดแอป", + "marketDesc": "ขยายโมดูลฟีเจอร์เพิ่มเติม", + "helpCenter": "ศูนย์ช่วยเหลือ", + "helpDesc": "คู่มือใช้งานและคำถามที่พบบ่อย", + "aboutUs": "เกี่ยวกับเรา", + "version": "เวอร์ชัน", + "logoutConfirm": "คุณแน่ใจหรือไม่ว่าต้องการออกจากระบบ?", + "appConfiguration": "การตั้งค่าแอป", + "businessExtension": "ขยายธุรกิจ", + "system": "ระบบ", + "user": "ผู้ใช้", + "employeeList": "การจัดการพนักงาน", + "noEmployees": "ไม่มีพนักงาน", + "addEmployee": "เพิ่มพนักงาน", + "employeeName": "ชื่อพนักงาน", + "enterEmployeeName": "กรอกชื่อพนักงาน", + "enterPhoneNumber": "กรอกหมายเลขโทรศัพท์", + "role": "ตำแหน่ง", + "confirmAdd": "ยืนยันการเพิ่ม", + "call": "โทร", + "setOnLeave": "ตั้งสถานะลาออก", + "restoreActive": "กู้คืนสถานะใช้งาน", + "deleteEmployee": "ลบพนักงาน", + "deleteConfirm": "คุณแน่ใจหรือไม่ว่าต้องการลบพนักงาน {name}?", + "addSuccess": "เพิ่มพนักงานสำเร็จ", + "deleteSuccess": "ลบพนักงานแล้ว", + "pleaseComplete": "กรุณากรอกข้อมูลให้ครบถ้วน", + "storeList": "การจัดการร้านค้า", + "hotel": "โรงแรม", + "scenic": "สถานที่ท่องเที่ยว", + "spa": "สปา", + "open": "เปิด", + "closed": "ปิด", + "totalRevenue": "รายได้รวม", + "orderCount": "จำนวนคำสั่งซื้อ", + "avgOrderValue": "มูลค่าคำสั่งซื้อเฉลี่ย", + "verifyRate": "อัตราการตรวจสอบ", + "vsLastWeek": "เทียบกับสัปดาห์ที่แล้ว", + "orderTrend": "แนวโน้มคำสั่งซื้อ", + "revenueComposition": "องค์ประกอบรายได้", + "topSales": "สินค้าขายดี", + "installed": "ติดตั้งแล้ว", + "install": "ติดตั้ง", + "installSuccess": "ติดตั้งสำเร็จ", + "opening": "กำลังเปิด...", + "needHelp": "ต้องการความช่วยเหลือ?", + "faqSubtitle": "คำถามที่พบบ่อยเพื่อค้นหาคำตอบอย่างรวดเร็ว", + "faq": "คำถามที่พบบ่อย", + "faq1Q": "วิธีเผยแพร่เหตุการณ์?", + "faq1A": "แตะปุ่ม '+' บนหน้าหลัก เลือก 'เผยแพร่เหตุการณ์' กรอกชื่อหน่วยงาน รายละเอียด เวลา ฯลฯ แล้วแตะเผยแพร่ ระบบจะส่งการแจ้งเตือนโดยอัตโนมัติ", + "faq2Q": "วิธีตรวจสอบคำสั่งซื้อ?", + "faq2A": "แตะ 'ตรวจสอบ' ในเมนูลัด สแกน QR Code ด้วยกล้อง หลังจากจดจำได้แล้ว ไปที่รายละเอียดคำสั่งซื้อและแตะ 'ตรวจสอบ' เพื่อยืนยัน", + "faq3Q": "สถานะใบงานมีอะไรบ้าง?", + "faq3A": "สามสถานะ: รอดำเนินการ (สร้างใหม่) กำลังดำเนินการ (มอบหมายแล้ว) เสร็จสิ้น (แก้ไขแล้ว) คุณสามารถกรองตามสถานะในแท็บใบงาน", + "faq4Q": "ความแตกต่างระหว่างเจ้าของและพนักงานคืออะไร?", + "faq4A": "เจ้าของมีสิทธิ์ทั้งหมดรวมถึงการจัดการพนักงานและรายงานข้อมูล พนักงานสามารถจัดการงานประจำวันเช่นเผยแพร่เหตุการณ์และตรวจสอบคำสั่งซื้อได้เท่านั้น", + "faq5Q": "วิธีสลับธีมและภาษา?", + "faq5A": "ไปที่ 'การตั้งค่า' แตะ 'ธีม' เพื่อสลับโหมดสว่าง/มืด แตะ 'ภาษา' เพื่อสลับระหว่างภาษาจีนตัวย่อและภาษาอังกฤษ", + "faq6Q": "ถ้าสแกนไม่ได้ต้องทำอย่างไร?", + "faq6A": "ตรวจสอบให้แน่ใจว่าได้อนุญาตสิทธิ์กล้อง จัด QR Code ให้อยู่ตรงกลาง รักษาระยะห่างและแสงที่เหมาะสม คุณยังสามารถกรอกหมายเลขคำสั่งซื้อด้วยตนเองได้", + "stillQuestions": "ยังมีคำถาม?", + "contactTeam": "ติดต่อทีมสนับสนุนของเราเพื่อขอความช่วยเหลือเพิ่มเติม", + "contactService": "ติดต่อฝ่ายสนับสนุน", + "productIntro": "แนะนำผลิตภัณฑ์", + "productDesc": "จื้อเหนียน จัดการเป็นแอปจัดการอัจฉริยะสำหรับโรงแรมและสถานที่ท่องเที่ยว ด้วยผู้ช่วย AI การเผยแพร่เหตุการณ์ การจัดการคำสั่งซื้อ และการตรวจสอบ QR ช่วยให้ธุรกิจดำเนินงานได้อย่างมีประสิทธิภาพ", + "officialWebsite": "เว็บไซต์ทางการ", + "servicePhone": "โทรบริการ", + "techSupport": "ฝ่ายสนับสนุนทางเทคนิค", + "companyAddress": "ที่อยู่", + "copyright": "ลิขสิทธิ์ 2024 จื้อเหนียน เทค", + "allRightsReserved": "สงวนลิขสิทธิ์ทั้งหมด", + "customerName": "ชื่อลูกค้า", + "contactPhone": "เบอร์ติดต่อ", + "belongScenic": "สังกัดสถานที่ท่องเที่ยว", + "name": "ชื่อ", + "phone": "โทรศัพท์", + "createdWorkOrder": "สร้างใบงานนี้", + "staffStartedProcessing": "พนักงานเริ่มดำเนินการ", + "workOrderFinished": "ใบงานเสร็จสิ้นแล้ว", + "unknownWorkOrder": "ใบงานที่ไม่รู้จัก", + "workOrderLoadFailed": "โหลดข้อมูลใบงานล้มเหลว", + "other": "อื่นๆ", + "receptionist": "แผนกต้อนรับ", + "maintenance": "ช่างซ่อมบำรุง", + "cleaner": "แม่บ้าน", + "admin": "ธุรการ", + "lifeguard": "ผู้ช่วยชีวิต", + "chef": "พ่อครัว", + "security": "รปภ.", + "active": "ปฏิบัติงาน", + "onLeave": "ลา", + "confirmDelete": "ยืนยันการลบ", + "delete": "ลบ", + "openScenic": "เปิดให้เข้าชม", + "monday": "จ.", + "tuesday": "อ.", + "wednesday": "พ.", + "thursday": "พฤ.", + "friday": "ศ.", + "saturday": "ส.", + "sunday": "อา.", + "ticket": "ตั๋ว", + "catering": "อาหารและเครื่องดื่ม", + "adultTicket": "ตั๋วผู้ใหญ่", + "luxurySuite": "ห้องสวีทหรู", + "familyPackage": "แพ็คเกจครอบครัว", + "spaPackage": "แพ็คเกจสปา", + "orderUnit": " รายการ", + "latestVersion": "เวอร์ชันล่าสุด", + "agreementTitle": "ข้อตกลงผู้ใช้ จื้อเหนียน จัดการ", + "privacyTitle": "นโยบายความเป็นส่วนตัว จื้อเหนียน จัดการ", + "lastUpdated": "อัปเดตล่าสุด: ธันวาคม 2024", + "agreementSection1": "1. ขอบเขตข้อตกลง", + "agreementPara1": "ข้อตกลงนี้เป็นข้อตกลงระหว่างคุณกับ จื้อเหนียน เทค เกี่ยวกับการใช้บริการ จื้อเหนียน จัดการ บริการรวมถึงแต่ไม่จำกัดเพียง การเผยแพร่เหตุการณ์ การจัดการคำสั่งซื้อ การประมวลผลใบงาน การตรวจสอบ QR ฯลฯ", + "agreementSection2": "2. การลงทะเบียนบัญชี", + "agreementPara2": "คุณต้องลงทะเบียนบัญชีเพื่อใช้บริการของเรา กรุณากรอกหมายเลขโทรศัพท์ที่ถูกต้องและตั้งรหัสผ่านที่ปลอดภัย คุณรับผิดชอบต่อการกระทำทั้งหมดภายใต้บัญชีของคุณ", + "agreementSection3": "3. กฎการใช้บริการ", + "agreementPara3": "คุณต้องปฏิบัติตามกฎหมายและระเบียบข้อบังคับเมื่อใช้บริการ ห้ามกระทำการที่ผิดกฎหมายหรือรบกวนการทำงานปกติของบริการ", + "agreementSection4": "4. กรรมสิทธิ์ข้อมูล", + "agreementPara4": "ข้อมูลธุรกิจที่เกิดขึ้นระหว่างการใช้งานเป็นของคุณ เราอาจใช้ข้อมูลที่ไม่ระบุตัวตนเพื่อปรับปรุงบริการ แต่จะไม่เปิดเผยข้อมูลธุรกิจเฉพาะของคุณแก่บุคคลที่สาม", + "agreementSection5": "5. การเปลี่ยนแปลงและยุติบริการ", + "agreementPara5": "เราขอสงวนสิทธิ์ในการปรับเนื้อหาบริการตามความจำเป็น หากคุณละเมิดข้อตกลงนี้ เราอาจระงับหรือยุติบัญชีของคุณ", + "agreementSection6": "6. ข้อจำกัดความรับผิดชอบ", + "agreementPara6": "เราพยายามอย่างเต็มที่เพื่อความเสถียรและความปลอดภัยของบริการ แต่ไม่รับผิดชอบต่อการหยุดชะงักของบริการหรือการสูญหายของข้อมูลที่เกิดจากเหตุสุดวิสัย ความล้มเหลวของเครือข่าย หรือสาเหตุจากบุคคลที่สาม", + "agreementSection7": "7. การแก้ไขข้อตกลง", + "agreementPara7": "เราขอสงวนสิทธิ์ในการแก้ไขข้อตกลงนี้ได้ตลอดเวลา ข้อตกลงที่แก้ไขจะเผยแพร่ในแอป และการใช้งานต่อถือว่ายอมรับ", + "privacySection1": "1. การเก็บรวบรวมข้อมูล", + "privacyPara1": "เราเก็บรวบรวม: ข้อมูลการลงทะเบียนบัญชี (เบอร์โทร ชื่อ) ข้อมูลการดำเนินงานธุรกิจ (เหตุการณ์ คำสั่งซื้อ ใบงาน) และข้อมูลอุปกรณ์ (เพื่อปรับปรุงบริการ)", + "privacySection2": "2. การใช้ข้อมูล", + "privacyPara2": "เราใช้ข้อมูลของคุณเพื่อ: ให้บริการและจัดการบริการ ยืนยันตัวตน รักษาความปลอดภัย ปรับปรุงบริการ และให้การสนับสนุนลูกค้า เราจะไม่ขายข้อมูลส่วนบุคคลของคุณ", + "privacySection3": "3. การแบ่งปันข้อมูล", + "privacyPara3": "เราอาจแบ่งปันข้อมูลเมื่อ: ได้รับความยินยอมจากคุณอย่างชัดเจน กฎหมายกำหนด หรือเพื่อปกป้องสิทธิ์ที่ชอบธรรมของเรา เราจะทำให้ข้อมูลไม่ระบุตัวตนก่อนแบ่งปัน", + "privacySection4": "4. การปกป้องข้อมูล", + "privacyPara4": "เราใช้มาตรการรักษาความปลอดภัยตามมาตรฐานอุตสาหกรรม รวมถึงการเข้ารหัสข้อมูล การควบคุมการเข้าถึง และการตรวจสอบความปลอดภัย แต่การส่งผ่านอินเทอร์เน็ตไม่สามารถรับประกันความปลอดภัยได้อย่างสมบูรณ์", + "privacySection5": "5. สิทธิ์ของคุณ", + "privacyPara5": "คุณมีสิทธิ์เข้าถึง แก้ไข และลบข้อมูลส่วนบุคคลของคุณ คุณยังสามารถยกเลิกบัญชีได้ตลอดเวลา หลังจากนั้นเราจะลบข้อมูลของคุณ (ยกเว้นตามที่กฎหมายกำหนด)", + "privacySection6": "6. Cookies และการจัดเก็บข้อมูลในเครื่อง", + "privacyPara6": "เราใช้การจัดเก็บข้อมูลในเครื่องเพื่อบันทึกสถานะการเข้าสู่ระบบและการตั้งค่าแอป เพื่อประสบการณ์ที่ดีขึ้น คุณสามารถล้างข้อมูลเหล่านี้ได้ในการตั้งค่า", + "privacySection7": "7. การอัปเดตนโยบายความเป็นส่วนตัว", + "privacyPara7": "เราอาจอัปเดตนโยบายความเป็นส่วนตัวเป็นครั้งคราว การอัปเดตจะแจ้งในแอป และการใช้งานต่อถือว่ายอมรับ" +} \ No newline at end of file diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb new file mode 100644 index 0000000..5e7e961 --- /dev/null +++ b/lib/l10n/app_zh.arb @@ -0,0 +1,294 @@ +{ + "@@locale": "zh", + "appTitle": "智念管理", + "appSubtitle": "酒店景区智能管理平台", + "welcomeLogin": "欢迎登录", + "enterAccountInfo": "请输入您的账号信息", + "phoneNumber": "手机号", + "enterPhone": "请输入手机号", + "password": "密码", + "enterPassword": "请输入密码", + "forgotPassword": "忘记密码?", + "login": "登录", + "agreePrefix": "我已阅读并同意", + "userAgreement": "《用户协议》", + "privacyPolicy": "《隐私政策》", + "roleHint": "尾号88为老板角色,其他为员工角色", + "loginSuccess": "登录成功", + "loginFailed": "登录失败", + + "aiAssistant": "智念AI助手", + "online": "在线", + "boss": "老板", + "employee": "员工", + "settings": "设置", + "logout": "退出登录", + "enterMessage": "输入消息...", + "eventPublish": "事件发布", + "orderWork": "订单工单", + "verify": "核销", + "appExtension": "应用扩展", + "thinking": "思考中...", + "sendFailed": "抱歉,消息发送失败,请稍后重试。", + + "publishEvent": "发布事件", + "eventRecords": "事件记录", + "basicInfo": "基本信息", + "entityName": "实体名称", + "entityNameHint": "请输入实体名称,如:大堂吧、游泳池", + "eventDesc": "事件描述", + "eventDescHint": "请详细描述事件内容...", + "eventImages": "事件图片", + "addImage": "添加图片", + "timeSettings": "时间设置", + "publishTime": "发布时间", + "effectiveTime": "生效时间", + "endTime": "结束时间", + "selectTime": "请选择", + "setDefaultTime": "设置默认时间", + "popupReminder": "弹窗提醒", + "popupReminderDesc": "事件生效时弹出提醒通知", + "publish": "发布事件", + "publishSuccess": "事件发布成功", + "noEvents": "暂无事件记录", + "published": "已发布", + "draft": "草稿", + "expired": "已过期", + + "ordersAndWorkOrders": "订单工单", + "workOrder": "工单", + "order": "订单", + "all": "全部", + "pending": "待处理", + "processing": "处理中", + "completed": "已完成", + "pendingPayment": "待支付", + "pendingVerify": "待核销", + "verified": "已核销", + "pendingRefund": "待退款", + "refunded": "已退款", + "noWorkOrders": "暂无工单", + "noOrders": "暂无订单", + + "workOrderDetail": "工单详情", + "orderDetail": "订单详情", + "unknownLocation": "未知位置", + "workOrderId": "工单编号", + "creator": "创建人", + "assignee": "指派给", + "notAssigned": "未指派", + "category": "分类", + "priority": "优先级", + "urgent": "紧急", + "high": "高", + "normal": "普通", + "createTime": "创建时间", + "problemDesc": "问题描述", + "attachments": "附件图片", + "progress": "处理进度", + "workOrderCreated": "工单创建", + "workOrderAssigned": "工单指派", + "startProcessing": "开始处理", + "workOrderCompleted": "工单完成", + + "productInfo": "商品信息", + "quantity": "数量", + "unitPrice": "单价", + "customerInfo": "客户信息", + "paymentInfo": "支付信息", + "orderAmount": "订单金额", + "actualAmount": "实付金额", + "paymentTime": "支付时间", + "orderNo": "订单编号", + "verifyTime": "核销时间", + "refundTime": "退款时间", + "verifyCode": "核销码", + "remark": "备注", + + "scanResult": "扫码结果", + "scanning": "正在识别...", + "scanSuccess": "识别成功", + "orderInfo": "订单信息", + "product": "商品", + "amount": "金额", + "contactCustomer": "联系客户", + "confirmVerify": "确认核销", + "cancel": "取消", + "confirm": "确认", + "notVerifiable": "该订单不可核销", + "verifySuccess": "核销成功!", + + "appConfiguration": "应用配置", + "businessExtension": "业务扩展", + "systemLabel": "系统", + "user": "用户", + "pushNotification": "消息通知", + "soundNotification": "声音提醒", + "enabled": "已开启", + "disabled": "已关闭", + "themeSettings": "主题设置", + "lightMode": "浅色模式", + "darkMode": "深色模式", + "followSystem": "跟随系统", + "languageSettings": "语言设置", + "simplifiedChinese": "简体中文", + "english": "English", + "thai": "ภาษาไทย", + "storeManagement": "门店管理", + "storeDesc": "管理酒店/景区信息", + "employeeManagement": "员工管理", + "employeeDesc": "添加和管理员工账号", + "dataReport": "数据报表", + "reportDesc": "查看运营数据分析", + "appMarket": "应用市场", + "marketDesc": "扩展更多功能模块", + "helpCenter": "帮助中心", + "helpDesc": "使用指南与常见问题", + "aboutUs": "关于我们", + "version": "版本", + "logoutConfirm": "确定要退出登录吗?", + + "employeeList": "员工管理", + "noEmployees": "暂无员工", + "addEmployee": "添加员工", + "employeeName": "员工姓名", + "enterEmployeeName": "请输入员工姓名", + "enterPhoneNumber": "请输入手机号", + "role": "岗位", + "confirmAdd": "确认添加", + "call": "拨打电话", + "setOnLeave": "设为休假", + "restoreActive": "恢复在职", + "deleteEmployee": "删除员工", + "deleteConfirm": "确定要删除员工 {name} 吗?", + "addSuccess": "员工添加成功", + "deleteSuccess": "员工已删除", + "pleaseComplete": "请填写完整信息", + + "storeList": "门店管理", + "hotel": "酒店", + "scenic": "景区", + "spa": "SPA", + "open": "营业中", + "closed": "已关闭", + + "totalRevenue": "总营收", + "orderCount": "订单数", + "avgOrderValue": "客单价", + "verifyRate": "核销率", + "vsLastWeek": "较上周", + "orderTrend": "订单趋势", + "revenueComposition": "营收构成", + "topSales": "热销排行", + + "installed": "已安装", + "open": "打开", + "install": "安装", + "installSuccess": "安装成功", + "opening": "正在打开...", + + "needHelp": "需要帮助?", + "faqSubtitle": "常见问题解答,快速找到答案", + "faq": "常见问题", + "faq1Q": "如何发布事件通知?", + "faq1A": "在首页点击底部输入框左侧的「+」按钮,选择「事件发布」,填写实体名称、事件描述、时间等信息后点击发布即可。系统会自动在设定时间推送提醒。", + "faq2Q": "如何核销订单?", + "faq2A": "在首页快捷菜单中点击「核销」,使用手机摄像头扫描客户出示的二维码,识别成功后会跳转到订单详情页,点击底部「核销」按钮并确认即可完成。", + "faq3Q": "工单状态有哪些?", + "faq3A": "工单共有三种状态:待处理(刚创建未分配)、处理中(已分配给相关人员)、已完成(问题已解决)。可以在「订单工单」页面的工单Tab中按状态筛选。", + "faq4Q": "老板和员工角色有什么区别?", + "faq4A": "老板角色拥有完整的操作权限,包括员工管理、数据报表等管理功能;员工角色只能处理日常业务,如发布事件、处理工单、核销订单等。", + "faq5Q": "如何切换主题和语言?", + "faq5A": "进入「设置」页面,点击「主题设置」可在浅色/深色模式间切换;点击「语言设置」可切换简体中文和英文界面。", + "faq6Q": "扫码没有反应怎么办?", + "faq6A": "请确保摄像头权限已开启,将二维码对准扫描框中央,保持适当距离和光线。如持续无法识别,可手动输入订单号进行核销。", + "stillQuestions": "仍有问题?", + "contactTeam": "联系我们的客服团队获取更多帮助", + "contactService": "联系客服", + + "customerName": "客户姓名", + "contactPhone": "联系电话", + "belongScenic": "所属景区", + "name": "姓名", + "phone": "电话", + + "createdWorkOrder": "创建了该工单", + "staffStartedProcessing": "维修人员开始处理", + "workOrderFinished": "工单已处理完成", + "unknownWorkOrder": "未知工单", + "workOrderLoadFailed": "工单信息加载失败", + "system": "系统", + "other": "其他", + + "receptionist": "前台", + "maintenance": "维修工", + "cleaner": "保洁", + "admin": "行政", + "lifeguard": "救生员", + "chef": "厨师", + "security": "保安", + "active": "在职", + "onLeave": "休假", + "confirmDelete": "确认删除", + "delete": "删除", + + "openScenic": "开放中", + + "monday": "周一", + "tuesday": "周二", + "wednesday": "周三", + "thursday": "周四", + "friday": "周五", + "saturday": "周六", + "sunday": "周日", + "ticket": "门票", + "catering": "餐饮", + "adultTicket": "成人门票", + "luxurySuite": "豪华套房", + "familyPackage": "亲子套票", + "spaPackage": "SPA套餐", + "orderUnit": "单", + + "latestVersion": "最新版本", + + "agreementTitle": "智念管理用户协议", + "privacyTitle": "智念管理隐私政策", + "lastUpdated": "最后更新日期:2024年12月", + "agreementSection1": "一、协议范围", + "agreementPara1": "本协议是您与智念科技之间关于使用智念管理服务所订立的协议。智念管理服务包括但不限于事件发布、订单管理、工单处理、扫码核销等功能。", + "agreementSection2": "二、账号注册", + "agreementPara2": "您需要注册一个账号才能使用我们的服务。注册时需提供真实有效的手机号码,并设置安全密码。您应对账号下的所有行为负责。", + "agreementSection3": "三、服务使用规范", + "agreementPara3": "您在使用服务时应遵守法律法规,不得利用本服务从事违法违规活动。不得干扰服务的正常运行,不得侵犯他人合法权益。", + "agreementSection4": "四、数据所有权", + "agreementPara4": "您在使用服务过程中产生的业务数据归您所有。我们有权在必要范围内使用匿名化数据进行服务优化,但不会向第三方披露您的具体业务数据。", + "agreementSection5": "五、服务变更与终止", + "agreementPara5": "我们有权根据业务发展需要调整服务内容。如您违反本协议,我们有权暂停或终止您的账号使用权限。", + "agreementSection6": "六、免责声明", + "agreementPara6": "我们尽力保证服务的稳定性和安全性,但不对因不可抗力、网络故障、第三方原因等导致的服务中断或数据丢失承担责任。", + "agreementSection7": "七、协议修改", + "agreementPara7": "我们保留随时修改本协议的权利。修改后的协议将在应用内公布,继续使用即视为接受修改。", + "privacySection1": "一、信息收集", + "privacyPara1": "我们收集的信息包括:账号注册信息(手机号、姓名)、业务操作数据(事件、订单、工单记录)、设备信息(用于服务优化)。", + "privacySection2": "二、信息使用", + "privacyPara2": "我们使用您的信息用于:提供和管理服务、身份验证、安全保障、服务优化、客服支持。我们不会将您的个人信息出售给任何第三方。", + "privacySection3": "三、信息共享", + "privacyPara3": "在以下情况下我们可能会共享信息:获得您的明确同意、法律法规要求、保护我们的合法权益。共享前我们会进行匿名化处理。", + "privacySection4": "四、信息保护", + "privacyPara4": "我们采用行业标准的安全措施保护您的信息,包括数据加密、访问控制、安全审计等。但互联网传输无法保证绝对安全。", + "privacySection5": "五、您的权利", + "privacyPara5": "您有权访问、更正、删除您的个人信息。您也可以随时注销账号,注销后我们将删除您的个人数据(法律法规要求保留的除外)。", + "privacySection6": "六、Cookie 与本地存储", + "privacyPara6": "我们使用本地存储技术保存登录状态和应用设置,以提升使用体验。您可以在设置中清除这些数据。", + "privacySection7": "七、隐私政策更新", + "privacyPara7": "我们可能会不时更新本隐私政策。更新后的政策将在应用内提示,继续使用即视为接受更新。", + + "productIntro": "产品简介", + "productDesc": "智念管理是一款专为酒店、景区等文旅行业打造的智能管理应用。通过AI助手、事件发布、订单工单管理、扫码核销等功能,帮助商家高效运营、提升服务质量。", + "officialWebsite": "官方网站", + "servicePhone": "客服电话", + "techSupport": "技术支持", + "companyAddress": "公司地址", + "copyright": "Copyright 2024 智念科技", + "allRightsReserved": "All Rights Reserved" +} diff --git a/lib/main.dart b/lib/main.dart new file mode 100644 index 0000000..d5ad3e8 --- /dev/null +++ b/lib/main.dart @@ -0,0 +1,15 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:hive_flutter/hive_flutter.dart'; +import 'app.dart'; + +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + await Hive.initFlutter(); + await Hive.openBox('zhinianBox'); + runApp( + const ProviderScope( + child: ZhinianApp(), + ), + ); +} diff --git a/lib/models/event.dart b/lib/models/event.dart new file mode 100644 index 0000000..043f40a --- /dev/null +++ b/lib/models/event.dart @@ -0,0 +1,85 @@ +class EventItem { + final String id; + final String entityName; + final String description; + final List images; + final DateTime publishTime; + final DateTime? effectiveTime; + final DateTime? endTime; + final bool popupReminder; + final String status; + final String? creatorName; + final DateTime createdAt; + + const EventItem({ + required this.id, + required this.entityName, + required this.description, + required this.images, + required this.publishTime, + this.effectiveTime, + this.endTime, + required this.popupReminder, + required this.status, + this.creatorName, + required this.createdAt, + }); + + EventItem copyWith({ + String? id, + String? entityName, + String? description, + List? images, + DateTime? publishTime, + DateTime? effectiveTime, + DateTime? endTime, + bool? popupReminder, + String? status, + String? creatorName, + DateTime? createdAt, + }) { + return EventItem( + id: id ?? this.id, + entityName: entityName ?? this.entityName, + description: description ?? this.description, + images: images ?? this.images, + publishTime: publishTime ?? this.publishTime, + effectiveTime: effectiveTime ?? this.effectiveTime, + endTime: endTime ?? this.endTime, + popupReminder: popupReminder ?? this.popupReminder, + status: status ?? this.status, + creatorName: creatorName ?? this.creatorName, + createdAt: createdAt ?? this.createdAt, + ); + } + + Map toJson() => { + 'id': id, + 'entityName': entityName, + 'description': description, + 'images': images, + 'publishTime': publishTime.toIso8601String(), + 'effectiveTime': effectiveTime?.toIso8601String(), + 'endTime': endTime?.toIso8601String(), + 'popupReminder': popupReminder, + 'status': status, + 'creatorName': creatorName, + 'createdAt': createdAt.toIso8601String(), + }; + + factory EventItem.fromJson(Map json) => EventItem( + id: json['id'] ?? '', + entityName: json['entityName'] ?? '', + description: json['description'] ?? '', + images: json['images'] != null ? List.from(json['images']) : [], + publishTime: DateTime.tryParse(json['publishTime'] ?? '') ?? DateTime.now(), + effectiveTime: json['effectiveTime'] != null + ? DateTime.tryParse(json['effectiveTime']) + : null, + endTime: json['endTime'] != null ? DateTime.tryParse(json['endTime']) : null, + popupReminder: json['popupReminder'] ?? false, + status: json['status'] ?? 'draft', + creatorName: json['creatorName'], + createdAt: DateTime.tryParse(json['createdAt'] ?? '') ?? DateTime.now(), + ); +} diff --git a/lib/models/message.dart b/lib/models/message.dart new file mode 100644 index 0000000..95e01b8 --- /dev/null +++ b/lib/models/message.dart @@ -0,0 +1,59 @@ +enum MessageType { text, markdown, image, loading } + +enum MessageSender { user, ai } + +class ChatMessage { + final String id; + final String content; + final MessageType type; + final MessageSender sender; + final DateTime timestamp; + final List? imageUrls; + + const ChatMessage({ + required this.id, + required this.content, + required this.type, + required this.sender, + required this.timestamp, + this.imageUrls, + }); + + ChatMessage copyWith({ + String? id, + String? content, + MessageType? type, + MessageSender? sender, + DateTime? timestamp, + List? imageUrls, + }) { + return ChatMessage( + id: id ?? this.id, + content: content ?? this.content, + type: type ?? this.type, + sender: sender ?? this.sender, + timestamp: timestamp ?? this.timestamp, + imageUrls: imageUrls ?? this.imageUrls, + ); + } + + Map toJson() => { + 'id': id, + 'content': content, + 'type': type.name, + 'sender': sender.name, + 'timestamp': timestamp.toIso8601String(), + 'imageUrls': imageUrls, + }; + + factory ChatMessage.fromJson(Map json) => ChatMessage( + id: json['id'] ?? '', + content: json['content'] ?? '', + type: MessageType.values.byName(json['type'] ?? 'text'), + sender: MessageSender.values.byName(json['sender'] ?? 'user'), + timestamp: DateTime.tryParse(json['timestamp'] ?? '') ?? DateTime.now(), + imageUrls: json['imageUrls'] != null + ? List.from(json['imageUrls']) + : null, + ); +} diff --git a/lib/models/order.dart b/lib/models/order.dart new file mode 100644 index 0000000..53cc6d5 --- /dev/null +++ b/lib/models/order.dart @@ -0,0 +1,155 @@ +import 'package:flutter/material.dart'; + +enum OrderStatus { + all, + pendingPayment, + pendingVerification, + verified, + pendingRefund, + refunded, +} + +class OrderItem { + final String id; + final String orderNo; + final String customerName; + final String customerPhone; + final String productName; + final double amount; + final OrderStatus status; + final DateTime createdAt; + final DateTime? paidAt; + final DateTime? verifiedAt; + final DateTime? refundedAt; + final String? verifyCode; + final int quantity; + final String? remark; + final String? scenicSpot; + + const OrderItem({ + required this.id, + required this.orderNo, + required this.customerName, + required this.customerPhone, + required this.productName, + required this.amount, + required this.status, + required this.createdAt, + this.paidAt, + this.verifiedAt, + this.refundedAt, + this.verifyCode, + required this.quantity, + this.remark, + this.scenicSpot, + }); + + String get statusText { + switch (status) { + case OrderStatus.pendingPayment: + return '待支付'; + case OrderStatus.pendingVerification: + return '待核销'; + case OrderStatus.verified: + return '已核销'; + case OrderStatus.pendingRefund: + return '待退款'; + case OrderStatus.refunded: + return '已退款'; + default: + return '全部'; + } + } + + Color get statusColor { + switch (status) { + case OrderStatus.pendingPayment: + return const Color(0xFFF59E0B); + case OrderStatus.pendingVerification: + return const Color(0xFF3B82F6); + case OrderStatus.verified: + return const Color(0xFF10B981); + case OrderStatus.pendingRefund: + return const Color(0xFFEF4444); + case OrderStatus.refunded: + return const Color(0xFF64748B); + default: + return const Color(0xFF64748B); + } + } + + bool get canVerify => status == OrderStatus.pendingVerification; + bool get canRefund => status == OrderStatus.pendingVerification || status == OrderStatus.verified; + + OrderItem copyWith({ + String? id, + String? orderNo, + String? customerName, + String? customerPhone, + String? productName, + double? amount, + OrderStatus? status, + DateTime? createdAt, + DateTime? paidAt, + DateTime? verifiedAt, + DateTime? refundedAt, + String? verifyCode, + int? quantity, + String? remark, + String? scenicSpot, + }) { + return OrderItem( + id: id ?? this.id, + orderNo: orderNo ?? this.orderNo, + customerName: customerName ?? this.customerName, + customerPhone: customerPhone ?? this.customerPhone, + productName: productName ?? this.productName, + amount: amount ?? this.amount, + status: status ?? this.status, + createdAt: createdAt ?? this.createdAt, + paidAt: paidAt ?? this.paidAt, + verifiedAt: verifiedAt ?? this.verifiedAt, + refundedAt: refundedAt ?? this.refundedAt, + verifyCode: verifyCode ?? this.verifyCode, + quantity: quantity ?? this.quantity, + remark: remark ?? this.remark, + scenicSpot: scenicSpot ?? this.scenicSpot, + ); + } + + Map toJson() => { + 'id': id, + 'orderNo': orderNo, + 'customerName': customerName, + 'customerPhone': customerPhone, + 'productName': productName, + 'amount': amount, + 'status': status.name, + 'createdAt': createdAt.toIso8601String(), + 'paidAt': paidAt?.toIso8601String(), + 'verifiedAt': verifiedAt?.toIso8601String(), + 'refundedAt': refundedAt?.toIso8601String(), + 'verifyCode': verifyCode, + 'quantity': quantity, + 'remark': remark, + 'scenicSpot': scenicSpot, + }; + + factory OrderItem.fromJson(Map json) => OrderItem( + id: json['id'] ?? '', + orderNo: json['orderNo'] ?? '', + customerName: json['customerName'] ?? '', + customerPhone: json['customerPhone'] ?? '', + productName: json['productName'] ?? '', + amount: (json['amount'] ?? 0.0).toDouble(), + status: OrderStatus.values.byName(json['status'] ?? 'pendingPayment'), + createdAt: DateTime.tryParse(json['createdAt'] ?? '') ?? DateTime.now(), + paidAt: json['paidAt'] != null ? DateTime.tryParse(json['paidAt']) : null, + verifiedAt: json['verifiedAt'] != null ? DateTime.tryParse(json['verifiedAt']) : null, + refundedAt: json['refundedAt'] != null ? DateTime.tryParse(json['refundedAt']) : null, + verifyCode: json['verifyCode'], + quantity: json['quantity'] ?? 1, + remark: json['remark'], + scenicSpot: json['scenicSpot'], + ); +} diff --git a/lib/models/user.dart b/lib/models/user.dart new file mode 100644 index 0000000..0438c7b --- /dev/null +++ b/lib/models/user.dart @@ -0,0 +1,69 @@ +enum UserRole { boss, employee } + +class User { + final String id; + final String phone; + final String name; + final String avatar; + final UserRole role; + final String token; + final String? hotelName; + final String? scenicName; + + const User({ + required this.id, + required this.phone, + required this.name, + required this.avatar, + required this.role, + required this.token, + this.hotelName, + this.scenicName, + }); + + bool get isBoss => role == UserRole.boss; + + User copyWith({ + String? id, + String? phone, + String? name, + String? avatar, + UserRole? role, + String? token, + String? hotelName, + String? scenicName, + }) { + return User( + id: id ?? this.id, + phone: phone ?? this.phone, + name: name ?? this.name, + avatar: avatar ?? this.avatar, + role: role ?? this.role, + token: token ?? this.token, + hotelName: hotelName ?? this.hotelName, + scenicName: scenicName ?? this.scenicName, + ); + } + + Map toJson() => { + 'id': id, + 'phone': phone, + 'name': name, + 'avatar': avatar, + 'role': role.name, + 'token': token, + 'hotelName': hotelName, + 'scenicName': scenicName, + }; + + factory User.fromJson(Map json) => User( + id: json['id'] ?? '', + phone: json['phone'] ?? '', + name: json['name'] ?? '', + avatar: json['avatar'] ?? '', + role: UserRole.values.byName(json['role'] ?? 'employee'), + token: json['token'] ?? '', + hotelName: json['hotelName'], + scenicName: json['scenicName'], + ); +} diff --git a/lib/models/work_order.dart b/lib/models/work_order.dart new file mode 100644 index 0000000..c9a1b49 --- /dev/null +++ b/lib/models/work_order.dart @@ -0,0 +1,121 @@ +import 'package:flutter/material.dart'; + +enum WorkOrderStatus { all, pending, processing, completed } + +class WorkOrder { + final String id; + final String title; + final String description; + final WorkOrderStatus status; + final String creatorName; + final String? assigneeName; + final DateTime createdAt; + final DateTime? completedAt; + final String priority; + final String category; + final List images; + final String? location; + + const WorkOrder({ + required this.id, + required this.title, + required this.description, + required this.status, + required this.creatorName, + this.assigneeName, + required this.createdAt, + this.completedAt, + required this.priority, + required this.category, + required this.images, + this.location, + }); + + String get statusText { + switch (status) { + case WorkOrderStatus.pending: + return '待处理'; + case WorkOrderStatus.processing: + return '处理中'; + case WorkOrderStatus.completed: + return '已完成'; + default: + return '全部'; + } + } + + Color get statusColor { + switch (status) { + case WorkOrderStatus.pending: + return const Color(0xFFF59E0B); + case WorkOrderStatus.processing: + return const Color(0xFF3B82F6); + case WorkOrderStatus.completed: + return const Color(0xFF10B981); + default: + return const Color(0xFF64748B); + } + } + + WorkOrder copyWith({ + String? id, + String? title, + String? description, + WorkOrderStatus? status, + String? creatorName, + String? assigneeName, + DateTime? createdAt, + DateTime? completedAt, + String? priority, + String? category, + List? images, + String? location, + }) { + return WorkOrder( + id: id ?? this.id, + title: title ?? this.title, + description: description ?? this.description, + status: status ?? this.status, + creatorName: creatorName ?? this.creatorName, + assigneeName: assigneeName ?? this.assigneeName, + createdAt: createdAt ?? this.createdAt, + completedAt: completedAt ?? this.completedAt, + priority: priority ?? this.priority, + category: category ?? this.category, + images: images ?? this.images, + location: location ?? this.location, + ); + } + + Map toJson() => { + 'id': id, + 'title': title, + 'description': description, + 'status': status.name, + 'creatorName': creatorName, + 'assigneeName': assigneeName, + 'createdAt': createdAt.toIso8601String(), + 'completedAt': completedAt?.toIso8601String(), + 'priority': priority, + 'category': category, + 'images': images, + 'location': location, + }; + + factory WorkOrder.fromJson(Map json) => WorkOrder( + id: json['id'] ?? '', + title: json['title'] ?? '', + description: json['description'] ?? '', + status: WorkOrderStatus.values.byName(json['status'] ?? 'pending'), + creatorName: json['creatorName'] ?? '', + assigneeName: json['assigneeName'], + createdAt: DateTime.tryParse(json['createdAt'] ?? '') ?? DateTime.now(), + completedAt: json['completedAt'] != null + ? DateTime.tryParse(json['completedAt']) + : null, + priority: json['priority'] ?? 'normal', + category: json['category'] ?? '', + images: json['images'] != null ? List.from(json['images']) : [], + location: json['location'], + ); +} diff --git a/lib/pages/about_page.dart b/lib/pages/about_page.dart new file mode 100644 index 0000000..6f027ea --- /dev/null +++ b/lib/pages/about_page.dart @@ -0,0 +1,169 @@ +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import '../theme.dart'; +import '../l10n/app_localizations.dart'; + +class AboutPage extends StatelessWidget { + const AboutPage({super.key}); + + @override + Widget build(BuildContext context) { + final l10n = AppLocalizations.of(context)!; + return Scaffold( + backgroundColor: AppColors.background, + appBar: AppBar(title: Text(l10n.aboutUs)), + body: SingleChildScrollView( + padding: const EdgeInsets.all(20), + child: Column( + children: [ + const SizedBox(height: 20), + Container( + width: 100, + height: 100, + decoration: BoxDecoration( + gradient: AppGradients.primary, + borderRadius: BorderRadius.circular(24), + ), + child: const Icon(Icons.auto_awesome, color: Colors.white, size: 48), + ), + const SizedBox(height: 20), + Text( + l10n.appTitle, + style: const TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: AppColors.textPrimary, + ), + ), + const SizedBox(height: 4), + Text( + '${l10n.version} 1.0.0', + style: const TextStyle(fontSize: 14, color: AppColors.textSecondary), + ), + const SizedBox(height: 8), + Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), + decoration: BoxDecoration( + color: AppColors.success.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(20), + ), + child: Text( + l10n.latestVersion, + style: const TextStyle( + fontSize: 12, + color: AppColors.success, + fontWeight: FontWeight.w500, + ), + ), + ), + const SizedBox(height: 32), + Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: AppColors.surface, + borderRadius: BorderRadius.circular(16), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + l10n.productIntro, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: AppColors.textPrimary, + ), + ), + const SizedBox(height: 12), + Text( + l10n.productDesc, + style: const TextStyle( + fontSize: 14, + color: AppColors.textSecondary, + height: 1.7, + ), + ), + ], + ), + ), + const SizedBox(height: 16), + Container( + decoration: BoxDecoration( + color: AppColors.surface, + borderRadius: BorderRadius.circular(16), + ), + child: Column( + children: [ + _buildAboutRow(l10n.officialWebsite, 'www.zhinian.com'), + const Divider(height: 1, indent: 20), + _buildAboutRow(l10n.servicePhone, '400-888-8888'), + const Divider(height: 1, indent: 20), + _buildAboutRow(l10n.techSupport, 'support@zhinian.com'), + const Divider(height: 1, indent: 20), + _buildAboutRow(l10n.companyAddress, '浙江省杭州市西湖区'), + ], + ), + ), + const SizedBox(height: 16), + Container( + decoration: BoxDecoration( + color: AppColors.surface, + borderRadius: BorderRadius.circular(16), + ), + child: Column( + children: [ + ListTile( + leading: const Icon(Icons.description_outlined, color: AppColors.primary), + title: Text(l10n.userAgreement), + trailing: const 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), + onTap: () => context.push('/settings/policy?type=privacy'), + ), + ], + ), + ), + const SizedBox(height: 32), + Text( + l10n.copyright, + style: const TextStyle(fontSize: 12, color: AppColors.textTertiary), + ), + const SizedBox(height: 8), + Text( + l10n.allRightsReserved, + style: const TextStyle(fontSize: 12, color: AppColors.textTertiary), + ), + ], + ), + ), + ); + } + + Widget _buildAboutRow(String label, String value) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16), + child: Row( + children: [ + Text( + label, + style: const TextStyle(fontSize: 14, color: AppColors.textSecondary), + ), + const Spacer(), + Text( + value, + style: const 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 new file mode 100644 index 0000000..5912f41 --- /dev/null +++ b/lib/pages/app_market_page.dart @@ -0,0 +1,204 @@ +import 'package:flutter/material.dart'; +import '../theme.dart'; +import '../l10n/app_localizations.dart'; + +class AppMarketPage extends StatefulWidget { + 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)!; + return Scaffold( + backgroundColor: AppColors.background, + appBar: AppBar(title: Text(l10n.appMarket)), + body: ListView.builder( + padding: const EdgeInsets.all(16), + itemCount: _apps.length, + itemBuilder: (context, index) { + final app = _apps[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.withValues(alpha: 0.03), + blurRadius: 10, + offset: const Offset(0, 2), + ), + ], + ), + child: Row( + children: [ + Container( + width: 52, + height: 52, + decoration: BoxDecoration( + color: (app['color'] as Color).withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(14), + ), + child: Icon(app['icon'] as IconData, + color: app['color'] as Color, size: 26), + ), + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + 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), + ), + child: Text( + l10n.installed, + style: const TextStyle( + fontSize: 10, + color: AppColors.success, + fontWeight: FontWeight.w500, + ), + ), + ), + ], + ], + ), + const SizedBox(height: 6), + Text( + app['desc'], + style: const TextStyle( + fontSize: 13, + color: AppColors.textSecondary, + ), + ), + ], + ), + ), + 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)), + ), + ], + ), + ); + }, + ), + ); + } +} diff --git a/lib/pages/employee_page.dart b/lib/pages/employee_page.dart new file mode 100644 index 0000000..06d12f8 --- /dev/null +++ b/lib/pages/employee_page.dart @@ -0,0 +1,429 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import '../providers/auth_provider.dart'; +import '../theme.dart'; +import '../l10n/app_localizations.dart'; + +class EmployeePage extends ConsumerStatefulWidget { + const EmployeePage({super.key}); + + @override + ConsumerState createState() => _EmployeePageState(); +} + +class _EmployeePageState extends ConsumerState { + final List> _employees = [ + {'name': '李员工', 'phone': '138****6666', 'role': '前台', 'status': '在职'}, + {'name': '王维修', 'phone': '139****5555', 'role': '维修工', 'status': '在职'}, + {'name': '张清洁', 'phone': '137****4444', 'role': '保洁', 'status': '休假'}, + {'name': '刘行政', 'phone': '136****3333', 'role': '行政', 'status': '在职'}, + {'name': '赵救生员', 'phone': '135****2222', 'role': '救生员', 'status': '在职'}, + ]; + + void _showAddEmployeeSheet() { + final l10n = AppLocalizations.of(context)!; + final nameCtrl = TextEditingController(); + final phoneCtrl = TextEditingController(); + String role = l10n.receptionist; + final roles = [ + l10n.receptionist, + l10n.maintenance, + l10n.cleaner, + l10n.admin, + l10n.lifeguard, + l10n.chef, + l10n.security, + ]; + + showModalBottomSheet( + context: context, + backgroundColor: Colors.transparent, + isScrollControlled: true, + builder: (ctx) { + return Padding( + padding: EdgeInsets.only(bottom: MediaQuery.of(ctx).viewInsets.bottom), + child: Container( + padding: const EdgeInsets.all(24), + decoration: const BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.vertical(top: Radius.circular(24)), + ), + child: SafeArea( + 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: 20), + Text( + l10n.addEmployee, + style: const TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + color: AppColors.textPrimary, + ), + ), + const SizedBox(height: 20), + TextField( + controller: nameCtrl, + decoration: InputDecoration( + hintText: l10n.employeeName, + prefixIcon: const Icon(Icons.person_outline), + filled: true, + fillColor: AppColors.background, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide.none, + ), + ), + ), + const SizedBox(height: 12), + TextField( + controller: phoneCtrl, + keyboardType: TextInputType.phone, + decoration: InputDecoration( + hintText: l10n.phoneNumber, + prefixIcon: const Icon(Icons.phone_outlined), + filled: true, + fillColor: AppColors.background, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide.none, + ), + ), + ), + const SizedBox(height: 12), + StatefulBuilder( + builder: (context, setLocalState) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 12), + decoration: BoxDecoration( + color: AppColors.background, + borderRadius: BorderRadius.circular(12), + ), + child: DropdownButtonHideUnderline( + child: DropdownButton( + isExpanded: true, + value: role, + items: roles + .map((r) => DropdownMenuItem( + value: r, + child: Text(r), + )) + .toList(), + onChanged: (v) { + if (v != null) { + setLocalState(() => role = v); + } + }, + ), + ), + ); + }, + ), + const SizedBox(height: 24), + SizedBox( + width: double.infinity, + height: 52, + child: ElevatedButton( + onPressed: () { + if (nameCtrl.text.trim().isEmpty || + phoneCtrl.text.trim().isEmpty) { + ScaffoldMessenger.of(ctx).showSnackBar( + SnackBar(content: Text(l10n.pleaseComplete)), + ); + return; + } + setState(() { + _employees.add({ + 'name': nameCtrl.text.trim(), + 'phone': phoneCtrl.text.trim(), + 'role': role, + 'status': l10n.active, + }); + }); + Navigator.of(ctx).pop(); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(l10n.addSuccess), + backgroundColor: AppColors.success, + ), + ); + }, + style: ElevatedButton.styleFrom( + backgroundColor: AppColors.primary, + foregroundColor: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(14), + ), + ), + child: Text(l10n.confirmAdd, + style: const TextStyle( + fontSize: 16, fontWeight: FontWeight.w600)), + ), + ), + ], + ), + ), + ), + ); + }, + ); + } + + void _showEmployeeActions(int index) { + final l10n = AppLocalizations.of(context)!; + final emp = _employees[index]; + showModalBottomSheet( + context: context, + backgroundColor: Colors.transparent, + builder: (ctx) { + return Container( + padding: const EdgeInsets.all(20), + decoration: const BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.vertical(top: Radius.circular(24)), + ), + child: SafeArea( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 40, + height: 4, + decoration: BoxDecoration( + color: AppColors.divider, + borderRadius: BorderRadius.circular(2), + ), + ), + const SizedBox(height: 20), + Text( + emp['name'], + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: AppColors.textPrimary, + ), + ), + const SizedBox(height: 4), + Text( + '${emp['role']} | ${emp['phone']}', + style: const TextStyle( + fontSize: 14, + color: AppColors.textSecondary, + ), + ), + const SizedBox(height: 20), + ListTile( + leading: const Icon(Icons.phone, color: AppColors.primary), + title: Text(l10n.call), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + onTap: () { + Navigator.of(ctx).pop(); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('${l10n.call} ${emp['name']}...')), + ); + }, + ), + ListTile( + leading: Icon( + emp['status'] == l10n.active + ? Icons.pause_circle_outline + : Icons.play_circle_outline, + color: AppColors.warning, + ), + title: Text(emp['status'] == l10n.active ? l10n.setOnLeave : l10n.restoreActive), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + onTap: () { + Navigator.of(ctx).pop(); + setState(() { + _employees[index]['status'] = + emp['status'] == l10n.active ? l10n.onLeave : l10n.active; + }); + }, + ), + ListTile( + leading: const Icon(Icons.delete_outline, color: AppColors.error), + title: Text(l10n.deleteEmployee, + style: const TextStyle(color: AppColors.error)), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + onTap: () { + Navigator.of(ctx).pop(); + showDialog( + context: context, + builder: (_) => AlertDialog( + title: Text(l10n.confirmDelete), + content: Text(l10n.deleteConfirm(emp['name'])), + actions: [ + TextButton( + onPressed: () => Navigator.of(_).pop(), + child: Text(l10n.cancel), + ), + TextButton( + onPressed: () { + Navigator.of(_).pop(); + setState(() => _employees.removeAt(index)); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(l10n.deleteSuccess), + backgroundColor: AppColors.error, + ), + ); + }, + child: Text(l10n.delete, + style: const TextStyle(color: AppColors.error)), + ), + ], + ), + ); + }, + ), + ], + ), + ), + ); + }, + ); + } + + @override + Widget build(BuildContext context) { + final l10n = AppLocalizations.of(context)!; + final authState = ref.watch(authProvider); + final isBoss = authState.user?.isBoss ?? false; + + return Scaffold( + backgroundColor: AppColors.background, + appBar: AppBar(title: Text(l10n.employeeList)), + body: _employees.isEmpty + ? Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.people_outline, + size: 64, color: AppColors.textTertiary.withValues(alpha: 0.5)), + const SizedBox(height: 16), + Text(l10n.noEmployees, + style: const TextStyle( + fontSize: 16, color: AppColors.textSecondary)), + ], + ), + ) + : ListView.builder( + padding: const EdgeInsets.all(16), + itemCount: _employees.length, + itemBuilder: (context, index) { + final emp = _employees[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.withValues(alpha: 0.03), + blurRadius: 10, + offset: const Offset(0, 2), + ), + ], + ), + child: Row( + children: [ + CircleAvatar( + radius: 24, + backgroundColor: + AppColors.primary.withValues(alpha: 0.1), + child: Text( + emp['name'].toString().substring(0, 1), + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: AppColors.primary, + ), + ), + ), + const SizedBox(width: 14), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Text( + emp['name'], + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: AppColors.textPrimary, + ), + ), + const SizedBox(width: 8), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 8, vertical: 2), + decoration: BoxDecoration( + color: emp['status'] == l10n.active + ? AppColors.success.withValues(alpha: 0.1) + : AppColors.warning.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(4), + ), + child: Text( + emp['status'], + style: TextStyle( + fontSize: 11, + color: emp['status'] == l10n.active + ? AppColors.success + : AppColors.warning, + fontWeight: FontWeight.w500, + ), + ), + ), + ], + ), + const SizedBox(height: 6), + Text( + '${emp['role']} | ${emp['phone']}', + style: const TextStyle( + fontSize: 13, + color: AppColors.textSecondary, + ), + ), + ], + ), + ), + if (isBoss) + IconButton( + icon: const Icon(Icons.more_vert, + color: AppColors.textTertiary), + onPressed: () => _showEmployeeActions(index), + ), + ], + ), + ); + }, + ), + floatingActionButton: isBoss + ? FloatingActionButton( + onPressed: _showAddEmployeeSheet, + child: const Icon(Icons.add), + ) + : null, + ); + } +} diff --git a/lib/pages/event_list_page.dart b/lib/pages/event_list_page.dart new file mode 100644 index 0000000..0374dca --- /dev/null +++ b/lib/pages/event_list_page.dart @@ -0,0 +1,209 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_animate/flutter_animate.dart'; +import '../providers/event_provider.dart'; +import '../theme.dart'; +import '../l10n/app_localizations.dart'; + +class EventListPage extends ConsumerStatefulWidget { + const EventListPage({super.key}); + + @override + ConsumerState createState() => _EventListPageState(); +} + +class _EventListPageState extends ConsumerState { + @override + void initState() { + super.initState(); + Future.microtask(() { + ref.read(eventProvider.notifier).loadEvents(); + }); + } + + String _formatDate(DateTime date) { + return '${date.month}/${date.day} ${date.hour.toString().padLeft(2, '0')}:${date.minute.toString().padLeft(2, '0')}'; + } + + Color _getStatusColor(String status) { + switch (status) { + case 'published': + return AppColors.success; + case 'draft': + return AppColors.warning; + case 'expired': + return AppColors.textTertiary; + default: + return AppColors.primary; + } + } + + String _getStatusText(String status, AppLocalizations l10n) { + switch (status) { + case 'published': + return l10n.published; + case 'draft': + return l10n.draft; + case 'expired': + return l10n.expired; + default: + return status; + } + } + + @override + Widget build(BuildContext context) { + final l10n = AppLocalizations.of(context)!; + final eventState = ref.watch(eventProvider); + + return Scaffold( + backgroundColor: AppColors.background, + appBar: AppBar( + title: Text(l10n.eventRecords), + ), + body: eventState.isLoading + ? const Center(child: CircularProgressIndicator()) + : eventState.events.isEmpty + ? _buildEmptyState(l10n) + : ListView.builder( + padding: const EdgeInsets.all(16), + itemCount: eventState.events.length, + itemBuilder: (context, index) { + final event = eventState.events[index]; + return _buildEventCard(event, index, l10n); + }, + ), + ); + } + + Widget _buildEmptyState(AppLocalizations l10n) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.event_note_outlined, + size: 64, + color: AppColors.textTertiary.withOpacity(0.5), + ), + const SizedBox(height: 16), + Text( + l10n.noEvents, + style: const TextStyle(fontSize: 16, color: AppColors.textSecondary), + ), + ], + ), + ); + } + + Widget _buildEventCard(event, int index, AppLocalizations l10n) { + 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: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.all(16), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: 44, + height: 44, + decoration: BoxDecoration( + gradient: AppGradients.primary, + borderRadius: BorderRadius.circular(12), + ), + child: const Icon(Icons.event, color: Colors.white, size: 22), + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded( + child: Text( + event.entityName, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: AppColors.textPrimary, + ), + ), + ), + Container( + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), + decoration: BoxDecoration( + color: _getStatusColor(event.status).withOpacity(0.1), + borderRadius: BorderRadius.circular(20), + ), + child: Text( + _getStatusText(event.status, l10n), + style: TextStyle( + fontSize: 11, + fontWeight: FontWeight.w500, + color: _getStatusColor(event.status), + ), + ), + ), + ], + ), + const SizedBox(height: 6), + Text( + event.description, + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + fontSize: 14, + color: AppColors.textSecondary, + height: 1.4, + ), + ), + ], + ), + ), + ], + ), + ), + const Divider(height: 1, indent: 16, endIndent: 16), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + child: Row( + children: [ + Icon(Icons.access_time, size: 14, color: AppColors.textTertiary), + const SizedBox(width: 4), + Text( + '${l10n.publishTime}: ${_formatDate(event.publishTime)}', + style: const TextStyle(fontSize: 12, color: AppColors.textTertiary), + ), + if (event.popupReminder) ...[ + const SizedBox(width: 12), + Icon(Icons.notifications_active, size: 14, color: AppColors.warning), + ], + const Spacer(), + if (event.creatorName != null) + Text( + event.creatorName!, + style: const TextStyle(fontSize: 12, color: AppColors.textTertiary), + ), + ], + ), + ), + ], + ), + ).animate().fadeIn(duration: 300.ms, delay: (index * 50).ms).slideY(begin: 0.1, end: 0, duration: 300.ms); + } +} diff --git a/lib/pages/event_publish_page.dart b/lib/pages/event_publish_page.dart new file mode 100644 index 0000000..43d8dc8 --- /dev/null +++ b/lib/pages/event_publish_page.dart @@ -0,0 +1,486 @@ +import 'dart:io'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; +import 'package:image_picker/image_picker.dart'; +import 'package:flutter_animate/flutter_animate.dart'; +import '../providers/event_provider.dart'; +import '../theme.dart'; +import '../l10n/app_localizations.dart'; + +class EventPublishPage extends ConsumerStatefulWidget { + const EventPublishPage({super.key}); + + @override + ConsumerState createState() => _EventPublishPageState(); +} + +class _EventPublishPageState extends ConsumerState { + final _entityController = TextEditingController(); + final _descController = TextEditingController(); + final _formKey = GlobalKey(); + final List _selectedImages = []; + DateTime? _publishTime; + DateTime? _effectiveTime; + DateTime? _endTime; + bool _popupReminder = false; + + @override + void dispose() { + _entityController.dispose(); + _descController.dispose(); + super.dispose(); + } + + void _setDefaultTimes() { + final now = DateTime.now(); + setState(() { + _publishTime = now; + _effectiveTime = now.add(const Duration(hours: 1)); + _endTime = now.add(const Duration(days: 7)); + }); + } + + Future _pickImage() async { + final picker = ImagePicker(); + final picked = await picker.pickImage(source: ImageSource.gallery); + if (picked != null) { + setState(() { + _selectedImages.add(File(picked.path)); + }); + } + } + + Future _selectDateTime(BuildContext context, bool isPublish) async { + final date = await showDatePicker( + context: context, + initialDate: DateTime.now(), + firstDate: DateTime(2024), + lastDate: DateTime(2030), + builder: (context, child) { + return Theme( + data: Theme.of(context).copyWith( + colorScheme: const ColorScheme.light(primary: AppColors.primary), + ), + child: child!, + ); + }, + ); + if (date == null) return; + + if (!context.mounted) return; + final time = await showTimePicker( + context: context, + initialTime: TimeOfDay.now(), + builder: (context, child) { + return Theme( + data: Theme.of(context).copyWith( + colorScheme: const ColorScheme.light(primary: AppColors.primary), + ), + child: child!, + ); + }, + ); + if (time == null) return; + + setState(() { + final dt = DateTime(date.year, date.month, date.day, time.hour, time.minute); + if (isPublish) { + _publishTime = dt; + } else { + _endTime = dt; + } + }); + } + + Future _selectEffectiveTime(BuildContext context) async { + final date = await showDatePicker( + context: context, + initialDate: DateTime.now(), + firstDate: DateTime(2024), + lastDate: DateTime(2030), + builder: (context, child) { + return Theme( + data: Theme.of(context).copyWith( + colorScheme: const ColorScheme.light(primary: AppColors.primary), + ), + child: child!, + ); + }, + ); + if (date == null) return; + + if (!context.mounted) return; + final time = await showTimePicker( + context: context, + initialTime: TimeOfDay.now(), + builder: (context, child) { + return Theme( + data: Theme.of(context).copyWith( + colorScheme: const ColorScheme.light(primary: AppColors.primary), + ), + child: child!, + ); + }, + ); + if (time == null) return; + + setState(() { + _effectiveTime = DateTime(date.year, date.month, date.day, time.hour, time.minute); + }); + } + + void _publish() { + if (!_formKey.currentState!.validate()) return; + + ref.read(eventProvider.notifier).publishEvent({ + 'entityName': _entityController.text.trim(), + 'description': _descController.text.trim(), + 'images': _selectedImages.map((f) => f.path).toList(), + 'publishTime': _publishTime?.toIso8601String(), + 'effectiveTime': _effectiveTime?.toIso8601String(), + 'endTime': _endTime?.toIso8601String(), + 'popupReminder': _popupReminder, + }).then((_) { + final state = ref.read(eventProvider); + if (state.publishSuccess) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(AppLocalizations.of(context)!.publishSuccess), + backgroundColor: AppColors.success, + behavior: SnackBarBehavior.floating, + ), + ); + ref.read(eventProvider.notifier).clearPublishSuccess(); + context.pop(); + } + }); + } + + @override + Widget build(BuildContext context) { + final l10n = AppLocalizations.of(context)!; + final eventState = ref.watch(eventProvider); + + return Scaffold( + backgroundColor: AppColors.background, + appBar: AppBar( + title: Text(l10n.publishEvent), + actions: [ + TextButton( + onPressed: () => context.push('/event/list'), + child: Text(l10n.eventRecords), + ), + ], + ), + body: Form( + key: _formKey, + child: ListView( + padding: const EdgeInsets.all(20), + children: [ + _buildSectionTitle(l10n.basicInfo), + const SizedBox(height: 12), + _buildTextField( + controller: _entityController, + label: l10n.entityName, + hint: l10n.entityNameHint, + icon: Icons.business_outlined, + validator: (v) => v == null || v.isEmpty ? l10n.entityName : null, + ), + const SizedBox(height: 16), + _buildTextField( + controller: _descController, + label: l10n.eventDesc, + hint: l10n.eventDescHint, + icon: Icons.description_outlined, + maxLines: 4, + validator: (v) => v == null || v.isEmpty ? l10n.eventDesc : null, + ), + const SizedBox(height: 24), + _buildSectionTitle(l10n.eventImages), + const SizedBox(height: 12), + _buildImagePicker(l10n), + const SizedBox(height: 24), + _buildSectionTitle(l10n.timeSettings), + const SizedBox(height: 12), + _buildTimeSelector( + label: l10n.publishTime, + value: _publishTime, + icon: Icons.access_time, + onTap: () => _selectDateTime(context, true), + l10n: l10n, + ), + const SizedBox(height: 12), + _buildTimeSelector( + label: l10n.effectiveTime, + value: _effectiveTime, + icon: Icons.play_circle_outline, + onTap: () => _selectEffectiveTime(context), + l10n: l10n, + ), + const SizedBox(height: 12), + _buildTimeSelector( + label: l10n.endTime, + value: _endTime, + icon: Icons.stop_circle_outlined, + onTap: () => _selectDateTime(context, false), + l10n: l10n, + ), + const SizedBox(height: 12), + Align( + alignment: Alignment.centerRight, + child: TextButton.icon( + onPressed: _setDefaultTimes, + icon: const Icon(Icons.auto_fix_high, size: 18), + label: Text(l10n.setDefaultTime), + ), + ), + const SizedBox(height: 16), + _buildSwitchTile(l10n), + const SizedBox(height: 32), + SizedBox( + width: double.infinity, + height: 52, + child: ElevatedButton( + onPressed: eventState.isLoading ? null : _publish, + style: ElevatedButton.styleFrom( + backgroundColor: AppColors.primary, + foregroundColor: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(14), + ), + ), + child: eventState.isLoading + ? const SizedBox( + width: 24, + height: 24, + child: CircularProgressIndicator( + strokeWidth: 2.5, + valueColor: AlwaysStoppedAnimation(Colors.white), + ), + ) + : Text( + l10n.publishEvent, + style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600), + ), + ), + ), + ], + ), + ), + ); + } + + Widget _buildSectionTitle(String title) { + return Text( + title, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: AppColors.textPrimary, + ), + ); + } + + Widget _buildTextField({ + required TextEditingController controller, + required String label, + required String hint, + required IconData icon, + int maxLines = 1, + String? Function(String?)? validator, + }) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + label, + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, + color: AppColors.textPrimary, + ), + ), + const SizedBox(height: 8), + TextFormField( + controller: controller, + maxLines: maxLines, + validator: validator, + decoration: InputDecoration( + hintText: hint, + prefixIcon: Icon(icon, color: AppColors.textTertiary, size: 20), + filled: true, + fillColor: AppColors.surface, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide.none, + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide.none, + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: const BorderSide(color: AppColors.primary, width: 1.5), + ), + contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), + ), + ), + ], + ); + } + + Widget _buildImagePicker(AppLocalizations l10n) { + return Wrap( + spacing: 12, + runSpacing: 12, + children: [ + ..._selectedImages.asMap().entries.map((entry) { + return Stack( + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(12), + child: Image.file( + entry.value, + width: 100, + height: 100, + fit: BoxFit.cover, + ), + ), + Positioned( + top: 4, + right: 4, + child: GestureDetector( + onTap: () { + setState(() { + _selectedImages.removeAt(entry.key); + }); + }, + child: Container( + padding: const EdgeInsets.all(4), + decoration: const BoxDecoration( + color: Colors.black54, + shape: BoxShape.circle, + ), + child: const Icon(Icons.close, size: 14, color: Colors.white), + ), + ), + ), + ], + ); + }), + GestureDetector( + onTap: _pickImage, + child: Container( + width: 100, + height: 100, + decoration: BoxDecoration( + color: AppColors.surface, + borderRadius: BorderRadius.circular(12), + border: Border.all(color: AppColors.divider, width: 1.5), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const 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)), + ], + ), + ), + ), + ], + ); + } + + Widget _buildTimeSelector({ + required String label, + required DateTime? value, + required IconData icon, + required VoidCallback onTap, + required AppLocalizations l10n, + }) { + return GestureDetector( + onTap: onTap, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), + decoration: BoxDecoration( + color: AppColors.surface, + borderRadius: BorderRadius.circular(12), + ), + child: Row( + children: [ + Icon(icon, color: AppColors.primary, size: 20), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + label, + style: const TextStyle(fontSize: 12, color: AppColors.textSecondary), + ), + const SizedBox(height: 2), + Text( + value != null + ? '${value.year}-${value.month.toString().padLeft(2, '0')}-${value.day.toString().padLeft(2, '0')} ${value.hour.toString().padLeft(2, '0')}:${value.minute.toString().padLeft(2, '0')}' + : '${l10n.selectTime}$label', + style: TextStyle( + fontSize: 15, + color: value != null ? AppColors.textPrimary : AppColors.textTertiary, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + const Icon(Icons.chevron_right, color: AppColors.textTertiary), + ], + ), + ), + ); + } + + Widget _buildSwitchTile(AppLocalizations l10n) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4), + decoration: BoxDecoration( + color: AppColors.surface, + borderRadius: BorderRadius.circular(12), + ), + child: Row( + children: [ + Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: AppColors.warning.withOpacity(0.1), + borderRadius: BorderRadius.circular(8), + ), + child: const Icon(Icons.notifications_active_outlined, color: AppColors.warning, size: 20), + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + l10n.popupReminder, + style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w500, color: AppColors.textPrimary), + ), + Text( + l10n.popupReminderDesc, + style: const TextStyle(fontSize: 12, color: AppColors.textSecondary), + ), + ], + ), + ), + Switch( + value: _popupReminder, + onChanged: (v) => setState(() => _popupReminder = v), + activeColor: AppColors.primary, + ), + ], + ), + ); + } +} diff --git a/lib/pages/help_page.dart b/lib/pages/help_page.dart new file mode 100644 index 0000000..0e6cc1d --- /dev/null +++ b/lib/pages/help_page.dart @@ -0,0 +1,228 @@ +import 'package:flutter/material.dart'; +import 'package:url_launcher/url_launcher.dart'; +import '../theme.dart'; +import '../l10n/app_localizations.dart'; + +class HelpPage extends StatelessWidget { + const HelpPage({super.key}); + + @override + Widget build(BuildContext context) { + final l10n = AppLocalizations.of(context)!; + final faqs = [ + ( + l10n.faq1Q, + l10n.faq1A + ), + ( + l10n.faq2Q, + l10n.faq2A + ), + ( + l10n.faq3Q, + l10n.faq3A + ), + ( + l10n.faq4Q, + l10n.faq4A + ), + ( + l10n.faq5Q, + l10n.faq5A + ), + ( + l10n.faq6Q, + l10n.faq6A + ), + ]; + + return Scaffold( + backgroundColor: AppColors.background, + appBar: AppBar(title: Text(l10n.helpCenter)), + body: ListView( + padding: const EdgeInsets.all(16), + children: [ + Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + gradient: AppGradients.primary, + borderRadius: BorderRadius.circular(16), + ), + child: Row( + children: [ + const Icon(Icons.support_agent, color: Colors.white, size: 40), + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + l10n.needHelp, + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + const SizedBox(height: 4), + Text( + l10n.faqSubtitle, + style: const TextStyle(fontSize: 14, color: Colors.white70), + ), + ], + ), + ), + ], + ), + ), + const SizedBox(height: 20), + Text( + l10n.faq, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: AppColors.textPrimary, + ), + ), + const SizedBox(height: 12), + ...faqs.asMap().entries.map((entry) { + return _FaqItem( + question: entry.value.$1, + answer: entry.value.$2, + index: entry.key, + ); + }).toList(), + const SizedBox(height: 24), + Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: AppColors.surface, + borderRadius: BorderRadius.circular(16), + ), + child: Column( + children: [ + const Icon(Icons.headset_mic_outlined, size: 40, color: AppColors.primary), + const SizedBox(height: 12), + Text( + l10n.stillQuestions, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: AppColors.textPrimary, + ), + ), + const SizedBox(height: 8), + Text( + l10n.contactTeam, + style: const TextStyle(fontSize: 14, color: AppColors.textSecondary), + ), + const SizedBox(height: 16), + SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: () async { + final uri = Uri.parse('tel:4008888888'); + if (await canLaunchUrl(uri)) { + await launchUrl(uri); + } + }, + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric(vertical: 14), + ), + child: Text(l10n.contactService), + ), + ), + ], + ), + ), + ], + ), + ); + } +} + +class _FaqItem extends StatefulWidget { + final String question; + final String answer; + final int index; + + const _FaqItem({ + required this.question, + required this.answer, + required this.index, + }); + + @override + State<_FaqItem> createState() => _FaqItemState(); +} + +class _FaqItemState extends State<_FaqItem> { + bool _expanded = false; + + @override + Widget build(BuildContext context) { + return Container( + margin: const EdgeInsets.only(bottom: 10), + decoration: BoxDecoration( + color: AppColors.surface, + borderRadius: BorderRadius.circular(12), + ), + child: Column( + children: [ + ListTile( + leading: Container( + width: 28, + height: 28, + decoration: BoxDecoration( + color: AppColors.primary.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(6), + ), + child: Center( + child: Text( + '${widget.index + 1}', + style: const TextStyle( + fontSize: 13, + fontWeight: FontWeight.bold, + color: AppColors.primary, + ), + ), + ), + ), + title: Text( + widget.question, + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, + color: AppColors.textPrimary, + ), + ), + trailing: AnimatedRotation( + turns: _expanded ? 0.5 : 0, + duration: const Duration(milliseconds: 200), + child: const Icon(Icons.keyboard_arrow_down, color: AppColors.textTertiary), + ), + onTap: () => setState(() => _expanded = !_expanded), + ), + AnimatedCrossFade( + firstChild: const SizedBox.shrink(), + secondChild: Padding( + padding: const EdgeInsets.fromLTRB(72, 0, 16, 16), + child: Text( + widget.answer, + style: const TextStyle( + fontSize: 14, + color: AppColors.textSecondary, + height: 1.6, + ), + ), + ), + crossFadeState: _expanded + ? CrossFadeState.showSecond + : CrossFadeState.showFirst, + duration: const Duration(milliseconds: 200), + ), + ], + ), + ); + } +} diff --git a/lib/pages/home_page.dart b/lib/pages/home_page.dart new file mode 100644 index 0000000..d40c95d --- /dev/null +++ b/lib/pages/home_page.dart @@ -0,0 +1,686 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +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 '../providers/auth_provider.dart'; +import '../providers/chat_provider.dart'; +import '../models/message.dart'; +import '../theme.dart'; +import '../l10n/app_localizations.dart'; + +class HomePage extends ConsumerStatefulWidget { + const HomePage({super.key}); + + @override + ConsumerState createState() => _HomePageState(); +} + +class _HomePageState extends ConsumerState { + final _messageController = TextEditingController(); + final _scrollController = ScrollController(); + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) { + _scrollToBottom(); + }); + } + + @override + void dispose() { + _messageController.dispose(); + _scrollController.dispose(); + super.dispose(); + } + + void _scrollToBottom() { + if (_scrollController.hasClients) { + _scrollController.animateTo( + _scrollController.position.maxScrollExtent, + duration: const Duration(milliseconds: 300), + curve: Curves.easeOut, + ); + } + } + + void _sendMessage() { + final text = _messageController.text.trim(); + if (text.isEmpty) return; + _messageController.clear(); + ref.read(chatProvider.notifier).sendMessage(text).then((_) { + Future.delayed(const Duration(milliseconds: 100), _scrollToBottom); + }); + setState(() {}); + } + + void _openScanner() { + Navigator.of(context).push( + MaterialPageRoute( + builder: (_) => const ScannerPage(), + ), + ); + } + + @override + Widget build(BuildContext context) { + final l10n = AppLocalizations.of(context)!; + final authState = ref.watch(authProvider); + final chatState = ref.watch(chatProvider); + final user = authState.user; + + ref.listen(chatProvider, (prev, next) { + if (next.messages.length != (prev?.messages.length ?? 0)) { + Future.delayed(const Duration(milliseconds: 100), _scrollToBottom); + } + }); + + return Scaffold( + backgroundColor: AppColors.background, + appBar: AppBar( + backgroundColor: AppColors.surface, + elevation: 0, + automaticallyImplyLeading: false, + title: Row( + children: [ + Container( + width: 36, + height: 36, + decoration: BoxDecoration( + gradient: AppGradients.primary, + borderRadius: BorderRadius.circular(10), + ), + child: const Icon(Icons.auto_awesome, color: Colors.white, size: 20), + ), + const SizedBox(width: 10), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + l10n.aiAssistant, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: AppColors.textPrimary, + ), + ), + Row( + children: [ + Container( + width: 6, + height: 6, + decoration: const BoxDecoration( + color: AppColors.success, + shape: BoxShape.circle, + ), + ), + const SizedBox(width: 4), + Text( + l10n.online, + style: const TextStyle(fontSize: 11, color: AppColors.textSecondary), + ), + ], + ), + ], + ), + ], + ), + actions: [ + if (user != null) + PopupMenuButton( + offset: const Offset(0, 40), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + 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)), + ], + ), + ), + ], + ), + ], + ), + body: Column( + children: [ + Expanded( + child: ListView.builder( + controller: _scrollController, + padding: const EdgeInsets.all(16), + itemCount: chatState.messages.length, + itemBuilder: (context, index) { + final message = chatState.messages[index]; + return _buildMessageBubble(message, index, l10n); + }, + ), + ), + _buildQuickActions(l10n), + _buildInputArea(l10n), + ], + ), + ); + } + + Widget _buildMessageBubble(ChatMessage message, int index, AppLocalizations l10n) { + final isUser = message.sender == MessageSender.user; + + if (message.type == MessageType.loading) { + return Align( + alignment: Alignment.centerLeft, + child: Container( + margin: const EdgeInsets.only(bottom: 16), + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + decoration: BoxDecoration( + color: AppColors.chatAiBubble, + borderRadius: BorderRadius.circular(16), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox( + width: 16, + height: 16, + child: CircularProgressIndicator( + strokeWidth: 2, + valueColor: AlwaysStoppedAnimation( + AppColors.primary.withOpacity(0.6), + ), + ), + ), + const SizedBox(width: 8), + Text( + l10n.thinking, + style: TextStyle( + fontSize: 14, + color: AppColors.textSecondary, + ), + ), + ], + ), + ), + ); + } + + return Align( + alignment: isUser ? Alignment.centerRight : Alignment.centerLeft, + child: Container( + margin: const EdgeInsets.only(bottom: 16), + constraints: BoxConstraints( + maxWidth: MediaQuery.of(context).size.width * 0.82, + ), + child: Column( + crossAxisAlignment: + isUser ? CrossAxisAlignment.end : CrossAxisAlignment.start, + children: [ + Container( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + decoration: BoxDecoration( + color: isUser ? AppColors.chatUserBubble : AppColors.chatAiBubble, + borderRadius: BorderRadius.only( + topLeft: const Radius.circular(16), + topRight: const Radius.circular(16), + bottomLeft: Radius.circular(isUser ? 16 : 4), + bottomRight: Radius.circular(isUser ? 4 : 16), + ), + ), + child: isUser || message.type == MessageType.text + ? Text( + message.content, + style: TextStyle( + fontSize: 15, + color: isUser ? Colors.white : AppColors.textPrimary, + height: 1.5, + ), + ) + : MarkdownBody( + data: message.content, + styleSheet: MarkdownStyleSheet( + p: const TextStyle( + fontSize: 15, + color: AppColors.textPrimary, + height: 1.6, + ), + code: TextStyle( + fontSize: 13, + color: AppColors.primary, + backgroundColor: AppColors.primary.withOpacity(0.08), + fontFamily: 'monospace', + ), + codeblockDecoration: BoxDecoration( + color: AppColors.primary.withOpacity(0.05), + borderRadius: BorderRadius.circular(8), + ), + blockquote: TextStyle( + fontSize: 14, + color: AppColors.textSecondary, + fontStyle: FontStyle.italic, + ), + blockquoteDecoration: BoxDecoration( + border: Border( + left: BorderSide( + color: AppColors.primary.withOpacity(0.4), + width: 3, + ), + ), + ), + tableHead: const TextStyle( + fontWeight: FontWeight.w600, + color: AppColors.textPrimary, + ), + tableBody: const TextStyle( + color: AppColors.textSecondary, + ), + tableBorder: TableBorder.all( + color: AppColors.divider, + width: 1, + ), + tableCellsPadding: const EdgeInsets.all(8), + h1: const TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + color: AppColors.textPrimary, + ), + h2: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: AppColors.textPrimary, + ), + h3: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: AppColors.textPrimary, + ), + strong: const TextStyle( + fontWeight: FontWeight.w600, + color: AppColors.primary, + ), + ), + ), + ), + const SizedBox(height: 4), + Text( + _formatTime(message.timestamp), + style: TextStyle( + fontSize: 11, + color: AppColors.textTertiary, + ), + ), + ], + ), + ), + ).animate().fadeIn(duration: 300.ms).slideY(begin: 0.1, end: 0, duration: 300.ms); + } + + 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, + 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(), + ), + ], + ), + ], + ), + ); + } + + 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), + ), + ], + ), + child: SafeArea( + child: Row( + 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, + ), + filled: true, + fillColor: AppColors.background, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(20), + borderSide: BorderSide.none, + ), + 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, + ), + ), + ), + ], + ), + ), + ); + } + + String _formatTime(DateTime time) { + final now = DateTime.now(); + if (time.year == now.year && time.month == now.month && time.day == now.day) { + return '${time.hour.toString().padLeft(2, '0')}:${time.minute.toString().padLeft(2, '0')}'; + } + return '${time.month}/${time.day} ${time.hour.toString().padLeft(2, '0')}:${time.minute.toString().padLeft(2, '0')}'; + } +} + +class _ActionChip extends StatelessWidget { + final IconData icon; + final String label; + final Color color; + final VoidCallback onTap; + + const _ActionChip({ + required this.icon, + required this.label, + required this.color, + required this.onTap, + }); + + @override + Widget build(BuildContext context) { + return GestureDetector( + 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( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(icon, size: 18, color: color), + const SizedBox(width: 6), + Text( + label, + style: TextStyle( + fontSize: 13, + fontWeight: FontWeight.w500, + color: color, + ), + ), + ], + ), + ), + ); + } +} + +class ScannerPage extends StatefulWidget { + const ScannerPage({super.key}); + + @override + State createState() => _ScannerPageState(); +} + +class _ScannerPageState extends State { + bool _isScanned = false; + + @override + Widget build(BuildContext context) { + final l10n = AppLocalizations.of(context)!; + return Scaffold( + backgroundColor: Colors.black, + body: Stack( + children: [ + MobileScanner( + onDetect: (capture) { + if (_isScanned) return; + final barcodes = capture.barcodes; + for (final barcode in barcodes) { + final code = barcode.rawValue; + if (code != null && code.isNotEmpty) { + _isScanned = true; + Navigator.of(context).pop(); + context.push('/scan-result?code=$code'); + break; + } + } + }, + ), + _buildOverlay(), + SafeArea( + child: Padding( + padding: const EdgeInsets.all(16), + child: Row( + children: [ + GestureDetector( + onTap: () => Navigator.of(context).pop(), + child: Container( + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + color: Colors.black.withOpacity(0.5), + borderRadius: BorderRadius.circular(12), + ), + child: const Icon(Icons.close, color: Colors.white), + ), + ), + const Spacer(), + Container( + padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8), + decoration: BoxDecoration( + color: Colors.black.withOpacity(0.5), + borderRadius: BorderRadius.circular(20), + ), + child: Text( + l10n.scanning, + style: const TextStyle(color: Colors.white, fontSize: 13), + ), + ), + const Spacer(), + const SizedBox(width: 44), + ], + ), + ), + ), + ], + ), + ); + } + + Widget _buildOverlay() { + return Center( + child: Container( + width: 260, + height: 260, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + border: Border.all( + color: AppColors.primary.withOpacity(0.8), + width: 2, + ), + ), + child: Stack( + children: [ + Positioned( + top: 0, + left: 0, + child: _buildCorner(true, true), + ), + Positioned( + top: 0, + right: 0, + child: _buildCorner(true, false), + ), + Positioned( + bottom: 0, + left: 0, + child: _buildCorner(false, true), + ), + Positioned( + bottom: 0, + right: 0, + child: _buildCorner(false, false), + ), + ], + ), + ), + ); + } + + Widget _buildCorner(bool top, bool left) { + return Container( + width: 30, + height: 30, + decoration: BoxDecoration( + border: Border( + top: top ? const BorderSide(color: AppColors.primary, width: 4) : BorderSide.none, + bottom: !top ? const BorderSide(color: AppColors.primary, width: 4) : BorderSide.none, + left: left ? const BorderSide(color: AppColors.primary, width: 4) : BorderSide.none, + right: !left ? const BorderSide(color: AppColors.primary, width: 4) : BorderSide.none, + ), + borderRadius: BorderRadius.only( + topLeft: top && left ? const Radius.circular(12) : Radius.zero, + topRight: top && !left ? const Radius.circular(12) : Radius.zero, + bottomLeft: !top && left ? const Radius.circular(12) : Radius.zero, + bottomRight: !top && !left ? const Radius.circular(12) : Radius.zero, + ), + ), + ); + } +} diff --git a/lib/pages/login_page.dart b/lib/pages/login_page.dart new file mode 100644 index 0000000..eee7d9b --- /dev/null +++ b/lib/pages/login_page.dart @@ -0,0 +1,423 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_animate/flutter_animate.dart'; +import '../providers/auth_provider.dart'; +import '../theme.dart'; +import '../l10n/app_localizations.dart'; + +class LoginPage extends ConsumerStatefulWidget { + const LoginPage({super.key}); + + @override + ConsumerState createState() => _LoginPageState(); +} + +class _LoginPageState extends ConsumerState + with SingleTickerProviderStateMixin { + final _phoneController = TextEditingController(); + final _passwordController = TextEditingController(); + final _formKey = GlobalKey(); + 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(); + } + + @override + void dispose() { + _phoneController.dispose(); + _passwordController.dispose(); + _animController.dispose(); + super.dispose(); + } + + void _login() { + if (!_formKey.currentState!.validate()) return; + if (!_isAgreed) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(AppLocalizations.of(context)!.pleaseComplete)), + ); + return; + } + ref.read(authProvider.notifier).login( + _phoneController.text.trim(), + _passwordController.text, + ); + } + + @override + Widget build(BuildContext context) { + final l10n = AppLocalizations.of(context)!; + final authState = ref.watch(authProvider); + + ref.listen(authProvider, (previous, next) { + if (next.error != null) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + 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, + 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), + ), + child: Row( + children: [ + Icon( + Icons.info_outline, + size: 16, + color: AppColors.primary.withOpacity(0.7), + ), + const SizedBox(width: 8), + Expanded( + child: Text( + l10n.roleHint, + style: TextStyle( + fontSize: 12, + color: AppColors.textSecondary, + ), + ), + ), + ], + ), + ), + ], + ), + ), + ); + } + + 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, + }) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + label, + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, + color: AppColors.textPrimary, + ), + ), + 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, + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide.none, + ), + 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), + ), + ), + ], + ); + } +} diff --git a/lib/pages/order_detail_page.dart b/lib/pages/order_detail_page.dart new file mode 100644 index 0000000..14888a4 --- /dev/null +++ b/lib/pages/order_detail_page.dart @@ -0,0 +1,343 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; +import '../providers/order_provider.dart'; +import '../theme.dart'; +import '../l10n/app_localizations.dart'; + +class OrderDetailPage extends ConsumerStatefulWidget { + final String orderId; + const OrderDetailPage({super.key, required this.orderId}); + + @override + ConsumerState createState() => _OrderDetailPageState(); +} + +class _OrderDetailPageState extends ConsumerState { + @override + void initState() { + super.initState(); + Future.microtask(() { + ref.read(orderProvider.notifier).getDetail(widget.orderId); + }); + } + + @override + Widget build(BuildContext context) { + final l10n = AppLocalizations.of(context)!; + final state = ref.watch(orderProvider); + 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: SingleChildScrollView( + padding: const EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildStatusHeader(order), + const SizedBox(height: 20), + _buildProductCard(order, l10n), + const SizedBox(height: 20), + _buildCustomerCard(order, l10n), + const SizedBox(height: 20), + _buildPaymentCard(order, l10n), + const SizedBox(height: 20), + _buildInfoCard(order, l10n), + ], + ), + ), + ); + } + + Widget _buildStatusHeader(order) { + return Container( + padding: const EdgeInsets.all(24), + decoration: BoxDecoration( + gradient: AppGradients.primary, + borderRadius: BorderRadius.circular(20), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.2), + borderRadius: BorderRadius.circular(20), + ), + child: Text( + order.statusText, + style: const TextStyle( + color: Colors.white, + fontSize: 13, + fontWeight: FontWeight.w600, + ), + ), + ), + const Spacer(), + Text( + order.orderNo, + style: TextStyle( + fontSize: 14, + color: Colors.white.withOpacity(0.8), + fontFamily: 'monospace', + ), + ), + ], + ), + const SizedBox(height: 20), + Text( + '¥${order.amount.toStringAsFixed(2)}', + style: const TextStyle( + fontSize: 36, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + const SizedBox(height: 8), + Text( + order.productName, + style: TextStyle( + fontSize: 16, + color: Colors.white.withOpacity(0.9), + ), + ), + ], + ), + ); + } + + Widget _buildProductCard(order, AppLocalizations l10n) { + return Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: AppColors.surface, + borderRadius: BorderRadius.circular(16), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + l10n.productInfo, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: AppColors.textPrimary, + ), + ), + const SizedBox(height: 16), + Row( + children: [ + Container( + width: 64, + height: 64, + decoration: BoxDecoration( + color: AppColors.primary.withOpacity(0.1), + borderRadius: BorderRadius.circular(12), + ), + child: const Icon(Icons.image, color: AppColors.primary, size: 28), + ), + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + order.productName, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: AppColors.textPrimary, + ), + ), + const SizedBox(height: 6), + Text( + '${l10n.quantity}: x${order.quantity}', + style: const TextStyle( + fontSize: 14, + color: AppColors.textSecondary, + ), + ), + const SizedBox(height: 4), + Text( + '${l10n.unitPrice}: ¥${(order.amount / order.quantity).toStringAsFixed(2)}', + style: const TextStyle( + fontSize: 13, + color: AppColors.textTertiary, + ), + ), + ], + ), + ), + ], + ), + ], + ), + ); + } + + Widget _buildCustomerCard(order, AppLocalizations l10n) { + return Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: AppColors.surface, + borderRadius: BorderRadius.circular(16), + ), + child: Column( + children: [ + _buildInfoRow(l10n.customerName, order.customerName, Icons.person_outline), + const Divider(height: 24), + _buildInfoRow(l10n.contactPhone, order.customerPhone, Icons.phone_outlined), + if (order.scenicSpot != null) ...[ + const Divider(height: 24), + _buildInfoRow(l10n.belongScenic, order.scenicSpot!, Icons.place_outlined), + ], + ], + ), + ); + } + + Widget _buildPaymentCard(order, AppLocalizations l10n) { + return Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: AppColors.surface, + borderRadius: BorderRadius.circular(16), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + l10n.paymentInfo, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: AppColors.textPrimary, + ), + ), + const SizedBox(height: 16), + _buildPaymentRow(l10n.orderAmount, '¥${order.amount.toStringAsFixed(2)}', isBold: true), + const SizedBox(height: 8), + _buildPaymentRow(l10n.actualAmount, '¥${order.amount.toStringAsFixed(2)}'), + if (order.paidAt != null) ...[ + const SizedBox(height: 8), + _buildPaymentRow( + l10n.paymentTime, + '${order.paidAt!.month}/${order.paidAt!.day} ${order.paidAt!.hour.toString().padLeft(2, '0')}:${order.paidAt!.minute.toString().padLeft(2, '0')}', + ), + ], + ], + ), + ); + } + + Widget _buildPaymentRow(String label, String value, {bool isBold = false}) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + label, + style: TextStyle( + fontSize: 14, + color: isBold ? AppColors.textPrimary : AppColors.textSecondary, + ), + ), + Text( + value, + style: TextStyle( + fontSize: 15, + fontWeight: isBold ? FontWeight.w600 : FontWeight.w400, + color: isBold ? AppColors.primary : AppColors.textPrimary, + ), + ), + ], + ); + } + + Widget _buildInfoCard(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: [ + Text( + l10n.orderInfo, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: AppColors.textPrimary, + ), + ), + const SizedBox(height: 16), + _buildInfoRow(l10n.orderNo, order.orderNo, Icons.tag), + const Divider(height: 24), + _buildInfoRow(l10n.createTime, _formatDateTime(order.createdAt), Icons.access_time), + if (order.verifiedAt != null) ...[ + const Divider(height: 24), + _buildInfoRow(l10n.verifyTime, _formatDateTime(order.verifiedAt!), Icons.check_circle_outline), + ], + if (order.refundedAt != null) ...[ + const Divider(height: 24), + _buildInfoRow(l10n.refundTime, _formatDateTime(order.refundedAt!), Icons.replay), + ], + if (order.verifyCode != null) ...[ + const Divider(height: 24), + _buildInfoRow(l10n.verifyCode, order.verifyCode!, Icons.qr_code), + ], + if (order.remark != null) ...[ + const Divider(height: 24), + _buildInfoRow(l10n.remark, order.remark!, Icons.notes), + ], + ], + ), + ); + } + + Widget _buildInfoRow(String label, String value, IconData icon) { + return Row( + children: [ + Icon(icon, size: 18, color: AppColors.primary), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + label, + style: const TextStyle(fontSize: 12, color: AppColors.textTertiary), + ), + const SizedBox(height: 2), + Text( + value, + style: const TextStyle( + fontSize: 15, + fontWeight: FontWeight.w500, + color: AppColors.textPrimary, + ), + ), + ], + ), + ), + ], + ); + } + + String _formatDateTime(DateTime dt) { + return '${dt.year}-${dt.month.toString().padLeft(2, '0')}-${dt.day.toString().padLeft(2, '0')} ${dt.hour.toString().padLeft(2, '0')}:${dt.minute.toString().padLeft(2, '0')}'; + } +} diff --git a/lib/pages/order_work_page.dart b/lib/pages/order_work_page.dart new file mode 100644 index 0000000..90cc978 --- /dev/null +++ b/lib/pages/order_work_page.dart @@ -0,0 +1,517 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; +import 'package:flutter_animate/flutter_animate.dart'; +import '../providers/work_order_provider.dart'; +import '../providers/order_provider.dart'; +import '../models/work_order.dart'; +import '../models/order.dart'; +import '../theme.dart'; +import '../l10n/app_localizations.dart'; + +class OrderWorkPage extends ConsumerStatefulWidget { + final String initialTab; + const OrderWorkPage({super.key, this.initialTab = 'work'}); + + @override + ConsumerState createState() => _OrderWorkPageState(); +} + +class _OrderWorkPageState extends ConsumerState + with SingleTickerProviderStateMixin { + late TabController _tabController; + WorkOrderStatus _workStatus = WorkOrderStatus.all; + OrderStatus _orderStatus = OrderStatus.all; + + @override + void initState() { + super.initState(); + _tabController = TabController( + length: 2, + vsync: this, + initialIndex: widget.initialTab == 'order' ? 1 : 0, + ); + _tabController.addListener(_onTabChanged); + Future.microtask(() { + ref.read(workOrderProvider.notifier).loadOrders(WorkOrderStatus.all); + ref.read(orderProvider.notifier).loadOrders(OrderStatus.all); + }); + } + + void _onTabChanged() { + if (!_tabController.indexIsChanging) { + setState(() {}); + } + } + + @override + void dispose() { + _tabController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final l10n = AppLocalizations.of(context)!; + return Scaffold( + 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), + ), + 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( + controller: _tabController, + children: [ + _WorkOrderTab( + status: _workStatus, + onStatusChanged: (s) { + setState(() => _workStatus = s); + ref.read(workOrderProvider.notifier).loadOrders(s); + }, + ), + _OrderTab( + status: _orderStatus, + onStatusChanged: (s) { + setState(() => _orderStatus = s); + ref.read(orderProvider.notifier).loadOrders(s); + }, + ), + ], + ), + ); + } +} + +class _WorkOrderTab extends ConsumerWidget { + final WorkOrderStatus status; + final ValueChanged onStatusChanged; + + const _WorkOrderTab({required this.status, required this.onStatusChanged}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final l10n = AppLocalizations.of(context)!; + final state = ref.watch(workOrderProvider); + + return Column( + children: [ + _buildWorkFilterChips(context, l10n), + Expanded( + child: state.isLoading + ? const Center(child: CircularProgressIndicator()) + : state.orders.isEmpty + ? _buildEmptyState(l10n.noWorkOrders) + : ListView.builder( + padding: const EdgeInsets.all(16), + itemCount: state.orders.length, + itemBuilder: (context, index) { + return _buildWorkOrderCard(context, state.orders[index], index); + }, + ), + ), + ], + ); + } + + Widget _buildWorkFilterChips(BuildContext context, AppLocalizations l10n) { + final filters = [ + (WorkOrderStatus.all, l10n.all), + (WorkOrderStatus.pending, l10n.pending), + (WorkOrderStatus.processing, l10n.processing), + (WorkOrderStatus.completed, l10n.completed), + ]; + + return Container( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + color: AppColors.surface, + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + children: filters.map((f) { + final isSelected = status == f.$1; + return Padding( + padding: const EdgeInsets.only(right: 8), + child: FilterChip( + selected: isSelected, + onSelected: (_) => onStatusChanged(f.$1), + backgroundColor: AppColors.background, + selectedColor: AppColors.primary.withOpacity(0.1), + checkmarkColor: AppColors.primary, + label: Text(f.$2), + labelStyle: TextStyle( + fontSize: 13, + fontWeight: isSelected ? FontWeight.w600 : FontWeight.w400, + color: isSelected ? AppColors.primary : AppColors.textSecondary, + ), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + side: BorderSide( + color: isSelected ? AppColors.primary : Colors.transparent, + ), + ), + ), + ); + }).toList(), + ), + ), + ); + } + + Widget _buildWorkOrderCard(BuildContext context, WorkOrder order, int index) { + final l10n = AppLocalizations.of(context)!; + return GestureDetector( + onTap: () => context.push('/work-order/${order.id}'), + child: 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: [ + Expanded( + child: Text( + order.title, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: AppColors.textPrimary, + ), + ), + ), + Container( + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), + decoration: BoxDecoration( + color: order.statusColor.withOpacity(0.1), + borderRadius: BorderRadius.circular(20), + ), + child: Text( + order.statusText, + style: TextStyle( + fontSize: 11, + fontWeight: FontWeight.w500, + color: order.statusColor, + ), + ), + ), + ], + ), + const SizedBox(height: 8), + Text( + order.description, + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + fontSize: 14, + color: AppColors.textSecondary, + height: 1.4, + ), + ), + const SizedBox(height: 12), + Row( + children: [ + Icon(Icons.person_outline, size: 14, color: AppColors.textTertiary), + const SizedBox(width: 4), + Text( + '${l10n.creator}: ${order.creatorName}', + style: const 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), + ), + ], + const Spacer(), + Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), + decoration: BoxDecoration( + color: _getPriorityColor(order.priority).withOpacity(0.1), + borderRadius: BorderRadius.circular(4), + ), + child: Text( + _getPriorityText(context, order.priority), + style: TextStyle( + fontSize: 11, + color: _getPriorityColor(order.priority), + fontWeight: FontWeight.w500, + ), + ), + ), + ], + ), + ], + ), + ), + ).animate().fadeIn(duration: 300.ms, delay: (index * 50).ms).slideY(begin: 0.1, end: 0, duration: 300.ms); + } + + Color _getPriorityColor(String priority) { + switch (priority) { + case 'urgent': + return AppColors.error; + case 'high': + return AppColors.warning; + default: + return AppColors.textTertiary; + } + } + + String _getPriorityText(BuildContext context, String priority) { + final l10n = AppLocalizations.of(context)!; + switch (priority) { + case 'urgent': + return l10n.urgent; + case 'high': + return l10n.high; + default: + return l10n.normal; + } + } + + Widget _buildEmptyState(String text) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + 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)), + ], + ), + ); + } +} + +class _OrderTab extends ConsumerWidget { + final OrderStatus status; + final ValueChanged onStatusChanged; + + const _OrderTab({required this.status, required this.onStatusChanged}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final l10n = AppLocalizations.of(context)!; + final state = ref.watch(orderProvider); + + return Column( + children: [ + _buildOrderFilterChips(context, l10n), + Expanded( + child: state.isLoading + ? const Center(child: CircularProgressIndicator()) + : state.orders.isEmpty + ? _buildEmptyState(l10n.noOrders) + : ListView.builder( + padding: const EdgeInsets.all(16), + itemCount: state.orders.length, + itemBuilder: (context, index) { + return _buildOrderCard(context, state.orders[index], index); + }, + ), + ), + ], + ); + } + + Widget _buildOrderFilterChips(BuildContext context, AppLocalizations l10n) { + final filters = [ + (OrderStatus.all, l10n.all), + (OrderStatus.pendingPayment, l10n.pendingPayment), + (OrderStatus.pendingVerification, l10n.pendingVerify), + (OrderStatus.verified, l10n.verified), + (OrderStatus.pendingRefund, l10n.pendingRefund), + (OrderStatus.refunded, l10n.refunded), + ]; + + return Container( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + color: AppColors.surface, + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + children: filters.map((f) { + final isSelected = status == f.$1; + return Padding( + padding: const EdgeInsets.only(right: 8), + child: FilterChip( + selected: isSelected, + onSelected: (_) => onStatusChanged(f.$1), + backgroundColor: AppColors.background, + selectedColor: AppColors.primary.withOpacity(0.1), + checkmarkColor: AppColors.primary, + label: Text(f.$2), + labelStyle: TextStyle( + fontSize: 13, + fontWeight: isSelected ? FontWeight.w600 : FontWeight.w400, + color: isSelected ? AppColors.primary : AppColors.textSecondary, + ), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + side: BorderSide( + color: isSelected ? AppColors.primary : Colors.transparent, + ), + ), + ), + ); + }).toList(), + ), + ), + ); + } + + Widget _buildOrderCard(BuildContext context, OrderItem order, int index) { + final l10n = AppLocalizations.of(context)!; + return GestureDetector( + onTap: () => context.push('/order/${order.id}'), + child: 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: [ + Expanded( + child: Text( + order.orderNo, + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + color: AppColors.textPrimary, + ), + ), + ), + Container( + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), + decoration: BoxDecoration( + color: order.statusColor.withOpacity(0.1), + borderRadius: BorderRadius.circular(20), + ), + child: Text( + order.statusText, + style: TextStyle( + fontSize: 11, + fontWeight: FontWeight.w500, + color: order.statusColor, + ), + ), + ), + ], + ), + const SizedBox(height: 12), + Text( + order.productName, + style: const TextStyle( + fontSize: 15, + fontWeight: FontWeight.w500, + color: AppColors.textPrimary, + ), + ), + const SizedBox(height: 8), + Row( + children: [ + Icon(Icons.person_outline, size: 14, color: AppColors.textTertiary), + const SizedBox(width: 4), + Text( + order.customerName, + style: const 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), + ), + ], + ), + const SizedBox(height: 12), + Row( + children: [ + Text( + '¥${order.amount.toStringAsFixed(2)}', + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: AppColors.primary, + ), + ), + const Spacer(), + if (order.verifyCode != null) + Container( + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), + decoration: BoxDecoration( + color: AppColors.background, + borderRadius: BorderRadius.circular(6), + ), + child: Text( + '${l10n.verifyCode}: ${order.verifyCode}', + style: const TextStyle( + fontSize: 12, + fontFamily: 'monospace', + color: AppColors.textSecondary, + ), + ), + ), + ], + ), + ], + ), + ), + ).animate().fadeIn(duration: 300.ms, delay: (index * 50).ms).slideY(begin: 0.1, end: 0, duration: 300.ms); + } + + Widget _buildEmptyState(String text) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + 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)), + ], + ), + ); + } +} diff --git a/lib/pages/policy_page.dart b/lib/pages/policy_page.dart new file mode 100644 index 0000000..030170a --- /dev/null +++ b/lib/pages/policy_page.dart @@ -0,0 +1,114 @@ +import 'package:flutter/material.dart'; +import '../theme.dart'; +import '../l10n/app_localizations.dart'; + +class PolicyPage extends StatelessWidget { + final String type; + const PolicyPage({super.key, required this.type}); + + bool get isAgreement => type == 'agreement'; + + @override + Widget build(BuildContext context) { + final l10n = AppLocalizations.of(context)!; + return Scaffold( + backgroundColor: AppColors.background, + appBar: AppBar( + title: Text(isAgreement ? l10n.userAgreement : l10n.privacyPolicy), + ), + body: SingleChildScrollView( + padding: const EdgeInsets.all(20), + child: Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: AppColors.surface, + borderRadius: BorderRadius.circular(16), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + isAgreement ? l10n.agreementTitle : l10n.privacyTitle, + style: const TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + color: AppColors.textPrimary, + ), + ), + const SizedBox(height: 8), + Text( + l10n.lastUpdated, + style: const TextStyle(fontSize: 12, color: AppColors.textTertiary), + ), + const SizedBox(height: 24), + if (isAgreement) ..._buildAgreementContent(context, l10n) else ..._buildPrivacyContent(context, l10n), + ], + ), + ), + ), + ); + } + + List _buildAgreementContent(BuildContext context, AppLocalizations l10n) { + return [ + _section(l10n.agreementSection1), + _paragraph(l10n.agreementPara1), + _section(l10n.agreementSection2), + _paragraph(l10n.agreementPara2), + _section(l10n.agreementSection3), + _paragraph(l10n.agreementPara3), + _section(l10n.agreementSection4), + _paragraph(l10n.agreementPara4), + _section(l10n.agreementSection5), + _paragraph(l10n.agreementPara5), + _section(l10n.agreementSection6), + _paragraph(l10n.agreementPara6), + _section(l10n.agreementSection7), + _paragraph(l10n.agreementPara7), + ]; + } + + List _buildPrivacyContent(BuildContext context, AppLocalizations l10n) { + return [ + _section(l10n.privacySection1), + _paragraph(l10n.privacyPara1), + _section(l10n.privacySection2), + _paragraph(l10n.privacyPara2), + _section(l10n.privacySection3), + _paragraph(l10n.privacyPara3), + _section(l10n.privacySection4), + _paragraph(l10n.privacyPara4), + _section(l10n.privacySection5), + _paragraph(l10n.privacyPara5), + _section(l10n.privacySection6), + _paragraph(l10n.privacyPara6), + _section(l10n.privacySection7), + _paragraph(l10n.privacyPara7), + ]; + } + + Widget _section(String title) { + return Padding( + padding: const EdgeInsets.only(top: 20, bottom: 8), + child: Text( + title, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: AppColors.textPrimary, + ), + ), + ); + } + + Widget _paragraph(String text) { + return Text( + text, + style: const TextStyle( + fontSize: 14, + color: AppColors.textSecondary, + height: 1.7, + ), + ); + } +} diff --git a/lib/pages/report_page.dart b/lib/pages/report_page.dart new file mode 100644 index 0000000..9523031 --- /dev/null +++ b/lib/pages/report_page.dart @@ -0,0 +1,250 @@ +import 'package:flutter/material.dart'; +import '../theme.dart'; +import '../l10n/app_localizations.dart'; + +class ReportPage extends StatelessWidget { + const ReportPage({super.key}); + + @override + Widget build(BuildContext context) { + final l10n = AppLocalizations.of(context)!; + return Scaffold( + backgroundColor: AppColors.background, + appBar: AppBar(title: Text(l10n.dataReport)), + body: ListView( + padding: const EdgeInsets.all(20), + children: [ + _buildSummaryCards(context, l10n), + const SizedBox(height: 20), + _buildChartCard(context, l10n.orderTrend, [ + _buildBar(context, l10n.monday, 0.6), + _buildBar(context, l10n.tuesday, 0.8), + _buildBar(context, l10n.wednesday, 0.5), + _buildBar(context, l10n.thursday, 0.9), + _buildBar(context, l10n.friday, 0.7), + _buildBar(context, l10n.saturday, 1.0), + _buildBar(context, l10n.sunday, 0.75), + ]), + const SizedBox(height: 20), + _buildChartCard(context, l10n.revenueComposition, [ + _buildPieItem(context, l10n.ticket, 0.45, const Color(0xFF1A56DB)), + _buildPieItem(context, l10n.hotel, 0.30, const Color(0xFF10B981)), + _buildPieItem(context, l10n.catering, 0.15, const Color(0xFFF59E0B)), + _buildPieItem(context, l10n.spa, 0.10, const Color(0xFFEC4899)), + ]), + const SizedBox(height: 20), + _buildRankingCard(context, l10n), + ], + ), + ); + } + + Widget _buildSummaryCards(BuildContext context, AppLocalizations l10n) { + final items = [ + (l10n.totalRevenue, '¥128,560', '${l10n.vsLastWeek} +12%', const Color(0xFF1A56DB)), + (l10n.orderCount, '1,286', '${l10n.vsLastWeek} +8%', const Color(0xFF10B981)), + (l10n.avgOrderValue, '¥168', '${l10n.vsLastWeek} +3%', const Color(0xFFF59E0B)), + (l10n.verifyRate, '96.5%', '${l10n.vsLastWeek} +2%', const Color(0xFF8B5CF6)), + ]; + + return GridView.count( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + crossAxisCount: 2, + crossAxisSpacing: 12, + mainAxisSpacing: 12, + childAspectRatio: 1.3, + children: items.map((item) { + return Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: AppColors.surface, + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.04), + blurRadius: 10, + offset: const Offset(0, 2), + ), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + item.$1, + style: const TextStyle(fontSize: 13, color: AppColors.textSecondary), + ), + const SizedBox(height: 8), + Text( + item.$2, + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + color: item.$4, + ), + ), + const SizedBox(height: 6), + Text( + item.$3, + style: const TextStyle(fontSize: 12, color: AppColors.success), + ), + ], + ), + ); + }).toList(), + ); + } + + Widget _buildChartCard(BuildContext context, String title, List children) { + return Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: AppColors.surface, + borderRadius: BorderRadius.circular(16), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: AppColors.textPrimary, + ), + ), + const SizedBox(height: 20), + ...children, + ], + ), + ); + } + + Widget _buildBar(BuildContext context, String label, double value) { + return Padding( + padding: const EdgeInsets.only(bottom: 12), + child: Row( + children: [ + SizedBox( + width: 40, + child: Text(label, style: const TextStyle(fontSize: 12, color: AppColors.textSecondary)), + ), + Expanded( + child: Container( + height: 8, + decoration: BoxDecoration( + color: AppColors.background, + borderRadius: BorderRadius.circular(4), + ), + child: FractionallySizedBox( + alignment: Alignment.centerLeft, + widthFactor: value, + child: Container( + decoration: BoxDecoration( + gradient: AppGradients.primary, + borderRadius: BorderRadius.circular(4), + ), + ), + ), + ), + ), + const SizedBox(width: 10), + Text('${(value * 100).toInt()}%', style: const TextStyle(fontSize: 12, color: AppColors.textSecondary)), + ], + ), + ); + } + + Widget _buildPieItem(BuildContext context, String label, double value, Color color) { + return Padding( + padding: const EdgeInsets.only(bottom: 10), + child: Row( + children: [ + Container( + width: 12, + height: 12, + decoration: BoxDecoration(color: color, borderRadius: BorderRadius.circular(3)), + ), + const SizedBox(width: 10), + Expanded( + child: Text(label, style: const TextStyle(fontSize: 14, color: AppColors.textPrimary)), + ), + Text('${(value * 100).toInt()}%', style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600, color: AppColors.textPrimary)), + ], + ), + ); + } + + Widget _buildRankingCard(BuildContext context, AppLocalizations l10n) { + final products = [ + (l10n.adultTicket, '¥45,280', '352${l10n.orderUnit}'), + (l10n.luxurySuite, '¥38,560', '43${l10n.orderUnit}'), + (l10n.familyPackage, '¥28,800', '74${l10n.orderUnit}'), + (l10n.spaPackage, '¥15,960', '26${l10n.orderUnit}'), + ]; + + return Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: AppColors.surface, + borderRadius: BorderRadius.circular(16), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + l10n.topSales, + style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: AppColors.textPrimary), + ), + const SizedBox(height: 16), + ...products.asMap().entries.map((entry) { + final rank = entry.key + 1; + final product = entry.value; + return Padding( + padding: const EdgeInsets.only(bottom: 14), + child: Row( + children: [ + Container( + width: 26, + height: 26, + decoration: BoxDecoration( + color: rank <= 3 ? AppColors.primary.withValues(alpha: 0.1) : AppColors.background, + borderRadius: BorderRadius.circular(6), + ), + child: Center( + child: Text( + '$rank', + style: TextStyle( + fontSize: 13, + fontWeight: FontWeight.bold, + color: rank <= 3 ? AppColors.primary : AppColors.textTertiary, + ), + ), + ), + ), + const SizedBox(width: 12), + Expanded( + child: Text( + product.$1, + style: const TextStyle(fontSize: 14, color: AppColors.textPrimary), + ), + ), + Text( + product.$2, + style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600, color: AppColors.primary), + ), + const SizedBox(width: 12), + Text( + product.$3, + style: const TextStyle(fontSize: 12, color: AppColors.textTertiary), + ), + ], + ), + ); + }).toList(), + ], + ), + ); + } +} diff --git a/lib/pages/scan_result_page.dart b/lib/pages/scan_result_page.dart new file mode 100644 index 0000000..924bb1d --- /dev/null +++ b/lib/pages/scan_result_page.dart @@ -0,0 +1,425 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; +import 'package:url_launcher/url_launcher.dart'; +import '../providers/order_provider.dart'; +import '../theme.dart'; +import '../l10n/app_localizations.dart'; + +class ScanResultPage extends ConsumerStatefulWidget { + final String scanCode; + const ScanResultPage({super.key, required this.scanCode}); + + @override + ConsumerState createState() => _ScanResultPageState(); +} + +class _ScanResultPageState extends ConsumerState { + bool _showActionSheet = false; + + @override + void initState() { + super.initState(); + Future.microtask(() { + ref.read(orderProvider.notifier).scanVerify(widget.scanCode); + }); + } + + void _showVerifyDialog() { + final l10n = AppLocalizations.of(context)!; + final order = ref.read(orderProvider).currentOrder; + if (order == null || !order.canVerify) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(l10n.notVerifiable)), + ); + return; + } + + showModalBottomSheet( + context: context, + backgroundColor: Colors.transparent, + isScrollControlled: true, + builder: (context) { + return Container( + padding: const EdgeInsets.all(24), + decoration: const BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.vertical(top: Radius.circular(24)), + ), + child: SafeArea( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 40, + height: 4, + decoration: BoxDecoration( + color: AppColors.divider, + borderRadius: BorderRadius.circular(2), + ), + ), + const SizedBox(height: 24), + Container( + width: 64, + height: 64, + decoration: BoxDecoration( + color: AppColors.primary.withOpacity(0.1), + shape: BoxShape.circle, + ), + child: const Icon(Icons.qr_code_scanner, color: AppColors.primary, size: 32), + ), + const SizedBox(height: 20), + Text( + l10n.confirmVerify, + style: const TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + color: AppColors.textPrimary, + ), + ), + const SizedBox(height: 8), + Text( + '${l10n.orderNo}: ${order.orderNo}', + style: const TextStyle(fontSize: 14, color: AppColors.textSecondary), + ), + const SizedBox(height: 4), + Text( + '${l10n.customerName}: ${order.customerName}', + style: const TextStyle(fontSize: 14, color: AppColors.textSecondary), + ), + const SizedBox(height: 4), + Text( + '${l10n.amount}: ¥${order.amount.toStringAsFixed(2)}', + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: AppColors.primary, + ), + ), + const SizedBox(height: 24), + Row( + children: [ + Expanded( + child: OutlinedButton( + onPressed: () => Navigator.of(context).pop(), + style: OutlinedButton.styleFrom( + padding: const EdgeInsets.symmetric(vertical: 14), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + side: const BorderSide(color: AppColors.divider), + ), + child: Text(l10n.cancel), + ), + ), + const SizedBox(width: 16), + Expanded( + child: ElevatedButton( + onPressed: () { + Navigator.of(context).pop(); + _doVerify(); + }, + style: ElevatedButton.styleFrom( + backgroundColor: AppColors.primary, + foregroundColor: Colors.white, + padding: const EdgeInsets.symmetric(vertical: 14), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + child: Text(l10n.confirmVerify), + ), + ), + ], + ), + ], + ), + ), + ); + }, + ); + } + + void _doVerify() { + final l10n = AppLocalizations.of(context)!; + final order = ref.read(orderProvider).currentOrder; + if (order == null) return; + + ref.read(orderProvider.notifier).verifyOrder(order.id).then((_) { + final state = ref.read(orderProvider); + if (state.verifySuccess) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(l10n.verifySuccess), + backgroundColor: AppColors.success, + behavior: SnackBarBehavior.floating, + ), + ); + ref.read(orderProvider.notifier).clearVerifySuccess(); + setState(() => _showActionSheet = false); + } + }); + } + + Future _makePhoneCall() async { + final order = ref.read(orderProvider).currentOrder; + if (order == null) return; + final phone = order.customerPhone.replaceAll('*', '0'); + final uri = Uri.parse('tel:$phone'); + if (await canLaunchUrl(uri)) { + await launchUrl(uri); + } + } + + @override + Widget build(BuildContext context) { + final l10n = AppLocalizations.of(context)!; + final state = ref.watch(orderProvider); + final order = state.currentOrder; + + if (state.isLoading || order == null) { + return Scaffold( + backgroundColor: AppColors.background, + appBar: AppBar(title: Text(l10n.scanResult)), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const CircularProgressIndicator(), + const SizedBox(height: 16), + Text(l10n.scanning, style: const TextStyle(color: AppColors.textSecondary)), + ], + ), + ), + ); + } + + return Scaffold( + backgroundColor: AppColors.background, + appBar: AppBar( + title: Text(l10n.scanResult), + leading: IconButton( + icon: const Icon(Icons.close), + onPressed: () => context.go('/home'), + ), + ), + body: Stack( + children: [ + SingleChildScrollView( + padding: const EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildResultHeader(order, l10n), + const SizedBox(height: 20), + _buildOrderInfoCard(order, l10n), + const SizedBox(height: 20), + _buildCustomerCard(order, l10n), + const SizedBox(height: 100), + ], + ), + ), + if (order.canVerify) + Positioned( + left: 0, + right: 0, + bottom: 0, + child: _buildBottomActions(order, l10n), + ), + ], + ), + ); + } + + Widget _buildResultHeader(order, AppLocalizations l10n) { + return Container( + padding: const EdgeInsets.all(24), + decoration: BoxDecoration( + gradient: AppGradients.primary, + borderRadius: BorderRadius.circular(20), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.2), + borderRadius: BorderRadius.circular(20), + ), + child: Text( + order.statusText, + style: const TextStyle( + color: Colors.white, + fontSize: 13, + fontWeight: FontWeight.w600, + ), + ), + ), + ], + ), + const SizedBox(height: 16), + Text( + l10n.scanSuccess, + style: const TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + const SizedBox(height: 8), + Text( + '${l10n.orderNo}: ${order.orderNo}', + style: TextStyle( + fontSize: 14, + color: Colors.white.withOpacity(0.85), + fontFamily: 'monospace', + ), + ), + ], + ), + ); + } + + Widget _buildOrderInfoCard(order, AppLocalizations l10n) { + return Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: AppColors.surface, + borderRadius: BorderRadius.circular(16), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + l10n.orderInfo, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: AppColors.textPrimary, + ), + ), + const SizedBox(height: 16), + _buildInfoRow(l10n.product, order.productName, Icons.shopping_bag_outlined), + const Divider(height: 20), + _buildInfoRow(l10n.quantity, 'x${order.quantity}', Icons.confirmation_number_outlined), + const Divider(height: 20), + _buildInfoRow(l10n.amount, '¥${order.amount.toStringAsFixed(2)}', Icons.payments_outlined), + if (order.verifyCode != null) ...[ + const Divider(height: 20), + _buildInfoRow(l10n.verifyCode, order.verifyCode!, Icons.qr_code), + ], + ], + ), + ); + } + + Widget _buildCustomerCard(order, AppLocalizations l10n) { + return Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: AppColors.surface, + borderRadius: BorderRadius.circular(16), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + l10n.customerInfo, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: AppColors.textPrimary, + ), + ), + const SizedBox(height: 16), + _buildInfoRow(l10n.name, order.customerName, Icons.person_outline), + const Divider(height: 20), + _buildInfoRow(l10n.phone, order.customerPhone, Icons.phone_outlined), + ], + ), + ); + } + + Widget _buildInfoRow(String label, String value, IconData icon) { + return Row( + children: [ + Icon(icon, size: 18, color: AppColors.primary), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + label, + style: const TextStyle(fontSize: 12, color: AppColors.textTertiary), + ), + const SizedBox(height: 2), + Text( + value, + style: const TextStyle( + fontSize: 15, + fontWeight: FontWeight.w500, + color: AppColors.textPrimary, + ), + ), + ], + ), + ), + ], + ); + } + + Widget _buildBottomActions(order, AppLocalizations l10n) { + return Container( + padding: const EdgeInsets.fromLTRB(20, 16, 20, 28), + decoration: BoxDecoration( + color: AppColors.surface, + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.08), + blurRadius: 20, + offset: const Offset(0, -4), + ), + ], + ), + child: SafeArea( + child: Row( + children: [ + Expanded( + child: OutlinedButton.icon( + onPressed: _makePhoneCall, + icon: const Icon(Icons.phone, size: 20), + label: Text(l10n.contactCustomer), + style: OutlinedButton.styleFrom( + padding: const EdgeInsets.symmetric(vertical: 14), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(14), + ), + ), + ), + ), + const SizedBox(width: 16), + Expanded( + child: ElevatedButton.icon( + onPressed: _showVerifyDialog, + icon: const Icon(Icons.check_circle, size: 20), + label: Text(l10n.verify), + style: ElevatedButton.styleFrom( + backgroundColor: AppColors.success, + foregroundColor: Colors.white, + padding: const EdgeInsets.symmetric(vertical: 14), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(14), + ), + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages/settings_page.dart b/lib/pages/settings_page.dart new file mode 100644 index 0000000..9122f1b --- /dev/null +++ b/lib/pages/settings_page.dart @@ -0,0 +1,434 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; +import '../providers/auth_provider.dart'; +import '../providers/settings_provider.dart'; +import '../theme.dart'; +import '../l10n/app_localizations.dart'; + +class SettingsPage extends ConsumerWidget { + const SettingsPage({super.key}); + + void _showThemePicker(BuildContext context, WidgetRef ref) { + final current = ref.read(settingsProvider).themeMode; + showModalBottomSheet( + context: context, + backgroundColor: Colors.transparent, + builder: (ctx) { + 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)), + ), + child: SafeArea( + child: Column( + mainAxisSize: MainAxisSize.min, + 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)), + 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), + ], + ), + ), + ); + }, + ); + } + + Widget _themeOption(BuildContext ctx, WidgetRef ref, AppThemeMode mode, String label, IconData icon, AppThemeMode current) { + final selected = current == mode; + return ListTile( + leading: Icon(icon, color: selected ? AppColors.primary : AppColors.textTertiary), + title: Text(label, style: TextStyle(fontWeight: selected ? FontWeight.w600 : FontWeight.w400, color: AppColors.textPrimary)), + trailing: selected ? const Icon(Icons.check_circle, color: AppColors.primary) : null, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + onTap: () { + ref.read(settingsProvider.notifier).setThemeMode(mode); + Navigator.of(ctx).pop(); + }, + ); + } + + void _showLanguagePicker(BuildContext context, WidgetRef ref) { + final current = ref.read(settingsProvider).locale; + showModalBottomSheet( + context: context, + backgroundColor: Colors.transparent, + builder: (ctx) { + 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)), + ), + child: SafeArea( + child: Column( + mainAxisSize: MainAxisSize.min, + 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)), + 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), + ], + ), + ), + ); + }, + ); + } + + Widget _languageOption(BuildContext ctx, WidgetRef ref, 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)), + trailing: selected ? const Icon(Icons.check_circle, color: AppColors.primary) : null, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + onTap: () { + ref.read(settingsProvider.notifier).setLocale(locale); + Navigator.of(ctx).pop(); + }, + ); + } + + @override + Widget build(BuildContext context, WidgetRef ref) { + final l10n = AppLocalizations.of(context)!; + final authState = ref.watch(authProvider); + final settings = ref.watch(settingsProvider); + final user = authState.user; + final isBoss = user?.isBoss ?? false; + + final themeLabel = switch (settings.themeMode) { + AppThemeMode.light => l10n.lightMode, + AppThemeMode.dark => l10n.darkMode, + AppThemeMode.system => l10n.followSystem, + }; + + final localeLabel = switch (settings.locale.languageCode) { + 'zh' => l10n.simplifiedChinese, + 'en' => l10n.english, + 'th' => l10n.thai, + _ => 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, + 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, + ), + ), + ), + ], + ), + ), + 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, + ), + ); + } + + Widget _buildSettingsCard(List<_SettingItem> items) { + return Container( + decoration: BoxDecoration( + color: AppColors.surface, + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.03), + blurRadius: 10, + offset: const Offset(0, 2), + ), + ], + ), + child: Column( + children: items.asMap().entries.map((entry) { + final item = entry.value; + final isLast = entry.key == items.length - 1; + return Column( + children: [ + ListTile( + leading: Container( + width: 40, + height: 40, + decoration: BoxDecoration( + color: item.iconColor.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(10), + ), + child: Icon(item.icon, color: item.iconColor, size: 20), + ), + title: Row( + children: [ + Text( + item.title, + style: const TextStyle( + fontSize: 15, + fontWeight: FontWeight.w500, + color: AppColors.textPrimary, + ), + ), + if (item.showBadge) ...[ + const SizedBox(width: 8), + Container( + width: 8, + height: 8, + decoration: const BoxDecoration( + color: AppColors.error, + shape: BoxShape.circle, + ), + ), + ], + ], + ), + subtitle: Text( + item.subtitle, + style: const TextStyle( + fontSize: 13, + color: AppColors.textTertiary, + ), + ), + trailing: item.trailing ?? const Icon(Icons.chevron_right, color: AppColors.textTertiary, size: 20), + onTap: item.onTap, + ), + if (!isLast) + const Divider(height: 1, indent: 72), + ], + ); + }).toList(), + ), + ); + } +} + +class _SettingItem { + final IconData icon; + final Color iconColor; + final String title; + final String subtitle; + final VoidCallback onTap; + final bool showBadge; + final Widget? trailing; + + const _SettingItem({ + required this.icon, + required this.iconColor, + required this.title, + required this.subtitle, + required this.onTap, + this.showBadge = false, + this.trailing, + }); +} diff --git a/lib/pages/store_page.dart b/lib/pages/store_page.dart new file mode 100644 index 0000000..575df01 --- /dev/null +++ b/lib/pages/store_page.dart @@ -0,0 +1,164 @@ +import 'package:flutter/material.dart'; +import '../theme.dart'; +import '../l10n/app_localizations.dart'; + +class StorePage extends StatelessWidget { + const StorePage({super.key}); + + @override + Widget build(BuildContext context) { + final l10n = AppLocalizations.of(context)!; + return Scaffold( + backgroundColor: AppColors.background, + appBar: AppBar(title: Text(l10n.storeList)), + body: ListView( + padding: const EdgeInsets.all(20), + children: [ + _buildStoreCard( + name: '智念度假酒店', + address: '浙江省杭州市西湖区灵隐路18号', + phone: '0571-88888888', + status: l10n.open, + type: l10n.hotel, + imageColor: const Color(0xFF1A56DB), + icon: Icons.hotel, + ), + const SizedBox(height: 16), + _buildStoreCard( + name: '智念风景区', + address: '浙江省杭州市西湖区龙井路88号', + phone: '0571-88888899', + status: l10n.openScenic, + type: l10n.scenic, + imageColor: const Color(0xFF10B981), + icon: Icons.park, + ), + const SizedBox(height: 16), + _buildStoreCard( + name: '智念SPA养生中心', + address: '浙江省杭州市西湖区杨公堤12号', + phone: '0571-88888877', + status: l10n.open, + type: l10n.spa, + imageColor: const Color(0xFFEC4899), + icon: Icons.spa, + ), + ], + ), + ); + } + + Widget _buildStoreCard({ + required String name, + required String address, + required String phone, + required String status, + required String type, + required Color imageColor, + required IconData icon, + }) { + return Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: AppColors.surface, + borderRadius: BorderRadius.circular(20), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.04), + blurRadius: 12, + offset: const Offset(0, 4), + ), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + width: 56, + height: 56, + decoration: BoxDecoration( + color: imageColor.withOpacity(0.1), + borderRadius: BorderRadius.circular(14), + ), + child: Icon(icon, color: imageColor, size: 28), + ), + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + name, + style: const TextStyle( + fontSize: 17, + fontWeight: FontWeight.w600, + color: AppColors.textPrimary, + ), + ), + const SizedBox(height: 6), + Row( + children: [ + Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3), + decoration: BoxDecoration( + color: AppColors.success.withOpacity(0.1), + borderRadius: BorderRadius.circular(20), + ), + child: Text( + status, + style: const TextStyle( + fontSize: 11, + color: AppColors.success, + fontWeight: FontWeight.w500, + ), + ), + ), + const SizedBox(width: 8), + Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3), + decoration: BoxDecoration( + color: AppColors.background, + borderRadius: BorderRadius.circular(20), + ), + child: Text( + type, + style: const TextStyle( + fontSize: 11, + color: AppColors.textSecondary, + ), + ), + ), + ], + ), + ], + ), + ), + const Icon(Icons.chevron_right, color: AppColors.textTertiary), + ], + ), + const Divider(height: 24), + _buildInfoRow(Icons.location_on_outlined, address), + const SizedBox(height: 8), + _buildInfoRow(Icons.phone_outlined, phone), + ], + ), + ); + } + + Widget _buildInfoRow(IconData icon, String text) { + return Row( + children: [ + Icon(icon, size: 16, color: AppColors.textTertiary), + const SizedBox(width: 8), + Expanded( + child: Text( + text, + style: const TextStyle(fontSize: 13, color: AppColors.textSecondary), + ), + ), + ], + ); + } +} diff --git a/lib/pages/work_order_detail_page.dart b/lib/pages/work_order_detail_page.dart new file mode 100644 index 0000000..7d8c1cf --- /dev/null +++ b/lib/pages/work_order_detail_page.dart @@ -0,0 +1,421 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import '../models/work_order.dart'; +import '../providers/work_order_provider.dart'; +import '../theme.dart'; +import '../l10n/app_localizations.dart'; + +class WorkOrderDetailPage extends ConsumerStatefulWidget { + final String orderId; + const WorkOrderDetailPage({super.key, required this.orderId}); + + @override + ConsumerState createState() => _WorkOrderDetailPageState(); +} + +class _WorkOrderDetailPageState extends ConsumerState { + @override + void initState() { + super.initState(); + Future.microtask(() { + ref.read(workOrderProvider.notifier).getDetail(widget.orderId); + }); + } + + @override + Widget build(BuildContext context) { + final l10n = AppLocalizations.of(context)!; + final state = ref.watch(workOrderProvider); + final order = state.orders.firstWhere( + (o) => o.id == widget.orderId, + orElse: () => state.orders.isNotEmpty ? state.orders.first : _mockOrder(context, l10n), + ); + + return Scaffold( + backgroundColor: AppColors.background, + appBar: AppBar(title: Text(l10n.workOrderDetail)), + body: SingleChildScrollView( + padding: const EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + 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), + ], + ), + ), + ); + } + + Widget _buildHeader(BuildContext context, WorkOrder order, AppLocalizations l10n) { + return Container( + padding: const EdgeInsets.all(24), + decoration: BoxDecoration( + gradient: AppGradients.primary, + borderRadius: BorderRadius.circular(20), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.2), + borderRadius: BorderRadius.circular(20), + ), + child: Text( + order.statusText, + style: const TextStyle( + color: Colors.white, + fontSize: 13, + fontWeight: FontWeight.w600, + ), + ), + ), + const SizedBox(height: 16), + Text( + order.title, + style: const TextStyle( + fontSize: 22, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + const SizedBox(height: 8), + Row( + children: [ + Icon(Icons.location_on_outlined, size: 16, color: Colors.white.withOpacity(0.8)), + const SizedBox(width: 6), + Text( + order.location ?? l10n.unknownLocation, + style: TextStyle( + fontSize: 14, + color: Colors.white.withOpacity(0.85), + ), + ), + ], + ), + ], + ), + ); + } + + Widget _buildInfoCard(BuildContext context, WorkOrder order, AppLocalizations l10n) { + return Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: AppColors.surface, + borderRadius: BorderRadius.circular(16), + ), + child: Column( + children: [ + _buildInfoRow(l10n.workOrderId, order.id, Icons.tag), + 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), + const Divider(height: 24), + _buildInfoRow(l10n.priority, _getPriorityText(context, order.priority), Icons.flag_outlined), + const Divider(height: 24), + _buildInfoRow( + l10n.createTime, + '${order.createdAt.month}/${order.createdAt.day} ${order.createdAt.hour.toString().padLeft(2, '0')}:${order.createdAt.minute.toString().padLeft(2, '0')}', + Icons.access_time, + ), + ], + ), + ); + } + + Widget _buildInfoRow(String label, String value, IconData icon) { + return Row( + children: [ + Icon(icon, size: 18, color: AppColors.primary), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + label, + style: const TextStyle(fontSize: 12, color: AppColors.textTertiary), + ), + const SizedBox(height: 2), + Text( + value, + style: const TextStyle( + fontSize: 15, + fontWeight: FontWeight.w500, + color: AppColors.textPrimary, + ), + ), + ], + ), + ), + ], + ); + } + + Widget _buildDescriptionCard(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.description_outlined, size: 18, color: AppColors.primary), + const SizedBox(width: 8), + Text( + l10n.problemDesc, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: AppColors.textPrimary, + ), + ), + ], + ), + const SizedBox(height: 12), + Text( + order.description, + style: const TextStyle( + fontSize: 15, + color: AppColors.textSecondary, + height: 1.6, + ), + ), + ], + ), + ); + } + + 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(), + ), + ], + ), + ); + } + + Widget _buildTimeline(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.timeline, size: 18, color: AppColors.primary), + const SizedBox(width: 8), + Text( + l10n.progress, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: AppColors.textPrimary, + ), + ), + ], + ), + const SizedBox(height: 20), + _TimelineItem( + isFirst: true, + isActive: true, + title: l10n.workOrderCreated, + subtitle: '${order.creatorName} ${l10n.createdWorkOrder}', + time: '${order.createdAt.month}/${order.createdAt.day} ${order.createdAt.hour.toString().padLeft(2, '0')}:${order.createdAt.minute.toString().padLeft(2, '0')}', + ), + if (order.assigneeName != null) + _TimelineItem( + isActive: true, + title: l10n.workOrderAssigned, + subtitle: '${l10n.assignee} ${order.assigneeName}', + time: '${order.createdAt.month}/${order.createdAt.day} ${(order.createdAt.hour + 1).toString().padLeft(2, '0')}:${order.createdAt.minute.toString().padLeft(2, '0')}', + ), + if (order.status.index >= 2) + _TimelineItem( + isActive: true, + title: l10n.startProcessing, + subtitle: l10n.staffStartedProcessing, + time: '${order.createdAt.month}/${order.createdAt.day} ${(order.createdAt.hour + 2).toString().padLeft(2, '0')}:${order.createdAt.minute.toString().padLeft(2, '0')}', + ), + if (order.completedAt != null) + _TimelineItem( + isLast: true, + isActive: true, + title: l10n.workOrderCompleted, + subtitle: l10n.workOrderFinished, + time: '${order.completedAt!.month}/${order.completedAt!.day} ${order.completedAt!.hour.toString().padLeft(2, '0')}:${order.completedAt!.minute.toString().padLeft(2, '0')}', + ), + ], + ), + ); + } + + String _getPriorityText(BuildContext context, String priority) { + final l10n = AppLocalizations.of(context)!; + switch (priority) { + case 'urgent': + return l10n.urgent; + case 'high': + return l10n.high; + default: + return l10n.normal; + } + } + + WorkOrder _mockOrder(BuildContext context, AppLocalizations l10n) { + return WorkOrder( + id: widget.orderId, + title: l10n.unknownWorkOrder, + description: l10n.workOrderLoadFailed, + status: WorkOrderStatus.pending, + creatorName: l10n.system, + createdAt: DateTime.now(), + priority: 'normal', + category: l10n.other, + images: [], + ); + } +} + +class _TimelineItem extends StatelessWidget { + final bool isFirst; + final bool isLast; + final bool isActive; + final String title; + final String subtitle; + final String time; + + const _TimelineItem({ + this.isFirst = false, + this.isLast = false, + required this.isActive, + required this.title, + required this.subtitle, + required this.time, + }); + + @override + Widget build(BuildContext context) { + return IntrinsicHeight( + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Column( + children: [ + if (!isFirst) + Container(width: 2, height: 20, color: isActive ? AppColors.primary.withOpacity(0.3) : AppColors.divider), + Container( + width: 12, + height: 12, + decoration: BoxDecoration( + color: isActive ? AppColors.primary : AppColors.divider, + shape: BoxShape.circle, + border: Border.all(color: AppColors.surface, width: 2), + ), + ), + if (!isLast) + Expanded(child: Container(width: 2, color: isActive ? AppColors.primary.withOpacity(0.3) : AppColors.divider)), + ], + ), + const SizedBox(width: 16), + Expanded( + child: Padding( + padding: EdgeInsets.only(bottom: isLast ? 0 : 24), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: TextStyle( + fontSize: 15, + fontWeight: FontWeight.w600, + color: isActive ? AppColors.textPrimary : AppColors.textTertiary, + ), + ), + const SizedBox(height: 4), + Text( + subtitle, + style: TextStyle( + fontSize: 13, + color: isActive ? AppColors.textSecondary : AppColors.textTertiary, + ), + ), + const SizedBox(height: 4), + Text( + time, + style: TextStyle( + fontSize: 12, + color: AppColors.textTertiary, + ), + ), + ], + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/providers/auth_provider.dart b/lib/providers/auth_provider.dart new file mode 100644 index 0000000..c2c6c4c --- /dev/null +++ b/lib/providers/auth_provider.dart @@ -0,0 +1,57 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import '../models/user.dart'; +import '../services/storage_service.dart'; +import '../services/api_service.dart'; + +class AuthState { + final User? user; + final bool isLoading; + final String? error; + + const AuthState({this.user, this.isLoading = false, this.error}); + + bool get isLoggedIn => user != null; + + AuthState copyWith({User? user, bool? isLoading, String? error}) { + return AuthState( + user: user ?? this.user, + isLoading: isLoading ?? this.isLoading, + error: error, + ); + } +} + +class AuthNotifier extends StateNotifier { + AuthNotifier() : super(const AuthState()) { + _checkLoginStatus(); + } + + Future _checkLoginStatus() async { + await storageService.init(); + final user = storageService.getUser(); + if (user != null) { + state = state.copyWith(user: user); + } + } + + Future login(String phone, String password) async { + state = state.copyWith(isLoading: true, error: null); + try { + final response = await apiService.login(phone, password); + final user = User.fromJson(response['user']); + await storageService.saveUser(user); + state = state.copyWith(user: user, isLoading: false); + } catch (e) { + state = state.copyWith(isLoading: false, error: e.toString()); + } + } + + Future logout() async { + await storageService.clearUser(); + state = const AuthState(); + } +} + +final authProvider = StateNotifierProvider((ref) { + return AuthNotifier(); +}); diff --git a/lib/providers/chat_provider.dart b/lib/providers/chat_provider.dart new file mode 100644 index 0000000..b63c27e --- /dev/null +++ b/lib/providers/chat_provider.dart @@ -0,0 +1,89 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import '../models/message.dart'; +import '../services/api_service.dart'; + +class ChatState { + final List messages; + final bool isLoading; + final String? error; + + const ChatState({this.messages = const [], this.isLoading = false, this.error}); + + ChatState copyWith({List? messages, bool? isLoading, String? error}) { + return ChatState( + messages: messages ?? this.messages, + isLoading: isLoading ?? this.isLoading, + error: error, + ); + } +} + +class ChatNotifier extends StateNotifier { + ChatNotifier() : super(const ChatState()) { + _loadHistory(); + } + + Future _loadHistory() async { + try { + final messages = await apiService.getChatHistory(); + state = state.copyWith(messages: messages); + } catch (_) {} + } + + Future sendMessage(String content) async { + if (content.trim().isEmpty) return; + + final userMessage = ChatMessage( + id: 'msg_user_${DateTime.now().millisecondsSinceEpoch}', + content: content.trim(), + type: MessageType.text, + sender: MessageSender.user, + timestamp: DateTime.now(), + ); + + state = state.copyWith( + messages: [...state.messages, userMessage], + isLoading: true, + ); + + final loadingMessage = ChatMessage( + id: 'msg_loading_${DateTime.now().millisecondsSinceEpoch}', + content: '', + type: MessageType.loading, + sender: MessageSender.ai, + timestamp: DateTime.now(), + ); + + state = state.copyWith( + messages: [...state.messages, loadingMessage], + ); + + try { + final response = await apiService.sendMessage(content); + final updatedMessages = [...state.messages]; + updatedMessages.removeLast(); + updatedMessages.add(response); + state = state.copyWith(messages: updatedMessages, isLoading: false); + } catch (e) { + final updatedMessages = [...state.messages]; + updatedMessages.removeLast(); + updatedMessages.add(ChatMessage( + id: 'msg_err_${DateTime.now().millisecondsSinceEpoch}', + content: '抱歉,消息发送失败,请稍后重试。', + type: MessageType.text, + sender: MessageSender.ai, + timestamp: DateTime.now(), + )); + state = state.copyWith(messages: updatedMessages, isLoading: false, error: e.toString()); + } + } + + void clearMessages() { + state = const ChatState(); + _loadHistory(); + } +} + +final chatProvider = StateNotifierProvider((ref) { + return ChatNotifier(); +}); diff --git a/lib/providers/event_provider.dart b/lib/providers/event_provider.dart new file mode 100644 index 0000000..69a4d21 --- /dev/null +++ b/lib/providers/event_provider.dart @@ -0,0 +1,67 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import '../models/event.dart'; +import '../services/api_service.dart'; + +class EventState { + final List events; + final bool isLoading; + final String? error; + final bool publishSuccess; + + const EventState({ + this.events = const [], + this.isLoading = false, + this.error, + this.publishSuccess = false, + }); + + EventState copyWith({ + List? events, + bool? isLoading, + String? error, + bool? publishSuccess, + }) { + return EventState( + events: events ?? this.events, + isLoading: isLoading ?? this.isLoading, + error: error, + publishSuccess: publishSuccess ?? this.publishSuccess, + ); + } +} + +class EventNotifier extends StateNotifier { + EventNotifier() : super(const EventState()); + + Future loadEvents() async { + state = state.copyWith(isLoading: true, error: null); + try { + final events = await apiService.getEvents(); + state = state.copyWith(events: events, isLoading: false); + } catch (e) { + state = state.copyWith(isLoading: false, error: e.toString()); + } + } + + Future publishEvent(Map data) async { + state = state.copyWith(isLoading: true, error: null, publishSuccess: false); + try { + final event = await apiService.publishEvent(data); + state = state.copyWith( + events: [event, ...state.events], + isLoading: false, + publishSuccess: true, + ); + } catch (e) { + state = state.copyWith(isLoading: false, error: e.toString(), publishSuccess: false); + } + } + + void clearPublishSuccess() { + state = state.copyWith(publishSuccess: false); + } +} + +final eventProvider = StateNotifierProvider((ref) { + return EventNotifier(); +}); diff --git a/lib/providers/order_provider.dart b/lib/providers/order_provider.dart new file mode 100644 index 0000000..2b7d755 --- /dev/null +++ b/lib/providers/order_provider.dart @@ -0,0 +1,91 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import '../models/order.dart'; +import '../services/api_service.dart'; + +class OrderState { + final List orders; + final OrderItem? currentOrder; + final bool isLoading; + final String? error; + final bool verifySuccess; + + const OrderState({ + this.orders = const [], + this.currentOrder, + this.isLoading = false, + this.error, + this.verifySuccess = false, + }); + + OrderState copyWith({ + List? orders, + OrderItem? currentOrder, + bool? isLoading, + String? error, + bool? verifySuccess, + }) { + return OrderState( + orders: orders ?? this.orders, + currentOrder: currentOrder ?? this.currentOrder, + isLoading: isLoading ?? this.isLoading, + error: error, + verifySuccess: verifySuccess ?? this.verifySuccess, + ); + } +} + +class OrderNotifier extends StateNotifier { + OrderNotifier() : super(const OrderState()); + + Future loadOrders(OrderStatus status) async { + state = state.copyWith(isLoading: true, error: null); + try { + final orders = await apiService.getOrders(status); + state = state.copyWith(orders: orders, isLoading: false); + } catch (e) { + state = state.copyWith(isLoading: false, error: e.toString()); + } + } + + Future getDetail(String id) async { + state = state.copyWith(isLoading: true, error: null); + try { + final order = await apiService.getOrderDetail(id); + state = state.copyWith(currentOrder: order, isLoading: false); + } catch (e) { + state = state.copyWith(isLoading: false, error: e.toString()); + } + } + + Future verifyOrder(String id) async { + state = state.copyWith(isLoading: true, error: null, verifySuccess: false); + try { + final order = await apiService.verifyOrder(id); + state = state.copyWith( + currentOrder: order, + isLoading: false, + verifySuccess: true, + ); + } catch (e) { + state = state.copyWith(isLoading: false, error: e.toString()); + } + } + + Future scanVerify(String code) async { + state = state.copyWith(isLoading: true, error: null); + try { + final order = await apiService.scanVerify(code); + state = state.copyWith(currentOrder: order, isLoading: false); + } catch (e) { + state = state.copyWith(isLoading: false, error: e.toString()); + } + } + + void clearVerifySuccess() { + state = state.copyWith(verifySuccess: false); + } +} + +final orderProvider = StateNotifierProvider((ref) { + return OrderNotifier(); +}); diff --git a/lib/providers/settings_provider.dart b/lib/providers/settings_provider.dart new file mode 100644 index 0000000..8fafa72 --- /dev/null +++ b/lib/providers/settings_provider.dart @@ -0,0 +1,93 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import '../services/storage_service.dart'; + +enum AppThemeMode { light, dark, system } + +class SettingsState { + final bool pushNotification; + final bool soundNotification; + final AppThemeMode themeMode; + final Locale locale; + + const SettingsState({ + this.pushNotification = true, + this.soundNotification = true, + this.themeMode = AppThemeMode.light, + this.locale = const Locale('zh', 'CN'), + }); + + SettingsState copyWith({ + bool? pushNotification, + bool? soundNotification, + AppThemeMode? themeMode, + Locale? locale, + }) { + return SettingsState( + pushNotification: pushNotification ?? this.pushNotification, + soundNotification: soundNotification ?? this.soundNotification, + themeMode: themeMode ?? this.themeMode, + locale: locale ?? this.locale, + ); + } + + ThemeMode get flutterThemeMode { + switch (themeMode) { + case AppThemeMode.light: + return ThemeMode.light; + case AppThemeMode.dark: + return ThemeMode.dark; + case AppThemeMode.system: + return ThemeMode.system; + } + } +} + +class SettingsNotifier extends StateNotifier { + SettingsNotifier() : super(const SettingsState()) { + _loadSettings(); + } + + Future _loadSettings() async { + await storageService.init(); + final push = storageService.getBool('push_notification'); + final sound = storageService.getBool('sound_notification'); + final theme = storageService.getString('theme_mode'); + final lang = storageService.getString('locale'); + + state = state.copyWith( + pushNotification: push ?? true, + soundNotification: sound ?? true, + themeMode: theme != null + ? AppThemeMode.values.byName(theme) + : AppThemeMode.light, + locale: lang != null + ? Locale(lang.split('_')[0], lang.split('_')[1]) + : const Locale('zh', 'CN'), + ); + } + + Future setPushNotification(bool value) async { + await storageService.setBool('push_notification', value); + state = state.copyWith(pushNotification: value); + } + + Future setSoundNotification(bool value) async { + await storageService.setBool('sound_notification', value); + state = state.copyWith(soundNotification: value); + } + + Future setThemeMode(AppThemeMode mode) async { + await storageService.setString('theme_mode', mode.name); + state = state.copyWith(themeMode: mode); + } + + Future setLocale(Locale locale) async { + await storageService.setString('locale', '${locale.languageCode}_${locale.countryCode}'); + state = state.copyWith(locale: locale); + } +} + +final settingsProvider = StateNotifierProvider((ref) { + return SettingsNotifier(); +}); diff --git a/lib/providers/work_order_provider.dart b/lib/providers/work_order_provider.dart new file mode 100644 index 0000000..08bf2d8 --- /dev/null +++ b/lib/providers/work_order_provider.dart @@ -0,0 +1,45 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import '../models/work_order.dart'; +import '../services/api_service.dart'; + +class WorkOrderState { + final List orders; + final bool isLoading; + final String? error; + + const WorkOrderState({this.orders = const [], this.isLoading = false, this.error}); + + WorkOrderState copyWith({List? orders, bool? isLoading, String? error}) { + return WorkOrderState( + orders: orders ?? this.orders, + isLoading: isLoading ?? this.isLoading, + error: error, + ); + } +} + +class WorkOrderNotifier extends StateNotifier { + WorkOrderNotifier() : super(const WorkOrderState()); + + Future loadOrders(WorkOrderStatus status) async { + state = state.copyWith(isLoading: true, error: null); + try { + final orders = await apiService.getWorkOrders(status); + state = state.copyWith(orders: orders, isLoading: false); + } catch (e) { + state = state.copyWith(isLoading: false, error: e.toString()); + } + } + + Future getDetail(String id) async { + try { + return await apiService.getWorkOrderDetail(id); + } catch (_) { + return null; + } + } +} + +final workOrderProvider = StateNotifierProvider((ref) { + return WorkOrderNotifier(); +}); diff --git a/lib/router.dart b/lib/router.dart new file mode 100644 index 0000000..570a979 --- /dev/null +++ b/lib/router.dart @@ -0,0 +1,121 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; +import 'providers/auth_provider.dart'; +import 'pages/login_page.dart'; +import 'pages/home_page.dart'; +import 'pages/event_publish_page.dart'; +import 'pages/event_list_page.dart'; +import 'pages/order_work_page.dart'; +import 'pages/work_order_detail_page.dart'; +import 'pages/order_detail_page.dart'; +import 'pages/scan_result_page.dart'; +import 'pages/settings_page.dart'; +import 'pages/employee_page.dart'; +import 'pages/store_page.dart'; +import 'pages/report_page.dart'; +import 'pages/app_market_page.dart'; +import 'pages/help_page.dart'; +import 'pages/about_page.dart'; +import 'pages/policy_page.dart'; + +final routerProvider = Provider((ref) { + final authState = ref.watch(authProvider); + + return GoRouter( + initialLocation: '/login', + redirect: (context, state) { + final isLoggedIn = authState.isLoggedIn; + final isLoginRoute = state.matchedLocation == '/login'; + + if (!isLoggedIn && !isLoginRoute) { + return '/login'; + } + if (isLoggedIn && isLoginRoute) { + return '/home'; + } + return null; + }, + routes: [ + GoRoute( + path: '/login', + builder: (context, state) => const LoginPage(), + ), + GoRoute( + path: '/home', + builder: (context, state) => const HomePage(), + ), + GoRoute( + path: '/event/publish', + builder: (context, state) => const EventPublishPage(), + ), + GoRoute( + path: '/event/list', + builder: (context, state) => const EventListPage(), + ), + GoRoute( + path: '/order-work', + builder: (context, state) { + final tab = state.uri.queryParameters['tab'] ?? 'work'; + return OrderWorkPage(initialTab: tab); + }, + ), + GoRoute( + path: '/work-order/:id', + builder: (context, state) { + final id = state.pathParameters['id']!; + return WorkOrderDetailPage(orderId: id); + }, + ), + GoRoute( + path: '/order/:id', + builder: (context, state) { + final id = state.pathParameters['id']!; + return OrderDetailPage(orderId: id); + }, + ), + GoRoute( + path: '/scan-result', + builder: (context, state) { + final code = state.uri.queryParameters['code'] ?? ''; + return ScanResultPage(scanCode: code); + }, + ), + GoRoute( + path: '/settings', + builder: (context, state) => const SettingsPage(), + ), + GoRoute( + path: '/settings/employees', + builder: (context, state) => const EmployeePage(), + ), + GoRoute( + path: '/settings/store', + builder: (context, state) => const StorePage(), + ), + GoRoute( + path: '/settings/report', + builder: (context, state) => const ReportPage(), + ), + GoRoute( + path: '/settings/app-market', + builder: (context, state) => const AppMarketPage(), + ), + GoRoute( + path: '/settings/help', + builder: (context, state) => const HelpPage(), + ), + GoRoute( + path: '/settings/about', + builder: (context, state) => const AboutPage(), + ), + GoRoute( + path: '/settings/policy', + builder: (context, state) { + final type = state.uri.queryParameters['type'] ?? 'agreement'; + return PolicyPage(type: type); + }, + ), + ], + ); +}); diff --git a/lib/services/api_service.dart b/lib/services/api_service.dart new file mode 100644 index 0000000..a6add35 --- /dev/null +++ b/lib/services/api_service.dart @@ -0,0 +1,313 @@ +import 'dart:math'; +import '../models/user.dart'; +import '../models/message.dart'; +import '../models/event.dart'; +import '../models/work_order.dart'; +import '../models/order.dart'; + +class ApiService { + static final ApiService _instance = ApiService._internal(); + factory ApiService() => _instance; + ApiService._internal(); + + final _random = Random(); + + Future> login(String phone, String password) async { + await Future.delayed(const Duration(milliseconds: 800)); + + if (phone.length != 11) { + throw Exception('手机号格式不正确'); + } + if (password.length < 6) { + throw Exception('密码不能少于6位'); + } + + final isBoss = phone.endsWith('88'); + return { + 'user': { + 'id': 'user_${_random.nextInt(99999)}', + 'phone': phone, + 'name': isBoss ? '张老板' : '李员工', + 'avatar': 'https://api.dicebear.com/7.x/avataaars/svg?seed=$phone', + 'role': isBoss ? 'boss' : 'employee', + 'token': 'token_${DateTime.now().millisecondsSinceEpoch}', + 'hotelName': '智念度假酒店', + 'scenicName': '智念风景区', + }, + 'token': 'mock_token_${DateTime.now().millisecondsSinceEpoch}', + }; + } + + Future> getChatHistory() async { + await Future.delayed(const Duration(milliseconds: 500)); + return [ + ChatMessage( + id: 'msg_1', + content: '您好,我是智念AI助手,有什么可以帮您的吗?', + type: MessageType.markdown, + sender: MessageSender.ai, + timestamp: DateTime.now().subtract(const Duration(minutes: 5)), + ), + ]; + } + + Future sendMessage(String content) async { + await Future.delayed(const Duration(milliseconds: 1200)); + + final responses = [ + '收到您的消息,我已记录并会尽快处理。\n\n**当前状态**:已受理\n**预计处理时间**:2小时内', + '好的,我来帮您查询一下。\n\n| 项目 | 状态 | 时间 |\n|------|------|------|\n| 订单核销 | 已完成 | 10:30 |\n| 工单处理 | 进行中 | 11:00 |', + '感谢您的反馈!根据系统记录,今日共有 **15笔订单** 待处理,**3个工单** 需要跟进。', + '已为您生成今日数据报表:\n\n- 总订单数:128笔\n- 核销完成:96笔\n- 待处理:32笔\n- 退款申请:2笔', + '事件已发布成功!\n\n> 事件将在设定的时间自动生效,系统会提前30分钟发送提醒。', + ]; + + return ChatMessage( + id: 'msg_${DateTime.now().millisecondsSinceEpoch}', + content: responses[_random.nextInt(responses.length)], + type: MessageType.markdown, + sender: MessageSender.ai, + timestamp: DateTime.now(), + ); + } + + Future> getEvents() async { + await Future.delayed(const Duration(milliseconds: 600)); + return List.generate(8, (index) { + final now = DateTime.now(); + return EventItem( + id: 'evt_$index', + entityName: ['大堂吧', '游泳池', '健身房', '餐厅', 'SPA中心', '会议室', '停车场', '花园'][index], + description: '这是关于${_getEntityName(index)}的维护通知,请相关人员注意安排时间。', + images: [], + publishTime: now.subtract(Duration(days: index)), + effectiveTime: now.add(Duration(hours: index * 2)), + endTime: now.add(Duration(hours: index * 2 + 4)), + popupReminder: index % 2 == 0, + status: ['published', 'draft', 'published', 'expired'][index % 4], + creatorName: index % 2 == 0 ? '张老板' : '李员工', + createdAt: now.subtract(Duration(days: index)), + ); + }); + } + + String _getEntityName(int index) { + return ['大堂吧', '游泳池', '健身房', '餐厅', 'SPA中心', '会议室', '停车场', '花园'][index]; + } + + Future publishEvent(Map data) async { + await Future.delayed(const Duration(milliseconds: 800)); + return EventItem( + id: 'evt_${DateTime.now().millisecondsSinceEpoch}', + entityName: data['entityName'] ?? '', + description: data['description'] ?? '', + images: List.from(data['images'] ?? []), + publishTime: DateTime.now(), + effectiveTime: data['effectiveTime'] != null ? DateTime.parse(data['effectiveTime']) : null, + endTime: data['endTime'] != null ? DateTime.parse(data['endTime']) : null, + popupReminder: data['popupReminder'] ?? false, + status: 'published', + creatorName: '当前用户', + createdAt: DateTime.now(), + ); + } + + Future> getWorkOrders(WorkOrderStatus status) async { + await Future.delayed(const Duration(milliseconds: 600)); + final allOrders = [ + WorkOrder( + id: 'wo_1', + title: '客房空调维修', + description: '308房间空调制冷效果不佳,需要安排维修人员检查。', + status: WorkOrderStatus.pending, + creatorName: '前台小王', + assigneeName: '维修老张', + createdAt: DateTime.now().subtract(const Duration(hours: 2)), + priority: 'high', + category: '设备维修', + images: [], + location: '3楼308房间', + ), + WorkOrder( + id: 'wo_2', + title: '花园草坪修剪', + description: '后花园草坪需要定期修剪,预计耗时2小时。', + status: WorkOrderStatus.processing, + creatorName: '张老板', + assigneeName: '园丁老李', + createdAt: DateTime.now().subtract(const Duration(hours: 5)), + priority: 'normal', + category: '环境维护', + images: [], + location: '后花园', + ), + WorkOrder( + id: 'wo_3', + title: '餐厅设备清洁', + description: '厨房排风系统需要深度清洁。', + status: WorkOrderStatus.completed, + creatorName: '厨师长', + assigneeName: '清洁团队', + createdAt: DateTime.now().subtract(const Duration(days: 1)), + completedAt: DateTime.now().subtract(const Duration(hours: 3)), + priority: 'normal', + category: '清洁', + images: [], + location: '主厨房', + ), + WorkOrder( + id: 'wo_4', + title: '游泳池水质检测', + description: '每日例行水质检测,PH值和氯含量需要记录。', + status: WorkOrderStatus.pending, + creatorName: '救生员小张', + createdAt: DateTime.now().subtract(const Duration(minutes: 30)), + priority: 'high', + category: '安全检查', + images: [], + location: '游泳池', + ), + WorkOrder( + id: 'wo_5', + title: '会议室投影仪调试', + description: '下午有重要会议,需要提前调试投影设备。', + status: WorkOrderStatus.processing, + creatorName: '行政小刘', + assigneeName: 'IT小王', + createdAt: DateTime.now().subtract(const Duration(hours: 1)), + priority: 'urgent', + category: '设备调试', + images: [], + location: 'A座会议室', + ), + ]; + + if (status == WorkOrderStatus.all) return allOrders; + return allOrders.where((o) => o.status == status).toList(); + } + + Future getWorkOrderDetail(String id) async { + await Future.delayed(const Duration(milliseconds: 400)); + final orders = await getWorkOrders(WorkOrderStatus.all); + return orders.firstWhere((o) => o.id == id, orElse: () => orders.first); + } + + Future> getOrders(OrderStatus status) async { + await Future.delayed(const Duration(milliseconds: 600)); + final allOrders = [ + OrderItem( + id: 'ord_1', + orderNo: 'ZN20240507001', + customerName: '王先生', + customerPhone: '138****8888', + productName: '智念风景区成人票', + amount: 128.00, + status: OrderStatus.pendingVerification, + createdAt: DateTime.now().subtract(const Duration(hours: 2)), + paidAt: DateTime.now().subtract(const Duration(hours: 2)), + verifyCode: 'VK8823', + quantity: 2, + scenicSpot: '智念风景区', + ), + OrderItem( + id: 'ord_2', + orderNo: 'ZN20240507002', + customerName: '李女士', + customerPhone: '139****6666', + productName: '度假酒店豪华套房', + amount: 888.00, + status: OrderStatus.pendingPayment, + createdAt: DateTime.now().subtract(const Duration(minutes: 30)), + quantity: 1, + scenicSpot: '智念度假酒店', + ), + OrderItem( + id: 'ord_3', + orderNo: 'ZN20240507003', + customerName: '张 family', + customerPhone: '137****5555', + productName: '家庭亲子套票', + amount: 388.00, + status: OrderStatus.verified, + createdAt: DateTime.now().subtract(const Duration(days: 1)), + paidAt: DateTime.now().subtract(const Duration(days: 1)), + verifiedAt: DateTime.now().subtract(const Duration(hours: 5)), + verifyCode: 'VK9912', + quantity: 3, + scenicSpot: '智念风景区', + ), + OrderItem( + id: 'ord_4', + orderNo: 'ZN20240507004', + customerName: '陈先生', + customerPhone: '136****4444', + productName: 'SPA养生套餐', + amount: 598.00, + status: OrderStatus.pendingRefund, + createdAt: DateTime.now().subtract(const Duration(days: 2)), + paidAt: DateTime.now().subtract(const Duration(days: 2)), + verifyCode: 'VK7734', + quantity: 1, + remark: '客户行程变更申请退款', + scenicSpot: '智念度假酒店', + ), + OrderItem( + id: 'ord_5', + orderNo: 'ZN20240507005', + customerName: '赵女士', + customerPhone: '135****3333', + productName: '景区门票+酒店套餐', + amount: 1588.00, + status: OrderStatus.refunded, + createdAt: DateTime.now().subtract(const Duration(days: 3)), + paidAt: DateTime.now().subtract(const Duration(days: 3)), + refundedAt: DateTime.now().subtract(const Duration(days: 1)), + verifyCode: 'VK6651', + quantity: 2, + scenicSpot: '智念度假区', + ), + ]; + + if (status == OrderStatus.all) return allOrders; + return allOrders.where((o) => o.status == status).toList(); + } + + Future getOrderDetail(String id) async { + await Future.delayed(const Duration(milliseconds: 400)); + final orders = await getOrders(OrderStatus.all); + return orders.firstWhere((o) => o.id == id, orElse: () => orders.first); + } + + Future verifyOrder(String id) async { + await Future.delayed(const Duration(milliseconds: 600)); + final order = await getOrderDetail(id); + return order.copyWith( + status: OrderStatus.verified, + verifiedAt: DateTime.now(), + ); + } + + Future scanVerify(String code) async { + await Future.delayed(const Duration(milliseconds: 800)); + final orders = await getOrders(OrderStatus.all); + final order = orders.firstWhere( + (o) => o.verifyCode == code || o.orderNo == code, + orElse: () => OrderItem( + id: 'ord_scan_${DateTime.now().millisecondsSinceEpoch}', + orderNo: code, + customerName: '扫码客户', + customerPhone: '138****0000', + productName: '景区门票', + amount: 128.00, + status: OrderStatus.pendingVerification, + createdAt: DateTime.now(), + verifyCode: code, + quantity: 1, + scenicSpot: '智念风景区', + ), + ); + return order; + } +} + +final apiService = ApiService(); diff --git a/lib/services/storage_service.dart b/lib/services/storage_service.dart new file mode 100644 index 0000000..f018b14 --- /dev/null +++ b/lib/services/storage_service.dart @@ -0,0 +1,58 @@ +import 'dart:convert'; +import 'package:hive/hive.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import '../models/user.dart'; + +class StorageService { + static final StorageService _instance = StorageService._internal(); + factory StorageService() => _instance; + StorageService._internal(); + + Box? _box; + SharedPreferences? _prefs; + + Future init() async { + _box = Hive.box('zhinianBox'); + _prefs = await SharedPreferences.getInstance(); + } + + Future saveUser(User user) async { + await _box?.put('user', jsonEncode(user.toJson())); + } + + User? getUser() { + final data = _box?.get('user'); + if (data == null) return null; + try { + return User.fromJson(jsonDecode(data)); + } catch (_) { + return null; + } + } + + Future clearUser() async { + await _box?.delete('user'); + } + + Future setString(String key, String value) async { + await _prefs?.setString(key, value); + } + + String? getString(String key) { + return _prefs?.getString(key); + } + + Future setBool(String key, bool value) async { + await _prefs?.setBool(key, value); + } + + bool? getBool(String key) { + return _prefs?.getBool(key); + } + + Future remove(String key) async { + await _prefs?.remove(key); + } +} + +final storageService = StorageService(); diff --git a/lib/theme.dart b/lib/theme.dart new file mode 100644 index 0000000..e2fb9b6 --- /dev/null +++ b/lib/theme.dart @@ -0,0 +1,244 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:flutter_animate/flutter_animate.dart'; + +class AppColors { + 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); +} + +class AppTheme { + static ThemeData get lightTheme { + 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, + ), + scaffoldBackgroundColor: AppColors.background, + textTheme: GoogleFonts.notoSansScTextTheme().copyWith( + displayLarge: GoogleFonts.notoSansSc( + fontSize: 32, fontWeight: FontWeight.bold, color: AppColors.textPrimary, + ), + displayMedium: GoogleFonts.notoSansSc( + fontSize: 24, fontWeight: FontWeight.bold, color: AppColors.textPrimary, + ), + titleLarge: GoogleFonts.notoSansSc( + fontSize: 20, fontWeight: FontWeight.w600, color: AppColors.textPrimary, + ), + titleMedium: GoogleFonts.notoSansSc( + fontSize: 16, fontWeight: FontWeight.w600, color: AppColors.textPrimary, + ), + bodyLarge: GoogleFonts.notoSansSc( + fontSize: 16, color: AppColors.textPrimary, + ), + bodyMedium: GoogleFonts.notoSansSc( + fontSize: 14, color: AppColors.textSecondary, + ), + labelLarge: GoogleFonts.notoSansSc( + fontSize: 14, fontWeight: FontWeight.w500, + ), + ), + appBarTheme: AppBarTheme( + elevation: 0, + centerTitle: true, + backgroundColor: AppColors.surface, + foregroundColor: AppColors.textPrimary, + systemOverlayStyle: SystemUiOverlayStyle.dark, + titleTextStyle: GoogleFonts.notoSansSc( + fontSize: 18, fontWeight: FontWeight.w600, color: AppColors.textPrimary, + ), + ), + cardTheme: CardThemeData( + elevation: 0, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + color: AppColors.surface, + ), + inputDecorationTheme: InputDecorationTheme( + filled: true, + fillColor: AppColors.background, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide.none, + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide.none, + ), + 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), + hintStyle: GoogleFonts.notoSansSc( + fontSize: 14, color: AppColors.textTertiary, + ), + ), + elevatedButtonTheme: ElevatedButtonThemeData( + style: ElevatedButton.styleFrom( + elevation: 0, + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 14), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + backgroundColor: AppColors.primary, + foregroundColor: Colors.white, + textStyle: GoogleFonts.notoSansSc( + fontSize: 16, fontWeight: FontWeight.w600, + ), + ), + ), + outlinedButtonTheme: OutlinedButtonThemeData( + style: OutlinedButton.styleFrom( + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 14), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + side: const BorderSide(color: AppColors.primary), + foregroundColor: AppColors.primary, + textStyle: GoogleFonts.notoSansSc( + fontSize: 14, fontWeight: FontWeight.w500, + ), + ), + ), + textButtonTheme: TextButtonThemeData( + style: TextButton.styleFrom( + foregroundColor: AppColors.primary, + textStyle: GoogleFonts.notoSansSc( + fontSize: 14, fontWeight: FontWeight.w500, + ), + ), + ), + floatingActionButtonTheme: const FloatingActionButtonThemeData( + backgroundColor: AppColors.primary, + foregroundColor: Colors.white, + elevation: 4, + ), + bottomNavigationBarTheme: const BottomNavigationBarThemeData( + backgroundColor: AppColors.surface, + selectedItemColor: AppColors.primary, + unselectedItemColor: AppColors.textTertiary, + type: BottomNavigationBarType.fixed, + elevation: 8, + ), + chipTheme: ChipThemeData( + backgroundColor: AppColors.background, + selectedColor: AppColors.primary.withOpacity(0.1), + labelStyle: GoogleFonts.notoSansSc(fontSize: 12), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + dividerTheme: const DividerThemeData( + color: AppColors.divider, + thickness: 1, + space: 1, + ), + tabBarTheme: TabBarThemeData( + labelColor: AppColors.primary, + unselectedLabelColor: AppColors.textSecondary, + labelStyle: GoogleFonts.notoSansSc( + fontSize: 14, fontWeight: FontWeight.w600, + ), + unselectedLabelStyle: GoogleFonts.notoSansSc( + fontSize: 14, fontWeight: FontWeight.w400, + ), + indicatorSize: TabBarIndicatorSize.tab, + ), + ); + } + + 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, + ); + + static const card = LinearGradient( + colors: [Color(0xFF1A56DB), Color(0xFF2563EB)], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ); + + static const shimmer = LinearGradient( + colors: [Color(0xFFE2E8F0), Color(0xFFF1F5F9), Color(0xFFE2E8F0)], + stops: [0.0, 0.5, 1.0], + begin: Alignment(-1.0, -0.3), + end: Alignment(1.0, 0.3), + ); +} + +class AppAnimations { + static const defaultDuration = Duration(milliseconds: 300); + static const slowDuration = Duration(milliseconds: 500); + static const fastDuration = Duration(milliseconds: 150); + + static Widget fadeSlide(Widget child, {int delay = 0}) { + return child + .animate() + .fadeIn(duration: defaultDuration, delay: Duration(milliseconds: delay)) + .slideY(begin: 0.2, end: 0, duration: defaultDuration, delay: Duration(milliseconds: delay)); + } + + static Widget scaleIn(Widget child, {int delay = 0}) { + return child + .animate() + .scale(begin: const Offset(0.9, 0.9), duration: defaultDuration, delay: Duration(milliseconds: delay)) + .fadeIn(duration: defaultDuration, delay: Duration(milliseconds: delay)); + } +} diff --git a/macos/.gitignore b/macos/.gitignore new file mode 100644 index 0000000..746adbb --- /dev/null +++ b/macos/.gitignore @@ -0,0 +1,7 @@ +# Flutter-related +**/Flutter/ephemeral/ +**/Pods/ + +# Xcode-related +**/dgph +**/xcuserdata/ diff --git a/macos/Flutter/Flutter-Debug.xcconfig b/macos/Flutter/Flutter-Debug.xcconfig new file mode 100644 index 0000000..4b81f9b --- /dev/null +++ b/macos/Flutter/Flutter-Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/macos/Flutter/Flutter-Release.xcconfig b/macos/Flutter/Flutter-Release.xcconfig new file mode 100644 index 0000000..5caa9d1 --- /dev/null +++ b/macos/Flutter/Flutter-Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift new file mode 100644 index 0000000..250040f --- /dev/null +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -0,0 +1,20 @@ +// +// Generated file. Do not edit. +// + +import FlutterMacOS +import Foundation + +import file_selector_macos +import mobile_scanner +import shared_preferences_foundation +import sqflite_darwin +import url_launcher_macos + +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")) + SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) + UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) +} diff --git a/macos/Podfile b/macos/Podfile new file mode 100644 index 0000000..ff5ddb3 --- /dev/null +++ b/macos/Podfile @@ -0,0 +1,42 @@ +platform :osx, '10.15' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_macos_podfile_setup + +target 'Runner' do + use_frameworks! + + flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_macos_build_settings(target) + end +end diff --git a/macos/Runner.xcodeproj/project.pbxproj b/macos/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..e6dbb0c --- /dev/null +++ b/macos/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,705 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXAggregateTarget section */ + 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; + buildPhases = ( + 33CC111E2044C6BF0003C045 /* ShellScript */, + ); + dependencies = ( + ); + name = "Flutter Assemble"; + productName = FLX; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC10EC2044A3C60003C045; + remoteInfo = Runner; + }; + 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC111A2044C6BA0003C045; + remoteInfo = FLX; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 33CC110E2044A8840003C045 /* Bundle Framework */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Bundle Framework"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; + 33CC10ED2044A3C60003C045 /* zhinian_manage.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "zhinian_manage.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; + 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; + 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; + 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; + 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 331C80D2294CF70F00263BE5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EA2044A3C60003C045 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C80D6294CF71000263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C80D7294CF71000263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 33BA886A226E78AF003329D5 /* Configs */ = { + isa = PBXGroup; + children = ( + 33E5194F232828860026EE4D /* AppInfo.xcconfig */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, + ); + path = Configs; + sourceTree = ""; + }; + 33CC10E42044A3C60003C045 = { + isa = PBXGroup; + children = ( + 33FAB671232836740065AC1E /* Runner */, + 33CEB47122A05771004F2AC0 /* Flutter */, + 331C80D6294CF71000263BE5 /* RunnerTests */, + 33CC10EE2044A3C60003C045 /* Products */, + D73912EC22F37F3D000D13A0 /* Frameworks */, + ); + sourceTree = ""; + }; + 33CC10EE2044A3C60003C045 /* Products */ = { + isa = PBXGroup; + children = ( + 33CC10ED2044A3C60003C045 /* zhinian_manage.app */, + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 33CC11242044D66E0003C045 /* Resources */ = { + isa = PBXGroup; + children = ( + 33CC10F22044A3C60003C045 /* Assets.xcassets */, + 33CC10F42044A3C60003C045 /* MainMenu.xib */, + 33CC10F72044A3C60003C045 /* Info.plist */, + ); + name = Resources; + path = ..; + sourceTree = ""; + }; + 33CEB47122A05771004F2AC0 /* Flutter */ = { + isa = PBXGroup; + children = ( + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, + ); + path = Flutter; + sourceTree = ""; + }; + 33FAB671232836740065AC1E /* Runner */ = { + isa = PBXGroup; + children = ( + 33CC10F02044A3C60003C045 /* AppDelegate.swift */, + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, + 33E51913231747F40026EE4D /* DebugProfile.entitlements */, + 33E51914231749380026EE4D /* Release.entitlements */, + 33CC11242044D66E0003C045 /* Resources */, + 33BA886A226E78AF003329D5 /* Configs */, + ); + path = Runner; + sourceTree = ""; + }; + D73912EC22F37F3D000D13A0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C80D4294CF70F00263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C80D1294CF70F00263BE5 /* Sources */, + 331C80D2294CF70F00263BE5 /* Frameworks */, + 331C80D3294CF70F00263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C80DA294CF71000263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 33CC10EC2044A3C60003C045 /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 33CC10E92044A3C60003C045 /* Sources */, + 33CC10EA2044A3C60003C045 /* Frameworks */, + 33CC10EB2044A3C60003C045 /* Resources */, + 33CC110E2044A8840003C045 /* Bundle Framework */, + 3399D490228B24CF009A79C7 /* ShellScript */, + ); + buildRules = ( + ); + dependencies = ( + 33CC11202044C79F0003C045 /* PBXTargetDependency */, + ); + name = Runner; + productName = Runner; + productReference = 33CC10ED2044A3C60003C045 /* zhinian_manage.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 33CC10E52044A3C60003C045 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastSwiftUpdateCheck = 0920; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C80D4294CF70F00263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 33CC10EC2044A3C60003C045; + }; + 33CC10EC2044A3C60003C045 = { + CreatedOnToolsVersion = 9.2; + LastSwiftMigration = 1100; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.Sandbox = { + enabled = 1; + }; + }; + }; + 33CC111A2044C6BA0003C045 = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Manual; + }; + }; + }; + buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 33CC10E42044A3C60003C045; + productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 33CC10EC2044A3C60003C045 /* Runner */, + 331C80D4294CF70F00263BE5 /* RunnerTests */, + 33CC111A2044C6BA0003C045 /* Flutter Assemble */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C80D3294CF70F00263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EB2044A3C60003C045 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3399D490228B24CF009A79C7 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; + }; + 33CC111E2044C6BF0003C045 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + Flutter/ephemeral/FlutterInputs.xcfilelist, + ); + inputPaths = ( + Flutter/ephemeral/tripwire, + ); + outputFileListPaths = ( + Flutter/ephemeral/FlutterOutputs.xcfilelist, + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C80D1294CF70F00263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10E92044A3C60003C045 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C80DA294CF71000263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC10EC2044A3C60003C045 /* Runner */; + targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */; + }; + 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; + targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 33CC10F52044A3C60003C045 /* Base */, + ); + name = MainMenu.xib; + path = Runner; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 331C80DB294CF71000263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + 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)/zhinian_manage.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/zhinian_manage"; + }; + name = Debug; + }; + 331C80DC294CF71000263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + 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)/zhinian_manage.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/zhinian_manage"; + }; + name = Release; + }; + 331C80DD294CF71000263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + 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)/zhinian_manage.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/zhinian_manage"; + }; + name = Profile; + }; + 338D0CE9231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + 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_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + 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_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Profile; + }; + 338D0CEA231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Profile; + }; + 338D0CEB231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Profile; + }; + 33CC10F92044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + 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_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + 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_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + 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_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 33CC10FA2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + 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_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + 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_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 33CC10FC2044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 33CC10FD2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 33CC111C2044C6BA0003C045 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 33CC111D2044C6BA0003C045 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C80DB294CF71000263BE5 /* Debug */, + 331C80DC294CF71000263BE5 /* Release */, + 331C80DD294CF71000263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10F92044A3C60003C045 /* Debug */, + 33CC10FA2044A3C60003C045 /* Release */, + 338D0CE9231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10FC2044A3C60003C045 /* Debug */, + 33CC10FD2044A3C60003C045 /* Release */, + 338D0CEA231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC111C2044C6BA0003C045 /* Debug */, + 33CC111D2044C6BA0003C045 /* Release */, + 338D0CEB231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 33CC10E52044A3C60003C045 /* Project object */; +} diff --git a/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..1525424 --- /dev/null +++ b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/macos/Runner.xcworkspace/contents.xcworkspacedata b/macos/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1d526a1 --- /dev/null +++ b/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/macos/Runner/AppDelegate.swift b/macos/Runner/AppDelegate.swift new file mode 100644 index 0000000..b3c1761 --- /dev/null +++ b/macos/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import Cocoa +import FlutterMacOS + +@main +class AppDelegate: FlutterAppDelegate { + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } + + override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { + return true + } +} diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..a2ec33f --- /dev/null +++ b/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_16.png", + "scale" : "1x" + }, + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "2x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "1x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_64.png", + "scale" : "2x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_128.png", + "scale" : "1x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "2x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "1x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "2x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "1x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_1024.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png new file mode 100644 index 0000000..82b6f9d Binary files /dev/null and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png new file mode 100644 index 0000000..13b35eb Binary files /dev/null and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png new file mode 100644 index 0000000..0a3f5fa Binary files /dev/null and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png new file mode 100644 index 0000000..bdb5722 Binary files /dev/null and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png new file mode 100644 index 0000000..f083318 Binary files /dev/null and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png new file mode 100644 index 0000000..326c0e7 Binary files /dev/null and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png new file mode 100644 index 0000000..2f1632c Binary files /dev/null and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/macos/Runner/Base.lproj/MainMenu.xib b/macos/Runner/Base.lproj/MainMenu.xib new file mode 100644 index 0000000..80e867a --- /dev/null +++ b/macos/Runner/Base.lproj/MainMenu.xib @@ -0,0 +1,343 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/macos/Runner/Configs/AppInfo.xcconfig b/macos/Runner/Configs/AppInfo.xcconfig new file mode 100644 index 0000000..29dd0cb --- /dev/null +++ b/macos/Runner/Configs/AppInfo.xcconfig @@ -0,0 +1,14 @@ +// Application-level settings for the Runner target. +// +// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the +// future. If not, the values below would default to using the project name when this becomes a +// 'flutter create' template. + +// The application's name. By default this is also the title of the Flutter window. +PRODUCT_NAME = zhinian_manage + +// The application's bundle identifier +PRODUCT_BUNDLE_IDENTIFIER = com.example.zhinianManage + +// The copyright displayed in application information +PRODUCT_COPYRIGHT = Copyright © 2026 com.example. All rights reserved. diff --git a/macos/Runner/Configs/Debug.xcconfig b/macos/Runner/Configs/Debug.xcconfig new file mode 100644 index 0000000..36b0fd9 --- /dev/null +++ b/macos/Runner/Configs/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Debug.xcconfig" +#include "Warnings.xcconfig" diff --git a/macos/Runner/Configs/Release.xcconfig b/macos/Runner/Configs/Release.xcconfig new file mode 100644 index 0000000..dff4f49 --- /dev/null +++ b/macos/Runner/Configs/Release.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Release.xcconfig" +#include "Warnings.xcconfig" diff --git a/macos/Runner/Configs/Warnings.xcconfig b/macos/Runner/Configs/Warnings.xcconfig new file mode 100644 index 0000000..42bcbf4 --- /dev/null +++ b/macos/Runner/Configs/Warnings.xcconfig @@ -0,0 +1,13 @@ +WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings +GCC_WARN_UNDECLARED_SELECTOR = YES +CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES +CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE +CLANG_WARN__DUPLICATE_METHOD_MATCH = YES +CLANG_WARN_PRAGMA_PACK = YES +CLANG_WARN_STRICT_PROTOTYPES = YES +CLANG_WARN_COMMA = YES +GCC_WARN_STRICT_SELECTOR_MATCH = YES +CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES +CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES +GCC_WARN_SHADOW = YES +CLANG_WARN_UNREACHABLE_CODE = YES diff --git a/macos/Runner/DebugProfile.entitlements b/macos/Runner/DebugProfile.entitlements new file mode 100644 index 0000000..dddb8a3 --- /dev/null +++ b/macos/Runner/DebugProfile.entitlements @@ -0,0 +1,12 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.network.server + + + diff --git a/macos/Runner/Info.plist b/macos/Runner/Info.plist new file mode 100644 index 0000000..4789daa --- /dev/null +++ b/macos/Runner/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + $(PRODUCT_COPYRIGHT) + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/macos/Runner/MainFlutterWindow.swift b/macos/Runner/MainFlutterWindow.swift new file mode 100644 index 0000000..3cc05eb --- /dev/null +++ b/macos/Runner/MainFlutterWindow.swift @@ -0,0 +1,15 @@ +import Cocoa +import FlutterMacOS + +class MainFlutterWindow: NSWindow { + override func awakeFromNib() { + let flutterViewController = FlutterViewController() + let windowFrame = self.frame + self.contentViewController = flutterViewController + self.setFrame(windowFrame, display: true) + + RegisterGeneratedPlugins(registry: flutterViewController) + + super.awakeFromNib() + } +} diff --git a/macos/Runner/Release.entitlements b/macos/Runner/Release.entitlements new file mode 100644 index 0000000..852fa1a --- /dev/null +++ b/macos/Runner/Release.entitlements @@ -0,0 +1,8 @@ + + + + + com.apple.security.app-sandbox + + + diff --git a/macos/RunnerTests/RunnerTests.swift b/macos/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..61f3bd1 --- /dev/null +++ b/macos/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Cocoa +import FlutterMacOS +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/pubspec.lock b/pubspec.lock new file mode 100644 index 0000000..f126f70 --- /dev/null +++ b/pubspec.lock @@ -0,0 +1,922 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + archive: + dependency: transitive + description: + name: archive + sha256: a96e8b390886ee8abb49b7bd3ac8df6f451c621619f52a26e815fdcf568959ff + url: "https://pub.dev" + source: hosted + version: "4.0.9" + args: + dependency: transitive + description: + name: args + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 + url: "https://pub.dev" + source: hosted + version: "2.7.0" + async: + dependency: transitive + description: + name: async + sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 + url: "https://pub.dev" + source: hosted + version: "2.13.1" + badges: + dependency: "direct main" + description: + name: badges + sha256: cf1c88fb3777df69ccd630b80de5267f54efa4a39381b0404a7c03d56cb7c041 + url: "https://pub.dev" + source: hosted + version: "3.2.0" + cached_network_image: + dependency: "direct main" + description: + name: cached_network_image + sha256: "7c1183e361e5c8b0a0f21a28401eecdbde252441106a9816400dd4c2b2424916" + url: "https://pub.dev" + source: hosted + version: "3.4.1" + cached_network_image_platform_interface: + dependency: transitive + description: + name: cached_network_image_platform_interface + sha256: "35814b016e37fbdc91f7ae18c8caf49ba5c88501813f73ce8a07027a395e2829" + url: "https://pub.dev" + source: hosted + version: "4.1.1" + cached_network_image_web: + dependency: transitive + description: + name: cached_network_image_web + sha256: "980842f4e8e2535b8dbd3d5ca0b1f0ba66bf61d14cc3a17a9b4788a3685ba062" + url: "https://pub.dev" + source: hosted + version: "1.3.1" + characters: + dependency: transitive + description: + name: characters + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + clock: + dependency: transitive + description: + name: clock + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.dev" + source: hosted + version: "1.1.2" + code_assets: + dependency: transitive + description: + name: code_assets + sha256: "83ccdaa064c980b5596c35dd64a8d3ecc68620174ab9b90b6343b753aa721687" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + cross_file: + dependency: transitive + description: + name: cross_file + sha256: "28bb3ae56f117b5aec029d702a90f57d285cd975c3c5c281eaca38dbc47c5937" + url: "https://pub.dev" + source: hosted + version: "0.3.5+2" + crypto: + dependency: transitive + description: + name: crypto + sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf + url: "https://pub.dev" + source: hosted + version: "3.0.7" + dio: + dependency: "direct main" + description: + name: dio + sha256: aff32c08f92787a557dd5c0145ac91536481831a01b4648136373cddb0e64f8c + url: "https://pub.dev" + source: hosted + version: "5.9.2" + dio_web_adapter: + dependency: transitive + description: + name: dio_web_adapter + sha256: "2f9e64323a7c3c7ef69567d5c800424a11f8337b8b228bad02524c9fb3c1f340" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + ffi: + dependency: transitive + description: + name: ffi + sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" + file_selector_linux: + dependency: transitive + description: + name: file_selector_linux + sha256: "2567f398e06ac72dcf2e98a0c95df2a9edd03c2c2e0cacd4780f20cdf56263a0" + url: "https://pub.dev" + source: hosted + version: "0.9.4" + file_selector_macos: + dependency: transitive + description: + name: file_selector_macos + sha256: "5e0bbe9c312416f1787a68259ea1505b52f258c587f12920422671807c4d618a" + url: "https://pub.dev" + source: hosted + version: "0.9.5" + file_selector_platform_interface: + dependency: transitive + description: + name: file_selector_platform_interface + sha256: "35e0bd61ebcdb91a3505813b055b09b79dfdc7d0aee9c09a7ba59ae4bb13dc85" + url: "https://pub.dev" + source: hosted + version: "2.7.0" + file_selector_windows: + dependency: transitive + description: + name: file_selector_windows + sha256: "62197474ae75893a62df75939c777763d39c2bc5f73ce5b88497208bc269abfd" + url: "https://pub.dev" + source: hosted + version: "0.9.3+5" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be + url: "https://pub.dev" + source: hosted + version: "1.1.1" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_animate: + dependency: "direct main" + description: + name: flutter_animate + sha256: "7befe2d3252728afb77aecaaea1dec88a89d35b9b1d2eea6d04479e8af9117b5" + url: "https://pub.dev" + source: hosted + version: "4.5.2" + flutter_cache_manager: + dependency: transitive + description: + name: flutter_cache_manager + sha256: "400b6592f16a4409a7f2bb929a9a7e38c72cceb8ffb99ee57bbf2cb2cecf8386" + url: "https://pub.dev" + source: hosted + version: "3.4.1" + flutter_hooks: + dependency: "direct main" + description: + name: flutter_hooks + sha256: cde36b12f7188c85286fba9b38cc5a902e7279f36dd676967106c041dc9dde70 + url: "https://pub.dev" + source: hosted + version: "0.20.5" + flutter_localizations: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_markdown: + dependency: "direct main" + description: + name: flutter_markdown + sha256: "04c4722cc36ec5af38acc38ece70d22d3c2123c61305d555750a091517bbe504" + url: "https://pub.dev" + source: hosted + version: "0.6.23" + flutter_plugin_android_lifecycle: + dependency: transitive + description: + name: flutter_plugin_android_lifecycle + sha256: "38d1c268de9097ff59cf0e844ac38759fc78f76836d37edad06fa21e182055a0" + url: "https://pub.dev" + source: hosted + version: "2.0.34" + flutter_riverpod: + dependency: "direct main" + description: + name: flutter_riverpod + sha256: "9532ee6db4a943a1ed8383072a2e3eeda041db5657cdf6d2acecf3c21ecbe7e1" + url: "https://pub.dev" + source: hosted + version: "2.6.1" + flutter_shaders: + dependency: transitive + description: + name: flutter_shaders + sha256: "34794acadd8275d971e02df03afee3dee0f98dbfb8c4837082ad0034f612a3e2" + url: "https://pub.dev" + source: hosted + version: "0.1.3" + flutter_slidable: + dependency: "direct main" + description: + name: flutter_slidable + sha256: a857de7ea701f276fd6a6c4c67ae885b60729a3449e42766bb0e655171042801 + url: "https://pub.dev" + source: hosted + version: "3.1.2" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + freezed_annotation: + dependency: "direct main" + description: + name: freezed_annotation + sha256: c2e2d632dd9b8a2b7751117abcfc2b4888ecfe181bd9fca7170d9ef02e595fe2 + url: "https://pub.dev" + source: hosted + version: "2.4.4" + glob: + dependency: transitive + description: + name: glob + sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de + url: "https://pub.dev" + source: hosted + version: "2.1.3" + go_router: + dependency: "direct main" + description: + name: go_router + sha256: b465e99ce64ba75e61c8c0ce3d87b66d8ac07f0b35d0a7e0263fcfc10f99e836 + url: "https://pub.dev" + source: hosted + version: "13.2.5" + google_fonts: + dependency: "direct main" + description: + name: google_fonts + sha256: ba03d03bcaa2f6cb7bd920e3b5027181db75ab524f8891c8bc3aa603885b8055 + url: "https://pub.dev" + source: hosted + version: "6.3.3" + hive: + dependency: "direct main" + description: + name: hive + sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941" + url: "https://pub.dev" + source: hosted + version: "2.2.3" + hive_flutter: + dependency: "direct main" + description: + name: hive_flutter + sha256: dca1da446b1d808a51689fb5d0c6c9510c0a2ba01e22805d492c73b68e33eecc + url: "https://pub.dev" + source: hosted + version: "1.1.0" + hooks: + dependency: transitive + description: + name: hooks + sha256: "025f060e86d2d4c3c47b56e33caf7f93bf9283340f26d23424ebcfccf34f621e" + url: "https://pub.dev" + source: hosted + version: "1.0.3" + hooks_riverpod: + dependency: "direct main" + description: + name: hooks_riverpod + sha256: "70bba33cfc5670c84b796e6929c54b8bc5be7d0fe15bb28c2560500b9ad06966" + url: "https://pub.dev" + source: hosted + version: "2.6.1" + http: + dependency: transitive + description: + name: http + sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" + url: "https://pub.dev" + source: hosted + version: "1.6.0" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.dev" + source: hosted + version: "4.1.2" + image_picker: + dependency: "direct main" + description: + name: image_picker + sha256: "91c025426c2881c551100bce834e201c835a170151545f58d17da5180ca7d9ac" + url: "https://pub.dev" + source: hosted + version: "1.2.2" + image_picker_android: + dependency: transitive + description: + name: image_picker_android + sha256: d5b3e1774af29c9ab00103afb0d4614070f924d2e0057ac867ec98800114793f + url: "https://pub.dev" + source: hosted + version: "0.8.13+17" + image_picker_for_web: + dependency: transitive + description: + name: image_picker_for_web + sha256: "66257a3191ab360d23a55c8241c91a6e329d31e94efa7be9cf7a212e65850214" + url: "https://pub.dev" + source: hosted + version: "3.1.1" + image_picker_ios: + dependency: transitive + description: + name: image_picker_ios + sha256: b9c4a438a9ff4f60808c9cf0039b93a42bb6c2211ef6ebb647394b2b3fa84588 + url: "https://pub.dev" + source: hosted + version: "0.8.13+6" + image_picker_linux: + dependency: transitive + description: + name: image_picker_linux + sha256: "1f81c5f2046b9ab724f85523e4af65be1d47b038160a8c8deed909762c308ed4" + url: "https://pub.dev" + source: hosted + version: "0.2.2" + image_picker_macos: + dependency: transitive + description: + name: image_picker_macos + sha256: "86f0f15a309de7e1a552c12df9ce5b59fe927e71385329355aec4776c6a8ec91" + url: "https://pub.dev" + source: hosted + version: "0.2.2+1" + image_picker_platform_interface: + dependency: transitive + description: + name: image_picker_platform_interface + sha256: "567e056716333a1647c64bb6bd873cff7622233a5c3f694be28a583d4715690c" + url: "https://pub.dev" + source: hosted + version: "2.11.1" + image_picker_windows: + dependency: transitive + description: + name: image_picker_windows + sha256: d248c86554a72b5495a31c56f060cf73a41c7ff541689327b1a7dbccc33adfae + url: "https://pub.dev" + source: hosted + version: "0.2.2" + intl: + dependency: "direct main" + description: + name: intl + sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" + url: "https://pub.dev" + source: hosted + version: "0.20.2" + jni: + dependency: transitive + description: + name: jni + sha256: c2230682d5bc2362c1c9e8d3c7f406d9cbba23ab3f2e203a025dd47e0fb2e68f + url: "https://pub.dev" + source: hosted + version: "1.0.0" + jni_flutter: + dependency: transitive + description: + name: jni_flutter + sha256: "8b59e590786050b1cd866677dddaf76b1ade5e7bc751abe04b86e84d379d3ba6" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + js: + dependency: transitive + description: + name: js + sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc" + url: "https://pub.dev" + source: hosted + version: "0.7.2" + json_annotation: + dependency: "direct main" + description: + name: json_annotation + sha256: cb09e7dac6210041fad964ed7fbee004f14258b4eca4040f72d1234062ace4c8 + url: "https://pub.dev" + source: hosted + version: "4.11.0" + logging: + dependency: transitive + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.dev" + source: hosted + version: "1.3.0" + lottie: + dependency: "direct main" + description: + name: lottie + sha256: "8b6359a7422167014aa73ce763fa133fb832065dcc0ac4d1dec1f603a5cef7d0" + url: "https://pub.dev" + source: hosted + version: "3.3.3" + markdown: + dependency: transitive + description: + name: markdown + sha256: ee85086ad7698b42522c6ad42fe195f1b9898e4d974a1af4576c1a3a176cada9 + url: "https://pub.dev" + source: hosted + version: "7.3.1" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + url: "https://pub.dev" + source: hosted + version: "0.11.1" + meta: + dependency: transitive + description: + name: meta + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" + url: "https://pub.dev" + source: hosted + version: "1.17.0" + mime: + dependency: transitive + description: + name: mime + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + mobile_scanner: + dependency: "direct main" + description: + name: mobile_scanner + sha256: "1b60b8f9d4ce0cb0e7d7bc223c955d083a0737bee66fa1fcfe5de48225e0d5b3" + url: "https://pub.dev" + source: hosted + version: "3.5.7" + native_toolchain_c: + dependency: transitive + description: + name: native_toolchain_c + sha256: "6ba77bb18063eebe9de401f5e6437e95e1438af0a87a3a39084fbd37c90df572" + url: "https://pub.dev" + source: hosted + version: "0.17.6" + objective_c: + dependency: transitive + description: + name: objective_c + sha256: "100a1c87616ab6ed41ec263b083c0ef3261ee6cd1dc3b0f35f8ddfa4f996fe52" + url: "https://pub.dev" + source: hosted + version: "9.3.0" + octo_image: + dependency: transitive + description: + name: octo_image + sha256: "34faa6639a78c7e3cbe79be6f9f96535867e879748ade7d17c9b1ae7536293bd" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + package_config: + dependency: transitive + description: + name: package_config + sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc + url: "https://pub.dev" + source: hosted + version: "2.2.0" + path: + dependency: transitive + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + path_provider: + dependency: transitive + description: + name: path_provider + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" + url: "https://pub.dev" + source: hosted + version: "2.1.5" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: "69cbd515a62b94d32a7944f086b2f82b4ac40a1d45bebfc00813a430ab2dabcd" + url: "https://pub.dev" + source: hosted + version: "2.3.1" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699" + url: "https://pub.dev" + source: hosted + version: "2.6.0" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" + platform: + dependency: transitive + description: + name: platform + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" + url: "https://pub.dev" + source: hosted + version: "3.1.6" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + posix: + dependency: transitive + description: + name: posix + sha256: "185ef7606574f789b40f289c233efa52e96dead518aed988e040a10737febb07" + url: "https://pub.dev" + source: hosted + version: "6.5.0" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + pull_to_refresh_flutter3: + dependency: "direct main" + description: + name: pull_to_refresh_flutter3 + sha256: "37a88d901cca9a46dbdd46523de8e7b35a3e58634a0e775b1a5904981f69b353" + url: "https://pub.dev" + source: hosted + version: "2.0.2" + record_use: + dependency: transitive + description: + name: record_use + sha256: "2551bd8eecfe95d14ae75f6021ad0248be5c27f138c2ec12fcb52b500b3ba1ed" + url: "https://pub.dev" + source: hosted + version: "0.6.0" + riverpod: + dependency: transitive + description: + name: riverpod + sha256: "59062512288d3056b2321804332a13ffdd1bf16df70dcc8e506e411280a72959" + url: "https://pub.dev" + source: hosted + version: "2.6.1" + rxdart: + dependency: transitive + description: + name: rxdart + sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962" + url: "https://pub.dev" + source: hosted + version: "0.28.0" + shared_preferences: + dependency: "direct main" + description: + name: shared_preferences + sha256: c3025c5534b01739267eb7d76959bbc25a6d10f6988e1c2a3036940133dd10bf + url: "https://pub.dev" + source: hosted + version: "2.5.5" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: e8d4762b1e2e8578fc4d0fd548cebf24afd24f49719c08974df92834565e2c53 + url: "https://pub.dev" + source: hosted + version: "2.4.23" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: "4e7eaffc2b17ba398759f1151415869a34771ba11ebbccd1b0145472a619a64f" + url: "https://pub.dev" + source: hosted + version: "2.5.6" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + sha256: "649dc798a33931919ea356c4305c2d1f81619ea6e92244070b520187b5140ef9" + url: "https://pub.dev" + source: hosted + version: "2.4.2" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 + url: "https://pub.dev" + source: hosted + version: "2.4.3" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shimmer: + dependency: "direct main" + description: + name: shimmer + sha256: "5f88c883a22e9f9f299e5ba0e4f7e6054857224976a5d9f839d4ebdc94a14ac9" + url: "https://pub.dev" + source: hosted + version: "3.0.0" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + source_span: + dependency: transitive + description: + name: source_span + sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" + url: "https://pub.dev" + source: hosted + version: "1.10.2" + sqflite: + dependency: transitive + description: + name: sqflite + sha256: "564cfed0746fe53140c23b70b308e045c3b31f17778f2f326ccb7d804ea0250a" + url: "https://pub.dev" + source: hosted + version: "2.4.2+1" + sqflite_android: + dependency: transitive + description: + name: sqflite_android + sha256: "881e28efdcc9950fd8e9bb42713dcf1103e62a2e7168f23c9338d82db13dec40" + url: "https://pub.dev" + source: hosted + version: "2.4.2+3" + sqflite_common: + dependency: transitive + description: + name: sqflite_common + sha256: f8a08a13fb8f0f8c590df89d745000bed44a673ed94bac846739e1a016875c21 + url: "https://pub.dev" + source: hosted + version: "2.5.7" + sqflite_darwin: + dependency: transitive + description: + name: sqflite_darwin + sha256: "279832e5cde3fe99e8571879498c9211f3ca6391b0d818df4e17d9fff5c6ccb3" + url: "https://pub.dev" + source: hosted + version: "2.4.2" + sqflite_platform_interface: + dependency: transitive + description: + name: sqflite_platform_interface + sha256: "8dd4515c7bdcae0a785b0062859336de775e8c65db81ae33dd5445f35be61920" + url: "https://pub.dev" + source: hosted + version: "2.4.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev" + source: hosted + version: "1.12.1" + state_notifier: + dependency: transitive + description: + name: state_notifier + sha256: b8677376aa54f2d7c58280d5a007f9e8774f1968d1fb1c096adcb4792fba29bb + url: "https://pub.dev" + source: hosted + version: "1.0.0" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" + source: hosted + version: "1.4.1" + synchronized: + dependency: transitive + description: + name: synchronized + sha256: c254ade258ec8282947a0acbbc90b9575b4f19673533ee46f2f6e9b3aeefd7c0 + url: "https://pub.dev" + source: hosted + version: "3.4.0" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" + source: hosted + version: "1.2.2" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + url_launcher: + dependency: "direct main" + description: + name: url_launcher + sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8 + url: "https://pub.dev" + source: hosted + version: "6.3.2" + url_launcher_android: + dependency: transitive + description: + name: url_launcher_android + sha256: "3bb000251e55d4a209aa0e2e563309dc9bb2befea2295fd0cec1f51760aac572" + url: "https://pub.dev" + source: hosted + version: "6.3.29" + url_launcher_ios: + dependency: transitive + description: + name: url_launcher_ios + sha256: "580fe5dfb51671ae38191d316e027f6b76272b026370708c2d898799750a02b0" + url: "https://pub.dev" + source: hosted + version: "6.4.1" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + sha256: d5e14138b3bc193a0f63c10a53c94b91d399df0512b1f29b94a043db7482384a + url: "https://pub.dev" + source: hosted + version: "3.2.2" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + sha256: "368adf46f71ad3c21b8f06614adb38346f193f3a59ba8fe9a2fd74133070ba18" + url: "https://pub.dev" + source: hosted + version: "3.2.5" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + sha256: "85c81589622fbc87c1c683aaea164d3604a7777495a79d91e39ffcdec39ddb34" + url: "https://pub.dev" + source: hosted + version: "2.4.3" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + sha256: "712c70ab1b99744ff066053cbe3e80c73332b38d46e5e945c98689b2e66fc15f" + url: "https://pub.dev" + source: hosted + version: "3.1.5" + uuid: + dependency: "direct main" + description: + name: uuid + sha256: "1fef9e8e11e2991bb773070d4656b7bd5d850967a2456cfc83cf47925ba79489" + url: "https://pub.dev" + source: hosted + version: "4.5.3" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b + url: "https://pub.dev" + source: hosted + version: "2.2.0" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + yaml: + dependency: transitive + description: + name: yaml + sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce + url: "https://pub.dev" + source: hosted + version: "3.1.3" +sdks: + dart: ">=3.10.3 <4.0.0" + flutter: ">=3.38.4" diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 0000000..d07a2b6 --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,41 @@ +name: zhinian_manage +description: 智念酒店景区管理端 +publish_to: 'none' +version: 1.0.0+1 + +environment: + sdk: '>=3.0.0 <4.0.0' + +dependencies: + flutter: + sdk: flutter + flutter_localizations: + sdk: flutter + intl: ^0.20.2 + flutter_riverpod: ^2.4.9 + hooks_riverpod: ^2.4.9 + flutter_hooks: ^0.20.3 + go_router: ^13.0.1 + dio: ^5.4.0 + hive: ^2.2.3 + hive_flutter: ^1.1.0 + shared_preferences: ^2.2.2 + mobile_scanner: ^3.5.5 + flutter_markdown: ^0.6.18 + image_picker: ^1.0.7 + url_launcher: ^6.2.2 + shimmer: ^3.0.0 + google_fonts: ^6.1.0 + cached_network_image: ^3.3.1 + flutter_animate: ^4.3.0 + badges: ^3.1.2 + flutter_slidable: ^3.0.1 + pull_to_refresh_flutter3: ^2.0.2 + lottie: ^3.0.0 + uuid: ^4.2.2 + freezed_annotation: ^2.4.1 + json_annotation: ^4.8.1 + +flutter: + uses-material-design: true + generate: true \ No newline at end of file diff --git a/test/widget_test.dart b/test/widget_test.dart new file mode 100644 index 0000000..34dd628 --- /dev/null +++ b/test/widget_test.dart @@ -0,0 +1,30 @@ +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility in the flutter_test package. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:zhinian_manage/main.dart'; + +void main() { + testWidgets('Counter increments smoke test', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(const MyApp()); + + // Verify that our counter starts at 0. + expect(find.text('0'), findsOneWidget); + expect(find.text('1'), findsNothing); + + // Tap the '+' icon and trigger a frame. + await tester.tap(find.byIcon(Icons.add)); + await tester.pump(); + + // Verify that our counter has incremented. + expect(find.text('0'), findsNothing); + expect(find.text('1'), findsOneWidget); + }); +} diff --git a/web/favicon.png b/web/favicon.png new file mode 100644 index 0000000..8aaa46a Binary files /dev/null and b/web/favicon.png differ diff --git a/web/icons/Icon-192.png b/web/icons/Icon-192.png new file mode 100644 index 0000000..b749bfe Binary files /dev/null and b/web/icons/Icon-192.png differ diff --git a/web/icons/Icon-512.png b/web/icons/Icon-512.png new file mode 100644 index 0000000..88cfd48 Binary files /dev/null and b/web/icons/Icon-512.png differ diff --git a/web/icons/Icon-maskable-192.png b/web/icons/Icon-maskable-192.png new file mode 100644 index 0000000..eb9b4d7 Binary files /dev/null and b/web/icons/Icon-maskable-192.png differ diff --git a/web/icons/Icon-maskable-512.png b/web/icons/Icon-maskable-512.png new file mode 100644 index 0000000..d69c566 Binary files /dev/null and b/web/icons/Icon-maskable-512.png differ diff --git a/web/index.html b/web/index.html new file mode 100644 index 0000000..4b480b7 --- /dev/null +++ b/web/index.html @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + zhinian_manage + + + + + + diff --git a/web/manifest.json b/web/manifest.json new file mode 100644 index 0000000..16d65bb --- /dev/null +++ b/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "zhinian_manage", + "short_name": "zhinian_manage", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +}