Compare commits

..

20 Commits

Author SHA1 Message Date
5486f0832d Remove unused copyWith method from Status class and simplify status assignment in ScheduleBloc 2025-06-30 15:22:02 +03:00
fd239a3907 Merge branch 'dev' of https://github.com/SyncrowIOT/web into fix-schedule 2025-06-30 15:17:37 +03:00
e2d6f5eea8 Update device type from '1GT' to '2GT' in TwoGangGlassSwitchControlView 2025-06-30 15:11:17 +03:00
289922071a Add countdown functionality and device type support across device management views 2025-06-30 15:05:59 +03:00
8594168548 hardcoded device location to dubai for demo purposes. 2025-06-30 14:22:54 +03:00
bd9a74b380 fix touch gangs realtime. 2025-06-30 13:58:10 +03:00
af48bbead5 fix-occupancy-devices-bug (#318)
<!--
  Thanks for contributing!

Provide a description of your changes below and a general summary in the
title

Please look at the following checklist to ensure that your PR can be
accepted quickly:
-->

## Description

handle more cases when decoding analytics devices.

## Type of Change

<!--- Put an `x` in all the boxes that apply: -->

- [ ]  New feature (non-breaking change which adds functionality)
- [x] 🛠️ Bug fix (non-breaking change which fixes an issue)
- [ ]  Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] 🧹 Code refactor
- [ ]  Build configuration change
- [ ] 📝 Documentation
- [ ] 🗑️ Chore
2025-06-30 11:28:38 +03:00
3c80724c1e Sp 1707 fe preferences calibration fix UI (#317)
<!--
  Thanks for contributing!

Provide a description of your changes below and a general summary in the
title

Please look at the following checklist to ensure that your PR can be
accepted quickly:
-->

## Jira Ticket
[SP-1707](https://syncrow.atlassian.net/browse/SP-1707)

## Description

fix the UI as wanted in Figma 

## Type of Change

<!--- Put an `x` in all the boxes that apply: -->

- [ ]  New feature (non-breaking change which adds functionality)
- [x] 🛠️ Bug fix (non-breaking change which fixes an issue)
- [ ]  Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] 🧹 Code refactor
- [ ]  Build configuration change
- [ ] 📝 Documentation
- [ ] 🗑️ Chore 


[SP-1707]:
https://syncrow.atlassian.net/browse/SP-1707?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
2025-06-30 11:21:26 +03:00
cdc76c2c8e handle more cases when decoding analytics devices. 2025-06-30 11:04:22 +03:00
dfb120e7cf [FE] Smart curtain module device Icon and other devices icons are appearing as a sensor Icon on Add device dialog on space management (#316)
<!--
  Thanks for contributing!

Provide a description of your changes below and a general summary in the
title

Please look at the following checklist to ensure that your PR can be
accepted quickly:
-->

## Jira Ticket
[SP-1804](https://syncrow.atlassian.net/browse/SP-1804)

## Description

add the new product types and map to the icons

## Type of Change

<!--- Put an `x` in all the boxes that apply: -->

- [ ]  New feature (non-breaking change which adds functionality)
- [x] 🛠️ Bug fix (non-breaking change which fixes an issue)
- [ ]  Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] 🧹 Code refactor
- [ ]  Build configuration change
- [ ] 📝 Documentation
- [ ] 🗑️ Chore 


[SP-1804]:
https://syncrow.atlassian.net/browse/SP-1804?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
2025-06-30 10:47:37 +03:00
8c3861e83c fix the button border Raduis when hovering 2025-06-30 10:44:16 +03:00
b90f25f7b0 fix all UI notes make white dialogs with fix calibrating textfield for seconds && fix icon of completed action dialog 2025-06-30 10:39:23 +03:00
4d51321675 add the new devices to mapIconToProduct func 2025-06-30 10:05:15 +03:00
b5e7776ccb Sp 1705 fe create scheduling UI fixes (#315)
<!--
  Thanks for contributing!

Provide a description of your changes below and a general summary in the
title

Please look at the following checklist to ensure that your PR can be
accepted quickly:
-->

## Jira Ticket
[SP-1705](https://syncrow.atlassian.net/browse/SP-1705)

## Description

i fixed edit and insure integrating with backend for schedule and remove
countdown from UI if category cur module

## Type of Change

<!--- Put an `x` in all the boxes that apply: -->

- [ ]  New feature (non-breaking change which adds functionality)
- [x] 🛠️ Bug fix (non-breaking change which fixes an issue)
- [ ]  Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] 🧹 Code refactor
- [ ]  Build configuration change
- [ ] 📝 Documentation
- [ ] 🗑️ Chore 


[SP-1705]:
https://syncrow.atlassian.net/browse/SP-1705?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
2025-06-30 09:34:11 +03:00
32938404dd PR requested changes 2025-06-30 09:28:09 +03:00
0cfd58d820 fix to send fit data to integrate with API (was true and false)now cur module send close and open with control key 2025-06-30 08:56:42 +03:00
d4625a8f04 fix edit to accept string of cur module 2025-06-30 08:45:18 +03:00
9f24606613 Merge branch 'dev' of https://github.com/SyncrowIOT/web into SP-1705-fe-create-scheduling-ui-fixes 2025-06-30 08:28:40 +03:00
e87dffd76b when it is CUR module there is no countdown and other selector 2025-06-30 08:28:19 +03:00
0c220a1f34 [FE] UI Enhancement: Update Confirmation Dialog on "Create Visitor Password" Flow (#310)
… the requested ticket)

<!--
  Thanks for contributing!

Provide a description of your changes below and a general summary in the
title

Please look at the following checklist to ensure that your PR can be
accepted quickly:
-->

## Jira Ticket
[SP-1660](https://syncrow.atlassian.net/browse/SP-1660)

## Description
enhance UI in create visitor insure dialog as wanted in Ticket (in figma
not updated yet)

## Type of Change

<!--- Put an `x` in all the boxes that apply: -->

- [ ]  New feature (non-breaking change which adds functionality)
- [x] 🛠️ Bug fix (non-breaking change which fixes an issue)
- [ ]  Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] 🧹 Code refactor
- [ ]  Build configuration change
- [ ] 📝 Documentation
- [ ] 🗑️ Chore 


[SP-1660]:
https://syncrow.atlassian.net/browse/SP-1660?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
2025-06-29 16:40:15 +03:00
34 changed files with 543 additions and 413 deletions

View File

@ -0,0 +1,4 @@
<svg width="50" height="50" viewBox="0 0 50 50" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M37.8033 16.3567C37.2313 15.7848 36.3039 15.7847 35.7318 16.3568L21.5532 30.5353L14.2681 23.2504C13.6962 22.6784 12.7686 22.6783 12.1966 23.2505C11.6246 23.8226 11.6246 24.75 12.1966 25.3221L20.5174 33.6427C20.8034 33.9287 21.1783 34.0717 21.5531 34.0717C21.928 34.0717 22.3029 33.9287 22.5888 33.6426L37.8033 18.4283C38.3754 17.8563 38.3754 16.9288 37.8033 16.3567Z" fill="#023DFE" fill-opacity="0.7"/>
<path d="M42.6776 7.32236C37.9558 2.60049 31.6776 0 25 0C18.3223 0 12.0442 2.60049 7.32236 7.32236C2.60039 12.0443 0 18.3224 0 25C0 31.6778 2.60039 37.9559 7.32236 42.6777C12.0441 47.3996 18.3223 50 25 50C31.6777 50 37.9558 47.3996 42.6776 42.6777C47.3995 37.9559 50 31.6778 50 25C50 18.3224 47.3995 12.0443 42.6776 7.32236ZM25 47.0703C12.8304 47.0703 2.92969 37.1696 2.92969 25C2.92969 12.8304 12.8304 2.92969 25 2.92969C37.1696 2.92969 47.0703 12.8304 47.0703 25C47.0703 37.1696 37.1696 47.0703 25 47.0703Z" fill="#023DFE" fill-opacity="0.7"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -39,8 +39,12 @@ class AnalyticsDevice {
? ProductDevice.fromJson(json['productDevice'] as Map<String, dynamic>)
: null,
spaceUuid: json['spaceUuid'] as String?,
latitude: json['lat'] != null ? double.parse(json['lat'] as String? ?? '0.0') : null,
longitude: json['lon'] != null ? double.parse(json['lon'] as String? ?? '0.0') : null,
latitude: json['lat'] != null && json['lat'] != ''
? double.tryParse(json['lat']?.toString() ?? '0.0')
: null,
longitude: json['lon'] != null && json['lon'] != ''
? double.tryParse(json['lon']?.toString() ?? '0.0')
: null,
);
}
}

View File

@ -17,8 +17,8 @@ class DeviceLocationDetailsServiceDecorator implements DeviceLocationService {
'reverse',
queryParameters: {
'format': 'json',
'lat': param.latitude,
'lon': param.longitude,
'lat': 25.1880567,
'lon': 55.266608,
},
);

View File

@ -60,4 +60,13 @@ class Status {
factory Status.fromJson(String source) => Status.fromMap(json.decode(source));
String toJson() => json.encode(toMap());
Status copyWith({
String? code,
dynamic value,
}) {
return Status(
code: code ?? this.code,
value: value ?? this.value,
);
}
}

View File

@ -62,9 +62,10 @@ class CurtainModuleItems extends StatelessWidget with HelperResponsiveLayout {
BlocProvider.of<CurtainModuleBloc>(context),
child: BuildScheduleView(
deviceUuid: deviceId,
category: 'CUR_2',
category: 'timer',
code: 'control',
countdownCode: 'timer',
deviceType: 'CUR_2',
),
));
},

View File

@ -4,6 +4,7 @@ import 'package:syncrow_web/pages/device_managment/curtain_module/bloc/curtain_m
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';
import 'package:syncrow_web/utils/color_manager.dart';
class AccurteCalibratingDialog extends StatelessWidget {
final String deviceId;
@ -17,6 +18,7 @@ class AccurteCalibratingDialog extends StatelessWidget {
@override
Widget build(_) {
return AlertDialog(
backgroundColor: ColorsManager.whiteColors,
contentPadding: EdgeInsets.zero,
content: AccurateDialogWidget(
title: 'Calibrating',

View File

@ -2,6 +2,7 @@ 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';
import 'package:syncrow_web/utils/color_manager.dart';
class AccurateCalibrationDialog extends StatelessWidget {
final String deviceId;
@ -15,6 +16,7 @@ class AccurateCalibrationDialog extends StatelessWidget {
@override
Widget build(_) {
return AlertDialog(
backgroundColor: ColorsManager.whiteColors,
contentPadding: EdgeInsets.zero,
content: AccurateDialogWidget(
title: 'Accurate Calibration',

View File

@ -22,6 +22,7 @@ class AccurateDialogWidget extends StatelessWidget {
child: Column(
children: [
Expanded(
flex: 3,
child: Column(
children: [
Padding(
@ -43,17 +44,22 @@ class AccurateDialogWidget extends StatelessWidget {
),
),
Expanded(
flex: 5,
child: body,
),
Expanded(
flex: 2,
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
const Divider(),
const Expanded(child: Divider()),
Row(
children: [
Expanded(
child: InkWell(
borderRadius: const BorderRadius.only(
bottomLeft: Radius.circular(26),
),
onTap: leftOnTap,
child: Container(
height: 40,
@ -64,6 +70,9 @@ class AccurateDialogWidget extends StatelessWidget {
color: ColorsManager.grayBorder,
),
),
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(26),
),
),
child: const Text(
'Cancel',
@ -74,6 +83,9 @@ class AccurateDialogWidget extends StatelessWidget {
),
Expanded(
child: InkWell(
borderRadius: const BorderRadius.only(
bottomRight: Radius.circular(26),
),
onTap: rightOnTap,
child: Container(
height: 40,
@ -84,6 +96,9 @@ class AccurateDialogWidget extends StatelessWidget {
color: ColorsManager.grayBorder,
),
),
borderRadius: BorderRadius.only(
bottomRight: Radius.circular(26),
),
),
child: const Text(
'Next',

View File

@ -28,7 +28,7 @@ class NormalTextBodyForDialog extends StatelessWidget {
title,
style: const TextStyle(
color: ColorsManager.grayColor,
fontSize: 17,
fontSize: 15,
),
),
),
@ -42,7 +42,7 @@ class NormalTextBodyForDialog extends StatelessWidget {
const Text('1. ',
style: TextStyle(
color: ColorsManager.grayColor,
fontSize: 17,
fontSize: 15,
)),
SizedBox(
width: 450,
@ -50,7 +50,7 @@ class NormalTextBodyForDialog extends StatelessWidget {
step1,
style: const TextStyle(
color: ColorsManager.grayColor,
fontSize: 17,
fontSize: 15,
),
),
),
@ -67,13 +67,13 @@ class NormalTextBodyForDialog extends StatelessWidget {
const Text('2. ',
style: TextStyle(
color: ColorsManager.grayColor,
fontSize: 17,
fontSize: 15,
)),
Text(
step2,
style: const TextStyle(
color: ColorsManager.grayColor,
fontSize: 17,
fontSize: 15,
),
),
],

View File

@ -63,6 +63,7 @@ class _QuickCalibratingDialogState extends State<QuickCalibratingDialog> {
@override
Widget build(_) {
return AlertDialog(
backgroundColor: ColorsManager.whiteColors,
contentPadding: EdgeInsets.zero,
content: AccurateDialogWidget(
title: 'Calibrating',
@ -71,7 +72,7 @@ class _QuickCalibratingDialogState extends State<QuickCalibratingDialog> {
children: [
const Expanded(
child: Align(
alignment: Alignment.center,
alignment: Alignment.topCenter,
child: Padding(
padding: EdgeInsets.only(right: 75),
child: Text(
@ -85,17 +86,21 @@ class _QuickCalibratingDialogState extends State<QuickCalibratingDialog> {
child: Align(
alignment: Alignment.center,
child: Container(
width: 110,
width: 130,
padding: const EdgeInsets.all(5),
decoration: BoxDecoration(
color: ColorsManager.whiteColors,
color: ColorsManager.neutralGray.withValues(
alpha: 0.5,
),
borderRadius: BorderRadius.circular(12),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
child: NumberInputField(controller: _controller),
child: Padding(
padding: const EdgeInsetsGeometry.only(left: 5),
child: NumberInputField(controller: _controller)),
),
Expanded(
child: Text(
@ -127,7 +132,7 @@ class _QuickCalibratingDialogState extends State<QuickCalibratingDialog> {
),
const Expanded(
child: Align(
alignment: Alignment.center,
alignment: Alignment.bottomCenter,
child: Text(
'2.click Next to Complete the calibration',
style: TextStyle(color: ColorsManager.lightGrayColor),

View File

@ -2,6 +2,7 @@ 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';
import 'package:syncrow_web/utils/color_manager.dart';
class QuickCalibrationDialog extends StatelessWidget {
final int timControl;
@ -17,6 +18,7 @@ class QuickCalibrationDialog extends StatelessWidget {
@override
Widget build(_) {
return AlertDialog(
backgroundColor: ColorsManager.whiteColors,
contentPadding: EdgeInsets.zero,
content: AccurateDialogWidget(
title: 'Quick Calibration',

View File

@ -40,7 +40,7 @@ class OneGangGlassSwitchBloc
emit(OneGangGlassSwitchLoading());
try {
final status = await DevicesManagementApi().getDeviceStatus(event.deviceId);
_listenToChanges(event.deviceId, emit);
_listenToChanges(event.deviceId);
deviceStatus = OneGangGlassStatusModel.fromJson(event.deviceId, status.status);
emit(OneGangGlassSwitchStatusLoaded(deviceStatus));
} catch (e) {
@ -48,42 +48,28 @@ class OneGangGlassSwitchBloc
}
}
void _listenToChanges(
String deviceId,
Emitter<OneGangGlassSwitchState> emit,
) {
StreamSubscription<DatabaseEvent>? _deviceStatusSubscription;
void _listenToChanges(String deviceId) {
try {
final ref = FirebaseDatabase.instance.ref('device-status/$deviceId');
final stream = ref.onValue;
_deviceStatusSubscription = ref.onValue.listen((DatabaseEvent event) async {
if (event.snapshot.value == null) return;
stream.listen((DatabaseEvent event) {
final data = event.snapshot.value as Map<dynamic, dynamic>?;
if (data == null) return;
final usersMap = event.snapshot.value! as Map<dynamic, dynamic>;
final statusList = <Status>[];
if (data['status'] != null) {
for (var element in data['status']) {
statusList.add(
Status(
code: element['code'].toString(),
value: element['value'].toString(),
),
);
}
}
if (statusList.isNotEmpty) {
final newStatus = OneGangGlassStatusModel.fromJson(deviceId, statusList);
if (newStatus != deviceStatus) {
deviceStatus = newStatus;
if (!isClosed) {
add(StatusUpdated(deviceStatus));
}
}
}
usersMap['status'].forEach((element) {
statusList.add(Status(code: element['code'], value: element['value']));
});
deviceStatus =
OneGangGlassStatusModel.fromJson(usersMap['productUuid'], statusList);
add(StatusUpdated(deviceStatus));
});
} catch (e) {
emit(OneGangGlassSwitchError('Failed to listen to changes: $e'));
}
} catch (_) {}
}
void _onStatusUpdated(
@ -174,4 +160,10 @@ class OneGangGlassSwitchBloc
deviceStatus = deviceStatus.copyWith(switch1: value);
}
}
@override
Future<void> close() {
_deviceStatusSubscription?.cancel();
return super.close();
}
}

View File

@ -90,6 +90,8 @@ class OneGangGlassSwitchControlView extends StatelessWidget
child: BuildScheduleView(
category: 'switch_1',
deviceUuid: deviceId,
countdownCode: 'countdown_1',
deviceType: '1GT',
),
));
},

View File

@ -80,6 +80,8 @@ class WallLightDeviceControl extends StatelessWidget
child: BuildScheduleView(
category: 'switch_1',
deviceUuid: deviceId,
countdownCode: 'countdown_1',
deviceType: '1G',
),
));
},

View File

@ -47,7 +47,7 @@ class ScheduleBloc extends Bloc<ScheduleEvent, ScheduleState> {
final success = await RemoteControlDeviceService().controlDevice(
deviceUuid: deviceId,
status: Status(
code: 'countdown_1',
code: event.countdownCode,
value: 0,
),
);
@ -80,15 +80,18 @@ class ScheduleBloc extends Bloc<ScheduleEvent, ScheduleState> {
) {
if (state is ScheduleLoaded) {
final currentState = state as ScheduleLoaded;
emit(currentState.copyWith(
countdownSeconds: currentState.countdownSeconds,
selectedTime: currentState.selectedTime,
deviceId: deviceId,
scheduleMode: event.scheduleMode,
countdownRemaining: Duration.zero,
countdownHours: 0,
countdownMinutes: 0,
inchingHours: 0,
inchingMinutes: 0,
isCountdownActive: false,
countdownHours: currentState.countdownHours,
countdownMinutes: currentState.countdownMinutes,
inchingHours: currentState.inchingHours,
inchingMinutes: currentState.inchingMinutes,
isInchingActive: false,
isCountdownActive: currentState.countdownRemaining > Duration.zero,
));
}
}
@ -221,7 +224,6 @@ class ScheduleBloc extends Bloc<ScheduleEvent, ScheduleState> {
deviceId,
event.category,
);
if (state is ScheduleLoaded) {
final currentState = state as ScheduleLoaded;
emit(currentState.copyWith(
@ -285,12 +287,22 @@ class ScheduleBloc extends Bloc<ScheduleEvent, ScheduleState> {
) async {
try {
if (state is ScheduleLoaded) {
Status status = Status(code: '', value: '');
if (event.deviceType == 'CUR_2') {
status = status.copyWith(
code: 'control',
value: event.functionOn == true ? 'open' : 'close');
} else {
status =
status.copyWith(code: event.category, value: event.functionOn);
}
final dateTime = DateTime.parse(event.time);
final updatedSchedule = ScheduleEntry(
scheduleId: event.scheduleId,
category: event.category,
time: getTimeStampWithoutSeconds(dateTime).toString(),
function: Status(code: event.category, value: event.functionOn),
function: status,
days: event.selectedDays,
);
final success = await DevicesManagementApi().editScheduleRecord(
@ -396,7 +408,7 @@ class ScheduleBloc extends Bloc<ScheduleEvent, ScheduleState> {
final totalSeconds =
Duration(hours: event.hours, minutes: event.minutes).inSeconds;
final code = event.mode == ScheduleModes.countdown
? 'countdown_1'
? event.countDownCode
: 'switch_inching';
final currentState = state as ScheduleLoaded;
final duration = Duration(seconds: totalSeconds);
@ -423,7 +435,7 @@ class ScheduleBloc extends Bloc<ScheduleEvent, ScheduleState> {
);
if (success) {
if (code == 'countdown_1') {
if (code == event.countDownCode) {
final countdownDuration = Duration(seconds: totalSeconds);
emit(
@ -437,7 +449,7 @@ class ScheduleBloc extends Bloc<ScheduleEvent, ScheduleState> {
);
if (countdownDuration.inSeconds > 0) {
_startCountdownTimer(emit, countdownDuration);
_startCountdownTimer(emit, countdownDuration, event.countDownCode);
} else {
_countdownTimer?.cancel();
emit(
@ -467,9 +479,7 @@ class ScheduleBloc extends Bloc<ScheduleEvent, ScheduleState> {
}
void _startCountdownTimer(
Emitter<ScheduleState> emit,
Duration duration,
) {
Emitter<ScheduleState> emit, Duration duration, String countdownCode) {
_countdownTimer?.cancel();
_countdownTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
if (_currentCountdown != null && _currentCountdown! > Duration.zero) {
@ -479,6 +489,7 @@ class ScheduleBloc extends Bloc<ScheduleEvent, ScheduleState> {
} else {
timer.cancel();
add(StopScheduleEvent(
countdownCode: countdownCode,
mode: _currentCountdown == null
? ScheduleModes.countdown
: ScheduleModes.inching,
@ -515,70 +526,75 @@ class ScheduleBloc extends Bloc<ScheduleEvent, ScheduleState> {
try {
final status =
await DevicesManagementApi().getDeviceStatus(event.deviceId);
print(status.status);
int totalSeconds = 0;
final countdownItem = status.status.firstWhere(
(item) => item.code == event.countdownCode,
orElse: () => Status(code: '', value: 0),
);
totalSeconds = (countdownItem.value as int?) ?? 0;
final countdownHours = totalSeconds ~/ 3600;
final countdownMinutes = (totalSeconds % 3600) ~/ 60;
final countdownSeconds = totalSeconds % 60;
final deviceStatus =
WaterHeaterStatusModel.fromJson(event.deviceId, status.status);
final isCountdownActive = totalSeconds > 0;
final isInchingActive = !isCountdownActive &&
(deviceStatus.inchingHours > 0 || deviceStatus.inchingMinutes > 0);
final scheduleMode =
deviceStatus.countdownHours > 0 || deviceStatus.countdownMinutes > 0
? ScheduleModes.countdown
: deviceStatus.inchingHours > 0 || deviceStatus.inchingMinutes > 0
? ScheduleModes.inching
: ScheduleModes.schedule;
final isCountdown = scheduleMode == ScheduleModes.countdown;
final isInching = scheduleMode == ScheduleModes.inching;
final newState = state is ScheduleLoaded
? (state as ScheduleLoaded).copyWith(
scheduleMode: ScheduleModes.schedule,
countdownHours: countdownHours,
countdownMinutes: countdownMinutes,
countdownSeconds: countdownSeconds,
inchingHours: deviceStatus.inchingHours,
inchingMinutes: deviceStatus.inchingMinutes,
isCountdownActive: isCountdownActive,
isInchingActive: isInchingActive,
countdownRemaining: isCountdownActive
? Duration(seconds: totalSeconds)
: Duration.zero,
)
: ScheduleLoaded(
scheduleMode: ScheduleModes.schedule,
schedules: const [],
selectedTime: null,
selectedDays: List.filled(7, false),
functionOn: false,
isEditing: false,
deviceId: event.deviceId,
countdownHours: countdownHours,
countdownMinutes: countdownMinutes,
countdownSeconds: countdownSeconds,
inchingHours: deviceStatus.inchingHours,
inchingMinutes: deviceStatus.inchingMinutes,
isCountdownActive: isCountdownActive,
isInchingActive: isInchingActive,
countdownRemaining: isCountdownActive
? Duration(seconds: totalSeconds)
: Duration.zero,
);
emit(newState);
Duration? countdownRemaining;
var isCountdownActive = false;
var isInchingActive = false;
if (isCountdownActive) {
_countdownTimer?.cancel();
_currentCountdown = Duration(seconds: totalSeconds);
countdownRemaining = _currentCountdown!;
if (isCountdown) {
countdownRemaining = Duration(
hours: deviceStatus.countdownHours,
minutes: deviceStatus.countdownMinutes,
);
isCountdownActive = countdownRemaining > Duration.zero;
} else if (isInching) {
isInchingActive = Duration(
hours: deviceStatus.inchingHours,
minutes: deviceStatus.inchingMinutes,
) >
Duration.zero;
}
if (state is ScheduleLoaded) {
final currentState = state as ScheduleLoaded;
emit(currentState.copyWith(
scheduleMode: scheduleMode,
countdownHours: deviceStatus.countdownHours,
countdownMinutes: deviceStatus.countdownMinutes,
inchingHours: deviceStatus.inchingHours,
inchingMinutes: deviceStatus.inchingMinutes,
isCountdownActive: isCountdownActive,
isInchingActive: isInchingActive,
countdownRemaining: countdownRemaining ?? Duration.zero,
));
if (totalSeconds > 0) {
_startCountdownTimer(
emit, Duration(seconds: totalSeconds), event.countdownCode);
} else {
add(StopScheduleEvent(
countdownCode: event.countdownCode,
mode: ScheduleModes.countdown,
deviceId: event.deviceId,
));
}
} else {
emit(ScheduleLoaded(
schedules: const [],
selectedTime: null,
selectedDays: List.filled(7, false),
functionOn: false,
isEditing: false,
deviceId: deviceId,
scheduleMode: scheduleMode,
countdownHours: deviceStatus.countdownHours,
countdownMinutes: deviceStatus.countdownMinutes,
inchingHours: deviceStatus.inchingHours,
inchingMinutes: deviceStatus.inchingMinutes,
isCountdownActive: isCountdownActive,
isInchingActive: isInchingActive,
countdownRemaining: countdownRemaining ?? Duration.zero,
));
_countdownTimer?.cancel();
}
// if (isCountdownActive && countdownRemaining != null) {
// _startCountdownTimer(emit, countdownRemaining);
// }
} catch (e) {
emit(ScheduleError('Failed to fetch device status: $e'));
}

View File

@ -91,6 +91,7 @@ class ScheduleEditEvent extends ScheduleEvent {
final String time;
final List<String> selectedDays;
final bool functionOn;
final String deviceType;
const ScheduleEditEvent({
required this.scheduleId,
@ -98,6 +99,7 @@ class ScheduleEditEvent extends ScheduleEvent {
required this.time,
required this.selectedDays,
required this.functionOn,
required this.deviceType,
});
@override
@ -107,6 +109,7 @@ class ScheduleEditEvent extends ScheduleEvent {
time,
selectedDays,
functionOn,
deviceType,
];
}
@ -138,11 +141,13 @@ class ScheduleUpdateEntryEvent extends ScheduleEvent {
class UpdateScheduleModeEvent extends ScheduleEvent {
final ScheduleModes scheduleMode;
final String countdownCode;
const UpdateScheduleModeEvent({required this.scheduleMode});
const UpdateScheduleModeEvent(
{required this.scheduleMode, required this.countdownCode});
@override
List<Object> get props => [scheduleMode];
List<Object> get props => [scheduleMode, countdownCode!];
}
class UpdateCountdownTimeEvent extends ScheduleEvent {
@ -177,28 +182,32 @@ class StartScheduleEvent extends ScheduleEvent {
final ScheduleModes mode;
final int hours;
final int minutes;
final String countDownCode;
const StartScheduleEvent({
required this.mode,
required this.hours,
required this.minutes,
required this.countDownCode,
});
@override
List<Object?> get props => [mode, hours, minutes];
List<Object?> get props => [mode, hours, minutes, countDownCode];
}
class StopScheduleEvent extends ScheduleEvent {
final ScheduleModes mode;
final String deviceId;
final String countdownCode;
const StopScheduleEvent({
required this.mode,
required this.deviceId,
required this.countdownCode,
});
@override
List<Object?> get props => [mode, deviceId];
List<Object?> get props => [mode, deviceId, countdownCode];
}
class ScheduleDecrementCountdownEvent extends ScheduleEvent {
@ -210,11 +219,13 @@ class ScheduleDecrementCountdownEvent extends ScheduleEvent {
class ScheduleFetchStatusEvent extends ScheduleEvent {
final String deviceId;
final String countdownCode;
const ScheduleFetchStatusEvent(this.deviceId);
const ScheduleFetchStatusEvent(
{required this.deviceId, required this.countdownCode});
@override
List<Object> get props => [deviceId];
List<Object> get props => [deviceId, countdownCode];
}
class DeleteScheduleEvent extends ScheduleEvent {

View File

@ -29,7 +29,7 @@ class ScheduleLoaded extends ScheduleState {
final int inchingSeconds;
final bool isInchingActive;
final ScheduleModes scheduleMode;
final Duration? countdownRemaining;
final Duration countdownRemaining;
final int? countdownSeconds;
const ScheduleLoaded({
@ -48,7 +48,7 @@ class ScheduleLoaded extends ScheduleState {
this.inchingMinutes = 0,
this.isInchingActive = false,
this.scheduleMode = ScheduleModes.countdown,
this.countdownRemaining,
this.countdownRemaining = Duration.zero,
});
ScheduleLoaded copyWith({

View File

@ -11,6 +11,7 @@ class CountdownModeButtons extends StatelessWidget {
final String deviceId;
final int hours;
final int minutes;
final String countDownCode;
const CountdownModeButtons({
super.key,
@ -18,6 +19,7 @@ class CountdownModeButtons extends StatelessWidget {
required this.deviceId,
required this.hours,
required this.minutes,
required this.countDownCode,
});
@override
@ -43,6 +45,7 @@ class CountdownModeButtons extends StatelessWidget {
StopScheduleEvent(
mode: ScheduleModes.countdown,
deviceId: deviceId,
countdownCode: countDownCode,
),
);
},
@ -54,10 +57,10 @@ class CountdownModeButtons extends StatelessWidget {
onPressed: () {
context.read<ScheduleBloc>().add(
StartScheduleEvent(
mode: ScheduleModes.countdown,
hours: hours,
minutes: minutes,
),
mode: ScheduleModes.countdown,
hours: hours,
minutes: minutes,
countDownCode: countDownCode),
);
},
backgroundColor: ColorsManager.primaryColor,

View File

@ -75,23 +75,33 @@ class _CountdownInchingViewState extends State<CountdownInchingView> {
final isCountDown = state.scheduleMode == ScheduleModes.countdown;
final isActive =
isCountDown ? state.isCountdownActive : state.isInchingActive;
final displayHours = isActive && state.countdownRemaining != null
? state.countdownRemaining!.inHours
: (isCountDown ? state.countdownHours : state.inchingHours);
final displayMinutes = isActive && state.countdownRemaining != null
? state.countdownRemaining!.inMinutes.remainder(60)
: (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!);
final displayHours =
isActive && state.countdownRemaining != Duration.zero
? state.countdownRemaining.inHours
: (isCountDown ? state.countdownHours : state.inchingHours);
if (displayHours == 0 && displayMinutes == 0 && displaySeconds == 0) {
final displayMinutes =
isActive && state.countdownRemaining != Duration.zero
? state.countdownRemaining.inMinutes.remainder(60)
: (isCountDown ? state.countdownMinutes : state.inchingMinutes);
final displaySeconds =
isActive && state.countdownRemaining != Duration.zero
? state.countdownRemaining.inSeconds.remainder(60)
: (isCountDown ? (state.countdownSeconds ?? 0) : 0);
_updateControllers(displayHours, displayMinutes, displaySeconds);
if (isActive &&
displayHours == 0 &&
displayMinutes == 0 &&
displaySeconds == 0) {
context.read<ScheduleBloc>().add(
StopScheduleEvent(
mode: ScheduleModes.countdown,
deviceId: widget.deviceId,
countdownCode: '',
),
);
}

View File

@ -43,7 +43,9 @@ class InchingModeButtons extends StatelessWidget {
onPressed: () {
context.read<ScheduleBloc>().add(
StopScheduleEvent(
deviceId: deviceId, mode: ScheduleModes.inching),
deviceId: deviceId,
mode: ScheduleModes.inching,
countdownCode: ''),
);
},
backgroundColor: Colors.red,

View File

@ -18,11 +18,15 @@ class BuildScheduleView extends StatelessWidget {
super.key,
required this.deviceUuid,
required this.category,
required this.countdownCode,
this.code,
required this.deviceType,
});
final String deviceUuid;
final String category;
final String? code;
final String? countdownCode;
final String deviceType;
@override
Widget build(BuildContext context) {
@ -31,7 +35,8 @@ class BuildScheduleView extends StatelessWidget {
deviceId: deviceUuid,
)
..add(ScheduleGetEvent(category: category))
..add(ScheduleFetchStatusEvent(deviceUuid)),
..add(ScheduleFetchStatusEvent(
deviceId: deviceUuid, countdownCode: countdownCode ?? '')),
child: Dialog(
backgroundColor: Colors.white,
insetPadding: const EdgeInsets.all(20),
@ -52,28 +57,32 @@ class BuildScheduleView extends StatelessWidget {
children: [
const ScheduleHeader(),
const SizedBox(height: 20),
ScheduleModeSelector(
currentMode: state.scheduleMode,
),
if (deviceType == 'CUR_2')
const SizedBox()
else
ScheduleModeSelector(
countdownCode: countdownCode ?? '',
currentMode: state.scheduleMode,
),
const SizedBox(height: 20),
if (state.scheduleMode == ScheduleModes.schedule)
ScheduleManagementUI(
deviceType: deviceType,
category: category,
deviceUuid: deviceUuid,
onAddSchedule: () async {
final entry = await ScheduleDialogHelper
.showAddScheduleDialog(
context,
schedule: ScheduleEntry(
category: category,
time: '',
function: Status(
code: code.toString(), value: null),
days: [],
),
isEdit: false,
code: code,
);
.showAddScheduleDialog(context,
schedule: ScheduleEntry(
category: category,
time: '',
function: Status(
code: code.toString(), value: null),
days: [],
),
isEdit: false,
code: code,
deviceType: deviceType);
if (entry != null) {
context.read<ScheduleBloc>().add(
ScheduleAddEvent(
@ -87,14 +96,16 @@ class BuildScheduleView extends StatelessWidget {
}
},
),
if (state.scheduleMode == ScheduleModes.countdown ||
state.scheduleMode == ScheduleModes.inching)
CountdownInchingView(
deviceId: deviceUuid,
),
if (deviceType != 'CUR_2')
if (state.scheduleMode == ScheduleModes.countdown ||
state.scheduleMode == ScheduleModes.inching)
CountdownInchingView(
deviceId: deviceUuid,
),
const SizedBox(height: 20),
if (state.scheduleMode == ScheduleModes.countdown)
CountdownModeButtons(
countDownCode: countdownCode ?? '',
isActive: state.isCountdownActive,
deviceId: deviceUuid,
hours: state.countdownHours,

View File

@ -5,14 +5,16 @@ import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
class ScheduleManagementUI extends StatelessWidget {
final String deviceUuid;
final String deviceUuid;
final VoidCallback onAddSchedule;
final String category;
final String deviceType;
const ScheduleManagementUI({
super.key,
required this.deviceUuid,
required this.onAddSchedule,
required this.deviceType,
this.category = 'switch_1',
});
@ -44,7 +46,11 @@ class ScheduleManagementUI extends StatelessWidget {
),
),
const SizedBox(height: 20),
ScheduleTableWidget(deviceUuid: deviceUuid, category: category),
ScheduleTableWidget(
deviceUuid: deviceUuid,
category: category,
deviceType: deviceType,
),
],
);
}

View File

@ -7,10 +7,12 @@ import 'package:syncrow_web/utils/extension/build_context_x.dart';
class ScheduleModeSelector extends StatelessWidget {
final ScheduleModes currentMode;
final String countdownCode;
const ScheduleModeSelector({
super.key,
required this.currentMode,
required this.countdownCode,
});
@override
@ -71,7 +73,8 @@ class ScheduleModeSelector extends StatelessWidget {
onChanged: (ScheduleModes? value) {
if (value != null) {
context.read<ScheduleBloc>().add(
UpdateScheduleModeEvent(scheduleMode: value),
UpdateScheduleModeEvent(
scheduleMode: value, countdownCode: countdownCode),
);
if (value == ScheduleModes.schedule) {
context.read<ScheduleBloc>().add(

View File

@ -12,11 +12,13 @@ import 'package:syncrow_web/utils/format_date_time.dart';
class ScheduleTableWidget extends StatelessWidget {
final String deviceUuid;
final String category;
final String deviceType;
const ScheduleTableWidget({
super.key,
required this.deviceUuid,
this.category = 'switch_1',
required this.deviceType,
});
@override
@ -25,13 +27,14 @@ class ScheduleTableWidget extends StatelessWidget {
create: (_) => ScheduleBloc(
deviceId: deviceUuid,
)..add(ScheduleGetEvent(category: category)),
child: _ScheduleTableView(),
child: _ScheduleTableView(deviceType),
);
}
}
class _ScheduleTableView extends StatelessWidget {
const _ScheduleTableView();
final String deviceType;
const _ScheduleTableView(this.deviceType);
@override
Widget build(BuildContext context) {
@ -81,7 +84,7 @@ class _ScheduleTableView extends StatelessWidget {
bottomLeft: Radius.circular(20),
bottomRight: Radius.circular(20)),
),
child: _buildTableBody(state.schedules, context));
child: _buildTableBody(state.schedules, context, deviceType));
}
if (state is ScheduleError) {
return Center(child: Text(state.error));
@ -123,7 +126,8 @@ class _ScheduleTableView extends StatelessWidget {
);
}
Widget _buildTableBody(List<ScheduleModel> schedules, BuildContext context) {
Widget _buildTableBody(
List<ScheduleModel> schedules, BuildContext context, String deviceType) {
return SizedBox(
height: 200,
child: SingleChildScrollView(
@ -132,7 +136,8 @@ class _ScheduleTableView extends StatelessWidget {
defaultVerticalAlignment: TableCellVerticalAlignment.middle,
children: [
for (int i = 0; i < schedules.length; i++)
_buildScheduleRow(schedules[i], i, context),
_buildScheduleRow(schedules[i], i, context,
deviceType: deviceType),
],
),
),
@ -155,25 +160,19 @@ class _ScheduleTableView extends StatelessWidget {
}
TableRow _buildScheduleRow(
ScheduleModel schedule, int index, BuildContext context) {
ScheduleModel schedule, int index, BuildContext context,
{required String deviceType}) {
return TableRow(
children: [
Center(
child: GestureDetector(
behavior: HitTestBehavior.opaque,
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(
ScheduleUpdateEntryEvent(
category: schedule.category,
scheduleId: schedule.scheduleId,
functionOn: temp,
// schedule.function.value,
functionOn: schedule.function.value,
enable: !schedule.enable,
),
);
@ -195,8 +194,9 @@ class _ScheduleTableView extends StatelessWidget {
child: Text(_getSelectedDays(
ScheduleModel.parseSelectedDays(schedule.days)))),
Center(child: Text(formatIsoStringToTime(schedule.time, context))),
if (schedule.category == 'CUR_2')
Center(child: Text(schedule.function.value))
if (deviceType == 'CUR_2')
Center(
child: Text(schedule.function.value == true ? 'open' : 'close'))
else
Center(child: Text(schedule.function.value ? 'On' : 'Off')),
Center(
@ -206,18 +206,27 @@ class _ScheduleTableView extends StatelessWidget {
TextButton(
style: TextButton.styleFrom(padding: EdgeInsets.zero),
onPressed: () {
ScheduleDialogHelper.showAddScheduleDialog(
context,
schedule: ScheduleEntry.fromScheduleModel(schedule),
isEdit: true,
).then((updatedSchedule) {
ScheduleDialogHelper.showAddScheduleDialog(context,
schedule: ScheduleEntry.fromScheduleModel(schedule),
isEdit: true,
deviceType: deviceType)
.then((updatedSchedule) {
if (updatedSchedule != null) {
bool temp;
if (deviceType == 'CUR_2') {
updatedSchedule.function.value == 'open'
? temp = true
: temp = false;
} else {
temp = updatedSchedule.function.value;
}
context.read<ScheduleBloc>().add(
ScheduleEditEvent(
deviceType: deviceType,
scheduleId: schedule.scheduleId,
category: schedule.category,
time: updatedSchedule.time,
functionOn: updatedSchedule.function.value,
functionOn: temp,
selectedDays: updatedSchedule.days),
);
}

View File

@ -41,7 +41,7 @@ class ThreeGangGlassSwitchBloc
emit(ThreeGangGlassSwitchLoading());
try {
final status = await DevicesManagementApi().getDeviceStatus(event.deviceId);
_listenToChanges(event.deviceId, emit);
_listenToChanges(event.deviceId);
deviceStatus =
ThreeGangGlassStatusModel.fromJson(event.deviceId, status.status);
emit(ThreeGangGlassSwitchStatusLoaded(deviceStatus));
@ -50,42 +50,28 @@ class ThreeGangGlassSwitchBloc
}
}
void _listenToChanges(
String deviceId,
Emitter<ThreeGangGlassSwitchState> emit,
) {
StreamSubscription<DatabaseEvent>? _deviceStatusSubscription;
void _listenToChanges(String deviceId) {
try {
final ref = FirebaseDatabase.instance.ref('device-status/$deviceId');
final stream = ref.onValue;
_deviceStatusSubscription = ref.onValue.listen((DatabaseEvent event) async {
if (event.snapshot.value == null) return;
stream.listen((DatabaseEvent event) {
final data = event.snapshot.value as Map<dynamic, dynamic>?;
if (data == null) return;
final usersMap = event.snapshot.value! as Map<dynamic, dynamic>;
final statusList = <Status>[];
if (data['status'] != null) {
for (var element in data['status']) {
statusList.add(
Status(
code: element['code'].toString(),
value: element['value'].toString(),
),
);
}
}
if (statusList.isNotEmpty) {
final newStatus = ThreeGangGlassStatusModel.fromJson(deviceId, statusList);
if (newStatus != deviceStatus) {
deviceStatus = newStatus;
if (!isClosed) {
add(StatusUpdated(deviceStatus));
}
}
}
usersMap['status'].forEach((element) {
statusList.add(Status(code: element['code'], value: element['value']));
});
deviceStatus =
ThreeGangGlassStatusModel.fromJson(usersMap['productUuid'], statusList);
add(StatusUpdated(deviceStatus));
});
} catch (e) {
emit(ThreeGangGlassSwitchError('Failed to listen to changes: $e'));
}
} catch (_) {}
}
void _onStatusUpdated(
@ -184,4 +170,10 @@ class ThreeGangGlassSwitchBloc
break;
}
}
@override
Future<void> close() {
_deviceStatusSubscription?.cancel();
return super.close();
}
}

View File

@ -111,6 +111,8 @@ class ThreeGangGlassSwitchControlView extends StatelessWidget
child: BuildScheduleView(
category: 'switch_1',
deviceUuid: deviceId,
countdownCode: 'countdown_1',
deviceType: '3GT',
),
));
},
@ -127,6 +129,8 @@ class ThreeGangGlassSwitchControlView extends StatelessWidget
child: BuildScheduleView(
category: 'switch_2',
deviceUuid: deviceId,
countdownCode: 'countdown_2',
deviceType: '3GT',
),
));
},
@ -143,6 +147,8 @@ class ThreeGangGlassSwitchControlView extends StatelessWidget
child: BuildScheduleView(
category: 'switch_3',
deviceUuid: deviceId,
countdownCode: 'countdown_3',
deviceType: '3GT',
),
));
},

View File

@ -102,6 +102,8 @@ class LivingRoomDeviceControlsView extends StatelessWidget
child: BuildScheduleView(
deviceUuid: deviceId,
category: 'switch_1',
countdownCode: 'countdown_1',
deviceType: '3G',
),
));
},
@ -118,6 +120,8 @@ class LivingRoomDeviceControlsView extends StatelessWidget
child: BuildScheduleView(
deviceUuid: deviceId,
category: 'switch_2',
countdownCode: 'countdown_2',
deviceType: '3G',
),
));
},
@ -134,6 +138,8 @@ class LivingRoomDeviceControlsView extends StatelessWidget
child: BuildScheduleView(
deviceUuid: deviceId,
category: 'switch_3',
countdownCode: 'countdown_3',
deviceType: '3G',
),
));
},

View File

@ -1,173 +1,177 @@
import 'dart:async';
import 'dart:developer';
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:firebase_database/firebase_database.dart';
import 'package:flutter/foundation.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/two_g_glass_switch/models/two_gang_glass_status_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';
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:firebase_database/firebase_database.dart';
import 'package:flutter/foundation.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/two_g_glass_switch/models/two_gang_glass_status_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 'two_gang_glass_switch_event.dart';
part 'two_gang_glass_switch_state.dart';
part 'two_gang_glass_switch_event.dart';
part 'two_gang_glass_switch_state.dart';
class TwoGangGlassSwitchBloc
extends Bloc<TwoGangGlassSwitchEvent, TwoGangGlassSwitchState> {
final String deviceId;
final ControlDeviceService controlDeviceService;
final BatchControlDevicesService batchControlDevicesService;
class TwoGangGlassSwitchBloc
extends Bloc<TwoGangGlassSwitchEvent, TwoGangGlassSwitchState> {
final String deviceId;
final ControlDeviceService controlDeviceService;
final BatchControlDevicesService batchControlDevicesService;
late TwoGangGlassStatusModel deviceStatus;
late TwoGangGlassStatusModel deviceStatus;
TwoGangGlassSwitchBloc({
required this.deviceId,
required this.controlDeviceService,
required this.batchControlDevicesService,
}) : super(TwoGangGlassSwitchInitial()) {
on<TwoGangGlassSwitchFetchDeviceEvent>(_onFetchDeviceStatus);
on<TwoGangGlassSwitchControl>(_onControl);
on<TwoGangGlassSwitchBatchControl>(_onBatchControl);
on<TwoGangGlassSwitchFetchBatchStatusEvent>(_onFetchBatchStatus);
on<TwoGangGlassFactoryReset>(_onFactoryReset);
on<StatusUpdated>(_onStatusUpdated);
}
Future<void> _onFetchDeviceStatus(
TwoGangGlassSwitchFetchDeviceEvent event,
Emitter<TwoGangGlassSwitchState> emit,
) async {
emit(TwoGangGlassSwitchLoading());
try {
final status = await DevicesManagementApi().getDeviceStatus(event.deviceId);
deviceStatus = TwoGangGlassStatusModel.fromJson(event.deviceId, status.status);
_listenToChanges(event.deviceId);
emit(TwoGangGlassSwitchStatusLoaded(deviceStatus));
} catch (e) {
emit(TwoGangGlassSwitchError(e.toString()));
TwoGangGlassSwitchBloc({
required this.deviceId,
required this.controlDeviceService,
required this.batchControlDevicesService,
}) : super(TwoGangGlassSwitchInitial()) {
on<TwoGangGlassSwitchFetchDeviceEvent>(_onFetchDeviceStatus);
on<TwoGangGlassSwitchControl>(_onControl);
on<TwoGangGlassSwitchBatchControl>(_onBatchControl);
on<TwoGangGlassSwitchFetchBatchStatusEvent>(_onFetchBatchStatus);
on<TwoGangGlassFactoryReset>(_onFactoryReset);
on<StatusUpdated>(_onStatusUpdated);
}
}
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 = TwoGangGlassStatusModel.fromJson(deviceId, statusList);
add(StatusUpdated(deviceStatus));
});
} catch (_) {
log(
'Error listening to changes',
name: 'TwoGangGlassSwitchBloc._listenToChanges',
);
}
}
Future<void> _onControl(
TwoGangGlassSwitchControl event,
Emitter<TwoGangGlassSwitchState> emit,
) async {
emit(TwoGangGlassSwitchLoading());
_updateLocalValue(event.code, event.value);
emit(TwoGangGlassSwitchStatusLoaded(deviceStatus));
try {
await controlDeviceService.controlDevice(
deviceUuid: event.deviceId,
status: Status(code: event.code, value: event.value),
);
} catch (e) {
_updateLocalValue(event.code, !event.value);
emit(TwoGangGlassSwitchError(e.toString()));
}
}
Future<void> _onBatchControl(
TwoGangGlassSwitchBatchControl event,
Emitter<TwoGangGlassSwitchState> emit,
) async {
emit(TwoGangGlassSwitchLoading());
_updateLocalValue(event.code, event.value);
emit(TwoGangGlassSwitchBatchStatusLoaded(deviceStatus));
try {
await batchControlDevicesService.batchControlDevices(
uuids: event.deviceIds,
code: event.code,
value: event.value,
);
} catch (e) {
_updateLocalValue(event.code, !event.value);
emit(TwoGangGlassSwitchError(e.toString()));
}
}
Future<void> _onFetchBatchStatus(
TwoGangGlassSwitchFetchBatchStatusEvent event,
Emitter<TwoGangGlassSwitchState> emit,
) async {
emit(TwoGangGlassSwitchLoading());
try {
final status = await DevicesManagementApi().getBatchStatus(event.deviceIds);
deviceStatus = TwoGangGlassStatusModel.fromJson(
event.deviceIds.first,
status.status,
);
emit(TwoGangGlassSwitchBatchStatusLoaded(deviceStatus));
} catch (e) {
emit(TwoGangGlassSwitchError(e.toString()));
}
}
Future<void> _onFactoryReset(
TwoGangGlassFactoryReset event,
Emitter<TwoGangGlassSwitchState> emit,
) async {
emit(TwoGangGlassSwitchLoading());
try {
final response = await DevicesManagementApi().factoryReset(
event.factoryReset,
event.deviceId,
);
if (!response) {
emit(TwoGangGlassSwitchError('Failed to reset device'));
} else {
add(TwoGangGlassSwitchFetchDeviceEvent(event.deviceId));
Future<void> _onFetchDeviceStatus(
TwoGangGlassSwitchFetchDeviceEvent event,
Emitter<TwoGangGlassSwitchState> emit,
) async {
emit(TwoGangGlassSwitchLoading());
try {
final status = await DevicesManagementApi().getDeviceStatus(event.deviceId);
deviceStatus = TwoGangGlassStatusModel.fromJson(event.deviceId, status.status);
_listenToChanges(event.deviceId);
emit(TwoGangGlassSwitchStatusLoaded(deviceStatus));
} catch (e) {
emit(TwoGangGlassSwitchError(e.toString()));
}
} catch (e) {
emit(TwoGangGlassSwitchError(e.toString()));
}
}
void _onStatusUpdated(
StatusUpdated event,
Emitter<TwoGangGlassSwitchState> emit,
) {
deviceStatus = event.deviceStatus;
emit(TwoGangGlassSwitchStatusLoaded(deviceStatus));
}
StreamSubscription<DatabaseEvent>? _deviceStatusSubscription;
void _updateLocalValue(String code, bool value) {
switch (code) {
case 'switch_1':
deviceStatus = deviceStatus.copyWith(switch1: value);
break;
case 'switch_2':
deviceStatus = deviceStatus.copyWith(switch2: value);
break;
void _listenToChanges(String deviceId) {
try {
final ref = FirebaseDatabase.instance.ref('device-status/$deviceId');
_deviceStatusSubscription = ref.onValue.listen((DatabaseEvent event) async {
if (event.snapshot.value == null) return;
final usersMap = event.snapshot.value! as Map<dynamic, dynamic>;
final statusList = <Status>[];
usersMap['status'].forEach((element) {
statusList.add(Status(code: element['code'], value: element['value']));
});
deviceStatus =
TwoGangGlassStatusModel.fromJson(usersMap['productUuid'], statusList);
add(StatusUpdated(deviceStatus));
});
} catch (_) {}
}
Future<void> _onControl(
TwoGangGlassSwitchControl event,
Emitter<TwoGangGlassSwitchState> emit,
) async {
emit(TwoGangGlassSwitchLoading());
_updateLocalValue(event.code, event.value);
emit(TwoGangGlassSwitchStatusLoaded(deviceStatus));
try {
await controlDeviceService.controlDevice(
deviceUuid: event.deviceId,
status: Status(code: event.code, value: event.value),
);
} catch (e) {
_updateLocalValue(event.code, !event.value);
emit(TwoGangGlassSwitchError(e.toString()));
}
}
Future<void> _onBatchControl(
TwoGangGlassSwitchBatchControl event,
Emitter<TwoGangGlassSwitchState> emit,
) async {
emit(TwoGangGlassSwitchLoading());
_updateLocalValue(event.code, event.value);
emit(TwoGangGlassSwitchBatchStatusLoaded(deviceStatus));
try {
await batchControlDevicesService.batchControlDevices(
uuids: event.deviceIds,
code: event.code,
value: event.value,
);
} catch (e) {
_updateLocalValue(event.code, !event.value);
emit(TwoGangGlassSwitchError(e.toString()));
}
}
Future<void> _onFetchBatchStatus(
TwoGangGlassSwitchFetchBatchStatusEvent event,
Emitter<TwoGangGlassSwitchState> emit,
) async {
emit(TwoGangGlassSwitchLoading());
try {
final status = await DevicesManagementApi().getBatchStatus(event.deviceIds);
deviceStatus = TwoGangGlassStatusModel.fromJson(
event.deviceIds.first,
status.status,
);
emit(TwoGangGlassSwitchBatchStatusLoaded(deviceStatus));
} catch (e) {
emit(TwoGangGlassSwitchError(e.toString()));
}
}
Future<void> _onFactoryReset(
TwoGangGlassFactoryReset event,
Emitter<TwoGangGlassSwitchState> emit,
) async {
emit(TwoGangGlassSwitchLoading());
try {
final response = await DevicesManagementApi().factoryReset(
event.factoryReset,
event.deviceId,
);
if (!response) {
emit(TwoGangGlassSwitchError('Failed to reset device'));
} else {
add(TwoGangGlassSwitchFetchDeviceEvent(event.deviceId));
}
} catch (e) {
emit(TwoGangGlassSwitchError(e.toString()));
}
}
void _onStatusUpdated(
StatusUpdated event,
Emitter<TwoGangGlassSwitchState> emit,
) {
deviceStatus = event.deviceStatus;
emit(TwoGangGlassSwitchStatusLoaded(deviceStatus));
}
void _updateLocalValue(String code, bool value) {
switch (code) {
case 'switch_1':
deviceStatus = deviceStatus.copyWith(switch1: value);
break;
case 'switch_2':
deviceStatus = deviceStatus.copyWith(switch2: value);
break;
}
}
@override
Future<void> close() {
_deviceStatusSubscription?.cancel();
return super.close();
}
}

View File

@ -102,6 +102,8 @@ class TwoGangGlassSwitchControlView extends StatelessWidget
builder: (ctx) => BlocProvider.value(
value: BlocProvider.of<TwoGangGlassSwitchBloc>(context),
child: BuildScheduleView(
deviceType: '2GT',
countdownCode: 'countdown_1',
deviceUuid: deviceId,
category: 'switch_1',
),
@ -118,6 +120,8 @@ class TwoGangGlassSwitchControlView extends StatelessWidget
builder: (ctx) => BlocProvider.value(
value: BlocProvider.of<TwoGangGlassSwitchBloc>(context),
child: BuildScheduleView(
deviceType: '2GT',
countdownCode: 'countdown_2',
deviceUuid: deviceId,
category: 'switch_2',
),

View File

@ -97,6 +97,8 @@ class TwoGangBatchControlView extends StatelessWidget
child: BuildScheduleView(
category: 'switch_1',
deviceUuid: deviceIds.first,
countdownCode: 'countdown_1',
deviceType: '2G',
),
));
},
@ -114,6 +116,8 @@ class TwoGangBatchControlView extends StatelessWidget
child: BuildScheduleView(
category: 'switch_2',
deviceUuid: deviceIds.first,
countdownCode: 'countdown_2',
deviceType: '2G',
),
));
},
@ -121,10 +125,7 @@ class TwoGangBatchControlView extends StatelessWidget
subtitle: 'Scheduling',
iconPath: Assets.scheduling,
),
// FirmwareUpdateWidget(
// deviceId: deviceIds.first,
// version: 12,
// ),
FactoryResetWidget(callFactoryReset: () {
context.read<TwoGangSwitchBloc>().add(
TwoGangFactoryReset(

View File

@ -103,6 +103,8 @@ class TwoGangDeviceControlView extends StatelessWidget
child: BuildScheduleView(
deviceUuid: deviceId,
category: 'switch_1',
countdownCode: 'countdown_1',
deviceType: '2G',
),
));
},
@ -125,6 +127,8 @@ class TwoGangDeviceControlView extends StatelessWidget
child: BuildScheduleView(
deviceUuid: deviceId,
category: 'switch_2',
countdownCode: 'countdown_2',
deviceType: '2G',
),
));
},

View File

@ -18,9 +18,10 @@ class ScheduleDialogHelper {
ScheduleEntry? schedule,
bool isEdit = false,
String? code,
required String deviceType,
}) {
bool temp;
if (schedule?.category == 'CUR_2') {
if (deviceType == 'CUR_2') {
temp = schedule!.function.value == 'open' ? true : false;
} else {
temp = schedule!.function.value;
@ -103,8 +104,7 @@ class ScheduleDialogHelper {
setState(() => selectedDays[i] = v);
}),
const SizedBox(height: 16),
_buildFunctionSwitch(schedule!.category, ctx, functionOn!,
(v) {
_buildFunctionSwitch(deviceType, ctx, functionOn!, (v) {
setState(() => functionOn = v);
}),
],
@ -120,32 +120,29 @@ class ScheduleDialogHelper {
),
),
SizedBox(
width: 100,
child: ElevatedButton(
onPressed: () {
dynamic temp;
if (schedule?.category == 'CUR_2') {
temp = functionOn! ? 'open' : 'close';
} else {
temp = functionOn;
}
print(temp);
final entry = ScheduleEntry(
category: schedule?.category ?? 'switch_1',
time: _formatTimeOfDayToISO(selectedTime),
function: Status(
code: code ?? 'switch_1',
value: temp,
// functionOn,
),
days: _convertSelectedDaysToStrings(selectedDays),
scheduleId: schedule?.scheduleId,
);
Navigator.pop(ctx, entry);
},
child: const Text('Save'),
),
),
width: 100,
child: ElevatedButton(
onPressed: () {
dynamic temp;
if (deviceType == 'CUR_2') {
temp = functionOn! ? 'open' : 'close';
} else {
temp = functionOn;
}
final entry = ScheduleEntry(
category: schedule?.category ?? 'switch_1',
time: _formatTimeOfDayToISO(selectedTime),
function: Status(
code: code ?? 'switch_1',
value: temp,
),
days: _convertSelectedDaysToStrings(selectedDays),
scheduleId: schedule.scheduleId,
);
Navigator.pop(ctx, entry);
},
child: const Text('Save'),
)),
],
);
},

View File

@ -84,6 +84,8 @@ class WaterHeaterDeviceControlView extends StatelessWidget
child: BuildScheduleView(
deviceUuid: device.uuid ?? '',
category: 'switch_1',
countdownCode: 'countdown_1',
deviceType: 'WH',
),
));
},

View File

@ -58,11 +58,14 @@ class ProductModel {
'3G': Assets.Gang3SwitchIcon,
'3GT': Assets.threeTouchSwitch,
'CUR': Assets.curtain,
'CUR_2': Assets.curtain,
'GD': Assets.garageDoor,
'GW': Assets.SmartGatewayIcon,
'DL': Assets.DoorLockIcon,
'WL': Assets.waterLeakSensor,
'WH': Assets.waterHeater,
'WM': Assets.waterLeakSensor,
'SOS': Assets.sos,
'AC': Assets.ac,
'CPS': Assets.presenceSensor,
'PC': Assets.powerClamp,