Commit c2036814 authored by 张宏's avatar 张宏

基础框架结构

parents
{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"flutter_secure_storage","path":"/Users/zh/.pub-cache/hosted/pub.dev/flutter_secure_storage-9.2.4/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"fluttertoast","path":"/Users/zh/.pub-cache/hosted/pub.dev/fluttertoast-9.1.0/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_foundation","path":"/Users/zh/.pub-cache/hosted/pub.dev/path_provider_foundation-2.6.0/","native_build":false,"dependencies":[],"dev_dependency":false}],"android":[{"name":"flutter_secure_storage","path":"/Users/zh/.pub-cache/hosted/pub.dev/flutter_secure_storage-9.2.4/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"fluttertoast","path":"/Users/zh/.pub-cache/hosted/pub.dev/fluttertoast-9.1.0/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"jni","path":"/Users/zh/.pub-cache/hosted/pub.dev/jni-1.0.0/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"jni_flutter","path":"/Users/zh/.pub-cache/hosted/pub.dev/jni_flutter-1.0.1/","native_build":true,"dependencies":["jni"],"dev_dependency":false},{"name":"path_provider_android","path":"/Users/zh/.pub-cache/hosted/pub.dev/path_provider_android-2.3.1/","native_build":false,"dependencies":["jni","jni_flutter"],"dev_dependency":false}],"macos":[{"name":"flutter_secure_storage_macos","path":"/Users/zh/.pub-cache/hosted/pub.dev/flutter_secure_storage_macos-3.1.3/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_foundation","path":"/Users/zh/.pub-cache/hosted/pub.dev/path_provider_foundation-2.6.0/","native_build":false,"dependencies":[],"dev_dependency":false}],"linux":[{"name":"flutter_secure_storage_linux","path":"/Users/zh/.pub-cache/hosted/pub.dev/flutter_secure_storage_linux-1.2.3/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"jni","path":"/Users/zh/.pub-cache/hosted/pub.dev/jni-1.0.0/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_linux","path":"/Users/zh/.pub-cache/hosted/pub.dev/path_provider_linux-2.2.1/","native_build":false,"dependencies":[],"dev_dependency":false}],"windows":[{"name":"flutter_secure_storage_windows","path":"/Users/zh/.pub-cache/hosted/pub.dev/flutter_secure_storage_windows-3.1.2/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"jni","path":"/Users/zh/.pub-cache/hosted/pub.dev/jni-1.0.0/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_windows","path":"/Users/zh/.pub-cache/hosted/pub.dev/path_provider_windows-2.3.0/","native_build":false,"dependencies":[],"dev_dependency":false}],"web":[{"name":"flutter_secure_storage_web","path":"/Users/zh/.pub-cache/hosted/pub.dev/flutter_secure_storage_web-1.2.1/","dependencies":[],"dev_dependency":false},{"name":"fluttertoast","path":"/Users/zh/.pub-cache/hosted/pub.dev/fluttertoast-9.1.0/","dependencies":[],"dev_dependency":false}]},"dependencyGraph":[{"name":"flutter_secure_storage","dependencies":["flutter_secure_storage_linux","flutter_secure_storage_macos","flutter_secure_storage_web","flutter_secure_storage_windows"]},{"name":"flutter_secure_storage_linux","dependencies":[]},{"name":"flutter_secure_storage_macos","dependencies":[]},{"name":"flutter_secure_storage_web","dependencies":[]},{"name":"flutter_secure_storage_windows","dependencies":["path_provider"]},{"name":"fluttertoast","dependencies":[]},{"name":"jni","dependencies":[]},{"name":"jni_flutter","dependencies":["jni"]},{"name":"path_provider","dependencies":["path_provider_android","path_provider_foundation","path_provider_linux","path_provider_windows"]},{"name":"path_provider_android","dependencies":["jni","jni_flutter"]},{"name":"path_provider_foundation","dependencies":[]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_windows","dependencies":[]}],"date_created":"2026-06-15 16:07:29.493993","version":"3.41.9","swift_package_manager_enabled":{"ios":false,"macos":false}}
\ No newline at end of file
{
"flutter": "3.41.9"
}
\ No newline at end of file
# FVM Version Cache
.fvm/
# claude code
.claude/
.trae/
.dart_tool/
build/
pubspec.lock
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "laki_icu_app (dev)",
"request": "launch",
"type": "dart",
"args": [
"--dart-define-from-file=env/dev.json",
"--dart-define=CHANNEL=default"
]
},
{
"name": "laki_icu_app (test)",
"request": "launch",
"type": "dart",
"args": [
"--dart-define-from-file=env/test.json",
"--dart-define=CHANNEL=default"
]
},
]
}
\ No newline at end of file
{
"dart.flutterSdkPath": ".fvm/versions/3.41.9"
}
\ No newline at end of file
## 任务分解及实现明细
## 要求
1.view页面必须以_view结尾
## 其他
### 路由生产
dart run build_runner build
dart run build_runner watch
## 打包
### ✅ dev
flutter build apk --dart-define=APP_ENV=dev --flavor dev
### ✅ staging(不要写 test)
flutter build apk --dart-define=APP_ENV=staging --flavor staging
### ✅ prod
flutter build apk --dart-define=APP_ENV=prod --flavor prod
---
### 版本处理
版本对齐后,必须清除旧缓存,否则 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
kotlin version: 2.2.20
error message: java.nio.file.FileSystemException: /Users/zh/Library/Application Support/kotlin/daemon/kotlin-daemon-client-tsmarker7986096895907272988.tmp: Operation not permitted
error message: Daemon compilation failed: Could not connect to Kotlin compile daemon
java.lang.RuntimeException: Could not connect to Kotlin compile daemon
at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemon(GradleKotlinCompilerWork.kt:214)
at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemonOrFallbackImpl(GradleKotlinCompilerWork.kt:159)
at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.run(GradleKotlinCompilerWork.kt:111)
at org.jetbrains.kotlin.compilerRunner.GradleCompilerRunnerWithWorkers$GradleKotlinCompilerWorkAction.execute(GradleCompilerRunnerWithWorkers.kt:74)
at org.gradle.workers.internal.DefaultWorkerServer.execute(DefaultWorkerServer.java:63)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:66)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:62)
at org.gradle.internal.classloader.ClassLoaderUtils.executeInClassloader(ClassLoaderUtils.java:100)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1.lambda$execute$0(NoIsolationWorkerFactory.java:62)
at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:44)
at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:41)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:210)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:205)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:67)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:60)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:167)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:60)
at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:54)
at org.gradle.workers.internal.AbstractWorker.executeWrappedInBuildOperation(AbstractWorker.java:41)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1.execute(NoIsolationWorkerFactory.java:59)
at org.gradle.workers.internal.DefaultWorkerExecutor.lambda$submitWork$0(DefaultWorkerExecutor.java:174)
at java.base/java.util.concurrent.FutureTask.run(Unknown Source)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runExecution(DefaultConditionalExecutionQueue.java:194)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.access$700(DefaultConditionalExecutionQueue.java:127)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner$1.run(DefaultConditionalExecutionQueue.java:169)
at org.gradle.internal.Factories$1.create(Factories.java:31)
at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:263)
at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:127)
at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:132)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runBatch(DefaultConditionalExecutionQueue.java:164)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.run(DefaultConditionalExecutionQueue.java:133)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source)
at java.base/java.util.concurrent.FutureTask.run(Unknown Source)
at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
at org.gradle.internal.concurrent.AbstractManagedExecutor$1.run(AbstractManagedExecutor.java:48)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at java.base/java.lang.Thread.run(Unknown Source)
kotlin version: 2.2.20
error message: java.nio.file.FileSystemException: /Users/zh/Library/Application Support/kotlin/daemon/kotlin-daemon-client-tsmarker10873538016775179128.tmp: Operation not permitted
error message: Daemon compilation failed: Could not connect to Kotlin compile daemon
java.lang.RuntimeException: Could not connect to Kotlin compile daemon
at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemon(GradleKotlinCompilerWork.kt:214)
at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemonOrFallbackImpl(GradleKotlinCompilerWork.kt:159)
at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.run(GradleKotlinCompilerWork.kt:111)
at org.jetbrains.kotlin.compilerRunner.GradleCompilerRunnerWithWorkers$GradleKotlinCompilerWorkAction.execute(GradleCompilerRunnerWithWorkers.kt:74)
at org.gradle.workers.internal.DefaultWorkerServer.execute(DefaultWorkerServer.java:63)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:66)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:62)
at org.gradle.internal.classloader.ClassLoaderUtils.executeInClassloader(ClassLoaderUtils.java:100)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1.lambda$execute$0(NoIsolationWorkerFactory.java:62)
at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:44)
at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:41)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:210)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:205)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:67)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:60)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:167)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:60)
at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:54)
at org.gradle.workers.internal.AbstractWorker.executeWrappedInBuildOperation(AbstractWorker.java:41)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1.execute(NoIsolationWorkerFactory.java:59)
at org.gradle.workers.internal.DefaultWorkerExecutor.lambda$submitWork$0(DefaultWorkerExecutor.java:174)
at java.base/java.util.concurrent.FutureTask.run(Unknown Source)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runExecution(DefaultConditionalExecutionQueue.java:194)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.access$700(DefaultConditionalExecutionQueue.java:127)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner$1.run(DefaultConditionalExecutionQueue.java:169)
at org.gradle.internal.Factories$1.create(Factories.java:31)
at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:263)
at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:127)
at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:132)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runBatch(DefaultConditionalExecutionQueue.java:164)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.run(DefaultConditionalExecutionQueue.java:133)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source)
at java.base/java.util.concurrent.FutureTask.run(Unknown Source)
at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
at org.gradle.internal.concurrent.AbstractManagedExecutor$1.run(AbstractManagedExecutor.java:48)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at java.base/java.lang.Thread.run(Unknown Source)
kotlin version: 2.2.20
error message: java.nio.file.FileSystemException: /Users/zh/Library/Application Support/kotlin/daemon/kotlin-daemon-client-tsmarker4138795620887972361.tmp: Operation not permitted
error message: Daemon compilation failed: Could not connect to Kotlin compile daemon
java.lang.RuntimeException: Could not connect to Kotlin compile daemon
at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemon(GradleKotlinCompilerWork.kt:214)
at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemonOrFallbackImpl(GradleKotlinCompilerWork.kt:159)
at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.run(GradleKotlinCompilerWork.kt:111)
at org.jetbrains.kotlin.compilerRunner.GradleCompilerRunnerWithWorkers$GradleKotlinCompilerWorkAction.execute(GradleCompilerRunnerWithWorkers.kt:74)
at org.gradle.workers.internal.DefaultWorkerServer.execute(DefaultWorkerServer.java:63)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:66)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:62)
at org.gradle.internal.classloader.ClassLoaderUtils.executeInClassloader(ClassLoaderUtils.java:100)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1.lambda$execute$0(NoIsolationWorkerFactory.java:62)
at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:44)
at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:41)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:210)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:205)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:67)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:60)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:167)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:60)
at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:54)
at org.gradle.workers.internal.AbstractWorker.executeWrappedInBuildOperation(AbstractWorker.java:41)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1.execute(NoIsolationWorkerFactory.java:59)
at org.gradle.workers.internal.DefaultWorkerExecutor.lambda$submitWork$0(DefaultWorkerExecutor.java:174)
at java.base/java.util.concurrent.FutureTask.run(Unknown Source)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runExecution(DefaultConditionalExecutionQueue.java:194)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.access$700(DefaultConditionalExecutionQueue.java:127)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner$1.run(DefaultConditionalExecutionQueue.java:169)
at org.gradle.internal.Factories$1.create(Factories.java:31)
at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:263)
at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:127)
at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:132)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runBatch(DefaultConditionalExecutionQueue.java:164)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.run(DefaultConditionalExecutionQueue.java:133)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source)
at java.base/java.util.concurrent.FutureTask.run(Unknown Source)
at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
at org.gradle.internal.concurrent.AbstractManagedExecutor$1.run(AbstractManagedExecutor.java:48)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at java.base/java.lang.Thread.run(Unknown Source)
kotlin version: 2.2.20
error message: java.nio.file.FileSystemException: /Users/zh/Library/Application Support/kotlin/daemon/kotlin-daemon-client-tsmarker16051873057854116263.tmp: Operation not permitted
error message: Daemon compilation failed: Could not connect to Kotlin compile daemon
java.lang.RuntimeException: Could not connect to Kotlin compile daemon
at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemon(GradleKotlinCompilerWork.kt:214)
at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemonOrFallbackImpl(GradleKotlinCompilerWork.kt:159)
at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.run(GradleKotlinCompilerWork.kt:111)
at org.jetbrains.kotlin.compilerRunner.GradleCompilerRunnerWithWorkers$GradleKotlinCompilerWorkAction.execute(GradleCompilerRunnerWithWorkers.kt:74)
at org.gradle.workers.internal.DefaultWorkerServer.execute(DefaultWorkerServer.java:63)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:66)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:62)
at org.gradle.internal.classloader.ClassLoaderUtils.executeInClassloader(ClassLoaderUtils.java:100)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1.lambda$execute$0(NoIsolationWorkerFactory.java:62)
at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:44)
at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:41)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:210)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:205)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:67)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:60)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:167)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:60)
at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:54)
at org.gradle.workers.internal.AbstractWorker.executeWrappedInBuildOperation(AbstractWorker.java:41)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1.execute(NoIsolationWorkerFactory.java:59)
at org.gradle.workers.internal.DefaultWorkerExecutor.lambda$submitWork$0(DefaultWorkerExecutor.java:174)
at java.base/java.util.concurrent.FutureTask.run(Unknown Source)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runExecution(DefaultConditionalExecutionQueue.java:194)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.access$700(DefaultConditionalExecutionQueue.java:127)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner$1.run(DefaultConditionalExecutionQueue.java:169)
at org.gradle.internal.Factories$1.create(Factories.java:31)
at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:263)
at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:127)
at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:132)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runBatch(DefaultConditionalExecutionQueue.java:164)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.run(DefaultConditionalExecutionQueue.java:133)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source)
at java.base/java.util.concurrent.FutureTask.run(Unknown Source)
at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
at org.gradle.internal.concurrent.AbstractManagedExecutor$1.run(AbstractManagedExecutor.java:48)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at java.base/java.lang.Thread.run(Unknown Source)
kotlin version: 2.2.20
error message: java.nio.file.FileSystemException: /Users/zh/Library/Application Support/kotlin/daemon/kotlin-daemon-client-tsmarker7076556644550313058.tmp: Operation not permitted
error message: Daemon compilation failed: Could not connect to Kotlin compile daemon
java.lang.RuntimeException: Could not connect to Kotlin compile daemon
at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemon(GradleKotlinCompilerWork.kt:214)
at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemonOrFallbackImpl(GradleKotlinCompilerWork.kt:159)
at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.run(GradleKotlinCompilerWork.kt:111)
at org.jetbrains.kotlin.compilerRunner.GradleCompilerRunnerWithWorkers$GradleKotlinCompilerWorkAction.execute(GradleCompilerRunnerWithWorkers.kt:74)
at org.gradle.workers.internal.DefaultWorkerServer.execute(DefaultWorkerServer.java:63)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:66)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:62)
at org.gradle.internal.classloader.ClassLoaderUtils.executeInClassloader(ClassLoaderUtils.java:100)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1.lambda$execute$0(NoIsolationWorkerFactory.java:62)
at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:44)
at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:41)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:210)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:205)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:67)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:60)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:167)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:60)
at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:54)
at org.gradle.workers.internal.AbstractWorker.executeWrappedInBuildOperation(AbstractWorker.java:41)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1.execute(NoIsolationWorkerFactory.java:59)
at org.gradle.workers.internal.DefaultWorkerExecutor.lambda$submitWork$0(DefaultWorkerExecutor.java:174)
at java.base/java.util.concurrent.FutureTask.run(Unknown Source)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runExecution(DefaultConditionalExecutionQueue.java:194)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.access$700(DefaultConditionalExecutionQueue.java:127)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner$1.run(DefaultConditionalExecutionQueue.java:169)
at org.gradle.internal.Factories$1.create(Factories.java:31)
at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:263)
at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:127)
at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:132)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runBatch(DefaultConditionalExecutionQueue.java:164)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.run(DefaultConditionalExecutionQueue.java:133)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source)
at java.base/java.util.concurrent.FutureTask.run(Unknown Source)
at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
at org.gradle.internal.concurrent.AbstractManagedExecutor$1.run(AbstractManagedExecutor.java:48)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at java.base/java.lang.Thread.run(Unknown Source)
kotlin version: 2.2.20
error message: java.nio.file.FileSystemException: /Users/zh/Library/Application Support/kotlin/daemon/kotlin-daemon-client-tsmarker502891341984223454.tmp: Operation not permitted
error message: Daemon compilation failed: Could not connect to Kotlin compile daemon
java.lang.RuntimeException: Could not connect to Kotlin compile daemon
at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemon(GradleKotlinCompilerWork.kt:214)
at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemonOrFallbackImpl(GradleKotlinCompilerWork.kt:159)
at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.run(GradleKotlinCompilerWork.kt:111)
at org.jetbrains.kotlin.compilerRunner.GradleCompilerRunnerWithWorkers$GradleKotlinCompilerWorkAction.execute(GradleCompilerRunnerWithWorkers.kt:74)
at org.gradle.workers.internal.DefaultWorkerServer.execute(DefaultWorkerServer.java:63)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:66)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:62)
at org.gradle.internal.classloader.ClassLoaderUtils.executeInClassloader(ClassLoaderUtils.java:100)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1.lambda$execute$0(NoIsolationWorkerFactory.java:62)
at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:44)
at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:41)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:210)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:205)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:67)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:60)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:167)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:60)
at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:54)
at org.gradle.workers.internal.AbstractWorker.executeWrappedInBuildOperation(AbstractWorker.java:41)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1.execute(NoIsolationWorkerFactory.java:59)
at org.gradle.workers.internal.DefaultWorkerExecutor.lambda$submitWork$0(DefaultWorkerExecutor.java:174)
at java.base/java.util.concurrent.FutureTask.run(Unknown Source)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runExecution(DefaultConditionalExecutionQueue.java:194)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.access$700(DefaultConditionalExecutionQueue.java:127)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner$1.run(DefaultConditionalExecutionQueue.java:169)
at org.gradle.internal.Factories$1.create(Factories.java:31)
at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:263)
at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:127)
at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:132)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runBatch(DefaultConditionalExecutionQueue.java:164)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.run(DefaultConditionalExecutionQueue.java:133)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source)
at java.base/java.util.concurrent.FutureTask.run(Unknown Source)
at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
at org.gradle.internal.concurrent.AbstractManagedExecutor$1.run(AbstractManagedExecutor.java:48)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at java.base/java.lang.Thread.run(Unknown Source)
kotlin version: 2.2.20
error message: java.nio.file.FileSystemException: /Users/zh/Library/Application Support/kotlin/daemon/kotlin-daemon-client-tsmarker3159307626954874882.tmp: Operation not permitted
error message: Daemon compilation failed: Could not connect to Kotlin compile daemon
java.lang.RuntimeException: Could not connect to Kotlin compile daemon
at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemon(GradleKotlinCompilerWork.kt:214)
at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemonOrFallbackImpl(GradleKotlinCompilerWork.kt:159)
at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.run(GradleKotlinCompilerWork.kt:111)
at org.jetbrains.kotlin.compilerRunner.GradleCompilerRunnerWithWorkers$GradleKotlinCompilerWorkAction.execute(GradleCompilerRunnerWithWorkers.kt:74)
at org.gradle.workers.internal.DefaultWorkerServer.execute(DefaultWorkerServer.java:63)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:66)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:62)
at org.gradle.internal.classloader.ClassLoaderUtils.executeInClassloader(ClassLoaderUtils.java:100)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1.lambda$execute$0(NoIsolationWorkerFactory.java:62)
at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:44)
at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:41)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:210)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:205)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:67)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:60)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:167)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:60)
at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:54)
at org.gradle.workers.internal.AbstractWorker.executeWrappedInBuildOperation(AbstractWorker.java:41)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1.execute(NoIsolationWorkerFactory.java:59)
at org.gradle.workers.internal.DefaultWorkerExecutor.lambda$submitWork$0(DefaultWorkerExecutor.java:174)
at java.base/java.util.concurrent.FutureTask.run(Unknown Source)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runExecution(DefaultConditionalExecutionQueue.java:194)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.access$700(DefaultConditionalExecutionQueue.java:127)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner$1.run(DefaultConditionalExecutionQueue.java:169)
at org.gradle.internal.Factories$1.create(Factories.java:31)
at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:263)
at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:127)
at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:132)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runBatch(DefaultConditionalExecutionQueue.java:164)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.run(DefaultConditionalExecutionQueue.java:133)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source)
at java.base/java.util.concurrent.FutureTask.run(Unknown Source)
at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
at org.gradle.internal.concurrent.AbstractManagedExecutor$1.run(AbstractManagedExecutor.java:48)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at java.base/java.lang.Thread.run(Unknown Source)
kotlin version: 2.2.20
error message: java.nio.file.FileSystemException: /Users/zh/Library/Application Support/kotlin/daemon/kotlin-daemon-client-tsmarker9022342056352800620.tmp: Operation not permitted
error message: Daemon compilation failed: Could not connect to Kotlin compile daemon
java.lang.RuntimeException: Could not connect to Kotlin compile daemon
at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemon(GradleKotlinCompilerWork.kt:214)
at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemonOrFallbackImpl(GradleKotlinCompilerWork.kt:159)
at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.run(GradleKotlinCompilerWork.kt:111)
at org.jetbrains.kotlin.compilerRunner.GradleCompilerRunnerWithWorkers$GradleKotlinCompilerWorkAction.execute(GradleCompilerRunnerWithWorkers.kt:74)
at org.gradle.workers.internal.DefaultWorkerServer.execute(DefaultWorkerServer.java:63)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:66)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:62)
at org.gradle.internal.classloader.ClassLoaderUtils.executeInClassloader(ClassLoaderUtils.java:100)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1.lambda$execute$0(NoIsolationWorkerFactory.java:62)
at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:44)
at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:41)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:210)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:205)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:67)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:60)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:167)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:60)
at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:54)
at org.gradle.workers.internal.AbstractWorker.executeWrappedInBuildOperation(AbstractWorker.java:41)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1.execute(NoIsolationWorkerFactory.java:59)
at org.gradle.workers.internal.DefaultWorkerExecutor.lambda$submitWork$0(DefaultWorkerExecutor.java:174)
at java.base/java.util.concurrent.FutureTask.run(Unknown Source)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runExecution(DefaultConditionalExecutionQueue.java:194)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.access$700(DefaultConditionalExecutionQueue.java:127)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner$1.run(DefaultConditionalExecutionQueue.java:169)
at org.gradle.internal.Factories$1.create(Factories.java:31)
at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:263)
at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:127)
at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:132)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runBatch(DefaultConditionalExecutionQueue.java:164)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.run(DefaultConditionalExecutionQueue.java:133)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source)
at java.base/java.util.concurrent.FutureTask.run(Unknown Source)
at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
at org.gradle.internal.concurrent.AbstractManagedExecutor$1.run(AbstractManagedExecutor.java:48)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at java.base/java.lang.Thread.run(Unknown Source)
kotlin version: 2.2.20
error message: java.nio.file.FileSystemException: /Users/zh/Library/Application Support/kotlin/daemon/kotlin-daemon-client-tsmarker7283588154690007197.tmp: Operation not permitted
error message: Daemon compilation failed: Could not connect to Kotlin compile daemon
java.lang.RuntimeException: Could not connect to Kotlin compile daemon
at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemon(GradleKotlinCompilerWork.kt:214)
at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemonOrFallbackImpl(GradleKotlinCompilerWork.kt:159)
at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.run(GradleKotlinCompilerWork.kt:111)
at org.jetbrains.kotlin.compilerRunner.GradleCompilerRunnerWithWorkers$GradleKotlinCompilerWorkAction.execute(GradleCompilerRunnerWithWorkers.kt:74)
at org.gradle.workers.internal.DefaultWorkerServer.execute(DefaultWorkerServer.java:63)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:66)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:62)
at org.gradle.internal.classloader.ClassLoaderUtils.executeInClassloader(ClassLoaderUtils.java:100)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1.lambda$execute$0(NoIsolationWorkerFactory.java:62)
at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:44)
at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:41)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:210)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:205)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:67)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:60)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:167)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:60)
at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:54)
at org.gradle.workers.internal.AbstractWorker.executeWrappedInBuildOperation(AbstractWorker.java:41)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1.execute(NoIsolationWorkerFactory.java:59)
at org.gradle.workers.internal.DefaultWorkerExecutor.lambda$submitWork$0(DefaultWorkerExecutor.java:174)
at java.base/java.util.concurrent.FutureTask.run(Unknown Source)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runExecution(DefaultConditionalExecutionQueue.java:194)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.access$700(DefaultConditionalExecutionQueue.java:127)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner$1.run(DefaultConditionalExecutionQueue.java:169)
at org.gradle.internal.Factories$1.create(Factories.java:31)
at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:263)
at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:127)
at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:132)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runBatch(DefaultConditionalExecutionQueue.java:164)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.run(DefaultConditionalExecutionQueue.java:133)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source)
at java.base/java.util.concurrent.FutureTask.run(Unknown Source)
at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
at org.gradle.internal.concurrent.AbstractManagedExecutor$1.run(AbstractManagedExecutor.java:48)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at java.base/java.lang.Thread.run(Unknown Source)
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
}
flavorDimensions "environment"
productFlavors {
dev {
dimension "environment"
applicationIdSuffix ".dev"
versionNameSuffix "-dev"
resValue "string", "app_name", "智能酒店-dev"
}
staging {
dimension "environment"
applicationIdSuffix ".staging"
versionNameSuffix "-staging"
resValue "string", "app_name", "智能酒店-staging"
}
prod {
dimension "environment"
applicationIdSuffix ""
versionNameSuffix ""
resValue "string", "app_name", "智能酒店"
}
}
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
}
}
// 自定义 APK 输出文件名
applicationVariants.all { variant ->
variant.outputs.all {
if (variant.flavorName == "dev" || variant.flavorName == "staging") {
outputFileName = "app-${variant.flavorName}.apk"
} else {
outputFileName = "app-${variant.flavorName}-${variant.buildType.name}.apk"
}
}
}
}
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="@string/app_name"
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" />
<item>
<bitmap
android:gravity="center"
android:src="@drawable/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" />
<item>
<bitmap
android:gravity="center"
android:src="@drawable/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>
<string name="app_name">智能酒店</string>
</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 = '2.2.20'
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.13-bin.zip
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")
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="FacetManager">
<facet type="android" name="Android">
<configuration>
<option name="ALLOW_USER_CONFIGURATION" value="false" />
<option name="GEN_FOLDER_RELATIVE_PATH_APT" value="/gen" />
<option name="GEN_FOLDER_RELATIVE_PATH_AIDL" value="/gen" />
<option name="MANIFEST_FILE_RELATIVE_PATH" value="/app/src/main/AndroidManifest.xml" />
<option name="RES_FOLDER_RELATIVE_PATH" value="/app/src/main/res" />
<option name="ASSETS_FOLDER_RELATIVE_PATH" value="/app/src/main/assets" />
<option name="LIBS_FOLDER_RELATIVE_PATH" value="/app/src/main/libs" />
<option name="PROGUARD_LOGS_FOLDER_RELATIVE_PATH" value="/app/src/main/proguard_logs" />
</configuration>
</facet>
</component>
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/app/src/main/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/app/src/main/kotlin" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/gen" isTestSource="false" generated="true" />
</content>
<orderEntry type="jdk" jdkName="Android API 24 Platform" jdkType="Android SDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="Flutter for Android" level="project" />
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
</component>
</module>
description: This file stores settings for Dart & Flutter DevTools.
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
extensions:
{
"APP_ENV": "dev",
"SERVICE_URL": "http://121.41.57.178:9090"
}
{
"APP_ENV": "test",
"SERVICE_URL": "http://localhost:8080"
}
**/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>
</dict>
</plist>
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
FLTEnableImpeller=NO
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
FLTEnableImpeller=NO
# 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!
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
DEPENDENCIES:
- Flutter (from `Flutter`)
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
- fluttertoast (from `.symlinks/plugins/fluttertoast/ios`)
EXTERNAL SOURCES:
Flutter:
:path: Flutter
flutter_secure_storage:
:path: ".symlinks/plugins/flutter_secure_storage/ios"
fluttertoast:
:path: ".symlinks/plugins/fluttertoast/ios"
SPEC CHECKSUMS:
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
flutter_secure_storage: 1ed9476fba7e7a782b22888f956cce43e2c62f13
fluttertoast: fe6790210fdba20801685be946e3a2124b72eef5
PODFILE CHECKSUM: 4f1c12611da7338d21589c0b2ecd6bd20b109694
COCOAPODS: 1.16.2
// !$*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 */; };
D5C802D83219AEE785935A60 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C0943F033B4C937D3DE2DF11 /* Pods_RunnerTests.framework */; };
EE639139734B4C96126F3B83 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D781F8E4B0863CE311979134 /* Pods_Runner.framework */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 97C146E61CF9000F007C117D /* Project object */;
proxyType = 1;
remoteGlobalIDString = 97C146ED1CF9000F007C117D;
remoteInfo = Runner;
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
2443BDB972DCE9B29147B4BC /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
268EEEECBBE3E978DEA18EED /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; };
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
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 = "<group>"; };
586D230F291BC84B8E844455 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
85FEE81D4B5ACA5ED89F3625 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
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 = "<group>"; };
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
B5D9CB5DEF01982D88B62D1D /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
C0943F033B4C937D3DE2DF11 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
D781F8E4B0863CE311979134 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
FB75E68007A0827C8670AC20 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
09D5A5EC9CBEF82FCCB7F96A /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
D5C802D83219AEE785935A60 /* Pods_RunnerTests.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EB1CF9000F007C117D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
EE639139734B4C96126F3B83 /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
0F60C9CEFB6465B7793E8B4D /* Pods */ = {
isa = PBXGroup;
children = (
85FEE81D4B5ACA5ED89F3625 /* Pods-Runner.debug.xcconfig */,
586D230F291BC84B8E844455 /* Pods-Runner.release.xcconfig */,
2443BDB972DCE9B29147B4BC /* Pods-Runner.profile.xcconfig */,
FB75E68007A0827C8670AC20 /* Pods-RunnerTests.debug.xcconfig */,
B5D9CB5DEF01982D88B62D1D /* Pods-RunnerTests.release.xcconfig */,
268EEEECBBE3E978DEA18EED /* Pods-RunnerTests.profile.xcconfig */,
);
name = Pods;
path = Pods;
sourceTree = "<group>";
};
331C8082294A63A400263BE5 /* RunnerTests */ = {
isa = PBXGroup;
children = (
331C807B294A618700263BE5 /* RunnerTests.swift */,
);
path = RunnerTests;
sourceTree = "<group>";
};
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
9740EEB31CF90195004384FC /* Generated.xcconfig */,
);
name = Flutter;
sourceTree = "<group>";
};
97C146E51CF9000F007C117D = {
isa = PBXGroup;
children = (
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
331C8082294A63A400263BE5 /* RunnerTests */,
0F60C9CEFB6465B7793E8B4D /* Pods */,
D9CC5E926AE246E7CFB0524F /* Frameworks */,
);
sourceTree = "<group>";
};
97C146EF1CF9000F007C117D /* Products */ = {
isa = PBXGroup;
children = (
97C146EE1CF9000F007C117D /* Runner.app */,
331C8081294A63A400263BE5 /* RunnerTests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
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 = "<group>";
};
D9CC5E926AE246E7CFB0524F /* Frameworks */ = {
isa = PBXGroup;
children = (
D781F8E4B0863CE311979134 /* Pods_Runner.framework */,
C0943F033B4C937D3DE2DF11 /* Pods_RunnerTests.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
331C8080294A63A400263BE5 /* RunnerTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
buildPhases = (
35602183E27ADC71E335DD69 /* [CP] Check Pods Manifest.lock */,
331C807D294A63A400263BE5 /* Sources */,
331C807F294A63A400263BE5 /* Resources */,
09D5A5EC9CBEF82FCCB7F96A /* Frameworks */,
);
buildRules = (
);
dependencies = (
331C8086294A63A400263BE5 /* PBXTargetDependency */,
);
name = RunnerTests;
productName = RunnerTests;
productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
97C146ED1CF9000F007C117D /* Runner */ = {
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
7C459C0FBCC1A576B4204836 /* [CP] Check Pods Manifest.lock */,
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
56DDF7F4C644F7A96944CE16 /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
dependencies = (
);
name = Runner;
productName = Runner;
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = YES;
LastUpgradeCheck = 1510;
ORGANIZATIONNAME = "";
TargetAttributes = {
331C8080294A63A400263BE5 = {
CreatedOnToolsVersion = 14.0;
TestTargetID = 97C146ED1CF9000F007C117D;
};
97C146ED1CF9000F007C117D = {
CreatedOnToolsVersion = 7.3.1;
LastSwiftMigration = 1100;
};
};
};
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 97C146E51CF9000F007C117D;
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
97C146ED1CF9000F007C117D /* Runner */,
331C8080294A63A400263BE5 /* RunnerTests */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
331C807F294A63A400263BE5 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EC1CF9000F007C117D /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
35602183E27ADC71E335DD69 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
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";
};
56DDF7F4C644F7A96944CE16 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
7C459C0FBCC1A576B4204836 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
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 = "<group>";
};
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C147001CF9000F007C117D /* Base */,
);
name = LaunchScreen.storyboard;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
249021D3217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
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;
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)";
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.smartHotelApp;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Profile;
};
331C8088294A63A400263BE5 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = FB75E68007A0827C8670AC20 /* Pods-RunnerTests.debug.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.smartHotelApp.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Debug;
};
331C8089294A63A400263BE5 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = B5D9CB5DEF01982D88B62D1D /* Pods-RunnerTests.release.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.smartHotelApp.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Release;
};
331C808A294A63A400263BE5 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 268EEEECBBE3E978DEA18EED /* Pods-RunnerTests.profile.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.smartHotelApp.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;
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;
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;
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;
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)";
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.smartHotelApp;
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)";
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.smartHotelApp;
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 */;
}
<?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 = "1510"
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"
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
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"
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
enableGPUValidationMode = "1"
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 Flutter
import UIKit
@main
@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) {
GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry)
}
}
{
"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>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>智能酒店</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>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<false/>
<key>UISceneConfigurations</key>
<dict>
<key>UIWindowSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneClassName</key>
<string>UIWindowScene</string>
<key>UISceneConfigurationName</key>
<string>flutter</string>
<key>UISceneDelegateClassName</key>
<string>FlutterSceneDelegate</string>
<key>UISceneStoryboardFile</key>
<string>Main</string>
</dict>
</array>
</dict>
</dict>
<key>UIApplicationSupportsIndirectInputEvents</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>
</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 '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);
on<AuthFetchUserInfo>(_onFetchUserInfo);
}
Future<void> _onLoginRequested(
AuthLoginRequested event,
Emitter<AuthState> emit,
) async {
emit(AuthLoading());
try {
await _authService.login(event.username, event.password);
// 登录成功后获取用户信息
final userInfo = await _authService.getUserInfo();
emit(AuthSuccess(userInfo));
} catch (e) {
emit(AuthFailure(e.toString()));
}
}
Future<void> _onTokenExpired(
AuthTokenExpiredEvent event,
Emitter<AuthState> emit,
) async {
// 防止重复处理:如果已经在过期或登出状态,直接跳过
if (state is AuthTokenExpired || state is AuthLoggedOut) return;
await _storageService.deleteToken();
await _storageService.deleteUserInfo();
await _storageService.deleteClientId();
emit(const AuthTokenExpired());
}
Future<void> _onLogoutRequested(
AuthLogoutRequestedEvent event,
Emitter<AuthState> emit,
) async {
try {
await _authService.logout();
} catch (_) {
// 即使接口调用失败也继续执行本地登出
}
await _storageService.deleteToken();
await _storageService.deleteUserInfo();
await _storageService.deleteClientId();
emit(const AuthLoggedOut());
}
Future<void> _onFetchUserInfo(
AuthFetchUserInfo event,
Emitter<AuthState> emit,
) async {
try {
final userInfo = await _authService.getUserInfo();
emit(AuthSuccess(userInfo));
} catch (e) {
// 获取用户信息失败,尝试从本地读取
final storedUserInfo = await _authService.getStoredUserInfo();
if (storedUserInfo != null) {
emit(AuthSuccess(storedUserInfo));
}
}
}
}
\ No newline at end of file
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();
}
class AuthFetchUserInfo extends AuthEvent {
const AuthFetchUserInfo();
}
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/http/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/utils/toast_utils.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());
} else {
// Token 有效,尝试恢复用户信息
_authBloc.add(const AuthFetchUserInfo());
}
}
}
@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.replaceAll([const DefaultLayoutRoute()]);
}
if (state is AuthTokenExpired) {
showToast('登录已过期,请重新登录');
_appRouter.replaceAll([LoginRoute()]);
}
if (state is AuthLoggedOut) {
_appRouter.replaceAll([LoginRoute()]);
}
},
),
// 未来在这里添加其他 Bloc 的监听器:
// BlocListener<UserBloc, UserState>(listener: ...),
// BlocListener<SettingsBloc, SettingsState>(listener: ...),
],
child: MaterialApp.router(
scaffoldMessengerKey: _scaffoldMessengerKey,
routerConfig: _appRouter.config(),
debugShowCheckedModeBanner: false,
),
),
);
},
);
}
}
import 'package:equatable/equatable.dart';
class LoginBO extends Equatable {
final String? scope;
final String? openid;
final String? accessToken;
final String? refreshToken;
final int? expireIn;
final int? refreshExpireIn;
final String? clientId;
const LoginBO({
this.scope,
this.openid,
this.accessToken,
this.refreshToken,
this.expireIn,
this.refreshExpireIn,
this.clientId,
});
factory LoginBO.fromJson(Map<String, dynamic> json) {
return LoginBO(
scope: json['scope'] as String?,
openid: json['openid'] as String?,
accessToken: json['access_token'] as String?,
refreshToken: json['refresh_token'] as String?,
expireIn: json['expire_in'] as int?,
refreshExpireIn: json['refresh_expire_in'] as int?,
clientId: json['client_id'] as String?,
);
}
@override
List<Object?> get props => [
scope,
openid,
accessToken,
refreshToken,
expireIn,
refreshExpireIn,
clientId,
];
}
\ No newline at end of file
import 'package:equatable/equatable.dart';
class UserInfoBO extends Equatable {
final int userId;
final String userName;
final String nickName;
final String phonenumber;
final String email;
final String sex;
final String avatarUrl;
final String deptName;
final String tenantId;
const UserInfoBO({
required this.userId,
required this.userName,
required this.nickName,
required this.phonenumber,
required this.email,
required this.sex,
required this.avatarUrl,
required this.deptName,
required this.tenantId,
});
factory UserInfoBO.fromJson(Map<String, dynamic> json) {
return UserInfoBO(
userId: int.tryParse(json['userId']?.toString() ?? '') ?? 0,
userName: json['userName'] as String? ?? '',
nickName: json['nickName'] as String? ?? '',
phonenumber: json['phonenumber'] as String? ?? '',
email: json['email'] as String? ?? '',
sex: json['sex'] as String? ?? '',
avatarUrl: json['avatarUrl'] as String? ?? '',
deptName: json['deptName'] as String? ?? '',
tenantId: json['tenantId'] as String? ?? '',
);
}
Map<String, dynamic> toJson() {
return {
'userId': userId,
'userName': userName,
'nickName': nickName,
'phonenumber': phonenumber,
'email': email,
'sex': sex,
'avatarUrl': avatarUrl,
'deptName': deptName,
'tenantId': tenantId,
};
}
@override
List<Object?> get props => [
userId,
userName,
nickName,
phonenumber,
email,
sex,
avatarUrl,
deptName,
tenantId,
];
}
\ No newline at end of file
import '../utils/http/response_model.dart';
import '../utils/http/dio_request.dart';
import '../models/bo/login_bo.dart';
import '../models/bo/user_info_bo.dart';
class AuthRepository {
Future<ResponseModel<LoginBO>> login(Map<String, dynamic> params) {
return DioRequest.instance.post<LoginBO>(
'/app/auth/login/password',
data: params,
fromJsonT: (data) => LoginBO.fromJson(data as Map<String, dynamic>),
);
}
Future<ResponseModel<UserInfoBO>> getUserInfo() {
return DioRequest.instance.get<UserInfoBO>(
'/app/auth/user-info',
fromJsonT: (data) => UserInfoBO.fromJson(data as Map<String, dynamic>),
);
}
Future<ResponseModel> logout() {
return DioRequest.instance.post('/app/auth/logout');
}
}
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 => [
// LoginRoute / LoginView(登录页)
AutoRoute(page: LoginRoute.page, initial: true),
// DefaultLayoutRoute / DefaultLayoutView(默认布局页/主框架)
AutoRoute(
page: DefaultLayoutRoute.page,
children: [
// HomeRoute / HomeView(首页)
AutoRoute(page: HomeRoute.page, initial: true),
],
),
];
}
// 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 _i4;
import 'package:flutter/material.dart' as _i5;
import 'package:smart_hotel_app/views/home/index/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;
abstract class $AppRouter extends _i4.RootStackRouter {
$AppRouter({super.navigatorKey});
@override
final Map<String, _i4.PageFactory> pagesMap = {
DefaultLayoutRoute.name: (routeData) {
return _i4.AutoRoutePage<dynamic>(
routeData: routeData,
child: const _i1.DefaultLayoutView(),
);
},
HomeRoute.name: (routeData) {
return _i4.AutoRoutePage<dynamic>(
routeData: routeData,
child: const _i2.HomeView(),
);
},
LoginRoute.name: (routeData) {
final args = routeData.argsAs<LoginRouteArgs>(
orElse: () => const LoginRouteArgs());
return _i4.AutoRoutePage<dynamic>(
routeData: routeData,
child: _i3.LoginView(key: args.key),
);
},
};
}
/// generated route for
/// [_i1.DefaultLayoutView]
class DefaultLayoutRoute extends _i4.PageRouteInfo<void> {
const DefaultLayoutRoute({List<_i4.PageRouteInfo>? children})
: super(
DefaultLayoutRoute.name,
initialChildren: children,
);
static const String name = 'DefaultLayoutRoute';
static const _i4.PageInfo<void> page = _i4.PageInfo<void>(name);
}
/// generated route for
/// [_i2.HomeView]
class HomeRoute extends _i4.PageRouteInfo<void> {
const HomeRoute({List<_i4.PageRouteInfo>? children})
: super(
HomeRoute.name,
initialChildren: children,
);
static const String name = 'HomeRoute';
static const _i4.PageInfo<void> page = _i4.PageInfo<void>(name);
}
/// generated route for
/// [_i3.LoginView]
class LoginRoute extends _i4.PageRouteInfo<LoginRouteArgs> {
LoginRoute({
_i5.Key? key,
List<_i4.PageRouteInfo>? children,
}) : super(
LoginRoute.name,
args: LoginRouteArgs(key: key),
initialChildren: children,
);
static const String name = 'LoginRoute';
static const _i4.PageInfo<LoginRouteArgs> page =
_i4.PageInfo<LoginRouteArgs>(name);
}
class LoginRouteArgs {
const LoginRouteArgs({this.key});
final _i5.Key? key;
@override
String toString() {
return 'LoginRouteArgs{key: $key}';
}
}
import '../models/bo/login_bo.dart';
import '../models/bo/user_info_bo.dart';
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<LoginBO> login(String username, String password) async {
final result = await _authRepository.login({
'username': username,
'password': password,
});
if (result.success && result.data != null) {
final loginBO = result.data!;
if (loginBO.accessToken != null && loginBO.accessToken!.isNotEmpty) {
await _storageService.saveToken(
loginBO.accessToken!,
expiryHours: Constants.tokenExpiryHours,
);
}
if (loginBO.clientId != null && loginBO.clientId!.isNotEmpty) {
await _storageService.saveClientId(loginBO.clientId!);
}
return loginBO;
}
throw Exception(result.msg);
}
Future<UserInfoBO> getUserInfo() async {
final result = await _authRepository.getUserInfo();
if (result.success && result.data != null) {
final userInfo = result.data!;
await _storageService.saveUserInfo(userInfo);
return userInfo;
}
throw Exception(result.msg);
}
Future<UserInfoBO?> getStoredUserInfo() async {
return _storageService.getUserInfo();
}
Future<void> logout() async {
final result = await _authRepository.logout();
if (!result.success) {
throw Exception(result.msg);
}
}
Future<bool> isTokenExpired() async {
return _storageService.isTokenExpired();
}
}
\ No newline at end of file
class Constants {
// ==================== 环境配置 ====================
/// 当前环境:dev / staging / prod,通过 --dart-define=APP_ENV=xxx 注入
static const String env = String.fromEnvironment('APP_ENV', defaultValue: 'dev');
/// 根据环境返回对应的 API 地址
static String get baseUrl {
switch (env) {
case 'dev':
return 'http://121.41.57.178:9090';
case 'staging':
return 'http://121.41.57.178:9090';
case 'prod':
return 'http://121.41.57.178:9090';
default:
return 'http://121.41.57.178:9090';
}
}
// ==================== 超时配置 ====================
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';
}
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 {}
import 'package:dio/dio.dart';
import '../constants.dart';
import '../event_bus.dart';
import 'exceptions/app_exception.dart';
import 'interceptors/error_interceptor.dart';
import 'interceptors/request_interceptor.dart';
import 'interceptors/response_interceptor.dart';
import '../storage/storage_service.dart';
import 'response_model.dart';
class DioRequest {
static final DioRequest instance = DioRequest._internal();
factory DioRequest() => instance;
DioRequest._internal();
late final Dio _dio;
final Map<String, CancelToken> _cancelTokens = {};
late final StorageService _storageService;
void init(StorageService storageService) {
_storageService = storageService;
_dio = Dio(BaseOptions(
baseUrl: Constants.baseUrl,
connectTimeout: const Duration(milliseconds: Constants.connectTimeout),
receiveTimeout: const Duration(milliseconds: Constants.receiveTimeout),
sendTimeout: const Duration(milliseconds: Constants.sendTimeout),
));
_dio.interceptors.addAll([
RequestInterceptor(_storageService),
ResponseInterceptor(),
ErrorInterceptor(),
]);
}
Future<ResponseModel<T>> get<T>(
String path, {
Map<String, dynamic>? queryParameters,
Options? options,
CancelToken? cancelToken,
T Function(dynamic)? fromJsonT,
}) async {
return _request<T>(
path,
method: 'GET',
queryParameters: queryParameters,
options: options,
cancelToken: cancelToken,
fromJsonT: fromJsonT,
);
}
Future<ResponseModel<T>> post<T>(
String path, {
dynamic data,
Map<String, dynamic>? queryParameters,
Options? options,
CancelToken? cancelToken,
T Function(dynamic)? fromJsonT,
}) async {
return _request<T>(
path,
method: 'POST',
data: data,
queryParameters: queryParameters,
options: options,
cancelToken: cancelToken,
fromJsonT: fromJsonT,
);
}
Future<ResponseModel<T>> put<T>(
String path, {
dynamic data,
Map<String, dynamic>? queryParameters,
Options? options,
CancelToken? cancelToken,
T Function(dynamic)? fromJsonT,
}) async {
return _request<T>(
path,
method: 'PUT',
data: data,
queryParameters: queryParameters,
options: options,
cancelToken: cancelToken,
fromJsonT: fromJsonT,
);
}
Future<ResponseModel<T>> delete<T>(
String path, {
dynamic data,
Map<String, dynamic>? queryParameters,
Options? options,
CancelToken? cancelToken,
T Function(dynamic)? fromJsonT,
}) async {
return _request<T>(
path,
method: 'DELETE',
data: data,
queryParameters: queryParameters,
options: options,
cancelToken: cancelToken,
fromJsonT: fromJsonT,
);
}
Future<ResponseModel<T>> patch<T>(
String path, {
dynamic data,
Map<String, dynamic>? queryParameters,
Options? options,
CancelToken? cancelToken,
T Function(dynamic)? fromJsonT,
}) async {
return _request<T>(
path,
method: 'PATCH',
data: data,
queryParameters: queryParameters,
options: options,
cancelToken: cancelToken,
fromJsonT: fromJsonT,
);
}
Future<ResponseModel<T>> uploadFile<T>(
String path,
String filePath, {
String fieldName = 'file',
Map<String, dynamic>? data,
Map<String, dynamic>? queryParameters,
Options? options,
CancelToken? cancelToken,
ProgressCallback? onSendProgress,
T Function(dynamic)? fromJsonT,
}) async {
final formData = FormData.fromMap({
if (data != null) ...data,
fieldName: await MultipartFile.fromFile(filePath),
});
return _request<T>(
path,
method: 'POST',
data: formData,
queryParameters: queryParameters,
options: options,
cancelToken: cancelToken,
fromJsonT: fromJsonT,
onSendProgress: onSendProgress,
);
}
Future<ResponseModel<T>> uploadFiles<T>(
String path,
List<String> filePaths, {
String fieldName = 'files',
Map<String, dynamic>? data,
Map<String, dynamic>? queryParameters,
Options? options,
CancelToken? cancelToken,
ProgressCallback? onSendProgress,
T Function(dynamic)? fromJsonT,
}) async {
final formData = FormData.fromMap({
if (data != null) ...data,
fieldName: await Future.wait(
filePaths.map((path) => MultipartFile.fromFile(path)),
),
});
return _request<T>(
path,
method: 'POST',
data: formData,
queryParameters: queryParameters,
options: options,
cancelToken: cancelToken,
fromJsonT: fromJsonT,
onSendProgress: onSendProgress,
);
}
Future<ResponseModel<T>> uploadFormData<T>(
String path,
FormData formData, {
Map<String, dynamic>? queryParameters,
Options? options,
CancelToken? cancelToken,
ProgressCallback? onSendProgress,
T Function(dynamic)? fromJsonT,
}) async {
return _request<T>(
path,
method: 'POST',
data: formData,
queryParameters: queryParameters,
options: options,
cancelToken: cancelToken,
fromJsonT: fromJsonT,
onSendProgress: onSendProgress,
);
}
Future<ResponseModel<String>> downloadFile(
String url,
String savePath, {
Map<String, dynamic>? queryParameters,
Options? options,
CancelToken? cancelToken,
ProgressCallback? onReceiveProgress,
}) async {
final token = cancelToken ?? CancelToken();
final uuid = _generateUuid();
_cancelTokens[uuid] = token;
try {
await _dio.download(
url,
savePath,
queryParameters: queryParameters,
options: options,
cancelToken: token,
onReceiveProgress: onReceiveProgress,
);
return ResponseModel(
code: 200,
msg: '下载成功',
data: savePath,
uuid: uuid,
success: true,
timestamp: DateTime.now().millisecondsSinceEpoch,
);
} catch (e) {
return _handleError<String>(e, uuid);
} finally {
_cancelTokens.remove(uuid);
}
}
Future<ResponseModel<List<int>>> downloadBytes(
String url, {
Map<String, dynamic>? queryParameters,
Options? options,
CancelToken? cancelToken,
ProgressCallback? onReceiveProgress,
}) async {
final token = cancelToken ?? CancelToken();
final uuid = _generateUuid();
_cancelTokens[uuid] = token;
try {
final response = await _dio.get<List<int>>(
url,
queryParameters: queryParameters,
options: (options ?? Options()).copyWith(
responseType: ResponseType.bytes,
),
cancelToken: token,
onReceiveProgress: onReceiveProgress,
);
return ResponseModel<List<int>>(
code: 200,
msg: '下载成功',
data: response.data,
uuid: uuid,
success: true,
timestamp: DateTime.now().millisecondsSinceEpoch,
);
} catch (e) {
return _handleError<List<int>>(e, uuid);
} finally {
_cancelTokens.remove(uuid);
}
}
void cancelRequest(String uuid) {
final token = _cancelTokens[uuid];
if (token != null && !token.isCancelled) {
token.cancel('Request cancelled by user');
_cancelTokens.remove(uuid);
}
}
void cancelAll() {
_cancelTokens.forEach((uuid, token) {
if (!token.isCancelled) {
token.cancel('All requests cancelled');
}
});
_cancelTokens.clear();
}
Future<ResponseModel<T>> _request<T>(
String path, {
required String method,
dynamic data,
Map<String, dynamic>? queryParameters,
Options? options,
CancelToken? cancelToken,
T Function(dynamic)? fromJsonT,
ProgressCallback? onSendProgress,
ProgressCallback? onReceiveProgress,
}) async {
final token = cancelToken ?? CancelToken();
final uuid = _generateUuid();
_cancelTokens[uuid] = token;
try {
final response = await _dio.request(
path,
data: data,
queryParameters: queryParameters,
options: (options ?? Options()).copyWith(
method: method,
),
cancelToken: token,
onSendProgress: onSendProgress,
onReceiveProgress: onReceiveProgress,
);
Map<String, dynamic> jsonData;
if (response.data is Map<String, dynamic>) {
jsonData = Map<String, dynamic>.from(response.data);
// 如果响应中有token但没有data,把token放到data里
// if (jsonData.containsKey('token') && !jsonData.containsKey('data')) {
// jsonData['data'] = {'token': jsonData['token']};
// }
} else {
jsonData = {
'code': response.statusCode ?? -1,
'msg': '请求成功',
'data': response.data
};
}
final result = ResponseModel<T>.fromJson(
jsonData,
uuid: uuid,
fromJsonT: fromJsonT,
);
// 当后端返回 HTTP 200 但 body 中 code 为 401 时,
// 表示 token 已在后端失效,触发 token 过期事件
if (result.code == 401) {
eventBus.emit(TokenExpiredEvent());
}
return result;
} catch (e) {
return _handleError<T>(e, uuid);
} finally {
_cancelTokens.remove(uuid);
}
}
ResponseModel<T> _handleError<T>(dynamic error, String uuid) {
if (error is DioException && error.error is AppException) {
final appException = error.error as AppException;
return ResponseModel<T>.error(
uuid: appException.uuid ?? uuid,
msg: appException.message,
code: appException.code ?? -1,
);
}
return ResponseModel<T>.error(
uuid: uuid,
msg: error.toString(),
);
}
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();
}
}
\ No newline at end of file
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)';
}
}
\ No newline at end of file
import 'app_exception.dart';
class BusinessException extends AppException {
BusinessException({
required String message,
int? code,
String? uuid,
}) : super(message: message, code: code, uuid: uuid);
}
\ No newline at end of file
import 'app_exception.dart';
class CancelException extends AppException {
CancelException({
String message = '请求已取消',
String? uuid,
}) : super(message: message, code: -1, uuid: uuid);
}
\ No newline at end of file
import 'app_exception.dart';
class NetworkException extends AppException {
NetworkException({
String message = '网络连接异常,请检查网络后重试',
int? code,
String? uuid,
}) : super(message: message, code: code, uuid: uuid);
}
\ No newline at end of file
import 'app_exception.dart';
class ServerException extends AppException {
ServerException({
String message = '服务器异常,请稍后重试',
int? code,
String? uuid,
}) : super(message: message, code: code, uuid: uuid);
}
\ No newline at end of file
import 'dart:io';
import 'package:dio/dio.dart';
import '../../logger.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) {
Log.error('========== Error START ==========');
Log.error('UUID: $uuid');
Log.error('Error Type: ${err.type}');
Log.error('Error Message: ${err.message}');
Log.error('Exception: $exception');
if (err.response != null) {
Log.error('Status Code: ${err.response?.statusCode}');
Log.error('Response Data: ${err.response?.data}');
}
Log.error('========== Error END ==========');
}
}
\ No newline at end of file
import 'package:dio/dio.dart';
import '../../logger.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';
final clientId = await _storageService.getClientId();
if (clientId != null && clientId.isNotEmpty) {
headers['clientid'] = clientId;
}
}
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) {
Log.info('********************** Request START **********************');
Log.info('UUID: $uuid');
Log.info('Method: ${options.method} URL: ${options.uri}');
// Log.info('URL: ${options.uri}');
Log.info('Headers: ${options.headers}');
if (options.data != null) {
Log.info('POST入参: ${options.data}');
}
if (options.queryParameters.isNotEmpty) {
Log.info('URL入参: ${options.queryParameters}');
}
Log.info('********************** Request END **********************');
}
}
\ No newline at end of file
import 'package:dio/dio.dart';
import '../../logger.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) {
Log.warn('====================== Response START ======================');
Log.warn('UUID: $uuid');
Log.warn('Status Code: ${response.statusCode}');
Log.warn('Data: ${response.data}');
Log.warn('====================== Response END ======================');
}
}
\ 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)';
}
}
\ No newline at end of file
import 'dart:convert';
import 'package:logger/logger.dart' as pkg;
class _LongMessagePrinter extends pkg.LogPrinter {
final pkg.PrettyPrinter _prettyPrinter;
final int _chunkSize;
_LongMessagePrinter({
required int methodCount,
required int errorMethodCount,
required int lineLength,
required bool colors,
required bool printEmojis,
int chunkSize = 800,
}) : _prettyPrinter = pkg.PrettyPrinter(
methodCount: methodCount,
errorMethodCount: errorMethodCount,
lineLength: lineLength,
colors: colors,
printEmojis: printEmojis,
),
_chunkSize = chunkSize;
String _tryPrettyJson(String message) {
try {
final trimmed = message.trim();
if ((trimmed.startsWith('{') && trimmed.endsWith('}')) ||
(trimmed.startsWith('[') && trimmed.endsWith(']'))) {
final decoded = jsonDecode(trimmed);
return const JsonEncoder.withIndent(' ').convert(decoded);
}
} catch (_) {}
return message;
}
List<String> _chunk(String text) {
if (text.length <= _chunkSize) return [text];
final chunks = <String>[];
for (var i = 0; i < text.length; i += _chunkSize) {
chunks.add(text.substring(i, i + _chunkSize > text.length ? text.length : i + _chunkSize));
}
return chunks;
}
@override
List<String> log(pkg.LogEvent event) {
final original = event.message;
final output = StringBuffer();
final lines = const LineSplitter().convert(_tryPrettyJson(original.toString()));
for (final line in lines) {
for (final chunk in _chunk(line)) {
output.writeln(chunk);
}
}
final wrappedEvent = pkg.LogEvent(
event.level,
output.toString().trimRight(),
error: event.error,
stackTrace: event.stackTrace,
);
return _prettyPrinter.log(wrappedEvent);
}
}
class Log {
static final pkg.Logger _logger = pkg.Logger(
printer: _LongMessagePrinter(
methodCount: 0,
errorMethodCount: 5,
lineLength: 90,
colors: true,
printEmojis: true,
chunkSize: 800,
),
);
static void info(String message) {
_logger.i(message);
}
static void warn(String message) {
_logger.w(message);
}
static void error(String message, [dynamic error]) {
_logger.e(message, error: error);
}
static void request(String message) {
_logger.i('[REQUEST] $message');
}
static void response(String message) {
_logger.i('[RESPONSE] $message');
}
}
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:smart_hotel_app/models/bo/user_info_bo.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';
static const String _keyUserInfo = 'user_info';
static const String _keyClientId = 'client_id';
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> saveUserInfo(UserInfoBO userInfo) async {
final jsonStr =
'${userInfo.userId}|${userInfo.userName}|${userInfo.nickName}|${userInfo.phonenumber}|${userInfo.email}|${userInfo.sex}|${userInfo.avatarUrl}|${userInfo.deptName}|${userInfo.tenantId}';
await _storage.write(key: _keyUserInfo, value: jsonStr);
}
Future<UserInfoBO?> getUserInfo() async {
final value = await _storage.read(key: _keyUserInfo);
if (value == null || value.isEmpty) return null;
final parts = value.split('|');
if (parts.length < 9) return null;
return UserInfoBO(
userId: int.tryParse(parts[0]) ?? 0,
userName: parts[1],
nickName: parts[2],
phonenumber: parts[3],
email: parts[4],
sex: parts[5],
avatarUrl: parts[6],
deptName: parts[7],
tenantId: parts[8],
);
}
Future<void> deleteUserInfo() async {
await _storage.delete(key: _keyUserInfo);
}
// ==================== ClientId 相关 ====================
Future<void> saveClientId(String clientId) async {
await _storage.write(key: _keyClientId, value: clientId);
}
Future<String?> getClientId() async {
return await _storage.read(key: _keyClientId);
}
Future<void> deleteClientId() async {
await _storage.delete(key: _keyClientId);
}
// ==================== 记住密码相关 ====================
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();
}
}
\ No newline at end of file
library tcp;
export 'service/index.dart';
\ No newline at end of file
export 'tcp_client_cubit.dart';
export 'tcp_client_state.dart';
export 'tcp_server_cubit.dart';
export 'tcp_server_state.dart';
export 'tcp_connection_status.dart';
\ No newline at end of file
import 'dart:async';
import 'dart:io';
import 'dart:typed_data';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'tcp_client_state.dart';
import 'tcp_connection_status.dart';
class TcpClientCubit extends Cubit<TcpClientState> {
Socket? _socket;
String? _address;
int? _port;
int _timeout = 5000;
int _maxRetryCount = 3;
int _retryInterval = 2000;
int _currentRetry = 0;
Timer? _reconnectTimer;
Timer? _heartbeatTimer;
bool _isManualDisconnect = false;
bool _isConnecting = false;
final StreamController<Uint8List> _dataController = StreamController.broadcast();
/// 接收到的数据流
Stream<Uint8List> get onData => _dataController.stream;
TcpClientCubit() : super(const TcpClientState());
/// 配置连接参数
void config({
required String address,
required int port,
int timeout = 5000,
int maxRetry = 3,
int retryInterval = 2000,
}) {
_address = address;
_port = port;
_timeout = timeout;
_maxRetryCount = maxRetry;
_retryInterval = retryInterval;
emit(state.copyWith(address: address, port: port));
}
/// 连接服务器
Future<bool> connect() async {
if (_address == null || _port == null) {
emit(state.copyWith(error: '未配置地址或端口'));
return false;
}
if (state.isConnected) return true;
if (_isConnecting) return false;
_isConnecting = true;
_isManualDisconnect = false;
emit(state.copyWith(connectionStatus: TcpConnectionStatus.connecting, clearError: true));
try {
_socket = await Socket.connect(
_address!,
_port!,
timeout: Duration(milliseconds: _timeout),
);
_socket!.setOption(SocketOption.tcpNoDelay, true);
_listenSocket();
_currentRetry = 0;
emit(state.copyWith(connectionStatus: TcpConnectionStatus.connected));
_startHeartbeat();
return true;
} catch (e) {
emit(state.copyWith(
connectionStatus: TcpConnectionStatus.error,
error: e.toString(),
));
_tryReconnect();
return false;
} finally {
_isConnecting = false;
}
}
/// 监听 Socket 数据
void _listenSocket() {
_socket!.listen(
(data) {
final uint8List = Uint8List.fromList(data);
if (!_dataController.isClosed) {
_dataController.add(uint8List);
}
},
onError: (e) {
emit(state.copyWith(
connectionStatus: TcpConnectionStatus.error,
error: e.toString(),
));
_tryReconnect();
},
onDone: () {
if (!_isManualDisconnect) {
_tryReconnect();
}
},
);
}
/// 自动重连
void _tryReconnect() {
if (_isManualDisconnect) return;
if (_currentRetry >= _maxRetryCount) {
emit(state.copyWith(connectionStatus: TcpConnectionStatus.disconnected));
return;
}
_currentRetry++;
emit(state.copyWith(connectionStatus: TcpConnectionStatus.reconnecting));
_reconnectTimer?.cancel();
_reconnectTimer = Timer(Duration(milliseconds: _retryInterval), () {
connect();
});
}
/// 发送 Uint8List
Future<bool> send(Uint8List data) async {
if (_socket == null || !state.isConnected) return false;
try {
_socket!.add(data);
await _socket!.flush();
return true;
} catch (e) {
return false;
}
}
/// 发送十六进制字符串
Future<bool> sendHex(String hex) async {
hex = hex.replaceAll(RegExp(r'\s+'), '');
if (hex.length % 2 != 0) return false;
try {
List<int> bytes = [];
for (int i = 0; i < hex.length; i += 2) {
bytes.add(int.parse(hex.substring(i, i + 2), radix: 16));
}
return await send(Uint8List.fromList(bytes));
} catch (e) {
return false;
}
}
/// 发送字符串 UTF8
Future<bool> sendString(String msg) async {
return await send(Uint8List.fromList(msg.codeUnits));
}
/// 心跳保活(每 15 秒发送一次)
void _startHeartbeat() {
_heartbeatTimer?.cancel();
_heartbeatTimer = Timer.periodic(const Duration(seconds: 15), (timer) {
if (state.isConnected) {
sendHex('00 00 00 00');
}
});
}
/// 手动断开
void disconnect() {
_isManualDisconnect = true;
_reconnectTimer?.cancel();
_heartbeatTimer?.cancel();
_socket?.close();
_socket = null;
emit(state.copyWith(connectionStatus: TcpConnectionStatus.disconnected));
}
@override
Future<void> close() {
disconnect();
_dataController.close();
return super.close();
}
}
\ No newline at end of file
import 'package:equatable/equatable.dart';
import 'tcp_connection_status.dart';
class TcpClientState extends Equatable {
final TcpConnectionStatus connectionStatus;
final String? address;
final int? port;
final String? error;
const TcpClientState({
this.connectionStatus = TcpConnectionStatus.disconnected,
this.address,
this.port,
this.error,
});
TcpClientState copyWith({
TcpConnectionStatus? connectionStatus,
String? address,
int? port,
String? error,
bool clearError = false,
}) {
return TcpClientState(
connectionStatus: connectionStatus ?? this.connectionStatus,
address: address ?? this.address,
port: port ?? this.port,
error: clearError ? null : (error ?? this.error),
);
}
bool get isConnected => connectionStatus == TcpConnectionStatus.connected;
@override
List<Object?> get props => [connectionStatus, address, port, error];
}
\ No newline at end of file
/// TCP 连接状态枚举
enum TcpConnectionStatus {
/// 未连接
disconnected,
/// 连接中
connecting,
/// 已连接
connected,
/// 重连中
reconnecting,
/// 错误
error,
}
\ No newline at end of file
import 'dart:async';
import 'dart:io';
import 'dart:typed_data';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'tcp_server_state.dart';
class TcpServerCubit extends Cubit<TcpServerState> {
ServerSocket? _serverSocket;
final List<Socket> _clients = [];
final StreamController<Map<String, dynamic>> _dataController = StreamController.broadcast();
/// 接收到的客户端数据流
Stream<Map<String, dynamic>> get onClientData => _dataController.stream;
TcpServerCubit() : super(const TcpServerState());
/// 设置端口
void setPort(int port) {
emit(state.copyWith(port: port));
}
/// 启动服务端
Future<void> start() async {
try {
_serverSocket = await ServerSocket.bind(InternetAddress.anyIPv4, state.port);
emit(state.copyWith(isRunning: true, port: _serverSocket!.port));
_listenClients();
} catch (e) {
if (e.toString().contains('already in use')) {
emit(state.copyWith(port: state.port + 1));
start();
} else {
emit(state.copyWith(error: e.toString()));
}
}
}
/// 监听客户端连接
void _listenClients() {
_serverSocket!.listen((client) {
_clients.add(client);
emit(state.copyWith(connectedClients: _clients.length));
_handleClient(client);
});
}
/// 处理单个客户端数据
void _handleClient(Socket client) {
client.listen(
(data) {
_dataController.add({
'client': client,
'address': client.remoteAddress.address,
'port': client.remotePort,
'data': Uint8List.fromList(data),
});
},
onDone: () => _removeClient(client),
onError: (e) => _removeClient(client),
);
}
/// 群发字节数据给所有客户端
void sendToAll(Uint8List data) {
for (var c in _clients) {
try {
c.add(data);
} catch (_) {}
}
}
/// 群发十六进制字符串给所有客户端
void sendHexToAll(String hex) {
hex = hex.replaceAll(RegExp(r'\s+'), '');
List<int> bytes = [];
for (int i = 0; i < hex.length; i += 2) {
bytes.add(int.parse(hex.substring(i, i + 2), radix: 16));
}
sendToAll(Uint8List.fromList(bytes));
}
/// 移除客户端
void _removeClient(Socket client) {
_clients.remove(client);
emit(state.copyWith(connectedClients: _clients.length));
client.destroy();
}
/// 停止服务端
Future<void> stop() async {
for (var c in _clients) {
await c.close();
}
_clients.clear();
await _serverSocket?.close();
emit(state.copyWith(isRunning: false, connectedClients: 0));
}
@override
Future<void> close() async {
await stop();
_dataController.close();
return super.close();
}
}
\ No newline at end of file
import 'package:equatable/equatable.dart';
class TcpServerState extends Equatable {
final bool isRunning;
final int port;
final int connectedClients;
final String? error;
const TcpServerState({
this.isRunning = false,
this.port = 0,
this.connectedClients = 0,
this.error,
});
TcpServerState copyWith({
bool? isRunning,
int? port,
int? connectedClients,
String? error,
bool clearError = false,
}) {
return TcpServerState(
isRunning: isRunning ?? this.isRunning,
port: port ?? this.port,
connectedClients: connectedClients ?? this.connectedClients,
error: clearError ? null : (error ?? this.error),
);
}
@override
List<Object?> get props => [isRunning, port, connectedClients, error];
}
\ No newline at end of file
# TCP 模块使用手册
# TCP 模块使用手册
## 概述
本模块基于 `flutter_bloc` + `Cubit` 架构,提供 TCP 客户端和服务端通信能力,与项目整体技术栈保持一致。
### 模块结构
```
lib/utils/tcp/
├── index.dart
└── service/
├── index.dart # 统一导出
├── tcp_connection_status.dart # 连接状态枚举
├── tcp_client_state.dart # 客户端状态
├── tcp_client_cubit.dart # 客户端 Cubit
├── tcp_server_state.dart # 服务端状态
└── tcp_server_cubit.dart # 服务端 Cubit
```
### 依赖
本模块依赖 `flutter_bloc``equatable`,已在项目 `pubspec.yaml` 中配置,无需额外引入。
---
## 一、TCP 客户端(TcpClientCubit)
### 1.1 State 说明
`TcpClientState` 包含以下字段:
| 字段 | 类型 | 说明 |
|------|------|------|
| `connectionStatus` | `TcpConnectionStatus` | 当前连接状态 |
| `address` | `String?` | 目标服务器地址 |
| `port` | `int?` | 目标服务器端口 |
| `error` | `String?` | 最近一次错误信息 |
| `isConnected` | `bool` | 便捷属性:是否已连接(仅 `connected` 状态返回 true) |
### 1.2 TcpConnectionStatus 枚举
| 值 | 说明 |
|---|---|
| `disconnected` | 未连接 / 已断开 |
| `connecting` | 正在建立连接 |
| `connected` | 已连接,可正常通信 |
| `reconnecting` | 自动重连中 |
| `error` | 连接出错 |
**状态转换流程:**
```
disconnected → connecting → connected
↓ ↓
error ←───────┘ (连接异常/Socket 错误)
reconnecting → ... (最多重试 maxRetry 次)
disconnected (重试耗尽)
```
手动调用 `disconnect()` 后状态直接回到 `disconnected`,且不会触发自动重连。
### 1.3 公共 API 参考
| 方法/属性 | 签名 | 返回值 | 说明 |
|-----------|------|--------|------|
| `config()` | `config({required address, required port, timeout, maxRetry, retryInterval})` | `void` | 配置连接参数。必须先调用此方法再 connect |
| `connect()` | `Future<bool>` | `bool` | 发起连接。成功返回 `true`;已连接返回 `true`;正在连接中返回 `false`;失败返回 `false` 并触发自动重连 |
| `disconnect()` | `void` | — | 手动断开连接,停止心跳和重连 |
| `send()` | `Future<bool>(Uint8List data)` | `bool` | 发送字节数组。未连接或发送失败返回 `false` |
| `sendHex()` | `Future<bool>(String hex)` | `bool` | 发送十六进制字符串(支持空格分隔)。奇数长度或非法字符返回 `false` |
| `sendString()` | `Future<bool>(String msg)` | `bool` | 发送字符串(使用 UTF-8 编码)。未连接返回 `false` |
| `onData` | `Stream<Uint8List>` (getter) | Stream | 接收到的原始数据流,广播流,可多次订阅 |
| `close()` | `Future<void>` | — | 资源清理:断开连接 + 关闭数据流 |
#### config() 参数详情
| 参数 | 类型 | 默认值 | 必填 | 说明 |
|------|------|--------|------|------|
| `address` | `String` | — | 是 | 服务器 IP 或域名 |
| `port` | `int` | — | 是 | 服务器端口 |
| `timeout` | `int` | `5000` | 否 | 连接超时时间(毫秒) |
| `maxRetry` | `int` | `3` | 否 | 断线后最大自动重试次数 |
| `retryInterval` | `int` | `2000` | 否 | 每次重试的间隔(毫秒) |
### 1.4 并发安全机制
`connect()` 方法内置了**并发防护**
- 如果当前已有连接正在进行中(`connecting` 状态),再次调用 `connect()` 会立即返回 `false`,不会创建重复连接
- 使用内部标志位 `_isConnecting` 配合 `try/finally` 保证状态正确释放
- **使用建议**:无需在外层加锁,多次快速点击"连接"按钮是安全的
```dart
// 安全示例:连续多次调用不会产生问题
cubit.connect(); // 第1次:正常发起连接
cubit.connect(); // 第2次:立即返回 false,忽略
cubit.connect(); // 第3次:立即返回 false,忽略
// 最终只会有一个 Socket 连接
```
### 1.5 心跳机制
`TcpClientCubit` 内置心跳保活功能:
- **触发时机**:连接成功后自动启动
- **间隔**:每 **15 秒** 发送一次
- **内容**:十六进制 `00 00 00 00`(4 字节全零)
- **停止时机**:调用 `disconnect()` 时自动取消;连接断开时也会因 `state.isConnected == false` 而不再发送
- **自定义**:如需修改心跳内容或间隔,可直接编辑 `_startHeartbeat()` 方法
> 注意:心跳包通过 `sendHex()` 发送,如果发送失败不会影响连接状态(静默失败)。
### 1.6 自动重连机制
当以下情况发生时,客户端会尝试自动重连:
- `Socket.connect()` 抛出异常(网络不通、服务器未启动等)
- Socket 的 `onError` 回调被触发
- Socket 的 `onDone` 回调被触发(对端关闭连接)
**重连规则:**
| 条件 | 行为 |
|------|------|
| 当前重试次数 < `maxRetry` | 等待 `retryInterval` 毫秒后重新调用 `connect()` |
| 当前重试次数 >= `maxRetry` | 放弃重连,状态变为 `disconnected` |
| 手动调用了 `disconnect()` | 不触发重连(`_isManualDisconnect = true` 阻止) |
每次重连成功后,`_currentRetry` 计数器归零。
### 1.7 基本使用
#### 第一步:在 Widget 树中注入
```dart
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:smart_hotel_app/utils/tcp/service/tcp_client_cubit.dart';
class MyPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => TcpClientCubit(),
child: MyPageBody(),
);
}
}
```
#### 第二步:配置并连接
```dart
class MyPageBody extends StatelessWidget {
@override
Widget build(BuildContext context) {
final cubit = context.read<TcpClientCubit>();
// 先配置,后连接
cubit.config(
address: '192.168.1.100',
port: 8888,
timeout: 5000,
maxRetry: 3,
retryInterval: 2000,
);
// 发起连接(返回 Future<bool>)
cubit.connect();
return /* ... UI */;
}
}
```
#### 第三步:监听连接状态变化
```dart
// 方式一:BlocBuilder —— 根据状态构建 UI
BlocBuilder<TcpClientCubit, TcpClientState>(
builder: (context, state) {
switch (state.connectionStatus) {
case TcpConnectionStatus.disconnected:
return Text('未连接');
case TcpConnectionStatus.connecting:
return Row(
children: [
CircularProgressIndicator(strokeWidth: 2),
SizedBox(width: 8),
Text('连接中...'),
],
);
case TcpConnectionStatus.connected:
return Text('已连接 ${state.address}:${state.port}');
case TcpConnectionStatus.reconnecting:
return Text('重连中...');
case TcpConnectionStatus.error:
return Text('错误: ${state.error}', style: TextStyle(color: Colors.red));
}
},
)
// 方式二:BlocListener —— 监听状态变化执行副作用(不重建 UI)
BlocListener<TcpClientCubit, TcpClientState>(
listener: (context, state) {
if (state.connectionStatus == TcpConnectionStatus.connected) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('连接成功')),
);
}
if (state.connectionStatus == TcpConnectionStatus.disconnected &&
state.error != null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('连接失败: ${state.error}')),
);
}
},
child: /* ... UI */,
)
```
#### 第四步:监听接收到的数据
```dart
class _MyPageBodyState extends State<MyPageBody> {
StreamSubscription<Uint8List>? _dataSubscription;
@override
void initState() {
super.initState();
final cubit = context.read<TcpClientCubit>();
// 订阅数据流(onData 是广播流,可多次订阅)
_dataSubscription = cubit.onData.listen((data) {
// 将字节转为十六进制字符串显示
String hex = data
.map((b) => b.toRadixString(16).padLeft(2, '0').toUpperCase())
.join(' ');
print('收到数据 (${data.length} bytes): $hex');
// 在此处解析业务协议...
});
}
@override
void dispose() {
_dataSubscription?.cancel(); // 务必取消订阅防止内存泄漏
super.dispose();
}
}
```
#### 第五步:发送数据
```dart
final cubit = context.read<TcpClientCubit>();
// 方式一:发送十六进制字符串(最常用,支持空格分隔)
bool ok = await cubit.sendHex('AA BB CC DD');
if (!ok) print('发送失败');
// 方式二:发送原始字节数组
await cubit.send(Uint8List.fromList([0xAA, 0xBB, 0xCC, 0xDD]));
// 方式三:发送文本字符串(UTF-8 编码)
await cubit.sendString('Hello World');
```
**发送方法对比:**
| 方法 | 适用场景 | 输入格式 | 返回值含义 |
|------|---------|----------|-----------|
| `sendHex()` | 发送协议指令 | 十六进制字符串(如 `"AA BB"`) | `false` = 格式错误/未连接/发送异常 |
| `send()` | 发送原始字节 | `Uint8List` | `false` = 未连接/发送异常 |
| `sendString()` | 发送文本消息 | `String` | `false` = 未连接/发送异常 |
> **注意**:所有发送方法在未连接状态下都会安全地返回 `false`,不会抛出异常。
#### 第六步:断开连接与资源清理
```dart
// 仅断开连接(保留 Cubit,可再次 connect)
context.read<TcpClientCubit>().disconnect();
// 完全销毁 Cubit(通常不需要手动调用,BlocProvider 会处理)
// context.read<TcpClientCubit>().close();
```
### 1.8 完整示例
```dart
import 'dart:async';
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:smart_hotel_app/utils/tcp/service/tcp_client_cubit.dart';
import 'package:smart_hotel_app/utils/tcp/service/tcp_client_state.dart';
import 'package:smart_hotel_app/utils/tcp/service/tcp_connection_status.dart';
class TcpClientDemoPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => TcpClientCubit(),
child: _TcpClientDemoView(),
);
}
}
class _TcpClientDemoView extends StatefulWidget {
@override
State<_TcpClientDemoView> createState() => _TcpClientDemoViewState();
}
class _TcpClientDemoViewState extends State<_TcpClientDemoView> {
StreamSubscription<Uint8List>? _dataSub;
final TextEditingController _ipController = TextEditingController(text: '192.168.1.100');
final TextEditingController _hexController = TextEditingController(text: 'AA BB CC DD');
List<String> _logMessages = [];
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) => _init());
}
void _init() {
final cubit = context.read<TcpClientCubit>();
// 监听接收数据
_dataSub = cubit.onData.listen((data) {
String hex = data
.map((b) => b.toRadixString(16).padLeft(2, '0').toUpperCase())
.join(' ');
_addLog('<< 接收 [${data.length} bytes] $hex');
});
// 自动配置并连接
cubit.config(address: _ipController.text, port: 8888);
cubit.connect();
}
void _addLog(String msg) {
setState(() => _logMessages.insert(0, '${DateTime.now().toString().substring(11, 19)} $msg'));
}
@override
void dispose() {
_dataSub?.cancel();
_ipController.dispose();
_hexController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('TCP 客户端 Demo')),
body: BlocBuilder<TcpClientCubit, TcpClientState>(
builder: (context, state) {
final cubit = context.read<TcpClientCubit>();
final isConn = state.isConnected;
final statusText = {
TcpConnectionStatus.disconnected: '未连接',
TcpConnectionStatus.connecting: '连接中...',
TcpConnectionStatus.connected: '已连接',
TcpConnectionStatus.reconnecting: '重连中...',
TcpConnectionStatus.error: '错误',
}[state.connectionStatus]!;
return Padding(
padding: EdgeInsets.all(16),
child: Column(
children: [
// 状态栏
Container(
padding: EdgeInsets.all(12),
decoration: BoxDecoration(
color: isConn ? Colors.green[50] : Colors.grey[200],
borderRadius: BorderRadius.circular(8),
),
child: Row(
children: [
Icon(
isConn ? Icons.wifi : Icons.wifi_off,
color: isConn ? Colors.green : Colors.grey,
),
SizedBox(width: 8),
Text('$statusText ${state.address ?? ""}:${state.port ?? ""}'),
],
),
),
if (state.error != null)
Padding(
padding: EdgeInsets.only(top: 8),
child: Text(state.error!, style: TextStyle(color: Colors.red, fontSize: 12)),
),
SizedBox(height: 16),
// 操作按钮行
Row(
children: [
Expanded(
child: ElevatedButton.icon(
onPressed: isConn ? null : () {
cubit.config(address: _ipController.text, port: 8888);
cubit.connect();
},
icon: Icon(Icons.link),
label: Text('连接'),
),
),
SizedBox(width: 8),
Expanded(
child: OutlinedButton.icon(
onPressed: isConn ? () => cubit.disconnect() : null,
icon: Icon(Icons.link_off),
label: Text('断开'),
),
),
],
),
SizedBox(height: 12),
// 发送区域
TextField(
controller: _hexController,
decoration: InputDecoration(
labelText: '十六进制数据',
suffixIcon: IconButton(
icon: Icon(Icons.send),
onPressed: isConn ? () async {
bool ok = await cubit.sendHex(_hexController.text);
if (ok) {
_addLog('>> 发送 ${_hexController.text}');
} else {
_addLog('!! 发送失败');
}
} : null,
),
),
),
SizedBox(height: 12),
// 日志区域
Expanded(
child: Container(
width: double.infinity,
padding: EdgeInsets.all(8),
decoration: BoxDecoration(
border: Border.all(color: Colors.grey[300]!),
borderRadius: BorderRadius.circular(4),
),
child: _logMessages.isEmpty
? Center(child: Text('暂无日志', style: TextStyle(color: Colors.grey)))
: ListView.builder(
itemCount: _logMessages.length,
itemBuilder: (_, i) => Text(
_logMessages[i],
style: TextStyle(fontSize: 12, fontFamily: 'monospace'),
),
),
),
),
],
),
);
},
),
);
}
}
```
---
## 二、TCP 服务端(TcpServerCubit)
### 2.1 State 说明
`TcpServerState` 包含以下字段:
| 字段 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `isRunning` | `bool` | `false` | 服务是否正在运行(监听中) |
| `port` | `int` | `0` | 当前实际监听的端口 |
| `connectedClients` | `int` | `0` | 当前在线的客户端数量 |
| `error` | `String?` | `null` | 最近一次错误信息 |
### 2.2 公共 API 参考
| 方法/属性 | 签名 | 返回值 | 说明 |
|-----------|------|--------|------|
| `setPort()` | `setPort(int port)` | `void` | 设置目标监听端口。必须在 `start()` 之前调用 |
| `start()` | `Future<void>` | — | 启动 TCP 服务端监听。端口被占用时自动递增重试 |
| `stop()` | `Future<void>` | — | 停止服务端,关闭所有客户端连接 |
| `sendToAll()` | `sendToAll(Uint8List data)` | `void` | 向所有已连接客户端群发字节数据 |
| `sendHexToAll()` | `sendHexToAll(String hex)` | `void` | 向所有已连接客户端群发十六进制字符串 |
| `onClientData` | `Stream<Map<String,dynamic>>` (getter) | Stream | 客户端数据流,每条消息包含 client/address/port/data |
| `close()` | `Future<void>` | — | 资源清理:停服 + 关闭数据流 |
### 2.3 onClientData 数据格式
`onClientData` 流中的每条消息为 `Map<String, dynamic>`,包含以下字段:
| 字段 | 类型 | 说明 |
|------|------|------|
| `client` | `Socket` | 发送数据的客户端 Socket 对象(可用于定向回复) |
| `address` | `String` | 客户端 IP 地址(如 `"192.168.1.50"`) |
| `port` | `int` | 客户端端口号 |
| `data` | `Uint8List` | 接收到的原始字节数据 |
### 2.4 端口占用自动递增
`start()` 方法内置了端口冲突自动处理:
- 绑定指定端口时如果抛出 `already in use` 异常,会自动将端口号 +1 后重试
- 例如:设置端口 `8888` 被占用 → 尝试 `8889` → 再被占用 → 尝试 `8890` ...
- 最终绑定成功的端口号会更新到 `state.port`
> **注意**:此递归没有上限保护。如果在密集端口环境下使用,建议提前确认目标端口可用性。
### 2.5 基本使用
#### 第一步:注入
```dart
class MyPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => TcpServerCubit(),
child: MyPageBody(),
);
}
}
```
#### 第二步:设置端口并启动
```dart
final cubit = context.read<TcpServerCubit>();
// 设置监听端口
cubit.setPort(8888);
// 启动服务(异步)
await cubit.start();
print('服务运行在端口: ${cubit.state.port}'); // 可能因端口占用而自增
```
#### 第三步:监听客户端数据
```dart
final cubit = context.read<TcpServerCubit>();
// 订阅客户端数据流
cubit.onClientData.listen((msg) {
Socket client = msg['client']; // 可用于单独回复该客户端
String address = msg['address']; // 客户端 IP
int port = msg['port']; // 客户端端口
Uint8List data = msg['data']; // 收到的字节
print('[客户端 $address:$port] 收到 ${data.length} 字节');
// 示例:将收到数据转为 hex 显示
String hex = data
.map((b) => b.toRadixString(16).padLeft(2, '0').toUpperCase())
.join(' ');
print(' 数据: $hex');
});
```
#### 第四步:向客户端发送数据
```dart
final cubit = context.read<TcpServerCubit>();
// 群发字节数据给所有客户端
cubit.sendToAll(Uint8List.fromList([0xAA, 0xBB, 0xCC]));
// 群发十六进制字符串给所有客户端
cubit.sendHexToAll('AA BB CC DD EE FF');
```
#### 第五步:停止服务
```dart
await context.read<TcpServerCubit>().stop();
```
### 2.6 完整示例
```dart
import 'dart:async';
import 'dart:io';
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:smart_hotel_app/utils/tcp/service/tcp_server_cubit.dart';
import 'package:smart_hotel_app/utils/tcp/service/tcp_server_state.dart';
class TcpServerDemoPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => TcpServerCubit(),
child: _TcpServerDemoView(),
);
}
}
class _TcpServerDemoView extends StatefulWidget {
@override
State<_TcpServerDemoView> createState() => _TcpServerDemoViewState();
}
class _TcpServerDemoViewState extends State<_TcpServerDemoView> {
StreamSubscription? _dataSub;
final TextEditingController _portController = TextEditingController(text: '8888');
final TextEditingController _hexController = TextEditingController(text: 'AA BB CC DD');
List<String> _logMessages = [];
void _addLog(String msg) {
setState(() => _logMessages.insert(0, '${DateTime.now().toString().substring(11, 19)} $msg'));
}
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) => _init());
}
void _init() {
final cubit = context.read<TcpServerCubit>();
_dataSub = cubit.onClientData.listen((msg) {
final addr = msg['address'];
final data = msg['data'] as Uint8List;
final hex = data.map((b) => b.toRadixString(16).padLeft(2, '0').toUpperCase()).join(' ');
_addLog('[$addr] << ${data.length}B $hex');
});
}
@override
void dispose() {
_dataSub?.cancel();
_portController.dispose();
_hexController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('TCP 服务端 Demo')),
body: BlocBuilder<TcpServerCubit, TcpServerState>(
builder: (context, state) {
final cubit = context.read<TcpServerCubit>();
final running = state.isRunning;
return Padding(
padding: EdgeInsets.all(16),
child: Column(
children: [
// 状态卡片
Container(
width: double.infinity,
padding: EdgeInsets.all(12),
decoration: BoxDecoration(
color: running ? Colors.blue[50] : Colors.grey[200],
borderRadius: BorderRadius.circular(8),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(running ? Icons.dns : Icons.dns_outlined,
color: running ? Colors.blue : Colors.grey),
SizedBox(width: 8),
Text(running ? '服务运行中' : '服务已停止',
style: TextStyle(fontWeight: FontWeight.bold)),
],
),
SizedBox(height: 4),
Text('监听端口: ${state.port} | 在线客户端: ${state.connectedClients}',
style: TextStyle(fontSize: 13)),
],
),
),
if (state.error != null)
Padding(
padding: EdgeInsets.only(top: 8),
child: Text(state.error!, style: TextStyle(color: Colors.red, fontSize: 12)),
),
SizedBox(height: 16),
// 端口输入 + 启动/停止按钮
Row(
children: [
Expanded(
flex: 2,
child: TextField(
controller: _portController,
keyboardType: TextInputType.number,
decoration: InputDecoration(labelText: '端口', border: OutlineInputBorder()),
enabled: !running,
),
),
SizedBox(width: 8),
Expanded(
child: ElevatedButton.icon(
onPressed: running ? null : () {
cubit.setPort(int.parse(_portController.text));
cubit.start();
},
icon: Icon(Icons.play_arrow),
label: Text('启动'),
),
),
SizedBox(width: 8),
Expanded(
child: OutlinedButton.icon(
onPressed: running ? () => cubit.stop() : null,
icon: Icon(Icons.stop),
label: Text('停止'),
),
),
],
),
SizedBox(height: 12),
// 群发输入
TextField(
controller: _hexController,
decoration: InputDecoration(
labelText: '群发十六进制数据',
suffixIcon: IconButton(
icon: Icon(Icons.send),
onPressed: running ? () {
cubit.sendHexToAll(_hexController.text);
_addLog('>> 群发 ${_hexController.text}');
} : null,
),
),
),
SizedBox(height: 12),
// 日志
Expanded(
child: Container(
width: double.infinity,
padding: EdgeInsets.all(8),
decoration: BoxDecoration(
border: Border.all(color: Colors.grey[300]!),
borderRadius: BorderRadius.circular(4),
),
child: _logMessages.isEmpty
? Center(child: Text('等待客户端连接...', style: TextStyle(color: Colors.grey)))
: ListView.builder(
itemCount: _logMessages.length,
itemBuilder: (_, i) => Text(
_logMessages[i],
style: TextStyle(fontSize: 12, fontFamily: 'monospace'),
),
),
),
),
],
),
);
},
),
);
}
}
```
---
## 三、高级用法:客户端+服务端同时使用
在需要同时作为客户端和服务端的场景(如 P2P 通信、设备桥接等),可以分别注入两个 Cubit:
```dart
MultiBlocProvider(
providers: [
BlocProvider<TcpClientCubit>(create: (_) => TcpClientCubit()),
BlocProvider<TcpServerCubit>(create: (_) => TcpServerCubit()),
],
child: MyPage(),
)
```
两个 Cubit 完全独立,各自管理自己的 Socket 和状态,互不干扰。
**典型场景:智能酒店中控**
- **作为服务端**:监听局域网内设备的连接请求(设备主动上报状态)
- **作为客户端**:主动向特定设备下发控制指令
```dart
class HotelControlPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
// 服务端:监听设备上报
BlocProvider<TcpServerCubit>(
create: (_) => TcpServerCubit()..setPort(9000)..start(),
),
// 客户端:向设备下发指令
BlocProvider<TcpClientCubit>(
create: (_) => TcpClientCubit(),
),
],
child: _HotelControlView(),
);
}
}
```
---
## 四、在业务 Cubit 中封装使用
推荐将 TCP 通信封装在业务 Cubit 中,而不是直接在 Widget 层操作。这样可以将协议解析、状态管理与 UI 解耦。
### 4.1 业务 Cubit 封装示例
```dart
/// 设备控制业务 Cubit
class DeviceControlCubit extends Cubit<DeviceControlState> {
final TcpClientCubit _tcpClient;
StreamSubscription<Uint8List>? _dataSub;
DeviceControlCubit(this._tcpClient) : super(DeviceControlState.initial()) {
// 订阅 TCP 数据流
_dataSub = _tcpClient.onData.listen(_handleDeviceResponse);
}
/// 连接设备
Future<void> connectDevice(String ip, int port) async {
emit(state.copyWith(status: DeviceStatus.connecting));
_tcpClient.config(address: ip, port: port, timeout: 3000);
bool success = await _tcpClient.connect();
if (success) {
emit(state.copyWith(status: DeviceStatus.connected, deviceIp: ip));
} else {
emit(state.copyWith(status: DeviceStatus.error, error: _tcpClient.state.error));
}
}
/// 发送控制指令
Future<void> sendCommand(DeviceCommand command) async {
if (!_tcpClient.state.isConnected) {
emit(state.copyWith(error: '设备未连接'));
return;
}
emit(state.copyWith(isSending: true));
bool ok = await _tcpClient.sendHex(command.hexCode);
if (!ok) {
emit(state.copyWith(error: '指令发送失败'));
}
emit(state.copyWith(isSending: false));
}
/// 处理设备响应数据
void _handleDeviceResponse(Uint8List data) {
// 在这里实现你的协议解析逻辑
// 示例:假设前两字节是命令类型,后续是数据
if (data.length >= 2) {
final cmdType = data[0];
switch (cmdType) {
case 0x01: // 设备状态上报
emit(state.copyWith(deviceOnline: data[1] == 0x01));
break;
case 0x02: // 温度数据
final temp = (data[1] << 8) | data[2];
emit(state.copyWith(temperature: temp / 10.0));
break;
// ... 其他命令类型
}
}
}
/// 断开连接
void disconnect() {
_tcpClient.disconnect();
emit(state.copyWith(status: DeviceStatus.disconnected));
}
@override
Future<void> close() {
_dataSub?.cancel();
_tcpClient.disconnect();
return super.close();
}
}
```
### 4.2 注入方式
```dart
// 方式一:RepositoryProvider 注入 TCP Cubit,再传给业务 Cubit
RepositoryProvider<TcpClientCubit>(
create: (_) => TcpClientCubit(),
child: BlocProvider(
create: (context) => DeviceControlCubit(context.read<TcpClientCubit>()),
child: DeviceControlPage(),
),
)
// 方式二:直接在 BlocProvider.create 中创建
BlocProvider(
create: (context) {
final tcp = TcpClientCubit();
return DeviceControlCubit(tcp); // 由 DeviceControlCubit 管理 tcp 生命周期
},
child: DeviceControlPage(),
)
```
---
## 五、生命周期与资源清理
### 5.1 清理时序图
```
Widget 销毁 (dispose)
BlocProvider 自动调用 cubit.close()
├── TcpClientCubit.close()
│ ├── disconnect() → 取消重连Timer + 心跳Timer + 关闭Socket
│ └── _dataController.close() → 关闭数据流
├── TcpServerCubit.close()
│ ├── stop() → 关闭所有客户端Socket + 关闭ServerSocket
│ └── _dataController.close() → 关闭数据流
└── super.close() → Cubit 自身销毁
```
### 5.2 各场景下的清理策略
| 场景 | 推荐做法 | 说明 |
|------|---------|------|
| 页面跳转后不再需要 | 不做任何处理 | `BlocProvider` 自动调用 `close()`,完整清理 |
| 页面重建但保持连接 | 使用全局/顶层 `BlocProvider` | 不要放在页面级 `BlocProvider` 中 |
| 手动临时断开 | 调用 `cubit.disconnect()` | Cubit 保留,可以再次 `connect()` |
| 应用退出 | 不需要特殊处理 | Flutter 会依次销毁 Widget 树 |
### 5.3 StreamSubscription 管理
**务必在 `dispose()` 中取消订阅**,否则会导致内存泄漏:
```dart
// ✅ 正确做法
class _MyWidgetState extends State<MyWidget> {
StreamSubscription<Uint8List>? _sub;
@override
void initState() {
super.initState();
_sub = context.read<TcpClientCubit>().onData.listen(/* ... */);
}
@override
void dispose() {
_sub?.cancel(); // 必须!
super.dispose();
}
}
// ❌ 错误做法:忘记 cancel
@override
void dispose() {
// _sub 未取消 → 内存泄漏
super.dispose();
}
```
---
## 六、常见问题排查(FAQ)
### Q1: connect() 返回 false 但没有 error 信息?
可能原因:
- 地址/端口未配置(需先调用 `config()`
- 已有一个连接正在进行中(并发防护机制拦截)
解决方法:
```dart
if (!await cubit.connect()) {
if (cubit.state.address == null) {
print('请先调用 config() 配置地址和端口');
} else if (cubit.state.connectionStatus == TcpConnectionStatus.connecting) {
print('连接进行中,请勿重复调用');
} else {
print('连接失败: ${cubit.state.error}');
}
}
```
### Q2: 发送数据对方没收到?
检查项:
1. `state.isConnected` 是否为 `true`
2. `sendHex()` / `sendString()` 返回值是否为 `true`
3. 对方是否还在连接状态
4. 网络防火墙是否放行了对应端口
### Q3: 如何知道当前连接的是哪台服务器?
```dart
// 从 state 中读取
final addr = cubit.state.address; // "192.168.1.100"
final port = cubit.state.port; // 8888
```
### Q4: 服务端如何识别不同客户端?
`onClientData` 流中每条消息都携带了 `client`(Socket 对象)、`address``port`,可用于区分:
```dart
cubit.onClientData.listen((msg) {
final key = '${msg['address']}:${msg['port']}'; // 唯一标识一个客户端
print('来自 $key 的消息');
});
```
### Q5: 心跳包内容可以自定义吗?
目前心跳固定发送 `00 00 00 00`。如需修改,直接编辑 `tcp_client_cubit.dart` 中的 `_startHeartbeat()` 方法即可。
---
## 七、从旧 GetX 代码迁移
| 旧 API(GetX) | 新 API(Cubit) | 备注 |
|---|---|---|
| `Get.put(TcpClientService())` | `BlocProvider(create: (_) => TcpClientCubit())` | 依赖注入方式变更 |
| `tcpClient.connectionState.value` | `state.connectionStatus` | 枚举名称略有调整 |
| `ever(tcpClient.connectionState, cb)` | `BlocListener<TcpClientCubit, TcpClientState>(listener: cb)` | 副作用监听 |
| `Obx(() => Text(...))` | `BlocBuilder<TcpClientCubit, TcpClientState>(builder: ...)` | 响应式 UI 构建 |
| `Get.put(TcpServerService())` | `BlocProvider(create: (_) => TcpServerCubit())` | 同上 |
| `tcpServer.connectedClients.value` | `state.connectedClients` | 直接读取 state |
| `TcpConnectionEnum.connected` | `TcpConnectionStatus.connected` | 枚举类名变更 |
import 'dart:ui';
import 'package:fluttertoast/fluttertoast.dart';
void showToast(String msg) {
Fluttertoast.showToast(
msg: msg,
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.CENTER,
timeInSecForIosWeb: 2,
backgroundColor: const Color(0xDD000000),
textColor: const Color(0xFFFFFFFF),
fontSize: 16.0,
);
}
\ No newline at end of file
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
@RoutePage()
class HomeView extends StatelessWidget {
const HomeView({super.key});
@override
Widget build(BuildContext context) {
return const Center(
child: Text("你好"),
);
}
}
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
@RoutePage()
class DefaultLayoutView extends StatelessWidget {
const DefaultLayoutView({super.key});
@override
Widget build(BuildContext context) {
return const AutoRouter();
}
}
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/utils/storage/storage_service.dart';
import 'package:smart_hotel_app/views/login/cubit/login_state.dart';
class LoginCubit extends Cubit<LoginState> {
final AuthBloc authBloc;
final StorageService storageService;
LoginCubit({
required this.authBloc,
required this.storageService,
}) : super(const LoginState()) {
_listenAuthState();
}
void _listenAuthState() {
authBloc.stream.listen((authState) {
if (authState is AuthFailure) {
emit(state.copyWith(isLoading: false, error: authState.error));
} else if (authState is AuthLoading) {
emit(state.copyWith(isLoading: true, error: null));
} else if (authState is AuthSuccess) {
emit(state.copyWith(isLoading: false, error: null));
}
});
}
void toggleRememberPassword(bool value) {
emit(state.copyWith(rememberPassword: value));
}
void login({
required String username,
required String password,
bool rememberPassword = false,
}) {
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) {
emit(state.copyWith(
rememberPassword: true,
savedUsername: credentials['username'],
savedPassword: credentials['password'],
));
}
}
void forgotPwd() {
// TODO: 跳转到忘记密码页面
}
}
\ No newline at end of file
import 'package:equatable/equatable.dart';
class LoginState extends Equatable {
final bool rememberPassword;
final bool isLoading;
final String? error;
final String? savedUsername;
final String? savedPassword;
const LoginState({
this.rememberPassword = false,
this.isLoading = false,
this.error,
this.savedUsername,
this.savedPassword,
});
LoginState copyWith({
bool? rememberPassword,
bool? isLoading,
String? error,
String? savedUsername,
String? savedPassword,
}) {
return LoginState(
rememberPassword: rememberPassword ?? this.rememberPassword,
isLoading: isLoading ?? this.isLoading,
error: error,
savedUsername: savedUsername ?? this.savedUsername,
savedPassword: savedPassword ?? this.savedPassword,
);
}
@override
List<Object?> get props => [
rememberPassword,
isLoading,
error,
savedUsername,
savedPassword,
];
}
\ No newline at end of file
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.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/cubit/login_cubit.dart';
import 'package:smart_hotel_app/views/login/cubit/login_state.dart';
import 'package:smart_hotel_app/views/login/widget/btn_login.dart';
import 'package:smart_hotel_app/views/login/widget/password_field.dart';
import 'package:smart_hotel_app/views/login/widget/remember_password_row.dart';
import 'package:smart_hotel_app/views/login/widget/username_field.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
@RoutePage()
class LoginView extends StatelessWidget {
LoginView({super.key});
final _frmGlobalKey = GlobalKey<FormState>();
final _txtUserNameController = TextEditingController();
final _txtPwdController = TextEditingController();
@override
Widget build(BuildContext context) {
SystemChrome.setSystemUIOverlayStyle(
const SystemUiOverlayStyle(
statusBarColor: Colors.transparent,
statusBarIconBrightness: Brightness.dark,
statusBarBrightness: Brightness.light,
),
);
return BlocProvider(
create: (_) {
final cubit = LoginCubit(
authBloc: context.read<AuthBloc>(),
storageService: StorageService(),
);
cubit.loadRememberCredentials();
return cubit;
},
child: AnnotatedRegion<SystemUiOverlayStyle>(
value: const SystemUiOverlayStyle(
statusBarColor: Colors.transparent,
statusBarIconBrightness: Brightness.dark,
statusBarBrightness: Brightness.light,
),
child: Scaffold(
backgroundColor: const Color(0xFFF5F7FA),
resizeToAvoidBottomInset: true,
body: SafeArea(
top: false,
child: Container(
width: double.infinity,
height: double.infinity,
padding: EdgeInsets.only(left: 50.w, right: 50.w),
decoration: const BoxDecoration(
color: Color(0xFFF5F7FA),
),
child: SingleChildScrollView(
child: BlocBuilder<LoginCubit, LoginState>(
builder: (context, state) {
final cubit = context.read<LoginCubit>();
if (state.savedUsername != null) {
_txtUserNameController.text = state.savedUsername!;
}
if (state.savedPassword != null) {
_txtPwdController.text = state.savedPassword!;
}
return Column(
children: [
SizedBox(height: 150.h),
Container(
margin: EdgeInsets.only(bottom: 80.h),
child: Column(
children: [
Container(
width: 160.w,
height: 160.w,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(40.w),
gradient: const LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Color(0xFF00C6FF),
Color(0xFF0072FF),
],
),
),
child: ClipRRect(
borderRadius: BorderRadius.circular(40.w),
child: Image.asset(
'lib/assets/login/logo@2x.png',
fit: BoxFit.cover,
),
),
),
SizedBox(height: 40.h),
const Text(
'智能酒店管理系统',
style: TextStyle(
color: Color(0xFF333333),
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 10.h),
const Text(
'Intelligent Hotel Management System',
style: TextStyle(
color: Color(0xFF999999),
fontSize: 14,
),
),
],
),
),
Form(
key: _frmGlobalKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
UsernameField(
controller: _txtUserNameController,
),
SizedBox(height: 20.h),
PasswordField(
controller: _txtPwdController,
),
SizedBox(height: 30.h),
RememberPasswordRow(
value: state.rememberPassword,
onChanged: (val) {
cubit.toggleRememberPassword(val ?? false);
},
onForgotPassword: cubit.forgotPwd,
),
SizedBox(height: 10.h),
BlocConsumer<AuthBloc, AuthState>(
listener: (context, authState) {
if (authState is AuthFailure) {
Fluttertoast.showToast(
msg: authState.error,
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.CENTER,
timeInSecForIosWeb: 2,
backgroundColor: Colors.red,
textColor: Colors.white,
fontSize: 16.0,
);
}
},
builder: (context, authState) {
return BtnLogin(
isLoading: authState is AuthLoading,
onPressed: () {
if (_frmGlobalKey.currentState!
.validate()) {
cubit.login(
username:
_txtUserNameController.text
.trim(),
password:
_txtPwdController.text.trim(),
rememberPassword:
state.rememberPassword,
);
}
},
);
},
),
],
),
),
],
);
},
),
),
),
),
),
),
);
}
}
\ No newline at end of file
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: 104.h,
decoration: BoxDecoration(
color: Color.fromRGBO(73, 149, 234, 1),
borderRadius: BorderRadius.circular(40.w),
),
child: ElevatedButton(
onPressed: isLoading ? null : onPressed,
style: ElevatedButton.styleFrom(
backgroundColor: Colors.transparent,
shadowColor: Colors.transparent,
elevation: 0,
),
child: isLoading
? SizedBox(
width: 40.w,
height: 40.w,
child: CircularProgressIndicator(
strokeWidth: 3.w,
color: Colors.white,
),
)
: Text(
"登录",
style: TextStyle(
color: Colors.white,
fontSize: 40.sp,
fontWeight: FontWeight.bold,
),
),
),
);
}
}
\ 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: TextStyle(color: Color(0xFF333333), fontSize: 28.sp),
validator: (val) {
if (val == null || val.isEmpty) {
return "密码不能为空";
}
return null;
},
decoration: InputDecoration(
filled: true,
hintText: "password",
hintStyle: TextStyle(color: Color(0xFF999999), fontSize: 28.sp),
prefixIcon: Icon(Icons.lock, color: Color(0xFF999999), size: 40.w),
prefixIconConstraints: BoxConstraints(minWidth: 80.w, minHeight: 80.w),
suffixIcon: IconButton(
icon: Icon(
_obscureText ? Icons.visibility_off : Icons.visibility,
color: Color(0xFF999999),
size: 40.w,
),
onPressed: _toggleVisibility,
),
suffixIconConstraints: BoxConstraints(minWidth: 80.w, minHeight: 80.w),
contentPadding: EdgeInsets.symmetric(vertical: 20.h, horizontal: 24.w),
helperText: " ",
helperStyle: TextStyle(height: 1, fontSize: 24.sp),
errorStyle: TextStyle(color: Colors.red, fontSize: 24.sp, height: 1),
errorMaxLines: 1,
fillColor: Colors.white,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(24.w),
borderSide: BorderSide.none,
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(24.w),
borderSide: BorderSide(color: Color(0xFFF5F7FA), width: 1),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(24.w),
borderSide: BorderSide(color: Color(0xFFF5F7FA), width: 1),
),
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(24.w),
borderSide: BorderSide(color: Colors.red, width: 1),
),
focusedErrorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(24.w),
borderSide: 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: 40.w,
height: 40.w,
child: Transform.scale(
scale: 0.8,
child: Checkbox(
value: value,
onChanged: onChanged,
activeColor: Color(0xFF4A90E2),
checkColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(25.w),
),
side: BorderSide(color: Color(0xFF4A90E2)),
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
),
),
SizedBox(width: 10.w),
Text(
'记住密码',
style: TextStyle(
color: Color(0xFF0A0D14),
fontSize: 24.sp,
),
),
],
),
),
GestureDetector(
onTap: onForgotPassword,
child: Text(
'忘记密码?',
style: TextStyle(
color: Color(0xFF0A0D14),
fontSize: 24.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: TextStyle(color: Color(0xFF333333), fontSize: 28.sp),
validator: (val) {
if (val == null || val.isEmpty) {
return "用户名不能为空哦";
}
return null;
},
decoration: InputDecoration(
filled: true,
hintText: "admin",
hintStyle: TextStyle(color: Color(0xFF999999), fontSize: 28.sp),
prefixIcon: Icon(Icons.person, color: Color(0xFF999999), size: 40.w),
prefixIconConstraints: BoxConstraints(minWidth: 80.w, minHeight: 80.w),
contentPadding: EdgeInsets.symmetric(vertical: 20.h, horizontal: 24.w),
helperText: " ",
helperStyle: TextStyle(height: 1, fontSize: 24.sp),
errorStyle: TextStyle(color: Colors.red, fontSize: 24.sp, height: 1),
errorMaxLines: 1,
fillColor: Colors.white,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(24.w),
borderSide: BorderSide.none,
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(24.w),
borderSide: BorderSide(color: Color(0xFFF5F7FA), width: 1),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(24.w),
borderSide: BorderSide(color: Color(0xFFF5F7FA), width: 1),
),
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(24.w),
borderSide: BorderSide(color: Colors.red, width: 1),
),
focusedErrorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(24.w),
borderSide: BorderSide(color: Colors.red, width: 1),
),
),
);
}
}
\ No newline at end of file
flutter/ephemeral
# Project-level configuration.
cmake_minimum_required(VERSION 3.10)
project(runner LANGUAGES CXX)
# The name of the executable created for the application. Change this to change
# the on-disk name of your application.
set(BINARY_NAME "smart_hotel_app")
# The unique GTK application identifier for this application. See:
# https://wiki.gnome.org/HowDoI/ChooseApplicationID
set(APPLICATION_ID "com.example.smart_hotel_app")
# Explicitly opt in to modern CMake behaviors to avoid warnings with recent
# versions of CMake.
cmake_policy(SET CMP0063 NEW)
# Load bundled libraries from the lib/ directory relative to the binary.
set(CMAKE_INSTALL_RPATH "$ORIGIN/lib")
# Root filesystem for cross-building.
if(FLUTTER_TARGET_PLATFORM_SYSROOT)
set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT})
set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT})
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
endif()
# Define build configuration options.
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
set(CMAKE_BUILD_TYPE "Debug" CACHE
STRING "Flutter build mode" FORCE)
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
"Debug" "Profile" "Release")
endif()
# Compilation settings that should be applied to most targets.
#
# Be cautious about adding new options here, as plugins use this function by
# default. In most cases, you should add new options to specific targets instead
# of modifying this function.
function(APPLY_STANDARD_SETTINGS TARGET)
target_compile_features(${TARGET} PUBLIC cxx_std_14)
target_compile_options(${TARGET} PRIVATE -Wall -Werror)
target_compile_options(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:-O3>")
target_compile_definitions(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:NDEBUG>")
endfunction()
# Flutter library and tool build rules.
set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
add_subdirectory(${FLUTTER_MANAGED_DIR})
# System-level dependencies.
find_package(PkgConfig REQUIRED)
pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}")
# Define the application target. To change its name, change BINARY_NAME above,
# not the value here, or `flutter run` will no longer work.
#
# Any new source files that you add to the application should be added here.
add_executable(${BINARY_NAME}
"main.cc"
"my_application.cc"
"${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
)
# Apply the standard set of build settings. This can be removed for applications
# that need different build settings.
apply_standard_settings(${BINARY_NAME})
# Add dependency libraries. Add any application-specific dependencies here.
target_link_libraries(${BINARY_NAME} PRIVATE flutter)
target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK)
# Run the Flutter tool portions of the build. This must not be removed.
add_dependencies(${BINARY_NAME} flutter_assemble)
# Only the install-generated bundle's copy of the executable will launch
# correctly, since the resources must in the right relative locations. To avoid
# people trying to run the unbundled copy, put it in a subdirectory instead of
# the default top-level location.
set_target_properties(${BINARY_NAME}
PROPERTIES
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run"
)
# Generated plugin build rules, which manage building the plugins and adding
# them to the application.
include(flutter/generated_plugins.cmake)
# === Installation ===
# By default, "installing" just makes a relocatable bundle in the build
# directory.
set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle")
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
endif()
# Start with a clean build bundle directory every time.
install(CODE "
file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\")
" COMPONENT Runtime)
set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib")
install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
COMPONENT Runtime)
install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
COMPONENT Runtime)
install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES})
install(FILES "${bundled_library}"
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
endforeach(bundled_library)
# Copy the native assets provided by the build.dart from all packages.
set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/")
install(DIRECTORY "${NATIVE_ASSETS_DIR}"
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
# Fully re-copy the assets directory on each build to avoid having stale files
# from a previous install.
set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
install(CODE "
file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
" COMPONENT Runtime)
install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
# Install the AOT library on non-Debug builds only.
if(NOT CMAKE_BUILD_TYPE MATCHES "Debug")
install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
endif()
# This file controls Flutter-level build steps. It should not be edited.
cmake_minimum_required(VERSION 3.10)
set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
# Configuration provided via flutter tool.
include(${EPHEMERAL_DIR}/generated_config.cmake)
# TODO: Move the rest of this into files in ephemeral. See
# https://github.com/flutter/flutter/issues/57146.
# Serves the same purpose as list(TRANSFORM ... PREPEND ...),
# which isn't available in 3.10.
function(list_prepend LIST_NAME PREFIX)
set(NEW_LIST "")
foreach(element ${${LIST_NAME}})
list(APPEND NEW_LIST "${PREFIX}${element}")
endforeach(element)
set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE)
endfunction()
# === Flutter Library ===
# System-level dependencies.
find_package(PkgConfig REQUIRED)
pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0)
pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0)
set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so")
# Published to parent scope for install step.
set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE)
list(APPEND FLUTTER_LIBRARY_HEADERS
"fl_basic_message_channel.h"
"fl_binary_codec.h"
"fl_binary_messenger.h"
"fl_dart_project.h"
"fl_engine.h"
"fl_json_message_codec.h"
"fl_json_method_codec.h"
"fl_message_codec.h"
"fl_method_call.h"
"fl_method_channel.h"
"fl_method_codec.h"
"fl_method_response.h"
"fl_plugin_registrar.h"
"fl_plugin_registry.h"
"fl_standard_message_codec.h"
"fl_standard_method_codec.h"
"fl_string_codec.h"
"fl_value.h"
"fl_view.h"
"flutter_linux.h"
)
list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/")
add_library(flutter INTERFACE)
target_include_directories(flutter INTERFACE
"${EPHEMERAL_DIR}"
)
target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}")
target_link_libraries(flutter INTERFACE
PkgConfig::GTK
PkgConfig::GLIB
PkgConfig::GIO
)
add_dependencies(flutter flutter_assemble)
# === Flutter tool backend ===
# _phony_ is a non-existent file to force this command to run every time,
# since currently there's no way to get a full input/output list from the
# flutter tool.
add_custom_command(
OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
${CMAKE_CURRENT_BINARY_DIR}/_phony_
COMMAND ${CMAKE_COMMAND} -E env
${FLUTTER_TOOL_ENVIRONMENT}
"${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh"
${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE}
VERBATIM
)
add_custom_target(flutter_assemble DEPENDS
"${FLUTTER_LIBRARY}"
${FLUTTER_LIBRARY_HEADERS}
)
//
// Generated file. Do not edit.
//
// clang-format off
#include "generated_plugin_registrant.h"
#include <flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h>
void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin");
flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar);
}
//
// Generated file. Do not edit.
//
// clang-format off
#ifndef GENERATED_PLUGIN_REGISTRANT_
#define GENERATED_PLUGIN_REGISTRANT_
#include <flutter_linux/flutter_linux.h>
// Registers Flutter plugins.
void fl_register_plugins(FlPluginRegistry* registry);
#endif // GENERATED_PLUGIN_REGISTRANT_
#
# Generated file, do not edit.
#
list(APPEND FLUTTER_PLUGIN_LIST
flutter_secure_storage_linux
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST
jni
)
set(PLUGIN_BUNDLED_LIBRARIES)
foreach(plugin ${FLUTTER_PLUGIN_LIST})
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin})
target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
endforeach(plugin)
foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin})
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
endforeach(ffi_plugin)
#include "my_application.h"
int main(int argc, char** argv) {
g_autoptr(MyApplication) app = my_application_new();
return g_application_run(G_APPLICATION(app), argc, argv);
}
#include "my_application.h"
#include <flutter_linux/flutter_linux.h>
#ifdef GDK_WINDOWING_X11
#include <gdk/gdkx.h>
#endif
#include "flutter/generated_plugin_registrant.h"
struct _MyApplication {
GtkApplication parent_instance;
char** dart_entrypoint_arguments;
};
G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION)
// Implements GApplication::activate.
static void my_application_activate(GApplication* application) {
MyApplication* self = MY_APPLICATION(application);
GtkWindow* window =
GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application)));
// Use a header bar when running in GNOME as this is the common style used
// by applications and is the setup most users will be using (e.g. Ubuntu
// desktop).
// If running on X and not using GNOME then just use a traditional title bar
// in case the window manager does more exotic layout, e.g. tiling.
// If running on Wayland assume the header bar will work (may need changing
// if future cases occur).
gboolean use_header_bar = TRUE;
#ifdef GDK_WINDOWING_X11
GdkScreen* screen = gtk_window_get_screen(window);
if (GDK_IS_X11_SCREEN(screen)) {
const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen);
if (g_strcmp0(wm_name, "GNOME Shell") != 0) {
use_header_bar = FALSE;
}
}
#endif
if (use_header_bar) {
GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new());
gtk_widget_show(GTK_WIDGET(header_bar));
gtk_header_bar_set_title(header_bar, "smart_hotel_app");
gtk_header_bar_set_show_close_button(header_bar, TRUE);
gtk_window_set_titlebar(window, GTK_WIDGET(header_bar));
} else {
gtk_window_set_title(window, "smart_hotel_app");
}
gtk_window_set_default_size(window, 1280, 720);
gtk_widget_show(GTK_WIDGET(window));
g_autoptr(FlDartProject) project = fl_dart_project_new();
fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments);
FlView* view = fl_view_new(project);
gtk_widget_show(GTK_WIDGET(view));
gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view));
fl_register_plugins(FL_PLUGIN_REGISTRY(view));
gtk_widget_grab_focus(GTK_WIDGET(view));
}
// Implements GApplication::local_command_line.
static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) {
MyApplication* self = MY_APPLICATION(application);
// Strip out the first argument as it is the binary name.
self->dart_entrypoint_arguments = g_strdupv(*arguments + 1);
g_autoptr(GError) error = nullptr;
if (!g_application_register(application, nullptr, &error)) {
g_warning("Failed to register: %s", error->message);
*exit_status = 1;
return TRUE;
}
g_application_activate(application);
*exit_status = 0;
return TRUE;
}
// Implements GObject::dispose.
static void my_application_dispose(GObject* object) {
MyApplication* self = MY_APPLICATION(object);
g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev);
G_OBJECT_CLASS(my_application_parent_class)->dispose(object);
}
static void my_application_class_init(MyApplicationClass* klass) {
G_APPLICATION_CLASS(klass)->activate = my_application_activate;
G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line;
G_OBJECT_CLASS(klass)->dispose = my_application_dispose;
}
static void my_application_init(MyApplication* self) {}
MyApplication* my_application_new() {
return MY_APPLICATION(g_object_new(my_application_get_type(),
"application-id", APPLICATION_ID,
"flags", G_APPLICATION_NON_UNIQUE,
nullptr));
}
#ifndef FLUTTER_MY_APPLICATION_H_
#define FLUTTER_MY_APPLICATION_H_
#include <gtk/gtk.h>
G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION,
GtkApplication)
/**
* my_application_new:
*
* Creates a new Flutter-based application.
*
* Returns: a new #MyApplication.
*/
MyApplication* my_application_new();
#endif // FLUTTER_MY_APPLICATION_H_
cmake_minimum_required(VERSION 3.13)
project(runner LANGUAGES CXX)
# Define the application target. To change its name, change BINARY_NAME in the
# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer
# work.
#
# Any new source files that you add to the application should be added here.
add_executable(${BINARY_NAME}
"main.cc"
"my_application.cc"
"${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
)
# Apply the standard set of build settings. This can be removed for applications
# that need different build settings.
apply_standard_settings(${BINARY_NAME})
# Add preprocessor definitions for the application ID.
add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}")
# Add dependency libraries. Add any application-specific dependencies here.
target_link_libraries(${BINARY_NAME} PRIVATE flutter)
target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK)
target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}")
#include "my_application.h"
int main(int argc, char** argv) {
g_autoptr(MyApplication) app = my_application_new();
return g_application_run(G_APPLICATION(app), argc, argv);
}
#include "my_application.h"
#include <flutter_linux/flutter_linux.h>
#ifdef GDK_WINDOWING_X11
#include <gdk/gdkx.h>
#endif
#include "flutter/generated_plugin_registrant.h"
struct _MyApplication {
GtkApplication parent_instance;
char** dart_entrypoint_arguments;
};
G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION)
// Called when first Flutter frame received.
static void first_frame_cb(MyApplication* self, FlView* view) {
gtk_widget_show(gtk_widget_get_toplevel(GTK_WIDGET(view)));
}
// Implements GApplication::activate.
static void my_application_activate(GApplication* application) {
MyApplication* self = MY_APPLICATION(application);
GtkWindow* window =
GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application)));
// Use a header bar when running in GNOME as this is the common style used
// by applications and is the setup most users will be using (e.g. Ubuntu
// desktop).
// If running on X and not using GNOME then just use a traditional title bar
// in case the window manager does more exotic layout, e.g. tiling.
// If running on Wayland assume the header bar will work (may need changing
// if future cases occur).
gboolean use_header_bar = TRUE;
#ifdef GDK_WINDOWING_X11
GdkScreen* screen = gtk_window_get_screen(window);
if (GDK_IS_X11_SCREEN(screen)) {
const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen);
if (g_strcmp0(wm_name, "GNOME Shell") != 0) {
use_header_bar = FALSE;
}
}
#endif
if (use_header_bar) {
GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new());
gtk_widget_show(GTK_WIDGET(header_bar));
gtk_header_bar_set_title(header_bar, "smart_hotel_app");
gtk_header_bar_set_show_close_button(header_bar, TRUE);
gtk_window_set_titlebar(window, GTK_WIDGET(header_bar));
} else {
gtk_window_set_title(window, "smart_hotel_app");
}
gtk_window_set_default_size(window, 1280, 720);
g_autoptr(FlDartProject) project = fl_dart_project_new();
fl_dart_project_set_dart_entrypoint_arguments(
project, self->dart_entrypoint_arguments);
FlView* view = fl_view_new(project);
GdkRGBA background_color;
// Background defaults to black, override it here if necessary, e.g. #00000000
// for transparent.
gdk_rgba_parse(&background_color, "#000000");
fl_view_set_background_color(view, &background_color);
gtk_widget_show(GTK_WIDGET(view));
gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view));
// Show the window when Flutter renders.
// Requires the view to be realized so we can start rendering.
g_signal_connect_swapped(view, "first-frame", G_CALLBACK(first_frame_cb),
self);
gtk_widget_realize(GTK_WIDGET(view));
fl_register_plugins(FL_PLUGIN_REGISTRY(view));
gtk_widget_grab_focus(GTK_WIDGET(view));
}
// Implements GApplication::local_command_line.
static gboolean my_application_local_command_line(GApplication* application,
gchar*** arguments,
int* exit_status) {
MyApplication* self = MY_APPLICATION(application);
// Strip out the first argument as it is the binary name.
self->dart_entrypoint_arguments = g_strdupv(*arguments + 1);
g_autoptr(GError) error = nullptr;
if (!g_application_register(application, nullptr, &error)) {
g_warning("Failed to register: %s", error->message);
*exit_status = 1;
return TRUE;
}
g_application_activate(application);
*exit_status = 0;
return TRUE;
}
// Implements GApplication::startup.
static void my_application_startup(GApplication* application) {
// MyApplication* self = MY_APPLICATION(object);
// Perform any actions required at application startup.
G_APPLICATION_CLASS(my_application_parent_class)->startup(application);
}
// Implements GApplication::shutdown.
static void my_application_shutdown(GApplication* application) {
// MyApplication* self = MY_APPLICATION(object);
// Perform any actions required at application shutdown.
G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application);
}
// Implements GObject::dispose.
static void my_application_dispose(GObject* object) {
MyApplication* self = MY_APPLICATION(object);
g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev);
G_OBJECT_CLASS(my_application_parent_class)->dispose(object);
}
static void my_application_class_init(MyApplicationClass* klass) {
G_APPLICATION_CLASS(klass)->activate = my_application_activate;
G_APPLICATION_CLASS(klass)->local_command_line =
my_application_local_command_line;
G_APPLICATION_CLASS(klass)->startup = my_application_startup;
G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown;
G_OBJECT_CLASS(klass)->dispose = my_application_dispose;
}
static void my_application_init(MyApplication* self) {}
MyApplication* my_application_new() {
// Set the program name to the application ID, which helps various systems
// like GTK and desktop environments map this running application to its
// corresponding .desktop file. This ensures better integration by allowing
// the application to be recognized beyond its binary name.
g_set_prgname(APPLICATION_ID);
return MY_APPLICATION(g_object_new(my_application_get_type(),
"application-id", APPLICATION_ID, "flags",
G_APPLICATION_NON_UNIQUE, nullptr));
}
#ifndef FLUTTER_MY_APPLICATION_H_
#define FLUTTER_MY_APPLICATION_H_
#include <gtk/gtk.h>
G_DECLARE_FINAL_TYPE(MyApplication,
my_application,
MY,
APPLICATION,
GtkApplication)
/**
* my_application_new:
*
* Creates a new Flutter-based application.
*
* Returns: a new #MyApplication.
*/
MyApplication* my_application_new();
#endif // FLUTTER_MY_APPLICATION_H_
# Flutter-related
**/Flutter/ephemeral/
**/Pods/
# Xcode-related
**/dgph
**/xcuserdata/
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "ephemeral/Flutter-Generated.xcconfig"
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "ephemeral/Flutter-Generated.xcconfig"
//
// Generated file. Do not edit.
//
import FlutterMacOS
import Foundation
import flutter_secure_storage_macos
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
}
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!
use_modular_headers!
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
PODS:
- flutter_secure_storage_macos (6.1.3):
- FlutterMacOS
- FlutterMacOS (1.0.0)
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
DEPENDENCIES:
- flutter_secure_storage_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos`)
- FlutterMacOS (from `Flutter/ephemeral`)
- path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
EXTERNAL SOURCES:
flutter_secure_storage_macos:
:path: Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos
FlutterMacOS:
:path: Flutter/ephemeral
path_provider_foundation:
:path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin
SPEC CHECKSUMS:
flutter_secure_storage_macos: 7f45e30f838cf2659862a4e4e3ee1c347c2b3b54
FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
PODFILE CHECKSUM: 9ebaf0ce3d369aaa26a9ea0e159195ed94724cf3
COCOAPODS: 1.16.2
// !$*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 */; };
A9904737725241406B1F764A /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6913C73ACBD9DBD0D8F3CF53 /* Pods_Runner.framework */; };
AA2511AB6A727172664CBACE /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A4CA6AB92F7BBA99F6D3F719 /* Pods_RunnerTests.framework */; };
/* 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 */
1750591ED073FE1F8A404F03 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
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 = "<group>"; };
333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = "<group>"; };
335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = "<group>"; };
33CC10ED2044A3C60003C045 /* smart_hotel_app.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = smart_hotel_app.app; sourceTree = BUILT_PRODUCTS_DIR; };
33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = "<group>"; };
33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; };
33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = "<group>"; };
33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = "<group>"; };
33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = "<group>"; };
33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = "<group>"; };
33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = "<group>"; };
33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = "<group>"; };
33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = "<group>"; };
33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = "<group>"; };
5AF8814C9D5EE9A6B4DE96B7 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
6913C73ACBD9DBD0D8F3CF53 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; };
A4CA6AB92F7BBA99F6D3F719 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
BBB391427D55F9002009B5AF /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; };
C779CA853CE6D3F6B8B91805 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
C8F246C0131A2C65494C474E /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
FB8AE0EF1C94CA8FF34D8251 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
331C80D2294CF70F00263BE5 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
AA2511AB6A727172664CBACE /* Pods_RunnerTests.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
33CC10EA2044A3C60003C045 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
A9904737725241406B1F764A /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
08484717D36EA345072FEF22 /* Pods */ = {
isa = PBXGroup;
children = (
1750591ED073FE1F8A404F03 /* Pods-Runner.debug.xcconfig */,
5AF8814C9D5EE9A6B4DE96B7 /* Pods-Runner.release.xcconfig */,
C779CA853CE6D3F6B8B91805 /* Pods-Runner.profile.xcconfig */,
FB8AE0EF1C94CA8FF34D8251 /* Pods-RunnerTests.debug.xcconfig */,
C8F246C0131A2C65494C474E /* Pods-RunnerTests.release.xcconfig */,
BBB391427D55F9002009B5AF /* Pods-RunnerTests.profile.xcconfig */,
);
name = Pods;
path = Pods;
sourceTree = "<group>";
};
331C80D6294CF71000263BE5 /* RunnerTests */ = {
isa = PBXGroup;
children = (
331C80D7294CF71000263BE5 /* RunnerTests.swift */,
);
path = RunnerTests;
sourceTree = "<group>";
};
33BA886A226E78AF003329D5 /* Configs */ = {
isa = PBXGroup;
children = (
33E5194F232828860026EE4D /* AppInfo.xcconfig */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
333000ED22D3DE5D00554162 /* Warnings.xcconfig */,
);
path = Configs;
sourceTree = "<group>";
};
33CC10E42044A3C60003C045 = {
isa = PBXGroup;
children = (
33FAB671232836740065AC1E /* Runner */,
33CEB47122A05771004F2AC0 /* Flutter */,
331C80D6294CF71000263BE5 /* RunnerTests */,
33CC10EE2044A3C60003C045 /* Products */,
D73912EC22F37F3D000D13A0 /* Frameworks */,
08484717D36EA345072FEF22 /* Pods */,
);
sourceTree = "<group>";
};
33CC10EE2044A3C60003C045 /* Products */ = {
isa = PBXGroup;
children = (
33CC10ED2044A3C60003C045 /* smart_hotel_app.app */,
331C80D5294CF71000263BE5 /* RunnerTests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
33CC11242044D66E0003C045 /* Resources */ = {
isa = PBXGroup;
children = (
33CC10F22044A3C60003C045 /* Assets.xcassets */,
33CC10F42044A3C60003C045 /* MainMenu.xib */,
33CC10F72044A3C60003C045 /* Info.plist */,
);
name = Resources;
path = ..;
sourceTree = "<group>";
};
33CEB47122A05771004F2AC0 /* Flutter */ = {
isa = PBXGroup;
children = (
335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */,
33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */,
33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */,
33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */,
);
path = Flutter;
sourceTree = "<group>";
};
33FAB671232836740065AC1E /* Runner */ = {
isa = PBXGroup;
children = (
33CC10F02044A3C60003C045 /* AppDelegate.swift */,
33CC11122044BFA00003C045 /* MainFlutterWindow.swift */,
33E51913231747F40026EE4D /* DebugProfile.entitlements */,
33E51914231749380026EE4D /* Release.entitlements */,
33CC11242044D66E0003C045 /* Resources */,
33BA886A226E78AF003329D5 /* Configs */,
);
path = Runner;
sourceTree = "<group>";
};
D73912EC22F37F3D000D13A0 /* Frameworks */ = {
isa = PBXGroup;
children = (
6913C73ACBD9DBD0D8F3CF53 /* Pods_Runner.framework */,
A4CA6AB92F7BBA99F6D3F719 /* Pods_RunnerTests.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
331C80D4294CF70F00263BE5 /* RunnerTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
buildPhases = (
46188DED442B9FE5DC38335B /* [CP] Check Pods Manifest.lock */,
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 = (
6E840153FDBEDDC4A37682B6 /* [CP] Check Pods Manifest.lock */,
33CC10E92044A3C60003C045 /* Sources */,
33CC10EA2044A3C60003C045 /* Frameworks */,
33CC10EB2044A3C60003C045 /* Resources */,
33CC110E2044A8840003C045 /* Bundle Framework */,
3399D490228B24CF009A79C7 /* ShellScript */,
A8F70DEC0AF119630AF4A23B /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
dependencies = (
33CC11202044C79F0003C045 /* PBXTargetDependency */,
);
name = Runner;
productName = Runner;
productReference = 33CC10ED2044A3C60003C045 /* smart_hotel_app.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
33CC10E52044A3C60003C045 /* Project object */ = {
isa = PBXProject;
attributes = {
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";
};
46188DED442B9FE5DC38335B /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
6E840153FDBEDDC4A37682B6 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
A8F70DEC0AF119630AF4A23B /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
/* 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 = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
331C80DB294CF71000263BE5 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = FB8AE0EF1C94CA8FF34D8251 /* Pods-RunnerTests.debug.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.smartHotelApp.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/smart_hotel_app.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/smart_hotel_app";
};
name = Debug;
};
331C80DC294CF71000263BE5 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = C8F246C0131A2C65494C474E /* Pods-RunnerTests.release.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.smartHotelApp.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/smart_hotel_app.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/smart_hotel_app";
};
name = Release;
};
331C80DD294CF71000263BE5 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = BBB391427D55F9002009B5AF /* Pods-RunnerTests.profile.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.smartHotelApp.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/smart_hotel_app.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/smart_hotel_app";
};
name = Profile;
};
338D0CE9231458BD00FA5F75 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
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;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
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;
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;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
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;
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;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
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 */;
}
<?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"?>
<Scheme
LastUpgradeVersion = "1510"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
BuildableName = "smart_hotel_app.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 = "33CC10EC2044A3C60003C045"
BuildableName = "smart_hotel_app.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables>
<TestableReference
skipped = "NO"
parallelizable = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "331C80D4294CF70F00263BE5"
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"
enableGPUValidationMode = "1"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
BuildableName = "smart_hotel_app.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 = "33CC10EC2044A3C60003C045"
BuildableName = "smart_hotel_app.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>
import Cocoa
import FlutterMacOS
@main
class AppDelegate: FlutterAppDelegate {
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
return true
}
override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool {
return true
}
}
{
"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"
}
}
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14490.70"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
<connections>
<outlet property="delegate" destination="Voe-Tx-rLC" id="GzC-gU-4Uq"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModule="Runner" customModuleProvider="target">
<connections>
<outlet property="applicationMenu" destination="uQy-DD-JDr" id="XBo-yE-nKs"/>
<outlet property="mainFlutterWindow" destination="QvC-M9-y7g" id="gIp-Ho-8D9"/>
</connections>
</customObject>
<customObject id="YLy-65-1bz" customClass="NSFontManager"/>
<menu title="Main Menu" systemMenu="main" id="AYu-sK-qS6">
<items>
<menuItem title="APP_NAME" id="1Xt-HY-uBw">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="APP_NAME" systemMenu="apple" id="uQy-DD-JDr">
<items>
<menuItem title="About APP_NAME" id="5kV-Vb-QxS">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="orderFrontStandardAboutPanel:" target="-1" id="Exp-CZ-Vem"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="VOq-y0-SEH"/>
<menuItem title="Preferences…" keyEquivalent="," id="BOF-NM-1cW"/>
<menuItem isSeparatorItem="YES" id="wFC-TO-SCJ"/>
<menuItem title="Services" id="NMo-om-nkz">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Services" systemMenu="services" id="hz9-B4-Xy5"/>
</menuItem>
<menuItem isSeparatorItem="YES" id="4je-JR-u6R"/>
<menuItem title="Hide APP_NAME" keyEquivalent="h" id="Olw-nP-bQN">
<connections>
<action selector="hide:" target="-1" id="PnN-Uc-m68"/>
</connections>
</menuItem>
<menuItem title="Hide Others" keyEquivalent="h" id="Vdr-fp-XzO">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="hideOtherApplications:" target="-1" id="VT4-aY-XCT"/>
</connections>
</menuItem>
<menuItem title="Show All" id="Kd2-mp-pUS">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="unhideAllApplications:" target="-1" id="Dhg-Le-xox"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="kCx-OE-vgT"/>
<menuItem title="Quit APP_NAME" keyEquivalent="q" id="4sb-4s-VLi">
<connections>
<action selector="terminate:" target="-1" id="Te7-pn-YzF"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Edit" id="5QF-Oa-p0T">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Edit" id="W48-6f-4Dl">
<items>
<menuItem title="Undo" keyEquivalent="z" id="dRJ-4n-Yzg">
<connections>
<action selector="undo:" target="-1" id="M6e-cu-g7V"/>
</connections>
</menuItem>
<menuItem title="Redo" keyEquivalent="Z" id="6dh-zS-Vam">
<connections>
<action selector="redo:" target="-1" id="oIA-Rs-6OD"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="WRV-NI-Exz"/>
<menuItem title="Cut" keyEquivalent="x" id="uRl-iY-unG">
<connections>
<action selector="cut:" target="-1" id="YJe-68-I9s"/>
</connections>
</menuItem>
<menuItem title="Copy" keyEquivalent="c" id="x3v-GG-iWU">
<connections>
<action selector="copy:" target="-1" id="G1f-GL-Joy"/>
</connections>
</menuItem>
<menuItem title="Paste" keyEquivalent="v" id="gVA-U4-sdL">
<connections>
<action selector="paste:" target="-1" id="UvS-8e-Qdg"/>
</connections>
</menuItem>
<menuItem title="Paste and Match Style" keyEquivalent="V" id="WeT-3V-zwk">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="pasteAsPlainText:" target="-1" id="cEh-KX-wJQ"/>
</connections>
</menuItem>
<menuItem title="Delete" id="pa3-QI-u2k">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="delete:" target="-1" id="0Mk-Ml-PaM"/>
</connections>
</menuItem>
<menuItem title="Select All" keyEquivalent="a" id="Ruw-6m-B2m">
<connections>
<action selector="selectAll:" target="-1" id="VNm-Mi-diN"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="uyl-h8-XO2"/>
<menuItem title="Find" id="4EN-yA-p0u">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Find" id="1b7-l0-nxx">
<items>
<menuItem title="Find…" tag="1" keyEquivalent="f" id="Xz5-n4-O0W">
<connections>
<action selector="performFindPanelAction:" target="-1" id="cD7-Qs-BN4"/>
</connections>
</menuItem>
<menuItem title="Find and Replace…" tag="12" keyEquivalent="f" id="YEy-JH-Tfz">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="performFindPanelAction:" target="-1" id="WD3-Gg-5AJ"/>
</connections>
</menuItem>
<menuItem title="Find Next" tag="2" keyEquivalent="g" id="q09-fT-Sye">
<connections>
<action selector="performFindPanelAction:" target="-1" id="NDo-RZ-v9R"/>
</connections>
</menuItem>
<menuItem title="Find Previous" tag="3" keyEquivalent="G" id="OwM-mh-QMV">
<connections>
<action selector="performFindPanelAction:" target="-1" id="HOh-sY-3ay"/>
</connections>
</menuItem>
<menuItem title="Use Selection for Find" tag="7" keyEquivalent="e" id="buJ-ug-pKt">
<connections>
<action selector="performFindPanelAction:" target="-1" id="U76-nv-p5D"/>
</connections>
</menuItem>
<menuItem title="Jump to Selection" keyEquivalent="j" id="S0p-oC-mLd">
<connections>
<action selector="centerSelectionInVisibleArea:" target="-1" id="IOG-6D-g5B"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Spelling and Grammar" id="Dv1-io-Yv7">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Spelling" id="3IN-sU-3Bg">
<items>
<menuItem title="Show Spelling and Grammar" keyEquivalent=":" id="HFo-cy-zxI">
<connections>
<action selector="showGuessPanel:" target="-1" id="vFj-Ks-hy3"/>
</connections>
</menuItem>
<menuItem title="Check Document Now" keyEquivalent=";" id="hz2-CU-CR7">
<connections>
<action selector="checkSpelling:" target="-1" id="fz7-VC-reM"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="bNw-od-mp5"/>
<menuItem title="Check Spelling While Typing" id="rbD-Rh-wIN">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleContinuousSpellChecking:" target="-1" id="7w6-Qz-0kB"/>
</connections>
</menuItem>
<menuItem title="Check Grammar With Spelling" id="mK6-2p-4JG">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleGrammarChecking:" target="-1" id="muD-Qn-j4w"/>
</connections>
</menuItem>
<menuItem title="Correct Spelling Automatically" id="78Y-hA-62v">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticSpellingCorrection:" target="-1" id="2lM-Qi-WAP"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Substitutions" id="9ic-FL-obx">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Substitutions" id="FeM-D8-WVr">
<items>
<menuItem title="Show Substitutions" id="z6F-FW-3nz">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="orderFrontSubstitutionsPanel:" target="-1" id="oku-mr-iSq"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="gPx-C9-uUO"/>
<menuItem title="Smart Copy/Paste" id="9yt-4B-nSM">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleSmartInsertDelete:" target="-1" id="3IJ-Se-DZD"/>
</connections>
</menuItem>
<menuItem title="Smart Quotes" id="hQb-2v-fYv">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticQuoteSubstitution:" target="-1" id="ptq-xd-QOA"/>
</connections>
</menuItem>
<menuItem title="Smart Dashes" id="rgM-f4-ycn">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticDashSubstitution:" target="-1" id="oCt-pO-9gS"/>
</connections>
</menuItem>
<menuItem title="Smart Links" id="cwL-P1-jid">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticLinkDetection:" target="-1" id="Gip-E3-Fov"/>
</connections>
</menuItem>
<menuItem title="Data Detectors" id="tRr-pd-1PS">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticDataDetection:" target="-1" id="R1I-Nq-Kbl"/>
</connections>
</menuItem>
<menuItem title="Text Replacement" id="HFQ-gK-NFA">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticTextReplacement:" target="-1" id="DvP-Fe-Py6"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Transformations" id="2oI-Rn-ZJC">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Transformations" id="c8a-y6-VQd">
<items>
<menuItem title="Make Upper Case" id="vmV-6d-7jI">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="uppercaseWord:" target="-1" id="sPh-Tk-edu"/>
</connections>
</menuItem>
<menuItem title="Make Lower Case" id="d9M-CD-aMd">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="lowercaseWord:" target="-1" id="iUZ-b5-hil"/>
</connections>
</menuItem>
<menuItem title="Capitalize" id="UEZ-Bs-lqG">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="capitalizeWord:" target="-1" id="26H-TL-nsh"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Speech" id="xrE-MZ-jX0">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Speech" id="3rS-ZA-NoH">
<items>
<menuItem title="Start Speaking" id="Ynk-f8-cLZ">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="startSpeaking:" target="-1" id="654-Ng-kyl"/>
</connections>
</menuItem>
<menuItem title="Stop Speaking" id="Oyz-dy-DGm">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="stopSpeaking:" target="-1" id="dX8-6p-jy9"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="View" id="H8h-7b-M4v">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="View" id="HyV-fh-RgO">
<items>
<menuItem title="Enter Full Screen" keyEquivalent="f" id="4J7-dP-txa">
<modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/>
<connections>
<action selector="toggleFullScreen:" target="-1" id="dU3-MA-1Rq"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Window" id="aUF-d1-5bR">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Window" systemMenu="window" id="Td7-aD-5lo">
<items>
<menuItem title="Minimize" keyEquivalent="m" id="OY7-WF-poV">
<connections>
<action selector="performMiniaturize:" target="-1" id="VwT-WD-YPe"/>
</connections>
</menuItem>
<menuItem title="Zoom" id="R4o-n2-Eq4">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="performZoom:" target="-1" id="DIl-cC-cCs"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="eu3-7i-yIM"/>
<menuItem title="Bring All to Front" id="LE2-aR-0XJ">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="arrangeInFront:" target="-1" id="DRN-fu-gQh"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Help" id="EPT-qC-fAb">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Help" systemMenu="help" id="rJ0-wn-3NY"/>
</menuItem>
</items>
<point key="canvasLocation" x="142" y="-258"/>
</menu>
<window title="APP_NAME" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" animationBehavior="default" id="QvC-M9-y7g" customClass="MainFlutterWindow" customModule="Runner" customModuleProvider="target">
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
<rect key="contentRect" x="335" y="390" width="800" height="600"/>
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1577"/>
<view key="contentView" wantsLayer="YES" id="EiT-Mj-1SZ">
<rect key="frame" x="0.0" y="0.0" width="800" height="600"/>
<autoresizingMask key="autoresizingMask"/>
</view>
</window>
</objects>
</document>
// 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 = smart_hotel_app
// The application's bundle identifier
PRODUCT_BUNDLE_IDENTIFIER = com.example.smartHotelApp
// The copyright displayed in application information
PRODUCT_COPYRIGHT = Copyright © 2026 com.example. All rights reserved.
#include "../../Flutter/Flutter-Debug.xcconfig"
#include "Warnings.xcconfig"
#include "../../Flutter/Flutter-Release.xcconfig"
#include "Warnings.xcconfig"
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
<?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>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.network.server</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>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIconFile</key>
<string></string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSMinimumSystemVersion</key>
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
<key>NSHumanReadableCopyright</key>
<string>$(PRODUCT_COPYRIGHT)</string>
<key>NSMainNibFile</key>
<string>MainMenu</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
</dict>
</plist>
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()
}
}
<?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>com.apple.security.app-sandbox</key>
<true/>
</dict>
</plist>
import FlutterMacOS
import Cocoa
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.
}
}
name: smart_hotel_app
description: "A new Flutter project."
# The following line prevents the package from being accidentally published to
# pub.dev using `flutter pub publish`. This is preferred for private packages.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# The following defines the version and build number for your application.
# A version number is three numbers separated by dots, like 1.2.43
# followed by an optional build number separated by a +.
# Both the version and the builder number may be overridden in flutter
# build by specifying --build-name and --build-number, respectively.
# In Android, build-name is used as versionName while build-number used as versionCode.
# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix.
version: 1.0.0+1
environment:
sdk: '>=3.2.5 <4.0.0'
# sdk: ^3.10.7
# Dependencies specify other packages that your package needs in order to work.
# To automatically upgrade your package dependencies to the latest versions
# consider running `flutter pub upgrade --major-versions`. Alternatively,
# dependencies can be manually updated by changing the version numbers below to
# the latest version available on pub.dev. To see which dependencies have newer
# versions available, run `flutter pub outdated`.
dependencies:
flutter:
sdk: flutter
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.2
auto_route: ^7.9.2
dio: ^5.4.0
flutter_secure_storage: ^9.0.0
flutter_bloc: ^8.1.5
equatable: ^2.0.5
flutter_screenutil: ^5.9.3
fluttertoast: ^9.1.0
loading_animation_widget: ^1.3.0
# fl_chart: ^1.2.0
flutter_switch: ^0.3.2
fl_chart: ^0.71.0
infinite_scroll_pagination: ^5.1.1
logger: ^2.7.0
# fl_chart: ^0.71.0
# fl_chart: ^1.1.0
dev_dependencies:
flutter_test:
sdk: flutter
# The "flutter_lints" package below contains a set of recommended lints to
# encourage good coding practices. The lint set provided by the package is
# activated in the `analysis_options.yaml` file located at the root of your
# package. See that file for information about deactivating specific lint
# rules and activating additional ones.
flutter_lints: ^2.0.0
auto_route_generator: ^7.3.2
build_runner: ^2.4.9
flutter_launcher_icons: ^0.13.1
# 依赖覆盖 - 解决 win32-5.2.0 与 Dart 3.10 兼容性问题
dependency_overrides:
win32: 5.5.1
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
# The following section is specific to Flutter packages.
flutter:
# The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in
# the material Icons class.
uses-material-design: true
# To add assets to your application, add an assets section, like this:
assets:
- lib/assets/
- lib/assets/login/
- lib/assets/tab/
- lib/assets/icon/
# - images/a_dot_ham.jpeg
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/assets-and-images/#resolution-aware
# For details regarding adding assets from package dependencies, see
# https://flutter.dev/assets-and-images/#from-packages
# To add custom fonts to your application, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
# fonts:
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic
# - family: Trajan Pro
# fonts:
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
# For details regarding fonts from package dependencies,
# see https://flutter.dev/custom-fonts/#from-packages
flutter_launcher_icons:
android: "ic_launcher"
ios: true
image_path: "lib/assets/login/logo@2x.png"
min_sdk_android: 21
// 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:smart_hotel_app/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);
});
}
<!DOCTYPE html>
<html>
<head>
<!--
If you are serving your web app in a path other than the root, change the
href value below to reflect the base path you are serving from.
The path provided below has to start and end with a slash "/" in order for
it to work correctly.
For more details:
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base
This is a placeholder for base href that will be replaced by the value of
the `--base-href` argument provided to `flutter build`.
-->
<base href="$FLUTTER_BASE_HREF">
<meta charset="UTF-8">
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
<meta name="description" content="A new Flutter project.">
<!-- iOS meta tags & icons -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="smart_hotel_app">
<link rel="apple-touch-icon" href="icons/Icon-192.png">
<!-- Favicon -->
<link rel="icon" type="image/png" href="favicon.png"/>
<title>smart_hotel_app</title>
<link rel="manifest" href="manifest.json">
<script>
// The value below is injected by flutter build, do not touch.
const serviceWorkerVersion = null;
</script>
<!-- This script adds the flutter initialization JS code -->
<script src="flutter.js" defer></script>
</head>
<body>
<script>
window.addEventListener('load', function(ev) {
// Download main.dart.js
_flutter.loader.loadEntrypoint({
serviceWorker: {
serviceWorkerVersion: serviceWorkerVersion,
},
onEntrypointLoaded: function(engineInitializer) {
engineInitializer.initializeEngine().then(function(appRunner) {
appRunner.runApp();
});
}
});
});
</script>
</body>
</html>
{
"name": "smart_hotel_app",
"short_name": "smart_hotel_app",
"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"
}
]
}
flutter/ephemeral/
# Visual Studio user-specific files.
*.suo
*.user
*.userosscache
*.sln.docstates
# Visual Studio build-related files.
x64/
x86/
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!*.[Cc]ache/
# Project-level configuration.
cmake_minimum_required(VERSION 3.14)
project(smart_hotel_app LANGUAGES CXX)
# The name of the executable created for the application. Change this to change
# the on-disk name of your application.
set(BINARY_NAME "smart_hotel_app")
# Explicitly opt in to modern CMake behaviors to avoid warnings with recent
# versions of CMake.
cmake_policy(VERSION 3.14...3.25)
# Define build configuration option.
get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
if(IS_MULTICONFIG)
set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release"
CACHE STRING "" FORCE)
else()
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
set(CMAKE_BUILD_TYPE "Debug" CACHE
STRING "Flutter build mode" FORCE)
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
"Debug" "Profile" "Release")
endif()
endif()
# Define settings for the Profile build mode.
set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}")
set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}")
set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}")
set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}")
# Use Unicode for all projects.
add_definitions(-DUNICODE -D_UNICODE)
# Compilation settings that should be applied to most targets.
#
# Be cautious about adding new options here, as plugins use this function by
# default. In most cases, you should add new options to specific targets instead
# of modifying this function.
function(APPLY_STANDARD_SETTINGS TARGET)
target_compile_features(${TARGET} PUBLIC cxx_std_17)
target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100")
target_compile_options(${TARGET} PRIVATE /EHsc)
target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0")
target_compile_definitions(${TARGET} PRIVATE "$<$<CONFIG:Debug>:_DEBUG>")
endfunction()
# Flutter library and tool build rules.
set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
add_subdirectory(${FLUTTER_MANAGED_DIR})
# Application build; see runner/CMakeLists.txt.
add_subdirectory("runner")
# Generated plugin build rules, which manage building the plugins and adding
# them to the application.
include(flutter/generated_plugins.cmake)
# === Installation ===
# Support files are copied into place next to the executable, so that it can
# run in place. This is done instead of making a separate bundle (as on Linux)
# so that building and running from within Visual Studio will work.
set(BUILD_BUNDLE_DIR "$<TARGET_FILE_DIR:${BINARY_NAME}>")
# Make the "install" step default, as it's required to run.
set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1)
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
endif()
set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}")
install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
COMPONENT Runtime)
install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
COMPONENT Runtime)
install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
if(PLUGIN_BUNDLED_LIBRARIES)
install(FILES "${PLUGIN_BUNDLED_LIBRARIES}"
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
endif()
# Copy the native assets provided by the build.dart from all packages.
set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/")
install(DIRECTORY "${NATIVE_ASSETS_DIR}"
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
# Fully re-copy the assets directory on each build to avoid having stale files
# from a previous install.
set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
install(CODE "
file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
" COMPONENT Runtime)
install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
# Install the AOT library on non-Debug builds only.
install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
CONFIGURATIONS Profile;Release
COMPONENT Runtime)
# This file controls Flutter-level build steps. It should not be edited.
cmake_minimum_required(VERSION 3.14)
set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
# Configuration provided via flutter tool.
include(${EPHEMERAL_DIR}/generated_config.cmake)
# TODO: Move the rest of this into files in ephemeral. See
# https://github.com/flutter/flutter/issues/57146.
set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper")
# Set fallback configurations for older versions of the flutter tool.
if (NOT DEFINED FLUTTER_TARGET_PLATFORM)
set(FLUTTER_TARGET_PLATFORM "windows-x64")
endif()
# === Flutter Library ===
set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll")
# Published to parent scope for install step.
set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE)
list(APPEND FLUTTER_LIBRARY_HEADERS
"flutter_export.h"
"flutter_windows.h"
"flutter_messenger.h"
"flutter_plugin_registrar.h"
"flutter_texture_registrar.h"
)
list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/")
add_library(flutter INTERFACE)
target_include_directories(flutter INTERFACE
"${EPHEMERAL_DIR}"
)
target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib")
add_dependencies(flutter flutter_assemble)
# === Wrapper ===
list(APPEND CPP_WRAPPER_SOURCES_CORE
"core_implementations.cc"
"standard_codec.cc"
)
list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/")
list(APPEND CPP_WRAPPER_SOURCES_PLUGIN
"plugin_registrar.cc"
)
list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/")
list(APPEND CPP_WRAPPER_SOURCES_APP
"flutter_engine.cc"
"flutter_view_controller.cc"
)
list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/")
# Wrapper sources needed for a plugin.
add_library(flutter_wrapper_plugin STATIC
${CPP_WRAPPER_SOURCES_CORE}
${CPP_WRAPPER_SOURCES_PLUGIN}
)
apply_standard_settings(flutter_wrapper_plugin)
set_target_properties(flutter_wrapper_plugin PROPERTIES
POSITION_INDEPENDENT_CODE ON)
set_target_properties(flutter_wrapper_plugin PROPERTIES
CXX_VISIBILITY_PRESET hidden)
target_link_libraries(flutter_wrapper_plugin PUBLIC flutter)
target_include_directories(flutter_wrapper_plugin PUBLIC
"${WRAPPER_ROOT}/include"
)
add_dependencies(flutter_wrapper_plugin flutter_assemble)
# Wrapper sources needed for the runner.
add_library(flutter_wrapper_app STATIC
${CPP_WRAPPER_SOURCES_CORE}
${CPP_WRAPPER_SOURCES_APP}
)
apply_standard_settings(flutter_wrapper_app)
target_link_libraries(flutter_wrapper_app PUBLIC flutter)
target_include_directories(flutter_wrapper_app PUBLIC
"${WRAPPER_ROOT}/include"
)
add_dependencies(flutter_wrapper_app flutter_assemble)
# === Flutter tool backend ===
# _phony_ is a non-existent file to force this command to run every time,
# since currently there's no way to get a full input/output list from the
# flutter tool.
set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_")
set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE)
add_custom_command(
OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN}
${CPP_WRAPPER_SOURCES_APP}
${PHONY_OUTPUT}
COMMAND ${CMAKE_COMMAND} -E env
${FLUTTER_TOOL_ENVIRONMENT}
"${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat"
${FLUTTER_TARGET_PLATFORM} $<CONFIG>
VERBATIM
)
add_custom_target(flutter_assemble DEPENDS
"${FLUTTER_LIBRARY}"
${FLUTTER_LIBRARY_HEADERS}
${CPP_WRAPPER_SOURCES_CORE}
${CPP_WRAPPER_SOURCES_PLUGIN}
${CPP_WRAPPER_SOURCES_APP}
)
//
// Generated file. Do not edit.
//
// clang-format off
#include "generated_plugin_registrant.h"
#include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h>
void RegisterPlugins(flutter::PluginRegistry* registry) {
FlutterSecureStorageWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin"));
}
//
// Generated file. Do not edit.
//
// clang-format off
#ifndef GENERATED_PLUGIN_REGISTRANT_
#define GENERATED_PLUGIN_REGISTRANT_
#include <flutter/plugin_registry.h>
// Registers Flutter plugins.
void RegisterPlugins(flutter::PluginRegistry* registry);
#endif // GENERATED_PLUGIN_REGISTRANT_
#
# Generated file, do not edit.
#
list(APPEND FLUTTER_PLUGIN_LIST
flutter_secure_storage_windows
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST
jni
)
set(PLUGIN_BUNDLED_LIBRARIES)
foreach(plugin ${FLUTTER_PLUGIN_LIST})
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin})
target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
endforeach(plugin)
foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin})
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
endforeach(ffi_plugin)
cmake_minimum_required(VERSION 3.14)
project(runner LANGUAGES CXX)
# Define the application target. To change its name, change BINARY_NAME in the
# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer
# work.
#
# Any new source files that you add to the application should be added here.
add_executable(${BINARY_NAME} WIN32
"flutter_window.cpp"
"main.cpp"
"utils.cpp"
"win32_window.cpp"
"${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
"Runner.rc"
"runner.exe.manifest"
)
# Apply the standard set of build settings. This can be removed for applications
# that need different build settings.
apply_standard_settings(${BINARY_NAME})
# Add preprocessor definitions for the build version.
target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"")
target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}")
target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}")
target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}")
target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}")
# Disable Windows macros that collide with C++ standard library functions.
target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX")
# Add dependency libraries and include directories. Add any application-specific
# dependencies here.
target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app)
target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib")
target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}")
# Run the Flutter tool portions of the build. This must not be removed.
add_dependencies(${BINARY_NAME} flutter_assemble)
// Microsoft Visual C++ generated resource script.
//
#pragma code_page(65001)
#include "resource.h"
#define APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 2 resource.
//
#include "winres.h"
/////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
// English (United States) resources
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
#ifdef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// TEXTINCLUDE
//
1 TEXTINCLUDE
BEGIN
"resource.h\0"
END
2 TEXTINCLUDE
BEGIN
"#include ""winres.h""\r\n"
"\0"
END
3 TEXTINCLUDE
BEGIN
"\r\n"
"\0"
END
#endif // APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Icon
//
// Icon with lowest ID value placed first to ensure application icon
// remains consistent on all systems.
IDI_APP_ICON ICON "resources\\app_icon.ico"
/////////////////////////////////////////////////////////////////////////////
//
// Version
//
#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD)
#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD
#else
#define VERSION_AS_NUMBER 1,0,0,0
#endif
#if defined(FLUTTER_VERSION)
#define VERSION_AS_STRING FLUTTER_VERSION
#else
#define VERSION_AS_STRING "1.0.0"
#endif
VS_VERSION_INFO VERSIONINFO
FILEVERSION VERSION_AS_NUMBER
PRODUCTVERSION VERSION_AS_NUMBER
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
#ifdef _DEBUG
FILEFLAGS VS_FF_DEBUG
#else
FILEFLAGS 0x0L
#endif
FILEOS VOS__WINDOWS32
FILETYPE VFT_APP
FILESUBTYPE 0x0L
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904e4"
BEGIN
VALUE "CompanyName", "com.example" "\0"
VALUE "FileDescription", "smart_hotel_app" "\0"
VALUE "FileVersion", VERSION_AS_STRING "\0"
VALUE "InternalName", "smart_hotel_app" "\0"
VALUE "LegalCopyright", "Copyright (C) 2026 com.example. All rights reserved." "\0"
VALUE "OriginalFilename", "smart_hotel_app.exe" "\0"
VALUE "ProductName", "smart_hotel_app" "\0"
VALUE "ProductVersion", VERSION_AS_STRING "\0"
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1252
END
END
#endif // English (United States) resources
/////////////////////////////////////////////////////////////////////////////
#ifndef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
//
/////////////////////////////////////////////////////////////////////////////
#endif // not APSTUDIO_INVOKED
#include "flutter_window.h"
#include <optional>
#include "flutter/generated_plugin_registrant.h"
FlutterWindow::FlutterWindow(const flutter::DartProject& project)
: project_(project) {}
FlutterWindow::~FlutterWindow() {}
bool FlutterWindow::OnCreate() {
if (!Win32Window::OnCreate()) {
return false;
}
RECT frame = GetClientArea();
// The size here must match the window dimensions to avoid unnecessary surface
// creation / destruction in the startup path.
flutter_controller_ = std::make_unique<flutter::FlutterViewController>(
frame.right - frame.left, frame.bottom - frame.top, project_);
// Ensure that basic setup of the controller was successful.
if (!flutter_controller_->engine() || !flutter_controller_->view()) {
return false;
}
RegisterPlugins(flutter_controller_->engine());
SetChildContent(flutter_controller_->view()->GetNativeWindow());
flutter_controller_->engine()->SetNextFrameCallback([&]() {
this->Show();
});
// Flutter can complete the first frame before the "show window" callback is
// registered. The following call ensures a frame is pending to ensure the
// window is shown. It is a no-op if the first frame hasn't completed yet.
flutter_controller_->ForceRedraw();
return true;
}
void FlutterWindow::OnDestroy() {
if (flutter_controller_) {
flutter_controller_ = nullptr;
}
Win32Window::OnDestroy();
}
LRESULT
FlutterWindow::MessageHandler(HWND hwnd, UINT const message,
WPARAM const wparam,
LPARAM const lparam) noexcept {
// Give Flutter, including plugins, an opportunity to handle window messages.
if (flutter_controller_) {
std::optional<LRESULT> result =
flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam,
lparam);
if (result) {
return *result;
}
}
switch (message) {
case WM_FONTCHANGE:
flutter_controller_->engine()->ReloadSystemFonts();
break;
}
return Win32Window::MessageHandler(hwnd, message, wparam, lparam);
}
#ifndef RUNNER_FLUTTER_WINDOW_H_
#define RUNNER_FLUTTER_WINDOW_H_
#include <flutter/dart_project.h>
#include <flutter/flutter_view_controller.h>
#include <memory>
#include "win32_window.h"
// A window that does nothing but host a Flutter view.
class FlutterWindow : public Win32Window {
public:
// Creates a new FlutterWindow hosting a Flutter view running |project|.
explicit FlutterWindow(const flutter::DartProject& project);
virtual ~FlutterWindow();
protected:
// Win32Window:
bool OnCreate() override;
void OnDestroy() override;
LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam,
LPARAM const lparam) noexcept override;
private:
// The project to run.
flutter::DartProject project_;
// The Flutter instance hosted by this window.
std::unique_ptr<flutter::FlutterViewController> flutter_controller_;
};
#endif // RUNNER_FLUTTER_WINDOW_H_
#include <flutter/dart_project.h>
#include <flutter/flutter_view_controller.h>
#include <windows.h>
#include "flutter_window.h"
#include "utils.h"
int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
_In_ wchar_t *command_line, _In_ int show_command) {
// Attach to console when present (e.g., 'flutter run') or create a
// new console when running with a debugger.
if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) {
CreateAndAttachConsole();
}
// Initialize COM, so that it is available for use in the library and/or
// plugins.
::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
flutter::DartProject project(L"data");
std::vector<std::string> command_line_arguments =
GetCommandLineArguments();
project.set_dart_entrypoint_arguments(std::move(command_line_arguments));
FlutterWindow window(project);
Win32Window::Point origin(10, 10);
Win32Window::Size size(1280, 720);
if (!window.Create(L"smart_hotel_app", origin, size)) {
return EXIT_FAILURE;
}
window.SetQuitOnClose(true);
::MSG msg;
while (::GetMessage(&msg, nullptr, 0, 0)) {
::TranslateMessage(&msg);
::DispatchMessage(&msg);
}
::CoUninitialize();
return EXIT_SUCCESS;
}
//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by Runner.rc
//
#define IDI_APP_ICON 101
// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 102
#define _APS_NEXT_COMMAND_VALUE 40001
#define _APS_NEXT_CONTROL_VALUE 1001
#define _APS_NEXT_SYMED_VALUE 101
#endif
#endif
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
</windowsSettings>
</application>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- Windows 10 and Windows 11 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
<!-- Windows 8.1 -->
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
<!-- Windows 8 -->
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
<!-- Windows 7 -->
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
</application>
</compatibility>
</assembly>
#include "utils.h"
#include <flutter_windows.h>
#include <io.h>
#include <stdio.h>
#include <windows.h>
#include <iostream>
void CreateAndAttachConsole() {
if (::AllocConsole()) {
FILE *unused;
if (freopen_s(&unused, "CONOUT$", "w", stdout)) {
_dup2(_fileno(stdout), 1);
}
if (freopen_s(&unused, "CONOUT$", "w", stderr)) {
_dup2(_fileno(stdout), 2);
}
std::ios::sync_with_stdio();
FlutterDesktopResyncOutputStreams();
}
}
std::vector<std::string> GetCommandLineArguments() {
// Convert the UTF-16 command line arguments to UTF-8 for the Engine to use.
int argc;
wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc);
if (argv == nullptr) {
return std::vector<std::string>();
}
std::vector<std::string> command_line_arguments;
// Skip the first argument as it's the binary name.
for (int i = 1; i < argc; i++) {
command_line_arguments.push_back(Utf8FromUtf16(argv[i]));
}
::LocalFree(argv);
return command_line_arguments;
}
std::string Utf8FromUtf16(const wchar_t* utf16_string) {
if (utf16_string == nullptr) {
return std::string();
}
int target_length = ::WideCharToMultiByte(
CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string,
-1, nullptr, 0, nullptr, nullptr)
-1; // remove the trailing null character
int input_length = (int)wcslen(utf16_string);
std::string utf8_string;
if (target_length <= 0 || target_length > utf8_string.max_size()) {
return utf8_string;
}
utf8_string.resize(target_length);
int converted_length = ::WideCharToMultiByte(
CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string,
input_length, utf8_string.data(), target_length, nullptr, nullptr);
if (converted_length == 0) {
return std::string();
}
return utf8_string;
}
#ifndef RUNNER_UTILS_H_
#define RUNNER_UTILS_H_
#include <string>
#include <vector>
// Creates a console for the process, and redirects stdout and stderr to
// it for both the runner and the Flutter library.
void CreateAndAttachConsole();
// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string
// encoded in UTF-8. Returns an empty std::string on failure.
std::string Utf8FromUtf16(const wchar_t* utf16_string);
// Gets the command line arguments passed in as a std::vector<std::string>,
// encoded in UTF-8. Returns an empty std::vector<std::string> on failure.
std::vector<std::string> GetCommandLineArguments();
#endif // RUNNER_UTILS_H_
#include "win32_window.h"
#include <dwmapi.h>
#include <flutter_windows.h>
#include "resource.h"
namespace {
/// Window attribute that enables dark mode window decorations.
///
/// Redefined in case the developer's machine has a Windows SDK older than
/// version 10.0.22000.0.
/// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute
#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE
#define DWMWA_USE_IMMERSIVE_DARK_MODE 20
#endif
constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW";
/// Registry key for app theme preference.
///
/// A value of 0 indicates apps should use dark mode. A non-zero or missing
/// value indicates apps should use light mode.
constexpr const wchar_t kGetPreferredBrightnessRegKey[] =
L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize";
constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme";
// The number of Win32Window objects that currently exist.
static int g_active_window_count = 0;
using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd);
// Scale helper to convert logical scaler values to physical using passed in
// scale factor
int Scale(int source, double scale_factor) {
return static_cast<int>(source * scale_factor);
}
// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module.
// This API is only needed for PerMonitor V1 awareness mode.
void EnableFullDpiSupportIfAvailable(HWND hwnd) {
HMODULE user32_module = LoadLibraryA("User32.dll");
if (!user32_module) {
return;
}
auto enable_non_client_dpi_scaling =
reinterpret_cast<EnableNonClientDpiScaling*>(
GetProcAddress(user32_module, "EnableNonClientDpiScaling"));
if (enable_non_client_dpi_scaling != nullptr) {
enable_non_client_dpi_scaling(hwnd);
}
FreeLibrary(user32_module);
}
} // namespace
// Manages the Win32Window's window class registration.
class WindowClassRegistrar {
public:
~WindowClassRegistrar() = default;
// Returns the singleton registrar instance.
static WindowClassRegistrar* GetInstance() {
if (!instance_) {
instance_ = new WindowClassRegistrar();
}
return instance_;
}
// Returns the name of the window class, registering the class if it hasn't
// previously been registered.
const wchar_t* GetWindowClass();
// Unregisters the window class. Should only be called if there are no
// instances of the window.
void UnregisterWindowClass();
private:
WindowClassRegistrar() = default;
static WindowClassRegistrar* instance_;
bool class_registered_ = false;
};
WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr;
const wchar_t* WindowClassRegistrar::GetWindowClass() {
if (!class_registered_) {
WNDCLASS window_class{};
window_class.hCursor = LoadCursor(nullptr, IDC_ARROW);
window_class.lpszClassName = kWindowClassName;
window_class.style = CS_HREDRAW | CS_VREDRAW;
window_class.cbClsExtra = 0;
window_class.cbWndExtra = 0;
window_class.hInstance = GetModuleHandle(nullptr);
window_class.hIcon =
LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON));
window_class.hbrBackground = 0;
window_class.lpszMenuName = nullptr;
window_class.lpfnWndProc = Win32Window::WndProc;
RegisterClass(&window_class);
class_registered_ = true;
}
return kWindowClassName;
}
void WindowClassRegistrar::UnregisterWindowClass() {
UnregisterClass(kWindowClassName, nullptr);
class_registered_ = false;
}
Win32Window::Win32Window() {
++g_active_window_count;
}
Win32Window::~Win32Window() {
--g_active_window_count;
Destroy();
}
bool Win32Window::Create(const std::wstring& title,
const Point& origin,
const Size& size) {
Destroy();
const wchar_t* window_class =
WindowClassRegistrar::GetInstance()->GetWindowClass();
const POINT target_point = {static_cast<LONG>(origin.x),
static_cast<LONG>(origin.y)};
HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST);
UINT dpi = FlutterDesktopGetDpiForMonitor(monitor);
double scale_factor = dpi / 96.0;
HWND window = CreateWindow(
window_class, title.c_str(), WS_OVERLAPPEDWINDOW,
Scale(origin.x, scale_factor), Scale(origin.y, scale_factor),
Scale(size.width, scale_factor), Scale(size.height, scale_factor),
nullptr, nullptr, GetModuleHandle(nullptr), this);
if (!window) {
return false;
}
UpdateTheme(window);
return OnCreate();
}
bool Win32Window::Show() {
return ShowWindow(window_handle_, SW_SHOWNORMAL);
}
// static
LRESULT CALLBACK Win32Window::WndProc(HWND const window,
UINT const message,
WPARAM const wparam,
LPARAM const lparam) noexcept {
if (message == WM_NCCREATE) {
auto window_struct = reinterpret_cast<CREATESTRUCT*>(lparam);
SetWindowLongPtr(window, GWLP_USERDATA,
reinterpret_cast<LONG_PTR>(window_struct->lpCreateParams));
auto that = static_cast<Win32Window*>(window_struct->lpCreateParams);
EnableFullDpiSupportIfAvailable(window);
that->window_handle_ = window;
} else if (Win32Window* that = GetThisFromHandle(window)) {
return that->MessageHandler(window, message, wparam, lparam);
}
return DefWindowProc(window, message, wparam, lparam);
}
LRESULT
Win32Window::MessageHandler(HWND hwnd,
UINT const message,
WPARAM const wparam,
LPARAM const lparam) noexcept {
switch (message) {
case WM_DESTROY:
window_handle_ = nullptr;
Destroy();
if (quit_on_close_) {
PostQuitMessage(0);
}
return 0;
case WM_DPICHANGED: {
auto newRectSize = reinterpret_cast<RECT*>(lparam);
LONG newWidth = newRectSize->right - newRectSize->left;
LONG newHeight = newRectSize->bottom - newRectSize->top;
SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth,
newHeight, SWP_NOZORDER | SWP_NOACTIVATE);
return 0;
}
case WM_SIZE: {
RECT rect = GetClientArea();
if (child_content_ != nullptr) {
// Size and position the child window.
MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left,
rect.bottom - rect.top, TRUE);
}
return 0;
}
case WM_ACTIVATE:
if (child_content_ != nullptr) {
SetFocus(child_content_);
}
return 0;
case WM_DWMCOLORIZATIONCOLORCHANGED:
UpdateTheme(hwnd);
return 0;
}
return DefWindowProc(window_handle_, message, wparam, lparam);
}
void Win32Window::Destroy() {
OnDestroy();
if (window_handle_) {
DestroyWindow(window_handle_);
window_handle_ = nullptr;
}
if (g_active_window_count == 0) {
WindowClassRegistrar::GetInstance()->UnregisterWindowClass();
}
}
Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept {
return reinterpret_cast<Win32Window*>(
GetWindowLongPtr(window, GWLP_USERDATA));
}
void Win32Window::SetChildContent(HWND content) {
child_content_ = content;
SetParent(content, window_handle_);
RECT frame = GetClientArea();
MoveWindow(content, frame.left, frame.top, frame.right - frame.left,
frame.bottom - frame.top, true);
SetFocus(child_content_);
}
RECT Win32Window::GetClientArea() {
RECT frame;
GetClientRect(window_handle_, &frame);
return frame;
}
HWND Win32Window::GetHandle() {
return window_handle_;
}
void Win32Window::SetQuitOnClose(bool quit_on_close) {
quit_on_close_ = quit_on_close;
}
bool Win32Window::OnCreate() {
// No-op; provided for subclasses.
return true;
}
void Win32Window::OnDestroy() {
// No-op; provided for subclasses.
}
void Win32Window::UpdateTheme(HWND const window) {
DWORD light_mode;
DWORD light_mode_size = sizeof(light_mode);
LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey,
kGetPreferredBrightnessRegValue,
RRF_RT_REG_DWORD, nullptr, &light_mode,
&light_mode_size);
if (result == ERROR_SUCCESS) {
BOOL enable_dark_mode = light_mode == 0;
DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE,
&enable_dark_mode, sizeof(enable_dark_mode));
}
}
#ifndef RUNNER_WIN32_WINDOW_H_
#define RUNNER_WIN32_WINDOW_H_
#include <windows.h>
#include <functional>
#include <memory>
#include <string>
// A class abstraction for a high DPI-aware Win32 Window. Intended to be
// inherited from by classes that wish to specialize with custom
// rendering and input handling
class Win32Window {
public:
struct Point {
unsigned int x;
unsigned int y;
Point(unsigned int x, unsigned int y) : x(x), y(y) {}
};
struct Size {
unsigned int width;
unsigned int height;
Size(unsigned int width, unsigned int height)
: width(width), height(height) {}
};
Win32Window();
virtual ~Win32Window();
// Creates a win32 window with |title| that is positioned and sized using
// |origin| and |size|. New windows are created on the default monitor. Window
// sizes are specified to the OS in physical pixels, hence to ensure a
// consistent size this function will scale the inputted width and height as
// as appropriate for the default monitor. The window is invisible until
// |Show| is called. Returns true if the window was created successfully.
bool Create(const std::wstring& title, const Point& origin, const Size& size);
// Show the current window. Returns true if the window was successfully shown.
bool Show();
// Release OS resources associated with window.
void Destroy();
// Inserts |content| into the window tree.
void SetChildContent(HWND content);
// Returns the backing Window handle to enable clients to set icon and other
// window properties. Returns nullptr if the window has been destroyed.
HWND GetHandle();
// If true, closing this window will quit the application.
void SetQuitOnClose(bool quit_on_close);
// Return a RECT representing the bounds of the current client area.
RECT GetClientArea();
protected:
// Processes and route salient window messages for mouse handling,
// size change and DPI. Delegates handling of these to member overloads that
// inheriting classes can handle.
virtual LRESULT MessageHandler(HWND window,
UINT const message,
WPARAM const wparam,
LPARAM const lparam) noexcept;
// Called when CreateAndShow is called, allowing subclass window-related
// setup. Subclasses should return false if setup fails.
virtual bool OnCreate();
// Called when Destroy is called.
virtual void OnDestroy();
private:
friend class WindowClassRegistrar;
// OS callback called by message pump. Handles the WM_NCCREATE message which
// is passed when the non-client area is being created and enables automatic
// non-client DPI scaling so that the non-client area automatically
// responds to changes in DPI. All other messages are handled by
// MessageHandler.
static LRESULT CALLBACK WndProc(HWND const window,
UINT const message,
WPARAM const wparam,
LPARAM const lparam) noexcept;
// Retrieves a class instance pointer for |window|
static Win32Window* GetThisFromHandle(HWND const window) noexcept;
// Update the window frame's theme to match the system theme.
static void UpdateTheme(HWND const window);
bool quit_on_close_ = false;
// window handle for top level window.
HWND window_handle_ = nullptr;
// window handle for hosted content.
HWND child_content_ = nullptr;
};
#endif // RUNNER_WIN32_WINDOW_H_
# 智慧酒店 App 上手帮助
# 智慧酒店 App 上手帮助
## 1. 环境搭建
### 1.1 前置条件
```bash
# 确认 Flutter 版本 (需要 3.2.5+)
flutter --version
# 确认 Android SDK / Xcode 已安装
flutter doctor
```
### 1.2 项目初始化
```bash
cd smart_hotel_app
# 安装依赖
flutter pub get
# 生成路由代码(修改路由配置后需要重新执行)
flutter pub run build_runner build --delete-conflicting-outputs
```
### 1.3 运行项目
```bash
# 连接设备后运行
flutter run
# 指定设备
flutter run -d <device_id>
```
---
## 2. 项目架构速览
```
lib/
├── main.dart ← 入口,全局依赖初始化
├── blocs/auth/ ← 全局认证状态(BLoC 模式)
├── http/ ← 网络层:Dio封装 + 拦截器
├── models/bo/ ← 数据模型
├── repositories/ ← API 调用层
├── routes/ ← 路由配置(auto_route)
├── services/ ← 业务逻辑层
├── utils/ ← 工具:常量、事件总线、存储、日志
└── views/ ← 页面,每个页面 = cubit/ + widget/ + view.dart
├── layout/ ← 底部Tab框架
├── login/ ← 登录
├── home/ ← 告警Tab
├── service/ ← 管理Tab
├── report/ ← 能耗Tab
├── profile/ ← 我的Tab
└── search/ ← 搜索
```
**分层关系**: View → Cubit/BLoC → Service → Repository → DioRequest → API
> **注意**: 当前阶段 View 层已完成,但 Cubit 中数据为 mock 硬编码。对接真实 API 时需要补充 Service 和 Repository 层,详见 [对接方案.md](./对接方案.md)。
---
## 3. 如何新增一个页面(完整分层流程)
以新增「运维日志」页面为例,演示 **View → Cubit → Service → Repository → API** 的完整开发流程。
### 3.1 第一步:定义 BO 模型
创建 `lib/models/bo/operation_log_bo.dart`:
```dart
import 'package:equatable/equatable.dart';
class OperationLogBO extends Equatable {
final String id;
final String title;
final String time;
const OperationLogBO({
required this.id,
required this.title,
required this.time,
});
factory OperationLogBO.fromJson(Map<String, dynamic> json) {
return OperationLogBO(
id: json['id'] as String,
title: json['title'] as String,
time: json['time'] as String,
);
}
@override
List<Object?> get props => [id, title, time];
}
```
### 3.2 第二步:新建 Repository
创建 `lib/repositories/operation_log_repository.dart`:
```dart
import '../http/response_model.dart';
import '../http/dio_request.dart';
import '../models/bo/operation_log_bo.dart';
class OperationLogRepository {
Future<ResponseModel<List<OperationLogBO>>> getLogs() {
return DioRequest.instance.get<List<OperationLogBO>>(
'/api/operation-logs',
fromJsonT: (data) {
final list = data as List<dynamic>;
return list
.map((e) => OperationLogBO.fromJson(e as Map<String, dynamic>))
.toList();
},
);
}
}
```
### 3.3 第三步:新建 Service
创建 `lib/services/operation_log_service.dart`:
```dart
import '../repositories/operation_log_repository.dart';
import '../models/bo/operation_log_bo.dart';
class OperationLogService {
final OperationLogRepository _repository;
OperationLogService({required OperationLogRepository repository})
: _repository = repository;
Future<List<OperationLogBO>> getLogs() async {
final result = await _repository.getLogs();
if (result.success && result.data != null) {
return result.data!;
}
throw Exception(result.msg);
}
}
```
### 3.4 第四步:定义 State
创建 `lib/views/home/operation_log/cubit/operation_log_state.dart`:
```dart
import 'package:equatable/equatable.dart';
import 'package:smart_hotel_app/models/bo/operation_log_bo.dart';
class OperationLogState extends Equatable {
final List<OperationLogBO> logs;
final bool isLoading;
final String? error;
const OperationLogState({
this.logs = const [],
this.isLoading = false,
this.error,
});
OperationLogState copyWith({
List<OperationLogBO>? logs,
bool? isLoading,
String? error,
}) {
return OperationLogState(
logs: logs ?? this.logs,
isLoading: isLoading ?? this.isLoading,
error: error,
);
}
@override
List<Object?> get props => [logs, isLoading, error];
}
```
### 3.5 第五步:定义 Cubit(调用 Service)
创建 `lib/views/home/operation_log/cubit/operation_log_cubit.dart`:
```dart
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:smart_hotel_app/services/operation_log_service.dart';
import 'operation_log_state.dart';
class OperationLogCubit extends Cubit<OperationLogState> {
final OperationLogService _service;
OperationLogCubit({required OperationLogService service})
: _service = service,
super(const OperationLogState()) {
loadData();
}
Future<void> loadData() async {
emit(state.copyWith(isLoading: true));
try {
final logs = await _service.getLogs();
emit(state.copyWith(isLoading: false, logs: logs));
} catch (e) {
emit(state.copyWith(isLoading: false, error: e.toString()));
}
}
}
```
### 3.6 第六步:创建 View
创建 `lib/views/home/operation_log/operation_log_view.dart`:
```dart
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'cubit/operation_log_cubit.dart';
import 'cubit/operation_log_state.dart';
import 'package:smart_hotel_app/services/operation_log_service.dart';
import 'package:smart_hotel_app/repositories/operation_log_repository.dart';
@RoutePage()
class OperationLogView extends StatelessWidget {
const OperationLogView({super.key});
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => OperationLogCubit(
service: OperationLogService(
repository: OperationLogRepository(),
),
),
child: Scaffold(
appBar: AppBar(title: const Text('运维日志')),
body: BlocBuilder<OperationLogCubit, OperationLogState>(
builder: (context, state) {
if (state.isLoading) {
return const Center(child: CircularProgressIndicator());
}
if (state.error != null) {
return Center(child: Text('错误: ${state.error}'));
}
return ListView.builder(
itemCount: state.logs.length,
itemBuilder: (context, index) {
final log = state.logs[index];
return ListTile(
title: Text(log.title),
subtitle: Text(log.time),
);
},
);
},
),
),
);
}
}
```
### 3.7 第七步:注册路由
[app_router.dart](file:///Users/zh/Documents/work/zkrq/coding/smart_hotel_app/lib/routes/app_router.dart) 中添加:
```dart
AutoRoute(page: OperationLogRoute.page),
```
然后运行代码生成:
```bash
flutter pub run build_runner build --delete-conflicting-outputs
```
### 3.8 第八步:页面跳转
```dart
// 在任意页面中导航
context.pushRoute(const OperationLogRoute());
```
---
## 4. 如何发起网络请求(完整流程)
### 4.1 数据流向总览
```
View (UI) → Cubit (状态管理) → Service (业务逻辑) → Repository (API调用) → DioRequest (网络层)
```
### 4.2 新增 Repository
`lib/repositories/` 下新建文件,使用 `fromJsonT` 参数传入反序列化函数:
```dart
import '../http/response_model.dart';
import '../http/dio_request.dart';
import '../models/bo/device_bo.dart';
class DeviceRepository {
/// 获取设备列表
Future<ResponseModel<List<DeviceBO>>> getDeviceList() {
return DioRequest.instance.get<List<DeviceBO>>(
'/api/devices',
fromJsonT: (data) {
final list = data as List<dynamic>;
return list
.map((e) => DeviceBO.fromJson(e as Map<String, dynamic>))
.toList();
},
);
}
/// 获取设备详情
Future<ResponseModel<DeviceBO>> getDeviceDetail(String id) {
return DioRequest.instance.get<DeviceBO>(
'/api/devices/$id',
fromJsonT: (data) => DeviceBO.fromJson(data as Map<String, dynamic>),
);
}
/// 发送设备控制指令
Future<ResponseModel> controlDevice(String id, Map<String, dynamic> params) {
return DioRequest.instance.post('/api/devices/$id/control', data: params);
}
}
```
#### 4.2.1 POST 请求示例(简单到复杂)
**示例 1:简单 POST,无请求体,无响应解析**
```dart
/// 退出登录 - 最简单的 POST
Future<ResponseModel> logout() {
return DioRequest.instance.post('/api/logout');
}
```
**示例 2:POST 带简单请求体,不解析响应**
```dart
/// 登录 - 表单提交
Future<ResponseModel> login(String username, String password) {
return DioRequest.instance.post('/api/login', data: {
'username': username,
'password': password,
});
}
```
**示例 3:POST 带路径参数,不解析响应**
```dart
/// 切换规则开关
Future<ResponseModel> toggleRule(String ruleId, bool enabled) {
return DioRequest.instance.post('/api/rules/$ruleId/toggle', data: {
'enabled': enabled,
});
}
```
**示例 4:POST 带路径参数 + 查询参数,不解析响应**
```dart
/// 提交巡检结果(带查询参数如 ?inspectionId=xxx)
Future<ResponseModel> submitInspectionResult(
String deviceId, {
required String inspectionId,
required Map<String, dynamic> result,
}) {
return DioRequest.instance.post(
'/api/inspections/devices/$deviceId/submit',
data: result,
queryParameters: {'inspectionId': inspectionId},
);
}
```
**示例 5:POST 带复杂请求体,解析响应数据**
```dart
/// 批量设备控制 - 返回操作结果列表
Future<ResponseModel<List<ControlResultBO>>> batchControlDevices(
List<Map<String, dynamic>> commands,
) {
return DioRequest.instance.post<List<ControlResultBO>>(
'/api/devices/batch-control',
data: {
'commands': commands,
'timestamp': DateTime.now().millisecondsSinceEpoch,
},
fromJsonT: (data) {
final list = data as List<dynamic>;
return list
.map((e) => ControlResultBO.fromJson(e as Map<String, dynamic>))
.toList();
},
);
}
```
**示例 6:POST 带可选参数,解析单个响应对象**
```dart
/// 创建巡检任务 - 部分字段可选
Future<ResponseModel<InspectionTaskBO>> createInspectionTask({
required String name,
required List<String> deviceIds,
String? assignee,
DateTime? scheduledTime,
String? remark,
}) {
final body = <String, dynamic>{
'name': name,
'deviceIds': deviceIds,
};
if (assignee != null) body['assignee'] = assignee;
if (scheduledTime != null) body['scheduledTime'] = scheduledTime.toIso8601String();
if (remark != null) body['remark'] = remark;
return DioRequest.instance.post<InspectionTaskBO>(
'/api/inspections',
data: body,
fromJsonT: (data) => InspectionTaskBO.fromJson(data as Map<String, dynamic>),
);
}
```
**示例 7:PUT 请求(更新资源)**
```dart
/// 更新房间设备状态
Future<ResponseModel<RoomBO>> updateRoomDevice(
String roomNumber,
String deviceName,
Map<String, dynamic> settings,
) {
return DioRequest.instance.put<RoomBO>(
'/api/rooms/$roomNumber/devices/$deviceName',
data: settings,
fromJsonT: (data) => RoomBO.fromJson(data as Map<String, dynamic>),
);
}
```
**示例 8:DELETE 请求**
```dart
/// 删除巡检记录
Future<ResponseModel> deleteInspectionRecord(String recordId) {
return DioRequest.instance.delete('/api/inspections/records/$recordId');
}
```
### 4.3 新增 Service(业务逻辑封装)
`lib/services/` 下新建文件:
```dart
import '../repositories/device_repository.dart';
import '../models/bo/device_bo.dart';
class DeviceService {
final DeviceRepository _repository;
DeviceService({required DeviceRepository repository})
: _repository = repository;
Future<List<DeviceBO>> getDevices() async {
final result = await _repository.getDeviceList();
if (result.success && result.data != null) {
return result.data!;
}
throw Exception(result.msg);
}
Future<DeviceBO> getDeviceDetail(String id) async {
final result = await _repository.getDeviceDetail(id);
if (result.success && result.data != null) {
return result.data!;
}
throw Exception(result.msg);
}
}
```
### 4.4 在 Cubit 中调用 Service
```dart
class DeviceCubit extends Cubit<DeviceState> {
final DeviceService _service;
DeviceCubit({required DeviceService service})
: _service = service,
super(const DeviceState()) {
loadDevices();
}
Future<void> loadDevices() async {
emit(state.copyWith(isLoading: true));
try {
final devices = await _service.getDevices();
emit(state.copyWith(isLoading: false, devices: devices));
} catch (e) {
emit(state.copyWith(isLoading: false, error: e.toString()));
}
}
}
```
### 4.5 API 响应格式约定
后端接口返回标准 JSON 格式:
```json
{
"code": 200,
"msg": "success",
"data": { ... }
}
```
`ResponseModel.fromJson` 会自动解析,`fromJsonT` 参数可传入自定义反序列化函数。
---
## 5. 全局状态访问
### 5.1 读取 AuthBloc 状态
```dart
final authState = context.read<AuthBloc>().state;
if (authState is AuthSuccess) {
final username = authState.userInfo.username;
}
```
### 5.2 监听 Token 过期
```dart
// Token 过期时自动弹出登录页,无需手动处理
// 网络拦截器检测到 401 时发出 TokenExpiredEvent
// main.dart 中已订阅该事件,自动跳转登录页
```
---
## 6. 本地存储使用
```dart
final storage = StorageService();
// Token 操作
await storage.saveToken('your_token');
final token = await storage.getToken();
final isExpired = await storage.isTokenExpired();
// 记住密码
await storage.saveRememberCredentials(username: 'admin', password: '123456');
final (username, password, enabled) = await storage.getRememberCredentials();
await storage.clearRememberCredentials();
```
---
## 7. 屏幕适配
项目使用 `flutter_screenutil`,设计稿基准为常规移动端尺寸。
```dart
// 使用 .w / .h / .sp / .r 进行适配
Container(
width: 100.w, // 根据屏幕宽度等比缩放
height: 50.h, // 根据屏幕高度等比缩放
padding: EdgeInsets.all(28.w),
child: Text('标题', style: TextStyle(fontSize: 32.sp)),
)
```
---
## 8. 图表使用
使用 `fl_chart` 绘制图表,项目中已有示例:
| 文件 | 图表类型 | 示例组件 |
|------|---------|---------|
| `home/abnormal/widget/temperature_char_block.dart` | 折线图 | 温度曲线 |
| `home/device/widget/power_char_block.dart` | 柱状图 | 功率图表 |
| `report/index/widget/weekly_power_chart.dart` | 柱状图 | 周用电量 |
| `report/energy/widget/hourly_chart_block.dart` | 折线图 | 分时能耗 |
| `report/energy/widget/zone_chart_block.dart` | 饼图 | 区域能耗占比 |
新增图表可参考这些文件。
---
## 9. 常见问题
### Q: `build_runner` 报错怎么办?
```bash
# 先清理再重新生成
flutter pub run build_runner clean
flutter pub run build_runner build --delete-conflicting-outputs
```
### Q: 如何切换 API 环境地址?
修改 [constants.dart](file:///Users/zh/Documents/work/zkrq/coding/smart_hotel_app/lib/utils/constants.dart) 中的 `baseUrl`
### Q: 路由跳转后白屏?
检查是否执行了 `build_runner` 生成 `app_router.gr.dart`,以及 View 文件是否添加 `@RoutePage()`
### Q: 如何添加新的底部 Tab?
1.[app_router.dart](file:///Users/zh/Documents/work/zkrq/coding/smart_hotel_app/lib/routes/app_router.dart)`DefaultLayoutRoute``children` 中添加新路由
2.[default_view.dart](file:///Users/zh/Documents/work/zkrq/coding/smart_hotel_app/lib/views/layout/default_view.dart)`routes``BottomNavigationBar` 中添加对应项
3. 运行 `build_runner`
---
## 10. 代码规范提醒
- **命名约定**:
- 文件名: `snake_case.dart`
- 类名: `PascalCase`
- 变量/方法: `camelCase`
- 常量: `camelCase``SCREAMING_SNAKE_CASE`
- **每个页面模块结构**: 必须包含 `cubit/` + `widget/` + `view.dart`
- **State 不可变**: 始终使用 `copyWith` 更新状态,不要直接修改 state 字段
- **UI 与逻辑分离**: View 只负责 UI 渲染,业务逻辑放在 Cubit/Service 中
- **组件化**: 可复用的 UI 块抽成 `widget/` 下的独立组件,通过参数接收数据
\ No newline at end of file
# 智慧酒店 App 技术方案
# 智慧酒店 App 技术方案
## 1. 项目概述
**项目名称**: smart_hotel_app(智慧酒店管理 App)
**技术栈**: Flutter 3.x + Dart 3.2+
**项目定位**: 为酒店运维管理人员提供设备监控、巡检管理、能耗报告、服务管理等一站式移动端解决方案。
**核心功能模块**:
| 模块 | TAB 名称 | 功能描述 |
|------|----------|---------|
| 告警模块 | 告警 | 异常告警列表、告警详情(温度/电压异常) |
| 管理模块 | 管理 | 巡检任务管理、巡检拓扑图、设备详情、房间设备控制 |
| 能耗模块 | 能耗 | 能耗总览、设备能耗列表、房间能耗报告、区域能耗报告、规则管理 |
| 个人中心 | 我的 | 用户信息、菜单入口 |
---
## 2. 架构设计
### 2.1 整体架构
项目采用 **分层架构 + BLoC 状态管理** 模式,自上而下分为:
```
┌─────────────────────────────────────────────┐
│ Views(UI 层) │
│ 页面/视图组件(View) + 可复用组件(Widget) │
├─────────────────────────────────────────────┤
│ Cubit/BLoC(状态管理层) │
│ 业务状态管理 + 事件处理 │
├─────────────────────────────────────────────┤
│ Service(业务逻辑层) │
│ 业务逻辑封装、数据转换 │
├─────────────────────────────────────────────┤
│ Repository(数据仓库层) │
│ 数据源抽象,连接 HTTP 请求 │
├─────────────────────────────────────────────┤
│ HTTP(网络层) │
│ Dio 封装 + 拦截器链(请求/响应/错误) │
├─────────────────────────────────────────────┤
│ Models(数据模型层) │
│ BO/DTO 对象定义 │
└─────────────────────────────────────────────┘
```
### 2.3 Provider 分层结构
项目通过 `MultiBlocProvider` 的嵌套实现不同作用域的状态管理:
```
MaterialApp (main.dart)
└─ MultiBlocProvider (全局)
├─ AuthBloc ← 所有页面都能访问
├─ LoginRoute() ← 可以 read<AuthBloc>
└─ DefaultLayoutRoute() ← 可以 read<AuthBloc>
└─ MultiBlocProvider (仅主页范围)
├─ HomeIndexCubit ← 仅在 tab 页面内可访问
├─ ServiceIndexCubit ← 仅在 tab 页面内可访问
├─ ReportIndexCubit ← 仅在 tab 页面内可访问
└─ AutoTabsScaffold
├─ HomeView ← 可以 read<AuthBloc> + read<HomeIndexCubit>
├─ ServiceView ← 可以 read<AuthBloc> + read<ServiceIndexCubit>
├─ ReportView ← 可以 read<AuthBloc> + read<ReportIndexCubit>
└─ ProfileView ← 可以 read<AuthBloc>(但不能读上面三个Cubit)
```
| 层级 | 定义位置 | 作用域 | 提供者 | 特点 |
|------|---------|--------|--------|------|
| 全局 Provider | `main.dart` | 整个 App | `AuthBloc`(认证) | `.value()` 复用已有实例,App 存活期间一直存在 |
| 主页 Provider | `default_view.dart` | 仅底部 Tab 页面 | `HomeIndexCubit``ServiceIndexCubit``ReportIndexCubit` | `create` 创建新实例,仅在主页存活期间存在 |
**子 `MultiBlocProvider` 可以访问父 `MultiBlocProvider` 提供的内容**(tab 页面中 `context.read<AuthBloc>()` 依然有效),反过来不行(`AuthBloc` 里不能读取业务 Cubit)。
项目未使用 DI 框架(如 get_it),而是采用 **手动注入** 方式。在 `main.dart``_MyAppState.initState()` 中集中创建核心单例,通过构造函数向下传递。
```
main.dart
└─ initState()
├─ StorageService() → 全局存储服务
├─ DioRequest.instance.init() → 网络请求初始化
├─ AuthRepository() → 认证仓库
├─ AuthService() → 认证业务服务
├─ AuthBloc() → 全局认证状态
└─ AppRouter() → 路由配置
```
---
## 3. 目录结构详解
```
lib/
├── main.dart # 应用入口,全局初始化
├── assets/ # 静态资源
│ ├── icon/ # 图标资源
│ ├── login/ # 登录页资源
│ └── tab/ # 底部导航栏图标
├── blocs/ # 全局 BLoC 状态管理
│ └── auth/ # 认证模块
│ ├── auth_bloc.dart # AuthBloc 业务逻辑
│ ├── auth_event.dart # AuthEvent 事件定义
│ └── auth_state.dart # AuthState 状态定义
├── http/ # 网络请求层
│ ├── dio_request.dart # Dio 单例封装(GET/POST/PUT/DELETE)
│ ├── response_model.dart # 统一响应模型 ResponseModel<T>
│ ├── exceptions/ # 异常定义
│ │ ├── app_exception.dart # 基类异常
│ │ ├── business_exception.dart
│ │ ├── cancel_exception.dart
│ │ ├── network_exception.dart
│ │ └── server_exception.dart
│ └── interceptors/ # Dio 拦截器
│ ├── request_interceptor.dart # 请求拦截(Token 注入、UUID)
│ ├── response_interceptor.dart # 响应拦截(统一处理)
│ └── error_interceptor.dart # 错误拦截(异常转换)
├── models/ # 数据模型
│ └── bo/ # 业务对象 Business Object
│ └── user_info_bo.dart # 用户信息
├── repositories/ # 数据仓库层
│ └── auth_repository.dart # 认证 API 调用
├── routes/ # 路由配置(auto_route)
│ ├── app_router.dart # 路由表定义
│ └── app_router.gr.dart # 自动生成的代码
├── services/ # 业务服务层
│ └── auth_service.dart # 认证业务逻辑
├── utils/ # 工具类
│ ├── constants.dart # 全局常量(BaseURL、超时、错误码)
│ ├── event_bus.dart # 全局事件总线
│ ├── logger.dart # 日志工具
│ └── storage/ # 本地存储
│ └── storage_service.dart # FlutterSecureStorage 封装
└── views/ # 视图层
├── layout/ # 布局框架
│ └── default_view.dart # 底部 Tab 框架(AutoTabsScaffold)
├── login/ # 登录模块
│ ├── login_view.dart
│ ├── cubit/
│ └── widget/
├── home/ # 告警 Tab(首页)
│ ├── index/ # 告警首页
│ ├── abnormal/ # 告警详情
│ ├── abnormal_list/ # 告警列表
│ ├── device/ # 设备详情
│ ├── inspection/ # 巡检详情
│ ├── inspection_device/ # 巡检设备
│ ├── inspection_history/ # 巡检历史
│ └── inspection_topology/ # 巡检拓扑
├── service/ # 管理 Tab
│ ├── index/ # 管理首页
│ ├── room/ # 房间详情
│ └── device/ # 设备控制
├── report/ # 能耗 Tab
│ ├── index/ # 能耗首页
│ ├── device/ # 设备能耗列表/详情
│ ├── energy/ # 区域能耗
│ ├── room/ # 房间能耗
│ └── rule/ # 规则管理
├── profile/ # 我的 Tab
│ └── index/ # 个人中心
└── search/ # 全局搜索
└── index_view.dart
```
---
## 3.1 数据模型(BO)规范
所有业务数据模型统一定义在 `models/bo/` 目录下,按模块分文件。**当前状态**:大部分数据模型散落在各页面的 `state.dart` 文件中(如 `AlarmItem``InspectionDevice``RoomInfo` 等),需要在对接时迁移到 `models/bo/`
### 建议的 BO 文件组织
```
models/bo/
├── user_info_bo.dart # 用户信息(已有)
├── alarm_bo.dart # 告警相关:AlarmItem, AlarmInfo, AlarmDetail
├── device_bo.dart # 设备相关:DeviceInfo, EquipmentItem, RoomDevice
├── inspection_bo.dart # 巡检相关:InspectionDevice, InspectionRecord, InspectionItem
├── topology_bo.dart # 拓扑相关:TopologyNode
├── energy_bo.dart # 能耗相关:ZoneData, EnergyOverview, MeterData
├── room_bo.dart # 房间相关:RoomInfo, RoomDeviceStatus, RoomStatus
├── rule_bo.dart # 规则相关:RuleInfo
└── report_bo.dart # 报告相关:ReportMetrics, WeeklyPowerData
```
### BO 模型定义规范
```dart
// 必须使用 Equatable,提供 copyWith
class SomeBO extends Equatable {
final String id;
final String name;
// ... 字段
const SomeBO({required this.id, required this.name});
// 从 JSON 反序列化(用于 API 响应解析)
factory SomeBO.fromJson(Map<String, dynamic> json) {
return SomeBO(
id: json['id'] as String,
name: json['name'] as String,
);
}
// 序列化为 JSON(用于 API 请求体)
Map<String, dynamic> toJson() => {'id': id, 'name': name};
// copyWith(用于状态更新)
SomeBO copyWith({String? id, String? name}) =>
SomeBO(id: id ?? this.id, name: name ?? this.name);
@override
List<Object?> get props => [id, name];
}
```
---
## 4. 核心技术说明
### 4.1 状态管理:BLoC + Cubit
项目混合使用 **BLoC**(全局认证)和 **Cubit**(页面级状态)两种模式:
#### BLoC 使用场景:全局认证流程
| 文件 | 职责 |
|------|------|
| `auth_event.dart` | 定义事件:`AuthLoginRequested``AuthTokenExpiredEvent``AuthLogoutRequestedEvent` |
| `auth_state.dart` | 定义状态:`AuthInitial``AuthLoading``AuthSuccess``AuthFailure``AuthTokenExpired``AuthLoggedOut` |
| `auth_bloc.dart` | 事件处理:登录、Token过期、退出登录 |
`main.dart` 中通过 `BlocProvider` 注入全局 AuthBloc,监听 `AuthTokenExpired` 状态跳转登录页,监听 `AuthLoggedOut` 执行退出。
#### Cubit 使用场景:页面级状态
每个功能页面都有独立的 **Cubit + State** 组合:
```
views/{模块}/{页面}/
├── cubit/
│ ├── xxx_cubit.dart # 业务逻辑
│ └── xxx_state.dart # 数据状态 + copyWith
└── widget/ # 可复用组件
```
State 类使用 `Equatable` 便于比较,提供 `copyWith` 方法实现不可变更新。
**数据流向**:
```
Cubit.emit(state.copyWith(xxx: newValue))
→ BlocBuilder 监听变化
→ Widget rebuild
```
### 4.2 路由系统:auto_route
使用 `auto_route` 实现声明式路由。
**路由配置**[app_router.dart](file:///Users/zh/Documents/work/zkrq/coding/smart_hotel_app/lib/routes/app_router.dart)):
```
LoginRoute (initial)
→ DefaultLayoutRoute (AutoTabsScaffold)
├── HomeRoute (告警首页, initial tab)
├── ServiceIndexRoute (管理首页)
├── ReportIndexRoute (能耗首页)
└── ProfileRoute (我的)
→ SearchRoute (全局搜索)
→ AbnormalDetailRoute (告警详情)
→ DeviceDetailRoute (设备详情)
→ ... 其他二级页面
```
- 所有页面使用 `@RoutePage()` 注解标记
- 根路由为 `LoginRoute`,登录成功跳转到 `DefaultLayoutRoute`
- `DefaultLayoutRoute` 内嵌 4 个 Tab,通过 `AutoTabsScaffold` + `BottomNavigationBar` 实现
- 二级页面(详情)在 Tab 外层定义,支持全屏 push
### 4.3 网络请求层
#### DioRequest 单例
封装 Dio 实例,提供 `get/post/put/delete` 方法,返回 `ResponseModel<T>`
**配置项**[constants.dart](file:///Users/zh/Documents/work/zkrq/coding/smart_hotel_app/lib/utils/constants.dart)):
| 配置 | 值 | 说明 |
|------|-----|------|
| baseUrl | `http://localhost:8080` | API 基础地址 |
| connectTimeout | 30000ms | 连接超时 |
| receiveTimeout | 30000ms | 接收超时 |
| sendTimeout | 30000ms | 发送超时 |
#### 拦截器链
```
RequestInterceptor
→ 注入 Authorization Bearer Token
→ 注入 X-Request-UUID 追踪ID
→ 注入 X-Timestamp 时间戳
→ 输出请求日志
ResponseInterceptor
→ 响应日志输出
→ 响应数据预处理
ErrorInterceptor
→ DioException 转 AppException 子类
→ 业务异常 / 网络异常 / 服务器异常分离处理
```
#### ResponseModel
```dart
class ResponseModel<T> {
final int code; // 业务状态码
final String msg; // 提示信息
final T? data; // 泛型数据
final String uuid; // 请求唯一标识
final bool success; // code == 200
final int timestamp; // 时间戳
}
```
### 4.4 数据持久化:FlutterSecureStorage
StorageService 封装 `flutter_secure_storage`,提供:
| 功能 | 方法 |
|------|------|
| Token 存取 | `saveToken()` / `getToken()` / `deleteToken()` / `hasToken()` |
| Token 过期检查 | `isTokenExpired()` / `getTokenExpiry()` |
| 记住密码 | `saveRememberCredentials()` / `getRememberCredentials()` / `clearRememberCredentials()` |
Android 端启用 `encryptedSharedPreferences: true` 增强安全性。
### 4.5 事件总线
全局 `eventBus``StreamController.broadcast`)用于跨模块通信:
| 事件 | 用途 |
|------|------|
| `TokenExpiredEvent` | Token 过期通知,触发全局重新登录 |
| `LogoutEvent` | 退出登录通知 |
`main.dart` 中订阅 `TokenExpiredEvent`,触发 `AuthBloc``AuthTokenExpiredEvent`
---
## 5. 业务模块设计
### 5.1 认证流程
```
LoginView → 输入用户名密码 → AuthBloc(AuthLoginRequested)
→ AuthService.login()
→ AuthRepository.login() → POST /login
→ 成功: StorageService.saveToken() → AuthSuccess → 跳转首页
→ 失败: AuthFailure → 显示错误
Token 过期:
网络拦截器 (401) → ErrorInterceptor → eventBus(TokenExpiredEvent)
→ AuthBloc(AuthTokenExpiredEvent) → 清除 Token → 跳转登录页
```
### 5.2 页面状态管理模式
所有业务页面遵循统一模式:
```
1. 定义 State: extends Equatable, 提供 copyWith
2. 定义 Cubit: extends Cubit<State>, 构造函数初始化数据
3. View 使用 BlocProvider 创建 Cubit
4. UI 通过 BlocBuilder 监听 State 变化
5. 子组件通过接口参数接收数据(props down, events up)
```
### 5.3 子模块页面与路由对应
#### 告警模块(Home)
| 页面 | 路由 | 功能 |
|------|------|------|
| 告警首页 | `HomeRoute` | 统计概览、任务列表、温度告警、电压波动 |
| 告警详情 | `AbnormalDetailRoute` | 温度曲线、告警信息、处理操作 |
| 告警列表 | `AbnormalListRoute` | 告警筛选、状态过滤、列表展示 |
| 设备详情 | `DeviceDetailRoute` | 设备参数、功率曲线、状态信息 |
| 巡检详情 | `InspectionDetailRoute` | 巡检任务、设备列表、筛选面板 |
| 巡检设备 | `InspectionDeviceRoute` | 设备概览、巡检项目、巡检历史 |
| 巡检历史 | `InspectionHistoryRoute` | 巡检记录列表、统计卡片 |
| 巡检拓扑 | `InspectionTopologyRoute` | 网络拓扑图、状态图例 |
#### 管理模块(Service)
| 页面 | 路由 | 功能 |
|------|------|------|
| 管理首页 | `ServiceIndexRoute` | 服务统计、房间管理卡片 |
| 房间详情 | `ServiceRoomDetailRoute` | 房间信息、设备状态、设备控制 |
| 设备控制 | `ServiceDeviceDetailRoute` | 模式选择、温度控制、风速调节 |
#### 能耗模块(Report)
| 页面 | 路由 | 功能 |
|------|------|------|
| 能耗首页 | `ReportIndexRoute` | 设备在线率、能耗指标、周用电图、房间分布 |
| 设备能耗列表 | `ReportDeviceListRoute` | 设备卡片、筛选Tab |
| 设备能耗详情 | `ReportDeviceDetailRoute` | 单设备能耗详情 |
| 区域能耗 | `ReportEnergyDetailRoute` | 能耗统计、分时/分区图表 |
| 房间能耗 | `ReportRoomDetailRoute` | 楼层选择、房间网格、详情卡片 |
| 规则管理 | `ReportRuleManagementRoute` | 规则卡片管理 |
### 5.4 当前状态:Mock 数据阶段
**页面 View 层已完成开发**,所有页面 UI 可正常渲染,但数据全部为硬编码 mock 数据。
**现状分析**
| 层级 | 状态 | 说明 |
|------|------|------|
| View 层 | 已完成 | 所有页面 UI 组件已完成 |
| State 层 | 已完成 | 各页面 State + copyWith 已定义 |
| Cubit 层 | Mock 阶段 | `initData()` 中直接塞入 mock 数据,未调用 Service |
| Service 层 | 仅 Auth | 只有 `AuthService` 存在,页面级 Service 全部缺失 |
| Repository 层 | 仅 Auth | 只有 `AuthRepository` 存在,页面级 Repository 全部缺失 |
| BO 模型 | 散落 | 数据模型定义在 state 文件中,未统一到 `models/bo/` |
### 5.5 对接改造步骤(View 层不变)
每个页面模块的改造遵循统一流程,**View 层代码无需修改**(因为 Cubit/State 接口不变):
```
第一步:将 State 中的数据模型提取到 models/bo/ 目录
第二步:新增 Repository(API 调用)
第三步:新增 Service(业务逻辑)
第四步:修改 Cubit:将 initData() 中的 mock 数据替换为 Service 调用
第五步:在 main.dart 中注入 Service/Repository(如需要全局单例)
```
**改造示例**(以告警列表页为例):
```
改造前:
AbnormalListCubit.initData() → 直接 emit(mockData)
改造后:
AbnormalListCubit.loadData() → AbnormalListService.getAlarms()
→ AbnormalListRepository.getAlarms() → DioRequest.get('/api/alarms')
→ ResponseModel.data → AlarmListBO.fromJson() → emit(realData)
```
详细对接规范见 [对接方案.md](./对接方案.md)
---
## 6. 依赖库清单
| 库 | 版本 | 用途 |
|-----|------|------|
| flutter_bloc | ^8.1.5 | BLoC/Cubit 状态管理 |
| equatable | ^2.0.5 | 值对象相等比较 |
| auto_route | ^7.9.2 | 声明式路由管理 |
| dio | ^5.4.0 | HTTP 网络请求 |
| flutter_secure_storage | ^9.0.0 | 安全本地存储 |
| flutter_screenutil | ^5.9.3 | 屏幕适配(设计稿基准) |
| fl_chart | ^0.71.0 | 图表绘制(折线/柱状/饼图) |
| flutter_switch | ^0.3.2 | 开关组件 |
| fluttertoast | ^8.2.5 | Toast 提示 |
| loading_animation_widget | ^1.3.0 | 加载动画 |
---
## 7. 开发环境要求
| 工具 | 版本要求 |
|------|---------|
| Flutter SDK | >= 3.2.5 |
| Dart SDK | >= 3.2.5 < 4.0.0 |
| Android Studio / VS Code | 最新稳定版 |
| Android minSdk | 21+ |
| iOS Deployment Target | 12.0+ |
---
## 8. 状态码约定
| 状态码 | 含义 |
|--------|------|
| 200 | 请求成功 |
| 401 | Token 过期/未授权 |
| 403 | 禁止访问 |
| 404 | 资源不存在 |
| 500 | 服务器错误 |
---
## 9. 关键设计决策
1. **Cubit 而非 BLoC**: 大多数页面业务逻辑简单,使用 Cubit 减少样板代码(无需定义 Event 类),仅在认证等复杂流程使用 BLoC。
2. **手动依赖注入**: 项目规模适中,未引入 get_it 等 DI 框架,依赖关系清晰可控。
3. **auto_route 代码生成**: 路由通过 `@RoutePage()` 注解 + `build_runner` 自动生成 `app_router.gr.dart`,减少手写路由模板代码。
4. **State copyWith 模式**: 所有 State 不可变,通过 `copyWith` 更新部分字段,保证状态变更可追踪。
5. **Token 自动注入**: `RequestInterceptor` 在每次请求时自动从 SecureStorage 读取 Token 并注入 Header。
6. **统一错误处理**: `ErrorInterceptor` 将 Dio 异常转换为项目内部异常类型,方便上层统一处理。
\ No newline at end of file
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