Merge branch 'dev' of https://github.com/SyncrowIOT/web into feature/space-management

This commit is contained in:
hannathkadher
2024-11-11 20:57:11 +04:00
282 changed files with 19897 additions and 2847 deletions

View File

@ -14,12 +14,15 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
Timer? _timer;
AcBloc({required this.deviceId}) : super(AcsInitialState()) {
on<AcFetchDeviceStatus>(_onFetchAcStatus);
on<AcControl>(_onAcControl);
on<AcFetchDeviceStatusEvent>(_onFetchAcStatus);
on<AcFetchBatchStatusEvent>(_onFetchAcBatchStatus);
on<AcControlEvent>(_onAcControl);
on<AcBatchControlEvent>(_onAcBatchControl);
on<AcFactoryResetEvent>(_onFactoryReset);
}
FutureOr<void> _onFetchAcStatus(
AcFetchDeviceStatus event, Emitter<AcsState> emit) async {
AcFetchDeviceStatusEvent event, Emitter<AcsState> emit) async {
emit(AcsLoadingState());
try {
final status =
@ -31,7 +34,8 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
}
}
FutureOr<void> _onAcControl(AcControl event, Emitter<AcsState> emit) async {
FutureOr<void> _onAcControl(
AcControlEvent event, Emitter<AcsState> emit) async {
final oldValue = _getValueByCode(event.code);
_updateLocalValue(event.code, event.value, emit);
@ -39,6 +43,7 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
emit(ACStatusLoaded(deviceStatus));
await _runDebounce(
isBatch: false,
deviceId: event.deviceId,
code: event.code,
value: event.value,
@ -48,27 +53,43 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
}
Future<void> _runDebounce({
required String deviceId,
required dynamic deviceId,
required String code,
required dynamic value,
required dynamic oldValue,
required Emitter<AcsState> emit,
required bool isBatch,
}) async {
late String id;
if (deviceId is List) {
id = deviceId.first;
} else {
id = deviceId;
}
if (_timer != null) {
_timer!.cancel();
}
_timer = Timer(const Duration(seconds: 1), () async {
try {
final response = await DevicesManagementApi()
.deviceControl(deviceId, Status(code: code, value: value));
late bool response;
if (isBatch) {
response = await DevicesManagementApi()
.deviceBatchControl(deviceId, code, value);
} else {
response = await DevicesManagementApi()
.deviceControl(deviceId, Status(code: code, value: value));
}
if (!response) {
_revertValueAndEmit(deviceId, code, oldValue, emit);
_revertValueAndEmit(id, code, oldValue, emit);
}
} catch (e) {
if (e is DioException && e.response != null) {
debugPrint('Error response: ${e.response?.data}');
}
_revertValueAndEmit(deviceId, code, oldValue, emit);
_revertValueAndEmit(id, code, oldValue, emit);
}
});
}
@ -77,7 +98,6 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
String deviceId, String code, dynamic oldValue, Emitter<AcsState> emit) {
_updateLocalValue(code, oldValue, emit);
emit(ACStatusLoaded(deviceStatus));
emit(const AcsFailedState(error: 'Failed to control the device.'));
}
void _updateLocalValue(String code, dynamic value, Emitter<AcsState> emit) {
@ -133,4 +153,54 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
return null;
}
}
FutureOr<void> _onFetchAcBatchStatus(
AcFetchBatchStatusEvent event, Emitter<AcsState> emit) async {
emit(AcsLoadingState());
try {
final status =
await DevicesManagementApi().getBatchStatus(event.devicesIds);
deviceStatus =
AcStatusModel.fromJson(event.devicesIds.first, status.status);
emit(ACStatusLoaded(deviceStatus));
} catch (e) {
emit(AcsFailedState(error: e.toString()));
}
}
FutureOr<void> _onAcBatchControl(
AcBatchControlEvent event, Emitter<AcsState> emit) async {
final oldValue = _getValueByCode(event.code);
_updateLocalValue(event.code, event.value, emit);
emit(ACStatusLoaded(deviceStatus));
await _runDebounce(
isBatch: true,
deviceId: event.devicesIds,
code: event.code,
value: event.value,
oldValue: oldValue,
emit: emit,
);
}
FutureOr<void> _onFactoryReset(
AcFactoryResetEvent event, Emitter<AcsState> emit) async {
emit(AcsLoadingState());
try {
final response = await DevicesManagementApi().factoryReset(
event.factoryResetModel,
event.deviceId,
);
if (!response) {
emit(const AcsFailedState(error: 'Failed'));
} else {
add(AcFetchDeviceStatusEvent(event.deviceId));
}
} catch (e) {
emit(AcsFailedState(error: e.toString()));
}
}
}

View File

@ -1,4 +1,5 @@
import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart';
sealed class AcsEvent extends Equatable {
const AcsEvent();
@ -7,21 +8,30 @@ sealed class AcsEvent extends Equatable {
List<Object> get props => [];
}
class AcFetchDeviceStatus extends AcsEvent {
class AcFetchDeviceStatusEvent extends AcsEvent {
final String deviceId;
const AcFetchDeviceStatus(this.deviceId);
const AcFetchDeviceStatusEvent(this.deviceId);
@override
List<Object> get props => [deviceId];
}
class AcControl extends AcsEvent {
class AcFetchBatchStatusEvent extends AcsEvent {
final List<String> devicesIds;
const AcFetchBatchStatusEvent(this.devicesIds);
@override
List<Object> get props => [devicesIds];
}
class AcControlEvent extends AcsEvent {
final String deviceId;
final String code;
final dynamic value;
const AcControl({
const AcControlEvent({
required this.deviceId,
required this.code,
required this.value,
@ -30,3 +40,31 @@ class AcControl extends AcsEvent {
@override
List<Object> get props => [deviceId, code, value];
}
class AcBatchControlEvent extends AcsEvent {
final List<String> devicesIds;
final String code;
final dynamic value;
const AcBatchControlEvent({
required this.devicesIds,
required this.code,
required this.value,
});
@override
List<Object> get props => [devicesIds, code, value];
}
class AcFactoryResetEvent extends AcsEvent {
final String deviceId;
final FactoryResetModel factoryResetModel;
const AcFactoryResetEvent({
required this.deviceId,
required this.factoryResetModel,
});
@override
List<Object> get props => [deviceId, factoryResetModel];
}

View File

@ -22,6 +22,16 @@ class ACStatusLoaded extends AcsState {
List<Object> get props => [status, timestamp];
}
class AcBatchStatusLoaded extends AcsState {
final AcStatusModel status;
final DateTime timestamp;
AcBatchStatusLoaded(this.status) : timestamp = DateTime.now();
@override
List<Object> get props => [status, timestamp];
}
class AcsFailedState extends AcsState {
final String error;

View File

@ -40,16 +40,16 @@ class AcStatusModel {
acSwitch = status.value ?? false;
break;
case 'mode':
mode = status.value ?? 'cold'; // default to 'cold' if null
mode = status.value ?? 'cold';
break;
case 'temp_set':
tempSet = status.value ?? 210; // default value if null
tempSet = status.value ?? 210;
break;
case 'temp_current':
currentTemp = status.value ?? 210; // default value if null
currentTemp = status.value ?? 210;
break;
case 'level':
fanSpeeds = status.value ?? 'low'; // default value if null
fanSpeeds = status.value ?? 'low';
break;
case 'child_lock':
childLock = status.value ?? false;

View File

@ -0,0 +1,161 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_bloc.dart';
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_event.dart';
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_state.dart';
import 'package:syncrow_web/pages/device_managment/ac/view/batch_control_list/batch_ac_mode.dart';
import 'package:syncrow_web/pages/device_managment/ac/view/batch_control_list/batch_current_temp.dart';
import 'package:syncrow_web/pages/device_managment/ac/view/batch_control_list/batch_fan_speed.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart';
import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart';
import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
class AcDeviceBatchControlView extends StatelessWidget with HelperResponsiveLayout {
const AcDeviceBatchControlView({super.key, required this.devicesIds});
final List<String> devicesIds;
@override
Widget build(BuildContext context) {
final isExtraLarge = isExtraLargeScreenSize(context);
final isLarge = isLargeScreenSize(context);
final isMedium = isMediumScreenSize(context);
return BlocProvider(
create: (context) => AcBloc(deviceId: devicesIds.first)..add(AcFetchBatchStatusEvent(devicesIds)),
child: BlocBuilder<AcBloc, AcsState>(
builder: (context, state) {
if (state is ACStatusLoaded) {
return GridView(
padding: const EdgeInsets.symmetric(horizontal: 50, vertical: 20),
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: isLarge || isExtraLarge
? 3
: isMedium
? 2
: 1,
mainAxisExtent: 140,
crossAxisSpacing: 12,
mainAxisSpacing: 12,
),
children: [
ToggleWidget(
deviceId: devicesIds.first,
code: 'switch',
value: state.status.acSwitch,
label: 'ThermoState',
icon: Assets.ac,
onChange: (value) {
context.read<AcBloc>().add(AcBatchControlEvent(
devicesIds: devicesIds,
code: 'switch',
value: value,
));
},
),
BatchCurrentTemp(
currentTemp: state.status.currentTemp,
tempSet: state.status.tempSet,
code: 'temp_set',
devicesIds: devicesIds,
isBatch: true,
),
BatchAcMode(
value: state.status.acMode,
code: 'mode',
devicesIds: devicesIds,
),
BatchFanSpeedControl(
value: state.status.acFanSpeed,
code: 'level',
devicesIds: devicesIds,
),
ToggleWidget(
label: '',
labelWidget: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
IconButton(
onPressed: () {},
icon: const Icon(
Icons.remove,
size: 28,
color: ColorsManager.greyColor,
),
),
Text(
'06',
style: context.textTheme.titleLarge!.copyWith(
color: ColorsManager.dialogBlueTitle,
fontWeight: FontWeight.bold,
),
),
Text(
'h',
style: context.textTheme.bodySmall!.copyWith(color: ColorsManager.blackColor),
),
Text(
'30',
style: context.textTheme.titleLarge!.copyWith(
color: ColorsManager.dialogBlueTitle,
fontWeight: FontWeight.bold,
),
),
Text('m', style: context.textTheme.bodySmall!.copyWith(color: ColorsManager.blackColor)),
IconButton(
onPressed: () {},
icon: const Icon(
Icons.add,
size: 28,
color: ColorsManager.greyColor,
),
),
],
),
value: false,
code: 'ac_schedule',
deviceId: devicesIds.first,
icon: Assets.acSchedule,
onChange: (value) {},
),
ToggleWidget(
deviceId: devicesIds.first,
code: 'child_lock',
value: state.status.childLock,
label: 'Child Lock',
icon: state.status.childLock ? Assets.acLock : Assets.unlock,
onChange: (value) {
context.read<AcBloc>().add(AcBatchControlEvent(
devicesIds: devicesIds,
code: 'child_lock',
value: value,
));
},
),
FirmwareUpdateWidget(deviceId: devicesIds.first, version: 5),
FactoryResetWidget(
callFactoryReset: () {
context.read<AcBloc>().add(AcFactoryResetEvent(
deviceId: state.status.uuid,
factoryResetModel: FactoryResetModel(devicesUuid: devicesIds),
));
},
),
],
);
} else if (state is AcsLoadingState) {
return const Center(child: CircularProgressIndicator());
} else {
return const Center(child: Text('Error fetching status'));
}
},
),
);
}
}

View File

@ -4,25 +4,27 @@ import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_bloc.dart';
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_event.dart';
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_state.dart';
import 'package:syncrow_web/pages/device_managment/ac/view/control_list/ac_mode.dart';
import 'package:syncrow_web/pages/device_managment/ac/view/control_list/ac_toggle.dart';
import 'package:syncrow_web/pages/device_managment/ac/view/control_list/current_temp.dart';
import 'package:syncrow_web/pages/device_managment/ac/view/control_list/fan_speed.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
class AcDeviceControl extends StatelessWidget with HelperResponsiveLayout {
const AcDeviceControl({super.key, required this.device});
class AcDeviceControlsView extends StatelessWidget with HelperResponsiveLayout {
const AcDeviceControlsView({super.key, required this.device});
final AllDevicesModel device;
@override
Widget build(BuildContext context) {
final isExtraLarge = isExtraLargeScreenSize(context);
final isLarge = isLargeScreenSize(context);
final isMedium = isMediumScreenSize(context);
return BlocProvider(
create: (context) => AcBloc(deviceId: device.uuid!)
..add(AcFetchDeviceStatus(device.uuid!)),
create: (context) => AcBloc(deviceId: device.uuid!)..add(AcFetchDeviceStatusEvent(device.uuid!)),
child: BlocBuilder<AcBloc, AcsState>(
builder: (context, state) {
if (state is ACStatusLoaded) {
@ -31,20 +33,31 @@ class AcDeviceControl extends StatelessWidget with HelperResponsiveLayout {
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: isLarge
crossAxisCount: isLarge || isExtraLarge
? 3
: isMedium
? 2
: 1,
mainAxisExtent: 140,
crossAxisSpacing: 12,
mainAxisSpacing: 12,
mainAxisSpacing: 16,
),
children: [
AcToggle(
ToggleWidget(
label: 'Thermostat',
value: state.status.acSwitch,
code: 'switch',
deviceId: device.uuid!,
icon: Assets.ac,
onChange: (value) {
context.read<AcBloc>().add(
AcControlEvent(
deviceId: device.uuid!,
code: 'switch',
value: value,
),
);
},
),
CurrentTemp(
currentTemp: state.status.currentTemp,
@ -62,12 +75,71 @@ class AcDeviceControl extends StatelessWidget with HelperResponsiveLayout {
code: 'level',
deviceId: device.uuid!,
),
AcToggle(
value: state.status.childLock,
code: 'child_lock',
ToggleWidget(
label: '',
labelWidget: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
IconButton(
padding: const EdgeInsets.all(0),
onPressed: () {},
icon: const Icon(
Icons.remove,
size: 28,
color: ColorsManager.greyColor,
),
),
Text(
'06',
style: context.textTheme.titleLarge!.copyWith(
color: ColorsManager.dialogBlueTitle,
fontWeight: FontWeight.bold,
),
),
Text(
'h',
style: context.textTheme.bodySmall!.copyWith(color: ColorsManager.blackColor),
),
Text(
'30',
style: context.textTheme.titleLarge!.copyWith(
color: ColorsManager.dialogBlueTitle,
fontWeight: FontWeight.bold,
),
),
Text('m', style: context.textTheme.bodySmall!.copyWith(color: ColorsManager.blackColor)),
IconButton(
padding: const EdgeInsets.all(0),
onPressed: () {},
icon: const Icon(
Icons.add,
size: 28,
color: ColorsManager.greyColor,
),
),
],
),
value: false,
code: 'ac_schedule',
deviceId: device.uuid!,
description: 'Child Lock',
icon: Assets.childLock,
icon: Assets.acSchedule,
onChange: (value) {},
),
ToggleWidget(
deviceId: device.uuid!,
code: 'child_lock',
value: state.status.childLock,
label: 'Lock',
icon: state.status.childLock ? Assets.acLock : Assets.unlock,
onChange: (value) {
context.read<AcBloc>().add(
AcControlEvent(
deviceId: device.uuid!,
code: 'child_lock',
value: value,
),
);
},
),
],
);

View File

@ -0,0 +1,73 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_bloc.dart';
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_event.dart';
import 'package:syncrow_web/pages/device_managment/ac/model/ac_model.dart';
import 'package:syncrow_web/pages/device_managment/shared/device_controls_container.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
class BatchAcMode extends StatelessWidget {
const BatchAcMode({
super.key,
required this.value,
required this.code,
required this.devicesIds,
});
final TempModes value;
final String code;
final List<String> devicesIds;
@override
Widget build(BuildContext context) {
return DeviceControlsContainer(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
_buildIconContainer(context, TempModes.cold, Assets.freezing, value == TempModes.cold),
_buildIconContainer(context, TempModes.hot, Assets.acSun, value == TempModes.hot),
_buildIconContainer(context, TempModes.wind, Assets.acAirConditioner, value == TempModes.wind),
],
),
);
}
Widget _buildIconContainer(BuildContext context, TempModes mode, String assetPath, bool isSelected) {
return Flexible(
child: GestureDetector(
onTap: () {
context.read<AcBloc>().add(
AcBatchControlEvent(
devicesIds: devicesIds,
code: code,
value: mode.name,
),
);
},
child: Container(
width: 50,
height: 50,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: ColorsManager.whiteColors,
border: Border.all(
color: isSelected ? Colors.blue : Colors.transparent,
width: 2.0,
),
),
margin: const EdgeInsets.symmetric(horizontal: 4),
padding: const EdgeInsets.all(4),
child: ClipOval(
child: SvgPicture.asset(
assetPath,
fit: BoxFit.contain,
),
),
),
),
);
}
}

View File

@ -0,0 +1,132 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_bloc.dart';
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_event.dart';
import 'package:syncrow_web/pages/device_managment/shared/celciuse_symbol.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';
class BatchCurrentTemp extends StatefulWidget {
const BatchCurrentTemp({
super.key,
required this.code,
required this.devicesIds,
required this.currentTemp,
required this.tempSet,
this.isBatch,
});
final String code;
final List<String> devicesIds;
final int currentTemp;
final int tempSet;
final bool? isBatch;
@override
State<BatchCurrentTemp> createState() => _CurrentTempState();
}
class _CurrentTempState extends State<BatchCurrentTemp> {
late double _adjustedValue;
Timer? _debounce;
@override
void initState() {
super.initState();
_adjustedValue = _initialAdjustedValue(widget.tempSet);
}
double _initialAdjustedValue(dynamic value) {
if (value is int || value is double) {
double doubleValue = value.toDouble();
return doubleValue > 99 ? doubleValue / 10 : doubleValue;
} else {
throw ArgumentError('Invalid value type: Expected int or double');
}
}
void _onValueChanged(double newValue) {
if (_debounce?.isActive ?? false) {
_debounce?.cancel();
}
_debounce = Timer(const Duration(milliseconds: 500), () {
context.read<AcBloc>().add(
AcBatchControlEvent(
devicesIds: widget.devicesIds,
code: widget.code,
value: (newValue * 10).toInt(),
),
);
});
}
@override
void dispose() {
_debounce?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return DeviceControlsContainer(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
widget.isBatch == true
? Text(
'Set Temperature',
style: Theme.of(context).textTheme.bodySmall!.copyWith(color: Colors.grey),
)
: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Current Temperature',
style: Theme.of(context).textTheme.bodySmall!.copyWith(color: Colors.grey),
),
const SizedBox(
height: 5,
),
Row(
children: [
Text(
(widget.currentTemp > 99 ? widget.currentTemp / 10 : widget.currentTemp).toString(),
style: Theme.of(context).textTheme.bodySmall!.copyWith(color: Colors.grey),
),
const CelsiusSymbol(
color: Colors.grey,
)
],
),
],
),
const Spacer(),
IncrementDecrementWidget(
value: _adjustedValue.toString(),
description: '°C',
descriptionColor: ColorsManager.dialogBlueTitle,
onIncrement: () {
if (_adjustedValue < 30) {
setState(() {
_adjustedValue = _adjustedValue + 0.5;
});
_onValueChanged(_adjustedValue);
}
},
onDecrement: () {
if (_adjustedValue > 20) {
setState(() {
_adjustedValue = _adjustedValue - 0.5;
});
_onValueChanged(_adjustedValue);
}
}),
],
),
);
}
}

View File

@ -0,0 +1,83 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_bloc.dart';
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_event.dart';
import 'package:syncrow_web/pages/device_managment/ac/model/ac_model.dart';
import 'package:syncrow_web/pages/device_managment/shared/device_controls_container.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
class BatchFanSpeedControl extends StatelessWidget {
const BatchFanSpeedControl({
super.key,
required this.value,
required this.code,
required this.devicesIds,
});
final FanSpeeds value;
final String code;
final List<String> devicesIds;
@override
Widget build(BuildContext context) {
return DeviceControlsContainer(
padding: 8,
child: Column(
children: [
Wrap(
runSpacing: 8,
spacing: 8,
children: [
_buildIconContainer(context, FanSpeeds.auto, Assets.acFanAuto, value == FanSpeeds.auto),
_buildIconContainer(context, FanSpeeds.low, Assets.acFanLow, value == FanSpeeds.low),
],
),
const SizedBox(height: 8),
Wrap(
runSpacing: 8,
spacing: 8,
children: [
_buildIconContainer(context, FanSpeeds.middle, Assets.acFanMiddle, value == FanSpeeds.middle),
_buildIconContainer(context, FanSpeeds.high, Assets.acFanHigh, value == FanSpeeds.high),
],
)
],
),
);
}
Widget _buildIconContainer(BuildContext context, FanSpeeds speed, String assetPath, bool isSelected) {
return GestureDetector(
onTap: () {
context.read<AcBloc>().add(
AcBatchControlEvent(
devicesIds: devicesIds,
code: code,
value: speed.name,
),
);
},
child: Container(
width: 50,
height: 50,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: ColorsManager.whiteColors,
border: Border.all(
color: isSelected ? Colors.blue : Colors.transparent,
width: 2.0,
),
),
padding: const EdgeInsets.all(8),
child: ClipOval(
child: SvgPicture.asset(
assetPath,
fit: BoxFit.contain,
),
),
),
);
}
}

View File

@ -1,11 +1,12 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/ac/model/ac_model.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_bloc.dart';
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_event.dart';
import 'package:syncrow_web/pages/device_managment/ac/model/ac_model.dart';
import 'package:syncrow_web/pages/device_managment/shared/device_controls_container.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
class AcMode extends StatelessWidget {
const AcMode({
@ -21,35 +22,25 @@ class AcMode extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
color: ColorsManager.greyColor.withOpacity(0.2),
border: Border.all(color: ColorsManager.boxDivider),
),
padding: const EdgeInsets.all(16),
return DeviceControlsContainer(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
_buildIconContainer(context, TempModes.cold, Assets.freezing,
value == TempModes.cold),
_buildIconContainer(
context, TempModes.hot, Assets.acSun, value == TempModes.hot),
_buildIconContainer(context, TempModes.wind, Assets.acAirConditioner,
value == TempModes.wind),
_buildIconContainer(context, TempModes.cold, Assets.freezing, value == TempModes.cold),
_buildIconContainer(context, TempModes.hot, Assets.acSun, value == TempModes.hot),
_buildIconContainer(context, TempModes.wind, Assets.acAirConditioner, value == TempModes.wind),
],
),
);
}
Widget _buildIconContainer(
BuildContext context, TempModes mode, String assetPath, bool isSelected) {
Widget _buildIconContainer(BuildContext context, TempModes mode, String assetPath, bool isSelected) {
return Flexible(
child: GestureDetector(
onTap: () {
context.read<AcBloc>().add(
AcControl(
AcControlEvent(
deviceId: deviceId,
code: code,
value: mode.name,

View File

@ -1,9 +1,9 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_bloc.dart';
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_event.dart';
import 'package:syncrow_web/pages/device_managment/shared/device_controls_container.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
@ -25,13 +25,7 @@ class AcToggle extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
color: ColorsManager.greyColor.withOpacity(0.2),
border: Border.all(color: ColorsManager.boxDivider),
),
padding: const EdgeInsets.all(16),
return DeviceControlsContainer(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@ -39,16 +33,21 @@ class AcToggle extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ClipOval(
child: Container(
color: ColorsManager.whiteColors,
child: SvgPicture.asset(
icon ?? Assets.acDevice,
width: 60,
height: 60,
fit: BoxFit.cover,
Container(
width: 60,
height: 60,
decoration: const BoxDecoration(
shape: BoxShape.circle,
color: ColorsManager.whiteColors,
),
)),
padding: const EdgeInsets.all(8),
child: ClipOval(
child: SvgPicture.asset(
icon ?? Assets.lightPulp,
fit: BoxFit.contain,
),
),
),
SizedBox(
height: 20,
width: 35,
@ -57,7 +56,7 @@ class AcToggle extends StatelessWidget {
value: value,
onChanged: (newValue) {
context.read<AcBloc>().add(
AcControl(
AcControlEvent(
deviceId: deviceId,
code: code,
value: newValue,

View File

@ -1,11 +1,13 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_bloc.dart';
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_event.dart';
import 'package:syncrow_web/pages/device_managment/shared/celciuse_symbol.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/pages/device_managment/ac/bloc/ac_bloc.dart';
class CurrentTemp extends StatefulWidget {
const CurrentTemp({
@ -50,7 +52,7 @@ class _CurrentTempState extends State<CurrentTemp> {
}
_debounce = Timer(const Duration(milliseconds: 500), () {
context.read<AcBloc>().add(
AcControl(
AcControlEvent(
deviceId: widget.deviceId,
code: widget.code,
value: (newValue * 10).toInt(),
@ -67,13 +69,7 @@ class _CurrentTempState extends State<CurrentTemp> {
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
color: ColorsManager.greyColor.withOpacity(0.2),
border: Border.all(color: ColorsManager.boxDivider),
),
padding: const EdgeInsets.all(16),
return DeviceControlsContainer(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@ -83,10 +79,7 @@ class _CurrentTempState extends State<CurrentTemp> {
children: [
Text(
'Current Temperature',
style: Theme.of(context)
.textTheme
.bodySmall!
.copyWith(color: Colors.grey),
style: Theme.of(context).textTheme.bodySmall!.copyWith(color: Colors.grey),
),
const SizedBox(
height: 5,
@ -94,14 +87,8 @@ class _CurrentTempState extends State<CurrentTemp> {
Row(
children: [
Text(
(widget.currentTemp > 99
? widget.currentTemp / 10
: widget.currentTemp)
.toString(),
style: Theme.of(context)
.textTheme
.bodySmall!
.copyWith(color: Colors.grey),
(widget.currentTemp > 99 ? widget.currentTemp / 10 : widget.currentTemp).toString(),
style: Theme.of(context).textTheme.bodySmall!.copyWith(color: Colors.grey),
),
const CelsiusSymbol(
color: Colors.grey,
@ -116,16 +103,20 @@ class _CurrentTempState extends State<CurrentTemp> {
description: '°C',
descriptionColor: ColorsManager.dialogBlueTitle,
onIncrement: () {
setState(() {
_adjustedValue++;
});
_onValueChanged(_adjustedValue);
if (_adjustedValue < 30) {
setState(() {
_adjustedValue = _adjustedValue + 0.5;
});
_onValueChanged(_adjustedValue);
}
},
onDecrement: () {
setState(() {
_adjustedValue--;
});
_onValueChanged(_adjustedValue);
if (_adjustedValue > 20) {
setState(() {
_adjustedValue = _adjustedValue - 0.5;
});
_onValueChanged(_adjustedValue);
}
}),
],
),

View File

@ -1,11 +1,12 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/ac/model/ac_model.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_bloc.dart';
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_event.dart';
import 'package:syncrow_web/pages/device_managment/ac/model/ac_model.dart';
import 'package:syncrow_web/pages/device_managment/shared/device_controls_container.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
class FanSpeedControl extends StatelessWidget {
const FanSpeedControl({
@ -21,33 +22,24 @@ class FanSpeedControl extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
color: ColorsManager.greyColor.withOpacity(0.2),
border: Border.all(color: ColorsManager.boxDivider),
),
padding: const EdgeInsets.all(8),
return DeviceControlsContainer(
child: Column(
children: [
Wrap(
runSpacing: 8,
spacing: 8,
children: [
_buildIconContainer(context, FanSpeeds.auto, Assets.acFanAuto,
value == FanSpeeds.auto),
_buildIconContainer(context, FanSpeeds.low, Assets.acFanLow,
value == FanSpeeds.low),
_buildIconContainer(context, FanSpeeds.auto, Assets.acFanAuto, value == FanSpeeds.auto),
_buildIconContainer(context, FanSpeeds.low, Assets.acFanLow, value == FanSpeeds.low),
],
),
const SizedBox(height: 8),
Wrap(
runSpacing: 8,
spacing: 8,
children: [
_buildIconContainer(context, FanSpeeds.middle, Assets.acFanMiddle,
value == FanSpeeds.middle),
_buildIconContainer(context, FanSpeeds.high, Assets.acFanHigh,
value == FanSpeeds.high),
_buildIconContainer(context, FanSpeeds.middle, Assets.acFanMiddle, value == FanSpeeds.middle),
_buildIconContainer(context, FanSpeeds.high, Assets.acFanHigh, value == FanSpeeds.high),
],
)
],
@ -55,12 +47,11 @@ class FanSpeedControl extends StatelessWidget {
);
}
Widget _buildIconContainer(BuildContext context, FanSpeeds speed,
String assetPath, bool isSelected) {
Widget _buildIconContainer(BuildContext context, FanSpeeds speed, String assetPath, bool isSelected) {
return GestureDetector(
onTap: () {
context.read<AcBloc>().add(
AcControl(
AcControlEvent(
deviceId: deviceId,
code: code,
value: speed.name,

View File

@ -1,5 +1,5 @@
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.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/services/devices_mang_api.dart';
@ -14,6 +14,10 @@ class DeviceManagementBloc
int _offlineCount = 0;
int _lowBatteryCount = 0;
List<AllDevicesModel> _selectedDevices = [];
List<AllDevicesModel> _filteredDevices = [];
String currentProductName = '';
String? currentCommunity;
String? currentUnitName;
DeviceManagementBloc() : super(DeviceManagementInitial()) {
on<FetchDevices>(_onFetchDevices);
@ -21,6 +25,9 @@ class DeviceManagementBloc
on<SelectedFilterChanged>(_onSelectedFilterChanged);
on<SearchDevices>(_onSearchDevices);
on<SelectDevice>(_onSelectDevice);
on<ResetFilters>(_onResetFilters);
on<ResetSelectedDevices>(_onResetSelectedDevices);
on<UpdateSelection>(_onUpdateSelection);
}
Future<void> _onFetchDevices(
@ -28,14 +35,18 @@ class DeviceManagementBloc
emit(DeviceManagementLoading());
try {
final devices = await DevicesManagementApi().fetchDevices();
_selectedDevices.clear();
_devices = devices;
_filteredDevices = devices;
_calculateDeviceCounts();
emit(DeviceManagementLoaded(
devices: devices,
selectedIndex: _selectedIndex,
selectedIndex: 0,
onlineCount: _onlineCount,
offlineCount: _offlineCount,
lowBatteryCount: _lowBatteryCount,
selectedDevice: null,
isControlButtonEnabled: false,
));
} catch (e) {
emit(DeviceManagementInitial());
@ -43,9 +54,9 @@ class DeviceManagementBloc
}
void _onFilterDevices(
FilterDevices event, Emitter<DeviceManagementState> emit) {
FilterDevices event, Emitter<DeviceManagementState> emit) async {
if (_devices.isNotEmpty) {
final filteredDevices = _devices.where((device) {
_filteredDevices = List.from(_devices.where((device) {
switch (event.filter) {
case 'Online':
return device.online == true;
@ -56,13 +67,64 @@ class DeviceManagementBloc
default:
return true;
}
}).toList();
}).toList());
emit(DeviceManagementFiltered(
filteredDevices: filteredDevices,
filteredDevices: _filteredDevices,
selectedIndex: _selectedIndex,
onlineCount: _onlineCount,
offlineCount: _offlineCount,
lowBatteryCount: _lowBatteryCount,
selectedDevice: _selectedDevices.isNotEmpty ? _selectedDevices : null,
isControlButtonEnabled: _selectedDevices.isNotEmpty,
));
if (currentProductName.isNotEmpty) {
add(SearchDevices(productName: currentProductName));
}
}
}
Future<void> _onResetFilters(
ResetFilters event, Emitter<DeviceManagementState> emit) async {
currentProductName = '';
_selectedDevices.clear();
_filteredDevices = List.from(_devices);
_selectedIndex = 0;
emit(DeviceManagementLoaded(
devices: _devices,
selectedIndex: 0,
onlineCount: _onlineCount,
offlineCount: _offlineCount,
lowBatteryCount: _lowBatteryCount,
selectedDevice: null,
isControlButtonEnabled: false,
));
}
void _onResetSelectedDevices(
ResetSelectedDevices event, Emitter<DeviceManagementState> emit) {
_selectedDevices.clear();
if (state is DeviceManagementLoaded) {
emit(DeviceManagementLoaded(
devices: _devices,
selectedIndex: _selectedIndex,
onlineCount: _onlineCount,
offlineCount: _offlineCount,
lowBatteryCount: _lowBatteryCount,
selectedDevice: null,
isControlButtonEnabled: false,
));
} else if (state is DeviceManagementFiltered) {
emit(DeviceManagementFiltered(
filteredDevices: (state as DeviceManagementFiltered).filteredDevices,
selectedIndex: _selectedIndex,
onlineCount: _onlineCount,
offlineCount: _offlineCount,
lowBatteryCount: _lowBatteryCount,
selectedDevice: null,
isControlButtonEnabled: false,
));
}
}
@ -75,13 +137,18 @@ class DeviceManagementBloc
void _onSelectDevice(
SelectDevice event, Emitter<DeviceManagementState> emit) {
if (_selectedDevices.contains(event.selectedDevice)) {
_selectedDevices.remove(event.selectedDevice);
final selectedUuid = event.selectedDevice.uuid;
if (_selectedDevices.any((device) => device.uuid == selectedUuid)) {
_selectedDevices.removeWhere((device) => device.uuid == selectedUuid);
} else {
_selectedDevices.add(event.selectedDevice);
}
bool isControlButtonEnabled = _selectedDevices.length == 1;
List<AllDevicesModel> clonedSelectedDevices = List.from(_selectedDevices);
bool isControlButtonEnabled =
_checkIfControlButtonEnabled(clonedSelectedDevices);
if (state is DeviceManagementLoaded) {
emit(DeviceManagementLoaded(
@ -90,7 +157,9 @@ class DeviceManagementBloc
onlineCount: _onlineCount,
offlineCount: _offlineCount,
lowBatteryCount: _lowBatteryCount,
selectedDevice: isControlButtonEnabled ? _selectedDevices.first : null,
selectedDevice:
clonedSelectedDevices.isNotEmpty ? clonedSelectedDevices : null,
isControlButtonEnabled: isControlButtonEnabled,
));
} else if (state is DeviceManagementFiltered) {
emit(DeviceManagementFiltered(
@ -99,11 +168,66 @@ class DeviceManagementBloc
onlineCount: _onlineCount,
offlineCount: _offlineCount,
lowBatteryCount: _lowBatteryCount,
selectedDevice: isControlButtonEnabled ? _selectedDevices.first : null,
selectedDevice:
clonedSelectedDevices.isNotEmpty ? clonedSelectedDevices : null,
isControlButtonEnabled: isControlButtonEnabled,
));
}
}
void _onUpdateSelection(
UpdateSelection event, Emitter<DeviceManagementState> emit) {
List<AllDevicesModel> selectedDevices = [];
List<AllDevicesModel> devicesToSelectFrom = [];
if (state is DeviceManagementLoaded) {
devicesToSelectFrom = (state as DeviceManagementLoaded).devices;
} else if (state is DeviceManagementFiltered) {
devicesToSelectFrom = (state as DeviceManagementFiltered).filteredDevices;
}
for (int i = 0; i < event.selectedRows.length; i++) {
if (event.selectedRows[i]) {
selectedDevices.add(devicesToSelectFrom[i]);
}
}
if (state is DeviceManagementLoaded) {
final loadedState = state as DeviceManagementLoaded;
emit(DeviceManagementLoaded(
devices: loadedState.devices,
selectedIndex: loadedState.selectedIndex,
onlineCount: loadedState.onlineCount,
offlineCount: loadedState.offlineCount,
lowBatteryCount: loadedState.lowBatteryCount,
selectedDevice: selectedDevices,
isControlButtonEnabled: _checkIfControlButtonEnabled(selectedDevices),
));
} else if (state is DeviceManagementFiltered) {
final filteredState = state as DeviceManagementFiltered;
emit(DeviceManagementFiltered(
filteredDevices: filteredState.filteredDevices,
selectedIndex: filteredState.selectedIndex,
onlineCount: filteredState.onlineCount,
offlineCount: filteredState.offlineCount,
lowBatteryCount: filteredState.lowBatteryCount,
selectedDevice: selectedDevices,
isControlButtonEnabled: _checkIfControlButtonEnabled(selectedDevices),
));
}
}
bool _checkIfControlButtonEnabled(List<AllDevicesModel> selectedDevices) {
if (selectedDevices.length > 1) {
final productTypes =
selectedDevices.map((device) => device.productType).toSet();
return productTypes.length == 1;
} else if (selectedDevices.length == 1) {
return true;
}
return false;
}
void _calculateDeviceCounts() {
_onlineCount = _devices.where((device) => device.online == true).length;
_offlineCount = _devices.where((device) => device.online == false).length;
@ -128,27 +252,61 @@ class DeviceManagementBloc
void _onSearchDevices(
SearchDevices event, Emitter<DeviceManagementState> emit) {
if (_devices.isNotEmpty) {
final filteredDevices = _devices.where((device) {
if ((event.community == null || event.community!.isEmpty) &&
(event.unitName == null || event.unitName!.isEmpty) &&
(event.productName == null || event.productName!.isEmpty)) {
currentProductName = '';
if (state is DeviceManagementFiltered) {
add(FilterDevices(_getFilterFromIndex(_selectedIndex)));
} else {
return;
}
}
if (event.productName == currentProductName &&
event.community == currentCommunity &&
event.unitName == currentUnitName &&
event.searchField) {
return;
}
currentProductName = event.productName ?? '';
currentCommunity = event.community;
currentUnitName = event.unitName;
List<AllDevicesModel> devicesToSearch = _filteredDevices;
if (devicesToSearch.isNotEmpty) {
final filteredDevices = devicesToSearch.where((device) {
final matchesCommunity = event.community == null ||
event.community!.isEmpty ||
(device.room?.name
(device.community?.name
?.toLowerCase()
.contains(event.community!.toLowerCase()) ??
false);
final matchesUnit = event.unitName == null ||
event.unitName!.isEmpty ||
(device.unit?.name
?.toLowerCase()
.contains(event.unitName!.toLowerCase()) ??
false);
(device.spaces != null &&
device.spaces!.isNotEmpty &&
device.spaces![0].spaceName
!.toLowerCase()
.contains(event.unitName!.toLowerCase()));
final matchesProductName = event.productName == null ||
event.productName!.isEmpty ||
(device.name
?.toLowerCase()
.contains(event.productName!.toLowerCase()) ??
false);
return matchesCommunity && matchesUnit && matchesProductName;
final matchesDeviceName = event.productName == null ||
event.productName!.isEmpty ||
(device.categoryName
?.toLowerCase()
.contains(event.productName!.toLowerCase()) ??
false);
return matchesCommunity &&
matchesUnit &&
(matchesProductName || matchesDeviceName);
}).toList();
emit(DeviceManagementFiltered(
@ -157,6 +315,8 @@ class DeviceManagementBloc
onlineCount: _onlineCount,
offlineCount: _offlineCount,
lowBatteryCount: _lowBatteryCount,
selectedDevice: null,
isControlButtonEnabled: false,
));
}
}

View File

@ -31,11 +31,13 @@ class SearchDevices extends DeviceManagementEvent {
final String? community;
final String? unitName;
final String? productName;
final bool searchField;
const SearchDevices({
this.community,
this.unitName,
this.productName,
this.searchField = false,
});
@override
@ -50,3 +52,13 @@ class SelectDevice extends DeviceManagementEvent {
@override
List<Object?> get props => [selectedDevice];
}
class ResetFilters extends DeviceManagementEvent {}
class ResetSelectedDevices extends DeviceManagementEvent {}
class UpdateSelection extends DeviceManagementEvent {
final List<bool> selectedRows;
const UpdateSelection(this.selectedRows);
}

View File

@ -17,7 +17,8 @@ class DeviceManagementLoaded extends DeviceManagementState {
final int onlineCount;
final int offlineCount;
final int lowBatteryCount;
final AllDevicesModel? selectedDevice;
final List<AllDevicesModel>? selectedDevice;
final bool isControlButtonEnabled;
const DeviceManagementLoaded({
required this.devices,
@ -26,6 +27,7 @@ class DeviceManagementLoaded extends DeviceManagementState {
required this.offlineCount,
required this.lowBatteryCount,
this.selectedDevice,
required this.isControlButtonEnabled,
});
@override
@ -35,7 +37,8 @@ class DeviceManagementLoaded extends DeviceManagementState {
onlineCount,
offlineCount,
lowBatteryCount,
selectedDevice
selectedDevice,
isControlButtonEnabled
];
}
@ -45,7 +48,8 @@ class DeviceManagementFiltered extends DeviceManagementState {
final int onlineCount;
final int offlineCount;
final int lowBatteryCount;
final AllDevicesModel? selectedDevice;
final List<AllDevicesModel>? selectedDevice;
final bool isControlButtonEnabled;
const DeviceManagementFiltered({
required this.filteredDevices,
@ -54,6 +58,7 @@ class DeviceManagementFiltered extends DeviceManagementState {
required this.offlineCount,
required this.lowBatteryCount,
this.selectedDevice,
required this.isControlButtonEnabled,
});
@override
@ -63,7 +68,8 @@ class DeviceManagementFiltered extends DeviceManagementState {
onlineCount,
offlineCount,
lowBatteryCount,
selectedDevice
selectedDevice,
isControlButtonEnabled
];
}

View File

@ -1,34 +1,199 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/device_managment/ac/view/ac_device_batch_control.dart';
import 'package:syncrow_web/pages/device_managment/ac/view/ac_device_control.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/view/ceiling_sensor_batch_control.dart';
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/view/ceiling_sensor_controls.dart';
import 'package:syncrow_web/pages/device_managment/door_lock/view/door_lock_status_view.dart';
import 'package:syncrow_web/pages/device_managment/curtain/view/curtain_batch_status_view.dart';
import 'package:syncrow_web/pages/device_managment/curtain/view/curtain_status_view.dart';
import 'package:syncrow_web/pages/device_managment/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/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';
import 'package:syncrow_web/pages/device_managment/gateway/view/gateway_view.dart';
import 'package:syncrow_web/pages/device_managment/living_room_switch/view/living_room_device_control.dart';
import 'package:syncrow_web/pages/device_managment/main_door_sensor/view/main_door_control_view.dart';
import 'package:syncrow_web/pages/device_managment/main_door_sensor/view/main_door_sensor_batch_view.dart';
import 'package:syncrow_web/pages/device_managment/one_g_glass_switch/view/one_gang_glass_batch_control_view.dart';
import 'package:syncrow_web/pages/device_managment/one_gang_switch/view/wall_light_batch_control.dart';
import 'package:syncrow_web/pages/device_managment/one_gang_switch/view/wall_light_device_control.dart';
import 'package:syncrow_web/pages/device_managment/power_clamp/view/power_clamp_batch_control_view.dart';
import 'package:syncrow_web/pages/device_managment/power_clamp/view/smart_power_device_control.dart';
import 'package:syncrow_web/pages/device_managment/sos/view/sos_batch_control_view.dart';
import 'package:syncrow_web/pages/device_managment/sos/view/sos_device_control_view.dart';
import 'package:syncrow_web/pages/device_managment/three_g_glass_switch/view/three_gang_glass_switch_batch_control_view.dart';
import 'package:syncrow_web/pages/device_managment/three_g_glass_switch/view/three_gang_glass_switch_control_view.dart';
import 'package:syncrow_web/pages/device_managment/three_gang_switch/view/living_room_batch_controls.dart';
import 'package:syncrow_web/pages/device_managment/three_gang_switch/view/living_room_device_control.dart';
import 'package:syncrow_web/pages/device_managment/two_g_glass_switch/view/two_gang_glass_switch_batch_control_view.dart';
import 'package:syncrow_web/pages/device_managment/two_g_glass_switch/view/two_gang_glass_switch_control_view.dart';
import 'package:syncrow_web/pages/device_managment/two_gang_switch/view/wall_light_batch_control.dart';
import 'package:syncrow_web/pages/device_managment/two_gang_switch/view/wall_light_device_control.dart';
import 'package:syncrow_web/pages/device_managment/wall_sensor/view/wall_sensor_batch_control.dart';
import 'package:syncrow_web/pages/device_managment/wall_sensor/view/wall_sensor_conrtols.dart';
import 'package:syncrow_web/pages/device_managment/water_heater/view/water_heater_batch_control.dart';
import 'package:syncrow_web/pages/device_managment/water_heater/view/water_heater_device_control.dart';
import 'package:syncrow_web/pages/device_managment/water_leak/view/water_leak_batch_control_view.dart';
import 'package:syncrow_web/pages/device_managment/water_leak/view/water_leak_control_view.dart';
import '../../one_g_glass_switch/view/one_gang_glass_switch_control_view.dart';
mixin RouteControlsBasedCode {
Widget routeControlsWidgets({required AllDevicesModel device}) {
switch (device.productType) {
case '1G':
return WallLightDeviceControl(
deviceId: device.uuid!,
);
case '2G':
return TwoGangDeviceControlView(
deviceId: device.uuid!,
);
case '3G':
return LivingRoomDeviceControl(
return LivingRoomDeviceControlsView(
deviceId: device.uuid!,
);
case '1GT':
return OneGangGlassSwitchControlView(
deviceId: device.uuid!,
);
case '2GT':
return TwoGangGlassSwitchControlView(
deviceId: device.uuid!,
);
case '3GT':
return ThreeGangGlassSwitchControlView(
deviceId: device.uuid!,
);
case 'GW':
return GateWayControls(
return GateWayControlsView(
gatewayId: device.uuid!,
);
case 'DL':
return DoorLockView(device: device);
return DoorLockControlsView(device: device);
case 'WPS':
return WallSensorControls(device: device);
return WallSensorControlsView(device: device);
case 'CPS':
return CeilingSensorControls(
return CeilingSensorControlsView(
device: device,
);
case 'CUR':
return CurtainStatusControlsView(
deviceId: device.uuid!,
);
case 'AC':
return AcDeviceControl(device: device);
return AcDeviceControlsView(device: device);
case 'WH':
return WaterHeaterDeviceControlView(
device: device,
);
case 'DS':
return MainDoorSensorControlView(device: device);
case 'GD':
return GarageDoorControlView(
deviceId: device.uuid!,
);
case 'WL':
return WaterLeakView(
deviceId: device.uuid!,
);
case 'PC':
return SmartPowerDeviceControl(
deviceId: device.uuid!,
);
case 'SOS':
return SosDeviceControlsView(device: device);
default:
return const SizedBox();
}
}
/*
3G:
1G:
2G:
GW:
DL:
WPS:
CPS:
AC:
CUR:
WH:
DS:
*/
Widget routeBatchControlsWidgets({required List<AllDevicesModel> devices}) {
switch (devices.first.productType) {
case '1G':
return WallLightBatchControlView(
deviceIds: devices.where((e) => (e.productType == '1G')).map((e) => e.uuid!).toList(),
);
case '2G':
return TwoGangBatchControlView(
deviceIds: devices.where((e) => (e.productType == '2G')).map((e) => e.uuid!).toList(),
);
case '3G':
return LivingRoomBatchControlsView(
deviceIds: devices.where((e) => (e.productType == '3G')).map((e) => e.uuid!).toList(),
);
case '1GT':
return OneGangGlassSwitchBatchControlView(
deviceIds: devices.where((e) => (e.productType == '1GT')).map((e) => e.uuid!).toList(),
);
case '2GT':
return TwoGangGlassSwitchBatchControlView(
deviceIds: devices.where((e) => (e.productType == '2GT')).map((e) => e.uuid!).toList(),
);
case '3GT':
return ThreeGangGlassSwitchBatchControlView(
deviceIds: devices.where((e) => (e.productType == '3GT')).map((e) => e.uuid!).toList(),
);
case 'GW':
return GatewayBatchControlView(
gatewayIds: devices.where((e) => (e.productType == 'GW')).map((e) => e.uuid!).toList(),
);
case 'DL':
return DoorLockBatchControlView(
devicesIds: devices.where((e) => (e.productType == 'DL')).map((e) => e.uuid!).toList());
case 'WPS':
return WallSensorBatchControlView(
devicesIds: devices.where((e) => (e.productType == 'WPS')).map((e) => e.uuid!).toList());
case 'CPS':
return CeilingSensorBatchControlView(
devicesIds: devices.where((e) => (e.productType == 'CPS')).map((e) => e.uuid!).toList(),
);
case 'CUR':
return CurtainBatchStatusView(
devicesIds: devices.where((e) => (e.productType == 'CUR')).map((e) => e.uuid!).toList(),
);
case 'AC':
return AcDeviceBatchControlView(
devicesIds: devices.where((e) => (e.productType == 'AC')).map((e) => e.uuid!).toList());
case 'WH':
return WaterHEaterBatchControlView(
deviceIds: devices.where((e) => (e.productType == 'WH')).map((e) => e.uuid!).toList(),
);
case 'DS':
return MainDoorSensorBatchView(
devicesIds: devices.where((e) => (e.productType == 'DS')).map((e) => e.uuid!).toList(),
);
case 'GD':
return GarageDoorBatchControlView(
deviceIds: devices.where((e) => (e.productType == 'GD')).map((e) => e.uuid!).toList(),
);
case 'WL':
return WaterLeakBatchControlView(
deviceIds: devices.where((e) => (e.productType == 'WL')).map((e) => e.uuid!).toList(),
);
case 'PC':
return PowerClampBatchControlView(
deviceIds: devices.where((e) => (e.productType == 'PC')).map((e) => e.uuid!).toList(),
);
case 'SOS':
return SOSBatchControlView(
deviceIds: devices.where((e) => (e.productType == 'SOS')).map((e) => e.uuid!).toList(),
);
default:
return const SizedBox();
}

View File

@ -0,0 +1,18 @@
class DeviceCommunityModel {
String? uuid;
String? name;
DeviceCommunityModel({this.uuid, this.name});
DeviceCommunityModel.fromJson(Map<String, dynamic> json) {
uuid = json['uuid']?.toString();
name = json['name']?.toString();
}
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
data['uuid'] = uuid;
data['name'] = name;
return data;
}
}

View File

@ -13,11 +13,15 @@ class DeviceReport {
DeviceReport.fromJson(Map<String, dynamic> json)
: deviceUuid = json['deviceUuid'] as String?,
startTime = json['startTime'] as int?,
endTime = json['endTime'] as int?,
data = (json['data'] as List<dynamic>?)
?.map((e) => DeviceEvent.fromJson(e as Map<String, dynamic>))
.toList();
startTime = int.tryParse(json['startTime'].toString()) ??
json['startTime'] as int?,
endTime =
int.tryParse(json['endTime'].toString()) ?? json['endTime'] as int?,
data = json['data'] != null
? (json['data'] as List<dynamic>?)
?.map((e) => DeviceEvent.fromJson(e as Map<String, dynamic>))
.toList()
: [];
Map<String, dynamic> toJson() => {
'deviceUuid': deviceUuid,

View File

@ -0,0 +1,18 @@
class DeviceSpaceModel {
String? uuid;
String? spaceName;
DeviceSpaceModel({this.uuid, this.spaceName});
DeviceSpaceModel.fromJson(Map<String, dynamic> json) {
uuid = json['uuid']?.toString();
spaceName = json['spaceName']?.toString();
}
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
data['uuid'] = uuid;
data['spaceName'] = spaceName;
return data;
}
}

View File

@ -1,5 +1,8 @@
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_community.model.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_space_model.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/room.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/unit.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
class AllDevicesModel {
/*
@ -39,6 +42,7 @@ class AllDevicesModel {
DevicesModelRoom? room;
DevicesModelUnit? unit;
DeviceCommunityModel? community;
String? productUuid;
String? productType;
String? permissionType;
@ -62,10 +66,13 @@ class AllDevicesModel {
int? updateTime;
String? uuid;
int? batteryLevel;
String? productName;
List<DeviceSpaceModel>? spaces;
AllDevicesModel({
this.room,
this.unit,
this.community,
this.productUuid,
this.productType,
this.permissionType,
@ -89,6 +96,8 @@ class AllDevicesModel {
this.updateTime,
this.uuid,
this.batteryLevel,
this.productName,
this.spaces,
});
AllDevicesModel.fromJson(Map<String, dynamic> json) {
room = (json['room'] != null && (json['room'] is Map))
@ -97,6 +106,9 @@ class AllDevicesModel {
unit = (json['unit'] != null && (json['unit'] is Map))
? DevicesModelUnit.fromJson(json['unit'])
: null;
community = (json['community'] != null && (json['community'] is Map))
? DeviceCommunityModel.fromJson(json['community'])
: null;
productUuid = json['productUuid']?.toString();
productType = json['productType']?.toString();
permissionType = json['permissionType']?.toString();
@ -105,7 +117,7 @@ class AllDevicesModel {
categoryName = json['categoryName']?.toString();
createTime = int.tryParse(json['createTime']?.toString() ?? '');
gatewayId = json['gatewayId']?.toString();
icon = json['icon']?.toString();
icon = json['icon'] ?? _getDefaultIcon(productType);
ip = json['ip']?.toString();
lat = json['lat']?.toString();
localKey = json['localKey']?.toString();
@ -119,8 +131,43 @@ class AllDevicesModel {
timeZone = json['timeZone']?.toString();
updateTime = int.tryParse(json['updateTime']?.toString() ?? '');
uuid = json['uuid']?.toString();
batteryLevel = int.tryParse(json['batteryLevel']?.toString() ?? '');
batteryLevel = int.tryParse(json['battery']?.toString() ?? '');
productName = json['productName']?.toString();
if (json['spaces'] != null && json['spaces'] is List) {
spaces = (json['spaces'] as List)
.map((space) => DeviceSpaceModel.fromJson(space))
.toList();
}
}
String _getDefaultIcon(String? productType) {
switch (productType) {
case 'LightBulb':
return Assets.lightBulb;
case 'CeilingSensor':
case 'WallSensor':
return Assets.sensors;
case 'AC':
return Assets.ac;
case 'DoorLock':
return Assets.doorLock;
case 'Curtain':
return Assets.curtain;
case '3G':
case '2G':
case '1G':
return Assets.gangSwitch;
case 'Gateway':
return Assets.gateway;
case 'WH':
return Assets.blackLogo;
case 'DS':
return Assets.sensors;
default:
return Assets.logo;
}
}
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
if (room != null) {
@ -129,6 +176,9 @@ class AllDevicesModel {
if (unit != null) {
data['unit'] = unit!.toJson();
}
if (community != null) {
data['community'] = community!.toJson();
}
data['productUuid'] = productUuid;
data['productType'] = productType;
data['permissionType'] = permissionType;
@ -151,7 +201,74 @@ class AllDevicesModel {
data['timeZone'] = timeZone;
data['updateTime'] = updateTime;
data['uuid'] = uuid;
data['batteryLevel'] = batteryLevel;
data['battery'] = batteryLevel;
data['productName'] = productName;
if (spaces != null) {
data['spaces'] = spaces!.map((space) => space.toJson()).toList();
}
return data;
}
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is AllDevicesModel &&
other.room == room &&
other.unit == unit &&
other.productUuid == productUuid &&
other.productType == productType &&
other.permissionType == permissionType &&
other.activeTime == activeTime &&
other.category == category &&
other.categoryName == categoryName &&
other.createTime == createTime &&
other.gatewayId == gatewayId &&
other.icon == icon &&
other.ip == ip &&
other.lat == lat &&
other.localKey == localKey &&
other.lon == lon &&
other.model == model &&
other.name == name &&
other.nodeId == nodeId &&
other.online == online &&
other.ownerId == ownerId &&
other.sub == sub &&
other.timeZone == timeZone &&
other.updateTime == updateTime &&
other.uuid == uuid &&
other.productName == productName &&
other.batteryLevel == batteryLevel;
}
@override
int get hashCode {
return room.hashCode ^
unit.hashCode ^
productUuid.hashCode ^
productType.hashCode ^
permissionType.hashCode ^
activeTime.hashCode ^
category.hashCode ^
categoryName.hashCode ^
createTime.hashCode ^
gatewayId.hashCode ^
icon.hashCode ^
ip.hashCode ^
lat.hashCode ^
localKey.hashCode ^
lon.hashCode ^
model.hashCode ^
name.hashCode ^
nodeId.hashCode ^
online.hashCode ^
ownerId.hashCode ^
sub.hashCode ^
timeZone.hashCode ^
updateTime.hashCode ^
uuid.hashCode ^
productName.hashCode ^
batteryLevel.hashCode;
}
}

View File

@ -0,0 +1,55 @@
import 'package:flutter/foundation.dart';
class FactoryResetModel {
final List<String> devicesUuid;
FactoryResetModel({
required this.devicesUuid,
});
factory FactoryResetModel.fromJson(Map<String, dynamic> json) {
return FactoryResetModel(
devicesUuid: List<String>.from(json['devicesUuid']),
);
}
Map<String, dynamic> toJson() {
return {
'devicesUuid': devicesUuid,
};
}
FactoryResetModel copyWith({
List<String>? devicesUuid,
}) {
return FactoryResetModel(
devicesUuid: devicesUuid ?? this.devicesUuid,
);
}
Map<String, dynamic> toMap() {
return {
'devicesUuid': devicesUuid,
};
}
factory FactoryResetModel.fromMap(Map<String, dynamic> map) {
return FactoryResetModel(
devicesUuid: List<String>.from(map['devicesUuid']),
);
}
@override
String toString() => 'FactoryReset(devicesUuid: $devicesUuid)';
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is FactoryResetModel &&
listEquals(other.devicesUuid, devicesUuid);
}
@override
int get hashCode => devicesUuid.hashCode;
}

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/bloc/device_managment_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/widgets/device_managment_body.dart';
import 'package:syncrow_web/pages/device_managment/shared/navigate_home_grid_view.dart';
import 'package:syncrow_web/web_layout/web_scaffold.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
@ -13,24 +14,25 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout {
return BlocProvider(
create: (context) => DeviceManagementBloc()..add(FetchDevices()),
child: WebScaffold(
appBarTitle: Text(
'Device Management',
style: Theme.of(context).textTheme.headlineLarge,
appBarTitle: FittedBox(
child: Text(
'Device Management',
style: Theme.of(context).textTheme.headlineLarge,
),
),
enableMenuSideba: isLargeScreenSize(context),
rightBody: const NavigateHomeGridView(),
scaffoldBody: BlocBuilder<DeviceManagementBloc, DeviceManagementState>(
builder: (context, state) {
if (state is DeviceManagementLoading) {
return const Center(child: CircularProgressIndicator());
} else if (state is DeviceManagementLoaded ||
state is DeviceManagementFiltered) {
} else if (state is DeviceManagementLoaded || state is DeviceManagementFiltered) {
final devices = state is DeviceManagementLoaded
? state.devices
: (state as DeviceManagementFiltered).filteredDevices;
return DeviceManagementBody(devices: devices);
} else {
return const Center(child: Text('No Devices Found'));
return const Center(child: Text('Error fetching Devices'));
}
},
),
@ -38,3 +40,6 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout {
);
}
}

View File

@ -1,13 +1,13 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/core/extension/build_context_x.dart';
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
import 'package:syncrow_web/pages/common/custom_table.dart';
import 'package:syncrow_web/pages/common/filter/filter_widget.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/bloc/device_managment_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
import 'package:syncrow_web/pages/device_managment/shared/device_control_dialog.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/widgets/device_search_filters.dart';
import 'package:syncrow_web/pages/device_managment/shared/device_batch_control_dialog.dart';
import 'package:syncrow_web/pages/device_managment/shared/device_control_dialog.dart';
import 'package:syncrow_web/utils/format_date_time.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
import 'package:syncrow_web/utils/style.dart';
@ -27,6 +27,7 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
int offlineCount = 0;
int lowBatteryCount = 0;
bool isControlButtonEnabled = false;
List<AllDevicesModel> selectedDevices = [];
if (state is DeviceManagementLoaded) {
devicesToShow = state.devices;
@ -34,87 +35,108 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
onlineCount = state.onlineCount;
offlineCount = state.offlineCount;
lowBatteryCount = state.lowBatteryCount;
isControlButtonEnabled = state.selectedDevice != null;
isControlButtonEnabled = state.isControlButtonEnabled;
selectedDevices = state.selectedDevice ?? [];
} else if (state is DeviceManagementFiltered) {
devicesToShow = state.filteredDevices;
selectedIndex = state.selectedIndex;
onlineCount = state.onlineCount;
offlineCount = state.offlineCount;
lowBatteryCount = state.lowBatteryCount;
isControlButtonEnabled = state.selectedDevice != null;
isControlButtonEnabled = state.isControlButtonEnabled;
selectedDevices = state.selectedDevice ?? [];
} else if (state is DeviceManagementInitial) {
devicesToShow = [];
selectedIndex = 0;
isControlButtonEnabled = false;
}
final tabs = [
'All (${devices.length})',
'All',
'Online ($onlineCount)',
'Offline ($offlineCount)',
'Low Battery ($lowBatteryCount)',
];
return CustomScrollView(
slivers: [
SliverToBoxAdapter(
child: Container(
padding: isLargeScreenSize(context)
? const EdgeInsets.all(30)
: const EdgeInsets.all(15),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
FilterWidget(
size: MediaQuery.of(context).size,
tabs: tabs,
selectedIndex: selectedIndex,
onTabChanged: (index) {
context
.read<DeviceManagementBloc>()
.add(SelectedFilterChanged(index));
},
),
const SizedBox(height: 20),
const DeviceSearchFilters(),
const SizedBox(height: 12),
Container(
height: 43,
width: isSmallScreenSize(context) ? double.infinity : 100,
decoration: containerDecoration,
child: Center(
child: DefaultButton(
onPressed: isControlButtonEnabled
? () {
final selectedDevice = context
.read<DeviceManagementBloc>()
.selectedDevices
.first;
final buttonLabel =
(selectedDevices.length > 1) ? 'Batch Control' : 'Control';
return Column(
children: [
Container(
padding: isLargeScreenSize(context)
? const EdgeInsets.all(30)
: const EdgeInsets.all(15),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
FilterWidget(
size: MediaQuery.of(context).size,
tabs: tabs,
selectedIndex: selectedIndex,
onTabChanged: (index) {
context
.read<DeviceManagementBloc>()
.add(SelectedFilterChanged(index));
},
),
const SizedBox(height: 20),
const DeviceSearchFilters(),
const SizedBox(height: 12),
Container(
height: 45,
width: 125,
decoration: containerDecoration,
child: Center(
child: DefaultButton(
onPressed: isControlButtonEnabled
? () {
if (selectedDevices.length == 1) {
showDialog(
context: context,
builder: (context) => DeviceControlDialog(
device: selectedDevice),
device: selectedDevices.first,
),
);
} else if (selectedDevices.length > 1) {
final productTypes = selectedDevices
.map((device) => device.productType)
.toSet();
if (productTypes.length == 1) {
showDialog(
context: context,
builder: (context) =>
DeviceBatchControlDialog(
devices: selectedDevices,
),
);
}
}
: null,
borderRadius: 9,
child: Text(
'Control',
style: TextStyle(
color: isControlButtonEnabled
? Colors.white
: Colors.grey,
),
}
: null,
borderRadius: 9,
child: Text(
buttonLabel,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 12,
color: isControlButtonEnabled
? Colors.white
: Colors.grey,
),
),
),
),
],
),
),
],
),
),
SliverFillRemaining(
Expanded(
child: Padding(
padding: isLargeScreenSize(context)
? const EdgeInsets.all(30)
: const EdgeInsets.all(15),
child: DynamicTable(
withSelectAll: true,
cellDecoration: containerDecoration,
onRowSelected: (index, isSelected, row) {
final selectedDevice = devicesToShow[index];
@ -123,28 +145,42 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
.add(SelectDevice(selectedDevice));
},
withCheckBox: true,
size: context.screenSize,
size: MediaQuery.of(context).size,
uuidIndex: 2,
headers: const [
'Device Name',
'Product Name',
'Device ID',
'Unit Name',
'Room',
'Space Name',
'location',
'Battery Level',
'Installation Date and Time',
'Status',
'Last Offline Date and Time',
],
data: devicesToShow.map((device) {
final combinedSpaceNames = device.spaces != null
? device.spaces!
.map((space) => space.spaceName)
.join(' > ') +
(device.community != null
? ' > ${device.community!.name}'
: '')
: (device.community != null
? device.community!.name
: '');
return [
device.categoryName ?? '',
device.name ?? '',
device.productName ?? '',
device.uuid ?? '',
device.unit?.name ?? '',
device.room?.name ?? '',
(device.spaces != null && device.spaces!.isNotEmpty)
? device.spaces![0].spaceName
: '',
combinedSpaceNames,
device.batteryLevel != null
? '${device.batteryLevel}%'
: '',
: '-',
formatDateTime(DateTime.fromMillisecondsSinceEpoch(
(device.createTime ?? 0) * 1000)),
device.online == true ? 'Online' : 'Offline',
@ -152,10 +188,20 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
(device.updateTime ?? 0) * 1000)),
];
}).toList(),
onSelectionChanged: (selectedRows) {
context
.read<DeviceManagementBloc>()
.add(UpdateSelection(selectedRows));
},
initialSelectedIds: context
.read<DeviceManagementBloc>()
.selectedDevices
.map((device) => device.uuid!)
.toList(),
isEmpty: devicesToShow.isEmpty,
),
),
),
)
],
);
},

View File

@ -4,6 +4,7 @@ import 'package:syncrow_web/pages/common/text_field/custom_text_field.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/bloc/device_managment_bloc.dart';
import 'package:syncrow_web/pages/common/buttons/search_reset_buttons.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
import 'package:syncrow_web/utils/style.dart';
class DeviceSearchFilters extends StatefulWidget {
const DeviceSearchFilters({super.key});
@ -28,12 +29,12 @@ class _DeviceSearchFiltersState extends State<DeviceSearchFilters>
@override
Widget build(BuildContext context) {
return isLargeScreenSize(context)
return isExtraLargeScreenSize(context)
? Row(
children: [
_buildSearchField("Community", communityController, 200),
const SizedBox(width: 20),
_buildSearchField("Unit Name", unitNameController, 200),
_buildSearchField("Space Name", unitNameController, 200),
const SizedBox(width: 20),
_buildSearchField(
"Device Name / Product Name", productNameController, 300),
@ -45,10 +46,17 @@ class _DeviceSearchFiltersState extends State<DeviceSearchFilters>
spacing: 20,
runSpacing: 10,
children: [
_buildSearchField("Community", communityController, 200),
_buildSearchField("Unit Name", unitNameController, 200),
_buildSearchField(
"Device Name / Product Name", productNameController, 300),
"Community",
communityController,
200,
),
_buildSearchField("Space Name", unitNameController, 200),
_buildSearchField(
"Device Name / Product Name",
productNameController,
300,
),
_buildSearchResetButtons(),
],
);
@ -56,11 +64,20 @@ class _DeviceSearchFiltersState extends State<DeviceSearchFilters>
Widget _buildSearchField(
String title, TextEditingController controller, double width) {
return StatefulTextField(
title: title,
width: width,
elevation: 2,
controller: controller,
return Container(
child: StatefulTextField(
title: title,
width: width,
elevation: 2,
controller: controller,
onSubmitted: () {
context.read<DeviceManagementBloc>().add(SearchDevices(
productName: productNameController.text,
unitName: unitNameController.text,
community: communityController.text,
searchField: true));
},
),
);
}
@ -68,16 +85,18 @@ class _DeviceSearchFiltersState extends State<DeviceSearchFilters>
return SearchResetButtons(
onSearch: () {
context.read<DeviceManagementBloc>().add(SearchDevices(
community: communityController.text,
unitName: unitNameController.text,
productName: productNameController.text,
));
community: communityController.text,
unitName: unitNameController.text,
productName: productNameController.text,
searchField: true));
},
onReset: () {
communityController.clear();
unitNameController.clear();
productNameController.clear();
context.read<DeviceManagementBloc>().add(FetchDevices());
context.read<DeviceManagementBloc>()
..add(ResetFilters())
..add(FetchDevices());
},
);
}

View File

@ -1,123 +0,0 @@
import 'dart:async';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/event.dart';
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/state.dart';
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/model/ceiling_sensor_model.dart';
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/model/help_description.dart';
import 'package:syncrow_web/services/devices_mang_api.dart';
class CeilingSensorBloc extends Bloc<CeilingSensorEvent, CeilingSensorState> {
final String deviceId;
late CeilingSensorModel deviceStatus;
Timer? _timer;
CeilingSensorBloc({required this.deviceId}) : super(CeilingInitialState()) {
on<CeilingInitialEvent>(_fetchCeilingSensorStatus);
on<CeilingChangeValueEvent>(_changeValue);
on<GetCeilingDeviceReportsEvent>(_getDeviceReports);
on<ShowCeilingDescriptionEvent>(_showDescription);
on<BackToCeilingGridViewEvent>(_backToGridView);
}
void _fetchCeilingSensorStatus(
CeilingInitialEvent event, Emitter<CeilingSensorState> emit) async {
emit(CeilingLoadingInitialState());
try {
var response = await DevicesManagementApi().getDeviceStatus(deviceId);
deviceStatus = CeilingSensorModel.fromJson(response.status);
emit(CeilingUpdateState(ceilingSensorModel: deviceStatus));
// _listenToChanges();
} catch (e) {
emit(CeilingFailedState(error: e.toString()));
return;
}
}
// _listenToChanges() {
// try {
// DatabaseReference ref = FirebaseDatabase.instance.ref('device-status/$deviceId');
// Stream<DatabaseEvent> stream = ref.onValue;
// stream.listen((DatabaseEvent event) {
// Map<dynamic, dynamic> usersMap = event.snapshot.value as Map<dynamic, dynamic>;
// List<StatusModel> statusList = [];
// usersMap['status'].forEach((element) {
// statusList.add(StatusModel(code: element['code'], value: element['value']));
// });
// deviceStatus = WallSensorModel.fromJson(statusList);
// add(WallSensorUpdatedEvent());
// });
// } catch (_) {}
// }
void _changeValue(
CeilingChangeValueEvent event, Emitter<CeilingSensorState> emit) async {
emit(CeilingLoadingNewSate(ceilingSensorModel: deviceStatus));
if (event.code == 'sensitivity') {
deviceStatus.sensitivity = event.value;
} else if (event.code == 'none_body_time') {
deviceStatus.noBodyTime = event.value;
} else if (event.code == 'moving_max_dis') {
deviceStatus.maxDistance = event.value;
}
emit(CeilingUpdateState(ceilingSensorModel: deviceStatus));
await _runDeBouncer(
deviceId: deviceId, code: event.code, value: event.value);
}
_runDeBouncer({
required String deviceId,
required String code,
required dynamic value,
}) {
if (_timer != null) {
_timer!.cancel();
}
_timer = Timer(const Duration(seconds: 1), () async {
try {
final response = await DevicesManagementApi()
.deviceControl(deviceId, Status(code: code, value: value));
if (!response) {
add(CeilingInitialEvent());
}
} catch (_) {
await Future.delayed(const Duration(milliseconds: 500));
add(CeilingInitialEvent());
}
});
}
FutureOr<void> _getDeviceReports(GetCeilingDeviceReportsEvent event,
Emitter<CeilingSensorState> emit) async {
if (event.code.isEmpty) {
emit(ShowCeilingDescriptionState(description: reportString));
return;
} else {
emit(CeilingReportsLoadingState());
try {
await DevicesManagementApi.getDeviceReports(deviceId, event.code)
.then((value) {
emit(CeilingReportsState(deviceReport: value));
});
} catch (e) {
emit(CeilingReportsFailedState(error: e.toString()));
return;
}
}
}
void _showDescription(
ShowCeilingDescriptionEvent event, Emitter<CeilingSensorState> emit) {
emit(ShowCeilingDescriptionState(description: event.description));
}
void _backToGridView(
BackToCeilingGridViewEvent event, Emitter<CeilingSensorState> emit) {
emit(CeilingUpdateState(ceilingSensorModel: deviceStatus));
}
}

View File

@ -0,0 +1,206 @@
import 'dart:async';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/ceiling_event.dart';
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/ceiling_state.dart';
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/model/ceiling_sensor_model.dart';
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/model/help_description.dart';
import 'package:syncrow_web/services/devices_mang_api.dart';
class CeilingSensorBloc extends Bloc<CeilingSensorEvent, CeilingSensorState> {
final String deviceId;
late CeilingSensorModel deviceStatus;
Timer? _timer;
CeilingSensorBloc({required this.deviceId}) : super(CeilingInitialState()) {
on<CeilingInitialEvent>(_fetchCeilingSensorStatus);
on<CeilingFetchDeviceStatusEvent>(_fetchCeilingSensorBatchControl);
on<CeilingChangeValueEvent>(_changeValue);
on<CeilingBatchControlEvent>(_onBatchControl);
on<GetCeilingDeviceReportsEvent>(_getDeviceReports);
on<ShowCeilingDescriptionEvent>(_showDescription);
on<BackToCeilingGridViewEvent>(_backToGridView);
on<CeilingFactoryResetEvent>(_onFactoryReset);
}
void _fetchCeilingSensorStatus(
CeilingInitialEvent event, Emitter<CeilingSensorState> emit) async {
emit(CeilingLoadingInitialState());
try {
var response = await DevicesManagementApi().getDeviceStatus(event.deviceId);
deviceStatus = CeilingSensorModel.fromJson(response.status);
emit(CeilingUpdateState(ceilingSensorModel: deviceStatus));
// _listenToChanges();
} catch (e) {
emit(CeilingFailedState(error: e.toString()));
return;
}
}
// _listenToChanges() {
// try {
// DatabaseReference ref = FirebaseDatabase.instance.ref('device-status/$deviceId');
// Stream<DatabaseEvent> stream = ref.onValue;
// stream.listen((DatabaseEvent event) {
// Map<dynamic, dynamic> usersMap = event.snapshot.value as Map<dynamic, dynamic>;
// List<StatusModel> statusList = [];
// usersMap['status'].forEach((element) {
// statusList.add(StatusModel(code: element['code'], value: element['value']));
// });
// deviceStatus = WallSensorModel.fromJson(statusList);
// add(WallSensorUpdatedEvent());
// });
// } catch (_) {}
// }
void _changeValue(CeilingChangeValueEvent event, Emitter<CeilingSensorState> emit) async {
emit(CeilingLoadingNewSate(ceilingSensorModel: deviceStatus));
if (event.code == 'sensitivity') {
deviceStatus.sensitivity = event.value;
} else if (event.code == 'none_body_time') {
deviceStatus.noBodyTime = event.value;
} else if (event.code == 'moving_max_dis') {
deviceStatus.maxDistance = event.value;
} else if (event.code == 'scene') {
deviceStatus.spaceType = getSpaceType(event.value);
}
emit(CeilingUpdateState(ceilingSensorModel: deviceStatus));
await _runDeBouncer(
deviceId: deviceId,
code: event.code,
value: event.value,
emit: emit,
isBatch: false,
);
}
Future<void> _onBatchControl(
CeilingBatchControlEvent event, Emitter<CeilingSensorState> emit) async {
emit(CeilingLoadingNewSate(ceilingSensorModel: deviceStatus));
if (event.code == 'sensitivity') {
deviceStatus.sensitivity = event.value;
} else if (event.code == 'none_body_time') {
deviceStatus.noBodyTime = event.value;
} else if (event.code == 'moving_max_dis') {
deviceStatus.maxDistance = event.value;
} else if (event.code == 'scene') {
deviceStatus.spaceType = getSpaceType(event.value);
}
emit(CeilingUpdateState(ceilingSensorModel: deviceStatus));
await _runDeBouncer(
deviceId: event.deviceIds,
code: event.code,
value: event.value,
emit: emit,
isBatch: true,
);
}
_runDeBouncer({
required dynamic deviceId,
required String code,
required dynamic value,
required Emitter<CeilingSensorState> emit,
required bool isBatch,
}) {
late String id;
if (deviceId is List) {
id = deviceId.first;
} else {
id = deviceId;
}
if (_timer != null) {
_timer!.cancel();
}
_timer = Timer(const Duration(seconds: 1), () async {
try {
late bool response;
if (isBatch) {
response = await DevicesManagementApi().deviceBatchControl(deviceId, code, value);
} else {
response = await DevicesManagementApi()
.deviceControl(deviceId, Status(code: code, value: value));
}
if (!response) {
add(CeilingInitialEvent(id));
}
if (response == true && code == 'scene') {
emit(CeilingLoadingInitialState());
await Future.delayed(const Duration(seconds: 1));
add(CeilingInitialEvent(id));
}
} catch (_) {
await Future.delayed(const Duration(milliseconds: 500));
add(CeilingInitialEvent(id));
}
});
}
FutureOr<void> _getDeviceReports(
GetCeilingDeviceReportsEvent event, Emitter<CeilingSensorState> emit) async {
if (event.code.isEmpty) {
emit(ShowCeilingDescriptionState(description: reportString));
return;
} else {
emit(CeilingReportsLoadingState());
// final from = DateTime.now().subtract(const Duration(days: 30)).millisecondsSinceEpoch;
// final to = DateTime.now().millisecondsSinceEpoch;
try {
// await DevicesManagementApi.getDeviceReportsByDate(deviceId, event.code, from.toString(), to.toString())
await DevicesManagementApi.getDeviceReports(deviceId, event.code).then((value) {
emit(CeilingReportsState(deviceReport: value));
});
} catch (e) {
emit(CeilingReportsFailedState(error: e.toString()));
return;
}
}
}
void _showDescription(ShowCeilingDescriptionEvent event, Emitter<CeilingSensorState> emit) {
emit(ShowCeilingDescriptionState(description: event.description));
}
void _backToGridView(BackToCeilingGridViewEvent event, Emitter<CeilingSensorState> emit) {
emit(CeilingUpdateState(ceilingSensorModel: deviceStatus));
}
FutureOr<void> _fetchCeilingSensorBatchControl(
CeilingFetchDeviceStatusEvent event, Emitter<CeilingSensorState> emit) async {
emit(CeilingLoadingInitialState());
try {
var response = await DevicesManagementApi().getBatchStatus(event.devicesIds);
deviceStatus = CeilingSensorModel.fromJson(response.status);
emit(CeilingUpdateState(ceilingSensorModel: deviceStatus));
} catch (e) {
emit(CeilingFailedState(error: e.toString()));
return;
}
}
FutureOr<void> _onFactoryReset(
CeilingFactoryResetEvent event, Emitter<CeilingSensorState> emit) async {
emit(CeilingLoadingNewSate(ceilingSensorModel: deviceStatus));
try {
final response = await DevicesManagementApi().factoryReset(
event.factoryResetModel,
event.devicesId,
);
if (!response) {
emit(const CeilingFailedState(error: 'Failed'));
} else {
emit(CeilingUpdateState(ceilingSensorModel: deviceStatus));
}
} catch (e) {
emit(CeilingFailedState(error: e.toString()));
}
}
}

View File

@ -0,0 +1,85 @@
import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart';
abstract class CeilingSensorEvent extends Equatable {
const CeilingSensorEvent();
@override
List<Object> get props => [];
}
class CeilingInitialEvent extends CeilingSensorEvent {
final String deviceId;
const CeilingInitialEvent(this.deviceId);
@override
List<Object> get props => [deviceId];
}
class CeilingFetchDeviceStatusEvent extends CeilingSensorEvent {
final List<String> devicesIds;
const CeilingFetchDeviceStatusEvent(this.devicesIds);
@override
List<Object> get props => [devicesIds];
}
class CeilingBatchControlEvent extends CeilingSensorEvent {
final List<String> deviceIds;
final String code;
final dynamic value;
const CeilingBatchControlEvent({
required this.deviceIds,
required this.code,
required this.value,
});
@override
List<Object> get props => [deviceIds, code, value];
}
class CeilingChangeValueEvent extends CeilingSensorEvent {
final dynamic value;
final String code;
const CeilingChangeValueEvent({required this.value, required this.code});
@override
List<Object> get props => [value, code];
}
class GetCeilingDeviceReportsEvent extends CeilingSensorEvent {
final String code;
final String deviceUuid;
const GetCeilingDeviceReportsEvent(
{required this.code, required this.deviceUuid});
@override
List<Object> get props => [code, deviceUuid];
}
class ShowCeilingDescriptionEvent extends CeilingSensorEvent {
final String description;
const ShowCeilingDescriptionEvent({required this.description});
@override
List<Object> get props => [description];
}
class BackToCeilingGridViewEvent extends CeilingSensorEvent {}
class CeilingFactoryResetEvent extends CeilingSensorEvent {
final String devicesId;
final FactoryResetModel factoryResetModel;
const CeilingFactoryResetEvent({
required this.devicesId,
required this.factoryResetModel,
});
@override
List<Object> get props => [devicesId, factoryResetModel];
}

View File

@ -1,41 +0,0 @@
import 'package:equatable/equatable.dart';
abstract class CeilingSensorEvent extends Equatable {
const CeilingSensorEvent();
@override
List<Object> get props => [];
}
class CeilingInitialEvent extends CeilingSensorEvent {}
class CeilingChangeValueEvent extends CeilingSensorEvent {
final dynamic value;
final String code;
const CeilingChangeValueEvent({required this.value, required this.code});
@override
List<Object> get props => [value, code];
}
class GetCeilingDeviceReportsEvent extends CeilingSensorEvent {
final String code;
final String deviceUuid;
const GetCeilingDeviceReportsEvent(
{required this.code, required this.deviceUuid});
@override
List<Object> get props => [code, deviceUuid];
}
class ShowCeilingDescriptionEvent extends CeilingSensorEvent {
final String description;
const ShowCeilingDescriptionEvent({required this.description});
@override
List<Object> get props => [description];
}
class BackToCeilingGridViewEvent extends CeilingSensorEvent {}

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
class CeilingSensorModel {
@ -10,6 +11,7 @@ class CeilingSensorModel {
String bodyMovement;
String noBodyTime;
int maxDistance;
SpaceTypes spaceType;
CeilingSensorModel({
required this.presenceState,
@ -20,6 +22,7 @@ class CeilingSensorModel {
required this.bodyMovement,
required this.noBodyTime,
required this.maxDistance,
required this.spaceType,
});
factory CeilingSensorModel.fromJson(List<Status> jsonList) {
@ -31,6 +34,7 @@ class CeilingSensorModel {
String _bodyMovement = 'none';
String _noBodyTime = 'none';
int _maxDis = 0;
SpaceTypes _spaceType = SpaceTypes.none;
try {
for (var status in jsonList) {
@ -38,17 +42,23 @@ class CeilingSensorModel {
case 'presence_state':
_presenceState = status.value ?? 'none';
break;
case 'scene':
_spaceType = getSpaceType(status.value ?? 'none');
break;
case 'sensitivity':
_sensitivity = status.value is int ? status.value : int.tryParse(status.value ?? '1') ?? 1;
_sensitivity =
status.value is int ? status.value : int.tryParse(status.value ?? '1') ?? 1;
break;
case 'checking_result':
_checkingResult = status.value ?? '';
break;
case 'presence_range':
_presenceRange = status.value is int ? status.value : int.tryParse(status.value ?? '0') ?? 0;
_presenceRange =
status.value is int ? status.value : int.tryParse(status.value ?? '0') ?? 0;
break;
case 'sports_para':
_sportsPara = status.value is int ? status.value : int.tryParse(status.value ?? '0') ?? 0;
_sportsPara =
status.value is int ? status.value : int.tryParse(status.value ?? '0') ?? 0;
break;
case 'body_movement':
_bodyMovement = status.value ?? '';
@ -74,6 +84,55 @@ class CeilingSensorModel {
bodyMovement: _bodyMovement,
noBodyTime: _noBodyTime,
maxDistance: _maxDis,
spaceType: _spaceType,
);
}
CeilingSensorModel copyWith({
String? presenceState,
int? sensitivity,
String? checkingResult,
int? presenceRange,
int? sportsPara,
String? bodyMovement,
String? noBodyTime,
int? maxDistance,
SpaceTypes? spaceType,
}) {
return CeilingSensorModel(
presenceState: presenceState ?? this.presenceState,
sensitivity: sensitivity ?? this.sensitivity,
checkingResult: checkingResult ?? this.checkingResult,
presenceRange: presenceRange ?? this.presenceRange,
sportsPara: sportsPara ?? this.sportsPara,
bodyMovement: bodyMovement ?? this.bodyMovement,
noBodyTime: noBodyTime ?? this.noBodyTime,
maxDistance: maxDistance ?? this.maxDistance,
spaceType: spaceType ?? this.spaceType,
);
}
}
enum SpaceTypes {
none,
parlour,
area,
toilet,
bedroom,
}
SpaceTypes getSpaceType(String value) {
switch (value) {
case 'parlour':
return SpaceTypes.parlour;
case 'area':
return SpaceTypes.area;
case 'toilet':
return SpaceTypes.toilet;
case 'bedroom':
return SpaceTypes.bedroom;
case 'none':
default:
return SpaceTypes.none;
}
}

View File

@ -0,0 +1,127 @@
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/ceiling_sensor/bloc/ceiling_bloc.dart';
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/ceiling_event.dart';
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/ceiling_state.dart';
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/model/ceiling_sensor_model.dart';
import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart';
import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_space_type.dart';
import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_update_data.dart';
import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presense_nobody_time.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
class CeilingSensorBatchControlView extends StatelessWidget with HelperResponsiveLayout {
const CeilingSensorBatchControlView({super.key, required this.devicesIds});
final List<String> devicesIds;
@override
Widget build(BuildContext context) {
final isExtraLarge = isExtraLargeScreenSize(context);
final isLarge = isLargeScreenSize(context);
final isMedium = isMediumScreenSize(context);
return BlocProvider(
create: (context) => CeilingSensorBloc(deviceId: devicesIds.first)
..add(CeilingFetchDeviceStatusEvent(devicesIds)),
child: BlocBuilder<CeilingSensorBloc, CeilingSensorState>(
builder: (context, state) {
if (state is CeilingLoadingInitialState || state is CeilingReportsLoadingState) {
return const Center(child: CircularProgressIndicator());
} else if (state is CeilingUpdateState) {
return _buildGridView(
context, state.ceilingSensorModel, isExtraLarge, isLarge, isMedium);
}
return const Center(child: Text('Error fetching status'));
},
),
);
}
Widget _buildGridView(BuildContext context, CeilingSensorModel model, bool isExtraLarge,
bool isLarge, bool isMedium) {
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: [
PresenceSpaceType(
description: 'Space Type',
value: model.spaceType,
action: (String value) => context.read<CeilingSensorBloc>().add(
CeilingBatchControlEvent(
deviceIds: devicesIds,
code: 'scene',
value: value,
),
),
),
PresenceUpdateData(
value: model.sensitivity.toDouble(),
title: 'Sensitivity:',
minValue: 1,
maxValue: 10,
steps: 1,
action: (int value) {
context.read<CeilingSensorBloc>().add(
CeilingBatchControlEvent(
deviceIds: devicesIds,
code: 'sensitivity',
value: value,
),
);
},
),
PresenceUpdateData(
value: model.maxDistance.toDouble(),
title: 'Maximum Distance:',
minValue: 0,
maxValue: 500,
steps: 50,
description: 'm',
action: (int value) => context.read<CeilingSensorBloc>().add(
CeilingBatchControlEvent(
deviceIds: devicesIds,
code: 'moving_max_dis',
value: value,
),
),
),
PresenceNoBodyTime(
value: model.noBodyTime,
title: 'Nobody Time:',
description: '',
action: (String value) => context.read<CeilingSensorBloc>().add(
CeilingBatchControlEvent(
deviceIds: devicesIds,
code: 'nobody_time',
value: value,
),
),
),
FirmwareUpdateWidget(deviceId: devicesIds.first, version: 4),
FactoryResetWidget(
callFactoryReset: () {
context.read<CeilingSensorBloc>().add(
CeilingFactoryResetEvent(
devicesId: devicesIds.first,
factoryResetModel: FactoryResetModel(devicesUuid: devicesIds),
),
);
},
),
],
);
}
}

View File

@ -1,9 +1,9 @@
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/ceiling_sensor/bloc/bloc.dart';
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/event.dart';
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/state.dart';
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/ceiling_bloc.dart';
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/ceiling_event.dart';
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/ceiling_state.dart';
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/model/ceiling_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_space_type.dart';
@ -16,49 +16,44 @@ import 'package:syncrow_web/pages/device_managment/shared/table/report_table.dar
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
class CeilingSensorControls extends StatelessWidget
with HelperResponsiveLayout {
const CeilingSensorControls({super.key, required this.device});
class CeilingSensorControlsView extends StatelessWidget with HelperResponsiveLayout {
const CeilingSensorControlsView({super.key, required this.device});
final AllDevicesModel device;
@override
Widget build(BuildContext context) {
final isExtraLarge = isExtraLargeScreenSize(context);
final isLarge = isLargeScreenSize(context);
final isMedium = isMediumScreenSize(context);
return BlocProvider(
create: (context) => CeilingSensorBloc(deviceId: device.uuid ?? '')
..add(CeilingInitialEvent()),
..add(CeilingInitialEvent(device.uuid ?? '')),
child: BlocBuilder<CeilingSensorBloc, CeilingSensorState>(
builder: (context, state) {
if (state is CeilingLoadingInitialState ||
state is CeilingReportsLoadingState) {
if (state is CeilingLoadingInitialState || state is CeilingReportsLoadingState) {
return const Center(child: CircularProgressIndicator());
} else if (state is CeilingUpdateState) {
return _buildGridView(
context, state.ceilingSensorModel, isLarge, isMedium);
context, state.ceilingSensorModel, isExtraLarge, isLarge, isMedium);
} else if (state is CeilingReportsState) {
return ReportsTable(
report: state.deviceReport,
onRowTap: (index) {},
onClose: () {
context
.read<CeilingSensorBloc>()
.add(BackToCeilingGridViewEvent());
context.read<CeilingSensorBloc>().add(BackToCeilingGridViewEvent());
},
);
} else if (state is ShowCeilingDescriptionState) {
return DescriptionView(
description: state.description,
onClose: () {
context
.read<CeilingSensorBloc>()
.add(BackToCeilingGridViewEvent());
context.read<CeilingSensorBloc>().add(BackToCeilingGridViewEvent());
},
);
} else if (state is CeilingReportsFailedState) {
final model = context.read<CeilingSensorBloc>().deviceStatus;
return _buildGridView(context, model, isLarge, isMedium);
return _buildGridView(context, model, isExtraLarge, isLarge, isMedium);
}
return const Center(child: Text('Error fetching status'));
},
@ -66,14 +61,14 @@ class CeilingSensorControls extends StatelessWidget
);
}
Widget _buildGridView(BuildContext context, CeilingSensorModel model,
Widget _buildGridView(BuildContext context, CeilingSensorModel model, bool isExtraLarge,
bool isLarge, bool isMedium) {
return GridView(
padding: const EdgeInsets.symmetric(horizontal: 50),
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: isLarge
crossAxisCount: isLarge || isExtraLarge
? 3
: isMedium
? 2
@ -96,21 +91,21 @@ class CeilingSensorControls extends StatelessWidget
postfix: 'm',
description: 'Detection Range',
),
const PresenceSpaceType(
listOfIcons: [
Assets.office,
Assets.parlour,
Assets.dyi,
Assets.bathroom,
Assets.bedroom,
],
PresenceSpaceType(
description: 'Space Type',
value: model.spaceType,
action: (String value) => context.read<CeilingSensorBloc>().add(
CeilingChangeValueEvent(
code: 'scene',
value: value,
),
),
),
PresenceUpdateData(
value: model.sensitivity.toDouble(),
title: 'Sensitivity:',
minValue: 1,
maxValue: 5,
maxValue: 10,
steps: 1,
action: (int value) {
context.read<CeilingSensorBloc>().add(
@ -138,7 +133,7 @@ class CeilingSensorControls extends StatelessWidget
PresenceNoBodyTime(
value: model.noBodyTime,
title: 'Nobody Time:',
// description: 'hr',
description: '',
action: (String value) => context.read<CeilingSensorBloc>().add(
CeilingChangeValueEvent(
code: 'nobody_time',
@ -148,8 +143,8 @@ class CeilingSensorControls extends StatelessWidget
),
GestureDetector(
onTap: () {
context.read<CeilingSensorBloc>().add(GetCeilingDeviceReportsEvent(
code: 'presence_state', deviceUuid: device.uuid!));
context.read<CeilingSensorBloc>().add(
GetCeilingDeviceReportsEvent(code: 'presence_state', deviceUuid: device.uuid!));
},
child: const PresenceStaticWidget(
icon: Assets.illuminanceRecordIcon,
@ -158,8 +153,9 @@ class CeilingSensorControls extends StatelessWidget
),
GestureDetector(
onTap: () {
context.read<CeilingSensorBloc>().add(GetCeilingDeviceReportsEvent(
code: '', deviceUuid: device.uuid!));
context
.read<CeilingSensorBloc>()
.add(GetCeilingDeviceReportsEvent(code: '', deviceUuid: device.uuid!));
},
child: const PresenceStaticWidget(
icon: Assets.helpDescriptionIcon,

View File

@ -0,0 +1,161 @@
import 'dart:async';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
import 'package:syncrow_web/pages/device_managment/curtain/bloc/curtain_event.dart';
import 'package:syncrow_web/pages/device_managment/curtain/bloc/curtain_state.dart';
import 'package:syncrow_web/services/devices_mang_api.dart';
class CurtainBloc extends Bloc<CurtainEvent, CurtainState> {
late bool deviceStatus;
final String deviceId;
Timer? _timer;
CurtainBloc({required this.deviceId}) : super(CurtainInitial()) {
on<CurtainFetchDeviceStatus>(_onFetchDeviceStatus);
on<CurtainFetchBatchStatus>(_onFetchBatchStatus);
on<CurtainControl>(_onCurtainControl);
on<CurtainBatchControl>(_onCurtainBatchControl);
on<CurtainFactoryReset>(_onFactoryReset);
}
FutureOr<void> _onFetchDeviceStatus(
CurtainFetchDeviceStatus event, Emitter<CurtainState> emit) async {
emit(CurtainStatusLoading());
try {
final status =
await DevicesManagementApi().getDeviceStatus(event.deviceId);
deviceStatus = _checkStatus(status.status[0].value);
emit(CurtainStatusLoaded(deviceStatus));
} catch (e) {
emit(CurtainError(e.toString()));
}
}
FutureOr<void> _onCurtainControl(
CurtainControl event, Emitter<CurtainState> emit) async {
final oldValue = deviceStatus;
_updateLocalValue(event.value, emit);
emit(CurtainStatusLoaded(deviceStatus));
await _runDebounce(
deviceId: event.deviceId,
code: event.code,
value: event.value,
oldValue: oldValue,
emit: emit,
isBatch: false,
);
}
Future<void> _runDebounce({
required dynamic deviceId,
required String code,
required bool value,
required bool oldValue,
required Emitter<CurtainState> emit,
required bool isBatch,
}) async {
late String id;
if (deviceId is List) {
id = deviceId.first;
} else {
id = deviceId;
}
if (_timer != null) {
_timer!.cancel();
}
_timer = Timer(const Duration(seconds: 1), () async {
try {
final controlValue = value ? 'open' : 'close';
late bool response;
if (isBatch) {
response = await DevicesManagementApi()
.deviceBatchControl(deviceId, code, controlValue);
} else {
response = await DevicesManagementApi()
.deviceControl(deviceId, Status(code: code, value: controlValue));
}
if (!response) {
_revertValueAndEmit(id, oldValue, emit);
}
} catch (e) {
_revertValueAndEmit(id, oldValue, emit);
}
});
}
void _revertValueAndEmit(
String deviceId, bool oldValue, Emitter<CurtainState> emit) {
_updateLocalValue(oldValue, emit);
emit(CurtainStatusLoaded(deviceStatus));
emit(const CurtainControlError('Failed to control the device.'));
}
void _updateLocalValue(bool value, Emitter<CurtainState> emit) {
deviceStatus = value;
emit(CurtainStatusLoaded(deviceStatus));
}
bool _checkStatus(String command) {
return command.toLowerCase() == 'open';
}
FutureOr<void> _onFetchBatchStatus(
CurtainFetchBatchStatus event, Emitter<CurtainState> emit) async {
emit(CurtainStatusLoading());
try {
final status =
await DevicesManagementApi().getBatchStatus(event.devicesIds);
deviceStatus = _checkStatus(status.status[0].value);
emit(CurtainStatusLoaded(deviceStatus));
} catch (e) {
emit(CurtainError(e.toString()));
}
}
FutureOr<void> _onCurtainBatchControl(
CurtainBatchControl event, Emitter<CurtainState> emit) async {
final oldValue = deviceStatus;
_updateLocalValue(event.value, emit);
emit(CurtainStatusLoaded(deviceStatus));
await _runDebounce(
deviceId: event.devicesIds,
code: event.code,
value: event.value,
oldValue: oldValue,
emit: emit,
isBatch: true,
);
}
FutureOr<void> _onFactoryReset(
CurtainFactoryReset event, Emitter<CurtainState> emit) async {
emit(CurtainStatusLoading());
try {
final response = await DevicesManagementApi().factoryReset(
event.factoryReset,
event.deviceId,
);
if (!response) {
emit(const CurtainControlError('Failed'));
} else {
add(CurtainFetchDeviceStatus(event.deviceId));
}
} catch (e) {
emit(CurtainControlError(e.toString()));
}
}
}

View File

@ -0,0 +1,62 @@
import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart';
sealed class CurtainEvent extends Equatable {
const CurtainEvent();
@override
List<Object> get props => [];
}
class CurtainFetchDeviceStatus extends CurtainEvent {
final String deviceId;
const CurtainFetchDeviceStatus(this.deviceId);
@override
List<Object> get props => [deviceId];
}
class CurtainFetchBatchStatus extends CurtainEvent {
final List<String> devicesIds;
const CurtainFetchBatchStatus(this.devicesIds);
@override
List<Object> get props => [devicesIds];
}
class CurtainControl extends CurtainEvent {
final String deviceId;
final String code;
final bool value;
const CurtainControl(
{required this.deviceId, required this.code, required this.value});
@override
List<Object> get props => [deviceId, code, value];
}
class CurtainBatchControl extends CurtainEvent {
final List<String> devicesIds;
final String code;
final bool value;
const CurtainBatchControl(
{required this.devicesIds, required this.code, required this.value});
@override
List<Object> get props => [devicesIds, code, value];
}
class CurtainFactoryReset extends CurtainEvent {
final String deviceId;
final FactoryResetModel factoryReset;
const CurtainFactoryReset(
{required this.deviceId, required this.factoryReset});
@override
List<Object> get props => [deviceId, factoryReset];
}

View File

@ -0,0 +1,40 @@
import 'package:equatable/equatable.dart';
sealed class CurtainState extends Equatable {
const CurtainState();
@override
List<Object> get props => [];
}
final class CurtainInitial extends CurtainState {}
class CurtainStatusLoading extends CurtainState {}
class CurtainStatusLoaded extends CurtainState {
final bool status;
const CurtainStatusLoaded(this.status);
@override
List<Object> get props => [status];
}
class CurtainError extends CurtainState {
final String message;
const CurtainError(this.message);
@override
List<Object> get props => [message];
}
class CurtainControlError extends CurtainState {
final String message;
const CurtainControlError(this.message);
@override
List<Object> get props => [message];
}

View File

@ -0,0 +1,32 @@
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
class CurtainModel {
final String productUuid;
final String productType;
final List<Status> status;
CurtainModel({
required this.productUuid,
required this.productType,
required this.status,
});
factory CurtainModel.fromJson(dynamic json) {
var statusList = json['status'] as List;
List<Status> status = statusList.map((i) => Status.fromJson(i)).toList();
return CurtainModel(
productUuid: json['productUuid'],
productType: json['productType'],
status: status,
);
}
Map<String, dynamic> toJson() {
return {
'productUuid': productUuid,
'productType': productType,
'status': status.map((s) => s.toJson()).toList(),
};
}
}

View File

@ -0,0 +1,85 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/common/curtain_toggle.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart';
import 'package:syncrow_web/pages/device_managment/curtain/bloc/curtain_bloc.dart';
import 'package:syncrow_web/pages/device_managment/curtain/bloc/curtain_event.dart';
import 'package:syncrow_web/pages/device_managment/curtain/bloc/curtain_state.dart';
import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart';
import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
class CurtainBatchStatusView extends StatelessWidget
with HelperResponsiveLayout {
const CurtainBatchStatusView({super.key, required this.devicesIds});
final List<String> devicesIds;
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => CurtainBloc(deviceId: devicesIds.first)
..add(CurtainFetchBatchStatus(devicesIds)),
child: BlocBuilder<CurtainBloc, CurtainState>(
builder: (context, state) {
if (state is CurtainStatusLoading) {
return const Center(child: CircularProgressIndicator());
} else if (state is CurtainStatusLoaded) {
return _buildStatusControls(context, state.status);
} else if (state is CurtainError || state is CurtainControlError) {
return const Center(child: Text('Error fetching status'));
} else {
return const Center(child: CircularProgressIndicator());
}
},
),
);
}
Widget _buildStatusControls(BuildContext context, bool status) {
final isExtraLarge = isExtraLargeScreenSize(context);
final isLarge = isLargeScreenSize(context);
final isMedium = isMediumScreenSize(context);
return GridView(
padding: const EdgeInsets.symmetric(horizontal: 50, vertical: 20),
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: isLarge || isExtraLarge
? 3
: isMedium
? 2
: 1,
mainAxisExtent: 140,
crossAxisSpacing: 12,
mainAxisSpacing: 12,
),
children: [
CurtainToggle(
value: status,
code: 'control',
deviceId: devicesIds.first,
label: 'Curtains',
onChanged: (value) {
context.read<CurtainBloc>().add(CurtainBatchControl(
devicesIds: devicesIds,
code: 'control',
value: value,
));
},
),
FirmwareUpdateWidget(deviceId: devicesIds.first, version: 5),
FactoryResetWidget(
callFactoryReset: () {
context.read<CurtainBloc>().add(
CurtainFactoryReset(
deviceId: devicesIds.first,
factoryReset: FactoryResetModel(devicesUuid: devicesIds),
),
);
},
),
],
);
}
}

View File

@ -0,0 +1,75 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/common/curtain_toggle.dart';
import 'package:syncrow_web/pages/device_managment/curtain/bloc/curtain_bloc.dart';
import 'package:syncrow_web/pages/device_managment/curtain/bloc/curtain_event.dart';
import 'package:syncrow_web/pages/device_managment/curtain/bloc/curtain_state.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
class CurtainStatusControlsView extends StatelessWidget
with HelperResponsiveLayout {
final String deviceId;
const CurtainStatusControlsView({super.key, required this.deviceId});
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => CurtainBloc(deviceId: deviceId)
..add(CurtainFetchDeviceStatus(deviceId)),
child: BlocBuilder<CurtainBloc, CurtainState>(
builder: (context, state) {
if (state is CurtainStatusLoading) {
return const Center(child: CircularProgressIndicator());
} else if (state is CurtainStatusLoaded) {
return _buildStatusControls(context, state.status);
} else if (state is CurtainError || state is CurtainControlError) {
return const Center(child: Text('Error fetching status'));
} else {
return const Center(child: CircularProgressIndicator());
}
},
),
);
}
Widget _buildStatusControls(BuildContext context, bool status) {
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: [
const SizedBox.shrink(),
CurtainToggle(
value: status,
code: 'control',
deviceId: deviceId,
label: 'Curtains',
onChanged: (value) {
context.read<CurtainBloc>().add(
CurtainControl(
deviceId: deviceId,
code: 'control',
value: value,
),
);
},
),
const SizedBox.shrink(),
],
);
}
}

View File

@ -17,6 +17,7 @@ class DoorLockBloc extends Bloc<DoorLockEvent, DoorLockState> {
on<DoorLockFetchStatus>(_onFetchDeviceStatus);
//on<DoorLockControl>(_onDoorLockControl);
on<UpdateLockEvent>(_updateLock);
on<DoorLockFactoryReset>(_onFactoryReset);
}
FutureOr<void> _onFetchDeviceStatus(
@ -113,4 +114,22 @@ class DoorLockBloc extends Bloc<DoorLockEvent, DoorLockState> {
return null;
}
}
FutureOr<void> _onFactoryReset(
DoorLockFactoryReset event, Emitter<DoorLockState> emit) async {
emit(DoorLockStatusLoading());
try {
final response = await DevicesManagementApi().factoryReset(
event.factoryReset,
event.deviceId,
);
if (!response) {
emit(const DoorLockControlError('Failed'));
} else {
add(DoorLockFetchStatus(event.deviceId));
}
} catch (e) {
emit(DoorLockControlError(e.toString()));
}
}
}

View File

@ -1,4 +1,5 @@
import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart';
sealed class DoorLockEvent extends Equatable {
const DoorLockEvent();
@ -37,3 +38,16 @@ class UpdateLockEvent extends DoorLockEvent {
@override
List<Object> get props => [value];
}
class DoorLockFactoryReset extends DoorLockEvent {
final String deviceId;
final FactoryResetModel factoryReset;
const DoorLockFactoryReset({
required this.deviceId,
required this.factoryReset,
});
@override
List<Object> get props => [deviceId, factoryReset];
}

View File

@ -0,0 +1,49 @@
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/door_lock/bloc/door_lock_bloc.dart';
import 'package:syncrow_web/pages/device_managment/door_lock/bloc/door_lock_event.dart';
import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart';
import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
class DoorLockBatchControlView extends StatelessWidget
with HelperResponsiveLayout {
const DoorLockBatchControlView({super.key, required this.devicesIds});
final List<String> devicesIds;
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
width: 170,
height: 140,
child: FirmwareUpdateWidget(
deviceId: devicesIds.first,
version: 12,
),
),
const SizedBox(
width: 12,
),
SizedBox(
width: 170,
height: 140,
child: FactoryResetWidget(
callFactoryReset: () {
BlocProvider.of<DoorLockBloc>(context).add(
DoorLockFactoryReset(
deviceId: devicesIds.first,
factoryReset: FactoryResetModel(devicesUuid: devicesIds),
),
);
},
),
),
],
);
}
}

View File

@ -7,10 +7,10 @@ import 'package:syncrow_web/pages/device_managment/door_lock/bloc/door_lock_stat
import 'package:syncrow_web/pages/device_managment/door_lock/models/door_lock_status_model.dart';
import 'package:syncrow_web/pages/device_managment/door_lock/widget/door_button.dart';
class DoorLockView extends StatelessWidget {
class DoorLockControlsView extends StatelessWidget {
final AllDevicesModel device;
const DoorLockView({super.key, required this.device});
const DoorLockControlsView({super.key, required this.device});
@override
Widget build(BuildContext context) {
@ -34,9 +34,9 @@ class DoorLockView extends StatelessWidget {
} else if (state is UpdateState) {
return _buildStatusControls(context, state.smartDoorModel);
} else if (state is DoorLockControlError) {
return Center(child: Text(state.message));
return const SizedBox();
} else {
return const Center(child: CircularProgressIndicator());
return const Center(child: Text('Error fetching status'));
}
},
),

View File

@ -0,0 +1,423 @@
import 'dart:async';
import 'package:bloc/bloc.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/garage_door/bloc/garage_door_event.dart';
import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_state.dart';
import 'package:syncrow_web/pages/device_managment/garage_door/models/garage_door_model.dart';
import 'package:syncrow_web/pages/device_managment/water_heater/models/schedule_entry.dart';
import 'package:syncrow_web/pages/device_managment/water_heater/models/schedule_model.dart';
import 'package:syncrow_web/pages/device_managment/water_heater/models/water_heater_status_model.dart';
import 'package:syncrow_web/services/devices_mang_api.dart';
import 'package:syncrow_web/utils/format_date_time.dart';
class GarageDoorBloc extends Bloc<GarageDoorEvent, GarageDoorState> {
final String deviceId;
late GarageDoorStatusModel deviceStatus;
Timer? _timer;
GarageDoorBloc({required this.deviceId}) : super(GarageDoorInitialState()) {
on<GarageDoorInitialEvent>(_fetchGarageDoorStatus);
on<GarageDoorControlEvent>(_garageDoorControlEvent);
on<AddGarageDoorScheduleEvent>(_addSchedule);
on<UpdateGarageDoorScheduleEvent>(_updateSchedule);
on<DeleteGarageDoorScheduleEvent>(_deleteSchedule);
on<FetchGarageDoorSchedulesEvent>(_fetchSchedules);
on<IncreaseGarageDoorDelayEvent>(_increaseDelay);
on<DecreaseGarageDoorDelayEvent>(_decreaseDelay);
on<FetchGarageDoorRecordsEvent>(_fetchRecords);
on<GarageDoorUpdatedEvent>(_handleUpdate);
on<UpdateSelectedTimeEvent>(_updateSelectedTime);
on<UpdateSelectedDayEvent>(_updateSelectedDay);
on<UpdateFunctionOnEvent>(_updateFunctionOn);
on<InitializeAddScheduleEvent>(_initializeAddSchedule);
on<BackToGarageDoorGridViewEvent>(_backToGridView);
on<UpdateCountdownAlarmEvent>(_onUpdateCountdownAlarm);
on<UpdateTrTimeConEvent>(_onUpdateTrTimeCon);
on<GarageDoorBatchControlEvent>(_onBatchControl);
on<GarageDoorFetchBatchStatusEvent>(_onFetchBatchStatus);
on<GarageDoorFactoryResetEvent>(_onFactoryReset);
on<EditGarageDoorScheduleEvent>(_onEditSchedule);
}
void _fetchGarageDoorStatus(GarageDoorInitialEvent event, Emitter<GarageDoorState> emit) async {
emit(GarageDoorLoadingState());
try {
var response = await DevicesManagementApi().getDeviceStatus(event.deviceId);
deviceStatus = GarageDoorStatusModel.fromJson(deviceId, response.status);
emit(GarageDoorLoadedState(status: deviceStatus));
} catch (e) {
emit(GarageDoorErrorState(message: e.toString()));
}
}
Future<void> _onFetchBatchStatus(GarageDoorFetchBatchStatusEvent event, Emitter<GarageDoorState> emit) async {
emit(GarageDoorLoadingState());
try {
final status = await DevicesManagementApi().getBatchStatus(event.deviceIds);
deviceStatus = GarageDoorStatusModel.fromJson(event.deviceIds.first, status.status);
emit(GarageDoorBatchStatusLoaded(deviceStatus));
} catch (e) {
emit(GarageDoorBatchControlError(e.toString()));
}
}
Future<void> _addSchedule(AddGarageDoorScheduleEvent event, Emitter<GarageDoorState> emit) async {
try {
ScheduleEntry newSchedule = ScheduleEntry(
category: event.category,
time: formatTimeOfDayToISO(event.time),
function: Status(code: 'switch_1', value: event.functionOn),
days: ScheduleModel.convertSelectedDaysToStrings(event.selectedDays),
);
bool success = await DevicesManagementApi().addScheduleRecord(newSchedule, deviceId);
if (success) {
add(FetchGarageDoorSchedulesEvent(deviceId: deviceId, category: 'switch_1'));
} else {
emit(GarageDoorLoadedState(status: deviceStatus));
}
} catch (e) {
emit(GarageDoorLoadedState(status: deviceStatus));
}
}
void _onUpdateCountdownAlarm(UpdateCountdownAlarmEvent event, Emitter<GarageDoorState> emit) {
final currentState = state;
if (currentState is GarageDoorLoadedState) {
emit(currentState.copyWith(
status: currentState.status.copyWith(countdownAlarm: event.countdownAlarm),
));
}
}
void _onUpdateTrTimeCon(UpdateTrTimeConEvent event, Emitter<GarageDoorState> emit) {
final currentState = state;
if (currentState is GarageDoorLoadedState) {
emit(currentState.copyWith(
status: currentState.status.copyWith(trTimeCon: event.trTimeCon),
));
}
}
Future<void> _updateSchedule(UpdateGarageDoorScheduleEvent event, Emitter<GarageDoorState> emit) async {
try {
final updatedSchedules = deviceStatus.schedules?.map((schedule) {
if (schedule.scheduleId == event.scheduleId) {
return schedule.copyWith(
function: Status(code: 'switch_1', value: event.functionOn),
enable: event.enable,
);
}
return schedule;
}).toList();
bool success = await DevicesManagementApi().updateScheduleRecord(
enable: event.enable,
uuid: deviceStatus.uuid,
scheduleId: event.scheduleId,
);
if (success) {
deviceStatus = deviceStatus.copyWith(schedules: updatedSchedules);
emit(GarageDoorLoadedState(status: deviceStatus));
} else {
emit(GarageDoorLoadedState(status: deviceStatus));
}
} catch (e) {
emit(GarageDoorLoadedState(status: deviceStatus));
}
}
Future<void> _deleteSchedule(DeleteGarageDoorScheduleEvent event, Emitter<GarageDoorState> emit) async {
try {
bool success = await DevicesManagementApi().deleteScheduleRecord(deviceStatus.uuid, event.scheduleId);
if (success) {
final updatedSchedules =
deviceStatus.schedules?.where((schedule) => schedule.scheduleId != event.scheduleId).toList();
deviceStatus = deviceStatus.copyWith(schedules: updatedSchedules);
emit(GarageDoorLoadedState(status: deviceStatus));
} else {
emit(GarageDoorLoadedState(status: deviceStatus));
}
} catch (e) {
emit(GarageDoorLoadedState(status: deviceStatus));
}
}
Future<void> _fetchSchedules(FetchGarageDoorSchedulesEvent event, Emitter<GarageDoorState> emit) async {
emit(ScheduleGarageLoadingState());
try {
List<ScheduleModel> schedules =
await DevicesManagementApi().getDeviceSchedules(deviceStatus.uuid, event.category);
deviceStatus = deviceStatus.copyWith(schedules: schedules);
emit(
GarageDoorLoadedState(
status: deviceStatus,
scheduleMode: ScheduleModes.schedule,
),
);
} catch (e) {
emit(
GarageDoorLoadedState(
status: deviceStatus,
scheduleMode: ScheduleModes.schedule,
),
);
}
}
Future<void> _updateSelectedTime(UpdateSelectedTimeEvent event, Emitter<GarageDoorState> emit) async {
final currentState = state;
if (currentState is GarageDoorLoadedState) {
emit(currentState.copyWith(selectedTime: event.selectedTime));
}
}
Future<void> _updateSelectedDay(UpdateSelectedDayEvent event, Emitter<GarageDoorState> emit) async {
final currentState = state;
if (currentState is GarageDoorLoadedState) {
List<bool> updatedDays = List.from(currentState.selectedDays);
updatedDays[event.dayIndex] = event.isSelected;
emit(currentState.copyWith(selectedDays: updatedDays, selectedTime: currentState.selectedTime));
}
}
Future<void> _updateFunctionOn(UpdateFunctionOnEvent event, Emitter<GarageDoorState> emit) async {
final currentState = state;
if (currentState is GarageDoorLoadedState) {
emit(currentState.copyWith(functionOn: event.functionOn, selectedTime: currentState.selectedTime));
}
}
Future<void> _initializeAddSchedule(InitializeAddScheduleEvent event, Emitter<GarageDoorState> emit) async {
final currentState = state;
if (currentState is GarageDoorLoadedState) {
emit(currentState.copyWith(
selectedTime: event.selectedTime,
selectedDays: event.selectedDays,
functionOn: event.functionOn,
isEditing: event.isEditing,
));
}
}
Future<void> _fetchRecords(FetchGarageDoorRecordsEvent event, Emitter<GarageDoorState> emit) async {
emit(GarageDoorReportsLoadingState());
try {
final from = DateTime.now().subtract(const Duration(days: 30)).millisecondsSinceEpoch;
final to = DateTime.now().millisecondsSinceEpoch;
final DeviceReport records =
await DevicesManagementApi.getDeviceReportsByDate(event.deviceId, 'switch_1', from.toString(), to.toString());
emit(GarageDoorReportsState(deviceReport: records));
} catch (e) {
emit(GarageDoorReportsFailedState(error: e.toString()));
}
}
Future<void> _onBatchControl(GarageDoorBatchControlEvent event, Emitter<GarageDoorState> emit) async {
final oldValue = event.code == 'switch_1' ? deviceStatus.switch1 : false;
_updateLocalValue(event.code, event.value);
emit(GarageDoorBatchStatusLoaded(deviceStatus));
final success = await _runDeBouncer(
deviceId: event.deviceIds,
code: event.code,
value: event.value,
oldValue: oldValue,
emit: emit,
isBatch: true,
);
if (!success) {
_revertValue(event.code, oldValue, emit);
}
}
void _backToGridView(BackToGarageDoorGridViewEvent event, Emitter<GarageDoorState> emit) {
emit(GarageDoorLoadedState(status: deviceStatus));
}
void _handleUpdate(GarageDoorUpdatedEvent event, Emitter<GarageDoorState> emit) {
emit(GarageDoorLoadedState(status: deviceStatus));
}
Future<bool> _runDeBouncer({
required dynamic deviceId,
required String code,
required dynamic value,
required dynamic oldValue,
required Emitter<GarageDoorState> emit,
required bool isBatch,
}) async {
try {
late bool status;
await Future.delayed(const Duration(milliseconds: 500));
if (isBatch) {
status = await DevicesManagementApi().deviceBatchControl(deviceId, code, value);
} else {
status = await DevicesManagementApi().deviceControl(deviceId, Status(code: code, value: value));
}
if (!status) {
_revertValue(code, oldValue, emit);
return false;
} else {
return true;
}
} catch (e) {
_revertValue(code, oldValue, emit);
return false;
}
}
Future<void> _onFactoryReset(GarageDoorFactoryResetEvent event, Emitter<GarageDoorState> emit) async {
emit(GarageDoorLoadingState());
try {
final response = await DevicesManagementApi().factoryReset(event.factoryReset, event.deviceId);
if (!response) {
emit(const GarageDoorErrorState(message: 'Failed to reset device'));
} else {
emit(GarageDoorLoadedState(status: deviceStatus));
}
} catch (e) {
emit(GarageDoorErrorState(message: e.toString()));
}
}
void _increaseDelay(IncreaseGarageDoorDelayEvent event, Emitter<GarageDoorState> emit) async {
// if (deviceStatus.countdown1 != 0) {
try {
deviceStatus = deviceStatus.copyWith(delay: deviceStatus.delay + Duration(minutes: 10));
emit(GarageDoorLoadedState(status: deviceStatus));
add(GarageDoorControlEvent(deviceId: event.deviceId, value: deviceStatus.delay.inSeconds, code: 'countdown_1'));
} catch (e) {
emit(GarageDoorErrorState(message: e.toString()));
}
// }
}
void _decreaseDelay(DecreaseGarageDoorDelayEvent event, Emitter<GarageDoorState> emit) async {
// if (deviceStatus.countdown1 != 0) {
try {
if (deviceStatus.delay.inMinutes > 10) {
deviceStatus = deviceStatus.copyWith(delay: deviceStatus.delay - Duration(minutes: 10));
}
emit(GarageDoorLoadedState(status: deviceStatus));
add(GarageDoorControlEvent(deviceId: event.deviceId, value: deviceStatus.delay.inSeconds, code: 'countdown_1'));
} catch (e) {
emit(GarageDoorErrorState(message: e.toString()));
}
//}
}
void _garageDoorControlEvent(GarageDoorControlEvent event, Emitter<GarageDoorState> emit) async {
final oldValue = event.code == 'countdown_1' ? deviceStatus.countdown1 : deviceStatus.switch1;
_updateLocalValue(event.code, event.value);
emit(GarageDoorLoadedState(status: deviceStatus));
final success = await _runDeBouncer(
deviceId: event.deviceId,
code: event.code,
value: event.value,
oldValue: oldValue,
emit: emit,
isBatch: false,
);
if (!success) {
_revertValue(event.code, oldValue, emit);
}
}
void _revertValue(String code, dynamic oldValue, Emitter<GarageDoorState> emit) {
switch (code) {
case 'switch_1':
if (oldValue is bool) {
deviceStatus = deviceStatus.copyWith(switch1: oldValue);
}
break;
case 'countdown_1':
if (oldValue is int) {
deviceStatus = deviceStatus.copyWith(countdown1: oldValue, delay: Duration(seconds: oldValue));
}
break;
// Add other cases if needed
default:
break;
}
if (state is GarageDoorLoadedState) {
final currentState = state as GarageDoorLoadedState;
emit(currentState.copyWith(status: deviceStatus));
}
}
void _updateLocalValue(String code, dynamic value) {
switch (code) {
case 'switch_1':
if (value is bool) {
deviceStatus = deviceStatus.copyWith(switch1: value);
}
break;
case 'countdown_1':
if (value is int) {
deviceStatus = deviceStatus.copyWith(countdown1: value, delay: Duration(seconds: value));
}
break;
case 'countdown_alarm':
if (value is int) {
deviceStatus = deviceStatus.copyWith(countdownAlarm: value);
}
break;
case 'tr_timecon':
if (value is int) {
deviceStatus = deviceStatus.copyWith(trTimeCon: value);
}
break;
case 'door_state_1':
if (value is String) {
deviceStatus = deviceStatus.copyWith(doorState1: value);
}
break;
case 'door_control_1':
if (value is String) {
deviceStatus = deviceStatus.copyWith(doorControl1: value);
}
break;
case 'voice_control_1':
if (value is bool) {
deviceStatus = deviceStatus.copyWith(voiceControl1: value);
}
break;
case 'door_contact_state':
if (value is bool) {
deviceStatus = deviceStatus.copyWith(doorContactState: value);
}
default:
break;
}
}
@override
Future<void> close() {
_timer?.cancel();
return super.close();
}
FutureOr<void> _onEditSchedule(EditGarageDoorScheduleEvent event, Emitter<GarageDoorState> emit) async {
try {
ScheduleEntry newSchedule = ScheduleEntry(
scheduleId: event.scheduleId,
category: event.category,
time: formatTimeOfDayToISO(event.time),
function: Status(code: 'switch_1', value: event.functionOn),
days: ScheduleModel.convertSelectedDaysToStrings(event.selectedDays),
);
bool success = await DevicesManagementApi().editScheduleRecord(deviceId, newSchedule);
if (success) {
add(FetchGarageDoorSchedulesEvent(deviceId: deviceId, category: 'switch_1'));
} else {
emit(GarageDoorLoadedState(status: deviceStatus));
}
} catch (e) {
emit(GarageDoorLoadedState(status: deviceStatus));
}
}
}

View File

@ -0,0 +1,234 @@
// lib/pages/device_managment/garage_door/bloc/event.dart
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart';
abstract class GarageDoorEvent extends Equatable {
const GarageDoorEvent();
@override
List<Object?> get props => [];
}
class GarageDoorInitialEvent extends GarageDoorEvent {
final String deviceId;
const GarageDoorInitialEvent(this.deviceId);
@override
List<Object?> get props => [deviceId];
}
class GarageDoorControlEvent extends GarageDoorEvent {
final String deviceId;
final dynamic value;
final String code;
const GarageDoorControlEvent({required this.deviceId, required this.value, required this.code});
@override
List<Object?> get props => [deviceId, value];
}
class AddGarageDoorScheduleEvent extends GarageDoorEvent {
final String category;
final TimeOfDay time;
final bool functionOn;
final List<bool> selectedDays;
const AddGarageDoorScheduleEvent({
required this.category,
required this.time,
required this.functionOn,
required this.selectedDays,
});
}
class EditGarageDoorScheduleEvent extends GarageDoorEvent {
final String scheduleId;
final String category;
final TimeOfDay time;
final bool functionOn;
final List<bool> selectedDays;
const EditGarageDoorScheduleEvent({
required this.scheduleId,
required this.category,
required this.time,
required this.functionOn,
required this.selectedDays,
});
}
class UpdateGarageDoorScheduleEvent extends GarageDoorEvent {
final String deviceId;
final String scheduleId;
final bool enable;
final bool functionOn;
final int index;
const UpdateGarageDoorScheduleEvent({
required this.deviceId,
required this.scheduleId,
required this.enable,
required this.functionOn,
required this.index,
});
}
class DeleteGarageDoorScheduleEvent extends GarageDoorEvent {
final String deviceId;
final String scheduleId;
final int index;
const DeleteGarageDoorScheduleEvent({
required this.deviceId,
required this.scheduleId,
required this.index,
});
}
class FetchGarageDoorSchedulesEvent extends GarageDoorEvent {
final String deviceId;
final String category;
const FetchGarageDoorSchedulesEvent({
required this.deviceId,
required this.category,
});
}
class IncreaseGarageDoorDelayEvent extends GarageDoorEvent {
final String deviceId;
const IncreaseGarageDoorDelayEvent({required this.deviceId});
@override
List<Object?> get props => [deviceId];
}
class DecreaseGarageDoorDelayEvent extends GarageDoorEvent {
final String deviceId;
const DecreaseGarageDoorDelayEvent({required this.deviceId});
@override
List<Object?> get props => [deviceId];
}
class FetchGarageDoorRecordsEvent extends GarageDoorEvent {
final String deviceId;
final String code;
const FetchGarageDoorRecordsEvent({required this.deviceId, required this.code});
@override
List<Object?> get props => [deviceId, code];
}
class BackToGarageDoorGridViewEvent extends GarageDoorEvent {}
class GarageDoorUpdatedEvent extends GarageDoorEvent {}
class UpdateSelectedTimeEvent extends GarageDoorEvent {
final TimeOfDay? selectedTime;
const UpdateSelectedTimeEvent(this.selectedTime);
@override
List<Object?> get props => [selectedTime];
}
class UpdateSelectedDayEvent extends GarageDoorEvent {
final int dayIndex;
final bool isSelected;
const UpdateSelectedDayEvent(this.dayIndex, this.isSelected);
@override
List<Object?> get props => [dayIndex, isSelected];
}
class UpdateFunctionOnEvent extends GarageDoorEvent {
final bool functionOn;
const UpdateFunctionOnEvent({required this.functionOn});
@override
List<Object?> get props => [functionOn];
}
class InitializeAddScheduleEvent extends GarageDoorEvent {
final TimeOfDay? selectedTime;
final List<bool> selectedDays;
final bool functionOn;
final bool isEditing;
final int? index;
const InitializeAddScheduleEvent({
required this.selectedTime,
required this.selectedDays,
required this.functionOn,
required this.isEditing,
this.index,
});
@override
List<Object?> get props => [
selectedTime,
selectedDays,
functionOn,
isEditing,
index,
];
}
class UpdateCountdownAlarmEvent extends GarageDoorEvent {
final int countdownAlarm;
const UpdateCountdownAlarmEvent(this.countdownAlarm);
}
class UpdateTrTimeConEvent extends GarageDoorEvent {
final int trTimeCon;
const UpdateTrTimeConEvent(this.trTimeCon);
}
class GarageDoorBatchControlEvent extends GarageDoorEvent {
final List<String> deviceIds;
final String code;
final bool value;
const GarageDoorBatchControlEvent({
required this.deviceIds,
required this.code,
required this.value,
});
@override
List<Object?> get props => [deviceIds, code, value];
}
class GarageDoorFetchBatchStatusEvent extends GarageDoorEvent {
final List<String> deviceIds;
const GarageDoorFetchBatchStatusEvent(this.deviceIds);
@override
List<Object?> get props => [deviceIds];
}
class GarageDoorFactoryResetEvent extends GarageDoorEvent {
final FactoryResetModel factoryReset;
final String deviceId;
const GarageDoorFactoryResetEvent({
required this.factoryReset,
required this.deviceId,
});
@override
List<Object?> get props => [factoryReset, deviceId];
}

View File

@ -0,0 +1,141 @@
// lib/pages/device_managment/garage_door/bloc/state.dart
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_reports.dart';
import 'package:syncrow_web/pages/device_managment/garage_door/models/garage_door_model.dart';
import 'package:syncrow_web/pages/device_managment/water_heater/models/water_heater_status_model.dart';
abstract class GarageDoorState extends Equatable {
const GarageDoorState();
@override
List<Object?> get props => [];
}
class GarageDoorInitialState extends GarageDoorState {}
class GarageDoorLoadingState extends GarageDoorState {}
class GarageDoorLoadedState extends GarageDoorState {
final GarageDoorStatusModel status;
final Duration? delay;
final DeviceReport? records;
final List<bool> selectedDays;
final TimeOfDay? selectedTime;
final bool functionOn;
final bool isEditing;
final ScheduleModes? scheduleMode;
const GarageDoorLoadedState({
required this.status,
this.delay,
this.records,
this.selectedDays = const [false, false, false, false, false, false, false],
this.selectedTime,
this.functionOn = false,
this.isEditing = false,
this.scheduleMode = ScheduleModes.schedule,
});
@override
List<Object?> get props => [
status,
delay,
records,
selectedDays,
selectedTime,
functionOn,
isEditing,
scheduleMode,
];
GarageDoorLoadedState copyWith({
GarageDoorStatusModel? status,
Duration? delay,
DeviceReport? records,
List<bool>? selectedDays,
TimeOfDay? selectedTime,
bool? functionOn,
bool? isEditing,
ScheduleModes? scheduleMode,
}) {
return GarageDoorLoadedState(
status: status ?? this.status,
delay: delay ?? this.delay,
records: records ?? this.records,
selectedDays: selectedDays ?? this.selectedDays,
selectedTime: selectedTime,
functionOn: functionOn ?? this.functionOn,
isEditing: isEditing ?? this.isEditing,
scheduleMode: scheduleMode ?? this.scheduleMode,
);
}
}
class GarageDoorErrorState extends GarageDoorState {
final String message;
const GarageDoorErrorState({required this.message});
@override
List<Object?> get props => [message];
}
class GarageDoorLoadingNewState extends GarageDoorState {
final GarageDoorStatusModel garageDoorModel;
const GarageDoorLoadingNewState({required this.garageDoorModel});
@override
List<Object?> get props => [garageDoorModel];
}
class GarageDoorReportsLoadingState extends GarageDoorState {}
class GarageDoorReportsFailedState extends GarageDoorState {
final String error;
const GarageDoorReportsFailedState({required this.error});
@override
List<Object?> get props => [error];
}
class GarageDoorReportsState extends GarageDoorState {
final DeviceReport deviceReport;
const GarageDoorReportsState({required this.deviceReport});
@override
List<Object?> get props => [deviceReport];
}
class ShowGarageDoorDescriptionState extends GarageDoorState {
final String description;
const ShowGarageDoorDescriptionState({required this.description});
@override
List<Object?> get props => [description];
}
class ScheduleGarageLoadingState extends GarageDoorState {}
class GarageDoorBatchStatusLoaded extends GarageDoorState {
final GarageDoorStatusModel status;
const GarageDoorBatchStatusLoaded(this.status);
@override
List<Object?> get props => [status];
}
class GarageDoorBatchControlError extends GarageDoorState {
final String message;
const GarageDoorBatchControlError(this.message);
@override
List<Object?> get props => [message];
}

View File

@ -0,0 +1,385 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_bloc.dart';
import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_event.dart';
import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_state.dart';
import 'package:syncrow_web/pages/device_managment/garage_door/widgets/opening_clsoing_time_dialog_body.dart';
import 'package:syncrow_web/pages/device_managment/garage_door/widgets/time_out_alarm_dialog_body.dart';
import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_display_data.dart';
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart';
import 'package:syncrow_web/pages/device_managment/water_heater/models/schedule_model.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
class GarageDoorDialogHelper {
static void showAddGarageDoorScheduleDialog(BuildContext context,
{ScheduleModel? schedule, int? index, bool? isEdit}) {
final bloc = context.read<GarageDoorBloc>();
if (schedule == null) {
bloc.add((const UpdateSelectedTimeEvent(null)));
bloc.add(InitializeAddScheduleEvent(
selectedTime: null,
selectedDays: List.filled(7, false),
functionOn: false,
isEditing: false,
));
} else {
final time = _convertStringToTimeOfDay(schedule.time);
final selectedDays = _convertDaysStringToBooleans(schedule.days);
bloc.add(InitializeAddScheduleEvent(
selectedTime: time,
selectedDays: selectedDays,
functionOn: schedule.function.value,
isEditing: true,
index: index,
));
}
showDialog(
context: context,
builder: (ctx) {
return BlocProvider.value(
value: bloc,
child: BlocBuilder<GarageDoorBloc, GarageDoorState>(
builder: (context, state) {
if (state is GarageDoorLoadedState) {
return AlertDialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const SizedBox(),
Text(
'Scheduling',
style: context.textTheme.titleLarge!.copyWith(
color: ColorsManager.dialogBlueTitle,
fontWeight: FontWeight.bold,
),
),
const SizedBox(),
],
),
const SizedBox(height: 24),
SizedBox(
width: 150,
height: 40,
child: DefaultButton(
padding: 8,
backgroundColor: ColorsManager.boxColor,
borderRadius: 15,
onPressed: () async {
TimeOfDay? time = await showTimePicker(
context: context,
initialTime: state.selectedTime ?? TimeOfDay.now(),
builder: (context, child) {
return Theme(
data: Theme.of(context).copyWith(
colorScheme: const ColorScheme.light(
primary: ColorsManager.primaryColor,
),
),
child: child!,
);
},
);
if (time != null) {
bloc.add(UpdateSelectedTimeEvent(time));
}
},
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
state.selectedTime == null ? 'Time' : state.selectedTime!.format(context),
style: context.textTheme.bodySmall!.copyWith(
color: ColorsManager.grayColor,
),
),
const Icon(
Icons.access_time,
color: ColorsManager.grayColor,
size: 18,
),
],
),
),
),
const SizedBox(height: 16),
_buildDayCheckboxes(context, state.selectedDays, isEdit: isEdit),
const SizedBox(height: 16),
_buildFunctionSwitch(context, state.functionOn, isEdit),
],
),
actions: [
SizedBox(
width: 200,
child: DefaultButton(
height: 40,
onPressed: () {
Navigator.pop(context);
},
backgroundColor: ColorsManager.boxColor,
child: Text(
'Cancel',
style: context.textTheme.bodyMedium,
),
),
),
SizedBox(
width: 200,
child: DefaultButton(
height: 40,
onPressed: () {
if (state.selectedTime != null) {
if (state.isEditing && index != null) {
bloc.add(EditGarageDoorScheduleEvent(
scheduleId: schedule?.scheduleId ?? '',
category: 'switch_1',
time: state.selectedTime!,
selectedDays: state.selectedDays,
functionOn: state.functionOn,
));
} else {
bloc.add(AddGarageDoorScheduleEvent(
category: 'switch_1',
time: state.selectedTime!,
selectedDays: state.selectedDays,
functionOn: state.functionOn,
));
}
Navigator.pop(context);
}
},
backgroundColor: ColorsManager.primaryColor,
child: const Text('Save'),
),
),
],
);
}
return const SizedBox();
},
),
);
},
);
}
static TimeOfDay _convertStringToTimeOfDay(String timeString) {
final regex = RegExp(r'^(\d{2}):(\d{2})$');
final match = regex.firstMatch(timeString);
if (match != null) {
final hour = int.parse(match.group(1)!);
final minute = int.parse(match.group(2)!);
return TimeOfDay(hour: hour, minute: minute);
} else {
throw const FormatException('Invalid time format');
}
}
static List<bool> _convertDaysStringToBooleans(List<String> selectedDays) {
final daysOfWeek = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
List<bool> daysBoolean = List.filled(7, false);
for (int i = 0; i < daysOfWeek.length; i++) {
if (selectedDays.contains(daysOfWeek[i])) {
daysBoolean[i] = true;
}
}
return daysBoolean;
}
static Widget _buildDayCheckboxes(BuildContext context, List<bool> selectedDays, {bool? isEdit}) {
final dayLabels = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
return Row(
children: List.generate(7, (index) {
return Row(
children: [
Checkbox(
value: selectedDays[index],
onChanged: (bool? value) {
context.read<GarageDoorBloc>().add(UpdateSelectedDayEvent(index, value!));
},
),
Text(dayLabels[index]),
],
);
}),
);
}
static Widget _buildFunctionSwitch(BuildContext context, bool isOn, bool? isEdit) {
return Row(
children: [
Text(
'Function:',
style: context.textTheme.bodySmall!.copyWith(color: ColorsManager.grayColor),
),
const SizedBox(width: 10),
Radio<bool>(
value: true,
groupValue: isOn,
onChanged: (bool? value) {
context.read<GarageDoorBloc>().add(const UpdateFunctionOnEvent(functionOn: true));
},
),
const Text('On'),
const SizedBox(width: 10),
Radio<bool>(
value: false,
groupValue: isOn,
onChanged: (bool? value) {
context.read<GarageDoorBloc>().add(const UpdateFunctionOnEvent(functionOn: false));
},
),
const Text('Off'),
],
);
}
static void showPreferencesDialog(BuildContext context) {
final bloc = context.read<GarageDoorBloc>();
showDialog(
context: context,
builder: (ctx) {
return BlocProvider.value(
value: bloc,
child: BlocBuilder<GarageDoorBloc, GarageDoorState>(
builder: (context, state) {
if (state is GarageDoorLoadedState) {
return AlertDialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const SizedBox(),
/// The dialog is closed when the user taps on the close button or when the
/// [GarageDoorBloc] state changes.
Text(
'Preferences',
style: context.textTheme.titleLarge!.copyWith(
color: ColorsManager.dialogBlueTitle,
fontWeight: FontWeight.bold,
),
),
const SizedBox(),
],
),
const SizedBox(height: 24),
Row(
children: [
const SizedBox(width: 24),
SizedBox(
width: 190,
height: 150,
child: GestureDetector(
onTap: () {
context.customAlertDialog(
alertBody: TimeOutAlarmDialogBody(bloc),
title: 'Time Out Alarm',
onConfirm: () {
final updatedState = context.read<GarageDoorBloc>().state;
if (updatedState is GarageDoorLoadedState) {
context.read<GarageDoorBloc>().add(
GarageDoorControlEvent(
deviceId: updatedState.status.uuid,
code: 'countdown_alarm',
value: updatedState.status.countdownAlarm,
),
);
Navigator.pop(context);
}
});
},
child: ToggleWidget(
icon: "-1",
value: state.status.doorState1 == "close_time_alarm" ? false : true,
code: 'door_state_1',
deviceId: bloc.deviceId,
label: 'Alarm when door is open',
onChange: (value) {
context.read<GarageDoorBloc>().add(
GarageDoorControlEvent(
deviceId: bloc.deviceId,
code: 'door_state_1',
value: state.status.doorState1 == "close_time_alarm"
? "unclosed_time"
: "close_time_alarm",
),
);
}),
),
),
const SizedBox(
width: 20,
),
SizedBox(
width: 190,
height: 150,
child: GestureDetector(
onTap: () {
context.customAlertDialog(
alertBody: OpeningAndClosingTimeDialogBody(
bloc: bloc,
onDurationChanged: (newDuration) {
context.read<GarageDoorBloc>().add(
UpdateTrTimeConEvent(newDuration),
);
},
),
title: 'Opening and Closing Time',
onConfirm: () {
final updatedState = context.read<GarageDoorBloc>().state;
if (updatedState is GarageDoorLoadedState) {
context.read<GarageDoorBloc>().add(
GarageDoorControlEvent(
deviceId: updatedState.status.uuid,
code: 'tr_timecon',
value: updatedState.status.trTimeCon,
),
);
Navigator.pop(context);
}
});
},
child: PresenceDisplayValue(
value: state.status.trTimeCon.toString(),
postfix: 'sec',
description: 'Opening & Closing Time',
),
),
),
const SizedBox(width: 24),
],
)
],
),
);
}
return const SizedBox();
},
),
);
},
);
}
}

View File

@ -0,0 +1,123 @@
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
import 'package:syncrow_web/pages/device_managment/water_heater/models/schedule_model.dart';
class GarageDoorStatusModel {
final String uuid;
final bool switch1;
final int countdown1;
final bool doorContactState;
final int trTimeCon;
final int countdownAlarm;
final String doorControl1;
final bool voiceControl1;
final String doorState1;
// final bool isOpen;
final Duration delay;
final List<ScheduleModel>? schedules; // Add schedules field
GarageDoorStatusModel({
required this.uuid,
required this.switch1,
required this.countdown1,
required this.doorContactState,
required this.trTimeCon,
required this.countdownAlarm,
required this.doorControl1,
required this.voiceControl1,
required this.doorState1,
// required this.isOpen,
required this.delay,
required this.schedules, // Initialize schedules
});
factory GarageDoorStatusModel.fromJson(String id, List<Status> jsonList) {
late bool switch1;
late int countdown1;
late bool doorContactState;
late int trTimeCon;
late int countdownAlarm;
late String doorControl1;
late bool voiceControl1;
late String doorState1;
List<ScheduleModel> schedules = []; // Initialize schedules
for (var status in jsonList) {
switch (status.code) {
case 'switch_1':
switch1 = status.value ?? false;
break;
case 'countdown_1':
countdown1 = status.value ?? 0;
break;
case 'doorcontact_state':
doorContactState = status.value ?? false;
break;
case 'tr_timecon':
trTimeCon = status.value ?? 0;
break;
case 'countdown_alarm':
countdownAlarm = status.value ?? 0;
break;
case 'door_control_1':
doorControl1 = status.value ?? 'closed';
break;
case 'voice_control_1':
voiceControl1 = status.value ?? false;
break;
case 'door_state_1':
doorState1 = status.value ?? 'close_time_alarm';
break;
}
}
return GarageDoorStatusModel(
uuid: id,
switch1: switch1,
countdown1: countdown1,
doorContactState: doorContactState,
trTimeCon: trTimeCon,
countdownAlarm: countdownAlarm,
doorControl1: doorControl1,
voiceControl1: voiceControl1,
doorState1: doorState1,
// isOpen: doorState1 == 'open' ? true : false,
delay: Duration(seconds: countdown1),
schedules: schedules, // Assign schedules
);
}
GarageDoorStatusModel copyWith({
String? uuid,
bool? switch1,
int? countdown1,
bool? doorContactState,
int? trTimeCon,
int? countdownAlarm,
String? doorControl1,
bool? voiceControl1,
String? doorState1,
// bool? isOpen,
Duration? delay,
List<ScheduleModel>? schedules, // Add schedules to copyWith
}) {
return GarageDoorStatusModel(
uuid: uuid ?? this.uuid,
switch1: switch1 ?? this.switch1,
countdown1: countdown1 ?? this.countdown1,
doorContactState: doorContactState ?? this.doorContactState,
trTimeCon: trTimeCon ?? this.trTimeCon,
countdownAlarm: countdownAlarm ?? this.countdownAlarm,
doorControl1: doorControl1 ?? this.doorControl1,
voiceControl1: voiceControl1 ?? this.voiceControl1,
doorState1: doorState1 ?? this.doorState1,
// isOpen: isOpen ?? this.isOpen,
delay: delay ?? this.delay,
schedules: schedules ?? this.schedules, // Copy schedules
);
}
@override
String toString() {
return 'GarageDoorStatusModel(uuid: $uuid, switch1: $switch1, countdown1: $countdown1, doorContactState: $doorContactState, trTimeCon: $trTimeCon, countdownAlarm: $countdownAlarm, doorControl1: $doorControl1, voiceControl1: $voiceControl1, doorState1: $doorState1, delay: $delay, schedules: $schedules)';
}
}

View File

@ -0,0 +1,95 @@
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/garage_door/bloc/garage_door_bloc.dart';
import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_event.dart';
import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_state.dart';
import 'package:syncrow_web/pages/device_managment/garage_door/models/garage_door_model.dart';
import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart';
import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
class GarageDoorBatchControlView extends StatelessWidget
with HelperResponsiveLayout {
final List<String> deviceIds;
const GarageDoorBatchControlView({Key? key, required this.deviceIds})
: super(key: key);
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => GarageDoorBloc(deviceId: deviceIds.first)
..add(GarageDoorFetchBatchStatusEvent(deviceIds)),
child: BlocBuilder<GarageDoorBloc, GarageDoorState>(
builder: (context, state) {
if (state is GarageDoorLoadingState) {
return const Center(child: CircularProgressIndicator());
} else if (state is GarageDoorBatchStatusLoaded) {
return _buildStatusControls(context, state.status);
} else if (state is GarageDoorBatchControlError) {
return Center(child: Text('Error: ${state.message}'));
} else {
return const Center(child: CircularProgressIndicator());
}
},
),
);
}
Widget _buildStatusControls(
BuildContext context, GarageDoorStatusModel status) {
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: [
ToggleWidget(
value: status.switch1,
code: 'switch_1',
deviceId: deviceIds.first,
label: 'Garage Door',
icon: status.switch1 ? Assets.openedDoor : Assets.closedDoor,
onChange: (value) {
context.read<GarageDoorBloc>().add(
GarageDoorBatchControlEvent(
deviceIds: deviceIds,
code: 'switch_1',
value: value,
),
);
},
),
FirmwareUpdateWidget(
deviceId: deviceIds.first,
version: 12,
),
FactoryResetWidget(
callFactoryReset: () {
context.read<GarageDoorBloc>().add(
GarageDoorFactoryResetEvent(
deviceId: deviceIds.first,
factoryReset: FactoryResetModel(devicesUuid: deviceIds),
),
);
},
),
],
);
}
}

View File

@ -0,0 +1,188 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_bloc.dart';
import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_event.dart';
import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_state.dart';
import 'package:syncrow_web/pages/device_managment/garage_door/helper/garage_door_helper.dart';
import 'package:syncrow_web/pages/device_managment/garage_door/models/garage_door_model.dart';
import 'package:syncrow_web/pages/device_managment/garage_door/widgets/schedule_garage_view.dart';
import 'package:syncrow_web/pages/device_managment/shared/icon_name_status_container.dart';
import 'package:syncrow_web/pages/device_managment/shared/table/report_table.dart';
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
class GarageDoorControlView extends StatelessWidget with HelperResponsiveLayout {
final String deviceId;
const GarageDoorControlView({required this.deviceId, super.key});
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => GarageDoorBloc(deviceId: deviceId)..add(GarageDoorInitialEvent(deviceId)),
child: BlocBuilder<GarageDoorBloc, GarageDoorState>(
builder: (context, state) {
if (state is GarageDoorLoadingState) {
return const Center(child: CircularProgressIndicator());
} else if (state is GarageDoorReportsState) {
return ReportsTable(
report: state.deviceReport,
hideValueShowDescription: true,
garageDoorSensor: true,
onRowTap: (index) {},
onClose: () {
context.read<GarageDoorBloc>().add(BackToGarageDoorGridViewEvent());
},
);
} else if (state is GarageDoorLoadedState) {
return _buildControlView(context, state.status);
} else if (state is GarageDoorErrorState) {
return Center(child: Text('Error: ${state.message}'));
}
return const Center(child: Text('Unknown state'));
},
),
);
}
Widget _buildControlView(BuildContext context, GarageDoorStatusModel status) {
final isExtraLarge = isExtraLargeScreenSize(context);
final isLarge = isLargeScreenSize(context);
final isMedium = isMediumScreenSize(context);
return GridView(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
padding: const EdgeInsets.symmetric(horizontal: 50),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: isLarge || isExtraLarge
? 3
: isMedium
? 2
: 1,
childAspectRatio: 1.5,
mainAxisExtent: 140,
crossAxisSpacing: 12,
mainAxisSpacing: 12,
),
children: [
IconNameStatusContainer(
isFullIcon: false,
name: status.switch1 ? 'Opened' : 'Closed',
icon: status.switch1 ? Assets.openedDoor : Assets.closedDoor,
onTap: () {
context.read<GarageDoorBloc>().add(
GarageDoorControlEvent(deviceId: status.uuid, value: !status.switch1, code: 'switch_1'),
);
},
status: status.switch1,
textColor: ColorsManager.blackColor,
),
IconNameStatusContainer(
onTap: () {
context.read<GarageDoorBloc>().add(
FetchGarageDoorSchedulesEvent(deviceId: deviceId, category: 'switch_1'),
);
showDialog(
context: context,
builder: (ctx) => BlocProvider.value(
value: BlocProvider.of<GarageDoorBloc>(context),
child: BuildGarageDoorScheduleView(status: status),
));
},
name: 'Scheduling',
icon: Assets.acSchedule,
status: false,
textColor: ColorsManager.blackColor,
isFullIcon: false,
),
ToggleWidget(
label: '',
labelWidget: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
IconButton(
onPressed: () {
context.read<GarageDoorBloc>().add(DecreaseGarageDoorDelayEvent(deviceId: status.uuid));
},
icon: const Icon(
Icons.remove,
size: 28,
color: ColorsManager.greyColor,
),
padding: EdgeInsets.zero,
),
Text(
status.delay.inHours.toString().padLeft(2, '0'),
style: context.textTheme.titleLarge!.copyWith(
color: ColorsManager.dialogBlueTitle,
fontWeight: FontWeight.bold,
),
),
Text(
'h',
style: context.textTheme.bodySmall!.copyWith(color: ColorsManager.blackColor),
),
Text(
(status.delay.inMinutes % 60).toString().padLeft(2, '0'),
style: context.textTheme.titleLarge!.copyWith(
color: ColorsManager.dialogBlueTitle,
fontWeight: FontWeight.bold,
),
),
Text(
'm',
style: context.textTheme.bodySmall!.copyWith(color: ColorsManager.blackColor),
),
IconButton(
onPressed: () {
context.read<GarageDoorBloc>().add(IncreaseGarageDoorDelayEvent(deviceId: status.uuid));
},
icon: const Icon(
Icons.add,
size: 28,
color: ColorsManager.greyColor,
),
padding: EdgeInsets.zero,
),
],
),
value: status.countdown1 != 0 ? true : false,
code: 'countdown_1',
deviceId: status.uuid,
icon: Assets.doorDelay,
onChange: (value) {
context.read<GarageDoorBloc>().add(
GarageDoorControlEvent(
deviceId: status.uuid, value: value ? status.delay.inSeconds : 0, code: 'countdown_1'),
);
},
),
IconNameStatusContainer(
isFullIcon: false,
name: 'Records',
icon: Assets.records,
onTap: () {
context.read<GarageDoorBloc>().add(FetchGarageDoorRecordsEvent(code: 'switch_1', deviceId: status.uuid));
},
status: false,
textColor: ColorsManager.blackColor,
),
IconNameStatusContainer(
isFullIcon: false,
name: 'Preferences',
icon: Assets.preferences,
onTap: () {
GarageDoorDialogHelper.showPreferencesDialog(context);
},
status: false,
textColor: ColorsManager.blackColor,
),
],
);
}
}

View File

@ -0,0 +1,53 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_bloc.dart';
import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_state.dart';
import 'package:syncrow_web/pages/device_managment/garage_door/widgets/seconds_picker.dart';
class OpeningAndClosingTimeDialogBody extends StatefulWidget {
final ValueChanged<int> onDurationChanged;
final GarageDoorBloc bloc;
OpeningAndClosingTimeDialogBody({
required this.onDurationChanged,
required this.bloc,
});
@override
_OpeningAndClosingTimeDialogBodyState createState() =>
_OpeningAndClosingTimeDialogBodyState();
}
class _OpeningAndClosingTimeDialogBodyState
extends State<OpeningAndClosingTimeDialogBody> {
late int durationInSeconds;
@override
void didChangeDependencies() {
super.didChangeDependencies();
final currentState = widget.bloc.state;
if (currentState is GarageDoorLoadedState) {
setState(() {
durationInSeconds = currentState.status.trTimeCon;
});
}
}
@override
Widget build(BuildContext context) {
return Container(
height: 120,
color: Colors.white,
child: SecondsPicker(
initialSeconds: durationInSeconds,
onSecondsChanged: (newSeconds) {
setState(() {
durationInSeconds = newSeconds;
});
widget.onDurationChanged(newSeconds);
},
),
);
}
}

View File

@ -0,0 +1,212 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_bloc.dart';
import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_event.dart';
import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_state.dart';
import 'package:syncrow_web/pages/device_managment/garage_door/helper/garage_door_helper.dart';
import 'package:syncrow_web/pages/device_managment/water_heater/models/schedule_model.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:syncrow_web/utils/format_date_time.dart';
class ScheduleGarageTableWidget extends StatelessWidget {
final GarageDoorLoadedState state;
const ScheduleGarageTableWidget({
super.key,
required this.state,
});
@override
Widget build(BuildContext context) {
return Column(
children: [
Table(
border: TableBorder.all(
color: ColorsManager.graysColor,
borderRadius: const BorderRadius.only(topLeft: Radius.circular(20), topRight: Radius.circular(20)),
),
children: [
TableRow(
decoration: const BoxDecoration(
color: ColorsManager.boxColor,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(20),
topRight: Radius.circular(20),
),
),
children: [
_buildTableHeader('Active'),
_buildTableHeader('Days'),
_buildTableHeader('Time'),
_buildTableHeader('Function'),
_buildTableHeader('Action'),
],
),
],
),
BlocBuilder<GarageDoorBloc, GarageDoorState>(
builder: (context, state) {
if (state is ScheduleGarageLoadingState) {
return const SizedBox(height: 200, child: Center(child: CircularProgressIndicator()));
}
if (state is GarageDoorLoadedState && state.status.schedules?.isEmpty == true) {
return _buildEmptyState(context);
} else if (state is GarageDoorLoadedState) {
return Container(
height: 200,
decoration: BoxDecoration(
border: Border.all(color: ColorsManager.graysColor),
borderRadius:
const BorderRadius.only(bottomLeft: Radius.circular(20), bottomRight: Radius.circular(20)),
),
child: _buildTableBody(state, context));
}
return const SizedBox(
height: 200,
);
},
),
],
);
}
Widget _buildEmptyState(BuildContext context) {
return Container(
height: 200,
decoration: BoxDecoration(
border: Border.all(color: ColorsManager.graysColor),
borderRadius: const BorderRadius.only(bottomLeft: Radius.circular(20), bottomRight: Radius.circular(20)),
),
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
SvgPicture.asset(Assets.emptyRecords, width: 40, height: 40),
const SizedBox(height: 8),
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
'No schedules added yet',
style: context.textTheme.bodySmall!.copyWith(
fontSize: 13,
color: ColorsManager.grayColor,
),
),
),
],
),
),
);
}
Widget _buildTableBody(GarageDoorLoadedState state, BuildContext context) {
return SizedBox(
height: 200,
child: SingleChildScrollView(
child: Table(
border: TableBorder.all(color: ColorsManager.graysColor),
defaultVerticalAlignment: TableCellVerticalAlignment.middle,
children: [
if (state.status.schedules != null)
for (int i = 0; i < state.status.schedules!.length; i++)
_buildScheduleRow(state.status.schedules![i], i, context, state),
],
),
),
);
}
Widget _buildTableHeader(String label) {
return TableCell(
child: Padding(
padding: const EdgeInsets.all(12),
child: Text(
label,
style: const TextStyle(
fontSize: 13,
color: ColorsManager.grayColor,
),
),
),
);
}
TableRow _buildScheduleRow(ScheduleModel schedule, int index, BuildContext context, GarageDoorLoadedState state) {
return TableRow(
children: [
Center(
child: GestureDetector(
onTap: () {
context.read<GarageDoorBloc>().add(UpdateGarageDoorScheduleEvent(
index: index,
enable: !schedule.enable,
scheduleId: schedule.scheduleId,
deviceId: state.status.uuid,
functionOn: schedule.function.value,
));
},
child: SizedBox(
width: 24,
height: 24,
child: schedule.enable
? const Icon(Icons.radio_button_checked, color: ColorsManager.blueColor)
: const Icon(
Icons.radio_button_unchecked,
color: ColorsManager.grayColor,
),
),
),
),
Center(child: Text(_getSelectedDays(ScheduleModel.parseSelectedDays(schedule.days)))),
Center(child: Text(formatIsoStringToTime(schedule.time, context))),
Center(child: Text(schedule.function.value ? 'On' : 'Off')),
Center(
child: Wrap(
runAlignment: WrapAlignment.center,
children: [
TextButton(
style: TextButton.styleFrom(padding: EdgeInsets.zero),
onPressed: () {
GarageDoorDialogHelper.showAddGarageDoorScheduleDialog(context,
schedule: schedule, index: index, isEdit: true);
},
child: Text(
'Edit',
style: context.textTheme.bodySmall!.copyWith(color: ColorsManager.blueColor),
),
),
TextButton(
style: TextButton.styleFrom(padding: EdgeInsets.zero),
onPressed: () {
context.read<GarageDoorBloc>().add(DeleteGarageDoorScheduleEvent(
index: index,
scheduleId: schedule.scheduleId,
deviceId: state.status.uuid,
));
},
child: Text(
'Delete',
style: context.textTheme.bodySmall!.copyWith(color: ColorsManager.blueColor),
),
),
],
),
),
],
);
}
String _getSelectedDays(List<bool> selectedDays) {
final days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
List<String> selectedDaysStr = [];
for (int i = 0; i < selectedDays.length; i++) {
if (selectedDays[i]) {
selectedDaysStr.add(days[i]);
}
}
return selectedDaysStr.join(', ');
}
}

View File

@ -0,0 +1,46 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class ScheduleGarageHeader extends StatelessWidget {
const ScheduleGarageHeader({super.key});
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const SizedBox(),
Text(
'Scheduling',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 22,
color: ColorsManager.dialogBlueTitle,
),
),
Container(
width: 25,
decoration: BoxDecoration(
color: Colors.transparent,
shape: BoxShape.circle,
border: Border.all(
color: Colors.grey,
width: 1.0,
),
),
child: IconButton(
padding: const EdgeInsets.all(1),
icon: const Icon(
Icons.close,
color: Colors.grey,
size: 18,
),
onPressed: () {
Navigator.of(context).pop();
},
),
),
],
);
}
}

View File

@ -0,0 +1,50 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_state.dart';
import 'package:syncrow_web/pages/device_managment/garage_door/widgets/schedule__garage_table.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
class ScheduleGarageManagementUI extends StatelessWidget {
final GarageDoorLoadedState state;
final Function onAddSchedule;
const ScheduleGarageManagementUI({
super.key,
required this.state,
required this.onAddSchedule,
});
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: 170,
height: 40,
child: DefaultButton(
borderColor: ColorsManager.boxColor,
padding: 2,
backgroundColor: ColorsManager.graysColor,
borderRadius: 15,
onPressed: () => onAddSchedule(),
child: Row(
children: [
const Icon(Icons.add, color: ColorsManager.primaryColor),
Text(
' Add new schedule',
style: context.textTheme.bodySmall!.copyWith(
color: ColorsManager.blackColor,
),
),
],
),
),
),
const SizedBox(height: 20),
ScheduleGarageTableWidget(state: state),
],
);
}
}

View File

@ -0,0 +1,45 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
class ScheduleGarageModeButtons extends StatelessWidget {
final VoidCallback onSave;
const ScheduleGarageModeButtons({
super.key,
required this.onSave,
});
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
children: [
Expanded(
child: DefaultButton(
height: 40,
onPressed: () {
Navigator.pop(context);
},
backgroundColor: ColorsManager.boxColor,
child: Text(
'Cancel',
style: context.textTheme.bodyMedium,
),
),
),
const SizedBox(width: 20),
Expanded(
child: DefaultButton(
height: 40,
onPressed: onSave,
backgroundColor: ColorsManager.primaryColor,
child: const Text('Save'),
),
),
],
);
}
}

View File

@ -0,0 +1,68 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_bloc.dart';
import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_event.dart';
import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_state.dart';
import 'package:syncrow_web/pages/device_managment/water_heater/models/water_heater_status_model.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
class ScheduleGarageDoorModeSelector extends StatelessWidget {
final GarageDoorLoadedState state;
const ScheduleGarageDoorModeSelector({super.key, required this.state});
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Type:',
style: context.textTheme.bodySmall!.copyWith(
fontSize: 13,
color: ColorsManager.grayColor,
),
),
const SizedBox(height: 4),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
_buildRadioTile(context, 'Schedule', ScheduleModes.schedule, state),
],
),
],
);
}
Widget _buildRadioTile(BuildContext context, String label, ScheduleModes mode, GarageDoorLoadedState state) {
return Flexible(
child: ListTile(
contentPadding: EdgeInsets.zero,
title: Text(
label,
style: context.textTheme.bodySmall!.copyWith(
fontSize: 13,
color: ColorsManager.blackColor,
),
),
leading: Radio<ScheduleModes>(
value: mode,
groupValue: state.scheduleMode,
onChanged: (ScheduleModes? value) {
if (value != null) {
if (value == ScheduleModes.schedule) {
context.read<GarageDoorBloc>().add(
FetchGarageDoorSchedulesEvent(
category: 'switch_1',
deviceId: state.status.uuid,
),
);
}
}
},
),
),
);
}
}

View File

@ -0,0 +1,96 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_bloc.dart';
import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_state.dart';
import 'package:syncrow_web/pages/device_managment/garage_door/helper/garage_door_helper.dart';
import 'package:syncrow_web/pages/device_managment/garage_door/models/garage_door_model.dart';
import 'package:syncrow_web/pages/device_managment/garage_door/widgets/schedule_garage_header.dart';
import 'package:syncrow_web/pages/device_managment/garage_door/widgets/schedule_garage_managment_ui.dart';
import 'package:syncrow_web/pages/device_managment/garage_door/widgets/schedule_garage_mode_buttons.dart';
class BuildGarageDoorScheduleView extends StatefulWidget {
const BuildGarageDoorScheduleView({super.key, required this.status});
final GarageDoorStatusModel status;
@override
State<BuildGarageDoorScheduleView> createState() => _BuildScheduleViewState();
}
class _BuildScheduleViewState extends State<BuildGarageDoorScheduleView> {
@override
Widget build(BuildContext context) {
final bloc = BlocProvider.of<GarageDoorBloc>(context);
return BlocProvider.value(
value: bloc,
child: Dialog(
backgroundColor: Colors.white,
insetPadding: const EdgeInsets.all(20),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
child: SizedBox(
width: 700,
child: SingleChildScrollView(
child: Padding(
padding:
const EdgeInsets.symmetric(horizontal: 40.0, vertical: 20),
child: BlocBuilder<GarageDoorBloc, GarageDoorState>(
builder: (context, state) {
if (state is GarageDoorLoadedState) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const ScheduleGarageHeader(),
const SizedBox(height: 20),
ScheduleGarageManagementUI(
state: state,
onAddSchedule: () {
GarageDoorDialogHelper
.showAddGarageDoorScheduleDialog(context,
schedule: null, index: null, isEdit: false);
},
),
const SizedBox(height: 20),
ScheduleGarageModeButtons(
onSave: () {
Navigator.pop(context);
},
),
],
);
}
if (state is ScheduleGarageLoadingState) {
return SizedBox(
height: 200,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const ScheduleGarageHeader(),
const SizedBox(
height: 50,
),
const Center(child: CircularProgressIndicator()),
const SizedBox(
height: 20,
),
ScheduleGarageModeButtons(
onSave: () {},
),
],
));
}
return const SizedBox(
height: 200,
child: ScheduleGarageHeader(),
);
},
),
),
),
),
),
);
}
}

View File

@ -0,0 +1,52 @@
import 'package:flutter/material.dart';
class SecondsPicker extends StatefulWidget {
final int initialSeconds;
final ValueChanged<int> onSecondsChanged;
SecondsPicker({
required this.initialSeconds,
required this.onSecondsChanged,
});
@override
_SecondsPickerState createState() => _SecondsPickerState();
}
class _SecondsPickerState extends State<SecondsPicker> {
late FixedExtentScrollController _scrollController;
@override
void initState() {
super.initState();
_scrollController = FixedExtentScrollController(
initialItem: widget.initialSeconds,
);
}
@override
Widget build(BuildContext context) {
return Container(
height: 120,
color: Colors.white,
child: ListWheelScrollView.useDelegate(
controller: _scrollController,
itemExtent: 48,
onSelectedItemChanged: (index) {
widget.onSecondsChanged(index);
},
physics: const FixedExtentScrollPhysics(),
childDelegate: ListWheelChildBuilderDelegate(
builder: (context, index) {
return Center(
child: Text(
'$index sec',
style: const TextStyle(fontSize: 24),
),
);
},
),
),
);
}
}

View File

@ -0,0 +1,49 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_bloc.dart';
import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_event.dart';
import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_state.dart';
class TimeOutAlarmDialogBody extends StatefulWidget {
TimeOutAlarmDialogBody(this.bloc);
final GarageDoorBloc bloc;
@override
_TimeOutAlarmDialogBodyState createState() => _TimeOutAlarmDialogBodyState();
}
class _TimeOutAlarmDialogBodyState extends State<TimeOutAlarmDialogBody> {
int durationInSeconds = 0;
@override
void didChangeDependencies() {
super.didChangeDependencies();
final currentState = widget.bloc.state;
if (currentState is GarageDoorLoadedState) {
if (currentState.status.countdownAlarm != 0) {
setState(() {
durationInSeconds = currentState.status.countdownAlarm;
});
}
}
}
@override
Widget build(BuildContext context) {
return Container(
height: 120,
color: Colors.white,
child: CupertinoTimerPicker(
itemExtent: 120,
mode: CupertinoTimerPickerMode.hm,
initialTimerDuration: Duration(seconds: durationInSeconds),
onTimerDurationChanged: (newDuration) {
widget.bloc.add(
UpdateCountdownAlarmEvent(newDuration.inSeconds),
);
},
),
);
}
}

View File

@ -2,6 +2,7 @@ import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart';
import 'package:syncrow_web/pages/visitor_password/model/device_model.dart';
import 'package:syncrow_web/services/devices_mang_api.dart';
@ -12,14 +13,13 @@ class GateWayBloc extends Bloc<GateWayEvent, GateWayState> {
GateWayBloc() : super(GateWayInitial()) {
on<GateWayFetch>((event, emit) {});
on<GatWayById>(_getGatWayById);
on<GateWayFactoryReset>(_onFactoryReset);
}
FutureOr<void> _getGatWayById(
GatWayById event, Emitter<GateWayState> emit) async {
FutureOr<void> _getGatWayById(GatWayById event, Emitter<GateWayState> emit) async {
emit(GatewayLoadingState());
try {
List<DeviceModel> devicesList =
await DevicesManagementApi.getDevicesByGatewayId(event.getWayId);
List<DeviceModel> devicesList = await DevicesManagementApi.getDevicesByGatewayId(event.getWayId);
emit(UpdateGatewayState(list: devicesList));
} catch (e) {
@ -27,4 +27,21 @@ class GateWayBloc extends Bloc<GateWayEvent, GateWayState> {
return;
}
}
FutureOr<void> _onFactoryReset(GateWayFactoryReset event, Emitter<GateWayState> emit) async {
emit(GatewayLoadingState());
try {
final response = await DevicesManagementApi().factoryReset(
event.factoryReset,
event.deviceId,
);
if (!response) {
emit(const ErrorState(message: 'Failed'));
} else {
add(GatWayById(event.deviceId));
}
} catch (e) {
emit(ErrorState(message: e.toString()));
}
}
}

View File

@ -18,3 +18,15 @@ class GatWayById extends GateWayEvent {
final String getWayId;
const GatWayById(this.getWayId);
}
class GateWayFactoryReset extends GateWayEvent {
final String deviceId;
final FactoryResetModel factoryReset;
const GateWayFactoryReset({
required this.deviceId,
required this.factoryReset,
});
@override
List<Object> get props => [deviceId, factoryReset];
}

View File

@ -0,0 +1,59 @@
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/gateway/bloc/gate_way_bloc.dart';
import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart';
import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
class GatewayBatchControlView extends StatelessWidget
with HelperResponsiveLayout {
const GatewayBatchControlView({super.key, required this.gatewayIds});
final List<String> gatewayIds;
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => GateWayBloc()..add(GatWayById(gatewayIds.first)),
child: BlocBuilder<GateWayBloc, GateWayState>(
builder: (context, state) {
if (state is GatewayLoadingState) {
return const Center(child: CircularProgressIndicator());
} else if (state is UpdateGatewayState) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
width: 170,
height: 140,
child: FirmwareUpdateWidget(
deviceId: gatewayIds.first, version: 2)),
const SizedBox(
width: 12,
),
SizedBox(
width: 170,
height: 140,
child: FactoryResetWidget(
callFactoryReset: () {
context.read<GateWayBloc>().add(
GateWayFactoryReset(
deviceId: gatewayIds.first,
factoryReset:
FactoryResetModel(devicesUuid: gatewayIds),
),
);
},
),
),
],
);
} else {
return const Center(child: Text('Error fetching status'));
}
},
),
);
}
}

View File

@ -5,15 +5,17 @@ import 'package:syncrow_web/pages/device_managment/gateway/bloc/gate_way_bloc.da
import 'package:syncrow_web/pages/device_managment/shared/device_controls_container.dart';
import 'package:syncrow_web/pages/visitor_password/model/device_model.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
class GateWayControls extends StatelessWidget with HelperResponsiveLayout {
const GateWayControls({super.key, required this.gatewayId});
class GateWayControlsView extends StatelessWidget with HelperResponsiveLayout {
const GateWayControlsView({super.key, required this.gatewayId});
final String gatewayId;
@override
Widget build(BuildContext context) {
final isExtraLarge = isExtraLargeScreenSize(context);
final isLarge = isLargeScreenSize(context);
final isMedium = isMediumScreenSize(context);
@ -24,28 +26,64 @@ class GateWayControls extends StatelessWidget with HelperResponsiveLayout {
if (state is GatewayLoadingState) {
return const Center(child: CircularProgressIndicator());
} else if (state is UpdateGatewayState) {
return GridView.builder(
padding: const EdgeInsets.symmetric(horizontal: 50),
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: isLarge
? 3
: isMedium
? 2
: 1,
mainAxisExtent: 140,
crossAxisSpacing: 12,
mainAxisSpacing: 12,
),
itemCount: state.list.length,
itemBuilder: (context, index) {
final device = state.list[index];
return _DeviceItem(device: device);
},
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
padding: const EdgeInsets.symmetric(horizontal: 50),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
"Bluetooth Devices:",
style: context.textTheme.bodyMedium!.copyWith(
color: ColorsManager.grayColor,
),
),
const SizedBox(height: 12),
Text(
"No devices found",
style: context.textTheme.bodySmall!.copyWith(
color: ColorsManager.blackColor,
),
),
const SizedBox(height: 30),
Text(
"ZigBee Devices:",
style: context.textTheme.bodyMedium!.copyWith(
color: ColorsManager.grayColor,
),
),
],
),
),
const SizedBox(height: 12),
GridView.builder(
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,
),
itemCount: state.list.length,
itemBuilder: (context, index) {
final device = state.list[index];
return _DeviceItem(device: device);
},
),
],
);
} else {
return const Center(child: Text('Error fetching devices'));
return const Center(child: Text('Error fetching status'));
}
},
),
@ -66,26 +104,23 @@ class _DeviceItem extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: 60,
ClipOval(
child: Container(
height: 60,
decoration: const BoxDecoration(
shape: BoxShape.circle,
color: ColorsManager.whiteColors,
width: 60,
padding: const EdgeInsets.all(8),
color: ColorsManager.whiteColors,
child: SvgPicture.asset(
device.icon ?? 'assets/icons/gateway.svg',
width: 35,
height: 35,
fit: BoxFit.contain,
),
margin: const EdgeInsets.symmetric(horizontal: 4),
padding: const EdgeInsets.all(4),
child: ClipOval(
child: SvgPicture.asset(
device.icon,
fit: BoxFit.fill,
),
),
),
)),
const Spacer(),
Text(
device.name ?? 'Unknown Device',
textAlign: TextAlign.center,
textAlign: TextAlign.start,
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 14,

View File

@ -1,29 +0,0 @@
part of 'living_room_bloc.dart';
sealed class LivingRoomEvent extends Equatable {
const LivingRoomEvent();
@override
List<Object> get props => [];
}
class LivingRoomFetchDeviceStatus extends LivingRoomEvent {
final String deviceId;
const LivingRoomFetchDeviceStatus(this.deviceId);
@override
List<Object> get props => [deviceId];
}
class LivingRoomControl extends LivingRoomEvent {
final String deviceId;
final String code;
final bool value;
const LivingRoomControl(
{required this.deviceId, required this.code, required this.value});
@override
List<Object> get props => [deviceId, code, value];
}

View File

@ -1,80 +0,0 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_web/pages/device_managment/living_room_switch/bloc/living_room_bloc.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
class ToggleWidget extends StatelessWidget {
final bool value;
final String code;
final String deviceId;
final String label;
const ToggleWidget({
super.key,
required this.value,
required this.code,
required this.deviceId,
required this.label,
});
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
color: ColorsManager.greyColor.withOpacity(0.2),
border: Border.all(color: ColorsManager.boxDivider),
),
padding: const EdgeInsets.all(16),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
ClipOval(
child: Container(
color: ColorsManager.whiteColors,
child: SvgPicture.asset(
Assets.lightPulp,
width: 60,
height: 60,
fit: BoxFit.cover,
),
)),
SizedBox(
height: 20,
width: 35,
child: CupertinoSwitch(
value: value,
activeColor: ColorsManager.dialogBlueTitle,
onChanged: (newValue) {
context.read<LivingRoomBloc>().add(
LivingRoomControl(
deviceId: deviceId,
code: code,
value: newValue,
),
);
},
),
),
],
),
const Spacer(),
Text(
label,
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 14,
),
),
],
),
);
}
}

View File

@ -0,0 +1,159 @@
import 'dart:async';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
import 'package:syncrow_web/pages/device_managment/main_door_sensor/bloc/main_door_sensor_event.dart';
import 'package:syncrow_web/pages/device_managment/main_door_sensor/bloc/main_door_sensor_state.dart';
import 'package:syncrow_web/pages/device_managment/main_door_sensor/models/main_door_status_model.dart';
import 'package:syncrow_web/services/devices_mang_api.dart';
class MainDoorSensorBloc
extends Bloc<MainDoorSensorEvent, MainDoorSensorState> {
MainDoorSensorBloc() : super(MainDoorSensorInitial()) {
on<MainDoorSensorFetchDeviceEvent>(_onFetchDeviceStatus);
on<MainDoorSensorControl>(_onControl);
on<MainDoorSensorFetchBatchEvent>(_onFetchBatchStatus);
on<MainDoorSensorReportsEvent>(_fetchReports);
on<MainDoorSensorFactoryReset>(_factoryReset);
}
late MainDoorSensorStatusModel deviceStatus;
Timer? _timer;
FutureOr<void> _onFetchDeviceStatus(MainDoorSensorFetchDeviceEvent event,
Emitter<MainDoorSensorState> emit) async {
emit(MainDoorSensorLoadingState());
try {
final status = await DevicesManagementApi()
.getDeviceStatus(event.deviceId)
.then((value) => value.status);
deviceStatus = MainDoorSensorStatusModel.fromJson(event.deviceId, status);
emit(MainDoorSensorDeviceStatusLoaded(deviceStatus));
} catch (e) {
emit(MainDoorSensorFailedState(error: e.toString()));
}
}
FutureOr<void> _onControl(
MainDoorSensorControl event, Emitter<MainDoorSensorState> emit) async {
final oldValue = _getValueByCode(event.code);
_updateLocalValue(event.code, event.value);
emit(MainDoorSensorDeviceStatusLoaded(deviceStatus));
await _runDebounce(
deviceId: event.deviceId,
code: event.code,
value: event.value,
oldValue: oldValue,
emit: emit,
);
}
Future<void> _runDebounce({
required String deviceId,
required String code,
required bool value,
required bool oldValue,
required Emitter<MainDoorSensorState> emit,
}) async {
if (_timer != null) {
_timer!.cancel();
}
_timer = Timer(const Duration(milliseconds: 500), () async {
try {
final response = await DevicesManagementApi()
.deviceControl(deviceId, Status(code: code, value: value));
if (!response) {
_revertValueAndEmit(deviceId, code, oldValue, emit);
}
} catch (e) {
if (e is DioException && e.response != null) {
debugPrint('Error response: ${e.response?.data}');
}
_revertValueAndEmit(deviceId, code, oldValue, emit);
}
});
}
void _revertValueAndEmit(String deviceId, String code, bool oldValue,
Emitter<MainDoorSensorState> emit) {
_updateLocalValue(code, oldValue);
emit(MainDoorSensorDeviceStatusLoaded(deviceStatus));
}
void _updateLocalValue(String code, bool value) {
switch (code) {
case 'doorcontact_state':
deviceStatus = deviceStatus.copyWith(doorContactState: value);
break;
default:
break;
}
}
bool _getValueByCode(String code) {
switch (code) {
case 'doorcontact_state':
return deviceStatus.doorContactState;
default:
return false;
}
}
// Fetch batch status for multiple devices (if needed)
FutureOr<void> _onFetchBatchStatus(MainDoorSensorFetchBatchEvent event,
Emitter<MainDoorSensorState> emit) async {
emit(MainDoorSensorLoadingState());
try {
// final batchStatus =
// await DevicesManagementApi().getBatchDeviceStatus(event.deviceIds);
// Assuming you need to update multiple devices status here
// You might need a list or map of MainDoorSensorStatusModel for batch processing
// emit(MainDoorSensorBatchStatusLoaded(batchStatus));
} catch (e) {
emit(MainDoorSensorBatchFailedState(error: e.toString()));
}
}
FutureOr<void> _fetchReports(MainDoorSensorReportsEvent event,
Emitter<MainDoorSensorState> emit) async {
emit(MainDoorSensorLoadingState());
try {
final reports = await DevicesManagementApi.getDeviceReportsByDate(
event.deviceId, event.code, event.from, event.to);
emit(MainDoorSensorReportLoaded(reports));
} catch (e) {
emit(MainDoorSensorFailedState(error: e.toString()));
}
}
@override
Future<void> close() {
_timer?.cancel();
return super.close();
}
FutureOr<void> _factoryReset(MainDoorSensorFactoryReset event,
Emitter<MainDoorSensorState> emit) async {
emit(MainDoorSensorLoadingState());
try {
final response = await DevicesManagementApi().factoryReset(
event.factoryReset,
event.deviceId,
);
if (!response) {
emit(MainDoorSensorFailedState(error: 'Failed'));
} else {
add(MainDoorSensorFetchDeviceEvent(event.deviceId));
}
} catch (e) {
emit(MainDoorSensorFailedState(error: e.toString()));
}
}
}

View File

@ -0,0 +1,73 @@
import 'package:equatable/equatable.dart';
import '../../all_devices/models/factory_reset_model.dart';
class MainDoorSensorEvent extends Equatable {
@override
List<Object?> get props => [];
}
class MainDoorSensorFetchDeviceEvent extends MainDoorSensorEvent {
final String deviceId;
MainDoorSensorFetchDeviceEvent(this.deviceId);
@override
List<Object> get props => [deviceId];
}
class MainDoorSensorFetchBatchEvent extends MainDoorSensorEvent {
final String deviceId;
MainDoorSensorFetchBatchEvent(this.deviceId);
@override
List<Object> get props => [deviceId];
}
class MainDoorSensorControl extends MainDoorSensorEvent {
final String deviceId;
final String code;
final bool value;
MainDoorSensorControl(
{required this.deviceId, required this.code, required this.value});
@override
List<Object> get props => [deviceId, code, value];
}
class MainDoorSensorBatchControl extends MainDoorSensorEvent {
final List<String> deviceId;
final String code;
final bool value;
MainDoorSensorBatchControl(
{required this.deviceId, required this.code, required this.value});
@override
List<Object> get props => [deviceId, code, value];
}
class MainDoorSensorReportsEvent extends MainDoorSensorEvent {
final String deviceId;
final String code;
final String from;
final String to;
@override
List<Object> get props => [deviceId, code, from, to];
MainDoorSensorReportsEvent(
{required this.deviceId,
required this.code,
required this.from,
required this.to});
}
class MainDoorSensorFactoryReset extends MainDoorSensorEvent {
final String deviceId;
final FactoryResetModel factoryReset;
MainDoorSensorFactoryReset(
{required this.deviceId, required this.factoryReset});
}

View File

@ -0,0 +1,68 @@
import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_reports.dart';
import 'package:syncrow_web/pages/device_managment/main_door_sensor/models/main_door_status_model.dart';
class MainDoorSensorState extends Equatable {
@override
List<Object?> get props => [];
}
class MainDoorSensorInitial extends MainDoorSensorState {}
class MainDoorSensorLoadingState extends MainDoorSensorState {}
class MainDoorSensorDeviceStatusLoaded extends MainDoorSensorState {
final MainDoorSensorStatusModel status;
MainDoorSensorDeviceStatusLoaded(this.status);
@override
List<Object?> get props => [status];
}
class MainDoorSensorFailedState extends MainDoorSensorState {
final String error;
MainDoorSensorFailedState({required this.error});
@override
List<Object?> get props => [error];
}
class MainDoorSensorBatchFailedState extends MainDoorSensorState {
final String error;
MainDoorSensorBatchFailedState({required this.error});
@override
List<Object?> get props => [error];
}
class MainDoorSensorBatchStatusLoaded extends MainDoorSensorState {
final List<MainDoorSensorStatusModel> status;
MainDoorSensorBatchStatusLoaded(this.status);
@override
List<Object?> get props => [status];
}
class MainDoorSensorReportLoaded extends MainDoorSensorState {
final DeviceReport deviceReport;
MainDoorSensorReportLoaded(this.deviceReport);
@override
List<Object?> get props => [deviceReport];
}
class MainDoorSensorReportsLoadingState extends MainDoorSensorState {}
class MainDoorSensorReportsFailedState extends MainDoorSensorState {
final String error;
MainDoorSensorReportsFailedState({required this.error});
@override
List<Object?> get props => [error];
}

View File

@ -0,0 +1,47 @@
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
class MainDoorSensorStatusModel {
final String uuid;
final bool doorContactState;
final int batteryPercentage;
MainDoorSensorStatusModel({
required this.uuid,
required this.doorContactState,
required this.batteryPercentage,
});
factory MainDoorSensorStatusModel.fromJson(String id, List<Status> jsonList) {
late bool doorContactState = false;
late int batteryPercentage = 0;
for (var status in jsonList) {
switch (status.code) {
case 'doorcontact_state':
doorContactState = status.value ?? false;
break;
case 'battery_percentage':
batteryPercentage = status.value ?? 0;
break;
}
}
return MainDoorSensorStatusModel(
uuid: id,
doorContactState: doorContactState,
batteryPercentage: batteryPercentage,
);
}
MainDoorSensorStatusModel copyWith({
String? uuid,
bool? doorContactState,
int? batteryPercentage,
}) {
return MainDoorSensorStatusModel(
uuid: uuid ?? this.uuid,
doorContactState: doorContactState ?? this.doorContactState,
batteryPercentage: batteryPercentage ?? this.batteryPercentage,
);
}
}

View File

@ -0,0 +1,113 @@
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/main_door_sensor/bloc/main_door_sensor_bloc.dart';
import 'package:syncrow_web/pages/device_managment/main_door_sensor/bloc/main_door_sensor_event.dart';
import 'package:syncrow_web/pages/device_managment/main_door_sensor/bloc/main_door_sensor_state.dart';
import 'package:syncrow_web/pages/device_managment/main_door_sensor/models/main_door_status_model.dart';
import 'package:syncrow_web/pages/device_managment/main_door_sensor/widgets/notification_dialog.dart';
import 'package:syncrow_web/pages/device_managment/shared/icon_name_status_container.dart';
import 'package:syncrow_web/pages/device_managment/shared/table/report_table.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
class MainDoorSensorControlView extends StatelessWidget with HelperResponsiveLayout {
const MainDoorSensorControlView({super.key, required this.device});
final AllDevicesModel device;
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => MainDoorSensorBloc()..add(MainDoorSensorFetchDeviceEvent(device.uuid!)),
child: BlocBuilder<MainDoorSensorBloc, MainDoorSensorState>(
builder: (context, state) {
if (state is MainDoorSensorLoadingState || state is MainDoorSensorReportsLoadingState) {
return const Center(child: CircularProgressIndicator());
} else if (state is MainDoorSensorDeviceStatusLoaded) {
return _buildStatusControls(context, state.status);
} else if (state is MainDoorSensorReportLoaded) {
return ReportsTable(
report: state.deviceReport,
onRowTap: (index) {},
onClose: () {
context.read<MainDoorSensorBloc>().add(MainDoorSensorFetchDeviceEvent(device.uuid!));
},
hideValueShowDescription: true,
mainDoorSensor: true,
);
} else if (state is MainDoorSensorFailedState || state is MainDoorSensorBatchFailedState) {
return const Center(child: Text('Error fetching status'));
} else {
return const Center(child: CircularProgressIndicator());
}
},
));
}
Widget _buildStatusControls(BuildContext context, MainDoorSensorStatusModel status) {
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: [
IconNameStatusContainer(
isFullIcon: true,
name: status.doorContactState ? 'Open' : 'Close',
icon: Assets.openCloseDoor,
onTap: () {},
status: status.doorContactState,
textColor: status.doorContactState ? ColorsManager.red : ColorsManager.blackColor,
paddingAmount: 8,
),
IconNameStatusContainer(
isFullIcon: true,
name: 'Open/Close\nRecord',
icon: Assets.openCloseRecords,
onTap: () {
final from = DateTime.now().subtract(const Duration(days: 30)).millisecondsSinceEpoch;
final to = DateTime.now().millisecondsSinceEpoch;
context.read<MainDoorSensorBloc>().add(
MainDoorSensorReportsEvent(
deviceId: device.uuid!,
code: 'doorcontact_state',
from: from.toString(),
to: to.toString(),
),
);
},
status: false,
textColor: ColorsManager.blackColor,
),
IconNameStatusContainer(
isFullIcon: false,
name: 'Notifications\nSettings',
icon: Assets.mainDoorNotifi,
onTap: () {
showDialog(
context: context,
builder: (context) => const NotificationDialog(),
);
},
status: false,
textColor: ColorsManager.blackColor,
paddingAmount: 14,
),
],
);
}
}

View File

@ -0,0 +1,47 @@
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/main_door_sensor/bloc/main_door_sensor_bloc.dart';
import 'package:syncrow_web/pages/device_managment/main_door_sensor/bloc/main_door_sensor_event.dart';
import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart';
import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
class MainDoorSensorBatchView extends StatelessWidget {
const MainDoorSensorBatchView({super.key, required this.devicesIds});
final List<String> devicesIds;
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
width: 170,
height: 140,
child: FirmwareUpdateWidget(
deviceId: devicesIds.first,
version: 12,
),
),
const SizedBox(
width: 12,
),
SizedBox(
width: 170,
height: 140,
child: FactoryResetWidget(
callFactoryReset: () {
BlocProvider.of<MainDoorSensorBloc>(context).add(
MainDoorSensorFactoryReset(
deviceId: devicesIds.first,
factoryReset: FactoryResetModel(devicesUuid: devicesIds),
),
);
},
),
),
],
);
}
}

View File

@ -0,0 +1,130 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class NotificationDialog extends StatefulWidget {
const NotificationDialog({super.key});
@override
State<NotificationDialog> createState() => _NotificationDialogState();
}
class _NotificationDialogState extends State<NotificationDialog> {
bool isLowBatteryNotificationEnabled = true;
bool isClosingRemindersEnabled = true;
bool isDoorAlarmEnabled = true;
@override
Widget build(BuildContext context) {
return Dialog(
backgroundColor: Colors.white,
insetPadding: const EdgeInsets.all(20),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
child: SizedBox(
width: 660,
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const SizedBox(),
Text(
'Notification Settings',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 22,
color: ColorsManager.dialogBlueTitle,
),
),
Container(
width: 25,
decoration: BoxDecoration(
color: Colors.transparent,
shape: BoxShape.circle,
border: Border.all(
color: Colors.grey,
width: 1.0,
),
),
child: IconButton(
padding: EdgeInsets.all(1),
icon: const Icon(
Icons.close,
color: Colors.grey,
size: 18,
),
onPressed: () {
Navigator.of(context).pop();
},
),
),
],
),
const SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
SizedBox(
width: 170,
height: 135,
child: ToggleWidget(
value: isLowBatteryNotificationEnabled,
code: 'notification',
deviceId: '',
label: 'Low Battery',
onChange: (v) {
setState(() {
isLowBatteryNotificationEnabled = v;
});
},
icon: '-1',
),
),
SizedBox(
width: 170,
height: 135,
child: ToggleWidget(
value: isClosingRemindersEnabled,
code: 'notification',
deviceId: '',
label: 'Closing\nReminders',
onChange: (v) {
setState(() {
isClosingRemindersEnabled = v;
});
},
icon: '-1',
),
),
SizedBox(
width: 170,
height: 135,
child: ToggleWidget(
value: isDoorAlarmEnabled,
code: 'notification',
deviceId: '',
label: 'Door Alarm',
onChange: (v) {
setState(() {
isDoorAlarmEnabled = v;
});
},
icon: '-1',
),
),
],
),
],
),
),
),
),
);
}
}

View File

@ -0,0 +1,159 @@
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:meta/meta.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/one_g_glass_switch/models/once_gang_glass_status_model.dart';
import 'package:syncrow_web/services/devices_mang_api.dart';
part 'one_gang_glass_switch_event.dart';
part 'one_gang_glass_switch_state.dart';
class OneGangGlassSwitchBloc extends Bloc<OneGangGlassSwitchEvent, OneGangGlassSwitchState> {
OneGangGlassStatusModel deviceStatus;
Timer? _timer;
OneGangGlassSwitchBloc({required String deviceId})
: deviceStatus = OneGangGlassStatusModel(uuid: deviceId, switch1: false, countDown: 0),
super(OneGangGlassSwitchInitial()) {
on<OneGangGlassSwitchFetchDeviceEvent>(_onFetchDeviceStatus);
on<OneGangGlassSwitchControl>(_onControl);
on<OneGangGlassSwitchBatchControl>(_onBatchControl);
on<OneGangGlassSwitchFetchBatchStatusEvent>(_onFetchBatchStatus);
on<OneGangGlassFactoryResetEvent>(_onFactoryReset);
}
Future<void> _onFetchDeviceStatus(
OneGangGlassSwitchFetchDeviceEvent event, Emitter<OneGangGlassSwitchState> emit) async {
emit(OneGangGlassSwitchLoading());
try {
final status = await DevicesManagementApi().getDeviceStatus(event.deviceId);
deviceStatus = OneGangGlassStatusModel.fromJson(event.deviceId, status.status);
emit(OneGangGlassSwitchStatusLoaded(deviceStatus));
} catch (e) {
emit(OneGangGlassSwitchError(e.toString()));
}
}
Future<void> _onControl(OneGangGlassSwitchControl event, Emitter<OneGangGlassSwitchState> emit) async {
final oldValue = _getValueByCode(event.code);
_updateLocalValue(event.code, event.value);
emit(OneGangGlassSwitchStatusLoaded(deviceStatus));
await _runDebounce(
deviceId: event.deviceId,
code: event.code,
value: event.value,
oldValue: oldValue,
emit: emit,
isBatch: false,
);
}
Future<void> _onFactoryReset(OneGangGlassFactoryResetEvent event, Emitter<OneGangGlassSwitchState> emit) async {
emit(OneGangGlassSwitchLoading());
try {
final response = await DevicesManagementApi().factoryReset(event.factoryReset, event.deviceId);
if (!response) {
emit(OneGangGlassSwitchError('Failed to reset device'));
} else {
emit(OneGangGlassSwitchStatusLoaded(deviceStatus));
}
} catch (e) {
emit(OneGangGlassSwitchError(e.toString()));
}
}
Future<void> _onBatchControl(OneGangGlassSwitchBatchControl event, Emitter<OneGangGlassSwitchState> emit) async {
final oldValue = _getValueByCode(event.code);
_updateLocalValue(event.code, event.value);
emit(OneGangGlassSwitchStatusLoaded(deviceStatus));
await _runDebounce(
deviceId: event.deviceIds,
code: event.code,
value: event.value,
oldValue: oldValue,
emit: emit,
isBatch: true,
);
}
Future<void> _onFetchBatchStatus(
OneGangGlassSwitchFetchBatchStatusEvent event, Emitter<OneGangGlassSwitchState> emit) async {
emit(OneGangGlassSwitchLoading());
try {
final status = await DevicesManagementApi().getBatchStatus(event.deviceIds);
deviceStatus = OneGangGlassStatusModel.fromJson(event.deviceIds.first, status.status);
emit(OneGangGlassSwitchStatusLoaded(deviceStatus));
} catch (e) {
emit(OneGangGlassSwitchError(e.toString()));
}
}
Future<void> _runDebounce({
required dynamic deviceId,
required String code,
required bool value,
required bool oldValue,
required Emitter<OneGangGlassSwitchState> emit,
required bool isBatch,
}) async {
late String id;
if (deviceId is List) {
id = deviceId.first;
} else {
id = deviceId;
}
if (_timer != null) {
_timer!.cancel();
}
_timer = Timer(const Duration(milliseconds: 500), () async {
try {
late bool response;
if (isBatch) {
response = await DevicesManagementApi().deviceBatchControl(deviceId, code, value);
} else {
response = await DevicesManagementApi().deviceControl(deviceId, Status(code: code, value: value));
}
if (!response) {
_revertValueAndEmit(id, code, oldValue, emit);
}
} catch (e) {
_revertValueAndEmit(id, code, oldValue, emit);
}
});
}
void _revertValueAndEmit(String deviceId, String code, bool oldValue, Emitter<OneGangGlassSwitchState> emit) {
_updateLocalValue(code, oldValue);
emit(OneGangGlassSwitchStatusLoaded(deviceStatus));
}
void _updateLocalValue(String code, bool value) {
if (code == 'switch_1') {
deviceStatus = deviceStatus.copyWith(switch1: value);
}
}
bool _getValueByCode(String code) {
switch (code) {
case 'switch_1':
return deviceStatus.switch1;
default:
return false;
}
}
@override
Future<void> close() {
_timer?.cancel();
return super.close();
}
}

View File

@ -0,0 +1,50 @@
part of 'one_gang_glass_switch_bloc.dart';
@immutable
abstract class OneGangGlassSwitchEvent {}
class OneGangGlassSwitchFetchDeviceEvent extends OneGangGlassSwitchEvent {
final String deviceId;
OneGangGlassSwitchFetchDeviceEvent(this.deviceId);
}
class OneGangGlassSwitchControl extends OneGangGlassSwitchEvent {
final String deviceId;
final String code;
final bool value;
OneGangGlassSwitchControl({
required this.deviceId,
required this.code,
required this.value,
});
}
class OneGangGlassSwitchBatchControl extends OneGangGlassSwitchEvent {
final List<String> deviceIds;
final String code;
final bool value;
OneGangGlassSwitchBatchControl({
required this.deviceIds,
required this.code,
required this.value,
});
}
class OneGangGlassSwitchFetchBatchStatusEvent extends OneGangGlassSwitchEvent {
final List<String> deviceIds;
OneGangGlassSwitchFetchBatchStatusEvent(this.deviceIds);
}
class OneGangGlassFactoryResetEvent extends OneGangGlassSwitchEvent {
final FactoryResetModel factoryReset;
final String deviceId;
OneGangGlassFactoryResetEvent({
required this.factoryReset,
required this.deviceId,
});
}

View File

@ -0,0 +1,32 @@
part of 'one_gang_glass_switch_bloc.dart';
@immutable
abstract class OneGangGlassSwitchState {}
class OneGangGlassSwitchInitial extends OneGangGlassSwitchState {}
class OneGangGlassSwitchLoading extends OneGangGlassSwitchState {}
class OneGangGlassSwitchStatusLoaded extends OneGangGlassSwitchState {
final OneGangGlassStatusModel status;
OneGangGlassSwitchStatusLoaded(this.status);
}
class OneGangGlassSwitchError extends OneGangGlassSwitchState {
final String message;
OneGangGlassSwitchError(this.message);
}
class OneGangGlassSwitchBatchStatusLoaded extends OneGangGlassSwitchState {
final OneGangGlassStatusModel status;
OneGangGlassSwitchBatchStatusLoaded(this.status);
}
class OneGangGlassSwitchBatchControlError extends OneGangGlassSwitchState {
final String message;
OneGangGlassSwitchBatchControlError(this.message);
}

View File

@ -0,0 +1,50 @@
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
class OneGangGlassStatusModel {
final String uuid;
final bool switch1;
final int countDown;
OneGangGlassStatusModel({
required this.uuid,
required this.switch1,
required this.countDown,
});
factory OneGangGlassStatusModel.fromJson(String id, List<Status> jsonList) {
late bool switch1;
late int countDown;
for (var status in jsonList) {
switch (status.code) {
case 'switch_1':
switch1 = status.value ?? false;
break;
case 'countdown_1':
countDown = status.value ?? 0;
break;
}
}
return OneGangGlassStatusModel(
uuid: id,
switch1: switch1,
countDown: countDown,
);
}
OneGangGlassStatusModel copyWith({
String? uuid,
bool? switch1,
int? countDown,
}) {
return OneGangGlassStatusModel(
uuid: uuid ?? this.uuid,
switch1: switch1 ?? this.switch1,
countDown: countDown ?? this.countDown,
);
}
@override
String toString() => 'OneGangGlassStatusModel(uuid: $uuid, switch1: $switch1, countDown: $countDown)';
}

View File

@ -0,0 +1,91 @@
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/one_g_glass_switch/bloc/one_gang_glass_switch_bloc.dart';
import 'package:syncrow_web/pages/device_managment/one_g_glass_switch/models/once_gang_glass_status_model.dart';
import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart';
import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
class OneGangGlassSwitchBatchControlView extends StatelessWidget
with HelperResponsiveLayout {
final List<String> deviceIds;
const OneGangGlassSwitchBatchControlView(
{required this.deviceIds, super.key});
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => OneGangGlassSwitchBloc(deviceId: deviceIds.first)
..add(OneGangGlassSwitchFetchBatchStatusEvent(deviceIds)),
child: BlocBuilder<OneGangGlassSwitchBloc, OneGangGlassSwitchState>(
builder: (context, state) {
if (state is OneGangGlassSwitchLoading) {
return const Center(child: CircularProgressIndicator());
} else if (state is OneGangGlassSwitchStatusLoaded) {
return _buildStatusControls(context, state.status);
} else if (state is OneGangGlassSwitchError) {
return const Center(child: Text('Error fetching status'));
} else {
return const Center(child: CircularProgressIndicator());
}
},
),
);
}
Widget _buildStatusControls(
BuildContext context, OneGangGlassStatusModel status) {
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: [
ToggleWidget(
value: status.switch1,
code: 'switch_1',
deviceId: deviceIds.first,
label: 'Wall Light',
onChange: (value) {
context.read<OneGangGlassSwitchBloc>().add(
OneGangGlassSwitchBatchControl(
deviceIds: deviceIds,
code: 'switch_1',
value: value,
),
);
},
),
FirmwareUpdateWidget(
deviceId: deviceIds.first,
version: 12,
),
FactoryResetWidget(
callFactoryReset: () {
context.read<OneGangGlassSwitchBloc>().add(
OneGangGlassFactoryResetEvent(
factoryReset: FactoryResetModel(devicesUuid: deviceIds),
deviceId: deviceIds.first,
),
);
},
),
],
);
}
}

View File

@ -0,0 +1,90 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/one_g_glass_switch/bloc/one_gang_glass_switch_bloc.dart';
import 'package:syncrow_web/pages/device_managment/one_g_glass_switch/models/once_gang_glass_status_model.dart';
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
class OneGangGlassSwitchControlView extends StatelessWidget with HelperResponsiveLayout {
final String deviceId;
const OneGangGlassSwitchControlView({required this.deviceId, Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) =>
OneGangGlassSwitchBloc(deviceId: deviceId)..add(OneGangGlassSwitchFetchDeviceEvent(deviceId)),
child: BlocBuilder<OneGangGlassSwitchBloc, OneGangGlassSwitchState>(
builder: (context, state) {
if (state is OneGangGlassSwitchLoading) {
return const Center(child: CircularProgressIndicator());
} else if (state is OneGangGlassSwitchStatusLoaded) {
return _buildStatusControls(context, state.status);
} else if (state is OneGangGlassSwitchError) {
return const Center(child: Text('Error fetching status'));
} else {
return const Center(child: CircularProgressIndicator());
}
},
),
);
}
Widget _buildStatusControls(BuildContext context, OneGangGlassStatusModel status) {
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: [
ToggleWidget(
value: status.switch1,
code: 'switch_1',
deviceId: deviceId,
label: "Wall Light",
onChange: (value) {
context.read<OneGangGlassSwitchBloc>().add(
OneGangGlassSwitchControl(
deviceId: deviceId,
code: 'switch_1',
value: value,
),
);
},
),
ToggleWidget(
value: false,
code: '',
deviceId: deviceId,
label: 'Preferences',
icon: Assets.preferences,
onChange: (value) {},
showToggle: false,
),
ToggleWidget(
value: false,
code: '',
deviceId: deviceId,
label: 'Scheduling',
icon: Assets.scheduling,
onChange: (value) {},
showToggle: false,
),
],
);
}
}

View File

@ -0,0 +1,175 @@
import 'dart:async';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
import 'package:syncrow_web/pages/device_managment/one_gang_switch/bloc/wall_light_switch_event.dart';
import 'package:syncrow_web/pages/device_managment/one_gang_switch/bloc/wall_light_switch_state.dart';
import 'package:syncrow_web/pages/device_managment/one_gang_switch/models/wall_light_status_model.dart';
import 'package:syncrow_web/services/devices_mang_api.dart';
class WallLightSwitchBloc
extends Bloc<WallLightSwitchEvent, WallLightSwitchState> {
WallLightSwitchBloc({required this.deviceId})
: super(WallLightSwitchInitial()) {
on<WallLightSwitchFetchDeviceEvent>(_onFetchDeviceStatus);
on<WallLightSwitchControl>(_onControl);
on<WallLightSwitchFetchBatchEvent>(_onFetchBatchStatus);
on<WallLightSwitchBatchControl>(_onBatchControl);
on<WallLightFactoryReset>(_onFactoryReset);
}
late WallLightStatusModel deviceStatus;
final String deviceId;
Timer? _timer;
FutureOr<void> _onFetchDeviceStatus(WallLightSwitchFetchDeviceEvent event,
Emitter<WallLightSwitchState> emit) async {
emit(WallLightSwitchLoading());
try {
final status =
await DevicesManagementApi().getDeviceStatus(event.deviceId);
deviceStatus =
WallLightStatusModel.fromJson(event.deviceId, status.status);
emit(WallLightSwitchStatusLoaded(deviceStatus));
} catch (e) {
emit(WallLightSwitchError(e.toString()));
}
}
FutureOr<void> _onControl(
WallLightSwitchControl event, Emitter<WallLightSwitchState> emit) async {
final oldValue = _getValueByCode(event.code);
_updateLocalValue(event.code, event.value);
emit(WallLightSwitchStatusLoaded(deviceStatus));
await _runDebounce(
deviceId: event.deviceId,
code: event.code,
value: event.value,
oldValue: oldValue,
emit: emit,
isBatch: false,
);
}
Future<void> _runDebounce({
required dynamic deviceId,
required String code,
required bool value,
required bool oldValue,
required Emitter<WallLightSwitchState> emit,
required bool isBatch,
}) async {
late String id;
if (deviceId is List) {
id = deviceId.first;
} else {
id = deviceId;
}
if (_timer != null) {
_timer!.cancel();
}
_timer = Timer(const Duration(milliseconds: 500), () async {
try {
late bool response;
if (isBatch) {
response = await DevicesManagementApi()
.deviceBatchControl(deviceId, code, value);
} else {
response = await DevicesManagementApi()
.deviceControl(deviceId, Status(code: code, value: value));
}
if (!response) {
_revertValueAndEmit(id, code, oldValue, emit);
}
} catch (e) {
_revertValueAndEmit(id, code, oldValue, emit);
}
});
}
void _revertValueAndEmit(String deviceId, String code, bool oldValue,
Emitter<WallLightSwitchState> emit) {
_updateLocalValue(code, oldValue);
emit(WallLightSwitchStatusLoaded(deviceStatus));
}
void _updateLocalValue(String code, bool value) {
if (code == 'switch_1') {
deviceStatus = deviceStatus.copyWith(switch1: value);
}
}
bool _getValueByCode(String code) {
switch (code) {
case 'switch_1':
return deviceStatus.switch1;
default:
return false;
}
}
Future<void> _onFetchBatchStatus(WallLightSwitchFetchBatchEvent event,
Emitter<WallLightSwitchState> emit) async {
emit(WallLightSwitchLoading());
try {
final status =
await DevicesManagementApi().getBatchStatus(event.devicesIds);
deviceStatus =
WallLightStatusModel.fromJson(event.devicesIds.first, status.status);
emit(WallLightSwitchStatusLoaded(deviceStatus));
} catch (e) {
emit(WallLightSwitchError(e.toString()));
}
}
@override
Future<void> close() {
_timer?.cancel();
return super.close();
}
FutureOr<void> _onBatchControl(WallLightSwitchBatchControl event,
Emitter<WallLightSwitchState> emit) async {
final oldValue = _getValueByCode(event.code);
_updateLocalValue(event.code, event.value);
emit(WallLightSwitchStatusLoaded(deviceStatus));
await _runDebounce(
deviceId: event.devicesIds,
code: event.code,
value: event.value,
oldValue: oldValue,
emit: emit,
isBatch: true,
);
}
FutureOr<void> _onFactoryReset(
WallLightFactoryReset event, Emitter<WallLightSwitchState> emit) async {
emit(WallLightSwitchLoading());
try {
final response = await DevicesManagementApi().factoryReset(
event.factoryReset,
event.deviceId,
);
if (!response) {
emit(WallLightSwitchError('Failed'));
} else {
emit(WallLightSwitchStatusLoaded(deviceStatus));
}
} catch (e) {
emit(WallLightSwitchError(e.toString()));
}
}
}

View File

@ -0,0 +1,59 @@
import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart';
class WallLightSwitchEvent extends Equatable {
@override
List<Object?> get props => [];
}
class WallLightSwitchFetchDeviceEvent extends WallLightSwitchEvent {
final String deviceId;
WallLightSwitchFetchDeviceEvent(this.deviceId);
@override
List<Object> get props => [deviceId];
}
class WallLightSwitchControl extends WallLightSwitchEvent {
final String deviceId;
final String code;
final bool value;
WallLightSwitchControl(
{required this.deviceId, required this.code, required this.value});
@override
List<Object> get props => [deviceId, code, value];
}
class WallLightSwitchFetchBatchEvent extends WallLightSwitchEvent {
final List<String> devicesIds;
WallLightSwitchFetchBatchEvent(this.devicesIds);
@override
List<Object> get props => [devicesIds];
}
class WallLightSwitchBatchControl extends WallLightSwitchEvent {
final List<String> devicesIds;
final String code;
final bool value;
WallLightSwitchBatchControl(
{required this.devicesIds, required this.code, required this.value});
@override
List<Object> get props => [devicesIds, code, value];
}
class WallLightFactoryReset extends WallLightSwitchEvent {
final String deviceId;
final FactoryResetModel factoryReset;
WallLightFactoryReset({required this.deviceId, required this.factoryReset});
@override
List<Object> get props => [deviceId, factoryReset];
}

View File

@ -0,0 +1,56 @@
import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/device_managment/one_gang_switch/models/wall_light_status_model.dart';
class WallLightSwitchState extends Equatable {
@override
List<Object?> get props => [];
}
class WallLightSwitchInitial extends WallLightSwitchState {}
class WallLightSwitchLoading extends WallLightSwitchState {}
class WallLightSwitchStatusLoaded extends WallLightSwitchState {
final WallLightStatusModel status;
WallLightSwitchStatusLoaded(this.status);
@override
List<Object> get props => [status];
}
class WallLightSwitchError extends WallLightSwitchState {
final String message;
WallLightSwitchError(this.message);
@override
List<Object> get props => [message];
}
class WallLightSwitchControlError extends WallLightSwitchState {
final String message;
WallLightSwitchControlError(this.message);
@override
List<Object> get props => [message];
}
class WallLightSwitchBatchControlError extends WallLightSwitchState {
final String message;
WallLightSwitchBatchControlError(this.message);
@override
List<Object> get props => [message];
}
class WallLightSwitchBatchStatusLoaded extends WallLightSwitchState {
final List<String> status;
WallLightSwitchBatchStatusLoaded(this.status);
@override
List<Object> get props => [status];
}

View File

@ -0,0 +1,47 @@
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
class WallLightStatusModel {
final String uuid;
final bool switch1;
final int countDown;
WallLightStatusModel({
required this.uuid,
required this.switch1,
required this.countDown,
});
factory WallLightStatusModel.fromJson(String id, List<Status> jsonList) {
late bool switch1;
late int countDown;
for (var status in jsonList) {
switch (status.code) {
case 'switch_1':
switch1 = status.value ?? false;
break;
case 'countdown_1':
countDown = status.value ?? 0;
break;
}
}
return WallLightStatusModel(
uuid: id,
switch1: switch1,
countDown: countDown,
);
}
WallLightStatusModel copyWith({
String? uuid,
bool? switch1,
int? countDown,
}) {
return WallLightStatusModel(
uuid: uuid ?? this.uuid,
switch1: switch1 ?? this.switch1,
countDown: countDown ?? this.countDown,
);
}
}

View File

@ -0,0 +1,92 @@
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/one_gang_switch/bloc/wall_light_switch_bloc.dart';
import 'package:syncrow_web/pages/device_managment/one_gang_switch/bloc/wall_light_switch_event.dart';
import 'package:syncrow_web/pages/device_managment/one_gang_switch/bloc/wall_light_switch_state.dart';
import 'package:syncrow_web/pages/device_managment/one_gang_switch/models/wall_light_status_model.dart';
import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart';
import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
class WallLightBatchControlView extends StatelessWidget
with HelperResponsiveLayout {
const WallLightBatchControlView({super.key, required this.deviceIds});
final List<String> deviceIds;
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => WallLightSwitchBloc(deviceId: deviceIds.first)
..add(WallLightSwitchFetchBatchEvent(deviceIds)),
child: BlocBuilder<WallLightSwitchBloc, WallLightSwitchState>(
builder: (context, state) {
if (state is WallLightSwitchLoading) {
return const Center(child: CircularProgressIndicator());
} else if (state is WallLightSwitchStatusLoaded) {
return _buildStatusControls(context, state.status);
} else if (state is WallLightSwitchError ||
state is WallLightSwitchControlError) {
return const Center(child: Text('Error fetching status'));
} else {
return const Center(child: CircularProgressIndicator());
}
},
),
);
}
Widget _buildStatusControls(
BuildContext context, WallLightStatusModel status) {
final isExtraLarge = isExtraLargeScreenSize(context);
final isLarge = isLargeScreenSize(context);
final isMedium = isMediumScreenSize(context);
return SizedBox(
child: 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: [
ToggleWidget(
value: status.switch1,
code: 'switch_1',
deviceId: deviceIds.first,
label: 'Wall Light',
onChange: (value) {
context.read<WallLightSwitchBloc>().add(
WallLightSwitchBatchControl(
devicesIds: deviceIds,
code: 'switch_1',
value: value,
),
);
},
),
FirmwareUpdateWidget(
deviceId: deviceIds.first,
version: 12,
),
FactoryResetWidget(
callFactoryReset: () {
context.read<WallLightSwitchBloc>().add(WallLightFactoryReset(
deviceId: status.uuid,
factoryReset: FactoryResetModel(devicesUuid: deviceIds)));
},
),
],
),
);
}
}

View File

@ -0,0 +1,75 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/one_gang_switch/bloc/wall_light_switch_bloc.dart';
import 'package:syncrow_web/pages/device_managment/one_gang_switch/bloc/wall_light_switch_event.dart';
import 'package:syncrow_web/pages/device_managment/one_gang_switch/bloc/wall_light_switch_state.dart';
import 'package:syncrow_web/pages/device_managment/one_gang_switch/models/wall_light_status_model.dart';
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
class WallLightDeviceControl extends StatelessWidget
with HelperResponsiveLayout {
final String deviceId;
const WallLightDeviceControl({super.key, required this.deviceId});
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => WallLightSwitchBloc(deviceId: deviceId)
..add(WallLightSwitchFetchDeviceEvent(deviceId)),
child: BlocBuilder<WallLightSwitchBloc, WallLightSwitchState>(
builder: (context, state) {
if (state is WallLightSwitchLoading) {
return const Center(child: CircularProgressIndicator());
} else if (state is WallLightSwitchStatusLoaded) {
return _buildStatusControls(context, state.status);
} else if (state is WallLightSwitchError ||
state is WallLightSwitchControlError) {
return const Center(child: Text('Error fetching status'));
} else {
return const Center(child: CircularProgressIndicator());
}
},
),
);
}
Widget _buildStatusControls(
BuildContext context, WallLightStatusModel status) {
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: [
const SizedBox(),
ToggleWidget(
value: status.switch1,
code: 'switch_1',
deviceId: deviceId,
label: 'Wall Light',
onChange: (value) {
context.read<WallLightSwitchBloc>().add(WallLightSwitchControl(
deviceId: deviceId,
code: 'switch_1',
value: value,
));
},
),
const SizedBox(),
],
);
}
}

View File

@ -0,0 +1,792 @@
import 'dart:async';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:intl/intl.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
import 'package:syncrow_web/pages/device_managment/power_clamp/bloc/smart_power_event.dart';
import 'package:syncrow_web/pages/device_managment/power_clamp/bloc/smart_power_state.dart';
import 'package:syncrow_web/pages/device_managment/power_clamp/models/device_event.dart';
import 'package:syncrow_web/pages/device_managment/power_clamp/models/power_clamp_batch_model.dart';
import 'package:syncrow_web/pages/device_managment/power_clamp/models/power_clamp_model.dart';
import 'package:syncrow_web/pages/device_managment/power_clamp/view/power_chart.dart';
import 'package:syncrow_web/services/devices_mang_api.dart';
class SmartPowerBloc extends Bloc<SmartPowerEvent, SmartPowerState> {
SmartPowerBloc({required this.deviceId}) : super(SmartPowerInitial()) {
on<SmartPowerFetchDeviceEvent>(_onFetchDeviceStatus);
on<SmartPowerArrowPressedEvent>(_onArrowPressed);
on<SmartPowerFetchBatchEvent>(_onFetchBatchStatus);
on<SmartPowerPageChangedEvent>(_onPageChanged);
on<PowerBatchControlEvent>(_onBatchControl);
on<FilterRecordsByDateEvent>(_filterRecordsByDate);
on<SelectDateEvent>(checkDayMonthYearSelected);
on<SmartPowerFactoryReset>(_onFactoryReset);
}
late PowerClampModel deviceStatus;
late PowerClampBatchModel deviceBatchStatus;
final String deviceId;
Timer? _timer;
List<Map<String, dynamic>> phaseData = [];
int currentPage = 0;
List<EventDevice> record = [
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2024-10-23 11:15:43'),
value: '2286'),
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2024-10-23 11:15:35'),
value: '2285'),
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2024-10-23 11:15:29'),
value: '2284'),
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2024-10-23 11:15:25'),
value: '2285'),
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2024-10-23 11:15:21'),
value: '2284'),
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2024-10-23 11:15:17'),
value: '2285'),
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2024-10-23 11:15:07'),
value: '2286'),
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2024-10-23 11:14:47'),
value: '2285'),
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2024-10-23 11:14:40'),
value: '2284'),
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2024-10-23 11:14:23'),
value: '2285'),
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2024-10-23 11:14:13'),
value: '2284'),
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2023-10-23 11:15:43'),
value: '2286'),
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2023-10-23 11:15:35'),
value: '2285'),
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2023-10-23 11:15:29'),
value: '2284'),
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2023-10-23 11:15:25'),
value: '2285'),
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2023-10-23 11:15:21'),
value: '2284'),
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2023-10-23 11:15:17'),
value: '2285'),
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2023-10-23 11:15:07'),
value: '2286'),
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2023-10-23 11:14:47'),
value: '2285'),
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2023-10-23 11:14:40'),
value: '2284'),
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2023-10-23 11:14:23'),
value: '2285'),
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2023-10-23 11:14:13'),
value: '2284'),
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2023-02-23 11:15:43'),
value: '2286'),
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2023-02-23 11:15:35'),
value: '2285'),
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2023-02-23 11:15:29'),
value: '2284'),
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2023-02-23 11:15:25'),
value: '2285'),
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2023-02-23 11:15:21'),
value: '2284'),
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2023-02-23 11:15:17'),
value: '2285'),
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2023-02-23 11:15:07'),
value: '2286'),
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2023-02-23 11:14:47'),
value: '2285'),
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2023-02-23 11:14:40'),
value: '2284'),
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2023-02-23 11:14:23'),
value: '2285'),
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2023-02-23 11:14:13'),
value: '2284'),
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2023-02-11 11:15:43'),
value: '2286'),
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2023-02-11 11:15:35'),
value: '2285'),
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2023-02-12 11:15:29'),
value: '2284'),
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2023-02-13 11:15:25'),
value: '2285'),
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2023-02-14 11:15:21'),
value: '2284'),
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2023-02-15 11:15:17'),
value: '2285'),
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2023-02-16 11:15:07'),
value: '2286'),
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2023-02-17 11:14:47'),
value: '2285'),
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2023-02-18 11:14:40'),
value: '2284'),
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2023-02-19 11:14:23'),
value: '2285'),
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2023-02-20 11:14:13'),
value: '2284'),
];
FutureOr<void> _onFetchDeviceStatus(
SmartPowerFetchDeviceEvent event, Emitter<SmartPowerState> emit) async {
emit(SmartPowerLoading());
try {
var status =
await DevicesManagementApi().getPowerClampInfo(event.deviceId);
deviceStatus = PowerClampModel.fromJson(status);
phaseData = [
{
'name': 'Phase A',
'voltage': '${deviceStatus.status.phaseA.dataPoints[0].value / 10} V',
'current': '${deviceStatus.status.phaseA.dataPoints[1].value / 10} A',
'activePower': '${deviceStatus.status.phaseA.dataPoints[2].value} W',
'powerFactor': '${deviceStatus.status.phaseA.dataPoints[3].value}',
},
{
'name': 'Phase B',
'voltage': '${deviceStatus.status.phaseB.dataPoints[0].value / 10} V',
'current': '${deviceStatus.status.phaseB.dataPoints[1].value / 10} A',
'activePower': '${deviceStatus.status.phaseB.dataPoints[2].value} W',
'powerFactor': '${deviceStatus.status.phaseB.dataPoints[3].value}',
},
{
'name': 'Phase C',
'voltage': '${deviceStatus.status.phaseC.dataPoints[0].value / 10} V',
'current': '${deviceStatus.status.phaseC.dataPoints[1].value / 10} A',
'activePower': '${deviceStatus.status.phaseC.dataPoints[2].value} W',
'powerFactor': '${deviceStatus.status.phaseC.dataPoints[3].value}',
},
];
emit(GetDeviceStatus());
} catch (e) {
emit(SmartPowerError(e.toString()));
}
}
FutureOr<void> _onArrowPressed(
SmartPowerArrowPressedEvent event, Emitter<SmartPowerState> emit) {
currentPage = (currentPage + event.direction + 4) % 4;
emit(SmartPowerStatusLoaded(deviceStatus, currentPage));
emit(GetDeviceStatus());
}
FutureOr<void> _onPageChanged(
SmartPowerPageChangedEvent event, Emitter<SmartPowerState> emit) {
currentPage = event.page;
emit(SmartPowerStatusLoaded(deviceStatus, currentPage));
emit(GetDeviceStatus());
}
Future<void> _onFactoryReset(
SmartPowerFactoryReset event, Emitter<SmartPowerState> emit) async {
emit(SmartPowerLoading());
try {
final response = await DevicesManagementApi().factoryReset(
event.factoryReset,
event.deviceId,
);
if (response) {
emit(SmartPowerInitial());
} else {
emit(SmartPowerError('Factory reset failed'));
}
} catch (e) {
emit(SmartPowerError(e.toString()));
}
}
Future<void> _onBatchControl(
PowerBatchControlEvent event, Emitter<SmartPowerState> emit) async {
final oldValue = deviceStatus.status;
_updateLocalValue(event.code, event.value);
// emit(WaterLeakBatchStatusLoadedState(deviceStatus!));
await _runDebounce(
deviceId: event.deviceIds,
code: event.code,
value: event.value,
oldValue: oldValue,
emit: emit,
isBatch: true,
);
}
Future<void> _onFetchBatchStatus(
SmartPowerFetchBatchEvent event, Emitter<SmartPowerState> emit) async {
emit(SmartPowerLoading());
try {
final response =
await DevicesManagementApi().getPowerStatus(event.devicesIds);
PowerClampBatchModel deviceStatus =
PowerClampBatchModel.fromJson(response);
emit(SmartPowerLoadBatchControll(deviceStatus));
} catch (e) {
debugPrint('=========error====$e');
emit(SmartPowerError(e.toString()));
}
}
Future<void> _runDebounce({
required dynamic deviceId,
required String code,
required dynamic value,
required dynamic oldValue,
required Emitter<SmartPowerState> emit,
required bool isBatch,
}) async {
late String id;
if (deviceId is List) {
id = deviceId.first;
} else {
id = deviceId;
}
if (_timer != null) {
_timer!.cancel();
}
_timer = Timer(const Duration(milliseconds: 500), () async {
try {
late bool response;
if (isBatch) {
response = await DevicesManagementApi()
.deviceBatchControl(deviceId, code, value);
} else {
response = await DevicesManagementApi()
.deviceControl(deviceId, Status(code: code, value: value));
}
if (!response) {
_revertValueAndEmit(id, code, oldValue, emit);
}
} catch (e) {
_revertValueAndEmit(id, code, oldValue, emit);
}
});
}
void _updateLocalValue(String code, dynamic value) {
if (code == 'watersensor_state') {
deviceStatus = deviceStatus.copyWith(statusPower: value);
} else if (code == 'battery_percentage') {
deviceStatus = deviceStatus.copyWith(statusPower: value);
}
}
void _revertValueAndEmit(String deviceId, String code, dynamic oldValue,
Emitter<SmartPowerState> emit) {
_updateLocalValue(code, oldValue);
emit(SmartPowerLoadBatchControll(deviceBatchStatus));
}
@override
Future<void> close() {
_timer?.cancel();
return super.close();
}
List<EventDevice> filteredRecords = [];
int currentIndex = 0;
final List<String> views = ['Day', 'Month', 'Year'];
Widget dateSwitcher() {
void switchView(int direction) {
currentIndex = (currentIndex + direction + views.length) % views.length;
}
return StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
IconButton(
icon: const Icon(Icons.arrow_left),
onPressed: () {
setState(() {
switchView(-1);
});
},
),
Text(
views[currentIndex],
style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w500),
),
IconButton(
icon: const Icon(Icons.arrow_right),
onPressed: () {
setState(() {
switchView(1);
});
},
),
],
);
},
);
}
Future<DateTime?> selectMonthAndYear(BuildContext context) async {
int selectedYear = DateTime.now().year;
int selectedMonth = DateTime.now().month;
FixedExtentScrollController yearController =
FixedExtentScrollController(initialItem: selectedYear - 1905);
FixedExtentScrollController monthController =
FixedExtentScrollController(initialItem: selectedMonth - 1);
return await showDialog<DateTime>(
context: context,
builder: (BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(10),
),
height: 350,
width: 350,
child: Column(
children: [
const Padding(
padding: EdgeInsets.all(16.0),
child: Text(
'Select Month and Year',
style:
TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
),
const Divider(),
Expanded(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Spacer(),
Expanded(
child: ListWheelScrollView.useDelegate(
controller: yearController,
overAndUnderCenterOpacity: 0.2,
itemExtent: 50,
onSelectedItemChanged: (index) {
selectedYear = 1905 + index;
},
childDelegate: ListWheelChildBuilderDelegate(
builder: (context, index) {
return Center(
child: Text(
(1905 + index).toString(),
style: const TextStyle(fontSize: 18),
),
);
},
childCount: 200,
),
),
),
Expanded(
flex: 2,
child: ListWheelScrollView.useDelegate(
controller: monthController,
overAndUnderCenterOpacity: 0.2,
itemExtent: 50,
onSelectedItemChanged: (index) {
selectedMonth = index + 1;
},
childDelegate: ListWheelChildBuilderDelegate(
builder: (context, index) {
return Center(
child: Text(
DateFormat.MMMM()
.format(DateTime(0, index + 1)),
style: const TextStyle(fontSize: 18),
),
);
},
childCount: 12,
),
),
),
const Spacer(),
],
),
),
const Divider(),
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16.0, vertical: 8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
TextButton(
child: const Text('Cancel'),
onPressed: () {
Navigator.of(context).pop();
},
),
TextButton(
child: const Text('OK'),
onPressed: () {
final selectedDateTime =
DateTime(selectedYear, selectedMonth);
Navigator.of(context).pop(selectedDateTime);
},
),
],
),
),
],
),
),
],
);
},
);
}
Future<DateTime?> selectYear(BuildContext context) async {
int selectedYear = DateTime.now().year;
FixedExtentScrollController yearController =
FixedExtentScrollController(initialItem: selectedYear - 1905);
return await showDialog<DateTime>(
context: context,
builder: (BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(10),
),
height: 350,
width: 350,
child: Column(
children: [
const Padding(
padding: EdgeInsets.all(16.0),
child: Text(
'Select Year',
style:
TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
),
const Divider(),
Expanded(
child: ListWheelScrollView.useDelegate(
controller: yearController,
overAndUnderCenterOpacity: 0.2,
itemExtent: 50,
onSelectedItemChanged: (index) {
selectedYear = 1905 + index;
},
childDelegate: ListWheelChildBuilderDelegate(
builder: (context, index) {
return Center(
child: Text(
(1905 + index).toString(),
style: const TextStyle(fontSize: 18),
),
);
},
childCount: 200,
),
),
),
const Divider(),
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16.0, vertical: 8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
TextButton(
child: const Text('Cancel'),
onPressed: () {
Navigator.of(context).pop();
},
),
TextButton(
child: const Text('OK'),
onPressed: () {
final selectedDateTime = DateTime(selectedYear);
Navigator.of(context).pop(selectedDateTime);
},
),
],
),
),
],
),
),
],
);
},
);
}
Future<DateTime?> dayMonthYearPicker({
required BuildContext context,
}) async {
DateTime selectedDate = DateTime.now();
return await showDialog<DateTime>(
context: context,
builder: (BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
height: 350,
width: 350,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(10),
),
child: Column(
children: [
Expanded(
child: CupertinoDatePicker(
mode: CupertinoDatePickerMode.date,
initialDateTime: DateTime.now(),
minimumYear: 1900,
maximumYear: DateTime.now().year,
onDateTimeChanged: (DateTime newDateTime) {
selectedDate = newDateTime;
},
),
),
const Divider(),
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16.0, vertical: 8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
TextButton(
child: const Text('Cancel'),
onPressed: () {
Navigator.of(context).pop();
},
),
TextButton(
child: const Text('OK'),
onPressed: () {
Navigator.of(context).pop(selectedDate);
},
),
],
),
),
],
),
),
],
);
},
);
}
DateTime? dateTime = DateTime.now();
String formattedDate = DateFormat('yyyy/MM/dd').format(DateTime.now());
void checkDayMonthYearSelected(
SelectDateEvent event, Emitter<SmartPowerState> emit) async {
Future<DateTime?> Function(BuildContext context)? dateSelector;
String dateFormat;
switch (currentIndex) {
case 0:
dateSelector = (context) {
return dayMonthYearPicker(context: context);
};
dateFormat = 'yyyy/MM/dd';
break;
case 1:
dateSelector = (context) {
return selectMonthAndYear(context);
};
dateFormat = 'yyyy-MM';
break;
case 2:
dateSelector = (context) {
return selectYear(context);
};
dateFormat = 'yyyy';
break;
default:
return;
}
Future.delayed(const Duration(milliseconds: 500), () {
emit(FakeState());
});
// Use the selected picker
await dateSelector(event.context).then((newDate) {
if (newDate.toString() == 'null') {
emit(GetDeviceStatus());
} else {
dateTime = newDate;
add(FilterRecordsByDateEvent(
selectedDate: newDate!,
viewType: views[currentIndex],
));
}
// formattedDate = newDate.toString();
});
emit(FilterRecordsState(filteredRecords: energyDataList));
}
List<EnergyData> energyDataList = [];
void _filterRecordsByDate(
FilterRecordsByDateEvent event, Emitter<SmartPowerState> emit) {
// emit(SmartPowerLoading());
if (event.viewType == 'Year') {
formattedDate = event.selectedDate.year.toString();
filteredRecords = record
.where((record) => record.eventTime!.year == event.selectedDate.year)
.toList();
} else if (event.viewType == 'Month') {
formattedDate =
"${event.selectedDate.year.toString()}-${getMonthShortName(event.selectedDate.month)}";
filteredRecords = record
.where((record) =>
record.eventTime!.year == event.selectedDate.year &&
record.eventTime!.month == event.selectedDate.month)
.toList();
} else if (event.viewType == 'Day') {
formattedDate =
"${event.selectedDate.year.toString()}-${getMonthShortName(event.selectedDate.month)}-${event.selectedDate.day}";
filteredRecords = record
.where((record) =>
record.eventTime!.year == event.selectedDate.year &&
record.eventTime!.month == event.selectedDate.month &&
record.eventTime!.day == event.selectedDate.day)
.toList();
}
selectDateRange();
energyDataList = filteredRecords.map((eventDevice) {
return EnergyData(
event.viewType == 'Year'
? getMonthShortName(
int.tryParse(DateFormat('MM').format(eventDevice.eventTime!))!)
: event.viewType == 'Month'
? DateFormat('yyyy/MM/dd').format(eventDevice.eventTime!)
: DateFormat('HH:mm:ss').format(eventDevice.eventTime!),
double.parse(eventDevice.value!),
);
}).toList();
emit(FilterRecordsState(filteredRecords: energyDataList));
}
String getMonthShortName(int month) {
final date = DateTime(0, month);
return DateFormat.MMM().format(date);
}
String endChartDate = '';
void selectDateRange() async {
DateTime startDate = dateTime!;
DateTime endDate = DateTime(startDate.year, startDate.month + 1, 1)
.subtract(Duration(days: 1));
String formattedEndDate = DateFormat('dd/MM/yyyy').format(endDate);
endChartDate = ' - $formattedEndDate';
}
}

View File

@ -0,0 +1,115 @@
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart';
class SmartPowerEvent extends Equatable {
@override
List<Object?> get props => [];
}
class SmartPowerFetchDeviceEvent extends SmartPowerEvent {
final String deviceId;
SmartPowerFetchDeviceEvent(this.deviceId);
@override
List<Object> get props => [deviceId];
}
class SmartPowerControl extends SmartPowerEvent {
final String deviceId;
final String code;
final bool value;
SmartPowerControl(
{required this.deviceId, required this.code, required this.value});
@override
List<Object> get props => [deviceId, code, value];
}
class SmartPowerFetchBatchEvent extends SmartPowerEvent {
final List<String> devicesIds;
SmartPowerFetchBatchEvent(this.devicesIds);
@override
List<Object> get props => [devicesIds];
}
class SmartPowerBatchControl extends SmartPowerEvent {
final List<String> devicesIds;
final String code;
final bool value;
SmartPowerBatchControl(
{required this.devicesIds, required this.code, required this.value});
@override
List<Object> get props => [devicesIds, code, value];
}
class SmartPowerFactoryReset extends SmartPowerEvent {
final String deviceId;
final FactoryResetModel factoryReset;
SmartPowerFactoryReset({required this.deviceId, required this.factoryReset});
@override
List<Object> get props => [deviceId, factoryReset];
}
class PageChangedEvent extends SmartPowerEvent {
final int newPage;
PageChangedEvent(this.newPage);
}
class PageArrowPressedEvent extends SmartPowerEvent {
final int direction;
PageArrowPressedEvent(this.direction);
}
class SmartPowerArrowPressedEvent extends SmartPowerEvent {
final int direction;
SmartPowerArrowPressedEvent(this.direction);
}
class SmartPowerPageChangedEvent extends SmartPowerEvent {
final int page;
SmartPowerPageChangedEvent(this.page);
}
class SelectDateEvent extends SmartPowerEvent {
BuildContext context;
SelectDateEvent({required this.context});
}
class FilterRecordsByDateEvent extends SmartPowerEvent {
final DateTime selectedDate;
final String viewType; // 'Day', 'Month', 'Year'
FilterRecordsByDateEvent(
{required this.selectedDate, required this.viewType});
}
class FetchPowerClampBatchStatusEvent extends SmartPowerEvent {
final List<String> deviceIds;
FetchPowerClampBatchStatusEvent(this.deviceIds);
@override
List<Object> get props => [deviceIds];
}class PowerBatchControlEvent extends SmartPowerEvent {
final List<String> deviceIds;
final String code;
final dynamic value;
PowerBatchControlEvent({
required this.deviceIds,
required this.code,
required this.value,
});
@override
List<Object> get props => [deviceIds, code, value];
}

View File

@ -0,0 +1,77 @@
import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/device_managment/power_clamp/models/power_clamp_batch_model.dart';
import 'package:syncrow_web/pages/device_managment/power_clamp/models/power_clamp_model.dart';
import 'package:syncrow_web/pages/device_managment/power_clamp/view/power_chart.dart';
class SmartPowerState extends Equatable {
@override
List<Object?> get props => [];
}
class SmartPowerInitial extends SmartPowerState {}
class SmartPowerLoading extends SmartPowerState {}
class GetDeviceStatus extends SmartPowerState {}
//GetDeviceStatus
class SmartPowerLoadBatchControll extends SmartPowerState {
final PowerClampBatchModel status;
SmartPowerLoadBatchControll(this.status);
@override
List<Object> get props => [status];
}
class DateSelectedState extends SmartPowerState {}
class FakeState extends SmartPowerState {}
class SmartPowerStatusLoaded extends SmartPowerState {
final PowerClampModel deviceStatus;
final int currentPage;
SmartPowerStatusLoaded(this.deviceStatus, this.currentPage);
}
class SmartPowerError extends SmartPowerState {
final String message;
SmartPowerError(this.message);
@override
List<Object> get props => [message];
}
class SmartPowerControlError extends SmartPowerState {
final String message;
SmartPowerControlError(this.message);
@override
List<Object> get props => [message];
}
class SmartPowerBatchControlError extends SmartPowerState {
final String message;
SmartPowerBatchControlError(this.message);
@override
List<Object> get props => [message];
}
class SmartPowerBatchStatusLoaded extends SmartPowerState {
final List<String> status;
SmartPowerBatchStatusLoaded(this.status);
@override
List<Object> get props => [status];
}
class FilterRecordsState extends SmartPowerState {
final List<EnergyData> filteredRecords;
FilterRecordsState({required this.filteredRecords});
}

View File

@ -0,0 +1,23 @@
class EventDevice {
final String? code;
final DateTime? eventTime;
final String? value;
EventDevice({
this.code,
this.eventTime,
this.value,
});
EventDevice.fromJson(Map<String, dynamic> json)
: code = json['code'] as String?,
eventTime = json['eventTime'] ,
value = json['value'] as String?;
Map<String, dynamic> toJson() => {
'code': code,
'eventTime': eventTime,
'value': value,
};
}

View File

@ -0,0 +1,49 @@
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
abstract class PowerClampModel1 {
String get productUuid;
String get productType;
}
class PowerClampBatchModel extends PowerClampModel1 {
@override
final String productUuid;
@override
final String productType;
final List<Status> status;
PowerClampBatchModel({
required this.productUuid,
required this.productType,
required this.status,
});
factory PowerClampBatchModel.fromJson(Map<String, dynamic> json) {
String productUuid = json['productUuid'] ?? '';
String productType = json['productType'] ?? '';
List<Status> statusList = [];
if (json['status'] != null && json['status'] is List) {
statusList =
(json['status'] as List).map((e) => Status.fromJson(e)).toList();
}
return PowerClampBatchModel(
productUuid: productUuid,
productType: productType,
status: statusList,
);
}
PowerClampBatchModel copyWith({
String? productUuid,
String? productType,
List<Status>? status,
}) {
return PowerClampBatchModel(
productUuid: productUuid ?? this.productUuid,
productType: productType ?? this.productType,
status: status ?? this.status,
);
}
}

View File

@ -0,0 +1,98 @@
// PowerClampModel class to represent the response
class PowerClampModel {
String productUuid;
String productType;
PowerStatus status;
PowerClampModel({
required this.productUuid,
required this.productType,
required this.status,
});
factory PowerClampModel.fromJson(Map<String, dynamic> json) {
return PowerClampModel(
productUuid: json['productUuid'],
productType: json['productType'],
status: PowerStatus.fromJson(json['status']),
);
}
PowerClampModel copyWith({
String? productUuid,
String? productType,
PowerStatus? statusPower,
}) {
return PowerClampModel(
productUuid: productUuid ?? this.productUuid,
productType: productType ?? this.productType,
status: statusPower ?? this.status,
);
}
}
class PowerStatus {
Phase phaseA;
Phase phaseB;
Phase phaseC;
Phase general;
PowerStatus({
required this.phaseA,
required this.phaseB,
required this.phaseC,
required this.general,
});
factory PowerStatus.fromJson(Map<String, dynamic> json) {
return PowerStatus(
phaseA: Phase.fromJson(json['phaseA']),
phaseB: Phase.fromJson(json['phaseB']),
phaseC: Phase.fromJson(json['phaseC']),
general: Phase.fromJson(json['general']
// List<DataPoint>.from(
// json['general'].map((x) => DataPoint.fromJson(x))),
));
}
}
class Phase {
List<DataPoint> dataPoints;
Phase({required this.dataPoints});
factory Phase.fromJson(List<dynamic> json) {
return Phase(
dataPoints: json.map((x) => DataPoint.fromJson(x)).toList(),
);
}
}
class DataPoint {
dynamic code;
dynamic customName;
dynamic dpId;
dynamic time;
dynamic type;
dynamic value;
DataPoint({
required this.code,
required this.customName,
required this.dpId,
required this.time,
required this.type,
required this.value,
});
factory DataPoint.fromJson(Map<String, dynamic> json) {
return DataPoint(
code: json['code'],
customName: json['customName'],
dpId: json['dpId'],
time: json['time'],
type: json['type'],
value: json['value'],
);
}
}

View File

@ -0,0 +1,124 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/device_managment/power_clamp/view/power_info_card.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
class PhaseWidget extends StatefulWidget {
final List<Map<String, dynamic>> phaseData;
PhaseWidget({
required this.phaseData,
});
@override
_PhaseWidgetState createState() => _PhaseWidgetState();
}
class _PhaseWidgetState extends State<PhaseWidget> {
int _selectedPhaseIndex = 0;
@override
Widget build(BuildContext context) {
return Column(
children: [
SizedBox(height: 10),
Row(
children: List.generate(widget.phaseData.length, (index) {
return InkWell(
onTap: () {
setState(() {
_selectedPhaseIndex = index;
});
},
child: Padding(
padding: const EdgeInsets.only(left: 10, right: 10),
child: Text(
widget.phaseData[index]['name'],
style: TextStyle(
fontWeight: FontWeight.bold,
color: _selectedPhaseIndex == index
? Colors.black
: Colors.grey,
),
),
),
);
}),
),
SizedBox(height: 10),
_selectedPhaseIndex == 0
? phase(
totalActive: widget.phaseData[0]['activePower'] ?? '0',
totalCurrent: widget.phaseData[0]['current'] ?? '0',
totalFactor: widget.phaseData[0]['powerFactor'] ?? '0',
totalVoltage: widget.phaseData[0]['voltage'] ?? '0',
)
: _selectedPhaseIndex == 1
? phase(
totalActive: widget.phaseData[1]['activePower'] ?? '0',
totalCurrent: widget.phaseData[1]['current'] ?? '0',
totalFactor: widget.phaseData[1]['powerFactor'] ?? '0',
totalVoltage: widget.phaseData[1]['voltage'] ?? '0',
)
: phase(
totalActive: widget.phaseData[2]['activePower'] ?? '0',
totalCurrent: widget.phaseData[2]['current'] ?? '0',
totalFactor: widget.phaseData[2]['powerFactor'] ?? '0',
totalVoltage: widget.phaseData[2]['voltage'] ?? '0',
),
],
);
}
}
class phase extends StatelessWidget {
const phase({
super.key,
required this.totalVoltage,
required this.totalCurrent,
required this.totalActive,
required this.totalFactor,
});
final String totalVoltage;
final String totalCurrent;
final String totalActive;
final String totalFactor;
@override
Widget build(BuildContext context) {
return Container(
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
PowerClampInfoCard(
iconPath: Assets.voltageIcon,
title: 'Voltage',
value: totalVoltage,
unit: '',
),
PowerClampInfoCard(
iconPath: Assets.voltMeterIcon,
title: 'Current',
value: totalCurrent,
unit: '',
),
PowerClampInfoCard(
iconPath: Assets.powerActiveIcon,
title: 'Active Power',
value: totalActive,
unit: '',
),
PowerClampInfoCard(
iconPath: Assets.speedoMeter,
title: 'Power Factor',
value: totalFactor,
unit: '',
),
],
),
],
),
);
}
}

View File

@ -0,0 +1,281 @@
import 'package:flutter/material.dart';
import 'package:fl_chart/fl_chart.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class EnergyConsumptionPage extends StatefulWidget {
final List<dynamic> chartData;
final double totalConsumption;
final String date;
final String formattedDate;
final Widget widget;
final Function()? onTap;
EnergyConsumptionPage({
required this.chartData,
required this.totalConsumption,
required this.date,
required this.widget,
required this.onTap,
required this.formattedDate,
});
@override
_EnergyConsumptionPageState createState() => _EnergyConsumptionPageState();
}
class _EnergyConsumptionPageState extends State<EnergyConsumptionPage> {
late List<dynamic> _chartData;
@override
void initState() {
_chartData = widget.chartData;
super.initState();
}
@override
Widget build(BuildContext context) {
return Container(
color: ColorsManager.whiteColors,
child: Column(
children: [
const Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Total Consumption',
style: TextStyle(
fontWeight: FontWeight.w700,
fontSize: 20,
),
),
Text(
'8623.20 kWh',
style: TextStyle(
fontWeight: FontWeight.w700,
fontSize: 20,
),
),
],
),
const Row(
children: [
Text(
'Energy consumption',
style: TextStyle(
color: ColorsManager.grayColor,
fontWeight: FontWeight.w700,
fontSize: 12,
),
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
widget.formattedDate,
style: const TextStyle(
color: ColorsManager.grayColor,
fontWeight: FontWeight.w400,
fontSize: 8,
),
),
const Text(
'1000.00 kWh',
style: TextStyle(
color: ColorsManager.grayColor,
fontWeight: FontWeight.w400,
fontSize: 8,
),
),
],
),
Column(
children: [
Padding(
padding: const EdgeInsets.only(top: 10),
child: SizedBox(
height: MediaQuery.of(context).size.height * 0.11,
child: LineChart(
LineChartData(
lineTouchData: LineTouchData(
handleBuiltInTouches: true,
touchSpotThreshold: 2,
getTouchLineEnd: (barData, spotIndex) {
return 10.0;
},
touchTooltipData: LineTouchTooltipData(
getTooltipColor: (touchTooltipItem) => Colors.white,
tooltipRoundedRadius: 10.0,
tooltipPadding: const EdgeInsets.all(8.0),
tooltipBorder: const BorderSide(
color: ColorsManager.grayColor, width: 1),
getTooltipItems: (List<LineBarSpot> touchedSpots) {
return touchedSpots.map((spot) {
return LineTooltipItem(
'${spot.x},\n ${spot.y.toStringAsFixed(2)} kWh',
const TextStyle(
color: Colors.blue,
fontWeight: FontWeight.bold,
fontSize: 12,
),
);
}).toList();
},
)),
titlesData: FlTitlesData(
bottomTitles: const AxisTitles(
sideTitles: SideTitles(
showTitles: false,
),
),
leftTitles: const AxisTitles(
sideTitles: SideTitles(
showTitles: false,
),
),
rightTitles: const AxisTitles(
sideTitles: SideTitles(
showTitles: false,
),
),
topTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: false,
reservedSize: 70,
getTitlesWidget: (value, meta) {
int index = value.toInt();
if (index >= 0 && index < _chartData.length) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: RotatedBox(
quarterTurns: -1,
child: Text(_chartData[index].time,
style: TextStyle(fontSize: 10)),
),
);
}
return const SizedBox.shrink();
},
),
),
),
gridData: FlGridData(
show: true,
drawVerticalLine: true,
horizontalInterval: 1,
verticalInterval: 1,
getDrawingVerticalLine: (value) {
return FlLine(
color: Colors.grey.withOpacity(0.2),
dashArray: [8, 8],
strokeWidth: 1,
);
},
getDrawingHorizontalLine: (value) {
return FlLine(
color: Colors.grey.withOpacity(0.2),
dashArray: [5, 5],
strokeWidth: 1,
);
},
drawHorizontalLine: false,
),
lineBarsData: [
LineChartBarData(
preventCurveOvershootingThreshold: 0.1,
curveSmoothness: 0.5,
preventCurveOverShooting: true,
aboveBarData: BarAreaData(),
spots: _chartData
.asMap()
.entries
.map((entry) => FlSpot(entry.key.toDouble(),
entry.value.consumption))
.toList(),
isCurved: true,
color: ColorsManager.primaryColor.withOpacity(0.6),
show: true,
shadow: const Shadow(color: Colors.black12),
belowBarData: BarAreaData(
show: true,
gradient: LinearGradient(
colors: [
ColorsManager.primaryColor.withOpacity(0.5),
Colors.blue.withOpacity(0.1),
],
begin: Alignment.center,
end: Alignment.bottomCenter,
),
),
dotData: const FlDotData(
show: false,
),
isStrokeCapRound: true,
barWidth: 2,
),
],
borderData: FlBorderData(
show: false,
border: Border.all(
color: Color(0xff023DFE).withOpacity(0.7),
width: 10,
),
),
),
),
),
),
Padding(
padding: const EdgeInsets.all(5.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Container(
decoration: BoxDecoration(
color: ColorsManager.graysColor,
borderRadius: BorderRadius.circular(10),
),
child: Container(child: widget.widget),
),
),
const SizedBox(
width: 20,
),
Expanded(
child: Container(
padding: const EdgeInsets.all(5.0),
decoration: BoxDecoration(
color: ColorsManager.graysColor,
borderRadius: BorderRadius.circular(10),
),
child: InkWell(
onTap: widget.onTap,
child: Center(
child: SizedBox(
child: Padding(
padding: const EdgeInsets.all(5),
child: Text(widget.date),
),
),
),
),
),
),
],
),
)
],
),
],
),
);
}
}
class EnergyData {
EnergyData(this.time, this.consumption);
final String time;
final double consumption;
}

View File

@ -0,0 +1,66 @@
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/power_clamp/bloc/smart_power_bloc.dart';
import 'package:syncrow_web/pages/device_managment/power_clamp/bloc/smart_power_event.dart';
import 'package:syncrow_web/pages/device_managment/power_clamp/bloc/smart_power_state.dart';
import 'package:syncrow_web/pages/device_managment/power_clamp/models/power_clamp_batch_model.dart';
import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart';
import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
class PowerClampBatchControlView extends StatelessWidget
with HelperResponsiveLayout {
final List<String> deviceIds;
const PowerClampBatchControlView({Key? key, required this.deviceIds})
: super(key: key);
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => SmartPowerBloc(deviceId: deviceIds.first)
..add(SmartPowerFetchBatchEvent(deviceIds)),
child: BlocBuilder<SmartPowerBloc, SmartPowerState>(
builder: (context, state) {
if (state is SmartPowerLoading) {
return const Center(child: CircularProgressIndicator());
} else if (state is SmartPowerLoadBatchControll) {
return _buildStatusControls(context, state.status);
} else if (state is SmartPowerError) {
return Center(child: Text('Error: ${state.message}'));
} else {
return const Center(child: CircularProgressIndicator());
}
},
),
);
}
Widget _buildStatusControls(
BuildContext context, PowerClampBatchModel status) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
width: 170,
// height: 140,
child: FirmwareUpdateWidget(deviceId: deviceIds.first, version: 2)),
const SizedBox(
width: 12,
),
SizedBox(
width: 170,
height: 140,
child: FactoryResetWidget(
callFactoryReset: () {
context.read<SmartPowerBloc>().add(SmartPowerFactoryReset(
deviceId: deviceIds.first,
factoryReset: FactoryResetModel(devicesUuid: deviceIds)));
},
),
),
],
);
}
}

View File

@ -0,0 +1,81 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class PowerClampInfoCard extends StatelessWidget {
final String iconPath;
final String title;
final String value;
final String unit;
const PowerClampInfoCard({
Key? key,
required this.iconPath,
required this.title,
required this.value,
required this.unit,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Expanded(
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 6),
decoration: BoxDecoration(
color: ColorsManager.whiteColors,
borderRadius: BorderRadius.circular(20),
),
height: 55,
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.start,
children: [
const SizedBox(
width: 16,
),
SvgPicture.asset(
iconPath,
fit: BoxFit.fill,
),
const SizedBox(
width: 18,
),
Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
title,
style: const TextStyle(
fontSize: 8,
fontWeight: FontWeight.w400,
),
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
value,
style: const TextStyle(
fontSize: 15,
fontWeight: FontWeight.w700,
),
),
Text(
unit,
style: const TextStyle(
fontSize: 15,
fontWeight: FontWeight.w700,
),
),
],
),
],
)
],
),
),
);
}
}

View File

@ -0,0 +1,279 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/power_clamp/bloc/smart_power_bloc.dart';
import 'package:syncrow_web/pages/device_managment/power_clamp/bloc/smart_power_event.dart';
import 'package:syncrow_web/pages/device_managment/power_clamp/bloc/smart_power_state.dart';
import 'package:syncrow_web/pages/device_managment/power_clamp/view/phase_widget.dart';
import 'package:syncrow_web/pages/device_managment/power_clamp/view/power_chart.dart';
import 'package:syncrow_web/pages/device_managment/power_clamp/view/power_info_card.dart';
import 'package:syncrow_web/pages/device_managment/shared/device_controls_container.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
//Smart Power Clamp
class SmartPowerDeviceControl extends StatelessWidget
with HelperResponsiveLayout {
final String deviceId;
const SmartPowerDeviceControl({super.key, required this.deviceId});
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => SmartPowerBloc(deviceId: deviceId)
..add(SmartPowerFetchDeviceEvent(deviceId)),
child: BlocBuilder<SmartPowerBloc, SmartPowerState>(
builder: (context, state) {
final _blocProvider = BlocProvider.of<SmartPowerBloc>(context);
if (state is SmartPowerLoading) {
return const Center(child: CircularProgressIndicator());
} else if (state is FakeState) {
return _buildStatusControls(
currentPage: _blocProvider.currentPage,
context: context,
blocProvider: _blocProvider,
);
} else if (state is GetDeviceStatus) {
return _buildStatusControls(
currentPage: _blocProvider.currentPage,
context: context,
blocProvider: _blocProvider,
);
} else if (state is FilterRecordsState) {
return _buildStatusControls(
currentPage: _blocProvider.currentPage,
context: context,
blocProvider: _blocProvider,
);
}
return const Center(child: CircularProgressIndicator());
// }
},
),
);
}
Widget _buildStatusControls({
required BuildContext context,
required SmartPowerBloc blocProvider,
required int currentPage,
}) {
PageController _pageController = PageController(initialPage: currentPage);
return Container(
padding: const EdgeInsets.symmetric(horizontal: 50),
child: DeviceControlsContainer(
child: Column(
children: [
const Row(
children: [
Text(
'Live',
style: TextStyle(
fontSize: 32,
fontWeight: FontWeight.w700,
color: ColorsManager.textPrimaryColor),
),
],
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 10),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
PowerClampInfoCard(
iconPath: Assets.powerActiveIcon,
title: 'Active',
value: blocProvider
.deviceStatus.status.general.dataPoints[2].value
.toString(),
unit: '',
),
PowerClampInfoCard(
iconPath: Assets.voltMeterIcon,
title: 'Current',
value: blocProvider
.deviceStatus.status.general.dataPoints[1].value
.toString(),
unit: ' A',
),
PowerClampInfoCard(
iconPath: Assets.frequencyIcon,
title: 'Frequency',
value: blocProvider
.deviceStatus.status.general.dataPoints[4].value
.toString(),
unit: ' Hz',
),
],
),
),
PhaseWidget(
phaseData: blocProvider.phaseData,
),
const SizedBox(
height: 10,
),
Container(
padding: const EdgeInsets.only(
top: 10,
left: 20,
right: 20,
bottom: 10,
),
decoration: BoxDecoration(
color: ColorsManager.whiteColors,
borderRadius: BorderRadius.circular(20),
),
height: 300,
child: Column(
children: [
Container(
decoration: BoxDecoration(
color: ColorsManager.graysColor,
borderRadius: BorderRadius.circular(20),
),
height: 50,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
IconButton(
icon: const Icon(Icons.arrow_left),
onPressed: () {
blocProvider.add(SmartPowerArrowPressedEvent(-1));
_pageController.previousPage(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
);
},
),
Text(
currentPage == 0
? 'Total'
: currentPage == 1
? 'Phase A'
: currentPage == 2
? 'Phase B'
: 'Phase C',
style: const TextStyle(fontSize: 18),
),
IconButton(
icon: const Icon(Icons.arrow_right),
onPressed: () {
blocProvider.add(SmartPowerArrowPressedEvent(1));
_pageController.nextPage(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
);
},
),
],
),
),
const SizedBox(
height: 5,
),
Expanded(
flex: 2,
child: PageView(
controller: _pageController,
onPageChanged: (int page) {
blocProvider.add(SmartPowerPageChangedEvent(page));
},
physics: const NeverScrollableScrollPhysics(),
children: [
EnergyConsumptionPage(
formattedDate:
'${blocProvider.dateTime!.day}/${blocProvider.dateTime!.month}/${blocProvider.dateTime!.year} ${blocProvider.endChartDate}',
onTap: () {
blocProvider.add(SelectDateEvent(context: context));
blocProvider.add(FilterRecordsByDateEvent(
selectedDate: blocProvider.dateTime!,
viewType: blocProvider
.views[blocProvider.currentIndex]));
},
widget: blocProvider.dateSwitcher(),
chartData: blocProvider.energyDataList.isNotEmpty
? blocProvider.energyDataList
: [
EnergyData('12:00 AM', 4.0),
EnergyData('01:00 AM', 3.5),
EnergyData('02:00 AM', 3.8),
EnergyData('03:00 AM', 3.2),
EnergyData('04:00 AM', 4.0),
EnergyData('05:00 AM', 3.4),
EnergyData('06:00 AM', 3.2),
EnergyData('07:00 AM', 3.5),
EnergyData('08:00 AM', 3.8),
EnergyData('09:00 AM', 3.6),
EnergyData('10:00 AM', 3.9),
EnergyData('11:00 AM', 4.0),
],
totalConsumption: 10000,
date: blocProvider.formattedDate,
),
EnergyConsumptionPage(
formattedDate:
'${blocProvider.dateTime!.day}/${blocProvider.dateTime!.month}/${blocProvider.dateTime!.year} ${blocProvider.endChartDate}',
onTap: () {
blocProvider.add(SelectDateEvent(context: context));
},
widget: blocProvider.dateSwitcher(),
chartData: blocProvider.energyDataList.isNotEmpty
? blocProvider.energyDataList
: [
EnergyData('12:00 AM', 4.0),
EnergyData('01:00 AM', 3.5),
EnergyData('02:00 AM', 3.8),
EnergyData('03:00 AM', 3.2),
EnergyData('04:00 AM', 4.0),
EnergyData('05:00 AM', 3.4),
EnergyData('06:00 AM', 3.2),
EnergyData('07:00 AM', 3.5),
EnergyData('08:00 AM', 3.8),
EnergyData('09:00 AM', 3.6),
EnergyData('10:00 AM', 3.9),
EnergyData('11:00 AM', 4.0),
],
totalConsumption: 10000,
date: blocProvider.formattedDate,
),
EnergyConsumptionPage(
formattedDate:
'${blocProvider.dateTime!.day}/${blocProvider.dateTime!.month}/${blocProvider.dateTime!.year} ${blocProvider.endChartDate}',
onTap: () {
blocProvider.add(SelectDateEvent(context: context));
},
widget: blocProvider.dateSwitcher(),
chartData: blocProvider.energyDataList.isNotEmpty
? blocProvider.energyDataList
: [
EnergyData('12:00 AM', 4.0),
EnergyData('01:00 AM', 6.5),
EnergyData('02:00 AM', 3.8),
EnergyData('03:00 AM', 3.2),
EnergyData('04:00 AM', 6.0),
EnergyData('05:00 AM', 3.4),
EnergyData('06:00 AM', 5.2),
EnergyData('07:00 AM', 3.5),
EnergyData('08:00 AM', 3.8),
EnergyData('09:00 AM', 5.6),
EnergyData('10:00 AM', 6.9),
EnergyData('11:00 AM', 6.0),
],
totalConsumption: 10000,
date: blocProvider.formattedDate,
),
],
),
),
],
),
),
],
),
),
);
}
}

View File

@ -0,0 +1,122 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
import 'package:syncrow_web/pages/device_managment/shared/device_controls_container.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
class FactoryResetWidget extends StatefulWidget {
const FactoryResetWidget({super.key, required this.callFactoryReset});
final Function() callFactoryReset;
@override
State<FactoryResetWidget> createState() => _FactoryResetWidgetState();
}
class _FactoryResetWidgetState extends State<FactoryResetWidget> {
bool _showConfirmation = false;
void _toggleConfirmation() {
setState(() {
_showConfirmation = !_showConfirmation;
});
}
@override
Widget build(BuildContext context) {
return DeviceControlsContainer(
child: _showConfirmation
? Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Factory Reset',
style: context.textTheme.titleMedium!.copyWith(
fontWeight: FontWeight.bold,
color: ColorsManager.blackColor,
),
),
Text(
'Are you sure?',
style: context.textTheme.bodySmall!.copyWith(
color: ColorsManager.grayColor,
),
),
const SizedBox(height: 16),
Row(
children: [
Flexible(
child: DefaultButton(
height: 20,
elevation: 0,
padding: 0,
onPressed: _toggleConfirmation,
backgroundColor: ColorsManager.greyColor,
child: Text(
'Cancel',
style: context.textTheme.bodyMedium!.copyWith(
color: ColorsManager.blackColor,
fontWeight: FontWeight.w400,
fontSize: 12,
),
),
),
),
const SizedBox(width: 8),
Flexible(
child: DefaultButton(
height: 20,
elevation: 0,
padding: 0,
onPressed: widget.callFactoryReset,
backgroundColor: ColorsManager.red,
child: Text(
'Reset',
style: context.textTheme.bodyMedium!.copyWith(
color: ColorsManager.whiteColors,
fontWeight: FontWeight.w400,
fontSize: 12,
),
),
),
),
],
),
],
)
: GestureDetector(
onTap: _toggleConfirmation,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
ClipOval(
child: Container(
color: ColorsManager.whiteColors,
height: 60,
width: 60,
child: Padding(
padding: const EdgeInsets.all(12.0),
child: SvgPicture.asset(
Assets.factoryReset,
fit: BoxFit.cover,
),
),
),
),
Text(
'Factory Reset',
style: context.textTheme.titleMedium!.copyWith(
fontWeight: FontWeight.w400,
color: ColorsManager.blackColor,
),
),
],
),
),
);
}
}

View File

@ -0,0 +1,128 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
import 'package:syncrow_web/pages/device_managment/shared/device_controls_container.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
class FirmwareUpdateWidget extends StatefulWidget {
const FirmwareUpdateWidget({super.key, required this.deviceId, required this.version});
final String deviceId;
final int version;
@override
State<FirmwareUpdateWidget> createState() => _FirmwareUpdateWidgetState();
}
class _FirmwareUpdateWidgetState extends State<FirmwareUpdateWidget> {
bool _showConfirmation = false;
void _toggleConfirmation() {
setState(() {
_showConfirmation = !_showConfirmation;
});
}
@override
Widget build(BuildContext context) {
return DeviceControlsContainer(
child: _showConfirmation
? Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
children: [
Text(
'Firmware Update',
style: context.textTheme.titleMedium!.copyWith(
fontWeight: FontWeight.bold,
color: ColorsManager.blackColor,
),
),
Text(
'Are you sure?',
style: context.textTheme.bodySmall!.copyWith(
color: ColorsManager.grayColor,
),
),
],
),
Row(
children: [
Flexible(
child: DefaultButton(
height: 20,
elevation: 0,
padding: 0,
onPressed: _toggleConfirmation,
backgroundColor: ColorsManager.greyColor,
child: Text(
'Cancel',
style: context.textTheme.bodyMedium!.copyWith(
color: ColorsManager.blackColor,
fontWeight: FontWeight.w400,
fontSize: 12,
),
),
),
),
const SizedBox(width: 8),
Flexible(
child: DefaultButton(
height: 20,
elevation: 0,
padding: 0,
onPressed: () {
_toggleConfirmation();
},
backgroundColor: ColorsManager.primaryColor,
child: Text(
'Update',
style: context.textTheme.bodyMedium!.copyWith(
color: ColorsManager.whiteColors,
fontWeight: FontWeight.w400,
fontSize: 12,
),
),
),
),
],
),
],
)
: GestureDetector(
onTap: _toggleConfirmation,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
ClipOval(
child: Container(
color: ColorsManager.whiteColors,
height: 60,
width: 60,
child: Padding(
padding: const EdgeInsets.all(12.0),
child: SvgPicture.asset(
Assets.firmware,
fit: BoxFit.cover,
),
),
),
),
Text(
'Firmware Update',
style: context.textTheme.titleMedium!.copyWith(
fontWeight: FontWeight.w400,
color: ColorsManager.blackColor,
),
),
],
),
),
);
}
}

View File

@ -0,0 +1,143 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/helper/route_controls_based_code.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
class DeviceBatchControlDialog extends StatelessWidget with RouteControlsBasedCode {
final List<AllDevicesModel> devices;
const DeviceBatchControlDialog({super.key, required this.devices});
@override
Widget build(BuildContext context) {
return Dialog(
backgroundColor: Colors.white,
insetPadding: const EdgeInsets.all(20),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
child: SizedBox(
width: devices.length < 2 ? 500 : 800,
// height: context.screenHeight * 0.7,
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const SizedBox(),
Column(
children: [
Text(
getBatchDialogName(devices.first),
style: context.textTheme.titleLarge!.copyWith(
color: ColorsManager.dialogBlueTitle,
fontWeight: FontWeight.bold,
),
),
const SizedBox(
height: 8,
),
Text(
"Batch Control",
style: context.textTheme.bodySmall!.copyWith(
color: ColorsManager.dialogBlueTitle,
),
),
],
),
Container(
width: 25,
decoration: BoxDecoration(
color: Colors.transparent,
shape: BoxShape.circle,
border: Border.all(
color: Colors.grey,
width: 1.0,
),
),
child: IconButton(
padding: const EdgeInsets.all(1),
icon: const Icon(
Icons.close,
color: Colors.grey,
size: 18,
),
onPressed: () {
Navigator.of(context).pop();
},
),
),
],
),
const SizedBox(height: 20),
//// BUILD DEVICE CONTROLS
///
//// ROUTE TO SPECIFIC CONTROL VIEW BASED ON DEVICE CATEGORY
routeBatchControlsWidgets(devices: devices),
],
),
),
),
),
);
}
}
String getBatchDialogName(AllDevicesModel device) {
/*
3G:
1G:
2G:
GW:
DL:
WPS:
CPS:
AC:
CUR:
WH:
*/
switch (device.productType) {
case '1G':
return "Smart Light Switch";
case '2G':
return "Smart Light Switch";
case '3G':
return "Smart Light Switch";
case 'GW':
return "Gateway";
case 'DL':
return "Door Lock";
case 'WPS':
return "White Presence Sensor";
case 'CPS':
return "Black Presence Sensor";
case 'CUR':
return "Smart Curtains";
case 'WH':
return "Smart Water Heater";
case 'AC':
return "Smart AC";
case 'DS':
return "Door / Window Sensor";
case '1GT':
return "Touch Switch";
case '2GT':
return "Touch Switch";
case '3GT':
return "Touch Switch";
case 'GD':
return "Garage Door Opener";
case 'WL':
return "Water Leak Sensor";
case 'SOS':
return "SOS";
default:
return device.categoryName ?? 'Device Control';
}
}

View File

@ -1,11 +1,9 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:syncrow_web/core/extension/build_context_x.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/helper/route_controls_based_code.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
import 'package:syncrow_web/pages/device_managment/shared/device_batch_control_dialog.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/format_date_time.dart';
class DeviceControlDialog extends StatelessWidget with RouteControlsBasedCode {
final AllDevicesModel device;
@ -22,7 +20,7 @@ class DeviceControlDialog extends StatelessWidget with RouteControlsBasedCode {
),
child: SizedBox(
width: 798,
height: context.screenHeight * 0.7,
//height: context.screenHeight * 0.7,
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(20.0),
@ -34,7 +32,7 @@ class DeviceControlDialog extends StatelessWidget with RouteControlsBasedCode {
children: [
const SizedBox(),
Text(
device.categoryName ?? 'Device Control',
getBatchDialogName(device),
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 22,
@ -67,7 +65,7 @@ class DeviceControlDialog extends StatelessWidget with RouteControlsBasedCode {
),
const SizedBox(height: 20),
_buildDeviceInfoSection(),
const SizedBox(height: 20),
//const SizedBox(height: 20),
//// BUILD DEVICE CONTROLS
///
//// ROUTE TO SPECIFIC CONTROL VIEW BASED ON DEVICE CATEGORY
@ -87,31 +85,50 @@ class DeviceControlDialog extends StatelessWidget with RouteControlsBasedCode {
children: [
TableRow(
children: [
_buildInfoRow('Product Name:', device.categoryName ?? 'N/A'),
_buildInfoRow('Product Name:', device.productName ?? 'N/A'),
_buildInfoRow('Device ID:', device.uuid ?? ''),
],
),
TableRow(children: [
_buildInfoRow('Virtual Address:',
'Area - Street 1 - Building 1 - First Floor'),
_buildInfoRow('Virtual Address:', device.ip ?? '-'),
const SizedBox.shrink(),
]),
TableRow(
children: [
_buildInfoRow('Unit Name:', device.unit?.name ?? 'N/A'),
_buildInfoRow('Space Name:', device.unit?.name ?? 'N/A'),
_buildInfoRow('Room:', device.room?.name ?? 'N/A'),
],
),
TableRow(
children: [
_buildInfoRow('Installation Date and Time:', '09/08/2024 13:30'),
const SizedBox.shrink(),
_buildInfoRow(
'Installation Date and Time:',
formatDateTime(
DateTime.fromMillisecondsSinceEpoch(
((device.createTime ?? 0) * 1000),
),
),
),
_buildInfoRow(
'Battery Level:',
device.batteryLevel != null ? '${device.batteryLevel ?? 0}%' : "-",
statusColor: device.batteryLevel != null
? (device.batteryLevel! < 20 ? ColorsManager.red : ColorsManager.green)
: null,
),
],
),
TableRow(
children: [
_buildInfoRow('Status:', 'Online', statusColor: Colors.green),
_buildInfoRow('Last Offline Date and Time:', '-'),
_buildInfoRow(
'Last Offline Date and Time:',
formatDateTime(
DateTime.fromMillisecondsSinceEpoch(
((device.updateTime ?? 0) * 1000),
),
),
),
],
),
],

View File

@ -1,20 +1,27 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class DeviceControlsContainer extends StatelessWidget {
const DeviceControlsContainer({required this.child, super.key});
const DeviceControlsContainer({required this.child, this.padding, super.key});
final Widget child;
final double? padding;
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
return Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
color: ColorsManager.greyColor.withOpacity(0.2),
border: Border.all(color: ColorsManager.boxDivider),
),
padding: const EdgeInsets.all(12),
child: child,
elevation: 3,
surfaceTintColor: Colors.transparent,
child: Container(
decoration: BoxDecoration(
color: Colors.grey.shade100,
borderRadius: BorderRadius.circular(20),
),
padding:
EdgeInsets.symmetric(vertical: padding ?? 10, horizontal: padding ?? 16), //EdgeInsets.all(padding ?? 12),
child: child,
),
);
}
}

Some files were not shown because too many files have changed in this diff Show More