mirror of
https://github.com/SyncrowIOT/web.git
synced 2025-07-10 07:07:19 +00:00
Merge branch 'dev' of https://github.com/SyncrowIOT/web into feature/space-management
This commit is contained in:
@ -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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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];
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
161
lib/pages/device_managment/ac/view/ac_device_batch_control.dart
Normal file
161
lib/pages/device_managment/ac/view/ac_device_batch_control.dart
Normal 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'));
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
|
@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
}
|
||||
}),
|
||||
],
|
||||
),
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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,
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
@ -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 {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
},
|
||||
|
@ -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());
|
||||
},
|
||||
);
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
206
lib/pages/device_managment/ceiling_sensor/bloc/ceiling_bloc.dart
Normal file
206
lib/pages/device_managment/ceiling_sensor/bloc/ceiling_bloc.dart
Normal 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()));
|
||||
}
|
||||
}
|
||||
}
|
@ -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];
|
||||
}
|
@ -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 {}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -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,
|
||||
|
161
lib/pages/device_managment/curtain/bloc/curtain_bloc.dart
Normal file
161
lib/pages/device_managment/curtain/bloc/curtain_bloc.dart
Normal 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()));
|
||||
}
|
||||
}
|
||||
}
|
62
lib/pages/device_managment/curtain/bloc/curtain_event.dart
Normal file
62
lib/pages/device_managment/curtain/bloc/curtain_event.dart
Normal 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];
|
||||
}
|
40
lib/pages/device_managment/curtain/bloc/curtain_state.dart
Normal file
40
lib/pages/device_managment/curtain/bloc/curtain_state.dart
Normal 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];
|
||||
}
|
32
lib/pages/device_managment/curtain/model/curtain_model.dart
Normal file
32
lib/pages/device_managment/curtain/model/curtain_model.dart
Normal 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(),
|
||||
};
|
||||
}
|
||||
}
|
@ -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),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -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(),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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];
|
||||
}
|
||||
|
@ -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),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -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'));
|
||||
}
|
||||
},
|
||||
),
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
@ -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];
|
||||
}
|
@ -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];
|
||||
}
|
@ -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();
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
@ -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)';
|
||||
}
|
||||
}
|
@ -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),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -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,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -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);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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(', ');
|
||||
}
|
||||
}
|
@ -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();
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -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),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -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'),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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(),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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];
|
||||
}
|
||||
|
@ -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'));
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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,
|
||||
|
@ -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];
|
||||
}
|
@ -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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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()));
|
||||
}
|
||||
}
|
||||
}
|
@ -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});
|
||||
}
|
@ -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];
|
||||
}
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
@ -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,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -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),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -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',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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,
|
||||
});
|
||||
}
|
@ -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);
|
||||
}
|
@ -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)';
|
||||
}
|
@ -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,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -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,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -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()));
|
||||
}
|
||||
}
|
||||
}
|
@ -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];
|
||||
}
|
@ -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];
|
||||
}
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
@ -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)));
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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(),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -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';
|
||||
}
|
||||
}
|
@ -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];
|
||||
}
|
@ -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});
|
||||
}
|
@ -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,
|
||||
};
|
||||
}
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
@ -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'],
|
||||
);
|
||||
}
|
||||
}
|
124
lib/pages/device_managment/power_clamp/view/phase_widget.dart
Normal file
124
lib/pages/device_managment/power_clamp/view/phase_widget.dart
Normal 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: '',
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
281
lib/pages/device_managment/power_clamp/view/power_chart.dart
Normal file
281
lib/pages/device_managment/power_clamp/view/power_chart.dart
Normal 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;
|
||||
}
|
@ -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)));
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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';
|
||||
}
|
||||
}
|
@ -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),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
|
@ -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
Reference in New Issue
Block a user