mirror of
https://github.com/SyncrowIOT/web.git
synced 2025-07-11 07:38:05 +00:00
Compare commits
1 Commits
SP-1705-fe
...
SP-1620-FE
Author | SHA1 | Date | |
---|---|---|---|
a1826b43ac |
10
.github/.github/dependabot.yaml
vendored
10
.github/.github/dependabot.yaml
vendored
@ -1,10 +0,0 @@
|
|||||||
version: 2
|
|
||||||
updates:
|
|
||||||
- package-ecosystem: "github-actions"
|
|
||||||
directory: "/"
|
|
||||||
schedule:
|
|
||||||
interval: "daily"
|
|
||||||
- package-ecosystem: "pub"
|
|
||||||
directory: "/"
|
|
||||||
schedule:
|
|
||||||
interval: "daily"
|
|
@ -1,8 +0,0 @@
|
|||||||
<svg width="23" height="13" viewBox="0 0 23 13" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path d="M1.24512 2.00263V11L1.90308 11.278L7.5311 6.94877C7.82484 6.72277 7.82484 6.27987 7.5311 6.05388L1.90308 1.72461L1.24512 2.00263Z" fill="#023DFE" fill-opacity="0.7"/>
|
|
||||||
<path d="M1.90344 1.7231L1.68312 1.55364C1.31186 1.2681 0.774414 1.53272 0.774414 2.00105V10.9984C0.774414 11.4668 1.31186 11.7315 1.68312 11.4459L1.90344 11.2764V1.7231Z" fill="#023DFE"/>
|
|
||||||
<path d="M12.0646 0.855469H11.5001C11.1883 0.855469 10.9355 1.10819 10.9355 1.41998V11.5813H12.0646C12.3764 11.5813 12.6291 11.3285 12.6291 11.0167V1.41998C12.6291 1.10826 12.3764 0.855469 12.0646 0.855469Z" fill="#023DFE" fill-opacity="0.7"/>
|
|
||||||
<path d="M12.6291 11.0168C12.0056 11.0168 11.5001 10.5113 11.5001 9.88779V0.855469H10.9356C10.6238 0.855469 10.3711 1.10819 10.3711 1.41998V11.5813C10.3711 11.893 10.6238 12.1458 10.9356 12.1458H12.0646C12.3764 12.1458 12.6291 11.893 12.6291 11.5813V11.0168Z" fill="#023DFE"/>
|
|
||||||
<path d="M21.4247 2.01953L16.1094 6.50343L21.4247 11.1061L22.226 10.7315V2.27062L21.4247 2.01953Z" fill="#023DFE" fill-opacity="0.7"/>
|
|
||||||
<path d="M17.3084 6.94723C17.0147 6.7213 17.0147 6.27833 17.3084 6.05233L22.2263 2.26933V2.00108C22.2263 1.53275 21.6889 1.26807 21.3176 1.55367L15.4693 6.05233C15.1756 6.27833 15.1756 6.7213 15.4693 6.94723L21.3176 11.4459C21.6889 11.7314 22.2263 11.4668 22.2263 10.9985V10.7302L17.3084 6.94723Z" fill="#023DFE"/>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 1.4 KiB |
@ -1,8 +0,0 @@
|
|||||||
<svg width="22" height="12" viewBox="0 0 22 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path d="M15.2227 1.27411V10.2715L15.8806 10.5495L21.5086 6.22025C21.8024 5.99426 21.8024 5.55136 21.5086 5.32536L15.8806 0.996094L15.2227 1.27411Z" fill="#023DFE" fill-opacity="0.7"/>
|
|
||||||
<path d="M15.881 0.994589L15.6607 0.825126C15.2894 0.539589 14.752 0.804208 14.752 1.27254V10.2699C14.752 10.7383 15.2894 11.0029 15.6607 10.7173L15.881 10.5479V0.994589Z" fill="#023DFE"/>
|
|
||||||
<path d="M12.0646 0.128906H11.5001C11.1883 0.128906 10.9355 0.381631 10.9355 0.693418V10.8547H12.0646C12.3764 10.8547 12.6291 10.602 12.6291 10.2902V0.693418C12.6291 0.381699 12.3764 0.128906 12.0646 0.128906Z" fill="#023DFE" fill-opacity="0.7"/>
|
|
||||||
<path d="M12.6291 10.2903C12.0056 10.2903 11.5001 9.78474 11.5001 9.16123V0.128906H10.9356C10.6238 0.128906 10.3711 0.381631 10.3711 0.693418V10.8547C10.3711 11.1665 10.6238 11.4192 10.9356 11.4192H12.0646C12.3764 11.4192 12.6291 11.1665 12.6291 10.8547V10.2903Z" fill="#023DFE"/>
|
|
||||||
<path d="M6.95005 1.29297L1.63477 5.77687L6.95005 10.3795L7.75136 10.005V1.54405L6.95005 1.29297Z" fill="#023DFE" fill-opacity="0.7"/>
|
|
||||||
<path d="M2.83379 6.21871C2.54005 5.99278 2.54005 5.54981 2.83379 5.32382L7.7517 1.54081V1.27257C7.7517 0.804238 7.21426 0.539551 6.843 0.825156L0.994719 5.32382C0.700979 5.54981 0.700979 5.99278 0.994719 6.21871L6.843 10.7174C7.21426 11.0029 7.7517 10.7383 7.7517 10.27V10.0017L2.83379 6.21871Z" fill="#023DFE"/>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 1.4 KiB |
@ -1,6 +0,0 @@
|
|||||||
<svg width="10" height="12" viewBox="0 0 10 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path d="M8.81262 0.277344H8.24811C7.93632 0.277344 7.68359 0.530068 7.68359 0.841855V11.0031H8.81262C9.1244 11.0031 9.37713 10.7504 9.37713 10.4386V0.841855C9.37713 0.530136 9.1244 0.277344 8.81262 0.277344Z" fill="#023DFE" fill-opacity="0.7"/>
|
|
||||||
<path d="M9.37719 10.4387C8.75361 10.4387 8.24816 9.93317 8.24816 9.30967V0.277344H7.68365C7.37187 0.277344 7.11914 0.530068 7.11914 0.841855V11.0031C7.11914 11.3149 7.37187 11.5676 7.68365 11.5676H8.81268C9.12446 11.5676 9.37719 11.3149 9.37719 11.0031V10.4387Z" fill="#023DFE"/>
|
|
||||||
<path d="M2.5548 0.277344H1.99029C1.67851 0.277344 1.42578 0.530068 1.42578 0.841855V11.0031H2.5548C2.86659 11.0031 3.11932 10.7504 3.11932 10.4386V0.841855C3.11932 0.530136 2.86659 0.277344 2.5548 0.277344Z" fill="#023DFE" fill-opacity="0.7"/>
|
|
||||||
<path d="M3.11937 10.4387C2.4958 10.4387 1.99035 9.93317 1.99035 9.30967V0.277344H1.42584C1.11405 0.277344 0.861328 0.530068 0.861328 0.841855V11.0031C0.861328 11.3149 1.11405 11.5676 1.42584 11.5676H2.55486C2.86665 11.5676 3.11937 11.3149 3.11937 11.0031V10.4387Z" fill="#023DFE"/>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 1.1 KiB |
@ -1,10 +0,0 @@
|
|||||||
<svg width="17" height="17" viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<g clip-path="url(#clip0_10119_2631)">
|
|
||||||
<path d="M16.4229 10.9077V14.4803C16.4229 15.1238 15.9015 15.6453 15.2579 15.6453C14.6143 15.6453 14.0928 15.1238 14.0928 14.4803V14.3684C12.644 15.7134 10.7197 16.5 8.65572 16.5C5.42291 16.5 2.52657 14.573 1.27576 11.5914C1.21425 11.4446 1.18535 11.2917 1.18535 11.1417C1.18535 10.6854 1.45378 10.2539 1.89977 10.0661C2.49302 9.81722 3.17574 10.0959 3.4246 10.6901C4.31098 12.804 6.36475 14.1699 8.65572 14.1699C10.3973 14.1699 11.9999 13.3804 13.0578 12.0728H11.6849C11.0413 12.0728 10.5198 11.5513 10.5198 10.9077C10.5198 10.2641 11.0413 9.74265 11.6849 9.74265H15.2574C15.901 9.74265 16.4229 10.2641 16.4229 10.9077ZM5.31572 7.413C5.9593 7.413 6.48078 6.89105 6.48078 6.24794C6.48078 5.60436 5.9593 5.08288 5.31572 5.08288H4.13342C5.18897 3.68388 6.84661 2.83012 8.65572 2.83012C10.9472 2.83012 13.0005 4.1965 13.8873 6.31039C14.1357 6.90364 14.8184 7.18278 15.4117 6.93439C16.0049 6.68554 16.2841 6.00328 16.0357 5.40956C14.7844 2.42701 11.8881 0.5 8.65572 0.5C6.4421 0.5 4.38554 1.40455 2.90824 2.93218V2.67493C2.90824 2.03135 2.3863 1.50987 1.74318 1.50987C1.09961 1.50987 0.578125 2.03135 0.578125 2.67493V6.24794C0.578125 6.55691 0.701155 6.8533 0.919255 7.07234C1.13782 7.2909 1.43375 7.413 1.74318 7.413H5.31572Z" fill="#023DFE" fill-opacity="0.7"/>
|
|
||||||
</g>
|
|
||||||
<defs>
|
|
||||||
<clipPath id="clip0_10119_2631">
|
|
||||||
<rect width="16" height="16" fill="white" transform="translate(0.5 0.5)"/>
|
|
||||||
</clipPath>
|
|
||||||
</defs>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 1.5 KiB |
Binary file not shown.
Before Width: | Height: | Size: 290 KiB |
@ -1,10 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
class AppLoadingIndicator extends StatelessWidget {
|
|
||||||
const AppLoadingIndicator({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return const Center(child: CircularProgressIndicator());
|
|
||||||
}
|
|
||||||
}
|
|
@ -11,6 +11,7 @@ import 'package:syncrow_web/pages/home/bloc/home_event.dart';
|
|||||||
import 'package:syncrow_web/pages/routines/bloc/create_routine_bloc/create_routine_bloc.dart';
|
import 'package:syncrow_web/pages/routines/bloc/create_routine_bloc/create_routine_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
|
import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
|
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_event.dart';
|
||||||
import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_bloc.dart';
|
import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_bloc.dart';
|
||||||
import 'package:syncrow_web/services/locator.dart';
|
import 'package:syncrow_web/services/locator.dart';
|
||||||
import 'package:syncrow_web/utils/app_routes.dart';
|
import 'package:syncrow_web/utils/app_routes.dart';
|
||||||
@ -20,10 +21,8 @@ import 'package:syncrow_web/utils/theme/theme.dart';
|
|||||||
|
|
||||||
Future<void> main() async {
|
Future<void> main() async {
|
||||||
try {
|
try {
|
||||||
const environment = String.fromEnvironment(
|
const environment =
|
||||||
'FLAVOR',
|
String.fromEnvironment('FLAVOR', defaultValue: 'production');
|
||||||
defaultValue: 'production',
|
|
||||||
);
|
|
||||||
await dotenv.load(fileName: '.env.$environment');
|
await dotenv.load(fileName: '.env.$environment');
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
await Firebase.initializeApp(
|
await Firebase.initializeApp(
|
||||||
@ -41,7 +40,7 @@ class MyApp extends StatelessWidget {
|
|||||||
initialLocation: RoutesConst.auth,
|
initialLocation: RoutesConst.auth,
|
||||||
routes: AppRoutes.getRoutes(),
|
routes: AppRoutes.getRoutes(),
|
||||||
redirect: (context, state) async {
|
redirect: (context, state) async {
|
||||||
final checkToken = await AuthBloc.getTokenAndValidate();
|
String checkToken = await AuthBloc.getTokenAndValidate();
|
||||||
final loggedIn = checkToken == 'Success';
|
final loggedIn = checkToken == 'Success';
|
||||||
final goingToLogin = state.uri.toString() == RoutesConst.auth;
|
final goingToLogin = state.uri.toString() == RoutesConst.auth;
|
||||||
|
|
||||||
@ -59,7 +58,8 @@ class MyApp extends StatelessWidget {
|
|||||||
BlocProvider<CreateRoutineBloc>(
|
BlocProvider<CreateRoutineBloc>(
|
||||||
create: (context) => CreateRoutineBloc(),
|
create: (context) => CreateRoutineBloc(),
|
||||||
),
|
),
|
||||||
BlocProvider(create: (context) => HomeBloc()..add(FetchUserInfo())),
|
BlocProvider(
|
||||||
|
create: (context) => HomeBloc()..add(const FetchUserInfo())),
|
||||||
BlocProvider<VisitorPasswordBloc>(
|
BlocProvider<VisitorPasswordBloc>(
|
||||||
create: (context) => VisitorPasswordBloc(),
|
create: (context) => VisitorPasswordBloc(),
|
||||||
),
|
),
|
||||||
@ -67,7 +67,7 @@ class MyApp extends StatelessWidget {
|
|||||||
create: (context) => RoutineBloc(),
|
create: (context) => RoutineBloc(),
|
||||||
),
|
),
|
||||||
BlocProvider<SpaceTreeBloc>(
|
BlocProvider<SpaceTreeBloc>(
|
||||||
create: (context) => SpaceTreeBloc(),
|
create: (context) => SpaceTreeBloc()..add(InitialEvent()),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
child: MaterialApp.router(
|
child: MaterialApp.router(
|
||||||
|
@ -11,6 +11,7 @@ import 'package:syncrow_web/pages/home/bloc/home_event.dart';
|
|||||||
import 'package:syncrow_web/pages/routines/bloc/create_routine_bloc/create_routine_bloc.dart';
|
import 'package:syncrow_web/pages/routines/bloc/create_routine_bloc/create_routine_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
|
import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
|
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_event.dart';
|
||||||
import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_bloc.dart';
|
import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_bloc.dart';
|
||||||
import 'package:syncrow_web/services/locator.dart';
|
import 'package:syncrow_web/services/locator.dart';
|
||||||
import 'package:syncrow_web/utils/app_routes.dart';
|
import 'package:syncrow_web/utils/app_routes.dart';
|
||||||
@ -20,10 +21,7 @@ import 'package:syncrow_web/utils/theme/theme.dart';
|
|||||||
|
|
||||||
Future<void> main() async {
|
Future<void> main() async {
|
||||||
try {
|
try {
|
||||||
const environment = String.fromEnvironment(
|
const environment = String.fromEnvironment('FLAVOR', defaultValue: 'development');
|
||||||
'FLAVOR',
|
|
||||||
defaultValue: 'development',
|
|
||||||
);
|
|
||||||
await dotenv.load(fileName: '.env.$environment');
|
await dotenv.load(fileName: '.env.$environment');
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
await Firebase.initializeApp(
|
await Firebase.initializeApp(
|
||||||
@ -41,7 +39,7 @@ class MyApp extends StatelessWidget {
|
|||||||
initialLocation: RoutesConst.auth,
|
initialLocation: RoutesConst.auth,
|
||||||
routes: AppRoutes.getRoutes(),
|
routes: AppRoutes.getRoutes(),
|
||||||
redirect: (context, state) async {
|
redirect: (context, state) async {
|
||||||
final checkToken = await AuthBloc.getTokenAndValidate();
|
String checkToken = await AuthBloc.getTokenAndValidate();
|
||||||
final loggedIn = checkToken == 'Success';
|
final loggedIn = checkToken == 'Success';
|
||||||
final goingToLogin = state.uri.toString() == RoutesConst.auth;
|
final goingToLogin = state.uri.toString() == RoutesConst.auth;
|
||||||
|
|
||||||
@ -59,7 +57,7 @@ class MyApp extends StatelessWidget {
|
|||||||
BlocProvider<CreateRoutineBloc>(
|
BlocProvider<CreateRoutineBloc>(
|
||||||
create: (context) => CreateRoutineBloc(),
|
create: (context) => CreateRoutineBloc(),
|
||||||
),
|
),
|
||||||
BlocProvider(create: (context) => HomeBloc()..add(FetchUserInfo())),
|
BlocProvider(create: (context) => HomeBloc()..add(const FetchUserInfo())),
|
||||||
BlocProvider<VisitorPasswordBloc>(
|
BlocProvider<VisitorPasswordBloc>(
|
||||||
create: (context) => VisitorPasswordBloc(),
|
create: (context) => VisitorPasswordBloc(),
|
||||||
),
|
),
|
||||||
@ -67,7 +65,7 @@ class MyApp extends StatelessWidget {
|
|||||||
create: (context) => RoutineBloc(),
|
create: (context) => RoutineBloc(),
|
||||||
),
|
),
|
||||||
BlocProvider<SpaceTreeBloc>(
|
BlocProvider<SpaceTreeBloc>(
|
||||||
create: (context) => SpaceTreeBloc(),
|
create: (context) => SpaceTreeBloc()..add(InitialEvent()),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
child: MaterialApp.router(
|
child: MaterialApp.router(
|
||||||
|
@ -11,6 +11,7 @@ import 'package:syncrow_web/pages/home/bloc/home_event.dart';
|
|||||||
import 'package:syncrow_web/pages/routines/bloc/create_routine_bloc/create_routine_bloc.dart';
|
import 'package:syncrow_web/pages/routines/bloc/create_routine_bloc/create_routine_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
|
import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
|
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_event.dart';
|
||||||
import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_bloc.dart';
|
import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_bloc.dart';
|
||||||
import 'package:syncrow_web/services/locator.dart';
|
import 'package:syncrow_web/services/locator.dart';
|
||||||
import 'package:syncrow_web/utils/app_routes.dart';
|
import 'package:syncrow_web/utils/app_routes.dart';
|
||||||
@ -38,7 +39,7 @@ class MyApp extends StatelessWidget {
|
|||||||
initialLocation: RoutesConst.auth,
|
initialLocation: RoutesConst.auth,
|
||||||
routes: AppRoutes.getRoutes(),
|
routes: AppRoutes.getRoutes(),
|
||||||
redirect: (context, state) async {
|
redirect: (context, state) async {
|
||||||
final checkToken = await AuthBloc.getTokenAndValidate();
|
String checkToken = await AuthBloc.getTokenAndValidate();
|
||||||
final loggedIn = checkToken == 'Success';
|
final loggedIn = checkToken == 'Success';
|
||||||
final goingToLogin = state.uri.toString() == RoutesConst.auth;
|
final goingToLogin = state.uri.toString() == RoutesConst.auth;
|
||||||
|
|
||||||
@ -56,7 +57,7 @@ class MyApp extends StatelessWidget {
|
|||||||
BlocProvider<CreateRoutineBloc>(
|
BlocProvider<CreateRoutineBloc>(
|
||||||
create: (context) => CreateRoutineBloc(),
|
create: (context) => CreateRoutineBloc(),
|
||||||
),
|
),
|
||||||
BlocProvider(create: (context) => HomeBloc()..add(FetchUserInfo())),
|
BlocProvider(create: (context) => HomeBloc()..add(const FetchUserInfo())),
|
||||||
BlocProvider<VisitorPasswordBloc>(
|
BlocProvider<VisitorPasswordBloc>(
|
||||||
create: (context) => VisitorPasswordBloc(),
|
create: (context) => VisitorPasswordBloc(),
|
||||||
),
|
),
|
||||||
@ -64,7 +65,7 @@ class MyApp extends StatelessWidget {
|
|||||||
create: (context) => RoutineBloc(),
|
create: (context) => RoutineBloc(),
|
||||||
),
|
),
|
||||||
BlocProvider<SpaceTreeBloc>(
|
BlocProvider<SpaceTreeBloc>(
|
||||||
create: (context) => SpaceTreeBloc(),
|
create: (context) => SpaceTreeBloc()..add(InitialEvent()),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
child: MaterialApp.router(
|
child: MaterialApp.router(
|
||||||
|
@ -15,9 +15,7 @@ class AirQualityDataModel extends Equatable {
|
|||||||
return AirQualityDataModel(
|
return AirQualityDataModel(
|
||||||
date: DateTime.parse(json['date'] as String),
|
date: DateTime.parse(json['date'] as String),
|
||||||
data: (json['data'] as List<dynamic>)
|
data: (json['data'] as List<dynamic>)
|
||||||
.map(
|
.map((e) => AirQualityPercentageData.fromJson(e as Map<String, dynamic>))
|
||||||
(e) => AirQualityPercentageData.fromJson(e as Map<String, dynamic>),
|
|
||||||
)
|
|
||||||
.toList(),
|
.toList(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -25,9 +23,9 @@ class AirQualityDataModel extends Equatable {
|
|||||||
static final Map<String, Color> metricColors = {
|
static final Map<String, Color> metricColors = {
|
||||||
'good': ColorsManager.goodGreen.withValues(alpha: 0.7),
|
'good': ColorsManager.goodGreen.withValues(alpha: 0.7),
|
||||||
'moderate': ColorsManager.moderateYellow.withValues(alpha: 0.7),
|
'moderate': ColorsManager.moderateYellow.withValues(alpha: 0.7),
|
||||||
'unhealthy_sensitive': ColorsManager.poorOrange.withValues(alpha: 0.7),
|
'poor': ColorsManager.poorOrange.withValues(alpha: 0.7),
|
||||||
'unhealthy': ColorsManager.unhealthyRed.withValues(alpha: 0.7),
|
'unhealthy': ColorsManager.unhealthyRed.withValues(alpha: 0.7),
|
||||||
'very_unhealthy': ColorsManager.severePink.withValues(alpha: 0.7),
|
'severe': ColorsManager.severePink.withValues(alpha: 0.7),
|
||||||
'hazardous': ColorsManager.hazardousPurple.withValues(alpha: 0.7),
|
'hazardous': ColorsManager.hazardousPurple.withValues(alpha: 0.7),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -38,19 +36,22 @@ class AirQualityDataModel extends Equatable {
|
|||||||
class AirQualityPercentageData extends Equatable {
|
class AirQualityPercentageData extends Equatable {
|
||||||
const AirQualityPercentageData({
|
const AirQualityPercentageData({
|
||||||
required this.type,
|
required this.type,
|
||||||
|
required this.name,
|
||||||
required this.percentage,
|
required this.percentage,
|
||||||
});
|
});
|
||||||
|
|
||||||
final String type;
|
final String type;
|
||||||
|
final String name;
|
||||||
final double percentage;
|
final double percentage;
|
||||||
|
|
||||||
factory AirQualityPercentageData.fromJson(Map<String, dynamic> json) {
|
factory AirQualityPercentageData.fromJson(Map<String, dynamic> json) {
|
||||||
return AirQualityPercentageData(
|
return AirQualityPercentageData(
|
||||||
type: json['type'] as String? ?? '',
|
type: json['type'] as String? ?? '',
|
||||||
|
name: json['name'] as String? ?? '',
|
||||||
percentage: (json['percentage'] as num?)?.toDouble() ?? 0,
|
percentage: (json['percentage'] as num?)?.toDouble() ?? 0,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [type, percentage];
|
List<Object?> get props => [type, name, percentage];
|
||||||
}
|
}
|
||||||
|
@ -25,8 +25,8 @@ class AnalyticsDevice {
|
|||||||
|
|
||||||
factory AnalyticsDevice.fromJson(Map<String, dynamic> json) {
|
factory AnalyticsDevice.fromJson(Map<String, dynamic> json) {
|
||||||
return AnalyticsDevice(
|
return AnalyticsDevice(
|
||||||
uuid: json['uuid'] as String? ?? '',
|
uuid: json['uuid'] as String,
|
||||||
name: json['name'] as String? ?? '',
|
name: json['name'] as String,
|
||||||
createdAt: json['createdAt'] != null
|
createdAt: json['createdAt'] != null
|
||||||
? DateTime.parse(json['createdAt'] as String)
|
? DateTime.parse(json['createdAt'] as String)
|
||||||
: null,
|
: null,
|
||||||
@ -39,8 +39,8 @@ class AnalyticsDevice {
|
|||||||
? ProductDevice.fromJson(json['productDevice'] as Map<String, dynamic>)
|
? ProductDevice.fromJson(json['productDevice'] as Map<String, dynamic>)
|
||||||
: null,
|
: null,
|
||||||
spaceUuid: json['spaceUuid'] as String?,
|
spaceUuid: json['spaceUuid'] as String?,
|
||||||
latitude: json['lat'] != null ? double.parse(json['lat'] as String? ?? '0.0') : null,
|
latitude: json['lat'] != null ? double.parse(json['lat'] as String) : null,
|
||||||
longitude: json['lon'] != null ? double.parse(json['lon'] as String? ?? '0.0') : null,
|
longitude: json['lon'] != null ? double.parse(json['lon'] as String) : null,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,21 +14,12 @@ class OccupancyHeatMapModel extends Equatable {
|
|||||||
});
|
});
|
||||||
|
|
||||||
factory OccupancyHeatMapModel.fromJson(Map<String, dynamic> json) {
|
factory OccupancyHeatMapModel.fromJson(Map<String, dynamic> json) {
|
||||||
final eventDate = json['event_date'] as String?;
|
|
||||||
final year = eventDate?.split('-')[0];
|
|
||||||
final month = eventDate?.split('-')[1];
|
|
||||||
final day = eventDate?.split('-')[2];
|
|
||||||
|
|
||||||
return OccupancyHeatMapModel(
|
return OccupancyHeatMapModel(
|
||||||
uuid: json['uuid'] as String? ?? '',
|
uuid: json['uuid'] as String? ?? '',
|
||||||
eventDate: DateTime.utc(
|
eventDate: DateTime.parse(
|
||||||
int.parse(year ?? '2025'),
|
json['event_date'] as String? ?? '${DateTime.now()}',
|
||||||
int.parse(month ?? '1'),
|
|
||||||
int.parse(day ?? '1'),
|
|
||||||
),
|
),
|
||||||
countTotalPresenceDetected: num.parse(
|
countTotalPresenceDetected: json['count_total_presence_detected'] as int? ?? 0,
|
||||||
json['count_total_presence_detected']?.toString() ?? '0',
|
|
||||||
).toInt(),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,9 +38,9 @@ class RangeOfAqiValue extends Equatable {
|
|||||||
factory RangeOfAqiValue.fromJson(Map<String, dynamic> json) {
|
factory RangeOfAqiValue.fromJson(Map<String, dynamic> json) {
|
||||||
return RangeOfAqiValue(
|
return RangeOfAqiValue(
|
||||||
type: json['type'] as String,
|
type: json['type'] as String,
|
||||||
min: (json['min'] as num? ?? 0).toDouble(),
|
min: (json['min'] as num).toDouble(),
|
||||||
average: (json['average'] as num? ?? 0).toDouble(),
|
average: (json['average'] as num).toDouble(),
|
||||||
max: (json['max'] as num? ?? 0).toDouble(),
|
max: (json['max'] as num).toDouble(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,6 +33,7 @@ class AirQualityDistributionBloc
|
|||||||
state.copyWith(
|
state.copyWith(
|
||||||
status: AirQualityDistributionStatus.success,
|
status: AirQualityDistributionStatus.success,
|
||||||
chartData: result,
|
chartData: result,
|
||||||
|
filteredChartData: _arrangeChartDataByType(result, state.selectedAqiType),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -46,17 +47,35 @@ class AirQualityDistributionBloc
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onClearAirQualityDistribution(
|
Future<void> _onClearAirQualityDistribution(
|
||||||
ClearAirQualityDistribution event,
|
ClearAirQualityDistribution event,
|
||||||
Emitter<AirQualityDistributionState> emit,
|
Emitter<AirQualityDistributionState> emit,
|
||||||
) {
|
) async {
|
||||||
emit(AirQualityDistributionState(selectedAqiType: state.selectedAqiType));
|
emit(const AirQualityDistributionState());
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onUpdateAqiTypeEvent(
|
void _onUpdateAqiTypeEvent(
|
||||||
UpdateAqiTypeEvent event,
|
UpdateAqiTypeEvent event,
|
||||||
Emitter<AirQualityDistributionState> emit,
|
Emitter<AirQualityDistributionState> emit,
|
||||||
) {
|
) {
|
||||||
emit(state.copyWith(selectedAqiType: event.aqiType));
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
selectedAqiType: event.aqiType,
|
||||||
|
filteredChartData: _arrangeChartDataByType(state.chartData, event.aqiType),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<AirQualityDataModel> _arrangeChartDataByType(
|
||||||
|
List<AirQualityDataModel> data,
|
||||||
|
AqiType aqiType,
|
||||||
|
) {
|
||||||
|
final filteredData = data.map(
|
||||||
|
(data) => AirQualityDataModel(
|
||||||
|
date: data.date,
|
||||||
|
data: data.data.where((value) => value.type == aqiType.code).toList(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return filteredData.toList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,24 +11,28 @@ class AirQualityDistributionState extends Equatable {
|
|||||||
const AirQualityDistributionState({
|
const AirQualityDistributionState({
|
||||||
this.status = AirQualityDistributionStatus.initial,
|
this.status = AirQualityDistributionStatus.initial,
|
||||||
this.chartData = const [],
|
this.chartData = const [],
|
||||||
|
this.filteredChartData = const [],
|
||||||
this.errorMessage,
|
this.errorMessage,
|
||||||
this.selectedAqiType = AqiType.aqi,
|
this.selectedAqiType = AqiType.aqi,
|
||||||
});
|
});
|
||||||
|
|
||||||
final AirQualityDistributionStatus status;
|
final AirQualityDistributionStatus status;
|
||||||
final List<AirQualityDataModel> chartData;
|
final List<AirQualityDataModel> chartData;
|
||||||
|
final List<AirQualityDataModel> filteredChartData;
|
||||||
final String? errorMessage;
|
final String? errorMessage;
|
||||||
final AqiType selectedAqiType;
|
final AqiType selectedAqiType;
|
||||||
|
|
||||||
AirQualityDistributionState copyWith({
|
AirQualityDistributionState copyWith({
|
||||||
AirQualityDistributionStatus? status,
|
AirQualityDistributionStatus? status,
|
||||||
List<AirQualityDataModel>? chartData,
|
List<AirQualityDataModel>? chartData,
|
||||||
|
List<AirQualityDataModel>? filteredChartData,
|
||||||
String? errorMessage,
|
String? errorMessage,
|
||||||
AqiType? selectedAqiType,
|
AqiType? selectedAqiType,
|
||||||
}) {
|
}) {
|
||||||
return AirQualityDistributionState(
|
return AirQualityDistributionState(
|
||||||
status: status ?? this.status,
|
status: status ?? this.status,
|
||||||
chartData: chartData ?? this.chartData,
|
chartData: chartData ?? this.chartData,
|
||||||
|
filteredChartData: filteredChartData ?? this.filteredChartData,
|
||||||
errorMessage: errorMessage ?? this.errorMessage,
|
errorMessage: errorMessage ?? this.errorMessage,
|
||||||
selectedAqiType: selectedAqiType ?? this.selectedAqiType,
|
selectedAqiType: selectedAqiType ?? this.selectedAqiType,
|
||||||
);
|
);
|
||||||
|
@ -75,6 +75,6 @@ class RangeOfAqiBloc extends Bloc<RangeOfAqiEvent, RangeOfAqiState> {
|
|||||||
ClearRangeOfAqiEvent event,
|
ClearRangeOfAqiEvent event,
|
||||||
Emitter<RangeOfAqiState> emit,
|
Emitter<RangeOfAqiState> emit,
|
||||||
) {
|
) {
|
||||||
emit(RangeOfAqiState(selectedAqiType: state.selectedAqiType));
|
emit(const RangeOfAqiState());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
|||||||
import 'package:syncrow_web/pages/analytics/modules/air_quality/blocs/air_quality_distribution/air_quality_distribution_bloc.dart';
|
import 'package:syncrow_web/pages/analytics/modules/air_quality/blocs/air_quality_distribution/air_quality_distribution_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/air_quality/blocs/device_location/device_location_bloc.dart';
|
import 'package:syncrow_web/pages/analytics/modules/air_quality/blocs/device_location/device_location_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/air_quality/blocs/range_of_aqi/range_of_aqi_bloc.dart';
|
import 'package:syncrow_web/pages/analytics/modules/air_quality/blocs/range_of_aqi/range_of_aqi_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_type_dropdown.dart';
|
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_date_picker_bloc/analytics_date_picker_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_devices/analytics_devices_bloc.dart';
|
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_devices/analytics_devices_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/realtime_device_changes/realtime_device_changes_bloc.dart';
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/realtime_device_changes/realtime_device_changes_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/params/get_air_quality_distribution_param.dart';
|
import 'package:syncrow_web/pages/analytics/params/get_air_quality_distribution_param.dart';
|
||||||
@ -21,14 +21,12 @@ abstract final class FetchAirQualityDataHelper {
|
|||||||
required String spaceUuid,
|
required String spaceUuid,
|
||||||
bool shouldFetchAnalyticsDevices = true,
|
bool shouldFetchAnalyticsDevices = true,
|
||||||
}) {
|
}) {
|
||||||
final aqiType = context.read<AirQualityDistributionBloc>().state.selectedAqiType;
|
final date = context.read<AnalyticsDatePickerBloc>().state.monthlyDate;
|
||||||
if (shouldFetchAnalyticsDevices) {
|
|
||||||
loadAnalyticsDevices(
|
loadAnalyticsDevices(
|
||||||
context,
|
context,
|
||||||
communityUuid: communityUuid,
|
communityUuid: communityUuid,
|
||||||
spaceUuid: spaceUuid,
|
spaceUuid: spaceUuid,
|
||||||
);
|
);
|
||||||
}
|
|
||||||
loadRangeOfAqi(
|
loadRangeOfAqi(
|
||||||
context,
|
context,
|
||||||
spaceUuid: spaceUuid,
|
spaceUuid: spaceUuid,
|
||||||
@ -38,7 +36,6 @@ abstract final class FetchAirQualityDataHelper {
|
|||||||
context,
|
context,
|
||||||
spaceUuid: spaceUuid,
|
spaceUuid: spaceUuid,
|
||||||
date: date,
|
date: date,
|
||||||
aqiType: aqiType,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,15 +104,10 @@ abstract final class FetchAirQualityDataHelper {
|
|||||||
BuildContext context, {
|
BuildContext context, {
|
||||||
required String spaceUuid,
|
required String spaceUuid,
|
||||||
required DateTime date,
|
required DateTime date,
|
||||||
required AqiType aqiType,
|
|
||||||
}) {
|
}) {
|
||||||
context.read<AirQualityDistributionBloc>().add(
|
context.read<AirQualityDistributionBloc>().add(
|
||||||
LoadAirQualityDistribution(
|
LoadAirQualityDistribution(
|
||||||
GetAirQualityDistributionParam(
|
GetAirQualityDistributionParam(spaceUuid: spaceUuid, date: date),
|
||||||
spaceUuid: spaceUuid,
|
|
||||||
date: date,
|
|
||||||
aqiType: aqiType,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -18,16 +18,11 @@ abstract final class RangeOfAqiChartsHelper {
|
|||||||
(ColorsManager.hazardousPurple, 'Hazardous'),
|
(ColorsManager.hazardousPurple, 'Hazardous'),
|
||||||
];
|
];
|
||||||
|
|
||||||
static FlTitlesData titlesData(
|
static FlTitlesData titlesData(BuildContext context, List<RangeOfAqi> data) {
|
||||||
BuildContext context,
|
|
||||||
List<RangeOfAqi> data, {
|
|
||||||
double leftSideInterval = 50,
|
|
||||||
}) {
|
|
||||||
final titlesData = EnergyManagementChartsHelper.titlesData(context);
|
final titlesData = EnergyManagementChartsHelper.titlesData(context);
|
||||||
return titlesData.copyWith(
|
return titlesData.copyWith(
|
||||||
bottomTitles: titlesData.bottomTitles.copyWith(
|
bottomTitles: titlesData.bottomTitles.copyWith(
|
||||||
sideTitles: titlesData.bottomTitles.sideTitles.copyWith(
|
sideTitles: titlesData.bottomTitles.sideTitles.copyWith(
|
||||||
reservedSize: 36,
|
|
||||||
getTitlesWidget: (value, meta) => Padding(
|
getTitlesWidget: (value, meta) => Padding(
|
||||||
padding: const EdgeInsetsDirectional.only(top: 20.0),
|
padding: const EdgeInsetsDirectional.only(top: 20.0),
|
||||||
child: Text(
|
child: Text(
|
||||||
@ -43,11 +38,10 @@ abstract final class RangeOfAqiChartsHelper {
|
|||||||
leftTitles: titlesData.leftTitles.copyWith(
|
leftTitles: titlesData.leftTitles.copyWith(
|
||||||
sideTitles: titlesData.leftTitles.sideTitles.copyWith(
|
sideTitles: titlesData.leftTitles.sideTitles.copyWith(
|
||||||
reservedSize: 70,
|
reservedSize: 70,
|
||||||
interval: leftSideInterval,
|
interval: 50,
|
||||||
maxIncluded: false,
|
maxIncluded: false,
|
||||||
minIncluded: true,
|
|
||||||
getTitlesWidget: (value, meta) {
|
getTitlesWidget: (value, meta) {
|
||||||
final text = value.toInt().toString();
|
final text = value >= 300 ? '301+' : value.toInt().toString();
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsetsDirectional.only(end: 12),
|
padding: const EdgeInsetsDirectional.only(end: 12),
|
||||||
child: FittedBox(
|
child: FittedBox(
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/air_quality_end_side_widget.dart';
|
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/air_quality_end_side_widget.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_distribution_chart_box.dart';
|
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_distribution_chart_box.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_legend.dart';
|
|
||||||
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/range_of_aqi_chart_box.dart';
|
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/range_of_aqi_chart_box.dart';
|
||||||
|
|
||||||
class AirQualityView extends StatelessWidget {
|
class AirQualityView extends StatelessWidget {
|
||||||
@ -21,10 +20,6 @@ class AirQualityView extends StatelessWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
spacing: 32,
|
spacing: 32,
|
||||||
children: [
|
children: [
|
||||||
SizedBox(
|
|
||||||
height: height * 0.1,
|
|
||||||
child: const AqiLegend(),
|
|
||||||
),
|
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: height * 1.2,
|
height: height * 1.2,
|
||||||
child: const AirQualityEndSideWidget(),
|
child: const AirQualityEndSideWidget(),
|
||||||
@ -45,7 +40,7 @@ class AirQualityView extends StatelessWidget {
|
|||||||
return SingleChildScrollView(
|
return SingleChildScrollView(
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: _padding,
|
padding: _padding,
|
||||||
height: height * 1.2,
|
height: height * 1.1,
|
||||||
child: const Column(
|
child: const Column(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
@ -57,9 +52,8 @@ class AirQualityView extends StatelessWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
spacing: 20,
|
spacing: 20,
|
||||||
children: [
|
children: [
|
||||||
Expanded(flex: 2, child: AqiLegend()),
|
Expanded(child: RangeOfAqiChartBox()),
|
||||||
Expanded(flex: 12, child: RangeOfAqiChartBox()),
|
Expanded(child: AqiDistributionChartBox()),
|
||||||
Expanded(flex: 12, child: AqiDistributionChartBox()),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -65,7 +65,7 @@ class AqiDeviceInfo extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
final tvocValue = _getValueForStatus(
|
final tvocValue = _getValueForStatus(
|
||||||
status,
|
status,
|
||||||
'voc_value',
|
'tvoc_value',
|
||||||
formatter: (value) => (value / 100).toStringAsFixed(2),
|
formatter: (value) => (value / 100).toStringAsFixed(2),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -3,7 +3,6 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/models/air_quality_data_model.dart';
|
import 'package:syncrow_web/pages/analytics/models/air_quality_data_model.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/helpers/energy_management_charts_helper.dart';
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/helpers/energy_management_charts_helper.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/widgets/charts_x_axis_title.dart';
|
|
||||||
import 'package:syncrow_web/utils/color_manager.dart';
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||||
|
|
||||||
@ -17,6 +16,11 @@ class AqiDistributionChart extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final sortedData = List<AirQualityDataModel>.from(chartData)
|
||||||
|
..sort(
|
||||||
|
(a, b) => a.date.compareTo(b.date),
|
||||||
|
);
|
||||||
|
|
||||||
return BarChart(
|
return BarChart(
|
||||||
BarChartData(
|
BarChartData(
|
||||||
maxY: 100.1,
|
maxY: 100.1,
|
||||||
@ -26,30 +30,29 @@ class AqiDistributionChart extends StatelessWidget {
|
|||||||
borderData: EnergyManagementChartsHelper.borderData(),
|
borderData: EnergyManagementChartsHelper.borderData(),
|
||||||
barTouchData: _barTouchData(context),
|
barTouchData: _barTouchData(context),
|
||||||
titlesData: _titlesData(context),
|
titlesData: _titlesData(context),
|
||||||
barGroups: _buildBarGroups(),
|
barGroups: _buildBarGroups(sortedData),
|
||||||
),
|
),
|
||||||
duration: Duration.zero,
|
duration: Duration.zero,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<BarChartGroupData> _buildBarGroups() {
|
List<BarChartGroupData> _buildBarGroups(List<AirQualityDataModel> sortedData) {
|
||||||
final groups = <BarChartGroupData>[];
|
return List.generate(sortedData.length, (index) {
|
||||||
for (var i = 0; i < chartData.length; i++) {
|
final data = sortedData[index];
|
||||||
final data = chartData[i];
|
|
||||||
final isAllZero = data.data.every((d) => d.percentage == 0);
|
|
||||||
if (isAllZero) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
final stackItems = <BarChartRodData>[];
|
final stackItems = <BarChartRodData>[];
|
||||||
double currentY = 0;
|
double currentY = 0;
|
||||||
var isFirstElement = true;
|
bool isFirstElement = true;
|
||||||
|
|
||||||
for (final percentageData in data.data) {
|
// Sort data by type to ensure consistent order
|
||||||
|
final sortedPercentageData = List<AirQualityPercentageData>.from(data.data)
|
||||||
|
..sort((a, b) => a.type.compareTo(b.type));
|
||||||
|
|
||||||
|
for (final percentageData in sortedPercentageData) {
|
||||||
stackItems.add(
|
stackItems.add(
|
||||||
BarChartRodData(
|
BarChartRodData(
|
||||||
fromY: currentY,
|
fromY: currentY,
|
||||||
toY: currentY + percentageData.percentage ,
|
toY: currentY + percentageData.percentage ,
|
||||||
color: AirQualityDataModel.metricColors[percentageData.type],
|
color: AirQualityDataModel.metricColors[percentageData.name]!,
|
||||||
borderRadius: isFirstElement
|
borderRadius: isFirstElement
|
||||||
? const BorderRadius.only(
|
? const BorderRadius.only(
|
||||||
topLeft: Radius.circular(22),
|
topLeft: Radius.circular(22),
|
||||||
@ -62,15 +65,13 @@ class AqiDistributionChart extends StatelessWidget {
|
|||||||
currentY += percentageData.percentage + _rodStackItemsSpacing;
|
currentY += percentageData.percentage + _rodStackItemsSpacing;
|
||||||
isFirstElement = false;
|
isFirstElement = false;
|
||||||
}
|
}
|
||||||
groups.add(
|
|
||||||
BarChartGroupData(
|
return BarChartGroupData(
|
||||||
x: i,
|
x: index,
|
||||||
barRods: stackItems,
|
barRods: stackItems,
|
||||||
groupVertically: true,
|
groupVertically: true,
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
return groups;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
BarTouchData _barTouchData(BuildContext context) {
|
BarTouchData _barTouchData(BuildContext context) {
|
||||||
@ -81,27 +82,25 @@ class AqiDistributionChart extends StatelessWidget {
|
|||||||
color: ColorsManager.semiTransparentBlack,
|
color: ColorsManager.semiTransparentBlack,
|
||||||
),
|
),
|
||||||
tooltipRoundedRadius: 16,
|
tooltipRoundedRadius: 16,
|
||||||
maxContentWidth: 500,
|
|
||||||
tooltipPadding: const EdgeInsets.all(8),
|
tooltipPadding: const EdgeInsets.all(8),
|
||||||
getTooltipItem: (group, groupIndex, rod, rodIndex) {
|
getTooltipItem: (group, groupIndex, rod, rodIndex) {
|
||||||
final data = chartData[group.x];
|
final data = chartData[group.x.toInt()];
|
||||||
|
|
||||||
final children = <TextSpan>[];
|
final List<TextSpan> children = [];
|
||||||
|
|
||||||
final textStyle = context.textTheme.bodySmall?.copyWith(
|
final textStyle = context.textTheme.bodySmall?.copyWith(
|
||||||
color: ColorsManager.blackColor,
|
color: ColorsManager.blackColor,
|
||||||
fontSize: 11,
|
fontSize: 12,
|
||||||
);
|
);
|
||||||
|
|
||||||
for (final percentageData in data.data) {
|
// Sort data by type to ensure consistent order
|
||||||
if (percentageData.percentage == 0) {
|
final sortedPercentageData = List<AirQualityPercentageData>.from(data.data)
|
||||||
continue;
|
..sort((a, b) => a.type.compareTo(b.type));
|
||||||
}
|
|
||||||
final percentage = percentageData.percentage.toStringAsFixed(1);
|
for (final percentageData in sortedPercentageData) {
|
||||||
final type = percentageData.type[0].toUpperCase() +
|
|
||||||
percentageData.type.substring(1).replaceAll('_', ' ');
|
|
||||||
children.add(TextSpan(
|
children.add(TextSpan(
|
||||||
text: '\n$type: $percentage%',
|
text:
|
||||||
|
'\n${percentageData.type.toUpperCase()}: ${percentageData.percentage.toStringAsFixed(1)}%',
|
||||||
style: textStyle,
|
style: textStyle,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@ -110,10 +109,9 @@ class AqiDistributionChart extends StatelessWidget {
|
|||||||
DateFormat('dd/MM/yyyy').format(data.date),
|
DateFormat('dd/MM/yyyy').format(data.date),
|
||||||
context.textTheme.bodyMedium!.copyWith(
|
context.textTheme.bodyMedium!.copyWith(
|
||||||
color: ColorsManager.blackColor,
|
color: ColorsManager.blackColor,
|
||||||
fontSize: 12,
|
fontSize: 16,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
),
|
),
|
||||||
textAlign: TextAlign.start,
|
|
||||||
children: children,
|
children: children,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -130,6 +128,7 @@ class AqiDistributionChart extends StatelessWidget {
|
|||||||
final leftTitles = titlesData.leftTitles.copyWith(
|
final leftTitles = titlesData.leftTitles.copyWith(
|
||||||
sideTitles: titlesData.leftTitles.sideTitles.copyWith(
|
sideTitles: titlesData.leftTitles.sideTitles.copyWith(
|
||||||
reservedSize: 70,
|
reservedSize: 70,
|
||||||
|
interval: 20,
|
||||||
maxIncluded: false,
|
maxIncluded: false,
|
||||||
minIncluded: true,
|
minIncluded: true,
|
||||||
getTitlesWidget: (value, meta) => Padding(
|
getTitlesWidget: (value, meta) => Padding(
|
||||||
@ -150,9 +149,8 @@ class AqiDistributionChart extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
|
|
||||||
final bottomTitles = AxisTitles(
|
final bottomTitles = AxisTitles(
|
||||||
axisNameWidget: const ChartsXAxisTitle(),
|
|
||||||
sideTitles: SideTitles(
|
sideTitles: SideTitles(
|
||||||
showTitles: chartData.isNotEmpty,
|
showTitles: true,
|
||||||
getTitlesWidget: (value, _) => FittedBox(
|
getTitlesWidget: (value, _) => FittedBox(
|
||||||
alignment: AlignmentDirectional.bottomCenter,
|
alignment: AlignmentDirectional.bottomCenter,
|
||||||
fit: BoxFit.scaleDown,
|
fit: BoxFit.scaleDown,
|
||||||
@ -160,7 +158,7 @@ class AqiDistributionChart extends StatelessWidget {
|
|||||||
chartData[value.toInt()].date.day.toString(),
|
chartData[value.toInt()].date.day.toString(),
|
||||||
style: context.textTheme.bodySmall?.copyWith(
|
style: context.textTheme.bodySmall?.copyWith(
|
||||||
color: ColorsManager.lightGreyColor,
|
color: ColorsManager.lightGreyColor,
|
||||||
fontSize: 12,
|
fontSize: 8,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -33,7 +33,7 @@ class AqiDistributionChartBox extends StatelessWidget {
|
|||||||
const Divider(),
|
const Divider(),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: AqiDistributionChart(chartData: state.chartData),
|
child: AqiDistributionChart(chartData: state.filteredChartData),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -2,11 +2,8 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/air_quality/blocs/air_quality_distribution/air_quality_distribution_bloc.dart';
|
import 'package:syncrow_web/pages/analytics/modules/air_quality/blocs/air_quality_distribution/air_quality_distribution_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_type_dropdown.dart';
|
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_type_dropdown.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_date_picker_bloc/analytics_date_picker_bloc.dart';
|
|
||||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/chart_title.dart';
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/chart_title.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/params/get_air_quality_distribution_param.dart';
|
|
||||||
import 'package:syncrow_web/pages/analytics/widgets/charts_loading_widget.dart';
|
import 'package:syncrow_web/pages/analytics/widgets/charts_loading_widget.dart';
|
||||||
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
|
|
||||||
|
|
||||||
class AqiDistributionChartTitle extends StatelessWidget {
|
class AqiDistributionChartTitle extends StatelessWidget {
|
||||||
const AqiDistributionChartTitle({required this.isLoading, super.key});
|
const AqiDistributionChartTitle({required this.isLoading, super.key});
|
||||||
@ -19,7 +16,7 @@ class AqiDistributionChartTitle extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
ChartsLoadingWidget(isLoading: isLoading),
|
ChartsLoadingWidget(isLoading: isLoading),
|
||||||
const Expanded(
|
const Expanded(
|
||||||
flex: 4,
|
flex: 3,
|
||||||
child: FittedBox(
|
child: FittedBox(
|
||||||
fit: BoxFit.scaleDown,
|
fit: BoxFit.scaleDown,
|
||||||
alignment: AlignmentDirectional.centerStart,
|
alignment: AlignmentDirectional.centerStart,
|
||||||
@ -28,45 +25,20 @@ class AqiDistributionChartTitle extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Expanded(
|
FittedBox(
|
||||||
flex: 2,
|
|
||||||
child: FittedBox(
|
|
||||||
alignment: AlignmentDirectional.centerEnd,
|
alignment: AlignmentDirectional.centerEnd,
|
||||||
fit: BoxFit.scaleDown,
|
fit: BoxFit.scaleDown,
|
||||||
child: AqiTypeDropdown(
|
child: AqiTypeDropdown(
|
||||||
selectedAqiType: context.watch<AirQualityDistributionBloc>().state.selectedAqiType,
|
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
final bloc = context.read<AirQualityDistributionBloc>();
|
context
|
||||||
try {
|
.read<AirQualityDistributionBloc>()
|
||||||
final param = _makeLoadAqiDistributionParam(context, value);
|
.add(UpdateAqiTypeEvent(value));
|
||||||
bloc.add(LoadAirQualityDistribution(param));
|
|
||||||
} catch (_) {
|
|
||||||
return;
|
|
||||||
} finally {
|
|
||||||
bloc.add(UpdateAqiTypeEvent(value));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
GetAirQualityDistributionParam _makeLoadAqiDistributionParam(
|
|
||||||
BuildContext context,
|
|
||||||
AqiType aqiType,
|
|
||||||
) {
|
|
||||||
final date = context.read<AnalyticsDatePickerBloc>().state.monthlyDate;
|
|
||||||
final spaceUuid =
|
|
||||||
context.read<SpaceTreeBloc>().state.selectedSpaces.firstOrNull ?? '';
|
|
||||||
if (spaceUuid.isEmpty) throw Exception('Space UUID is empty');
|
|
||||||
return GetAirQualityDistributionParam(
|
|
||||||
date: date,
|
|
||||||
spaceUuid: spaceUuid,
|
|
||||||
aqiType: aqiType,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,38 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:syncrow_web/pages/analytics/modules/air_quality/helpers/range_of_aqi_charts_helper.dart';
|
|
||||||
import 'package:syncrow_web/pages/analytics/modules/analytics/widgets/chart_informative_cell.dart';
|
|
||||||
import 'package:syncrow_web/utils/style.dart';
|
|
||||||
|
|
||||||
class AqiLegend extends StatelessWidget {
|
|
||||||
const AqiLegend({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Container(
|
|
||||||
padding: const EdgeInsetsDirectional.all(20),
|
|
||||||
decoration: subSectionContainerDecoration.copyWith(
|
|
||||||
borderRadius: BorderRadius.circular(20),
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
spacing: 16,
|
|
||||||
children: RangeOfAqiChartsHelper.gradientData.map((e) {
|
|
||||||
return Flexible(
|
|
||||||
flex: 4,
|
|
||||||
child: FittedBox(
|
|
||||||
fit: BoxFit.fill,
|
|
||||||
child: ChartInformativeCell(
|
|
||||||
color: e.$1,
|
|
||||||
title: FittedBox(
|
|
||||||
fit: BoxFit.fill,
|
|
||||||
child: Text(e.$2),
|
|
||||||
),
|
|
||||||
height: null,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}).toList(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -47,29 +47,19 @@ class AqiLocationInfoCell extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Align(
|
Align(
|
||||||
alignment: AlignmentDirectional.bottomCenter,
|
|
||||||
child: Row(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: SvgPicture.asset(
|
|
||||||
svgPath,
|
|
||||||
fit: BoxFit.scaleDown,
|
|
||||||
alignment: AlignmentDirectional.bottomStart,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: FittedBox(
|
|
||||||
fit: BoxFit.scaleDown,
|
|
||||||
alignment: AlignmentDirectional.bottomEnd,
|
alignment: AlignmentDirectional.bottomEnd,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsetsDirectional.all(10),
|
padding: const EdgeInsetsDirectional.all(10),
|
||||||
|
child: SizedBox(
|
||||||
|
height: 40,
|
||||||
|
width: 120,
|
||||||
|
child: FittedBox(
|
||||||
|
fit: BoxFit.scaleDown,
|
||||||
|
alignment: AlignmentDirectional.bottomEnd,
|
||||||
child: Text(
|
child: Text(
|
||||||
value,
|
value,
|
||||||
style: context.textTheme.bodySmall?.copyWith(
|
style: context.textTheme.bodySmall?.copyWith(
|
||||||
color: ColorsManager.vividBlue.withValues(
|
color: ColorsManager.vividBlue.withValues(alpha: 0.7),
|
||||||
alpha: 0.7,
|
|
||||||
),
|
|
||||||
fontWeight: FontWeight.w700,
|
fontWeight: FontWeight.w700,
|
||||||
fontSize: 24,
|
fontSize: 24,
|
||||||
),
|
),
|
||||||
@ -77,7 +67,16 @@ class AqiLocationInfoCell extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
|
Align(
|
||||||
|
alignment: AlignmentDirectional.bottomStart,
|
||||||
|
child: SizedBox.square(
|
||||||
|
dimension: MediaQuery.sizeOf(context).width * 0.45,
|
||||||
|
child: FittedBox(
|
||||||
|
fit: BoxFit.scaleDown,
|
||||||
|
alignment: AlignmentDirectional.bottomStart,
|
||||||
|
child: SvgPicture.asset(svgPath),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -6,8 +6,8 @@ enum AqiType {
|
|||||||
aqi('AQI', '', 'aqi'),
|
aqi('AQI', '', 'aqi'),
|
||||||
pm25('PM2.5', 'µg/m³', 'pm25'),
|
pm25('PM2.5', 'µg/m³', 'pm25'),
|
||||||
pm10('PM10', 'µg/m³', 'pm10'),
|
pm10('PM10', 'µg/m³', 'pm10'),
|
||||||
hcho('HCHO', 'mg/m³', 'ch2o'),
|
hcho('HCHO', 'mg/m³', 'hcho'),
|
||||||
tvoc('TVOC', 'mg/m³', 'voc'),
|
tvoc('TVOC', 'µg/m³', 'tvoc'),
|
||||||
co2('CO2', 'ppm', 'co2');
|
co2('CO2', 'ppm', 'co2');
|
||||||
|
|
||||||
const AqiType(this.value, this.unit, this.code);
|
const AqiType(this.value, this.unit, this.code);
|
||||||
@ -18,20 +18,19 @@ enum AqiType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class AqiTypeDropdown extends StatefulWidget {
|
class AqiTypeDropdown extends StatefulWidget {
|
||||||
const AqiTypeDropdown({
|
const AqiTypeDropdown({super.key, required this.onChanged});
|
||||||
required this.onChanged,
|
|
||||||
this.selectedAqiType,
|
|
||||||
super.key,
|
|
||||||
});
|
|
||||||
|
|
||||||
final ValueChanged<AqiType?> onChanged;
|
final ValueChanged<AqiType?> onChanged;
|
||||||
final AqiType? selectedAqiType;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<AqiTypeDropdown> createState() => _AqiTypeDropdownState();
|
State<AqiTypeDropdown> createState() => _AqiTypeDropdownState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AqiTypeDropdownState extends State<AqiTypeDropdown> {
|
class _AqiTypeDropdownState extends State<AqiTypeDropdown> {
|
||||||
|
AqiType? _selectedItem = AqiType.aqi;
|
||||||
|
|
||||||
|
void _updateSelectedItem(AqiType? item) => setState(() => _selectedItem = item);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Container(
|
return Container(
|
||||||
@ -42,8 +41,8 @@ class _AqiTypeDropdownState extends State<AqiTypeDropdown> {
|
|||||||
width: 1,
|
width: 1,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: DropdownButton<AqiType>(
|
child: DropdownButton<AqiType?>(
|
||||||
value: widget.selectedAqiType,
|
value: _selectedItem,
|
||||||
isDense: true,
|
isDense: true,
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
dropdownColor: ColorsManager.whiteColors,
|
dropdownColor: ColorsManager.whiteColors,
|
||||||
@ -60,7 +59,10 @@ class _AqiTypeDropdownState extends State<AqiTypeDropdown> {
|
|||||||
items: AqiType.values
|
items: AqiType.values
|
||||||
.map((e) => DropdownMenuItem(value: e, child: Text(e.value)))
|
.map((e) => DropdownMenuItem(value: e, child: Text(e.value)))
|
||||||
.toList(),
|
.toList(),
|
||||||
onChanged: widget.onChanged,
|
onChanged: (value) {
|
||||||
|
_updateSelectedItem(value);
|
||||||
|
widget.onChanged(value);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -2,18 +2,15 @@ import 'package:fl_chart/fl_chart.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/models/range_of_aqi.dart';
|
import 'package:syncrow_web/pages/analytics/models/range_of_aqi.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/air_quality/helpers/range_of_aqi_charts_helper.dart';
|
import 'package:syncrow_web/pages/analytics/modules/air_quality/helpers/range_of_aqi_charts_helper.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_type_dropdown.dart';
|
|
||||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/helpers/energy_management_charts_helper.dart';
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/helpers/energy_management_charts_helper.dart';
|
||||||
import 'package:syncrow_web/utils/color_manager.dart';
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
|
||||||
class RangeOfAqiChart extends StatelessWidget {
|
class RangeOfAqiChart extends StatelessWidget {
|
||||||
final List<RangeOfAqi> chartData;
|
final List<RangeOfAqi> chartData;
|
||||||
final AqiType selectedAqiType;
|
|
||||||
|
|
||||||
const RangeOfAqiChart({
|
const RangeOfAqiChart({
|
||||||
super.key,
|
super.key,
|
||||||
required this.chartData,
|
required this.chartData,
|
||||||
required this.selectedAqiType,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
List<(List<double> values, Color color, Color? dotColor)> get _lines {
|
List<(List<double> values, Color color, Color? dotColor)> get _lines {
|
||||||
@ -48,34 +45,15 @@ class RangeOfAqiChart extends StatelessWidget {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
(double maxY, double interval) get _maxYForAqiType {
|
|
||||||
const aqiMaxValues = <AqiType, (double maxY, double interval)>{
|
|
||||||
AqiType.aqi: (401, 100),
|
|
||||||
AqiType.pm25: (351, 50),
|
|
||||||
AqiType.pm10: (501, 100),
|
|
||||||
AqiType.hcho: (301, 50),
|
|
||||||
AqiType.tvoc: (501, 50),
|
|
||||||
AqiType.co2: (1251, 250),
|
|
||||||
};
|
|
||||||
|
|
||||||
return aqiMaxValues[selectedAqiType]!;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return LineChart(
|
return LineChart(
|
||||||
LineChartData(
|
LineChartData(
|
||||||
minY: 0,
|
minY: 0,
|
||||||
maxY: _maxYForAqiType.$1,
|
maxY: 301,
|
||||||
clipData: const FlClipData.vertical(),
|
clipData: const FlClipData.vertical(),
|
||||||
gridData: EnergyManagementChartsHelper.gridData(
|
gridData: EnergyManagementChartsHelper.gridData(horizontalInterval: 50),
|
||||||
horizontalInterval: _maxYForAqiType.$2,
|
titlesData: RangeOfAqiChartsHelper.titlesData(context, chartData),
|
||||||
),
|
|
||||||
titlesData: RangeOfAqiChartsHelper.titlesData(
|
|
||||||
context,
|
|
||||||
chartData,
|
|
||||||
leftSideInterval: _maxYForAqiType.$2,
|
|
||||||
),
|
|
||||||
borderData: EnergyManagementChartsHelper.borderData(),
|
borderData: EnergyManagementChartsHelper.borderData(),
|
||||||
lineTouchData: RangeOfAqiChartsHelper.lineTouchData(chartData),
|
lineTouchData: RangeOfAqiChartsHelper.lineTouchData(chartData),
|
||||||
betweenBarsData: [
|
betweenBarsData: [
|
||||||
@ -85,7 +63,7 @@ class RangeOfAqiChart extends StatelessWidget {
|
|||||||
gradient: LinearGradient(
|
gradient: LinearGradient(
|
||||||
begin: Alignment.bottomCenter,
|
begin: Alignment.bottomCenter,
|
||||||
end: Alignment.topCenter,
|
end: Alignment.topCenter,
|
||||||
stops: const [0.0, 0.2, 0.4, 0.6, 0.8, 1.0],
|
stops: [0.0, 0.2, 0.4, 0.6, 0.8, 1.0],
|
||||||
colors: RangeOfAqiChartsHelper.gradientData.map((e) {
|
colors: RangeOfAqiChartsHelper.gradientData.map((e) {
|
||||||
final (color, _) = e;
|
final (color, _) = e;
|
||||||
return color.withValues(alpha: 0.6);
|
return color.withValues(alpha: 0.6);
|
||||||
|
@ -32,12 +32,7 @@ class RangeOfAqiChartBox extends StatelessWidget {
|
|||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
const Divider(),
|
const Divider(),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
Expanded(
|
Expanded(child: RangeOfAqiChart(chartData: state.filteredRangeOfAqi)),
|
||||||
child: RangeOfAqiChart(
|
|
||||||
chartData: state.filteredRangeOfAqi,
|
|
||||||
selectedAqiType: state.selectedAqiType,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -63,15 +63,15 @@ class RangeOfAqiChartTitle extends StatelessWidget {
|
|||||||
fit: BoxFit.scaleDown,
|
fit: BoxFit.scaleDown,
|
||||||
alignment: AlignmentDirectional.centerEnd,
|
alignment: AlignmentDirectional.centerEnd,
|
||||||
child: AqiTypeDropdown(
|
child: AqiTypeDropdown(
|
||||||
selectedAqiType: context.watch<RangeOfAqiBloc>().state.selectedAqiType,
|
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
final spaceTreeState = context.read<SpaceTreeBloc>().state;
|
final spaceTreeState = context.read<SpaceTreeBloc>().state;
|
||||||
final spaceUuid = spaceTreeState.selectedSpaces.firstOrNull;
|
final spaceUuid = spaceTreeState.selectedSpaces.firstOrNull;
|
||||||
|
|
||||||
|
if (spaceUuid == null) return;
|
||||||
|
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
context.read<RangeOfAqiBloc>().add(UpdateAqiTypeEvent(value));
|
context.read<RangeOfAqiBloc>().add(UpdateAqiTypeEvent(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (spaceUuid == null) return;
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -16,7 +16,7 @@ import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/real
|
|||||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/total_energy_consumption/total_energy_consumption_bloc.dart';
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/total_energy_consumption/total_energy_consumption_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/occupancy/blocs/occupancy/occupancy_bloc.dart';
|
import 'package:syncrow_web/pages/analytics/modules/occupancy/blocs/occupancy/occupancy_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/occupancy/blocs/occupancy_heat_map/occupancy_heat_map_bloc.dart';
|
import 'package:syncrow_web/pages/analytics/modules/occupancy/blocs/occupancy_heat_map/occupancy_heat_map_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/services/air_quality_distribution/remote_air_quality_distribution_service.dart';
|
import 'package:syncrow_web/pages/analytics/services/air_quality_distribution/fake_air_quality_distribution_service.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/services/analytics_devices/analytics_devices_service_delagate.dart';
|
import 'package:syncrow_web/pages/analytics/services/analytics_devices/analytics_devices_service_delagate.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/services/analytics_devices/remote_energy_management_analytics_devices_service.dart';
|
import 'package:syncrow_web/pages/analytics/services/analytics_devices/remote_energy_management_analytics_devices_service.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/services/analytics_devices/remote_occupancy_analytics_devices_service.dart';
|
import 'package:syncrow_web/pages/analytics/services/analytics_devices/remote_occupancy_analytics_devices_service.dart';
|
||||||
@ -27,12 +27,10 @@ import 'package:syncrow_web/pages/analytics/services/energy_consumption_per_devi
|
|||||||
import 'package:syncrow_web/pages/analytics/services/occupacy/remote_occupancy_service.dart';
|
import 'package:syncrow_web/pages/analytics/services/occupacy/remote_occupancy_service.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/services/occupancy_heat_map/remote_occupancy_heat_map_service.dart';
|
import 'package:syncrow_web/pages/analytics/services/occupancy_heat_map/remote_occupancy_heat_map_service.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/services/power_clamp_info/remote_power_clamp_info_service.dart';
|
import 'package:syncrow_web/pages/analytics/services/power_clamp_info/remote_power_clamp_info_service.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/services/range_of_aqi/remote_range_of_aqi_service.dart';
|
import 'package:syncrow_web/pages/analytics/services/range_of_aqi/fake_range_of_aqi_service.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/services/realtime_device_service/firebase_realtime_device_service.dart';
|
import 'package:syncrow_web/pages/analytics/services/realtime_device_service/firebase_realtime_device_service.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/services/total_energy_consumption/remote_total_energy_consumption_service.dart';
|
import 'package:syncrow_web/pages/analytics/services/total_energy_consumption/remote_total_energy_consumption_service.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/shared/navigate_home_grid_view.dart';
|
import 'package:syncrow_web/pages/device_managment/shared/navigate_home_grid_view.dart';
|
||||||
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
|
|
||||||
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_event.dart';
|
|
||||||
import 'package:syncrow_web/services/api/http_service.dart';
|
import 'package:syncrow_web/services/api/http_service.dart';
|
||||||
import 'package:syncrow_web/utils/theme/responsive_text_theme.dart';
|
import 'package:syncrow_web/utils/theme/responsive_text_theme.dart';
|
||||||
import 'package:syncrow_web/web_layout/web_scaffold.dart';
|
import 'package:syncrow_web/web_layout/web_scaffold.dart';
|
||||||
@ -106,12 +104,12 @@ class _AnalyticsPageState extends State<AnalyticsPage> {
|
|||||||
),
|
),
|
||||||
BlocProvider(
|
BlocProvider(
|
||||||
create: (context) => RangeOfAqiBloc(
|
create: (context) => RangeOfAqiBloc(
|
||||||
RemoteRangeOfAqiService(_httpService),
|
FakeRangeOfAqiService(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
BlocProvider(
|
BlocProvider(
|
||||||
create: (context) => AirQualityDistributionBloc(
|
create: (context) => AirQualityDistributionBloc(
|
||||||
RemoteAirQualityDistributionService(_httpService),
|
FakeAirQualityDistributionService(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
BlocProvider(
|
BlocProvider(
|
||||||
@ -132,19 +130,9 @@ class _AnalyticsPageState extends State<AnalyticsPage> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class AnalyticsPageForm extends StatefulWidget {
|
class AnalyticsPageForm extends StatelessWidget {
|
||||||
const AnalyticsPageForm({super.key});
|
const AnalyticsPageForm({super.key});
|
||||||
|
|
||||||
@override
|
|
||||||
State<AnalyticsPageForm> createState() => _AnalyticsPageFormState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _AnalyticsPageFormState extends State<AnalyticsPageForm> {
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
context.read<SpaceTreeBloc>().add(InitialEvent());
|
|
||||||
super.initState();
|
|
||||||
}
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return WebScaffold(
|
return WebScaffold(
|
||||||
|
@ -20,7 +20,7 @@ class AnalyticsDateFilterButton extends StatefulWidget {
|
|||||||
final void Function(DateTime)? onDateSelected;
|
final void Function(DateTime)? onDateSelected;
|
||||||
final DatePickerType datePickerType;
|
final DatePickerType datePickerType;
|
||||||
|
|
||||||
static final Color _color = ColorsManager.blackColor.withValues(alpha: 0.8);
|
static final _color = ColorsManager.blackColor.withValues(alpha: 0.8);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<AnalyticsDateFilterButton> createState() =>
|
State<AnalyticsDateFilterButton> createState() =>
|
||||||
@ -60,9 +60,10 @@ class _AnalyticsDateFilterButtonState extends State<AnalyticsDateFilterButton> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
showDialog<void>(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (_) => switch (widget.datePickerType) {
|
builder: (_) {
|
||||||
|
return switch (widget.datePickerType) {
|
||||||
DatePickerType.month => MonthPickerWidget(
|
DatePickerType.month => MonthPickerWidget(
|
||||||
selectedDate: widget.selectedDate,
|
selectedDate: widget.selectedDate,
|
||||||
onDateSelected: (value) {
|
onDateSelected: (value) {
|
||||||
@ -75,6 +76,7 @@ class _AnalyticsDateFilterButtonState extends State<AnalyticsDateFilterButton> {
|
|||||||
widget.onDateSelected?.call(value);
|
widget.onDateSelected?.call(value);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
};
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -118,7 +118,7 @@ class AnalyticsPageTabsAndChildren extends StatelessWidget {
|
|||||||
communityUuid: communities.firstOrNull ?? '',
|
communityUuid: communities.firstOrNull ?? '',
|
||||||
spaceUuid: spaces.firstOrNull ?? '',
|
spaceUuid: spaces.firstOrNull ?? '',
|
||||||
);
|
);
|
||||||
return;
|
break;
|
||||||
case AnalyticsPageTab.airQuality:
|
case AnalyticsPageTab.airQuality:
|
||||||
_onAirQualityDateChanged(
|
_onAirQualityDateChanged(
|
||||||
context,
|
context,
|
||||||
@ -126,9 +126,8 @@ class AnalyticsPageTabsAndChildren extends StatelessWidget {
|
|||||||
communityUuid: communities.firstOrNull ?? '',
|
communityUuid: communities.firstOrNull ?? '',
|
||||||
spaceUuid: spaces.firstOrNull ?? '',
|
spaceUuid: spaces.firstOrNull ?? '',
|
||||||
);
|
);
|
||||||
return;
|
|
||||||
default:
|
default:
|
||||||
return;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -158,7 +157,6 @@ class AnalyticsPageTabsAndChildren extends StatelessWidget {
|
|||||||
required String communityUuid,
|
required String communityUuid,
|
||||||
required String spaceUuid,
|
required String spaceUuid,
|
||||||
}) {
|
}) {
|
||||||
if (spaceUuid.isEmpty) return;
|
|
||||||
FetchAirQualityDataHelper.loadAirQualityData(
|
FetchAirQualityDataHelper.loadAirQualityData(
|
||||||
context,
|
context,
|
||||||
date: date,
|
date: date,
|
||||||
|
@ -7,18 +7,16 @@ class ChartInformativeCell extends StatelessWidget {
|
|||||||
required this.title,
|
required this.title,
|
||||||
required this.color,
|
required this.color,
|
||||||
this.hasBorder = false,
|
this.hasBorder = false,
|
||||||
this.height,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
final Widget title;
|
final Widget title;
|
||||||
final Color color;
|
final Color color;
|
||||||
final bool hasBorder;
|
final bool hasBorder;
|
||||||
final double? height;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Container(
|
return Container(
|
||||||
height: height ?? MediaQuery.sizeOf(context).height * 0.0385,
|
height: MediaQuery.sizeOf(context).height * 0.0385,
|
||||||
padding: const EdgeInsetsDirectional.symmetric(
|
padding: const EdgeInsetsDirectional.symmetric(
|
||||||
vertical: 8,
|
vertical: 8,
|
||||||
horizontal: 12,
|
horizontal: 12,
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import 'package:fl_chart/fl_chart.dart';
|
import 'package:fl_chart/fl_chart.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/helpers/format_number_to_kwh.dart';
|
import 'package:syncrow_web/pages/analytics/helpers/format_number_to_kwh.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/widgets/charts_x_axis_title.dart';
|
|
||||||
import 'package:syncrow_web/utils/color_manager.dart';
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||||
|
|
||||||
@ -16,7 +15,6 @@ abstract final class EnergyManagementChartsHelper {
|
|||||||
return FlTitlesData(
|
return FlTitlesData(
|
||||||
show: true,
|
show: true,
|
||||||
bottomTitles: AxisTitles(
|
bottomTitles: AxisTitles(
|
||||||
axisNameWidget: const ChartsXAxisTitle(),
|
|
||||||
drawBelowEverything: true,
|
drawBelowEverything: true,
|
||||||
sideTitles: SideTitles(
|
sideTitles: SideTitles(
|
||||||
interval: 1,
|
interval: 1,
|
||||||
@ -40,7 +38,7 @@ abstract final class EnergyManagementChartsHelper {
|
|||||||
sideTitles: SideTitles(
|
sideTitles: SideTitles(
|
||||||
showTitles: true,
|
showTitles: true,
|
||||||
maxIncluded: false,
|
maxIncluded: false,
|
||||||
minIncluded: false,
|
minIncluded: true,
|
||||||
interval: leftTitlesInterval,
|
interval: leftTitlesInterval,
|
||||||
reservedSize: 110,
|
reservedSize: 110,
|
||||||
getTitlesWidget: (value, meta) => Padding(
|
getTitlesWidget: (value, meta) => Padding(
|
||||||
@ -64,12 +62,17 @@ abstract final class EnergyManagementChartsHelper {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static String getToolTipLabel(double value) => value.formatNumberToKwh;
|
static String getToolTipLabel(num month, double value) {
|
||||||
|
final monthLabel = month.toString();
|
||||||
|
final valueLabel = value.formatNumberToKwh;
|
||||||
|
final labels = [monthLabel, valueLabel];
|
||||||
|
return labels.where((element) => element.isNotEmpty).join(', ');
|
||||||
|
}
|
||||||
|
|
||||||
static List<LineTooltipItem?> getTooltipItems(List<LineBarSpot> touchedSpots) {
|
static List<LineTooltipItem?> getTooltipItems(List<LineBarSpot> touchedSpots) {
|
||||||
return touchedSpots.map((spot) {
|
return touchedSpots.map((spot) {
|
||||||
return LineTooltipItem(
|
return LineTooltipItem(
|
||||||
getToolTipLabel(spot.y),
|
getToolTipLabel(spot.x, spot.y),
|
||||||
const TextStyle(
|
const TextStyle(
|
||||||
color: ColorsManager.textPrimaryColor,
|
color: ColorsManager.textPrimaryColor,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
|
@ -46,7 +46,7 @@ class AnalyticsEnergyManagementView extends StatelessWidget {
|
|||||||
spacing: 32,
|
spacing: 32,
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
flex: 7,
|
flex: 2,
|
||||||
child: Column(
|
child: Column(
|
||||||
spacing: 20,
|
spacing: 20,
|
||||||
children: [
|
children: [
|
||||||
@ -55,7 +55,7 @@ class AnalyticsEnergyManagementView extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Expanded(flex: 4, child: PowerClampEnergyDataWidget()),
|
Expanded(child: PowerClampEnergyDataWidget()),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -27,30 +27,16 @@ class AnalyticsDeviceDropdown extends StatelessWidget {
|
|||||||
width: 1,
|
width: 1,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: Visibility(
|
|
||||||
visible: state.status != AnalyticsDevicesStatus.loading,
|
|
||||||
replacement: _buildLoadingIndicator(),
|
|
||||||
child: Visibility(
|
child: Visibility(
|
||||||
visible: state.devices.isNotEmpty,
|
visible: state.devices.isNotEmpty,
|
||||||
replacement: _buildNoDevicesFound(context),
|
replacement: _buildNoDevicesFound(context),
|
||||||
child: _buildDevicesDropdown(context, state),
|
child: _buildDevicesDropdown(context, state),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildLoadingIndicator() {
|
|
||||||
return const Center(
|
|
||||||
child: SizedBox(
|
|
||||||
width: 24,
|
|
||||||
height: 24,
|
|
||||||
child: CircularProgressIndicator(strokeWidth: 3),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static const _defaultPadding = EdgeInsetsDirectional.symmetric(
|
static const _defaultPadding = EdgeInsetsDirectional.symmetric(
|
||||||
horizontal: 20,
|
horizontal: 20,
|
||||||
vertical: 2,
|
vertical: 2,
|
||||||
|
@ -37,7 +37,7 @@ class EnergyConsumptionPerDeviceChartBox extends StatelessWidget {
|
|||||||
fit: BoxFit.scaleDown,
|
fit: BoxFit.scaleDown,
|
||||||
alignment: AlignmentDirectional.centerStart,
|
alignment: AlignmentDirectional.centerStart,
|
||||||
child: ChartTitle(
|
child: ChartTitle(
|
||||||
title: Text('Device energy consumed'),
|
title: Text('Energy Consumption per Device'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -14,17 +14,14 @@ class TotalEnergyConsumptionChart extends StatelessWidget {
|
|||||||
return Expanded(
|
return Expanded(
|
||||||
child: LineChart(
|
child: LineChart(
|
||||||
LineChartData(
|
LineChartData(
|
||||||
maxY: chartData.isEmpty
|
|
||||||
? null
|
|
||||||
: chartData.map((e) => e.value).reduce((a, b) => a > b ? a : b) + 250,
|
|
||||||
clipData: const FlClipData.vertical(),
|
clipData: const FlClipData.vertical(),
|
||||||
titlesData: EnergyManagementChartsHelper.titlesData(
|
titlesData: EnergyManagementChartsHelper.titlesData(
|
||||||
context,
|
context,
|
||||||
leftTitlesInterval: 500,
|
leftTitlesInterval: 250,
|
||||||
),
|
),
|
||||||
gridData: EnergyManagementChartsHelper.gridData().copyWith(
|
gridData: EnergyManagementChartsHelper.gridData().copyWith(
|
||||||
checkToShowHorizontalLine: (value) => true,
|
checkToShowHorizontalLine: (value) => true,
|
||||||
horizontalInterval: 500,
|
horizontalInterval: 250,
|
||||||
),
|
),
|
||||||
borderData: EnergyManagementChartsHelper.borderData(),
|
borderData: EnergyManagementChartsHelper.borderData(),
|
||||||
lineTouchData: EnergyManagementChartsHelper.lineTouchData(),
|
lineTouchData: EnergyManagementChartsHelper.lineTouchData(),
|
||||||
@ -32,6 +29,7 @@ class TotalEnergyConsumptionChart extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
duration: Duration.zero,
|
duration: Duration.zero,
|
||||||
curve: Curves.easeIn,
|
curve: Curves.easeIn,
|
||||||
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,7 @@ class TotalEnergyConsumptionChartBox extends StatelessWidget {
|
|||||||
child: FittedBox(
|
child: FittedBox(
|
||||||
alignment: AlignmentDirectional.centerStart,
|
alignment: AlignmentDirectional.centerStart,
|
||||||
fit: BoxFit.scaleDown,
|
fit: BoxFit.scaleDown,
|
||||||
child: ChartTitle(title: Text('Space energy consumed')),
|
child: ChartTitle(title: Text('Total Energy Consumption')),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const Spacer(flex: 4),
|
const Spacer(flex: 4),
|
||||||
|
@ -81,7 +81,7 @@ abstract final class FetchOccupancyDataHelper {
|
|||||||
param: GetAnalyticsDevicesParam(
|
param: GetAnalyticsDevicesParam(
|
||||||
communityUuid: communityUuid,
|
communityUuid: communityUuid,
|
||||||
spaceUuid: spaceUuid,
|
spaceUuid: spaceUuid,
|
||||||
deviceTypes: ['WPS', 'CPS', 'NCPS'],
|
deviceTypes: ['WPS', 'CPS'],
|
||||||
requestType: AnalyticsDeviceRequestType.occupancy,
|
requestType: AnalyticsDeviceRequestType.occupancy,
|
||||||
),
|
),
|
||||||
onSuccess: (device) {
|
onSuccess: (device) {
|
||||||
|
@ -20,7 +20,7 @@ class AnalyticsOccupancyView extends StatelessWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
spacing: 32,
|
spacing: 32,
|
||||||
children: [
|
children: [
|
||||||
SizedBox(height: height * 0.8, child: const OccupancyEndSideBar()),
|
SizedBox(height: height * 0.46, child: const OccupancyEndSideBar()),
|
||||||
SizedBox(height: height * 0.5, child: const OccupancyChartBox()),
|
SizedBox(height: height * 0.5, child: const OccupancyChartBox()),
|
||||||
SizedBox(height: height * 0.5, child: const OccupancyHeatMapBox()),
|
SizedBox(height: height * 0.5, child: const OccupancyHeatMapBox()),
|
||||||
],
|
],
|
||||||
@ -31,12 +31,12 @@ class AnalyticsOccupancyView extends StatelessWidget {
|
|||||||
return SingleChildScrollView(
|
return SingleChildScrollView(
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: _padding,
|
padding: _padding,
|
||||||
height: height * 1,
|
height: height * 0.9,
|
||||||
child: const Row(
|
child: const Row(
|
||||||
spacing: 32,
|
spacing: 32,
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
flex: 7,
|
flex: 5,
|
||||||
child: Column(
|
child: Column(
|
||||||
spacing: 20,
|
spacing: 20,
|
||||||
children: [
|
children: [
|
||||||
@ -45,7 +45,7 @@ class AnalyticsOccupancyView extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Expanded(flex: 4, child: OccupancyEndSideBar()),
|
Expanded(flex: 2, child: OccupancyEndSideBar()),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -39,7 +39,7 @@ class HeatMapTooltip extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const Divider(height: 2, thickness: 1),
|
const Divider(height: 2, thickness: 1),
|
||||||
Text(
|
Text(
|
||||||
'Occupancy detected: $value',
|
'$value Occupants',
|
||||||
style: context.textTheme.bodySmall?.copyWith(
|
style: context.textTheme.bodySmall?.copyWith(
|
||||||
fontSize: 10,
|
fontSize: 10,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
|
@ -52,7 +52,7 @@ class _InteractiveHeatMapState extends State<InteractiveHeatMap> {
|
|||||||
color: Colors.transparent,
|
color: Colors.transparent,
|
||||||
child: Transform.translate(
|
child: Transform.translate(
|
||||||
offset: Offset(-(widget.cellSize * 2.5), -50),
|
offset: Offset(-(widget.cellSize * 2.5), -50),
|
||||||
child: HeatMapTooltip(date: item.date.toUtc(), value: item.value),
|
child: HeatMapTooltip(date: item.date, value: item.value),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -2,7 +2,6 @@ import 'package:fl_chart/fl_chart.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/models/occupacy.dart';
|
import 'package:syncrow_web/pages/analytics/models/occupacy.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/helpers/energy_management_charts_helper.dart';
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/helpers/energy_management_charts_helper.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/widgets/charts_x_axis_title.dart';
|
|
||||||
import 'package:syncrow_web/utils/color_manager.dart';
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||||
|
|
||||||
@ -89,8 +88,8 @@ class OccupancyChart extends StatelessWidget {
|
|||||||
}) {
|
}) {
|
||||||
final data = chartData;
|
final data = chartData;
|
||||||
|
|
||||||
final occupancyValue = double.parse(data[group.x].occupancy);
|
final occupancyValue = double.parse(data[group.x.toInt()].occupancy);
|
||||||
final percentage = '${occupancyValue.toStringAsFixed(0)}%';
|
final percentage = '${(occupancyValue).toStringAsFixed(0)}%';
|
||||||
|
|
||||||
return BarTooltipItem(
|
return BarTooltipItem(
|
||||||
percentage,
|
percentage,
|
||||||
@ -117,7 +116,7 @@ class OccupancyChart extends StatelessWidget {
|
|||||||
alignment: AlignmentDirectional.centerStart,
|
alignment: AlignmentDirectional.centerStart,
|
||||||
fit: BoxFit.scaleDown,
|
fit: BoxFit.scaleDown,
|
||||||
child: Text(
|
child: Text(
|
||||||
'${value.toStringAsFixed(0)}%',
|
'${(value).toStringAsFixed(0)}%',
|
||||||
style: context.textTheme.bodySmall?.copyWith(
|
style: context.textTheme.bodySmall?.copyWith(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
color: ColorsManager.greyColor,
|
color: ColorsManager.greyColor,
|
||||||
@ -129,7 +128,6 @@ class OccupancyChart extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
|
|
||||||
final bottomTitles = AxisTitles(
|
final bottomTitles = AxisTitles(
|
||||||
axisNameWidget: const ChartsXAxisTitle(),
|
|
||||||
sideTitles: SideTitles(
|
sideTitles: SideTitles(
|
||||||
showTitles: true,
|
showTitles: true,
|
||||||
getTitlesWidget: (value, _) => FittedBox(
|
getTitlesWidget: (value, _) => FittedBox(
|
||||||
|
@ -23,9 +23,9 @@ class OccupancyEndSideBar extends StatelessWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
const AnalyticsSidebarHeader(title: 'Presence Sensor'),
|
const AnalyticsSidebarHeader(title: 'Presnce Sensor'),
|
||||||
Expanded(
|
SizedBox(
|
||||||
child: SizedBox(
|
height: MediaQuery.sizeOf(context).height * 0.2,
|
||||||
child: PowerClampEnergyStatusWidget(
|
child: PowerClampEnergyStatusWidget(
|
||||||
status: [
|
status: [
|
||||||
PowerClampEnergyStatus(
|
PowerClampEnergyStatus(
|
||||||
@ -54,14 +54,7 @@ class OccupancyEndSideBar extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
Expanded(
|
|
||||||
flex: 2,
|
|
||||||
child: FittedBox(
|
|
||||||
child: Image.asset(Assets.autocadOccupancyImage),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -9,13 +9,8 @@ import 'package:syncrow_web/pages/analytics/modules/occupancy/widgets/occupancy_
|
|||||||
import 'package:syncrow_web/utils/color_manager.dart';
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
|
||||||
class OccupancyHeatMap extends StatelessWidget {
|
class OccupancyHeatMap extends StatelessWidget {
|
||||||
const OccupancyHeatMap({
|
const OccupancyHeatMap({required this.heatMapData, super.key});
|
||||||
required this.heatMapData,
|
|
||||||
required this.selectedDate,
|
|
||||||
super.key,
|
|
||||||
});
|
|
||||||
final Map<DateTime, int> heatMapData;
|
final Map<DateTime, int> heatMapData;
|
||||||
final DateTime selectedDate;
|
|
||||||
|
|
||||||
static const _cellSize = 16.0;
|
static const _cellSize = 16.0;
|
||||||
static const _totalWeeks = 53;
|
static const _totalWeeks = 53;
|
||||||
@ -25,7 +20,7 @@ class OccupancyHeatMap extends StatelessWidget {
|
|||||||
: 0;
|
: 0;
|
||||||
|
|
||||||
DateTime _getStartingDate() {
|
DateTime _getStartingDate() {
|
||||||
final jan1 = DateTime.utc(selectedDate.year, 1, 1);
|
final jan1 = DateTime(DateTime.now().year, 1, 1);
|
||||||
final startOfWeek = jan1.subtract(Duration(days: jan1.weekday - 1));
|
final startOfWeek = jan1.subtract(Duration(days: jan1.weekday - 1));
|
||||||
return startOfWeek;
|
return startOfWeek;
|
||||||
}
|
}
|
||||||
|
@ -70,8 +70,6 @@ class OccupancyHeatMapBox extends StatelessWidget {
|
|||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: OccupancyHeatMap(
|
child: OccupancyHeatMap(
|
||||||
selectedDate:
|
|
||||||
context.watch<AnalyticsDatePickerBloc>().state.yearlyDate,
|
|
||||||
heatMapData: state.heatMapData.asMap().map(
|
heatMapData: state.heatMapData.asMap().map(
|
||||||
(_, value) => MapEntry(
|
(_, value) => MapEntry(
|
||||||
value.eventDate,
|
value.eventDate,
|
||||||
|
@ -34,8 +34,8 @@ class OccupancyHeatMapGradient extends StatelessWidget {
|
|||||||
width: 1,
|
width: 1,
|
||||||
),
|
),
|
||||||
gradient: LinearGradient(
|
gradient: LinearGradient(
|
||||||
begin: AlignmentDirectional.centerStart,
|
begin: AlignmentDirectional.centerEnd,
|
||||||
end: AlignmentDirectional.centerEnd,
|
end: AlignmentDirectional.centerStart,
|
||||||
colors: _heatMapColors(),
|
colors: _heatMapColors(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -28,11 +28,11 @@ class OccupancyPainter extends CustomPainter {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void paint(Canvas canvas, Size size) {
|
void paint(Canvas canvas, Size size) {
|
||||||
final fillPaint = Paint();
|
final Paint fillPaint = Paint();
|
||||||
final borderPaint = Paint()
|
final Paint borderPaint = Paint()
|
||||||
..color = ColorsManager.grayBorder.withValues(alpha: 0.4)
|
..color = ColorsManager.grayBorder.withValues(alpha: 0.4)
|
||||||
..style = PaintingStyle.stroke;
|
..style = PaintingStyle.stroke;
|
||||||
final hoveredBorderPaint = Paint()
|
final Paint hoveredBorderPaint = Paint()
|
||||||
..color = Colors.black
|
..color = Colors.black
|
||||||
..style = PaintingStyle.stroke
|
..style = PaintingStyle.stroke
|
||||||
..strokeWidth = 1.5;
|
..strokeWidth = 1.5;
|
||||||
@ -48,6 +48,7 @@ class OccupancyPainter extends CustomPainter {
|
|||||||
final rect = Rect.fromLTWH(x, y, cellSize, cellSize);
|
final rect = Rect.fromLTWH(x, y, cellSize, cellSize);
|
||||||
canvas.drawRect(rect, fillPaint);
|
canvas.drawRect(rect, fillPaint);
|
||||||
|
|
||||||
|
// Highlight the hovered item
|
||||||
if (hoveredItem != null && hoveredItem!.index == item.index) {
|
if (hoveredItem != null && hoveredItem!.index == item.index) {
|
||||||
canvas.drawRect(rect, hoveredBorderPaint);
|
canvas.drawRect(rect, hoveredBorderPaint);
|
||||||
} else {
|
} else {
|
||||||
@ -72,16 +73,16 @@ class OccupancyPainter extends CustomPainter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _drawDashedLine(Canvas canvas, Offset start, Offset end, Paint paint) {
|
void _drawDashedLine(Canvas canvas, Offset start, Offset end, Paint paint) {
|
||||||
const dashWidth = 2.0;
|
const double dashWidth = 2.0;
|
||||||
const dashSpace = 4.0;
|
const double dashSpace = 4.0;
|
||||||
final totalLength = (end - start).distance;
|
final double totalLength = (end - start).distance;
|
||||||
final direction = (end - start) / (end - start).distance;
|
final Offset direction = (end - start) / (end - start).distance;
|
||||||
|
|
||||||
var currentLength = 0.0;
|
double currentLength = 0.0;
|
||||||
while (currentLength < totalLength) {
|
while (currentLength < totalLength) {
|
||||||
final dashStart = start + direction * currentLength;
|
final Offset dashStart = start + direction * currentLength;
|
||||||
final nextLength = currentLength + dashWidth;
|
final double nextLength = currentLength + dashWidth;
|
||||||
final dashEnd =
|
final Offset dashEnd =
|
||||||
start + direction * (nextLength < totalLength ? nextLength : totalLength);
|
start + direction * (nextLength < totalLength ? nextLength : totalLength);
|
||||||
canvas.drawLine(dashStart, dashEnd, paint);
|
canvas.drawLine(dashStart, dashEnd, paint);
|
||||||
currentLength = nextLength + dashSpace;
|
currentLength = nextLength + dashSpace;
|
||||||
@ -90,9 +91,8 @@ class OccupancyPainter extends CustomPainter {
|
|||||||
|
|
||||||
Color _getColor(int value) {
|
Color _getColor(int value) {
|
||||||
if (maxValue == 0) return ColorsManager.vividBlue.withValues(alpha: 0);
|
if (maxValue == 0) return ColorsManager.vividBlue.withValues(alpha: 0);
|
||||||
final clampedValue = 0.075 + (1 * value.clamp(0, maxValue) / maxValue);
|
final opacity = value.clamp(0, maxValue) / maxValue;
|
||||||
final opacity = value == 0 ? 0 : clampedValue;
|
return ColorsManager.vividBlue.withValues(alpha: opacity);
|
||||||
return ColorsManager.vividBlue.withValues(alpha: opacity.toDouble());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -1,14 +1,9 @@
|
|||||||
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_type_dropdown.dart';
|
|
||||||
|
|
||||||
class GetAirQualityDistributionParam {
|
class GetAirQualityDistributionParam {
|
||||||
final DateTime date;
|
final DateTime date;
|
||||||
final String spaceUuid;
|
final String spaceUuid;
|
||||||
final AqiType aqiType;
|
|
||||||
|
|
||||||
const GetAirQualityDistributionParam(
|
const GetAirQualityDistributionParam({
|
||||||
{
|
|
||||||
required this.date,
|
required this.date,
|
||||||
required this.spaceUuid,
|
required this.spaceUuid,
|
||||||
required this.aqiType,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,95 @@
|
|||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:syncrow_web/pages/analytics/models/air_quality_data_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_type_dropdown.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/params/get_air_quality_distribution_param.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/services/air_quality_distribution/air_quality_distribution_service.dart';
|
||||||
|
|
||||||
|
class FakeAirQualityDistributionService implements AirQualityDistributionService {
|
||||||
|
final _random = Random();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<AirQualityDataModel>> getAirQualityDistribution(
|
||||||
|
GetAirQualityDistributionParam param,
|
||||||
|
) async {
|
||||||
|
return Future.delayed(
|
||||||
|
const Duration(milliseconds: 400),
|
||||||
|
() => List.generate(30, (index) {
|
||||||
|
final date = DateTime(2025, 5, 1).add(Duration(days: index));
|
||||||
|
|
||||||
|
final values = _generateRandomPercentages();
|
||||||
|
final nullMask = List.generate(6, (_) => _shouldBeNull());
|
||||||
|
|
||||||
|
if (nullMask.every((isNull) => isNull)) {
|
||||||
|
nullMask[_random.nextInt(6)] = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
final nonNullValues = _redistributePercentages(values, nullMask);
|
||||||
|
|
||||||
|
return AirQualityDataModel(
|
||||||
|
date: date,
|
||||||
|
data: [
|
||||||
|
AirQualityPercentageData(
|
||||||
|
type: AqiType.aqi.code,
|
||||||
|
percentage: nonNullValues[0],
|
||||||
|
name: 'good',
|
||||||
|
),
|
||||||
|
AirQualityPercentageData(
|
||||||
|
name: 'moderate',
|
||||||
|
type: AqiType.co2.code,
|
||||||
|
percentage: nonNullValues[1],
|
||||||
|
),
|
||||||
|
AirQualityPercentageData(
|
||||||
|
name: 'poor',
|
||||||
|
percentage: nonNullValues[2],
|
||||||
|
type: AqiType.hcho.code,
|
||||||
|
|
||||||
|
),
|
||||||
|
AirQualityPercentageData(
|
||||||
|
name: 'unhealthy',
|
||||||
|
percentage: nonNullValues[3],
|
||||||
|
type: AqiType.pm10.code,
|
||||||
|
),
|
||||||
|
AirQualityPercentageData(
|
||||||
|
name: 'severe',
|
||||||
|
type: AqiType.pm25.code,
|
||||||
|
percentage: nonNullValues[4],
|
||||||
|
),
|
||||||
|
AirQualityPercentageData(
|
||||||
|
name: 'hazardous',
|
||||||
|
percentage: nonNullValues[5],
|
||||||
|
type: AqiType.co2.code,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<double> _redistributePercentages(
|
||||||
|
List<double> originalValues,
|
||||||
|
List<bool> nullMask,
|
||||||
|
) {
|
||||||
|
double nonNullSum = 0;
|
||||||
|
for (int i = 0; i < originalValues.length; i++) {
|
||||||
|
if (!nullMask[i]) {
|
||||||
|
nonNullSum += originalValues[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return List.generate(originalValues.length, (i) {
|
||||||
|
if (nullMask[i]) return 0;
|
||||||
|
return (originalValues[i] / nonNullSum * 100).roundToDouble();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _shouldBeNull() => _random.nextDouble() < 0.6;
|
||||||
|
|
||||||
|
List<double> _generateRandomPercentages() {
|
||||||
|
final values = List.generate(6, (_) => _random.nextDouble());
|
||||||
|
|
||||||
|
final sum = values.reduce((a, b) => a + b);
|
||||||
|
|
||||||
|
return values.map((value) => (value / sum * 100).roundToDouble()).toList();
|
||||||
|
}
|
||||||
|
}
|
@ -3,8 +3,7 @@ import 'package:syncrow_web/pages/analytics/params/get_air_quality_distribution_
|
|||||||
import 'package:syncrow_web/pages/analytics/services/air_quality_distribution/air_quality_distribution_service.dart';
|
import 'package:syncrow_web/pages/analytics/services/air_quality_distribution/air_quality_distribution_service.dart';
|
||||||
import 'package:syncrow_web/services/api/http_service.dart';
|
import 'package:syncrow_web/services/api/http_service.dart';
|
||||||
|
|
||||||
final class RemoteAirQualityDistributionService
|
class RemoteAirQualityDistributionService implements AirQualityDistributionService {
|
||||||
implements AirQualityDistributionService {
|
|
||||||
RemoteAirQualityDistributionService(this._httpService);
|
RemoteAirQualityDistributionService(this._httpService);
|
||||||
|
|
||||||
final HTTPService _httpService;
|
final HTTPService _httpService;
|
||||||
@ -15,10 +14,10 @@ final class RemoteAirQualityDistributionService
|
|||||||
) async {
|
) async {
|
||||||
try {
|
try {
|
||||||
final response = await _httpService.get(
|
final response = await _httpService.get(
|
||||||
path: '/aqi/distribution/space/${param.spaceUuid}',
|
path: 'endpoint',
|
||||||
queryParameters: {
|
queryParameters: {
|
||||||
'monthDate': _formatDate(param.date),
|
'spaceUuid': param.spaceUuid,
|
||||||
'pollutantType': param.aqiType.code,
|
'date': param.date.toIso8601String(),
|
||||||
},
|
},
|
||||||
expectedResponseModel: (data) {
|
expectedResponseModel: (data) {
|
||||||
final json = data as Map<String, dynamic>? ?? {};
|
final json = data as Map<String, dynamic>? ?? {};
|
||||||
@ -34,8 +33,4 @@ final class RemoteAirQualityDistributionService
|
|||||||
throw Exception('Failed to load energy consumption per phase: $e');
|
throw Exception('Failed to load energy consumption per phase: $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static String _formatDate(DateTime date) {
|
|
||||||
return '${date.year}-${date.month.toString().padLeft(2, '0')}';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -26,15 +26,15 @@ class DeviceLocationDetailsServiceDecorator implements DeviceLocationService {
|
|||||||
if (data != null) {
|
if (data != null) {
|
||||||
final addressData = data['address'] as Map<String, dynamic>;
|
final addressData = data['address'] as Map<String, dynamic>;
|
||||||
return deviceLocationInfo.copyWith(
|
return deviceLocationInfo.copyWith(
|
||||||
city: addressData['city'] as String?,
|
city: addressData['city'],
|
||||||
country: addressData['country_code']?.toString().toUpperCase(),
|
country: addressData['country_code'].toString().toUpperCase(),
|
||||||
address: addressData['state'] as String?,
|
address: addressData['state'],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return deviceLocationInfo;
|
return deviceLocationInfo;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw Exception('Failed to load device location info: $e');
|
throw Exception('Failed to load device location info: ${e.toString()}');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,36 @@
|
|||||||
|
import 'package:syncrow_web/pages/analytics/models/range_of_aqi.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_type_dropdown.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/params/get_range_of_aqi_param.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/services/range_of_aqi/range_of_aqi_service.dart';
|
||||||
|
|
||||||
|
class FakeRangeOfAqiService implements RangeOfAqiService {
|
||||||
|
@override
|
||||||
|
Future<List<RangeOfAqi>> load(GetRangeOfAqiParam param) async {
|
||||||
|
return await Future.delayed(const Duration(milliseconds: 800), () {
|
||||||
|
final random = DateTime.now().millisecondsSinceEpoch;
|
||||||
|
|
||||||
|
return List.generate(30, (index) {
|
||||||
|
final date = DateTime(2025, 5, 1).add(Duration(days: index));
|
||||||
|
|
||||||
|
final min = ((random + index * 17) % 200).toDouble();
|
||||||
|
final avgDelta = ((random + index * 23) % 50).toDouble() + 20;
|
||||||
|
final maxDelta = ((random + index * 31) % 50).toDouble() + 30;
|
||||||
|
|
||||||
|
final avg = (min + avgDelta).clamp(0.0, 301.0);
|
||||||
|
final max = (avg + maxDelta).clamp(0.0, 301.0);
|
||||||
|
|
||||||
|
return RangeOfAqi(
|
||||||
|
data: [
|
||||||
|
RangeOfAqiValue(type: AqiType.aqi.code, min: min, average: avg, max: max),
|
||||||
|
RangeOfAqiValue(type: AqiType.pm25.code, min: min, average: avg, max: max),
|
||||||
|
RangeOfAqiValue(type: AqiType.pm10.code, min: min, average: avg, max: max),
|
||||||
|
RangeOfAqiValue(type: AqiType.hcho.code, min: min, average: avg, max: max),
|
||||||
|
RangeOfAqiValue(type: AqiType.tvoc.code, min: min, average: avg, max: max),
|
||||||
|
RangeOfAqiValue(type: AqiType.co2.code, min: min, average: avg, max: max),
|
||||||
|
],
|
||||||
|
date: date,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -12,8 +12,11 @@ final class RemoteRangeOfAqiService implements RangeOfAqiService {
|
|||||||
Future<List<RangeOfAqi>> load(GetRangeOfAqiParam param) async {
|
Future<List<RangeOfAqi>> load(GetRangeOfAqiParam param) async {
|
||||||
try {
|
try {
|
||||||
final response = await _httpService.get(
|
final response = await _httpService.get(
|
||||||
path: '/aqi/range/space/${param.spaceUuid}',
|
path: 'endpoint',
|
||||||
queryParameters: {'monthDate': _formatDate(param.date)},
|
queryParameters: {
|
||||||
|
'spaceUuid': param.spaceUuid,
|
||||||
|
'date': param.date.toIso8601String(),
|
||||||
|
},
|
||||||
expectedResponseModel: (data) {
|
expectedResponseModel: (data) {
|
||||||
final json = data as Map<String, dynamic>? ?? {};
|
final json = data as Map<String, dynamic>? ?? {};
|
||||||
final mappedData = json['data'] as List<dynamic>? ?? [];
|
final mappedData = json['data'] as List<dynamic>? ?? [];
|
||||||
@ -25,11 +28,7 @@ final class RemoteRangeOfAqiService implements RangeOfAqiService {
|
|||||||
);
|
);
|
||||||
return response;
|
return response;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw Exception('Failed to load range of aqi: $e');
|
throw Exception('Failed to load energy consumption per phase: $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static String _formatDate(DateTime date) {
|
|
||||||
return '${date.year}-${date.month.toString().padLeft(2, '0')}';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,23 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:syncrow_web/utils/color_manager.dart';
|
|
||||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
|
||||||
|
|
||||||
class ChartsXAxisTitle extends StatelessWidget {
|
|
||||||
const ChartsXAxisTitle({
|
|
||||||
this.label = 'Day of month',
|
|
||||||
super.key,
|
|
||||||
});
|
|
||||||
|
|
||||||
final String label;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Text(
|
|
||||||
label,
|
|
||||||
style: context.textTheme.bodySmall?.copyWith(
|
|
||||||
color: ColorsManager.lightGreyColor,
|
|
||||||
fontSize: 8,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -36,8 +36,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
|||||||
|
|
||||||
////////////////////////////// forget password //////////////////////////////////
|
////////////////////////////// forget password //////////////////////////////////
|
||||||
final TextEditingController forgetEmailController = TextEditingController();
|
final TextEditingController forgetEmailController = TextEditingController();
|
||||||
final TextEditingController forgetPasswordController =
|
final TextEditingController forgetPasswordController = TextEditingController();
|
||||||
TextEditingController();
|
|
||||||
final TextEditingController forgetOtp = TextEditingController();
|
final TextEditingController forgetOtp = TextEditingController();
|
||||||
final forgetFormKey = GlobalKey<FormState>();
|
final forgetFormKey = GlobalKey<FormState>();
|
||||||
final forgetEmailKey = GlobalKey<FormState>();
|
final forgetEmailKey = GlobalKey<FormState>();
|
||||||
@ -54,8 +53,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_remainingTime = 1;
|
_remainingTime = 1;
|
||||||
add(UpdateTimerEvent(
|
add(UpdateTimerEvent(remainingTime: _remainingTime, isButtonEnabled: false));
|
||||||
remainingTime: _remainingTime, isButtonEnabled: false));
|
|
||||||
try {
|
try {
|
||||||
forgetEmailValidate = '';
|
forgetEmailValidate = '';
|
||||||
_remainingTime = (await AuthenticationAPI.sendOtp(
|
_remainingTime = (await AuthenticationAPI.sendOtp(
|
||||||
@ -92,8 +90,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
|||||||
_timer?.cancel();
|
_timer?.cancel();
|
||||||
add(const UpdateTimerEvent(remainingTime: 0, isButtonEnabled: true));
|
add(const UpdateTimerEvent(remainingTime: 0, isButtonEnabled: true));
|
||||||
} else {
|
} else {
|
||||||
add(UpdateTimerEvent(
|
add(UpdateTimerEvent(remainingTime: _remainingTime, isButtonEnabled: false));
|
||||||
remainingTime: _remainingTime, isButtonEnabled: false));
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -125,6 +122,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
String? validateCode(String? value) {
|
String? validateCode(String? value) {
|
||||||
if (value == null || value.isEmpty) {
|
if (value == null || value.isEmpty) {
|
||||||
return 'Code is required';
|
return 'Code is required';
|
||||||
@ -133,9 +131,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _onUpdateTimer(UpdateTimerEvent event, Emitter<AuthState> emit) {
|
void _onUpdateTimer(UpdateTimerEvent event, Emitter<AuthState> emit) {
|
||||||
emit(TimerState(
|
emit(TimerState(isButtonEnabled: event.isButtonEnabled, remainingTime: event.remainingTime));
|
||||||
isButtonEnabled: event.isButtonEnabled,
|
|
||||||
remainingTime: event.remainingTime));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////// login /////////////////////////////////////
|
///////////////////////////////////// login /////////////////////////////////////
|
||||||
@ -155,6 +151,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
|||||||
static UserModel? user;
|
static UserModel? user;
|
||||||
bool showValidationMessage = false;
|
bool showValidationMessage = false;
|
||||||
|
|
||||||
|
|
||||||
void _login(LoginButtonPressed event, Emitter<AuthState> emit) async {
|
void _login(LoginButtonPressed event, Emitter<AuthState> emit) async {
|
||||||
emit(AuthLoading());
|
emit(AuthLoading());
|
||||||
if (isChecked) {
|
if (isChecked) {
|
||||||
@ -173,11 +170,11 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
|||||||
);
|
);
|
||||||
} on APIException catch (e) {
|
} on APIException catch (e) {
|
||||||
validate = e.message;
|
validate = e.message;
|
||||||
emit(LoginFailure(error: validate));
|
emit(LoginInitial());
|
||||||
return;
|
return;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
validate = 'Something went wrong';
|
validate = 'Something went wrong';
|
||||||
emit(LoginFailure(error: validate));
|
emit(LoginInitial());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -200,6 +197,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
checkBoxToggle(
|
checkBoxToggle(
|
||||||
CheckBoxEvent event,
|
CheckBoxEvent event,
|
||||||
Emitter<AuthState> emit,
|
Emitter<AuthState> emit,
|
||||||
@ -341,14 +339,12 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
|||||||
static Future<String> getTokenAndValidate() async {
|
static Future<String> getTokenAndValidate() async {
|
||||||
try {
|
try {
|
||||||
const storage = FlutterSecureStorage();
|
const storage = FlutterSecureStorage();
|
||||||
final firstLaunch = await SharedPreferencesHelper.readBoolFromSP(
|
final firstLaunch =
|
||||||
StringsManager.firstLaunch) ??
|
await SharedPreferencesHelper.readBoolFromSP(StringsManager.firstLaunch) ?? true;
|
||||||
true;
|
|
||||||
if (firstLaunch) {
|
if (firstLaunch) {
|
||||||
storage.deleteAll();
|
storage.deleteAll();
|
||||||
}
|
}
|
||||||
await SharedPreferencesHelper.saveBoolToSP(
|
await SharedPreferencesHelper.saveBoolToSP(StringsManager.firstLaunch, false);
|
||||||
StringsManager.firstLaunch, false);
|
|
||||||
final value = await storage.read(key: Token.loginAccessTokenKey) ?? '';
|
final value = await storage.read(key: Token.loginAccessTokenKey) ?? '';
|
||||||
if (value.isEmpty) {
|
if (value.isEmpty) {
|
||||||
return 'Token not found';
|
return 'Token not found';
|
||||||
@ -401,9 +397,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
|||||||
final String formattedTime = [
|
final String formattedTime = [
|
||||||
if (days > 0) '${days}d', // Append 'd' for days
|
if (days > 0) '${days}d', // Append 'd' for days
|
||||||
if (days > 0 || hours > 0)
|
if (days > 0 || hours > 0)
|
||||||
hours
|
hours.toString().padLeft(2, '0'), // Show hours if there are days or hours
|
||||||
.toString()
|
|
||||||
.padLeft(2, '0'), // Show hours if there are days or hours
|
|
||||||
minutes.toString().padLeft(2, '0'),
|
minutes.toString().padLeft(2, '0'),
|
||||||
seconds.toString().padLeft(2, '0'),
|
seconds.toString().padLeft(2, '0'),
|
||||||
].join(':');
|
].join(':');
|
||||||
|
@ -50,11 +50,20 @@ class _DynamicTableState extends State<DynamicTable> {
|
|||||||
bool _selectAll = false;
|
bool _selectAll = false;
|
||||||
final ScrollController _verticalScrollController = ScrollController();
|
final ScrollController _verticalScrollController = ScrollController();
|
||||||
final ScrollController _horizontalScrollController = ScrollController();
|
final ScrollController _horizontalScrollController = ScrollController();
|
||||||
|
late ScrollController _horizontalHeaderScrollController;
|
||||||
|
late ScrollController _horizontalBodyScrollController;
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_initializeSelection();
|
_initializeSelection();
|
||||||
|
_horizontalHeaderScrollController = ScrollController();
|
||||||
|
_horizontalBodyScrollController = ScrollController();
|
||||||
|
|
||||||
|
// Synchronize horizontal scrolling
|
||||||
|
_horizontalBodyScrollController.addListener(() {
|
||||||
|
_horizontalHeaderScrollController
|
||||||
|
.jumpTo(_horizontalBodyScrollController.offset);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -104,58 +113,70 @@ class _DynamicTableState extends State<DynamicTable> {
|
|||||||
context.read<DeviceManagementBloc>().add(UpdateSelection(_selectedRows));
|
context.read<DeviceManagementBloc>().add(UpdateSelection(_selectedRows));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_horizontalHeaderScrollController.dispose();
|
||||||
|
_horizontalBodyScrollController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Container(
|
return Container(
|
||||||
decoration: widget.cellDecoration,
|
decoration: widget.cellDecoration,
|
||||||
child: Scrollbar(
|
|
||||||
controller: _verticalScrollController,
|
|
||||||
thumbVisibility: true,
|
|
||||||
trackVisibility: true,
|
|
||||||
child: Scrollbar(
|
|
||||||
//fixed the horizontal scrollbar issue
|
|
||||||
controller: _horizontalScrollController,
|
|
||||||
thumbVisibility: true,
|
|
||||||
trackVisibility: true,
|
|
||||||
notificationPredicate: (notif) => notif.depth == 1,
|
|
||||||
child: SingleChildScrollView(
|
|
||||||
controller: _verticalScrollController,
|
|
||||||
child: SingleChildScrollView(
|
|
||||||
controller: _horizontalScrollController,
|
|
||||||
scrollDirection: Axis.horizontal,
|
|
||||||
child: SizedBox(
|
|
||||||
width: widget.size.width,
|
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Container(
|
Container(
|
||||||
decoration: widget.headerDecoration ??
|
decoration: widget.headerDecoration ??
|
||||||
const BoxDecoration(
|
const BoxDecoration(color: ColorsManager.boxColor),
|
||||||
color: ColorsManager.boxColor,
|
child: SingleChildScrollView(
|
||||||
),
|
scrollDirection: Axis.horizontal,
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
controller: _horizontalHeaderScrollController,
|
||||||
|
child: SizedBox(
|
||||||
|
width: widget.size.width,
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
if (widget.withCheckBox) _buildSelectAllCheckbox(),
|
if (widget.withCheckBox) _buildSelectAllCheckbox(),
|
||||||
...List.generate(widget.headers.length, (index) {
|
...List.generate(widget.headers.length, (index) {
|
||||||
return _buildTableHeaderCell(
|
return _buildTableHeaderCell(
|
||||||
widget.headers[index], index);
|
widget.headers[index], index);
|
||||||
})
|
}),
|
||||||
//...widget.headers.map((header) => _buildTableHeaderCell(header)),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Scrollbar(
|
||||||
|
controller: _verticalScrollController,
|
||||||
|
thumbVisibility: true,
|
||||||
|
trackVisibility: true,
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
controller: _verticalScrollController,
|
||||||
|
child: Scrollbar(
|
||||||
|
controller: _horizontalBodyScrollController,
|
||||||
|
thumbVisibility: false,
|
||||||
|
trackVisibility: false,
|
||||||
|
notificationPredicate: (notif) => notif.depth == 1,
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
controller: _horizontalBodyScrollController,
|
||||||
|
child: Container(
|
||||||
|
color: ColorsManager.whiteColors,
|
||||||
|
child: SizedBox(
|
||||||
width: widget.size.width,
|
width: widget.size.width,
|
||||||
child: widget.isEmpty
|
child: widget.isEmpty
|
||||||
? _buildEmptyState()
|
? _buildEmptyState()
|
||||||
: Column(
|
: Column(
|
||||||
children:
|
children: List.generate(widget.data.length,
|
||||||
List.generate(widget.data.length, (rowIndex) {
|
(rowIndex) {
|
||||||
final row = widget.data[rowIndex];
|
final row = widget.data[rowIndex];
|
||||||
return Row(
|
return Row(
|
||||||
children: [
|
children: [
|
||||||
if (widget.withCheckBox)
|
if (widget.withCheckBox)
|
||||||
_buildRowCheckbox(
|
_buildRowCheckbox(rowIndex,
|
||||||
rowIndex, widget.size.height * 0.08),
|
widget.size.height * 0.08),
|
||||||
...row.asMap().entries.map((entry) {
|
...row.asMap().entries.map((entry) {
|
||||||
return _buildTableCell(
|
return _buildTableCell(
|
||||||
entry.value.toString(),
|
entry.value.toString(),
|
||||||
@ -169,20 +190,35 @@ class _DynamicTableState extends State<DynamicTable> {
|
|||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildEmptyState() => Container(
|
Widget _buildSelectAllCheckbox() {
|
||||||
height: widget.size.height,
|
return Container(
|
||||||
color: ColorsManager.whiteColors,
|
width: 50,
|
||||||
child: Column(
|
decoration: const BoxDecoration(
|
||||||
|
border: Border.symmetric(
|
||||||
|
vertical: BorderSide(color: ColorsManager.boxDivider),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Checkbox(
|
||||||
|
value: _selectAll,
|
||||||
|
onChanged: widget.withSelectAll && widget.data.isNotEmpty
|
||||||
|
? _toggleSelectAll
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildEmptyState() => Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
@ -206,27 +242,8 @@ class _DynamicTableState extends State<DynamicTable> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
SizedBox(height: widget.size.height * 0.5),
|
|
||||||
],
|
],
|
||||||
),
|
|
||||||
);
|
);
|
||||||
Widget _buildSelectAllCheckbox() {
|
|
||||||
return Container(
|
|
||||||
width: 50,
|
|
||||||
decoration: const BoxDecoration(
|
|
||||||
border: Border.symmetric(
|
|
||||||
vertical: BorderSide(color: ColorsManager.boxDivider),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Checkbox(
|
|
||||||
value: _selectAll,
|
|
||||||
onChanged: widget.withSelectAll && widget.data.isNotEmpty
|
|
||||||
? _toggleSelectAll
|
|
||||||
: null,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildRowCheckbox(int index, double size) {
|
Widget _buildRowCheckbox(int index, double size) {
|
||||||
return Container(
|
return Container(
|
||||||
width: 50,
|
width: 50,
|
||||||
@ -281,8 +298,12 @@ class _DynamicTableState extends State<DynamicTable> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildTableCell(String content, double size,
|
Widget _buildTableCell(
|
||||||
{required int rowIndex, required int columnIndex}) {
|
String content,
|
||||||
|
double size, {
|
||||||
|
required int rowIndex,
|
||||||
|
required int columnIndex,
|
||||||
|
}) {
|
||||||
bool isBatteryLevel = content.endsWith('%');
|
bool isBatteryLevel = content.endsWith('%');
|
||||||
double? batteryLevel;
|
double? batteryLevel;
|
||||||
|
|
||||||
@ -290,6 +311,7 @@ class _DynamicTableState extends State<DynamicTable> {
|
|||||||
batteryLevel = double.tryParse(content.replaceAll('%', '').trim());
|
batteryLevel = double.tryParse(content.replaceAll('%', '').trim());
|
||||||
}
|
}
|
||||||
bool isSettingsColumn = widget.headers[columnIndex] == 'Settings';
|
bool isSettingsColumn = widget.headers[columnIndex] == 'Settings';
|
||||||
|
|
||||||
if (isSettingsColumn) {
|
if (isSettingsColumn) {
|
||||||
return buildSettingsIcon(
|
return buildSettingsIcon(
|
||||||
width: 120,
|
width: 120,
|
||||||
@ -394,10 +416,11 @@ class _DynamicTableState extends State<DynamicTable> {
|
|||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.all(8.0),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: SvgPicture.asset(
|
child: SvgPicture.asset(
|
||||||
Assets.settings,
|
Assets.settings, // ضع المسار الصحيح هنا
|
||||||
width: 40,
|
width: 40,
|
||||||
height: 22,
|
height: 22,
|
||||||
color: ColorsManager.primaryColor,
|
color: ColorsManager
|
||||||
|
.primaryColor, // نفس لون الأيقونة في الصورة
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -68,30 +68,24 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
StreamSubscription<DatabaseEvent>? _deviceStatusSubscription;
|
void _listenToChanges(deviceId) {
|
||||||
|
|
||||||
void _listenToChanges(String deviceId) {
|
|
||||||
try {
|
try {
|
||||||
final ref = FirebaseDatabase.instance.ref('device-status/$deviceId');
|
final ref = FirebaseDatabase.instance.ref('device-status/$deviceId');
|
||||||
_deviceStatusSubscription = ref.onValue.listen((DatabaseEvent event) async {
|
final stream = ref.onValue;
|
||||||
|
|
||||||
|
stream.listen((DatabaseEvent event) async {
|
||||||
if (event.snapshot.value == null) return;
|
if (event.snapshot.value == null) return;
|
||||||
|
|
||||||
final usersMap = event.snapshot.value! as Map<dynamic, dynamic>;
|
Map<dynamic, dynamic> usersMap =
|
||||||
|
event.snapshot.value as Map<dynamic, dynamic>;
|
||||||
|
|
||||||
final statusList = <Status>[];
|
List<Status> statusList = [];
|
||||||
|
|
||||||
usersMap['status'].forEach((element) {
|
usersMap['status'].forEach((element) {
|
||||||
statusList.add(Status(code: element['code'], value: element['value']));
|
statusList.add(Status(code: element['code'], value: element['value']));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
deviceStatus =
|
|
||||||
AcStatusModel.fromJson(usersMap['productUuid'], statusList);
|
|
||||||
|
|
||||||
deviceStatus = AcStatusModel.fromJson(usersMap['productUuid'], statusList);
|
deviceStatus = AcStatusModel.fromJson(usersMap['productUuid'], statusList);
|
||||||
print('Device status updated: ${deviceStatus.acSwitch}');
|
|
||||||
|
|
||||||
|
|
||||||
if (!isClosed) {
|
if (!isClosed) {
|
||||||
add(AcStatusUpdated(deviceStatus));
|
add(AcStatusUpdated(deviceStatus));
|
||||||
}
|
}
|
||||||
@ -111,14 +105,22 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
|
|||||||
AcControlEvent event,
|
AcControlEvent event,
|
||||||
Emitter<AcsState> emit,
|
Emitter<AcsState> emit,
|
||||||
) async {
|
) async {
|
||||||
try {
|
emit(AcsLoadingState());
|
||||||
_updateDeviceFunctionFromCode(event.code, event.value);
|
_updateDeviceFunctionFromCode(event.code, event.value);
|
||||||
emit(ACStatusLoaded(status: deviceStatus));
|
emit(ACStatusLoaded(status: deviceStatus));
|
||||||
await controlDeviceService.controlDevice(
|
|
||||||
|
try {
|
||||||
|
final success = await controlDeviceService.controlDevice(
|
||||||
deviceUuid: event.deviceId,
|
deviceUuid: event.deviceId,
|
||||||
status: Status(code: event.code, value: event.value),
|
status: Status(code: event.code, value: event.value),
|
||||||
);
|
);
|
||||||
} catch (e) {}
|
|
||||||
|
if (!success) {
|
||||||
|
emit(const AcsFailedState(error: 'Failed to control device'));
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
emit(AcsFailedState(error: e.toString()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FutureOr<void> _onFetchAcBatchStatus(
|
FutureOr<void> _onFetchAcBatchStatus(
|
||||||
@ -139,16 +141,23 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
|
|||||||
AcBatchControlEvent event,
|
AcBatchControlEvent event,
|
||||||
Emitter<AcsState> emit,
|
Emitter<AcsState> emit,
|
||||||
) async {
|
) async {
|
||||||
|
emit(AcsLoadingState());
|
||||||
_updateDeviceFunctionFromCode(event.code, event.value);
|
_updateDeviceFunctionFromCode(event.code, event.value);
|
||||||
emit(ACStatusLoaded(status: deviceStatus));
|
emit(ACStatusLoaded(status: deviceStatus));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await batchControlDevicesService.batchControlDevices(
|
final success = await batchControlDevicesService.batchControlDevices(
|
||||||
uuids: event.devicesIds,
|
uuids: event.devicesIds,
|
||||||
code: event.code,
|
code: event.code,
|
||||||
value: event.value,
|
value: event.value,
|
||||||
);
|
);
|
||||||
} catch (e) {}
|
|
||||||
|
if (!success) {
|
||||||
|
emit(const AcsFailedState(error: 'Failed to control devices'));
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
emit(AcsFailedState(error: e.toString()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onFactoryReset(
|
Future<void> _onFactoryReset(
|
||||||
@ -181,8 +190,8 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
|
|||||||
void _handleIncreaseTime(IncreaseTimeEvent event, Emitter<AcsState> emit) {
|
void _handleIncreaseTime(IncreaseTimeEvent event, Emitter<AcsState> emit) {
|
||||||
if (state is! ACStatusLoaded) return;
|
if (state is! ACStatusLoaded) return;
|
||||||
final currentState = state as ACStatusLoaded;
|
final currentState = state as ACStatusLoaded;
|
||||||
var newHours = scheduledHours;
|
int newHours = scheduledHours;
|
||||||
var newMinutes = scheduledMinutes + 30;
|
int newMinutes = scheduledMinutes + 30;
|
||||||
newHours += newMinutes ~/ 60;
|
newHours += newMinutes ~/ 60;
|
||||||
newMinutes = newMinutes % 60;
|
newMinutes = newMinutes % 60;
|
||||||
if (newHours > 23) {
|
if (newHours > 23) {
|
||||||
@ -204,7 +213,7 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
|
|||||||
) {
|
) {
|
||||||
if (state is! ACStatusLoaded) return;
|
if (state is! ACStatusLoaded) return;
|
||||||
final currentState = state as ACStatusLoaded;
|
final currentState = state as ACStatusLoaded;
|
||||||
var totalMinutes = (scheduledHours * 60) + scheduledMinutes;
|
int totalMinutes = (scheduledHours * 60) + scheduledMinutes;
|
||||||
totalMinutes = (totalMinutes - 30).clamp(0, 1440);
|
totalMinutes = (totalMinutes - 30).clamp(0, 1440);
|
||||||
scheduledHours = totalMinutes ~/ 60;
|
scheduledHours = totalMinutes ~/ 60;
|
||||||
scheduledMinutes = totalMinutes % 60;
|
scheduledMinutes = totalMinutes % 60;
|
||||||
@ -277,25 +286,21 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
|
|||||||
|
|
||||||
void _startCountdownTimer(Emitter<AcsState> emit) {
|
void _startCountdownTimer(Emitter<AcsState> emit) {
|
||||||
_countdownTimer?.cancel();
|
_countdownTimer?.cancel();
|
||||||
var totalSeconds = (scheduledHours * 3600) + (scheduledMinutes * 60);
|
int totalSeconds = (scheduledHours * 3600) + (scheduledMinutes * 60);
|
||||||
|
|
||||||
_countdownTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
|
_countdownTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
|
||||||
if (totalSeconds > 0) {
|
if (totalSeconds > 0) {
|
||||||
totalSeconds--;
|
totalSeconds--;
|
||||||
scheduledHours = totalSeconds ~/ 3600;
|
scheduledHours = totalSeconds ~/ 3600;
|
||||||
scheduledMinutes = (totalSeconds % 3600) ~/ 60;
|
scheduledMinutes = (totalSeconds % 3600) ~/ 60;
|
||||||
if (!isClosed) {
|
|
||||||
add(UpdateTimerEvent());
|
add(UpdateTimerEvent());
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
_countdownTimer?.cancel();
|
_countdownTimer?.cancel();
|
||||||
timerActive = false;
|
timerActive = false;
|
||||||
scheduledHours = 0;
|
scheduledHours = 0;
|
||||||
scheduledMinutes = 0;
|
scheduledMinutes = 0;
|
||||||
if (!isClosed) {
|
|
||||||
add(TimerCompletedEvent());
|
add(TimerCompletedEvent());
|
||||||
}
|
}
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -321,11 +326,9 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
|
|||||||
_startCountdownTimer(
|
_startCountdownTimer(
|
||||||
emit,
|
emit,
|
||||||
);
|
);
|
||||||
if (!isClosed) {
|
|
||||||
add(UpdateTimerEvent());
|
add(UpdateTimerEvent());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
void _updateDeviceFunctionFromCode(String code, dynamic value) {
|
void _updateDeviceFunctionFromCode(String code, dynamic value) {
|
||||||
switch (code) {
|
switch (code) {
|
||||||
@ -367,8 +370,6 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
|
|||||||
@override
|
@override
|
||||||
Future<void> close() {
|
Future<void> close() {
|
||||||
add(OnClose());
|
add(OnClose());
|
||||||
_countdownTimer?.cancel();
|
|
||||||
_deviceStatusSubscription?.cancel();
|
|
||||||
return super.close();
|
return super.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,12 +16,11 @@ class DeviceManagementBloc
|
|||||||
int _onlineCount = 0;
|
int _onlineCount = 0;
|
||||||
int _offlineCount = 0;
|
int _offlineCount = 0;
|
||||||
int _lowBatteryCount = 0;
|
int _lowBatteryCount = 0;
|
||||||
final List<AllDevicesModel> _selectedDevices = [];
|
List<AllDevicesModel> _selectedDevices = [];
|
||||||
List<AllDevicesModel> _filteredDevices = [];
|
List<AllDevicesModel> _filteredDevices = [];
|
||||||
String currentProductName = '';
|
String currentProductName = '';
|
||||||
String? currentCommunity;
|
String? currentCommunity;
|
||||||
String? currentUnitName;
|
String? currentUnitName;
|
||||||
String subSpaceName = '';
|
|
||||||
|
|
||||||
DeviceManagementBloc() : super(DeviceManagementInitial()) {
|
DeviceManagementBloc() : super(DeviceManagementInitial()) {
|
||||||
on<FetchDevices>(_onFetchDevices);
|
on<FetchDevices>(_onFetchDevices);
|
||||||
@ -32,28 +31,26 @@ class DeviceManagementBloc
|
|||||||
on<ResetFilters>(_onResetFilters);
|
on<ResetFilters>(_onResetFilters);
|
||||||
on<ResetSelectedDevices>(_onResetSelectedDevices);
|
on<ResetSelectedDevices>(_onResetSelectedDevices);
|
||||||
on<UpdateSelection>(_onUpdateSelection);
|
on<UpdateSelection>(_onUpdateSelection);
|
||||||
on<UpdateDeviceName>(_onUpdateDeviceName);
|
|
||||||
on<UpdateSubSpaceName>(_onUpdateSubSpaceName);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onFetchDevices(
|
Future<void> _onFetchDevices(
|
||||||
FetchDevices event, Emitter<DeviceManagementState> emit) async {
|
FetchDevices event, Emitter<DeviceManagementState> emit) async {
|
||||||
emit(DeviceManagementLoading());
|
emit(DeviceManagementLoading());
|
||||||
try {
|
try {
|
||||||
var devices = <AllDevicesModel>[];
|
List<AllDevicesModel> devices = [];
|
||||||
_devices.clear();
|
_devices.clear();
|
||||||
final spaceBloc = event.context.read<SpaceTreeBloc>();
|
var spaceBloc = event.context.read<SpaceTreeBloc>();
|
||||||
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
|
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
|
||||||
|
|
||||||
if (spaceBloc.state.selectedCommunities.isEmpty) {
|
if (spaceBloc.state.selectedCommunities.isEmpty) {
|
||||||
devices = await DevicesManagementApi().fetchDevices('', '', projectUuid);
|
devices = await DevicesManagementApi().fetchDevices('', '', projectUuid);
|
||||||
} else {
|
} else {
|
||||||
for (final community in spaceBloc.state.selectedCommunities) {
|
for (var community in spaceBloc.state.selectedCommunities) {
|
||||||
final spacesList =
|
List<String> spacesList =
|
||||||
spaceBloc.state.selectedCommunityAndSpaces[community] ?? [];
|
spaceBloc.state.selectedCommunityAndSpaces[community] ?? [];
|
||||||
for (final space in spacesList) {
|
for (var space in spacesList) {
|
||||||
devices.addAll(await DevicesManagementApi()
|
devices.addAll(await DevicesManagementApi().fetchDevices(
|
||||||
.fetchDevices(community, space, projectUuid));
|
community, space, projectUuid));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -76,7 +73,7 @@ class DeviceManagementBloc
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onFilterDevices(
|
void _onFilterDevices(
|
||||||
FilterDevices event, Emitter<DeviceManagementState> emit) async {
|
FilterDevices event, Emitter<DeviceManagementState> emit) async {
|
||||||
if (_devices.isNotEmpty) {
|
if (_devices.isNotEmpty) {
|
||||||
_filteredDevices = List.from(_devices.where((device) {
|
_filteredDevices = List.from(_devices.where((device) {
|
||||||
@ -103,7 +100,7 @@ class DeviceManagementBloc
|
|||||||
));
|
));
|
||||||
|
|
||||||
if (currentProductName.isNotEmpty) {
|
if (currentProductName.isNotEmpty) {
|
||||||
add(SearchDevices(deviceNameOrProductName: currentProductName));
|
add(SearchDevices(productName: currentProductName));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -158,7 +155,8 @@ class DeviceManagementBloc
|
|||||||
add(FilterDevices(_getFilterFromIndex(_selectedIndex)));
|
add(FilterDevices(_getFilterFromIndex(_selectedIndex)));
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onSelectDevice(SelectDevice event, Emitter<DeviceManagementState> emit) {
|
void _onSelectDevice(
|
||||||
|
SelectDevice event, Emitter<DeviceManagementState> emit) {
|
||||||
final selectedUuid = event.selectedDevice.uuid;
|
final selectedUuid = event.selectedDevice.uuid;
|
||||||
|
|
||||||
if (_selectedDevices.any((device) => device.uuid == selectedUuid)) {
|
if (_selectedDevices.any((device) => device.uuid == selectedUuid)) {
|
||||||
@ -167,9 +165,9 @@ class DeviceManagementBloc
|
|||||||
_selectedDevices.add(event.selectedDevice);
|
_selectedDevices.add(event.selectedDevice);
|
||||||
}
|
}
|
||||||
|
|
||||||
final clonedSelectedDevices = List<AllDevicesModel>.from(_selectedDevices);
|
List<AllDevicesModel> clonedSelectedDevices = List.from(_selectedDevices);
|
||||||
|
|
||||||
final isControlButtonEnabled =
|
bool isControlButtonEnabled =
|
||||||
_checkIfControlButtonEnabled(clonedSelectedDevices);
|
_checkIfControlButtonEnabled(clonedSelectedDevices);
|
||||||
|
|
||||||
if (state is DeviceManagementLoaded) {
|
if (state is DeviceManagementLoaded) {
|
||||||
@ -199,8 +197,8 @@ class DeviceManagementBloc
|
|||||||
|
|
||||||
void _onUpdateSelection(
|
void _onUpdateSelection(
|
||||||
UpdateSelection event, Emitter<DeviceManagementState> emit) {
|
UpdateSelection event, Emitter<DeviceManagementState> emit) {
|
||||||
final selectedDevices = <AllDevicesModel>[];
|
List<AllDevicesModel> selectedDevices = [];
|
||||||
var devicesToSelectFrom = <AllDevicesModel>[];
|
List<AllDevicesModel> devicesToSelectFrom = [];
|
||||||
|
|
||||||
if (state is DeviceManagementLoaded) {
|
if (state is DeviceManagementLoaded) {
|
||||||
devicesToSelectFrom = (state as DeviceManagementLoaded).devices;
|
devicesToSelectFrom = (state as DeviceManagementLoaded).devices;
|
||||||
@ -208,7 +206,7 @@ class DeviceManagementBloc
|
|||||||
devicesToSelectFrom = (state as DeviceManagementFiltered).filteredDevices;
|
devicesToSelectFrom = (state as DeviceManagementFiltered).filteredDevices;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var i = 0; i < event.selectedRows.length; i++) {
|
for (int i = 0; i < event.selectedRows.length; i++) {
|
||||||
if (event.selectedRows[i]) {
|
if (event.selectedRows[i]) {
|
||||||
selectedDevices.add(devicesToSelectFrom[i]);
|
selectedDevices.add(devicesToSelectFrom[i]);
|
||||||
}
|
}
|
||||||
@ -254,7 +252,8 @@ class DeviceManagementBloc
|
|||||||
_onlineCount = _devices.where((device) => device.online == true).length;
|
_onlineCount = _devices.where((device) => device.online == true).length;
|
||||||
_offlineCount = _devices.where((device) => device.online == false).length;
|
_offlineCount = _devices.where((device) => device.online == false).length;
|
||||||
_lowBatteryCount = _devices
|
_lowBatteryCount = _devices
|
||||||
.where((device) => device.batteryLevel != null && device.batteryLevel! < 20)
|
.where((device) =>
|
||||||
|
device.batteryLevel != null && device.batteryLevel! < 20)
|
||||||
.length;
|
.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -271,40 +270,33 @@ class DeviceManagementBloc
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onSearchDevices(SearchDevices event, Emitter<DeviceManagementState> emit) {
|
void _onSearchDevices(
|
||||||
|
SearchDevices event, Emitter<DeviceManagementState> emit) {
|
||||||
if ((event.community == null || event.community!.isEmpty) &&
|
if ((event.community == null || event.community!.isEmpty) &&
|
||||||
(event.unitName == null || event.unitName!.isEmpty) &&
|
(event.unitName == null || event.unitName!.isEmpty) &&
|
||||||
(event.deviceNameOrProductName == null ||
|
(event.productName == null || event.productName!.isEmpty)) {
|
||||||
event.deviceNameOrProductName!.isEmpty)) {
|
|
||||||
currentProductName = '';
|
currentProductName = '';
|
||||||
_filteredDevices = List.from(_devices);
|
if (state is DeviceManagementFiltered) {
|
||||||
emit(DeviceManagementLoaded(
|
add(FilterDevices(_getFilterFromIndex(_selectedIndex)));
|
||||||
devices: _devices,
|
} else {
|
||||||
selectedIndex: _selectedIndex,
|
|
||||||
onlineCount: _onlineCount,
|
|
||||||
offlineCount: _offlineCount,
|
|
||||||
lowBatteryCount: _lowBatteryCount,
|
|
||||||
selectedDevice: null,
|
|
||||||
isControlButtonEnabled: false,
|
|
||||||
));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (event.deviceNameOrProductName == currentProductName &&
|
}
|
||||||
|
|
||||||
|
if (event.productName == currentProductName &&
|
||||||
event.community == currentCommunity &&
|
event.community == currentCommunity &&
|
||||||
event.unitName == currentUnitName &&
|
event.unitName == currentUnitName &&
|
||||||
event.searchField) {
|
event.searchField) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
currentProductName = event.deviceNameOrProductName ?? '';
|
currentProductName = event.productName ?? '';
|
||||||
currentCommunity = event.community;
|
currentCommunity = event.community;
|
||||||
currentUnitName = event.unitName;
|
currentUnitName = event.unitName;
|
||||||
|
|
||||||
final devicesToSearch = _devices;
|
List<AllDevicesModel> devicesToSearch = _filteredDevices;
|
||||||
|
|
||||||
if (devicesToSearch.isNotEmpty) {
|
if (devicesToSearch.isNotEmpty) {
|
||||||
final searchText = event.deviceNameOrProductName?.toLowerCase() ?? '';
|
|
||||||
|
|
||||||
final filteredDevices = devicesToSearch.where((device) {
|
final filteredDevices = devicesToSearch.where((device) {
|
||||||
final matchesCommunity = event.community == null ||
|
final matchesCommunity = event.community == null ||
|
||||||
event.community!.isEmpty ||
|
event.community!.isEmpty ||
|
||||||
@ -312,25 +304,31 @@ class DeviceManagementBloc
|
|||||||
?.toLowerCase()
|
?.toLowerCase()
|
||||||
.contains(event.community!.toLowerCase()) ??
|
.contains(event.community!.toLowerCase()) ??
|
||||||
false);
|
false);
|
||||||
|
|
||||||
final matchesUnit = event.unitName == null ||
|
final matchesUnit = event.unitName == null ||
|
||||||
event.unitName!.isEmpty ||
|
event.unitName!.isEmpty ||
|
||||||
(device.spaces != null &&
|
(device.spaces != null &&
|
||||||
device.spaces!.any((space) =>
|
device.spaces!.isNotEmpty &&
|
||||||
space.spaceName != null &&
|
device.spaces![0].spaceName!
|
||||||
space.spaceName!
|
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.contains(event.unitName!.toLowerCase())));
|
.contains(event.unitName!.toLowerCase()));
|
||||||
|
final matchesProductName = event.productName == null ||
|
||||||
|
event.productName!.isEmpty ||
|
||||||
|
(device.name
|
||||||
|
?.toLowerCase()
|
||||||
|
.contains(event.productName!.toLowerCase()) ??
|
||||||
|
false);
|
||||||
|
final matchesDeviceName = event.productName == null ||
|
||||||
|
event.productName!.isEmpty ||
|
||||||
|
(device.categoryName
|
||||||
|
?.toLowerCase()
|
||||||
|
.contains(event.productName!.toLowerCase()) ??
|
||||||
|
false);
|
||||||
|
|
||||||
final matchesSearchText = searchText.isEmpty ||
|
return matchesCommunity &&
|
||||||
(device.name?.toLowerCase().contains(searchText) ?? false) ||
|
matchesUnit &&
|
||||||
(device.productName?.toLowerCase().contains(searchText) ?? false);
|
(matchesProductName || matchesDeviceName);
|
||||||
|
|
||||||
return matchesCommunity && matchesUnit && matchesSearchText;
|
|
||||||
}).toList();
|
}).toList();
|
||||||
|
|
||||||
_filteredDevices = filteredDevices;
|
|
||||||
|
|
||||||
emit(DeviceManagementFiltered(
|
emit(DeviceManagementFiltered(
|
||||||
filteredDevices: filteredDevices,
|
filteredDevices: filteredDevices,
|
||||||
selectedIndex: _selectedIndex,
|
selectedIndex: _selectedIndex,
|
||||||
@ -343,134 +341,5 @@ class DeviceManagementBloc
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onUpdateDeviceName(
|
|
||||||
UpdateDeviceName event, Emitter<DeviceManagementState> emit) {
|
|
||||||
final devices = _devices.map((device) {
|
|
||||||
if (device.uuid == event.deviceId) {
|
|
||||||
final modifiedDevice = device.copyWith(name: event.newName);
|
|
||||||
_selectedDevices.removeWhere((device) => device.uuid == event.deviceId);
|
|
||||||
_selectedDevices.add(modifiedDevice);
|
|
||||||
return modifiedDevice;
|
|
||||||
}
|
|
||||||
return device;
|
|
||||||
}).toList();
|
|
||||||
|
|
||||||
final filteredDevices = _filteredDevices.map((device) {
|
|
||||||
if (device.uuid == event.deviceId) {
|
|
||||||
final modifiedDevice = device.copyWith(name: event.newName);
|
|
||||||
_selectedDevices.removeWhere((device) => device.uuid == event.deviceId);
|
|
||||||
_selectedDevices.add(modifiedDevice);
|
|
||||||
return modifiedDevice;
|
|
||||||
}
|
|
||||||
return device;
|
|
||||||
}).toList();
|
|
||||||
|
|
||||||
_devices = devices;
|
|
||||||
_filteredDevices = filteredDevices;
|
|
||||||
|
|
||||||
if (state is DeviceManagementLoaded) {
|
|
||||||
final loaded = state as DeviceManagementLoaded;
|
|
||||||
final selectedDevices01 = _selectedDevices.map((device) {
|
|
||||||
if (device.uuid == event.deviceId) {
|
|
||||||
final modifiedDevice = device.copyWith(name: event.newName);
|
|
||||||
return modifiedDevice;
|
|
||||||
}
|
|
||||||
return device;
|
|
||||||
}).toList();
|
|
||||||
emit(DeviceManagementLoaded(
|
|
||||||
devices: devices,
|
|
||||||
selectedIndex: loaded.selectedIndex,
|
|
||||||
onlineCount: loaded.onlineCount,
|
|
||||||
offlineCount: loaded.offlineCount,
|
|
||||||
lowBatteryCount: loaded.lowBatteryCount,
|
|
||||||
selectedDevice: selectedDevices01,
|
|
||||||
isControlButtonEnabled: loaded.isControlButtonEnabled,
|
|
||||||
));
|
|
||||||
} else if (state is DeviceManagementFiltered) {
|
|
||||||
final filtered = state as DeviceManagementFiltered;
|
|
||||||
final selectedDevices01 = filtered.selectedDevice?.map((device) {
|
|
||||||
if (device.uuid == event.deviceId) {
|
|
||||||
final modifiedDevice = device.copyWith(name: event.newName);
|
|
||||||
return modifiedDevice;
|
|
||||||
}
|
|
||||||
return device;
|
|
||||||
}).toList();
|
|
||||||
emit(DeviceManagementFiltered(
|
|
||||||
filteredDevices: filteredDevices,
|
|
||||||
selectedIndex: filtered.selectedIndex,
|
|
||||||
onlineCount: filtered.onlineCount,
|
|
||||||
offlineCount: filtered.offlineCount,
|
|
||||||
lowBatteryCount: filtered.lowBatteryCount,
|
|
||||||
selectedDevice: selectedDevices01,
|
|
||||||
isControlButtonEnabled: filtered.isControlButtonEnabled,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _onUpdateSubSpaceName(
|
|
||||||
UpdateSubSpaceName event, Emitter<DeviceManagementState> emit) {
|
|
||||||
final devices = _devices.map((device) {
|
|
||||||
if (device.uuid == event.deviceId) {
|
|
||||||
return device.copyWith(
|
|
||||||
subspace:
|
|
||||||
device.subspace?.copyWith(subspaceName: event.newSubSpaceName));
|
|
||||||
}
|
|
||||||
return device;
|
|
||||||
}).toList();
|
|
||||||
|
|
||||||
final filteredDevices = _filteredDevices.map((device) {
|
|
||||||
if (device.uuid == event.deviceId) {
|
|
||||||
return device.copyWith(
|
|
||||||
subspace:
|
|
||||||
device.subspace?.copyWith(subspaceName: event.newSubSpaceName));
|
|
||||||
}
|
|
||||||
return device;
|
|
||||||
}).toList();
|
|
||||||
|
|
||||||
_devices = devices;
|
|
||||||
_filteredDevices = filteredDevices;
|
|
||||||
|
|
||||||
if (state is DeviceManagementLoaded) {
|
|
||||||
final loaded = state as DeviceManagementLoaded;
|
|
||||||
final selectedDevices = loaded.selectedDevice?.map((device) {
|
|
||||||
if (device.uuid == event.deviceId) {
|
|
||||||
return device.copyWith(
|
|
||||||
subspace:
|
|
||||||
device.subspace?.copyWith(subspaceName: event.newSubSpaceName));
|
|
||||||
}
|
|
||||||
return device;
|
|
||||||
}).toList();
|
|
||||||
emit(DeviceManagementLoaded(
|
|
||||||
devices: _devices,
|
|
||||||
selectedIndex: loaded.selectedIndex,
|
|
||||||
onlineCount: loaded.onlineCount,
|
|
||||||
offlineCount: loaded.offlineCount,
|
|
||||||
lowBatteryCount: loaded.lowBatteryCount,
|
|
||||||
selectedDevice: selectedDevices,
|
|
||||||
isControlButtonEnabled: loaded.isControlButtonEnabled,
|
|
||||||
));
|
|
||||||
} else if (state is DeviceManagementFiltered) {
|
|
||||||
// final filtered = state as DeviceManagementFiltered;
|
|
||||||
// emit(DeviceManagementFiltered(
|
|
||||||
// filteredDevices: _filteredDevices,
|
|
||||||
// selectedIndex: filtered.selectedIndex,
|
|
||||||
// onlineCount: filtered.onlineCount,
|
|
||||||
// offlineCount: filtered.offlineCount,
|
|
||||||
// lowBatteryCount: filtered.lowBatteryCount,
|
|
||||||
// selectedDevice: filtered.selectedDevice,
|
|
||||||
// isControlButtonEnabled: filtered.isControlButtonEnabled,
|
|
||||||
// ));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void changeSubspaceName(
|
|
||||||
String deviceId, String newSubSpaceName, String subspaceId) {
|
|
||||||
add(UpdateSubSpaceName(
|
|
||||||
deviceId: deviceId,
|
|
||||||
newSubSpaceName: newSubSpaceName,
|
|
||||||
subspaceId: subspaceId,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
List<AllDevicesModel> get selectedDevices => _selectedDevices;
|
List<AllDevicesModel> get selectedDevices => _selectedDevices;
|
||||||
}
|
}
|
||||||
|
@ -38,18 +38,18 @@ class SelectedFilterChanged extends DeviceManagementEvent {
|
|||||||
class SearchDevices extends DeviceManagementEvent {
|
class SearchDevices extends DeviceManagementEvent {
|
||||||
final String? community;
|
final String? community;
|
||||||
final String? unitName;
|
final String? unitName;
|
||||||
final String? deviceNameOrProductName;
|
final String? productName;
|
||||||
final bool searchField;
|
final bool searchField;
|
||||||
|
|
||||||
const SearchDevices({
|
const SearchDevices({
|
||||||
this.community,
|
this.community,
|
||||||
this.unitName,
|
this.unitName,
|
||||||
this.deviceNameOrProductName,
|
this.productName,
|
||||||
this.searchField = false,
|
this.searchField = false,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [community, unitName, deviceNameOrProductName];
|
List<Object?> get props => [community, unitName, productName];
|
||||||
}
|
}
|
||||||
|
|
||||||
class SelectDevice extends DeviceManagementEvent {
|
class SelectDevice extends DeviceManagementEvent {
|
||||||
@ -70,21 +70,3 @@ class UpdateSelection extends DeviceManagementEvent {
|
|||||||
|
|
||||||
const UpdateSelection(this.selectedRows);
|
const UpdateSelection(this.selectedRows);
|
||||||
}
|
}
|
||||||
|
|
||||||
class UpdateDeviceName extends DeviceManagementEvent {
|
|
||||||
final String deviceId;
|
|
||||||
final String newName;
|
|
||||||
|
|
||||||
const UpdateDeviceName({required this.deviceId, required this.newName});
|
|
||||||
}
|
|
||||||
|
|
||||||
class UpdateSubSpaceName extends DeviceManagementEvent {
|
|
||||||
final String deviceId;
|
|
||||||
final String newSubSpaceName;
|
|
||||||
final String subspaceId;
|
|
||||||
|
|
||||||
const UpdateSubSpaceName(
|
|
||||||
{required this.deviceId,
|
|
||||||
required this.newSubSpaceName,
|
|
||||||
required this.subspaceId});
|
|
||||||
}
|
|
||||||
|
@ -7,8 +7,6 @@ import 'package:syncrow_web/pages/device_managment/ceiling_sensor/view/ceiling_s
|
|||||||
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/view/ceiling_sensor_controls.dart';
|
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/view/ceiling_sensor_controls.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/curtain/view/curtain_batch_status_view.dart';
|
import 'package:syncrow_web/pages/device_managment/curtain/view/curtain_batch_status_view.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/curtain/view/curtain_status_view.dart';
|
import 'package:syncrow_web/pages/device_managment/curtain/view/curtain_status_view.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/curtain_module/view/curtain_module_batch.dart';
|
|
||||||
import 'package:syncrow_web/pages/device_managment/curtain_module/view/curtain_module_items.dart';
|
|
||||||
import 'package:syncrow_web/pages/device_managment/door_lock/view/door_lock_batch_control_view.dart';
|
import 'package:syncrow_web/pages/device_managment/door_lock/view/door_lock_batch_control_view.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/door_lock/view/door_lock_control_view.dart';
|
import 'package:syncrow_web/pages/device_managment/door_lock/view/door_lock_control_view.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/flush_mounted_presence_sensor/views/flush_mounted_presence_sensor_batch_control_view.dart';
|
import 'package:syncrow_web/pages/device_managment/flush_mounted_presence_sensor/views/flush_mounted_presence_sensor_batch_control_view.dart';
|
||||||
@ -20,7 +18,6 @@ import 'package:syncrow_web/pages/device_managment/gateway/view/gateway_view.dar
|
|||||||
import 'package:syncrow_web/pages/device_managment/main_door_sensor/view/main_door_control_view.dart';
|
import 'package:syncrow_web/pages/device_managment/main_door_sensor/view/main_door_control_view.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/main_door_sensor/view/main_door_sensor_batch_view.dart';
|
import 'package:syncrow_web/pages/device_managment/main_door_sensor/view/main_door_sensor_batch_view.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/one_g_glass_switch/view/one_gang_glass_batch_control_view.dart';
|
import 'package:syncrow_web/pages/device_managment/one_g_glass_switch/view/one_gang_glass_batch_control_view.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/one_g_glass_switch/view/one_gang_glass_switch_control_view.dart';
|
|
||||||
import 'package:syncrow_web/pages/device_managment/one_gang_switch/view/wall_light_batch_control.dart';
|
import 'package:syncrow_web/pages/device_managment/one_gang_switch/view/wall_light_batch_control.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/one_gang_switch/view/wall_light_device_control.dart';
|
import 'package:syncrow_web/pages/device_managment/one_gang_switch/view/wall_light_device_control.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/power_clamp/view/power_clamp_batch_control_view.dart';
|
import 'package:syncrow_web/pages/device_managment/power_clamp/view/power_clamp_batch_control_view.dart';
|
||||||
@ -42,6 +39,8 @@ import 'package:syncrow_web/pages/device_managment/water_heater/view/water_heate
|
|||||||
import 'package:syncrow_web/pages/device_managment/water_leak/view/water_leak_batch_control_view.dart';
|
import 'package:syncrow_web/pages/device_managment/water_leak/view/water_leak_batch_control_view.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/water_leak/view/water_leak_control_view.dart';
|
import 'package:syncrow_web/pages/device_managment/water_leak/view/water_leak_control_view.dart';
|
||||||
|
|
||||||
|
import '../../one_g_glass_switch/view/one_gang_glass_switch_control_view.dart';
|
||||||
|
|
||||||
mixin RouteControlsBasedCode {
|
mixin RouteControlsBasedCode {
|
||||||
Widget routeControlsWidgets({required AllDevicesModel device}) {
|
Widget routeControlsWidgets({required AllDevicesModel device}) {
|
||||||
switch (device.productType) {
|
switch (device.productType) {
|
||||||
@ -85,10 +84,6 @@ mixin RouteControlsBasedCode {
|
|||||||
return CurtainStatusControlsView(
|
return CurtainStatusControlsView(
|
||||||
deviceId: device.uuid!,
|
deviceId: device.uuid!,
|
||||||
);
|
);
|
||||||
case 'CUR_2':
|
|
||||||
return CurtainModuleItems(
|
|
||||||
deviceId: device.uuid!,
|
|
||||||
);
|
|
||||||
case 'AC':
|
case 'AC':
|
||||||
return AcDeviceControlsView(device: device);
|
return AcDeviceControlsView(device: device);
|
||||||
case 'WH':
|
case 'WH':
|
||||||
@ -137,140 +132,76 @@ mixin RouteControlsBasedCode {
|
|||||||
switch (devices.first.productType) {
|
switch (devices.first.productType) {
|
||||||
case '1G':
|
case '1G':
|
||||||
return WallLightBatchControlView(
|
return WallLightBatchControlView(
|
||||||
deviceIds: devices
|
deviceIds: devices.where((e) => (e.productType == '1G')).map((e) => e.uuid!).toList(),
|
||||||
.where((e) => e.productType == '1G')
|
|
||||||
.map((e) => e.uuid!)
|
|
||||||
.toList(),
|
|
||||||
);
|
);
|
||||||
case '2G':
|
case '2G':
|
||||||
return TwoGangBatchControlView(
|
return TwoGangBatchControlView(
|
||||||
deviceIds: devices
|
deviceIds: devices.where((e) => (e.productType == '2G')).map((e) => e.uuid!).toList(),
|
||||||
.where((e) => e.productType == '2G')
|
|
||||||
.map((e) => e.uuid!)
|
|
||||||
.toList(),
|
|
||||||
);
|
);
|
||||||
case '3G':
|
case '3G':
|
||||||
return LivingRoomBatchControlsView(
|
return LivingRoomBatchControlsView(
|
||||||
deviceIds: devices
|
deviceIds: devices.where((e) => (e.productType == '3G')).map((e) => e.uuid!).toList(),
|
||||||
.where((e) => e.productType == '3G')
|
|
||||||
.map((e) => e.uuid!)
|
|
||||||
.toList(),
|
|
||||||
);
|
);
|
||||||
case '1GT':
|
case '1GT':
|
||||||
return OneGangGlassSwitchBatchControlView(
|
return OneGangGlassSwitchBatchControlView(
|
||||||
deviceIds: devices
|
deviceIds: devices.where((e) => (e.productType == '1GT')).map((e) => e.uuid!).toList(),
|
||||||
.where((e) => e.productType == '1GT')
|
|
||||||
.map((e) => e.uuid!)
|
|
||||||
.toList(),
|
|
||||||
);
|
);
|
||||||
case '2GT':
|
case '2GT':
|
||||||
return TwoGangGlassSwitchBatchControlView(
|
return TwoGangGlassSwitchBatchControlView(
|
||||||
deviceIds: devices
|
deviceIds: devices.where((e) => (e.productType == '2GT')).map((e) => e.uuid!).toList(),
|
||||||
.where((e) => e.productType == '2GT')
|
|
||||||
.map((e) => e.uuid!)
|
|
||||||
.toList(),
|
|
||||||
);
|
);
|
||||||
case '3GT':
|
case '3GT':
|
||||||
return ThreeGangGlassSwitchBatchControlView(
|
return ThreeGangGlassSwitchBatchControlView(
|
||||||
deviceIds: devices
|
deviceIds: devices.where((e) => (e.productType == '3GT')).map((e) => e.uuid!).toList(),
|
||||||
.where((e) => e.productType == '3GT')
|
|
||||||
.map((e) => e.uuid!)
|
|
||||||
.toList(),
|
|
||||||
);
|
);
|
||||||
case 'GW':
|
case 'GW':
|
||||||
return GatewayBatchControlView(
|
return GatewayBatchControlView(
|
||||||
gatewayIds: devices
|
gatewayIds: devices.where((e) => (e.productType == 'GW')).map((e) => e.uuid!).toList(),
|
||||||
.where((e) => e.productType == 'GW')
|
|
||||||
.map((e) => e.uuid!)
|
|
||||||
.toList(),
|
|
||||||
);
|
);
|
||||||
case 'DL':
|
case 'DL':
|
||||||
return DoorLockBatchControlView(
|
return DoorLockBatchControlView(
|
||||||
devicesIds: devices
|
devicesIds: devices.where((e) => (e.productType == 'DL')).map((e) => e.uuid!).toList());
|
||||||
.where((e) => e.productType == 'DL')
|
|
||||||
.map((e) => e.uuid!)
|
|
||||||
.toList());
|
|
||||||
case 'WPS':
|
case 'WPS':
|
||||||
return WallSensorBatchControlView(
|
return WallSensorBatchControlView(
|
||||||
devicesIds: devices
|
devicesIds: devices.where((e) => (e.productType == 'WPS')).map((e) => e.uuid!).toList());
|
||||||
.where((e) => e.productType == 'WPS')
|
|
||||||
.map((e) => e.uuid!)
|
|
||||||
.toList());
|
|
||||||
case 'CPS':
|
case 'CPS':
|
||||||
return CeilingSensorBatchControlView(
|
return CeilingSensorBatchControlView(
|
||||||
devicesIds: devices
|
devicesIds: devices.where((e) => (e.productType == 'CPS')).map((e) => e.uuid!).toList(),
|
||||||
.where((e) => e.productType == 'CPS')
|
|
||||||
.map((e) => e.uuid!)
|
|
||||||
.toList(),
|
|
||||||
);
|
);
|
||||||
case 'CUR':
|
case 'CUR':
|
||||||
return CurtainBatchStatusView(
|
return CurtainBatchStatusView(
|
||||||
devicesIds: devices
|
devicesIds: devices.where((e) => (e.productType == 'CUR')).map((e) => e.uuid!).toList(),
|
||||||
.where((e) => e.productType == 'CUR')
|
|
||||||
.map((e) => e.uuid!)
|
|
||||||
.toList(),
|
|
||||||
);
|
|
||||||
case 'CUR_2':
|
|
||||||
return CurtainModuleBatchView(
|
|
||||||
devicesIds: devices
|
|
||||||
.where((e) => e.productType == 'CUR_2')
|
|
||||||
.map((e) => e.uuid!)
|
|
||||||
.toList(),
|
|
||||||
);
|
);
|
||||||
case 'AC':
|
case 'AC':
|
||||||
return AcDeviceBatchControlView(
|
return AcDeviceBatchControlView(
|
||||||
devicesIds: devices
|
devicesIds: devices.where((e) => (e.productType == 'AC')).map((e) => e.uuid!).toList());
|
||||||
.where((e) => e.productType == 'AC')
|
|
||||||
.map((e) => e.uuid!)
|
|
||||||
.toList());
|
|
||||||
case 'WH':
|
case 'WH':
|
||||||
return WaterHEaterBatchControlView(
|
return WaterHEaterBatchControlView(
|
||||||
deviceIds: devices
|
deviceIds: devices.where((e) => (e.productType == 'WH')).map((e) => e.uuid!).toList(),
|
||||||
.where((e) => e.productType == 'WH')
|
|
||||||
.map((e) => e.uuid!)
|
|
||||||
.toList(),
|
|
||||||
);
|
);
|
||||||
case 'DS':
|
case 'DS':
|
||||||
return MainDoorSensorBatchView(
|
return MainDoorSensorBatchView(
|
||||||
devicesIds: devices
|
devicesIds: devices.where((e) => (e.productType == 'DS')).map((e) => e.uuid!).toList(),
|
||||||
.where((e) => e.productType == 'DS')
|
|
||||||
.map((e) => e.uuid!)
|
|
||||||
.toList(),
|
|
||||||
);
|
);
|
||||||
case 'GD':
|
case 'GD':
|
||||||
return GarageDoorBatchControlView(
|
return GarageDoorBatchControlView(
|
||||||
deviceIds: devices
|
deviceIds: devices.where((e) => (e.productType == 'GD')).map((e) => e.uuid!).toList(),
|
||||||
.where((e) => e.productType == 'GD')
|
|
||||||
.map((e) => e.uuid!)
|
|
||||||
.toList(),
|
|
||||||
);
|
);
|
||||||
case 'WL':
|
case 'WL':
|
||||||
return WaterLeakBatchControlView(
|
return WaterLeakBatchControlView(
|
||||||
deviceIds: devices
|
deviceIds: devices.where((e) => (e.productType == 'WL')).map((e) => e.uuid!).toList(),
|
||||||
.where((e) => e.productType == 'WL')
|
|
||||||
.map((e) => e.uuid!)
|
|
||||||
.toList(),
|
|
||||||
);
|
);
|
||||||
case 'PC':
|
case 'PC':
|
||||||
return PowerClampBatchControlView(
|
return PowerClampBatchControlView(
|
||||||
deviceIds: devices
|
deviceIds: devices.where((e) => (e.productType == 'PC')).map((e) => e.uuid!).toList(),
|
||||||
.where((e) => e.productType == 'PC')
|
|
||||||
.map((e) => e.uuid!)
|
|
||||||
.toList(),
|
|
||||||
);
|
);
|
||||||
case 'SOS':
|
case 'SOS':
|
||||||
return SOSBatchControlView(
|
return SOSBatchControlView(
|
||||||
deviceIds: devices
|
deviceIds: devices.where((e) => (e.productType == 'SOS')).map((e) => e.uuid!).toList(),
|
||||||
.where((e) => e.productType == 'SOS')
|
|
||||||
.map((e) => e.uuid!)
|
|
||||||
.toList(),
|
|
||||||
);
|
);
|
||||||
case 'NCPS':
|
case 'NCPS':
|
||||||
return FlushMountedPresenceSensorBatchControlView(
|
return FlushMountedPresenceSensorBatchControlView(
|
||||||
devicesIds: devices
|
devicesIds: devices.where((e) => (e.productType == 'NCPS')).map((e) => e.uuid!).toList(),
|
||||||
.where((e) => e.productType == 'NCPS')
|
|
||||||
.map((e) => e.uuid!)
|
|
||||||
.toList(),
|
|
||||||
);
|
);
|
||||||
default:
|
default:
|
||||||
return const SizedBox();
|
return const SizedBox();
|
||||||
|
@ -57,16 +57,6 @@ class Status {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
Status copyWith({
|
|
||||||
String? code,
|
|
||||||
dynamic value,
|
|
||||||
}) {
|
|
||||||
return Status(
|
|
||||||
code: code ?? this.code,
|
|
||||||
value: value ?? this.value,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
factory Status.fromJson(String source) => Status.fromMap(json.decode(source));
|
factory Status.fromJson(String source) => Status.fromMap(json.decode(source));
|
||||||
|
|
||||||
String toJson() => json.encode(toMap());
|
String toJson() => json.encode(toMap());
|
||||||
|
@ -44,20 +44,4 @@ class DeviceSubspace {
|
|||||||
static List<Map<String, dynamic>> listToJson(List<DeviceSubspace> subspaces) {
|
static List<Map<String, dynamic>> listToJson(List<DeviceSubspace> subspaces) {
|
||||||
return subspaces.map((subspace) => subspace.toJson()).toList();
|
return subspaces.map((subspace) => subspace.toJson()).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
DeviceSubspace copyWith({
|
|
||||||
String? uuid,
|
|
||||||
DateTime? createdAt,
|
|
||||||
DateTime? updatedAt,
|
|
||||||
String? subspaceName,
|
|
||||||
bool? disabled,
|
|
||||||
}) {
|
|
||||||
return DeviceSubspace(
|
|
||||||
uuid: uuid ?? this.uuid,
|
|
||||||
createdAt: createdAt ?? this.createdAt,
|
|
||||||
updatedAt: updatedAt ?? this.updatedAt,
|
|
||||||
subspaceName: subspaceName ?? this.subspaceName,
|
|
||||||
disabled: disabled ?? this.disabled,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,6 @@ import 'package:syncrow_web/pages/device_managment/all_devices/models/device_tag
|
|||||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/room.dart';
|
import 'package:syncrow_web/pages/device_managment/all_devices/models/room.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/unit.dart';
|
import 'package:syncrow_web/pages/device_managment/all_devices/models/unit.dart';
|
||||||
import 'package:syncrow_web/pages/routines/models/ac/ac_function.dart';
|
import 'package:syncrow_web/pages/routines/models/ac/ac_function.dart';
|
||||||
import 'package:syncrow_web/pages/routines/models/curtain/curtain_function.dart';
|
|
||||||
import 'package:syncrow_web/pages/routines/models/device_functions.dart';
|
import 'package:syncrow_web/pages/routines/models/device_functions.dart';
|
||||||
import 'package:syncrow_web/pages/routines/models/flush/flush_functions.dart';
|
import 'package:syncrow_web/pages/routines/models/flush/flush_functions.dart';
|
||||||
import 'package:syncrow_web/pages/routines/models/gang_switches/one_gang_switch/one_gang_switch.dart';
|
import 'package:syncrow_web/pages/routines/models/gang_switches/one_gang_switch/one_gang_switch.dart';
|
||||||
@ -360,14 +359,6 @@ SOS
|
|||||||
uuid: uuid ?? '',
|
uuid: uuid ?? '',
|
||||||
name: name ?? '',
|
name: name ?? '',
|
||||||
);
|
);
|
||||||
case 'CUR':
|
|
||||||
return [
|
|
||||||
ControlCurtainFunction(
|
|
||||||
deviceId: uuid ?? '',
|
|
||||||
deviceName: name ?? '',
|
|
||||||
type: 'BOTH',
|
|
||||||
)
|
|
||||||
];
|
|
||||||
case 'NCPS':
|
case 'NCPS':
|
||||||
return [
|
return [
|
||||||
FlushPresenceDelayFunction(
|
FlushPresenceDelayFunction(
|
||||||
@ -450,10 +441,15 @@ SOS
|
|||||||
VoltageCStatusFunction(
|
VoltageCStatusFunction(
|
||||||
deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'),
|
deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'),
|
||||||
CurrentCStatusFunction(
|
CurrentCStatusFunction(
|
||||||
deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'),
|
deviceId: uuid ?? '',
|
||||||
|
deviceName: name ?? '',
|
||||||
|
type: 'IF'),
|
||||||
PowerFactorCStatusFunction(
|
PowerFactorCStatusFunction(
|
||||||
deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'),
|
deviceId: uuid ?? '',
|
||||||
|
deviceName: name ?? '',
|
||||||
|
type: 'IF'),
|
||||||
];
|
];
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
@ -588,72 +584,4 @@ SOS
|
|||||||
"NCPS": DeviceType.NCPS,
|
"NCPS": DeviceType.NCPS,
|
||||||
"PC": DeviceType.PC,
|
"PC": DeviceType.PC,
|
||||||
};
|
};
|
||||||
|
|
||||||
AllDevicesModel copyWith({
|
|
||||||
DevicesModelRoom? room,
|
|
||||||
DeviceSubspace? subspace,
|
|
||||||
DevicesModelUnit? unit,
|
|
||||||
DeviceCommunityModel? community,
|
|
||||||
String? productUuid,
|
|
||||||
String? productType,
|
|
||||||
String? permissionType,
|
|
||||||
int? activeTime,
|
|
||||||
String? category,
|
|
||||||
String? categoryName,
|
|
||||||
int? createTime,
|
|
||||||
String? gatewayId,
|
|
||||||
String? icon,
|
|
||||||
String? ip,
|
|
||||||
String? lat,
|
|
||||||
String? localKey,
|
|
||||||
String? lon,
|
|
||||||
String? model,
|
|
||||||
String? name,
|
|
||||||
String? nodeId,
|
|
||||||
bool? online,
|
|
||||||
String? ownerId,
|
|
||||||
bool? sub,
|
|
||||||
String? timeZone,
|
|
||||||
int? updateTime,
|
|
||||||
String? uuid,
|
|
||||||
int? batteryLevel,
|
|
||||||
String? productName,
|
|
||||||
List<DeviceSpaceModel>? spaces,
|
|
||||||
List<DeviceTagModel>? deviceTags,
|
|
||||||
DeviceSubSpace? deviceSubSpace,
|
|
||||||
}) {
|
|
||||||
return AllDevicesModel(
|
|
||||||
room: room ?? this.room,
|
|
||||||
subspace: subspace ?? this.subspace,
|
|
||||||
unit: unit ?? this.unit,
|
|
||||||
community: community ?? this.community,
|
|
||||||
productUuid: productUuid ?? this.productUuid,
|
|
||||||
productType: productType ?? this.productType,
|
|
||||||
permissionType: permissionType ?? this.permissionType,
|
|
||||||
activeTime: activeTime ?? this.activeTime,
|
|
||||||
category: category ?? this.category,
|
|
||||||
categoryName: categoryName ?? this.categoryName,
|
|
||||||
createTime: createTime ?? this.createTime,
|
|
||||||
gatewayId: gatewayId ?? this.gatewayId,
|
|
||||||
icon: icon ?? this.icon,
|
|
||||||
ip: ip ?? this.ip,
|
|
||||||
lat: lat ?? this.lat,
|
|
||||||
localKey: localKey ?? this.localKey,
|
|
||||||
lon: lon ?? this.lon,
|
|
||||||
model: model ?? this.model,
|
|
||||||
name: name ?? this.name,
|
|
||||||
nodeId: nodeId ?? this.nodeId,
|
|
||||||
online: online ?? this.online,
|
|
||||||
ownerId: ownerId ?? this.ownerId,
|
|
||||||
sub: sub ?? this.sub,
|
|
||||||
timeZone: timeZone ?? this.timeZone,
|
|
||||||
updateTime: updateTime ?? this.updateTime,
|
|
||||||
uuid: uuid ?? this.uuid,
|
|
||||||
batteryLevel: batteryLevel ?? this.batteryLevel,
|
|
||||||
productName: productName ?? this.productName,
|
|
||||||
spaces: spaces ?? this.spaces,
|
|
||||||
deviceTags: deviceTags ?? this.deviceTags,
|
|
||||||
deviceSubSpace: deviceSubSpace ?? this.deviceSubSpace,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -8,28 +8,15 @@ import 'package:syncrow_web/pages/routines/bloc/create_routine_bloc/create_routi
|
|||||||
import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
|
import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/routines/view/create_new_routine_view.dart';
|
import 'package:syncrow_web/pages/routines/view/create_new_routine_view.dart';
|
||||||
import 'package:syncrow_web/pages/routines/view/routines_view.dart';
|
import 'package:syncrow_web/pages/routines/view/routines_view.dart';
|
||||||
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
|
|
||||||
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_event.dart';
|
|
||||||
import 'package:syncrow_web/utils/color_manager.dart';
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||||
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
|
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
|
||||||
import 'package:syncrow_web/utils/theme/responsive_text_theme.dart';
|
import 'package:syncrow_web/utils/theme/responsive_text_theme.dart';
|
||||||
import 'package:syncrow_web/web_layout/web_scaffold.dart';
|
import 'package:syncrow_web/web_layout/web_scaffold.dart';
|
||||||
|
|
||||||
class DeviceManagementPage extends StatefulWidget with HelperResponsiveLayout {
|
class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout {
|
||||||
const DeviceManagementPage({super.key});
|
const DeviceManagementPage({super.key});
|
||||||
|
|
||||||
@override
|
|
||||||
State<DeviceManagementPage> createState() => _DeviceManagementPageState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _DeviceManagementPageState extends State<DeviceManagementPage> {
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
context.read<SpaceTreeBloc>().add(InitialEvent());
|
|
||||||
super.initState();
|
|
||||||
}
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MultiBlocProvider(
|
return MultiBlocProvider(
|
||||||
|
@ -23,7 +23,6 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocBuilder<DeviceManagementBloc, DeviceManagementState>(
|
return BlocBuilder<DeviceManagementBloc, DeviceManagementState>(
|
||||||
buildWhen: (previous, current) => previous != current,
|
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
List<AllDevicesModel> devicesToShow = [];
|
List<AllDevicesModel> devicesToShow = [];
|
||||||
int selectedIndex = 0;
|
int selectedIndex = 0;
|
||||||
@ -32,6 +31,7 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
|
|||||||
int lowBatteryCount = 0;
|
int lowBatteryCount = 0;
|
||||||
bool isControlButtonEnabled = false;
|
bool isControlButtonEnabled = false;
|
||||||
List<AllDevicesModel> selectedDevices = [];
|
List<AllDevicesModel> selectedDevices = [];
|
||||||
|
|
||||||
if (state is DeviceManagementLoaded) {
|
if (state is DeviceManagementLoaded) {
|
||||||
devicesToShow = state.devices;
|
devicesToShow = state.devices;
|
||||||
selectedIndex = state.selectedIndex;
|
selectedIndex = state.selectedIndex;
|
||||||
@ -62,8 +62,7 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
|
|||||||
|
|
||||||
final buttonLabel =
|
final buttonLabel =
|
||||||
(selectedDevices.length > 1) ? 'Batch Control' : 'Control';
|
(selectedDevices.length > 1) ? 'Batch Control' : 'Control';
|
||||||
final isAnyDeviceOffline =
|
|
||||||
selectedDevices.any((element) => !(element.online ?? false));
|
|
||||||
return Row(
|
return Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(child: SpaceTreeView(
|
Expanded(child: SpaceTreeView(
|
||||||
@ -104,28 +103,8 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
|
|||||||
decoration: containerDecoration,
|
decoration: containerDecoration,
|
||||||
child: Center(
|
child: Center(
|
||||||
child: DefaultButton(
|
child: DefaultButton(
|
||||||
backgroundColor: isAnyDeviceOffline
|
|
||||||
? ColorsManager.primaryColor
|
|
||||||
.withValues(alpha: 0.1)
|
|
||||||
: null,
|
|
||||||
onPressed: isControlButtonEnabled
|
onPressed: isControlButtonEnabled
|
||||||
? () {
|
? () {
|
||||||
if (isAnyDeviceOffline) {
|
|
||||||
ScaffoldMessenger.of(context)
|
|
||||||
.clearSnackBars();
|
|
||||||
ScaffoldMessenger.of(context)
|
|
||||||
.showSnackBar(
|
|
||||||
const SnackBar(
|
|
||||||
content: Text(
|
|
||||||
'This Device is Offline',
|
|
||||||
),
|
|
||||||
duration:
|
|
||||||
Duration(seconds: 2),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selectedDevices.length == 1) {
|
if (selectedDevices.length == 1) {
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
@ -192,7 +171,7 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
|
|||||||
'Product Name',
|
'Product Name',
|
||||||
'Device ID',
|
'Device ID',
|
||||||
'Space Name',
|
'Space Name',
|
||||||
'Location',
|
'location',
|
||||||
'Battery Level',
|
'Battery Level',
|
||||||
'Installation Date and Time',
|
'Installation Date and Time',
|
||||||
'Status',
|
'Status',
|
||||||
@ -244,7 +223,7 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
|
|||||||
.map((device) => device.uuid!)
|
.map((device) => device.uuid!)
|
||||||
.toList(),
|
.toList(),
|
||||||
isEmpty: devicesToShow.isEmpty,
|
isEmpty: devicesToShow.isEmpty,
|
||||||
onSettingsPressed: (rowIndex) async {
|
onSettingsPressed: (rowIndex) {
|
||||||
final device = devicesToShow[rowIndex];
|
final device = devicesToShow[rowIndex];
|
||||||
showDeviceSettingsSidebar(context, device);
|
showDeviceSettingsSidebar(context, device);
|
||||||
},
|
},
|
||||||
@ -266,7 +245,7 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
|
|||||||
barrierDismissible: true,
|
barrierDismissible: true,
|
||||||
barrierLabel: "Device Settings",
|
barrierLabel: "Device Settings",
|
||||||
transitionDuration: const Duration(milliseconds: 300),
|
transitionDuration: const Duration(milliseconds: 300),
|
||||||
pageBuilder: (_, anim1, anim2) {
|
pageBuilder: (context, anim1, anim2) {
|
||||||
return Align(
|
return Align(
|
||||||
alignment: Alignment.centerRight,
|
alignment: Alignment.centerRight,
|
||||||
child: Material(
|
child: Material(
|
||||||
@ -276,7 +255,6 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
|
|||||||
child: DeviceSettingsPanel(
|
child: DeviceSettingsPanel(
|
||||||
device: device,
|
device: device,
|
||||||
onClose: () => Navigator.of(context).pop(),
|
onClose: () => Navigator.of(context).pop(),
|
||||||
deviceManagementBloc: context.read<DeviceManagementBloc>(),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -53,7 +53,7 @@ class _DeviceSearchFiltersState extends State<DeviceSearchFilters>
|
|||||||
controller: controller,
|
controller: controller,
|
||||||
onSubmitted: () {
|
onSubmitted: () {
|
||||||
final searchDevicesEvent = SearchDevices(
|
final searchDevicesEvent = SearchDevices(
|
||||||
deviceNameOrProductName: _productNameController.text,
|
productName: _productNameController.text,
|
||||||
unitName: _unitNameController.text,
|
unitName: _unitNameController.text,
|
||||||
searchField: true,
|
searchField: true,
|
||||||
);
|
);
|
||||||
@ -68,7 +68,7 @@ class _DeviceSearchFiltersState extends State<DeviceSearchFilters>
|
|||||||
onSearch: () => context.read<DeviceManagementBloc>().add(
|
onSearch: () => context.read<DeviceManagementBloc>().add(
|
||||||
SearchDevices(
|
SearchDevices(
|
||||||
unitName: _unitNameController.text,
|
unitName: _unitNameController.text,
|
||||||
deviceNameOrProductName: _productNameController.text,
|
productName: _productNameController.text,
|
||||||
searchField: true,
|
searchField: true,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -1,379 +0,0 @@
|
|||||||
import 'dart:async';
|
|
||||||
import 'package:bloc/bloc.dart';
|
|
||||||
import 'package:equatable/equatable.dart';
|
|
||||||
import 'package:firebase_database/firebase_database.dart';
|
|
||||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
|
|
||||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart';
|
|
||||||
import 'package:syncrow_web/pages/device_managment/curtain_module/models/curtain_module_model.dart';
|
|
||||||
import 'package:syncrow_web/services/batch_control_devices_service.dart';
|
|
||||||
import 'package:syncrow_web/services/control_device_service.dart';
|
|
||||||
import 'package:syncrow_web/services/devices_mang_api.dart';
|
|
||||||
|
|
||||||
part 'curtain_module_event.dart';
|
|
||||||
part 'curtain_module_state.dart';
|
|
||||||
|
|
||||||
class CurtainModuleBloc extends Bloc<CurtainModuleEvent, CurtainModuleState> {
|
|
||||||
final ControlDeviceService controlDeviceService;
|
|
||||||
final BatchControlDevicesService batchControlDevicesService;
|
|
||||||
StreamSubscription<DatabaseEvent>? _firebaseSubscription;
|
|
||||||
|
|
||||||
CurtainModuleBloc({
|
|
||||||
required this.controlDeviceService,
|
|
||||||
required this.batchControlDevicesService,
|
|
||||||
}) : super(CurtainModuleInitial()) {
|
|
||||||
on<FetchCurtainModuleStatusEvent>(_onFetchCurtainModuleStatusEvent);
|
|
||||||
on<SendCurtainPercentToApiEvent>(_onSendCurtainPercentToApiEvent);
|
|
||||||
on<OpenCurtainEvent>(_onOpenCurtainEvent);
|
|
||||||
on<CloseCurtainEvent>(_onCloseCurtainEvent);
|
|
||||||
on<StopCurtainEvent>(_onStopCurtainEvent);
|
|
||||||
on<ChangeTimerControlEvent>(_onChangeTimerControlEvent);
|
|
||||||
on<CurCalibrationEvent>(_onChageCurCalibrationEvent);
|
|
||||||
on<ChangeElecMachineryModeEvent>(_onChangeElecMachineryModeEvent);
|
|
||||||
on<ChangeControlBackEvent>(_onChangeControlBackEvent);
|
|
||||||
on<ChangeControlBackModeEvent>(_onChangeControlBackModeEvent);
|
|
||||||
on<ChangeCurtainModuleStatusEvent>(_onChangeCurtainModuleStatusEvent);
|
|
||||||
//batch
|
|
||||||
on<CurtainModuleFetchBatchStatusEvent>(_onFetchCurtainModuleBatchStatus);
|
|
||||||
on<SendCurtainBatchPercentToApiEvent>(_onSendCurtainBatchPercentToApiEvent);
|
|
||||||
on<OpenCurtainBatchEvent>(_onOpenCurtainBatchEvent);
|
|
||||||
on<CloseCurtainBatchEvent>(_onCloseCurtainBatchEvent);
|
|
||||||
on<StopCurtainBatchEvent>(_onStopCurtainBatchEvent);
|
|
||||||
on<CurtainModuleFactoryReset>(_onFactoryReset);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _onFetchCurtainModuleStatusEvent(
|
|
||||||
FetchCurtainModuleStatusEvent event,
|
|
||||||
Emitter<CurtainModuleState> emit,
|
|
||||||
) async {
|
|
||||||
emit(CurtainModuleLoading());
|
|
||||||
final status = await DevicesManagementApi().getDeviceStatus(event.deviceId);
|
|
||||||
final result = Map.fromEntries(
|
|
||||||
status.status.map((element) => MapEntry(element.code, element.value)),
|
|
||||||
);
|
|
||||||
|
|
||||||
emit(CurtainModuleStatusLoaded(
|
|
||||||
curtainModuleStatus: CurtainModuleStatusModel.fromJson(result),
|
|
||||||
));
|
|
||||||
Map<String, dynamic> statusMap = {};
|
|
||||||
final ref =
|
|
||||||
FirebaseDatabase.instance.ref('device-status/${event.deviceId}');
|
|
||||||
final stream = ref.onValue;
|
|
||||||
|
|
||||||
stream.listen((DatabaseEvent DatabaseEvent) async {
|
|
||||||
if (DatabaseEvent.snapshot.value == null) return;
|
|
||||||
|
|
||||||
Map<dynamic, dynamic> usersMap =
|
|
||||||
DatabaseEvent.snapshot.value as Map<dynamic, dynamic>;
|
|
||||||
|
|
||||||
List<Status> statusList = [];
|
|
||||||
|
|
||||||
usersMap['status'].forEach((element) {
|
|
||||||
statusList.add(Status(code: element['code'], value: element['value']));
|
|
||||||
});
|
|
||||||
|
|
||||||
statusMap = {
|
|
||||||
for (final element in statusList) element.code: element.value,
|
|
||||||
};
|
|
||||||
if (!isClosed) {
|
|
||||||
add(
|
|
||||||
ChangeCurtainModuleStatusEvent(
|
|
||||||
deviceId: event.deviceId,
|
|
||||||
status: CurtainModuleStatusModel.fromJson(statusMap),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _onChangeCurtainModuleStatusEvent(
|
|
||||||
ChangeCurtainModuleStatusEvent event,
|
|
||||||
Emitter<CurtainModuleState> emit,
|
|
||||||
) async {
|
|
||||||
emit(CurtainModuleLoading());
|
|
||||||
emit(CurtainModuleStatusLoaded(curtainModuleStatus: event.status));
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _onSendCurtainPercentToApiEvent(
|
|
||||||
SendCurtainPercentToApiEvent event,
|
|
||||||
Emitter<CurtainModuleState> emit,
|
|
||||||
) async {
|
|
||||||
try {
|
|
||||||
await controlDeviceService.controlDevice(
|
|
||||||
deviceUuid: event.deviceId,
|
|
||||||
status: event.status,
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
emit(CurtainModuleError(message: 'Failed to send control command: $e'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _onOpenCurtainEvent(
|
|
||||||
OpenCurtainEvent event,
|
|
||||||
Emitter<CurtainModuleState> emit,
|
|
||||||
) async {
|
|
||||||
try {
|
|
||||||
await controlDeviceService.controlDevice(
|
|
||||||
deviceUuid: event.deviceId,
|
|
||||||
status: Status(code: 'control', value: 'open'),
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
emit(CurtainModuleError(message: 'Failed to open curtain: $e'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _onCloseCurtainEvent(
|
|
||||||
CloseCurtainEvent event,
|
|
||||||
Emitter<CurtainModuleState> emit,
|
|
||||||
) async {
|
|
||||||
try {
|
|
||||||
await controlDeviceService.controlDevice(
|
|
||||||
deviceUuid: event.deviceId,
|
|
||||||
status: Status(code: 'control', value: 'close'),
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
emit(CurtainModuleError(message: 'Failed to close curtain: $e'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _onStopCurtainEvent(
|
|
||||||
StopCurtainEvent event,
|
|
||||||
Emitter<CurtainModuleState> emit,
|
|
||||||
) async {
|
|
||||||
try {
|
|
||||||
await controlDeviceService.controlDevice(
|
|
||||||
deviceUuid: event.deviceId,
|
|
||||||
status: Status(code: 'control', value: 'stop'),
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
emit(CurtainModuleError(message: 'Failed to stop curtain: $e'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _onChangeTimerControlEvent(
|
|
||||||
ChangeTimerControlEvent event,
|
|
||||||
Emitter<CurtainModuleState> emit,
|
|
||||||
) async {
|
|
||||||
try {
|
|
||||||
if (event.timControl < 10 || event.timControl > 120) {
|
|
||||||
emit(const CurtainModuleError(
|
|
||||||
message: 'Timer control value must be between 10 and 120'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await controlDeviceService.controlDevice(
|
|
||||||
deviceUuid: event.deviceId,
|
|
||||||
status: Status(
|
|
||||||
code: 'tr_timecon',
|
|
||||||
value: event.timControl,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
emit(CurtainModuleError(message: 'Failed to change timer control: $e'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _onChageCurCalibrationEvent(
|
|
||||||
CurCalibrationEvent event,
|
|
||||||
Emitter<CurtainModuleState> emit,
|
|
||||||
) async {
|
|
||||||
try {
|
|
||||||
await controlDeviceService.controlDevice(
|
|
||||||
deviceUuid: event.deviceId,
|
|
||||||
status: Status(code: 'cur_calibration', value: 'start'),
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
emit(CurtainModuleError(message: 'Failed to start calibration: $e'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _onChangeElecMachineryModeEvent(
|
|
||||||
ChangeElecMachineryModeEvent event,
|
|
||||||
Emitter<CurtainModuleState> emit,
|
|
||||||
) async {
|
|
||||||
try {
|
|
||||||
await controlDeviceService.controlDevice(
|
|
||||||
deviceUuid: event.deviceId,
|
|
||||||
status: Status(
|
|
||||||
code: 'elec_machinery_mode',
|
|
||||||
value: event.elecMachineryMode,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
emit(CurtainModuleError(message: 'Failed to change mode: $e'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _onChangeControlBackEvent(
|
|
||||||
ChangeControlBackEvent event,
|
|
||||||
Emitter<CurtainModuleState> emit,
|
|
||||||
) async {
|
|
||||||
try {
|
|
||||||
await controlDeviceService.controlDevice(
|
|
||||||
deviceUuid: event.deviceId,
|
|
||||||
status: Status(
|
|
||||||
code: 'control_back',
|
|
||||||
value: event.controlBack,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
emit(CurtainModuleError(message: 'Failed to change control back: $e'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _onChangeControlBackModeEvent(
|
|
||||||
ChangeControlBackModeEvent event,
|
|
||||||
Emitter<CurtainModuleState> emit,
|
|
||||||
) async {
|
|
||||||
try {
|
|
||||||
await controlDeviceService.controlDevice(
|
|
||||||
deviceUuid: event.deviceId,
|
|
||||||
status: Status(
|
|
||||||
code: 'control_back_mode',
|
|
||||||
value: event.controlBackMode,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
emit(CurtainModuleError(
|
|
||||||
message: 'Failed to change control back mode: $e'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FutureOr<void> _onFetchCurtainModuleBatchStatus(
|
|
||||||
CurtainModuleFetchBatchStatusEvent event,
|
|
||||||
Emitter<CurtainModuleState> emit,
|
|
||||||
) async {
|
|
||||||
emit(CurtainModuleLoading());
|
|
||||||
try {
|
|
||||||
final status =
|
|
||||||
await DevicesManagementApi().getBatchStatus(event.devicesIds);
|
|
||||||
|
|
||||||
final result = Map.fromEntries(
|
|
||||||
status.status.map((element) => MapEntry(element.code, element.value)),
|
|
||||||
);
|
|
||||||
|
|
||||||
emit(CurtainModuleStatusLoaded(
|
|
||||||
curtainModuleStatus: CurtainModuleStatusModel.fromJson(result),
|
|
||||||
));
|
|
||||||
|
|
||||||
Map<String, dynamic> statusMap = {};
|
|
||||||
final ref = FirebaseDatabase.instance
|
|
||||||
.ref('device-status/${event.devicesIds.first}');
|
|
||||||
final stream = ref.onValue;
|
|
||||||
|
|
||||||
stream.listen((DatabaseEvent DatabaseEvent) async {
|
|
||||||
if (DatabaseEvent.snapshot.value == null) return;
|
|
||||||
|
|
||||||
Map<dynamic, dynamic> usersMap =
|
|
||||||
DatabaseEvent.snapshot.value as Map<dynamic, dynamic>;
|
|
||||||
|
|
||||||
List<Status> statusList = [];
|
|
||||||
|
|
||||||
usersMap['status'].forEach((element) {
|
|
||||||
statusList
|
|
||||||
.add(Status(code: element['code'], value: element['value']));
|
|
||||||
});
|
|
||||||
|
|
||||||
statusMap = {
|
|
||||||
for (final element in statusList) element.code: element.value,
|
|
||||||
};
|
|
||||||
if (!isClosed) {
|
|
||||||
add(
|
|
||||||
ChangeCurtainModuleStatusEvent(
|
|
||||||
deviceId: event.devicesIds.first,
|
|
||||||
status: CurtainModuleStatusModel.fromJson(statusMap),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
emit(CurtainModuleError(message: e.toString()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _onSendCurtainBatchPercentToApiEvent(
|
|
||||||
SendCurtainBatchPercentToApiEvent event,
|
|
||||||
Emitter<CurtainModuleState> emit,
|
|
||||||
) async {
|
|
||||||
try {
|
|
||||||
await batchControlDevicesService.batchControlDevices(
|
|
||||||
uuids: event.devicesId,
|
|
||||||
code: event.status.code,
|
|
||||||
value: event.status.value,
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
emit(CurtainModuleError(message: 'Failed to send control command: $e'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _onOpenCurtainBatchEvent(
|
|
||||||
OpenCurtainBatchEvent event,
|
|
||||||
Emitter<CurtainModuleState> emit,
|
|
||||||
) async {
|
|
||||||
try {
|
|
||||||
await batchControlDevicesService.batchControlDevices(
|
|
||||||
uuids: event.devicesId,
|
|
||||||
code: 'control',
|
|
||||||
value: 'open',
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
emit(CurtainModuleError(message: 'Failed to open curtain: $e'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _onCloseCurtainBatchEvent(
|
|
||||||
CloseCurtainBatchEvent event,
|
|
||||||
Emitter<CurtainModuleState> emit,
|
|
||||||
) async {
|
|
||||||
try {
|
|
||||||
await batchControlDevicesService.batchControlDevices(
|
|
||||||
uuids: event.devicesId,
|
|
||||||
code: 'control',
|
|
||||||
value: 'close',
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
emit(CurtainModuleError(message: 'Failed to close curtain: $e'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _onStopCurtainBatchEvent(
|
|
||||||
StopCurtainBatchEvent event,
|
|
||||||
Emitter<CurtainModuleState> emit,
|
|
||||||
) async {
|
|
||||||
try {
|
|
||||||
await batchControlDevicesService.batchControlDevices(
|
|
||||||
uuids: event.devicesId,
|
|
||||||
code: 'control',
|
|
||||||
value: 'stop',
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
emit(CurtainModuleError(message: 'Failed to stop curtain: $e'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _onFactoryReset(
|
|
||||||
CurtainModuleFactoryReset event,
|
|
||||||
Emitter<CurtainModuleState> emit,
|
|
||||||
) async {
|
|
||||||
emit(CurtainModuleLoading());
|
|
||||||
try {
|
|
||||||
final response = await DevicesManagementApi().factoryReset(
|
|
||||||
event.factoryReset,
|
|
||||||
event.deviceId,
|
|
||||||
);
|
|
||||||
if (!response) {
|
|
||||||
emit(const CurtainModuleError(message: 'Failed'));
|
|
||||||
} else {
|
|
||||||
add(
|
|
||||||
FetchCurtainModuleStatusEvent(deviceId: event.deviceId),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
emit(CurtainModuleError(message: e.toString()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> close() async {
|
|
||||||
await _firebaseSubscription?.cancel();
|
|
||||||
return super.close();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,193 +0,0 @@
|
|||||||
part of 'curtain_module_bloc.dart';
|
|
||||||
|
|
||||||
sealed class CurtainModuleEvent extends Equatable {
|
|
||||||
const CurtainModuleEvent();
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<Object> get props => [];
|
|
||||||
}
|
|
||||||
|
|
||||||
class FetchCurtainModuleStatusEvent extends CurtainModuleEvent {
|
|
||||||
final String deviceId;
|
|
||||||
const FetchCurtainModuleStatusEvent({required this.deviceId});
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<Object> get props => [deviceId];
|
|
||||||
}
|
|
||||||
|
|
||||||
class SendCurtainPercentToApiEvent extends CurtainModuleEvent {
|
|
||||||
final String deviceId;
|
|
||||||
final Status status;
|
|
||||||
|
|
||||||
const SendCurtainPercentToApiEvent({
|
|
||||||
required this.deviceId,
|
|
||||||
required this.status,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<Object> get props => [deviceId, status];
|
|
||||||
}
|
|
||||||
|
|
||||||
class OpenCurtainEvent extends CurtainModuleEvent {
|
|
||||||
final String deviceId;
|
|
||||||
|
|
||||||
const OpenCurtainEvent({required this.deviceId});
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<Object> get props => [deviceId];
|
|
||||||
}
|
|
||||||
|
|
||||||
class CloseCurtainEvent extends CurtainModuleEvent {
|
|
||||||
final String deviceId;
|
|
||||||
|
|
||||||
const CloseCurtainEvent({required this.deviceId});
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<Object> get props => [deviceId];
|
|
||||||
}
|
|
||||||
|
|
||||||
class StopCurtainEvent extends CurtainModuleEvent {
|
|
||||||
final String deviceId;
|
|
||||||
|
|
||||||
const StopCurtainEvent({required this.deviceId});
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<Object> get props => [deviceId];
|
|
||||||
}
|
|
||||||
|
|
||||||
class ChangeTimerControlEvent extends CurtainModuleEvent {
|
|
||||||
final String deviceId;
|
|
||||||
final int timControl;
|
|
||||||
|
|
||||||
const ChangeTimerControlEvent({
|
|
||||||
required this.deviceId,
|
|
||||||
required this.timControl,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<Object> get props => [deviceId, timControl];
|
|
||||||
}
|
|
||||||
|
|
||||||
class CurCalibrationEvent extends CurtainModuleEvent {
|
|
||||||
final String deviceId;
|
|
||||||
|
|
||||||
const CurCalibrationEvent({
|
|
||||||
required this.deviceId,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<Object> get props => [deviceId];
|
|
||||||
}
|
|
||||||
|
|
||||||
class ChangeElecMachineryModeEvent extends CurtainModuleEvent {
|
|
||||||
final String deviceId;
|
|
||||||
final String elecMachineryMode;
|
|
||||||
|
|
||||||
const ChangeElecMachineryModeEvent({
|
|
||||||
required this.deviceId,
|
|
||||||
required this.elecMachineryMode,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<Object> get props => [deviceId, elecMachineryMode];
|
|
||||||
}
|
|
||||||
|
|
||||||
class ChangeControlBackEvent extends CurtainModuleEvent {
|
|
||||||
final String deviceId;
|
|
||||||
final String controlBack;
|
|
||||||
|
|
||||||
const ChangeControlBackEvent({
|
|
||||||
required this.deviceId,
|
|
||||||
required this.controlBack,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<Object> get props => [deviceId, controlBack];
|
|
||||||
}
|
|
||||||
|
|
||||||
class ChangeControlBackModeEvent extends CurtainModuleEvent {
|
|
||||||
final String deviceId;
|
|
||||||
final String controlBackMode;
|
|
||||||
|
|
||||||
const ChangeControlBackModeEvent({
|
|
||||||
required this.deviceId,
|
|
||||||
required this.controlBackMode,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<Object> get props => [deviceId, controlBackMode];
|
|
||||||
}
|
|
||||||
|
|
||||||
class ChangeCurtainModuleStatusEvent extends CurtainModuleEvent {
|
|
||||||
final String deviceId;
|
|
||||||
final CurtainModuleStatusModel status;
|
|
||||||
|
|
||||||
const ChangeCurtainModuleStatusEvent({
|
|
||||||
required this.deviceId,
|
|
||||||
required this.status,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<Object> get props => [deviceId, status];
|
|
||||||
}
|
|
||||||
|
|
||||||
///batch
|
|
||||||
class CurtainModuleFetchBatchStatusEvent extends CurtainModuleEvent {
|
|
||||||
final List<String> devicesIds;
|
|
||||||
|
|
||||||
const CurtainModuleFetchBatchStatusEvent(this.devicesIds);
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<Object> get props => [devicesIds];
|
|
||||||
}
|
|
||||||
|
|
||||||
class SendCurtainBatchPercentToApiEvent extends CurtainModuleEvent {
|
|
||||||
final List<String> devicesId;
|
|
||||||
final Status status;
|
|
||||||
|
|
||||||
const SendCurtainBatchPercentToApiEvent({
|
|
||||||
required this.devicesId,
|
|
||||||
required this.status,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<Object> get props => [devicesId, status];
|
|
||||||
}
|
|
||||||
|
|
||||||
class OpenCurtainBatchEvent extends CurtainModuleEvent {
|
|
||||||
final List<String> devicesId;
|
|
||||||
|
|
||||||
const OpenCurtainBatchEvent({required this.devicesId});
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<Object> get props => [devicesId];
|
|
||||||
}
|
|
||||||
|
|
||||||
class CloseCurtainBatchEvent extends CurtainModuleEvent {
|
|
||||||
final List<String> devicesId;
|
|
||||||
|
|
||||||
const CloseCurtainBatchEvent({required this.devicesId});
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<Object> get props => [devicesId];
|
|
||||||
}
|
|
||||||
|
|
||||||
class StopCurtainBatchEvent extends CurtainModuleEvent {
|
|
||||||
final List<String> devicesId;
|
|
||||||
|
|
||||||
const StopCurtainBatchEvent({required this.devicesId});
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<Object> get props => [devicesId];
|
|
||||||
}
|
|
||||||
|
|
||||||
class CurtainModuleFactoryReset extends CurtainModuleEvent {
|
|
||||||
final String deviceId;
|
|
||||||
final FactoryResetModel factoryReset;
|
|
||||||
|
|
||||||
const CurtainModuleFactoryReset(
|
|
||||||
{required this.deviceId, required this.factoryReset});
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<Object> get props => [deviceId, factoryReset];
|
|
||||||
}
|
|
@ -1,37 +0,0 @@
|
|||||||
part of 'curtain_module_bloc.dart';
|
|
||||||
|
|
||||||
sealed class CurtainModuleState extends Equatable {
|
|
||||||
const CurtainModuleState();
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<Object> get props => [];
|
|
||||||
}
|
|
||||||
|
|
||||||
class CurtainModuleInitial extends CurtainModuleState {}
|
|
||||||
|
|
||||||
class CurtainModuleLoading extends CurtainModuleState {}
|
|
||||||
|
|
||||||
class CurtainModuleError extends CurtainModuleState {
|
|
||||||
final String message;
|
|
||||||
const CurtainModuleError({required this.message});
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<Object> get props => [message];
|
|
||||||
}
|
|
||||||
|
|
||||||
class CurtainModuleStatusLoaded extends CurtainModuleState {
|
|
||||||
final CurtainModuleStatusModel curtainModuleStatus;
|
|
||||||
|
|
||||||
const CurtainModuleStatusLoaded({required this.curtainModuleStatus});
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<Object> get props => [curtainModuleStatus];
|
|
||||||
}
|
|
||||||
class CurtainModuleStatusUpdated extends CurtainModuleState {
|
|
||||||
final CurtainModuleStatusModel curtainModuleStatus;
|
|
||||||
|
|
||||||
const CurtainModuleStatusUpdated({required this.curtainModuleStatus});
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<Object> get props => [curtainModuleStatus];
|
|
||||||
}
|
|
@ -1,53 +0,0 @@
|
|||||||
|
|
||||||
enum CurtainModuleControl {
|
|
||||||
open,
|
|
||||||
close,
|
|
||||||
stop,
|
|
||||||
}
|
|
||||||
|
|
||||||
// enum CurtainControlBackMode {
|
|
||||||
// foward,
|
|
||||||
// backward,
|
|
||||||
// }
|
|
||||||
|
|
||||||
class CurtainModuleStatusModel {
|
|
||||||
CurtainModuleControl control;
|
|
||||||
int percentControl;
|
|
||||||
String curCalibration;
|
|
||||||
// CurtainControlBackMode controlBackmode;
|
|
||||||
int trTimeControl;
|
|
||||||
String elecMachineryMode;
|
|
||||||
String controlBack;
|
|
||||||
CurtainModuleStatusModel({
|
|
||||||
required this.control,
|
|
||||||
required this.percentControl,
|
|
||||||
required this.curCalibration,
|
|
||||||
// required this.controlBackmode,
|
|
||||||
required this.trTimeControl,
|
|
||||||
required this.controlBack,
|
|
||||||
required this.elecMachineryMode,
|
|
||||||
});
|
|
||||||
factory CurtainModuleStatusModel.zero() => CurtainModuleStatusModel(
|
|
||||||
control: CurtainModuleControl.stop,
|
|
||||||
percentControl: 0,
|
|
||||||
// controlBackmode: CurtainControlBackMode.foward,
|
|
||||||
curCalibration: '',
|
|
||||||
trTimeControl: 0,
|
|
||||||
controlBack: '',
|
|
||||||
elecMachineryMode: '',
|
|
||||||
);
|
|
||||||
|
|
||||||
factory CurtainModuleStatusModel.fromJson(Map<String, dynamic> json) {
|
|
||||||
return CurtainModuleStatusModel(
|
|
||||||
control: CurtainModuleControl.values.firstWhere(
|
|
||||||
(e) => e.toString() == json['control'] as String,
|
|
||||||
orElse: () => CurtainModuleControl.stop,
|
|
||||||
),
|
|
||||||
percentControl: json['percent_control'] as int? ?? 0,
|
|
||||||
curCalibration: json['cur_calibration'] as String? ?? '',
|
|
||||||
trTimeControl: json['tr_timecon'] as int? ?? 0,
|
|
||||||
elecMachineryMode: json['elec_machinery_mode'] as String? ?? '',
|
|
||||||
controlBack: json['control_back'] as String? ?? '',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,80 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
||||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart';
|
|
||||||
import 'package:syncrow_web/pages/device_managment/curtain_module/bloc/curtain_module_bloc.dart';
|
|
||||||
import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/curtain_movment_widget.dart';
|
|
||||||
import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart';
|
|
||||||
import 'package:syncrow_web/pages/device_managment/shared/icon_name_status_container.dart';
|
|
||||||
import 'package:syncrow_web/services/batch_control_devices_service.dart';
|
|
||||||
import 'package:syncrow_web/services/control_device_service.dart';
|
|
||||||
import 'package:syncrow_web/utils/color_manager.dart';
|
|
||||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
|
||||||
|
|
||||||
class CurtainModuleBatchView extends StatelessWidget {
|
|
||||||
final List<String> devicesIds;
|
|
||||||
const CurtainModuleBatchView({
|
|
||||||
super.key,
|
|
||||||
required this.devicesIds,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return BlocProvider(
|
|
||||||
create: (context) => CurtainModuleBloc(
|
|
||||||
controlDeviceService: RemoteControlDeviceService(),
|
|
||||||
batchControlDevicesService: RemoteBatchControlDevicesService())
|
|
||||||
..add(CurtainModuleFetchBatchStatusEvent(devicesIds)),
|
|
||||||
child: _buildStatusControls(context),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildStatusControls(BuildContext context) {
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.only(left: 30),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
ControlCurtainMovementWidget(
|
|
||||||
devicesId: devicesIds,
|
|
||||||
),
|
|
||||||
const SizedBox(
|
|
||||||
height: 10,
|
|
||||||
),
|
|
||||||
SizedBox(
|
|
||||||
height: 120,
|
|
||||||
// width: 350,
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
// Expanded(
|
|
||||||
// child:
|
|
||||||
FactoryResetWidget(
|
|
||||||
callFactoryReset: () {
|
|
||||||
context.read<CurtainModuleBloc>().add(
|
|
||||||
CurtainModuleFactoryReset(
|
|
||||||
deviceId: devicesIds.first,
|
|
||||||
factoryReset:
|
|
||||||
FactoryResetModel(devicesUuid: devicesIds),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
// ),
|
|
||||||
// Expanded(
|
|
||||||
// child: IconNameStatusContainer(
|
|
||||||
// isFullIcon: false,
|
|
||||||
// name: 'Firmware Update',
|
|
||||||
// icon: Assets.firmware,
|
|
||||||
// onTap: () {},
|
|
||||||
// status: false,
|
|
||||||
// textColor: ColorsManager.blackColor,
|
|
||||||
// ),
|
|
||||||
// )
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,120 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
||||||
import 'package:syncrow_web/pages/device_managment/curtain_module/bloc/curtain_module_bloc.dart';
|
|
||||||
import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/curtain_movment_widget.dart';
|
|
||||||
import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/prefrences_dialog.dart';
|
|
||||||
import 'package:syncrow_web/pages/device_managment/schedule_device/schedule_widgets/schedual_view.dart';
|
|
||||||
import 'package:syncrow_web/pages/device_managment/schedule_device/schedule_widgets/schedule_control_button.dart';
|
|
||||||
import 'package:syncrow_web/pages/device_managment/shared/icon_name_status_container.dart';
|
|
||||||
import 'package:syncrow_web/services/batch_control_devices_service.dart';
|
|
||||||
import 'package:syncrow_web/services/control_device_service.dart';
|
|
||||||
import 'package:syncrow_web/utils/color_manager.dart';
|
|
||||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
|
||||||
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
|
|
||||||
|
|
||||||
class CurtainModuleItems extends StatelessWidget with HelperResponsiveLayout {
|
|
||||||
final String deviceId;
|
|
||||||
const CurtainModuleItems({
|
|
||||||
super.key,
|
|
||||||
required this.deviceId,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return BlocProvider(
|
|
||||||
create: (context) => CurtainModuleBloc(
|
|
||||||
controlDeviceService: RemoteControlDeviceService(),
|
|
||||||
batchControlDevicesService: RemoteBatchControlDevicesService())
|
|
||||||
..add(FetchCurtainModuleStatusEvent(deviceId: deviceId)),
|
|
||||||
child: BlocBuilder<CurtainModuleBloc, CurtainModuleState>(
|
|
||||||
builder: (context, state) {
|
|
||||||
return _buildStatusControls(context);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildStatusControls(BuildContext context) {
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.only(left: 30),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
ControlCurtainMovementWidget(
|
|
||||||
devicesId: [deviceId],
|
|
||||||
),
|
|
||||||
const SizedBox(
|
|
||||||
height: 10,
|
|
||||||
),
|
|
||||||
SizedBox(
|
|
||||||
height: 140,
|
|
||||||
width: 350,
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: ScheduleControlButton(
|
|
||||||
onTap: () {
|
|
||||||
showDialog<void>(
|
|
||||||
context: context,
|
|
||||||
builder: (ctx) => BlocProvider.value(
|
|
||||||
value:
|
|
||||||
BlocProvider.of<CurtainModuleBloc>(context),
|
|
||||||
child: BuildScheduleView(
|
|
||||||
deviceUuid: deviceId,
|
|
||||||
category: 'CUR_2',
|
|
||||||
code: 'control',
|
|
||||||
|
|
||||||
),
|
|
||||||
));
|
|
||||||
},
|
|
||||||
mainText: '',
|
|
||||||
subtitle: 'Scheduling',
|
|
||||||
iconPath: Assets.scheduling,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(
|
|
||||||
width: 10,
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: BlocBuilder<CurtainModuleBloc, CurtainModuleState>(
|
|
||||||
builder: (context, state) {
|
|
||||||
if (state is CurtainModuleLoading) {
|
|
||||||
return const Center(
|
|
||||||
child: CircularProgressIndicator(),
|
|
||||||
);
|
|
||||||
} else if (state is CurtainModuleStatusLoaded) {
|
|
||||||
return IconNameStatusContainer(
|
|
||||||
isFullIcon: false,
|
|
||||||
name: 'Preferences',
|
|
||||||
icon: Assets.preferences,
|
|
||||||
onTap: () => showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (_) => BlocProvider.value(
|
|
||||||
value: context.read<CurtainModuleBloc>(),
|
|
||||||
child: CurtainModulePrefrencesDialog(
|
|
||||||
curtainModuleBloc:
|
|
||||||
context.watch<CurtainModuleBloc>(),
|
|
||||||
deviceId: deviceId,
|
|
||||||
curtainModuleStatusModel:
|
|
||||||
state.curtainModuleStatus,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
status: false,
|
|
||||||
textColor: ColorsManager.blackColor,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return const SizedBox();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,48 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
||||||
import 'package:syncrow_web/pages/device_managment/curtain_module/bloc/curtain_module_bloc.dart';
|
|
||||||
import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/accurate_dialog_widget.dart';
|
|
||||||
import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/calibrate_completed_dialog.dart';
|
|
||||||
import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/normal_text_body_for_dialog.dart';
|
|
||||||
|
|
||||||
class AccurteCalibratingDialog extends StatelessWidget {
|
|
||||||
final String deviceId;
|
|
||||||
final BuildContext parentContext;
|
|
||||||
const AccurteCalibratingDialog({
|
|
||||||
super.key,
|
|
||||||
required this.deviceId,
|
|
||||||
required this.parentContext,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(_) {
|
|
||||||
return AlertDialog(
|
|
||||||
contentPadding: EdgeInsets.zero,
|
|
||||||
content: AccurateDialogWidget(
|
|
||||||
title: 'Calibrating',
|
|
||||||
body: const NormalTextBodyForDialog(
|
|
||||||
title: '',
|
|
||||||
step1:
|
|
||||||
'Click Close Button to make the Curtain run to Full Close and Position.',
|
|
||||||
step2: 'click Next to complete the Calibration.',
|
|
||||||
),
|
|
||||||
leftOnTap: () => Navigator.of(parentContext).pop(),
|
|
||||||
rightOnTap: () {
|
|
||||||
parentContext.read<CurtainModuleBloc>().add(
|
|
||||||
CurCalibrationEvent(
|
|
||||||
deviceId: deviceId,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
Navigator.of(parentContext).pop();
|
|
||||||
showDialog(
|
|
||||||
context: parentContext,
|
|
||||||
builder: (_) => CalibrateCompletedDialog(
|
|
||||||
parentContext: parentContext,
|
|
||||||
deviceId: deviceId,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,40 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/accurate_calibrating_dialog.dart';
|
|
||||||
import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/accurate_dialog_widget.dart';
|
|
||||||
import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/normal_text_body_for_dialog.dart';
|
|
||||||
|
|
||||||
class AccurateCalibrationDialog extends StatelessWidget {
|
|
||||||
final String deviceId;
|
|
||||||
final BuildContext parentContext;
|
|
||||||
const AccurateCalibrationDialog({
|
|
||||||
super.key,
|
|
||||||
required this.deviceId,
|
|
||||||
required this.parentContext,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(_) {
|
|
||||||
return AlertDialog(
|
|
||||||
contentPadding: EdgeInsets.zero,
|
|
||||||
content: AccurateDialogWidget(
|
|
||||||
title: 'Accurate Calibration',
|
|
||||||
body: const NormalTextBodyForDialog(
|
|
||||||
title: 'Prepare Calibration:',
|
|
||||||
step1: 'Run The Curtain to the Fully Open Position,and pause.',
|
|
||||||
step2: 'click Next to Start accurate calibration.',
|
|
||||||
),
|
|
||||||
leftOnTap: () => Navigator.of(parentContext).pop(),
|
|
||||||
rightOnTap: () {
|
|
||||||
Navigator.of(parentContext).pop();
|
|
||||||
showDialog(
|
|
||||||
context: parentContext,
|
|
||||||
builder: (_) => AccurteCalibratingDialog(
|
|
||||||
deviceId: deviceId,
|
|
||||||
parentContext: parentContext,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,106 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:syncrow_web/utils/color_manager.dart';
|
|
||||||
|
|
||||||
class AccurateDialogWidget extends StatelessWidget {
|
|
||||||
final String title;
|
|
||||||
final Widget body;
|
|
||||||
final void Function() leftOnTap;
|
|
||||||
final void Function() rightOnTap;
|
|
||||||
const AccurateDialogWidget({
|
|
||||||
super.key,
|
|
||||||
required this.title,
|
|
||||||
required this.body,
|
|
||||||
required this.leftOnTap,
|
|
||||||
required this.rightOnTap,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return SizedBox(
|
|
||||||
height: 250,
|
|
||||||
width: 500,
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.all(10),
|
|
||||||
child: Text(
|
|
||||||
title,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 24,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: ColorsManager.dialogBlueTitle,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const Divider(
|
|
||||||
indent: 60,
|
|
||||||
endIndent: 60,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: body,
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
|
||||||
children: [
|
|
||||||
const Divider(),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: InkWell(
|
|
||||||
onTap: leftOnTap,
|
|
||||||
child: Container(
|
|
||||||
height: 40,
|
|
||||||
alignment: Alignment.center,
|
|
||||||
decoration: const BoxDecoration(
|
|
||||||
border: Border(
|
|
||||||
right: BorderSide(
|
|
||||||
color: ColorsManager.grayBorder,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: const Text(
|
|
||||||
'Cancel',
|
|
||||||
style: TextStyle(color: ColorsManager.grayBorder),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: InkWell(
|
|
||||||
onTap: rightOnTap,
|
|
||||||
child: Container(
|
|
||||||
height: 40,
|
|
||||||
alignment: Alignment.center,
|
|
||||||
decoration: const BoxDecoration(
|
|
||||||
border: Border(
|
|
||||||
right: BorderSide(
|
|
||||||
color: ColorsManager.grayBorder,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: const Text(
|
|
||||||
'Next',
|
|
||||||
style: TextStyle(
|
|
||||||
color: ColorsManager.blueColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,89 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
||||||
import 'package:flutter_svg/svg.dart';
|
|
||||||
import 'package:syncrow_web/pages/device_managment/curtain_module/bloc/curtain_module_bloc.dart';
|
|
||||||
import 'package:syncrow_web/utils/color_manager.dart';
|
|
||||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
|
||||||
|
|
||||||
class CalibrateCompletedDialog extends StatelessWidget {
|
|
||||||
final BuildContext parentContext;
|
|
||||||
final String deviceId;
|
|
||||||
const CalibrateCompletedDialog({
|
|
||||||
super.key,
|
|
||||||
required this.parentContext,
|
|
||||||
required this.deviceId,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(_) {
|
|
||||||
return AlertDialog(
|
|
||||||
contentPadding: EdgeInsets.zero,
|
|
||||||
content: SizedBox(
|
|
||||||
height: 250,
|
|
||||||
width: 400,
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.all(10),
|
|
||||||
child: Text(
|
|
||||||
'Calibration Completed',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 24,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: ColorsManager.dialogBlueTitle,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 5),
|
|
||||||
const Divider(
|
|
||||||
indent: 10,
|
|
||||||
endIndent: 10,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: SvgPicture.asset(Assets.completedDoneIcon),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
|
||||||
children: [
|
|
||||||
const Divider(
|
|
||||||
indent: 10,
|
|
||||||
endIndent: 10,
|
|
||||||
),
|
|
||||||
InkWell(
|
|
||||||
onTap: () {
|
|
||||||
parentContext.read<CurtainModuleBloc>().add(
|
|
||||||
FetchCurtainModuleStatusEvent(
|
|
||||||
deviceId: deviceId,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
Navigator.of(parentContext).pop();
|
|
||||||
Navigator.of(parentContext).pop();
|
|
||||||
},
|
|
||||||
child: Container(
|
|
||||||
height: 40,
|
|
||||||
width: double.infinity,
|
|
||||||
alignment: Alignment.center,
|
|
||||||
child: const Text(
|
|
||||||
'Close',
|
|
||||||
style: TextStyle(
|
|
||||||
color: ColorsManager.grayBorder,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,41 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_svg/svg.dart';
|
|
||||||
import 'package:syncrow_web/utils/color_manager.dart';
|
|
||||||
|
|
||||||
class CurtainActionWidget extends StatelessWidget {
|
|
||||||
final String icon;
|
|
||||||
final void Function() onTap;
|
|
||||||
const CurtainActionWidget({
|
|
||||||
super.key,
|
|
||||||
required this.icon,
|
|
||||||
required this.onTap,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return InkWell(
|
|
||||||
onTap: onTap,
|
|
||||||
child: ClipOval(
|
|
||||||
child: Container(
|
|
||||||
height: 60,
|
|
||||||
width: 60,
|
|
||||||
padding: const EdgeInsets.all(8),
|
|
||||||
color: ColorsManager.whiteColors,
|
|
||||||
child: ClipOval(
|
|
||||||
child: Container(
|
|
||||||
height: 60,
|
|
||||||
width: 60,
|
|
||||||
padding: const EdgeInsets.all(8),
|
|
||||||
color: ColorsManager.graysColor,
|
|
||||||
child: SvgPicture.asset(
|
|
||||||
icon,
|
|
||||||
width: 35,
|
|
||||||
height: 35,
|
|
||||||
fit: BoxFit.contain,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,219 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
||||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
|
|
||||||
import 'package:syncrow_web/pages/device_managment/curtain_module/bloc/curtain_module_bloc.dart';
|
|
||||||
import 'package:syncrow_web/pages/device_managment/curtain_module/models/curtain_module_model.dart';
|
|
||||||
import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/curtain_action_widget.dart';
|
|
||||||
import 'package:syncrow_web/pages/device_managment/shared/device_controls_container.dart';
|
|
||||||
import 'package:syncrow_web/utils/color_manager.dart';
|
|
||||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
|
||||||
|
|
||||||
class ControlCurtainMovementWidget extends StatelessWidget {
|
|
||||||
final List<String> devicesId;
|
|
||||||
const ControlCurtainMovementWidget({
|
|
||||||
super.key,
|
|
||||||
required this.devicesId,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return SizedBox(
|
|
||||||
width: 550,
|
|
||||||
child: DeviceControlsContainer(
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
CurtainActionWidget(
|
|
||||||
icon: Assets.openCurtain,
|
|
||||||
onTap: () {
|
|
||||||
if (devicesId.length == 1) {
|
|
||||||
context.read<CurtainModuleBloc>().add(
|
|
||||||
OpenCurtainEvent(deviceId: devicesId.first),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
context.read<CurtainModuleBloc>().add(
|
|
||||||
OpenCurtainBatchEvent(devicesId: devicesId),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
const SizedBox(
|
|
||||||
width: 30,
|
|
||||||
),
|
|
||||||
CurtainActionWidget(
|
|
||||||
icon: Assets.pauseCurtain,
|
|
||||||
onTap: () {
|
|
||||||
if (devicesId.length == 1) {
|
|
||||||
context.read<CurtainModuleBloc>().add(
|
|
||||||
StopCurtainEvent(deviceId: devicesId.first),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
context.read<CurtainModuleBloc>().add(
|
|
||||||
StopCurtainBatchEvent(devicesId: devicesId),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
const SizedBox(
|
|
||||||
width: 30,
|
|
||||||
),
|
|
||||||
CurtainActionWidget(
|
|
||||||
icon: Assets.closeCurtain,
|
|
||||||
onTap: () {
|
|
||||||
if (devicesId.length == 1) {
|
|
||||||
context.read<CurtainModuleBloc>().add(
|
|
||||||
CloseCurtainEvent(deviceId: devicesId.first),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
context.read<CurtainModuleBloc>().add(
|
|
||||||
CloseCurtainBatchEvent(devicesId: devicesId),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
BlocBuilder<CurtainModuleBloc, CurtainModuleState>(
|
|
||||||
builder: (context, state) {
|
|
||||||
if (state is CurtainModuleError) {
|
|
||||||
return Center(
|
|
||||||
child: Text(
|
|
||||||
state.message,
|
|
||||||
style: const TextStyle(
|
|
||||||
color: ColorsManager.minBlueDot,
|
|
||||||
fontSize: 16,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else if (state is CurtainModuleLoading) {
|
|
||||||
return const Center(
|
|
||||||
child: CircularProgressIndicator(
|
|
||||||
color: ColorsManager.minBlueDot,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else if (state is CurtainModuleInitial) {
|
|
||||||
return const Center(
|
|
||||||
child: Text(
|
|
||||||
'No data available',
|
|
||||||
style: TextStyle(
|
|
||||||
color: ColorsManager.minBlueDot,
|
|
||||||
fontSize: 16,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else if (state is CurtainModuleStatusLoaded) {
|
|
||||||
return CurtainSliderWidget(
|
|
||||||
status: state.curtainModuleStatus,
|
|
||||||
devicesId: devicesId,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return const Center(
|
|
||||||
child: Text(
|
|
||||||
'Unknown state',
|
|
||||||
style: TextStyle(
|
|
||||||
color: ColorsManager.minBlueDot,
|
|
||||||
fontSize: 16,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class CurtainSliderWidget extends StatefulWidget {
|
|
||||||
final CurtainModuleStatusModel status;
|
|
||||||
final List<String> devicesId;
|
|
||||||
|
|
||||||
const CurtainSliderWidget({
|
|
||||||
super.key,
|
|
||||||
required this.status,
|
|
||||||
required this.devicesId,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<CurtainSliderWidget> createState() => _CurtainSliderWidgetState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _CurtainSliderWidgetState extends State<CurtainSliderWidget> {
|
|
||||||
double? _localValue; // For temporary drag state
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
// If user is dragging, use local value. Otherwise, use Firebase-synced state
|
|
||||||
final double currentSliderValue =
|
|
||||||
_localValue ?? widget.status.percentControl / 100;
|
|
||||||
|
|
||||||
return Column(
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'${(currentSliderValue * 100).round()}%',
|
|
||||||
style: const TextStyle(
|
|
||||||
color: ColorsManager.minBlueDot,
|
|
||||||
fontSize: 25,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Slider(
|
|
||||||
value: currentSliderValue,
|
|
||||||
min: 0,
|
|
||||||
max: 1,
|
|
||||||
divisions: 10, // 10% step
|
|
||||||
activeColor: ColorsManager.minBlueDot,
|
|
||||||
thumbColor: ColorsManager.primaryColor,
|
|
||||||
inactiveColor: ColorsManager.whiteColors,
|
|
||||||
|
|
||||||
// Start dragging — use local control
|
|
||||||
onChangeStart: (_) {
|
|
||||||
setState(() {
|
|
||||||
_localValue = currentSliderValue;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
// While dragging — update temporary value
|
|
||||||
onChanged: (value) {
|
|
||||||
final steppedValue = (value * 10).roundToDouble() / 10;
|
|
||||||
setState(() {
|
|
||||||
_localValue = steppedValue;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
// On release — send API and return to Firebase-controlled state
|
|
||||||
onChangeEnd: (value) {
|
|
||||||
final int targetPercent = (value * 100).round();
|
|
||||||
|
|
||||||
if (widget.devicesId.length == 1) {
|
|
||||||
context.read<CurtainModuleBloc>().add(
|
|
||||||
SendCurtainPercentToApiEvent(
|
|
||||||
deviceId: widget.devicesId.first,
|
|
||||||
status: Status(
|
|
||||||
code: 'percent_control',
|
|
||||||
value: targetPercent,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
context.read<CurtainModuleBloc>().add(
|
|
||||||
SendCurtainBatchPercentToApiEvent(
|
|
||||||
devicesId: widget.devicesId,
|
|
||||||
status: Status(
|
|
||||||
code: 'percent_control',
|
|
||||||
value: targetPercent,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Revert back to Firebase-synced stream
|
|
||||||
setState(() {
|
|
||||||
_localValue = null;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,86 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:syncrow_web/utils/color_manager.dart';
|
|
||||||
|
|
||||||
class NormalTextBodyForDialog extends StatelessWidget {
|
|
||||||
final String title;
|
|
||||||
final String step1;
|
|
||||||
final String step2;
|
|
||||||
|
|
||||||
const NormalTextBodyForDialog({
|
|
||||||
super.key,
|
|
||||||
required this.title,
|
|
||||||
required this.step1,
|
|
||||||
required this.step2,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Padding(
|
|
||||||
padding: EdgeInsetsGeometry.only(left: 15),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
if (title.isEmpty)
|
|
||||||
const SizedBox()
|
|
||||||
else
|
|
||||||
Expanded(
|
|
||||||
child: Text(
|
|
||||||
title,
|
|
||||||
style: const TextStyle(
|
|
||||||
color: ColorsManager.grayColor,
|
|
||||||
fontSize: 17,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: Row(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
const SizedBox(
|
|
||||||
width: 10,
|
|
||||||
),
|
|
||||||
const Text('1. ',
|
|
||||||
style: TextStyle(
|
|
||||||
color: ColorsManager.grayColor,
|
|
||||||
fontSize: 17,
|
|
||||||
)),
|
|
||||||
SizedBox(
|
|
||||||
width: 450,
|
|
||||||
child: Text(
|
|
||||||
step1,
|
|
||||||
style: const TextStyle(
|
|
||||||
color: ColorsManager.grayColor,
|
|
||||||
fontSize: 17,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: Row(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
const SizedBox(
|
|
||||||
width: 10,
|
|
||||||
),
|
|
||||||
const Text('2. ',
|
|
||||||
style: TextStyle(
|
|
||||||
color: ColorsManager.grayColor,
|
|
||||||
fontSize: 17,
|
|
||||||
)),
|
|
||||||
Text(
|
|
||||||
step2,
|
|
||||||
style: const TextStyle(
|
|
||||||
color: ColorsManager.grayColor,
|
|
||||||
fontSize: 17,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,27 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
import 'package:syncrow_web/utils/color_manager.dart';
|
|
||||||
|
|
||||||
class NumberInputField extends StatelessWidget {
|
|
||||||
final TextEditingController controller;
|
|
||||||
|
|
||||||
const NumberInputField({super.key, required this.controller});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return TextField(
|
|
||||||
controller: controller,
|
|
||||||
keyboardType: TextInputType.number,
|
|
||||||
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
|
|
||||||
decoration: const InputDecoration(
|
|
||||||
border: InputBorder.none,
|
|
||||||
isDense: true,
|
|
||||||
contentPadding: EdgeInsets.zero,
|
|
||||||
),
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 15,
|
|
||||||
color: ColorsManager.blackColor,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,79 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_svg/svg.dart';
|
|
||||||
import 'package:syncrow_web/utils/color_manager.dart';
|
|
||||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
|
||||||
import 'package:syncrow_web/web_layout/default_container.dart';
|
|
||||||
|
|
||||||
class PrefReversCardWidget extends StatelessWidget {
|
|
||||||
final void Function() onTap;
|
|
||||||
final String title;
|
|
||||||
final String body;
|
|
||||||
const PrefReversCardWidget({
|
|
||||||
super.key,
|
|
||||||
required this.title,
|
|
||||||
required this.body,
|
|
||||||
required this.onTap,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return DefaultContainer(
|
|
||||||
padding: const EdgeInsets.all(18),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
flex: 8,
|
|
||||||
child: Text(
|
|
||||||
title,
|
|
||||||
style: const TextStyle(
|
|
||||||
color: ColorsManager.grayBorder,
|
|
||||||
fontSize: 15,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(
|
|
||||||
width: 20,
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
flex: 2,
|
|
||||||
child: InkWell(
|
|
||||||
onTap: onTap,
|
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.all(3),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: ColorsManager.whiteColors,
|
|
||||||
borderRadius: const BorderRadius.horizontal(
|
|
||||||
left: Radius.circular(10),
|
|
||||||
right: Radius.circular(10)),
|
|
||||||
border: Border.all(color: ColorsManager.grayBorder)),
|
|
||||||
child: SvgPicture.asset(
|
|
||||||
Assets.reverseArrows,
|
|
||||||
height: 15,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
SizedBox(
|
|
||||||
width: 100,
|
|
||||||
child: Text(
|
|
||||||
body,
|
|
||||||
style: const TextStyle(
|
|
||||||
color: ColorsManager.blackColor,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
fontSize: 18,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,149 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
||||||
import 'package:syncrow_web/pages/device_managment/curtain_module/bloc/curtain_module_bloc.dart';
|
|
||||||
import 'package:syncrow_web/pages/device_managment/curtain_module/models/curtain_module_model.dart';
|
|
||||||
import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/accurate_calibration_dialog.dart';
|
|
||||||
import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/pref_revers_card_widget.dart';
|
|
||||||
import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/quick_calibration_dialog.dart';
|
|
||||||
import 'package:syncrow_web/utils/color_manager.dart';
|
|
||||||
import 'package:syncrow_web/web_layout/default_container.dart';
|
|
||||||
|
|
||||||
class CurtainModulePrefrencesDialog extends StatelessWidget {
|
|
||||||
final CurtainModuleStatusModel curtainModuleStatusModel;
|
|
||||||
final String deviceId;
|
|
||||||
final CurtainModuleBloc curtainModuleBloc;
|
|
||||||
const CurtainModulePrefrencesDialog({
|
|
||||||
super.key,
|
|
||||||
required this.curtainModuleStatusModel,
|
|
||||||
required this.deviceId,
|
|
||||||
required this.curtainModuleBloc,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(_) {
|
|
||||||
return AlertDialog(
|
|
||||||
backgroundColor: ColorsManager.CircleImageBackground,
|
|
||||||
contentPadding: const EdgeInsets.all(20),
|
|
||||||
title: Center(
|
|
||||||
child: Text(
|
|
||||||
'Preferences',
|
|
||||||
style: TextStyle(
|
|
||||||
color: ColorsManager.dialogBlueTitle,
|
|
||||||
fontSize: 24,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
)),
|
|
||||||
content: BlocBuilder<CurtainModuleBloc, CurtainModuleState>(
|
|
||||||
bloc: curtainModuleBloc,
|
|
||||||
builder: (context, state) {
|
|
||||||
if (state is CurtainModuleLoading) {
|
|
||||||
return const Center(
|
|
||||||
child: CircularProgressIndicator(),
|
|
||||||
);
|
|
||||||
} else if (state is CurtainModuleStatusLoaded) {
|
|
||||||
return SizedBox(
|
|
||||||
height: 300,
|
|
||||||
width: 400,
|
|
||||||
child: GridView(
|
|
||||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
|
||||||
crossAxisCount: 2,
|
|
||||||
childAspectRatio: 1.5,
|
|
||||||
mainAxisSpacing: 10,
|
|
||||||
crossAxisSpacing: 10,
|
|
||||||
),
|
|
||||||
children: [
|
|
||||||
PrefReversCardWidget(
|
|
||||||
title: state.curtainModuleStatus.controlBack,
|
|
||||||
body: 'Motor Steering',
|
|
||||||
onTap: () {
|
|
||||||
context.read<CurtainModuleBloc>().add(
|
|
||||||
ChangeControlBackEvent(
|
|
||||||
deviceId: deviceId,
|
|
||||||
controlBack:
|
|
||||||
state.curtainModuleStatus.controlBack ==
|
|
||||||
'forward'
|
|
||||||
? 'back'
|
|
||||||
: 'forward',
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
PrefReversCardWidget(
|
|
||||||
title: formatDeviceType(
|
|
||||||
state.curtainModuleStatus.elecMachineryMode),
|
|
||||||
body: 'Motor Mode',
|
|
||||||
onTap: () => context.read<CurtainModuleBloc>().add(
|
|
||||||
ChangeElecMachineryModeEvent(
|
|
||||||
deviceId: deviceId,
|
|
||||||
elecMachineryMode:
|
|
||||||
state.curtainModuleStatus.elecMachineryMode ==
|
|
||||||
'dry_contact'
|
|
||||||
? 'strong_power'
|
|
||||||
: 'dry_contact',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
DefaultContainer(
|
|
||||||
padding: const EdgeInsets.all(12),
|
|
||||||
child: InkWell(
|
|
||||||
onTap: () => showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (_) => AccurateCalibrationDialog(
|
|
||||||
deviceId: deviceId,
|
|
||||||
parentContext: context,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: const Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
|
||||||
children: [
|
|
||||||
Text('Accurte Calibration',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 18,
|
|
||||||
color: ColorsManager.blackColor,
|
|
||||||
)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
DefaultContainer(
|
|
||||||
padding: const EdgeInsets.all(12),
|
|
||||||
child: InkWell(
|
|
||||||
onTap: () => showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (_) => QuickCalibrationDialog(
|
|
||||||
timControl: state.curtainModuleStatus.trTimeControl,
|
|
||||||
deviceId: deviceId,
|
|
||||||
parentContext: context),
|
|
||||||
),
|
|
||||||
child: const Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
|
||||||
children: [
|
|
||||||
Text('Quick Calibration',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 18,
|
|
||||||
color: ColorsManager.blackColor,
|
|
||||||
)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return const SizedBox();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
String formatDeviceType(String raw) {
|
|
||||||
return raw
|
|
||||||
.split('_')
|
|
||||||
.map((word) => word.isNotEmpty
|
|
||||||
? '${word[0].toUpperCase()}${word.substring(1)}'
|
|
||||||
: '')
|
|
||||||
.join(' ');
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,144 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
||||||
import 'package:syncrow_web/pages/device_managment/curtain_module/bloc/curtain_module_bloc.dart';
|
|
||||||
import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/accurate_dialog_widget.dart';
|
|
||||||
import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/calibrate_completed_dialog.dart';
|
|
||||||
import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/number_input_textfield.dart';
|
|
||||||
import 'package:syncrow_web/utils/color_manager.dart';
|
|
||||||
|
|
||||||
class QuickCalibratingDialog extends StatefulWidget {
|
|
||||||
final int timControl;
|
|
||||||
final String deviceId;
|
|
||||||
final BuildContext parentContext;
|
|
||||||
const QuickCalibratingDialog({
|
|
||||||
super.key,
|
|
||||||
required this.timControl,
|
|
||||||
required this.deviceId,
|
|
||||||
required this.parentContext,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<QuickCalibratingDialog> createState() => _QuickCalibratingDialogState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _QuickCalibratingDialogState extends State<QuickCalibratingDialog> {
|
|
||||||
late TextEditingController _controller;
|
|
||||||
String? _errorText;
|
|
||||||
|
|
||||||
void _onRightTap() {
|
|
||||||
final value = int.tryParse(_controller.text);
|
|
||||||
|
|
||||||
if (value == null || value < 10 || value > 120) {
|
|
||||||
setState(() {
|
|
||||||
_errorText = 'Number should be between 10 and 120';
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setState(() {
|
|
||||||
_errorText = null;
|
|
||||||
});
|
|
||||||
widget.parentContext.read<CurtainModuleBloc>().add(
|
|
||||||
ChangeTimerControlEvent(
|
|
||||||
deviceId: widget.deviceId,
|
|
||||||
timControl: value,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
Navigator.of(widget.parentContext).pop();
|
|
||||||
showDialog(
|
|
||||||
context: widget.parentContext,
|
|
||||||
builder: (_) => CalibrateCompletedDialog(
|
|
||||||
parentContext: widget.parentContext,
|
|
||||||
deviceId: widget.deviceId,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
_controller = TextEditingController(text: widget.timControl.toString());
|
|
||||||
super.initState();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(_) {
|
|
||||||
return AlertDialog(
|
|
||||||
contentPadding: EdgeInsets.zero,
|
|
||||||
content: AccurateDialogWidget(
|
|
||||||
title: 'Calibrating',
|
|
||||||
body: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
const Expanded(
|
|
||||||
child: Align(
|
|
||||||
alignment: Alignment.center,
|
|
||||||
child: Padding(
|
|
||||||
padding: EdgeInsets.only(right: 75),
|
|
||||||
child: Text(
|
|
||||||
'1.please Enter the Travel Time:',
|
|
||||||
style: TextStyle(color: ColorsManager.lightGrayColor),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: Align(
|
|
||||||
alignment: Alignment.center,
|
|
||||||
child: Container(
|
|
||||||
width: 110,
|
|
||||||
padding: const EdgeInsets.all(5),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: ColorsManager.whiteColors,
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: NumberInputField(controller: _controller),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: Text(
|
|
||||||
'seconds',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 12,
|
|
||||||
color: ColorsManager.dialogBlueTitle,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (_errorText != null)
|
|
||||||
Expanded(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.only(top: 8.0),
|
|
||||||
child: Text(
|
|
||||||
_errorText!,
|
|
||||||
style: const TextStyle(
|
|
||||||
color: ColorsManager.red,
|
|
||||||
fontSize: 14,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const Expanded(
|
|
||||||
child: Align(
|
|
||||||
alignment: Alignment.center,
|
|
||||||
child: Text(
|
|
||||||
'2.click Next to Complete the calibration',
|
|
||||||
style: TextStyle(color: ColorsManager.lightGrayColor),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
leftOnTap: () => Navigator.of(widget.parentContext).pop(),
|
|
||||||
rightOnTap: _onRightTap,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,44 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/accurate_dialog_widget.dart';
|
|
||||||
import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/normal_text_body_for_dialog.dart';
|
|
||||||
import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/quick_calibrating_dialog.dart';
|
|
||||||
|
|
||||||
class QuickCalibrationDialog extends StatelessWidget {
|
|
||||||
final int timControl;
|
|
||||||
final String deviceId;
|
|
||||||
final BuildContext parentContext;
|
|
||||||
const QuickCalibrationDialog({
|
|
||||||
super.key,
|
|
||||||
required this.timControl,
|
|
||||||
required this.deviceId,
|
|
||||||
required this.parentContext,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(_) {
|
|
||||||
return AlertDialog(
|
|
||||||
contentPadding: EdgeInsets.zero,
|
|
||||||
content: AccurateDialogWidget(
|
|
||||||
title: 'Quick Calibration',
|
|
||||||
body: const NormalTextBodyForDialog(
|
|
||||||
title: 'Prepare Calibration:',
|
|
||||||
step1:
|
|
||||||
'Confirm that the curtain is in the fully closed and suspended state.',
|
|
||||||
step2: 'click Next to Start calibration.',
|
|
||||||
),
|
|
||||||
leftOnTap: () => Navigator.of(parentContext).pop(),
|
|
||||||
rightOnTap: () {
|
|
||||||
Navigator.of(parentContext).pop();
|
|
||||||
showDialog(
|
|
||||||
context: parentContext,
|
|
||||||
builder: (_) => QuickCalibratingDialog(
|
|
||||||
timControl: timControl,
|
|
||||||
deviceId: deviceId,
|
|
||||||
parentContext: parentContext,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,10 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
||||||
import 'package:flutter_svg/svg.dart';
|
import 'package:flutter_svg/svg.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart';
|
|
||||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
|
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/device_setting/bloc/setting_bloc_bloc.dart';
|
|
||||||
import 'package:syncrow_web/pages/device_managment/device_setting/settings_model/device_info_model.dart';
|
import 'package:syncrow_web/pages/device_managment/device_setting/settings_model/device_info_model.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/device_setting/settings_model/sub_space_model.dart';
|
import 'package:syncrow_web/pages/device_managment/device_setting/settings_model/sub_space_model.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/device_setting/sub_space_dialog.dart';
|
import 'package:syncrow_web/pages/device_managment/device_setting/sub_space_dialog.dart';
|
||||||
@ -19,14 +16,11 @@ class DeviceManagementContent extends StatelessWidget {
|
|||||||
required this.device,
|
required this.device,
|
||||||
required this.subSpaces,
|
required this.subSpaces,
|
||||||
required this.deviceInfo,
|
required this.deviceInfo,
|
||||||
required this.deviceManagementBloc,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
final AllDevicesModel device;
|
final AllDevicesModel device;
|
||||||
final List<SubSpaceModel> subSpaces;
|
final List<SubSpaceModel> subSpaces;
|
||||||
final DeviceInfoModel deviceInfo;
|
final DeviceInfoModel deviceInfo;
|
||||||
final DeviceManagementBloc deviceManagementBloc;
|
|
||||||
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -72,30 +66,14 @@ class DeviceManagementContent extends StatelessWidget {
|
|||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(10.0),
|
padding: const EdgeInsets.all(10.0),
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: () async {
|
onTap: () {
|
||||||
final selectedSubSpace = await showSubSpaceDialog(
|
showSubSpaceDialog(
|
||||||
context,
|
context,
|
||||||
communityUuid: device.community!.uuid!,
|
communityUuid: device.community!.uuid!,
|
||||||
spaceUuid: device.spaces!.first.uuid!,
|
spaceUuid: device.spaces!.first.uuid!,
|
||||||
subSpaces: subSpaces,
|
subSpaces: subSpaces,
|
||||||
selected: deviceInfo.subspace.uuid,
|
selected: device.subspace!.uuid,
|
||||||
);
|
);
|
||||||
if (selectedSubSpace != null) {
|
|
||||||
Future.delayed(const Duration(milliseconds: 500), () {
|
|
||||||
context.read<SettingDeviceBloc>().add(
|
|
||||||
SettingBlocAssignRoom(
|
|
||||||
communityUuid: device.community!.uuid!,
|
|
||||||
spaceUuid: device.spaces!.first.uuid!,
|
|
||||||
subSpaceUuid: selectedSubSpace.id ?? '',
|
|
||||||
),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
deviceManagementBloc.add(UpdateSubSpaceName(
|
|
||||||
subspaceId: selectedSubSpace.id!,
|
|
||||||
deviceId: device.uuid!,
|
|
||||||
newSubSpaceName: selectedSubSpace.name ?? ''));
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
child: infoRow(
|
child: infoRow(
|
||||||
label: 'Sub-Space:',
|
label: 'Sub-Space:',
|
||||||
|
@ -1,15 +1,13 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/rendering.dart';
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:flutter_svg/flutter_svg.dart';
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart';
|
|
||||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
|
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/device_setting/bloc/setting_bloc_bloc.dart';
|
|
||||||
import 'package:syncrow_web/pages/device_managment/device_setting/bloc/setting_bloc_state.dart';
|
|
||||||
import 'package:syncrow_web/pages/device_managment/device_setting/device_icon_type_helper.dart';
|
import 'package:syncrow_web/pages/device_managment/device_setting/device_icon_type_helper.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/device_setting/device_management_content.dart';
|
import 'package:syncrow_web/pages/device_managment/device_setting/device_management_content.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/device_setting/remove_device_widget.dart';
|
import 'package:syncrow_web/pages/device_managment/device_setting/remove_device_widget.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/device_setting/settings_model/device_info_model.dart';
|
import 'package:syncrow_web/pages/device_managment/device_setting/settings_model/device_info_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/device_managment/device_setting/bloc/setting_bloc_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/device_managment/device_setting/bloc/setting_bloc_state.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/device_setting/settings_model/sub_space_model.dart';
|
import 'package:syncrow_web/pages/device_managment/device_setting/settings_model/sub_space_model.dart';
|
||||||
import 'package:syncrow_web/utils/color_manager.dart';
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||||
@ -19,13 +17,7 @@ import 'package:syncrow_web/web_layout/default_container.dart';
|
|||||||
class DeviceSettingsPanel extends StatelessWidget {
|
class DeviceSettingsPanel extends StatelessWidget {
|
||||||
final VoidCallback? onClose;
|
final VoidCallback? onClose;
|
||||||
final AllDevicesModel device;
|
final AllDevicesModel device;
|
||||||
final DeviceManagementBloc deviceManagementBloc;
|
const DeviceSettingsPanel({super.key, this.onClose, required this.device});
|
||||||
const DeviceSettingsPanel({
|
|
||||||
super.key,
|
|
||||||
this.onClose,
|
|
||||||
required this.device,
|
|
||||||
required this.deviceManagementBloc,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -142,14 +134,8 @@ class DeviceSettingsPanel extends StatelessWidget {
|
|||||||
onFieldSubmitted: (value) {
|
onFieldSubmitted: (value) {
|
||||||
_bloc.add(const ChangeNameEvent(
|
_bloc.add(const ChangeNameEvent(
|
||||||
value: false));
|
value: false));
|
||||||
deviceManagementBloc
|
|
||||||
..add(UpdateDeviceName(
|
|
||||||
deviceId: device.uuid!,
|
|
||||||
newName: _bloc
|
|
||||||
.nameController
|
|
||||||
.text))..add(ResetSelectedDevices());
|
|
||||||
},
|
},
|
||||||
decoration:const InputDecoration(
|
decoration: InputDecoration(
|
||||||
isDense: true,
|
isDense: true,
|
||||||
contentPadding: EdgeInsets.zero,
|
contentPadding: EdgeInsets.zero,
|
||||||
border: InputBorder.none,
|
border: InputBorder.none,
|
||||||
@ -204,7 +190,6 @@ class DeviceSettingsPanel extends StatelessWidget {
|
|||||||
device: device,
|
device: device,
|
||||||
subSpaces: subSpaces.cast<SubSpaceModel>(),
|
subSpaces: subSpaces.cast<SubSpaceModel>(),
|
||||||
deviceInfo: deviceInfo,
|
deviceInfo: deviceInfo,
|
||||||
deviceManagementBloc: deviceManagementBloc,
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 32),
|
const SizedBox(height: 32),
|
||||||
RemoveDeviceWidget(bloc: _bloc),
|
RemoveDeviceWidget(bloc: _bloc),
|
||||||
|
@ -9,11 +9,13 @@ import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
|||||||
class SubSpaceDialog extends StatefulWidget {
|
class SubSpaceDialog extends StatefulWidget {
|
||||||
final List<SubSpaceModel> subSpaces;
|
final List<SubSpaceModel> subSpaces;
|
||||||
final String? selected;
|
final String? selected;
|
||||||
|
final void Function(SubSpaceModel?) onConfirmed;
|
||||||
|
|
||||||
const SubSpaceDialog({
|
const SubSpaceDialog({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.subSpaces,
|
required this.subSpaces,
|
||||||
this.selected,
|
this.selected,
|
||||||
|
required this.onConfirmed,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -84,21 +86,30 @@ class _SubSpaceDialogState extends State<SubSpaceDialog> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<SubSpaceModel?> showSubSpaceDialog(
|
void showSubSpaceDialog(
|
||||||
BuildContext context, {
|
BuildContext context, {
|
||||||
required List<SubSpaceModel> subSpaces,
|
required List<SubSpaceModel> subSpaces,
|
||||||
String? selected,
|
String? selected,
|
||||||
required String communityUuid,
|
required String communityUuid,
|
||||||
required String spaceUuid,
|
required String spaceUuid,
|
||||||
}) {
|
}) {
|
||||||
return showDialog<SubSpaceModel>(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (ctx) => BlocProvider.value(
|
barrierDismissible: true,
|
||||||
value: BlocProvider.of<SettingDeviceBloc>(context),
|
builder: (ctx) => SubSpaceDialog(
|
||||||
child: SubSpaceDialog(
|
|
||||||
subSpaces: subSpaces,
|
subSpaces: subSpaces,
|
||||||
selected: selected,
|
selected: selected,
|
||||||
),
|
onConfirmed: (selectedModel) {
|
||||||
|
if (selectedModel != null) {
|
||||||
|
context.read<SettingDeviceBloc>().add(
|
||||||
|
SettingBlocAssignRoom(
|
||||||
|
communityUuid: communityUuid,
|
||||||
|
spaceUuid: spaceUuid,
|
||||||
|
subSpaceUuid: selectedModel.id ?? '',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/device_managment/device_setting/bloc/setting_bloc_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/device_setting/settings_model/sub_space_model.dart';
|
import 'package:syncrow_web/pages/device_managment/device_setting/settings_model/sub_space_model.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/device_setting/sub_space_dialog.dart';
|
import 'package:syncrow_web/pages/device_managment/device_setting/sub_space_dialog.dart';
|
||||||
import 'package:syncrow_web/utils/color_manager.dart';
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
@ -62,10 +64,9 @@ class SubSpaceDialogButtons extends StatelessWidget {
|
|||||||
final selectedModel = widget.subSpaces.firstWhere(
|
final selectedModel = widget.subSpaces.firstWhere(
|
||||||
(space) => space.id == _selectedId,
|
(space) => space.id == _selectedId,
|
||||||
orElse: () =>
|
orElse: () =>
|
||||||
SubSpaceModel(id: null, name: '', devices: []),
|
SubSpaceModel(id: null, name: '', devices: []));
|
||||||
);
|
widget.onConfirmed(selectedModel);
|
||||||
Navigator.of(context)
|
Navigator.of(context).pop();
|
||||||
.pop(selectedModel);
|
|
||||||
},
|
},
|
||||||
child: Text(
|
child: Text(
|
||||||
'Confirm',
|
'Confirm',
|
||||||
@ -83,3 +84,31 @@ class SubSpaceDialogButtons extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void showSubSpaceDialog(
|
||||||
|
BuildContext context, {
|
||||||
|
required List<SubSpaceModel> subSpaces,
|
||||||
|
String? selected,
|
||||||
|
required String communityUuid,
|
||||||
|
required String spaceUuid,
|
||||||
|
}) {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
barrierDismissible: true,
|
||||||
|
builder: (ctx) => SubSpaceDialog(
|
||||||
|
subSpaces: subSpaces,
|
||||||
|
selected: selected,
|
||||||
|
onConfirmed: (selectedModel) {
|
||||||
|
if (selectedModel != null) {
|
||||||
|
context.read<SettingDeviceBloc>().add(
|
||||||
|
SettingBlocAssignRoom(
|
||||||
|
communityUuid: communityUuid,
|
||||||
|
spaceUuid: spaceUuid,
|
||||||
|
subSpaceUuid: selectedModel.id ?? '',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
// ignore_for_file: invalid_use_of_visible_for_testing_member
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:firebase_database/firebase_database.dart';
|
import 'package:firebase_database/firebase_database.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
@ -14,38 +16,45 @@ class DoorLockBloc extends Bloc<DoorLockEvent, DoorLockState> {
|
|||||||
|
|
||||||
DoorLockBloc({required this.deviceId}) : super(DoorLockInitial()) {
|
DoorLockBloc({required this.deviceId}) : super(DoorLockInitial()) {
|
||||||
on<DoorLockFetchStatus>(_onFetchDeviceStatus);
|
on<DoorLockFetchStatus>(_onFetchDeviceStatus);
|
||||||
|
//on<DoorLockControl>(_onDoorLockControl);
|
||||||
on<UpdateLockEvent>(_updateLock);
|
on<UpdateLockEvent>(_updateLock);
|
||||||
on<DoorLockFactoryReset>(_onFactoryReset);
|
on<DoorLockFactoryReset>(_onFactoryReset);
|
||||||
on<StatusUpdated>(_onStatusUpdated);
|
on<StatusUpdated>(_onStatusUpdated);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _listenToChanges(String deviceId) {
|
_listenToChanges(deviceId) {
|
||||||
try {
|
try {
|
||||||
final ref = FirebaseDatabase.instance.ref('device-status/$deviceId');
|
DatabaseReference ref =
|
||||||
ref.onValue.listen((event) {
|
FirebaseDatabase.instance.ref('device-status/$deviceId');
|
||||||
final data = event.snapshot.value;
|
Stream<DatabaseEvent> stream = ref.onValue;
|
||||||
if (data is Map) {
|
|
||||||
final statusData = data['status'] as List<dynamic>? ?? [];
|
|
||||||
final statusList = statusData.map((item) {
|
|
||||||
return Status(code: item['code'], value: item['value']);
|
|
||||||
}).toList();
|
|
||||||
|
|
||||||
final model =
|
stream.listen((DatabaseEvent event) {
|
||||||
DoorLockStatusModel.fromJson(data['productUuid'], statusList);
|
Map<dynamic, dynamic> usersMap =
|
||||||
|
event.snapshot.value as Map<dynamic, dynamic>;
|
||||||
|
|
||||||
|
List<Status> statusList = [];
|
||||||
|
usersMap['status'].forEach((element) {
|
||||||
|
statusList
|
||||||
|
.add(Status(code: element['code'], value: element['value']));
|
||||||
|
});
|
||||||
|
|
||||||
|
deviceStatus =
|
||||||
|
DoorLockStatusModel.fromJson(usersMap['productUuid'], statusList);
|
||||||
if (!isClosed) {
|
if (!isClosed) {
|
||||||
add(StatusUpdated(model));
|
add(StatusUpdated(deviceStatus));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onStatusUpdated(StatusUpdated event, Emitter<DoorLockState> emit) {
|
void _onStatusUpdated(StatusUpdated event, Emitter<DoorLockState> emit) {
|
||||||
|
emit(DoorLockStatusLoading());
|
||||||
|
|
||||||
deviceStatus = event.deviceStatus;
|
deviceStatus = event.deviceStatus;
|
||||||
emit(DoorLockStatusLoaded(deviceStatus));
|
emit(DoorLockStatusLoaded(deviceStatus));
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onFetchDeviceStatus(
|
FutureOr<void> _onFetchDeviceStatus(
|
||||||
DoorLockFetchStatus event, Emitter<DoorLockState> emit) async {
|
DoorLockFetchStatus event, Emitter<DoorLockState> emit) async {
|
||||||
emit(DoorLockStatusLoading());
|
emit(DoorLockStatusLoading());
|
||||||
try {
|
try {
|
||||||
@ -54,13 +63,14 @@ class DoorLockBloc extends Bloc<DoorLockEvent, DoorLockState> {
|
|||||||
deviceStatus =
|
deviceStatus =
|
||||||
DoorLockStatusModel.fromJson(event.deviceId, status.status);
|
DoorLockStatusModel.fromJson(event.deviceId, status.status);
|
||||||
_listenToChanges(event.deviceId);
|
_listenToChanges(event.deviceId);
|
||||||
|
|
||||||
emit(DoorLockStatusLoaded(deviceStatus));
|
emit(DoorLockStatusLoaded(deviceStatus));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
emit(DoorLockControlError(e.toString()));
|
emit(DoorLockControlError(e.toString()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _updateLock(
|
FutureOr<void> _updateLock(
|
||||||
UpdateLockEvent event, Emitter<DoorLockState> emit) async {
|
UpdateLockEvent event, Emitter<DoorLockState> emit) async {
|
||||||
final oldValue = deviceStatus.normalOpenSwitch;
|
final oldValue = deviceStatus.normalOpenSwitch;
|
||||||
deviceStatus = deviceStatus.copyWith(normalOpenSwitch: !oldValue);
|
deviceStatus = deviceStatus.copyWith(normalOpenSwitch: !oldValue);
|
||||||
@ -68,6 +78,7 @@ class DoorLockBloc extends Bloc<DoorLockEvent, DoorLockState> {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
final response = await DevicesManagementApi.openDoorLock(deviceId);
|
final response = await DevicesManagementApi.openDoorLock(deviceId);
|
||||||
|
|
||||||
if (!response) {
|
if (!response) {
|
||||||
_revertValueAndEmit(deviceId, 'normal_open_switch', oldValue, emit);
|
_revertValueAndEmit(deviceId, 'normal_open_switch', oldValue, emit);
|
||||||
}
|
}
|
||||||
@ -77,8 +88,35 @@ class DoorLockBloc extends Bloc<DoorLockEvent, DoorLockState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _revertValueAndEmit(String deviceId, String code, dynamic oldValue,
|
Future<void> _runDebounce({
|
||||||
Emitter<DoorLockState> emit) {
|
required String deviceId,
|
||||||
|
required String code,
|
||||||
|
required dynamic value,
|
||||||
|
required dynamic oldValue,
|
||||||
|
required Emitter<DoorLockState> emit,
|
||||||
|
}) async {
|
||||||
|
if (_timer != null) {
|
||||||
|
_timer!.cancel();
|
||||||
|
}
|
||||||
|
_timer = Timer(const Duration(seconds: 1), () async {
|
||||||
|
try {
|
||||||
|
final response = await DevicesManagementApi()
|
||||||
|
.deviceControl(deviceId, Status(code: code, value: value));
|
||||||
|
if (!response) {
|
||||||
|
_revertValueAndEmit(deviceId, code, oldValue, emit);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
_revertValueAndEmit(deviceId, code, oldValue, emit);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _revertValueAndEmit(
|
||||||
|
String deviceId,
|
||||||
|
String code,
|
||||||
|
dynamic oldValue,
|
||||||
|
Emitter<DoorLockState> emit,
|
||||||
|
) {
|
||||||
_updateLocalValue(code, oldValue);
|
_updateLocalValue(code, oldValue);
|
||||||
emit(DoorLockStatusLoaded(deviceStatus));
|
emit(DoorLockStatusLoaded(deviceStatus));
|
||||||
emit(const DoorLockControlError('Failed to control the device.'));
|
emit(const DoorLockControlError('Failed to control the device.'));
|
||||||
@ -86,23 +124,34 @@ class DoorLockBloc extends Bloc<DoorLockEvent, DoorLockState> {
|
|||||||
|
|
||||||
void _updateLocalValue(String code, dynamic value) {
|
void _updateLocalValue(String code, dynamic value) {
|
||||||
switch (code) {
|
switch (code) {
|
||||||
case 'normal_open_switch':
|
|
||||||
if (value is bool) {
|
|
||||||
deviceStatus = deviceStatus.copyWith(normalOpenSwitch: value);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'reverse_lock':
|
case 'reverse_lock':
|
||||||
if (value is bool) {
|
if (value is bool) {
|
||||||
deviceStatus = deviceStatus.copyWith(reverseLock: value);
|
deviceStatus = deviceStatus.copyWith(reverseLock: value);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case 'normal_open_switch':
|
||||||
|
if (value is bool) {
|
||||||
|
deviceStatus = deviceStatus.copyWith(normalOpenSwitch: value);
|
||||||
|
}
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
emit(DoorLockStatusLoaded(deviceStatus));
|
emit(DoorLockStatusLoaded(deviceStatus));
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onFactoryReset(
|
dynamic _getValueByCode(String code) {
|
||||||
|
switch (code) {
|
||||||
|
case 'reverse_lock':
|
||||||
|
return deviceStatus.reverseLock;
|
||||||
|
case 'normal_open_switch':
|
||||||
|
return deviceStatus.normalOpenSwitch;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FutureOr<void> _onFactoryReset(
|
||||||
DoorLockFactoryReset event, Emitter<DoorLockState> emit) async {
|
DoorLockFactoryReset event, Emitter<DoorLockState> emit) async {
|
||||||
emit(DoorLockStatusLoading());
|
emit(DoorLockStatusLoading());
|
||||||
try {
|
try {
|
||||||
|
@ -8,7 +8,7 @@ import 'package:syncrow_web/pages/device_managment/door_lock/models/door_lock_st
|
|||||||
import 'package:syncrow_web/utils/color_manager.dart';
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||||
|
|
||||||
class DoorLockButton extends StatelessWidget {
|
class DoorLockButton extends StatefulWidget {
|
||||||
const DoorLockButton({
|
const DoorLockButton({
|
||||||
super.key,
|
super.key,
|
||||||
required this.doorLock,
|
required this.doorLock,
|
||||||
@ -18,28 +18,70 @@ class DoorLockButton extends StatelessWidget {
|
|||||||
final AllDevicesModel doorLock;
|
final AllDevicesModel doorLock;
|
||||||
final DoorLockStatusModel smartDoorModel;
|
final DoorLockStatusModel smartDoorModel;
|
||||||
|
|
||||||
double _calculateProgress() {
|
@override
|
||||||
final value = smartDoorModel.unlockRequest;
|
State<DoorLockButton> createState() =>
|
||||||
if (value <= 0 || value > 30) return 0;
|
_DoorLockButtonState(smartDoorModel: smartDoorModel);
|
||||||
return value / 30.0;
|
}
|
||||||
|
|
||||||
|
class _DoorLockButtonState extends State<DoorLockButton>
|
||||||
|
with SingleTickerProviderStateMixin {
|
||||||
|
late AnimationController _animationController;
|
||||||
|
late Animation<double> _animation;
|
||||||
|
DoorLockStatusModel smartDoorModel;
|
||||||
|
|
||||||
|
_DoorLockButtonState({required this.smartDoorModel});
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_animationController = AnimationController(
|
||||||
|
vsync: this,
|
||||||
|
duration: const Duration(milliseconds: 500),
|
||||||
|
);
|
||||||
|
_animation = Tween<double>(begin: 0, end: 1).animate(_animationController)
|
||||||
|
..addListener(() {
|
||||||
|
setState(() {});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (smartDoorModel.unlockRequest > 0) {
|
||||||
|
_animationController.reverse(from: 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didUpdateWidget(covariant DoorLockButton oldWidget) {
|
||||||
|
super.didUpdateWidget(oldWidget);
|
||||||
|
if (oldWidget.smartDoorModel.normalOpenSwitch !=
|
||||||
|
widget.smartDoorModel.normalOpenSwitch) {
|
||||||
|
setState(() {
|
||||||
|
smartDoorModel = widget.smartDoorModel;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (smartDoorModel.unlockRequest > 0) {
|
||||||
|
_animationController.forward(from: 0);
|
||||||
|
} else {
|
||||||
|
_animationController.reverse(from: 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_animationController.dispose();
|
||||||
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final progress = _calculateProgress();
|
|
||||||
final isEnabled = smartDoorModel.unlockRequest > 0;
|
|
||||||
|
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
width: 255,
|
width: 255,
|
||||||
height: 255,
|
height: 255,
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: isEnabled
|
onTap: () {
|
||||||
? () {
|
_animationController.forward(from: 0);
|
||||||
BlocProvider.of<DoorLockBloc>(context).add(
|
BlocProvider.of<DoorLockBloc>(context)
|
||||||
UpdateLockEvent(value: !smartDoorModel.normalOpenSwitch),
|
.add(UpdateLockEvent(value: !smartDoorModel.normalOpenSwitch));
|
||||||
);
|
},
|
||||||
}
|
|
||||||
: null,
|
|
||||||
child: Container(
|
child: Container(
|
||||||
width: 255,
|
width: 255,
|
||||||
height: 255,
|
height: 255,
|
||||||
@ -73,10 +115,9 @@ class DoorLockButton extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (progress > 0)
|
|
||||||
SizedBox.expand(
|
SizedBox.expand(
|
||||||
child: CircularProgressIndicator(
|
child: CircularProgressIndicator(
|
||||||
value: progress,
|
value: _animation.value,
|
||||||
strokeWidth: 8,
|
strokeWidth: 8,
|
||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
valueColor: const AlwaysStoppedAnimation<Color>(
|
valueColor: const AlwaysStoppedAnimation<Color>(
|
||||||
|
@ -12,8 +12,7 @@ import 'package:syncrow_web/utils/constants/assets.dart';
|
|||||||
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
|
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
|
||||||
|
|
||||||
//Smart Power Clamp
|
//Smart Power Clamp
|
||||||
class SmartPowerDeviceControl extends StatelessWidget
|
class SmartPowerDeviceControl extends StatelessWidget with HelperResponsiveLayout {
|
||||||
with HelperResponsiveLayout {
|
|
||||||
final String deviceId;
|
final String deviceId;
|
||||||
|
|
||||||
const SmartPowerDeviceControl({super.key, required this.deviceId});
|
const SmartPowerDeviceControl({super.key, required this.deviceId});
|
||||||
@ -146,11 +145,8 @@ class SmartPowerDeviceControl extends StatelessWidget
|
|||||||
children: [
|
children: [
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.arrow_left),
|
icon: const Icon(Icons.arrow_left),
|
||||||
onPressed: blocProvider.currentPage <= 0
|
onPressed: () {
|
||||||
? null
|
blocProvider.add(SmartPowerArrowPressedEvent(-1));
|
||||||
: () {
|
|
||||||
blocProvider
|
|
||||||
.add(SmartPowerArrowPressedEvent(-1));
|
|
||||||
pageController.previousPage(
|
pageController.previousPage(
|
||||||
duration: const Duration(milliseconds: 300),
|
duration: const Duration(milliseconds: 300),
|
||||||
curve: Curves.easeInOut,
|
curve: Curves.easeInOut,
|
||||||
@ -169,11 +165,8 @@ class SmartPowerDeviceControl extends StatelessWidget
|
|||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.arrow_right),
|
icon: const Icon(Icons.arrow_right),
|
||||||
onPressed: blocProvider.currentPage >= 3
|
onPressed: () {
|
||||||
? null
|
blocProvider.add(SmartPowerArrowPressedEvent(1));
|
||||||
: () {
|
|
||||||
blocProvider
|
|
||||||
.add(SmartPowerArrowPressedEvent(1));
|
|
||||||
pageController.nextPage(
|
pageController.nextPage(
|
||||||
duration: const Duration(milliseconds: 300),
|
duration: const Duration(milliseconds: 300),
|
||||||
curve: Curves.easeInOut,
|
curve: Curves.easeInOut,
|
||||||
@ -202,8 +195,8 @@ class SmartPowerDeviceControl extends StatelessWidget
|
|||||||
blocProvider.add(SelectDateEvent(context: context));
|
blocProvider.add(SelectDateEvent(context: context));
|
||||||
blocProvider.add(FilterRecordsByDateEvent(
|
blocProvider.add(FilterRecordsByDateEvent(
|
||||||
selectedDate: blocProvider.dateTime!,
|
selectedDate: blocProvider.dateTime!,
|
||||||
viewType: blocProvider
|
viewType:
|
||||||
.views[blocProvider.currentIndex]));
|
blocProvider.views[blocProvider.currentIndex]));
|
||||||
},
|
},
|
||||||
widget: blocProvider.dateSwitcher(),
|
widget: blocProvider.dateSwitcher(),
|
||||||
chartData: blocProvider.energyDataList.isNotEmpty
|
chartData: blocProvider.energyDataList.isNotEmpty
|
||||||
|
@ -83,12 +83,6 @@ class ScheduleBloc extends Bloc<ScheduleEvent, ScheduleState> {
|
|||||||
emit(currentState.copyWith(
|
emit(currentState.copyWith(
|
||||||
scheduleMode: event.scheduleMode,
|
scheduleMode: event.scheduleMode,
|
||||||
countdownRemaining: Duration.zero,
|
countdownRemaining: Duration.zero,
|
||||||
countdownHours: 0,
|
|
||||||
countdownMinutes: 0,
|
|
||||||
inchingHours: 0,
|
|
||||||
inchingMinutes: 0,
|
|
||||||
isCountdownActive: false,
|
|
||||||
isInchingActive: false,
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -100,7 +94,6 @@ class ScheduleBloc extends Bloc<ScheduleEvent, ScheduleState> {
|
|||||||
if (state is ScheduleLoaded) {
|
if (state is ScheduleLoaded) {
|
||||||
final currentState = state as ScheduleLoaded;
|
final currentState = state as ScheduleLoaded;
|
||||||
emit(currentState.copyWith(
|
emit(currentState.copyWith(
|
||||||
countdownSeconds: event.seconds,
|
|
||||||
countdownHours: event.hours,
|
countdownHours: event.hours,
|
||||||
countdownMinutes: event.minutes,
|
countdownMinutes: event.minutes,
|
||||||
inchingHours: 0,
|
inchingHours: 0,
|
||||||
@ -120,7 +113,6 @@ class ScheduleBloc extends Bloc<ScheduleEvent, ScheduleState> {
|
|||||||
inchingHours: event.hours,
|
inchingHours: event.hours,
|
||||||
inchingMinutes: event.minutes,
|
inchingMinutes: event.minutes,
|
||||||
countdownRemaining: Duration.zero,
|
countdownRemaining: Duration.zero,
|
||||||
inchingSeconds: 0, // Add this
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -265,7 +257,7 @@ class ScheduleBloc extends Bloc<ScheduleEvent, ScheduleState> {
|
|||||||
category: event.category,
|
category: event.category,
|
||||||
deviceId: deviceId,
|
deviceId: deviceId,
|
||||||
time: getTimeStampWithoutSeconds(dateTime).toString(),
|
time: getTimeStampWithoutSeconds(dateTime).toString(),
|
||||||
code: event.code ?? event.category,
|
code: event.category,
|
||||||
value: event.functionOn,
|
value: event.functionOn,
|
||||||
days: event.selectedDays);
|
days: event.selectedDays);
|
||||||
if (success) {
|
if (success) {
|
||||||
@ -286,20 +278,11 @@ class ScheduleBloc extends Bloc<ScheduleEvent, ScheduleState> {
|
|||||||
try {
|
try {
|
||||||
if (state is ScheduleLoaded) {
|
if (state is ScheduleLoaded) {
|
||||||
final dateTime = DateTime.parse(event.time);
|
final dateTime = DateTime.parse(event.time);
|
||||||
Status status = Status(code: '', value: '');
|
|
||||||
if (event.category == 'CUR_2') {
|
|
||||||
status = status.copyWith(
|
|
||||||
code: 'control',
|
|
||||||
value: event.functionOn == true ? 'open' : 'close');
|
|
||||||
} else {
|
|
||||||
status =
|
|
||||||
status.copyWith(code: event.category, value: event.functionOn);
|
|
||||||
}
|
|
||||||
final updatedSchedule = ScheduleEntry(
|
final updatedSchedule = ScheduleEntry(
|
||||||
scheduleId: event.scheduleId,
|
scheduleId: event.scheduleId,
|
||||||
category: event.category,
|
category: event.category,
|
||||||
time: getTimeStampWithoutSeconds(dateTime).toString(),
|
time: getTimeStampWithoutSeconds(dateTime).toString(),
|
||||||
function: status,
|
function: Status(code: event.category, value: event.functionOn),
|
||||||
days: event.selectedDays,
|
days: event.selectedDays,
|
||||||
);
|
);
|
||||||
final success = await DevicesManagementApi().editScheduleRecord(
|
final success = await DevicesManagementApi().editScheduleRecord(
|
||||||
@ -441,7 +424,6 @@ class ScheduleBloc extends Bloc<ScheduleEvent, ScheduleState> {
|
|||||||
countdownMinutes: countdownDuration.inMinutes % 60,
|
countdownMinutes: countdownDuration.inMinutes % 60,
|
||||||
countdownRemaining: countdownDuration,
|
countdownRemaining: countdownDuration,
|
||||||
isCountdownActive: true,
|
isCountdownActive: true,
|
||||||
countdownSeconds: countdownDuration.inSeconds,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -455,7 +437,6 @@ class ScheduleBloc extends Bloc<ScheduleEvent, ScheduleState> {
|
|||||||
countdownMinutes: 0,
|
countdownMinutes: 0,
|
||||||
countdownRemaining: Duration.zero,
|
countdownRemaining: Duration.zero,
|
||||||
isCountdownActive: false,
|
isCountdownActive: false,
|
||||||
countdownSeconds: 0,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -467,7 +448,6 @@ class ScheduleBloc extends Bloc<ScheduleEvent, ScheduleState> {
|
|||||||
inchingMinutes: inchingDuration.inMinutes % 60,
|
inchingMinutes: inchingDuration.inMinutes % 60,
|
||||||
isInchingActive: true,
|
isInchingActive: true,
|
||||||
countdownRemaining: inchingDuration,
|
countdownRemaining: inchingDuration,
|
||||||
countdownSeconds: inchingDuration.inSeconds,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -594,7 +574,8 @@ class ScheduleBloc extends Bloc<ScheduleEvent, ScheduleState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String extractTime(String isoDateTime) {
|
String extractTime(String isoDateTime) {
|
||||||
return isoDateTime.split('T')[1].split('.')[0];
|
// Example input: "2025-06-19T15:45:00.000"
|
||||||
|
return isoDateTime.split('T')[1].split('.')[0]; // gives "15:45:00"
|
||||||
}
|
}
|
||||||
|
|
||||||
int? getTimeStampWithoutSeconds(DateTime? dateTime) {
|
int? getTimeStampWithoutSeconds(DateTime? dateTime) {
|
||||||
|
@ -70,19 +70,17 @@ class ScheduleAddEvent extends ScheduleEvent {
|
|||||||
final String category;
|
final String category;
|
||||||
final String time;
|
final String time;
|
||||||
final List<String> selectedDays;
|
final List<String> selectedDays;
|
||||||
final dynamic functionOn;
|
final bool functionOn;
|
||||||
final String? code;
|
|
||||||
|
|
||||||
const ScheduleAddEvent({
|
const ScheduleAddEvent({
|
||||||
required this.category,
|
required this.category,
|
||||||
required this.time,
|
required this.time,
|
||||||
required this.selectedDays,
|
required this.selectedDays,
|
||||||
required this.functionOn,
|
required this.functionOn,
|
||||||
required this.code,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [category, time, selectedDays, functionOn, code];
|
List<Object> get props => [category, time, selectedDays, functionOn];
|
||||||
}
|
}
|
||||||
|
|
||||||
class ScheduleEditEvent extends ScheduleEvent {
|
class ScheduleEditEvent extends ScheduleEvent {
|
||||||
@ -148,16 +146,14 @@ class UpdateScheduleModeEvent extends ScheduleEvent {
|
|||||||
class UpdateCountdownTimeEvent extends ScheduleEvent {
|
class UpdateCountdownTimeEvent extends ScheduleEvent {
|
||||||
final int hours;
|
final int hours;
|
||||||
final int minutes;
|
final int minutes;
|
||||||
final int seconds;
|
|
||||||
|
|
||||||
const UpdateCountdownTimeEvent({
|
const UpdateCountdownTimeEvent({
|
||||||
required this.hours,
|
required this.hours,
|
||||||
required this.minutes,
|
required this.minutes,
|
||||||
required this.seconds,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object> get props => [hours, minutes, seconds];
|
List<Object> get props => [hours, minutes];
|
||||||
}
|
}
|
||||||
|
|
||||||
class UpdateInchingTimeEvent extends ScheduleEvent {
|
class UpdateInchingTimeEvent extends ScheduleEvent {
|
||||||
|
@ -26,15 +26,11 @@ class ScheduleLoaded extends ScheduleState {
|
|||||||
final bool isCountdownActive;
|
final bool isCountdownActive;
|
||||||
final int inchingHours;
|
final int inchingHours;
|
||||||
final int inchingMinutes;
|
final int inchingMinutes;
|
||||||
final int inchingSeconds;
|
|
||||||
final bool isInchingActive;
|
final bool isInchingActive;
|
||||||
final ScheduleModes scheduleMode;
|
final ScheduleModes scheduleMode;
|
||||||
final Duration? countdownRemaining;
|
final Duration? countdownRemaining;
|
||||||
final int? countdownSeconds;
|
|
||||||
|
|
||||||
const ScheduleLoaded({
|
const ScheduleLoaded({
|
||||||
this.countdownSeconds = 0,
|
|
||||||
this.inchingSeconds = 0,
|
|
||||||
required this.schedules,
|
required this.schedules,
|
||||||
this.selectedTime,
|
this.selectedTime,
|
||||||
required this.selectedDays,
|
required this.selectedDays,
|
||||||
@ -65,9 +61,6 @@ class ScheduleLoaded extends ScheduleState {
|
|||||||
bool? isInchingActive,
|
bool? isInchingActive,
|
||||||
ScheduleModes? scheduleMode,
|
ScheduleModes? scheduleMode,
|
||||||
Duration? countdownRemaining,
|
Duration? countdownRemaining,
|
||||||
String? deviceId,
|
|
||||||
int? countdownSeconds,
|
|
||||||
int? inchingSeconds,
|
|
||||||
}) {
|
}) {
|
||||||
return ScheduleLoaded(
|
return ScheduleLoaded(
|
||||||
schedules: schedules ?? this.schedules,
|
schedules: schedules ?? this.schedules,
|
||||||
@ -75,7 +68,7 @@ class ScheduleLoaded extends ScheduleState {
|
|||||||
selectedDays: selectedDays ?? this.selectedDays,
|
selectedDays: selectedDays ?? this.selectedDays,
|
||||||
functionOn: functionOn ?? this.functionOn,
|
functionOn: functionOn ?? this.functionOn,
|
||||||
isEditing: isEditing ?? this.isEditing,
|
isEditing: isEditing ?? this.isEditing,
|
||||||
deviceId: deviceId ?? this.deviceId,
|
deviceId: deviceId,
|
||||||
countdownHours: countdownHours ?? this.countdownHours,
|
countdownHours: countdownHours ?? this.countdownHours,
|
||||||
countdownMinutes: countdownMinutes ?? this.countdownMinutes,
|
countdownMinutes: countdownMinutes ?? this.countdownMinutes,
|
||||||
isCountdownActive: isCountdownActive ?? this.isCountdownActive,
|
isCountdownActive: isCountdownActive ?? this.isCountdownActive,
|
||||||
@ -84,8 +77,6 @@ class ScheduleLoaded extends ScheduleState {
|
|||||||
isInchingActive: isInchingActive ?? this.isInchingActive,
|
isInchingActive: isInchingActive ?? this.isInchingActive,
|
||||||
scheduleMode: scheduleMode ?? this.scheduleMode,
|
scheduleMode: scheduleMode ?? this.scheduleMode,
|
||||||
countdownRemaining: countdownRemaining ?? this.countdownRemaining,
|
countdownRemaining: countdownRemaining ?? this.countdownRemaining,
|
||||||
countdownSeconds: countdownSeconds ?? this.countdownSeconds,
|
|
||||||
inchingSeconds: inchingSeconds ?? this.inchingSeconds,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,8 +96,6 @@ class ScheduleLoaded extends ScheduleState {
|
|||||||
isInchingActive,
|
isInchingActive,
|
||||||
scheduleMode,
|
scheduleMode,
|
||||||
countdownRemaining,
|
countdownRemaining,
|
||||||
countdownSeconds,
|
|
||||||
inchingSeconds,
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,8 +6,7 @@ import 'package:syncrow_web/utils/color_manager.dart';
|
|||||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||||
|
|
||||||
class CountdownInchingView extends StatefulWidget {
|
class CountdownInchingView extends StatefulWidget {
|
||||||
final String deviceId;
|
const CountdownInchingView({super.key});
|
||||||
const CountdownInchingView({super.key, required this.deviceId});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<CountdownInchingView> createState() => _CountdownInchingViewState();
|
State<CountdownInchingView> createState() => _CountdownInchingViewState();
|
||||||
@ -16,30 +15,25 @@ class CountdownInchingView extends StatefulWidget {
|
|||||||
class _CountdownInchingViewState extends State<CountdownInchingView> {
|
class _CountdownInchingViewState extends State<CountdownInchingView> {
|
||||||
late FixedExtentScrollController _hoursController;
|
late FixedExtentScrollController _hoursController;
|
||||||
late FixedExtentScrollController _minutesController;
|
late FixedExtentScrollController _minutesController;
|
||||||
late FixedExtentScrollController _secondsController;
|
|
||||||
|
|
||||||
int _lastHours = -1;
|
int _lastHours = -1;
|
||||||
int _lastMinutes = -1;
|
int _lastMinutes = -1;
|
||||||
int _lastSeconds = -1;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_hoursController = FixedExtentScrollController();
|
_hoursController = FixedExtentScrollController();
|
||||||
_minutesController = FixedExtentScrollController();
|
_minutesController = FixedExtentScrollController();
|
||||||
_secondsController = FixedExtentScrollController();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_hoursController.dispose();
|
_hoursController.dispose();
|
||||||
_minutesController.dispose();
|
_minutesController.dispose();
|
||||||
_secondsController.dispose();
|
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _updateControllers(
|
void _updateControllers(int displayHours, int displayMinutes) {
|
||||||
int displayHours, int displayMinutes, int displaySeconds) {
|
|
||||||
if (_lastHours != displayHours) {
|
if (_lastHours != displayHours) {
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
if (_hoursController.hasClients) {
|
if (_hoursController.hasClients) {
|
||||||
@ -56,15 +50,6 @@ class _CountdownInchingViewState extends State<CountdownInchingView> {
|
|||||||
});
|
});
|
||||||
_lastMinutes = displayMinutes;
|
_lastMinutes = displayMinutes;
|
||||||
}
|
}
|
||||||
// Update seconds controller
|
|
||||||
if (_lastSeconds != displaySeconds) {
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
||||||
if (_secondsController.hasClients) {
|
|
||||||
_secondsController.jumpToItem(displaySeconds);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
_lastSeconds = displaySeconds;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -72,6 +57,7 @@ class _CountdownInchingViewState extends State<CountdownInchingView> {
|
|||||||
return BlocBuilder<ScheduleBloc, ScheduleState>(
|
return BlocBuilder<ScheduleBloc, ScheduleState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
if (state is! ScheduleLoaded) return const SizedBox.shrink();
|
if (state is! ScheduleLoaded) return const SizedBox.shrink();
|
||||||
|
|
||||||
final isCountDown = state.scheduleMode == ScheduleModes.countdown;
|
final isCountDown = state.scheduleMode == ScheduleModes.countdown;
|
||||||
final isActive =
|
final isActive =
|
||||||
isCountDown ? state.isCountdownActive : state.isInchingActive;
|
isCountDown ? state.isCountdownActive : state.isInchingActive;
|
||||||
@ -81,21 +67,8 @@ class _CountdownInchingViewState extends State<CountdownInchingView> {
|
|||||||
final displayMinutes = isActive && state.countdownRemaining != null
|
final displayMinutes = isActive && state.countdownRemaining != null
|
||||||
? state.countdownRemaining!.inMinutes.remainder(60)
|
? state.countdownRemaining!.inMinutes.remainder(60)
|
||||||
: (isCountDown ? state.countdownMinutes : state.inchingMinutes);
|
: (isCountDown ? state.countdownMinutes : state.inchingMinutes);
|
||||||
final displaySeconds = isActive && state.countdownRemaining != null
|
|
||||||
? state.countdownRemaining!.inSeconds.remainder(60)
|
|
||||||
: (isCountDown ? state.countdownSeconds : state.inchingSeconds);
|
|
||||||
|
|
||||||
_updateControllers(displayHours, displayMinutes, displaySeconds!);
|
|
||||||
|
|
||||||
if (displayHours == 0 && displayMinutes == 0 && displaySeconds == 0) {
|
|
||||||
context.read<ScheduleBloc>().add(
|
|
||||||
StopScheduleEvent(
|
|
||||||
mode: ScheduleModes.countdown,
|
|
||||||
deviceId: widget.deviceId,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
_updateControllers(displayHours, displayMinutes);
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
@ -127,10 +100,7 @@ class _CountdownInchingViewState extends State<CountdownInchingView> {
|
|||||||
(value) {
|
(value) {
|
||||||
if (!isActive) {
|
if (!isActive) {
|
||||||
context.read<ScheduleBloc>().add(UpdateCountdownTimeEvent(
|
context.read<ScheduleBloc>().add(UpdateCountdownTimeEvent(
|
||||||
hours: value,
|
hours: value, minutes: displayMinutes));
|
||||||
minutes: displayMinutes,
|
|
||||||
seconds: displaySeconds,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
isActive: isActive,
|
isActive: isActive,
|
||||||
@ -145,31 +115,7 @@ class _CountdownInchingViewState extends State<CountdownInchingView> {
|
|||||||
(value) {
|
(value) {
|
||||||
if (!isActive) {
|
if (!isActive) {
|
||||||
context.read<ScheduleBloc>().add(UpdateCountdownTimeEvent(
|
context.read<ScheduleBloc>().add(UpdateCountdownTimeEvent(
|
||||||
hours: displayHours,
|
hours: displayHours, minutes: value));
|
||||||
minutes: value,
|
|
||||||
seconds: displaySeconds,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
isActive: isActive,
|
|
||||||
),
|
|
||||||
const SizedBox(width: 10),
|
|
||||||
if (isActive)
|
|
||||||
_buildPickerColumn(
|
|
||||||
context,
|
|
||||||
's',
|
|
||||||
displaySeconds,
|
|
||||||
60,
|
|
||||||
_secondsController,
|
|
||||||
(value) {
|
|
||||||
if (!isActive) {
|
|
||||||
context
|
|
||||||
.read<ScheduleBloc>()
|
|
||||||
.add(UpdateCountdownTimeEvent(
|
|
||||||
hours: displayHours,
|
|
||||||
minutes: displayMinutes,
|
|
||||||
seconds: value,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
isActive: isActive,
|
isActive: isActive,
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
|
|
||||||
import 'package:syncrow_web/pages/device_managment/schedule_device/bloc/schedule_bloc.dart';
|
import 'package:syncrow_web/pages/device_managment/schedule_device/bloc/schedule_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/schedule_device/schedule_widgets/count_down_button.dart';
|
import 'package:syncrow_web/pages/device_managment/schedule_device/schedule_widgets/count_down_button.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/schedule_device/schedule_widgets/count_down_inching_view.dart';
|
import 'package:syncrow_web/pages/device_managment/schedule_device/schedule_widgets/count_down_inching_view.dart';
|
||||||
@ -10,19 +9,13 @@ import 'package:syncrow_web/pages/device_managment/schedule_device/schedule_widg
|
|||||||
import 'package:syncrow_web/pages/device_managment/schedule_device/schedule_widgets/schedule_mode_buttons.dart';
|
import 'package:syncrow_web/pages/device_managment/schedule_device/schedule_widgets/schedule_mode_buttons.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/schedule_device/schedule_widgets/schedule_mode_selector.dart';
|
import 'package:syncrow_web/pages/device_managment/schedule_device/schedule_widgets/schedule_mode_selector.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/water_heater/helper/add_schedule_dialog_helper.dart';
|
import 'package:syncrow_web/pages/device_managment/water_heater/helper/add_schedule_dialog_helper.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/water_heater/models/schedule_entry.dart';
|
|
||||||
import 'package:syncrow_web/pages/device_managment/water_heater/models/water_heater_status_model.dart';
|
import 'package:syncrow_web/pages/device_managment/water_heater/models/water_heater_status_model.dart';
|
||||||
|
|
||||||
class BuildScheduleView extends StatelessWidget {
|
class BuildScheduleView extends StatelessWidget {
|
||||||
const BuildScheduleView({
|
const BuildScheduleView(
|
||||||
super.key,
|
{super.key, required this.deviceUuid, required this.category});
|
||||||
required this.deviceUuid,
|
|
||||||
required this.category,
|
|
||||||
this.code,
|
|
||||||
});
|
|
||||||
final String deviceUuid;
|
final String deviceUuid;
|
||||||
final String category;
|
final String category;
|
||||||
final String? code;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -52,9 +45,6 @@ class BuildScheduleView extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
const ScheduleHeader(),
|
const ScheduleHeader(),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
if (category == 'CUR_2')
|
|
||||||
const SizedBox()
|
|
||||||
else
|
|
||||||
ScheduleModeSelector(
|
ScheduleModeSelector(
|
||||||
currentMode: state.scheduleMode,
|
currentMode: state.scheduleMode,
|
||||||
),
|
),
|
||||||
@ -67,21 +57,13 @@ class BuildScheduleView extends StatelessWidget {
|
|||||||
final entry = await ScheduleDialogHelper
|
final entry = await ScheduleDialogHelper
|
||||||
.showAddScheduleDialog(
|
.showAddScheduleDialog(
|
||||||
context,
|
context,
|
||||||
schedule: ScheduleEntry(
|
schedule: null,
|
||||||
category: category,
|
|
||||||
time: '',
|
|
||||||
function: Status(
|
|
||||||
code: code.toString(), value: null),
|
|
||||||
days: [],
|
|
||||||
),
|
|
||||||
isEdit: false,
|
isEdit: false,
|
||||||
code: code,
|
|
||||||
);
|
);
|
||||||
if (entry != null) {
|
if (entry != null) {
|
||||||
context.read<ScheduleBloc>().add(
|
context.read<ScheduleBloc>().add(
|
||||||
ScheduleAddEvent(
|
ScheduleAddEvent(
|
||||||
category: category,
|
category: entry.category,
|
||||||
code: entry.function.code,
|
|
||||||
time: entry.time,
|
time: entry.time,
|
||||||
functionOn: entry.function.value,
|
functionOn: entry.function.value,
|
||||||
selectedDays: entry.days,
|
selectedDays: entry.days,
|
||||||
@ -92,9 +74,7 @@ class BuildScheduleView extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
if (state.scheduleMode == ScheduleModes.countdown ||
|
if (state.scheduleMode == ScheduleModes.countdown ||
|
||||||
state.scheduleMode == ScheduleModes.inching)
|
state.scheduleMode == ScheduleModes.inching)
|
||||||
CountdownInchingView(
|
const CountdownInchingView(),
|
||||||
deviceId: deviceUuid,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
if (state.scheduleMode == ScheduleModes.countdown)
|
if (state.scheduleMode == ScheduleModes.countdown)
|
||||||
CountdownModeButtons(
|
CountdownModeButtons(
|
||||||
|
@ -162,18 +162,11 @@ class _ScheduleTableView extends StatelessWidget {
|
|||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
behavior: HitTestBehavior.opaque,
|
behavior: HitTestBehavior.opaque,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
bool temp;
|
|
||||||
if (schedule.category == 'CUR_2') {
|
|
||||||
temp = schedule.function.value == 'open' ? true : false;
|
|
||||||
} else {
|
|
||||||
temp = schedule.function.value as bool;
|
|
||||||
}
|
|
||||||
context.read<ScheduleBloc>().add(
|
context.read<ScheduleBloc>().add(
|
||||||
ScheduleUpdateEntryEvent(
|
ScheduleUpdateEntryEvent(
|
||||||
category: schedule.category,
|
category: schedule.category,
|
||||||
scheduleId: schedule.scheduleId,
|
scheduleId: schedule.scheduleId,
|
||||||
functionOn: temp,
|
functionOn: schedule.function.value,
|
||||||
// schedule.function.value,
|
|
||||||
enable: !schedule.enable,
|
enable: !schedule.enable,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -195,9 +188,6 @@ class _ScheduleTableView extends StatelessWidget {
|
|||||||
child: Text(_getSelectedDays(
|
child: Text(_getSelectedDays(
|
||||||
ScheduleModel.parseSelectedDays(schedule.days)))),
|
ScheduleModel.parseSelectedDays(schedule.days)))),
|
||||||
Center(child: Text(formatIsoStringToTime(schedule.time, context))),
|
Center(child: Text(formatIsoStringToTime(schedule.time, context))),
|
||||||
if (schedule.category == 'CUR_2')
|
|
||||||
Center(child: Text(schedule.function.value))
|
|
||||||
else
|
|
||||||
Center(child: Text(schedule.function.value ? 'On' : 'Off')),
|
Center(child: Text(schedule.function.value ? 'On' : 'Off')),
|
||||||
Center(
|
Center(
|
||||||
child: Wrap(
|
child: Wrap(
|
||||||
@ -212,20 +202,12 @@ class _ScheduleTableView extends StatelessWidget {
|
|||||||
isEdit: true,
|
isEdit: true,
|
||||||
).then((updatedSchedule) {
|
).then((updatedSchedule) {
|
||||||
if (updatedSchedule != null) {
|
if (updatedSchedule != null) {
|
||||||
bool temp;
|
|
||||||
if (schedule.category == 'CUR_2') {
|
|
||||||
updatedSchedule.function.value == 'open'
|
|
||||||
? temp = true
|
|
||||||
: temp = false;
|
|
||||||
} else {
|
|
||||||
temp = updatedSchedule.function.value;
|
|
||||||
}
|
|
||||||
context.read<ScheduleBloc>().add(
|
context.read<ScheduleBloc>().add(
|
||||||
ScheduleEditEvent(
|
ScheduleEditEvent(
|
||||||
scheduleId: schedule.scheduleId,
|
scheduleId: schedule.scheduleId,
|
||||||
category: schedule.category,
|
category: schedule.category,
|
||||||
time: updatedSchedule.time,
|
time: updatedSchedule.time,
|
||||||
functionOn: temp,
|
functionOn: updatedSchedule.function.value,
|
||||||
selectedDays: updatedSchedule.days),
|
selectedDays: updatedSchedule.days),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user