Commit bd3c7628 authored by 张宏's avatar 张宏

初始化框架+登录(未对接ui+接口)

parents
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
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
.flutter-plugins-dependencies
.pub-cache/
.pub/
/build/
# 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
# 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: "67457e669f79e9f8d13d7a68fe09775fefbb79f4"
channel: "stable"
project_type: app
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: 67457e669f79e9f8d13d7a68fe09775fefbb79f4
base_revision: 67457e669f79e9f8d13d7a68fe09775fefbb79f4
- platform: android
create_revision: 67457e669f79e9f8d13d7a68fe09775fefbb79f4
base_revision: 67457e669f79e9f8d13d7a68fe09775fefbb79f4
- platform: ios
create_revision: 67457e669f79e9f8d13d7a68fe09775fefbb79f4
base_revision: 67457e669f79e9f8d13d7a68fe09775fefbb79f4
- platform: linux
create_revision: 67457e669f79e9f8d13d7a68fe09775fefbb79f4
base_revision: 67457e669f79e9f8d13d7a68fe09775fefbb79f4
- platform: macos
create_revision: 67457e669f79e9f8d13d7a68fe09775fefbb79f4
base_revision: 67457e669f79e9f8d13d7a68fe09775fefbb79f4
- platform: web
create_revision: 67457e669f79e9f8d13d7a68fe09775fefbb79f4
base_revision: 67457e669f79e9f8d13d7a68fe09775fefbb79f4
- platform: windows
create_revision: 67457e669f79e9f8d13d7a68fe09775fefbb79f4
base_revision: 67457e669f79e9f8d13d7a68fe09775fefbb79f4
# 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'
1.http及response 移动至 http
### 版本处理
版本对齐后,必须清除旧缓存,否则 Gradle 会继续读取损坏的元数据
```
# 1. 进入 android 目录
cd android
# 2. 强制停止 Gradle 守护进程 & 清理
./gradlew --stop
./gradlew clean
# 3. 删除本地缓存目录(Windows 请手动删除或使用 Git Bash)
rm -rf .gradle build ../build
# 4. 返回项目根目录,重建 Flutter 环境
cd ..
flutter clean
flutter pub get
# 5. 重新运行
flutter run
```
\ No newline at end of file
# This file configures the analyzer, which statically analyzes Dart code to
# check for errors, warnings, and lints.
#
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
# invoked from the command line by running `flutter analyze`.
# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml
linter:
# The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
# included above or to enable additional rules. A list of all available lints
# and their documentation is published at https://dart.dev/lints.
#
# Instead of disabling a lint rule for the entire project in the
# section below, it can also be suppressed for a single line of code
# or a specific dart file by using the `// ignore: name_of_lint` and
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint.
rules:
# avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options
gradle-wrapper.jar
/.gradle
/captures/
/gradlew
/gradlew.bat
/local.properties
GeneratedPluginRegistrant.java
# Remember to never publicly share your keystore.
# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
key.properties
**/*.keystore
**/*.jks
plugins {
id "com.android.application"
id "kotlin-android"
id "dev.flutter.flutter-gradle-plugin"
}
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader ->
localProperties.load(reader)
}
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
flutterVersionCode = '1'
}
def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
flutterVersionName = '1.0'
}
android {
namespace "com.example.smart_hotel_app"
compileSdkVersion flutter.compileSdkVersion
ndkVersion flutter.ndkVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.example.smart_hotel_app"
// You can update the following values to match your application needs.
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
minSdkVersion flutter.minSdkVersion
targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
}
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.debug
}
}
}
flutter {
source '../..'
}
dependencies {}
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:label="smart_hotel_app"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
</manifest>
package com.example.smart_hotel_app
import io.flutter.embedding.android.FlutterActivity
class MainActivity: FlutterActivity() {
}
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="?android:colorBackground" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>
buildscript {
ext.kotlin_version = '1.9.22'
repositories {
google()
maven { url uri('https://maven.aliyun.com/repository/google') }
mavenCentral()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
allprojects {
repositories {
google()
maven { url uri('https://maven.aliyun.com/repository/google') }
mavenCentral()
}
}
rootProject.buildDir = '../build'
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
project.evaluationDependsOn(':app')
}
tasks.register("clean", Delete) {
delete rootProject.buildDir
}
org.gradle.jvmargs=-Xmx4G
android.useAndroidX=true
android.enableJetifier=true
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
# distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip
distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-8.10.2-bin.zip
pluginManagement {
def flutterSdkPath = {
def properties = new Properties()
file("local.properties").withInputStream { properties.load(it) }
def flutterSdkPath = properties.getProperty("flutter.sdk")
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
return flutterSdkPath
}
settings.ext.flutterSdkPath = flutterSdkPath()
includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle")
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
plugins {
id "dev.flutter.flutter-gradle-plugin" version "1.0.0" apply false
}
}
plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
id "com.android.application" version "8.7.2" apply false
}
include ":app"
**/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
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>App</string>
<key>CFBundleIdentifier</key>
<string>io.flutter.flutter.app</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>App</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>12.0</string>
</dict>
</plist>
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "Generated.xcconfig"
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "Generated.xcconfig"
# Uncomment this line to define a global platform for your project
# platform :ios, '12.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!
use_modular_headers!
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
PODS:
- Flutter (1.0.0)
- flutter_secure_storage (6.0.0):
- Flutter
- fluttertoast (0.0.2):
- Flutter
- Toast
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
- Toast (4.1.1)
DEPENDENCIES:
- Flutter (from `Flutter`)
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
- fluttertoast (from `.symlinks/plugins/fluttertoast/ios`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
SPEC REPOS:
trunk:
- Toast
EXTERNAL SOURCES:
Flutter:
:path: Flutter
flutter_secure_storage:
:path: ".symlinks/plugins/flutter_secure_storage/ios"
fluttertoast:
:path: ".symlinks/plugins/fluttertoast/ios"
path_provider_foundation:
:path: ".symlinks/plugins/path_provider_foundation/darwin"
SPEC CHECKSUMS:
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
flutter_secure_storage: 1ed9476fba7e7a782b22888f956cce43e2c62f13
fluttertoast: eaafdac812d1d69931004e881c87a8643b1c9111
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
Toast: 1f5ea13423a1e6674c4abdac5be53587ae481c4e
PODFILE CHECKSUM: 819463e6a0290f5a72f145ba7cde16e8b6ef0796
COCOAPODS: 1.16.2
This diff is collapsed.
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1430"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables>
<TestableReference
skipped = "NO"
parallelizable = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "331C8080294A63A400263BE5"
BuildableName = "RunnerTests.xctest"
BlueprintName = "RunnerTests"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Profile"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>
import UIKit
import Flutter
@UIApplicationMain
@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)
}
}
{
"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"
}
}
{
"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"
}
}
# 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
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
<viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
</imageView>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
</constraints>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
<resources>
<image name="LaunchImage" width="168" height="185"/>
</resources>
</document>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
</dependencies>
<scenes>
<!--Flutter View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
</scene>
</scenes>
</document>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>Smart Hotel App</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>smart_hotel_app</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
</dict>
</plist>
#import "GeneratedPluginRegistrant.h"
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.
}
}
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:smart_hotel_app/models/bo/user_info_bo.dart';
import 'auth_event.dart';
import 'auth_state.dart';
import '../../services/auth_service.dart';
import '../../utils/storage/storage_service.dart';
class AuthBloc extends Bloc<AuthEvent, AuthState> {
final AuthService _authService;
final StorageService _storageService;
AuthBloc({
required AuthService authService,
required StorageService storageService,
}) : _authService = authService,
_storageService = storageService,
super(AuthInitial()) {
on<AuthLoginRequested>(_onLoginRequested);
on<AuthTokenExpiredEvent>(_onTokenExpired);
on<AuthLogoutRequestedEvent>(_onLogoutRequested);
}
Future<void> _onLoginRequested(
AuthLoginRequested event,
Emitter<AuthState> emit,
) async {
emit(AuthLoading());
try {
// todo 这里暂定返回bool
final success = await _authService.login(event.username, event.password);
if (success) {
final userInfo = UserInfoBO();
userInfo.username = event.username; // todo 这里应对从login接口获取到
emit(AuthSuccess(userInfo));
} else {
emit(const AuthFailure('登录失败,请检查用户名和密码'));
}
} catch (e) {
emit(AuthFailure(e.toString()));
}
}
Future<void> _onTokenExpired(
AuthTokenExpiredEvent event,
Emitter<AuthState> emit,
) async {
await _storageService.deleteToken();
emit(const AuthTokenExpired());
}
Future<void> _onLogoutRequested(
AuthLogoutRequestedEvent event,
Emitter<AuthState> emit,
) async {
await _storageService.deleteToken();
emit(const AuthLoggedOut());
}
}
import 'package:equatable/equatable.dart';
abstract class AuthEvent extends Equatable {
const AuthEvent();
@override
List<Object?> get props => [];
}
class AuthLoginRequested extends AuthEvent {
final String username;
final String password;
const AuthLoginRequested({
required this.username,
required this.password,
});
@override
List<Object?> get props => [username, password];
}
class AuthTokenExpiredEvent extends AuthEvent {
const AuthTokenExpiredEvent();
}
class AuthLogoutRequestedEvent extends AuthEvent {
const AuthLogoutRequestedEvent();
}
import 'package:equatable/equatable.dart';
import 'package:smart_hotel_app/models/bo/user_info_bo.dart';
abstract class AuthState extends Equatable {
const AuthState();
@override
List<Object?> get props => [];
}
class AuthInitial extends AuthState {}
class AuthLoading extends AuthState {}
class AuthSuccess extends AuthState {
final UserInfoBO userInfo;
const AuthSuccess(this.userInfo);
@override
List<Object?> get props => [userInfo];
}
class AuthFailure extends AuthState {
final String error;
const AuthFailure(this.error);
@override
List<Object?> get props => [error];
}
class AuthTokenExpired extends AuthState {
const AuthTokenExpired();
}
class AuthLoggedOut extends AuthState {
const AuthLoggedOut();
}
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:smart_hotel_app/blocs/auth/auth_bloc.dart';
import 'package:smart_hotel_app/blocs/auth/auth_event.dart';
import 'package:smart_hotel_app/blocs/auth/auth_state.dart';
import 'package:smart_hotel_app/routes/app_router.dart';
import 'package:smart_hotel_app/repositories/auth_repository.dart';
import 'package:smart_hotel_app/services/auth_service.dart';
import 'package:smart_hotel_app/utils/dio_request.dart';
import 'package:smart_hotel_app/utils/event_bus.dart';
import 'package:smart_hotel_app/utils/storage/storage_service.dart';
import 'package:smart_hotel_app/routes/app_router.gr.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
void main(List<String> args) {
WidgetsFlutterBinding.ensureInitialized();
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final _scaffoldMessengerKey = GlobalKey<ScaffoldMessengerState>();
late final StorageService _storageService;
late final AuthService _authService;
late final AppRouter _appRouter;
late final AuthBloc _authBloc;
StreamSubscription? _tokenExpiredSubscription;
@override
void initState() {
super.initState();
_storageService = StorageService();
DioRequest.instance.init(_storageService);
final authRepository = AuthRepository();
_authService = AuthService(
authRepository: authRepository,
storageService: _storageService,
);
_authBloc = AuthBloc(
authService: _authService,
storageService: _storageService,
);
_appRouter = AppRouter();
// 监听 Token 过期事件
_tokenExpiredSubscription =
eventBus.on<TokenExpiredEvent>().listen((event) {
_authBloc.add(const AuthTokenExpiredEvent());
});
_checkTokenOnStartup();
}
Future<void> _checkTokenOnStartup() async {
final hasToken = await _storageService.hasToken();
if (hasToken) {
final expired = await _authService.isTokenExpired();
if (expired) {
_authBloc.add(const AuthTokenExpiredEvent());
}
}
}
@override
void dispose() {
_tokenExpiredSubscription?.cancel();
_authBloc.close();
super.dispose();
}
@override
Widget build(BuildContext context) {
return ScreenUtilInit(
designSize: Size(750, 1334),
builder: (context, child) {
return MultiBlocProvider(
providers: [
BlocProvider<AuthBloc>.value(value: _authBloc),
// BlocProvider<CounterBloc>(create: (_) => CounterBloc()),
],
child: MultiBlocListener(
listeners: [
// AuthBloc 监听器 - 处理登录/登出/Token 过期
BlocListener<AuthBloc, AuthState>(
listener: (context, state) {
if (state is AuthSuccess) {
_appRouter.navigate(const DefaultLayoutRoute());
}
if (state is AuthTokenExpired) {
_scaffoldMessengerKey.currentState?.showSnackBar(
const SnackBar(
content: Text('登录已过期,请重新登录'),
backgroundColor: Colors.red,
),
);
_appRouter.replaceAll([const LoginRoute()]);
}
if (state is AuthLoggedOut) {
_appRouter.replaceAll([const LoginRoute()]);
}
},
),
// 未来在这里添加其他 Bloc 的监听器:
// BlocListener<UserBloc, UserState>(listener: ...),
// BlocListener<SettingsBloc, SettingsState>(listener: ...),
],
child: MaterialApp.router(
scaffoldMessengerKey: _scaffoldMessengerKey,
routerConfig: _appRouter.config(),
debugShowCheckedModeBanner: false,
),
),
);
},
);
}
}
/**
* 业务对象
*/
class UserInfoBO {
String? username;
String? rolename;
String? rolecode;
// 用户相关其他信息
}
\ No newline at end of file
class ResponseModel<T> {
final int code;
final String msg;
final T? data;
final String uuid;
final bool success;
final int timestamp;
ResponseModel({
required this.code,
required this.msg,
this.data,
required this.uuid,
required this.success,
required this.timestamp,
});
factory ResponseModel.fromJson(
Map<String, dynamic> json, {
required String uuid,
T Function(dynamic)? fromJsonT,
}) {
final code = json['code'] as int? ?? -1;
return ResponseModel<T>(
code: code,
msg: json['msg'] as String? ?? '未知错误',
data: json['data'] != null && fromJsonT != null
? fromJsonT(json['data'])
: json['data'] as T?,
uuid: uuid,
success: code == 200,
timestamp: DateTime.now().millisecondsSinceEpoch,
);
}
factory ResponseModel.error({
required String uuid,
required String msg,
int code = -1,
}) {
return ResponseModel<T>(
code: code,
msg: msg,
data: null,
uuid: uuid,
success: false,
timestamp: DateTime.now().millisecondsSinceEpoch,
);
}
Map<String, dynamic> toJson([Object? Function(T value)? toJsonT]) {
return {
'code': code,
'msg': msg,
'data': toJsonT != null && data != null ? toJsonT(data as T) : data,
'uuid': uuid,
'success': success,
'timestamp': timestamp,
};
}
ResponseModel<R> copyWith<R>({
int? code,
String? msg,
R? data,
String? uuid,
bool? success,
int? timestamp,
}) {
return ResponseModel<R>(
code: code ?? this.code,
msg: msg ?? this.msg,
data: data,
uuid: uuid ?? this.uuid,
success: success ?? this.success,
timestamp: timestamp ?? this.timestamp,
);
}
@override
String toString() {
return 'ResponseModel(code: $code, msg: $msg, uuid: $uuid, success: $success)';
}
}
import '../models/response_model.dart';
import '../utils/dio_request.dart';
class AuthRepository {
Future<ResponseModel> login(Map<String, dynamic> params) {
return DioRequest.instance.post('/login', data: params);
}
}
import 'package:auto_route/auto_route.dart';
import 'package:smart_hotel_app/routes/app_router.gr.dart';
@AutoRouterConfig(replaceInRouteName: 'View,Route')
class AppRouter extends $AppRouter {
@override
RouteType get defaultRouteType =>
const RouteType.material(); //.cupertino, .adaptive ..etc
@override
List<AutoRoute> get routes => [
AutoRoute(page: LoginRoute.page, initial: true),
AutoRoute(
page: DefaultLayoutRoute.page,
children: [
AutoRoute(page: HomeRoute.page, initial: true),
AutoRoute(page: NewsRoute.page),
AutoRoute(page: MessageRoute.page),
AutoRoute(page: ProfileRoute.page),
],
),
AutoRoute(page: SearchRoute.page)
];
}
// GENERATED CODE - DO NOT MODIFY BY HAND
// **************************************************************************
// AutoRouterGenerator
// **************************************************************************
// ignore_for_file: type=lint
// coverage:ignore-file
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:auto_route/auto_route.dart' as _i8;
import 'package:flutter/material.dart' as _i9;
import 'package:smart_hotel_app/views/home/index_view.dart' as _i2;
import 'package:smart_hotel_app/views/layout/default_view.dart' as _i1;
import 'package:smart_hotel_app/views/login/login_view.dart' as _i3;
import 'package:smart_hotel_app/views/message/index_view.dart' as _i4;
import 'package:smart_hotel_app/views/news/index_view.dart' as _i5;
import 'package:smart_hotel_app/views/profile/index_view.dart' as _i6;
import 'package:smart_hotel_app/views/search/index_view.dart' as _i7;
abstract class $AppRouter extends _i8.RootStackRouter {
$AppRouter({super.navigatorKey});
@override
final Map<String, _i8.PageFactory> pagesMap = {
DefaultLayoutRoute.name: (routeData) {
return _i8.AutoRoutePage<dynamic>(
routeData: routeData,
child: const _i1.DefaultLayoutView(),
);
},
HomeRoute.name: (routeData) {
return _i8.AutoRoutePage<dynamic>(
routeData: routeData,
child: const _i2.HomeView(),
);
},
LoginRoute.name: (routeData) {
return _i8.AutoRoutePage<dynamic>(
routeData: routeData,
child: const _i3.LoginView(),
);
},
MessageRoute.name: (routeData) {
return _i8.AutoRoutePage<dynamic>(
routeData: routeData,
child: const _i4.MessageView(),
);
},
NewsRoute.name: (routeData) {
return _i8.AutoRoutePage<dynamic>(
routeData: routeData,
child: const _i5.NewsView(),
);
},
ProfileRoute.name: (routeData) {
return _i8.AutoRoutePage<dynamic>(
routeData: routeData,
child: const _i6.ProfileView(),
);
},
SearchRoute.name: (routeData) {
final args = routeData.argsAs<SearchRouteArgs>(
orElse: () => const SearchRouteArgs());
return _i8.AutoRoutePage<dynamic>(
routeData: routeData,
child: _i7.SearchView(
key: args.key,
searchKey: args.searchKey,
),
);
},
};
}
/// generated route for
/// [_i1.DefaultLayoutView]
class DefaultLayoutRoute extends _i8.PageRouteInfo<void> {
const DefaultLayoutRoute({List<_i8.PageRouteInfo>? children})
: super(
DefaultLayoutRoute.name,
initialChildren: children,
);
static const String name = 'DefaultLayoutRoute';
static const _i8.PageInfo<void> page = _i8.PageInfo<void>(name);
}
/// generated route for
/// [_i2.HomeView]
class HomeRoute extends _i8.PageRouteInfo<void> {
const HomeRoute({List<_i8.PageRouteInfo>? children})
: super(
HomeRoute.name,
initialChildren: children,
);
static const String name = 'HomeRoute';
static const _i8.PageInfo<void> page = _i8.PageInfo<void>(name);
}
/// generated route for
/// [_i3.LoginView]
class LoginRoute extends _i8.PageRouteInfo<void> {
const LoginRoute({List<_i8.PageRouteInfo>? children})
: super(
LoginRoute.name,
initialChildren: children,
);
static const String name = 'LoginRoute';
static const _i8.PageInfo<void> page = _i8.PageInfo<void>(name);
}
/// generated route for
/// [_i4.MessageView]
class MessageRoute extends _i8.PageRouteInfo<void> {
const MessageRoute({List<_i8.PageRouteInfo>? children})
: super(
MessageRoute.name,
initialChildren: children,
);
static const String name = 'MessageRoute';
static const _i8.PageInfo<void> page = _i8.PageInfo<void>(name);
}
/// generated route for
/// [_i5.NewsView]
class NewsRoute extends _i8.PageRouteInfo<void> {
const NewsRoute({List<_i8.PageRouteInfo>? children})
: super(
NewsRoute.name,
initialChildren: children,
);
static const String name = 'NewsRoute';
static const _i8.PageInfo<void> page = _i8.PageInfo<void>(name);
}
/// generated route for
/// [_i6.ProfileView]
class ProfileRoute extends _i8.PageRouteInfo<void> {
const ProfileRoute({List<_i8.PageRouteInfo>? children})
: super(
ProfileRoute.name,
initialChildren: children,
);
static const String name = 'ProfileRoute';
static const _i8.PageInfo<void> page = _i8.PageInfo<void>(name);
}
/// generated route for
/// [_i7.SearchView]
class SearchRoute extends _i8.PageRouteInfo<SearchRouteArgs> {
SearchRoute({
_i9.Key? key,
String? searchKey,
List<_i8.PageRouteInfo>? children,
}) : super(
SearchRoute.name,
args: SearchRouteArgs(
key: key,
searchKey: searchKey,
),
initialChildren: children,
);
static const String name = 'SearchRoute';
static const _i8.PageInfo<SearchRouteArgs> page =
_i8.PageInfo<SearchRouteArgs>(name);
}
class SearchRouteArgs {
const SearchRouteArgs({
this.key,
this.searchKey,
});
final _i9.Key? key;
final String? searchKey;
@override
String toString() {
return 'SearchRouteArgs{key: $key, searchKey: $searchKey}';
}
}
import '../repositories/auth_repository.dart';
import '../utils/constants.dart';
import '../utils/storage/storage_service.dart';
class AuthService {
final AuthRepository _authRepository;
final StorageService _storageService;
AuthService({
required AuthRepository authRepository,
required StorageService storageService,
}) : _authRepository = authRepository,
_storageService = storageService;
Future<bool> login(String username, String password) async {
final result = await _authRepository.login({
'username': username,
'password': password,
});
if (result.success) {
final data = result.data;
if (data is Map<String, dynamic> && data.containsKey('token')) {
final token = data['token'] as String?;
if (token != null && token.isNotEmpty) {
await _storageService.saveToken(
token,
expiryHours: Constants.tokenExpiryHours,
);
return true;
}
}
}
return false;
}
Future<bool> isTokenExpired() async {
return _storageService.isTokenExpired();
}
}
class Constants {
// ==================== 环境配置 ====================
static const String baseUrl = 'http://localhost:8080';
// ==================== 超时配置 ====================
static const int connectTimeout = 30000;
static const int receiveTimeout = 30000;
static const int sendTimeout = 30000;
// ==================== 业务状态码 ====================
static const int successCode = 200;
static const int tokenExpiredCode = 401;
static const int forbiddenCode = 403;
static const int notFoundCode = 404;
static const int serverErrorCode = 500;
// ==================== 重试配置 ====================
static const int maxRetries = 3;
static const int retryInterval = 1000;
static const bool enableExponentialBackoff = true;
// ==================== 缓存配置 ====================
static const bool enableCache = false;
static const int cacheDuration = 300;
// ==================== Token 配置 ====================
static const int tokenExpiryHours = 24;
// ==================== 其他配置 ====================
static const String defaultLang = 'zh-CN';
}
This diff is collapsed.
import 'dart:async';
class EventBus {
final StreamController<dynamic> _controller = StreamController.broadcast();
Stream<T> on<T>() {
return _controller.stream.where((event) => event is T).cast<T>();
}
void emit(dynamic event) {
_controller.add(event);
}
void dispose() {
_controller.close();
}
}
// 全局事件总线实例
final eventBus = EventBus();
// Token 过期事件
class TokenExpiredEvent {}
// 退出登录事件
class LogoutEvent {}
class AppException implements Exception {
final String message;
final int? code;
final String? uuid;
AppException({
required this.message,
this.code,
this.uuid,
});
@override
String toString() {
return 'AppException: $message (code: $code, uuid: $uuid)';
}
}
import 'app_exception.dart';
class BusinessException extends AppException {
BusinessException({
required String message,
int? code,
String? uuid,
}) : super(message: message, code: code, uuid: uuid);
}
import 'app_exception.dart';
class CancelException extends AppException {
CancelException({
String message = '请求已取消',
String? uuid,
}) : super(message: message, code: -1, uuid: uuid);
}
import 'app_exception.dart';
class NetworkException extends AppException {
NetworkException({
String message = '网络连接异常,请检查网络后重试',
int? code,
String? uuid,
}) : super(message: message, code: code, uuid: uuid);
}
import 'app_exception.dart';
class ServerException extends AppException {
ServerException({
String message = '服务器异常,请稍后重试',
int? code,
String? uuid,
}) : super(message: message, code: code, uuid: uuid);
}
import 'dart:io';
import 'package:dio/dio.dart';
import '../event_bus.dart';
import '../exceptions/app_exception.dart';
import '../exceptions/business_exception.dart';
import '../exceptions/cancel_exception.dart';
import '../exceptions/network_exception.dart';
import '../exceptions/server_exception.dart';
class ErrorInterceptor extends Interceptor {
@override
void onError(DioException err, ErrorInterceptorHandler handler) {
final uuid = err.requestOptions.extra['uuid'] as String? ?? '';
final AppException exception = _handleError(err, uuid);
// 如果是 401,发送 Token 过期事件
if (err.response?.statusCode == 401) {
eventBus.emit(TokenExpiredEvent());
}
_printErrorLog(err, uuid, exception);
handler.next(DioException(
requestOptions: err.requestOptions,
error: exception,
type: err.type,
response: err.response,
));
}
AppException _handleError(DioException err, String uuid) {
switch (err.type) {
case DioExceptionType.connectionTimeout:
case DioExceptionType.sendTimeout:
case DioExceptionType.receiveTimeout:
return NetworkException(
message: '网络连接超时,请检查网络后重试',
code: -1,
uuid: uuid,
);
case DioExceptionType.badResponse:
final statusCode = err.response?.statusCode;
final data = err.response?.data;
String message = '服务器异常,请稍后重试';
if (data is Map<String, dynamic>) {
message = data['msg'] as String? ?? message;
}
if (statusCode == null) {
return ServerException(
message: message,
code: statusCode,
uuid: uuid,
);
}
switch (statusCode) {
case 400:
return BusinessException(
message: message,
code: statusCode,
uuid: uuid,
);
case 401:
return BusinessException(
message: '登录已过期,请重新登录',
code: statusCode,
uuid: uuid,
);
case 403:
return BusinessException(
message: '没有权限访问',
code: statusCode,
uuid: uuid,
);
case 404:
return ServerException(
message: '请求的资源不存在',
code: statusCode,
uuid: uuid,
);
case >= 500:
return ServerException(
message: message,
code: statusCode,
uuid: uuid,
);
default:
return BusinessException(
message: message,
code: statusCode,
uuid: uuid,
);
}
case DioExceptionType.cancel:
return CancelException(uuid: uuid);
case DioExceptionType.unknown:
if (err.error is SocketException) {
return NetworkException(
message: '网络连接失败,请检查网络后重试',
uuid: uuid,
);
}
return AppException(
message: err.message ?? '未知错误',
code: -1,
uuid: uuid,
);
case DioExceptionType.badCertificate:
return NetworkException(
message: '安全证书验证失败',
uuid: uuid,
);
case DioExceptionType.connectionError:
return NetworkException(
message: '网络连接失败,请检查网络后重试',
uuid: uuid,
);
}
}
void _printErrorLog(DioException err, String uuid, AppException exception) {
print('========== Error START ==========');
print('UUID: $uuid');
print('Error Type: ${err.type}');
print('Error Message: ${err.message}');
print('Exception: $exception');
if (err.response != null) {
print('Status Code: ${err.response?.statusCode}');
print('Response Data: ${err.response?.data}');
}
print('========== Error END ==========');
}
}
import 'package:dio/dio.dart';
import '../storage/storage_service.dart';
class RequestInterceptor extends Interceptor {
final StorageService _storageService;
RequestInterceptor(this._storageService);
@override
Future<void> onRequest(
RequestOptions options, RequestInterceptorHandler handler) async {
final uuid = _generateUuid();
options.extra['uuid'] = uuid;
final Map<String, dynamic> headers = {
'Content-Type': 'application/json',
'Accept': 'application/json',
'X-Request-UUID': uuid,
'X-Timestamp': DateTime.now().millisecondsSinceEpoch.toString(),
};
final token = await _storageService.getToken();
if (token != null && token.isNotEmpty) {
headers['Authorization'] = 'Bearer $token';
}
options.headers.addAll(headers);
_printRequestLog(options, uuid);
handler.next(options);
}
String _generateUuid() {
return '${DateTime.now().millisecondsSinceEpoch}_${_randomString(8)}';
}
String _randomString(int length) {
const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
return List.generate(length, (index) => chars[index % chars.length]).join();
}
void _printRequestLog(RequestOptions options, String uuid) {
print('========== Request START ==========');
print('UUID: $uuid');
print('Method: ${options.method}');
print('URL: ${options.uri}');
print('Headers: ${options.headers}');
if (options.data != null) {
print('Data: ${options.data}');
}
if (options.queryParameters.isNotEmpty) {
print('Params: ${options.queryParameters}');
}
print('========== Request END ==========');
}
}
import 'package:dio/dio.dart';
class ResponseInterceptor extends Interceptor {
@override
void onResponse(Response response, ResponseInterceptorHandler handler) {
final uuid = response.requestOptions.extra['uuid'] as String? ?? '';
_printResponseLog(response, uuid);
handler.next(response);
}
void _printResponseLog(Response response, String uuid) {
print('========== Response START ==========');
print('UUID: $uuid');
print('Status Code: ${response.statusCode}');
print('Data: ${response.data}');
print('========== Response END ==========');
}
}
class Log {
static void info(String message) {
// ignore: avoid_print
print('[INFO] $message');
}
static void error(String message, [dynamic error]) {
// ignore: avoid_print
print('[ERROR] $message');
if (error != null) {
// ignore: avoid_print
print('[ERROR] $error');
}
}
static void request(String message) {
// ignore: avoid_print
print('[REQUEST] $message');
}
static void response(String message) {
// ignore: avoid_print
print('[RESPONSE] $message');
}
}
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
class StorageService {
static const String _keyToken = 'auth_token';
static const String _keyTokenExpiry = 'auth_token_expiry';
static const String _keyRememberUsername = 'remember_username';
static const String _keyRememberPassword = 'remember_password';
static const String _keyRememberEnabled = 'remember_enabled';
final FlutterSecureStorage _storage = const FlutterSecureStorage(
aOptions: AndroidOptions(
encryptedSharedPreferences: true,
),
);
// ==================== Token 相关 ====================
Future<void> saveToken(String token, {int expiryHours = 24}) async {
await _storage.write(key: _keyToken, value: token);
final expiryTime =
DateTime.now().add(Duration(hours: expiryHours)).millisecondsSinceEpoch;
await _storage.write(key: _keyTokenExpiry, value: expiryTime.toString());
}
Future<String?> getToken() async {
return await _storage.read(key: _keyToken);
}
Future<int?> getTokenExpiry() async {
final value = await _storage.read(key: _keyTokenExpiry);
if (value == null) return null;
return int.tryParse(value);
}
Future<bool> isTokenExpired() async {
final expiry = await getTokenExpiry();
if (expiry == null) return false;
return DateTime.now().millisecondsSinceEpoch > expiry;
}
Future<void> deleteToken() async {
await _storage.delete(key: _keyToken);
await _storage.delete(key: _keyTokenExpiry);
}
Future<bool> hasToken() async {
final token = await getToken();
return token != null && token.isNotEmpty;
}
// ==================== 记住密码相关 ====================
Future<void> saveRememberCredentials({
required String username,
required String password,
}) async {
await _storage.write(key: _keyRememberUsername, value: username);
await _storage.write(key: _keyRememberPassword, value: password);
await _storage.write(key: _keyRememberEnabled, value: 'true');
}
Future<Map<String, String?>> getRememberCredentials() async {
final enabled = await _storage.read(key: _keyRememberEnabled);
if (enabled != 'true') {
return {'username': null, 'password': null};
}
final username = await _storage.read(key: _keyRememberUsername);
final password = await _storage.read(key: _keyRememberPassword);
return {'username': username, 'password': password};
}
Future<void> clearRememberCredentials() async {
await _storage.delete(key: _keyRememberUsername);
await _storage.delete(key: _keyRememberPassword);
await _storage.delete(key: _keyRememberEnabled);
}
// ==================== 清空所有数据 ====================
Future<void> clearAll() async {
await _storage.deleteAll();
}
}
import 'dart:math';
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:smart_hotel_app/routes/app_router.gr.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
@RoutePage()
class HomeView extends StatefulWidget {
const HomeView({super.key});
@override
State<HomeView> createState() => _HomeViewState();
}
class _HomeViewState extends State<HomeView> {
@override
Widget build(BuildContext context) {
return CustomScrollView(
slivers: [
// SliverToBoxAdapter(
// child: Container(
// height: ScreenUtil().statusBarHeight,
// width: double.infinity,
// color: Colors.red,
// ),
// ),
// banner区域
SliverToBoxAdapter(
child: Stack(
children: [
Container(
width: double.infinity,
height: 748.h,
decoration: BoxDecoration(
border: Border.all(width: 1.w, color: Colors.red),
color: Colors.yellow,
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(20),
bottomRight: Radius.circular(20)),
gradient: LinearGradient(
stops: [0.65, 1.0],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [Color(0xFF0B392F), Color(0x000B392F)])),
),
Container(
width: double.infinity,
height: 748.h,
padding: EdgeInsets.only(left: 23.w, right: 23.w),
decoration: BoxDecoration(
border: Border.all(width: 1.w, color: Colors.yellow)),
child: Column(
children: [
SizedBox(
height: ScreenUtil().statusBarHeight,
),
Container(
width: double.infinity,
alignment: Alignment.centerLeft,
padding: EdgeInsets.only(top: 23.h),
decoration: BoxDecoration(
border: Border.all(width: 1.w, color: Colors.red)),
child: Image.asset(
"lib/assets/scp_logo.png",
width: 304.w,
height: 54.h,
),
),
Flex(
direction: Axis.horizontal,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
flex: 4,
child: Container(
// width: 238.w,
height: 45.h,
decoration: BoxDecoration(
border: Border.all(
width: 1.w, color: Colors.red)),
child: Row(children: [
Icon(Icons.shop),
SizedBox(
width: 2.w,
),
Text(
"香蜜湖店",
style: TextStyle(
color: Color(0xFFFFFFFF),
fontSize: 32.sp),
),
SizedBox(
width: 5.w,
),
Icon(Icons.arrow_right_rounded)
]),
)),
Expanded(
flex: 6,
child: GestureDetector(
onTap: () {
print("点击搜索");
context.router.push(SearchRoute(searchKey: "hahaha"));
},
child: Container(
// width: 440.w,
height: 68.h,
padding: EdgeInsets.only(left: 20.w),
decoration: BoxDecoration(
border: Border.all(
width: 1.w, color: Colors.blue),
borderRadius:
BorderRadius.all(Radius.circular(36)),
color: Color(0xCCFFFFFF),
),
child: Row(
children: [Icon(Icons.search), Text("搜索")]),
)),
)
],
)
],
))
],
),
)
],
);
}
}
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:smart_hotel_app/routes/app_router.gr.dart';
@RoutePage()
class DefaultLayoutView extends StatefulWidget {
const DefaultLayoutView({super.key});
@override
State<DefaultLayoutView> createState() => _DefaultLayoutViewState();
}
class _DefaultLayoutViewState extends State<DefaultLayoutView> {
@override
Widget build(BuildContext context) {
return AutoTabsScaffold(
routes: const [HomeRoute(), NewsRoute(), MessageRoute(), ProfileRoute()],
bottomNavigationBuilder: (context, tabsRouter) {
return Container(
decoration: const BoxDecoration(
color: Colors.white,
border: Border(
top: BorderSide(color: Color(0xFFE5E5E5), width: 0.5),
),
),
child: BottomNavigationBar(
currentIndex: tabsRouter.activeIndex,
onTap: tabsRouter.setActiveIndex,
elevation: 0,
backgroundColor: Colors.white,
selectedItemColor: Colors.blue,
unselectedItemColor: const Color(0xFF666666),
type: BottomNavigationBarType.fixed,
selectedFontSize: 12,
unselectedFontSize: 12,
items: const [
BottomNavigationBarItem(icon: Icon(Icons.home), label: "首页"),
BottomNavigationBarItem(icon: Icon(Icons.new_label), label: "新闻"),
BottomNavigationBarItem(icon: Icon(Icons.message), label: "消息"),
BottomNavigationBarItem(
icon: Icon(Icons.my_location), label: "我的")
],
),
);
},
);
}
}
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
class BtnLogin extends StatelessWidget {
final bool isLoading;
final VoidCallback onPressed;
const BtnLogin({
super.key,
required this.isLoading,
required this.onPressed,
});
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
height: 70.h,
decoration: BoxDecoration(
color: Color.fromRGBO(89, 109, 243, 1),
borderRadius: BorderRadius.circular(20),
),
child: TextButton(
onPressed: isLoading ? null : onPressed,
child: isLoading
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
color: Colors.white,
),
)
: const Text(
"提交",
style: TextStyle(color: Colors.white),
),
),
);
}
}
\ No newline at end of file
import 'package:flutter/material.dart';
class BtnReset extends StatelessWidget {
final VoidCallback onPressed;
const BtnReset({
super.key,
required this.onPressed,
});
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
height: 50,
decoration: BoxDecoration(
color: Colors.grey,
borderRadius: BorderRadius.circular(20),
),
child: TextButton(
onPressed: onPressed,
child: const Text(
"重置",
style: TextStyle(color: Colors.white),
),
),
);
}
}
\ No newline at end of file
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
class PasswordField extends StatefulWidget {
final TextEditingController controller;
const PasswordField({
super.key,
required this.controller,
});
@override
State<PasswordField> createState() => _PasswordFieldState();
}
class _PasswordFieldState extends State<PasswordField> {
bool _obscureText = true;
void _toggleVisibility() {
setState(() {
_obscureText = !_obscureText;
});
}
@override
Widget build(BuildContext context) {
return TextFormField(
controller: widget.controller,
obscureText: _obscureText,
style: const TextStyle(color: Colors.white, fontSize: 14),
validator: (val) {
if (val == null || val.isEmpty) {
return "密码不能为空";
}
return null;
},
decoration: InputDecoration(
filled: true,
hintText: "请输入密码",
hintStyle: const TextStyle(color: Colors.white70, fontSize: 14),
prefixIcon: const Icon(Icons.lock, color: Colors.white, size: 20),
prefixIconConstraints: const BoxConstraints(minWidth: 40, minHeight: 40),
suffixIcon: IconButton(
icon: Icon(
_obscureText ? Icons.visibility_off : Icons.visibility,
color: Colors.white,
size: 20,
),
onPressed: _toggleVisibility,
),
suffixIconConstraints: const BoxConstraints(minWidth: 40, minHeight: 40),
contentPadding: const EdgeInsets.symmetric(vertical: 8, horizontal: 12),
helperText: " ",
helperStyle: TextStyle(height: 1.1, fontSize: 12.sp),
errorStyle: TextStyle(color: Colors.red, fontSize: 12.sp, height: 1.1),
errorMaxLines: 1,
fillColor: const Color(0xFF1A2B3C),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(25),
borderSide: const BorderSide(color: Colors.white30, width: 1),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(25),
borderSide: const BorderSide(color: Colors.white30, width: 1),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(25),
borderSide: const BorderSide(color: Colors.white, width: 1.5),
),
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(25),
borderSide: const BorderSide(color: Colors.red, width: 1),
),
focusedErrorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(25),
borderSide: const BorderSide(color: Colors.red, width: 1.5),
),
),
);
}
}
\ No newline at end of file
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
class RememberPasswordRow extends StatelessWidget {
final bool value;
final ValueChanged<bool?> onChanged;
final VoidCallback onForgotPassword;
const RememberPasswordRow({
super.key,
required this.value,
required this.onChanged,
required this.onForgotPassword,
});
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Container(
alignment: Alignment.center,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 32,
height: 32,
child: Transform.scale(
scale: 0.7,
child: Checkbox(
value: value,
onChanged: onChanged,
activeColor: const Color.fromRGBO(89, 109, 243, 1),
checkColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(4),
),
side: const BorderSide(color: Colors.white30),
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
),
),
Container(
margin: EdgeInsets.only(bottom: 2.h),
child: Text(
'记住密码',
style: TextStyle(color: Colors.white, fontSize: 18.sp),
),
)
],
),
),
GestureDetector(
onTap: onForgotPassword,
child: Text(
'忘记密码',
style: TextStyle(
color: Color.fromRGBO(89, 109, 243, 1),
fontSize: 18.sp,
),
),
),
],
);
}
}
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
class UsernameField extends StatelessWidget {
final TextEditingController controller;
const UsernameField({
super.key,
required this.controller,
});
@override
Widget build(BuildContext context) {
return TextFormField(
controller: controller,
style: const TextStyle(color: Colors.white, fontSize: 14),
validator: (val) {
if (val == null || val.isEmpty) {
return "用户名不能为空哦";
}
return null;
},
decoration: InputDecoration(
filled: true,
hintText: "请输入用户名",
hintStyle: const TextStyle(color: Colors.white70, fontSize: 14),
prefixIcon: const Icon(Icons.person, color: Colors.white, size: 20),
prefixIconConstraints: const BoxConstraints(minWidth: 40, minHeight: 40),
contentPadding: const EdgeInsets.symmetric(vertical: 8, horizontal: 12),
helperText: " ",
helperStyle: TextStyle(height: 1, fontSize: 12.sp),
errorStyle: TextStyle(color: Colors.red, fontSize: 12.sp, height: 1),
errorMaxLines: 1,
fillColor: const Color(0xFF1A2B3C),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(25),
borderSide: const BorderSide(color: Colors.white30, width: 1),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(25),
borderSide: const BorderSide(color: Colors.white30, width: 1),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(25),
borderSide: const BorderSide(color: Colors.white, width: 1.5),
),
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(25),
borderSide: const BorderSide(color: Colors.red, width: 1),
),
focusedErrorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(25),
borderSide: const BorderSide(color: Colors.red, width: 1.5),
),
),
);
}
}
\ No newline at end of file
import 'package:flutter/material.dart';
import 'package:smart_hotel_app/blocs/auth/auth_bloc.dart';
import 'package:smart_hotel_app/blocs/auth/auth_event.dart';
import 'package:smart_hotel_app/utils/storage/storage_service.dart';
class LoginController {
final AuthBloc authBloc;
final TextEditingController usernameController;
final TextEditingController pwdController;
final GlobalKey<FormState> frmGlobalKey;
final StorageService storageService;
LoginController({
required this.authBloc,
required this.usernameController,
required this.pwdController,
required this.frmGlobalKey,
required this.storageService,
});
void login(GlobalKey<FormState> frmKey, {bool rememberPassword = false}) {
if (frmKey.currentState!.validate()) {
final username = usernameController.text.trim();
final password = pwdController.text.trim();
if (rememberPassword) {
storageService.saveRememberCredentials(
username: username,
password: password,
);
} else {
storageService.clearRememberCredentials();
}
authBloc.add(AuthLoginRequested(username: username, password: password));
}
}
Future<void> loadRememberCredentials() async {
final credentials = await storageService.getRememberCredentials();
if (credentials['username'] != null) {
usernameController.text = credentials['username']!;
}
if (credentials['password'] != null) {
pwdController.text = credentials['password']!;
}
}
void forgotPwd() {
// TODO: 跳转到忘记密码页面
}
void resetFrm() {
usernameController.clear();
pwdController.clear();
frmGlobalKey.currentState?.reset();
}
}
\ No newline at end of file
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:smart_hotel_app/blocs/auth/auth_bloc.dart';
import 'package:smart_hotel_app/blocs/auth/auth_state.dart';
import 'package:smart_hotel_app/utils/storage/storage_service.dart';
import 'package:smart_hotel_app/views/login/child/btn_login.dart';
import 'package:smart_hotel_app/views/login/child/btn_reset.dart';
import 'package:smart_hotel_app/views/login/child/password_field.dart';
import 'package:smart_hotel_app/views/login/child/remember_password_row.dart';
import 'package:smart_hotel_app/views/login/child/username_field.dart';
import 'package:smart_hotel_app/views/login/controller.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
@RoutePage()
class LoginView extends StatefulWidget {
const LoginView({super.key});
@override
State<LoginView> createState() => _LoginViewState();
}
class _LoginViewState extends State<LoginView> {
late final LoginController _loginController;
final _frmGlobalKey = GlobalKey<FormState>();
final _txtUserNameController = TextEditingController();
final _txtPwdController = TextEditingController();
bool _rememberPassword = false;
@override
void initState() {
super.initState();
_loginController = LoginController(
authBloc: context.read<AuthBloc>(),
usernameController: _txtUserNameController,
pwdController: _txtPwdController,
frmGlobalKey: _frmGlobalKey,
storageService: StorageService(),
);
_loadSavedCredentials();
}
Future<void> _loadSavedCredentials() async {
final credentials = await _loginController.storageService.getRememberCredentials();
if (credentials['username'] != null) {
setState(() {
_rememberPassword = true;
_txtUserNameController.text = credentials['username']!;
_txtPwdController.text = credentials['password'] ?? '';
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
padding: EdgeInsets.only(left: 50.w, right: 50.w),
decoration: BoxDecoration(
color: Color(0xFF1A2B3C),
),
child: Column(
children: [
Container(
margin: EdgeInsets.only(top: 150.h, bottom: 150.h),
child: Column(
children: [
Image.asset(
'lib/assets/znjd_logo.png',
width: 100,
height: 100,
),
const SizedBox(height: 10),
Text("智能酒店管理系统", style: TextStyle(color: Colors.white, fontSize: 20.sp),),
Text("Intelligent Hotel Management System", style: TextStyle(color: Colors.white, fontSize: 20.sp),),
],
),
),
Container(
child: Form(
key: _frmGlobalKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
UsernameField(
controller: _txtUserNameController,
),
const SizedBox(height: 10),
PasswordField(
controller: _txtPwdController,
),
// const SizedBox(height: 5),
RememberPasswordRow(
value: _rememberPassword,
onChanged: (val) {
setState(() {
_rememberPassword = val ?? false;
});
},
onForgotPassword: _loginController.forgotPwd,
),
const SizedBox(height: 20),
BlocConsumer<AuthBloc, AuthState>(
listener: (context, state) {
if (state is AuthFailure) {
Fluttertoast.showToast(
msg: state.error,
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.CENTER,
timeInSecForIosWeb: 2,
backgroundColor: Colors.red,
textColor: Colors.white,
fontSize: 16.0);
}
},
builder: (context, state) {
return BtnLogin(
isLoading: state is AuthLoading,
onPressed: () => _loginController.login(
_frmGlobalKey,
rememberPassword: _rememberPassword,
),
);
},
),
],
),
),
),
])
),
);
}
}
\ No newline at end of file
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
@RoutePage()
class MessageView extends StatefulWidget {
const MessageView({super.key});
@override
State<MessageView> createState() => _MessageViewState();
}
class _MessageViewState extends State<MessageView> {
@override
Widget build(BuildContext context) {
return const Center(
child: Text('MessageView'),
);
}
}
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
@RoutePage()
class NewsView extends StatefulWidget {
const NewsView({super.key});
@override
State<NewsView> createState() => _NewsViewState();
}
class _NewsViewState extends State<NewsView> {
@override
Widget build(BuildContext context) {
return const Center(
child: Text('NewsView'),
);
}
}
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
@RoutePage()
class ProfileView extends StatefulWidget {
const ProfileView({super.key});
@override
State<ProfileView> createState() => _ProfileViewState();
}
class _ProfileViewState extends State<ProfileView> {
@override
Widget build(BuildContext context) {
return const Center(
child: Text('ProfileView'),
);
}
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment