Merge pull request #28 from SyncrowIOT/sep_bug_fixes

Sep bug fixes
This commit is contained in:
Abdullah
2024-09-25 00:25:28 +03:00
committed by GitHub
20 changed files with 113 additions and 84 deletions

View File

@ -1,7 +1,5 @@
# syncrow_web # syncrow_web
A new Flutter project.
## Getting Started ## Getting Started
This project is a starting point for a Flutter application. This project is a starting point for a Flutter application.

View File

@ -42,39 +42,36 @@ class DefaultButton extends StatelessWidget {
? null ? null
: customButtonStyle ?? : customButtonStyle ??
ButtonStyle( ButtonStyle(
textStyle: MaterialStateProperty.all( textStyle: WidgetStateProperty.all(
customTextStyle ?? customTextStyle ??
Theme.of(context).textTheme.bodySmall!.copyWith( Theme.of(context).textTheme.bodySmall!.copyWith(
fontSize: 13, fontSize: 13, color: foregroundColor, fontWeight: FontWeight.normal),
color: foregroundColor,
fontWeight: FontWeight.normal),
), ),
foregroundColor: MaterialStateProperty.all( foregroundColor: WidgetStateProperty.all(
isSecondary isSecondary
? Colors.black ? Colors.black
: enabled : enabled
? foregroundColor ?? Colors.white ? foregroundColor ?? Colors.white
: Colors.black, : Colors.black,
), ),
backgroundColor: MaterialStateProperty.resolveWith<Color>( backgroundColor: WidgetStateProperty.resolveWith<Color>((Set<WidgetState> states) {
(Set<MaterialState> states) {
return enabled return enabled
? backgroundColor ?? ColorsManager.primaryColor ? backgroundColor ?? ColorsManager.primaryColor
: Colors.black.withOpacity(0.2); : Colors.black.withOpacity(0.2);
}), }),
shape: MaterialStateProperty.all( shape: WidgetStateProperty.all(
RoundedRectangleBorder( RoundedRectangleBorder(
side: BorderSide(color: borderColor ?? Colors.transparent), side: BorderSide(color: borderColor ?? Colors.transparent),
borderRadius: BorderRadius.circular(borderRadius ?? 20), borderRadius: BorderRadius.circular(borderRadius ?? 20),
), ),
), ),
fixedSize: MaterialStateProperty.all( fixedSize: WidgetStateProperty.all(
const Size.fromHeight(50), const Size.fromHeight(50),
), ),
padding: MaterialStateProperty.all( padding: WidgetStateProperty.all(
EdgeInsets.all(padding ?? 10), EdgeInsets.all(padding ?? 10),
), ),
minimumSize: MaterialStateProperty.all( minimumSize: WidgetStateProperty.all(
const Size.fromHeight(50), const Size.fromHeight(50),
), ),
), ),

View File

@ -17,6 +17,7 @@ class DynamicTable extends StatefulWidget {
final void Function(int, bool, dynamic)? onRowSelected; final void Function(int, bool, dynamic)? onRowSelected;
final List<String>? initialSelectedIds; final List<String>? initialSelectedIds;
final int uuidIndex; final int uuidIndex;
final Function(dynamic selectedRows)? onSelectionChanged;
const DynamicTable({ const DynamicTable({
super.key, super.key,
required this.headers, required this.headers,
@ -32,6 +33,7 @@ class DynamicTable extends StatefulWidget {
this.onRowSelected, this.onRowSelected,
this.initialSelectedIds, this.initialSelectedIds,
required this.uuidIndex, required this.uuidIndex,
this.onSelectionChanged,
}); });
@override @override
@ -39,7 +41,7 @@ class DynamicTable extends StatefulWidget {
} }
class _DynamicTableState extends State<DynamicTable> { class _DynamicTableState extends State<DynamicTable> {
late List<bool> _selected; late List<bool> _selectedRows;
bool _selectAll = false; bool _selectAll = false;
@override @override
@ -51,47 +53,30 @@ class _DynamicTableState extends State<DynamicTable> {
@override @override
void didUpdateWidget(DynamicTable oldWidget) { void didUpdateWidget(DynamicTable oldWidget) {
super.didUpdateWidget(oldWidget); super.didUpdateWidget(oldWidget);
if (oldWidget.data != widget.data) { if (oldWidget.data.length != widget.data.length) {
_initializeSelection(); _initializeSelection();
} }
} }
void _initializeSelection() { void _initializeSelection() {
if (widget.data.isEmpty) { _selectedRows = List<bool>.filled(widget.data.length, false);
_selected = []; _selectAll = false;
_selectAll = false;
} else {
_selected = List<bool>.generate(widget.data.length, (index) {
// Check if the initialSelectedIds contains the deviceUuid
// uuidIndex is the index of the column containing the deviceUuid
final deviceUuid = widget.data[index][widget.uuidIndex];
return widget.initialSelectedIds != null &&
widget.initialSelectedIds!.contains(deviceUuid);
});
_selectAll = _selected.every((element) => element == true);
}
}
void _toggleRowSelection(int index) {
setState(() {
_selected[index] = !_selected[index];
if (widget.onRowSelected != null) {
widget.onRowSelected!(index, _selected[index], widget.data[index]);
}
});
} }
void _toggleSelectAll(bool? value) { void _toggleSelectAll(bool? value) {
setState(() { setState(() {
_selectAll = value ?? false; _selectAll = value ?? false;
_selected = List<bool>.filled(widget.data.length, _selectAll); _selectedRows = List<bool>.filled(widget.data.length, _selectAll);
for (int i = 0; i < widget.data.length; i++) {
if (widget.onRowSelected != null) {
widget.onRowSelected!(i, _selectAll, widget.data[i]);
}
}
}); });
widget.onSelectionChanged?.call(_selectedRows);
}
void _toggleRowSelection(int index) {
setState(() {
_selectedRows[index] = !_selectedRows[index];
_selectAll = _selectedRows.every((isSelected) => isSelected);
});
widget.onSelectionChanged?.call(_selectedRows);
} }
@override @override
@ -211,7 +196,7 @@ class _DynamicTableState extends State<DynamicTable> {
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Center( child: Center(
child: Checkbox( child: Checkbox(
value: _selected[index], value: _selectedRows[index],
onChanged: (bool? value) { onChanged: (bool? value) {
_toggleRowSelection(index); _toggleRowSelection(index);
}, },

View File

@ -46,6 +46,7 @@ class AcDeviceBatchControlView extends StatelessWidget
), ),
children: [ children: [
ToggleWidget( ToggleWidget(
icon: Assets.ac,
deviceId: devicesIds.first, deviceId: devicesIds.first,
code: 'switch', code: 'switch',
value: state.status.acSwitch, value: state.status.acSwitch,

View File

@ -40,6 +40,7 @@ class BatchFanSpeedControl extends StatelessWidget {
value == FanSpeeds.low), value == FanSpeeds.low),
], ],
), ),
const SizedBox(height: 8),
Wrap( Wrap(
runSpacing: 8, runSpacing: 8,
spacing: 8, spacing: 8,

View File

@ -43,7 +43,7 @@ class AcToggle extends StatelessWidget {
child: Container( child: Container(
color: ColorsManager.whiteColors, color: ColorsManager.whiteColors,
child: SvgPicture.asset( child: SvgPicture.asset(
icon ?? Assets.acDevice, icon ?? Assets.lightPulp,
width: 60, width: 60,
height: 60, height: 60,
fit: BoxFit.cover, fit: BoxFit.cover,

View File

@ -40,6 +40,7 @@ class FanSpeedControl extends StatelessWidget {
value == FanSpeeds.low), value == FanSpeeds.low),
], ],
), ),
const SizedBox(height: 8),
Wrap( Wrap(
runSpacing: 8, runSpacing: 8,
spacing: 8, spacing: 8,

View File

@ -25,6 +25,7 @@ class DeviceManagementBloc
on<SelectDevice>(_onSelectDevice); on<SelectDevice>(_onSelectDevice);
on<ResetFilters>(_onResetFilters); on<ResetFilters>(_onResetFilters);
on<ResetSelectedDevices>(_onResetSelectedDevices); on<ResetSelectedDevices>(_onResetSelectedDevices);
on<UpdateSelection>(_onUpdateSelection);
} }
Future<void> _onFetchDevices( Future<void> _onFetchDevices(
@ -172,6 +173,48 @@ class DeviceManagementBloc
} }
} }
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) { bool _checkIfControlButtonEnabled(List<AllDevicesModel> selectedDevices) {
if (selectedDevices.length > 1) { if (selectedDevices.length > 1) {
final productTypes = final productTypes =

View File

@ -54,3 +54,9 @@ class SelectDevice extends DeviceManagementEvent {
class ResetFilters extends DeviceManagementEvent {} class ResetFilters extends DeviceManagementEvent {}
class ResetSelectedDevices extends DeviceManagementEvent {} class ResetSelectedDevices extends DeviceManagementEvent {}
class UpdateSelection extends DeviceManagementEvent {
final List<bool> selectedRows;
const UpdateSelection(this.selectedRows);
}

View File

@ -37,8 +37,8 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
offlineCount = state.offlineCount; offlineCount = state.offlineCount;
lowBatteryCount = state.lowBatteryCount; lowBatteryCount = state.lowBatteryCount;
isControlButtonEnabled = state.isControlButtonEnabled; isControlButtonEnabled = state.isControlButtonEnabled;
selectedDevices = state.selectedDevice ?? selectedDevices =
context.read<DeviceManagementBloc>().selectedDevices; state.selectedDevice ?? context.read<DeviceManagementBloc>().selectedDevices;
} else if (state is DeviceManagementFiltered) { } else if (state is DeviceManagementFiltered) {
devicesToShow = state.filteredDevices; devicesToShow = state.filteredDevices;
selectedIndex = state.selectedIndex; selectedIndex = state.selectedIndex;
@ -46,8 +46,8 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
offlineCount = state.offlineCount; offlineCount = state.offlineCount;
lowBatteryCount = state.lowBatteryCount; lowBatteryCount = state.lowBatteryCount;
isControlButtonEnabled = state.isControlButtonEnabled; isControlButtonEnabled = state.isControlButtonEnabled;
selectedDevices = state.selectedDevice ?? selectedDevices =
context.read<DeviceManagementBloc>().selectedDevices; state.selectedDevice ?? context.read<DeviceManagementBloc>().selectedDevices;
} else if (state is DeviceManagementInitial) { } else if (state is DeviceManagementInitial) {
devicesToShow = []; devicesToShow = [];
selectedIndex = 0; selectedIndex = 0;
@ -61,15 +61,13 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
'Low Battery ($lowBatteryCount)', 'Low Battery ($lowBatteryCount)',
]; ];
final buttonLabel = final buttonLabel = (selectedDevices.length > 1) ? 'Batch Control' : 'Control';
(selectedDevices.length > 1) ? 'Batch Control' : 'Control';
return Column( return Column(
children: [ children: [
Container( Container(
padding: isLargeScreenSize(context) padding:
? const EdgeInsets.all(30) isLargeScreenSize(context) ? const EdgeInsets.all(30) : const EdgeInsets.all(15),
: const EdgeInsets.all(15),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@ -78,9 +76,7 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
tabs: tabs, tabs: tabs,
selectedIndex: selectedIndex, selectedIndex: selectedIndex,
onTabChanged: (index) { onTabChanged: (index) {
context context.read<DeviceManagementBloc>().add(SelectedFilterChanged(index));
.read<DeviceManagementBloc>()
.add(SelectedFilterChanged(index));
}, },
), ),
const SizedBox(height: 20), const SizedBox(height: 20),
@ -88,7 +84,7 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
const SizedBox(height: 12), const SizedBox(height: 12),
Container( Container(
height: 45, height: 45,
width: 100, width: 125,
decoration: containerDecoration, decoration: containerDecoration,
child: Center( child: Center(
child: DefaultButton( child: DefaultButton(
@ -102,14 +98,12 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
), ),
); );
} else if (selectedDevices.length > 1) { } else if (selectedDevices.length > 1) {
final productTypes = selectedDevices final productTypes =
.map((device) => device.productType) selectedDevices.map((device) => device.productType).toSet();
.toSet();
if (productTypes.length == 1) { if (productTypes.length == 1) {
showDialog( showDialog(
context: context, context: context,
builder: (context) => builder: (context) => DeviceBatchControlDialog(
DeviceBatchControlDialog(
devices: selectedDevices, devices: selectedDevices,
), ),
); );
@ -120,11 +114,10 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
borderRadius: 9, borderRadius: 9,
child: Text( child: Text(
buttonLabel, buttonLabel,
textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
color: isControlButtonEnabled color: isControlButtonEnabled ? Colors.white : Colors.grey,
? Colors.white
: Colors.grey,
), ),
), ),
), ),
@ -143,9 +136,7 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
cellDecoration: containerDecoration, cellDecoration: containerDecoration,
onRowSelected: (index, isSelected, row) { onRowSelected: (index, isSelected, row) {
final selectedDevice = devicesToShow[index]; final selectedDevice = devicesToShow[index];
context context.read<DeviceManagementBloc>().add(SelectDevice(selectedDevice));
.read<DeviceManagementBloc>()
.add(SelectDevice(selectedDevice));
}, },
withCheckBox: true, withCheckBox: true,
size: context.screenSize, size: context.screenSize,
@ -168,16 +159,17 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
device.uuid ?? '', device.uuid ?? '',
device.unit?.name ?? '', device.unit?.name ?? '',
device.room?.name ?? '', device.room?.name ?? '',
device.batteryLevel != null device.batteryLevel != null ? '${device.batteryLevel}%' : '-',
? '${device.batteryLevel}%' formatDateTime(
: '-', DateTime.fromMillisecondsSinceEpoch((device.createTime ?? 0) * 1000)),
formatDateTime(DateTime.fromMillisecondsSinceEpoch(
(device.createTime ?? 0) * 1000)),
device.online == true ? 'Online' : 'Offline', device.online == true ? 'Online' : 'Offline',
formatDateTime(DateTime.fromMillisecondsSinceEpoch( formatDateTime(
(device.updateTime ?? 0) * 1000)), DateTime.fromMillisecondsSinceEpoch((device.updateTime ?? 0) * 1000)),
]; ];
}).toList(), }).toList(),
onSelectionChanged: (selectedRows) {
context.read<DeviceManagementBloc>().add(UpdateSelection(selectedRows));
},
initialSelectedIds: context initialSelectedIds: context
.read<DeviceManagementBloc>() .read<DeviceManagementBloc>()
.selectedDevices .selectedDevices

View File

@ -47,12 +47,15 @@ class ToggleWidget extends StatelessWidget {
) )
: ClipOval( : ClipOval(
child: Container( child: Container(
height: 60,
width: 60,
padding: const EdgeInsets.all(8),
color: ColorsManager.whiteColors, color: ColorsManager.whiteColors,
child: SvgPicture.asset( child: SvgPicture.asset(
icon ?? Assets.lightPulp, icon ?? Assets.lightPulp,
width: 60, width: 35,
height: 60, height: 35,
fit: BoxFit.cover, fit: BoxFit.contain,
), ),
)), )),
Text( Text(

View File

@ -21,8 +21,10 @@ String formatTimeOfDayToISO(TimeOfDay time, {DateTime? currentDate}) {
time.hour, time.hour,
time.minute, time.minute,
); );
// Convert DateTime to Unix timestamp (in seconds)
final unixTimestamp = dateTime.millisecondsSinceEpoch ~/ 1000;
return dateTime.toUtc().toIso8601String(); return unixTimestamp.toString();
} }
String formatIsoStringToTime(String isoString, BuildContext context) { String formatIsoStringToTime(String isoString, BuildContext context) {

View File

@ -1,5 +1,5 @@
name: syncrow_web name: syncrow_web
description: "A new Flutter project." description: "Smart Home Solutions"
# The following line prevents the package from being accidentally published to # The following line prevents the package from being accidentally published to
# pub.dev using `flutter pub publish`. This is preferred for private packages. # pub.dev using `flutter pub publish`. This is preferred for private packages.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev publish_to: 'none' # Remove this line if you wish to publish to pub.dev

Binary file not shown.

Before

Width:  |  Height:  |  Size: 917 B

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 26 KiB

View File

@ -18,7 +18,7 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta content="IE=Edge" http-equiv="X-UA-Compatible"> <meta content="IE=Edge" http-equiv="X-UA-Compatible">
<meta name="description" content="A new Flutter project."> <meta name="description" content="Smart Home Solutions.">
<!-- iOS meta tags & icons --> <!-- iOS meta tags & icons -->
<meta name="apple-mobile-web-app-capable" content="yes"> <meta name="apple-mobile-web-app-capable" content="yes">

View File

@ -5,7 +5,7 @@
"display": "standalone", "display": "standalone",
"background_color": "#0175C2", "background_color": "#0175C2",
"theme_color": "#0175C2", "theme_color": "#0175C2",
"description": "A new Flutter project.", "description": "Smart Home Solutions",
"orientation": "portrait-primary", "orientation": "portrait-primary",
"prefer_related_applications": false, "prefer_related_applications": false,
"icons": [ "icons": [