Commit 4f547179 authored by 张宏's avatar 张宏

智能酒店页面开发

parent 165f6cbb
{
"flutter": "3.38.10"
}
\ No newline at end of file
......@@ -5,9 +5,11 @@
*.swp
.DS_Store
.atom/
.build/
.buildlog/
.history
.svn/
.swiftpm/
migrate_working_dir/
# IntelliJ related
......@@ -42,3 +44,6 @@ app.*.map.json
/android/app/debug
/android/app/profile
/android/app/release
# FVM Version Cache
.fvm/
\ No newline at end of file
......@@ -4,7 +4,7 @@
# This file should be version controlled and should not be manually edited.
version:
revision: "67457e669f79e9f8d13d7a68fe09775fefbb79f4"
revision: "c6f67dede3d4aa1aa7a69dd56a3494a5cde6cc80"
channel: "stable"
project_type: app
......@@ -13,26 +13,11 @@ project_type: app
migration:
platforms:
- platform: root
create_revision: 67457e669f79e9f8d13d7a68fe09775fefbb79f4
base_revision: 67457e669f79e9f8d13d7a68fe09775fefbb79f4
- platform: android
create_revision: 67457e669f79e9f8d13d7a68fe09775fefbb79f4
base_revision: 67457e669f79e9f8d13d7a68fe09775fefbb79f4
create_revision: c6f67dede3d4aa1aa7a69dd56a3494a5cde6cc80
base_revision: c6f67dede3d4aa1aa7a69dd56a3494a5cde6cc80
- platform: ios
create_revision: 67457e669f79e9f8d13d7a68fe09775fefbb79f4
base_revision: 67457e669f79e9f8d13d7a68fe09775fefbb79f4
- platform: linux
create_revision: 67457e669f79e9f8d13d7a68fe09775fefbb79f4
base_revision: 67457e669f79e9f8d13d7a68fe09775fefbb79f4
- platform: macos
create_revision: 67457e669f79e9f8d13d7a68fe09775fefbb79f4
base_revision: 67457e669f79e9f8d13d7a68fe09775fefbb79f4
- platform: web
create_revision: 67457e669f79e9f8d13d7a68fe09775fefbb79f4
base_revision: 67457e669f79e9f8d13d7a68fe09775fefbb79f4
- platform: windows
create_revision: 67457e669f79e9f8d13d7a68fe09775fefbb79f4
base_revision: 67457e669f79e9f8d13d7a68fe09775fefbb79f4
create_revision: c6f67dede3d4aa1aa7a69dd56a3494a5cde6cc80
base_revision: c6f67dede3d4aa1aa7a69dd56a3494a5cde6cc80
# User provided section
......
{
"dart.flutterSdkPath": ".fvm/versions/3.38.10"
}
\ No newline at end of file
# 智能酒店管理系统 - 架构迁移方案
## 一、现有架构分析
### 1.1 整体分层
```
lib/
├── assets/ # 静态资源
├── blocs/auth/ # 全局认证 Bloc(唯一的 Bloc)
├── http/ # 网络层(Dio + 拦截器)
├── models/bo/ # 业务对象
├── repositories/ # 数据仓库层
├── routes/ # 路由(auto_route)
├── services/ # 服务层
├── utils/ # 工具类(storage、event_bus、logger)
└── views/ # 视图层
├── login/ # 登录模块
├── home/ # 告警模块(index / abnormal / device)
├── service/ # 服务模块(index / device / room)
├── report/ # 看板模块(index / energy / room)
├── profile/ # 我的模块
├── search/ # 搜索页
└── layout/ # Tab 布局容器
```
### 1.2 当前状态管理方式
| 模块 | 当前方式 | 问题 |
|------|---------|------|
| Auth | `flutter_bloc` 的 `Bloc`(Event-driven)| 无 |
| 所有页面 | 自定义 `Controller` 类 + `StatefulWidget.setState()` | 无响应式、不可追溯、与 Widget 强耦合 |
### 1.3 当前 Controller 与 Widget Block 的交互模式
| 模式 | 描述 | 涉及模块 |
|------|------|---------|
| A | 父 View 创建 Controller,传给所有子组件 | home/index、profile/index |
| B | 父 View 创建 Controller,提取数据对象后传给子组件 | home/abnormal、home/device |
| C | 每个子组件独立创建 Controller 实例 | report/index(6个实例)、service/index、service/room、service/device |
| D | Controller 是静态类 | report/energy |
| E | 无 Controller 或空 Controller | report/room、search、layout |
---
## 二、目标架构
### 2.1 技术选型
| 层级 | 技术 | 说明 |
|------|------|------|
| 全局状态 | `flutter_bloc` 的 `Bloc` | Event-driven,支持事件追踪 |
| 页面状态 | `flutter_bloc` 的 `Cubit` | Function-call-driven,简洁高效 |
| 路由 | `auto_route` | 保持不变 |
| 网络 | `Dio` + 拦截器 | 保持不变 |
### 2.2 Bloc vs Cubit 划分原则
| 场景 | 使用 |
|------|------|
| 跨页面/全局共享状态(Auth、WebSocket、主题) | **Bloc** |
| 需要事件溯源、复杂业务流程 | **Bloc** |
| 单页面内 UI 状态、简单数据展示 | **Cubit** |
### 2.3 本项目的划分
#### 保持 Bloc(global)
| 名称 | 路径 | 状态 |
|------|------|------|
| AuthBloc | `blocs/auth/` | ✅ 已有,不变 |
#### 新增 Cubit(page-level,替代 Controller)
| Cubit | 路径 | 替代的 Controller |
|-------|------|-------------------|
| LoginCubit | `views/login/cubit/` | LoginController |
| HomeIndexCubit | `views/home/index/cubit/` | HomeIndexController |
| AbnormalCubit | `views/home/abnormal/cubit/` | HomeAbnormalController |
| DeviceCubit | `views/home/device/cubit/` | HomeDeviceController |
| ServiceIndexCubit | `views/service/index/cubit/` | ServiceIndexController |
| ServiceDeviceCubit | `views/service/device/cubit/` | ServiceDeviceController |
| ServiceRoomCubit | `views/service/room/cubit/` | ServiceRoomController |
| ReportIndexCubit | `views/report/index/cubit/` | ReportIndexController |
| EnergyCubit | `views/report/energy/cubit/` | ReportEnergyController |
| RoomReportCubit | `views/report/room/cubit/` | ReportRoomController |
| ProfileCubit | `views/profile/index/cubit/` | ProfileIndexController |
---
## 三、目录结构调整
### 3.1 调整后完整结构
```
lib/
blocs/ # 全局 Bloc
auth/
auth_bloc.dart # ✅ 不变
auth_event.dart # ✅ 不变
auth_state.dart # ✅ 不变
views/
login/
cubit/
login_cubit.dart # 新增
login_state.dart # 新增
widget/ # 原 child/ 改名
btn_login.dart
btn_reset.dart
password_field.dart
remember_password_row.dart
username_field.dart
login_view.dart # 重构
layout/
default_view.dart # 不变
search/
index_view.dart # 不变
home/
index/
cubit/
home_index_cubit.dart # 新增
home_index_state.dart # 新增
widget/ # 原 child/ 改名
equipment_list_block.dart
task_head_block.dart
task_total_block.dart
temperature_block.dart
voltage_operate_block.dart
index_view.dart # 重构
abnormal/
cubit/
abnormal_cubit.dart # 新增
abnormal_state.dart # 新增
widget/ # 原 child/ 改名
alarm_info_block.dart
temperature_block.dart
temperature_char_block.dart
abnormal_detail_view.dart # 重构
device/
cubit/
device_cubit.dart # 新增
device_state.dart # 新增
widget/ # 原 child/ 改名
device_parms_block.dart
power_char_block.dart
state_info_block.dart
device_detail_view.dart # 重构
service/
index/
cubit/
service_index_cubit.dart # 新增
service_index_state.dart # 新增
widget/ # 原 child/ 改名
room_management_block.dart
service_head_block.dart
service_stats_block.dart
index_view.dart # 重构
device/
cubit/
service_device_cubit.dart # 新增
service_device_state.dart # 新增
widget/ # 原 child/ 改名
mode_selection_block.dart
temperature_control_block.dart
wind_speed_block.dart
device_detail_view.dart # 重构
room/
cubit/
service_room_cubit.dart # 新增
service_room_state.dart # 新增
widget/ # 原 child/ 改名
bottom_operation_block.dart
device_control_block.dart
room_info_block.dart
room_detail_view.dart # 重构
report/
index/
cubit/
report_index_cubit.dart # 新增
report_index_state.dart # 新增
widget/ # 原 child/ 改名
device_online_rate.dart
quick_actions_block.dart
report_header_block.dart
report_metrics_block.dart
room_status_distribution.dart
weekly_power_chart.dart
index_view.dart # 重构
energy/
cubit/
energy_cubit.dart # 新增
energy_state.dart # 新增
widget/ # 原 child/ 改名
energy_stats_card_block.dart
hourly_chart_block.dart
zone_chart_block.dart
energy_detail_view.dart # 重构
room/
cubit/
room_report_cubit.dart # 新增
room_report_state.dart # 新增
room_detail.dart # 重构
profile/
index/
cubit/
profile_cubit.dart # 新增
profile_state.dart # 新增
widget/ # 原 child/ 改名
profile_header_block.dart
profile_menu_block.dart
index_view.dart # 重构
```
### 3.2 变更汇总
#### 删除的文件(11 个 Controller)
```
views/login/controller.dart
views/home/index/controller.dart
views/home/abnormal/controller.dart
views/home/device/controller.dart
views/service/index/controller.dart
views/service/device/controller.dart
views/service/room/controller.dart
views/report/index/controller.dart
views/report/energy/controller.dart
views/report/room/controller.dart
views/profile/index/controller.dart
```
#### 新增的文件(22 个,每个模块 1 个 cubit + 1 个 state)
```
各模块下 cubit/xxx_cubit.dart + cubit/xxx_state.dart
```
#### 重命名的文件夹(10 个 child → widget)
```
views/login/child/ → views/login/widget/
views/home/index/child/ → views/home/index/widget/
views/home/abnormal/child/ → views/home/abnormal/widget/
views/home/device/child/ → views/home/device/widget/
views/service/index/child/ → views/service/index/widget/
views/service/device/child/ → views/service/device/widget/
views/service/room/child/ → views/service/room/widget/
views/report/index/child/ → views/report/index/widget/
views/report/energy/child/ → views/report/energy/widget/
views/profile/index/child/ → views/profile/index/widget/
```
> `report/room/` 没有 child 文件夹,不需要重命名。
---
## 四、Widget Block 改造标准方案
### 4.1 方案选择
经过对比三种候选方案(A - Props 下传、B - 数据对象下传、C - 子组件直连 Cubit),确定采用 **A + C 混合方案**:
> **核心原则:区块级 BlocBuilder + 数据提取下传**
| 层级 | 职责 | 方式 |
|------|------|------|
| **View 层** | 提供 Cubit,布局 UI 区块 | `BlocProvider(create: ...)` |
| **View Body 层** | 每个 UI 区块用 `BlocBuilder` 读取 state,配合 `buildWhen` | `BlocBuilder<Cubit, State>(buildWhen: ..., builder: ...)` |
| **Widget 叶子节点** | 接收具体类型参数,纯 UI 渲染 | `StatelessWidget` + 构造函数参数 |
### 4.2 为什么选择此方案
| 优势 | 说明 |
|------|------|
| **精细化重建控制** | `buildWhen` 确保只重建有变化的区块,避免全页面刷新 |
| **叶子组件零依赖** | 不依赖 `flutter_bloc`、不依赖 `BuildContext`,可在任何项目复用 |
| **单元测试极简** | 测试叶子组件只需传入参数,不需要 mock 任何框架 |
| **状态可追溯** | 所有变更通过 `emit`,配合 bloc DevTools 可追踪完整状态流转 |
| **职责清晰** | View 负责布局+分发,Widget 负责渲染,Cubit 负责业务逻辑 |
### 4.3 代码模板
#### Cubit + State
```dart
// cubit/xxx_state.dart
import 'package:equatable/equatable.dart';
class XxxState extends Equatable {
final String title;
final int count;
const XxxState({
this.title = '',
this.count = 0,
});
XxxState copyWith({
String? title,
int? count,
}) {
return XxxState(
title: title ?? this.title,
count: count ?? this.count,
);
}
@override
List<Object?> get props => [title, count];
}
```
```dart
// cubit/xxx_cubit.dart
import 'package:flutter_bloc/flutter_bloc.dart';
import 'xxx_state.dart';
class XxxCubit extends Cubit<XxxState> {
XxxCubit() : super(const XxxState());
void loadData() {
emit(state.copyWith(title: 'Hello', count: 42));
}
void updateCount(int newCount) {
emit(state.copyWith(count: newCount));
}
}
```
#### View(StatelessWidget + BlocProvider)
```dart
// xxx_view.dart
@RoutePage()
class XxxView extends StatelessWidget {
const XxxView({super.key});
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => XxxCubit(),
child: const _XxxBody(),
);
}
}
class _XxxBody extends StatelessWidget {
const _XxxBody();
@override
Widget build(BuildContext context) {
return Scaffold(
body: SingleChildScrollView(
child: Column(
children: [
// 区块1 - 只在 count 变化时重建
BlocBuilder<XxxCubit, XxxState>(
buildWhen: (prev, curr) => prev.count != curr.count,
builder: (context, state) {
return ChildBlockA(count: state.count);
},
),
// 区块2 - 只在 title 变化时重建
BlocBuilder<XxxCubit, XxxState>(
buildWhen: (prev, curr) => prev.title != curr.title,
builder: (context, state) {
return ChildBlockB(
title: state.title,
onTap: () => context.read<XxxCubit>().updateCount(100),
);
},
),
],
),
),
);
}
}
```
#### 叶子 Widget(StatelessWidget + 具体参数)
```dart
// widget/child_block_a.dart
class ChildBlockA extends StatelessWidget {
final int count;
const ChildBlockA({super.key, required this.count});
@override
Widget build(BuildContext context) {
return Text('当前计数: $count');
}
}
```
### 4.4 有内部交互状态的 Widget Block 处理
部分 Widget Block 有内部交互状态(如开关、Tab 切换、选中项),改造方案:
| 交互类型 | 处理方式 | 示例 |
|---------|---------|------|
| **纯 UI 状态**(不影响其他组件)| 保留为局部 `StatefulWidget` | 密码可见性切换 |
| **影响其他组件** | 提升到 Cubit State | 楼层/类型筛选、房间选中 |
| **混合状态** | 拆分:局部状态保留,共享状态提升到 Cubit | 设备开关(局部)+ 总电源(Cubit) |
```dart
// 示例:RoomManagementBlock 的筛选状态提升到 Cubit
class ServiceIndexState extends Equatable {
final String? selectedFloor;
final String? selectedType;
final String? selectedRoom;
final String searchText;
final List<Map<String, dynamic>> rooms;
// ...
}
class ServiceIndexCubit extends Cubit<ServiceIndexState> {
void selectFloor(String? floor) {
emit(state.copyWith(selectedFloor: floor));
}
void selectType(String? type) {
emit(state.copyWith(selectedType: type));
}
void searchRoom(String text) {
emit(state.copyWith(searchText: text));
}
}
```
---
## 五、各模块迁移要点
### 5.1 login(登录)
- **改造难度**:⭐⭐⭐ 中等
- **涉及 AuthBloc 交互**:登录逻辑委托给 `AuthBloc`,`LoginCubit` 管理表单状态
- **LoginState 字段**:`rememberPassword`、`isLoading`
- **widget 子组件**:保持现有纯 UI 设计,不需要大改
### 5.2 home/index(告警首页)
- **改造难度**:⭐⭐⭐⭐ 较高
- **子组件最多**:5 个 widget block 需要改造
- **HomeIndexState 字段**:~20 个字段(告警数、温度、电压、设备列表等)
- **关键**:配合 `buildWhen` 控制各区块的细粒度重建
### 5.3 home/abnormal(告警详情)
- **改造难度**:⭐⭐ 低
- **子组件已有数据对象**:`AlarmInfo` 数据对象可迁移到 State 中
- **子组件已为 StatelessWidget**:改动最小
### 5.4 home/device(设备详情)
- **改造难度**:⭐⭐ 低
- **类似 abnormal**:`DeviceInfo` 数据对象迁移到 State
- **PowerCharBlock**:内部有 Tab 切换状态,保留为局部 StatefulWidget
### 5.5 service/index(服务首页)
- **改造难度**:⭐⭐⭐⭐ 较高
- **RoomManagementBlock**:复杂的筛选/搜索/选中逻辑需提升到 Cubit
- **性能关键**:房间数量多时,筛选逻辑应避免全量重建
### 5.6 service/device(设备控制)
- **改造难度**:⭐⭐⭐ 中等
- **子组件有交互状态**:温度调节、模式选择、风速选择需提升到 Cubit
- **开关状态**:可保留局部或提升到 Cubit
### 5.7 service/room(客房详情)
- **改造难度**:⭐⭐⭐ 中等
- **DeviceControlBlock**:电源开关状态需提升到 Cubit
- **导航回调**:空调点击导航跳转需通过回调或 Cubit 方法
### 5.8 report/index(看板首页)
- **改造难度**:⭐⭐⭐ 中等
- **当前问题**:6 个子组件各创建独立 Controller,改为共享 Cubit
- **子组件改为 StatelessWidget**:通过 BlocBuilder 接收数据
### 5.9 report/energy(能耗详情)
- **改造难度**:⭐ 低
- **当前是静态类**:数据直接迁移到 State,Cubit 支持动态更新
- **子组件已为 StatelessWidget**:改动最小
### 5.10 report/room(客房报表)
- **改造难度**:⭐ 最低
- **Controller 是空类**:几乎无改动
### 5.11 profile/index(个人中心)
- **改造难度**:⭐⭐ 低
- **Controller 简单**:菜单列表 + 导航回调
- **Menu item 的 onTap**:改为通过 Cubit 方法触发
---
## 六、迁移顺序
| 序号 | 模块 | 难度 | 原因 |
|------|------|------|------|
| 1 | `report/room` | ⭐ | Controller 空类,可作为练手 |
| 2 | `report/energy` | ⭐ | 静态类,结构最简单 |
| 3 | `profile/index` | ⭐⭐ | Controller 简单,影响范围小 |
| 4 | `home/abnormal` | ⭐⭐ | 子组件已是 StatelessWidget |
| 5 | `home/device` | ⭐⭐ | 类似 abnormal |
| 6 | `report/index` | ⭐⭐⭐ | 需要将 6 个独立 Controller 合并为 1 个 Cubit |
| 7 | `service/device` | ⭐⭐⭐ | 需要处理交互状态提升 |
| 8 | `service/room` | ⭐⭐⭐ | 需要处理开关状态提升 |
| 9 | `service/index` | ⭐⭐⭐⭐ | RoomManagementBlock 复杂筛选逻辑 |
| 10 | `home/index` | ⭐⭐⭐⭐ | 子组件最多,State 字段最多 |
| 11 | `login` | ⭐⭐⭐ | 需要配合 AuthBloc |
---
## 七、风险点与注意事项
### 7.1 import 路径变更
- 所有 `child/` → `widget/` 会导致 import 路径全变
- 建议:迁移一个模块后立即编译,避免大量报错
### 7.2 auto_route 生成文件
- `app_router.gr.dart` 中的 import 需同步更新
- 迁移完成后重新运行 `dart run build_runner build`
### 7.3 全局 BlocProvider 注册
`main.dart` 中当前只注册 AuthBloc:
```dart
MultiBlocProvider(
providers: [
BlocProvider<AuthBloc>.value(value: _authBloc),
],
)
```
**保持不变**。页面级 Cubit 在各页面内部通过 `BlocProvider(create: ...)` 创建,遵循"谁使用谁创建"原则。
### 7.4 不迁移的部分
| 部分 | 说明 |
|------|------|
| `routes/` | auto_route 配置不改 |
| `http/` | 网络层不变 |
| `services/` | 服务层不变 |
| `repositories/` | 仓储层不变 |
| `models/` | 数据模型不变 |
| `utils/` | 工具类不变 |
| `assets/` | 资源不变 |
| `layout/default_view.dart` | Tab 布局不变 |
| `search/index_view.dart` | 搜索页不变 |
---
## 八、迁移后的架构全景
```
┌──────────────────────────────────────────────────┐
│ main.dart │
│ ┌────────────────────────────────────────────┐ │
│ │ MultiBlocProvider([ │ │
│ │ BlocProvider<AuthBloc> ← 全局 Bloc │ │
│ │ ]) │ │
│ └────────────────────────────────────────────┘ │
├──────────────────────────────────────────────────┤
│ Views │
│ ┌──────────────────────────────────────────┐ │
│ │ BlocProvider(create: PageCubit) │ │
│ │ └── BlocBuilder<Cubit, State> │ │
│ │ ├── buildWhen: 区块1条件 │ │
│ │ │ └── ChildWidgetA(props) │ │
│ │ ├── BlocBuilder: 区块2条件 │ │
│ │ │ └── ChildWidgetB(props) │ │
│ │ └── BlocBuilder: 区块3条件 │ │
│ │ └── ChildWidgetC(props) │ │
│ └──────────────────────────────────────────┘ │
├──────────────────────────────────────────────────┤
│ Cubits (页面级) Blocs (全局) │
│ - LoginCubit - AuthBloc │
│ - HomeIndexCubit │
│ - AbnormalCubit │
│ - DeviceCubit │
│ - ServiceIndexCubit │
│ - ServiceDeviceCubit │
│ - ServiceRoomCubit │
│ - ReportIndexCubit │
│ - EnergyCubit │
│ - RoomReportCubit │
│ - ProfileCubit │
├──────────────────────────────────────────────────┤
│ Services → Repositories → HTTP (Dio) │
└──────────────────────────────────────────────────┘
```
\ No newline at end of file
plugins {
id("com.android.application")
id("kotlin-android")
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
id("dev.flutter.flutter-gradle-plugin")
}
android {
namespace = "com.example.smart_hotel_app"
compileSdk = flutter.compileSdkVersion
ndkVersion = flutter.ndkVersion
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_17.toString()
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId = "com.example.smart_hotel_app"
// You can update the following values to match your application needs.
// For more information, see: https://flutter.dev/to/review-gradle-config.
minSdk = flutter.minSdkVersion
targetSdk = flutter.targetSdkVersion
versionCode = flutter.versionCode
versionName = flutter.versionName
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig = signingConfigs.getByName("debug")
}
}
}
flutter {
source = "../.."
}
allprojects {
repositories {
google()
mavenCentral()
}
}
val newBuildDir: Directory =
rootProject.layout.buildDirectory
.dir("../../build")
.get()
rootProject.layout.buildDirectory.value(newBuildDir)
subprojects {
val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
project.layout.buildDirectory.value(newSubprojectBuildDir)
}
subprojects {
project.evaluationDependsOn(":app")
}
tasks.register<Delete>("clean") {
delete(rootProject.layout.buildDirectory)
}
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")
......@@ -21,6 +21,6 @@
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>12.0</string>
<string>13.0</string>
</dict>
</plist>
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "Generated.xcconfig"
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
FLTEnableImpeller=NO
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "Generated.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, '12.0'
# platform :ios, '13.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
......
......@@ -31,12 +31,12 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/path_provider_foundation/darwin"
SPEC CHECKSUMS:
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
flutter_secure_storage: 1ed9476fba7e7a782b22888f956cce43e2c62f13
fluttertoast: eaafdac812d1d69931004e881c87a8643b1c9111
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
Toast: 1f5ea13423a1e6674c4abdac5be53587ae481c4e
PODFILE CHECKSUM: 819463e6a0290f5a72f145ba7cde16e8b6ef0796
PODFILE CHECKSUM: 4f1c12611da7338d21589c0b2ecd6bd20b109694
COCOAPODS: 1.16.2
......@@ -11,11 +11,11 @@
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 */; };
85FB26E09E11FD856BAC14EF /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 64F99862400D2E97D21D32BA /* Pods_Runner.framework */; };
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 */; };
B16013532B8117FAA4D766BC /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 372D463B9D2BEFA7208C7A80 /* Pods_RunnerTests.framework */; };
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 */
......@@ -42,22 +42,18 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
051791B8C0AF029FFF52D525 /* 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>"; };
115D368338F55E1140194E52 /* 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>"; };
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>"; };
14E96D81C30071C3282FF74A /* 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>"; };
23479F369C0989C061CBB4DB /* 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>"; };
2713D8DF627313722951A2FF /* 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>"; };
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; };
372D463B9D2BEFA7208C7A80 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
64F99862400D2E97D21D32BA /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
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>"; };
8719FE19706BE0858D9904BA /* 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>"; };
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; };
......@@ -65,6 +61,10 @@
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 */
......@@ -72,7 +72,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
B16013532B8117FAA4D766BC /* Pods_RunnerTests.framework in Frameworks */,
D5C802D83219AEE785935A60 /* Pods_RunnerTests.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
......@@ -80,33 +80,33 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
85FB26E09E11FD856BAC14EF /* Pods_Runner.framework in Frameworks */,
EE639139734B4C96126F3B83 /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
331C8082294A63A400263BE5 /* RunnerTests */ = {
0F60C9CEFB6465B7793E8B4D /* Pods */ = {
isa = PBXGroup;
children = (
331C807B294A618700263BE5 /* RunnerTests.swift */,
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 */,
);
path = RunnerTests;
name = Pods;
path = Pods;
sourceTree = "<group>";
};
66E8526D5BD25141C068B8D6 /* Pods */ = {
331C8082294A63A400263BE5 /* RunnerTests */ = {
isa = PBXGroup;
children = (
115D368338F55E1140194E52 /* Pods-Runner.debug.xcconfig */,
23479F369C0989C061CBB4DB /* Pods-Runner.release.xcconfig */,
14E96D81C30071C3282FF74A /* Pods-Runner.profile.xcconfig */,
051791B8C0AF029FFF52D525 /* Pods-RunnerTests.debug.xcconfig */,
8719FE19706BE0858D9904BA /* Pods-RunnerTests.release.xcconfig */,
2713D8DF627313722951A2FF /* Pods-RunnerTests.profile.xcconfig */,
331C807B294A618700263BE5 /* RunnerTests.swift */,
);
name = Pods;
path = Pods;
path = RunnerTests;
sourceTree = "<group>";
};
9740EEB11CF90186004384FC /* Flutter */ = {
......@@ -127,8 +127,8 @@
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
331C8082294A63A400263BE5 /* RunnerTests */,
66E8526D5BD25141C068B8D6 /* Pods */,
BA29CEDA4CC471BE89A7C6C3 /* Frameworks */,
0F60C9CEFB6465B7793E8B4D /* Pods */,
D9CC5E926AE246E7CFB0524F /* Frameworks */,
);
sourceTree = "<group>";
};
......@@ -156,11 +156,11 @@
path = Runner;
sourceTree = "<group>";
};
BA29CEDA4CC471BE89A7C6C3 /* Frameworks */ = {
D9CC5E926AE246E7CFB0524F /* Frameworks */ = {
isa = PBXGroup;
children = (
64F99862400D2E97D21D32BA /* Pods_Runner.framework */,
372D463B9D2BEFA7208C7A80 /* Pods_RunnerTests.framework */,
D781F8E4B0863CE311979134 /* Pods_Runner.framework */,
C0943F033B4C937D3DE2DF11 /* Pods_RunnerTests.framework */,
);
name = Frameworks;
sourceTree = "<group>";
......@@ -172,7 +172,7 @@
isa = PBXNativeTarget;
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
buildPhases = (
E989984989FB47651406D579 /* [CP] Check Pods Manifest.lock */,
35602183E27ADC71E335DD69 /* [CP] Check Pods Manifest.lock */,
331C807D294A63A400263BE5 /* Sources */,
331C807F294A63A400263BE5 /* Resources */,
09D5A5EC9CBEF82FCCB7F96A /* Frameworks */,
......@@ -191,14 +191,14 @@
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
3FDD06C700FE6EC67D855A11 /* [CP] Check Pods Manifest.lock */,
7C459C0FBCC1A576B4204836 /* [CP] Check Pods Manifest.lock */,
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
DAC86BA610CD20A3D7947CF9 /* [CP] Embed Pods Frameworks */,
56DDF7F4C644F7A96944CE16 /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
......@@ -216,7 +216,7 @@
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = YES;
LastUpgradeCheck = 1430;
LastUpgradeCheck = 1510;
ORGANIZATIONNAME = "";
TargetAttributes = {
331C8080294A63A400263BE5 = {
......@@ -270,23 +270,7 @@
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
);
name = "Thin Binary";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
};
3FDD06C700FE6EC67D855A11 /* [CP] Check Pods Manifest.lock */ = {
35602183E27ADC71E335DD69 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
......@@ -301,29 +285,30 @@
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
"$(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;
};
9740EEB61CF901F6004384FC /* Run Script */ = {
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
);
name = "Run Script";
name = "Thin Binary";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
};
DAC86BA610CD20A3D7947CF9 /* [CP] Embed Pods Frameworks */ = {
56DDF7F4C644F7A96944CE16 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
......@@ -340,7 +325,7 @@
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
E989984989FB47651406D579 /* [CP] Check Pods Manifest.lock */ = {
7C459C0FBCC1A576B4204836 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
......@@ -355,13 +340,28 @@
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt",
"$(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 */
......@@ -453,7 +453,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
......@@ -485,7 +485,7 @@
};
331C8088294A63A400263BE5 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 051791B8C0AF029FFF52D525 /* Pods-RunnerTests.debug.xcconfig */;
baseConfigurationReference = FB75E68007A0827C8670AC20 /* Pods-RunnerTests.debug.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
......@@ -503,7 +503,7 @@
};
331C8089294A63A400263BE5 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 8719FE19706BE0858D9904BA /* Pods-RunnerTests.release.xcconfig */;
baseConfigurationReference = B5D9CB5DEF01982D88B62D1D /* Pods-RunnerTests.release.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
......@@ -519,7 +519,7 @@
};
331C808A294A63A400263BE5 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 2713D8DF627313722951A2FF /* Pods-RunnerTests.profile.xcconfig */;
baseConfigurationReference = 268EEEECBBE3E978DEA18EED /* Pods-RunnerTests.profile.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
......@@ -580,7 +580,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
......@@ -629,7 +629,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
......
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1430"
LastUpgradeVersion = "1510"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
......@@ -26,6 +26,7 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion>
<BuildableReference
......@@ -54,11 +55,13 @@
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">
......
import UIKit
import Flutter
@UIApplicationMain
@main
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
......
......@@ -101,10 +101,10 @@ class _MyAppState extends State<MyApp> {
backgroundColor: Colors.red,
),
);
_appRouter.replaceAll([const LoginRoute()]);
_appRouter.replaceAll([LoginRoute()]);
}
if (state is AuthLoggedOut) {
_appRouter.replaceAll([const LoginRoute()]);
_appRouter.replaceAll([LoginRoute()]);
}
},
),
......
......@@ -25,5 +25,9 @@ class AppRouter extends $AppRouter {
AutoRoute(page: ServiceRoomDetailRoute.page),
AutoRoute(page: ReportEnergyDetailRoute.page),
AutoRoute(page: ReportRoomDetailRoute.page),
AutoRoute(page: ServiceDeviceDetailRoute.page),
AutoRoute(page: InspectionDetailRoute.page),
AutoRoute(page: InspectionDeviceRoute.page),
AutoRoute(page: InspectionHistoryRoute.page),
];
}
......@@ -8,107 +8,155 @@
// coverage:ignore-file
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:auto_route/auto_route.dart' as _i13;
import 'package:flutter/material.dart' as _i14;
import 'package:auto_route/auto_route.dart' as _i17;
import 'package:flutter/material.dart' as _i18;
import 'package:smart_hotel_app/views/home/abnormal/abnormal_detail_view.dart'
as _i1;
import 'package:smart_hotel_app/views/home/device/device_detail_view.dart'
as _i3;
import 'package:smart_hotel_app/views/home/index/index_view.dart' as _i4;
import 'package:smart_hotel_app/views/home/inspection/inspection_detail_view.dart'
as _i5;
import 'package:smart_hotel_app/views/home/inspection_device/inspection_device_view.dart'
as _i6;
import 'package:smart_hotel_app/views/home/inspection_history/inspection_history_view.dart'
as _i7;
import 'package:smart_hotel_app/views/layout/default_view.dart' as _i2;
import 'package:smart_hotel_app/views/login/login_view.dart' as _i5;
import 'package:smart_hotel_app/views/profile/index_view.dart' as _i6;
import 'package:smart_hotel_app/views/login/login_view.dart' as _i8;
import 'package:smart_hotel_app/views/profile/index/index_view.dart' as _i9;
import 'package:smart_hotel_app/views/report/energy/energy_detail_view.dart'
as _i7;
import 'package:smart_hotel_app/views/report/index/index_view.dart' as _i8;
import 'package:smart_hotel_app/views/report/room/room_detail.dart' as _i9;
import 'package:smart_hotel_app/views/search/index_view.dart' as _i10;
import 'package:smart_hotel_app/views/service/index/index_view.dart' as _i11;
as _i10;
import 'package:smart_hotel_app/views/report/index/index_view.dart' as _i11;
import 'package:smart_hotel_app/views/report/room/room_detail.dart' as _i12;
import 'package:smart_hotel_app/views/search/index_view.dart' as _i13;
import 'package:smart_hotel_app/views/service/device/device_detail_view.dart'
as _i14;
import 'package:smart_hotel_app/views/service/index/index_view.dart' as _i15;
import 'package:smart_hotel_app/views/service/room/room_detail_view.dart'
as _i12;
as _i16;
abstract class $AppRouter extends _i13.RootStackRouter {
abstract class $AppRouter extends _i17.RootStackRouter {
$AppRouter({super.navigatorKey});
@override
final Map<String, _i13.PageFactory> pagesMap = {
final Map<String, _i17.PageFactory> pagesMap = {
AbnormalDetailRoute.name: (routeData) {
return _i13.AutoRoutePage<dynamic>(
return _i17.AutoRoutePage<dynamic>(
routeData: routeData,
child: const _i1.AbnormalDetailView(),
);
},
DefaultLayoutRoute.name: (routeData) {
return _i13.AutoRoutePage<dynamic>(
return _i17.AutoRoutePage<dynamic>(
routeData: routeData,
child: const _i2.DefaultLayoutView(),
);
},
DeviceDetailRoute.name: (routeData) {
return _i13.AutoRoutePage<dynamic>(
return _i17.AutoRoutePage<dynamic>(
routeData: routeData,
child: const _i3.DeviceDetailView(),
);
},
HomeRoute.name: (routeData) {
return _i13.AutoRoutePage<dynamic>(
return _i17.AutoRoutePage<dynamic>(
routeData: routeData,
child: const _i4.HomeView(),
);
},
InspectionDetailRoute.name: (routeData) {
return _i17.AutoRoutePage<dynamic>(
routeData: routeData,
child: const _i5.InspectionDetailView(),
);
},
InspectionDeviceRoute.name: (routeData) {
final pathParams = routeData.inheritedPathParams;
final args = routeData.argsAs<InspectionDeviceRouteArgs>(
orElse: () => InspectionDeviceRouteArgs(
deviceId: pathParams.getString('deviceId')));
return _i17.AutoRoutePage<dynamic>(
routeData: routeData,
child: _i6.InspectionDeviceView(
key: args.key,
deviceId: args.deviceId,
),
);
},
InspectionHistoryRoute.name: (routeData) {
final pathParams = routeData.inheritedPathParams;
final args = routeData.argsAs<InspectionHistoryRouteArgs>(
orElse: () => InspectionHistoryRouteArgs(
deviceId: pathParams.getString('deviceId')));
return _i17.AutoRoutePage<dynamic>(
routeData: routeData,
child: _i7.InspectionHistoryView(
key: args.key,
deviceId: args.deviceId,
),
);
},
LoginRoute.name: (routeData) {
return _i13.AutoRoutePage<dynamic>(
final args = routeData.argsAs<LoginRouteArgs>(
orElse: () => const LoginRouteArgs());
return _i17.AutoRoutePage<dynamic>(
routeData: routeData,
child: const _i5.LoginView(),
child: _i8.LoginView(key: args.key),
);
},
ProfileRoute.name: (routeData) {
return _i13.AutoRoutePage<dynamic>(
return _i17.AutoRoutePage<dynamic>(
routeData: routeData,
child: const _i6.ProfileView(),
child: const _i9.ProfileView(),
);
},
ReportEnergyDetailRoute.name: (routeData) {
return _i13.AutoRoutePage<dynamic>(
return _i17.AutoRoutePage<dynamic>(
routeData: routeData,
child: const _i7.ReportEnergyDetailView(),
child: const _i10.ReportEnergyDetailView(),
);
},
ReportIndexRoute.name: (routeData) {
return _i13.AutoRoutePage<dynamic>(
return _i17.AutoRoutePage<dynamic>(
routeData: routeData,
child: const _i8.ReportIndexView(),
child: const _i11.ReportIndexView(),
);
},
ReportRoomDetailRoute.name: (routeData) {
return _i13.AutoRoutePage<dynamic>(
return _i17.AutoRoutePage<dynamic>(
routeData: routeData,
child: const _i9.ReportRoomDetailView(),
child: const _i12.ReportRoomDetailView(),
);
},
SearchRoute.name: (routeData) {
final args = routeData.argsAs<SearchRouteArgs>(
orElse: () => const SearchRouteArgs());
return _i13.AutoRoutePage<dynamic>(
return _i17.AutoRoutePage<dynamic>(
routeData: routeData,
child: _i10.SearchView(
child: _i13.SearchView(
key: args.key,
searchKey: args.searchKey,
),
);
},
ServiceDeviceDetailRoute.name: (routeData) {
return _i17.AutoRoutePage<dynamic>(
routeData: routeData,
child: const _i14.ServiceDeviceDetailView(),
);
},
ServiceIndexRoute.name: (routeData) {
return _i13.AutoRoutePage<dynamic>(
return _i17.AutoRoutePage<dynamic>(
routeData: routeData,
child: const _i11.ServiceIndexView(),
child: const _i15.ServiceIndexView(),
);
},
ServiceRoomDetailRoute.name: (routeData) {
final args = routeData.argsAs<ServiceRoomDetailRouteArgs>(
orElse: () => const ServiceRoomDetailRouteArgs());
return _i13.AutoRoutePage<dynamic>(
return _i17.AutoRoutePage<dynamic>(
routeData: routeData,
child: _i12.ServiceRoomDetailView(
child: _i16.ServiceRoomDetailView(
key: args.key,
roomNumber: args.roomNumber,
),
......@@ -119,8 +167,8 @@ abstract class $AppRouter extends _i13.RootStackRouter {
/// generated route for
/// [_i1.AbnormalDetailView]
class AbnormalDetailRoute extends _i13.PageRouteInfo<void> {
const AbnormalDetailRoute({List<_i13.PageRouteInfo>? children})
class AbnormalDetailRoute extends _i17.PageRouteInfo<void> {
const AbnormalDetailRoute({List<_i17.PageRouteInfo>? children})
: super(
AbnormalDetailRoute.name,
initialChildren: children,
......@@ -128,13 +176,13 @@ class AbnormalDetailRoute extends _i13.PageRouteInfo<void> {
static const String name = 'AbnormalDetailRoute';
static const _i13.PageInfo<void> page = _i13.PageInfo<void>(name);
static const _i17.PageInfo<void> page = _i17.PageInfo<void>(name);
}
/// generated route for
/// [_i2.DefaultLayoutView]
class DefaultLayoutRoute extends _i13.PageRouteInfo<void> {
const DefaultLayoutRoute({List<_i13.PageRouteInfo>? children})
class DefaultLayoutRoute extends _i17.PageRouteInfo<void> {
const DefaultLayoutRoute({List<_i17.PageRouteInfo>? children})
: super(
DefaultLayoutRoute.name,
initialChildren: children,
......@@ -142,13 +190,13 @@ class DefaultLayoutRoute extends _i13.PageRouteInfo<void> {
static const String name = 'DefaultLayoutRoute';
static const _i13.PageInfo<void> page = _i13.PageInfo<void>(name);
static const _i17.PageInfo<void> page = _i17.PageInfo<void>(name);
}
/// generated route for
/// [_i3.DeviceDetailView]
class DeviceDetailRoute extends _i13.PageRouteInfo<void> {
const DeviceDetailRoute({List<_i13.PageRouteInfo>? children})
class DeviceDetailRoute extends _i17.PageRouteInfo<void> {
const DeviceDetailRoute({List<_i17.PageRouteInfo>? children})
: super(
DeviceDetailRoute.name,
initialChildren: children,
......@@ -156,13 +204,13 @@ class DeviceDetailRoute extends _i13.PageRouteInfo<void> {
static const String name = 'DeviceDetailRoute';
static const _i13.PageInfo<void> page = _i13.PageInfo<void>(name);
static const _i17.PageInfo<void> page = _i17.PageInfo<void>(name);
}
/// generated route for
/// [_i4.HomeView]
class HomeRoute extends _i13.PageRouteInfo<void> {
const HomeRoute({List<_i13.PageRouteInfo>? children})
class HomeRoute extends _i17.PageRouteInfo<void> {
const HomeRoute({List<_i17.PageRouteInfo>? children})
: super(
HomeRoute.name,
initialChildren: children,
......@@ -170,27 +218,136 @@ class HomeRoute extends _i13.PageRouteInfo<void> {
static const String name = 'HomeRoute';
static const _i13.PageInfo<void> page = _i13.PageInfo<void>(name);
static const _i17.PageInfo<void> page = _i17.PageInfo<void>(name);
}
/// generated route for
/// [_i5.LoginView]
class LoginRoute extends _i13.PageRouteInfo<void> {
const LoginRoute({List<_i13.PageRouteInfo>? children})
/// [_i5.InspectionDetailView]
class InspectionDetailRoute extends _i17.PageRouteInfo<void> {
const InspectionDetailRoute({List<_i17.PageRouteInfo>? children})
: super(
InspectionDetailRoute.name,
initialChildren: children,
);
static const String name = 'InspectionDetailRoute';
static const _i17.PageInfo<void> page = _i17.PageInfo<void>(name);
}
/// generated route for
/// [_i6.InspectionDeviceView]
class InspectionDeviceRoute
extends _i17.PageRouteInfo<InspectionDeviceRouteArgs> {
InspectionDeviceRoute({
_i18.Key? key,
required String deviceId,
List<_i17.PageRouteInfo>? children,
}) : super(
InspectionDeviceRoute.name,
args: InspectionDeviceRouteArgs(
key: key,
deviceId: deviceId,
),
rawPathParams: {'deviceId': deviceId},
initialChildren: children,
);
static const String name = 'InspectionDeviceRoute';
static const _i17.PageInfo<InspectionDeviceRouteArgs> page =
_i17.PageInfo<InspectionDeviceRouteArgs>(name);
}
class InspectionDeviceRouteArgs {
const InspectionDeviceRouteArgs({
this.key,
required this.deviceId,
});
final _i18.Key? key;
final String deviceId;
@override
String toString() {
return 'InspectionDeviceRouteArgs{key: $key, deviceId: $deviceId}';
}
}
/// generated route for
/// [_i7.InspectionHistoryView]
class InspectionHistoryRoute
extends _i17.PageRouteInfo<InspectionHistoryRouteArgs> {
InspectionHistoryRoute({
_i18.Key? key,
required String deviceId,
List<_i17.PageRouteInfo>? children,
}) : super(
InspectionHistoryRoute.name,
args: InspectionHistoryRouteArgs(
key: key,
deviceId: deviceId,
),
rawPathParams: {'deviceId': deviceId},
initialChildren: children,
);
static const String name = 'InspectionHistoryRoute';
static const _i17.PageInfo<InspectionHistoryRouteArgs> page =
_i17.PageInfo<InspectionHistoryRouteArgs>(name);
}
class InspectionHistoryRouteArgs {
const InspectionHistoryRouteArgs({
this.key,
required this.deviceId,
});
final _i18.Key? key;
final String deviceId;
@override
String toString() {
return 'InspectionHistoryRouteArgs{key: $key, deviceId: $deviceId}';
}
}
/// generated route for
/// [_i8.LoginView]
class LoginRoute extends _i17.PageRouteInfo<LoginRouteArgs> {
LoginRoute({
_i18.Key? key,
List<_i17.PageRouteInfo>? children,
}) : super(
LoginRoute.name,
args: LoginRouteArgs(key: key),
initialChildren: children,
);
static const String name = 'LoginRoute';
static const _i13.PageInfo<void> page = _i13.PageInfo<void>(name);
static const _i17.PageInfo<LoginRouteArgs> page =
_i17.PageInfo<LoginRouteArgs>(name);
}
class LoginRouteArgs {
const LoginRouteArgs({this.key});
final _i18.Key? key;
@override
String toString() {
return 'LoginRouteArgs{key: $key}';
}
}
/// generated route for
/// [_i6.ProfileView]
class ProfileRoute extends _i13.PageRouteInfo<void> {
const ProfileRoute({List<_i13.PageRouteInfo>? children})
/// [_i9.ProfileView]
class ProfileRoute extends _i17.PageRouteInfo<void> {
const ProfileRoute({List<_i17.PageRouteInfo>? children})
: super(
ProfileRoute.name,
initialChildren: children,
......@@ -198,13 +355,13 @@ class ProfileRoute extends _i13.PageRouteInfo<void> {
static const String name = 'ProfileRoute';
static const _i13.PageInfo<void> page = _i13.PageInfo<void>(name);
static const _i17.PageInfo<void> page = _i17.PageInfo<void>(name);
}
/// generated route for
/// [_i7.ReportEnergyDetailView]
class ReportEnergyDetailRoute extends _i13.PageRouteInfo<void> {
const ReportEnergyDetailRoute({List<_i13.PageRouteInfo>? children})
/// [_i10.ReportEnergyDetailView]
class ReportEnergyDetailRoute extends _i17.PageRouteInfo<void> {
const ReportEnergyDetailRoute({List<_i17.PageRouteInfo>? children})
: super(
ReportEnergyDetailRoute.name,
initialChildren: children,
......@@ -212,13 +369,13 @@ class ReportEnergyDetailRoute extends _i13.PageRouteInfo<void> {
static const String name = 'ReportEnergyDetailRoute';
static const _i13.PageInfo<void> page = _i13.PageInfo<void>(name);
static const _i17.PageInfo<void> page = _i17.PageInfo<void>(name);
}
/// generated route for
/// [_i8.ReportIndexView]
class ReportIndexRoute extends _i13.PageRouteInfo<void> {
const ReportIndexRoute({List<_i13.PageRouteInfo>? children})
/// [_i11.ReportIndexView]
class ReportIndexRoute extends _i17.PageRouteInfo<void> {
const ReportIndexRoute({List<_i17.PageRouteInfo>? children})
: super(
ReportIndexRoute.name,
initialChildren: children,
......@@ -226,13 +383,13 @@ class ReportIndexRoute extends _i13.PageRouteInfo<void> {
static const String name = 'ReportIndexRoute';
static const _i13.PageInfo<void> page = _i13.PageInfo<void>(name);
static const _i17.PageInfo<void> page = _i17.PageInfo<void>(name);
}
/// generated route for
/// [_i9.ReportRoomDetailView]
class ReportRoomDetailRoute extends _i13.PageRouteInfo<void> {
const ReportRoomDetailRoute({List<_i13.PageRouteInfo>? children})
/// [_i12.ReportRoomDetailView]
class ReportRoomDetailRoute extends _i17.PageRouteInfo<void> {
const ReportRoomDetailRoute({List<_i17.PageRouteInfo>? children})
: super(
ReportRoomDetailRoute.name,
initialChildren: children,
......@@ -240,16 +397,16 @@ class ReportRoomDetailRoute extends _i13.PageRouteInfo<void> {
static const String name = 'ReportRoomDetailRoute';
static const _i13.PageInfo<void> page = _i13.PageInfo<void>(name);
static const _i17.PageInfo<void> page = _i17.PageInfo<void>(name);
}
/// generated route for
/// [_i10.SearchView]
class SearchRoute extends _i13.PageRouteInfo<SearchRouteArgs> {
/// [_i13.SearchView]
class SearchRoute extends _i17.PageRouteInfo<SearchRouteArgs> {
SearchRoute({
_i14.Key? key,
_i18.Key? key,
String? searchKey,
List<_i13.PageRouteInfo>? children,
List<_i17.PageRouteInfo>? children,
}) : super(
SearchRoute.name,
args: SearchRouteArgs(
......@@ -261,8 +418,8 @@ class SearchRoute extends _i13.PageRouteInfo<SearchRouteArgs> {
static const String name = 'SearchRoute';
static const _i13.PageInfo<SearchRouteArgs> page =
_i13.PageInfo<SearchRouteArgs>(name);
static const _i17.PageInfo<SearchRouteArgs> page =
_i17.PageInfo<SearchRouteArgs>(name);
}
class SearchRouteArgs {
......@@ -271,7 +428,7 @@ class SearchRouteArgs {
this.searchKey,
});
final _i14.Key? key;
final _i18.Key? key;
final String? searchKey;
......@@ -282,9 +439,23 @@ class SearchRouteArgs {
}
/// generated route for
/// [_i11.ServiceIndexView]
class ServiceIndexRoute extends _i13.PageRouteInfo<void> {
const ServiceIndexRoute({List<_i13.PageRouteInfo>? children})
/// [_i14.ServiceDeviceDetailView]
class ServiceDeviceDetailRoute extends _i17.PageRouteInfo<void> {
const ServiceDeviceDetailRoute({List<_i17.PageRouteInfo>? children})
: super(
ServiceDeviceDetailRoute.name,
initialChildren: children,
);
static const String name = 'ServiceDeviceDetailRoute';
static const _i17.PageInfo<void> page = _i17.PageInfo<void>(name);
}
/// generated route for
/// [_i15.ServiceIndexView]
class ServiceIndexRoute extends _i17.PageRouteInfo<void> {
const ServiceIndexRoute({List<_i17.PageRouteInfo>? children})
: super(
ServiceIndexRoute.name,
initialChildren: children,
......@@ -292,17 +463,17 @@ class ServiceIndexRoute extends _i13.PageRouteInfo<void> {
static const String name = 'ServiceIndexRoute';
static const _i13.PageInfo<void> page = _i13.PageInfo<void>(name);
static const _i17.PageInfo<void> page = _i17.PageInfo<void>(name);
}
/// generated route for
/// [_i12.ServiceRoomDetailView]
/// [_i16.ServiceRoomDetailView]
class ServiceRoomDetailRoute
extends _i13.PageRouteInfo<ServiceRoomDetailRouteArgs> {
extends _i17.PageRouteInfo<ServiceRoomDetailRouteArgs> {
ServiceRoomDetailRoute({
_i14.Key? key,
_i18.Key? key,
String? roomNumber,
List<_i13.PageRouteInfo>? children,
List<_i17.PageRouteInfo>? children,
}) : super(
ServiceRoomDetailRoute.name,
args: ServiceRoomDetailRouteArgs(
......@@ -314,8 +485,8 @@ class ServiceRoomDetailRoute
static const String name = 'ServiceRoomDetailRoute';
static const _i13.PageInfo<ServiceRoomDetailRouteArgs> page =
_i13.PageInfo<ServiceRoomDetailRouteArgs>(name);
static const _i17.PageInfo<ServiceRoomDetailRouteArgs> page =
_i17.PageInfo<ServiceRoomDetailRouteArgs>(name);
}
class ServiceRoomDetailRouteArgs {
......@@ -324,7 +495,7 @@ class ServiceRoomDetailRouteArgs {
this.roomNumber,
});
final _i14.Key? key;
final _i18.Key? key;
final String? roomNumber;
......
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:smart_hotel_app/views/home/abnormal/child/alarm_info_block.dart';
import 'package:smart_hotel_app/views/home/abnormal/child/temperature_block.dart';
import 'package:smart_hotel_app/views/home/abnormal/child/temperature_char_block.dart';
import 'package:smart_hotel_app/views/home/abnormal/controller.dart';
import 'package:smart_hotel_app/routes/app_router.gr.dart';
import 'package:smart_hotel_app/views/home/abnormal/cubit/abnormal_cubit.dart';
import 'package:smart_hotel_app/views/home/abnormal/cubit/abnormal_state.dart';
import 'package:smart_hotel_app/views/home/abnormal/widget/alarm_info_block.dart';
import 'package:smart_hotel_app/views/home/abnormal/widget/temperature_block.dart';
import 'package:smart_hotel_app/views/home/abnormal/widget/temperature_char_block.dart';
@RoutePage()
class AbnormalDetailView extends StatefulWidget {
class AbnormalDetailView extends StatelessWidget {
const AbnormalDetailView({super.key});
@override
State<AbnormalDetailView> createState() => _AbnormalDetailViewState();
}
class _AbnormalDetailViewState extends State<AbnormalDetailView> {
late final HomeAbnormalController _controller;
late final AlarmInfo _alarmInfo;
@override
void initState() {
super.initState();
_controller = HomeAbnormalController(context);
_alarmInfo = _controller.getAlarmInfo();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color.fromRGBO(242, 243, 245, 1),
appBar: AppBar(
backgroundColor: Colors.white,
elevation: 0,
leading: IconButton(
icon: Icon(Icons.arrow_back_ios, color: Color.fromRGBO(100, 116, 139, 1),),
onPressed: () {
context.popRoute();
},
),
title: Text(
'告警详情',
style: TextStyle(
color: Color.fromRGBO(10, 13, 20, 1),
fontSize: 32.sp,
fontWeight: FontWeight.w200,
return BlocProvider(
create: (_) => AbnormalCubit(),
child: Scaffold(
backgroundColor: const Color.fromRGBO(242, 243, 245, 1),
appBar: AppBar(
backgroundColor: Colors.white,
elevation: 0,
leading: IconButton(
icon: const Icon(Icons.arrow_back_ios,
color: Color.fromRGBO(100, 116, 139, 1)),
onPressed: () {
context.popRoute();
},
),
title: Text(
'告警详情',
style: TextStyle(
color: const Color.fromRGBO(10, 13, 20, 1),
fontSize: 32.sp,
fontWeight: FontWeight.w200,
),
),
centerTitle: true,
),
centerTitle: true,
),
body: SingleChildScrollView(
padding: EdgeInsets.symmetric(horizontal: 28.w),
child: Column(
children: [
TemperatureBlock(alarmInfo: _alarmInfo),
SizedBox(height: 10.h),
TemperatureCharBlock(alarmInfo: _alarmInfo),
SizedBox(height: 10.h),
AlarmInfoBlock(alarmInfo: _alarmInfo),
SizedBox(height: 10.h),
_buildBottomButtons(),
SizedBox(height: 30.h),
],
body: SingleChildScrollView(
padding: EdgeInsets.symmetric(horizontal: 28.w),
child: BlocBuilder<AbnormalCubit, AbnormalState>(
builder: (context, state) {
final cubit = context.read<AbnormalCubit>();
return Column(
children: [
TemperatureBlock(alarmInfo: state.alarmInfo),
SizedBox(height: 10.h),
TemperatureCharBlock(alarmInfo: state.alarmInfo),
SizedBox(height: 10.h),
AlarmInfoBlock(alarmInfo: state.alarmInfo),
SizedBox(height: 10.h),
_buildBottomButtons(context, cubit),
SizedBox(height: 30.h),
],
);
},
),
),
),
);
}
Widget _buildBottomButtons() {
Widget _buildBottomButtons(BuildContext context, AbnormalCubit cubit) {
return Container(
width: double.infinity,
padding: EdgeInsets.symmetric(vertical: 15.h),
......@@ -77,16 +74,16 @@ class _AbnormalDetailViewState extends State<AbnormalDetailView> {
width: double.infinity,
height: 88.h,
decoration: BoxDecoration(
color: Color.fromRGBO(73, 149, 234, 1),
color: const Color.fromRGBO(73, 149, 234, 1),
borderRadius: BorderRadius.circular(16.r),
),
child: TextButton(
onPressed: _controller.handleConfirm,
onPressed: () => cubit.handleConfirm(),
child: Text(
'确认处理',
style: TextStyle(
fontSize: 28.sp,
color: Color.fromRGBO(237, 245, 255, 1),
color: const Color.fromRGBO(237, 245, 255, 1),
),
),
),
......@@ -96,23 +93,29 @@ class _AbnormalDetailViewState extends State<AbnormalDetailView> {
width: double.infinity,
height: 88.h,
decoration: BoxDecoration(
color: Color.fromRGBO(100, 116, 139, 0.2),
color: const Color.fromRGBO(100, 116, 139, 0.2),
borderRadius: BorderRadius.circular(16.r),
),
child: TextButton(
onPressed: _controller.handleViewDevice,
onPressed: () {
context.pushRoute(const DeviceDetailRoute());
},
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'查看关联设备',
'设备控制',
style: TextStyle(
fontSize: 28.sp,
color: Color.fromRGBO(100, 116, 139, 1),
color: const Color.fromRGBO(100, 116, 139, 1),
),
),
SizedBox(width: 8.w),
Image.asset("lib/assets/icon/right.png", width: 24.w, height: 21.h,),
Image.asset(
"lib/assets/icon/right.png",
width: 24.w,
height: 21.h,
),
],
),
),
......
import 'package:fl_chart/fl_chart.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:smart_hotel_app/views/home/abnormal/cubit/abnormal_state.dart';
class AbnormalCubit extends Cubit<AbnormalState> {
AbnormalCubit() : super(AbnormalState(alarmInfo: _getAlarmInfo()));
static AlarmInfo _getAlarmInfo() {
return const AlarmInfo(
alarmType: '温度异常告警',
deviceInfo: 'A-301智能空开·客房301',
alarmLevel: '高警',
urgency: '紧急',
currentTemp: '85℃',
voltage: '22V',
current: '3.2A',
triggerTime: '2026-05-09 14:23:18',
threshold: '温度>80℃触发',
duration: '2小时37分钟',
peak: '89.2℃ (15:02)',
linkAction: '自动断电保护',
date: '5月9日',
temperatureSpots: [
FlSpot(0, 40),
FlSpot(4, 40),
FlSpot(8, 15),
FlSpot(12, 55),
FlSpot(16, 50),
FlSpot(18, 55),
FlSpot(20, 75),
FlSpot(22, 82),
FlSpot(24, 85),
],
);
}
void handleConfirm() {
print('确认处理');
}
}
\ No newline at end of file
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:fl_chart/fl_chart.dart';
import 'package:smart_hotel_app/routes/app_router.gr.dart';
class AlarmInfo {
final String alarmType;
......@@ -19,7 +16,7 @@ class AlarmInfo {
final String date;
final List<FlSpot> temperatureSpots;
AlarmInfo({
const AlarmInfo({
required this.alarmType,
required this.deviceInfo,
required this.alarmLevel,
......@@ -37,49 +34,10 @@ class AlarmInfo {
});
}
class HomeAbnormalController {
final BuildContext context;
class AbnormalState {
final AlarmInfo alarmInfo;
HomeAbnormalController(this.context);
void navigateToDeviceDetail() {
context.pushRoute(const DeviceDetailRoute());
}
AlarmInfo getAlarmInfo() {
return AlarmInfo(
alarmType: '温度异常告警',
deviceInfo: 'A-301智能空开·客房301',
alarmLevel: '高警',
urgency: '紧急',
currentTemp: '85℃',
voltage: '22V',
current: '3.2A',
triggerTime: '2026-05-09 14:23:18',
threshold: '温度>80℃触发',
duration: '2小时37分钟',
peak: '89.2℃ (15:02)',
linkAction: '自动断电保护',
date: '5月9日',
temperatureSpots: [
FlSpot(0, 40),
FlSpot(4, 40),
FlSpot(8, 15),
FlSpot(12, 55),
FlSpot(16, 50),
FlSpot(18, 55),
FlSpot(20, 75),
FlSpot(22, 82),
FlSpot(24, 85),
],
);
}
void handleConfirm() {
print('确认处理');
}
void handleViewDevice() {
navigateToDeviceDetail();
}
const AbnormalState({
required this.alarmInfo,
});
}
\ No newline at end of file
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:smart_hotel_app/views/home/abnormal/controller.dart';
import 'package:smart_hotel_app/views/home/abnormal/cubit/abnormal_state.dart';
class AlarmInfoBlock extends StatelessWidget {
final AlarmInfo alarmInfo;
......
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:smart_hotel_app/views/home/abnormal/controller.dart';
import 'package:smart_hotel_app/views/home/abnormal/cubit/abnormal_state.dart';
class TemperatureBlock extends StatelessWidget {
final AlarmInfo alarmInfo;
......
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:fl_chart/fl_chart.dart';
import 'package:smart_hotel_app/views/home/abnormal/controller.dart';
import 'package:smart_hotel_app/views/home/abnormal/cubit/abnormal_state.dart';
class TemperatureCharBlock extends StatelessWidget {
final AlarmInfo alarmInfo;
......
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:smart_hotel_app/views/home/device/cubit/device_state.dart';
class DeviceCubit extends Cubit<DeviceState> {
DeviceCubit() : super(DeviceState(deviceInfo: _getDeviceInfo()));
static DeviceInfo _getDeviceInfo() {
return const DeviceInfo(
deviceName: '客房305 智能空开',
deviceId: 'A-305',
location: '3楼客房区',
status: '在线',
voltage: '220V',
current: '2.36A',
power: '520W',
temperature: '38°C',
powerData: [
40, 60, 85, 98, 85, 72, 60, 48, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0
],
tempData: [
28, 30, 32, 35, 33, 31, 30, 29, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0
],
);
}
void remotePowerOn() {}
void remotePowerOff() {}
}
\ No newline at end of file
import 'package:flutter/material.dart';
import 'package:fl_chart/fl_chart.dart';
class DeviceInfo {
final String deviceName;
final String deviceId;
......@@ -13,7 +10,7 @@ class DeviceInfo {
final List<int> powerData;
final List<int> tempData;
DeviceInfo({
const DeviceInfo({
required this.deviceName,
required this.deviceId,
required this.location,
......@@ -27,31 +24,8 @@ class DeviceInfo {
});
}
class HomeDeviceController {
final BuildContext context;
HomeDeviceController(this.context);
void remotePowerOn() {
// 远程送电
}
void remotePowerOff() {
// 远程断电
}
class DeviceState {
final DeviceInfo deviceInfo;
DeviceInfo getDeviceInfo() {
return DeviceInfo(
deviceName: '客房305 智能空开',
deviceId: 'A-305',
location: '3楼客房区',
status: '在线',
voltage: '220V',
current: '2.36A',
power: '520W',
temperature: '38°C',
powerData: [40, 60, 85, 98, 85, 72, 60, 48, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
tempData: [28, 30, 32, 35, 33, 31, 30, 29, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
);
}
const DeviceState({required this.deviceInfo});
}
\ No newline at end of file
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:smart_hotel_app/views/home/device/child/device_parms_block.dart';
import 'package:smart_hotel_app/views/home/device/child/power_char_block.dart';
import 'package:smart_hotel_app/views/home/device/child/state_info_block.dart';
import 'package:smart_hotel_app/views/home/device/controller.dart';
import 'package:smart_hotel_app/views/home/device/cubit/device_cubit.dart';
import 'package:smart_hotel_app/views/home/device/cubit/device_state.dart';
import 'package:smart_hotel_app/views/home/device/widget/device_parms_block.dart';
import 'package:smart_hotel_app/views/home/device/widget/power_char_block.dart';
import 'package:smart_hotel_app/views/home/device/widget/state_info_block.dart';
@RoutePage()
class DeviceDetailView extends StatefulWidget {
class DeviceDetailView extends StatelessWidget {
const DeviceDetailView({super.key});
@override
State<DeviceDetailView> createState() => _DeviceDetailViewState();
}
class _DeviceDetailViewState extends State<DeviceDetailView> {
late final HomeDeviceController _controller;
late final DeviceInfo _deviceInfo;
@override
void initState() {
super.initState();
_controller = HomeDeviceController(context);
_deviceInfo = _controller.getDeviceInfo();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color.fromRGBO(242, 243, 245, 1),
appBar: AppBar(
backgroundColor: Colors.white,
elevation: 0,
leading: IconButton(
icon: Icon(Icons.arrow_back_ios, color: Color.fromRGBO(100, 116, 139, 1),),
onPressed: () {
context.popRoute();
},
),
title: Text(
'设备详情',
style: TextStyle(
color: Color.fromRGBO(10, 13, 20, 1),
fontSize: 32.sp,
fontWeight: FontWeight.w200,
return BlocProvider(
create: (_) => DeviceCubit(),
child: Scaffold(
backgroundColor: const Color.fromRGBO(242, 243, 245, 1),
appBar: AppBar(
backgroundColor: Colors.white,
elevation: 0,
leading: IconButton(
icon: const Icon(Icons.arrow_back_ios,
color: Color.fromRGBO(100, 116, 139, 1)),
onPressed: () {
context.popRoute();
},
),
title: Text(
'设备控制',
style: TextStyle(
color: const Color.fromRGBO(10, 13, 20, 1),
fontSize: 32.sp,
fontWeight: FontWeight.w200,
),
),
centerTitle: true,
),
centerTitle: true,
),
body: SingleChildScrollView(
padding: EdgeInsets.symmetric(horizontal: 28.w),
child: Column(
children: [
StateInfoBlock(deviceInfo: _deviceInfo),
SizedBox(height: 10.h),
DeviceParmsBlock(deviceInfo: _deviceInfo),
SizedBox(height: 10.h),
PowerCharBlock(deviceInfo: _deviceInfo),
SizedBox(height: 10.h),
_buildBottomButtons(),
SizedBox(height: 30.h),
],
body: SingleChildScrollView(
padding: EdgeInsets.symmetric(horizontal: 28.w),
child: BlocBuilder<DeviceCubit, DeviceState>(
builder: (context, state) {
final cubit = context.read<DeviceCubit>();
return Column(
children: [
StateInfoBlock(deviceInfo: state.deviceInfo),
SizedBox(height: 10.h),
DeviceParmsBlock(deviceInfo: state.deviceInfo),
SizedBox(height: 10.h),
PowerCharBlock(deviceInfo: state.deviceInfo),
SizedBox(height: 10.h),
_buildBottomButtons(cubit),
SizedBox(height: 30.h),
],
);
},
),
),
),
);
}
Widget _buildBottomButtons() {
Widget _buildBottomButtons(DeviceCubit cubit) {
return Container(
width: double.infinity,
padding: EdgeInsets.symmetric(vertical: 15.h),
......@@ -78,34 +74,38 @@ class _DeviceDetailViewState extends State<DeviceDetailView> {
width: 300.w,
height: 88.h,
decoration: BoxDecoration(
color: Color.fromRGBO(73, 149, 234, 1),
color: const Color.fromRGBO(73, 149, 234, 1),
borderRadius: BorderRadius.circular(16.r),
),
child: TextButton(
onPressed: _controller.remotePowerOn,
onPressed: () => cubit.remotePowerOn(),
child: Text(
"远程送电",
style: TextStyle(fontSize: 28.sp, color: Color.fromRGBO(237, 245, 255, 1)),
style: TextStyle(
fontSize: 28.sp,
color: const Color.fromRGBO(237, 245, 255, 1)),
),
),
),
SizedBox(width: 30.w,),
SizedBox(width: 30.w),
Container(
width: 300.w,
height: 88.h,
decoration: BoxDecoration(
color: Color.fromRGBO(229, 241, 255, 1.0),
color: const Color.fromRGBO(229, 241, 255, 1.0),
border: Border.all(
color: Color.fromRGBO(73, 149, 234, 1),
color: const Color.fromRGBO(73, 149, 234, 1),
width: 1.w,
),
borderRadius: BorderRadius.circular(16.r),
),
child: TextButton(
onPressed: _controller.remotePowerOff,
onPressed: () => cubit.remotePowerOff(),
child: Text(
"远程断电",
style: TextStyle(fontSize: 28.sp, color: Color.fromRGBO(100, 116, 139, 1)),
style: TextStyle(
fontSize: 28.sp,
color: const Color.fromRGBO(100, 116, 139, 1)),
),
),
),
......
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:smart_hotel_app/views/home/device/controller.dart';
import 'package:smart_hotel_app/views/home/device/cubit/device_state.dart';
class DeviceParmsBlock extends StatelessWidget {
final DeviceInfo deviceInfo;
......
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:fl_chart/fl_chart.dart';
import 'package:smart_hotel_app/views/home/device/controller.dart';
import 'package:smart_hotel_app/views/home/device/cubit/device_state.dart';
class PowerCharBlock extends StatefulWidget {
final DeviceInfo deviceInfo;
......
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:smart_hotel_app/views/home/device/controller.dart';
import 'package:smart_hotel_app/views/home/device/cubit/device_state.dart';
class StateInfoBlock extends StatelessWidget {
final DeviceInfo deviceInfo;
......
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
// import 'package:smart_hotel_app/routes/app_router.dart';
import 'package:smart_hotel_app/routes/app_router.gr.dart';
class EquipmentItem {
final IconData icon;
final String title;
final String subtitle;
final String status;
final Color statusColor;
EquipmentItem({
required this.icon,
required this.title,
required this.subtitle,
required this.status,
required this.statusColor,
});
}
class HomeIndexController {
final BuildContext context;
HomeIndexController(this.context);
void handleToAbnormalDetail() {
context.pushRoute(const AbnormalDetailRoute());
}
int getPendingTaskCount() {
return 3;
}
int getHighAlarmCount() {
return 1;
}
int getMediumAlarmCount() {
return 1;
}
int getOnlineDeviceCount() {
return 48;
}
String getTemperatureAlarmTitle() {
return '温度异常告警';
}
String getTemperatureAlarmLocation() {
return '客房301·A-301智能空开';
}
String getCurrentTemperature() {
return '85';
}
String getCurrentVoltage() {
return '220';
}
String getCurrentCurrent() {
return '3.2';
}
String getWaitTime() {
return '2h37m';
}
String getAlarmLevel() {
return '紧急';
}
String getVoltageAlarmTitle() {
return '电压波动告警';
}
String getVoltageAlarmLocation() {
return '会议室2F-01';
}
String getVoltageValue() {
return '248';
}
String getNormalVoltage() {
return '235';
}
String getVoltageWaitTime() {
return '1h19m';
}
String getVoltageAlarmLevel() {
return '中警';
}
List<EquipmentItem> getEquipmentList() {
return [
EquipmentItem(
icon: Icons.power,
title: '客房305空开',
subtitle: '38°C·220V·520W',
status: '正常',
statusColor: Color.fromRGBO(26, 188, 156, 1.0),
),
EquipmentItem(
icon: Icons.power,
title: '大堂总空开L-01',
subtitle: '380V·42A·28.6kW',
status: '正常',
statusColor: Color.fromRGBO(26, 188, 156, 1.0),
),
EquipmentItem(
icon: Icons.wifi,
title: '招牌AP-03',
subtitle: '信号弱·RSSI-78dBm',
status: '注意',
statusColor: Color.fromRGBO(255, 193, 7, 1.0),
),
EquipmentItem(
icon: Icons.power,
title: '客房201空开',
subtitle: '25°C·220V·380W',
status: '正常',
statusColor: Color.fromRGBO(26, 188, 156, 1.0),
),
EquipmentItem(
icon: Icons.power,
title: '水泵控制箱',
subtitle: '运行中·2.2kW',
status: '正常',
statusColor: Color.fromRGBO(26, 188, 156, 1.0),
),
EquipmentItem(
icon: Icons.thermostat,
title: '冷库温控器',
subtitle: '-18°C·异常波动',
status: '告警',
statusColor: Color.fromRGBO(255, 77, 79, 1.0),
),
EquipmentItem(
icon: Icons.light,
title: '走廊照明回路',
subtitle: '12盏·功率240W',
status: '正常',
statusColor: Color.fromRGBO(26, 188, 156, 1.0),
),
EquipmentItem(
icon: Icons.wifi,
title: '会议室AP-01',
subtitle: '连接数12·信号强',
status: '正常',
statusColor: Color.fromRGBO(26, 188, 156, 1.0),
),
EquipmentItem(
icon: Icons.power,
title: '电梯控制柜',
subtitle: '运行中·电压稳定',
status: '正常',
statusColor: Color.fromRGBO(26, 188, 156, 1.0),
),
EquipmentItem(
icon: Icons.thermostat,
title: '空调外机',
subtitle: '压力偏高·需检查',
status: '注意',
statusColor: Color.fromRGBO(255, 193, 7, 1.0),
),
EquipmentItem(
icon: Icons.wifi,
title: '楼道AP-05',
subtitle: '离线·需重启',
status: '离线',
statusColor: Color.fromRGBO(102, 102, 102, 1.0),
),
EquipmentItem(
icon: Icons.power,
title: '厨房消毒柜',
subtitle: '消毒中·温度120°C',
status: '正常',
statusColor: Color.fromRGBO(26, 188, 156, 1.0),
),
];
}
}
\ No newline at end of file
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:smart_hotel_app/views/home/index/cubit/home_index_state.dart';
import 'package:flutter/material.dart';
class HomeIndexCubit extends Cubit<HomeIndexState> {
HomeIndexCubit() : super(const HomeIndexState()) {
initData();
}
void initData() {
emit(state.copyWith(
highAlarmCount: 1,
mediumAlarmCount: 1,
onlineDeviceCount: 48,
temperatureAlarmTitle: '温度异常告警',
temperatureAlarmLocation: '客房301·A-301智能空开',
currentTemperature: '85',
currentVoltage: '220',
currentCurrent: '3.2',
waitTime: '2h37m',
alarmLevel: '紧急',
voltageAlarmTitle: '电压波动告警',
voltageAlarmLocation: '会议室2F-01',
voltageValue: '248',
normalVoltage: '235',
voltageWaitTime: '1h19m',
voltageAlarmLevel: '中警',
equipmentList: const [
EquipmentItem(icon: Icons.power, title: '客房305空开', subtitle: '38°C·220V·520W', status: '正常', statusColor: Color.fromRGBO(26, 188, 156, 1.0)),
EquipmentItem(icon: Icons.power, title: '大堂总空开L-01', subtitle: '380V·42A·28.6kW', status: '正常', statusColor: Color.fromRGBO(26, 188, 156, 1.0)),
EquipmentItem(icon: Icons.wifi, title: '招牌AP-03', subtitle: '信号弱·RSSI-78dBm', status: '注意', statusColor: Color.fromRGBO(255, 193, 7, 1.0)),
EquipmentItem(icon: Icons.power, title: '客房201空开', subtitle: '25°C·220V·380W', status: '正常', statusColor: Color.fromRGBO(26, 188, 156, 1.0)),
EquipmentItem(icon: Icons.power, title: '水泵控制箱', subtitle: '运行中·2.2kW', status: '正常', statusColor: Color.fromRGBO(26, 188, 156, 1.0)),
EquipmentItem(icon: Icons.thermostat, title: '冷库温控器', subtitle: '-18°C·异常波动', status: '告警', statusColor: Color.fromRGBO(255, 77, 79, 1.0)),
EquipmentItem(icon: Icons.light, title: '走廊照明回路', subtitle: '12盏·功率240W', status: '正常', statusColor: Color.fromRGBO(26, 188, 156, 1.0)),
EquipmentItem(icon: Icons.wifi, title: '会议室AP-01', subtitle: '连接数12·信号强', status: '正常', statusColor: Color.fromRGBO(26, 188, 156, 1.0)),
EquipmentItem(icon: Icons.power, title: '电梯控制柜', subtitle: '运行中·电压稳定', status: '正常', statusColor: Color.fromRGBO(26, 188, 156, 1.0)),
EquipmentItem(icon: Icons.thermostat, title: '空调外机', subtitle: '压力偏高·需检查', status: '注意', statusColor: Color.fromRGBO(255, 193, 7, 1.0)),
EquipmentItem(icon: Icons.wifi, title: '楼道AP-05', subtitle: '离线·需重启', status: '离线', statusColor: Color.fromRGBO(102, 102, 102, 1.0)),
EquipmentItem(icon: Icons.power, title: '厨房消毒柜', subtitle: '消毒中·温度120°C', status: '正常', statusColor: Color.fromRGBO(26, 188, 156, 1.0)),
],
));
}
}
\ No newline at end of file
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
class EquipmentItem {
final IconData icon;
final String title;
final String subtitle;
final String status;
final Color statusColor;
const EquipmentItem({
required this.icon,
required this.title,
required this.subtitle,
required this.status,
required this.statusColor,
});
}
class HomeIndexState extends Equatable {
final int highAlarmCount;
final int mediumAlarmCount;
final int onlineDeviceCount;
final String temperatureAlarmTitle;
final String temperatureAlarmLocation;
final String currentTemperature;
final String currentVoltage;
final String currentCurrent;
final String waitTime;
final String alarmLevel;
final String voltageAlarmTitle;
final String voltageAlarmLocation;
final String voltageValue;
final String normalVoltage;
final String voltageWaitTime;
final String voltageAlarmLevel;
final List<EquipmentItem> equipmentList;
const HomeIndexState({
this.highAlarmCount = 0,
this.mediumAlarmCount = 0,
this.onlineDeviceCount = 0,
this.temperatureAlarmTitle = '',
this.temperatureAlarmLocation = '',
this.currentTemperature = '',
this.currentVoltage = '',
this.currentCurrent = '',
this.waitTime = '',
this.alarmLevel = '',
this.voltageAlarmTitle = '',
this.voltageAlarmLocation = '',
this.voltageValue = '',
this.normalVoltage = '',
this.voltageWaitTime = '',
this.voltageAlarmLevel = '',
this.equipmentList = const [],
});
HomeIndexState copyWith({
int? highAlarmCount,
int? mediumAlarmCount,
int? onlineDeviceCount,
String? temperatureAlarmTitle,
String? temperatureAlarmLocation,
String? currentTemperature,
String? currentVoltage,
String? currentCurrent,
String? waitTime,
String? alarmLevel,
String? voltageAlarmTitle,
String? voltageAlarmLocation,
String? voltageValue,
String? normalVoltage,
String? voltageWaitTime,
String? voltageAlarmLevel,
List<EquipmentItem>? equipmentList,
}) {
return HomeIndexState(
highAlarmCount: highAlarmCount ?? this.highAlarmCount,
mediumAlarmCount: mediumAlarmCount ?? this.mediumAlarmCount,
onlineDeviceCount: onlineDeviceCount ?? this.onlineDeviceCount,
temperatureAlarmTitle: temperatureAlarmTitle ?? this.temperatureAlarmTitle,
temperatureAlarmLocation: temperatureAlarmLocation ?? this.temperatureAlarmLocation,
currentTemperature: currentTemperature ?? this.currentTemperature,
currentVoltage: currentVoltage ?? this.currentVoltage,
currentCurrent: currentCurrent ?? this.currentCurrent,
waitTime: waitTime ?? this.waitTime,
alarmLevel: alarmLevel ?? this.alarmLevel,
voltageAlarmTitle: voltageAlarmTitle ?? this.voltageAlarmTitle,
voltageAlarmLocation: voltageAlarmLocation ?? this.voltageAlarmLocation,
voltageValue: voltageValue ?? this.voltageValue,
normalVoltage: normalVoltage ?? this.normalVoltage,
voltageWaitTime: voltageWaitTime ?? this.voltageWaitTime,
voltageAlarmLevel: voltageAlarmLevel ?? this.voltageAlarmLevel,
equipmentList: equipmentList ?? this.equipmentList,
);
}
@override
List<Object?> get props => [
highAlarmCount,
mediumAlarmCount,
onlineDeviceCount,
temperatureAlarmTitle,
temperatureAlarmLocation,
currentTemperature,
currentVoltage,
currentCurrent,
waitTime,
alarmLevel,
voltageAlarmTitle,
voltageAlarmLocation,
voltageValue,
normalVoltage,
voltageWaitTime,
voltageAlarmLevel,
equipmentList,
];
}
\ No newline at end of file
import 'dart:math';
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:smart_hotel_app/routes/app_router.gr.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:smart_hotel_app/views/home/index/child/equipment_list_block.dart';
import 'package:smart_hotel_app/views/home/index/child/task_head_block.dart';
import 'package:smart_hotel_app/views/home/index/child/task_total_block.dart';
import 'package:smart_hotel_app/views/home/index/child/temperature_block.dart';
import 'package:smart_hotel_app/views/home/index/child/voltage_operate_block.dart';
import 'package:smart_hotel_app/views/home/index/controller.dart';
import 'package:smart_hotel_app/views/home/index/cubit/home_index_cubit.dart';
import 'package:smart_hotel_app/views/home/index/cubit/home_index_state.dart';
import 'package:smart_hotel_app/views/home/index/widget/equipment_list_block.dart';
import 'package:smart_hotel_app/views/home/index/widget/task_head_block.dart';
import 'package:smart_hotel_app/views/home/index/widget/task_total_block.dart';
import 'package:smart_hotel_app/views/home/index/widget/temperature_block.dart';
import 'package:smart_hotel_app/views/home/index/widget/voltage_operate_block.dart';
@RoutePage()
class HomeView extends StatefulWidget {
class HomeView extends StatelessWidget {
const HomeView({super.key});
@override
State<HomeView> createState() => _HomeViewState();
}
class _HomeViewState extends State<HomeView> {
late final HomeIndexController _controller;
@override
void initState() {
super.initState();
_controller = HomeIndexController(context);
}
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
return SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: constraints.maxHeight,
),
child: Container(
width: double.infinity,
color: Color.fromRGBO(242, 243, 245, 1),
padding: EdgeInsets.symmetric(horizontal: 28.w),
child: Column(
children: [
TaskHeadBlock(controller: _controller),
SizedBox(height: 20.h),
TaskTotalBlock(controller: _controller),
SizedBox(height: 20.h),
TemperatureBlock(controller: _controller),
SizedBox(height: 20.h),
VoltageOperateBlock(controller: _controller),
SizedBox(height: 20.h),
EquipmentListBlock(controller: _controller),
SizedBox(height: 20.h),
],
return BlocProvider(
create: (_) => HomeIndexCubit(),
child: LayoutBuilder(
builder: (context, constraints) {
return SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: constraints.maxHeight,
),
child: Container(
width: double.infinity,
color: const Color.fromRGBO(242, 243, 245, 1),
padding: EdgeInsets.symmetric(horizontal: 28.w),
child: BlocBuilder<HomeIndexCubit, HomeIndexState>(
builder: (context, state) {
return Column(
children: [
const TaskHeadBlock(),
SizedBox(height: 20.h),
TaskTotalBlock(
highAlarmCount: state.highAlarmCount,
mediumAlarmCount: state.mediumAlarmCount,
onlineDeviceCount: state.onlineDeviceCount,
),
SizedBox(height: 20.h),
TemperatureBlock(
temperatureAlarmTitle: state.temperatureAlarmTitle,
temperatureAlarmLocation: state.temperatureAlarmLocation,
currentTemperature: state.currentTemperature,
currentVoltage: state.currentVoltage,
currentCurrent: state.currentCurrent,
waitTime: state.waitTime,
alarmLevel: state.alarmLevel,
),
SizedBox(height: 20.h),
VoltageOperateBlock(
voltageAlarmTitle: state.voltageAlarmTitle,
voltageAlarmLocation: state.voltageAlarmLocation,
voltageValue: state.voltageValue,
normalVoltage: state.normalVoltage,
voltageWaitTime: state.voltageWaitTime,
voltageAlarmLevel: state.voltageAlarmLevel,
),
SizedBox(height: 20.h),
EquipmentListBlock(
equipmentList: state.equipmentList,
),
SizedBox(height: 20.h),
],
);
},
),
),
),
),
);
},
);
},
),
);
}
}
}
\ No newline at end of file
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:smart_hotel_app/views/home/index/controller.dart';
import 'package:smart_hotel_app/views/home/index/cubit/home_index_state.dart';
import 'package:smart_hotel_app/routes/app_router.gr.dart';
class EquipmentListBlock extends StatefulWidget {
final HomeIndexController controller;
const EquipmentListBlock({super.key, required this.controller});
class EquipmentListBlock extends StatelessWidget {
final List<EquipmentItem> equipmentList;
@override
State<EquipmentListBlock> createState() => _EquipmentListBlockState();
}
const EquipmentListBlock({
super.key,
required this.equipmentList,
});
class _EquipmentListBlockState extends State<EquipmentListBlock> {
Widget _buildEquipmentItem(EquipmentItem item) {
return Container(
padding: EdgeInsets.symmetric(vertical: 16.h),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: Color.fromRGBO(235, 235, 235, 1.0),
color: const Color.fromRGBO(235, 235, 235, 1.0),
width: 1.w,
),
),
......@@ -29,12 +29,17 @@ class _EquipmentListBlockState extends State<EquipmentListBlock> {
width: 64.w,
height: 66.h,
decoration: BoxDecoration(
color: Color.fromRGBO(235, 244, 255, 1.0),
color: const Color.fromRGBO(235, 244, 255, 1.0),
borderRadius: BorderRadius.circular(12.r),
border: Border.all(
color: const Color.fromRGBO(66, 165, 245, 1.0),
width: 2.w,
style: BorderStyle.solid,
),
),
child: Icon(
item.icon,
color: Color.fromRGBO(66, 165, 245, 1.0),
color: const Color.fromRGBO(66, 165, 245, 1.0),
size: 32.sp,
),
),
......@@ -59,7 +64,7 @@ class _EquipmentListBlockState extends State<EquipmentListBlock> {
item.subtitle,
style: TextStyle(
fontSize: 24.sp,
color: Color.fromRGBO(100, 116, 139, 1.0),
color: const Color.fromRGBO(100, 116, 139, 1.0),
),
),
],
......@@ -80,11 +85,8 @@ class _EquipmentListBlockState extends State<EquipmentListBlock> {
@override
Widget build(BuildContext context) {
final equipmentList = widget.controller.getEquipmentList();
return Container(
width: double.infinity,
// margin: EdgeInsets.symmetric(horizontal: 14.w, vertical: 24.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
......@@ -100,28 +102,38 @@ class _EquipmentListBlockState extends State<EquipmentListBlock> {
color: Colors.black,
),
),
Text(
'查看全部>',
style: TextStyle(
fontSize: 28.sp,
fontWeight: FontWeight.bold,
color: Color.fromRGBO(73, 149, 234, 1.0),
GestureDetector(
onTap: () {
context.pushRoute(const InspectionDetailRoute());
},
child: Row(
children: [
Text(
'查看全部',
style: TextStyle(
fontSize: 24.sp,
color: const Color.fromRGBO(66, 165, 245, 1.0),
),
),
Icon(
Icons.chevron_right,
color: const Color.fromRGBO(66, 165, 245, 1.0),
size: 24.sp,
),
],
),
),
],
),
SizedBox(height: 12.h),
Container(
width: double.infinity,
padding: EdgeInsets.only(top: 20.w,bottom: 20.w,left: 42.w,right: 42.w),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20.r),
color: Colors.white,
borderRadius: BorderRadius.circular(20.r),
),
padding: EdgeInsets.symmetric(horizontal: 20.w),
child: Column(
children: [
...equipmentList.map((item) => _buildEquipmentItem(item)).toList(),
],
children: equipmentList.take(3).map((item) => _buildEquipmentItem(item)).toList(),
),
),
],
......
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:smart_hotel_app/views/home/index/controller.dart';
class TaskHeadBlock extends StatefulWidget {
final HomeIndexController controller;
const TaskHeadBlock({super.key, required this.controller});
class TaskHeadBlock extends StatelessWidget {
const TaskHeadBlock({super.key});
@override
State<TaskHeadBlock> createState() => _TaskHeadBlockState();
}
class _TaskHeadBlockState extends State<TaskHeadBlock> {
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
// height: 140.h,
padding: EdgeInsets.only(top: ScreenUtil().statusBarHeight),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'设备告警看板',
style: TextStyle(
fontSize: 28.sp,
fontWeight: FontWeight.bold,
color: Color.fromRGBO(51, 51, 51, 1.0),
),
),
SizedBox(height: 8.h),
Text(
'工程运维视角·今日${widget.controller.getPendingTaskCount()}条待处理',
style: TextStyle(
fontSize: 16.sp,
color: Color.fromRGBO(102, 102, 102, 1.0),
),
),
],
child: Text(
'设备告警看板',
style: TextStyle(
fontSize: 28.sp,
fontWeight: FontWeight.bold,
color: const Color.fromRGBO(51, 51, 51, 1.0),
),
),
);
}
......
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:smart_hotel_app/views/home/index/controller.dart';
class TaskTotalBlock extends StatefulWidget {
final HomeIndexController controller;
const TaskTotalBlock({super.key, required this.controller});
class TaskTotalBlock extends StatelessWidget {
final int highAlarmCount;
final int mediumAlarmCount;
final int onlineDeviceCount;
@override
State<TaskTotalBlock> createState() => _TaskTotalBlockState();
}
const TaskTotalBlock({
super.key,
required this.highAlarmCount,
required this.mediumAlarmCount,
required this.onlineDeviceCount,
});
class _TaskTotalBlockState extends State<TaskTotalBlock> {
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
// height: 180.h,
child: Flex(
direction: Axis.horizontal,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
// 高警待处理
Expanded(
child: Container(
margin: EdgeInsets.only(right: 10.w),
......@@ -35,21 +34,20 @@ class _TaskTotalBlockState extends State<TaskTotalBlock> {
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
'${widget.controller.getHighAlarmCount()}',
'$highAlarmCount',
style: TextStyle(
fontSize: 48.sp,
fontWeight: FontWeight.bold,
color: Color.fromRGBO(255, 77, 79, 1.0),
),
color: const Color.fromRGBO(255, 77, 79, 1.0),
),
// SizedBox(height: 5.h),
),
Transform.translate(
offset: Offset(0, -8.h),
child: Text(
'高警待处理',
style: TextStyle(
fontSize: 24.sp,
color: Color.fromRGBO(100, 116, 139, 1.0),
color: const Color.fromRGBO(100, 116, 139, 1.0),
),
),
),
......@@ -57,8 +55,6 @@ class _TaskTotalBlockState extends State<TaskTotalBlock> {
),
),
),
// 中警待处理
Expanded(
child: Container(
margin: EdgeInsets.symmetric(horizontal: 10.w),
......@@ -71,21 +67,20 @@ class _TaskTotalBlockState extends State<TaskTotalBlock> {
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'${widget.controller.getMediumAlarmCount()}',
'$mediumAlarmCount',
style: TextStyle(
fontSize: 48.sp,
fontWeight: FontWeight.bold,
color: Color.fromRGBO(100, 116, 139, 1.0),
color: const Color.fromRGBO(100, 116, 139, 1.0),
),
),
// SizedBox(height: 8.h),
Transform.translate(
offset: Offset(0, -8.h),
child: Text(
'中警待处理',
style: TextStyle(
fontSize: 24.sp,
color: Color.fromRGBO(100, 116, 139, 1.0),
color: const Color.fromRGBO(100, 116, 139, 1.0),
),
),
),
......@@ -93,8 +88,6 @@ class _TaskTotalBlockState extends State<TaskTotalBlock> {
),
),
),
// 设备在线
Expanded(
child: Container(
margin: EdgeInsets.only(left: 10.w),
......@@ -107,21 +100,20 @@ class _TaskTotalBlockState extends State<TaskTotalBlock> {
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'${widget.controller.getOnlineDeviceCount()}',
'$onlineDeviceCount',
style: TextStyle(
fontSize: 48.sp,
fontWeight: FontWeight.bold,
color: Color.fromRGBO(26, 188, 156, 1.0),
color: const Color.fromRGBO(34, 197, 94, 1.0),
),
),
// SizedBox(height: 8.h),
Transform.translate(
offset: Offset(0, -8.h),
child: Text(
'设备在线',
style: TextStyle(
fontSize: 24.sp,
color: Color.fromRGBO(100, 116, 139, 1.0),
color: const Color.fromRGBO(100, 116, 139, 1.0),
),
),
),
......
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:smart_hotel_app/views/home/index/controller.dart';
import 'package:smart_hotel_app/routes/app_router.gr.dart';
class TemperatureBlock extends StatefulWidget {
final HomeIndexController controller;
const TemperatureBlock({super.key, required this.controller});
class TemperatureBlock extends StatelessWidget {
final String temperatureAlarmTitle;
final String temperatureAlarmLocation;
final String currentTemperature;
final String currentVoltage;
final String currentCurrent;
final String waitTime;
final String alarmLevel;
@override
State<TemperatureBlock> createState() => _TemperatureBlockState();
}
const TemperatureBlock({
super.key,
required this.temperatureAlarmTitle,
required this.temperatureAlarmLocation,
required this.currentTemperature,
required this.currentVoltage,
required this.currentCurrent,
required this.waitTime,
required this.alarmLevel,
});
class _TemperatureBlockState extends State<TemperatureBlock> {
@override
Widget build(BuildContext context) {
return Container(
......@@ -23,7 +34,6 @@ class _TemperatureBlockState extends State<TemperatureBlock> {
),
child: Column(
children: [
// 描述信息
Container(
width: double.infinity,
margin: EdgeInsets.only(bottom: 16.h),
......@@ -34,12 +44,12 @@ class _TemperatureBlockState extends State<TemperatureBlock> {
width: 66.w,
height: 68.h,
decoration: BoxDecoration(
color: Color.fromRGBO(235, 244, 255, 1.0),
color: const Color.fromRGBO(235, 244, 255, 1.0),
borderRadius: BorderRadius.circular(12.r),
),
child: Icon(
Icons.thermostat_outlined,
color: Color.fromRGBO(66, 165, 245, 1.0),
color: const Color.fromRGBO(66, 165, 245, 1.0),
size: 32.sp,
),
),
......@@ -49,19 +59,19 @@ class _TemperatureBlockState extends State<TemperatureBlock> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
widget.controller.getTemperatureAlarmTitle(),
temperatureAlarmTitle,
style: TextStyle(
fontSize: 28.sp,
fontWeight: FontWeight.bold,
color: Color.fromRGBO(0, 0, 0, 1.0),
color: const Color.fromRGBO(0, 0, 0, 1.0),
),
),
SizedBox(height: 4.h),
Text(
widget.controller.getTemperatureAlarmLocation(),
temperatureAlarmLocation,
style: TextStyle(
fontSize: 24.sp,
color: Color.fromRGBO(100, 116, 139, 1.0),
color: const Color.fromRGBO(100, 116, 139, 1.0),
),
),
],
......@@ -71,19 +81,17 @@ class _TemperatureBlockState extends State<TemperatureBlock> {
'置顶',
style: TextStyle(
fontSize: 24.sp,
color: Color.fromRGBO(255, 100, 101, 1.0),
color: const Color.fromRGBO(255, 100, 101, 1.0),
),
),
],
),
),
// 数据行
Container(
width: double.infinity,
padding: EdgeInsets.symmetric(vertical: 20.h),
decoration: BoxDecoration(
color: Color.fromRGBO(235, 244, 255, 1.0),
color: const Color.fromRGBO(235, 244, 255, 1.0),
borderRadius: BorderRadius.circular(16.r),
),
child: Row(
......@@ -94,22 +102,19 @@ class _TemperatureBlockState extends State<TemperatureBlock> {
mainAxisSize: MainAxisSize.min,
children: [
Text(
'${widget.controller.getCurrentTemperature()}°C',
'$currentTemperature°C',
style: TextStyle(
fontSize: 40.sp,
fontWeight: FontWeight.bold,
color: Color.fromRGBO(255, 100, 101, 1.0),
color: const Color.fromRGBO(255, 77, 79, 1.0),
),
),
// SizedBox(height: 4.h),
Transform.translate(
offset: Offset(0, -8.h),
child: Text(
'当前温度',
style: TextStyle(
fontSize: 24.sp,
color: Color.fromRGBO(100, 116, 139, 1.0),
),
SizedBox(height: 4.h),
Text(
'当前温度',
style: TextStyle(
fontSize: 20.sp,
color: const Color.fromRGBO(100, 116, 139, 1.0),
),
),
],
......@@ -120,22 +125,19 @@ class _TemperatureBlockState extends State<TemperatureBlock> {
mainAxisSize: MainAxisSize.min,
children: [
Text(
'${widget.controller.getCurrentVoltage()}V',
'${currentVoltage}V',
style: TextStyle(
fontSize: 40.sp,
fontWeight: FontWeight.bold,
color: Color.fromRGBO(73, 149, 234, 1.0),
color: const Color.fromRGBO(66, 165, 245, 1.0),
),
),
// SizedBox(height: 4.h),
Transform.translate(
offset: Offset(0, -8.h),
child: Text(
'电压',
style: TextStyle(
fontSize: 24.sp,
color: Color.fromRGBO(100, 116, 139, 1.0),
),
SizedBox(height: 4.h),
Text(
'电压',
style: TextStyle(
fontSize: 20.sp,
color: const Color.fromRGBO(100, 116, 139, 1.0),
),
),
],
......@@ -143,25 +145,22 @@ class _TemperatureBlockState extends State<TemperatureBlock> {
),
Expanded(
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisSize: MainAxisSize.min,
children: [
Text(
'${widget.controller.getCurrentCurrent()}A',
'${currentCurrent}A',
style: TextStyle(
fontSize: 40.sp,
fontWeight: FontWeight.bold,
color: Color.fromRGBO(73, 149, 234, 1.0),
color: const Color.fromRGBO(66, 165, 245, 1.0),
),
),
// SizedBox(height: 4.h),
Transform.translate(
offset: Offset(0, -8.h),
child: Text(
'电流',
style: TextStyle(
fontSize: 24.sp,
color: Color.fromRGBO(100, 116, 139, 1.0),
),
SizedBox(height: 4.h),
Text(
'电流',
style: TextStyle(
fontSize: 20.sp,
color: const Color.fromRGBO(100, 116, 139, 1.0),
),
),
],
......@@ -170,63 +169,55 @@ class _TemperatureBlockState extends State<TemperatureBlock> {
],
),
),
// 操作行
Container(
width: double.infinity,
margin: EdgeInsets.only(top: 14.h),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Row(
children: [
Icon(
Icons.access_time,
size: 22.sp,
color: Color.fromRGBO(100, 116, 139, 1.0),
),
SizedBox(width: 8.w),
Text(
'已等待${widget.controller.getWaitTime()}·',
style: TextStyle(
fontSize: 24.sp,
color: Color.fromRGBO(100, 116, 139, 1.0),
),
),
Text(
widget.controller.getAlarmLevel(),
style: TextStyle(
fontSize: 24.sp,
color: Color.fromRGBO(255, 112, 112, 1.0),
// fontWeight: FontWeight.bold,
),
SizedBox(height: 16.h),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Icon(
Icons.access_time,
color: const Color.fromRGBO(100, 116, 139, 1.0),
size: 24.sp,
),
SizedBox(width: 8.w),
Text(
'已等待$waitTime·',
style: TextStyle(
fontSize: 24.sp,
color: const Color.fromRGBO(100, 116, 139, 1.0),
),
],
),
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Color.fromRGBO(73, 149, 234, 1.0),
minimumSize: Size(149.w, 60.h),
padding: EdgeInsets.symmetric(horizontal: 24.w),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16.r),
),
Text(
alarmLevel,
style: TextStyle(
fontSize: 24.sp,
fontWeight: FontWeight.bold,
color: const Color.fromRGBO(255, 77, 79, 1.0),
),
),
onPressed: () {
print("立即处理");
},
],
),
GestureDetector(
onTap: () {
context.pushRoute(const AbnormalDetailRoute());
},
child: Container(
padding: EdgeInsets.symmetric(horizontal: 32.w, vertical: 20.h),
decoration: BoxDecoration(
color: const Color.fromRGBO(66, 165, 245, 1.0),
borderRadius: BorderRadius.circular(16.r),
),
child: Text(
"立即处理",
'立即处理',
style: TextStyle(
fontSize: 24.sp,
color: Colors.white,
fontWeight: FontWeight.w200,
),
),
),
],
),
),
],
),
],
),
......
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:smart_hotel_app/views/home/index/controller.dart';
import 'package:smart_hotel_app/routes/app_router.gr.dart';
class VoltageOperateBlock extends StatefulWidget {
final HomeIndexController controller;
const VoltageOperateBlock({super.key, required this.controller});
class VoltageOperateBlock extends StatelessWidget {
final String voltageAlarmTitle;
final String voltageAlarmLocation;
final String voltageValue;
final String normalVoltage;
final String voltageWaitTime;
final String voltageAlarmLevel;
@override
State<VoltageOperateBlock> createState() => _VoltageOperateBlockState();
}
const VoltageOperateBlock({
super.key,
required this.voltageAlarmTitle,
required this.voltageAlarmLocation,
required this.voltageValue,
required this.normalVoltage,
required this.voltageWaitTime,
required this.voltageAlarmLevel,
});
class _VoltageOperateBlockState extends State<VoltageOperateBlock> {
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
// margin: EdgeInsets.symmetric(horizontal: 14.w, vertical: 24.h),
padding: EdgeInsets.all(20.w),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20.r),
......@@ -25,7 +33,6 @@ class _VoltageOperateBlockState extends State<VoltageOperateBlock> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 描述信息
Container(
width: double.infinity,
margin: EdgeInsets.only(bottom: 16.h),
......@@ -36,12 +43,17 @@ class _VoltageOperateBlockState extends State<VoltageOperateBlock> {
width: 64.w,
height: 66.h,
decoration: BoxDecoration(
color: Color.fromRGBO(235, 244, 255, 1.0),
color: const Color.fromRGBO(235, 244, 255, 1.0),
borderRadius: BorderRadius.circular(12.r),
border: Border.all(
color: const Color.fromRGBO(66, 165, 245, 1.0),
width: 2.w,
style: BorderStyle.solid,
),
),
child: Icon(
Icons.thermostat_outlined,
color: Color.fromRGBO(66, 165, 245, 1.0),
color: const Color.fromRGBO(66, 165, 245, 1.0),
size: 32.sp,
),
),
......@@ -51,7 +63,7 @@ class _VoltageOperateBlockState extends State<VoltageOperateBlock> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
widget.controller.getVoltageAlarmTitle(),
voltageAlarmTitle,
style: TextStyle(
fontSize: 28.sp,
fontWeight: FontWeight.bold,
......@@ -60,88 +72,82 @@ class _VoltageOperateBlockState extends State<VoltageOperateBlock> {
),
SizedBox(height: 4.h),
Text(
widget.controller.getVoltageAlarmLocation(),
voltageAlarmLocation,
style: TextStyle(
fontSize: 24.sp,
color: Color.fromRGBO(100, 116, 139, 1.0),
color: const Color.fromRGBO(100, 116, 139, 1.0),
),
),
],
),
),
Text(
widget.controller.getVoltageAlarmLevel(),
voltageAlarmLevel,
style: TextStyle(
fontSize: 24.sp,
fontWeight: FontWeight.bold,
color: Color.fromRGBO(255, 204, 21, 1.0),
color: const Color.fromRGBO(255, 204, 21, 1.0),
),
),
],
),
),
// 数据行
Container(
width: double.infinity,
margin: EdgeInsets.only(left: 80.w,bottom: 10.h),
margin: EdgeInsets.only(left: 80.w, bottom: 20.h),
child: Text(
'电压${widget.controller.getVoltageValue()}V (正常≤${widget.controller.getNormalVoltage()}V)·已等待${widget.controller.getVoltageWaitTime()}',
'电压${voltageValue}V (正常≤${normalVoltage}V)·已等待$voltageWaitTime',
style: TextStyle(
fontSize: 24.sp,
color: Color.fromRGBO(100, 116, 139, 1.0),
color: const Color.fromRGBO(100, 116, 139, 1.0),
),
),
),
// 操作行
Container(
width: double.infinity,
child: Row(
children: [
Expanded(
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Color.fromRGBO(73, 149, 234, 1.0),
minimumSize: Size(0, 60.h),
padding: EdgeInsets.symmetric(vertical: 16.h),
shape: RoundedRectangleBorder(
child: GestureDetector(
onTap: () {},
child: Container(
height: 88.h,
decoration: BoxDecoration(
color: const Color.fromRGBO(66, 165, 245, 1.0),
borderRadius: BorderRadius.circular(16.r),
),
),
onPressed: () {
print("确认处理");
},
child: Text(
"确认处理",
style: TextStyle(
fontSize: 24.sp,
color: Colors.white,
fontWeight: FontWeight.w200,
child: Center(
child: Text(
'确认处理',
style: TextStyle(
fontSize: 28.sp,
color: Colors.white,
),
),
),
),
),
),
SizedBox(width: 16.w),
SizedBox(width: 20.w),
Expanded(
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Color.fromRGBO(229, 241, 255, 1.0),
minimumSize: Size(0, 60.h),
padding: EdgeInsets.symmetric(vertical: 16.h),
shape: RoundedRectangleBorder(
child: GestureDetector(
onTap: () {
context.pushRoute(const AbnormalDetailRoute());
},
child: Container(
height: 88.h,
decoration: BoxDecoration(
color: const Color.fromRGBO(235, 244, 255, 1.0),
borderRadius: BorderRadius.circular(16.r),
),
),
onPressed: () {
widget.controller.handleToAbnormalDetail();
},
child: Text(
"查看详情",
style: TextStyle(
fontSize: 24.sp,
color: Color.fromRGBO(100, 116, 139, 1.0),
fontWeight: FontWeight.bold,
child: Center(
child: Text(
'查看详情',
style: TextStyle(
fontSize: 28.sp,
color: const Color.fromRGBO(100, 116, 139, 1.0),
),
),
),
),
),
......
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:smart_hotel_app/views/home/inspection/cubit/inspection_state.dart';
class InspectionCubit extends Cubit<InspectionState> {
static const List<String> allRooms = [
'客厅',
'主卧',
'玄关',
'厨房',
'书房',
'阳台',
];
static final List<InspectionDevice> allDevices = [
InspectionDevice(
id: '1',
icon: Icons.wifi,
name: '客厅主控网关',
type: '智能网关',
location: '客厅电视柜',
status: '正常',
time: '2024-01-15 14:30:00',
statusColor: const Color.fromRGBO(26, 188, 156, 1.0),
),
InspectionDevice(
id: '2',
icon: Icons.thermostat,
name: '温湿度传感器-客厅',
type: '传感器',
location: '客厅角落',
status: '正常',
time: '2024-01-15 14:25:00',
statusColor: const Color.fromRGBO(26, 188, 156, 1.0),
),
InspectionDevice(
id: '3',
icon: Icons.videocam,
name: '智能摄像头-门口',
type: '摄像头',
location: '入户门口',
status: '告警',
time: '2024-01-15 10:00:00',
statusColor: const Color.fromRGBO(255, 77, 79, 1.0),
),
InspectionDevice(
id: '4',
icon: Icons.light,
name: '智能吸顶灯-主卧',
type: '传感器',
location: '主卧天花板',
status: '正常',
time: '2024-01-15 14:25:00',
statusColor: const Color.fromRGBO(26, 188, 156, 1.0),
),
InspectionDevice(
id: '5',
icon: Icons.thermostat,
name: '温湿度传感器-主卧',
type: '传感器',
location: '主卧角落',
status: '正常',
time: '2024-01-15 14:25:00',
statusColor: const Color.fromRGBO(26, 188, 156, 1.0),
),
InspectionDevice(
id: '6',
icon: Icons.light,
name: '玄关灯-智能',
type: '智能灯',
location: '玄关',
status: '开启',
time: '2024-01-15 14:20:00',
statusColor: const Color.fromRGBO(26, 188, 156, 1.0),
),
InspectionDevice(
id: '7',
icon: Icons.sensor_door,
name: '门窗传感器-阳台',
type: '传感器',
location: '阳台门',
status: '离线',
time: '2024-01-15 12:00:00',
statusColor: const Color.fromRGBO(102, 102, 102, 1.0),
),
InspectionDevice(
id: '8',
icon: Icons.kitchen,
name: '智能插座-厨房',
type: '插座',
location: '厨房台面',
status: '关闭',
time: '2024-01-15 14:15:00',
statusColor: const Color.fromRGBO(100, 116, 139, 1.0),
),
InspectionDevice(
id: '9',
icon: Icons.smoke_free,
name: '烟雾报警器-厨房',
type: '报警器',
location: '厨房天花板',
status: '正常',
time: '2024-01-15 14:30:00',
statusColor: const Color.fromRGBO(26, 188, 156, 1.0),
),
InspectionDevice(
id: '10',
icon: Icons.desk,
name: '智能台灯-书房',
type: '智能灯',
location: '书房书桌',
status: '关闭',
time: '2024-01-15 14:10:00',
statusColor: const Color.fromRGBO(100, 116, 139, 1.0),
),
InspectionDevice(
id: '11',
icon: Icons.ac_unit,
name: '智能空调-客厅',
type: '空调',
location: '客厅',
status: '正常',
time: '2024-01-15 14:30:00',
statusColor: const Color.fromRGBO(26, 188, 156, 1.0),
),
];
InspectionCubit() : super(const InspectionState()) {
initData();
}
void initData() {
emit(state.copyWith(deviceList: _filterDevices()));
}
void selectTab(DeviceStatusTab tab) {
emit(state.copyWith(selectedTab: tab, deviceList: _filterDevices(selectedTab: tab)));
}
void toggleRoom(String room) {
final List<String> newSelectedRooms = List.from(state.selectedRooms);
if (newSelectedRooms.contains(room)) {
newSelectedRooms.remove(room);
} else {
newSelectedRooms.add(room);
}
emit(state.copyWith(
selectedRooms: newSelectedRooms,
deviceList: _filterDevices(selectedRooms: newSelectedRooms),
));
}
List<InspectionDevice> _filterDevices({
DeviceStatusTab? selectedTab,
List<String>? selectedRooms,
}) {
DeviceStatusTab tab = selectedTab ?? state.selectedTab;
List<String> rooms = selectedRooms ?? state.selectedRooms;
List<InspectionDevice> filtered = List.from(allDevices);
if (rooms.isNotEmpty) {
filtered = filtered.where((device) {
return rooms.any((room) => device.location.contains(room) || device.name.contains(room));
}).toList();
}
if (tab != DeviceStatusTab.total) {
filtered = filtered.where((device) {
switch (tab) {
case DeviceStatusTab.on:
return device.status == '正常' || device.status == '开启';
case DeviceStatusTab.off:
return device.status == '关闭';
case DeviceStatusTab.offline:
return device.status == '离线';
case DeviceStatusTab.alarm:
return device.status == '告警';
case DeviceStatusTab.fault:
return device.status == '故障';
default:
return true;
}
}).toList();
}
return filtered;
}
}
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
enum DeviceStatusTab { total, on, off, offline, alarm, fault }
class InspectionDevice {
final String id;
final IconData icon;
final String name;
final String type;
final String location;
final String status;
final String time;
final Color statusColor;
const InspectionDevice({
required this.id,
required this.icon,
required this.name,
required this.type,
required this.location,
required this.status,
required this.time,
required this.statusColor,
});
}
class InspectionState extends Equatable {
final DeviceStatusTab selectedTab;
final List<String> selectedRooms;
final List<InspectionDevice> deviceList;
final Map<DeviceStatusTab, int> tabCounts;
const InspectionState({
this.selectedTab = DeviceStatusTab.total,
this.selectedRooms = const ['客厅'],
this.deviceList = const [],
this.tabCounts = const {
DeviceStatusTab.total: 11,
DeviceStatusTab.on: 7,
DeviceStatusTab.off: 2,
DeviceStatusTab.offline: 1,
DeviceStatusTab.alarm: 1,
DeviceStatusTab.fault: 0,
},
});
InspectionState copyWith({
DeviceStatusTab? selectedTab,
List<String>? selectedRooms,
List<InspectionDevice>? deviceList,
Map<DeviceStatusTab, int>? tabCounts,
}) {
return InspectionState(
selectedTab: selectedTab ?? this.selectedTab,
selectedRooms: selectedRooms ?? this.selectedRooms,
deviceList: deviceList ?? this.deviceList,
tabCounts: tabCounts ?? this.tabCounts,
);
}
@override
List<Object?> get props => [
selectedTab,
selectedRooms,
deviceList,
tabCounts,
];
}
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:smart_hotel_app/views/home/inspection/cubit/inspection_cubit.dart';
import 'package:smart_hotel_app/views/home/inspection/cubit/inspection_state.dart';
import 'package:smart_hotel_app/views/home/inspection/widget/filter_panel.dart';
import 'package:smart_hotel_app/views/home/inspection/widget/device_list.dart';
@RoutePage()
class InspectionDetailView extends StatelessWidget {
const InspectionDetailView({super.key});
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => InspectionCubit(),
child: Scaffold(
backgroundColor: const Color.fromRGBO(242, 243, 245, 1),
appBar: AppBar(
backgroundColor: Colors.white,
elevation: 0,
leading: IconButton(
icon: const Icon(Icons.arrow_back_ios,
color: Color.fromRGBO(100, 116, 139, 1)),
onPressed: () {
context.popRoute();
},
),
title: Text(
'设备巡检',
style: TextStyle(
color: const Color.fromRGBO(10, 13, 20, 1),
fontSize: 32.sp,
fontWeight: FontWeight.w200,
),
),
centerTitle: true,
),
body: SingleChildScrollView(
padding: EdgeInsets.symmetric(horizontal: 28.w, vertical: 20.h),
child: BlocBuilder<InspectionCubit, InspectionState>(
builder: (context, state) {
final cubit = context.read<InspectionCubit>();
return Column(
children: [
FilterPanel(
selectedTab: state.selectedTab,
tabCounts: state.tabCounts,
onTabSelected: (tab) => cubit.selectTab(tab),
selectedRooms: state.selectedRooms,
onRoomToggle: (room) => cubit.toggleRoom(room),
),
SizedBox(height: 20.h),
DeviceList(deviceList: state.deviceList),
SizedBox(height: 30.h),
],
);
},
),
),
),
);
}
}
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:smart_hotel_app/routes/app_router.gr.dart';
import 'package:smart_hotel_app/views/home/inspection/cubit/inspection_state.dart';
import 'package:smart_hotel_app/views/home/inspection/widget/device_list_item.dart';
class DeviceList extends StatelessWidget {
final List<InspectionDevice> deviceList;
const DeviceList({
super.key,
required this.deviceList,
});
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'设备列表 (${deviceList.length})',
style: TextStyle(
fontSize: 28.sp,
fontWeight: FontWeight.bold,
color: const Color.fromRGBO(10, 13, 20, 1.0),
),
),
SizedBox(height: 12.h),
ListView.separated(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: deviceList.length,
separatorBuilder: (context, index) => SizedBox(height: 12.h),
itemBuilder: (context, index) {
return DeviceListItem(
device: deviceList[index],
onTap: () {
context.pushRoute(
InspectionDeviceRoute(deviceId: deviceList[index].id),
);
},
);
},
),
],
);
}
}
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:smart_hotel_app/views/home/inspection/cubit/inspection_state.dart';
class DeviceListItem extends StatelessWidget {
final InspectionDevice device;
final VoidCallback? onTap;
const DeviceListItem({
super.key,
required this.device,
this.onTap,
});
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onTap,
child: Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20.r),
),
padding: EdgeInsets.symmetric(horizontal: 20.w, vertical: 16.h),
child: Row(
children: [
Container(
width: 64.w,
height: 66.h,
decoration: BoxDecoration(
color: const Color.fromRGBO(235, 244, 255, 1.0),
borderRadius: BorderRadius.circular(12.r),
border: Border.all(
color: const Color.fromRGBO(66, 165, 245, 1.0),
width: 1.w,
style: BorderStyle.solid,
),
),
child: Icon(
device.icon,
color: const Color.fromRGBO(66, 165, 245, 1.0),
size: 32.sp,
),
),
SizedBox(width: 16.w),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(
device.name,
style: TextStyle(
fontSize: 28.sp,
fontWeight: FontWeight.bold,
color: const Color.fromRGBO(10, 13, 20, 1.0),
),
),
SizedBox(width: 8.w),
Text(
device.status,
style: TextStyle(
fontSize: 24.sp,
fontWeight: FontWeight.bold,
color: device.statusColor,
),
),
],
),
SizedBox(height: 4.h),
Text(
'${device.type} | ${device.location}',
style: TextStyle(
fontSize: 24.sp,
color: const Color.fromRGBO(100, 116, 139, 1.0),
),
),
SizedBox(height: 4.h),
Row(
children: [
Icon(
Icons.access_time,
color: const Color.fromRGBO(100, 116, 139, 1.0),
size: 20.sp,
),
SizedBox(width: 4.w),
Text(
device.time,
style: TextStyle(
fontSize: 20.sp,
color: const Color.fromRGBO(100, 116, 139, 1.0),
),
),
],
),
],
),
),
Container(
width: 48.w,
height: 48.h,
decoration: BoxDecoration(
color: const Color.fromRGBO(242, 243, 245, 1.0),
borderRadius: BorderRadius.circular(8.r),
),
child: Icon(
Icons.chevron_right,
color: const Color.fromRGBO(100, 116, 139, 1.0),
size: 24.sp,
),
),
],
),
),
);
}
}
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import '../cubit/inspection_state.dart';
class FilterPanel extends StatelessWidget {
final DeviceStatusTab selectedTab;
final Map<DeviceStatusTab, int> tabCounts;
final Function(DeviceStatusTab) onTabSelected;
final List<String> selectedRooms;
final Function(String) onRoomToggle;
const FilterPanel({
super.key,
required this.selectedTab,
required this.tabCounts,
required this.onTabSelected,
required this.selectedRooms,
required this.onRoomToggle,
});
static const List<String> allRooms = [
'客厅',
'主卧',
'玄关',
'厨房',
'书房',
'阳台',
];
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20.r),
),
padding: EdgeInsets.symmetric(horizontal: 20.w, vertical: 24.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildStatusTabs(),
SizedBox(height: 32.h),
_buildRoomSelector(),
],
),
);
}
Widget _buildStatusTabs() {
return Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: DeviceStatusTab.values.map((tab) {
final isSelected = selectedTab == tab;
final count = tabCounts[tab] ?? 0;
return Expanded(
child: GestureDetector(
onTap: () => onTabSelected(tab),
child: Stack(
clipBehavior: Clip.none,
alignment: Alignment.center,
children: [
Padding(
padding: EdgeInsets.only(top: 8.h),
child: Text(
_getTabLabel(tab),
style: TextStyle(
fontSize: 36.sp,
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
color: isSelected
? const Color.fromRGBO(66, 165, 245, 1.0)
: const Color.fromRGBO(100, 116, 139, 1.0),
),
),
),
Positioned(
top: 0,
right: -8.w,
child: Container(
width: 56.w,
height: 56.h,
decoration: BoxDecoration(
color: _getTabColor(tab),
borderRadius: BorderRadius.circular(28.r),
),
child: Center(
child: Text(
count.toString(),
style: TextStyle(
fontSize: 24.sp,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
),
),
),
],
),
),
);
}).toList(),
),
SizedBox(height: 12.h),
Container(
height: 4.h,
width: double.infinity,
color: const Color.fromRGBO(242, 243, 245, 1),
child: Stack(
children: [
LayoutBuilder(
builder: (context, constraints) {
final segmentWidth = constraints.maxWidth / DeviceStatusTab.values.length;
final selectedIndex = DeviceStatusTab.values.indexOf(selectedTab);
return AnimatedPositioned(
duration: const Duration(milliseconds: 200),
left: selectedIndex * segmentWidth,
width: segmentWidth,
child: Container(
height: 4.h,
color: const Color.fromRGBO(66, 165, 245, 1.0),
),
);
},
),
],
),
),
],
);
}
Widget _buildRoomSelector() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'全部区域',
style: TextStyle(
fontSize: 28.sp,
fontWeight: FontWeight.bold,
color: const Color.fromRGBO(100, 116, 139, 1.0),
),
),
SizedBox(height: 16.h),
Wrap(
spacing: 5.w,
runSpacing: 12.h,
children: allRooms.map((room) {
final isSelected = selectedRooms.contains(room);
return GestureDetector(
onTap: () => onRoomToggle(room),
child: Container(
width: 120.w,
padding: EdgeInsets.symmetric(horizontal: 28.w, vertical: 12.h),
decoration: BoxDecoration(
color: isSelected
? const Color.fromRGBO(235, 244, 255, 1.0)
: Colors.white,
borderRadius: BorderRadius.circular(12.r),
border: Border.all(
color: isSelected
? const Color.fromRGBO(66, 165, 245, 1.0)
: const Color.fromRGBO(200, 208, 220, 1.0),
width: 2.w,
),
),
child: Text(
room,
style: TextStyle(
fontSize: 28.sp,
color: isSelected
? const Color.fromRGBO(66, 165, 245, 1.0)
: const Color.fromRGBO(100, 116, 139, 1.0),
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
),
),
),
);
}).toList(),
),
],
);
}
String _getTabLabel(DeviceStatusTab tab) {
switch (tab) {
case DeviceStatusTab.total:
return '总计';
case DeviceStatusTab.on:
return '开启';
case DeviceStatusTab.off:
return '关闭';
case DeviceStatusTab.offline:
return '离线';
case DeviceStatusTab.alarm:
return '告警';
case DeviceStatusTab.fault:
return '故障';
}
}
Color _getTabColor(DeviceStatusTab tab) {
switch (tab) {
case DeviceStatusTab.total:
return const Color.fromRGBO(66, 165, 245, 1.0);
case DeviceStatusTab.on:
return const Color.fromRGBO(26, 188, 156, 1.0);
case DeviceStatusTab.off:
return const Color.fromRGBO(200, 208, 220, 1.0);
case DeviceStatusTab.offline:
return const Color.fromRGBO(100, 116, 139, 1.0);
case DeviceStatusTab.alarm:
return const Color.fromRGBO(247, 184, 75, 1.0);
case DeviceStatusTab.fault:
return const Color.fromRGBO(239, 68, 68, 1.0);
}
}
}
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'inspection_device_state.dart';
class InspectionDeviceCubit extends Cubit<InspectionDeviceState> {
InspectionDeviceCubit({required String deviceId})
: super(InspectionDeviceState(
deviceId: deviceId,
deviceName: '',
deviceType: '',
deviceStatus: '',
deviceModel: '',
installLocation: '',
area: '',
responsiblePerson: '',
lastInspectionTime: '',
inspectionHistory: [],
inspectionItems: [],
icon: Icons.devices,
)) {
_loadDeviceData(deviceId);
}
void _loadDeviceData(String deviceId) {
// TODO: 从API或本地数据库加载设备数据
// 这里使用模拟数据
final mockState = InspectionDeviceState(
deviceId: deviceId,
deviceName: '客厅主控网关',
deviceType: '智能网关',
deviceStatus: '正常',
deviceModel: 'SmartHub Pro V2',
installLocation: '客厅电视柜',
area: '客厅',
responsiblePerson: '张师傅',
lastInspectionTime: '2024-01-15 14:30:00',
inspectionHistory: [
InspectionHistoryItem(
inspectorName: '张师傅',
inspectionTime: '2024-01-15 14:30:00',
result: '通过',
remark: '设备运行正常,各项指标正常',
),
],
inspectionItems: [
InspectionItem(
name: '电源状态',
value: '正常',
result: '通过',
),
InspectionItem(
name: '网络连接',
value: '稳定',
result: '通过',
),
InspectionItem(
name: 'CPU使用率',
value: '23%',
result: '通过',
),
InspectionItem(
name: '内存占用',
value: '45%',
result: '通过',
),
],
icon: Icons.wifi,
);
emit(mockState);
}
}
import 'package:flutter/material.dart';
import 'package:equatable/equatable.dart';
class InspectionDeviceState extends Equatable {
final String deviceId;
final String deviceName;
final String deviceType;
final String deviceStatus;
final String deviceModel;
final String installLocation;
final String area;
final String responsiblePerson;
final String lastInspectionTime;
final List<InspectionHistoryItem> inspectionHistory;
final List<InspectionItem> inspectionItems;
final IconData icon;
const InspectionDeviceState({
required this.deviceId,
required this.deviceName,
required this.deviceType,
required this.deviceStatus,
required this.deviceModel,
required this.installLocation,
required this.area,
required this.responsiblePerson,
required this.lastInspectionTime,
required this.inspectionHistory,
required this.inspectionItems,
required this.icon,
});
InspectionDeviceState copyWith({
String? deviceId,
String? deviceName,
String? deviceType,
String? deviceStatus,
String? deviceModel,
String? installLocation,
String? area,
String? responsiblePerson,
String? lastInspectionTime,
List<InspectionHistoryItem>? inspectionHistory,
List<InspectionItem>? inspectionItems,
IconData? icon,
}) {
return InspectionDeviceState(
deviceId: deviceId ?? this.deviceId,
deviceName: deviceName ?? this.deviceName,
deviceType: deviceType ?? this.deviceType,
deviceStatus: deviceStatus ?? this.deviceStatus,
deviceModel: deviceModel ?? this.deviceModel,
installLocation: installLocation ?? this.installLocation,
area: area ?? this.area,
responsiblePerson: responsiblePerson ?? this.responsiblePerson,
lastInspectionTime: lastInspectionTime ?? this.lastInspectionTime,
inspectionHistory: inspectionHistory ?? this.inspectionHistory,
inspectionItems: inspectionItems ?? this.inspectionItems,
icon: icon ?? this.icon,
);
}
@override
List<Object?> get props => [
deviceId,
deviceName,
deviceType,
deviceStatus,
deviceModel,
installLocation,
area,
responsiblePerson,
lastInspectionTime,
inspectionHistory,
inspectionItems,
icon,
];
}
class InspectionHistoryItem extends Equatable {
final String inspectorName;
final String inspectionTime;
final String result;
final String remark;
const InspectionHistoryItem({
required this.inspectorName,
required this.inspectionTime,
required this.result,
required this.remark,
});
@override
List<Object?> get props => [
inspectorName,
inspectionTime,
result,
remark,
];
}
class InspectionItem extends Equatable {
final String name;
final String value;
final String result;
const InspectionItem({
required this.name,
required this.value,
required this.result,
});
@override
List<Object?> get props => [
name,
value,
result,
];
}
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'cubit/inspection_device_cubit.dart';
import 'cubit/inspection_device_state.dart';
import 'widget/device_overview_card.dart';
import 'widget/inspection_history_card.dart';
import 'widget/inspection_items_card.dart';
import 'widget/start_inspection_button.dart';
@RoutePage()
class InspectionDeviceView extends StatelessWidget {
final String deviceId;
const InspectionDeviceView({super.key, @pathParam required this.deviceId});
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => InspectionDeviceCubit(deviceId: deviceId),
child: Scaffold(
backgroundColor: const Color.fromRGBO(242, 243, 245, 1),
appBar: AppBar(
backgroundColor: Colors.white,
elevation: 0,
leading: IconButton(
icon: const Icon(Icons.arrow_back_ios,
color: Color.fromRGBO(100, 116, 139, 1)),
onPressed: () {
context.popRoute();
},
),
title: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'设备拓扑图',
style: TextStyle(
color: const Color.fromRGBO(100, 116, 139, 1),
fontSize: 32.sp,
fontWeight: FontWeight.w200,
),
),
Icon(
Icons.arrow_forward,
color: const Color.fromRGBO(100, 116, 139, 1),
),
],
),
centerTitle: true,
),
body: SingleChildScrollView(
padding: EdgeInsets.symmetric(horizontal: 28.w, vertical: 20.h),
child: BlocBuilder<InspectionDeviceCubit, InspectionDeviceState>(
builder: (context, state) {
return Column(
children: [
DeviceOverviewCard(
deviceName: state.deviceName,
deviceType: state.deviceType,
deviceStatus: state.deviceStatus,
deviceModel: state.deviceModel,
installLocation: state.installLocation,
area: state.area,
responsiblePerson: state.responsiblePerson,
lastInspectionTime: state.lastInspectionTime,
icon: state.icon,
),
SizedBox(height: 20.h),
InspectionHistoryCard(
historyList: state.inspectionHistory,
),
SizedBox(height: 20.h),
InspectionItemsCard(
items: state.inspectionItems,
),
SizedBox(height: 30.h),
StartInspectionButton(deviceId: state.deviceId),
SizedBox(height: 30.h),
],
);
},
),
),
),
);
}
}
\ No newline at end of file
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
class DeviceOverviewCard extends StatelessWidget {
final String deviceName;
final String deviceType;
final String deviceStatus;
final String deviceModel;
final String installLocation;
final String area;
final String responsiblePerson;
final String lastInspectionTime;
final IconData icon;
const DeviceOverviewCard({
super.key,
required this.deviceName,
required this.deviceType,
required this.deviceStatus,
required this.deviceModel,
required this.installLocation,
required this.area,
required this.responsiblePerson,
required this.lastInspectionTime,
required this.icon,
});
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20.r),
),
padding: EdgeInsets.symmetric(horizontal: 20.w, vertical: 24.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
width: 100.w,
height: 100.h,
decoration: BoxDecoration(
color: const Color.fromRGBO(235, 244, 255, 1.0),
shape: BoxShape.circle,
),
child: Icon(
icon,
color: const Color.fromRGBO(66, 165, 245, 1.0),
size: 50.sp,
),
),
SizedBox(width: 20.w),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
deviceName,
style: TextStyle(
fontSize: 36.sp,
fontWeight: FontWeight.bold,
color: const Color.fromRGBO(10, 13, 20, 1.0),
),
),
SizedBox(height: 12.h),
Text(
deviceType,
style: TextStyle(
fontSize: 28.sp,
color: const Color.fromRGBO(100, 116, 139, 1.0),
),
),
SizedBox(height: 16.h),
Container(
padding: EdgeInsets.symmetric(horizontal: 24.w, vertical: 10.h),
decoration: BoxDecoration(
color: const Color.fromRGBO(225, 245, 238, 1.0),
borderRadius: BorderRadius.circular(20.r),
),
child: Text(
deviceStatus,
style: TextStyle(
fontSize: 28.sp,
color: const Color.fromRGBO(26, 188, 156, 1.0),
fontWeight: FontWeight.bold,
),
),
),
],
),
),
],
),
SizedBox(height: 32.h),
Text(
'基本信息',
style: TextStyle(
fontSize: 32.sp,
fontWeight: FontWeight.bold,
color: const Color.fromRGBO(10, 13, 20, 1.0),
),
),
SizedBox(height: 24.h),
_buildInfoItem('设备型号', deviceModel),
SizedBox(height: 24.h),
_buildInfoItem('安装位置', installLocation),
SizedBox(height: 24.h),
_buildInfoItem('所属区域', area),
SizedBox(height: 24.h),
_buildInfoItem('责任人', responsiblePerson),
SizedBox(height: 24.h),
_buildInfoItem('最后巡检', lastInspectionTime),
],
),
);
}
Widget _buildInfoItem(String label, String value) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
label,
style: TextStyle(
fontSize: 28.sp,
color: const Color.fromRGBO(100, 116, 139, 1.0),
),
),
Text(
value,
style: TextStyle(
fontSize: 28.sp,
color: const Color.fromRGBO(100, 116, 139, 1.0),
),
),
],
);
}
}
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import '../cubit/inspection_device_state.dart';
class InspectionHistoryCard extends StatelessWidget {
final List<InspectionHistoryItem> historyList;
const InspectionHistoryCard({
super.key,
required this.historyList,
});
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20.r),
),
padding: EdgeInsets.symmetric(horizontal: 20.w, vertical: 16.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'巡检历史 (${historyList.length})',
style: TextStyle(
fontSize: 26.sp,
fontWeight: FontWeight.bold,
color: const Color.fromRGBO(10, 13, 20, 1.0),
),
),
SizedBox(height: 20.h),
...historyList.map((item) => _buildHistoryItem(item)).toList(),
],
),
);
}
Widget _buildHistoryItem(InspectionHistoryItem item) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Container(
width: 60.w,
height: 60.h,
decoration: BoxDecoration(
color: const Color.fromRGBO(242, 243, 245, 1.0),
borderRadius: BorderRadius.circular(8.r),
),
child: Icon(
Icons.person,
color: const Color.fromRGBO(100, 116, 139, 1.0),
size: 32.sp,
),
),
SizedBox(width: 12.w),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
item.inspectorName,
style: TextStyle(
fontSize: 26.sp,
fontWeight: FontWeight.bold,
color: const Color.fromRGBO(10, 13, 20, 1.0),
),
),
SizedBox(height: 4.h),
Row(
children: [
Icon(
Icons.access_time,
color: const Color.fromRGBO(100, 116, 139, 1.0),
size: 20.sp,
),
SizedBox(width: 4.w),
Text(
item.inspectionTime,
style: TextStyle(
fontSize: 22.sp,
color: const Color.fromRGBO(100, 116, 139, 1.0),
),
),
],
),
],
),
],
),
Container(
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 6.h),
decoration: BoxDecoration(
color: const Color.fromRGBO(225, 245, 238, 1.0),
borderRadius: BorderRadius.circular(10.r),
),
child: Row(
children: [
Icon(
Icons.check,
color: const Color.fromRGBO(26, 188, 156, 1.0),
size: 20.sp,
),
SizedBox(width: 4.w),
Text(
item.result,
style: TextStyle(
fontSize: 22.sp,
color: const Color.fromRGBO(26, 188, 156, 1.0),
fontWeight: FontWeight.bold,
),
),
],
),
),
],
),
SizedBox(height: 16.h),
Container(
width: double.infinity,
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 12.h),
decoration: BoxDecoration(
color: const Color.fromRGBO(242, 243, 245, 1.0),
borderRadius: BorderRadius.circular(12.r),
),
child: Text(
item.remark,
style: TextStyle(
fontSize: 22.sp,
color: const Color.fromRGBO(100, 116, 139, 1.0),
),
),
),
SizedBox(height: 20.h),
],
);
}
}
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import '../cubit/inspection_device_state.dart';
class InspectionItemsCard extends StatelessWidget {
final List<InspectionItem> items;
const InspectionItemsCard({
super.key,
required this.items,
});
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20.r),
),
padding: EdgeInsets.symmetric(horizontal: 20.w, vertical: 16.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'巡检项目',
style: TextStyle(
fontSize: 26.sp,
fontWeight: FontWeight.bold,
color: const Color.fromRGBO(10, 13, 20, 1.0),
),
),
SizedBox(height: 20.h),
...items.map((item) => _buildInspectionItem(item)).toList(),
],
),
);
}
Widget _buildInspectionItem(InspectionItem item) {
return Padding(
padding: EdgeInsets.only(bottom: 20.h),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
item.name,
style: TextStyle(
fontSize: 26.sp,
fontWeight: FontWeight.bold,
color: const Color.fromRGBO(10, 13, 20, 1.0),
),
),
),
Row(
children: [
Text(
item.value,
style: TextStyle(
fontSize: 24.sp,
color: const Color.fromRGBO(100, 116, 139, 1.0),
),
),
SizedBox(width: 12.w),
Container(
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 6.h),
decoration: BoxDecoration(
color: const Color.fromRGBO(225, 245, 238, 1.0),
borderRadius: BorderRadius.circular(10.r),
),
child: Row(
children: [
Icon(
Icons.check,
color: const Color.fromRGBO(26, 188, 156, 1.0),
size: 20.sp,
),
SizedBox(width: 4.w),
Text(
item.result,
style: TextStyle(
fontSize: 22.sp,
color: const Color.fromRGBO(26, 188, 156, 1.0),
fontWeight: FontWeight.bold,
),
),
],
),
),
],
),
],
),
);
}
}
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:smart_hotel_app/routes/app_router.gr.dart';
class StartInspectionButton extends StatelessWidget {
final String deviceId;
const StartInspectionButton({super.key, required this.deviceId});
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
context.pushRoute(
InspectionHistoryRoute(deviceId: deviceId),
);
},
child: Container(
width: double.infinity,
height: 88.h,
decoration: BoxDecoration(
color: const Color.fromRGBO(66, 165, 245, 1.0),
borderRadius: BorderRadius.circular(24.r),
),
child: Center(
child: Text(
'开始巡检',
style: TextStyle(
color: Colors.white,
fontSize: 32.sp,
fontWeight: FontWeight.bold,
),
),
),
),
);
}
}
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'inspection_history_state.dart';
class InspectionHistoryCubit extends Cubit<InspectionHistoryState> {
InspectionHistoryCubit({required String deviceId})
: super(InspectionHistoryState(
deviceId: deviceId,
totalCount: 0,
passCount: 0,
warningCount: 0,
failCount: 0,
records: [],
)) {
_loadHistoryData(deviceId);
}
void _loadHistoryData(String deviceId) {
// TODO: 从API或本地数据库加载巡检历史数据
// 这里使用模拟数据
final mockRecords = [
InspectionRecord(
deviceName: '客厅主控网关',
inspectorName: '张师傅',
inspectionTime: '2024-01-15 14:30:00',
result: '通过',
remark: '设备运行正常,各项指标正常',
items: [
const InspectionItemRecord(name: '电源状态', result: '通过'),
const InspectionItemRecord(name: '网络连接', result: '通过'),
const InspectionItemRecord(name: 'CPU使用率', result: '通过'),
const InspectionItemRecord(name: '内存占用', result: '通过'),
],
icon: Icons.wifi,
),
InspectionRecord(
deviceName: '智能摄像头-门口',
inspectorName: '王师傅',
inspectionTime: '2024-01-15 10:00:00',
result: '警告',
remark: '画面有轻微噪点,可能需要清洁镜头',
items: [
const InspectionItemRecord(name: '画面清晰度', result: '警告'),
const InspectionItemRecord(name: '夜视功能', result: '通过'),
const InspectionItemRecord(name: '录像存储', result: '通过'),
const InspectionItemRecord(name: '云台控制', result: '通过'),
],
icon: Icons.videocam,
),
InspectionRecord(
deviceName: '厨房智能插座',
inspectorName: '李师傅',
inspectionTime: '2024-01-15 10:00:00',
result: '不通过',
remark: '插座过热保护触发,需更换',
items: [
const InspectionItemRecord(name: '温度检测', result: '不通过'),
const InspectionItemRecord(name: '负载功率', result: '警告'),
const InspectionItemRecord(name: '过流保护', result: '通过'),
const InspectionItemRecord(name: '电源开关', result: '通过'),
],
icon: Icons.power,
),
];
final mockState = InspectionHistoryState(
deviceId: deviceId,
totalCount: mockRecords.length,
passCount: mockRecords.where((r) => r.result == '通过').length,
warningCount: mockRecords.where((r) => r.result == '警告').length,
failCount: mockRecords.where((r) => r.result == '不通过').length,
records: mockRecords,
);
emit(mockState);
}
}
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
class InspectionHistoryState extends Equatable {
final String deviceId;
final int totalCount;
final int passCount;
final int warningCount;
final int failCount;
final List<InspectionRecord> records;
const InspectionHistoryState({
required this.deviceId,
required this.totalCount,
required this.passCount,
required this.warningCount,
required this.failCount,
required this.records,
});
InspectionHistoryState copyWith({
String? deviceId,
int? totalCount,
int? passCount,
int? warningCount,
int? failCount,
List<InspectionRecord>? records,
}) {
return InspectionHistoryState(
deviceId: deviceId ?? this.deviceId,
totalCount: totalCount ?? this.totalCount,
passCount: passCount ?? this.passCount,
warningCount: warningCount ?? this.warningCount,
failCount: failCount ?? this.failCount,
records: records ?? this.records,
);
}
@override
List<Object?> get props => [
deviceId,
totalCount,
passCount,
warningCount,
failCount,
records,
];
}
class InspectionRecord extends Equatable {
final String deviceName;
final String inspectorName;
final String inspectionTime;
final String result;
final String remark;
final List<InspectionItemRecord> items;
final IconData icon;
const InspectionRecord({
required this.deviceName,
required this.inspectorName,
required this.inspectionTime,
required this.result,
required this.remark,
required this.items,
required this.icon,
});
@override
List<Object?> get props => [
deviceName,
inspectorName,
inspectionTime,
result,
remark,
items,
icon,
];
}
class InspectionItemRecord extends Equatable {
final String name;
final String result;
const InspectionItemRecord({
required this.name,
required this.result,
});
@override
List<Object?> get props => [name, result];
}
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'cubit/inspection_history_cubit.dart';
import 'cubit/inspection_history_state.dart';
import 'widget/statistics_card.dart';
import 'widget/inspection_record_list.dart';
@RoutePage()
class InspectionHistoryView extends StatelessWidget {
final String deviceId;
const InspectionHistoryView({super.key, @pathParam required this.deviceId});
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => InspectionHistoryCubit(deviceId: deviceId),
child: Scaffold(
backgroundColor: const Color.fromRGBO(242, 243, 245, 1),
appBar: AppBar(
backgroundColor: Colors.white,
elevation: 0,
leading: IconButton(
icon: const Icon(Icons.arrow_back_ios,
color: Color.fromRGBO(100, 116, 139, 1)),
onPressed: () {
context.popRoute();
},
),
title: Text(
'巡检记录',
style: TextStyle(
color: const Color.fromRGBO(10, 13, 20, 1),
fontSize: 32.sp,
fontWeight: FontWeight.w200,
),
),
centerTitle: true,
),
body: SingleChildScrollView(
padding: EdgeInsets.symmetric(horizontal: 28.w, vertical: 20.h),
child: BlocBuilder<InspectionHistoryCubit, InspectionHistoryState>(
builder: (context, state) {
return Column(
children: [
StatisticsCard(
totalCount: state.totalCount,
passCount: state.passCount,
warningCount: state.warningCount,
failCount: state.failCount,
),
SizedBox(height: 20.h),
InspectionRecordList(
records: state.records,
),
SizedBox(height: 30.h),
],
);
},
),
),
),
);
}
}
\ No newline at end of file
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import '../cubit/inspection_history_state.dart';
class InspectionRecordItem extends StatelessWidget {
final InspectionRecord record;
const InspectionRecordItem({
super.key,
required this.record,
});
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20.r),
),
padding: EdgeInsets.symmetric(horizontal: 20.w, vertical: 16.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Container(
width: 60.w,
height: 60.h,
decoration: BoxDecoration(
color: const Color.fromRGBO(242, 243, 245, 1.0),
borderRadius: BorderRadius.circular(8.r),
),
child: Icon(
record.icon,
color: const Color.fromRGBO(100, 116, 139, 1.0),
size: 32.sp,
),
),
SizedBox(width: 12.w),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
record.deviceName,
style: TextStyle(
fontSize: 26.sp,
fontWeight: FontWeight.bold,
color: const Color.fromRGBO(10, 13, 20, 1.0),
),
),
SizedBox(height: 8.h),
Row(
children: [
Icon(
Icons.person,
color: const Color.fromRGBO(66, 165, 245, 1.0),
size: 18.sp,
),
SizedBox(width: 4.w),
Text(
record.inspectorName,
style: TextStyle(
fontSize: 22.sp,
color: const Color.fromRGBO(100, 116, 139, 1.0),
),
),
],
),
],
),
],
),
Container(
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 6.h),
decoration: BoxDecoration(
color: _getResultColor(record.result).withOpacity(0.1),
borderRadius: BorderRadius.circular(10.r),
border: Border.all(
color: _getResultColor(record.result),
width: 1.w,
),
),
child: Row(
children: [
Icon(
_getResultIcon(record.result),
color: _getResultColor(record.result),
size: 18.sp,
),
SizedBox(width: 4.w),
Text(
record.result,
style: TextStyle(
fontSize: 22.sp,
color: _getResultColor(record.result),
fontWeight: FontWeight.bold,
),
),
],
),
),
],
),
SizedBox(height: 12.h),
Row(
children: [
Icon(
Icons.access_time,
color: const Color.fromRGBO(100, 116, 139, 1.0),
size: 20.sp,
),
SizedBox(width: 4.w),
Text(
record.inspectionTime,
style: TextStyle(
fontSize: 22.sp,
color: const Color.fromRGBO(100, 116, 139, 1.0),
),
),
],
),
SizedBox(height: 16.h),
Container(
width: double.infinity,
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 12.h),
decoration: BoxDecoration(
color: const Color.fromRGBO(242, 243, 245, 1.0),
borderRadius: BorderRadius.circular(12.r),
),
child: Text(
record.remark,
style: TextStyle(
fontSize: 22.sp,
color: const Color.fromRGBO(100, 116, 139, 1.0),
),
),
),
SizedBox(height: 16.h),
Divider(
color: const Color.fromRGBO(242, 243, 245, 1.0),
height: 1.h,
),
SizedBox(height: 16.h),
Text(
'巡检项目',
style: TextStyle(
fontSize: 24.sp,
fontWeight: FontWeight.bold,
color: const Color.fromRGBO(71, 85, 105, 1.0),
),
),
SizedBox(height: 12.h),
Wrap(
spacing: 8.w,
runSpacing: 8.h,
children: [
...record.items.map((item) => _buildItemTag(item)).toList(),
if (record.items.length > 3)
Container(
padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 6.h),
decoration: BoxDecoration(
color: const Color.fromRGBO(242, 243, 245, 1.0),
borderRadius: BorderRadius.circular(16.r),
),
child: Text(
'+${record.items.length - 3}',
style: TextStyle(
fontSize: 20.sp,
color: const Color.fromRGBO(100, 116, 139, 1.0),
),
),
),
],
),
],
),
);
}
Widget _buildItemTag(InspectionItemRecord item) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 6.h),
decoration: BoxDecoration(
color: _getResultColor(item.result).withOpacity(0.1),
borderRadius: BorderRadius.circular(16.r),
border: Border.all(
color: _getResultColor(item.result),
width: 1.w,
),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
_getResultIcon(item.result),
color: _getResultColor(item.result),
size: 16.sp,
),
SizedBox(width: 4.w),
Text(
item.name,
style: TextStyle(
fontSize: 20.sp,
color: _getResultColor(item.result),
),
),
],
),
);
}
Color _getResultColor(String result) {
switch (result) {
case '通过':
return const Color.fromRGBO(26, 188, 156, 1.0);
case '警告':
return const Color.fromRGBO(247, 184, 75, 1.0);
case '不通过':
return const Color.fromRGBO(239, 68, 68, 1.0);
default:
return const Color.fromRGBO(100, 116, 139, 1.0);
}
}
IconData _getResultIcon(String result) {
switch (result) {
case '通过':
return Icons.check;
case '警告':
return Icons.warning_amber_outlined;
case '不通过':
return Icons.close;
default:
return Icons.help;
}
}
}
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import '../cubit/inspection_history_state.dart';
import 'inspection_record_item.dart';
class InspectionRecordList extends StatelessWidget {
final List<InspectionRecord> records;
const InspectionRecordList({
super.key,
required this.records,
});
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'巡检历史 (${records.length})',
style: TextStyle(
fontSize: 28.sp,
fontWeight: FontWeight.bold,
color: const Color.fromRGBO(10, 13, 20, 1.0),
),
),
SizedBox(height: 16.h),
ListView.separated(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: records.length,
separatorBuilder: (context, index) => SizedBox(height: 16.h),
itemBuilder: (context, index) {
return InspectionRecordItem(
record: records[index],
);
},
),
],
);
}
}
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
class StatisticsCard extends StatelessWidget {
final int totalCount;
final int passCount;
final int warningCount;
final int failCount;
const StatisticsCard({
super.key,
required this.totalCount,
required this.passCount,
required this.warningCount,
required this.failCount,
});
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20.r),
),
padding: EdgeInsets.symmetric(horizontal: 20.w, vertical: 16.h),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_buildStatItem('总计', totalCount, const Color.fromRGBO(66, 165, 245, 1.0)),
_buildStatItem('通过', passCount, const Color.fromRGBO(26, 188, 156, 1.0)),
_buildStatItem('警告', warningCount, const Color.fromRGBO(247, 184, 75, 1.0)),
_buildStatItem('不通过', failCount, const Color.fromRGBO(239, 68, 68, 1.0)),
],
),
);
}
Widget _buildStatItem(String label, int count, Color color) {
return Column(
children: [
Text(
count.toString(),
style: TextStyle(
fontSize: 32.sp,
fontWeight: FontWeight.bold,
color: color,
),
),
SizedBox(height: 4.h),
Text(
label,
style: TextStyle(
fontSize: 22.sp,
color: const Color.fromRGBO(100, 116, 139, 1.0),
),
),
],
);
}
}
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/utils/storage/storage_service.dart';
import 'package:smart_hotel_app/views/login/cubit/login_state.dart';
class LoginController {
class LoginCubit extends Cubit<LoginState> {
final AuthBloc authBloc;
final TextEditingController usernameController;
final TextEditingController pwdController;
final GlobalKey<FormState> frmGlobalKey;
final StorageService storageService;
LoginController({
LoginCubit({
required this.authBloc,
required this.usernameController,
required this.pwdController,
required this.frmGlobalKey,
required this.storageService,
});
}) : super(const LoginState());
void login(GlobalKey<FormState> frmKey, {bool rememberPassword = false}) {
if (frmKey.currentState!.validate()) {
final username = usernameController.text.trim();
final password = pwdController.text.trim();
if (rememberPassword) {
storageService.saveRememberCredentials(
username: username,
password: password,
);
} else {
storageService.clearRememberCredentials();
}
void toggleRememberPassword(bool value) {
emit(state.copyWith(rememberPassword: value));
}
authBloc.add(AuthLoginRequested(username: username, password: password));
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) {
usernameController.text = credentials['username']!;
}
if (credentials['password'] != null) {
pwdController.text = credentials['password']!;
emit(state.copyWith(
rememberPassword: true,
savedUsername: credentials['username'],
savedPassword: credentials['password'],
));
}
}
void forgotPwd() {
// TODO: 跳转到忘记密码页面
}
void resetFrm() {
usernameController.clear();
pwdController.clear();
frmGlobalKey.currentState?.reset();
}
}
\ No newline at end of file
import 'package:equatable/equatable.dart';
class LoginState extends Equatable {
final bool rememberPassword;
final bool isLoading;
final String? savedUsername;
final String? savedPassword;
const LoginState({
this.rememberPassword = false,
this.isLoading = false,
this.savedUsername,
this.savedPassword,
});
LoginState copyWith({
bool? rememberPassword,
bool? isLoading,
String? savedUsername,
String? savedPassword,
}) {
return LoginState(
rememberPassword: rememberPassword ?? this.rememberPassword,
isLoading: isLoading ?? this.isLoading,
savedUsername: savedUsername ?? this.savedUsername,
savedPassword: savedPassword ?? this.savedPassword,
);
}
@override
List<Object?> get props => [
rememberPassword,
isLoading,
savedUsername,
savedPassword,
];
}
\ No newline at end of file
import 'dart:io';
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
......@@ -6,184 +5,182 @@ 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/routes/app_router.gr.dart';
import 'package:smart_hotel_app/utils/storage/storage_service.dart';
import 'package:smart_hotel_app/views/login/child/btn_login.dart';
import 'package:smart_hotel_app/views/login/child/btn_reset.dart';
import 'package:smart_hotel_app/views/login/child/password_field.dart';
import 'package:smart_hotel_app/views/login/child/remember_password_row.dart';
import 'package:smart_hotel_app/views/login/child/username_field.dart';
import 'package:smart_hotel_app/views/login/controller.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
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 StatefulWidget {
const LoginView({super.key});
class LoginView extends StatelessWidget {
LoginView({super.key});
@override
State<LoginView> createState() => _LoginViewState();
}
class _LoginViewState extends State<LoginView> {
late final LoginController _loginController;
final _frmGlobalKey = GlobalKey<FormState>();
final _txtUserNameController = TextEditingController();
final _txtPwdController = TextEditingController();
bool _rememberPassword = false;
@override
void initState() {
super.initState();
_loginController = LoginController(
authBloc: context.read<AuthBloc>(),
usernameController: _txtUserNameController,
pwdController: _txtPwdController,
frmGlobalKey: _frmGlobalKey,
storageService: StorageService(),
);
_loadSavedCredentials();
// 设置状态栏样式
Widget build(BuildContext context) {
SystemChrome.setSystemUIOverlayStyle(
SystemUiOverlayStyle(
const SystemUiOverlayStyle(
statusBarColor: Colors.transparent,
statusBarIconBrightness: Brightness.dark,
statusBarBrightness: Brightness.light,
),
);
}
Future<void> _loadSavedCredentials() async {
final credentials = await _loginController.storageService.getRememberCredentials();
if (credentials['username'] != null) {
setState(() {
_rememberPassword = true;
_txtUserNameController.text = credentials['username']!;
_txtPwdController.text = credentials['password'] ?? '';
});
}
}
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>();
@override
Widget build(BuildContext context) {
return AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle(
statusBarColor: Colors.transparent,
statusBarIconBrightness: Brightness.dark,
statusBarBrightness: Brightness.light,
),
child: Scaffold(
backgroundColor: 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: BoxDecoration(
color: Color(0xFFF5F7FA),
),
child: SingleChildScrollView(
child: Column(
children: [
SizedBox(height: 150.h),
Container(
margin: EdgeInsets.only(bottom: 80.h),
child: Column(
if (state.savedUsername != null) {
_txtUserNameController.text = state.savedUsername!;
}
if (state.savedPassword != null) {
_txtPwdController.text = state.savedPassword!;
}
return Column(
children: [
SizedBox(height: 150.h),
Container(
width: 160.w,
height: 160.w,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(40.w),
gradient: 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),
Text(
"智能酒店管理系统",
style: TextStyle(
color: Color(0xFF333333),
fontSize: 36.sp,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 10.h),
Text(
"Intelligent Hotel Management System",
style: TextStyle(
color: Color(0xFF999999),
fontSize: 24.sp,
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: 36,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 10.h),
const Text(
'Intelligent Hotel Management System',
style: TextStyle(
color: Color(0xFF999999),
fontSize: 24,
),
),
],
),
),
],
),
),
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: _rememberPassword,
onChanged: (val) {
setState(() {
_rememberPassword = val ?? false;
});
},
onForgotPassword: _loginController.forgotPwd,
),
SizedBox(height: 10.h),
BlocConsumer<AuthBloc, AuthState>(
listener: (context, state) {
if (state is AuthFailure) {
Fluttertoast.showToast(
msg: state.error,
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.CENTER,
timeInSecForIosWeb: 2,
backgroundColor: Colors.red,
textColor: Colors.white,
fontSize: 16.0);
}
},
builder: (context, state) {
return BtnLogin(
isLoading: state is AuthLoading,
onPressed: () => _loginController.login(
_frmGlobalKey,
rememberPassword: _rememberPassword,
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,
);
}
},
);
},
),
],
),
),
],
),
),
],
);
},
),
),
),
),
......@@ -191,4 +188,4 @@ class _LoginViewState extends State<LoginView> {
),
);
}
}
}
\ No newline at end of file
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter/material.dart';
import 'package:smart_hotel_app/views/profile/index/cubit/profile_state.dart';
class ProfileCubit extends Cubit<ProfileState> {
ProfileCubit() : super(const ProfileState()) {
initData();
}
void initData() {
emit(state.copyWith(
phoneNumber: '15391514233',
welcomeText: '欢迎来到智能酒店',
menuList: [
const MenuItem(icon: Icons.list, title: '设备列表'),
const MenuItem(icon: Icons.warning_rounded, title: '告警信息'),
const MenuItem(icon: Icons.info_outline, title: '帮助'),
const MenuItem(icon: Icons.person_outline, title: '关于'),
const MenuItem(icon: Icons.logout, title: '退出登录'),
const MenuItem(icon: Icons.logout, title: '注销账号'),
],
));
}
}
\ No newline at end of file
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
class MenuItem {
final IconData icon;
final String title;
final VoidCallback? onTap;
const MenuItem({
required this.icon,
required this.title,
this.onTap,
});
}
class ProfileState extends Equatable {
final String phoneNumber;
final String welcomeText;
final List<MenuItem> menuList;
const ProfileState({
this.phoneNumber = '',
this.welcomeText = '',
this.menuList = const [],
});
ProfileState copyWith({
String? phoneNumber,
String? welcomeText,
List<MenuItem>? menuList,
}) {
return ProfileState(
phoneNumber: phoneNumber ?? this.phoneNumber,
welcomeText: welcomeText ?? this.welcomeText,
menuList: menuList ?? this.menuList,
);
}
@override
List<Object?> get props => [phoneNumber, welcomeText, menuList];
}
\ No newline at end of file
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:smart_hotel_app/views/profile/index/cubit/profile_cubit.dart';
import 'package:smart_hotel_app/views/profile/index/cubit/profile_state.dart';
import 'package:smart_hotel_app/views/profile/index/widget/profile_header_block.dart';
import 'package:smart_hotel_app/views/profile/index/widget/profile_menu_block.dart';
@RoutePage()
class ProfileView extends StatelessWidget {
const ProfileView({super.key});
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => ProfileCubit(),
child: LayoutBuilder(
builder: (context, constraints) {
return SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: constraints.maxHeight,
),
child: Container(
width: double.infinity,
color: const Color.fromRGBO(242, 243, 245, 1),
child: BlocBuilder<ProfileCubit, ProfileState>(
builder: (context, state) {
return Column(
children: [
ProfileHeaderBlock(
phoneNumber: state.phoneNumber,
welcomeText: state.welcomeText,
),
SizedBox(height: 10.h),
Padding(
padding: EdgeInsets.symmetric(horizontal: 28.w),
child: ProfileMenuBlock(menuList: state.menuList),
),
],
);
},
),
),
),
);
},
),
);
}
}
\ No newline at end of file
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
class ProfileHeaderBlock extends StatelessWidget {
final String phoneNumber;
final String welcomeText;
const ProfileHeaderBlock({
super.key,
required this.phoneNumber,
required this.welcomeText,
});
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
color: Colors.white,
padding: EdgeInsets.symmetric(horizontal: 28.w, vertical: 32.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'我的',
style: TextStyle(
fontSize: 36.sp,
fontWeight: FontWeight.w500,
color: Colors.black,
),
),
SizedBox(height: 40.h),
Row(
children: [
Container(
width: 100.w,
height: 100.h,
decoration: const BoxDecoration(
shape: BoxShape.circle,
color: Color.fromRGBO(235, 244, 255, 1.0),
),
child: ClipOval(
child: Image.network(
'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=200&h=200&fit=crop&crop=face',
width: 100.w,
height: 100.h,
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) {
return Icon(
Icons.person,
size: 48.sp,
color: const Color.fromRGBO(66, 165, 245, 1.0),
);
},
),
),
),
SizedBox(width: 24.w),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
phoneNumber,
style: TextStyle(
fontSize: 32.sp,
fontWeight: FontWeight.bold,
color: Colors.black,
),
),
SizedBox(height: 12.h),
Text(
welcomeText,
style: TextStyle(
fontSize: 26.sp,
color: const Color.fromRGBO(100, 116, 139, 1.0),
),
),
],
),
],
),
],
),
);
}
}
\ No newline at end of file
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:smart_hotel_app/views/profile/index/cubit/profile_state.dart';
class ProfileMenuBlock extends StatelessWidget {
final List<MenuItem> menuList;
const ProfileMenuBlock({super.key, required this.menuList});
Widget _buildMenuItem(MenuItem item) {
return GestureDetector(
onTap: () => item.onTap?.call(),
child: Container(
width: double.infinity,
padding: EdgeInsets.symmetric(horizontal: 24.w, vertical: 28.h),
margin: EdgeInsets.only(bottom: 16.h),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(24.r),
),
child: Row(
children: [
Container(
width: 96.w,
height: 96.h,
decoration: BoxDecoration(
color: const Color.fromRGBO(229, 241, 255, 1),
borderRadius: BorderRadius.circular(28.r),
),
child: Icon(
item.icon,
color: const Color.fromRGBO(66, 165, 245, 1.0),
size: 40.sp,
),
),
SizedBox(width: 24.w),
Expanded(
child: Text(
item.title,
style: TextStyle(
fontSize: 30.sp,
fontWeight: FontWeight.w500,
color: const Color.fromRGBO(100, 116, 139, 1.0),
),
),
),
Icon(
Icons.chevron_right,
color: const Color.fromRGBO(100, 116, 139, 1.0),
size: 32.sp,
),
],
),
),
);
}
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
child: Column(
children: [
...menuList.map((item) => _buildMenuItem(item)).toList(),
],
),
);
}
}
\ No newline at end of file
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
@RoutePage()
class ProfileView extends StatefulWidget {
const ProfileView({super.key});
@override
State<ProfileView> createState() => _ProfileViewState();
}
class _ProfileViewState extends State<ProfileView> {
@override
Widget build(BuildContext context) {
return const Center(
child: Text('ProfileView'),
);
}
}
class ReportEnergyController {
static const double todayEnergy = 1284;
static const double todayEnergyChange = -8;
static const double monthEnergy = 38420;
static const double monthEnergyChange = -5;
static const double monthBill = 6982;
static const List<double> hourlyData = [
45, 38, 32, 28, 25, 30, 42, 78, 112, 145, 168, 175,
162, 148, 132, 115, 98, 82, 70, 58, 48, 42, 38, 35
];
static const List<ZoneData> zoneData = [
ZoneData(name: '客房区域', percentage: 68, energy: 873),
ZoneData(name: '公共区域', percentage: 18, energy: 231),
ZoneData(name: '餐饮区域', percentage: 10, energy: 128),
ZoneData(name: '其他', percentage: 4, energy: 52),
];
}
class ZoneData {
final String name;
final double percentage;
final double energy;
const ZoneData({
required this.name,
required this.percentage,
required this.energy,
});
}
\ No newline at end of file
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:smart_hotel_app/views/report/energy/cubit/energy_state.dart';
class EnergyCubit extends Cubit<EnergyState> {
EnergyCubit() : super(const EnergyState());
}
\ No newline at end of file
import 'package:equatable/equatable.dart';
class ZoneData {
final String name;
final double percentage;
final double energy;
const ZoneData({
required this.name,
required this.percentage,
required this.energy,
});
}
class EnergyState extends Equatable {
final double todayEnergy;
final double todayEnergyChange;
final double monthEnergy;
final double monthEnergyChange;
final double monthBill;
final List<double> hourlyData;
final List<ZoneData> zoneData;
const EnergyState({
this.todayEnergy = 1284,
this.todayEnergyChange = -8,
this.monthEnergy = 38420,
this.monthEnergyChange = -5,
this.monthBill = 6982,
this.hourlyData = const [
45, 38, 32, 28, 25, 30, 42, 78, 112, 145, 168, 175,
162, 148, 132, 115, 98, 82, 70, 58, 48, 42, 38, 35,
],
this.zoneData = const [
ZoneData(name: '客房区域', percentage: 68, energy: 873),
ZoneData(name: '公共区域', percentage: 18, energy: 231),
ZoneData(name: '餐饮区域', percentage: 10, energy: 128),
ZoneData(name: '其他', percentage: 4, energy: 52),
],
});
EnergyState copyWith({
double? todayEnergy,
double? todayEnergyChange,
double? monthEnergy,
double? monthEnergyChange,
double? monthBill,
List<double>? hourlyData,
List<ZoneData>? zoneData,
}) {
return EnergyState(
todayEnergy: todayEnergy ?? this.todayEnergy,
todayEnergyChange: todayEnergyChange ?? this.todayEnergyChange,
monthEnergy: monthEnergy ?? this.monthEnergy,
monthEnergyChange: monthEnergyChange ?? this.monthEnergyChange,
monthBill: monthBill ?? this.monthBill,
hourlyData: hourlyData ?? this.hourlyData,
zoneData: zoneData ?? this.zoneData,
);
}
@override
List<Object?> get props => [
todayEnergy,
todayEnergyChange,
monthEnergy,
monthEnergyChange,
monthBill,
hourlyData,
zoneData,
];
}
\ No newline at end of file
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:smart_hotel_app/views/report/energy/controller.dart';
import 'package:smart_hotel_app/views/report/energy/child/energy_stats_card_block.dart';
import 'package:smart_hotel_app/views/report/energy/child/hourly_chart_block.dart';
import 'package:smart_hotel_app/views/report/energy/child/zone_chart_block.dart';
import 'package:smart_hotel_app/views/report/energy/cubit/energy_cubit.dart';
import 'package:smart_hotel_app/views/report/energy/cubit/energy_state.dart';
import 'package:smart_hotel_app/views/report/energy/widget/energy_stats_card_block.dart';
import 'package:smart_hotel_app/views/report/energy/widget/hourly_chart_block.dart';
import 'package:smart_hotel_app/views/report/energy/widget/zone_chart_block.dart';
@RoutePage()
class ReportEnergyDetailView extends StatefulWidget {
class ReportEnergyDetailView extends StatelessWidget {
const ReportEnergyDetailView({super.key});
@override
State<ReportEnergyDetailView> createState() => _ReportEnergyDetailViewState();
}
class _ReportEnergyDetailViewState extends State<ReportEnergyDetailView> {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color.fromRGBO(15, 23, 42, 1),
appBar: AppBar(
backgroundColor: Color.fromRGBO(15, 23, 42, 1),
elevation: 0,
leading: IconButton(
icon: Icon(Icons.arrow_back, color: Colors.white),
onPressed: () {
context.popRoute();
},
return BlocProvider(
create: (_) => EnergyCubit(),
child: Scaffold(
backgroundColor: const Color.fromRGBO(15, 23, 42, 1),
appBar: AppBar(
backgroundColor: const Color.fromRGBO(15, 23, 42, 1),
elevation: 0,
leading: IconButton(
icon: const Icon(Icons.arrow_back, color: Colors.white),
onPressed: () {
context.popRoute();
},
),
title: Text(
'能耗分析',
style: TextStyle(
color: Colors.white,
fontSize: 18.sp,
fontWeight: FontWeight.bold,
),
),
centerTitle: true,
),
title: Text(
'能耗分析',
style: TextStyle(
color: Colors.white,
fontSize: 18.sp,
fontWeight: FontWeight.bold,
body: SingleChildScrollView(
child: BlocBuilder<EnergyCubit, EnergyState>(
builder: (context, state) {
return Column(
children: [
SizedBox(height: 10.h),
EnergyStatsCardBlock(
todayEnergy: state.todayEnergy,
todayEnergyChange: state.todayEnergyChange,
monthEnergy: state.monthEnergy,
monthEnergyChange: state.monthEnergyChange,
monthBill: state.monthBill,
),
SizedBox(height: 20.h),
HourlyChartBlock(hourlyData: state.hourlyData),
SizedBox(height: 20.h),
ZoneChartBlock(zoneData: state.zoneData),
SizedBox(height: 20.h),
],
);
},
),
),
centerTitle: true,
),
body: SingleChildScrollView(
child: Column(
children: [
SizedBox(height: 10.h),
const EnergyStatsCardBlock(),
SizedBox(height: 20.h),
const HourlyChartBlock(),
SizedBox(height: 20.h),
const ZoneChartBlock(),
SizedBox(height: 20.h),
],
),
),
);
}
}
\ No newline at end of file
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:smart_hotel_app/views/report/energy/controller.dart';
class EnergyStatsCardBlock extends StatelessWidget {
const EnergyStatsCardBlock({super.key});
final double todayEnergy;
final double todayEnergyChange;
final double monthEnergy;
final double monthEnergyChange;
final double monthBill;
const EnergyStatsCardBlock({
super.key,
required this.todayEnergy,
required this.todayEnergyChange,
required this.monthEnergy,
required this.monthEnergyChange,
required this.monthBill,
});
@override
Widget build(BuildContext context) {
......@@ -13,25 +25,25 @@ class EnergyStatsCardBlock extends StatelessWidget {
children: [
Expanded(
child: _buildCard(
'${ReportEnergyController.todayEnergy.toInt()}',
'${todayEnergy.toInt()}',
'今日(kWh)',
'${ReportEnergyController.todayEnergyChange.toInt()}%',
'${todayEnergyChange.toInt()}%',
true,
),
),
SizedBox(width: 12.w),
Expanded(
child: _buildCard(
'${ReportEnergyController.monthEnergy.toInt()}',
'${monthEnergy.toInt()}',
'本月(kWh)',
'${ReportEnergyController.monthEnergyChange.toInt()}%',
'${monthEnergyChange.toInt()}%',
true,
),
),
SizedBox(width: 12.w),
Expanded(
child: _buildCard(
'¥${ReportEnergyController.monthBill.toInt()}',
'¥${monthBill.toInt()}',
'本月电费预估',
null,
null,
......@@ -74,13 +86,17 @@ class EnergyStatsCardBlock extends StatelessWidget {
children: [
Icon(
isDown! ? Icons.arrow_drop_down : Icons.arrow_drop_up,
color: isDown ? const Color.fromRGBO(34, 197, 94, 1) : const Color.fromRGBO(239, 68, 68, 1),
color: isDown
? const Color.fromRGBO(34, 197, 94, 1)
: const Color.fromRGBO(239, 68, 68, 1),
size: 16.sp,
),
Text(
change,
style: TextStyle(
color: isDown ? const Color.fromRGBO(34, 197, 94, 1) : const Color.fromRGBO(239, 68, 68, 1),
color: isDown
? const Color.fromRGBO(34, 197, 94, 1)
: const Color.fromRGBO(239, 68, 68, 1),
fontSize: 12.sp,
),
),
......@@ -92,4 +108,4 @@ class EnergyStatsCardBlock extends StatelessWidget {
),
);
}
}
}
\ No newline at end of file
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:fl_chart/fl_chart.dart';
import 'package:smart_hotel_app/views/report/energy/controller.dart';
class HourlyChartBlock extends StatelessWidget {
const HourlyChartBlock({super.key});
final List<double> hourlyData;
const HourlyChartBlock({super.key, required this.hourlyData});
@override
Widget build(BuildContext context) {
......@@ -34,33 +35,21 @@ class HourlyChartBlock extends StatelessWidget {
alignment: BarChartAlignment.spaceBetween,
maxY: 200,
barTouchData: BarTouchData(enabled: false),
titlesData: FlTitlesData(
titlesData: const FlTitlesData(
show: true,
rightTitles: const AxisTitles(
rightTitles: AxisTitles(
sideTitles: SideTitles(showTitles: false),
),
topTitles: const AxisTitles(
topTitles: AxisTitles(
sideTitles: SideTitles(showTitles: false),
),
bottomTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
getTitlesWidget: (value, meta) {
if (value % 4 == 0) {
return Text(
'${value.toInt()}',
style: TextStyle(
color: const Color.fromRGBO(148, 163, 184, 1),
fontSize: 10.sp,
),
);
}
return const SizedBox.shrink();
},
reservedSize: 20,
),
),
leftTitles: const AxisTitles(
leftTitles: AxisTitles(
sideTitles: SideTitles(showTitles: false),
),
),
......@@ -76,7 +65,7 @@ class HourlyChartBlock extends StatelessWidget {
}
List<BarChartGroupData> _buildBarGroups() {
return ReportEnergyController.hourlyData.asMap().entries.map((entry) {
return hourlyData.asMap().entries.map((entry) {
final index = entry.key;
final value = entry.value;
return BarChartGroupData(
......@@ -88,13 +77,13 @@ class HourlyChartBlock extends StatelessWidget {
? const Color.fromRGBO(59, 130, 246, 1)
: const Color.fromRGBO(37, 99, 235, 1),
width: 10.w,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(4.r),
topRight: Radius.circular(4.r),
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(4),
topRight: Radius.circular(4),
),
),
],
);
}).toList();
}
}
}
\ No newline at end of file
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:smart_hotel_app/views/report/energy/controller.dart';
import 'package:smart_hotel_app/views/report/energy/cubit/energy_state.dart';
class ZoneChartBlock extends StatelessWidget {
const ZoneChartBlock({super.key});
final List<ZoneData> zoneData;
const ZoneChartBlock({super.key, required this.zoneData});
@override
Widget build(BuildContext context) {
......@@ -26,11 +28,12 @@ class ZoneChartBlock extends StatelessWidget {
),
),
SizedBox(height: 16.h),
...ReportEnergyController.zoneData.asMap().entries.map((entry) {
...zoneData.asMap().entries.map((entry) {
final index = entry.key;
final data = entry.value;
return Padding(
padding: EdgeInsets.only(bottom: index < ReportEnergyController.zoneData.length - 1 ? 16.h : 0),
padding: EdgeInsets.only(
bottom: index < zoneData.length - 1 ? 16.h : 0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
......@@ -85,4 +88,4 @@ class ZoneChartBlock extends StatelessWidget {
),
);
}
}
}
\ No newline at end of file
import 'package:auto_route/auto_route.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter/material.dart';
import 'package:smart_hotel_app/routes/app_router.gr.dart';
import 'package:smart_hotel_app/views/report/index/cubit/report_index_state.dart';
class ReportIndexController {
final BuildContext context;
ReportIndexController(this.context);
Map<String, dynamic> getReportHeader() {
return {
'title': '经营概览看板',
'subtitle': '管理层视角 · 关键指标一览',
};
class ReportIndexCubit extends Cubit<ReportIndexState> {
ReportIndexCubit() : super(const ReportIndexState()) {
initData();
}
Map<String, dynamic> getReportMetrics() {
return {
'powerUsage': {
'value': 1284,
void initData() {
emit(state.copyWith(
reportHeader: {
'title': '经营概览看板',
'subtitle': '管理层视角 · 关键指标一览',
},
reportMetrics: {
'powerUsage': {
'value': 1284,
'unit': 'kWh',
'change': -8,
'changeText': '↓ 8%',
'progress': 0.6,
},
'pendingAlerts': {
'value': 3,
'unit': '条',
'change': null,
'changeText': '需关注',
'progress': 0.3,
},
},
weeklyPowerUsage: {
'title': '本周用电趋势',
'unit': 'kWh',
'change': -8,
'changeText': '↓ 8%',
'progress': 0.6,
'dateRange': '5.7 - 5.13',
'data': [
{'day': '一', 'value': 120},
{'day': '二', 'value': 180},
{'day': '三', 'value': 150},
{'day': '四', 'value': 200},
{'day': '五', 'value': 250},
{'day': '六', 'value': 220},
{'day': '日', 'value': 164},
],
},
'pendingAlerts': {
'value': 3,
'unit': '条',
'change': null,
'changeText': '需关注',
'progress': 0.3,
roomStatusDistribution: {
'title': '客房状态分布',
'totalRooms': 52,
'statuses': [
{'name': '入住中', 'count': 45, 'color': const Color.fromRGBO(59, 130, 246, 1)},
{'name': '空闲', 'count': 7, 'color': const Color.fromRGBO(148, 163, 184, 1)},
{'name': '已预订', 'count': 5, 'color': const Color.fromRGBO(96, 165, 250, 1)},
{'name': '设备告警', 'count': 3, 'color': const Color.fromRGBO(249, 115, 22, 1)},
],
},
};
}
Map<String, dynamic> getWeeklyPowerUsage() {
return {
'title': '本周用电趋势',
'unit': 'kWh',
'dateRange': '5.7 - 5.13',
'data': [
{'day': '一', 'value': 120},
{'day': '二', 'value': 180},
{'day': '三', 'value': 150},
{'day': '四', 'value': 200},
{'day': '五', 'value': 250},
{'day': '六', 'value': 220},
{'day': '日', 'value': 164},
],
};
}
Map<String, dynamic> getRoomStatusDistribution() {
return {
'title': '客房状态分布',
'totalRooms': 52,
'statuses': [
{'name': '入住中', 'count': 45, 'color': Color.fromRGBO(59, 130, 246, 1)},
{'name': '空闲', 'count': 7, 'color': Color.fromRGBO(148, 163, 184, 1)},
{'name': '已预订', 'count': 5, 'color': Color.fromRGBO(96, 165, 250, 1)},
{'name': '设备告警', 'count': 3, 'color': Color.fromRGBO(249, 115, 22, 1)},
],
};
}
Map<String, dynamic> getDeviceOnlineRate() {
return {
'title': '设备在线率',
'rate': 1.0,
'rateText': '100%',
'devices': [
{'name': '智能空开', 'count': 16},
{'name': '客控设备', 'count': 24},
{'name': '网络设备', 'count': 8},
],
};
}
Map<String, dynamic> getQuickActions() {
return {
'title': '快捷操作',
'actions': [
{
'name': '告警记录',
'icon': Icons.warning_amber,
'backgroundColor': Color.fromRGBO(125, 96, 87, 1),
'iconColor': Color.fromRGBO(255, 152, 0, 1),
},
],
};
}
void navigateToEnergyDetail() {
context.pushRoute(const ReportEnergyDetailRoute());
}
void navigateToRoomDetail() {
context.pushRoute(const ReportRoomDetailRoute());
deviceOnlineRate: {
'title': '设备在线率',
'rate': 1.0,
'rateText': '100%',
'devices': [
{'name': '智能空开', 'count': 16},
{'name': '客控设备', 'count': 24},
{'name': '网络设备', 'count': 8},
],
},
quickActions: {
'title': '快捷操作',
'actions': [
{'name': '告警记录', 'icon': Icons.warning_amber_outlined},
{'name': '联动规则', 'icon': Icons.tune},
],
},
));
}
}
\ No newline at end of file
import 'package:equatable/equatable.dart';
class ReportIndexState extends Equatable {
final Map<String, dynamic> reportHeader;
final Map<String, dynamic> reportMetrics;
final Map<String, dynamic> weeklyPowerUsage;
final Map<String, dynamic> roomStatusDistribution;
final Map<String, dynamic> deviceOnlineRate;
final Map<String, dynamic> quickActions;
const ReportIndexState({
this.reportHeader = const {},
this.reportMetrics = const {},
this.weeklyPowerUsage = const {},
this.roomStatusDistribution = const {},
this.deviceOnlineRate = const {},
this.quickActions = const {},
});
ReportIndexState copyWith({
Map<String, dynamic>? reportHeader,
Map<String, dynamic>? reportMetrics,
Map<String, dynamic>? weeklyPowerUsage,
Map<String, dynamic>? roomStatusDistribution,
Map<String, dynamic>? deviceOnlineRate,
Map<String, dynamic>? quickActions,
}) {
return ReportIndexState(
reportHeader: reportHeader ?? this.reportHeader,
reportMetrics: reportMetrics ?? this.reportMetrics,
weeklyPowerUsage: weeklyPowerUsage ?? this.weeklyPowerUsage,
roomStatusDistribution: roomStatusDistribution ?? this.roomStatusDistribution,
deviceOnlineRate: deviceOnlineRate ?? this.deviceOnlineRate,
quickActions: quickActions ?? this.quickActions,
);
}
@override
List<Object?> get props => [
reportHeader,
reportMetrics,
weeklyPowerUsage,
roomStatusDistribution,
deviceOnlineRate,
quickActions,
];
}
\ No newline at end of file
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:smart_hotel_app/views/report/index/child/report_header_block.dart';
import 'package:smart_hotel_app/views/report/index/child/report_metrics_block.dart';
import 'package:smart_hotel_app/views/report/index/child/weekly_power_chart.dart';
import 'package:smart_hotel_app/views/report/index/child/room_status_distribution.dart';
import 'package:smart_hotel_app/views/report/index/child/device_online_rate.dart';
import 'package:smart_hotel_app/views/report/index/child/quick_actions_block.dart';
import 'package:smart_hotel_app/views/report/index/cubit/report_index_cubit.dart';
import 'package:smart_hotel_app/views/report/index/cubit/report_index_state.dart';
import 'package:smart_hotel_app/views/report/index/widget/device_online_rate.dart';
import 'package:smart_hotel_app/views/report/index/widget/quick_actions_block.dart';
import 'package:smart_hotel_app/views/report/index/widget/report_header_block.dart';
import 'package:smart_hotel_app/views/report/index/widget/report_metrics_block.dart';
import 'package:smart_hotel_app/views/report/index/widget/room_status_distribution.dart';
import 'package:smart_hotel_app/views/report/index/widget/weekly_power_chart.dart';
@RoutePage()
class ReportIndexView extends StatefulWidget {
class ReportIndexView extends StatelessWidget {
const ReportIndexView({super.key});
@override
State<ReportIndexView> createState() => _ReportIndexViewState();
}
class _ReportIndexViewState extends State<ReportIndexView> {
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
return SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: constraints.maxHeight,
),
child: Container(
width: double.infinity,
color: Color.fromRGBO(15, 23, 42, 1),
child: Column(
children: [
ReportHeaderBlock(),
SizedBox(height: 10.h),
ReportMetricsBlock(),
SizedBox(height: 10.h),
WeeklyPowerChart(),
SizedBox(height: 10.h),
RoomStatusDistribution(),
SizedBox(height: 10.h),
DeviceOnlineRate(),
SizedBox(height: 10.h),
QuickActionsBlock(),
SizedBox(height: 10.h),
],
),
),
),
);
},
return BlocProvider(
create: (_) => ReportIndexCubit(),
child: BlocBuilder<ReportIndexCubit, ReportIndexState>(
builder: (context, state) {
return LayoutBuilder(
builder: (context, constraints) {
return SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: constraints.maxHeight,
),
child: Container(
width: double.infinity,
color: const Color.fromRGBO(242, 243, 245, 1),
padding: EdgeInsets.symmetric(horizontal: 28.w),
child: Column(
children: [
ReportHeaderBlock(
reportHeader: state.reportHeader),
SizedBox(height: 10.h),
ReportMetricsBlock(
reportMetrics: state.reportMetrics),
SizedBox(height: 10.h),
WeeklyPowerChart(
weeklyPowerUsage: state.weeklyPowerUsage),
SizedBox(height: 10.h),
RoomStatusDistribution(
roomStatusDistribution:
state.roomStatusDistribution),
SizedBox(height: 10.h),
DeviceOnlineRate(
deviceOnlineRate: state.deviceOnlineRate),
SizedBox(height: 10.h),
QuickActionsBlock(quickActions: state.quickActions),
SizedBox(height: 10.h),
],
),
),
),
);
},
);
},
),
);
}
}
\ No newline at end of file
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:fl_chart/fl_chart.dart';
import 'package:smart_hotel_app/views/report/index/controller.dart';
class DeviceOnlineRate extends StatefulWidget {
const DeviceOnlineRate({super.key});
class DeviceOnlineRate extends StatelessWidget {
final Map<String, dynamic> deviceOnlineRate;
@override
State<DeviceOnlineRate> createState() => _DeviceOnlineRateState();
}
class _DeviceOnlineRateState extends State<DeviceOnlineRate> {
late final ReportIndexController _controller;
@override
void initState() {
super.initState();
_controller = ReportIndexController(context);
}
const DeviceOnlineRate({super.key, required this.deviceOnlineRate});
@override
Widget build(BuildContext context) {
final rateData = _controller.getDeviceOnlineRate();
final devices = rateData['devices'] as List<Map<String, dynamic>>;
final devices =
deviceOnlineRate['devices'] as List<Map<String, dynamic>>;
final totalDevices =
devices.fold<int>(0, (sum, device) => sum + (device['count'] as int));
final rate = deviceOnlineRate['rate'] as double;
return Container(
width: double.infinity,
margin: EdgeInsets.symmetric(horizontal: 20.w),
padding: EdgeInsets.all(20.w),
padding: EdgeInsets.all(28.w),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20.r),
color: Color.fromRGBO(30, 41, 59, 1),
border: Border.all(
color: Color.fromRGBO(71, 85, 105, 1),
width: 1.w,
),
borderRadius: BorderRadius.circular(24.r),
color: Colors.white,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
rateData['title'] as String,
style: TextStyle(
fontSize: 18.sp,
fontWeight: FontWeight.bold,
color: Colors.white,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
deviceOnlineRate['title'] as String,
style: TextStyle(
fontSize: 28.sp,
fontWeight: FontWeight.normal,
color: const Color.fromRGBO(107, 114, 128, 1),
),
),
Text(
'共 $totalDevices 台',
style: TextStyle(
fontSize: 22.sp,
color: const Color.fromRGBO(156, 163, 175, 1),
),
),
],
),
SizedBox(height: 20.h),
Row(
children: [
SizedBox(
height: 150.w,
width: 120.w,
height: 120.h,
child: Stack(
alignment: Alignment.center,
children: [
SizedBox(
width: 150.w,
height: 150.w,
child: PieChart(
PieChartData(
sections: [
PieChartSectionData(
value: rateData['rate'] as double,
color: Color.fromRGBO(34, 197, 94, 1),
radius: 15,
title: '',
),
PieChartSectionData(
value: 1 - (rateData['rate'] as double),
color: Color.fromRGBO(51, 65, 85, 1),
radius: 15,
title: '',
),
],
centerSpaceRadius: 50.w,
sectionsSpace: 0,
startDegreeOffset: -90,
),
PieChart(
PieChartData(
sections: [
PieChartSectionData(
value: rate,
color: const Color.fromRGBO(34, 197, 94, 1),
title: '',
radius: 50.w,
),
PieChartSectionData(
value: 1 - rate,
color: const Color.fromRGBO(229, 231, 235, 1),
title: '',
radius: 50.w,
),
],
sectionsSpace: 0,
centerSpaceRadius: 38.w,
),
),
Text(
rateData['rateText'].toString().replaceAll('%', ''),
deviceOnlineRate['rateText'] as String,
style: TextStyle(
fontSize: 32.sp,
fontSize: 24.sp,
fontWeight: FontWeight.bold,
color: Color.fromRGBO(34, 197, 94, 1),
color: const Color.fromRGBO(31, 41, 55, 1),
),
),
],
),
),
SizedBox(width: 30.w),
SizedBox(width: 24.w),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
...devices.map((device) {
return Padding(
padding: EdgeInsets.only(bottom: 12.h),
child: Text(
'${device['name']} ${device['count']} 台',
style: TextStyle(
fontSize: 16.sp,
color: Colors.white,
children: devices.map((device) {
return Padding(
padding: EdgeInsets.only(bottom: 8.h),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
device['name'] as String,
style: TextStyle(
fontSize: 22.sp,
color: const Color.fromRGBO(107, 114, 128, 1),
),
),
),
);
}).toList(),
],
Text(
'${device['count']} 台',
style: TextStyle(
fontSize: 22.sp,
color: const Color.fromRGBO(55, 65, 81, 1),
),
),
],
),
);
}).toList(),
),
),
],
......@@ -117,4 +120,4 @@ class _DeviceOnlineRateState extends State<DeviceOnlineRate> {
),
);
}
}
}
\ No newline at end of file
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:smart_hotel_app/views/report/index/controller.dart';
class QuickActionsBlock extends StatefulWidget {
const QuickActionsBlock({super.key});
class QuickActionsBlock extends StatelessWidget {
final Map<String, dynamic> quickActions;
@override
State<QuickActionsBlock> createState() => _QuickActionsBlockState();
}
class _QuickActionsBlockState extends State<QuickActionsBlock> {
late final ReportIndexController _controller;
@override
void initState() {
super.initState();
_controller = ReportIndexController(context);
}
const QuickActionsBlock({super.key, required this.quickActions});
@override
Widget build(BuildContext context) {
final actionsData = _controller.getQuickActions();
final actions = actionsData['actions'] as List<Map<String, dynamic>>;
final actions = quickActions['actions'] as List<Map<String, dynamic>>;
return Container(
width: double.infinity,
margin: EdgeInsets.symmetric(horizontal: 20.w),
padding: EdgeInsets.all(20.w),
padding: EdgeInsets.all(28.w),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20.r),
color: Color.fromRGBO(30, 41, 59, 1),
border: Border.all(
color: Color.fromRGBO(71, 85, 105, 1),
width: 1.w,
),
borderRadius: BorderRadius.circular(24.r),
color: Colors.white,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
actionsData['title'] as String,
quickActions['title'] as String,
style: TextStyle(
fontSize: 18.sp,
fontWeight: FontWeight.bold,
color: Colors.white,
fontSize: 28.sp,
fontWeight: FontWeight.normal,
color: const Color.fromRGBO(107, 114, 128, 1),
),
),
SizedBox(height: 20.h),
SizedBox(height: 28.h),
Row(
children: actions.map((action) {
return Expanded(
return Padding(
padding: EdgeInsets.only(right: 60.w),
child: Column(
children: [
Container(
width: 80.w,
height: 80.w,
height: 80.h,
decoration: BoxDecoration(
color: action['backgroundColor'] as Color,
borderRadius: BorderRadius.circular(40.r),
color: const Color.fromRGBO(239, 246, 255, 1),
borderRadius: BorderRadius.circular(16.r),
),
child: Icon(
action['icon'] as IconData,
color: action['iconColor'] as Color,
color: const Color.fromRGBO(59, 130, 246, 1),
size: 36.sp,
),
),
SizedBox(height: 12.h),
SizedBox(height: 8.h),
Text(
action['name'] as String,
style: TextStyle(
fontSize: 16.sp,
color: Colors.white,
fontSize: 22.sp,
color: const Color.fromRGBO(107, 114, 128, 1),
),
),
],
......@@ -82,4 +65,4 @@ class _QuickActionsBlockState extends State<QuickActionsBlock> {
),
);
}
}
}
\ No newline at end of file
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
class ReportHeaderBlock extends StatelessWidget {
final Map<String, dynamic> reportHeader;
const ReportHeaderBlock({super.key, required this.reportHeader});
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
padding: EdgeInsets.symmetric(horizontal: 0.w, vertical: 16.h),
child: Text(
reportHeader['title'] as String,
style: TextStyle(
fontSize: 36.sp,
fontWeight: FontWeight.normal,
color: const Color.fromRGBO(71, 71, 71, 1),
),
),
);
}
}
\ No newline at end of file
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:smart_hotel_app/views/report/index/controller.dart';
class ReportMetricsBlock extends StatefulWidget {
const ReportMetricsBlock({super.key});
class ReportMetricsBlock extends StatelessWidget {
final Map<String, dynamic> reportMetrics;
@override
State<ReportMetricsBlock> createState() => _ReportMetricsBlockState();
}
class _ReportMetricsBlockState extends State<ReportMetricsBlock> {
late final ReportIndexController _controller;
@override
void initState() {
super.initState();
_controller = ReportIndexController(context);
}
const ReportMetricsBlock({super.key, required this.reportMetrics});
@override
Widget build(BuildContext context) {
final metricsData = _controller.getReportMetrics();
final powerUsage = metricsData['powerUsage'] as Map<String, dynamic>;
final pendingAlerts = metricsData['pendingAlerts'] as Map<String, dynamic>;
final powerUsage = reportMetrics['powerUsage'] as Map<String, dynamic>;
final pendingAlerts = reportMetrics['pendingAlerts'] as Map<String, dynamic>;
return Container(
width: double.infinity,
padding: EdgeInsets.symmetric(horizontal: 20.w),
child: Row(
children: [
Expanded(
child: _buildMetricCard(
title: '今日用电',
value: powerUsage['value'],
unit: powerUsage['unit'] as String,
changeText: powerUsage['changeText'] as String,
changeColor: Color.fromRGBO(34, 197, 94, 1),
progress: powerUsage['progress'] as double,
progressColor: Color.fromRGBO(59, 130, 246, 1),
onTap: () => _controller.navigateToEnergyDetail(),
),
return Row(
children: [
Expanded(
child: _buildMetricCard(
title: '今日用电',
value: powerUsage['value'],
unit: powerUsage['unit'] as String,
changeText: powerUsage['changeText'] as String,
changeColor: const Color.fromRGBO(16, 185, 129, 1),
progress: powerUsage['progress'] as double,
progressColor: const Color.fromRGBO(59, 130, 246, 1),
),
SizedBox(width: 16.w),
Expanded(
child: _buildMetricCard(
title: '待处理告警',
value: pendingAlerts['value'],
unit: pendingAlerts['unit'] as String,
changeText: pendingAlerts['changeText'] as String,
changeColor: Color.fromRGBO(246, 158, 11, 1),
progress: pendingAlerts['progress'] as double,
progressColor: Color.fromRGBO(249, 115, 22, 1),
),
),
SizedBox(width: 20.w),
Expanded(
child: _buildMetricCard(
title: '待处理告警',
value: pendingAlerts['value'],
unit: pendingAlerts['unit'] as String,
changeText: pendingAlerts['changeText'] as String,
changeColor: const Color.fromRGBO(251, 191, 36, 1),
progress: pendingAlerts['progress'] as double,
progressColor: const Color.fromRGBO(251, 191, 36, 1),
),
],
),
),
],
);
}
Widget _buildMetricCard({
required String title,
required num value,
required int value,
required String unit,
required String changeText,
required Color changeColor,
required double progress,
required Color progressColor,
VoidCallback? onTap,
}) {
return GestureDetector(
onTap: onTap,
child: Container(
padding: EdgeInsets.all(20.w),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20.r),
color: Color.fromRGBO(30, 41, 59, 1),
border: Border.all(
color: Color.fromRGBO(71, 85, 105, 1),
width: 1.w,
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
title,
style: TextStyle(
fontSize: 16.sp,
color: Colors.grey,
),
),
Text(
changeText,
style: TextStyle(
fontSize: 14.sp,
color: changeColor,
fontWeight: FontWeight.w500,
),
),
],
return Container(
padding: EdgeInsets.all(20.w),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16.r),
color: Colors.white,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: TextStyle(
fontSize: 24.sp,
color: const Color.fromRGBO(107, 114, 128, 1),
),
SizedBox(height: 16.h),
Row(
crossAxisAlignment: CrossAxisAlignment.baseline,
textBaseline: TextBaseline.alphabetic,
children: [
Text(
value.toString().replaceAllMapped(
RegExp(r'(\d)(?=(\d{3})+(?!\d))'),
(Match match) => '${match[1]},',
),
style: TextStyle(
fontSize: 36.sp,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
SizedBox(height: 12.h),
Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
'$value',
style: TextStyle(
fontSize: 40.sp,
fontWeight: FontWeight.bold,
color: const Color.fromRGBO(31, 41, 55, 1),
),
SizedBox(width: 4.w),
Text(
),
SizedBox(width: 4.w),
Padding(
padding: EdgeInsets.only(bottom: 6.h),
child: Text(
unit,
style: TextStyle(
fontSize: 16.sp,
color: Colors.grey,
fontSize: 20.sp,
color: const Color.fromRGBO(107, 114, 128, 1),
),
),
],
),
SizedBox(height: 16.h),
Container(
height: 8.h,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4.r),
color: Color.fromRGBO(51, 65, 85, 1),
),
child: FractionallySizedBox(
widthFactor: progress,
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4.r),
color: progressColor,
),
],
),
SizedBox(height: 8.h),
Row(
children: [
Text(
changeText,
style: TextStyle(
fontSize: 20.sp,
color: changeColor,
),
),
],
),
SizedBox(height: 12.h),
ClipRRect(
borderRadius: BorderRadius.circular(4.r),
child: LinearProgressIndicator(
value: progress,
backgroundColor: const Color.fromRGBO(229, 231, 235, 1),
valueColor: AlwaysStoppedAnimation<Color>(progressColor),
minHeight: 6.h,
),
],
),
),
],
),
);
}
}
}
\ No newline at end of file
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:smart_hotel_app/views/report/index/controller.dart';
class RoomStatusDistribution extends StatefulWidget {
const RoomStatusDistribution({super.key});
class RoomStatusDistribution extends StatelessWidget {
final Map<String, dynamic> roomStatusDistribution;
@override
State<RoomStatusDistribution> createState() => _RoomStatusDistributionState();
}
class _RoomStatusDistributionState extends State<RoomStatusDistribution> {
late final ReportIndexController _controller;
@override
void initState() {
super.initState();
_controller = ReportIndexController(context);
}
const RoomStatusDistribution(
{super.key, required this.roomStatusDistribution});
@override
Widget build(BuildContext context) {
final distributionData = _controller.getRoomStatusDistribution();
final statuses = distributionData['statuses'] as List<Map<String, dynamic>>;
final totalRooms = distributionData['totalRooms'] as int;
final statuses =
roomStatusDistribution['statuses'] as List<Map<String, dynamic>>;
final totalRooms = roomStatusDistribution['totalRooms'] as int;
return GestureDetector(
onTap: () => _controller.navigateToRoomDetail(),
child: Container(
width: double.infinity,
margin: EdgeInsets.symmetric(horizontal: 20.w),
padding: EdgeInsets.all(20.w),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20.r),
color: Color.fromRGBO(30, 41, 59, 1),
border: Border.all(
color: Color.fromRGBO(71, 85, 105, 1),
width: 1.w,
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
distributionData['title'] as String,
style: TextStyle(
fontSize: 18.sp,
fontWeight: FontWeight.bold,
color: Colors.white,
),
return Container(
width: double.infinity,
padding: EdgeInsets.all(28.w),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(24.r),
color: Colors.white,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
roomStatusDistribution['title'] as String,
style: TextStyle(
fontSize: 28.sp,
fontWeight: FontWeight.normal,
color: const Color.fromRGBO(107, 114, 128, 1),
),
Text(
'共 $totalRooms 间',
style: TextStyle(
fontSize: 14.sp,
color: Colors.grey,
),
),
Text(
'共 $totalRooms 间',
style: TextStyle(
fontSize: 22.sp,
color: const Color.fromRGBO(156, 163, 175, 1),
),
],
),
SizedBox(height: 20.h),
...statuses.map((status) {
final count = status['count'] as int;
final color = status['color'] as Color;
final percentage = (count / totalRooms * 100).toStringAsFixed(0);
return Padding(
padding: EdgeInsets.only(bottom: 16.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
width: 12.w,
height: 12.w,
decoration: BoxDecoration(
color: color,
borderRadius: BorderRadius.circular(6.r),
),
],
),
SizedBox(height: 20.h),
...statuses.map((status) {
final count = status['count'] as int;
final color = status['color'] as Color;
final name = status['name'] as String;
final percentage = totalRooms > 0 ? count / totalRooms : 0.0;
return Padding(
padding: EdgeInsets.only(bottom: 16.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Container(
width: 12.w,
height: 12.h,
decoration: BoxDecoration(
color: color,
shape: BoxShape.circle,
),
),
),
SizedBox(width: 10.w),
Expanded(
child: Text(
status['name'] as String,
SizedBox(width: 8.w),
Text(
name,
style: TextStyle(
fontSize: 16.sp,
color: Colors.white,
fontSize: 24.sp,
color: const Color.fromRGBO(55, 65, 81, 1),
),
),
],
),
Text(
'$count 间',
style: TextStyle(
fontSize: 24.sp,
color: const Color.fromRGBO(55, 65, 81, 1),
),
Text(
'$count间 ($percentage%)',
style: TextStyle(
fontSize: 16.sp,
color: color,
fontWeight: FontWeight.bold,
),
),
],
),
SizedBox(height: 8.h),
Stack(
children: [
Container(
height: 8.h,
decoration: BoxDecoration(
color: const Color.fromRGBO(229, 231, 235, 1),
borderRadius: BorderRadius.circular(4.r),
),
],
),
SizedBox(height: 8.h),
Container(
width: double.infinity,
height: 12.h,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(6.r),
color: Color.fromRGBO(51, 65, 85, 1),
),
child: Align(
alignment: Alignment.centerLeft,
child: FractionallySizedBox(
widthFactor: count / totalRooms,
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(6.r),
color: color,
),
FractionallySizedBox(
widthFactor: percentage,
child: Container(
height: 8.h,
decoration: BoxDecoration(
color: color,
borderRadius: BorderRadius.circular(4.r),
),
),
),
),
],
),
);
}).toList(),
],
),
],
),
],
),
);
}).toList(),
],
),
);
}
}
}
\ No newline at end of file
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:fl_chart/fl_chart.dart';
import 'package:smart_hotel_app/views/report/index/controller.dart';
class WeeklyPowerChart extends StatefulWidget {
const WeeklyPowerChart({super.key});
class WeeklyPowerChart extends StatelessWidget {
final Map<String, dynamic> weeklyPowerUsage;
@override
State<WeeklyPowerChart> createState() => _WeeklyPowerChartState();
}
class _WeeklyPowerChartState extends State<WeeklyPowerChart> {
late final ReportIndexController _controller;
@override
void initState() {
super.initState();
_controller = ReportIndexController(context);
}
const WeeklyPowerChart({super.key, required this.weeklyPowerUsage});
@override
Widget build(BuildContext context) {
final chartData = _controller.getWeeklyPowerUsage();
final data = chartData['data'] as List<Map<String, dynamic>>;
final data = weeklyPowerUsage['data'] as List<Map<String, dynamic>>;
return Container(
width: double.infinity,
margin: EdgeInsets.symmetric(horizontal: 20.w),
padding: EdgeInsets.all(20.w),
padding: EdgeInsets.all(28.w),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20.r),
color: Color.fromRGBO(30, 41, 59, 1),
border: Border.all(
color: Color.fromRGBO(71, 85, 105, 1),
width: 1.w,
),
borderRadius: BorderRadius.circular(24.r),
color: Colors.white,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
......@@ -43,18 +25,18 @@ class _WeeklyPowerChartState extends State<WeeklyPowerChart> {
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'${chartData['title']} (${chartData['unit']})',
'${weeklyPowerUsage['title']} (${weeklyPowerUsage['unit']})',
style: TextStyle(
fontSize: 18.sp,
fontWeight: FontWeight.bold,
color: Colors.white,
fontSize: 28.sp,
fontWeight: FontWeight.normal,
color: const Color.fromRGBO(107, 114, 128, 1),
),
),
Text(
chartData['dateRange'] as String,
weeklyPowerUsage['dateRange'] as String,
style: TextStyle(
fontSize: 14.sp,
color: Colors.grey,
fontSize: 22.sp,
color: const Color.fromRGBO(156, 163, 175, 1),
),
),
],
......@@ -62,24 +44,23 @@ class _WeeklyPowerChartState extends State<WeeklyPowerChart> {
SizedBox(height: 20.h),
SizedBox(
height: 200.h,
child: LineChart(
LineChartData(
gridData: FlGridData(
show: true,
drawVerticalLine: false,
drawHorizontalLine: true,
getDrawingHorizontalLine: (value) {
return FlLine(
color: Color.fromRGBO(71, 85, 105, 1),
strokeWidth: 1,
);
},
),
child: BarChart(
BarChartData(
alignment: BarChartAlignment.spaceBetween,
maxY: 300,
barTouchData: BarTouchData(enabled: false),
titlesData: FlTitlesData(
show: true,
rightTitles: const AxisTitles(
sideTitles: SideTitles(showTitles: false),
),
topTitles: const AxisTitles(
sideTitles: SideTitles(showTitles: false),
),
bottomTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
reservedSize: 28,
getTitlesWidget: (value, meta) {
final index = value.toInt();
if (index >= 0 && index < data.length) {
......@@ -88,67 +69,40 @@ class _WeeklyPowerChartState extends State<WeeklyPowerChart> {
child: Text(
data[index]['day'] as String,
style: TextStyle(
color: Colors.grey,
fontSize: 14.sp,
fontSize: 22.sp,
color: const Color.fromRGBO(156, 163, 175, 1),
),
),
);
}
return const SizedBox.shrink();
},
reservedSize: 30.h,
),
),
leftTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: false,
),
),
topTitles: AxisTitles(
leftTitles: const AxisTitles(
sideTitles: SideTitles(showTitles: false),
),
rightTitles: AxisTitles(
sideTitles: SideTitles(showTitles: false),
),
),
borderData: FlBorderData(
show: false,
),
minX: 0,
maxX: (data.length - 1).toDouble(),
minY: 0,
maxY: 300,
lineBarsData: [
LineChartBarData(
spots: data.asMap().entries.map((entry) {
final index = entry.key;
final item = entry.value;
return FlSpot(
index.toDouble(),
(item['value'] as num).toDouble(),
);
}).toList(),
isCurved: true,
color: Color.fromRGBO(59, 130, 246, 1),
barWidth: 3,
isStrokeCapRound: true,
dotData: FlDotData(
show: true,
getDotPainter: (spot, percent, barData, index) {
return FlDotCirclePainter(
radius: 4,
color: Color.fromRGBO(59, 130, 246, 1),
strokeWidth: 2,
strokeColor: Colors.white,
);
},
),
belowBarData: BarAreaData(
show: true,
color: Color.fromRGBO(59, 130, 246, 0.2),
),
),
],
borderData: FlBorderData(show: false),
gridData: const FlGridData(show: false),
barGroups: data.asMap().entries.map((entry) {
final index = entry.key;
final value = (entry.value['value'] as int).toDouble();
return BarChartGroupData(
x: index,
barRods: [
BarChartRodData(
toY: value,
color: const Color.fromRGBO(59, 130, 246, 1),
width: 24.w,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(4),
topRight: Radius.circular(4),
),
),
],
);
}).toList(),
),
),
),
......@@ -156,4 +110,4 @@ class _WeeklyPowerChartState extends State<WeeklyPowerChart> {
),
);
}
}
}
\ No newline at end of file
class ReportRoomController {
}
\ No newline at end of file
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:smart_hotel_app/views/report/room/cubit/room_report_state.dart';
class RoomReportCubit extends Cubit<RoomReportState> {
RoomReportCubit() : super(const RoomReportState());
}
\ No newline at end of file
import 'package:equatable/equatable.dart';
class RoomReportState extends Equatable {
const RoomReportState();
RoomReportState copyWith() {
return const RoomReportState();
}
@override
List<Object?> get props => [];
}
\ No newline at end of file
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:smart_hotel_app/views/report/room/controller.dart';
import 'package:smart_hotel_app/views/report/room/cubit/room_report_cubit.dart';
@RoutePage()
class ReportRoomDetailView extends StatefulWidget {
class ReportRoomDetailView extends StatelessWidget {
const ReportRoomDetailView({super.key});
@override
State<ReportRoomDetailView> createState() => _ReportRoomDetailViewState();
}
class _ReportRoomDetailViewState extends State<ReportRoomDetailView> {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color.fromRGBO(15, 23, 42, 1),
appBar: AppBar(
backgroundColor: Color.fromRGBO(15, 23, 42, 1),
elevation: 0,
leading: IconButton(
icon: Icon(Icons.arrow_back, color: Colors.white),
onPressed: () {
context.popRoute();
},
return BlocProvider(
create: (_) => RoomReportCubit(),
child: Scaffold(
backgroundColor: const Color.fromRGBO(15, 23, 42, 1),
appBar: AppBar(
backgroundColor: const Color.fromRGBO(15, 23, 42, 1),
elevation: 0,
leading: IconButton(
icon: const Icon(Icons.arrow_back, color: Colors.white),
onPressed: () {
context.popRoute();
},
),
title: Text(
'能耗分析',
style: TextStyle(
color: Colors.white,
fontSize: 18.sp,
fontWeight: FontWeight.bold,
),
),
centerTitle: true,
),
title: Text(
'能耗分析',
style: TextStyle(
color: Colors.white,
fontSize: 18.sp,
fontWeight: FontWeight.bold,
body: SingleChildScrollView(
child: Column(
children: [
SizedBox(height: 10.h),
],
),
),
centerTitle: true,
),
body: SingleChildScrollView(
child: Column(
children: [
SizedBox(height: 10.h),
],
),
),
);
}
}
\ No newline at end of file
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
@RoutePage()
......
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter/material.dart';
import 'package:smart_hotel_app/views/service/device/cubit/service_device_state.dart';
class ServiceDeviceCubit extends Cubit<ServiceDeviceState> {
ServiceDeviceCubit() : super(const ServiceDeviceState()) {
initData();
}
void initData() {
emit(state.copyWith(
isOn: true,
targetTemperature: 24,
currentTemperature: 26,
minTemperature: 16,
maxTemperature: 34,
modeData: [
{'name': '制冷', 'icon': Icons.ac_unit, 'isSelected': true},
{'name': '制热', 'icon': Icons.heat_pump, 'isSelected': false},
{'name': '除湿', 'icon': Icons.water_drop, 'isSelected': false},
{'name': '送风', 'icon': Icons.air, 'isSelected': false},
],
currentSpeed: '自动',
speedOptions: ['自动', '低', '中', '高'],
));
}
void togglePower(bool value) {
emit(state.copyWith(isOn: value));
}
void setTargetTemperature(int temp) {
if (temp >= state.minTemperature && temp <= state.maxTemperature) {
emit(state.copyWith(targetTemperature: temp));
}
}
void increaseTemperature() {
if (state.targetTemperature < state.maxTemperature) {
emit(state.copyWith(targetTemperature: state.targetTemperature + 1));
}
}
void decreaseTemperature() {
if (state.targetTemperature > state.minTemperature) {
emit(state.copyWith(targetTemperature: state.targetTemperature - 1));
}
}
void selectMode(int index) {
final updatedModeData = state.modeData.map((mode) {
return {...mode, 'isSelected': false};
}).toList();
updatedModeData[index]['isSelected'] = true;
emit(state.copyWith(modeData: updatedModeData));
}
void setWindSpeed(String speed) {
emit(state.copyWith(currentSpeed: speed));
}
}
\ No newline at end of file
import 'package:equatable/equatable.dart';
class ServiceDeviceState extends Equatable {
final bool isOn;
final int targetTemperature;
final int currentTemperature;
final int minTemperature;
final int maxTemperature;
final List<Map<String, dynamic>> modeData;
final String currentSpeed;
final List<String> speedOptions;
const ServiceDeviceState({
this.isOn = true,
this.targetTemperature = 24,
this.currentTemperature = 26,
this.minTemperature = 16,
this.maxTemperature = 34,
this.modeData = const [],
this.currentSpeed = '自动',
this.speedOptions = const ['自动', '低', '中', '高'],
});
ServiceDeviceState copyWith({
bool? isOn,
int? targetTemperature,
int? currentTemperature,
int? minTemperature,
int? maxTemperature,
List<Map<String, dynamic>>? modeData,
String? currentSpeed,
List<String>? speedOptions,
}) {
return ServiceDeviceState(
isOn: isOn ?? this.isOn,
targetTemperature: targetTemperature ?? this.targetTemperature,
currentTemperature: currentTemperature ?? this.currentTemperature,
minTemperature: minTemperature ?? this.minTemperature,
maxTemperature: maxTemperature ?? this.maxTemperature,
modeData: modeData ?? this.modeData,
currentSpeed: currentSpeed ?? this.currentSpeed,
speedOptions: speedOptions ?? this.speedOptions,
);
}
@override
List<Object?> get props => [
isOn,
targetTemperature,
currentTemperature,
minTemperature,
maxTemperature,
modeData,
currentSpeed,
speedOptions,
];
}
\ No newline at end of file
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:smart_hotel_app/views/service/device/cubit/service_device_cubit.dart';
import 'package:smart_hotel_app/views/service/device/cubit/service_device_state.dart';
import 'package:smart_hotel_app/views/service/device/widget/mode_selection_block.dart';
import 'package:smart_hotel_app/views/service/device/widget/temperature_control_block.dart';
import 'package:smart_hotel_app/views/service/device/widget/wind_speed_block.dart';
@RoutePage()
class ServiceDeviceDetailView extends StatelessWidget {
const ServiceDeviceDetailView({super.key});
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => ServiceDeviceCubit(),
child: Scaffold(
backgroundColor: const Color.fromRGBO(242, 243, 245, 1),
appBar: AppBar(
backgroundColor: Colors.white,
elevation: 0,
leading: IconButton(
icon: const Icon(Icons.arrow_back_ios,
color: Color.fromRGBO(100, 116, 139, 1)),
onPressed: () {
context.popRoute();
},
),
title: Text(
'空调控制',
style: TextStyle(
color: const Color.fromRGBO(10, 13, 20, 1),
fontSize: 32.sp,
fontWeight: FontWeight.w200,
),
),
centerTitle: true,
),
body: SingleChildScrollView(
padding: EdgeInsets.symmetric(horizontal: 28.w),
child: BlocBuilder<ServiceDeviceCubit, ServiceDeviceState>(
builder: (context, state) {
final cubit = context.read<ServiceDeviceCubit>();
return Column(
children: [
SizedBox(height: 20.h),
TemperatureControlBlock(
isOn: state.isOn,
targetTemperature: state.targetTemperature,
currentTemperature: state.currentTemperature,
minTemperature: state.minTemperature,
maxTemperature: state.maxTemperature,
onTogglePower: cubit.togglePower,
onIncreaseTemperature: cubit.increaseTemperature,
onDecreaseTemperature: cubit.decreaseTemperature,
),
SizedBox(height: 20.h),
ModeSelectionBlock(
modeData: state.modeData,
onSelectMode: cubit.selectMode,
),
SizedBox(height: 20.h),
WindSpeedBlock(
currentSpeed: state.currentSpeed,
speedOptions: state.speedOptions,
onSetWindSpeed: cubit.setWindSpeed,
),
SizedBox(height: 20.h),
],
);
},
),
),
),
);
}
}
\ No newline at end of file
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:smart_hotel_app/views/report/index/controller.dart';
class ReportHeaderBlock extends StatefulWidget {
const ReportHeaderBlock({super.key});
class ModeSelectionBlock extends StatefulWidget {
final List<Map<String, dynamic>> modeData;
final Function(int) onSelectMode;
@override
State<ReportHeaderBlock> createState() => _ReportHeaderBlockState();
}
class _ReportHeaderBlockState extends State<ReportHeaderBlock> {
late final ReportIndexController _controller;
const ModeSelectionBlock({
super.key,
required this.modeData,
required this.onSelectMode,
});
@override
void initState() {
super.initState();
_controller = ReportIndexController(context);
}
State<ModeSelectionBlock> createState() => _ModeSelectionBlockState();
}
class _ModeSelectionBlockState extends State<ModeSelectionBlock> {
@override
Widget build(BuildContext context) {
final headerData = _controller.getReportHeader();
return Container(
width: double.infinity,
padding: EdgeInsets.symmetric(horizontal: 20.w, vertical: 24.h),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
padding: EdgeInsets.all(20.w),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(24.r),
color: Colors.white,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
headerData['title'] as String,
style: TextStyle(
fontSize: 28.sp,
fontWeight: FontWeight.bold,
color: Colors.white,
Text(
'模式选择',
style: TextStyle(
fontSize: 28.sp,
fontWeight: FontWeight.bold,
color: const Color.fromRGBO(31, 41, 55, 1),
),
),
SizedBox(height: 20.h),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: widget.modeData.asMap().entries.map((entry) {
final index = entry.key;
final mode = entry.value;
final isSelected = mode['isSelected'] as bool;
return GestureDetector(
onTap: () => widget.onSelectMode(index),
child: Container(
width: 120.w,
padding: EdgeInsets.symmetric(vertical: 16.h),
decoration: BoxDecoration(
color: isSelected
? const Color.fromRGBO(59, 130, 246, 1)
: const Color.fromRGBO(229, 231, 235, 1),
borderRadius: BorderRadius.circular(16.r),
),
),
SizedBox(height: 8.h),
Text(
headerData['subtitle'] as String,
style: TextStyle(
fontSize: 16.sp,
color: Colors.grey,
child: Column(
children: [
Icon(
mode['icon'] as IconData,
color: isSelected
? Colors.white
: const Color.fromRGBO(107, 114, 128, 1),
size: 32.sp,
),
SizedBox(height: 8.h),
Text(
mode['name'] as String,
style: TextStyle(
fontSize: 22.sp,
color: isSelected
? Colors.white
: const Color.fromRGBO(107, 114, 128, 1),
),
),
],
),
),
],
),
),
Container(
width: 56.w,
height: 56.w,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Color.fromRGBO(51, 65, 85, 1),
),
child: Icon(
Icons.star,
color: Color.fromRGBO(251, 191, 36, 1),
size: 28.sp,
),
);
}).toList(),
),
],
),
);
}
}
}
\ No newline at end of file
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:flutter_switch/flutter_switch.dart';
class TemperatureControlBlock extends StatefulWidget {
final bool isOn;
final int targetTemperature;
final int currentTemperature;
final int minTemperature;
final int maxTemperature;
final Function(bool) onTogglePower;
final VoidCallback onIncreaseTemperature;
final VoidCallback onDecreaseTemperature;
const TemperatureControlBlock({
super.key,
required this.isOn,
required this.targetTemperature,
required this.currentTemperature,
required this.minTemperature,
required this.maxTemperature,
required this.onTogglePower,
required this.onIncreaseTemperature,
required this.onDecreaseTemperature,
});
@override
State<TemperatureControlBlock> createState() =>
_TemperatureControlBlockState();
}
class _TemperatureControlBlockState extends State<TemperatureControlBlock> {
@override
Widget build(BuildContext context) {
final progress = (widget.targetTemperature - widget.minTemperature) /
(widget.maxTemperature - widget.minTemperature);
return Container(
width: double.infinity,
padding: EdgeInsets.all(20.w),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(24.r),
color: Colors.white,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'温度控制',
style: TextStyle(
fontSize: 28.sp,
fontWeight: FontWeight.bold,
color: const Color.fromRGBO(31, 41, 55, 1),
),
),
FlutterSwitch(
value: widget.isOn,
onToggle: widget.onTogglePower,
activeColor: const Color.fromRGBO(59, 130, 246, 1),
inactiveColor: const Color.fromRGBO(209, 213, 219, 1),
width: 60.w,
height: 30.h,
toggleSize: 26,
),
],
),
SizedBox(height: 20.h),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
'${widget.currentTemperature}°',
style: TextStyle(
fontSize: 64.sp,
fontWeight: FontWeight.bold,
color: widget.isOn
? const Color.fromRGBO(31, 41, 55, 1)
: const Color.fromRGBO(209, 213, 219, 1),
),
),
Column(
children: [
GestureDetector(
onTap: widget.onIncreaseTemperature,
child: Container(
width: 48.w,
height: 48.h,
decoration: BoxDecoration(
color: const Color.fromRGBO(229, 231, 235, 1),
borderRadius: BorderRadius.circular(12.r),
),
child: Icon(
Icons.add,
color: const Color.fromRGBO(107, 114, 128, 1),
size: 24.sp,
),
),
),
SizedBox(height: 8.h),
Text(
'${widget.targetTemperature}°',
style: TextStyle(
fontSize: 24.sp,
color: const Color.fromRGBO(107, 114, 128, 1),
),
),
SizedBox(height: 8.h),
GestureDetector(
onTap: widget.onDecreaseTemperature,
child: Container(
width: 48.w,
height: 48.h,
decoration: BoxDecoration(
color: const Color.fromRGBO(229, 231, 235, 1),
borderRadius: BorderRadius.circular(12.r),
),
child: Icon(
Icons.remove,
color: const Color.fromRGBO(107, 114, 128, 1),
size: 24.sp,
),
),
),
],
),
],
),
SizedBox(height: 20.h),
Stack(
children: [
Container(
height: 8.h,
decoration: BoxDecoration(
color: const Color.fromRGBO(229, 231, 235, 1),
borderRadius: BorderRadius.circular(4.r),
),
),
FractionallySizedBox(
widthFactor: progress,
child: Container(
height: 8.h,
decoration: BoxDecoration(
color: const Color.fromRGBO(59, 130, 246, 1),
borderRadius: BorderRadius.circular(4.r),
),
),
),
],
),
SizedBox(height: 8.h),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'${widget.minTemperature}°',
style: TextStyle(
fontSize: 22.sp,
color: const Color.fromRGBO(156, 163, 175, 1),
),
),
Text(
'${widget.maxTemperature}°',
style: TextStyle(
fontSize: 22.sp,
color: const Color.fromRGBO(156, 163, 175, 1),
),
),
],
),
],
),
);
}
}
\ No newline at end of file
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
class WindSpeedBlock extends StatefulWidget {
final String currentSpeed;
final List<String> speedOptions;
final Function(String) onSetWindSpeed;
const WindSpeedBlock({
super.key,
required this.currentSpeed,
required this.speedOptions,
required this.onSetWindSpeed,
});
@override
State<WindSpeedBlock> createState() => _WindSpeedBlockState();
}
class _WindSpeedBlockState extends State<WindSpeedBlock> {
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
padding: EdgeInsets.all(20.w),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(24.r),
color: Colors.white,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'风速控制',
style: TextStyle(
fontSize: 28.sp,
fontWeight: FontWeight.bold,
color: const Color.fromRGBO(31, 41, 55, 1),
),
),
SizedBox(height: 20.h),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: widget.speedOptions.map((speed) {
final isSelected = speed == widget.currentSpeed;
return GestureDetector(
onTap: () => widget.onSetWindSpeed(speed),
child: Container(
width: 120.w,
padding: EdgeInsets.symmetric(vertical: 16.h),
decoration: BoxDecoration(
color: isSelected
? const Color.fromRGBO(59, 130, 246, 1)
: const Color.fromRGBO(229, 231, 235, 1),
borderRadius: BorderRadius.circular(16.r),
),
child: Center(
child: Text(
speed,
style: TextStyle(
fontSize: 22.sp,
color: isSelected
? Colors.white
: const Color.fromRGBO(107, 114, 128, 1),
),
),
),
),
);
}).toList(),
),
],
),
);
}
}
\ No newline at end of file
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:smart_hotel_app/views/service/index/controller.dart';
class ServiceHeadBlock extends StatefulWidget {
const ServiceHeadBlock({super.key});
@override
State<ServiceHeadBlock> createState() => _ServiceHeadBlockState();
}
class _ServiceHeadBlockState extends State<ServiceHeadBlock> {
late final ServiceIndexController _controller;
@override
void initState() {
super.initState();
_controller = ServiceIndexController(context);
}
@override
Widget build(BuildContext context) {
final pendingCount = _controller.getPendingCount();
return Container(
width: double.infinity,
padding: EdgeInsets.only(top: ScreenUtil().statusBarHeight),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'客房服务看板',
style: TextStyle(
fontSize: 40.sp,
fontWeight: FontWeight.w200,
color: Color.fromRGBO(10, 13, 20, 1),
),
),
SizedBox(height: 8.h),
Text(
'服务员视角·$pendingCount条待响应需求',
style: TextStyle(
fontSize: 24.sp,
color: Color.fromRGBO(100, 116, 139, 1),
),
),
],
),
Container(
width: 64.w,
height: 64.h,
margin: EdgeInsets.only(top: 5.h),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(48.r),
color: Color.fromRGBO(229, 241, 255, 1),
border: Border.all(
color: Color.fromRGBO(73, 149, 234, 1),
width: 1.w,
),
),
child:
Image.asset("lib/assets/icon/person1.png",width: 38.w,height: 38.h),
// Icon(
// Icons.person,
// color: Color.fromRGBO(64, 158, 255, 1),
// size: 26.sp,
// ),
),
],
),
);
}
}
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:smart_hotel_app/views/service/index/controller.dart';
class ServiceStatsBlock extends StatefulWidget {
const ServiceStatsBlock({super.key});
@override
State<ServiceStatsBlock> createState() => _ServiceStatsBlockState();
}
class _ServiceStatsBlockState extends State<ServiceStatsBlock> {
late final ServiceIndexController _controller;
@override
void initState() {
super.initState();
_controller = ServiceIndexController(context);
}
@override
Widget build(BuildContext context) {
final pendingCount = _controller.getPendingCount();
final occupiedCount = _controller.getOccupiedRooms();
final vacantCount = _controller.getVacantRooms();
return Container(
width: double.infinity,
child: Row(
children: [
Expanded(
child: _buildStatCard(
pendingCount.toString(),
'待响应',
Color.fromRGBO(73, 149, 234, 1),
),
),
SizedBox(width: 12.w),
Expanded(
child: _buildStatCard(
occupiedCount.toString(),
'入住中',
Color.fromRGBO(20, 184, 166, 1),
),
),
SizedBox(width: 12.w),
Expanded(
child: _buildStatCard(
vacantCount.toString(),
'空闲可清洁',
Color.fromRGBO(10, 13, 20, 1),
),
),
],
),
);
}
Widget _buildStatCard(String value, String label, Color valueColor) {
return Container(
padding: EdgeInsets.symmetric(vertical: 20.h),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(24.r),
color: Colors.white,
boxShadow: [
BoxShadow(
color: Color.fromRGBO(0, 0, 0, 0.05),
blurRadius: 10.r,
offset: Offset(0, 2),
),
],
),
child: Column(
children: [
Text(
value,
style: TextStyle(
fontSize: 60.sp,
fontWeight: FontWeight.bold,
color: valueColor,
),
),
// SizedBox(height: 5.h),
Text(
label,
style: TextStyle(
fontSize: 24.sp,
color: Color.fromRGBO(100, 116, 139, 1),
),
),
],
),
);
}
}
import 'dart:math';
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:smart_hotel_app/routes/app_router.gr.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:smart_hotel_app/views/service/index/cubit/service_index_state.dart';
class ServiceIndexController {
final BuildContext context;
class ServiceIndexCubit extends Cubit<ServiceIndexState> {
Map<String, List<Map<String, dynamic>>>? _roomCache;
ServiceIndexController(this.context);
ServiceIndexCubit() : super(const ServiceIndexState()) {
initData();
}
Map<String, List<Map<String, dynamic>>>? _roomCache;
void initData() {
emit(state.copyWith(
pendingCount: 2,
occupiedRooms: 45,
vacantRooms: 7,
floors: ['1楼', '2楼', '3楼', '4楼', '5楼', '6楼'],
));
}
int getPendingCount() {
// 获取待响应需求数量
return 2;
void selectFloor(String? floor) {
emit(state.copyWith(selectedFloor: floor));
}
int getOccupiedRooms() {
// 获取入住中房间数
return 45;
void selectType(String? type) {
emit(state.copyWith(selectedType: type));
}
int getVacantRooms() {
// 获取空闲可清洁房间数
return 7;
void selectRoom(String? room) {
emit(state.copyWith(selectedRoom: room));
}
List<String> getFloors() {
// 获取楼层列表
return ['1楼', '2楼', '3楼', '4楼', '5楼', '6楼'];
void setSearchText(String text) {
emit(state.copyWith(searchText: text));
}
Map<String, List<Map<String, dynamic>>> _buildRoomCache() {
......@@ -53,7 +57,6 @@ class ServiceIndexController {
if (!_cacheValid) {
_roomCache = _buildRoomCache();
}
return _roomCache![floor] ?? [];
}
......@@ -72,22 +75,16 @@ class ServiceIndexController {
'type': types[typeIndex],
});
}
return rooms;
}
void powerOnRoom(String roomNumber) {
// 房间送电
// ignore: avoid_print
print('给 $roomNumber 号房送电');
}
void powerOffRoom(String roomNumber) {
// 房间断电
// ignore: avoid_print
print('给 $roomNumber 号房断电');
}
void viewRoom(String roomNumber) {
// 查看房间详情
context.pushRoute(ServiceRoomDetailRoute(roomNumber: roomNumber));
}
}
\ No newline at end of file
import 'package:equatable/equatable.dart';
class ServiceIndexState extends Equatable {
final int pendingCount;
final int occupiedRooms;
final int vacantRooms;
final List<String> floors;
final String? selectedFloor;
final String? selectedType;
final String? selectedRoom;
final String searchText;
final List<Map<String, dynamic>> rooms;
const ServiceIndexState({
this.pendingCount = 0,
this.occupiedRooms = 0,
this.vacantRooms = 0,
this.floors = const [],
this.selectedFloor,
this.selectedType,
this.selectedRoom,
this.searchText = '',
this.rooms = const [],
});
ServiceIndexState copyWith({
int? pendingCount,
int? occupiedRooms,
int? vacantRooms,
List<String>? floors,
String? selectedFloor,
String? selectedType,
String? selectedRoom,
String? searchText,
List<Map<String, dynamic>>? rooms,
}) {
return ServiceIndexState(
pendingCount: pendingCount ?? this.pendingCount,
occupiedRooms: occupiedRooms ?? this.occupiedRooms,
vacantRooms: vacantRooms ?? this.vacantRooms,
floors: floors ?? this.floors,
selectedFloor: selectedFloor ?? this.selectedFloor,
selectedType: selectedType ?? this.selectedType,
selectedRoom: selectedRoom ?? this.selectedRoom,
searchText: searchText ?? this.searchText,
rooms: rooms ?? this.rooms,
);
}
@override
List<Object?> get props => [
pendingCount,
occupiedRooms,
vacantRooms,
floors,
selectedFloor,
selectedType,
selectedRoom,
searchText,
rooms,
];
}
\ No newline at end of file
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:smart_hotel_app/views/service/index/child/service_head_block.dart';
import 'package:smart_hotel_app/views/service/index/child/service_stats_block.dart';
import 'package:smart_hotel_app/views/service/index/child/room_management_block.dart';
import 'package:smart_hotel_app/views/service/index/controller.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:smart_hotel_app/views/service/index/cubit/service_index_cubit.dart';
import 'package:smart_hotel_app/views/service/index/cubit/service_index_state.dart';
import 'package:smart_hotel_app/views/service/index/widget/room_management_block.dart';
import 'package:smart_hotel_app/views/service/index/widget/service_head_block.dart';
import 'package:smart_hotel_app/views/service/index/widget/service_stats_block.dart';
@RoutePage()
class ServiceIndexView extends StatefulWidget {
class ServiceIndexView extends StatelessWidget {
const ServiceIndexView({super.key});
@override
State<ServiceIndexView> createState() => _ServiceIndexViewState();
}
class _ServiceIndexViewState extends State<ServiceIndexView> {
late final ServiceIndexController _controller;
@override
void initState() {
super.initState();
_controller = ServiceIndexController(context);
}
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
return SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: constraints.maxHeight,
),
child: Container(
width: double.infinity,
color: Color.fromRGBO(242, 243, 245, 1),
padding: EdgeInsets.symmetric(horizontal: 28.w),
child: Column(
children: [
ServiceHeadBlock(),
SizedBox(height: 10.h),
ServiceStatsBlock(),
SizedBox(height: 10.h),
RoomManagementBlock(),
SizedBox(height: 10.h),
],
return BlocProvider(
create: (_) => ServiceIndexCubit(),
child: LayoutBuilder(
builder: (context, constraints) {
return SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(minHeight: constraints.maxHeight),
child: Container(
color: const Color.fromRGBO(242, 243, 245, 1),
child: BlocBuilder<ServiceIndexCubit, ServiceIndexState>(
builder: (context, state) {
return Column(
children: [
ServiceHeadBlock(pendingCount: state.pendingCount),
ServiceStatsBlock(
pendingCount: state.pendingCount,
occupiedCount: state.occupiedRooms,
vacantCount: state.vacantRooms,
),
RoomManagementBlock(),
],
);
},
),
),
),
),
);
},
);
},
),
);
}
}
\ No newline at end of file
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:smart_hotel_app/views/service/index/controller.dart';
import 'package:smart_hotel_app/views/service/index/cubit/service_index_cubit.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:smart_hotel_app/routes/app_router.gr.dart';
class RoomManagementBlock extends StatefulWidget {
const RoomManagementBlock({super.key});
......@@ -10,7 +13,6 @@ class RoomManagementBlock extends StatefulWidget {
}
class _RoomManagementBlockState extends State<RoomManagementBlock> {
late final ServiceIndexController _controller;
String? _selectedFloor;
String? _selectedType;
String? _selectedRoom;
......@@ -18,23 +20,15 @@ class _RoomManagementBlockState extends State<RoomManagementBlock> {
final TextEditingController _searchController = TextEditingController();
final ScrollController _scrollController = ScrollController();
@override
void initState() {
super.initState();
_controller = ServiceIndexController(context);
}
List<Map<String, dynamic>> _getFilteredRooms() {
List<Map<String, dynamic>> _getFilteredRooms(ServiceIndexCubit cubit) {
List<Map<String, dynamic>> allRooms;
if (_selectedFloor == null) {
allRooms = [];
for (final floor in _controller.getFloors()) {
allRooms.addAll(_controller.getRoomsByFloor(floor));
for (final floor in cubit.state.floors) {
allRooms.addAll(cubit.getRoomsByFloor(floor));
}
} else {
allRooms = _controller.getRoomsByFloor(_selectedFloor!);
allRooms = cubit.getRoomsByFloor(_selectedFloor!);
}
if (_selectedType != null) {
......@@ -50,22 +44,12 @@ class _RoomManagementBlockState extends State<RoomManagementBlock> {
.where((room) =>
(room['number'] as String).toLowerCase().contains(_searchText.toLowerCase()))
.toList();
debugPrint('搜索 "$_searchText" → 共 ${allRooms.length} 间 → 过滤后 ${filtered.length} 间');
return filtered;
}
void _showMessage(String message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(message),
duration: Duration(seconds: 2),
backgroundColor: Color.fromRGBO(51, 65, 85, 1),
),
);
}
@override
Widget build(BuildContext context) {
final cubit = context.read<ServiceIndexCubit>();
return Container(
width: double.infinity,
padding: EdgeInsets.all(20.w),
......@@ -74,30 +58,25 @@ class _RoomManagementBlockState extends State<RoomManagementBlock> {
color: Colors.white,
boxShadow: [
BoxShadow(
color: Color.fromRGBO(0, 0, 0, 0.03),
color: const Color.fromRGBO(0, 0, 0, 0.03),
blurRadius: 20.r,
offset: Offset(0, 4),
offset: const Offset(0, 4),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 楼层选择
_buildFloorSelector(),
_buildFloorSelector(cubit),
SizedBox(height: 18.h),
// 类型选择
_buildTypeSelector(),
SizedBox(height: 18.h),
// 搜索框
_buildSearchBox(),
SizedBox(height: 32.h),
_buildLine(),
SizedBox(height: 18.h),
// 房间区域
_buildRoomGrid(),
_buildRoomGrid(cubit),
SizedBox(height: 20.h),
// 选中状态提示
if (_selectedRoom != null)
Padding(
padding: EdgeInsets.only(bottom: 15.h),
......@@ -105,19 +84,18 @@ class _RoomManagementBlockState extends State<RoomManagementBlock> {
'已选:$_selectedRoom号房',
style: TextStyle(
fontSize: 24.sp,
color: Color.fromRGBO(100, 116, 139, 1),
color: const Color.fromRGBO(100, 116, 139, 1),
),
),
),
// 底部按钮
_buildBottomButtons(),
_buildBottomButtons(cubit),
],
),
);
}
Widget _buildFloorSelector() {
final floors = _controller.getFloors();
Widget _buildFloorSelector(ServiceIndexCubit cubit) {
final floors = cubit.state.floors;
return SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
......@@ -144,8 +122,11 @@ class _RoomManagementBlockState extends State<RoomManagementBlock> {
'$displayFloor楼',
style: TextStyle(
fontSize: 28.sp,
color: isSelected ? Color.fromRGBO(80, 162, 255, 1) : Color.fromRGBO(0, 0, 0, 1),
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
color: isSelected
? const Color.fromRGBO(80, 162, 255, 1)
: const Color.fromRGBO(0, 0, 0, 1),
fontWeight:
isSelected ? FontWeight.bold : FontWeight.normal,
),
),
SizedBox(height: 4.h),
......@@ -153,7 +134,9 @@ class _RoomManagementBlockState extends State<RoomManagementBlock> {
width: 50.w,
height: 3.h,
decoration: BoxDecoration(
color: isSelected ? Color.fromRGBO(80, 162, 255, 1) : Colors.transparent,
color: isSelected
? const Color.fromRGBO(80, 162, 255, 1)
: Colors.transparent,
borderRadius: BorderRadius.circular(2.r),
),
),
......@@ -175,7 +158,8 @@ class _RoomManagementBlockState extends State<RoomManagementBlock> {
final isSelected = _selectedType == type;
return Expanded(
child: Padding(
padding: EdgeInsets.only(right: index < types.length - 1 ? 10.w : 0),
padding:
EdgeInsets.only(right: index < types.length - 1 ? 10.w : 0),
child: GestureDetector(
onTap: () {
setState(() {
......@@ -191,18 +175,25 @@ class _RoomManagementBlockState extends State<RoomManagementBlock> {
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8.r),
border: Border.all(
color: isSelected ? Color.fromRGBO(73, 149, 234, 0.5) : Colors.transparent,
color: isSelected
? const Color.fromRGBO(73, 149, 234, 0.5)
: Colors.transparent,
width: 1.w,
),
color: isSelected ? Color.fromRGBO(73, 149, 234, 0.2) : Color.fromRGBO(238, 238, 238, 1),
color: isSelected
? const Color.fromRGBO(73, 149, 234, 0.2)
: const Color.fromRGBO(238, 238, 238, 1),
),
child: Center(
child: Text(
type,
style: TextStyle(
fontSize: 24.sp,
color: isSelected ? Color.fromRGBO(73, 149, 234, 1) : Color.fromRGBO(100, 116, 139, 1),
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
color: isSelected
? const Color.fromRGBO(73, 149, 234, 1)
: const Color.fromRGBO(100, 116, 139, 1),
fontWeight:
isSelected ? FontWeight.bold : FontWeight.normal,
),
),
),
......@@ -222,7 +213,7 @@ class _RoomManagementBlockState extends State<RoomManagementBlock> {
borderRadius: BorderRadius.circular(86.r),
color: Colors.white,
border: Border.all(
color: Color.fromRGBO(146, 191, 243, 1),
color: const Color.fromRGBO(146, 191, 243, 1),
width: 1.w,
),
),
......@@ -230,7 +221,7 @@ class _RoomManagementBlockState extends State<RoomManagementBlock> {
children: [
Icon(
Icons.search,
color: Color.fromRGBO(153, 153, 153, 1),
color: const Color.fromRGBO(153, 153, 153, 1),
size: 37.sp,
),
SizedBox(width: 26.w),
......@@ -244,13 +235,13 @@ class _RoomManagementBlockState extends State<RoomManagementBlock> {
},
style: TextStyle(
fontSize: 24.sp,
color: Color.fromRGBO(100, 116, 139, 1),
color: const Color.fromRGBO(100, 116, 139, 1),
),
decoration: InputDecoration(
hintText: '输入房号快速查找,如301...',
hintStyle: TextStyle(
fontSize: 24.sp,
color: Color.fromRGBO(100, 116, 139, 1),
color: const Color.fromRGBO(100, 116, 139, 1),
),
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16.h),
......@@ -266,19 +257,19 @@ class _RoomManagementBlockState extends State<RoomManagementBlock> {
Widget _buildLine() {
return Container(
height: 1.h,
color: Color.fromRGBO(100, 116, 139, 1),
color: const Color.fromRGBO(100, 116, 139, 1),
);
}
String _buildFilterLabel() {
final floorPart = _selectedFloor?.replaceAll('楼', '') ?? '';
return floorPart.isNotEmpty ? '客房${floorPart}楼·左右滑动选择' :"左右滑动选择";
return floorPart.isNotEmpty ? '客房${floorPart}楼·左右滑动选择' : '左右滑动选择';
}
Widget _buildRoomGrid() {
final filteredRooms = _getFilteredRooms();
final crossAxisCount = 12;
Widget _buildRoomGrid(ServiceIndexCubit cubit) {
final filteredRooms = _getFilteredRooms(cubit);
const crossAxisCount = 12;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
......@@ -289,14 +280,14 @@ class _RoomManagementBlockState extends State<RoomManagementBlock> {
_buildFilterLabel(),
style: TextStyle(
fontSize: 24.sp,
color: Color.fromRGBO(100, 116, 139, 1),
color: const Color.fromRGBO(100, 116, 139, 1),
),
),
Text(
'共${filteredRooms.length}间',
style: TextStyle(
fontSize: 24.sp,
color: Color.fromRGBO(73, 149, 234, 1),
color: const Color.fromRGBO(73, 149, 234, 1),
),
),
],
......@@ -308,14 +299,14 @@ class _RoomManagementBlockState extends State<RoomManagementBlock> {
padding: EdgeInsets.symmetric(vertical: 40.h),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15.r),
color: Color.fromRGBO(245, 245, 245, 1),
color: const Color.fromRGBO(245, 245, 245, 1),
),
child: Center(
child: Text(
'未找到匹配的房间',
style: TextStyle(
fontSize: 15.sp,
color: Color.fromRGBO(153, 153, 153, 1),
color: const Color.fromRGBO(153, 153, 153, 1),
),
),
),
......@@ -328,12 +319,13 @@ class _RoomManagementBlockState extends State<RoomManagementBlock> {
width: (120.w + 12.w) * crossAxisCount,
child: GridView.builder(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
physics: const NeverScrollableScrollPhysics(),
padding: EdgeInsets.zero,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
gridDelegate:
const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: crossAxisCount,
crossAxisSpacing: 12.w,
mainAxisSpacing: 12.h,
crossAxisSpacing: 12,
mainAxisSpacing: 12,
childAspectRatio: 1,
),
itemCount: filteredRooms.length,
......@@ -355,28 +347,31 @@ class _RoomManagementBlockState extends State<RoomManagementBlock> {
Color borderColor;
Color textColor;
Color statusTextColor;
IconData? statusIcon;
switch (status) {
case '入住':
backgroundColor = Color.fromRGBO(220, 236, 255, 1);
borderColor = isSelected ? Color.fromRGBO(64, 158, 255, 1) : Colors.transparent;
textColor = Color.fromRGBO(100, 116, 139, 1);
statusTextColor = Color.fromRGBO(73, 149, 234, 1);
backgroundColor = const Color.fromRGBO(220, 236, 255, 1);
borderColor = isSelected
? const Color.fromRGBO(64, 158, 255, 1)
: Colors.transparent;
textColor = const Color.fromRGBO(100, 116, 139, 1);
statusTextColor = const Color.fromRGBO(73, 149, 234, 1);
break;
case '待清洁':
backgroundColor = Color.fromRGBO(238, 238, 238, 1);
borderColor = isSelected ? Color.fromRGBO(64, 158, 255, 1) : Colors.transparent;
textColor = Color.fromRGBO(100, 116, 139, 1);
statusTextColor = Color.fromRGBO(73, 149, 234, 1);
statusIcon = Icons.warning_amber_rounded;
backgroundColor = const Color.fromRGBO(238, 238, 238, 1);
borderColor = isSelected
? const Color.fromRGBO(64, 158, 255, 1)
: Colors.transparent;
textColor = const Color.fromRGBO(100, 116, 139, 1);
statusTextColor = const Color.fromRGBO(73, 149, 234, 1);
break;
case '空闲':
default:
backgroundColor = Color.fromRGBO(238, 238, 238, 1);
borderColor = isSelected ? Color.fromRGBO(64, 158, 255, 1) : Colors.transparent;
textColor = Color.fromRGBO(100, 116, 139, 1);
statusTextColor = Color.fromRGBO(73, 149, 234, 1);
backgroundColor = const Color.fromRGBO(238, 238, 238, 1);
borderColor = isSelected
? const Color.fromRGBO(64, 158, 255, 1)
: Colors.transparent;
textColor = const Color.fromRGBO(100, 116, 139, 1);
statusTextColor = const Color.fromRGBO(73, 149, 234, 1);
}
return GestureDetector(
......@@ -413,17 +408,21 @@ class _RoomManagementBlockState extends State<RoomManagementBlock> {
'入住',
style: TextStyle(
fontSize: 24.sp,
color: statusTextColor,
color: statusTextColor,
),
);
} else if (status == '待清洁') {
return Image.asset("lib/assets/icon/jth.png", width: 24.sp, height: 30.sp,);
return Image.asset(
'lib/assets/icon/jth.png',
width: 24.sp,
height: 30.sp,
);
} else {
return Text(
' ',
style: TextStyle(
fontSize: 24.sp,
color: statusTextColor,
color: statusTextColor,
),
);
}
......@@ -435,15 +434,15 @@ class _RoomManagementBlockState extends State<RoomManagementBlock> {
);
}
Widget _buildBottomButtons() {
Widget _buildBottomButtons(ServiceIndexCubit cubit) {
final isRoomSelected = _selectedRoom != null;
return Row(
children: [
Expanded(
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Color.fromRGBO(73, 149, 234, 1),
disabledBackgroundColor: Color.fromRGBO(204, 204, 204, 1),
backgroundColor: const Color.fromRGBO(73, 149, 234, 1),
disabledBackgroundColor: const Color.fromRGBO(204, 204, 204, 1),
minimumSize: Size(double.infinity, 88.h),
padding: EdgeInsets.zero,
shape: RoundedRectangleBorder(
......@@ -451,12 +450,17 @@ class _RoomManagementBlockState extends State<RoomManagementBlock> {
),
elevation: 0,
),
onPressed: isRoomSelected ? () {
_controller.powerOnRoom(_selectedRoom!);
} : null,
onPressed: isRoomSelected
? () {
cubit.powerOnRoom(_selectedRoom!);
}
: null,
child: Text(
'送电',
style: TextStyle(fontSize: 28.sp, color: Colors.white, fontWeight: FontWeight.w500),
style: TextStyle(
fontSize: 28.sp,
color: Colors.white,
fontWeight: FontWeight.w500),
),
),
),
......@@ -464,25 +468,30 @@ class _RoomManagementBlockState extends State<RoomManagementBlock> {
Expanded(
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Color.fromRGBO(229, 241, 255, 1),
disabledBackgroundColor: Color.fromRGBO(204, 204, 204, 1),
backgroundColor: const Color.fromRGBO(229, 241, 255, 1),
disabledBackgroundColor: const Color.fromRGBO(204, 204, 204, 1),
minimumSize: Size(double.infinity, 88.h),
padding: EdgeInsets.zero,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16.r),
side: BorderSide(
side: const BorderSide(
color: Color.fromRGBO(73, 149, 234, 1),
width: 1.w,
width: 1,
),
),
elevation: 0,
),
onPressed: isRoomSelected ? () {
_controller.powerOffRoom(_selectedRoom!);
} : null,
onPressed: isRoomSelected
? () {
cubit.powerOffRoom(_selectedRoom!);
}
: null,
child: Text(
'断电',
style: TextStyle(fontSize: 28.sp, color: Color.fromRGBO(100, 116, 139, 1), fontWeight: FontWeight.w500),
style: TextStyle(
fontSize: 28.sp,
color: const Color.fromRGBO(100, 116, 139, 1),
fontWeight: FontWeight.w500),
),
),
),
......@@ -490,8 +499,8 @@ class _RoomManagementBlockState extends State<RoomManagementBlock> {
Expanded(
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Color.fromRGBO(100, 116, 139, 0.2),
disabledBackgroundColor: Color.fromRGBO(204, 204, 204, 1),
backgroundColor: const Color.fromRGBO(100, 116, 139, 0.2),
disabledBackgroundColor: const Color.fromRGBO(204, 204, 204, 1),
minimumSize: Size(double.infinity, 88.h),
padding: EdgeInsets.zero,
shape: RoundedRectangleBorder(
......@@ -499,16 +508,21 @@ class _RoomManagementBlockState extends State<RoomManagementBlock> {
),
elevation: 0,
),
onPressed: isRoomSelected ? () {
_controller.viewRoom(_selectedRoom!);
} : null,
onPressed: isRoomSelected
? () {
context.pushRoute(ServiceRoomDetailRoute(roomNumber: _selectedRoom));
}
: null,
child: Text(
'查看',
style: TextStyle(fontSize: 28.sp, color: Color.fromRGBO(100, 116, 139, 1), fontWeight: FontWeight.w500),
style: TextStyle(
fontSize: 28.sp,
color: const Color.fromRGBO(100, 116, 139, 1),
fontWeight: FontWeight.w500),
),
),
),
],
);
}
}
}
\ No newline at end of file
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
class ServiceHeadBlock extends StatelessWidget {
final int pendingCount;
const ServiceHeadBlock({super.key, required this.pendingCount});
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
padding: EdgeInsets.fromLTRB(28.w, 20.h, 28.w, 20.h),
color: Color.fromRGBO(73, 149, 234, 1),
child: Stack(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(Icons.emoji_emotions_outlined,
color: Color.fromRGBO(255, 255, 255, 1), size: 40.sp),
SizedBox(width: 10.w),
Text(
'早安,张先生',
style: TextStyle(
fontSize: 40.sp,
fontWeight: FontWeight.bold,
color: Colors.white,
fontFamily: 'FZLTXH',
),
),
],
),
SizedBox(height: 15.h),
Text(
'今日天气:晴 25°C ~ 35°C',
style: TextStyle(
fontSize: 24.sp,
fontWeight: FontWeight.bold,
color: Color.fromRGBO(255, 255, 255, 1),
fontFamily: 'FZLTXH',
),
),
],
),
Positioned(
right: 0,
bottom: 0,
child: GestureDetector(
onTap: () {},
child: Container(
padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 3.h),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20.r),
color: const Color.fromRGBO(1, 1, 1, 0.1),
),
child: Row(
children: [
Icon(Icons.notifications_outlined,
color: Colors.white, size: 38.sp),
SizedBox(width: 5.w),
Text(
'待响应需求 $pendingCount',
style: TextStyle(
fontSize: 24.sp,
fontWeight: FontWeight.bold,
color: Colors.white,
fontFamily: 'FZLTXH',
),
),
],
),
),
),
),
],
),
);
}
}
\ No newline at end of file
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
class ServiceStatsBlock extends StatelessWidget {
final int pendingCount;
final int occupiedCount;
final int vacantCount;
const ServiceStatsBlock({
super.key,
required this.pendingCount,
required this.occupiedCount,
required this.vacantCount,
});
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
padding: EdgeInsets.all(20.w),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(24.r),
color: Colors.white,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'服务概览',
style: TextStyle(
fontSize: 28.sp,
fontWeight: FontWeight.bold,
color: const Color.fromRGBO(31, 41, 55, 1),
),
),
SizedBox(height: 20.h),
Row(
children: [
_buildStatCard(
'待响应需求',
'$pendingCount',
const Color.fromRGBO(239, 68, 68, 1),
Icons.warning_amber_rounded,
),
SizedBox(width: 12.w),
_buildStatCard(
'入住中',
'$occupiedCount',
const Color.fromRGBO(34, 197, 94, 1),
Icons.meeting_room_outlined,
),
SizedBox(width: 12.w),
_buildStatCard(
'空闲可清洁',
'$vacantCount',
const Color.fromRGBO(59, 130, 246, 1),
Icons.cleaning_services_outlined,
),
],
),
],
),
);
}
Widget _buildStatCard(
String label, String value, Color color, IconData icon) {
return Expanded(
child: Container(
padding: EdgeInsets.all(16.w),
decoration: BoxDecoration(
color: color.withOpacity(0.08),
borderRadius: BorderRadius.circular(16.r),
),
child: Column(
children: [
Icon(icon, color: color, size: 28.sp),
SizedBox(height: 8.h),
Text(
value,
style: TextStyle(
fontSize: 32.sp,
fontWeight: FontWeight.bold,
color: color,
),
),
SizedBox(height: 4.h),
Text(
label,
style: TextStyle(
fontSize: 20.sp,
color: const Color.fromRGBO(107, 114, 128, 1),
),
),
],
),
),
);
}
}
\ No newline at end of file
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:flutter_switch/flutter_switch.dart';
import 'package:smart_hotel_app/views/service/room/controller.dart';
class DeviceControlBlock extends StatefulWidget {
const DeviceControlBlock({super.key});
@override
State<DeviceControlBlock> createState() => _DeviceControlBlockState();
}
class _DeviceControlBlockState extends State<DeviceControlBlock> {
late final ServiceRoomController _controller;
late bool _mainPowerOn;
late List<bool> _devicePowerStates;
@override
void initState() {
super.initState();
_controller = ServiceRoomController(context);
_mainPowerOn = _controller.getMainPowerOn();
final deviceControls = _controller.getDeviceControls();
_devicePowerStates = deviceControls.map((d) => d['isOn'] as bool).toList();
}
@override
Widget build(BuildContext context) {
final deviceControls = _controller.getDeviceControls();
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'设备控制',
style: TextStyle(
fontSize: 28.sp,
color: Color.fromRGBO(0, 0, 0, 1),
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 20.h),
Container(
width: double.infinity,
padding: EdgeInsets.all(20.w),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(24.r),
color: Colors.white,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildMainPowerControl(),
SizedBox(height: 20.h),
_buildDivider(),
SizedBox(height: 20.h),
_buildSubDeviceTitle(),
SizedBox(height: 20.h),
...deviceControls.asMap().entries.map((entry) {
final index = entry.key;
final device = entry.value;
return Column(
children: [
_buildDeviceControlItem(device, index),
if (index < deviceControls.length - 1) SizedBox(height: 20.h),
if (index < deviceControls.length - 1) _buildDivider(),
if (index < deviceControls.length - 1) SizedBox(height: 20.h),
],
);
}).toList(),
],
),
),
],
);
}
Widget _buildMainPowerControl() {
return Row(
children: [
Container(
width: 60.w,
height: 60.h,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12.r),
color: Color.fromRGBO(229, 241, 255, 1),
),
child: Icon(
Icons.power_settings_new_outlined,
size: 36.sp,
color: Color.fromRGBO(73, 149, 234, 1),
),
),
SizedBox(width: 16.w),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'房间总电源',
style: TextStyle(
fontSize: 28.sp,
color: Color.fromRGBO(0, 0, 0, 1),
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 4.h),
Text(
'控制整个房间供电',
style: TextStyle(
fontSize: 24.sp,
color: Color.fromRGBO(100, 116, 139, 1),
),
),
],
),
),
FlutterSwitch(
value: _mainPowerOn,
onToggle: (value) {
setState(() {
_mainPowerOn = value;
});
},
width: 60.w,
height: 30.h,
toggleSize: 26.sp,
borderRadius: 16.r,
padding: 3.r,
activeColor: Color.fromRGBO(80, 162, 255, 1),
inactiveColor: Color.fromRGBO(200, 200, 200, 1),
toggleColor: Colors.white,
),
],
);
}
Widget _buildDivider() {
return Container(
height: 1.h,
color: Color.fromRGBO(229, 231, 235, 1),
);
}
Widget _buildSubDeviceTitle() {
return Text(
'分设备控制',
style: TextStyle(
fontSize: 28.sp,
color: Color.fromRGBO(0, 0, 0, 1),
fontWeight: FontWeight.bold,
),
);
}
Widget _buildDeviceControlItem(Map<String, dynamic> device, int index) {
return Row(
children: [
Icon(
device['icon'] as IconData,
size: 40.sp,
color: Color.fromRGBO(73, 149, 234, 1),
),
SizedBox(width: 16.w),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
device['name'] as String,
style: TextStyle(
fontSize: 24.sp,
color: Color.fromRGBO(0, 0, 0, 1),
),
),
if (device['status'] != null) ...[
SizedBox(height: 4.h),
Text(
device['status'] as String,
style: TextStyle(
fontSize: 24.sp,
color: Color.fromRGBO(100, 116, 139, 1),
),
),
],
],
),
),
FlutterSwitch(
value: _devicePowerStates[index],
onToggle: (value) {
setState(() {
_devicePowerStates[index] = value;
});
},
width: 60.w,
height: 30.h,
toggleSize: 26.sp,
borderRadius: 16.r,
padding: 3.r,
activeColor: Color.fromRGBO(80, 162, 255, 1),
inactiveColor: Color.fromRGBO(200, 200, 200, 1),
toggleColor: Colors.white,
),
],
);
}
}
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:smart_hotel_app/views/service/room/controller.dart';
class RoomInfoBlock extends StatefulWidget {
const RoomInfoBlock({super.key});
@override
State<RoomInfoBlock> createState() => _RoomInfoBlockState();
}
class _RoomInfoBlockState extends State<RoomInfoBlock> {
late final ServiceRoomController _controller;
@override
void initState() {
super.initState();
_controller = ServiceRoomController(context);
}
@override
Widget build(BuildContext context) {
final roomDetail = _controller.getRoomDetail();
final roomStatus = _controller.getRoomStatus();
return Container(
width: double.infinity,
padding: EdgeInsets.all(20.w),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(24.r),
color: Colors.white,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildRoomHeader(roomDetail),
SizedBox(height: 20.h),
_buildStatusTags(roomStatus),
SizedBox(height: 20.h),
_buildDivider(),
SizedBox(height: 20.h),
_buildDeviceStatusTitle(),
SizedBox(height: 20.h),
_buildDeviceGrid(),
],
),
);
}
Widget _buildRoomHeader(Map<String, dynamic> roomDetail) {
return Row(
children: [
Container(
width: 64.w,
height: 66.h,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12.r),
color: Color.fromRGBO(229, 241, 255, 1),
),
child: Icon(
Icons.bed_outlined,
size: 36.sp,
color: Color.fromRGBO(73, 149, 234, 1),
),
),
SizedBox(width: 16.w),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(
roomDetail['roomNumber'] as String,
style: TextStyle(
fontSize: 32.sp,
color: Colors.black,
fontWeight: FontWeight.bold,
),
),
SizedBox(width: 12.w),
Text(
roomDetail['status'] as String,
style: TextStyle(
fontSize: 24.sp,
color: Color.fromRGBO(73, 149, 234, 1),
),
),
],
),
SizedBox(height: 6.h),
Text(
_buildGuestInfo(roomDetail),
style: TextStyle(
fontSize: 24.sp,
color: Color.fromRGBO(100, 116, 139, 1),
),
),
],
),
),
],
);
}
String _buildGuestInfo(Map<String, dynamic> roomDetail) {
final guestName = roomDetail['guestName'] as String?;
final checkInDate = roomDetail['checkInDate'] as String?;
final checkOutDate = roomDetail['checkOutDate'] as String?;
final roomType = roomDetail['roomType'] as String;
if (guestName != null && checkInDate != null && checkOutDate != null) {
return '$guestName·$checkInDate-$checkOutDate·$roomType';
}
return roomType;
}
Widget _buildStatusTags(Map<String, dynamic> roomStatus) {
return Wrap(
spacing: 12.w,
children: [
_buildStatusTag(
'有人',
roomStatus['hasGuest'] as bool,
),
_buildStatusTag(
'通电中',
roomStatus['powerOn'] as bool,
),
_buildStatusTag(
'wifi正常',
roomStatus['wifiNormal'] as bool,
),
],
);
}
Widget _buildStatusTag(String text, bool isActive) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 8.h),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8.r),
color: Color.fromRGBO(20, 184, 166, 0.2),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.check,
size: 20.sp,
color: Color.fromRGBO(16, 185, 129, 1),
),
SizedBox(width: 6.w),
Text(
text,
style: TextStyle(
fontSize: 24.sp,
color: Color.fromRGBO(20, 184, 166, 1),
),
),
],
),
);
}
Widget _buildDivider() {
return Container(
height: 1.h,
color: Color.fromRGBO(229, 231, 235, 1),
);
}
Widget _buildDeviceStatusTitle() {
return Text(
'房间设备状态',
style: TextStyle(
fontSize: 28.sp,
color: Color.fromRGBO(0, 0, 0, 1),
fontWeight: FontWeight.bold,
),
);
}
Widget _buildDeviceGrid() {
final devices = _controller.getDevices();
return GridView.builder(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
padding: EdgeInsets.zero,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: 16.w,
mainAxisSpacing: 16.h,
childAspectRatio: 2.2,
),
itemCount: devices.length,
itemBuilder: (context, index) {
return _buildDeviceItem(devices[index]);
},
);
}
Widget _buildDeviceItem(Map<String, dynamic> device) {
return Container(
padding: EdgeInsets.all(20.w),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(24.r),
color: Color.fromRGBO(229, 241, 255, 1),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(
children: [
Icon(
device['icon'] as IconData,
size: 32.sp,
color: Color.fromRGBO(73, 149, 234, 1),
),
SizedBox(width: 10.w),
Text(
device['name'] as String,
style: TextStyle(
fontSize: 28.sp,
color: Color.fromRGBO(73, 149, 234, 1),
fontWeight: FontWeight.bold,
),
),
],
),
SizedBox(height: 10.h),
Text(
device['status'] as String,
style: TextStyle(
fontSize: 24.sp,
color: (device['isOn'] as bool)
? Color.fromRGBO(20, 184, 166, 1)
: Color.fromRGBO(100, 116, 139, 1),
),
),
],
),
);
}
}
import 'package:flutter/material.dart';
class ServiceRoomController {
final BuildContext context;
final String? roomNumber;
ServiceRoomController(this.context, {this.roomNumber});
Map<String, dynamic> getRoomDetail() {
final statuses = ['空闲', '入住中', '待清洁'];
final roomTypes = ['标准大床房', '豪华双床房', '商务套房', '总统套房'];
final guestNames = ['张先生', '李女士', '王先生', '赵女士', '刘先生'];
final months = ['5月', '6月', '7月', '8月'];
final num = roomNumber ?? '302';
final numInt = int.tryParse(num) ?? 302;
final statusIndex = numInt % 3;
final typeIndex = numInt % 4;
final guestIndex = numInt % 5;
final monthIndex = (numInt ~/ 100) % 4;
return {
'roomNumber': num,
'status': statuses[statusIndex],
'guestName': statusIndex == 0 ? null : guestNames[guestIndex],
'checkInDate': statusIndex == 0 ? null : '${months[monthIndex]}${(numInt % 20) + 1}日',
'checkOutDate': statusIndex == 0 ? null : '${months[monthIndex]}${(numInt % 20) + 3}日',
'roomType': roomTypes[typeIndex],
};
}
Map<String, dynamic> getRoomStatus() {
final num = roomNumber ?? '302';
final numInt = int.tryParse(num) ?? 302;
final hasGuest = numInt % 2 == 0;
final powerOn = true;
final wifiNormal = true;
final now = DateTime.now();
final refreshTime = '${now.hour.toString().padLeft(2, '0')}:${now.minute.toString().padLeft(2, '0')}';
return {
'hasGuest': hasGuest,
'guestRefreshTime': refreshTime,
'powerOn': powerOn,
'currentPower': '1.24kW',
'wifiNormal': wifiNormal,
'wifiSignal': '-45dBm',
};
}
bool getMainPowerOn() {
final num = roomNumber ?? '302';
final numInt = int.tryParse(num) ?? 302;
return numInt % 2 == 0;
}
List<Map<String, dynamic>> getDevices() {
final num = roomNumber ?? '302';
final numInt = int.tryParse(num) ?? 302;
return [
{
'name': '空调',
'icon': Icons.air_outlined,
'status': '24°C·开',
'isOn': true,
},
{
'name': '照明',
'icon': Icons.light_outlined,
'status': '关',
'isOn': false,
},
{
'name': '电视',
'icon': Icons.tv_outlined,
'status': '开',
'isOn': true,
},
{
'name': '窗帘',
'icon': Icons.curtains_closed_outlined,
'status': '关',
'isOn': false,
},
];
}
List<Map<String, dynamic>> getDeviceControls() {
final num = roomNumber ?? '302';
final numInt = int.tryParse(num) ?? 302;
return [
{
'name': '空调',
'icon': Icons.air_outlined,
'status': '当前:制冷24°C·风速:自动',
'isOn': true,
},
{
'name': '主照明',
'icon': Icons.light_outlined,
'status': null,
'isOn': false,
},
{
'name': '电视',
'icon': Icons.tv_outlined,
'status': null,
'isOn': true,
},
];
}
}
\ No newline at end of file
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter/material.dart';
import 'package:smart_hotel_app/views/service/room/cubit/service_room_state.dart';
class ServiceRoomCubit extends Cubit<ServiceRoomState> {
final String? roomNumber;
ServiceRoomCubit({this.roomNumber}) : super(const ServiceRoomState()) {
initData();
}
void initData() {
final num = roomNumber ?? '302';
final numInt = int.tryParse(num) ?? 302;
final statuses = ['空闲', '入住中', '待清洁'];
final roomTypes = ['标准大床房', '豪华双床房', '商务套房', '总统套房'];
final guestNames = ['张先生', '李女士', '王先生', '赵女士', '刘先生'];
final months = ['5月', '6月', '7月', '8月'];
final statusIndex = numInt % 3;
final typeIndex = numInt % 4;
final guestIndex = numInt % 5;
final monthIndex = (numInt ~/ 100) % 4;
final hasGuest = numInt % 2 == 0;
final now = DateTime.now();
final refreshTime = '${now.hour.toString().padLeft(2, '0')}:${now.minute.toString().padLeft(2, '0')}';
emit(state.copyWith(
roomDetail: {
'roomNumber': num,
'status': statuses[statusIndex],
'guestName': statusIndex == 0 ? null : guestNames[guestIndex],
'checkInDate': statusIndex == 0 ? null : '${months[monthIndex]}${(numInt % 20) + 1}日',
'checkOutDate': statusIndex == 0 ? null : '${months[monthIndex]}${(numInt % 20) + 3}日',
'roomType': roomTypes[typeIndex],
},
roomStatus: {
'hasGuest': hasGuest,
'guestRefreshTime': refreshTime,
'powerOn': true,
'currentPower': '1.24kW',
'wifiNormal': true,
'wifiSignal': '-45dBm',
},
mainPowerOn: numInt % 2 == 0,
devices: [
{'name': '空调', 'icon': Icons.air_outlined, 'status': '24°C·开', 'isOn': true},
{'name': '照明', 'icon': Icons.light_outlined, 'status': '关', 'isOn': false},
{'name': '电视', 'icon': Icons.tv_outlined, 'status': '开', 'isOn': true},
{'name': '窗帘', 'icon': Icons.curtains_closed_outlined, 'status': '关', 'isOn': false},
],
deviceControls: [
{'name': '空调', 'icon': Icons.air_outlined, 'status': '当前:制冷24°C·风速:自动', 'isOn': true},
{'name': '主照明', 'icon': Icons.light_outlined, 'status': null, 'isOn': false},
{'name': '电视', 'icon': Icons.tv_outlined, 'status': null, 'isOn': true},
],
));
}
void toggleMainPower(bool value) {
emit(state.copyWith(mainPowerOn: value));
}
}
\ No newline at end of file
import 'package:equatable/equatable.dart';
class ServiceRoomState extends Equatable {
final Map<String, dynamic> roomDetail;
final Map<String, dynamic> roomStatus;
final bool mainPowerOn;
final List<Map<String, dynamic>> devices;
final List<Map<String, dynamic>> deviceControls;
const ServiceRoomState({
this.roomDetail = const {},
this.roomStatus = const {},
this.mainPowerOn = false,
this.devices = const [],
this.deviceControls = const [],
});
ServiceRoomState copyWith({
Map<String, dynamic>? roomDetail,
Map<String, dynamic>? roomStatus,
bool? mainPowerOn,
List<Map<String, dynamic>>? devices,
List<Map<String, dynamic>>? deviceControls,
}) {
return ServiceRoomState(
roomDetail: roomDetail ?? this.roomDetail,
roomStatus: roomStatus ?? this.roomStatus,
mainPowerOn: mainPowerOn ?? this.mainPowerOn,
devices: devices ?? this.devices,
deviceControls: deviceControls ?? this.deviceControls,
);
}
@override
List<Object?> get props => [
roomDetail,
roomStatus,
mainPowerOn,
devices,
deviceControls,
];
}
\ No newline at end of file
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:smart_hotel_app/views/service/room/controller.dart';
import 'package:smart_hotel_app/views/service/room/child/room_info_block.dart';
import 'package:smart_hotel_app/views/service/room/child/device_control_block.dart';
import 'package:smart_hotel_app/views/service/room/child/bottom_operation_block.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:smart_hotel_app/views/service/room/cubit/service_room_cubit.dart';
import 'package:smart_hotel_app/views/service/room/cubit/service_room_state.dart';
import 'package:smart_hotel_app/views/service/room/widget/bottom_operation_block.dart';
import 'package:smart_hotel_app/views/service/room/widget/device_control_block.dart';
import 'package:smart_hotel_app/views/service/room/widget/room_info_block.dart';
@RoutePage()
class ServiceRoomDetailView extends StatefulWidget {
class ServiceRoomDetailView extends StatelessWidget {
final String? roomNumber;
const ServiceRoomDetailView({super.key, this.roomNumber});
@override
State<ServiceRoomDetailView> createState() => _ServiceRoomDetailViewState();
}
class _ServiceRoomDetailViewState extends State<ServiceRoomDetailView> {
late final ServiceRoomController _controller;
@override
void initState() {
super.initState();
_controller = ServiceRoomController(context, roomNumber: widget.roomNumber);
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color.fromRGBO(242, 243, 245, 1),
appBar: AppBar(
backgroundColor: Colors.white,
elevation: 0,
leading: IconButton(
icon: Icon(Icons.arrow_back_ios, color: Color.fromRGBO(100, 116, 139, 1),),
onPressed: () {
context.popRoute();
},
return BlocProvider(
create: (_) => ServiceRoomCubit(roomNumber: roomNumber),
child: Scaffold(
backgroundColor: const Color.fromRGBO(242, 243, 245, 1),
appBar: AppBar(
backgroundColor: Colors.white,
elevation: 0,
leading: IconButton(
icon: const Icon(Icons.arrow_back_ios,
color: Color.fromRGBO(100, 116, 139, 1)),
onPressed: () {
context.popRoute();
},
),
title: Text(
'客房详情',
style: TextStyle(
color: const Color.fromRGBO(10, 13, 20, 1),
fontSize: 32.sp,
fontWeight: FontWeight.w200,
),
),
centerTitle: true,
),
title: Text(
// '${widget.roomNumber ?? '客房'}详情',
'客房详情',
style: TextStyle(
color: Color.fromRGBO(10, 13, 20, 1),
fontSize: 32.sp,
fontWeight: FontWeight.w200,
body: SingleChildScrollView(
padding: EdgeInsets.symmetric(horizontal: 28.w),
child: BlocBuilder<ServiceRoomCubit, ServiceRoomState>(
builder: (context, state) {
return Column(
children: [
SizedBox(height: 20.h),
RoomInfoBlock(
roomDetail: state.roomDetail,
roomStatus: state.roomStatus,
),
SizedBox(height: 20.h),
DeviceControlBlock(
mainPowerOn: state.mainPowerOn,
deviceControls: state.deviceControls,
devices: state.devices,
),
SizedBox(height: 20.h),
const BottomOperationBlock(),
SizedBox(height: 20.h),
],
);
},
),
),
centerTitle: true,
),
body: SingleChildScrollView(
padding: EdgeInsets.symmetric(horizontal: 28.w),
child: Column(
children: [
SizedBox(height: 20.h),
RoomInfoBlock(),
SizedBox(height: 20.h),
DeviceControlBlock(),
SizedBox(height: 20.h),
BottomOperationBlock(),
SizedBox(height: 20.h),
],
),
),
);
}
}
\ No newline at end of file
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:flutter_switch/flutter_switch.dart';
class DeviceControlBlock extends StatefulWidget {
final bool mainPowerOn;
final List<Map<String, dynamic>> deviceControls;
final List<Map<String, dynamic>> devices;
const DeviceControlBlock({
super.key,
required this.mainPowerOn,
required this.deviceControls,
required this.devices,
});
@override
State<DeviceControlBlock> createState() => _DeviceControlBlockState();
}
class _DeviceControlBlockState extends State<DeviceControlBlock> {
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
padding: EdgeInsets.all(20.w),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(24.r),
color: Colors.white,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'设备控制',
style: TextStyle(
fontSize: 28.sp,
fontWeight: FontWeight.bold,
color: const Color.fromRGBO(31, 41, 55, 1),
),
),
FlutterSwitch(
value: widget.mainPowerOn,
onToggle: (value) {},
activeColor: const Color.fromRGBO(59, 130, 246, 1),
inactiveColor: const Color.fromRGBO(209, 213, 219, 1),
width: 60.w,
height: 30.h,
toggleSize: 26,
),
],
),
SizedBox(height: 20.h),
...widget.devices.map((device) {
return Container(
margin: EdgeInsets.only(bottom: 12.h),
padding: EdgeInsets.all(16.w),
decoration: BoxDecoration(
color: const Color.fromRGBO(249, 250, 251, 1),
borderRadius: BorderRadius.circular(16.r),
),
child: Row(
children: [
Icon(
device['icon'] as IconData,
color: const Color.fromRGBO(107, 114, 128, 1),
size: 28.sp,
),
SizedBox(width: 12.w),
Expanded(
child: Text(
device['name'] as String,
style: TextStyle(
fontSize: 24.sp,
color: const Color.fromRGBO(31, 41, 55, 1),
),
),
),
Text(
device['status'] as String,
style: TextStyle(
fontSize: 22.sp,
color: const Color.fromRGBO(107, 114, 128, 1),
),
),
],
),
);
}).toList(),
],
),
);
}
}
\ No newline at end of file
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
class RoomInfoBlock extends StatelessWidget {
final Map<String, dynamic> roomDetail;
final Map<String, dynamic> roomStatus;
const RoomInfoBlock({
super.key,
required this.roomDetail,
required this.roomStatus,
});
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
padding: EdgeInsets.all(20.w),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(24.r),
color: Colors.white,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'${roomDetail['roomNumber']}号房',
style: TextStyle(
fontSize: 36.sp,
fontWeight: FontWeight.bold,
color: const Color.fromRGBO(31, 41, 55, 1),
),
),
Container(
padding:
EdgeInsets.symmetric(horizontal: 16.w, vertical: 6.h),
decoration: BoxDecoration(
color: _getStatusColor(roomDetail['status'] as String),
borderRadius: BorderRadius.circular(12.r),
),
child: Text(
roomDetail['status'] as String,
style: TextStyle(
fontSize: 22.sp,
color: Colors.white,
),
),
),
],
),
SizedBox(height: 16.h),
_buildInfoRow(
'房型', roomDetail['roomType'] as String),
_buildInfoRow(
'住客', roomDetail['guestName'] as String? ?? '无'),
if (roomDetail['checkInDate'] != null) ...[
_buildInfoRow(
'入住日期', roomDetail['checkInDate'] as String),
_buildInfoRow(
'预计退房', roomDetail['checkOutDate'] as String),
],
SizedBox(height: 16.h),
_buildInfoRow(
'当前功率', roomStatus['currentPower'] as String),
_buildInfoRow('WiFi信号',
'${roomStatus['wifiSignal'] as String} (${roomStatus['wifiNormal'] as bool ? '正常' : '异常'})'),
_buildInfoRow(
'刷新时间', roomStatus['guestRefreshTime'] as String),
],
),
);
}
Widget _buildInfoRow(String label, String value) {
return Padding(
padding: EdgeInsets.only(bottom: 8.h),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
label,
style: TextStyle(
fontSize: 24.sp,
color: const Color.fromRGBO(107, 114, 128, 1),
),
),
Text(
value,
style: TextStyle(
fontSize: 24.sp,
color: const Color.fromRGBO(31, 41, 55, 1),
),
),
],
),
);
}
Color _getStatusColor(String status) {
switch (status) {
case '入住中':
return const Color.fromRGBO(34, 197, 94, 1);
case '空闲':
return const Color.fromRGBO(59, 130, 246, 1);
case '待清洁':
return const Color.fromRGBO(251, 191, 36, 1);
default:
return const Color.fromRGBO(107, 114, 128, 1);
}
}
}
\ No newline at end of file
......@@ -7,7 +7,6 @@ list(APPEND FLUTTER_PLUGIN_LIST
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST
jni
)
set(PLUGIN_BUNDLED_LIBRARIES)
......
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_
......@@ -6,7 +6,9 @@ import FlutterMacOS
import Foundation
import flutter_secure_storage_macos
import path_provider_foundation
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
}
platform :osx, '10.14'
platform :osx, '10.15'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
......
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
......@@ -27,6 +27,8 @@
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 */
......@@ -60,11 +62,12 @@
/* 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; };
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>"; };
......@@ -76,8 +79,15 @@
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 */
......@@ -85,6 +95,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
AA2511AB6A727172664CBACE /* Pods_RunnerTests.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
......@@ -92,12 +103,27 @@
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 = (
......@@ -125,6 +151,7 @@
331C80D6294CF71000263BE5 /* RunnerTests */,
33CC10EE2044A3C60003C045 /* Products */,
D73912EC22F37F3D000D13A0 /* Frameworks */,
08484717D36EA345072FEF22 /* Pods */,
);
sourceTree = "<group>";
};
......@@ -175,6 +202,8 @@
D73912EC22F37F3D000D13A0 /* Frameworks */ = {
isa = PBXGroup;
children = (
6913C73ACBD9DBD0D8F3CF53 /* Pods_Runner.framework */,
A4CA6AB92F7BBA99F6D3F719 /* Pods_RunnerTests.framework */,
);
name = Frameworks;
sourceTree = "<group>";
......@@ -186,6 +215,7 @@
isa = PBXNativeTarget;
buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
buildPhases = (
46188DED442B9FE5DC38335B /* [CP] Check Pods Manifest.lock */,
331C80D1294CF70F00263BE5 /* Sources */,
331C80D2294CF70F00263BE5 /* Frameworks */,
331C80D3294CF70F00263BE5 /* Resources */,
......@@ -204,11 +234,13 @@
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 = (
);
......@@ -227,7 +259,7 @@
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 0920;
LastUpgradeCheck = 1430;
LastUpgradeCheck = 1510;
ORGANIZATIONNAME = "";
TargetAttributes = {
331C80D4294CF70F00263BE5 = {
......@@ -328,6 +360,67 @@
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 */
......@@ -379,6 +472,7 @@
/* Begin XCBuildConfiguration section */
331C80DB294CF71000263BE5 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = FB8AE0EF1C94CA8FF34D8251 /* Pods-RunnerTests.debug.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CURRENT_PROJECT_VERSION = 1;
......@@ -393,6 +487,7 @@
};
331C80DC294CF71000263BE5 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = C8F246C0131A2C65494C474E /* Pods-RunnerTests.release.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CURRENT_PROJECT_VERSION = 1;
......@@ -407,6 +502,7 @@
};
331C80DD294CF71000263BE5 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = BBB391427D55F9002009B5AF /* Pods-RunnerTests.profile.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CURRENT_PROJECT_VERSION = 1;
......@@ -457,7 +553,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.14;
MACOSX_DEPLOYMENT_TARGET = 10.15;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = macosx;
SWIFT_COMPILATION_MODE = wholemodule;
......@@ -536,7 +632,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.14;
MACOSX_DEPLOYMENT_TARGET = 10.15;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = macosx;
......@@ -583,7 +679,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.14;
MACOSX_DEPLOYMENT_TARGET = 10.15;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = macosx;
SWIFT_COMPILATION_MODE = wholemodule;
......
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1430"
LastUpgradeVersion = "1510"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
......@@ -59,6 +59,7 @@
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
enableGPUValidationMode = "1"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
......
......@@ -4,4 +4,7 @@
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>
import Cocoa
import FlutterMacOS
@NSApplicationMain
@main
class AppDelegate: FlutterAppDelegate {
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
return true
}
override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool {
return true
}
}
......@@ -5,34 +5,34 @@ packages:
dependency: transitive
description:
name: _fe_analyzer_shared
sha256: "0b2f2bd91ba804e53a61d757b986f89f1f9eaed5b11e4b2f5a2468d86d6c9fc7"
sha256: eb376e9acf6938204f90eb3b1f00b578640d3188b4c8a8ec054f9f479af8d051
url: "https://pub.dev"
source: hosted
version: "67.0.0"
version: "64.0.0"
analyzer:
dependency: transitive
description:
name: analyzer
sha256: "37577842a27e4338429a1cbc32679d508836510b056f1eedf0c8d20e39c1383d"
sha256: "69f54f967773f6c26c7dcb13e93d7ccee8b17a641689da39e878d5cf13b06893"
url: "https://pub.dev"
source: hosted
version: "6.4.1"
version: "6.2.0"
args:
dependency: transitive
description:
name: args
sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a"
url: "https://pub.dev"
source: hosted
version: "2.7.0"
version: "2.5.0"
async:
dependency: transitive
description:
name: async
sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37
sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
url: "https://pub.dev"
source: hosted
version: "2.13.1"
version: "2.11.0"
auto_route:
dependency: "direct main"
description:
......@@ -61,10 +61,10 @@ packages:
dependency: transitive
description:
name: boolean_selector
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
version: "2.1.1"
build:
dependency: transitive
description:
......@@ -77,18 +77,18 @@ packages:
dependency: transitive
description:
name: build_config
sha256: "4ae2de3e1e67ea270081eaee972e1bd8f027d459f249e0f1186730784c2e7e33"
sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1
url: "https://pub.dev"
source: hosted
version: "1.1.2"
version: "1.1.1"
build_daemon:
dependency: transitive
description:
name: build_daemon
sha256: bf05f6e12cfea92d3c09308d7bcdab1906cd8a179b023269eed00c071004b957
sha256: "0343061a33da9c5810b2d6cee51945127d8f4c060b7fbdd9d54917f0a3feaaa1"
url: "https://pub.dev"
source: hosted
version: "4.1.1"
version: "4.0.1"
build_resolvers:
dependency: transitive
description:
......@@ -101,18 +101,18 @@ packages:
dependency: "direct dev"
description:
name: build_runner
sha256: "028819cfb90051c6b5440c7e574d1896f8037e3c96cf17aaeb054c9311cfbf4d"
sha256: "3ac61a79bfb6f6cc11f693591063a7f19a7af628dc52f141743edac5c16e8c22"
url: "https://pub.dev"
source: hosted
version: "2.4.13"
version: "2.4.9"
build_runner_core:
dependency: transitive
description:
name: build_runner_core
sha256: f8126682b87a7282a339b871298cc12009cb67109cfa1614d6436fb0289193e0
sha256: "4ae8ffe5ac758da294ecf1802f2aff01558d8b1b00616aa7538ea9a8a5d50799"
url: "https://pub.dev"
source: hosted
version: "7.3.2"
version: "7.3.0"
built_collection:
dependency: transitive
description:
......@@ -133,18 +133,18 @@ packages:
dependency: transitive
description:
name: characters
sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
url: "https://pub.dev"
source: hosted
version: "1.4.1"
version: "1.4.0"
checked_yaml:
dependency: transitive
description:
name: checked_yaml
sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f"
sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff
url: "https://pub.dev"
source: hosted
version: "2.0.4"
version: "2.0.3"
clock:
dependency: transitive
description:
......@@ -153,22 +153,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.1.2"
code_assets:
dependency: transitive
description:
name: code_assets
sha256: "83ccdaa064c980b5596c35dd64a8d3ecc68620174ab9b90b6343b753aa721687"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
code_builder:
dependency: transitive
description:
name: code_builder
sha256: "6a6cab2ba4680d6423f34a9b972a4c9a94ebe1b62ecec4e1a1f2cba91fd1319d"
sha256: f692079e25e7869c14132d39f223f8eec9830eb76131925143b2129c4bb01b37
url: "https://pub.dev"
source: hosted
version: "4.11.1"
version: "4.10.0"
collection:
dependency: transitive
description:
......@@ -181,26 +173,26 @@ packages:
dependency: transitive
description:
name: convert
sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68
sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592"
url: "https://pub.dev"
source: hosted
version: "3.1.2"
version: "3.1.1"
crypto:
dependency: transitive
description:
name: crypto
sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf
sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
url: "https://pub.dev"
source: hosted
version: "3.0.7"
version: "3.0.3"
cupertino_icons:
dependency: "direct main"
description:
name: cupertino_icons
sha256: "41e005c33bd814be4d3096aff55b1908d419fde52ca656c8c47719ec745873cd"
sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6
url: "https://pub.dev"
source: hosted
version: "1.0.9"
version: "1.0.8"
dart_style:
dependency: transitive
description:
......@@ -221,10 +213,10 @@ packages:
dependency: transitive
description:
name: dio_web_adapter
sha256: "2f9e64323a7c3c7ef69567d5c800424a11f8337b8b228bad02524c9fb3c1f340"
sha256: "0a2e95fc6bdeb623bb623fc41e90e6924e9a3bbd65089f9221f83c185366b479"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
version: "1.1.1"
equatable:
dependency: "direct main"
description:
......@@ -269,10 +261,10 @@ packages:
dependency: "direct main"
description:
name: fl_chart
sha256: b938f77d042cbcd822936a7a359a7235bad8bd72070de1f827efc2cc297ac888
sha256: f2e9137f261d0f53a820f6b829c80ba570ac915284c8e32789d973834796eca0
url: "https://pub.dev"
source: hosted
version: "1.2.0"
version: "0.71.0"
flutter:
dependency: "direct main"
description: flutter
......@@ -372,10 +364,10 @@ packages:
dependency: "direct main"
description:
name: fluttertoast
sha256: "90778fe0497fe3a09166e8cf2e0867310ff434b794526589e77ec03cf08ba8e8"
sha256: "81b68579e23fcbcada2db3d50302813d2371664afe6165bc78148050ab94bf66"
url: "https://pub.dev"
source: hosted
version: "8.2.14"
version: "8.2.5"
frontend_server_client:
dependency: transitive
description:
......@@ -388,26 +380,18 @@ packages:
dependency: transitive
description:
name: glob
sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de
sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63"
url: "https://pub.dev"
source: hosted
version: "2.1.3"
version: "2.1.2"
graphs:
dependency: transitive
description:
name: graphs
sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0"
sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19
url: "https://pub.dev"
source: hosted
version: "2.3.2"
hooks:
dependency: transitive
description:
name: hooks
sha256: "025f060e86d2d4c3c47b56e33caf7f93bf9283340f26d23424ebcfccf34f621e"
url: "https://pub.dev"
source: hosted
version: "1.0.3"
version: "2.3.1"
http_multi_server:
dependency: transitive
description:
......@@ -420,34 +404,18 @@ packages:
dependency: transitive
description:
name: http_parser
sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b"
url: "https://pub.dev"
source: hosted
version: "4.1.2"
version: "4.0.2"
io:
dependency: transitive
description:
name: io
sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b
sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e"
url: "https://pub.dev"
source: hosted
version: "1.0.5"
jni:
dependency: transitive
description:
name: jni
sha256: c2230682d5bc2362c1c9e8d3c7f406d9cbba23ab3f2e203a025dd47e0fb2e68f
url: "https://pub.dev"
source: hosted
version: "1.0.0"
jni_flutter:
dependency: transitive
description:
name: jni_flutter
sha256: "8b59e590786050b1cd866677dddaf76b1ade5e7bc751abe04b86e84d379d3ba6"
url: "https://pub.dev"
source: hosted
version: "1.0.1"
version: "1.0.4"
js:
dependency: transitive
description:
......@@ -460,10 +428,10 @@ packages:
dependency: transitive
description:
name: json_annotation
sha256: "2a743920d81b7910627f68ee2c9ac1fc0bfee32b9fc3403587d7c6791ca12f80"
sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1"
url: "https://pub.dev"
source: hosted
version: "4.12.0"
version: "4.9.0"
leak_tracker:
dependency: transitive
description:
......@@ -508,26 +476,26 @@ packages:
dependency: transitive
description:
name: logging
sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340"
url: "https://pub.dev"
source: hosted
version: "1.3.0"
version: "1.2.0"
matcher:
dependency: transitive
description:
name: matcher
sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
url: "https://pub.dev"
source: hosted
version: "0.12.19"
version: "0.12.17"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b"
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
url: "https://pub.dev"
source: hosted
version: "0.13.0"
version: "0.11.1"
meta:
dependency: transitive
description:
......@@ -540,18 +508,10 @@ packages:
dependency: transitive
description:
name: mime
sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6"
url: "https://pub.dev"
source: hosted
version: "2.0.0"
native_toolchain_c:
dependency: transitive
description:
name: native_toolchain_c
sha256: "6ba77bb18063eebe9de401f5e6437e95e1438af0a87a3a39084fbd37c90df572"
sha256: "801fd0b26f14a4a58ccb09d5892c3fbdeff209594300a542492cf13fba9d247a"
url: "https://pub.dev"
source: hosted
version: "0.17.6"
version: "1.0.6"
nested:
dependency: transitive
description:
......@@ -560,22 +520,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.0"
objective_c:
dependency: transitive
description:
name: objective_c
sha256: "100a1c87616ab6ed41ec263b083c0ef3261ee6cd1dc3b0f35f8ddfa4f996fe52"
url: "https://pub.dev"
source: hosted
version: "9.3.0"
package_config:
dependency: transitive
description:
name: package_config
sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc
sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd"
url: "https://pub.dev"
source: hosted
version: "2.2.0"
version: "2.1.0"
path:
dependency: transitive
description:
......@@ -588,26 +540,26 @@ packages:
dependency: transitive
description:
name: path_provider
sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd"
sha256: fec0d61223fba3154d87759e3cc27fe2c8dc498f6386c6d6fc80d1afdd1bf378
url: "https://pub.dev"
source: hosted
version: "2.1.5"
version: "2.1.4"
path_provider_android:
dependency: transitive
description:
name: path_provider_android
sha256: "69cbd515a62b94d32a7944f086b2f82b4ac40a1d45bebfc00813a430ab2dabcd"
sha256: a248d8146ee5983446bf03ed5ea8f6533129a12b11f12057ad1b4a67a2b3b41d
url: "https://pub.dev"
source: hosted
version: "2.3.1"
version: "2.2.4"
path_provider_foundation:
dependency: transitive
description:
name: path_provider_foundation
sha256: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699"
sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16
url: "https://pub.dev"
source: hosted
version: "2.6.0"
version: "2.4.0"
path_provider_linux:
dependency: transitive
description:
......@@ -636,10 +588,10 @@ packages:
dependency: transitive
description:
name: petitparser
sha256: "91bd59303e9f769f108f8df05e371341b15d59e995e6806aefab827b58336675"
sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27
url: "https://pub.dev"
source: hosted
version: "7.0.2"
version: "6.0.2"
platform:
dependency: transitive
description:
......@@ -660,10 +612,10 @@ packages:
dependency: transitive
description:
name: pool
sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d"
sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a"
url: "https://pub.dev"
source: hosted
version: "1.5.2"
version: "1.5.1"
provider:
dependency: transitive
description:
......@@ -676,42 +628,34 @@ packages:
dependency: transitive
description:
name: pub_semver
sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585"
sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c"
url: "https://pub.dev"
source: hosted
version: "2.2.0"
version: "2.1.4"
pubspec_parse:
dependency: transitive
description:
name: pubspec_parse
sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082"
sha256: "81876843eb50dc2e1e5b151792c9a985c5ed2536914115ed04e9c8528f6647b0"
url: "https://pub.dev"
source: hosted
version: "1.5.0"
record_use:
dependency: transitive
description:
name: record_use
sha256: "2551bd8eecfe95d14ae75f6021ad0248be5c27f138c2ec12fcb52b500b3ba1ed"
url: "https://pub.dev"
source: hosted
version: "0.6.0"
version: "1.4.0"
shelf:
dependency: transitive
description:
name: shelf
sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12
sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4
url: "https://pub.dev"
source: hosted
version: "1.4.2"
version: "1.4.1"
shelf_web_socket:
dependency: transitive
description:
name: shelf_web_socket
sha256: cc36c297b52866d203dbf9332263c94becc2fe0ceaa9681d07b6ef9807023b67
sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1"
url: "https://pub.dev"
source: hosted
version: "2.0.1"
version: "1.0.4"
sky_engine:
dependency: transitive
description: flutter
......@@ -729,10 +673,10 @@ packages:
dependency: transitive
description:
name: source_span
sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab"
sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
url: "https://pub.dev"
source: hosted
version: "1.10.2"
version: "1.10.0"
stack_trace:
dependency: transitive
description:
......@@ -761,42 +705,42 @@ packages:
dependency: transitive
description:
name: string_scanner
sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
url: "https://pub.dev"
source: hosted
version: "1.4.1"
version: "1.2.0"
term_glyph:
dependency: transitive
description:
name: term_glyph
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
url: "https://pub.dev"
source: hosted
version: "1.2.2"
version: "1.2.1"
test_api:
dependency: transitive
description:
name: test_api
sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a"
sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55
url: "https://pub.dev"
source: hosted
version: "0.7.10"
version: "0.7.7"
timing:
dependency: transitive
description:
name: timing
sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe"
sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32"
url: "https://pub.dev"
source: hosted
version: "1.0.2"
version: "1.0.1"
typed_data:
dependency: transitive
description:
name: typed_data
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c
url: "https://pub.dev"
source: hosted
version: "1.4.0"
version: "1.3.2"
vector_math:
dependency: transitive
description:
......@@ -817,66 +761,50 @@ packages:
dependency: transitive
description:
name: watcher
sha256: "1398c9f081a753f9226febe8900fce8f7d0a67163334e1c94a2438339d79d635"
url: "https://pub.dev"
source: hosted
version: "1.2.1"
web:
dependency: transitive
description:
name: web
sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
sha256: "592ab6e2892f67760543fb712ff0177f4ec76c031f02f5b4ff8d3fc5eb9fb61a"
url: "https://pub.dev"
source: hosted
version: "1.1.1"
web_socket:
dependency: transitive
description:
name: web_socket
sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c"
url: "https://pub.dev"
source: hosted
version: "1.0.1"
version: "1.1.4"
web_socket_channel:
dependency: transitive
description:
name: web_socket_channel
sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8
sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b
url: "https://pub.dev"
source: hosted
version: "3.0.3"
version: "2.4.0"
win32:
dependency: transitive
dependency: "direct overridden"
description:
name: win32
sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e
sha256: a79dbe579cb51ecd6d30b17e0cae4e0ea15e2c0e66f69ad4198f22a6789e94f4
url: "https://pub.dev"
source: hosted
version: "5.15.0"
version: "5.5.1"
xdg_directories:
dependency: transitive
description:
name: xdg_directories
sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d
url: "https://pub.dev"
source: hosted
version: "1.1.0"
version: "1.0.4"
xml:
dependency: transitive
description:
name: xml
sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025"
sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226
url: "https://pub.dev"
source: hosted
version: "6.6.1"
version: "6.5.0"
yaml:
dependency: transitive
description:
name: yaml
sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce
sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5"
url: "https://pub.dev"
source: hosted
version: "3.1.3"
version: "3.1.2"
sdks:
dart: ">=3.10.3 <4.0.0"
flutter: ">=3.38.4"
dart: ">=3.8.0-0 <4.0.0"
flutter: ">=3.18.0-18.0.pre.54"
......@@ -20,6 +20,7 @@ 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
......@@ -43,8 +44,11 @@ dependencies:
flutter_screenutil: ^5.9.3
fluttertoast: ^8.2.5
loading_animation_widget: ^1.3.0
fl_chart: ^1.2.0
# fl_chart: ^1.2.0
flutter_switch: ^0.3.2
fl_chart: ^0.71.0
# fl_chart: ^0.71.0
# fl_chart: ^1.1.0
dev_dependencies:
flutter_test:
......@@ -59,6 +63,10 @@ dev_dependencies:
auto_route_generator: ^7.3.2
build_runner: ^2.4.9
# 依赖覆盖 - 解决 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
......
......@@ -7,7 +7,6 @@ list(APPEND FLUTTER_PLUGIN_LIST
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST
jni
)
set(PLUGIN_BUNDLED_LIBRARIES)
......
# 智慧酒店 App - 技术及流程文档
# 智慧酒店 App - 技术及流程文档
## 一、项目概览
| 项目 | 说明 |
|------|------|
| 项目名称 | smart_hotel_app |
| 类型 | Flutter 移动端 App |
| 用途 | 智慧酒店运营管理(设备告警、客房服务、经营看板、能耗分析) |
| Dart SDK | >=3.2.5 <4.0.0 |
| 设计稿基准 | 750 x 1334 (逻辑像素) |
| 路由方案 | auto_route (代码生成) |
| 状态管理 | flutter_bloc (BLoC 模式) |
| 网络请求 | Dio (单例封装) |
| 安全存储 | flutter_secure_storage |
---
## 二、技术栈
### 2.1 核心依赖
| 依赖 | 版本 | 用途 |
|------|------|------|
| `flutter_bloc` | ^8.1.5 | 状态管理(BLoC 模式) |
| `equatable` | ^2.0.5 | 值对象相等性比较(用于 Event/State) |
| `auto_route` | ^7.9.2 | 声明式路由管理 |
| `auto_route_generator` | ^7.3.2 | 路由代码生成器(dev) |
| `build_runner` | ^2.4.9 | 代码生成运行器(dev) |
| `dio` | ^5.4.0 | HTTP 网络请求 |
| `flutter_secure_storage` | ^9.0.0 | 安全本地存储(Token、密码) |
| `flutter_screenutil` | ^5.9.3 | 屏幕适配(基于 750x1334 设计稿) |
| `fluttertoast` | ^8.2.5 | Toast 提示 |
| `fl_chart` | ^1.2.0 | 图表组件(温度曲线、能耗柱状图等) |
| `loading_animation_widget` | ^1.3.0 | 加载动画 |
| `flutter_switch` | ^0.3.2 | 开关组件 |
### 2.2 静态资源
```
lib/assets/
├── icon/ # 图标(jth.png, person1.png, right.png)
├── login/ # 登录页 logo
└── tab/ # 底部导航 tab 图标(选中/未选中)
```
---
## 三、项目目录结构
```
lib/
├── main.dart # 应用入口,初始化依赖,BLoC 监听
├── assets/ # 静态图片资源
│ ├── icon/
│ ├── login/
│ └── tab/
├── blocs/ # 状态管理(BLoC 模式)
│ └── auth/
│ ├── auth_bloc.dart # 认证 BLoC 逻辑
│ ├── auth_event.dart # 认证事件定义
│ └── auth_state.dart # 认证状态定义
├── http/ # 网络请求层
│ ├── dio_request.dart # Dio 单例封装(GET/POST/PUT/DELETE/PATCH/上传/下载)
│ ├── response_model.dart # 统一响应模型 ResponseModel<T>
│ ├── exceptions/ # 异常定义
│ │ ├── app_exception.dart # 基础异常类
│ │ ├── business_exception.dart # 业务异常(400/401/403)
│ │ ├── cancel_exception.dart # 请求取消异常
│ │ ├── network_exception.dart # 网络超时异常
│ │ └── server_exception.dart # 服务端异常(404/500+)
│ └── interceptors/ # Dio 拦截器
│ ├── request_interceptor.dart # 请求拦截器(添加 Token/UUID/Header)
│ ├── response_interceptor.dart # 响应拦截器(日志打印)
│ └── error_interceptor.dart # 错误拦截器(异常转换 + 401 事件触发)
├── models/ # 数据模型
│ └── bo/ # 业务对象(Business Object)
│ └── user_info_bo.dart # 用户信息模型
├── repositories/ # 数据仓库层
│ └── auth_repository.dart # 认证仓库(调用 API)
├── routes/ # 路由配置
│ ├── app_router.dart # 路由定义(手动编写)
│ └── app_router.gr.dart # 路由生成代码(auto_route 自动生成)
├── services/ # 业务服务层
│ └── auth_service.dart # 认证服务(登录/Token 过期检查)
├── utils/ # 工具类
│ ├── constants.dart # 全局常量(BaseUrl/超时/业务码/Token 等)
│ ├── event_bus.dart # 全局事件总线(TokenExpiredEvent / LogoutEvent)
│ ├── logger.dart # 日志工具(Log.info/error/request/response)
│ └── storage/ # 本地存储
│ └── storage_service.dart # SecureStorage 封装(Token/记住密码)
└── views/ # 页面视图
├── layout/
│ └── default_view.dart # 主布局(BottomNavigationBar + Tab 页)
├── login/ # 登录模块
│ ├── login_view.dart # 登录页面
│ ├── controller.dart # 登录控制器
│ └── child/ # 登录子组件
│ ├── btn_login.dart # 登录按钮
│ ├── btn_reset.dart # 重置按钮
│ ├── password_field.dart # 密码输入框
│ ├── remember_password_row.dart # 记住密码行
│ └── username_field.dart # 用户名输入框
├── home/ # 首页(告警)模块
│ ├── index/ # 首页概览
│ │ ├── index_view.dart
│ │ ├── controller.dart
│ │ └── child/
│ │ ├── task_head_block.dart # 头部标题
│ │ ├── task_total_block.dart # 任务统计卡片
│ │ ├── temperature_block.dart # 温度告警块
│ │ ├── voltage_operate_block.dart # 电压操作块
│ │ └── equipment_list_block.dart # 设备列表
│ ├── abnormal/ # 异常详情页
│ │ ├── abnormal_detail_view.dart
│ │ ├── controller.dart
│ │ └── child/
│ │ ├── alarm_info_block.dart
│ │ ├── temperature_block.dart
│ │ └── temperature_char_block.dart
│ └── device/ # 设备详情页
│ ├── device_detail_view.dart
│ ├── controller.dart
│ └── child/
│ ├── device_parms_block.dart
│ ├── power_char_block.dart
│ └── state_info_block.dart
├── service/ # 服务模块
│ ├── index/ # 服务概览
│ │ ├── index_view.dart
│ │ ├── controller.dart
│ │ └── child/
│ │ ├── service_head_block.dart
│ │ ├── service_stats_block.dart
│ │ └── room_management_block.dart
│ └── room/ # 客房详情页
│ ├── room_detail_view.dart
│ ├── controller.dart
│ └── child/
│ ├── room_info_block.dart
│ ├── device_control_block.dart
│ └── bottom_operation_block.dart
├── report/ # 看板/报表模块
│ ├── index/ # 经营概览看板
│ │ ├── index_view.dart
│ │ ├── controller.dart
│ │ └── child/
│ │ ├── report_header_block.dart
│ │ ├── report_metrics_block.dart
│ │ ├── weekly_power_chart.dart
│ │ ├── room_status_distribution.dart
│ │ ├── device_online_rate.dart
│ │ └── quick_actions_block.dart
│ ├── energy/ # 能耗分析页
│ │ ├── energy_detail_view.dart
│ │ ├── controller.dart
│ │ └── child/
│ │ ├── energy_stats_card_block.dart
│ │ ├── hourly_chart_block.dart
│ │ └── zone_chart_block.dart
│ └── room/ # 房间能耗详情页
│ ├── room_detail.dart
│ └── controller.dart
├── profile/ # 我的页面
│ └── index_view.dart
└── search/ # 搜索页
└── index_view.dart
```
---
## 四、架构分层
项目采用清晰的分层架构,数据流向为单向:
```
┌─────────────────────────────────────────────────┐
│ UI 层 (views/) │
│ - View: 页面组件 (@RoutePage) │
│ - Controller: 页面逻辑控制器 │
│ - child/: 可复用 UI 子组件 │
└──────────────┬──────────────────────────────────┘
│ 依赖注入 (context.read<Bloc>())
┌──────────────▼──────────────────────────────────┐
│ 状态管理层 (blocs/) │
│ - Bloc: 状态机逻辑 │
│ - Event: 输入事件 (extends Equatable) │
│ - State: 输出状态 (extends Equatable) │
└──────────────┬──────────────────────────────────┘
│ 调用 Service
┌──────────────▼──────────────────────────────────┐
│ 业务服务层 (services/) │
│ - 封装业务逻辑,协调 Repository 和 Storage │
└──────────────┬──────────────────────────────────┘
│ 调用 Repository
┌──────────────▼──────────────────────────────────┐
│ 数据仓库层 (repositories/) │
│ - 调用 DioRequest 发送 HTTP 请求 │
└──────────────┬──────────────────────────────────┘
│ 调用 Dio
┌──────────────▼──────────────────────────────────┐
│ 网络层 (http/) │
│ - DioRequest: 单例 HTTP 客户端 │
│ - Interceptors: 请求/响应/错误拦截 │
│ - ResponseModel<T>: 统一响应模型 │
│ - Exceptions: 分类异常处理 │
└──────────────┬──────────────────────────────────┘
│
┌────▼────┐
│ 后端 API │
└─────────┘
```
**横切层:**
- `utils/storage/` - 本地安全存储(Token/记住密码)
- `utils/event_bus.dart` - 全局事件总线(Token 过期通知)
- `utils/logger.dart` - 日志工具
- `utils/constants.dart` - 全局配置常量
---
## 五、核心流程
### 5.1 应用启动流程
```
main()
│
├─ WidgetsFlutterBinding.ensureInitialized()
├─ runApp(MyApp())
│
└─ MyApp.initState()
├─ StorageService() 初始化
├─ DioRequest.instance.init(_storageService) // 初始化网络层
├─ AuthRepository() 创建
├─ AuthService(authRepository, storageService) 创建
├─ AuthBloc(authService, storageService) 创建
├─ AppRouter() 创建路由
├─ 监听 eventBus.on<TokenExpiredEvent>() // Token 过期全局监听
└─ _checkTokenOnStartup() // 检查本地 Token 有效
├─ 有 Token 且已过期 → AuthTokenExpiredEvent → 跳转登录页
└─ 无 Token 或有效 → 正常
```
### 5.2 路由结构
```
LoginRoute (initial: true) # 初始页面 - 登录页
│
DefaultLayoutRoute # 主布局(带 BottomNavigationBar)
├── HomeRoute (initial) # Tab1 - 设备告警看板
├── ServiceIndexRoute # Tab2 - 客房服务
├── ReportIndexRoute # Tab3 - 经营看板
└── ProfileRoute # Tab4 - 我的
│
SearchRoute # 搜索页(独立路由)
AbnormalDetailRoute # 告警详情页(push)
DeviceDetailRoute # 设备详情页(push)
ServiceRoomDetailRoute # 客房详情页(push,传参 roomNumber)
ReportEnergyDetailRoute # 能耗分析页(push)
ReportRoomDetailRoute # 房间能耗详情页(push)
```
**路由生成规则:** `@RoutePage()` 注解在有 `View` 或 `Route` 后缀的页面类上,运行 `build_runner` 自动生成 `app_router.gr.dart`。
### 5.3 登录流程
```
LoginView
│
├─ initState: 加载记住的账号密码
│ └─ storageService.getRememberCredentials()
│
├─ 用户点击登录按钮
│ └─ LoginController.login()
│ ├─ 表单校验
│ ├─ 记住密码 → storageService.saveRememberCredentials()
│ └─ authBloc.add(AuthLoginRequested(username, password))
│
└─ AuthBloc 状态流转:
AuthLoginRequested
├─ emit(AuthLoading) → UI 显示 loading
├─ authService.login(username, password)
│ ├─ authRepository.login() → POST /login
│ ├─ 返回成功 → storageService.saveToken()
│ └─ 返回失败 → throw
├─ 成功 → emit(AuthSuccess(userInfo))
└─ 失败 → emit(AuthFailure(error))
```
### 5.4 网络请求流程
```
Repository
│
└─ DioRequest.instance.get/post/put/delete()
│
├─ 生成唯一 UUID 用于请求追踪
├─ 创建 CancelToken(支持取消)
│
├─ RequestInterceptor.onRequest()
│ ├─ 添加 UUID 到 Header (X-Request-UUID)
│ ├─ 添加时间戳 (X-Timestamp)
│ ├─ 注入 Bearer Token (Authorization)
│ └─ 打印请求日志
│
├─ 发送 HTTP 请求
│
├─ ResponseInterceptor.onResponse()
│ └─ 打印响应日志
│
├─ 错误时 → ErrorInterceptor.onError()
│ ├─ 按 DioExceptionType 分类:
│ │ ├─ 超时 → NetworkException
│ │ ├─ 400 → BusinessException
│ │ ├─ 401 → BusinessException + 发送 TokenExpiredEvent
│ │ ├─ 403 → BusinessException
│ │ ├─ 404 → ServerException
│ │ ├─ 500+ → ServerException
│ │ └─ cancel → CancelException
│ └─ 打印错误日志
│
└─ 统一返回 ResponseModel<T>
├─ code: HTTP 状态码
├─ msg: 消息
├─ data: 泛型数据(支持 fromJsonT 转换)
├─ success: code == 200
├─ uuid: 请求追踪 ID
└─ timestamp: 时间戳
```
### 5.5 Token 管理流程
```
Token 存储:
├─ 登录成功 → storageService.saveToken(token, expiryHours: 24)
│ ├─ 保存 token 到 FlutterSecureStorage
│ └─ 保存过期时间戳 (当前时间 + 24h)
│
├─ 每次请求 → RequestInterceptor 自动注入 Bearer Token
│
├─ API 返回 401 → ErrorInterceptor 触发 eventBus.emit(TokenExpiredEvent())
│ └─ main.dart 中监听 → authBloc.add(AuthTokenExpiredEvent())
│ ├─ storageService.deleteToken()
│ └─ emit(AuthTokenExpired)
│ └─ AuthBloc 监听 → appRouter.replaceAll([LoginRoute()])
│
└─ 手动退出 → authBloc.add(AuthLogoutRequestedEvent())
├─ storageService.deleteToken()
└─ emit(AuthLoggedOut)
└─ AuthBloc 监听 → appRouter.replaceAll([LoginRoute()])
```
### 5.6 全局状态监听流程(main.dart)
```
MaterialApp.router
│
└─ MultiBlocListener
└─ BlocListener<AuthBloc, AuthState>
├─ AuthSuccess → router.replaceAll([DefaultLayoutRoute()]) // 进入主页
├─ AuthTokenExpired → SnackBar 提示 + router.replaceAll([LoginRoute()])
└─ AuthLoggedOut → router.replaceAll([LoginRoute()])
```
---
## 六、视图层开发规范
### 6.1 页面结构模板
每个页面模块遵循统一的组织方式:
```
views/<模块>/<子页面>/
├── xxx_view.dart # 页面组件(@RoutePage() 注解)
├── controller.dart # 页面逻辑控制器(纯 Dart 类,持有 BuildContext)
└── child/ # 页面级子组件(Block 粒度)
├── xxx_block.dart
└── ...
```
### 6.2 View 规范
- 使用 `@RoutePage()` 注解,由 auto_route 自动生成路由
- 使用 `StatefulWidget`,在 `initState` 中创建 Controller
- 通过 `LayoutBuilder` + `SingleChildScrollView` + `ConstrainedBox` 实现可滚动布局
- 子组件采用 Block 粒度拆分,通过构造函数传入 controller 或数据
### 6.3 Controller 规范
- 纯 Dart 类(非 Widget),持有 `BuildContext` 用于路由跳转
- 负责业务逻辑:数据获取、表单处理、路由导航
- 通过 `context.pushRoute(XxxRoute())` 进行页面跳转
- 通过 `context.popRoute()` 返回上一页
### 6.4 BLoC 使用规范
- **Event**: 继承 `Equatable`,重写 `props` 用于状态比较
- **State**: 继承 `Equatable`,每种状态一个子类
- `AuthInitial` - 初始状态
- `AuthLoading` - 加载中
- `AuthSuccess` - 成功(携带数据)
- `AuthFailure` - 失败(携带错误信息)
- `AuthTokenExpired` - Token 过期
- `AuthLoggedOut` - 已登出
- **Bloc**: 在 `main.dart` 中统一创建,通过 `BlocProvider.value()` 注入
- **UI 中使用**: `context.read<AuthBloc>().add(Event())` 发送事件
- **UI 中监听**: `BlocListener` 监听状态变化做副作用(跳转、弹窗)
- **UI 中构建**: `BlocBuilder` 根据状态构建不同 UI
---
## 七、关键业务模块说明
| 模块 | 路由 | 功能描述 |
|------|------|----------|
| **登录** | LoginRoute | 用户名/密码登录,记住密码,BLoC 状态管理 |
| **告警看板 (Home)** | HomeRoute | 设备告警概览:待处理任务、温度告警、电压告警、设备列表 |
| **告警详情** | AbnormalDetailRoute | 温度异常详情:温度曲线图(fl_chart)、告警信息、操作处理 |
| **设备详情** | DeviceDetailRoute | 智能空开详情:运行状态、参数、功率曲线图 |
| **客房服务** | ServiceIndexRoute | 客房管理:按楼层查看房间、送电/断电、跳转房间详情 |
| **客房详情** | ServiceRoomDetailRoute | 单客房详情:房间信息、设备控制、底部操作 |
| **经营看板** | ReportIndexRoute | 管理层看板:用电指标、客房状态分布、设备在线率、快捷操作 |
| **能耗分析** | ReportEnergyDetailRoute | 今日/月度能耗统计、24小时柱状图、区域占比图 |
| **房间能耗** | ReportRoomDetailRoute | 单独房间的能耗详情(页面待完善) |
| **搜索** | SearchRoute | 搜索功能(页面待完善) |
| **我的** | ProfileRoute | 个人中心(页面待完善) |
---
## 八、常量配置 (Constants)
| 配置项 | 值 | 说明 |
|--------|-----|------|
| baseUrl | http://localhost:8080 | 后端 API 地址 |
| connectTimeout | 30000ms | 连接超时 |
| receiveTimeout | 30000ms | 接收超时 |
| sendTimeout | 30000ms | 发送超时 |
| successCode | 200 | 业务成功码 |
| tokenExpiredCode | 401 | Token 过期码 |
| forbiddenCode | 403 | 无权限码 |
| maxRetries | 3 | 最大重试次数 |
| tokenExpiryHours | 24 | Token 过期时间(小时) |
---
## 九、开发命令
```bash
# 生成路由代码(修改 app_router.dart 后必须执行)
flutter pub run build_runner build --delete-conflicting-outputs
# 或使用 watch 模式(自动监听变更)
flutter pub run build_runner watch --delete-conflicting-outputs
# 运行项目
flutter run
# 依赖更新
flutter pub get
```
\ 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