From 97eb1c152b5846abf4a4a4a29899a6e676f683ea Mon Sep 17 00:00:00 2001 From: mohammad Date: Sun, 13 Apr 2025 12:18:56 +0300 Subject: [PATCH 01/19] Implement a countdown timer for the AC and fix bugs in the 'Forgot Password' --- .../auth/view/forget_password_web_page.dart | 19 +- .../device_managment/ac/bloc/ac_bloc.dart | 193 ++++++++++++++++-- .../device_managment/ac/bloc/ac_event.dart | 30 +++ .../device_managment/ac/bloc/ac_state.dart | 38 +++- .../device_managment/ac/model/ac_model.dart | 9 + .../ac/view/ac_device_control.dart | 132 ++++++++---- 6 files changed, 344 insertions(+), 77 deletions(-) diff --git a/lib/pages/auth/view/forget_password_web_page.dart b/lib/pages/auth/view/forget_password_web_page.dart index f389f44f..7686ea8f 100644 --- a/lib/pages/auth/view/forget_password_web_page.dart +++ b/lib/pages/auth/view/forget_password_web_page.dart @@ -201,20 +201,17 @@ class ForgetPasswordWebPage extends StatelessWidget { !state.isButtonEnabled && state.remainingTime != 1 ? null - : () { + : + () { if (forgetBloc - .forgetEmailKey.currentState! - .validate() || - forgetBloc - .forgetRegionKey.currentState! - .validate()) { - if (forgetBloc - .forgetRegionKey.currentState! - .validate()) { - forgetBloc.add(StartTimerEvent()); - } + .forgetEmailKey + .currentState! + .validate()) { + forgetBloc.add( + StartTimerEvent()); } }, + child: Text( 'Get Code ${state is TimerState && !state.isButtonEnabled && state.remainingTime != 1 ? "(${forgetBloc.formattedTime(state.remainingTime)}) " : ""}', style: TextStyle( diff --git a/lib/pages/device_managment/ac/bloc/ac_bloc.dart b/lib/pages/device_managment/ac/bloc/ac_bloc.dart index 7c35034e..076e9050 100644 --- a/lib/pages/device_managment/ac/bloc/ac_bloc.dart +++ b/lib/pages/device_managment/ac/bloc/ac_bloc.dart @@ -13,6 +13,7 @@ class AcBloc extends Bloc { late AcStatusModel deviceStatus; final String deviceId; Timer? _timer; + Timer? _countdownTimer; AcBloc({required this.deviceId}) : super(AcsInitialState()) { on(_onFetchAcStatus); @@ -21,7 +22,16 @@ class AcBloc extends Bloc { on(_onAcBatchControl); on(_onFactoryReset); on(_onAcStatusUpdated); + on(_onClose); + on(_handleIncreaseTime); + on(_handleDecreaseTime); + on(_handleUpdateTimer); + on(_handleToggleTimer); + on(_handleApiCountdownValue); } + bool timerActive = false; + int scheduledHours = 0; + int scheduledMinutes = 0; FutureOr _onFetchAcStatus( AcFetchDeviceStatusEvent event, Emitter emit) async { @@ -30,8 +40,23 @@ class AcBloc extends Bloc { final status = await DevicesManagementApi().getDeviceStatus(event.deviceId); deviceStatus = AcStatusModel.fromJson(event.deviceId, status.status); + if (deviceStatus.countdown1 != 0) { + // Convert API value to minutes + final totalMinutes = deviceStatus.countdown1 * 6; + scheduledHours = totalMinutes ~/ 60; + scheduledMinutes = totalMinutes % 60; + timerActive = true; + _startCountdownTimer(emit); + } + + emit(ACStatusLoaded( + status: deviceStatus, + scheduledHours: scheduledHours, + scheduledMinutes: scheduledMinutes, + isTimerActive: timerActive, + )); + _listenToChanges(event.deviceId); - emit(ACStatusLoaded(deviceStatus)); } catch (e) { emit(AcsFailedState(error: e.toString())); } @@ -70,31 +95,16 @@ class AcBloc extends Bloc { void _onAcStatusUpdated(AcStatusUpdated event, Emitter emit) { deviceStatus = event.deviceStatus; - emit(ACStatusLoaded(deviceStatus)); + emit(ACStatusLoaded(status: deviceStatus)); } - // Future testFirebaseConnection() async { - // // Reference to a test node in your database - // final testRef = FirebaseDatabase.instance.ref("test"); - - // // Write a test value - // await testRef.set("Hello, Firebase!"); - - // // Listen for changes on the test node - // testRef.onValue.listen((DatabaseEvent event) { - // final data = event.snapshot.value; - // print("Data from Firebase: $data"); - // // If you see "Hello, Firebase!" printed in your console, it means the connection works. - // }); - // } - FutureOr _onAcControl( AcControlEvent event, Emitter emit) async { final oldValue = _getValueByCode(event.code); _updateLocalValue(event.code, event.value, emit); - emit(ACStatusLoaded(deviceStatus)); + emit(ACStatusLoaded(status: deviceStatus)); await _runDebounce( isBatch: false, @@ -151,7 +161,7 @@ class AcBloc extends Bloc { void _revertValueAndEmit( String deviceId, String code, dynamic oldValue, Emitter emit) { _updateLocalValue(code, oldValue, emit); - emit(ACStatusLoaded(deviceStatus)); + emit(ACStatusLoaded(status: deviceStatus)); } void _updateLocalValue(String code, dynamic value, Emitter emit) { @@ -184,11 +194,16 @@ class AcBloc extends Bloc { if (value is bool) { deviceStatus = deviceStatus.copyWith(childLock: value); } + + case 'countdown_time': + if (value is int) { + deviceStatus = deviceStatus.copyWith(countdown1: value); + } break; default: break; } - emit(ACStatusLoaded(deviceStatus)); + emit(ACStatusLoaded(status: deviceStatus)); } dynamic _getValueByCode(String code) { @@ -203,6 +218,8 @@ class AcBloc extends Bloc { return deviceStatus.fanSpeedsString; case 'child_lock': return deviceStatus.childLock; + case 'countdown_time': + return deviceStatus.countdown1; default: return null; } @@ -216,7 +233,7 @@ class AcBloc extends Bloc { await DevicesManagementApi().getBatchStatus(event.devicesIds); deviceStatus = AcStatusModel.fromJson(event.devicesIds.first, status.status); - emit(ACStatusLoaded(deviceStatus)); + emit(ACStatusLoaded(status: deviceStatus)); } catch (e) { emit(AcsFailedState(error: e.toString())); } @@ -228,7 +245,7 @@ class AcBloc extends Bloc { _updateLocalValue(event.code, event.value, emit); - emit(ACStatusLoaded(deviceStatus)); + emit(ACStatusLoaded(status: deviceStatus)); await _runDebounce( isBatch: true, @@ -257,4 +274,136 @@ class AcBloc extends Bloc { emit(AcsFailedState(error: e.toString())); } } + + void _onClose(OnClose event, Emitter emit) { + _countdownTimer?.cancel(); + _timer?.cancel(); + } + + void _handleIncreaseTime(IncreaseTimeEvent event, Emitter emit) { + if (state is! ACStatusLoaded) return; + final currentState = state as ACStatusLoaded; + int newHours = scheduledHours; + int newMinutes = scheduledMinutes + 30; + newHours += newMinutes ~/ 60; + newMinutes = newMinutes % 60; + if (newHours > 23) { + newHours = 23; + newMinutes = 59; + } + scheduledHours = newHours; + scheduledMinutes = newMinutes; + + emit(currentState.copyWith( + scheduledHours: scheduledHours, + scheduledMinutes: scheduledMinutes, + )); + } + + void _handleDecreaseTime(DecreaseTimeEvent event, Emitter emit) { + if (state is! ACStatusLoaded) return; + final currentState = state as ACStatusLoaded; + int totalMinutes = (scheduledHours * 60) + scheduledMinutes; + totalMinutes = (totalMinutes - 30).clamp(0, 1440); + scheduledHours = totalMinutes ~/ 60; + scheduledMinutes = totalMinutes % 60; + + emit(currentState.copyWith( + scheduledHours: scheduledHours, + scheduledMinutes: scheduledMinutes, + )); + } + + Future _handleToggleTimer( + ToggleScheduleEvent event, Emitter emit) async { + if (state is! ACStatusLoaded) return; + final currentState = state as ACStatusLoaded; + + timerActive = !timerActive; + + if (timerActive) { + final totalMinutes = scheduledHours * 60 + scheduledMinutes; + if (totalMinutes <= 0) { + timerActive = false; + emit(currentState.copyWith(isTimerActive: timerActive)); + return; + } + + try { + final scaledValue = totalMinutes ~/ 6; + await DevicesManagementApi().deviceControl( + deviceId, + Status(code: 'countdown_time', value: scaledValue), + ); + _startCountdownTimer(emit); + emit(currentState.copyWith(isTimerActive: timerActive)); + } catch (e) { + timerActive = false; + emit(AcsFailedState(error: e.toString())); + } + } else { + await DevicesManagementApi().deviceControl( + deviceId, + Status(code: 'countdown_time', value: 0), + ); + _countdownTimer?.cancel(); + scheduledHours = 0; + scheduledMinutes = 0; + emit(currentState.copyWith( + isTimerActive: timerActive, + scheduledHours: 0, + scheduledMinutes: 0, + )); + } + } + + void _startCountdownTimer(Emitter emit) { + _countdownTimer?.cancel(); + int totalSeconds = (scheduledHours * 3600) + (scheduledMinutes * 60); + + _countdownTimer = Timer.periodic(const Duration(seconds: 1), (timer) { + if (totalSeconds > 0) { + totalSeconds--; + scheduledHours = totalSeconds ~/ 3600; + scheduledMinutes = (totalSeconds % 3600) ~/ 60; + add(UpdateTimerEvent()); + } else { + _countdownTimer?.cancel(); + timerActive = false; + scheduledHours = 0; + scheduledMinutes = 0; + add(TimerCompletedEvent()); + } + }); + } + + void _handleUpdateTimer(UpdateTimerEvent event, Emitter emit) { + if (state is ACStatusLoaded) { + final currentState = state as ACStatusLoaded; + emit(currentState.copyWith( + scheduledHours: scheduledHours, + scheduledMinutes: scheduledMinutes, + isTimerActive: timerActive, + )); + } + } + + void _handleApiCountdownValue( + ApiCountdownValueEvent event, Emitter emit) { + if (state is ACStatusLoaded) { + final totalMinutes = event.apiValue * 6; + final scheduledHours = totalMinutes ~/ 60; + scheduledMinutes = totalMinutes % 60; + _startCountdownTimer( + emit, + ); + add(UpdateTimerEvent()); + } + } + + @override + Future close() { + add(OnClose()); + return super.close(); + } } diff --git a/lib/pages/device_managment/ac/bloc/ac_event.dart b/lib/pages/device_managment/ac/bloc/ac_event.dart index 5492e198..9764f3ed 100644 --- a/lib/pages/device_managment/ac/bloc/ac_event.dart +++ b/lib/pages/device_managment/ac/bloc/ac_event.dart @@ -8,6 +8,7 @@ sealed class AcsEvent extends Equatable { @override List get props => []; } + class AcUpdated extends AcsEvent {} class AcFetchDeviceStatusEvent extends AcsEvent { @@ -18,10 +19,12 @@ class AcFetchDeviceStatusEvent extends AcsEvent { @override List get props => [deviceId]; } + class AcStatusUpdated extends AcsEvent { final AcStatusModel deviceStatus; AcStatusUpdated(this.deviceStatus); } + class AcFetchBatchStatusEvent extends AcsEvent { final List devicesIds; @@ -73,3 +76,30 @@ class AcFactoryResetEvent extends AcsEvent { @override List get props => [deviceId, factoryResetModel]; } + + + +class OnClose extends AcsEvent {} + +class IncreaseTimeEvent extends AcsEvent { + @override + List get props => []; +} + +class DecreaseTimeEvent extends AcsEvent { + @override + List get props => []; +} + +class ToggleScheduleEvent extends AcsEvent {} + +class TimerCompletedEvent extends AcsEvent {} + +class UpdateTimerEvent extends AcsEvent { +} + +class ApiCountdownValueEvent extends AcsEvent { + final int apiValue; + + const ApiCountdownValueEvent(this.apiValue); +} diff --git a/lib/pages/device_managment/ac/bloc/ac_state.dart b/lib/pages/device_managment/ac/bloc/ac_state.dart index dfd12e6d..3e1e2c68 100644 --- a/lib/pages/device_managment/ac/bloc/ac_state.dart +++ b/lib/pages/device_managment/ac/bloc/ac_state.dart @@ -2,8 +2,9 @@ import 'package:equatable/equatable.dart'; import 'package:syncrow_web/pages/device_managment/ac/model/ac_model.dart'; abstract class AcsState extends Equatable { - const AcsState(); + final bool isTimerActive; + const AcsState({this.isTimerActive = false}); @override List get props => []; } @@ -15,8 +16,30 @@ class AcsLoadingState extends AcsState {} class ACStatusLoaded extends AcsState { final AcStatusModel status; final DateTime timestamp; + final int scheduledHours; + final int scheduledMinutes; + final bool isTimerActive; - ACStatusLoaded(this.status) : timestamp = DateTime.now(); + ACStatusLoaded({ + required this.status, + this.scheduledHours = 0, + this.scheduledMinutes = 0, + this.isTimerActive = false, + }) : timestamp = DateTime.now(); + ACStatusLoaded copyWith({ + AcStatusModel? status, + int? scheduledHours, + int? scheduledMinutes, + bool? isTimerActive, + int? remainingTime, + }) { + return ACStatusLoaded( + status: status ?? this.status, + scheduledHours: scheduledHours ?? this.scheduledHours, + scheduledMinutes: scheduledMinutes ?? this.scheduledMinutes, + isTimerActive: isTimerActive ?? this.isTimerActive, + ); + } @override List get props => [status, timestamp]; @@ -40,3 +63,14 @@ class AcsFailedState extends AcsState { @override List get props => [error]; } + +class TimerRunInProgress extends AcsState { + final int remainingTime; + + const TimerRunInProgress(this.remainingTime); + + @override + List get props => [remainingTime]; +} + + diff --git a/lib/pages/device_managment/ac/model/ac_model.dart b/lib/pages/device_managment/ac/model/ac_model.dart index 1eb2145f..c67006b2 100644 --- a/lib/pages/device_managment/ac/model/ac_model.dart +++ b/lib/pages/device_managment/ac/model/ac_model.dart @@ -11,6 +11,7 @@ class AcStatusModel { final bool childLock; final TempModes acMode; final FanSpeeds acFanSpeed; + late final int countdown1; AcStatusModel({ required this.uuid, @@ -18,6 +19,7 @@ class AcStatusModel { required this.modeString, required this.tempSet, required this.currentTemp, + required this.countdown1, required this.fanSpeedsString, required this.childLock, }) : acMode = getACMode(modeString), @@ -30,6 +32,7 @@ class AcStatusModel { late int currentTemp; late String fanSpeeds; late bool childLock; + late int _countdown1 = 0; for (var status in jsonList) { switch (status.code) { @@ -51,6 +54,9 @@ class AcStatusModel { case 'child_lock': childLock = status.value ?? false; break; + case 'countdown_time': + _countdown1 = status.value ?? 0; + break; } } @@ -62,6 +68,7 @@ class AcStatusModel { currentTemp: currentTemp, fanSpeedsString: fanSpeeds, childLock: childLock, + countdown1: _countdown1, ); } @@ -73,6 +80,7 @@ class AcStatusModel { int? currentTemp, String? fanSpeedsString, bool? childLock, + int? countdown1, }) { return AcStatusModel( uuid: uuid ?? this.uuid, @@ -82,6 +90,7 @@ class AcStatusModel { currentTemp: currentTemp ?? this.currentTemp, fanSpeedsString: fanSpeedsString ?? this.fanSpeedsString, childLock: childLock ?? this.childLock, + countdown1: countdown1 ?? this.countdown1, ); } diff --git a/lib/pages/device_managment/ac/view/ac_device_control.dart b/lib/pages/device_managment/ac/view/ac_device_control.dart index 071344d7..e5c9bbbd 100644 --- a/lib/pages/device_managment/ac/view/ac_device_control.dart +++ b/lib/pages/device_managment/ac/view/ac_device_control.dart @@ -10,11 +10,10 @@ import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_mo 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 AcDeviceControlsView extends StatelessWidget with HelperResponsiveLayout { - const AcDeviceControlsView({super.key, required this.device}); + AcDeviceControlsView({super.key, required this.device}); final AllDevicesModel device; @@ -23,11 +22,15 @@ class AcDeviceControlsView extends StatelessWidget with HelperResponsiveLayout { final isExtraLarge = isExtraLargeScreenSize(context); final isLarge = isLargeScreenSize(context); final isMedium = isMediumScreenSize(context); + return BlocProvider( create: (context) => AcBloc(deviceId: device.uuid!) ..add(AcFetchDeviceStatusEvent(device.uuid!)), child: BlocBuilder( builder: (context, state) { + final acBloc = BlocProvider.of(context); + + print(state); if (state is ACStatusLoaded) { return GridView( padding: const EdgeInsets.symmetric(horizontal: 50), @@ -78,56 +81,101 @@ class AcDeviceControlsView extends StatelessWidget with HelperResponsiveLayout { ), ToggleWidget( label: '', - labelWidget: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, + labelWidget: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, children: [ - IconButton( - padding: const EdgeInsets.all(0), - onPressed: () {}, - icon: const Icon( - Icons.remove, - size: 28, - color: ColorsManager.greyColor, + Container( + width: MediaQuery.of(context).size.width, + decoration: const ShapeDecoration( + color: ColorsManager.primaryColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(30)), + ), ), ), - 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, + Center( + child: SizedBox( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + IconButton( + onPressed: () { + if (acBloc.timerActive == false) { + context + .read() + .add(DecreaseTimeEvent()); + } + }, + icon: const Icon(Icons.remove, + color: ColorsManager.greyColor), + ), + Text( + acBloc.scheduledHours + .toString() + .padLeft(2, '0'), + style: Theme.of(context) + .textTheme + .titleLarge! + .copyWith( + color: ColorsManager.dialogBlueTitle, + fontWeight: FontWeight.bold, + ), + ), + Text( + 'h', + style: Theme.of(context) + .textTheme + .bodySmall! + .copyWith( + color: ColorsManager.blackColor, + ), + ), + Text( + acBloc.scheduledMinutes + .toString() + .padLeft(2, '0'), + style: Theme.of(context) + .textTheme + .titleLarge! + .copyWith( + color: ColorsManager.dialogBlueTitle, + fontWeight: FontWeight.bold, + ), + ), + Text( + 'm', + style: Theme.of(context) + .textTheme + .bodySmall! + .copyWith( + color: ColorsManager.blackColor, + ), + ), + IconButton( + onPressed: () { + if (acBloc.timerActive == false) { + context + .read() + .add(IncreaseTimeEvent()); + } + }, + icon: const Icon(Icons.add, + color: ColorsManager.greyColor), + ), + ], + ), ), ), ], ), - value: false, + value: acBloc.timerActive, code: 'ac_schedule', deviceId: device.uuid!, icon: Assets.acSchedule, - onChange: (value) {}, + onChange: (value) { + context.read().add(ToggleScheduleEvent()); + }, ), ToggleWidget( deviceId: device.uuid!, From cb6d50d3671e7a1b122add70f8d6e1b4dcf0fbbc Mon Sep 17 00:00:00 2001 From: mohammad Date: Sun, 13 Apr 2025 12:21:35 +0300 Subject: [PATCH 02/19] remove unnecessary print statement --- lib/pages/device_managment/ac/view/ac_device_control.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/pages/device_managment/ac/view/ac_device_control.dart b/lib/pages/device_managment/ac/view/ac_device_control.dart index e5c9bbbd..4e8f896c 100644 --- a/lib/pages/device_managment/ac/view/ac_device_control.dart +++ b/lib/pages/device_managment/ac/view/ac_device_control.dart @@ -29,8 +29,6 @@ class AcDeviceControlsView extends StatelessWidget with HelperResponsiveLayout { child: BlocBuilder( builder: (context, state) { final acBloc = BlocProvider.of(context); - - print(state); if (state is ACStatusLoaded) { return GridView( padding: const EdgeInsets.symmetric(horizontal: 50), From bc32fe7941ebbab221c0b429d14b11a9c06b38d2 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Sun, 13 Apr 2025 12:25:03 +0300 Subject: [PATCH 03/19] removed unused method and its use. --- .../all_spaces/widgets/sidebar_widget.dart | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart index 17566da7..e4a5d14d 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart @@ -36,7 +36,8 @@ class _SidebarWidgetState extends State { @override void initState() { super.initState(); - _selectedId = widget.selectedSpaceUuid; // Initialize with the passed selected space UUID + _selectedId = + widget.selectedSpaceUuid; // Initialize with the passed selected space UUID } @override @@ -61,8 +62,8 @@ class _SidebarWidgetState extends State { return widget.communities.where((community) { final containsQueryInCommunity = community.name.toLowerCase().contains(_searchQuery.toLowerCase()); - final containsQueryInSpaces = - community.spaces.any((space) => _containsQuery(space, _searchQuery.toLowerCase())); + final containsQueryInSpaces = community.spaces + .any((space) => _containsQuery(space, _searchQuery.toLowerCase())); return containsQueryInCommunity || containsQueryInSpaces; }).toList(); @@ -71,8 +72,8 @@ class _SidebarWidgetState extends State { // Helper function to determine if any space or its children match the search query bool _containsQuery(SpaceModel space, String query) { final matchesSpace = space.name.toLowerCase().contains(query); - final matchesChildren = - space.children.any((child) => _containsQuery(child, query)); // Recursive check for children + final matchesChildren = space.children.any( + (child) => _containsQuery(child, query)); // Recursive check for children // If the space or any of its children match the query, expand this space if (matchesSpace || matchesChildren) { @@ -106,7 +107,8 @@ class _SidebarWidgetState extends State { width: 300, decoration: subSectionContainerDecoration, child: Column( - mainAxisSize: MainAxisSize.min, // Ensures the Column only takes necessary height + mainAxisSize: + MainAxisSize.min, // Ensures the Column only takes necessary height crossAxisAlignment: CrossAxisAlignment.start, children: [ // Communities title with the add button @@ -153,9 +155,9 @@ class _SidebarWidgetState extends State { // Community list Expanded( child: ListView( - children: filteredCommunities.map((community) { - return _buildCommunityTile(context, community); - }).toList(), + children: filteredCommunities + .map((community) => _buildCommunityTile(context, community)) + .toList(), ), ), ], @@ -192,9 +194,7 @@ class _SidebarWidgetState extends State { SelectCommunityEvent(selectedCommunity: community), ); }, - onExpansionChanged: (String title, bool expanded) { - _handleExpansionChange(community.uuid, expanded); - }, + onExpansionChanged: (title, expanded) {}, children: hasChildren ? community.spaces .where((space) => (space.status != SpaceStatus.deleted || @@ -205,7 +205,8 @@ class _SidebarWidgetState extends State { ); } - Widget _buildSpaceTile(SpaceModel space, CommunityModel community, {int depth = 1}) { + Widget _buildSpaceTile(SpaceModel space, CommunityModel community, + {int depth = 1}) { bool isExpandedSpace = _isSpaceOrChildSelected(space); return Padding( padding: EdgeInsets.only(left: depth * 16.0), @@ -214,9 +215,7 @@ class _SidebarWidgetState extends State { key: ValueKey(space.uuid), isSelected: _selectedId == space.uuid, initiallyExpanded: isExpandedSpace, - onExpansionChanged: (bool expanded) { - _handleExpansionChange(space.uuid ?? '', expanded); - }, + onExpansionChanged: (expanded) {}, onItemSelected: () { setState(() { _selectedId = space.uuid; @@ -224,14 +223,15 @@ class _SidebarWidgetState extends State { }); context.read().add( - SelectSpaceEvent(selectedCommunity: community, selectedSpace: space), + SelectSpaceEvent( + selectedCommunity: community, selectedSpace: space), ); }, children: space.children.isNotEmpty - ? space.children.map((childSpace) => _buildSpaceTile(childSpace, community)).toList() + ? space.children + .map((childSpace) => _buildSpaceTile(childSpace, community)) + .toList() : [], // Recursively render child spaces if available )); } - - void _handleExpansionChange(String uuid, bool expanded) {} } From 978934399e96766eb9b4064cdffca696be9b6670 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Sun, 13 Apr 2025 12:25:28 +0300 Subject: [PATCH 04/19] fixed invalid use of a private type in a public API in `SidebarWidget`. --- .../spaces_management/all_spaces/widgets/sidebar_widget.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart index e4a5d14d..81cec22f 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart @@ -25,7 +25,7 @@ class SidebarWidget extends StatefulWidget { }); @override - _SidebarWidgetState createState() => _SidebarWidgetState(); + State createState() => _SidebarWidgetState(); } class _SidebarWidgetState extends State { From 35a99ccda7c7ddc3d0654f562235ff993a02b492 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Sun, 13 Apr 2025 12:27:04 +0300 Subject: [PATCH 05/19] removed unnecessary comments from `SidebarWidget`. --- .../all_spaces/widgets/sidebar_widget.dart | 25 ++++++------------- 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart index 81cec22f..2e486cc6 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart @@ -29,15 +29,14 @@ class SidebarWidget extends StatefulWidget { } class _SidebarWidgetState extends State { - String _searchQuery = ''; // Track search query + String _searchQuery = ''; String? _selectedSpaceUuid; String? _selectedId; @override void initState() { super.initState(); - _selectedId = - widget.selectedSpaceUuid; // Initialize with the passed selected space UUID + _selectedId = widget.selectedSpaceUuid; } @override @@ -50,15 +49,12 @@ class _SidebarWidgetState extends State { } } - // Function to filter communities based on the search query List _filterCommunities() { if (_searchQuery.isEmpty) { - // Reset the selected community and space UUIDs if there's no query _selectedSpaceUuid = null; return widget.communities; } - // Filter communities and expand only those that match the query return widget.communities.where((community) { final containsQueryInCommunity = community.name.toLowerCase().contains(_searchQuery.toLowerCase()); @@ -69,13 +65,12 @@ class _SidebarWidgetState extends State { }).toList(); } - // Helper function to determine if any space or its children match the search query bool _containsQuery(SpaceModel space, String query) { final matchesSpace = space.name.toLowerCase().contains(query); final matchesChildren = space.children.any( - (child) => _containsQuery(child, query)); // Recursive check for children + (child) => _containsQuery(child, query), + ); - // If the space or any of its children match the query, expand this space if (matchesSpace || matchesChildren) { _selectedSpaceUuid = space.uuid; } @@ -84,12 +79,10 @@ class _SidebarWidgetState extends State { } bool _isSpaceOrChildSelected(SpaceModel space) { - // Return true if the current space or any of its child spaces is selected if (_selectedSpaceUuid == space.uuid) { return true; } - // Recursively check if any child spaces match the query for (var child in space.children) { if (_isSpaceOrChildSelected(child)) { return true; @@ -107,11 +100,9 @@ class _SidebarWidgetState extends State { width: 300, decoration: subSectionContainerDecoration, child: Column( - mainAxisSize: - MainAxisSize.min, // Ensures the Column only takes necessary height + mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - // Communities title with the add button Container( decoration: subSectionContainerDecoration, padding: const EdgeInsets.all(16.0), @@ -143,7 +134,6 @@ class _SidebarWidgetState extends State { ], ), ), - // Search bar CustomSearchBar( onSearchChanged: (query) { setState(() { @@ -152,7 +142,6 @@ class _SidebarWidgetState extends State { }, ), const SizedBox(height: 16), - // Community list Expanded( child: ListView( children: filteredCommunities @@ -185,7 +174,7 @@ class _SidebarWidgetState extends State { onItemSelected: () { setState(() { _selectedId = community.uuid; - _selectedSpaceUuid = null; // Update the selected community + _selectedSpaceUuid = null; }); context.read().add(CommunitySelectedEvent()); @@ -231,7 +220,7 @@ class _SidebarWidgetState extends State { ? space.children .map((childSpace) => _buildSpaceTile(childSpace, community)) .toList() - : [], // Recursively render child spaces if available + : [], )); } } From bfd3d4542e2a6829bc2871d65b6a00a49e35397a Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Sun, 13 Apr 2025 12:28:50 +0300 Subject: [PATCH 06/19] refactor: simplify onSearchChanged callback to use expressions. --- .../all_spaces/widgets/sidebar_widget.dart | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart index 2e486cc6..7c362dd7 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart @@ -135,11 +135,7 @@ class _SidebarWidgetState extends State { ), ), CustomSearchBar( - onSearchChanged: (query) { - setState(() { - _searchQuery = query; - }); - }, + onSearchChanged: (query) => setState(() => _searchQuery = query), ), const SizedBox(height: 16), Expanded( From cd9821679effb740aaa61b4716689c53272a007d Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Sun, 13 Apr 2025 12:29:38 +0300 Subject: [PATCH 07/19] added trailing commas. --- .../all_spaces/widgets/sidebar_widget.dart | 56 ++++++++++--------- 1 file changed, 29 insertions(+), 27 deletions(-) diff --git a/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart index 7c362dd7..482bb447 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart @@ -109,10 +109,12 @@ class _SidebarWidgetState extends State { child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text('Communities', - style: Theme.of(context).textTheme.titleMedium?.copyWith( - color: ColorsManager.blackColor, - )), + Text( + 'Communities', + style: Theme.of(context).textTheme.titleMedium?.copyWith( + color: ColorsManager.blackColor, + ), + ), GestureDetector( onTap: () => _navigateToBlank(context), child: Container( @@ -194,29 +196,29 @@ class _SidebarWidgetState extends State { {int depth = 1}) { bool isExpandedSpace = _isSpaceOrChildSelected(space); return Padding( - padding: EdgeInsets.only(left: depth * 16.0), - child: SpaceTile( - title: space.name, - key: ValueKey(space.uuid), - isSelected: _selectedId == space.uuid, - initiallyExpanded: isExpandedSpace, - onExpansionChanged: (expanded) {}, - onItemSelected: () { - setState(() { - _selectedId = space.uuid; - _selectedSpaceUuid = space.uuid; - }); + padding: EdgeInsets.only(left: depth * 16.0), + child: SpaceTile( + title: space.name, + key: ValueKey(space.uuid), + isSelected: _selectedId == space.uuid, + initiallyExpanded: isExpandedSpace, + onExpansionChanged: (expanded) {}, + onItemSelected: () { + setState(() { + _selectedId = space.uuid; + _selectedSpaceUuid = space.uuid; + }); - context.read().add( - SelectSpaceEvent( - selectedCommunity: community, selectedSpace: space), - ); - }, - children: space.children.isNotEmpty - ? space.children - .map((childSpace) => _buildSpaceTile(childSpace, community)) - .toList() - : [], - )); + context.read().add( + SelectSpaceEvent(selectedCommunity: community, selectedSpace: space), + ); + }, + children: space.children.isNotEmpty + ? space.children + .map((childSpace) => _buildSpaceTile(childSpace, community)) + .toList() + : [], + ), + ); } } From c2f5a8df10ba6d9f4b44da229b018c697fb286e4 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Sun, 13 Apr 2025 12:31:17 +0300 Subject: [PATCH 08/19] refactor: streamline context usage and improve readability in SidebarWidget --- .../all_spaces/widgets/sidebar_widget.dart | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart index 482bb447..d241a9c1 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart @@ -12,6 +12,7 @@ import 'package:syncrow_web/pages/spaces_management/structure_selector/bloc/cent import 'package:syncrow_web/pages/spaces_management/structure_selector/bloc/center_body_event.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/style.dart'; class SidebarWidget extends StatefulWidget { @@ -111,7 +112,7 @@ class _SidebarWidgetState extends State { children: [ Text( 'Communities', - style: Theme.of(context).textTheme.titleMedium?.copyWith( + style: context.textTheme.titleMedium?.copyWith( color: ColorsManager.blackColor, ), ), @@ -153,9 +154,7 @@ class _SidebarWidgetState extends State { } void _navigateToBlank(BuildContext context) { - setState(() { - _selectedId = ''; - }); + setState(() => _selectedId = ''); context.read().add( NewCommunityEvent(communities: widget.communities), ); @@ -194,14 +193,14 @@ class _SidebarWidgetState extends State { Widget _buildSpaceTile(SpaceModel space, CommunityModel community, {int depth = 1}) { - bool isExpandedSpace = _isSpaceOrChildSelected(space); + bool spaceIsExpanded = _isSpaceOrChildSelected(space); return Padding( padding: EdgeInsets.only(left: depth * 16.0), child: SpaceTile( title: space.name, key: ValueKey(space.uuid), isSelected: _selectedId == space.uuid, - initiallyExpanded: isExpandedSpace, + initiallyExpanded: spaceIsExpanded, onExpansionChanged: (expanded) {}, onItemSelected: () { setState(() { From 55695ca5db761e532469b0251a171f43a324516f Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Sun, 13 Apr 2025 12:33:50 +0300 Subject: [PATCH 09/19] refactor: improve readability and structure in SidebarWidget's space tile handling --- .../all_spaces/widgets/sidebar_widget.dart | 36 ++++++++++++------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart index d241a9c1..1e6b2bf5 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart @@ -113,8 +113,8 @@ class _SidebarWidgetState extends State { Text( 'Communities', style: context.textTheme.titleMedium?.copyWith( - color: ColorsManager.blackColor, - ), + color: ColorsManager.blackColor, + ), ), GestureDetector( onTap: () => _navigateToBlank(context), @@ -183,23 +183,30 @@ class _SidebarWidgetState extends State { onExpansionChanged: (title, expanded) {}, children: hasChildren ? community.spaces - .where((space) => (space.status != SpaceStatus.deleted || - space.status != SpaceStatus.parentDeleted)) - .map((space) => _buildSpaceTile(space, community)) + .where((space) { + final isDeleted = space.status != SpaceStatus.deleted; + final isParentDeleted = space.status != SpaceStatus.parentDeleted; + return (isDeleted || isParentDeleted); + }) + .map((space) => _buildSpaceTile(space: space, community: community)) .toList() : null, ); } - Widget _buildSpaceTile(SpaceModel space, CommunityModel community, - {int depth = 1}) { + Widget _buildSpaceTile({ + required SpaceModel space, + required CommunityModel community, + int depth = 1, + }) { bool spaceIsExpanded = _isSpaceOrChildSelected(space); + final isSelected = _selectedId == space.uuid; return Padding( padding: EdgeInsets.only(left: depth * 16.0), child: SpaceTile( title: space.name, key: ValueKey(space.uuid), - isSelected: _selectedId == space.uuid, + isSelected: isSelected, initiallyExpanded: spaceIsExpanded, onExpansionChanged: (expanded) {}, onItemSelected: () { @@ -212,11 +219,14 @@ class _SidebarWidgetState extends State { SelectSpaceEvent(selectedCommunity: community, selectedSpace: space), ); }, - children: space.children.isNotEmpty - ? space.children - .map((childSpace) => _buildSpaceTile(childSpace, community)) - .toList() - : [], + children: space.children + .map( + (childSpace) => _buildSpaceTile( + space: childSpace, + community: community, + ), + ) + .toList(), ), ); } From 62ee9a72d6143e47f6946421ecf60f22e347a330 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Sun, 13 Apr 2025 12:47:00 +0300 Subject: [PATCH 10/19] moved `SidebarHeader` and `SidebarAddCommunityButton` to their own files. --- .../widgets/sidebar_add_community_button.dart | 32 ++++++++ .../all_spaces/widgets/sidebar_header.dart | 29 +++++++ .../all_spaces/widgets/sidebar_widget.dart | 78 +++++-------------- 3 files changed, 79 insertions(+), 60 deletions(-) create mode 100644 lib/pages/spaces_management/all_spaces/widgets/sidebar_add_community_button.dart create mode 100644 lib/pages/spaces_management/all_spaces/widgets/sidebar_header.dart diff --git a/lib/pages/spaces_management/all_spaces/widgets/sidebar_add_community_button.dart b/lib/pages/spaces_management/all_spaces/widgets/sidebar_add_community_button.dart new file mode 100644 index 00000000..2f434350 --- /dev/null +++ b/lib/pages/spaces_management/all_spaces/widgets/sidebar_add_community_button.dart @@ -0,0 +1,32 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; + +class SidebarAddCommunityButton extends StatelessWidget { + const SidebarAddCommunityButton({super.key}); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: () => _navigateToBlank(context), + child: Container( + width: 30, + height: 30, + decoration: const BoxDecoration( + color: ColorsManager.whiteColors, + shape: BoxShape.circle, + ), + child: Center( + child: SvgPicture.asset( + Assets.roundedAddIcon, + width: 24, + height: 24, + ), + ), + ), + ); + } + + void _navigateToBlank(BuildContext context) {} +} diff --git a/lib/pages/spaces_management/all_spaces/widgets/sidebar_header.dart b/lib/pages/spaces_management/all_spaces/widgets/sidebar_header.dart new file mode 100644 index 00000000..1e386d4f --- /dev/null +++ b/lib/pages/spaces_management/all_spaces/widgets/sidebar_header.dart @@ -0,0 +1,29 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/sidebar_add_community_button.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; +import 'package:syncrow_web/utils/style.dart'; + +class SidebarHeader extends StatelessWidget { + const SidebarHeader({super.key}); + + @override + Widget build(BuildContext context) { + return Container( + decoration: subSectionContainerDecoration, + padding: const EdgeInsets.all(16.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Communities', + style: context.textTheme.titleMedium?.copyWith( + color: ColorsManager.blackColor, + ), + ), + const SidebarAddCommunityButton(), + ], + ), + ); + } +} diff --git a/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart index 1e6b2bf5..a98dead8 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart @@ -1,18 +1,15 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flutter_svg/svg.dart'; import 'package:syncrow_web/common/widgets/search_bar.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_event.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/community_tile.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/sidebar_header.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/space_tile_widget.dart'; import 'package:syncrow_web/pages/spaces_management/structure_selector/bloc/center_body_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/structure_selector/bloc/center_body_event.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/style.dart'; class SidebarWidget extends StatefulWidget { @@ -20,9 +17,9 @@ class SidebarWidget extends StatefulWidget { final String? selectedSpaceUuid; const SidebarWidget({ - super.key, required this.communities, this.selectedSpaceUuid, + super.key, }); @override @@ -36,21 +33,19 @@ class _SidebarWidgetState extends State { @override void initState() { - super.initState(); _selectedId = widget.selectedSpaceUuid; + super.initState(); } @override void didUpdateWidget(covariant SidebarWidget oldWidget) { - super.didUpdateWidget(oldWidget); if (widget.selectedSpaceUuid != oldWidget.selectedSpaceUuid) { - setState(() { - _selectedId = widget.selectedSpaceUuid; - }); + setState(() => _selectedId = widget.selectedSpaceUuid); } + super.didUpdateWidget(oldWidget); } - List _filterCommunities() { + List _filteredCommunities() { if (_searchQuery.isEmpty) { _selectedSpaceUuid = null; return widget.communities; @@ -59,17 +54,20 @@ class _SidebarWidgetState extends State { return widget.communities.where((community) { final containsQueryInCommunity = community.name.toLowerCase().contains(_searchQuery.toLowerCase()); - final containsQueryInSpaces = community.spaces - .any((space) => _containsQuery(space, _searchQuery.toLowerCase())); + final containsQueryInSpaces = community.spaces.any((space) => + _containsQuery(space: space, query: _searchQuery.toLowerCase())); return containsQueryInCommunity || containsQueryInSpaces; }).toList(); } - bool _containsQuery(SpaceModel space, String query) { + bool _containsQuery({ + required SpaceModel space, + required String query, + }) { final matchesSpace = space.name.toLowerCase().contains(query); final matchesChildren = space.children.any( - (child) => _containsQuery(child, query), + (child) => _containsQuery(space: child, query: query), ); if (matchesSpace || matchesChildren) { @@ -95,7 +93,7 @@ class _SidebarWidgetState extends State { @override Widget build(BuildContext context) { - final filteredCommunities = _filterCommunities(); + final filteredCommunities = _filteredCommunities(); return Container( width: 300, @@ -104,39 +102,7 @@ class _SidebarWidgetState extends State { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - Container( - decoration: subSectionContainerDecoration, - padding: const EdgeInsets.all(16.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - 'Communities', - style: context.textTheme.titleMedium?.copyWith( - color: ColorsManager.blackColor, - ), - ), - GestureDetector( - onTap: () => _navigateToBlank(context), - child: Container( - width: 30, - height: 30, - decoration: const BoxDecoration( - color: ColorsManager.whiteColors, - shape: BoxShape.circle, - ), - child: Center( - child: SvgPicture.asset( - Assets.roundedAddIcon, - width: 24, - height: 24, - ), - ), - ), - ), - ], - ), - ), + const SidebarHeader(), CustomSearchBar( onSearchChanged: (query) => setState(() => _searchQuery = query), ), @@ -153,15 +119,8 @@ class _SidebarWidgetState extends State { ); } - void _navigateToBlank(BuildContext context) { - setState(() => _selectedId = ''); - context.read().add( - NewCommunityEvent(communities: widget.communities), - ); - } - Widget _buildCommunityTile(BuildContext context, CommunityModel community) { - bool hasChildren = community.spaces.isNotEmpty; + final hasChildren = community.spaces.isNotEmpty; return CommunityTile( title: community.name, @@ -197,12 +156,11 @@ class _SidebarWidgetState extends State { Widget _buildSpaceTile({ required SpaceModel space, required CommunityModel community, - int depth = 1, }) { - bool spaceIsExpanded = _isSpaceOrChildSelected(space); + final spaceIsExpanded = _isSpaceOrChildSelected(space); final isSelected = _selectedId == space.uuid; return Padding( - padding: EdgeInsets.only(left: depth * 16.0), + padding: const EdgeInsetsDirectional.only(start: 16.0), child: SpaceTile( title: space.name, key: ValueKey(space.uuid), From c7c88987631551721db1dde86065c7bce5cfeb77 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Sun, 13 Apr 2025 12:52:38 +0300 Subject: [PATCH 11/19] removed redundant code. --- .../all_spaces/widgets/sidebar_widget.dart | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart index a98dead8..b7eacc0b 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart @@ -120,8 +120,6 @@ class _SidebarWidgetState extends State { } Widget _buildCommunityTile(BuildContext context, CommunityModel community) { - final hasChildren = community.spaces.isNotEmpty; - return CommunityTile( title: community.name, key: ValueKey(community.uuid), @@ -140,16 +138,14 @@ class _SidebarWidgetState extends State { ); }, onExpansionChanged: (title, expanded) {}, - children: hasChildren - ? community.spaces - .where((space) { - final isDeleted = space.status != SpaceStatus.deleted; - final isParentDeleted = space.status != SpaceStatus.parentDeleted; - return (isDeleted || isParentDeleted); - }) - .map((space) => _buildSpaceTile(space: space, community: community)) - .toList() - : null, + children: community.spaces + .where((space) { + final isDeleted = space.status != SpaceStatus.deleted; + final isParentDeleted = space.status != SpaceStatus.parentDeleted; + return (isDeleted || isParentDeleted); + }) + .map((space) => _buildSpaceTile(space: space, community: community)) + .toList(), ); } From 6493f02bccc7c3de144b5375c4807c04389aa79a Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Sun, 13 Apr 2025 13:08:23 +0300 Subject: [PATCH 12/19] simplify if statements readabaility. --- .../all_spaces/widgets/sidebar_widget.dart | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart index b7eacc0b..92e72326 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart @@ -78,14 +78,11 @@ class _SidebarWidgetState extends State { } bool _isSpaceOrChildSelected(SpaceModel space) { - if (_selectedSpaceUuid == space.uuid) { - return true; - } + if (_selectedSpaceUuid == space.uuid) return true; + for (var child in space.children) { - if (_isSpaceOrChildSelected(child)) { - return true; - } + if (_isSpaceOrChildSelected(child)) return true; } return false; From 79f5ef7871df02a53f511ac9df114eba14ec82c3 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Sun, 13 Apr 2025 13:12:48 +0300 Subject: [PATCH 13/19] simplify space selection logic. --- .../all_spaces/widgets/sidebar_widget.dart | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart index 92e72326..b103184e 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart @@ -78,14 +78,9 @@ class _SidebarWidgetState extends State { } bool _isSpaceOrChildSelected(SpaceModel space) { - if (_selectedSpaceUuid == space.uuid) return true; - - - for (var child in space.children) { - if (_isSpaceOrChildSelected(child)) return true; - } - - return false; + final isSpaceSelected = _selectedSpaceUuid == space.uuid; + final anySubSpaceIsSelected = space.children.any(_isSpaceOrChildSelected); + return isSpaceSelected || anySubSpaceIsSelected; } @override From acad0e8c9c79431f6a5158bc7df90a8f4468ac52 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Sun, 13 Apr 2025 14:50:07 +0300 Subject: [PATCH 14/19] SP-1189-FE-Add-Button-Not-clickable-Opening-Pop-up-in-Community-Screen --- .../widgets/sidebar_add_community_button.dart | 51 +++++++++++++------ .../all_spaces/widgets/sidebar_header.dart | 8 +-- .../all_spaces/widgets/sidebar_widget.dart | 5 +- 3 files changed, 44 insertions(+), 20 deletions(-) diff --git a/lib/pages/spaces_management/all_spaces/widgets/sidebar_add_community_button.dart b/lib/pages/spaces_management/all_spaces/widgets/sidebar_add_community_button.dart index 2f434350..5c769d48 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/sidebar_add_community_button.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/sidebar_add_community_button.dart @@ -1,32 +1,51 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/svg.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_event.dart'; +import 'package:syncrow_web/pages/spaces_management/create_community/view/create_community_dialog.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; class SidebarAddCommunityButton extends StatelessWidget { - const SidebarAddCommunityButton({super.key}); + const SidebarAddCommunityButton({ + required this.existingCommunityNames, + super.key, + }); + + final List existingCommunityNames; @override Widget build(BuildContext context) { - return GestureDetector( - onTap: () => _navigateToBlank(context), - child: Container( - width: 30, - height: 30, - decoration: const BoxDecoration( - color: ColorsManager.whiteColors, - shape: BoxShape.circle, - ), - child: Center( - child: SvgPicture.asset( - Assets.roundedAddIcon, - width: 24, - height: 24, + return SizedBox.square( + dimension: 30, + child: IconButton( + style: IconButton.styleFrom( + iconSize: 20, + backgroundColor: ColorsManager.circleImageBackground, + shape: const CircleBorder( + side: BorderSide( + color: ColorsManager.lightGrayBorderColor, + width: 3, + ), ), ), + onPressed: () => _showCreateCommunityDialog(context), + icon: SvgPicture.asset(Assets.addIcon), ), ); } - void _navigateToBlank(BuildContext context) {} + void _showCreateCommunityDialog(BuildContext context) => showDialog( + context: context, + builder: (context) => CreateCommunityDialog( + isEditMode: false, + existingCommunityNames: existingCommunityNames, + onCreateCommunity: (name, description) { + context.read().add( + CreateCommunityEvent(name, description, context), + ); + }, + ), + ); } diff --git a/lib/pages/spaces_management/all_spaces/widgets/sidebar_header.dart b/lib/pages/spaces_management/all_spaces/widgets/sidebar_header.dart index 1e386d4f..135be109 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/sidebar_header.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/sidebar_header.dart @@ -5,13 +5,15 @@ import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:syncrow_web/utils/style.dart'; class SidebarHeader extends StatelessWidget { - const SidebarHeader({super.key}); + const SidebarHeader({required this.existingCommunityNames, super.key}); + + final List existingCommunityNames; @override Widget build(BuildContext context) { return Container( decoration: subSectionContainerDecoration, - padding: const EdgeInsets.all(16.0), + padding: const EdgeInsets.all(16), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -21,7 +23,7 @@ class SidebarHeader extends StatelessWidget { color: ColorsManager.blackColor, ), ), - const SidebarAddCommunityButton(), + SidebarAddCommunityButton(existingCommunityNames: existingCommunityNames), ], ), ); diff --git a/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart index b103184e..35bb8ad2 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart @@ -94,7 +94,10 @@ class _SidebarWidgetState extends State { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - const SidebarHeader(), + SidebarHeader( + existingCommunityNames: + widget.communities.map((community) => community.name).toList(), + ), CustomSearchBar( onSearchChanged: (query) => setState(() => _searchQuery = query), ), From 9bf37243a6dd4f79f9d5cf3c7a2db0d1420f6fa4 Mon Sep 17 00:00:00 2001 From: mohammad Date: Sun, 13 Apr 2025 14:55:55 +0300 Subject: [PATCH 15/19] remove unused code and make a limitation for the nobody time picker --- .../device_managment/ac/model/ac_model.dart | 2 +- .../ac/view/ac_device_control.dart | 2 +- .../wall_sensor/time_wheel.dart | 65 ++++++++++++------- 3 files changed, 44 insertions(+), 25 deletions(-) diff --git a/lib/pages/device_managment/ac/model/ac_model.dart b/lib/pages/device_managment/ac/model/ac_model.dart index c67006b2..6afc778d 100644 --- a/lib/pages/device_managment/ac/model/ac_model.dart +++ b/lib/pages/device_managment/ac/model/ac_model.dart @@ -11,7 +11,7 @@ class AcStatusModel { final bool childLock; final TempModes acMode; final FanSpeeds acFanSpeed; - late final int countdown1; + final int countdown1; AcStatusModel({ required this.uuid, diff --git a/lib/pages/device_managment/ac/view/ac_device_control.dart b/lib/pages/device_managment/ac/view/ac_device_control.dart index 4e8f896c..8c33c853 100644 --- a/lib/pages/device_managment/ac/view/ac_device_control.dart +++ b/lib/pages/device_managment/ac/view/ac_device_control.dart @@ -13,7 +13,7 @@ import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; class AcDeviceControlsView extends StatelessWidget with HelperResponsiveLayout { - AcDeviceControlsView({super.key, required this.device}); + const AcDeviceControlsView({super.key, required this.device}); final AllDevicesModel device; diff --git a/lib/pages/routines/widgets/routine_dialogs/wall_sensor/time_wheel.dart b/lib/pages/routines/widgets/routine_dialogs/wall_sensor/time_wheel.dart index a5b24a22..56f74054 100644 --- a/lib/pages/routines/widgets/routine_dialogs/wall_sensor/time_wheel.dart +++ b/lib/pages/routines/widgets/routine_dialogs/wall_sensor/time_wheel.dart @@ -1,4 +1,3 @@ - import 'package:flutter/material.dart'; import 'package:syncrow_web/utils/color_manager.dart'; @@ -28,9 +27,12 @@ class _TimeWheelPickerState extends State { @override void initState() { super.initState(); - _hoursController = FixedExtentScrollController(initialItem: widget.initialHours); - _minutesController = FixedExtentScrollController(initialItem: widget.initialMinutes); - _secondsController = FixedExtentScrollController(initialItem: widget.initialSeconds); + _hoursController = + FixedExtentScrollController(initialItem: widget.initialHours); + _minutesController = + FixedExtentScrollController(initialItem: widget.initialMinutes); + _secondsController = + FixedExtentScrollController(initialItem: widget.initialSeconds); } @override @@ -47,6 +49,8 @@ class _TimeWheelPickerState extends State { } } + + @override void dispose() { _hoursController.dispose(); @@ -61,26 +65,28 @@ class _TimeWheelPickerState extends State { mainAxisAlignment: MainAxisAlignment.center, children: [ _buildPickerColumn( - label: 'h', - controller: _hoursController, - itemCount: 24, - onChanged: (value) => _handleTimeChange( - value, - _minutesController.selectedItem, - _secondsController.selectedItem, - ), - ), + label: 'h', + controller: _hoursController, + itemCount: 3, + onChanged: (value) { + _handleTimeChange( + value, + _minutesController.selectedItem, + _secondsController.selectedItem, + ); + }), const SizedBox(width: 5), _buildPickerColumn( - label: 'm', - controller: _minutesController, - itemCount: 60, - onChanged: (value) => _handleTimeChange( - _hoursController.selectedItem, - value, - _secondsController.selectedItem, - ), - ), + label: 'm', + controller: _minutesController, + itemCount: 60, + onChanged: (value) { + _handleTimeChange( + _hoursController.selectedItem, + value, + _secondsController.selectedItem, + ); + }), const SizedBox(width: 5), _buildPickerColumn( label: 's', @@ -97,6 +103,19 @@ class _TimeWheelPickerState extends State { } void _handleTimeChange(int hours, int minutes, int seconds) { + int total = hours * 3600 + minutes * 60 + seconds; + if (total > 10000) { + hours = 2; + minutes = 46; + seconds = 40; + total = 10000; + WidgetsBinding.instance.addPostFrameCallback((_) { + _hoursController.jumpToItem(hours); + _minutesController.jumpToItem(minutes); + _secondsController.jumpToItem(seconds); + }); + } + widget.onTimeChanged(hours, minutes, seconds); } @@ -147,4 +166,4 @@ class _TimeWheelPickerState extends State { ], ); } -} \ No newline at end of file +} From 4fae2d6be0c0ba395194a0db67493f8a4ba82617 Mon Sep 17 00:00:00 2001 From: mohammad Date: Sun, 13 Apr 2025 16:19:16 +0300 Subject: [PATCH 16/19] Refactor SosDeviceControlsView --- .../sos/view/sos_device_control_view.dart | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/pages/device_managment/sos/view/sos_device_control_view.dart b/lib/pages/device_managment/sos/view/sos_device_control_view.dart index dff67c55..dde4512b 100644 --- a/lib/pages/device_managment/sos/view/sos_device_control_view.dart +++ b/lib/pages/device_managment/sos/view/sos_device_control_view.dart @@ -13,7 +13,8 @@ import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_la import '../models/sos_status_model.dart'; -class SosDeviceControlsView extends StatelessWidget with HelperResponsiveLayout { +class SosDeviceControlsView extends StatelessWidget + with HelperResponsiveLayout { const SosDeviceControlsView({ super.key, required this.device, @@ -24,7 +25,8 @@ class SosDeviceControlsView extends StatelessWidget with HelperResponsiveLayout @override Widget build(BuildContext context) { return BlocProvider( - create: (context) => SosDeviceBloc()..add(GetDeviceStatus(device.uuid!)), + create: (context) => + SosDeviceBloc()..add(GetDeviceStatus(device.uuid!)), child: BlocBuilder( builder: (context, state) { if (state is SosDeviceLoadingState) { @@ -63,7 +65,8 @@ class SosDeviceControlsView extends StatelessWidget with HelperResponsiveLayout )); } - Widget _buildStatusControls(BuildContext context, SosStatusModel deviceStatus) { + Widget _buildStatusControls( + BuildContext context, SosStatusModel deviceStatus) { final isExtraLarge = isExtraLargeScreenSize(context); final isLarge = isLargeScreenSize(context); final isMedium = isMediumScreenSize(context); @@ -85,7 +88,7 @@ class SosDeviceControlsView extends StatelessWidget with HelperResponsiveLayout IconNameStatusContainer( isFullIcon: false, name: deviceStatus.sosStatus == 'sos' ? 'SOS' : 'Normal', - icon: deviceStatus.sosStatus == 'sos' ? Assets.sos : Assets.sosNormal, + icon: deviceStatus.sosStatus == 'sos' ? Assets.sosNormal : Assets.sos, onTap: () {}, status: false, textColor: ColorsManager.blackColor, From 140f4ff5e20e4e4b760d1ee355c8437db691bc8e Mon Sep 17 00:00:00 2001 From: mohammad Date: Mon, 14 Apr 2025 09:57:25 +0300 Subject: [PATCH 17/19] Refactor AC device controls and toggle widget --- .../device_managment/ac/bloc/ac_bloc.dart | 21 ++++++++++++------- .../shared/toggle_widget.dart | 3 --- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/lib/pages/device_managment/ac/bloc/ac_bloc.dart b/lib/pages/device_managment/ac/bloc/ac_bloc.dart index 076e9050..3da6e848 100644 --- a/lib/pages/device_managment/ac/bloc/ac_bloc.dart +++ b/lib/pages/device_managment/ac/bloc/ac_bloc.dart @@ -331,10 +331,13 @@ class AcBloc extends Bloc { try { final scaledValue = totalMinutes ~/ 6; - await DevicesManagementApi().deviceControl( - deviceId, - Status(code: 'countdown_time', value: scaledValue), - ); + Future.delayed(const Duration(seconds: 1), () async { + await DevicesManagementApi().deviceControl( + deviceId, + Status(code: 'countdown_time', value: scaledValue), + ); + }); + _startCountdownTimer(emit); emit(currentState.copyWith(isTimerActive: timerActive)); } catch (e) { @@ -342,10 +345,12 @@ class AcBloc extends Bloc { emit(AcsFailedState(error: e.toString())); } } else { - await DevicesManagementApi().deviceControl( - deviceId, - Status(code: 'countdown_time', value: 0), - ); + Future.delayed(const Duration(seconds: 1), () async { + await DevicesManagementApi().deviceControl( + deviceId, + Status(code: 'countdown_time', value: 0), + ); + }); _countdownTimer?.cancel(); scheduledHours = 0; scheduledMinutes = 0; diff --git a/lib/pages/device_managment/shared/toggle_widget.dart b/lib/pages/device_managment/shared/toggle_widget.dart index ad0ba8ad..4888572f 100644 --- a/lib/pages/device_managment/shared/toggle_widget.dart +++ b/lib/pages/device_managment/shared/toggle_widget.dart @@ -62,9 +62,6 @@ class ToggleWidget extends StatelessWidget { )), if (showToggle) Container( - height: 20, - width: 35, - padding: const EdgeInsets.only(right: 16, top: 10), child: CupertinoSwitch( value: value, activeColor: ColorsManager.dialogBlueTitle, From 1023170788c73c2f747eb9193fc6482a22e86f00 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Mon, 14 Apr 2025 11:25:36 +0300 Subject: [PATCH 18/19] Sort communities in create new routine dropdown. --- .../routines/create_new_routines/create_new_routines.dart | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/pages/routines/create_new_routines/create_new_routines.dart b/lib/pages/routines/create_new_routines/create_new_routines.dart index 0542f888..8f28208f 100644 --- a/lib/pages/routines/create_new_routines/create_new_routines.dart +++ b/lib/pages/routines/create_new_routines/create_new_routines.dart @@ -63,7 +63,11 @@ class _CreateNewRoutinesDialogState extends State { Padding( padding: const EdgeInsets.only(left: 15, right: 15), child: CommunityDropdown( - communities: _bloc.communities, + communities: _bloc.communities..sort( + (a, b) => a.name.toLowerCase().compareTo( + b.name.toLowerCase(), + ), + ), selectedValue: _selectedCommunity, onChanged: (String? newValue) { setState(() { From db84a9aa5ef3cf0b7d2ba0e8d44d35b37c0071a0 Mon Sep 17 00:00:00 2001 From: mohammad Date: Mon, 14 Apr 2025 16:03:34 +0300 Subject: [PATCH 19/19] fix a logic --- .../device_managment/ac/bloc/ac_bloc.dart | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/lib/pages/device_managment/ac/bloc/ac_bloc.dart b/lib/pages/device_managment/ac/bloc/ac_bloc.dart index 3da6e848..501d29d8 100644 --- a/lib/pages/device_managment/ac/bloc/ac_bloc.dart +++ b/lib/pages/device_managment/ac/bloc/ac_bloc.dart @@ -331,13 +331,14 @@ class AcBloc extends Bloc { try { final scaledValue = totalMinutes ~/ 6; - Future.delayed(const Duration(seconds: 1), () async { - await DevicesManagementApi().deviceControl( - deviceId, - Status(code: 'countdown_time', value: scaledValue), - ); - }); - + await _runDebounce( + isBatch: false, + deviceId: deviceId, + code: 'countdown_time', + value: scaledValue, + oldValue: scaledValue, + emit: emit, + ); _startCountdownTimer(emit); emit(currentState.copyWith(isTimerActive: timerActive)); } catch (e) { @@ -345,12 +346,14 @@ class AcBloc extends Bloc { emit(AcsFailedState(error: e.toString())); } } else { - Future.delayed(const Duration(seconds: 1), () async { - await DevicesManagementApi().deviceControl( - deviceId, - Status(code: 'countdown_time', value: 0), - ); - }); + await _runDebounce( + isBatch: false, + deviceId: deviceId, + code: 'countdown_time', + value: 0, + oldValue: 0, + emit: emit, + ); _countdownTimer?.cancel(); scheduledHours = 0; scheduledMinutes = 0;