mirror of
https://github.com/SyncrowIOT/web.git
synced 2025-07-15 01:35:25 +00:00
Compare commits
38 Commits
SP-1344-FE
...
SP-1464-FE
Author | SHA1 | Date | |
---|---|---|---|
778257644d | |||
c8e540e938 | |||
ba20998067 | |||
75b0b24543 | |||
fbc45b465f | |||
c9c939c59f | |||
e1a2465130 | |||
86164e746a | |||
4a5176cf22 | |||
2bb7a6950a | |||
46860619a0 | |||
7adce3b94c | |||
91f93d4395 | |||
42d6b64e58 | |||
b11d4186fb | |||
24130be665 | |||
8c960bd5f1 | |||
fb8ccdf0a6 | |||
1975a1b6f4 | |||
367d6717e7 | |||
8c637e40ff | |||
05b3180510 | |||
177a4711fe | |||
d7a37c6519 | |||
b901791079 | |||
5d16555748 | |||
bd15f30b8a | |||
5f2684bdf4 | |||
b4ef22ef0a | |||
d8afb562eb | |||
67a164e6d2 | |||
cf20bdcd42 | |||
84264391d9 | |||
2e4f904d3a | |||
c46cfb48a8 | |||
a0dd128557 | |||
34fa426163 | |||
1407c173b0 |
@ -31,7 +31,7 @@ jobs:
|
||||
run: flutter pub get
|
||||
|
||||
- name: Build Flutter Web App
|
||||
run: flutter build web --release --dart-define=FLAVOR=production
|
||||
run: flutter build web --web-renderer canvaskit -t lib/main.dart
|
||||
|
||||
- name: Build And Deploy
|
||||
id: builddeploy
|
||||
|
@ -31,7 +31,7 @@ jobs:
|
||||
run: flutter pub get
|
||||
|
||||
- name: Build Flutter Web App
|
||||
run: flutter build web --release --dart-define=FLAVOR=development
|
||||
run: flutter build web --web-renderer canvaskit -t lib/main_dev.dart
|
||||
|
||||
- name: Build And Deploy
|
||||
id: builddeploy
|
||||
|
33
.vscode/launch.json
vendored
33
.vscode/launch.json
vendored
@ -10,11 +10,12 @@
|
||||
"type": "dart",
|
||||
|
||||
"args": [
|
||||
|
||||
"--dart-define",
|
||||
|
||||
"FLAVOR=development"
|
||||
|
||||
"-d",
|
||||
"chrome",
|
||||
"--web-port",
|
||||
"3000",
|
||||
"-t",
|
||||
"lib/main_dev.dart",
|
||||
],
|
||||
|
||||
"flutterMode": "debug"
|
||||
@ -28,11 +29,12 @@
|
||||
"type": "dart",
|
||||
|
||||
"args": [
|
||||
|
||||
"--dart-define",
|
||||
|
||||
"FLAVOR=staging"
|
||||
|
||||
"-d",
|
||||
"chrome",
|
||||
"--web-port",
|
||||
"3000",
|
||||
"-t",
|
||||
"lib/main_staging.dart",
|
||||
],
|
||||
|
||||
"flutterMode": "debug"
|
||||
@ -46,11 +48,12 @@
|
||||
"type": "dart",
|
||||
|
||||
"args": [
|
||||
|
||||
"--dart-define",
|
||||
|
||||
"FLAVOR=production"
|
||||
|
||||
"-d",
|
||||
"chrome",
|
||||
"--web-port",
|
||||
"3000",
|
||||
"-t",
|
||||
"lib/main.dart",
|
||||
],
|
||||
|
||||
"flutterMode": "debug"
|
||||
|
@ -16,7 +16,12 @@ samples, guidance on mobile development, and a full API reference.
|
||||
|
||||
## USEFUL COMMANDS
|
||||
|
||||
Run on chrome: flutter run -d chrome --dart-define=FLAVOR='ENV_NAME'
|
||||
- Building for the Web
|
||||
- CanvasKit
|
||||
- `flutter build web --web-renderer canvaskit -t lib/main_dev.dart --output=build/web_dev` - build for DEVELOPMENT.
|
||||
- `flutter build web --web-renderer canvaskit -t lib/main_staging.dart --output=build/web_stg` - build for STAGING.
|
||||
- `flutter build web --web-renderer canvaskit -t lib/main.dart --output=build/web` - build for PRODUCTION.
|
||||
|
||||
- run command: `flutter run -d chrome --target=lib/main_dev.dart`
|
||||
|
||||
Build: flutter build web --release --dart-define=FLAVOR='ENV_NAME'
|
||||
|
||||
|
@ -16,12 +16,12 @@ import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_bloc.da
|
||||
import 'package:syncrow_web/services/locator.dart';
|
||||
import 'package:syncrow_web/utils/app_routes.dart';
|
||||
import 'package:syncrow_web/utils/constants/routes_const.dart';
|
||||
import 'package:syncrow_web/utils/navigation_service.dart';
|
||||
import 'package:syncrow_web/utils/theme/theme.dart';
|
||||
|
||||
Future<void> main() async {
|
||||
try {
|
||||
const environment =
|
||||
String.fromEnvironment('FLAVOR', defaultValue: 'development');
|
||||
const environment = String.fromEnvironment('FLAVOR', defaultValue: 'development');
|
||||
await dotenv.load(fileName: '.env.$environment');
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
await Firebase.initializeApp(
|
||||
@ -33,9 +33,7 @@ Future<void> main() async {
|
||||
}
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
MyApp({
|
||||
super.key,
|
||||
});
|
||||
MyApp({super.key});
|
||||
|
||||
final GoRouter _router = GoRouter(
|
||||
initialLocation: RoutesConst.auth,
|
||||
@ -56,11 +54,10 @@ class MyApp extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider<CreateRoutineBloc>(
|
||||
BlocProvider<CreateRoutineBloc>(
|
||||
create: (context) => CreateRoutineBloc(),
|
||||
),
|
||||
BlocProvider(
|
||||
create: (context) => HomeBloc()..add(const FetchUserInfo())),
|
||||
BlocProvider(create: (context) => HomeBloc()..add(const FetchUserInfo())),
|
||||
BlocProvider<VisitorPasswordBloc>(
|
||||
create: (context) => VisitorPasswordBloc(),
|
||||
),
|
||||
@ -81,6 +78,8 @@ class MyApp extends StatelessWidget {
|
||||
PointerDeviceKind.unknown,
|
||||
},
|
||||
),
|
||||
key: NavigationService.navigatorKey,
|
||||
// scaffoldMessengerKey: NavigationService.snackbarKey,
|
||||
theme: myTheme,
|
||||
routerConfig: _router,
|
||||
));
|
||||
|
87
lib/main_staging.dart
Normal file
87
lib/main_staging.dart
Normal file
@ -0,0 +1,87 @@
|
||||
import 'package:firebase_core/firebase_core.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:syncrow_web/firebase_options_prod.dart';
|
||||
import 'package:syncrow_web/pages/auth/bloc/auth_bloc.dart';
|
||||
import 'package:syncrow_web/pages/home/bloc/home_bloc.dart';
|
||||
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/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_event.dart';
|
||||
import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_bloc.dart';
|
||||
import 'package:syncrow_web/services/locator.dart';
|
||||
import 'package:syncrow_web/utils/app_routes.dart';
|
||||
import 'package:syncrow_web/utils/constants/routes_const.dart';
|
||||
import 'package:syncrow_web/utils/navigation_service.dart';
|
||||
import 'package:syncrow_web/utils/theme/theme.dart';
|
||||
|
||||
Future<void> main() async {
|
||||
try {
|
||||
const environment = String.fromEnvironment('FLAVOR', defaultValue: 'staging');
|
||||
await dotenv.load(fileName: '.env.$environment');
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
await Firebase.initializeApp(
|
||||
options: DefaultFirebaseOptionsStaging.currentPlatform,
|
||||
);
|
||||
initialSetup();
|
||||
} catch (_) {}
|
||||
runApp(MyApp());
|
||||
}
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
MyApp({super.key});
|
||||
|
||||
final GoRouter _router = GoRouter(
|
||||
initialLocation: RoutesConst.auth,
|
||||
routes: AppRoutes.getRoutes(),
|
||||
redirect: (context, state) async {
|
||||
String checkToken = await AuthBloc.getTokenAndValidate();
|
||||
final loggedIn = checkToken == 'Success';
|
||||
final goingToLogin = state.uri.toString() == RoutesConst.auth;
|
||||
|
||||
if (!loggedIn && !goingToLogin) return RoutesConst.auth;
|
||||
if (loggedIn && goingToLogin) return RoutesConst.home;
|
||||
|
||||
return null;
|
||||
},
|
||||
);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider<CreateRoutineBloc>(
|
||||
create: (context) => CreateRoutineBloc(),
|
||||
),
|
||||
BlocProvider(create: (context) => HomeBloc()..add(const FetchUserInfo())),
|
||||
BlocProvider<VisitorPasswordBloc>(
|
||||
create: (context) => VisitorPasswordBloc(),
|
||||
),
|
||||
BlocProvider<RoutineBloc>(
|
||||
create: (context) => RoutineBloc(),
|
||||
),
|
||||
BlocProvider<SpaceTreeBloc>(
|
||||
create: (context) => SpaceTreeBloc()..add(InitialEvent()),
|
||||
),
|
||||
],
|
||||
child: MaterialApp.router(
|
||||
debugShowCheckedModeBanner: false,
|
||||
scrollBehavior: const MaterialScrollBehavior().copyWith(
|
||||
dragDevices: {
|
||||
PointerDeviceKind.mouse,
|
||||
PointerDeviceKind.touch,
|
||||
PointerDeviceKind.stylus,
|
||||
PointerDeviceKind.unknown,
|
||||
},
|
||||
),
|
||||
key: NavigationService.navigatorKey,
|
||||
// scaffoldMessengerKey: NavigationService.snackbarKey,
|
||||
theme: myTheme,
|
||||
routerConfig: _router,
|
||||
));
|
||||
}
|
||||
}
|
@ -10,6 +10,7 @@ import 'package:syncrow_web/pages/auth/model/region_model.dart';
|
||||
import 'package:syncrow_web/pages/auth/model/token.dart';
|
||||
import 'package:syncrow_web/pages/auth/model/user_model.dart';
|
||||
import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
|
||||
import 'package:syncrow_web/pages/home/bloc/home_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/services/auth_api.dart';
|
||||
@ -432,9 +433,13 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
||||
}
|
||||
|
||||
static Future<void> logout(BuildContext context) async {
|
||||
final storage = FlutterSecureStorage();
|
||||
ProjectManager.clearProjectUUID();
|
||||
const storage = FlutterSecureStorage();
|
||||
context.read<SpaceTreeBloc>().add(ClearAllData());
|
||||
storage.deleteAll();
|
||||
user = null;
|
||||
context.read<HomeBloc>().user = null;
|
||||
await Future.wait<void>([
|
||||
ProjectManager.clearProjectUUID(),
|
||||
storage.deleteAll(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@ -55,12 +55,12 @@ class _LoginWebPageState extends State<LoginWebPage> with HelperResponsiveLayout
|
||||
final isSmallScreen = isSmallScreenSize(context);
|
||||
final isMediumScreen = isMediumScreenSize(context);
|
||||
Size size = MediaQuery.of(context).size;
|
||||
late ScrollController _scrollController;
|
||||
_scrollController = ScrollController();
|
||||
late ScrollController scrollController;
|
||||
scrollController = ScrollController();
|
||||
|
||||
void _scrollToCenter() {
|
||||
final double middlePosition = _scrollController.position.maxScrollExtent / 2;
|
||||
_scrollController.animateTo(
|
||||
void scrollToCenter() {
|
||||
final double middlePosition = scrollController.position.maxScrollExtent / 2;
|
||||
scrollController.animateTo(
|
||||
middlePosition,
|
||||
duration: const Duration(seconds: 1),
|
||||
curve: Curves.easeInOut,
|
||||
@ -68,7 +68,7 @@ class _LoginWebPageState extends State<LoginWebPage> with HelperResponsiveLayout
|
||||
}
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_scrollToCenter();
|
||||
scrollToCenter();
|
||||
});
|
||||
|
||||
return Stack(
|
||||
@ -76,7 +76,7 @@ class _LoginWebPageState extends State<LoginWebPage> with HelperResponsiveLayout
|
||||
FirstLayer(
|
||||
second: Center(
|
||||
child: ListView(
|
||||
controller: _scrollController,
|
||||
controller: scrollController,
|
||||
shrinkWrap: true,
|
||||
children: [
|
||||
Container(
|
||||
@ -199,7 +199,7 @@ class _LoginWebPageState extends State<LoginWebPage> with HelperResponsiveLayout
|
||||
width: size.width * 0.9,
|
||||
child: DropdownButtonHideUnderline(
|
||||
child: DropdownButton2<String>(
|
||||
style: TextStyle(color: Colors.black),
|
||||
style: const TextStyle(color: Colors.black),
|
||||
isExpanded: true,
|
||||
hint: Text(
|
||||
'Select your region/country',
|
||||
@ -336,6 +336,16 @@ class _LoginWebPageState extends State<LoginWebPage> with HelperResponsiveLayout
|
||||
obscureText: loginBloc.obscureText,
|
||||
keyboardType: TextInputType.visiblePassword,
|
||||
controller: loginBloc.loginPasswordController,
|
||||
onFieldSubmitted: (value) {
|
||||
if (loginBloc.loginFormKey.currentState!.validate()) {
|
||||
loginBloc.add(LoginButtonPressed(
|
||||
username: loginBloc.loginEmailController.text,
|
||||
password: value,
|
||||
));
|
||||
} else {
|
||||
loginBloc.add(ChangeValidateEvent());
|
||||
}
|
||||
},
|
||||
decoration: textBoxDecoration()!.copyWith(
|
||||
hintText: 'At least 8 characters',
|
||||
hintStyle: Theme.of(context)
|
||||
@ -393,7 +403,7 @@ class _LoginWebPageState extends State<LoginWebPage> with HelperResponsiveLayout
|
||||
Transform.scale(
|
||||
scale: 1.2,
|
||||
child: Checkbox(
|
||||
fillColor: MaterialStateProperty.all<Color>(Colors.white),
|
||||
fillColor: WidgetStateProperty.all<Color>(Colors.white),
|
||||
activeColor: Colors.white,
|
||||
value: loginBloc.isChecked,
|
||||
checkColor: Colors.black,
|
||||
|
@ -9,6 +9,8 @@ import 'package:syncrow_web/pages/device_managment/curtain/view/curtain_batch_st
|
||||
import 'package:syncrow_web/pages/device_managment/curtain/view/curtain_status_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/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_control_view.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/garage_door/view/garage_door_batch_control_view.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/garage_door/view/garage_door_control_view.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/gateway/view/gateway_batch_control.dart';
|
||||
@ -104,6 +106,9 @@ mixin RouteControlsBasedCode {
|
||||
);
|
||||
case 'SOS':
|
||||
return SosDeviceControlsView(device: device);
|
||||
|
||||
case 'NCPS':
|
||||
return FlushMountedPresenceSensorControlView(device: device);
|
||||
default:
|
||||
return const SizedBox();
|
||||
}
|
||||
@ -194,6 +199,10 @@ mixin RouteControlsBasedCode {
|
||||
return SOSBatchControlView(
|
||||
deviceIds: devices.where((e) => (e.productType == 'SOS')).map((e) => e.uuid!).toList(),
|
||||
);
|
||||
case 'NCPS':
|
||||
return FlushMountedPresenceSensorBatchControlView(
|
||||
devicesIds: devices.where((e) => (e.productType == 'NCPS')).map((e) => e.uuid!).toList(),
|
||||
);
|
||||
default:
|
||||
return const SizedBox();
|
||||
}
|
||||
|
@ -0,0 +1,250 @@
|
||||
import 'dart:async';
|
||||
import 'dart:developer';
|
||||
|
||||
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_reports.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/flush_mounted_presence_sensor/models/flush_mounted_presence_sensor_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 'flush_mounted_presence_sensor_event.dart';
|
||||
part 'flush_mounted_presence_sensor_state.dart';
|
||||
|
||||
class FlushMountedPresenceSensorBloc
|
||||
extends Bloc<FlushMountedPresenceSensorEvent, FlushMountedPresenceSensorState> {
|
||||
final String deviceId;
|
||||
final ControlDeviceService controlDeviceService;
|
||||
final BatchControlDevicesService batchControlDevicesService;
|
||||
|
||||
late FlushMountedPresenceSensorModel deviceStatus;
|
||||
FlushMountedPresenceSensorBloc({
|
||||
required this.deviceId,
|
||||
required this.controlDeviceService,
|
||||
required this.batchControlDevicesService,
|
||||
}) : super(FlushMountedPresenceSensorInitialState()) {
|
||||
on<FlushMountedPresenceSensorFetchStatusEvent>(
|
||||
_onFlushMountedPresenceSensorFetchStatusEvent,
|
||||
);
|
||||
on<FlushMountedPresenceSensorFetchBatchStatusEvent>(
|
||||
_onFlushMountedPresenceSensorFetchBatchStatusEvent);
|
||||
on<FlushMountedPresenceSensorChangeValueEvent>(
|
||||
_onFlushMountedPresenceSensorChangeValueEvent,
|
||||
);
|
||||
on<FlushMountedPresenceSensorBatchControlEvent>(
|
||||
_onFlushMountedPresenceSensorBatchControlEvent,
|
||||
);
|
||||
on<FlushMountedPresenceSensorGetDeviceReportsEvent>(
|
||||
_onFlushMountedPresenceSensorGetDeviceReportsEvent);
|
||||
on<FlushMountedPresenceSensorShowDescriptionEvent>(
|
||||
_onFlushMountedPresenceSensorShowDescriptionEvent,
|
||||
);
|
||||
on<FlushMountedPresenceSensorBackToGridViewEvent>(
|
||||
_onFlushMountedPresenceSensorBackToGridViewEvent,
|
||||
);
|
||||
on<FlushMountedPresenceSensorFactoryResetEvent>(
|
||||
_onFlushMountedPresenceSensorFactoryResetEvent,
|
||||
);
|
||||
on<FlushMountedPresenceSensorStatusUpdatedEvent>(
|
||||
_onFlushMountedPresenceSensorStatusUpdatedEvent,
|
||||
);
|
||||
}
|
||||
|
||||
void _onFlushMountedPresenceSensorFetchStatusEvent(
|
||||
FlushMountedPresenceSensorFetchStatusEvent event,
|
||||
Emitter<FlushMountedPresenceSensorState> emit,
|
||||
) async {
|
||||
emit(FlushMountedPresenceSensorLoadingInitialState());
|
||||
try {
|
||||
final response = await DevicesManagementApi().getDeviceStatus(deviceId);
|
||||
deviceStatus = FlushMountedPresenceSensorModel.fromJson(response.status);
|
||||
emit(FlushMountedPresenceSensorUpdateState(model: deviceStatus));
|
||||
_listenToChanges(deviceId);
|
||||
} catch (e) {
|
||||
emit(FlushMountedPresenceSensorFailedState(error: e.toString()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onFlushMountedPresenceSensorFetchBatchStatusEvent(
|
||||
FlushMountedPresenceSensorFetchBatchStatusEvent event,
|
||||
Emitter<FlushMountedPresenceSensorState> emit,
|
||||
) async {
|
||||
emit(FlushMountedPresenceSensorLoadingInitialState());
|
||||
try {
|
||||
final response = await DevicesManagementApi().getBatchStatus(event.devicesIds);
|
||||
deviceStatus = FlushMountedPresenceSensorModel.fromJson(response.status);
|
||||
emit(FlushMountedPresenceSensorUpdateState(model: deviceStatus));
|
||||
} catch (e) {
|
||||
emit(FlushMountedPresenceSensorFailedState(error: e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
void _listenToChanges(String deviceId) {
|
||||
try {
|
||||
final ref = FirebaseDatabase.instance.ref(
|
||||
'device-status/$deviceId',
|
||||
);
|
||||
|
||||
ref.onValue.listen((event) {
|
||||
final eventsMap = event.snapshot.value as Map<dynamic, dynamic>;
|
||||
|
||||
List<Status> statusList = [];
|
||||
eventsMap['status'].forEach((element) {
|
||||
statusList.add(
|
||||
Status(code: element['code'], value: element['value']),
|
||||
);
|
||||
});
|
||||
|
||||
deviceStatus = FlushMountedPresenceSensorModel.fromJson(statusList);
|
||||
if (!isClosed) {
|
||||
add(FlushMountedPresenceSensorStatusUpdatedEvent(deviceStatus));
|
||||
}
|
||||
});
|
||||
} catch (_) {
|
||||
log(
|
||||
'Error listening to changes',
|
||||
name: 'FlushMountedPresenceSensorBloc._listenToChanges',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _onFlushMountedPresenceSensorChangeValueEvent(
|
||||
FlushMountedPresenceSensorChangeValueEvent event,
|
||||
Emitter<FlushMountedPresenceSensorState> emit,
|
||||
) async {
|
||||
emit(FlushMountedPresenceSensorLoadingNewSate(model: deviceStatus));
|
||||
_updateDeviceFunctionFromCode(event.code, event.value);
|
||||
emit(FlushMountedPresenceSensorUpdateState(model: deviceStatus));
|
||||
try {
|
||||
await controlDeviceService.controlDevice(
|
||||
deviceUuid: deviceId,
|
||||
status: Status(code: event.code, value: event.value),
|
||||
);
|
||||
} catch (_) {
|
||||
await _reloadDeviceStatus();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onFlushMountedPresenceSensorBatchControlEvent(
|
||||
FlushMountedPresenceSensorBatchControlEvent event,
|
||||
Emitter<FlushMountedPresenceSensorState> emit,
|
||||
) async {
|
||||
emit(FlushMountedPresenceSensorLoadingNewSate(model: deviceStatus));
|
||||
_updateDeviceFunctionFromCode(event.code, event.value);
|
||||
emit(FlushMountedPresenceSensorUpdateState(model: deviceStatus));
|
||||
|
||||
try {
|
||||
await batchControlDevicesService.batchControlDevices(
|
||||
uuids: event.deviceIds,
|
||||
code: event.code,
|
||||
value: event.value,
|
||||
);
|
||||
} catch (_) {
|
||||
await _reloadDeviceStatus();
|
||||
}
|
||||
}
|
||||
|
||||
void _updateDeviceFunctionFromCode(String code, int value) {
|
||||
switch (code) {
|
||||
case FlushMountedPresenceSensorModel.codeFarDetection:
|
||||
deviceStatus.farDetection = value;
|
||||
break;
|
||||
case FlushMountedPresenceSensorModel.codeSensitivity:
|
||||
deviceStatus.sensitivity = value;
|
||||
break;
|
||||
case FlushMountedPresenceSensorModel.codeNoneDelay:
|
||||
deviceStatus.noneDelay = value;
|
||||
break;
|
||||
case FlushMountedPresenceSensorModel.codePresenceDelay:
|
||||
deviceStatus.presenceDelay = value;
|
||||
break;
|
||||
case FlushMountedPresenceSensorModel.codeNearDetection:
|
||||
deviceStatus.nearDetection = value;
|
||||
break;
|
||||
case FlushMountedPresenceSensorModel.codeOccurDistReduce:
|
||||
deviceStatus.occurDistReduce = value;
|
||||
break;
|
||||
case FlushMountedPresenceSensorModel.codeSensiReduce:
|
||||
deviceStatus.sensiReduce = value;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _reloadDeviceStatus() async {
|
||||
await Future.delayed(const Duration(milliseconds: 500), () {
|
||||
add(FlushMountedPresenceSensorFetchStatusEvent());
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _onFlushMountedPresenceSensorGetDeviceReportsEvent(
|
||||
FlushMountedPresenceSensorGetDeviceReportsEvent event,
|
||||
Emitter<FlushMountedPresenceSensorState> emit,
|
||||
) async {
|
||||
emit(FlushMountedPresenceSensorDeviceReportsLoadingState());
|
||||
|
||||
try {
|
||||
await DevicesManagementApi.getDeviceReports(deviceId, event.code)
|
||||
.then((value) {
|
||||
emit(FlushMountedPresenceSensorDeviceReportsState(
|
||||
deviceReport: value, code: event.code));
|
||||
});
|
||||
} catch (e) {
|
||||
emit(FlushMountedPresenceSensorDeviceReportsFailedState(error: e.toString()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void _onFlushMountedPresenceSensorShowDescriptionEvent(
|
||||
FlushMountedPresenceSensorShowDescriptionEvent event,
|
||||
Emitter<FlushMountedPresenceSensorState> emit,
|
||||
) {
|
||||
emit(FlushMountedPresenceSensorShowDescriptionState(
|
||||
description: event.description));
|
||||
}
|
||||
|
||||
void _onFlushMountedPresenceSensorBackToGridViewEvent(
|
||||
FlushMountedPresenceSensorBackToGridViewEvent event,
|
||||
Emitter<FlushMountedPresenceSensorState> emit,
|
||||
) {
|
||||
emit(FlushMountedPresenceSensorUpdateState(model: deviceStatus));
|
||||
}
|
||||
|
||||
Future<void> _onFlushMountedPresenceSensorFactoryResetEvent(
|
||||
FlushMountedPresenceSensorFactoryResetEvent event,
|
||||
Emitter<FlushMountedPresenceSensorState> emit,
|
||||
) async {
|
||||
emit(FlushMountedPresenceSensorLoadingNewSate(model: deviceStatus));
|
||||
try {
|
||||
final response = await DevicesManagementApi().factoryReset(
|
||||
event.factoryReset,
|
||||
event.deviceId,
|
||||
);
|
||||
if (!response) {
|
||||
emit(
|
||||
const FlushMountedPresenceSensorFailedState(
|
||||
error: 'Something went wrong with factory reset, please try again',
|
||||
),
|
||||
);
|
||||
} else {
|
||||
emit(FlushMountedPresenceSensorUpdateState(model: deviceStatus));
|
||||
}
|
||||
} catch (e) {
|
||||
emit(FlushMountedPresenceSensorFailedState(error: e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
void _onFlushMountedPresenceSensorStatusUpdatedEvent(
|
||||
FlushMountedPresenceSensorStatusUpdatedEvent event,
|
||||
Emitter<FlushMountedPresenceSensorState> emit,
|
||||
) {
|
||||
deviceStatus = event.model;
|
||||
emit(FlushMountedPresenceSensorUpdateState(model: deviceStatus));
|
||||
}
|
||||
}
|
@ -0,0 +1,94 @@
|
||||
part of 'flush_mounted_presence_sensor_bloc.dart';
|
||||
|
||||
sealed class FlushMountedPresenceSensorEvent extends Equatable {
|
||||
const FlushMountedPresenceSensorEvent();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
class FlushMountedPresenceSensorFetchStatusEvent
|
||||
extends FlushMountedPresenceSensorEvent {}
|
||||
|
||||
class FlushMountedPresenceSensorStatusUpdatedEvent
|
||||
extends FlushMountedPresenceSensorEvent {
|
||||
const FlushMountedPresenceSensorStatusUpdatedEvent(this.model);
|
||||
|
||||
final FlushMountedPresenceSensorModel model;
|
||||
|
||||
@override
|
||||
List<Object> get props => [model];
|
||||
}
|
||||
|
||||
class FlushMountedPresenceSensorChangeValueEvent
|
||||
extends FlushMountedPresenceSensorEvent {
|
||||
final int value;
|
||||
final String code;
|
||||
final bool isBatchControl;
|
||||
const FlushMountedPresenceSensorChangeValueEvent({
|
||||
required this.value,
|
||||
required this.code,
|
||||
this.isBatchControl = false,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object> get props => [value, code];
|
||||
}
|
||||
|
||||
class FlushMountedPresenceSensorFetchBatchStatusEvent
|
||||
extends FlushMountedPresenceSensorEvent {
|
||||
final List<String> devicesIds;
|
||||
const FlushMountedPresenceSensorFetchBatchStatusEvent(this.devicesIds);
|
||||
|
||||
@override
|
||||
List<Object> get props => [devicesIds];
|
||||
}
|
||||
|
||||
class FlushMountedPresenceSensorGetDeviceReportsEvent
|
||||
extends FlushMountedPresenceSensorEvent {
|
||||
final String deviceUuid;
|
||||
final String code;
|
||||
const FlushMountedPresenceSensorGetDeviceReportsEvent({
|
||||
required this.deviceUuid,
|
||||
required this.code,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object> get props => [deviceUuid, code];
|
||||
}
|
||||
|
||||
class FlushMountedPresenceSensorShowDescriptionEvent
|
||||
extends FlushMountedPresenceSensorEvent {
|
||||
final String description;
|
||||
const FlushMountedPresenceSensorShowDescriptionEvent({required this.description});
|
||||
}
|
||||
|
||||
class FlushMountedPresenceSensorBackToGridViewEvent
|
||||
extends FlushMountedPresenceSensorEvent {}
|
||||
|
||||
class FlushMountedPresenceSensorBatchControlEvent
|
||||
extends FlushMountedPresenceSensorEvent {
|
||||
final List<String> deviceIds;
|
||||
final String code;
|
||||
final dynamic value;
|
||||
|
||||
const FlushMountedPresenceSensorBatchControlEvent({
|
||||
required this.deviceIds,
|
||||
required this.code,
|
||||
required this.value,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object> get props => [deviceIds, code, value];
|
||||
}
|
||||
|
||||
class FlushMountedPresenceSensorFactoryResetEvent
|
||||
extends FlushMountedPresenceSensorEvent {
|
||||
final String deviceId;
|
||||
final FactoryResetModel factoryReset;
|
||||
|
||||
const FlushMountedPresenceSensorFactoryResetEvent({
|
||||
required this.deviceId,
|
||||
required this.factoryReset,
|
||||
});
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
part of 'flush_mounted_presence_sensor_bloc.dart';
|
||||
|
||||
sealed class FlushMountedPresenceSensorState extends Equatable {
|
||||
const FlushMountedPresenceSensorState();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
class FlushMountedPresenceSensorInitialState
|
||||
extends FlushMountedPresenceSensorState {}
|
||||
|
||||
class FlushMountedPresenceSensorLoadingInitialState
|
||||
extends FlushMountedPresenceSensorState {}
|
||||
|
||||
class FlushMountedPresenceSensorUpdateState extends FlushMountedPresenceSensorState {
|
||||
final FlushMountedPresenceSensorModel model;
|
||||
const FlushMountedPresenceSensorUpdateState({required this.model});
|
||||
|
||||
@override
|
||||
List<Object> get props => [model];
|
||||
}
|
||||
|
||||
class FlushMountedPresenceSensorLoadingNewSate
|
||||
extends FlushMountedPresenceSensorState {
|
||||
final FlushMountedPresenceSensorModel model;
|
||||
const FlushMountedPresenceSensorLoadingNewSate({required this.model});
|
||||
|
||||
@override
|
||||
List<Object> get props => [model];
|
||||
}
|
||||
|
||||
class FlushMountedPresenceSensorFailedState extends FlushMountedPresenceSensorState {
|
||||
final String error;
|
||||
|
||||
const FlushMountedPresenceSensorFailedState({required this.error});
|
||||
|
||||
@override
|
||||
List<Object> get props => [error];
|
||||
}
|
||||
|
||||
class FlushMountedPresenceSensorDeviceReportsLoadingState
|
||||
extends FlushMountedPresenceSensorState {}
|
||||
|
||||
class FlushMountedPresenceSensorDeviceReportsState
|
||||
extends FlushMountedPresenceSensorState {
|
||||
const FlushMountedPresenceSensorDeviceReportsState({
|
||||
required this.deviceReport,
|
||||
required this.code,
|
||||
});
|
||||
|
||||
final DeviceReport deviceReport;
|
||||
final String code;
|
||||
|
||||
@override
|
||||
List<Object> get props => [deviceReport, code];
|
||||
}
|
||||
|
||||
class FlushMountedPresenceSensorDeviceReportsFailedState
|
||||
extends FlushMountedPresenceSensorState {
|
||||
const FlushMountedPresenceSensorDeviceReportsFailedState({required this.error});
|
||||
|
||||
final String error;
|
||||
|
||||
@override
|
||||
List<Object> get props => [error];
|
||||
}
|
||||
|
||||
class FlushMountedPresenceSensorShowDescriptionState
|
||||
extends FlushMountedPresenceSensorState {
|
||||
const FlushMountedPresenceSensorShowDescriptionState({required this.description});
|
||||
|
||||
final String description;
|
||||
@override
|
||||
List<Object> get props => [description];
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
import 'package:syncrow_web/pages/device_managment/flush_mounted_presence_sensor/bloc/flush_mounted_presence_sensor_bloc.dart';
|
||||
import 'package:syncrow_web/services/batch_control_devices_service.dart';
|
||||
import 'package:syncrow_web/services/control_device_service.dart';
|
||||
|
||||
abstract final class FlushMountedPresenceSensorBlocFactory {
|
||||
const FlushMountedPresenceSensorBlocFactory._();
|
||||
|
||||
static FlushMountedPresenceSensorBloc create({
|
||||
required String deviceId,
|
||||
}) {
|
||||
return FlushMountedPresenceSensorBloc(
|
||||
deviceId: deviceId,
|
||||
controlDeviceService: DebouncedControlDeviceService(
|
||||
decoratee: RemoteControlDeviceService(),
|
||||
),
|
||||
batchControlDevicesService: DebouncedBatchControlDevicesService(
|
||||
decoratee: RemoteBatchControlDevicesService(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,99 @@
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
|
||||
|
||||
class FlushMountedPresenceSensorModel {
|
||||
FlushMountedPresenceSensorModel({
|
||||
required this.presenceState,
|
||||
required this.farDetection,
|
||||
required this.illuminance,
|
||||
required this.sensitivity,
|
||||
required this.occurDistReduce,
|
||||
required this.noneDelay,
|
||||
required this.presenceDelay,
|
||||
required this.nearDetection,
|
||||
required this.sensiReduce,
|
||||
required this.checkingResult,
|
||||
});
|
||||
|
||||
static const String codePresenceState = 'presence_state';
|
||||
static const String codeSensitivity = 'sensitivity';
|
||||
static const String codeNearDetection = 'near_detection';
|
||||
static const String codeFarDetection = 'far_detection';
|
||||
static const String codeCheckingResult = 'checking_result';
|
||||
static const String codePresenceDelay = 'presence_delay';
|
||||
static const String codeNoneDelay = 'none_delay';
|
||||
static const String codeOccurDistReduce = 'occur_dist_reduce';
|
||||
static const String codeIlluminance = 'illum_value';
|
||||
static const String codeSensiReduce = 'sensi_reduce';
|
||||
|
||||
String presenceState;
|
||||
int sensitivity;
|
||||
int nearDetection;
|
||||
int farDetection;
|
||||
String checkingResult;
|
||||
int presenceDelay;
|
||||
int noneDelay;
|
||||
int occurDistReduce;
|
||||
int illuminance;
|
||||
int sensiReduce;
|
||||
|
||||
factory FlushMountedPresenceSensorModel.fromJson(List<Status> jsonList) {
|
||||
String presenceState = 'none';
|
||||
int sensitivity = 0;
|
||||
int nearDetection = 0;
|
||||
int farDetection = 0;
|
||||
String checkingResult = 'none';
|
||||
int presenceDelay = 0;
|
||||
int noneDelay = 0;
|
||||
int occurDistReduce = 0;
|
||||
int illuminance = 0;
|
||||
int sensiReduce = 0;
|
||||
|
||||
for (var status in jsonList) {
|
||||
switch (status.code) {
|
||||
case codePresenceState:
|
||||
presenceState = status.value ?? 'presence';
|
||||
break;
|
||||
case codeSensitivity:
|
||||
sensitivity = status.value ?? 0;
|
||||
break;
|
||||
case codeNearDetection:
|
||||
nearDetection = status.value ?? 0;
|
||||
break;
|
||||
case codeFarDetection:
|
||||
farDetection = status.value ?? 0;
|
||||
break;
|
||||
case codeCheckingResult:
|
||||
checkingResult = status.value ?? 'check_success';
|
||||
break;
|
||||
case codePresenceDelay:
|
||||
presenceDelay = status.value ?? 0;
|
||||
break;
|
||||
case codeNoneDelay:
|
||||
noneDelay = status.value ?? 0;
|
||||
break;
|
||||
case codeOccurDistReduce:
|
||||
occurDistReduce = status.value ?? 0;
|
||||
break;
|
||||
case codeIlluminance:
|
||||
illuminance = status.value ?? 0;
|
||||
break;
|
||||
case codeSensiReduce:
|
||||
sensiReduce = status.value ?? 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return FlushMountedPresenceSensorModel(
|
||||
presenceState: presenceState,
|
||||
sensitivity: sensitivity,
|
||||
nearDetection: nearDetection,
|
||||
farDetection: farDetection,
|
||||
checkingResult: checkingResult,
|
||||
presenceDelay: presenceDelay,
|
||||
noneDelay: noneDelay,
|
||||
occurDistReduce: occurDistReduce,
|
||||
illuminance: illuminance,
|
||||
sensiReduce: sensiReduce,
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,183 @@
|
||||
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/flush_mounted_presence_sensor/bloc/flush_mounted_presence_sensor_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/flush_mounted_presence_sensor/factories/flush_mounted_presence_sensor_bloc_factory.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/flush_mounted_presence_sensor/models/flush_mounted_presence_sensor_model.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_update_data.dart';
|
||||
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
|
||||
|
||||
class FlushMountedPresenceSensorBatchControlView extends StatelessWidget
|
||||
with HelperResponsiveLayout {
|
||||
const FlushMountedPresenceSensorBatchControlView({
|
||||
required this.devicesIds,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final List<String> devicesIds;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => FlushMountedPresenceSensorBlocFactory.create(
|
||||
deviceId: devicesIds.first,
|
||||
)..add(FlushMountedPresenceSensorFetchBatchStatusEvent(devicesIds)),
|
||||
child: BlocBuilder<FlushMountedPresenceSensorBloc,
|
||||
FlushMountedPresenceSensorState>(
|
||||
builder: (context, state) {
|
||||
if (state is FlushMountedPresenceSensorLoadingInitialState ||
|
||||
state is FlushMountedPresenceSensorDeviceReportsLoadingState) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
} else if (state is FlushMountedPresenceSensorUpdateState) {
|
||||
return _buildGridView(context, state.model);
|
||||
}
|
||||
return const Center(child: Text('Error fetching status'));
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildGridView(
|
||||
BuildContext context,
|
||||
FlushMountedPresenceSensorModel model,
|
||||
) {
|
||||
final isExtraLarge = isExtraLargeScreenSize(context);
|
||||
final isLarge = isLargeScreenSize(context);
|
||||
final isMedium = isMediumScreenSize(context);
|
||||
return GridView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 50),
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: isLarge || isExtraLarge
|
||||
? 3
|
||||
: isMedium
|
||||
? 2
|
||||
: 1,
|
||||
mainAxisExtent: 140,
|
||||
crossAxisSpacing: 12,
|
||||
mainAxisSpacing: 12,
|
||||
),
|
||||
children: [
|
||||
PresenceUpdateData(
|
||||
value: model.sensitivity.toDouble(),
|
||||
title: 'Sensitivity:',
|
||||
minValue: 0,
|
||||
maxValue: 9,
|
||||
steps: 1,
|
||||
action: (int value) => context.read<FlushMountedPresenceSensorBloc>().add(
|
||||
FlushMountedPresenceSensorBatchControlEvent(
|
||||
deviceIds: devicesIds,
|
||||
code: FlushMountedPresenceSensorModel.codeSensitivity,
|
||||
value: value,
|
||||
),
|
||||
),
|
||||
),
|
||||
PresenceUpdateData(
|
||||
value: (model.nearDetection / 100).toDouble(),
|
||||
title: 'Nearest Detect Dist:',
|
||||
description: 'm',
|
||||
minValue: 0.0,
|
||||
maxValue: 9.5,
|
||||
steps: 0.1,
|
||||
valuesPercision: 1,
|
||||
action: (double value) =>
|
||||
context.read<FlushMountedPresenceSensorBloc>().add(
|
||||
FlushMountedPresenceSensorBatchControlEvent(
|
||||
deviceIds: devicesIds,
|
||||
code: FlushMountedPresenceSensorModel.codeNearDetection,
|
||||
value: (value * 100).toInt(),
|
||||
),
|
||||
),
|
||||
),
|
||||
PresenceUpdateData(
|
||||
value: (model.farDetection / 100).toDouble(),
|
||||
title: 'Max Detect Dist:',
|
||||
description: 'm',
|
||||
minValue: 0.0,
|
||||
maxValue: 9.5,
|
||||
steps: 0.1,
|
||||
valuesPercision: 1,
|
||||
action: (double value) =>
|
||||
context.read<FlushMountedPresenceSensorBloc>().add(
|
||||
FlushMountedPresenceSensorBatchControlEvent(
|
||||
deviceIds: devicesIds,
|
||||
code: FlushMountedPresenceSensorModel.codeFarDetection,
|
||||
value: (value * 100).toInt(),
|
||||
),
|
||||
),
|
||||
),
|
||||
PresenceUpdateData(
|
||||
value: model.presenceDelay.toDouble(),
|
||||
title: 'Trigger Level:',
|
||||
minValue: 0,
|
||||
maxValue: 3,
|
||||
steps: 1,
|
||||
action: (int value) => context.read<FlushMountedPresenceSensorBloc>().add(
|
||||
FlushMountedPresenceSensorBatchControlEvent(
|
||||
deviceIds: devicesIds,
|
||||
code: FlushMountedPresenceSensorModel.codePresenceDelay,
|
||||
value: value,
|
||||
),
|
||||
),
|
||||
),
|
||||
PresenceUpdateData(
|
||||
value: model.occurDistReduce.toDouble(),
|
||||
title: 'Indent Level:',
|
||||
minValue: 0,
|
||||
maxValue: 3,
|
||||
steps: 1,
|
||||
action: (int value) => context.read<FlushMountedPresenceSensorBloc>().add(
|
||||
FlushMountedPresenceSensorBatchControlEvent(
|
||||
deviceIds: devicesIds,
|
||||
code: FlushMountedPresenceSensorModel.codeOccurDistReduce,
|
||||
value: value,
|
||||
),
|
||||
),
|
||||
),
|
||||
PresenceUpdateData(
|
||||
value: (model.sensiReduce.toDouble()),
|
||||
title: 'Target Confirm Time:',
|
||||
description: 's',
|
||||
minValue: 0,
|
||||
maxValue: 3,
|
||||
steps: 1,
|
||||
action: (int value) => context.read<FlushMountedPresenceSensorBloc>().add(
|
||||
FlushMountedPresenceSensorBatchControlEvent(
|
||||
deviceIds: devicesIds,
|
||||
code: FlushMountedPresenceSensorModel.codeSensiReduce,
|
||||
value: value,
|
||||
),
|
||||
),
|
||||
),
|
||||
PresenceUpdateData(
|
||||
value: ((model.noneDelay / 10).toDouble()),
|
||||
description: 's',
|
||||
title: 'Disappe Delay:',
|
||||
minValue: 20,
|
||||
maxValue: 300,
|
||||
steps: 1,
|
||||
action: (double value) =>
|
||||
context.read<FlushMountedPresenceSensorBloc>().add(
|
||||
FlushMountedPresenceSensorBatchControlEvent(
|
||||
deviceIds: devicesIds,
|
||||
code: FlushMountedPresenceSensorModel.codeNoneDelay,
|
||||
value: (value * 10).round(),
|
||||
),
|
||||
),
|
||||
),
|
||||
FactoryResetWidget(
|
||||
callFactoryReset: () {
|
||||
context.read<FlushMountedPresenceSensorBloc>().add(
|
||||
FlushMountedPresenceSensorFactoryResetEvent(
|
||||
deviceId: devicesIds.first,
|
||||
factoryReset: FactoryResetModel(devicesUuid: devicesIds),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,217 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/flush_mounted_presence_sensor/bloc/flush_mounted_presence_sensor_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/flush_mounted_presence_sensor/factories/flush_mounted_presence_sensor_bloc_factory.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/flush_mounted_presence_sensor/models/flush_mounted_presence_sensor_model.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_display_data.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_static_widget.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_status.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_update_data.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/table/description_view.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/table/report_table.dart';
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
|
||||
|
||||
class FlushMountedPresenceSensorControlView extends StatelessWidget
|
||||
with HelperResponsiveLayout {
|
||||
const FlushMountedPresenceSensorControlView({super.key, required this.device});
|
||||
|
||||
final AllDevicesModel device;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => FlushMountedPresenceSensorBlocFactory.create(
|
||||
deviceId: device.uuid ?? '-1',
|
||||
)..add(FlushMountedPresenceSensorFetchStatusEvent()),
|
||||
child: BlocBuilder<FlushMountedPresenceSensorBloc,
|
||||
FlushMountedPresenceSensorState>(
|
||||
builder: (context, state) {
|
||||
if (state is FlushMountedPresenceSensorLoadingInitialState ||
|
||||
state is FlushMountedPresenceSensorDeviceReportsLoadingState) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
} else if (state is FlushMountedPresenceSensorUpdateState) {
|
||||
return _buildGridView(context, state.model);
|
||||
} else if (state is FlushMountedPresenceSensorDeviceReportsState) {
|
||||
return ReportsTable(
|
||||
report: state.deviceReport,
|
||||
thirdColumnTitle:
|
||||
state.code == 'illuminance_value' ? "Value" : 'Status',
|
||||
thirdColumnDescription:
|
||||
state.code == 'illuminance_value' ? "Lux" : null,
|
||||
onRowTap: (index) {},
|
||||
onClose: () {
|
||||
context
|
||||
.read<FlushMountedPresenceSensorBloc>()
|
||||
.add(FlushMountedPresenceSensorBackToGridViewEvent());
|
||||
},
|
||||
);
|
||||
} else if (state is FlushMountedPresenceSensorShowDescriptionState) {
|
||||
return DescriptionView(
|
||||
description: state.description,
|
||||
onClose: () {
|
||||
context
|
||||
.read<FlushMountedPresenceSensorBloc>()
|
||||
.add(FlushMountedPresenceSensorBackToGridViewEvent());
|
||||
},
|
||||
);
|
||||
} else if (state is FlushMountedPresenceSensorDeviceReportsFailedState) {
|
||||
final model =
|
||||
context.read<FlushMountedPresenceSensorBloc>().deviceStatus;
|
||||
return _buildGridView(context, model);
|
||||
}
|
||||
return const Center(
|
||||
child: Text('Error fetching status', textAlign: TextAlign.center),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildGridView(
|
||||
BuildContext context,
|
||||
FlushMountedPresenceSensorModel model,
|
||||
) {
|
||||
final isExtraLarge = isExtraLargeScreenSize(context);
|
||||
final isLarge = isLargeScreenSize(context);
|
||||
final isMedium = isMediumScreenSize(context);
|
||||
return GridView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 50),
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: isLarge || isExtraLarge
|
||||
? 3
|
||||
: isMedium
|
||||
? 2
|
||||
: 1,
|
||||
mainAxisExtent: 140,
|
||||
crossAxisSpacing: 12,
|
||||
mainAxisSpacing: 12,
|
||||
),
|
||||
children: [
|
||||
PresenceState(
|
||||
value: model.presenceState,
|
||||
),
|
||||
PresenceDisplayValue(
|
||||
value: model.illuminance.toString(),
|
||||
postfix: 'Lux',
|
||||
description: 'Illuminance Value',
|
||||
),
|
||||
PresenceUpdateData(
|
||||
value: model.sensitivity.toDouble(),
|
||||
title: 'Sensitivity:',
|
||||
minValue: 0,
|
||||
maxValue: 9,
|
||||
steps: 1,
|
||||
action: (int value) => context.read<FlushMountedPresenceSensorBloc>().add(
|
||||
FlushMountedPresenceSensorChangeValueEvent(
|
||||
code: FlushMountedPresenceSensorModel.codeSensitivity,
|
||||
value: value,
|
||||
),
|
||||
),
|
||||
),
|
||||
PresenceUpdateData(
|
||||
value: (model.nearDetection / 100).toDouble(),
|
||||
title: 'Nearest Detect Dist:',
|
||||
description: 'm',
|
||||
minValue: 0.0,
|
||||
maxValue: 9.5,
|
||||
steps: 0.1,
|
||||
valuesPercision: 1,
|
||||
action: (double value) =>
|
||||
context.read<FlushMountedPresenceSensorBloc>().add(
|
||||
FlushMountedPresenceSensorChangeValueEvent(
|
||||
code: FlushMountedPresenceSensorModel.codeNearDetection,
|
||||
value: (value * 100).toInt(),
|
||||
),
|
||||
),
|
||||
),
|
||||
PresenceUpdateData(
|
||||
value: (model.farDetection / 100).toDouble(),
|
||||
title: 'Max Detect Dist:',
|
||||
description: 'm',
|
||||
minValue: 0.0,
|
||||
maxValue: 9.5,
|
||||
steps: 0.1,
|
||||
valuesPercision: 1,
|
||||
action: (double value) =>
|
||||
context.read<FlushMountedPresenceSensorBloc>().add(
|
||||
FlushMountedPresenceSensorChangeValueEvent(
|
||||
code: FlushMountedPresenceSensorModel.codeFarDetection,
|
||||
value: (value * 100).toInt(),
|
||||
),
|
||||
),
|
||||
),
|
||||
PresenceUpdateData(
|
||||
value: (model.presenceDelay.toDouble()),
|
||||
title: 'Trigger Level:',
|
||||
minValue: 0,
|
||||
maxValue: 3,
|
||||
steps: 1,
|
||||
action: (int value) => context.read<FlushMountedPresenceSensorBloc>().add(
|
||||
FlushMountedPresenceSensorChangeValueEvent(
|
||||
code: FlushMountedPresenceSensorModel.codePresenceDelay,
|
||||
value: value,
|
||||
),
|
||||
),
|
||||
),
|
||||
PresenceUpdateData(
|
||||
value: (model.occurDistReduce.toDouble()),
|
||||
title: 'Indent Level:',
|
||||
minValue: 0,
|
||||
maxValue: 3,
|
||||
steps: 1,
|
||||
action: (int value) => context.read<FlushMountedPresenceSensorBloc>().add(
|
||||
FlushMountedPresenceSensorChangeValueEvent(
|
||||
code: FlushMountedPresenceSensorModel.codeOccurDistReduce,
|
||||
value: value,
|
||||
),
|
||||
),
|
||||
),
|
||||
PresenceUpdateData(
|
||||
value: (model.sensiReduce.toDouble()),
|
||||
title: 'Target Confirm Time:',
|
||||
description: 's',
|
||||
minValue: 0,
|
||||
maxValue: 3,
|
||||
steps: 1,
|
||||
action: (int value) => context.read<FlushMountedPresenceSensorBloc>().add(
|
||||
FlushMountedPresenceSensorChangeValueEvent(
|
||||
code: FlushMountedPresenceSensorModel.codeSensiReduce,
|
||||
value: value,
|
||||
),
|
||||
),
|
||||
),
|
||||
PresenceUpdateData(
|
||||
value: ((model.noneDelay / 10).toDouble()),
|
||||
description: 's',
|
||||
title: 'Disappe Delay:',
|
||||
minValue: 20,
|
||||
maxValue: 300,
|
||||
steps: 1,
|
||||
action: (double value) =>
|
||||
context.read<FlushMountedPresenceSensorBloc>().add(
|
||||
FlushMountedPresenceSensorChangeValueEvent(
|
||||
code: FlushMountedPresenceSensorModel.codeNoneDelay,
|
||||
value: (value * 10).round(),
|
||||
),
|
||||
),
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: () => context.read<FlushMountedPresenceSensorBloc>().add(
|
||||
FlushMountedPresenceSensorGetDeviceReportsEvent(
|
||||
code: 'presence_state',
|
||||
deviceUuid: device.uuid!,
|
||||
),
|
||||
),
|
||||
child: const PresenceStaticWidget(
|
||||
icon: Assets.presenceRecordIcon,
|
||||
description: 'Presence Record',
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/device_controls_container.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/increament_decreament.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
|
||||
class PresenceUpdateData extends StatefulWidget {
|
||||
const PresenceUpdateData({
|
||||
@ -13,6 +14,7 @@ class PresenceUpdateData extends StatefulWidget {
|
||||
required this.maxValue,
|
||||
required this.steps,
|
||||
this.description,
|
||||
this.valuesPercision = 0,
|
||||
});
|
||||
|
||||
final String title;
|
||||
@ -22,6 +24,7 @@ class PresenceUpdateData extends StatefulWidget {
|
||||
final double steps;
|
||||
final Function action;
|
||||
final String? description;
|
||||
final int valuesPercision;
|
||||
|
||||
@override
|
||||
State<PresenceUpdateData> createState() => _CurrentTempState();
|
||||
@ -45,7 +48,7 @@ class _CurrentTempState extends State<PresenceUpdateData> {
|
||||
}
|
||||
|
||||
void _onValueChanged(double newValue) {
|
||||
widget.action(newValue.toInt());
|
||||
widget.action(newValue);
|
||||
}
|
||||
|
||||
@override
|
||||
@ -62,11 +65,14 @@ class _CurrentTempState extends State<PresenceUpdateData> {
|
||||
children: [
|
||||
Text(
|
||||
widget.title,
|
||||
style: Theme.of(context).textTheme.bodySmall!.copyWith(
|
||||
color: ColorsManager.blackColor, fontWeight: FontWeight.w400, fontSize: 10),
|
||||
style: context.textTheme.bodySmall?.copyWith(
|
||||
color: ColorsManager.blackColor,
|
||||
fontWeight: FontWeight.w400,
|
||||
fontSize: 10,
|
||||
),
|
||||
),
|
||||
IncrementDecrementWidget(
|
||||
value: widget.value.toString(),
|
||||
value: widget.value.toStringAsFixed(widget.valuesPercision),
|
||||
description: widget.description ?? '',
|
||||
descriptionColor: ColorsManager.blackColor,
|
||||
onIncrement: () {
|
||||
|
@ -5,7 +5,6 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
|
||||
import 'package:syncrow_web/pages/routines/models/device_functions.dart';
|
||||
import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart';
|
||||
import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
@ -15,13 +14,18 @@ class SaveRoutineHelper {
|
||||
static Future<void> showSaveRoutineDialog(BuildContext context) async {
|
||||
return showDialog<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
builder: (context) {
|
||||
return BlocBuilder<RoutineBloc, RoutineState>(
|
||||
builder: (context, state) {
|
||||
final selectedConditionLabel = state.selectedAutomationOperator == 'and'
|
||||
? 'All Conditions are met'
|
||||
: 'Any Condition is met';
|
||||
|
||||
return AlertDialog(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
content: Container(
|
||||
width: MediaQuery.sizeOf(context).width * 0.5,
|
||||
width: context.screenWidth * 0.5,
|
||||
height: 500,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
@ -29,99 +33,42 @@ class SaveRoutineHelper {
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
DialogHeader('Create a scene: ${state.routineName ?? ""}'),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Left side - IF
|
||||
Expanded(
|
||||
child: ListView(
|
||||
// crossAxisAlignment: CrossAxisAlignment.start,
|
||||
shrinkWrap: true,
|
||||
children: [
|
||||
const Text(
|
||||
'IF:',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
if (state.isTabToRun)
|
||||
ListTile(
|
||||
leading: SvgPicture.asset(
|
||||
Assets.tabToRun,
|
||||
width: 24,
|
||||
height: 24,
|
||||
),
|
||||
title: const Text('Tab to run'),
|
||||
),
|
||||
if (state.isAutomation)
|
||||
...state.ifItems.map((item) {
|
||||
final functions =
|
||||
state.selectedFunctions[item['uniqueCustomId']] ?? [];
|
||||
return functionRow(item, context, functions);
|
||||
}),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 18),
|
||||
Text(
|
||||
'Create a scene: ${state.routineName ?? ""}',
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.headlineMedium!.copyWith(
|
||||
color: ColorsManager.primaryColorWithOpacity,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
// Right side - THEN items
|
||||
|
||||
Expanded(
|
||||
child: ListView(
|
||||
// crossAxisAlignment: CrossAxisAlignment.start,
|
||||
shrinkWrap: true,
|
||||
children: [
|
||||
const Text(
|
||||
'THEN:',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
...state.thenItems.map((item) {
|
||||
final functions =
|
||||
state.selectedFunctions[item['uniqueCustomId']] ?? [];
|
||||
return functionRow(item, context, functions);
|
||||
}),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 18),
|
||||
_buildDivider(),
|
||||
_buildListsLabelRow(selectedConditionLabel),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsetsDirectional.symmetric(
|
||||
horizontal: 16,
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
spacing: 24,
|
||||
children: [
|
||||
_buildIfConditions(state, context),
|
||||
Container(
|
||||
width: 1,
|
||||
color: ColorsManager.greyColor.withValues(alpha: 0.8),
|
||||
),
|
||||
),
|
||||
],
|
||||
_buildThenActions(state, context),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
// if (state.errorMessage != null || state.errorMessage!.isNotEmpty)
|
||||
// Padding(
|
||||
// padding: const EdgeInsets.all(8.0),
|
||||
// child: Text(
|
||||
// state.errorMessage!,
|
||||
// style: const TextStyle(color: Colors.red),
|
||||
// ),
|
||||
// ),
|
||||
DialogFooter(
|
||||
onCancel: () => Navigator.pop(context),
|
||||
onConfirm: () async {
|
||||
if (state.isAutomation) {
|
||||
if (state.isUpdate ?? false) {
|
||||
context.read<RoutineBloc>().add(const UpdateAutomation());
|
||||
} else {
|
||||
context.read<RoutineBloc>().add(const CreateAutomationEvent());
|
||||
}
|
||||
} else {
|
||||
if (state.isUpdate ?? false) {
|
||||
context.read<RoutineBloc>().add(const UpdateScene());
|
||||
} else {
|
||||
context.read<RoutineBloc>().add(const CreateSceneEvent());
|
||||
}
|
||||
}
|
||||
// if (state.errorMessage == null || state.errorMessage!.isEmpty) {
|
||||
Navigator.pop(context);
|
||||
// }
|
||||
},
|
||||
isConfirmEnabled: true,
|
||||
),
|
||||
_buildDivider(),
|
||||
const SizedBox(height: 8),
|
||||
_buildDialogFooter(context, state),
|
||||
const SizedBox(height: 8),
|
||||
],
|
||||
),
|
||||
),
|
||||
@ -132,8 +79,107 @@ class SaveRoutineHelper {
|
||||
);
|
||||
}
|
||||
|
||||
static Container _buildDivider() {
|
||||
return Container(
|
||||
height: 1,
|
||||
width: double.infinity,
|
||||
color: ColorsManager.greyColor,
|
||||
);
|
||||
}
|
||||
|
||||
static Widget _buildListsLabelRow(String selectedConditionLabel) {
|
||||
const textStyle = TextStyle(
|
||||
fontSize: 16,
|
||||
);
|
||||
return Container(
|
||||
color: ColorsManager.backgroundColor.withValues(alpha: 0.5),
|
||||
padding: const EdgeInsetsDirectional.all(20),
|
||||
child: Row(
|
||||
spacing: 16,
|
||||
children: [
|
||||
Expanded(child: Text('IF: $selectedConditionLabel', style: textStyle)),
|
||||
const Expanded(child: Text('THEN:', style: textStyle)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
static Widget _buildDialogFooter(BuildContext context, RoutineState state) {
|
||||
return Row(
|
||||
spacing: 16,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
DialogFooterButton(
|
||||
text: 'Cancel',
|
||||
onTap: () => Navigator.pop(context),
|
||||
),
|
||||
DialogFooterButton(
|
||||
text: 'Confirm',
|
||||
onTap: () {
|
||||
if (state.isAutomation) {
|
||||
if (state.isUpdate ?? false) {
|
||||
context.read<RoutineBloc>().add(const UpdateAutomation());
|
||||
} else {
|
||||
context.read<RoutineBloc>().add(const CreateAutomationEvent());
|
||||
}
|
||||
} else {
|
||||
if (state.isUpdate ?? false) {
|
||||
context.read<RoutineBloc>().add(const UpdateScene());
|
||||
} else {
|
||||
context.read<RoutineBloc>().add(const CreateSceneEvent());
|
||||
}
|
||||
}
|
||||
|
||||
Navigator.pop(context);
|
||||
},
|
||||
textColor: ColorsManager.primaryColorWithOpacity,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
static Widget _buildThenActions(RoutineState state, BuildContext context) {
|
||||
return Expanded(
|
||||
child: ListView(
|
||||
// shrinkWrap: true,
|
||||
children: state.thenItems.map((item) {
|
||||
final functions = state.selectedFunctions[item['uniqueCustomId']] ?? [];
|
||||
return functionRow(item, context, functions);
|
||||
}).toList(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
static Widget _buildIfConditions(RoutineState state, BuildContext context) {
|
||||
return Expanded(
|
||||
child: ListView(
|
||||
// shrinkWrap: true,
|
||||
children: [
|
||||
if (state.isTabToRun)
|
||||
ListTile(
|
||||
leading: SvgPicture.asset(
|
||||
Assets.tabToRun,
|
||||
width: 24,
|
||||
height: 24,
|
||||
),
|
||||
title: const Text('Tab to run'),
|
||||
),
|
||||
if (state.isAutomation)
|
||||
...state.ifItems.map((item) {
|
||||
final functions =
|
||||
state.selectedFunctions[item['uniqueCustomId']] ?? [];
|
||||
return functionRow(item, context, functions);
|
||||
}),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
static Widget functionRow(
|
||||
dynamic item, BuildContext context, List<DeviceFunctionData> functions) {
|
||||
dynamic item,
|
||||
BuildContext context,
|
||||
List<DeviceFunctionData> functions,
|
||||
) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 6),
|
||||
child: Row(
|
||||
@ -142,19 +188,36 @@ class SaveRoutineHelper {
|
||||
Expanded(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
spacing: 8,
|
||||
spacing: 17,
|
||||
children: [
|
||||
item['type'] == 'tap_to_run' || item['type'] == 'scene'
|
||||
? Image.memory(
|
||||
base64Decode(item['icon']),
|
||||
width: 22,
|
||||
height: 22,
|
||||
)
|
||||
: SvgPicture.asset(
|
||||
item['imagePath'],
|
||||
width: 22,
|
||||
height: 22,
|
||||
),
|
||||
Container(
|
||||
width: 22,
|
||||
height: 22,
|
||||
padding: const EdgeInsetsDirectional.all(4),
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: ColorsManager.textFieldGreyColor,
|
||||
border: Border.all(
|
||||
color: ColorsManager.neutralGray,
|
||||
width: 1.5,
|
||||
),
|
||||
),
|
||||
child: Center(
|
||||
child: item['type'] == 'tap_to_run' || item['type'] == 'scene'
|
||||
? Image.memory(
|
||||
base64Decode(item['icon']),
|
||||
width: 12,
|
||||
height: 22,
|
||||
fit: BoxFit.scaleDown,
|
||||
)
|
||||
: SvgPicture.asset(
|
||||
item['imagePath'],
|
||||
width: 12,
|
||||
height: 12,
|
||||
fit: BoxFit.scaleDown,
|
||||
),
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
@ -166,19 +229,25 @@ class SaveRoutineHelper {
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: context.textTheme.bodySmall?.copyWith(
|
||||
fontSize: 14,
|
||||
fontSize: 15,
|
||||
color: ColorsManager.textPrimaryColor,
|
||||
),
|
||||
),
|
||||
Wrap(
|
||||
runSpacing: 16,
|
||||
spacing: 4,
|
||||
children: functions
|
||||
.map((f) => Text(
|
||||
'${f.operationName}: ${f.value}',
|
||||
style: context.textTheme.bodySmall
|
||||
?.copyWith(color: ColorsManager.grayColor, fontSize: 8),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 3,
|
||||
))
|
||||
.map(
|
||||
(function) => Text(
|
||||
'${function.operationName}: ${function.value}',
|
||||
style: context.textTheme.bodySmall?.copyWith(
|
||||
color: ColorsManager.grayColor,
|
||||
fontSize: 8,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 3,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
],
|
||||
@ -197,7 +266,13 @@ class SaveRoutineHelper {
|
||||
child: Row(
|
||||
spacing: 2,
|
||||
children: [
|
||||
SizedBox(width: 8, height: 8, child: SvgPicture.asset(Assets.deviceTagIcon)),
|
||||
SizedBox(
|
||||
width: 8,
|
||||
height: 8,
|
||||
child: SvgPicture.asset(
|
||||
Assets.deviceTagIcon,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
item['tag'] ?? '',
|
||||
textAlign: TextAlign.center,
|
||||
@ -218,7 +293,12 @@ class SaveRoutineHelper {
|
||||
spacing: 2,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 8, height: 8, child: SvgPicture.asset(Assets.spaceLocationIcon)),
|
||||
width: 8,
|
||||
height: 8,
|
||||
child: SvgPicture.asset(
|
||||
Assets.spaceLocationIcon,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
item['subSpace'] ?? '',
|
||||
textAlign: TextAlign.center,
|
||||
|
@ -28,32 +28,40 @@ class DialogFooter extends StatelessWidget {
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
_buildFooterButton(
|
||||
context: context,
|
||||
DialogFooterButton(
|
||||
text: 'Cancel',
|
||||
onTap: onCancel,
|
||||
),
|
||||
if (isConfirmEnabled) ...[
|
||||
Container(width: 1, height: 50, color: ColorsManager.greyColor),
|
||||
_buildFooterButton(
|
||||
context: context,
|
||||
DialogFooterButton(
|
||||
text: 'Confirm',
|
||||
onTap: onConfirm,
|
||||
textColor:
|
||||
isConfirmEnabled ? ColorsManager.primaryColorWithOpacity : Colors.red,
|
||||
textColor: isConfirmEnabled
|
||||
? ColorsManager.primaryColorWithOpacity
|
||||
: Colors.red,
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildFooterButton({
|
||||
required BuildContext context,
|
||||
required String text,
|
||||
required VoidCallback? onTap,
|
||||
Color? textColor,
|
||||
}) {
|
||||
class DialogFooterButton extends StatelessWidget {
|
||||
const DialogFooterButton({
|
||||
required this.text,
|
||||
required this.onTap,
|
||||
this.textColor,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final String text;
|
||||
final VoidCallback? onTap;
|
||||
final Color? textColor;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Expanded(
|
||||
child: TextButton(
|
||||
style: TextButton.styleFrom(
|
||||
|
@ -16,6 +16,7 @@ class DialogHeader extends StatelessWidget {
|
||||
),
|
||||
Text(
|
||||
title,
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
color: ColorsManager.primaryColorWithOpacity,
|
||||
fontWeight: FontWeight.bold,
|
||||
|
@ -301,12 +301,13 @@ class OneGangSwitchHelper {
|
||||
DeviceFunctionData? selectedFunctionData,
|
||||
String selectCode,
|
||||
) {
|
||||
const twelveHoursInSeconds = 43200.0;
|
||||
final operationalValues = SwitchOperationalValue(
|
||||
icon: '',
|
||||
description: "sec",
|
||||
value: 0.0,
|
||||
minValue: 0,
|
||||
maxValue: 86400,
|
||||
maxValue: twelveHoursInSeconds,
|
||||
stepValue: 1,
|
||||
);
|
||||
return Slider(
|
||||
|
@ -303,12 +303,13 @@ class ThreeGangSwitchHelper {
|
||||
DeviceFunctionData? selectedFunctionData,
|
||||
String selectCode,
|
||||
) {
|
||||
const twelveHoursInSeconds = 43200.0;
|
||||
final operationalValues = SwitchOperationalValue(
|
||||
icon: '',
|
||||
description: "sec",
|
||||
value: 0.0,
|
||||
minValue: 0,
|
||||
maxValue: 86400,
|
||||
maxValue: twelveHoursInSeconds,
|
||||
stepValue: 1,
|
||||
);
|
||||
return Slider(
|
||||
|
@ -302,12 +302,13 @@ class TwoGangSwitchHelper {
|
||||
DeviceFunctionData? selectedFunctionData,
|
||||
String selectCode,
|
||||
) {
|
||||
const twelveHoursInSeconds = 43200.0;
|
||||
final operationalValues = SwitchOperationalValue(
|
||||
icon: '',
|
||||
description: "sec",
|
||||
value: 0.0,
|
||||
minValue: 0,
|
||||
maxValue: 86400,
|
||||
maxValue: twelveHoursInSeconds,
|
||||
stepValue: 1,
|
||||
);
|
||||
return Slider(
|
||||
|
@ -41,14 +41,12 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<SpaceTreeBloc, SpaceTreeState>(builder: (context, state) {
|
||||
final communities = state.searchQuery.isNotEmpty
|
||||
? state.filteredCommunity
|
||||
: state.communityList;
|
||||
final communities =
|
||||
state.searchQuery.isNotEmpty ? state.filteredCommunity : state.communityList;
|
||||
return Container(
|
||||
height: MediaQuery.sizeOf(context).height,
|
||||
decoration: widget.isSide == true
|
||||
? subSectionContainerDecoration.copyWith(
|
||||
color: ColorsManager.whiteColors)
|
||||
? subSectionContainerDecoration.copyWith(color: ColorsManager.whiteColors)
|
||||
: const BoxDecoration(color: ColorsManager.whiteColors),
|
||||
child: state is SpaceTreeLoadingState
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
@ -81,12 +79,10 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
|
||||
style: context.textTheme.bodyMedium?.copyWith(
|
||||
color: ColorsManager.blackColor,
|
||||
),
|
||||
onChanged: (value) =>
|
||||
context.read<SpaceTreeBloc>().add(
|
||||
SearchQueryEvent(value),
|
||||
),
|
||||
decoration:
|
||||
textBoxDecoration(radios: 20)?.copyWith(
|
||||
onChanged: (value) => context.read<SpaceTreeBloc>().add(
|
||||
SearchQueryEvent(value),
|
||||
),
|
||||
decoration: textBoxDecoration(radios: 20)?.copyWith(
|
||||
fillColor: Colors.white,
|
||||
suffixIcon: Padding(
|
||||
padding: const EdgeInsets.only(right: 16),
|
||||
@ -96,8 +92,7 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
|
||||
height: 24,
|
||||
),
|
||||
),
|
||||
hintStyle:
|
||||
context.textTheme.bodyMedium?.copyWith(
|
||||
hintStyle: context.textTheme.bodyMedium?.copyWith(
|
||||
fontWeight: FontWeight.w400,
|
||||
fontSize: 12,
|
||||
color: ColorsManager.textGray,
|
||||
@ -121,27 +116,30 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
|
||||
child: state.isSearching
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: SidebarCommunitiesList(
|
||||
onScrollToEnd: () => context.read<SpaceTreeBloc>().add(
|
||||
PaginationEvent(
|
||||
state.paginationModel,
|
||||
state.communityList,
|
||||
),
|
||||
),
|
||||
onScrollToEnd: () {
|
||||
if (!state.paginationIsLoading) {
|
||||
context.read<SpaceTreeBloc>().add(
|
||||
PaginationEvent(
|
||||
state.paginationModel,
|
||||
state.communityList,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
scrollController: _scrollController,
|
||||
communities: communities,
|
||||
itemBuilder: (context, index) {
|
||||
return CustomExpansionTileSpaceTree(
|
||||
title: communities[index].name,
|
||||
isSelected: state.selectedCommunities
|
||||
.contains(communities[index].uuid),
|
||||
isSoldCheck: state.selectedCommunities
|
||||
.contains(communities[index].uuid),
|
||||
onExpansionChanged: () =>
|
||||
context.read<SpaceTreeBloc>().add(
|
||||
OnCommunityExpanded(
|
||||
communities[index].uuid,
|
||||
),
|
||||
),
|
||||
isSelected:
|
||||
state.selectedCommunities.contains(communities[index].uuid),
|
||||
isSoldCheck:
|
||||
state.selectedCommunities.contains(communities[index].uuid),
|
||||
onExpansionChanged: () => context.read<SpaceTreeBloc>().add(
|
||||
OnCommunityExpanded(
|
||||
communities[index].uuid,
|
||||
),
|
||||
),
|
||||
isExpanded: state.expandedCommunities.contains(
|
||||
communities[index].uuid,
|
||||
),
|
||||
@ -158,8 +156,7 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
|
||||
(space) {
|
||||
return CustomExpansionTileSpaceTree(
|
||||
title: space.name,
|
||||
isExpanded:
|
||||
state.expandedSpaces.contains(space.uuid),
|
||||
isExpanded: state.expandedSpaces.contains(space.uuid),
|
||||
onItemSelected: () {
|
||||
context.read<SpaceTreeBloc>().add(
|
||||
OnSpaceSelected(
|
||||
@ -170,18 +167,15 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
|
||||
);
|
||||
widget.onSelect();
|
||||
},
|
||||
onExpansionChanged: () =>
|
||||
context.read<SpaceTreeBloc>().add(
|
||||
OnSpaceExpanded(
|
||||
communities[index].uuid,
|
||||
space.uuid ?? '',
|
||||
),
|
||||
),
|
||||
isSelected: state.selectedSpaces
|
||||
.contains(space.uuid) ||
|
||||
state.soldCheck.contains(space.uuid),
|
||||
isSoldCheck:
|
||||
onExpansionChanged: () => context.read<SpaceTreeBloc>().add(
|
||||
OnSpaceExpanded(
|
||||
communities[index].uuid,
|
||||
space.uuid ?? '',
|
||||
),
|
||||
),
|
||||
isSelected: state.selectedSpaces.contains(space.uuid) ||
|
||||
state.soldCheck.contains(space.uuid),
|
||||
isSoldCheck: state.soldCheck.contains(space.uuid),
|
||||
children: _buildNestedSpaces(
|
||||
context,
|
||||
state,
|
||||
@ -210,8 +204,8 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
|
||||
) {
|
||||
return space.children.map((child) {
|
||||
return CustomExpansionTileSpaceTree(
|
||||
isSelected: state.selectedSpaces.contains(child.uuid) ||
|
||||
state.soldCheck.contains(child.uuid),
|
||||
isSelected:
|
||||
state.selectedSpaces.contains(child.uuid) || state.soldCheck.contains(child.uuid),
|
||||
isSoldCheck: state.soldCheck.contains(child.uuid),
|
||||
title: child.name,
|
||||
isExpanded: state.expandedSpaces.contains(child.uuid),
|
||||
|
85
lib/services/batch_control_devices_service.dart
Normal file
85
lib/services/batch_control_devices_service.dart
Normal file
@ -0,0 +1,85 @@
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:syncrow_web/services/api/http_service.dart';
|
||||
import 'package:syncrow_web/utils/constants/api_const.dart';
|
||||
|
||||
abstract interface class BatchControlDevicesService {
|
||||
Future<bool> batchControlDevices({
|
||||
required List<String> uuids,
|
||||
required String code,
|
||||
required Object value,
|
||||
});
|
||||
}
|
||||
|
||||
final class RemoteBatchControlDevicesService implements BatchControlDevicesService {
|
||||
@override
|
||||
Future<bool> batchControlDevices({
|
||||
required List<String> uuids,
|
||||
required String code,
|
||||
required Object value,
|
||||
}) async {
|
||||
try {
|
||||
final body = {
|
||||
'devicesUuid': uuids,
|
||||
'code': code,
|
||||
'value': value,
|
||||
'operationType': 'COMMAND',
|
||||
};
|
||||
|
||||
final response = await HTTPService().post(
|
||||
path: ApiEndpoints.deviceBatchControl,
|
||||
body: body,
|
||||
showServerMessage: true,
|
||||
expectedResponseModel: (json) => (json['success'] as bool?) ?? false,
|
||||
);
|
||||
|
||||
return response;
|
||||
} catch (e) {
|
||||
log('Error fetching $e', name: 'BatchControlDevicesService');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final class DebouncedBatchControlDevicesService
|
||||
implements BatchControlDevicesService {
|
||||
final BatchControlDevicesService decoratee;
|
||||
final Duration debounceDuration;
|
||||
|
||||
final _pendingRequests = <(List<String> uuids, String code, Object value)>[];
|
||||
var _isProcessing = false;
|
||||
|
||||
DebouncedBatchControlDevicesService({
|
||||
required this.decoratee,
|
||||
this.debounceDuration = const Duration(milliseconds: 800),
|
||||
});
|
||||
|
||||
@override
|
||||
Future<bool> batchControlDevices({
|
||||
required List<String> uuids,
|
||||
required String code,
|
||||
required Object value,
|
||||
}) async {
|
||||
_pendingRequests.add((uuids, code, value));
|
||||
|
||||
if (_isProcessing) return false;
|
||||
|
||||
_isProcessing = true;
|
||||
|
||||
await Future.delayed(debounceDuration);
|
||||
|
||||
final lastRequest = _pendingRequests.last;
|
||||
_pendingRequests.clear();
|
||||
|
||||
try {
|
||||
final (lastRequestUuids, lastRequestCode, lastRequestValue) = lastRequest;
|
||||
return decoratee.batchControlDevices(
|
||||
uuids: lastRequestUuids,
|
||||
code: lastRequestCode,
|
||||
value: lastRequestValue,
|
||||
);
|
||||
} finally {
|
||||
_isProcessing = false;
|
||||
}
|
||||
}
|
||||
}
|
75
lib/services/control_device_service.dart
Normal file
75
lib/services/control_device_service.dart
Normal file
@ -0,0 +1,75 @@
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
|
||||
import 'package:syncrow_web/services/api/http_service.dart';
|
||||
import 'package:syncrow_web/utils/constants/api_const.dart';
|
||||
|
||||
abstract interface class ControlDeviceService {
|
||||
Future<bool> controlDevice({
|
||||
required String deviceUuid,
|
||||
required Status status,
|
||||
});
|
||||
}
|
||||
|
||||
final class RemoteControlDeviceService implements ControlDeviceService {
|
||||
@override
|
||||
Future<bool> controlDevice({
|
||||
required String deviceUuid,
|
||||
required Status status,
|
||||
}) async {
|
||||
try {
|
||||
final response = await HTTPService().post(
|
||||
path: ApiEndpoints.deviceControl.replaceAll('{uuid}', deviceUuid),
|
||||
body: status.toMap(),
|
||||
showServerMessage: true,
|
||||
expectedResponseModel: (json) {
|
||||
return (json['success'] as bool?) ?? false;
|
||||
},
|
||||
);
|
||||
return response;
|
||||
} catch (e) {
|
||||
log('Error fetching $e', name: 'ControlDeviceService');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final class DebouncedControlDeviceService implements ControlDeviceService {
|
||||
final ControlDeviceService decoratee;
|
||||
final Duration debounceDuration;
|
||||
|
||||
DebouncedControlDeviceService({
|
||||
required this.decoratee,
|
||||
this.debounceDuration = const Duration(milliseconds: 800),
|
||||
});
|
||||
|
||||
final _pendingRequests = <(String deviceUuid, Status status)>[];
|
||||
var _isProcessing = false;
|
||||
|
||||
@override
|
||||
Future<bool> controlDevice({
|
||||
required String deviceUuid,
|
||||
required Status status,
|
||||
}) async {
|
||||
_pendingRequests.add((deviceUuid, status));
|
||||
|
||||
if (_isProcessing) return false;
|
||||
|
||||
_isProcessing = true;
|
||||
|
||||
await Future.delayed(debounceDuration);
|
||||
|
||||
final lastRequest = _pendingRequests.last;
|
||||
_pendingRequests.clear();
|
||||
|
||||
try {
|
||||
final (lastRequestDeviceUuid, lastRequestStatus) = lastRequest;
|
||||
return decoratee.controlDevice(
|
||||
deviceUuid: lastRequestDeviceUuid,
|
||||
status: lastRequestStatus,
|
||||
);
|
||||
} finally {
|
||||
_isProcessing = false;
|
||||
}
|
||||
}
|
||||
}
|
@ -60,6 +60,7 @@ dependencies:
|
||||
firebase_core: ^3.11.0
|
||||
firebase_crashlytics: ^4.3.2
|
||||
firebase_database: ^11.3.2
|
||||
bloc: ^8.1.4
|
||||
|
||||
|
||||
dev_dependencies:
|
||||
|
Reference in New Issue
Block a user