Compare commits

..
Author SHA1 Message Date
Faris Armoush d8f40badc0 animated tab buttons. 2025-04-30 09:20:02 +03:00
Faris Armoush fdd5d0feed rem oved comments and removed copyWith fromAnalyticsTabState because the state object only has one property, so i replaced the state object with AnalyticsPageTab enum. 2025-04-30 09:17:52 +03:00
Faris Armoush fb1f79c7bb Made AnalyticsPageTabsAndChildren responsive. 2025-04-30 09:14:30 +03:00
Faris Armoush 1923ac7014 Progress towards 1493. 2025-04-29 17:00:06 +03:00
Faris Armoush c114161357 SP-1492. 2025-04-29 14:17:08 +03:00
mohammadnemer1 fe1dbb66ac Merge pull request #168 from SyncrowIOT/flush-presence-sensor-routines
fix real time garage door and add flush sensor to routines
2025-04-29 10:25:17 +03:00
hannathkadher b4de07de2f Merge pull request #167 from SyncrowIOT:bugfix/fix-repeated-duplication
fixed repeated duplication
2025-04-29 10:14:03 +04:00
hannathkadher acefe6b433 fixed repeated duplication 2025-04-29 10:13:11 +04:00
mohammad 63bc7a56de - Refactor the code in _RoutinesViewState to improve readability and maintainability.
- Update the indentation and add padding to the child widgets in the Column.
- Add a bottom padding to the empty state text in _buildEmptyState.
2025-04-28 16:49:22 +03:00
hannathkadher 7b3635deae Merge pull request #166 from SyncrowIOT/bugfix/clear-search
fixed community search caching
2025-04-28 16:27:38 +04:00
hannathkadher 58755eafe1 Merge branch 'dev' of https://github.com/SyncrowIOT/web into bugfix/clear-search 2025-04-28 16:26:47 +04:00
hannathkadher ce225818fb fixed community search caching 2025-04-28 16:25:43 +04:00
hannathkadher 8762a7aaa8 Merge pull request #165 from SyncrowIOT/bugfix/white-page-rendering 2025-04-28 15:22:37 +04:00
hannathkadher 8dc833b2c3 fixed blank page rendering 2025-04-28 15:21:28 +04:00
hannathkadher 13cef151aa Merge pull request #164 from SyncrowIOT/bugfix/space-model-with-tags 2025-04-28 14:37:26 +04:00
hannathkadher ab23be9828 fixed the issue in selecting space model and tag 2025-04-28 14:36:27 +04:00
hannathkadher 687b68ab22 Merge pull request #163 from SyncrowIOT/fix/duplication-flatten
Fix/duplication-flatten
2025-04-28 13:00:17 +04:00
hannathkadher 25614c3dd0 fix the flatten 2025-04-28 12:59:16 +04:00
hannathkadher 7cbe20ae88 remove unused code 2025-04-28 12:56:29 +04:00
hannathkadher 349fe6c555 realignment 2025-04-28 11:58:41 +04:00
hannathkadher 9779f3783c Merge branch 'dev' of https://github.com/SyncrowIOT/web into dev 2025-04-28 10:03:31 +04:00
hannathkadher fe3db663b6 realign initially 2025-04-28 10:03:27 +04:00
Faris Armoush 888d444752 Merge pull request #160 from SyncrowIOT/SP-1478-FE-On-devices-management-page-when-we-open-power-clamp-device-loading-indicator-remains-loading-and-no-data-is-displayed
Sp 1478 fe on devices management page when we open power clamp device loading indicator remains loading and no data is displayed
2025-04-28 08:59:17 +03:00
Faris Armoush bab5e06968 Merge pull request #159 from SyncrowIOT/SP-1463-rework
Sp 1463 rework
2025-04-28 08:58:39 +03:00
hannathkadher d7b6174dee Merge pull request #162 from SyncrowIOT:bugfix/save-spaces
fixed the save issue
2025-04-28 00:37:33 +04:00
hannathkadher 6ef0b2f9d1 fixed the save issue 2025-04-28 00:36:58 +04:00
hannathkadher 3ceb03826e Merge pull request #161 from SyncrowIOT/bugfix/searchquery 2025-04-27 22:44:57 +04:00
hannathkadher 52608b1f35 fixed search query 2025-04-27 22:42:57 +04:00
Faris Armoush ac2996629e resolved an exception that was thrown when resizing the DeviceSearchFilters. 2025-04-27 15:42:50 +03:00
Faris Armoush 51c52c66cb SP-1478-FE-On-devices-management-page-when-we-open-power-clamp-device-loading-indicator-remains-loading-and-no-data-is-displayed 2025-04-27 15:18:19 +03:00
Faris Armoush 0f56273d99 SP-1408 2025-04-27 12:10:38 +03:00
Faris Armoush 34d4d892d9 refactor: streamline value calculations in FlushMountedPresenceSensorControlView 2025-04-27 11:11:38 +03:00
Faris Armoush 3193fd26fe refactor: update presence delay value and enhance request handling in DebouncedBatchControlDevicesService 2025-04-27 11:04:54 +03:00
Faris Armoush 43802a9fad refactor: update detection value calculations and adjust parameters for presence delay 2025-04-27 10:57:44 +03:00
Faris Armoush 6e0b1775f0 fix: reorder constructor parameters for consistency in FlushMountedPresenceSensorControlView 2025-04-27 10:55:18 +03:00
Faris Armoush 233fb2ee2c refactor: improve formatting of clamp method for near and far detection values 2025-04-27 10:55:03 +03:00
Faris Armoush b26928b3d5 fixed ui bugs. 2025-04-27 10:14:35 +03:00
Faris Armoush 6fc35a7b9a enhanced device debouncing to accomodate for multiple calls from the different devices functions calls. 2025-04-27 10:14:19 +03:00
Faris Armoush 756457927c removed unnecessary isBatch flag from FlushMountedPresenceSensorChangeValueEvent. 2025-04-27 10:13:53 +03:00
hannathkadher f30d7c0117 Merge pull request #158 from SyncrowIOT:bugfix/duplicate-space
Bugfix/duplicate-space
2025-04-27 11:13:07 +04:00
hannathkadher 976d6e385a duplicated space 2025-04-27 11:12:03 +04:00
hannathkadher ff07e7509d fixed the issue in aligning child space 2025-04-26 16:04:41 +04:00
hannathkadher 17a582ab99 Merge pull request #157 from SyncrowIOT/SP-1415 2025-04-25 10:50:06 +04:00
hannathkadher 09fb604acc added filtering 2025-04-25 10:49:25 +04:00
Faris Armoush 2068df173d Merge pull request #155 from SyncrowIOT/SP-1441-rework-FE-On-routine-creation-page-When-the-user-drags-a-card-that-has-signs-and-selects-a-sign-without-a-number-then-confirms-the-value-appears-to-be-Null
Sp 1441 rework fe on routine creation page when the user drags a card that has signs and selects a sign without a number then confirms the value appears to be null
2025-04-24 16:25:39 +03:00
Faris Armoush bfc2a381d2 Merge pull request #156 from SyncrowIOT/SP-1464-FE-implement-Batch-Control-Dialog
Sp 1464 fe implement batch control dialog
2025-04-24 16:25:17 +03:00
Faris Armoush 778257644d reduced debounce duration. 2025-04-24 15:13:10 +03:00
Faris Armoush c8e540e938 Remove unnecessary event dispatch in FlushMountedPresenceSensorBlocFactory creation 2025-04-24 14:29:18 +03:00
Faris Armoush ba20998067 disabled realtime on batch control. 2025-04-24 14:21:38 +03:00
Faris Armoush 75b0b24543 Add Flush Mounted Presence Sensor support and update event handling 2025-04-24 14:13:13 +03:00
Faris Armoush c03b8f290c refactor function tap handlers to use RoutineTapFunctionHelper for improved code reuse and readability, and to remove code duplication. 2025-04-24 10:25:41 +03:00
Faris Armoush 2c684a9495 SP-1441 rework. 2025-04-23 16:58:50 +03:00
mohammadnemer1 fbc45b465f Merge pull request #153 from SyncrowIOT/SP-1344-FE-Real-Time-Issues-Ceiling-Sensor-AC-and-Garage-Door-Sensor
Refactor widget lifecycle methods for temperature control and presenc…
2025-04-23 16:29:36 +03:00
mohammad d1bb7b129f Refactor widget lifecycle methods for temperature control and presence sensor 2025-04-23 10:46:56 +03:00
56 changed files with 1298 additions and 564 deletions
+3
View File
@@ -0,0 +1,3 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M16.25 2.1875H14.6875V1.875C14.6875 1.62636 14.5887 1.3879 14.4129 1.21209C14.2371 1.03627 13.9986 0.9375 13.75 0.9375C13.5014 0.9375 13.2629 1.03627 13.0871 1.21209C12.9113 1.3879 12.8125 1.62636 12.8125 1.875V2.1875H7.1875V1.875C7.1875 1.62636 7.08873 1.3879 6.91291 1.21209C6.7371 1.03627 6.49864 0.9375 6.25 0.9375C6.00136 0.9375 5.7629 1.03627 5.58709 1.21209C5.41127 1.3879 5.3125 1.62636 5.3125 1.875V2.1875H3.75C3.3356 2.1875 2.93817 2.35212 2.64515 2.64515C2.35212 2.93817 2.1875 3.3356 2.1875 3.75V16.25C2.1875 16.6644 2.35212 17.0618 2.64515 17.3549C2.93817 17.6479 3.3356 17.8125 3.75 17.8125H16.25C16.6644 17.8125 17.0618 17.6479 17.3549 17.3549C17.6479 17.0618 17.8125 16.6644 17.8125 16.25V3.75C17.8125 3.3356 17.6479 2.93817 17.3549 2.64515C17.0618 2.35212 16.6644 2.1875 16.25 2.1875ZM5.3125 4.0625C5.3125 4.31114 5.41127 4.5496 5.58709 4.72541C5.7629 4.90123 6.00136 5 6.25 5C6.49864 5 6.7371 4.90123 6.91291 4.72541C7.08873 4.5496 7.1875 4.31114 7.1875 4.0625H12.8125C12.8125 4.31114 12.9113 4.5496 13.0871 4.72541C13.2629 4.90123 13.5014 5 13.75 5C13.9986 5 14.2371 4.90123 14.4129 4.72541C14.5887 4.5496 14.6875 4.31114 14.6875 4.0625H15.9375V5.9375H4.0625V4.0625H5.3125ZM4.0625 15.9375V7.8125H15.9375V15.9375H4.0625Z" fill="#475569"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

@@ -0,0 +1,18 @@
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/analytics/modules/analytics/enums/analytics_page_tab.dart';
part 'analytics_tab_event.dart';
class AnalyticsTabBloc extends Bloc<AnalyticsTabEvent, AnalyticsPageTab> {
AnalyticsTabBloc() : super(AnalyticsPageTab.energyManagement) {
on<UpdateAnalyticsTabEvent>(_onUpdateAnalyticsTabEvent);
}
void _onUpdateAnalyticsTabEvent(
UpdateAnalyticsTabEvent event,
Emitter<AnalyticsPageTab> emit,
) {
emit(event.analyticsTab);
}
}
@@ -0,0 +1,17 @@
part of 'analytics_tab_bloc.dart';
sealed class AnalyticsTabEvent extends Equatable {
const AnalyticsTabEvent();
@override
List<Object> get props => [];
}
class UpdateAnalyticsTabEvent extends AnalyticsTabEvent {
const UpdateAnalyticsTabEvent(this.analyticsTab);
final AnalyticsPageTab analyticsTab;
@override
List<Object> get props => [analyticsTab];
}
@@ -0,0 +1,27 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/analytics/modules/energy_management/views/analytics_energy_management_view.dart';
import 'package:syncrow_web/pages/analytics/modules/occupancy/views/analytics_occupancy_view.dart';
import 'package:syncrow_web/pages/analytics/modules/overview/views/analytics_overview_view.dart';
enum AnalyticsPageTab {
overview(
title: 'Overview',
child: AnalyticsOverviewView(),
),
energyManagement(
title: 'Energy Management',
child: AnalyticsEnergyManagementView(),
),
occupancy(
title: 'Occupancy',
child: AnalyticsOccupancyView(),
);
const AnalyticsPageTab({
required this.title,
required this.child,
});
final Widget child;
final String title;
}
@@ -0,0 +1,42 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/bloc/analytics_tab_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/analytics/widgets/analytics_communities_sidebar.dart';
import 'package:syncrow_web/pages/analytics/modules/analytics/widgets/analytics_page_tabs_and_children.dart';
import 'package:syncrow_web/pages/device_managment/shared/navigate_home_grid_view.dart';
import 'package:syncrow_web/utils/theme/responsive_text_theme.dart';
import 'package:syncrow_web/web_layout/web_scaffold.dart';
class AnalyticsPage extends StatelessWidget {
const AnalyticsPage({super.key});
@override
Widget build(BuildContext context) {
return BlocProvider<AnalyticsTabBloc>(
create: (context) => AnalyticsTabBloc(),
child: const AnalyticsPageForm(),
);
}
}
class AnalyticsPageForm extends StatelessWidget {
const AnalyticsPageForm({super.key});
@override
Widget build(BuildContext context) {
return WebScaffold(
rightBody: const NavigateHomeGridView(),
appBarTitle: Text(
'Syncrow Analytics',
style: ResponsiveTextTheme.of(context).deviceManagementTitle,
),
enableMenuSidebar: false,
scaffoldBody: const Row(
children: [
AnalyticsCommunitiesSidebar(),
Expanded(flex: 5, child: AnalyticsPageTabsAndChildren()),
],
),
);
}
}
@@ -0,0 +1,18 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/space_tree/view/space_tree_view.dart';
class AnalyticsCommunitiesSidebar extends StatelessWidget {
const AnalyticsCommunitiesSidebar({super.key});
@override
Widget build(BuildContext context) {
return Expanded(
child: SpaceTreeView(
title: const Text('Communities'),
shouldDisableDeselectingChildrenOfSelectedParent: true,
onSelect: () {},
isSide: false,
),
);
}
}
@@ -0,0 +1,57 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:intl/intl.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
class AnalyticsDateFilterButton extends StatelessWidget {
const AnalyticsDateFilterButton({super.key});
static final _color = ColorsManager.blackColor.withValues(alpha: 0.8);
@override
Widget build(BuildContext context) {
return TextButton.icon(
style: TextButton.styleFrom(
foregroundColor: _color,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
side: const BorderSide(
color: ColorsManager.greyColor,
width: 1,
),
),
backgroundColor: ColorsManager.transparentColor,
padding: const EdgeInsets.symmetric(
horizontal: 32,
vertical: 16,
),
),
icon: SvgPicture.asset(
Assets.blankCalendar,
height: 20,
width: 20,
colorFilter: ColorFilter.mode(_color, BlendMode.srcIn),
),
label: Text(
_concatenateDate(DateTime(2024, 1), DateTime(2024, 12)),
style: const TextStyle(
fontWeight: FontWeight.w700,
),
),
onPressed: () {},
);
}
String _formatDate(DateTime date) {
final formatter = DateFormat('MMM yyyy');
final formattedDate = formatter.format(date);
return formattedDate;
}
String _concatenateDate(DateTime startDate, DateTime endDate) {
final formattedStartDate = _formatDate(startDate);
final formattedEndDate = _formatDate(endDate);
return '$formattedStartDate - $formattedEndDate';
}
}
@@ -0,0 +1,38 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/bloc/analytics_tab_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/analytics/enums/analytics_page_tab.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class AnalyticsPageTabButton extends StatelessWidget {
const AnalyticsPageTabButton({
super.key,
required this.tab,
required this.isSelected,
});
final AnalyticsPageTab tab;
final bool isSelected;
@override
Widget build(BuildContext context) {
return TextButton(
onPressed: () => context.read<AnalyticsTabBloc>().add(
UpdateAnalyticsTabEvent(tab),
),
child: Text(
tab.title,
textAlign: TextAlign.center,
maxLines: 1,
softWrap: false,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontWeight: isSelected ? FontWeight.w700 : FontWeight.w400,
fontSize: 16,
color:
isSelected ? ColorsManager.slidingBlueColor : ColorsManager.textGray,
),
),
);
}
}
@@ -0,0 +1,84 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/bloc/analytics_tab_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/analytics/enums/analytics_page_tab.dart';
import 'package:syncrow_web/pages/analytics/modules/analytics/widgets/analytics_date_filter_button.dart';
import 'package:syncrow_web/pages/analytics/modules/analytics/widgets/analytics_page_tab_button.dart';
import 'package:syncrow_web/utils/style.dart';
class AnalyticsPageTabsAndChildren extends StatelessWidget {
const AnalyticsPageTabsAndChildren({super.key});
@override
Widget build(BuildContext context) {
return BlocBuilder<AnalyticsTabBloc, AnalyticsPageTab>(
buildWhen: (previous, current) => previous != current,
builder: (context, selectedTab) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Expanded(
child: Container(
width: MediaQuery.sizeOf(context).width * 1,
decoration: subSectionContainerDecoration,
padding: const EdgeInsets.symmetric(horizontal: 32),
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
flex: 4,
child: FittedBox(
alignment: AlignmentDirectional.centerStart,
fit: BoxFit.scaleDown,
child: Row(
spacing: 32,
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
...AnalyticsPageTab.values.map(
(tab) => AnimatedSwitcher(
switchInCurve: Curves.easeIn,
duration: const Duration(milliseconds: 200),
child: AnalyticsPageTabButton(
key: ValueKey(selectedTab),
tab: tab,
isSelected: tab == selectedTab,
),
),
),
],
),
),
),
const Spacer(),
const Expanded(
flex: 2,
child: FittedBox(
fit: BoxFit.scaleDown,
alignment: AlignmentDirectional.centerEnd,
child: AnalyticsDateFilterButton(),
),
),
],
),
),
),
Expanded(
flex: 8,
child: SizedBox(
width: MediaQuery.sizeOf(context).width,
child: AnimatedSwitcher(
switchInCurve: Curves.easeIn,
duration: const Duration(milliseconds: 200),
child: selectedTab.child,
),
),
),
],
),
);
}
}
@@ -0,0 +1,12 @@
import 'package:flutter/material.dart';
class AnalyticsEnergyManagementView extends StatelessWidget {
const AnalyticsEnergyManagementView({super.key});
@override
Widget build(BuildContext context) {
return const Center(
child: Text('EnergyManagementView is Working!'),
);
}
}
@@ -0,0 +1,12 @@
import 'package:flutter/material.dart';
class AnalyticsOccupancyView extends StatelessWidget {
const AnalyticsOccupancyView({super.key});
@override
Widget build(BuildContext context) {
return const Center(
child: Text('AnalyticsOccupancyView is Working!'),
);
}
}
@@ -0,0 +1,12 @@
import 'package:flutter/material.dart';
class AnalyticsOverviewView extends StatelessWidget {
const AnalyticsOverviewView({super.key});
@override
Widget build(BuildContext context) {
return const Center(
child: Text('Coming Soon!'),
);
}
}
@@ -1,8 +1,8 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/common/buttons/default_button.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'; import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:syncrow_web/utils/style.dart'; import 'package:syncrow_web/utils/style.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class SearchResetButtons extends StatelessWidget { class SearchResetButtons extends StatelessWidget {
const SearchResetButtons({ const SearchResetButtons({
@@ -17,8 +17,10 @@ class SearchResetButtons extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Row( return Row(
mainAxisSize: MainAxisSize.min,
children: [ children: [
Column( Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
const SizedBox(height: 25), const SizedBox(height: 25),
@@ -60,7 +60,15 @@ class _CurrentTempState extends State<CurrentTemp> {
); );
}); });
} }
@override
void didUpdateWidget(CurrentTemp oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.tempSet != widget.tempSet) {
setState(() {
_adjustedValue = _initialAdjustedValue(widget.tempSet);
});
}
}
@override @override
void dispose() { void dispose() {
_debounce?.cancel(); _debounce?.cancel();
@@ -9,6 +9,7 @@ import 'package:syncrow_web/pages/device_managment/curtain/view/curtain_batch_st
import 'package:syncrow_web/pages/device_managment/curtain/view/curtain_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_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/door_lock/view/door_lock_control_view.dart';
import 'package:syncrow_web/pages/device_managment/flush_mounted_presence_sensor/views/flush_mounted_presence_sensor_batch_control_view.dart';
import 'package:syncrow_web/pages/device_managment/flush_mounted_presence_sensor/views/flush_mounted_presence_sensor_control_view.dart'; import 'package:syncrow_web/pages/device_managment/flush_mounted_presence_sensor/views/flush_mounted_presence_sensor_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_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/garage_door/view/garage_door_control_view.dart';
@@ -198,6 +199,10 @@ mixin RouteControlsBasedCode {
return SOSBatchControlView( return SOSBatchControlView(
deviceIds: devices.where((e) => (e.productType == 'SOS')).map((e) => e.uuid!).toList(), deviceIds: devices.where((e) => (e.productType == 'SOS')).map((e) => e.uuid!).toList(),
); );
case 'NCPS':
return FlushMountedPresenceSensorBatchControlView(
devicesIds: devices.where((e) => (e.productType == 'NCPS')).map((e) => e.uuid!).toList(),
);
default: default:
return const SizedBox(); return const SizedBox();
} }
@@ -108,7 +108,7 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout {
return DeviceManagementBody( return DeviceManagementBody(
devices: deviceState.filteredDevices); devices: deviceState.filteredDevices);
} else { } else {
return const Center(child: Text('Error fetching Devices')); return const DeviceManagementBody(devices: []);
} }
}, },
); );
@@ -72,6 +72,7 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
child: state is DeviceManagementLoading child: state is DeviceManagementLoading
? const Center(child: CircularProgressIndicator()) ? const Center(child: CircularProgressIndicator())
: Column( : Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Container( Container(
padding: isLargeScreenSize(context) padding: isLargeScreenSize(context)
@@ -14,29 +14,29 @@ class DeviceSearchFilters extends StatefulWidget {
class _DeviceSearchFiltersState extends State<DeviceSearchFilters> class _DeviceSearchFiltersState extends State<DeviceSearchFilters>
with HelperResponsiveLayout { with HelperResponsiveLayout {
final _unitNameController = TextEditingController(); late final TextEditingController _unitNameController;
final _productNameController = TextEditingController(); late final TextEditingController _productNameController;
List<Widget> get _widgets => [ @override
_buildSearchField("Space Name", _unitNameController, 200), void initState() {
_buildSearchField("Device Name / Product Name", _productNameController, 300), _unitNameController = TextEditingController();
_buildSearchResetButtons(), _productNameController = TextEditingController();
]; super.initState();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (isExtraLargeScreenSize(context)) {
return Row(
children: _widgets
.map((e) => Padding(padding: const EdgeInsets.all(10), child: e))
.toList(),
);
}
return Wrap( return Wrap(
alignment: WrapAlignment.start,
runAlignment: WrapAlignment.start,
crossAxisAlignment: WrapCrossAlignment.center,
spacing: 20, spacing: 20,
runSpacing: 10, runSpacing: 10,
children: _widgets, children: [
_buildSearchField("Space Name", _unitNameController, 200),
_buildSearchField("Device Name / Product Name", _productNameController, 300),
_buildSearchResetButtons(),
],
); );
} }
@@ -16,7 +16,8 @@ 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/constants/assets.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
class CeilingSensorControlsView extends StatelessWidget with HelperResponsiveLayout { class CeilingSensorControlsView extends StatelessWidget
with HelperResponsiveLayout {
const CeilingSensorControlsView({super.key, required this.device}); const CeilingSensorControlsView({super.key, required this.device});
final AllDevicesModel device; final AllDevicesModel device;
@@ -31,29 +32,35 @@ class CeilingSensorControlsView extends StatelessWidget with HelperResponsiveLay
..add(CeilingInitialEvent(device.uuid ?? '')), ..add(CeilingInitialEvent(device.uuid ?? '')),
child: BlocBuilder<CeilingSensorBloc, CeilingSensorState>( child: BlocBuilder<CeilingSensorBloc, CeilingSensorState>(
builder: (context, state) { builder: (context, state) {
if (state is CeilingLoadingInitialState || state is CeilingReportsLoadingState) { if (state is CeilingLoadingInitialState ||
state is CeilingReportsLoadingState) {
return const Center(child: CircularProgressIndicator()); return const Center(child: CircularProgressIndicator());
} else if (state is CeilingUpdateState) { } else if (state is CeilingUpdateState) {
return _buildGridView( return _buildGridView(context, state.ceilingSensorModel,
context, state.ceilingSensorModel, isExtraLarge, isLarge, isMedium); isExtraLarge, isLarge, isMedium);
} else if (state is CeilingReportsState) { } else if (state is CeilingReportsState) {
return ReportsTable( return ReportsTable(
report: state.deviceReport, report: state.deviceReport,
onRowTap: (index) {}, onRowTap: (index) {},
onClose: () { onClose: () {
context.read<CeilingSensorBloc>().add(BackToCeilingGridViewEvent()); context
.read<CeilingSensorBloc>()
.add(BackToCeilingGridViewEvent());
}, },
); );
} else if (state is ShowCeilingDescriptionState) { } else if (state is ShowCeilingDescriptionState) {
return DescriptionView( return DescriptionView(
description: state.description, description: state.description,
onClose: () { onClose: () {
context.read<CeilingSensorBloc>().add(BackToCeilingGridViewEvent()); context
.read<CeilingSensorBloc>()
.add(BackToCeilingGridViewEvent());
}, },
); );
} else if (state is CeilingReportsFailedState) { } else if (state is CeilingReportsFailedState) {
final model = context.read<CeilingSensorBloc>().deviceStatus; final model = context.read<CeilingSensorBloc>().deviceStatus;
return _buildGridView(context, model, isExtraLarge, isLarge, isMedium); return _buildGridView(
context, model, isExtraLarge, isLarge, isMedium);
} }
return const Center(child: Text('Error fetching status')); return const Center(child: Text('Error fetching status'));
}, },
@@ -61,8 +68,8 @@ class CeilingSensorControlsView extends StatelessWidget with HelperResponsiveLay
); );
} }
Widget _buildGridView(BuildContext context, CeilingSensorModel model, bool isExtraLarge, Widget _buildGridView(BuildContext context, CeilingSensorModel model,
bool isLarge, bool isMedium) { bool isExtraLarge, bool isLarge, bool isMedium) {
return GridView( return GridView(
padding: const EdgeInsets.symmetric(horizontal: 50), padding: const EdgeInsets.symmetric(horizontal: 50),
shrinkWrap: true, shrinkWrap: true,
@@ -143,8 +150,8 @@ class CeilingSensorControlsView extends StatelessWidget with HelperResponsiveLay
), ),
GestureDetector( GestureDetector(
onTap: () { onTap: () {
context.read<CeilingSensorBloc>().add( context.read<CeilingSensorBloc>().add(GetCeilingDeviceReportsEvent(
GetCeilingDeviceReportsEvent(code: 'presence_state', deviceUuid: device.uuid!)); code: 'presence_state', deviceUuid: device.uuid!));
}, },
child: const PresenceStaticWidget( child: const PresenceStaticWidget(
icon: Assets.illuminanceRecordIcon, icon: Assets.illuminanceRecordIcon,
@@ -153,9 +160,8 @@ class CeilingSensorControlsView extends StatelessWidget with HelperResponsiveLay
), ),
GestureDetector( GestureDetector(
onTap: () { onTap: () {
context context.read<CeilingSensorBloc>().add(GetCeilingDeviceReportsEvent(
.read<CeilingSensorBloc>() code: '', deviceUuid: device.uuid!));
.add(GetCeilingDeviceReportsEvent(code: '', deviceUuid: device.uuid!));
}, },
child: const PresenceStaticWidget( child: const PresenceStaticWidget(
icon: Assets.helpDescriptionIcon, icon: Assets.helpDescriptionIcon,
@@ -49,6 +49,9 @@ class FlushMountedPresenceSensorBloc
on<FlushMountedPresenceSensorFactoryResetEvent>( on<FlushMountedPresenceSensorFactoryResetEvent>(
_onFlushMountedPresenceSensorFactoryResetEvent, _onFlushMountedPresenceSensorFactoryResetEvent,
); );
on<FlushMountedPresenceSensorStatusUpdatedEvent>(
_onFlushMountedPresenceSensorStatusUpdatedEvent,
);
} }
void _onFlushMountedPresenceSensorFetchStatusEvent( void _onFlushMountedPresenceSensorFetchStatusEvent(
@@ -60,7 +63,7 @@ class FlushMountedPresenceSensorBloc
final response = await DevicesManagementApi().getDeviceStatus(deviceId); final response = await DevicesManagementApi().getDeviceStatus(deviceId);
deviceStatus = FlushMountedPresenceSensorModel.fromJson(response.status); deviceStatus = FlushMountedPresenceSensorModel.fromJson(response.status);
emit(FlushMountedPresenceSensorUpdateState(model: deviceStatus)); emit(FlushMountedPresenceSensorUpdateState(model: deviceStatus));
_listenToChanges(emit, deviceId); _listenToChanges(deviceId);
} catch (e) { } catch (e) {
emit(FlushMountedPresenceSensorFailedState(error: e.toString())); emit(FlushMountedPresenceSensorFailedState(error: e.toString()));
return; return;
@@ -81,31 +84,33 @@ class FlushMountedPresenceSensorBloc
} }
} }
Future<void> _listenToChanges( void _listenToChanges(String deviceId) {
Emitter<FlushMountedPresenceSensorState> emit, try {
String deviceId, final ref = FirebaseDatabase.instance.ref(
) async { 'device-status/$deviceId',
final ref = FirebaseDatabase.instance.ref( );
'device-status/$deviceId',
); ref.onValue.listen((event) {
final eventsMap = event.snapshot.value as Map<dynamic, dynamic>;
await ref.onValue.listen(
(DatabaseEvent event) async {
Map<dynamic, dynamic> usersMap =
event.snapshot.value as Map<dynamic, dynamic>;
List<Status> statusList = []; List<Status> statusList = [];
eventsMap['status'].forEach((element) {
(usersMap['status'] as List<dynamic>?)?.forEach((element) { statusList.add(
statusList.add(Status(code: element['code'], value: element['value'])); Status(code: element['code'], value: element['value']),
);
}); });
deviceStatus = FlushMountedPresenceSensorModel.fromJson(statusList); deviceStatus = FlushMountedPresenceSensorModel.fromJson(statusList);
if (!emit.isDone) { if (!isClosed) {
emit(FlushMountedPresenceSensorLoadingNewSate(model: deviceStatus)); add(FlushMountedPresenceSensorStatusUpdatedEvent(deviceStatus));
} }
}, });
onError: (error) => log(error.toString(), name: 'FirebaseDatabaseError'), } catch (_) {
).asFuture(); log(
'Error listening to changes',
name: 'FlushMountedPresenceSensorBloc._listenToChanges',
);
}
} }
void _onFlushMountedPresenceSensorChangeValueEvent( void _onFlushMountedPresenceSensorChangeValueEvent(
@@ -234,4 +239,12 @@ class FlushMountedPresenceSensorBloc
emit(FlushMountedPresenceSensorFailedState(error: e.toString())); emit(FlushMountedPresenceSensorFailedState(error: e.toString()));
} }
} }
void _onFlushMountedPresenceSensorStatusUpdatedEvent(
FlushMountedPresenceSensorStatusUpdatedEvent event,
Emitter<FlushMountedPresenceSensorState> emit,
) {
deviceStatus = event.model;
emit(FlushMountedPresenceSensorUpdateState(model: deviceStatus));
}
} }
@@ -10,15 +10,24 @@ sealed class FlushMountedPresenceSensorEvent extends Equatable {
class FlushMountedPresenceSensorFetchStatusEvent class FlushMountedPresenceSensorFetchStatusEvent
extends FlushMountedPresenceSensorEvent {} extends FlushMountedPresenceSensorEvent {}
class FlushMountedPresenceSensorStatusUpdatedEvent
extends FlushMountedPresenceSensorEvent {
const FlushMountedPresenceSensorStatusUpdatedEvent(this.model);
final FlushMountedPresenceSensorModel model;
@override
List<Object> get props => [model];
}
class FlushMountedPresenceSensorChangeValueEvent class FlushMountedPresenceSensorChangeValueEvent
extends FlushMountedPresenceSensorEvent { extends FlushMountedPresenceSensorEvent {
final int value; final int value;
final String code; final String code;
final bool isBatchControl;
const FlushMountedPresenceSensorChangeValueEvent({ const FlushMountedPresenceSensorChangeValueEvent({
required this.value, required this.value,
required this.code, required this.code,
this.isBatchControl = false,
}); });
@override @override
@@ -16,6 +16,6 @@ abstract final class FlushMountedPresenceSensorBlocFactory {
batchControlDevicesService: DebouncedBatchControlDevicesService( batchControlDevicesService: DebouncedBatchControlDevicesService(
decoratee: RemoteBatchControlDevicesService(), decoratee: RemoteBatchControlDevicesService(),
), ),
)..add(FlushMountedPresenceSensorFetchStatusEvent()); );
} }
} }
@@ -22,7 +22,7 @@ class FlushMountedPresenceSensorBatchControlView extends StatelessWidget
return BlocProvider( return BlocProvider(
create: (context) => FlushMountedPresenceSensorBlocFactory.create( create: (context) => FlushMountedPresenceSensorBlocFactory.create(
deviceId: devicesIds.first, deviceId: devicesIds.first,
), )..add(FlushMountedPresenceSensorFetchBatchStatusEvent(devicesIds)),
child: BlocBuilder<FlushMountedPresenceSensorBloc, child: BlocBuilder<FlushMountedPresenceSensorBloc,
FlushMountedPresenceSensorState>( FlushMountedPresenceSensorState>(
builder: (context, state) { builder: (context, state) {
@@ -67,14 +67,15 @@ class FlushMountedPresenceSensorBatchControlView extends StatelessWidget
maxValue: 9, maxValue: 9,
steps: 1, steps: 1,
action: (int value) => context.read<FlushMountedPresenceSensorBloc>().add( action: (int value) => context.read<FlushMountedPresenceSensorBloc>().add(
FlushMountedPresenceSensorChangeValueEvent( FlushMountedPresenceSensorBatchControlEvent(
deviceIds: devicesIds,
code: FlushMountedPresenceSensorModel.codeSensitivity, code: FlushMountedPresenceSensorModel.codeSensitivity,
value: value, value: value,
), ),
), ),
), ),
PresenceUpdateData( PresenceUpdateData(
value: (model.nearDetection / 100).toDouble(), value: (model.nearDetection / 100).clamp(0.0, double.infinity),
title: 'Nearest Detect Dist:', title: 'Nearest Detect Dist:',
description: 'm', description: 'm',
minValue: 0.0, minValue: 0.0,
@@ -83,14 +84,15 @@ class FlushMountedPresenceSensorBatchControlView extends StatelessWidget
valuesPercision: 1, valuesPercision: 1,
action: (double value) => action: (double value) =>
context.read<FlushMountedPresenceSensorBloc>().add( context.read<FlushMountedPresenceSensorBloc>().add(
FlushMountedPresenceSensorChangeValueEvent( FlushMountedPresenceSensorBatchControlEvent(
deviceIds: devicesIds,
code: FlushMountedPresenceSensorModel.codeNearDetection, code: FlushMountedPresenceSensorModel.codeNearDetection,
value: (value * 100).toInt(), value: (value * 100).toInt(),
), ),
), ),
), ),
PresenceUpdateData( PresenceUpdateData(
value: (model.farDetection / 100).toDouble(), value: (model.farDetection / 100).clamp(0.0, double.infinity),
title: 'Max Detect Dist:', title: 'Max Detect Dist:',
description: 'm', description: 'm',
minValue: 0.0, minValue: 0.0,
@@ -99,51 +101,57 @@ class FlushMountedPresenceSensorBatchControlView extends StatelessWidget
valuesPercision: 1, valuesPercision: 1,
action: (double value) => action: (double value) =>
context.read<FlushMountedPresenceSensorBloc>().add( context.read<FlushMountedPresenceSensorBloc>().add(
FlushMountedPresenceSensorChangeValueEvent( FlushMountedPresenceSensorBatchControlEvent(
deviceIds: devicesIds,
code: FlushMountedPresenceSensorModel.codeFarDetection, code: FlushMountedPresenceSensorModel.codeFarDetection,
value: (value * 100).toInt(), value: (value * 100).toInt(),
), ),
), ),
), ),
PresenceUpdateData( PresenceUpdateData(
value: model.presenceDelay.toDouble(), value: model.sensiReduce.toDouble(),
title: 'Trigger Level:', title: 'Trigger Level:',
minValue: 0, minValue: 0,
maxValue: 3, maxValue: 3,
steps: 1, steps: 1,
action: (int value) => context.read<FlushMountedPresenceSensorBloc>().add( action: (int value) => context.read<FlushMountedPresenceSensorBloc>().add(
FlushMountedPresenceSensorChangeValueEvent( FlushMountedPresenceSensorBatchControlEvent(
code: FlushMountedPresenceSensorModel.codePresenceDelay, deviceIds: devicesIds,
code: FlushMountedPresenceSensorModel.codeSensiReduce,
value: value, value: value,
), ),
), ),
), ),
PresenceUpdateData( PresenceUpdateData(
value: (model.occurDistReduce.toDouble()), value: model.occurDistReduce.toDouble(),
title: 'Indent Level:', title: 'Indent Level:',
minValue: 0, minValue: 0,
maxValue: 3, maxValue: 3,
steps: 1, steps: 1,
action: (int value) => context.read<FlushMountedPresenceSensorBloc>().add( action: (int value) => context.read<FlushMountedPresenceSensorBloc>().add(
FlushMountedPresenceSensorChangeValueEvent( FlushMountedPresenceSensorBatchControlEvent(
deviceIds: devicesIds,
code: FlushMountedPresenceSensorModel.codeOccurDistReduce, code: FlushMountedPresenceSensorModel.codeOccurDistReduce,
value: value, value: value,
), ),
), ),
), ),
PresenceUpdateData( PresenceUpdateData(
value: (model.sensiReduce.toDouble()), value: (model.presenceDelay / 10).toDouble(),
title: 'Target Confirm Time:', title: 'Target Confirm Time:',
description: 's', description: 's',
minValue: 0, minValue: 0.0,
maxValue: 3, maxValue: 0.5,
steps: 1, steps: 0.1,
action: (int value) => context.read<FlushMountedPresenceSensorBloc>().add( valuesPercision: 1,
FlushMountedPresenceSensorChangeValueEvent( action: (double value) =>
code: FlushMountedPresenceSensorModel.codeSensiReduce, context.read<FlushMountedPresenceSensorBloc>().add(
value: value, FlushMountedPresenceSensorBatchControlEvent(
), deviceIds: devicesIds,
), code: FlushMountedPresenceSensorModel.codePresenceDelay,
value: (value * 10).toInt(),
),
),
), ),
PresenceUpdateData( PresenceUpdateData(
value: ((model.noneDelay / 10).toDouble()), value: ((model.noneDelay / 10).toDouble()),
@@ -154,7 +162,8 @@ class FlushMountedPresenceSensorBatchControlView extends StatelessWidget
steps: 1, steps: 1,
action: (double value) => action: (double value) =>
context.read<FlushMountedPresenceSensorBloc>().add( context.read<FlushMountedPresenceSensorBloc>().add(
FlushMountedPresenceSensorChangeValueEvent( FlushMountedPresenceSensorBatchControlEvent(
deviceIds: devicesIds,
code: FlushMountedPresenceSensorModel.codeNoneDelay, code: FlushMountedPresenceSensorModel.codeNoneDelay,
value: (value * 10).round(), value: (value * 10).round(),
), ),
@@ -15,7 +15,7 @@ import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_la
class FlushMountedPresenceSensorControlView extends StatelessWidget class FlushMountedPresenceSensorControlView extends StatelessWidget
with HelperResponsiveLayout { with HelperResponsiveLayout {
const FlushMountedPresenceSensorControlView({super.key, required this.device}); const FlushMountedPresenceSensorControlView({required this.device, super.key});
final AllDevicesModel device; final AllDevicesModel device;
@@ -24,7 +24,7 @@ class FlushMountedPresenceSensorControlView extends StatelessWidget
return BlocProvider( return BlocProvider(
create: (context) => FlushMountedPresenceSensorBlocFactory.create( create: (context) => FlushMountedPresenceSensorBlocFactory.create(
deviceId: device.uuid ?? '-1', deviceId: device.uuid ?? '-1',
), )..add(FlushMountedPresenceSensorFetchStatusEvent()),
child: BlocBuilder<FlushMountedPresenceSensorBloc, child: BlocBuilder<FlushMountedPresenceSensorBloc,
FlushMountedPresenceSensorState>( FlushMountedPresenceSensorState>(
builder: (context, state) { builder: (context, state) {
@@ -113,7 +113,7 @@ class FlushMountedPresenceSensorControlView extends StatelessWidget
), ),
), ),
PresenceUpdateData( PresenceUpdateData(
value: (model.nearDetection / 100).toDouble(), value: (model.nearDetection / 100).clamp(0.0, double.infinity),
title: 'Nearest Detect Dist:', title: 'Nearest Detect Dist:',
description: 'm', description: 'm',
minValue: 0.0, minValue: 0.0,
@@ -129,7 +129,7 @@ class FlushMountedPresenceSensorControlView extends StatelessWidget
), ),
), ),
PresenceUpdateData( PresenceUpdateData(
value: (model.farDetection / 100).toDouble(), value: (model.farDetection / 100).clamp(0.0, double.infinity),
title: 'Max Detect Dist:', title: 'Max Detect Dist:',
description: 'm', description: 'm',
minValue: 0.0, minValue: 0.0,
@@ -145,20 +145,20 @@ class FlushMountedPresenceSensorControlView extends StatelessWidget
), ),
), ),
PresenceUpdateData( PresenceUpdateData(
value: (model.presenceDelay.toDouble()), value: model.sensiReduce.toDouble(),
title: 'Trigger Level:', title: 'Trigger Level:',
minValue: 0, minValue: 0,
maxValue: 3, maxValue: 3,
steps: 1, steps: 1,
action: (int value) => context.read<FlushMountedPresenceSensorBloc>().add( action: (int value) => context.read<FlushMountedPresenceSensorBloc>().add(
FlushMountedPresenceSensorChangeValueEvent( FlushMountedPresenceSensorChangeValueEvent(
code: FlushMountedPresenceSensorModel.codePresenceDelay, code: FlushMountedPresenceSensorModel.codeSensiReduce,
value: value, value: value,
), ),
), ),
), ),
PresenceUpdateData( PresenceUpdateData(
value: (model.occurDistReduce.toDouble()), value: model.occurDistReduce.toDouble(),
title: 'Indent Level:', title: 'Indent Level:',
minValue: 0, minValue: 0,
maxValue: 3, maxValue: 3,
@@ -171,21 +171,23 @@ class FlushMountedPresenceSensorControlView extends StatelessWidget
), ),
), ),
PresenceUpdateData( PresenceUpdateData(
value: (model.sensiReduce.toDouble()), value: (model.presenceDelay / 10).toDouble(),
valuesPercision: 1,
title: 'Target Confirm Time:', title: 'Target Confirm Time:',
description: 's', description: 's',
minValue: 0, minValue: 0.0,
maxValue: 3, maxValue: 0.5,
steps: 1, steps: 0.1,
action: (int value) => context.read<FlushMountedPresenceSensorBloc>().add( action: (double value) =>
FlushMountedPresenceSensorChangeValueEvent( context.read<FlushMountedPresenceSensorBloc>().add(
code: FlushMountedPresenceSensorModel.codeSensiReduce, FlushMountedPresenceSensorChangeValueEvent(
value: value, code: FlushMountedPresenceSensorModel.codePresenceDelay,
), value: (value * 10).toInt(),
), ),
),
), ),
PresenceUpdateData( PresenceUpdateData(
value: ((model.noneDelay / 10).toDouble()), value: (model.noneDelay / 10).toDouble(),
description: 's', description: 's',
title: 'Disappe Delay:', title: 'Disappe Delay:',
minValue: 20, minValue: 20,
@@ -217,29 +217,31 @@ class SmartPowerBloc extends Bloc<SmartPowerEvent, SmartPowerState> {
try { try {
var status = var status =
await DevicesManagementApi().getPowerClampInfo(event.deviceId); await DevicesManagementApi().getPowerClampInfo(event.deviceId);
deviceStatus = PowerClampModel.fromJson(status); deviceStatus = PowerClampModel.fromJson(status as Map<String, Object?>? ??{});
final phaseADataPoints = deviceStatus.status.phaseA.dataPoints;
final phaseBDataPoints = deviceStatus.status.phaseB.dataPoints;
final phaseCDataPoints = deviceStatus.status.phaseC.dataPoints;
phaseData = [ phaseData = [
{ {
'name': 'Phase A', 'name': 'Phase A',
'voltage': '${deviceStatus.status.phaseA.dataPoints[0].value / 10} V', 'voltage': '${(phaseADataPoints.elementAtOrNull(0)?.value as num? ?? 0) / 10} V',
'current': '${deviceStatus.status.phaseA.dataPoints[1].value / 10} A', 'current': '${(phaseADataPoints.elementAtOrNull(1)?.value as num? ?? 0) / 10} A',
'activePower': '${deviceStatus.status.phaseA.dataPoints[2].value} W', 'activePower': '${phaseADataPoints.elementAtOrNull(2)?.value??'N/A'} W',
'powerFactor': '${deviceStatus.status.phaseA.dataPoints[3].value}', 'powerFactor': '${phaseADataPoints.elementAtOrNull(3)?.value??'N/A'}',
}, },
{ {
'name': 'Phase B', 'name': 'Phase B',
'voltage': '${deviceStatus.status.phaseB.dataPoints[0].value / 10} V', 'voltage': '${(phaseBDataPoints .elementAtOrNull(0)?.value as num? ?? 0) / 10} V',
'current': '${deviceStatus.status.phaseB.dataPoints[1].value / 10} A', 'current': '${(phaseBDataPoints .elementAtOrNull(1)?.value as num? ?? 0) / 10} A',
'activePower': '${deviceStatus.status.phaseB.dataPoints[2].value} W', 'activePower': '${phaseBDataPoints.elementAtOrNull(2)?.value??'N/A'} W',
'powerFactor': '${deviceStatus.status.phaseB.dataPoints[3].value}', 'powerFactor': '${phaseBDataPoints.elementAtOrNull(3)?.value??'N/A'}',
}, },
{ {
'name': 'Phase C', 'name': 'Phase C',
'voltage': '${deviceStatus.status.phaseC.dataPoints[0].value / 10} V', 'voltage': '${(phaseCDataPoints.elementAtOrNull(0)?.value as num? ?? 0) / 10} V',
'current': '${deviceStatus.status.phaseC.dataPoints[1].value / 10} A', 'current': '${(phaseCDataPoints.elementAtOrNull(1)?.value as num? ?? 0) / 10} A',
'activePower': '${deviceStatus.status.phaseC.dataPoints[2].value} W', 'activePower': '${phaseCDataPoints.elementAtOrNull(2)?.value ?? 'N/A'} W',
'powerFactor': '${deviceStatus.status.phaseC.dataPoints[3].value}', 'powerFactor': '${phaseCDataPoints.elementAtOrNull(3)?.value ?? 'N/A'}',
}, },
]; ];
emit(GetDeviceStatus()); emit(GetDeviceStatus());
@@ -785,7 +787,7 @@ class SmartPowerBloc extends Bloc<SmartPowerEvent, SmartPowerState> {
void selectDateRange() async { void selectDateRange() async {
DateTime startDate = dateTime!; DateTime startDate = dateTime!;
DateTime endDate = DateTime(startDate.year, startDate.month + 1, 1) DateTime endDate = DateTime(startDate.year, startDate.month + 1, 1)
.subtract(Duration(days: 1)); .subtract(const Duration(days: 1));
String formattedEndDate = DateFormat('dd/MM/yyyy').format(endDate); String formattedEndDate = DateFormat('dd/MM/yyyy').format(endDate);
endChartDate = ' - $formattedEndDate'; endChartDate = ' - $formattedEndDate';
} }
@@ -12,9 +12,9 @@ class PowerClampModel {
factory PowerClampModel.fromJson(Map<String, dynamic> json) { factory PowerClampModel.fromJson(Map<String, dynamic> json) {
return PowerClampModel( return PowerClampModel(
productUuid: json['productUuid'], productUuid: json['productUuid'] as String? ?? '',
productType: json['productType'], productType: json['productType'] as String? ?? '',
status: PowerStatus.fromJson(json['status']), status: PowerStatus.fromJson(json['status'] as Map<String, dynamic>? ?? {}),
); );
} }
@@ -26,7 +26,7 @@ class PowerClampModel {
return PowerClampModel( return PowerClampModel(
productUuid: productUuid ?? this.productUuid, productUuid: productUuid ?? this.productUuid,
productType: productType ?? this.productType, productType: productType ?? this.productType,
status: statusPower ?? this.status, status: statusPower ?? status,
); );
} }
} }
@@ -46,12 +46,10 @@ class PowerStatus {
factory PowerStatus.fromJson(Map<String, dynamic> json) { factory PowerStatus.fromJson(Map<String, dynamic> json) {
return PowerStatus( return PowerStatus(
phaseA: Phase.fromJson(json['phaseA']), phaseA: Phase.fromJson(json['phaseA']as List<dynamic>? ?? []),
phaseB: Phase.fromJson(json['phaseB']), phaseB: Phase.fromJson(json['phaseB']as List<dynamic>? ?? []),
phaseC: Phase.fromJson(json['phaseC']), phaseC: Phase.fromJson(json['phaseC']as List<dynamic>? ?? []),
general: Phase.fromJson(json['general'] general: Phase.fromJson(json['general']as List<dynamic>? ?? []
// List<DataPoint>.from(
// json['general'].map((x) => DataPoint.fromJson(x))),
)); ));
} }
} }
@@ -69,30 +67,30 @@ class Phase {
} }
class DataPoint { class DataPoint {
dynamic code; final String? code;
dynamic customName; final String? customName;
dynamic dpId; final int? dpId;
dynamic time; final int? time;
dynamic type; final String? type;
dynamic value; final dynamic value;
DataPoint({ DataPoint({
required this.code, this.code,
required this.customName, this.customName,
required this.dpId, this.dpId,
required this.time, this.time,
required this.type, this.type,
required this.value, this.value,
}); });
factory DataPoint.fromJson(Map<String, dynamic> json) { factory DataPoint.fromJson(Map<String, dynamic> json) {
return DataPoint( return DataPoint(
code: json['code'], code: json['code'] as String?,
customName: json['customName'], customName: json['customName'] as String?,
dpId: json['dpId'], dpId: json['dpId'] as int?,
time: json['time'], time: json['time'] as int?,
type: json['type'], type: json['type'] as String?,
value: json['value'], value: json['value'] as dynamic,
); );
} }
} }
@@ -1,5 +1,5 @@
import 'package:flutter/material.dart';
import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
class EnergyConsumptionPage extends StatefulWidget { class EnergyConsumptionPage extends StatefulWidget {
@@ -10,7 +10,8 @@ class EnergyConsumptionPage extends StatefulWidget {
final Widget widget; final Widget widget;
final Function()? onTap; final Function()? onTap;
EnergyConsumptionPage({ const EnergyConsumptionPage({
super.key,
required this.chartData, required this.chartData,
required this.totalConsumption, required this.totalConsumption,
required this.date, required this.date,
@@ -91,11 +92,12 @@ class _EnergyConsumptionPageState extends State<EnergyConsumptionPage> {
], ],
), ),
Column( Column(
mainAxisSize: MainAxisSize.min,
children: [ children: [
Padding( Padding(
padding: const EdgeInsets.only(top: 10), padding: const EdgeInsets.only(top: 10),
child: SizedBox( child: SizedBox(
height: MediaQuery.of(context).size.height * 0.11, height: MediaQuery.sizeOf(context).height * 0.09,
child: LineChart( child: LineChart(
LineChartData( LineChartData(
lineTouchData: LineTouchData( lineTouchData: LineTouchData(
@@ -151,7 +153,7 @@ class _EnergyConsumptionPageState extends State<EnergyConsumptionPage> {
child: RotatedBox( child: RotatedBox(
quarterTurns: -1, quarterTurns: -1,
child: Text(_chartData[index].time, child: Text(_chartData[index].time,
style: TextStyle(fontSize: 10)), style: const TextStyle(fontSize: 10)),
), ),
); );
} }
@@ -190,8 +192,8 @@ class _EnergyConsumptionPageState extends State<EnergyConsumptionPage> {
spots: _chartData spots: _chartData
.asMap() .asMap()
.entries .entries
.map((entry) => FlSpot(entry.key.toDouble(), .map((entry) => FlSpot(
entry.value.consumption)) entry.key.toDouble(), entry.value.consumption))
.toList(), .toList(),
isCurved: true, isCurved: true,
color: ColorsManager.primaryColor.withOpacity(0.6), color: ColorsManager.primaryColor.withOpacity(0.6),
@@ -218,7 +220,7 @@ class _EnergyConsumptionPageState extends State<EnergyConsumptionPage> {
borderData: FlBorderData( borderData: FlBorderData(
show: false, show: false,
border: Border.all( border: Border.all(
color: Color(0xff023DFE).withOpacity(0.7), color: const Color(0xff023DFE).withOpacity(0.7),
width: 10, width: 10,
), ),
), ),
@@ -253,11 +255,9 @@ class _EnergyConsumptionPageState extends State<EnergyConsumptionPage> {
child: InkWell( child: InkWell(
onTap: widget.onTap, onTap: widget.onTap,
child: Center( child: Center(
child: SizedBox( child: Padding(
child: Padding( padding: const EdgeInsets.all(5),
padding: const EdgeInsets.all(5), child: Text(widget.date),
child: Text(widget.date),
),
), ),
), ),
), ),
@@ -12,8 +12,7 @@ import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
//Smart Power Clamp //Smart Power Clamp
class SmartPowerDeviceControl extends StatelessWidget class SmartPowerDeviceControl extends StatelessWidget with HelperResponsiveLayout {
with HelperResponsiveLayout {
final String deviceId; final String deviceId;
const SmartPowerDeviceControl({super.key, required this.deviceId}); const SmartPowerDeviceControl({super.key, required this.deviceId});
@@ -25,27 +24,27 @@ class SmartPowerDeviceControl extends StatelessWidget
..add(SmartPowerFetchDeviceEvent(deviceId)), ..add(SmartPowerFetchDeviceEvent(deviceId)),
child: BlocBuilder<SmartPowerBloc, SmartPowerState>( child: BlocBuilder<SmartPowerBloc, SmartPowerState>(
builder: (context, state) { builder: (context, state) {
final _blocProvider = BlocProvider.of<SmartPowerBloc>(context); final blocProvider = BlocProvider.of<SmartPowerBloc>(context);
if (state is SmartPowerLoading) { if (state is SmartPowerLoading) {
return const Center(child: CircularProgressIndicator()); return const Center(child: CircularProgressIndicator());
} else if (state is FakeState) { } else if (state is FakeState) {
return _buildStatusControls( return _buildStatusControls(
currentPage: _blocProvider.currentPage, currentPage: blocProvider.currentPage,
context: context, context: context,
blocProvider: _blocProvider, blocProvider: blocProvider,
); );
} else if (state is GetDeviceStatus) { } else if (state is GetDeviceStatus) {
return _buildStatusControls( return _buildStatusControls(
currentPage: _blocProvider.currentPage, currentPage: blocProvider.currentPage,
context: context, context: context,
blocProvider: _blocProvider, blocProvider: blocProvider,
); );
} else if (state is FilterRecordsState) { } else if (state is FilterRecordsState) {
return _buildStatusControls( return _buildStatusControls(
currentPage: _blocProvider.currentPage, currentPage: blocProvider.currentPage,
context: context, context: context,
blocProvider: _blocProvider, blocProvider: blocProvider,
); );
} }
return const Center(child: CircularProgressIndicator()); return const Center(child: CircularProgressIndicator());
@@ -60,7 +59,7 @@ class SmartPowerDeviceControl extends StatelessWidget
required SmartPowerBloc blocProvider, required SmartPowerBloc blocProvider,
required int currentPage, required int currentPage,
}) { }) {
PageController _pageController = PageController(initialPage: currentPage); PageController pageController = PageController(initialPage: currentPage);
return Container( return Container(
padding: const EdgeInsets.symmetric(horizontal: 50), padding: const EdgeInsets.symmetric(horizontal: 50),
child: DeviceControlsContainer( child: DeviceControlsContainer(
@@ -85,25 +84,31 @@ class SmartPowerDeviceControl extends StatelessWidget
PowerClampInfoCard( PowerClampInfoCard(
iconPath: Assets.powerActiveIcon, iconPath: Assets.powerActiveIcon,
title: 'Active', title: 'Active',
value: blocProvider value: blocProvider.deviceStatus.status.general.dataPoints
.deviceStatus.status.general.dataPoints[2].value .elementAtOrNull(2)
.toString(), ?.value
.toString() ??
'',
unit: '', unit: '',
), ),
PowerClampInfoCard( PowerClampInfoCard(
iconPath: Assets.voltMeterIcon, iconPath: Assets.voltMeterIcon,
title: 'Current', title: 'Current',
value: blocProvider value: blocProvider.deviceStatus.status.general.dataPoints
.deviceStatus.status.general.dataPoints[1].value .elementAtOrNull(1)
.toString(), ?.value
.toString() ??
'',
unit: ' A', unit: ' A',
), ),
PowerClampInfoCard( PowerClampInfoCard(
iconPath: Assets.frequencyIcon, iconPath: Assets.frequencyIcon,
title: 'Frequency', title: 'Frequency',
value: blocProvider value: blocProvider.deviceStatus.status.general.dataPoints
.deviceStatus.status.general.dataPoints[4].value .elementAtOrNull(4)
.toString(), ?.value
.toString() ??
'',
unit: ' Hz', unit: ' Hz',
), ),
], ],
@@ -142,7 +147,7 @@ class SmartPowerDeviceControl extends StatelessWidget
icon: const Icon(Icons.arrow_left), icon: const Icon(Icons.arrow_left),
onPressed: () { onPressed: () {
blocProvider.add(SmartPowerArrowPressedEvent(-1)); blocProvider.add(SmartPowerArrowPressedEvent(-1));
_pageController.previousPage( pageController.previousPage(
duration: const Duration(milliseconds: 300), duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut, curve: Curves.easeInOut,
); );
@@ -162,7 +167,7 @@ class SmartPowerDeviceControl extends StatelessWidget
icon: const Icon(Icons.arrow_right), icon: const Icon(Icons.arrow_right),
onPressed: () { onPressed: () {
blocProvider.add(SmartPowerArrowPressedEvent(1)); blocProvider.add(SmartPowerArrowPressedEvent(1));
_pageController.nextPage( pageController.nextPage(
duration: const Duration(milliseconds: 300), duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut, curve: Curves.easeInOut,
); );
@@ -177,7 +182,7 @@ class SmartPowerDeviceControl extends StatelessWidget
Expanded( Expanded(
flex: 2, flex: 2,
child: PageView( child: PageView(
controller: _pageController, controller: pageController,
onPageChanged: (int page) { onPageChanged: (int page) {
blocProvider.add(SmartPowerPageChangedEvent(page)); blocProvider.add(SmartPowerPageChangedEvent(page));
}, },
@@ -190,8 +195,8 @@ class SmartPowerDeviceControl extends StatelessWidget
blocProvider.add(SelectDateEvent(context: context)); blocProvider.add(SelectDateEvent(context: context));
blocProvider.add(FilterRecordsByDateEvent( blocProvider.add(FilterRecordsByDateEvent(
selectedDate: blocProvider.dateTime!, selectedDate: blocProvider.dateTime!,
viewType: blocProvider viewType:
.views[blocProvider.currentIndex])); blocProvider.views[blocProvider.currentIndex]));
}, },
widget: blocProvider.dateSwitcher(), widget: blocProvider.dateSwitcher(),
chartData: blocProvider.energyDataList.isNotEmpty chartData: blocProvider.energyDataList.isNotEmpty
@@ -84,6 +84,16 @@ class _PresenceUpdateDataState extends State<PresenceNoBodyTime> {
} }
} }
@override
void didUpdateWidget(PresenceNoBodyTime oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.value != widget.value) {
setState(() {
_currentValue = widget.value;
});
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return DeviceControlsContainer( return DeviceControlsContainer(
@@ -21,6 +21,7 @@ class WallSensorBloc extends Bloc<WallSensorEvent, WallSensorState> {
on<ShowDescriptionEvent>(_showDescription); on<ShowDescriptionEvent>(_showDescription);
on<BackToGridViewEvent>(_backToGridView); on<BackToGridViewEvent>(_backToGridView);
on<WallSensorFactoryResetEvent>(_onFactoryReset); on<WallSensorFactoryResetEvent>(_onFactoryReset);
on<WallSensorRealtimeUpdateEvent>(_onRealtimeUpdate);
} }
void _fetchWallSensorStatus( void _fetchWallSensorStatus(
@@ -30,7 +31,7 @@ class WallSensorBloc extends Bloc<WallSensorEvent, WallSensorState> {
var response = await DevicesManagementApi().getDeviceStatus(deviceId); var response = await DevicesManagementApi().getDeviceStatus(deviceId);
deviceStatus = WallSensorModel.fromJson(response.status); deviceStatus = WallSensorModel.fromJson(response.status);
emit(WallSensorUpdateState(wallSensorModel: deviceStatus)); emit(WallSensorUpdateState(wallSensorModel: deviceStatus));
_listenToChanges(emit, deviceId); _listenToChanges(deviceId);
} catch (e) { } catch (e) {
emit(WallSensorFailedState(error: e.toString())); emit(WallSensorFailedState(error: e.toString()));
return; return;
@@ -52,28 +53,27 @@ class WallSensorBloc extends Bloc<WallSensorEvent, WallSensorState> {
} }
} }
_listenToChanges(Emitter<WallSensorState> emit, deviceId) { void _listenToChanges(String deviceId) {
try { DatabaseReference ref =
DatabaseReference ref = FirebaseDatabase.instance.ref('device-status/$deviceId');
FirebaseDatabase.instance.ref('device-status/$deviceId'); ref.onValue.listen((DatabaseEvent event) {
Stream<DatabaseEvent> stream = ref.onValue; final data = event.snapshot.value as Map<dynamic, dynamic>?;
if (data == null) return;
stream.listen((DatabaseEvent event) { final statusList = (data['status'] as List?)
Map<dynamic, dynamic> usersMap = ?.map((e) => Status(code: e['code'], value: e['value']))
event.snapshot.value as Map<dynamic, dynamic>; .toList();
List<Status> statusList = [];
usersMap['status'].forEach((element) { if (statusList != null) {
statusList final updatedDeviceStatus = WallSensorModel.fromJson(statusList);
.add(Status(code: element['code'], value: element['value'])); if (!isClosed) {
}); add(WallSensorRealtimeUpdateEvent(updatedDeviceStatus));
}
deviceStatus = WallSensorModel.fromJson(statusList); }
emit(WallSensorLoadingNewSate(wallSensorModel: deviceStatus)); });
});
} catch (_) {}
} }
void _changeValue( void _changeValue(
WallSensorChangeValueEvent event, Emitter<WallSensorState> emit) async { WallSensorChangeValueEvent event, Emitter<WallSensorState> emit) async {
emit(WallSensorLoadingNewSate(wallSensorModel: deviceStatus)); emit(WallSensorLoadingNewSate(wallSensorModel: deviceStatus));
@@ -195,4 +195,12 @@ class WallSensorBloc extends Bloc<WallSensorEvent, WallSensorState> {
emit(WallSensorFailedState(error: e.toString())); emit(WallSensorFailedState(error: e.toString()));
} }
} }
void _onRealtimeUpdate(
WallSensorRealtimeUpdateEvent event,
Emitter<WallSensorState> emit,
) {
deviceStatus = event.deviceStatus;
emit(WallSensorUpdateState(wallSensorModel: deviceStatus));
}
} }
@@ -1,5 +1,6 @@
import 'package:equatable/equatable.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/device_managment/all_devices/models/factory_reset_model.dart';
import 'package:syncrow_web/pages/device_managment/wall_sensor/model/wall_sensor_model.dart';
abstract class WallSensorEvent extends Equatable { abstract class WallSensorEvent extends Equatable {
const WallSensorEvent(); const WallSensorEvent();
@@ -70,3 +71,8 @@ class WallSensorFactoryResetEvent extends WallSensorEvent {
required this.factoryReset, required this.factoryReset,
}); });
} }
class WallSensorRealtimeUpdateEvent extends WallSensorEvent {
final WallSensorModel deviceStatus;
const WallSensorRealtimeUpdateEvent(this.deviceStatus);
}
+13
View File
@@ -142,6 +142,19 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
}, },
color: ColorsManager.primaryColor, color: ColorsManager.primaryColor,
), ),
HomeItemModel(
title: 'Syncrow Analytics',
icon: Assets.devicesIcon,
active: true,
onPress: (context) {
context.read<SpaceTreeBloc>().add(ClearCachedData());
BlocProvider.of<RoutineBloc>(context)
.add(const TriggerSwitchTabsEvent(isRoutineTab: false));
context.go(RoutesConst.analytics);
},
color: ColorsManager.primaryColor,
),
// HomeItemModel( // HomeItemModel(
// title: 'Move in', // title: 'Move in',
+13 -6
View File
@@ -50,8 +50,9 @@ class HomeMobilePage extends StatelessWidget {
height: size.height * 0.6, height: size.height * 0.6,
width: size.width * 0.68, width: size.width * 0.68,
child: GridView.builder( child: GridView.builder(
itemCount: 3, itemCount: homeItems.length,
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( gridDelegate:
const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2, crossAxisCount: 2,
crossAxisSpacing: 20.0, crossAxisSpacing: 20.0,
mainAxisSpacing: 20.0, mainAxisSpacing: 20.0,
@@ -60,10 +61,11 @@ class HomeMobilePage extends StatelessWidget {
itemBuilder: (context, index) { itemBuilder: (context, index) {
return HomeCard( return HomeCard(
index: index, index: index,
active: homeItems[index]['active'], active: homeBloc.homeItems[index].active!,
name: homeItems[index]['title'], name: homeBloc.homeItems[index].title!,
img: homeItems[index]['icon'], img: homeBloc.homeItems[index].icon!,
onTap: () => homeBloc.homeItems[index].onPress(context), onTap: () =>
homeBloc.homeItems[index].onPress(context),
); );
}, },
), ),
@@ -94,6 +96,11 @@ class HomeMobilePage extends StatelessWidget {
'icon': Assets.devicesIcon, 'icon': Assets.devicesIcon,
'active': true, 'active': true,
}, },
{
'title': 'Syncrow Analytics',
'icon': Assets.iconEdit,
'active': true,
},
// { // {
// 'title': 'Move in', // 'title': 'Move in',
// 'icon': Assets.moveinIcon, // 'icon': Assets.moveinIcon,
+4 -4
View File
@@ -1,10 +1,10 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/svg.dart'; import 'package:flutter_svg/svg.dart';
import 'package:syncrow_web/pages/home/bloc/home_event.dart';
import 'package:syncrow_web/pages/home/view/agreement_and_privacy_dialog.dart';
import 'package:syncrow_web/pages/home/bloc/home_bloc.dart'; import 'package:syncrow_web/pages/home/bloc/home_bloc.dart';
import 'package:syncrow_web/pages/home/bloc/home_event.dart';
import 'package:syncrow_web/pages/home/bloc/home_state.dart'; import 'package:syncrow_web/pages/home/bloc/home_state.dart';
import 'package:syncrow_web/pages/home/view/agreement_and_privacy_dialog.dart';
import 'package:syncrow_web/pages/home/view/home_card.dart'; import 'package:syncrow_web/pages/home/view/home_card.dart';
import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/web_layout/web_scaffold.dart'; import 'package:syncrow_web/web_layout/web_scaffold.dart';
@@ -24,7 +24,7 @@ class _HomeWebPageState extends State<HomeWebPage> {
void initState() { void initState() {
super.initState(); super.initState();
final homeBloc = BlocProvider.of<HomeBloc>(context); final homeBloc = BlocProvider.of<HomeBloc>(context);
homeBloc.add(FetchUserInfo()); homeBloc.add(const FetchUserInfo());
} }
@override @override
@@ -97,7 +97,7 @@ class _HomeWebPageState extends State<HomeWebPage> {
height: size.height * 0.6, height: size.height * 0.6,
width: size.width * 0.68, width: size.width * 0.68,
child: GridView.builder( child: GridView.builder(
itemCount: 3, // Change this count if needed. itemCount: homeBloc.homeItems.length,
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3, // Adjust as needed. crossAxisCount: 3, // Adjust as needed.
crossAxisSpacing: 20.0, crossAxisSpacing: 20.0,
+34 -30
View File
@@ -30,8 +30,8 @@ class _RoutinesViewState extends State<RoutinesView> {
final spaceId = result['space']; final spaceId = result['space'];
final bloc = BlocProvider.of<CreateRoutineBloc>(context); final bloc = BlocProvider.of<CreateRoutineBloc>(context);
final routineBloc = context.read<RoutineBloc>(); final routineBloc = context.read<RoutineBloc>();
bloc.add( bloc.add(SaveCommunityIdAndSpaceIdEvent(
SaveCommunityIdAndSpaceIdEvent(communityID: communityId, spaceID: spaceId)); communityID: communityId, spaceID: spaceId));
await Future.delayed(const Duration(seconds: 1)); await Future.delayed(const Duration(seconds: 1));
routineBloc.add(const CreateNewRoutineViewEvent(createRoutineView: true)); routineBloc.add(const CreateNewRoutineViewEvent(createRoutineView: true));
} }
@@ -61,34 +61,38 @@ class _RoutinesViewState extends State<RoutinesView> {
width: context.screenWidth, width: context.screenWidth,
child: SingleChildScrollView( child: SingleChildScrollView(
padding: const EdgeInsetsDirectional.all(16), padding: const EdgeInsetsDirectional.all(16),
child: Column( child: Padding(
crossAxisAlignment: CrossAxisAlignment.start, padding: const EdgeInsets.only(left: 20),
mainAxisAlignment: MainAxisAlignment.start, child: Column(
spacing: 16, crossAxisAlignment: CrossAxisAlignment.start,
children: [ mainAxisAlignment: MainAxisAlignment.start,
Text( spacing: 16,
"Create New Routines", children: [
style: Theme.of(context).textTheme.titleLarge?.copyWith( Text(
color: ColorsManager.grayColor, "Create New Routines",
fontWeight: FontWeight.bold, style:
), Theme.of(context).textTheme.titleLarge?.copyWith(
), color: ColorsManager.grayColor,
RoutineViewCard( fontWeight: FontWeight.bold,
isLoading: false, ),
onChanged: (v) {}, ),
status: '', RoutineViewCard(
spaceId: '', isLoading: false,
automationId: '', onChanged: (v) {},
communityId: '', status: '',
sceneId: '', spaceId: '',
cardType: '', automationId: '',
spaceName: '', communityId: '',
onTap: () => _handleRoutineCreation(context), sceneId: '',
icon: Icons.add, cardType: '',
textString: '', spaceName: '',
), onTap: () => _handleRoutineCreation(context),
const FetchRoutineScenesAutomation(), icon: Icons.add,
], textString: '',
),
const FetchRoutineScenesAutomation(),
],
),
), ),
), ),
), ),
@@ -16,7 +16,8 @@ class FetchRoutineScenesAutomation extends StatelessWidget
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocBuilder<RoutineBloc, RoutineState>( return BlocBuilder<RoutineBloc, RoutineState>(
builder: (context, state) { builder: (context, state) {
if (state.isLoading) return const Center(child: CircularProgressIndicator()); if (state.isLoading)
return const Center(child: CircularProgressIndicator());
return SingleChildScrollView( return SingleChildScrollView(
child: Padding( child: Padding(
@@ -40,7 +41,8 @@ class FetchRoutineScenesAutomation extends StatelessWidget
const SizedBox(height: 3), const SizedBox(height: 3),
Visibility( Visibility(
visible: state.automations.isNotEmpty, visible: state.automations.isNotEmpty,
replacement: _buildEmptyState(context, "No automations found"), replacement:
_buildEmptyState(context, "No automations found"),
child: SizedBox( child: SizedBox(
height: 200, height: 200,
child: _buildAutomations(state), child: _buildAutomations(state),
@@ -59,7 +61,8 @@ class FetchRoutineScenesAutomation extends StatelessWidget
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
itemCount: state.automations.length, itemCount: state.automations.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final isLoading = state.automations.contains(state.automations[index].id); final isLoading =
state.automations.contains(state.automations[index].id);
return Column( return Column(
children: [ children: [
@@ -179,10 +182,13 @@ class FetchRoutineScenesAutomation extends StatelessWidget
} }
Widget _buildEmptyState(BuildContext context, String title) { Widget _buildEmptyState(BuildContext context, String title) {
return Text( return Padding(
title, padding: const EdgeInsets.only(bottom: 100),
style: context.textTheme.bodyMedium?.copyWith( child: Text(
color: ColorsManager.grayColor, title,
style: context.textTheme.bodyMedium?.copyWith(
color: ColorsManager.grayColor,
),
), ),
); );
} }
@@ -9,6 +9,7 @@ import 'package:syncrow_web/pages/routines/models/ac/ac_operational_value.dart';
import 'package:syncrow_web/pages/routines/models/device_functions.dart'; import 'package:syncrow_web/pages/routines/models/device_functions.dart';
import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart'; import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart';
import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart'; import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/helpers/routine_tap_function_helper.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart';
@@ -74,11 +75,26 @@ class ACHelper {
child: _buildFunctionsList( child: _buildFunctionsList(
context: context, context: context,
acFunctions: acFunctions, acFunctions: acFunctions,
onFunctionSelected: (functionCode, operationName) => device: device,
context.read<FunctionBloc>().add(SelectFunction( onFunctionSelected: (functionCode, operationName) {
functionCode: functionCode, RoutineTapFunctionHelper.onTapFunction(
operationName: operationName, context,
)), functionCode: functionCode,
functionOperationName: operationName,
functionValueDescription:
selectedFunctionData.valueDescription,
deviceUuid: device?.uuid,
codesToAddIntoFunctionsWithDefaultValue: [
'temp_set',
'temp_current',
],
defaultValue: functionCode == 'temp_set'
? 200
: functionCode == 'temp_current'
? -100
: 0,
);
},
), ),
), ),
// Value selector // Value selector
@@ -137,6 +153,7 @@ class ACHelper {
required BuildContext context, required BuildContext context,
required List<ACFunction> acFunctions, required List<ACFunction> acFunctions,
required Function(String, String) onFunctionSelected, required Function(String, String) onFunctionSelected,
required AllDevicesModel? device,
}) { }) {
return ListView.separated( return ListView.separated(
shrinkWrap: false, shrinkWrap: false,
@@ -131,7 +131,12 @@ class _CeilingSensorDialogState extends State<CeilingSensorDialog> {
return Row( return Row(
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
CpsFunctionsList(cpsFunctions: _cpsFunctions), CpsFunctionsList(
cpsFunctions: _cpsFunctions,
device: widget.device,
selectedFunctionData: selectedFunctionData,
dialogType: widget.dialogType,
),
if (state.selectedFunction != null) if (state.selectedFunction != null)
Expanded( Expanded(
child: isToggleFunction child: isToggleFunction
@@ -1,14 +1,24 @@
import 'package:flutter/material.dart'; 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/routines/bloc/functions_bloc/functions_bloc_bloc.dart';
import 'package:syncrow_web/pages/routines/models/ceiling_presence_sensor_functions.dart'; import 'package:syncrow_web/pages/routines/models/ceiling_presence_sensor_functions.dart';
import 'package:syncrow_web/pages/routines/models/device_functions.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialog_function_list_tile.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialog_function_list_tile.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/helpers/routine_tap_function_helper.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
class CpsFunctionsList extends StatelessWidget { class CpsFunctionsList extends StatelessWidget {
const CpsFunctionsList({required this.cpsFunctions, super.key}); const CpsFunctionsList({
required this.cpsFunctions,
required this.device,
required this.selectedFunctionData,
required this.dialogType,
super.key,
});
final List<CpsFunctions> cpsFunctions; final List<CpsFunctions> cpsFunctions;
final AllDevicesModel? device;
final DeviceFunctionData? selectedFunctionData;
final String dialogType;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -26,12 +36,27 @@ class CpsFunctionsList extends StatelessWidget {
return RoutineDialogFunctionListTile( return RoutineDialogFunctionListTile(
iconPath: function.icon, iconPath: function.icon,
operationName: function.operationName, operationName: function.operationName,
onTap: () => context.read<FunctionBloc>().add( onTap: () => RoutineTapFunctionHelper.onTapFunction(
SelectFunction( context,
functionCode: function.code, functionCode: function.code,
operationName: function.operationName, functionOperationName: function.operationName,
), functionValueDescription: selectedFunctionData?.valueDescription,
), deviceUuid: device?.uuid,
codesToAddIntoFunctionsWithDefaultValue: [
'static_max_dis',
'presence_reference',
'moving_reference',
'perceptual_boundary',
'moving_boundary',
'moving_rigger_time',
'moving_static_time',
'none_body_time',
'moving_max_dis',
'moving_range',
'presence_range',
if (dialogType == "IF") 'sensitivity',
],
),
); );
}, },
), ),
@@ -0,0 +1,46 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart';
import 'package:syncrow_web/pages/routines/models/device_functions.dart';
abstract final class RoutineTapFunctionHelper {
const RoutineTapFunctionHelper._();
static void onTapFunction(
BuildContext context, {
required String functionCode,
required String functionOperationName,
required String? functionValueDescription,
required String? deviceUuid,
required List<String> codesToAddIntoFunctionsWithDefaultValue,
int defaultValue = 0,
}) {
final functionsBloc = context.read<FunctionBloc>();
functionsBloc.add(
SelectFunction(
functionCode: functionCode,
operationName: functionOperationName,
),
);
final addedFunctions = functionsBloc.state.addedFunctions;
final isFunctionAlreadyAdded = addedFunctions.any(
(e) => e.functionCode == functionCode,
);
final shouldAddFunction =
codesToAddIntoFunctionsWithDefaultValue.contains(functionCode);
if (!isFunctionAlreadyAdded && shouldAddFunction) {
context.read<FunctionBloc>().add(
AddFunction(
functionData: DeviceFunctionData(
entityId: deviceUuid ?? '',
functionCode: functionCode,
operationName: functionOperationName,
value: defaultValue,
condition: '==',
valueDescription: functionValueDescription,
),
),
);
}
}
}
@@ -11,6 +11,7 @@ import 'package:syncrow_web/pages/routines/models/gang_switches/one_gang_switch/
import 'package:syncrow_web/pages/routines/models/gang_switches/switch_operational_value.dart'; import 'package:syncrow_web/pages/routines/models/gang_switches/switch_operational_value.dart';
import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart'; import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart';
import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart'; import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/helpers/routine_tap_function_helper.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart';
@@ -24,21 +25,23 @@ class OneGangSwitchHelper {
required String uniqueCustomId, required String uniqueCustomId,
required bool removeComparetors, required bool removeComparetors,
}) async { }) async {
List<BaseSwitchFunction> oneGangFunctions = functions.whereType<BaseSwitchFunction>().toList(); List<BaseSwitchFunction> oneGangFunctions =
functions.whereType<BaseSwitchFunction>().toList();
return showDialog<Map<String, dynamic>?>( return showDialog<Map<String, dynamic>?>(
context: context, context: context,
builder: (BuildContext context) { builder: (BuildContext context) {
return BlocProvider( return BlocProvider(
create: (_) => FunctionBloc()..add(InitializeFunctions(deviceSelectedFunctions ?? [])), create: (_) => FunctionBloc()
..add(InitializeFunctions(deviceSelectedFunctions ?? [])),
child: AlertDialog( child: AlertDialog(
contentPadding: EdgeInsets.zero, contentPadding: EdgeInsets.zero,
content: BlocBuilder<FunctionBloc, FunctionBlocState>( content: BlocBuilder<FunctionBloc, FunctionBlocState>(
builder: (context, state) { builder: (context, state) {
final selectedFunction = state.selectedFunction; final selectedFunction = state.selectedFunction;
final selectedOperationName = state.selectedOperationName; final selectedOperationName = state.selectedOperationName;
final selectedFunctionData = final selectedFunctionData = state.addedFunctions
state.addedFunctions.firstWhere((f) => f.functionCode == selectedFunction, .firstWhere((f) => f.functionCode == selectedFunction,
orElse: () => DeviceFunctionData( orElse: () => DeviceFunctionData(
entityId: '', entityId: '',
functionCode: selectedFunction ?? '', functionCode: selectedFunction ?? '',
@@ -84,12 +87,19 @@ class OneGangSwitchHelper {
size: 16, size: 16,
color: ColorsManager.textGray, color: ColorsManager.textGray,
), ),
onTap: () { onTap: () =>
context.read<FunctionBloc>().add(SelectFunction( RoutineTapFunctionHelper.onTapFunction(
functionCode: function.code, context,
operationName: function.operationName, functionCode: function.code,
)); functionOperationName:
}, function.operationName,
functionValueDescription:
selectedFunctionData.valueDescription,
deviceUuid: device?.uuid,
codesToAddIntoFunctionsWithDefaultValue: [
'countdown_1',
],
),
); );
}, },
), ),
@@ -220,11 +230,11 @@ class OneGangSwitchHelper {
selectedFunctionData, selectedFunctionData,
), ),
const SizedBox(height: 20), const SizedBox(height: 20),
_buildCountDownDisplay( _buildCountDownDisplay(context, initialValue, device, operationName,
context, initialValue, device, operationName, selectedFunctionData, selectCode), selectedFunctionData, selectCode),
const SizedBox(height: 20), const SizedBox(height: 20),
_buildCountDownSlider( _buildCountDownSlider(context, initialValue, device, operationName,
context, initialValue, device, operationName, selectedFunctionData, selectCode), selectedFunctionData, selectCode),
], ],
); );
} }
@@ -314,9 +324,10 @@ class OneGangSwitchHelper {
value: (initialValue ?? 0).toDouble(), value: (initialValue ?? 0).toDouble(),
min: operationalValues.minValue?.toDouble() ?? 0.0, min: operationalValues.minValue?.toDouble() ?? 0.0,
max: operationalValues.maxValue?.toDouble() ?? 0.0, max: operationalValues.maxValue?.toDouble() ?? 0.0,
divisions: (((operationalValues.maxValue ?? 0) - (operationalValues.minValue ?? 0)) / divisions:
(operationalValues.stepValue ?? 1)) (((operationalValues.maxValue ?? 0) - (operationalValues.minValue ?? 0)) /
.round(), (operationalValues.stepValue ?? 1))
.round(),
onChanged: (value) { onChanged: (value) {
context.read<FunctionBloc>().add( context.read<FunctionBloc>().add(
AddFunction( AddFunction(
@@ -368,7 +379,9 @@ class OneGangSwitchHelper {
trailing: Icon( trailing: Icon(
isSelected ? Icons.radio_button_checked : Icons.radio_button_unchecked, isSelected ? Icons.radio_button_checked : Icons.radio_button_unchecked,
size: 24, size: 24,
color: isSelected ? ColorsManager.primaryColorWithOpacity : ColorsManager.textGray, color: isSelected
? ColorsManager.primaryColorWithOpacity
: ColorsManager.textGray,
), ),
onTap: () { onTap: () {
if (!isSelected) { if (!isSelected) {
@@ -10,6 +10,7 @@ import 'package:syncrow_web/pages/routines/models/gang_switches/base_switch_func
import 'package:syncrow_web/pages/routines/models/gang_switches/switch_operational_value.dart'; import 'package:syncrow_web/pages/routines/models/gang_switches/switch_operational_value.dart';
import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart'; import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart';
import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart'; import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/helpers/routine_tap_function_helper.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart';
@@ -85,15 +86,21 @@ class ThreeGangSwitchHelper {
size: 16, size: 16,
color: ColorsManager.textGray, color: ColorsManager.textGray,
), ),
onTap: () { onTap: () =>
context RoutineTapFunctionHelper.onTapFunction(
.read<FunctionBloc>() context,
.add(SelectFunction( functionCode: function.code,
functionCode: function.code, functionOperationName:
operationName: function.operationName,
function.operationName, functionValueDescription:
)); selectedFunctionData.valueDescription,
}, deviceUuid: device?.uuid,
codesToAddIntoFunctionsWithDefaultValue: [
'countdown_1',
'countdown_2',
'countdown_3',
],
),
); );
}, },
), ),
@@ -183,8 +190,7 @@ class ThreeGangSwitchHelper {
); );
} }
final selectedFn = final selectedFn = switchFunctions.firstWhere((f) => f.code == selectedFunction);
switchFunctions.firstWhere((f) => f.code == selectedFunction);
final values = selectedFn.getOperationalValues(); final values = selectedFn.getOperationalValues();
return _buildOperationalValuesList( return _buildOperationalValuesList(
@@ -266,8 +272,7 @@ class ThreeGangSwitchHelper {
minHeight: 40.0, minHeight: 40.0,
minWidth: 40.0, minWidth: 40.0,
), ),
isSelected: isSelected: conditions.map((c) => c == (currentCondition ?? "==")).toList(),
conditions.map((c) => c == (currentCondition ?? "==")).toList(),
children: conditions.map((c) => Text(c)).toList(), children: conditions.map((c) => Text(c)).toList(),
); );
} }
@@ -316,10 +321,10 @@ class ThreeGangSwitchHelper {
value: (initialValue ?? 0).toDouble(), value: (initialValue ?? 0).toDouble(),
min: operationalValues.minValue?.toDouble() ?? 0.0, min: operationalValues.minValue?.toDouble() ?? 0.0,
max: operationalValues.maxValue?.toDouble() ?? 0.0, max: operationalValues.maxValue?.toDouble() ?? 0.0,
divisions: (((operationalValues.maxValue ?? 0) - divisions:
(operationalValues.minValue ?? 0)) / (((operationalValues.maxValue ?? 0) - (operationalValues.minValue ?? 0)) /
(operationalValues.stepValue ?? 1)) (operationalValues.stepValue ?? 1))
.round(), .round(),
onChanged: (value) { onChanged: (value) {
context.read<FunctionBloc>().add( context.read<FunctionBloc>().add(
AddFunction( AddFunction(
@@ -369,9 +374,7 @@ class ThreeGangSwitchHelper {
style: context.textTheme.bodyMedium, style: context.textTheme.bodyMedium,
), ),
trailing: Icon( trailing: Icon(
isSelected isSelected ? Icons.radio_button_checked : Icons.radio_button_unchecked,
? Icons.radio_button_checked
: Icons.radio_button_unchecked,
size: 24, size: 24,
color: isSelected color: isSelected
? ColorsManager.primaryColorWithOpacity ? ColorsManager.primaryColorWithOpacity
@@ -387,8 +390,7 @@ class ThreeGangSwitchHelper {
operationName: operationName, operationName: operationName,
value: value.value, value: value.value,
condition: selectedFunctionData?.condition, condition: selectedFunctionData?.condition,
valueDescription: valueDescription: selectedFunctionData?.valueDescription,
selectedFunctionData?.valueDescription,
), ),
), ),
); );
@@ -10,6 +10,7 @@ import 'package:syncrow_web/pages/routines/models/gang_switches/base_switch_func
import 'package:syncrow_web/pages/routines/models/gang_switches/switch_operational_value.dart'; import 'package:syncrow_web/pages/routines/models/gang_switches/switch_operational_value.dart';
import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart'; import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart';
import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart'; import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/helpers/routine_tap_function_helper.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart';
@@ -85,15 +86,20 @@ class TwoGangSwitchHelper {
size: 16, size: 16,
color: ColorsManager.textGray, color: ColorsManager.textGray,
), ),
onTap: () { onTap: () =>
context RoutineTapFunctionHelper.onTapFunction(
.read<FunctionBloc>() context,
.add(SelectFunction( functionCode: function.code,
functionCode: function.code, functionOperationName:
operationName: function.operationName,
function.operationName, functionValueDescription:
)); selectedFunctionData.valueDescription,
}, deviceUuid: device?.uuid,
codesToAddIntoFunctionsWithDefaultValue: [
'countdown_1',
'countdown_2',
],
),
); );
}, },
), ),
@@ -167,8 +173,7 @@ class TwoGangSwitchHelper {
required String operationName, required String operationName,
required bool removeComparetors, required bool removeComparetors,
}) { }) {
if (selectedFunction == 'countdown_1' || if (selectedFunction == 'countdown_1' || selectedFunction == 'countdown_2') {
selectedFunction == 'countdown_2') {
final initialValue = selectedFunctionData?.value ?? 0; final initialValue = selectedFunctionData?.value ?? 0;
return _buildTemperatureSelector( return _buildTemperatureSelector(
context: context, context: context,
@@ -182,8 +187,7 @@ class TwoGangSwitchHelper {
); );
} }
final selectedFn = final selectedFn = switchFunctions.firstWhere((f) => f.code == selectedFunction);
switchFunctions.firstWhere((f) => f.code == selectedFunction);
final values = selectedFn.getOperationalValues(); final values = selectedFn.getOperationalValues();
return _buildOperationalValuesList( return _buildOperationalValuesList(
@@ -265,8 +269,7 @@ class TwoGangSwitchHelper {
minHeight: 40.0, minHeight: 40.0,
minWidth: 40.0, minWidth: 40.0,
), ),
isSelected: isSelected: conditions.map((c) => c == (currentCondition ?? "==")).toList(),
conditions.map((c) => c == (currentCondition ?? "==")).toList(),
children: conditions.map((c) => Text(c)).toList(), children: conditions.map((c) => Text(c)).toList(),
); );
} }
@@ -315,10 +318,10 @@ class TwoGangSwitchHelper {
value: (initialValue ?? 0).toDouble(), value: (initialValue ?? 0).toDouble(),
min: operationalValues.minValue?.toDouble() ?? 0.0, min: operationalValues.minValue?.toDouble() ?? 0.0,
max: operationalValues.maxValue?.toDouble() ?? 0.0, max: operationalValues.maxValue?.toDouble() ?? 0.0,
divisions: (((operationalValues.maxValue ?? 0) - divisions:
(operationalValues.minValue ?? 0)) / (((operationalValues.maxValue ?? 0) - (operationalValues.minValue ?? 0)) /
(operationalValues.stepValue ?? 1)) (operationalValues.stepValue ?? 1))
.round(), .round(),
onChanged: (value) { onChanged: (value) {
context.read<FunctionBloc>().add( context.read<FunctionBloc>().add(
AddFunction( AddFunction(
@@ -368,9 +371,7 @@ class TwoGangSwitchHelper {
style: context.textTheme.bodyMedium, style: context.textTheme.bodyMedium,
), ),
trailing: Icon( trailing: Icon(
isSelected isSelected ? Icons.radio_button_checked : Icons.radio_button_unchecked,
? Icons.radio_button_checked
: Icons.radio_button_unchecked,
size: 24, size: 24,
color: isSelected color: isSelected
? ColorsManager.primaryColorWithOpacity ? ColorsManager.primaryColorWithOpacity
@@ -386,8 +387,7 @@ class TwoGangSwitchHelper {
operationName: operationName, operationName: operationName,
value: value.value, value: value.value,
condition: selectedFunctionData?.condition, condition: selectedFunctionData?.condition,
valueDescription: valueDescription: selectedFunctionData?.valueDescription,
selectedFunctionData?.valueDescription,
), ),
), ),
); );
@@ -8,6 +8,7 @@ import 'package:syncrow_web/pages/routines/models/device_functions.dart';
import 'package:syncrow_web/pages/routines/models/wps/wps_functions.dart'; import 'package:syncrow_web/pages/routines/models/wps/wps_functions.dart';
import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart'; import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart';
import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart'; import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/helpers/routine_tap_function_helper.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/wall_sensor/wps_value_selector_widget.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/wall_sensor/wps_value_selector_widget.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart';
@@ -111,13 +112,23 @@ class _WallPresenceSensorState extends State<WallPresenceSensor> {
return Row( return Row(
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
_buildFunctionList(context), _buildFunctionList(context, state),
if (state.selectedFunction != null) _buildValueSelector(context, state), if (state.selectedFunction != null) _buildValueSelector(context, state),
], ],
); );
} }
Widget _buildFunctionList(BuildContext context) { Widget _buildFunctionList(BuildContext context, FunctionBlocState state) {
final selectedFunction = state.selectedFunction;
final selectedFunctionData = state.addedFunctions.firstWhere(
(f) => f.functionCode == selectedFunction,
orElse: () => DeviceFunctionData(
entityId: '',
functionCode: selectedFunction ?? '',
operationName: '',
value: null,
),
);
return SizedBox( return SizedBox(
width: 360, width: 360,
child: ListView.separated( child: ListView.separated(
@@ -149,12 +160,18 @@ class _WallPresenceSensorState extends State<WallPresenceSensor> {
size: 16, size: 16,
color: ColorsManager.textGray, color: ColorsManager.textGray,
), ),
onTap: () => context.read<FunctionBloc>().add( onTap: () => RoutineTapFunctionHelper.onTapFunction(
SelectFunction( context,
functionCode: function.code, functionCode: function.code,
operationName: function.operationName, functionOperationName: function.operationName,
), functionValueDescription: selectedFunctionData.valueDescription,
), deviceUuid: widget.device?.uuid,
codesToAddIntoFunctionsWithDefaultValue: [
'dis_current',
'presence_time',
'illuminance_value',
],
),
); );
}, },
), ),
+76 -29
View File
@@ -17,7 +17,15 @@ import 'package:syncrow_web/utils/style.dart';
class SpaceTreeView extends StatefulWidget { class SpaceTreeView extends StatefulWidget {
final bool? isSide; final bool? isSide;
final Function onSelect; final Function onSelect;
const SpaceTreeView({required this.onSelect, this.isSide, super.key}); final bool shouldDisableDeselectingChildrenOfSelectedParent;
final Widget? title;
const SpaceTreeView({
required this.onSelect,
this.isSide,
super.key,
this.shouldDisableDeselectingChildrenOfSelectedParent = false,
this.title,
});
@override @override
State<SpaceTreeView> createState() => _SpaceTreeViewState(); State<SpaceTreeView> createState() => _SpaceTreeViewState();
@@ -41,17 +49,31 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocBuilder<SpaceTreeBloc, SpaceTreeState>(builder: (context, state) { return BlocBuilder<SpaceTreeBloc, SpaceTreeState>(builder: (context, state) {
final communities = final communities = state.searchQuery.isNotEmpty
state.searchQuery.isNotEmpty ? state.filteredCommunity : state.communityList; ? state.filteredCommunity
: state.communityList;
return Container( return Container(
height: MediaQuery.sizeOf(context).height, height: MediaQuery.sizeOf(context).height,
decoration: widget.isSide == true decoration: widget.isSide == true
? subSectionContainerDecoration.copyWith(color: ColorsManager.whiteColors) ? subSectionContainerDecoration.copyWith(
color: ColorsManager.whiteColors)
: const BoxDecoration(color: ColorsManager.whiteColors), : const BoxDecoration(color: ColorsManager.whiteColors),
child: state is SpaceTreeLoadingState child: state is SpaceTreeLoadingState
? const Center(child: CircularProgressIndicator()) ? const Center(child: CircularProgressIndicator())
: Column( : Column(
children: [ children: [
if (widget.title != null)
Container(
alignment: AlignmentDirectional.centerStart,
padding: const EdgeInsets.all(24),
child: DefaultTextStyle(
style: context.textTheme.titleMedium!.copyWith(
color: ColorsManager.blackColor,
fontSize: 20,
),
child: widget.title!,
),
),
if (widget.isSide == true) if (widget.isSide == true)
Container( Container(
decoration: const BoxDecoration( decoration: const BoxDecoration(
@@ -79,10 +101,12 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
style: context.textTheme.bodyMedium?.copyWith( style: context.textTheme.bodyMedium?.copyWith(
color: ColorsManager.blackColor, color: ColorsManager.blackColor,
), ),
onChanged: (value) => context.read<SpaceTreeBloc>().add( onChanged: (value) =>
SearchQueryEvent(value), context.read<SpaceTreeBloc>().add(
), SearchQueryEvent(value),
decoration: textBoxDecoration(radios: 20)?.copyWith( ),
decoration:
textBoxDecoration(radios: 20)?.copyWith(
fillColor: Colors.white, fillColor: Colors.white,
suffixIcon: Padding( suffixIcon: Padding(
padding: const EdgeInsets.only(right: 16), padding: const EdgeInsets.only(right: 16),
@@ -92,7 +116,8 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
height: 24, height: 24,
), ),
), ),
hintStyle: context.textTheme.bodyMedium?.copyWith( hintStyle:
context.textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
fontSize: 12, fontSize: 12,
color: ColorsManager.textGray, color: ColorsManager.textGray,
@@ -131,15 +156,16 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
itemBuilder: (context, index) { itemBuilder: (context, index) {
return CustomExpansionTileSpaceTree( return CustomExpansionTileSpaceTree(
title: communities[index].name, title: communities[index].name,
isSelected: isSelected: state.selectedCommunities
state.selectedCommunities.contains(communities[index].uuid), .contains(communities[index].uuid),
isSoldCheck: isSoldCheck: state.selectedCommunities
state.selectedCommunities.contains(communities[index].uuid), .contains(communities[index].uuid),
onExpansionChanged: () => context.read<SpaceTreeBloc>().add( onExpansionChanged: () =>
OnCommunityExpanded( context.read<SpaceTreeBloc>().add(
communities[index].uuid, OnCommunityExpanded(
), communities[index].uuid,
), ),
),
isExpanded: state.expandedCommunities.contains( isExpanded: state.expandedCommunities.contains(
communities[index].uuid, communities[index].uuid,
), ),
@@ -156,8 +182,19 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
(space) { (space) {
return CustomExpansionTileSpaceTree( return CustomExpansionTileSpaceTree(
title: space.name, title: space.name,
isExpanded: state.expandedSpaces.contains(space.uuid), isExpanded:
state.expandedSpaces.contains(space.uuid),
onItemSelected: () { onItemSelected: () {
final isParentSelected = _isParentSelected(
state,
communities[index],
space,
);
if (widget
.shouldDisableDeselectingChildrenOfSelectedParent &&
isParentSelected) {
return;
}
context.read<SpaceTreeBloc>().add( context.read<SpaceTreeBloc>().add(
OnSpaceSelected( OnSpaceSelected(
communities[index], communities[index],
@@ -167,15 +204,18 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
); );
widget.onSelect(); widget.onSelect();
}, },
onExpansionChanged: () => context.read<SpaceTreeBloc>().add( onExpansionChanged: () =>
OnSpaceExpanded( context.read<SpaceTreeBloc>().add(
communities[index].uuid, OnSpaceExpanded(
space.uuid ?? '', communities[index].uuid,
), space.uuid ?? '',
), ),
isSelected: state.selectedSpaces.contains(space.uuid) || ),
isSelected: state.selectedSpaces
.contains(space.uuid) ||
state.soldCheck.contains(space.uuid),
isSoldCheck:
state.soldCheck.contains(space.uuid), state.soldCheck.contains(space.uuid),
isSoldCheck: state.soldCheck.contains(space.uuid),
children: _buildNestedSpaces( children: _buildNestedSpaces(
context, context,
state, state,
@@ -196,6 +236,13 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
}); });
} }
bool _isParentSelected(
SpaceTreeState state, CommunityModel community, SpaceModel space) {
return state.selectedCommunities.contains(community.uuid) ||
(space.spaceModel?.uuid != null &&
state.selectedSpaces.contains(space.spaceModel?.uuid));
}
List<Widget> _buildNestedSpaces( List<Widget> _buildNestedSpaces(
BuildContext context, BuildContext context,
SpaceTreeState state, SpaceTreeState state,
@@ -204,8 +251,8 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
) { ) {
return space.children.map((child) { return space.children.map((child) {
return CustomExpansionTileSpaceTree( return CustomExpansionTileSpaceTree(
isSelected: isSelected: state.selectedSpaces.contains(child.uuid) ||
state.selectedSpaces.contains(child.uuid) || state.soldCheck.contains(child.uuid), state.soldCheck.contains(child.uuid),
isSoldCheck: state.soldCheck.contains(child.uuid), isSoldCheck: state.soldCheck.contains(child.uuid),
title: child.name, title: child.name,
isExpanded: state.expandedSpaces.contains(child.uuid), isExpanded: state.expandedSpaces.contains(child.uuid),
@@ -4,6 +4,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/common/bloc/project_manager.dart'; import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart'; import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_event.dart'; import 'package:syncrow_web/pages/space_tree/bloc/space_tree_event.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_state.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/create_subspace_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/create_subspace_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
@@ -246,7 +247,9 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
final previousState = state; final previousState = state;
final projectUuid = await ProjectManager.getProjectUUID() ?? ''; final projectUuid = await ProjectManager.getProjectUUID() ?? '';
var spaceBloc = event.context.read<SpaceTreeBloc>(); var spaceBloc = event.context.read<SpaceTreeBloc>();
List<CommunityModel> communities = await _waitForCommunityList(spaceBloc); var spaceTreeState = event.context.read<SpaceTreeBloc>().state;
List<CommunityModel> communities = await _waitForCommunityList(spaceBloc, spaceTreeState);
await fetchSpaceModels(); await fetchSpaceModels();
await fetchTags(); await fetchTags();
@@ -277,11 +280,13 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
LoadCommunityAndSpacesEvent event, LoadCommunityAndSpacesEvent event,
Emitter<SpaceManagementState> emit, Emitter<SpaceManagementState> emit,
) async { ) async {
var spaceTreeState = event.context.read<SpaceTreeBloc>().state;
var spaceBloc = event.context.read<SpaceTreeBloc>(); var spaceBloc = event.context.read<SpaceTreeBloc>();
_onloadProducts(); _onloadProducts();
await fetchTags(); await fetchTags();
// Wait until `communityList` is loaded // Wait until `communityList` is loaded
List<CommunityModel> communities = await _waitForCommunityList(spaceBloc); List<CommunityModel> communities = await _waitForCommunityList(spaceBloc, spaceTreeState);
// Fetch space models after communities are available // Fetch space models after communities are available
final prevSpaceModels = await fetchSpaceModels(); final prevSpaceModels = await fetchSpaceModels();
@@ -292,23 +297,38 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
allTags: _cachedTags ?? [])); allTags: _cachedTags ?? []));
} }
Future<List<CommunityModel>> _waitForCommunityList(SpaceTreeBloc spaceBloc) async { Future<List<CommunityModel>> _waitForCommunityList(
SpaceTreeBloc spaceBloc, SpaceTreeState spaceTreeState) async {
// Check if communityList is already populated // Check if communityList is already populated
if (spaceBloc.state.communityList.isNotEmpty) { final filteredCommunities = spaceTreeState.searchQuery.isNotEmpty
return spaceBloc.state.communityList; ? spaceTreeState.filteredCommunity
: spaceTreeState.communityList;
if (filteredCommunities.isNotEmpty) {
return filteredCommunities;
} }
final completer = Completer<List<CommunityModel>>(); final completer = Completer<List<CommunityModel>>();
final subscription = spaceBloc.stream.listen((state) { final subscription = spaceBloc.stream.listen((state) {
if (state.communityList.isNotEmpty) { if (!completer.isCompleted && state.communityList.isNotEmpty) {
completer.complete(state.communityList); completer
.complete(state.searchQuery.isNotEmpty ? state.filteredCommunity : state.communityList);
} }
}); });
try {
final communities = await completer.future.timeout(
const Duration(seconds: 10),
onTimeout: () {
if (!completer.isCompleted) {
completer.complete([]);
}
return [];
},
);
// Return the list once available, then cancel the listener return communities;
final communities = await completer.future; } finally {
await subscription.cancel(); await subscription.cancel();
return communities; }
} }
void _onCommunityDelete( void _onCommunityDelete(
@@ -446,6 +466,7 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
if (previousState is SpaceManagementLoaded) { if (previousState is SpaceManagementLoaded) {
await _updateLoadedState( await _updateLoadedState(
event.context,
previousState, previousState,
allSpaces, allSpaces,
event.communityUuid, event.communityUuid,
@@ -462,6 +483,7 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
} }
Future<void> _updateLoadedState( Future<void> _updateLoadedState(
BuildContext context,
SpaceManagementLoaded previousState, SpaceManagementLoaded previousState,
List<SpaceModel> allSpaces, List<SpaceModel> allSpaces,
String communityUuid, String communityUuid,
@@ -469,7 +491,10 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
) async { ) async {
var prevSpaceModels = await fetchSpaceModels(); var prevSpaceModels = await fetchSpaceModels();
await fetchTags(); await fetchTags();
final communities = List<CommunityModel>.from(previousState.communities); final spaceTreeState = context.read<SpaceTreeBloc>().state;
final communities = spaceTreeState.searchQuery.isNotEmpty
? spaceTreeState.filteredCommunity
: spaceTreeState.communityList;
for (var community in communities) { for (var community in communities) {
if (community.uuid == communityUuid) { if (community.uuid == communityUuid) {
@@ -484,6 +509,8 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
spaceModels: prevSpaceModels, spaceModels: prevSpaceModels,
allTags: _cachedTags ?? [])); allTags: _cachedTags ?? []));
return; return;
} else {
print("Community not found");
} }
} }
} }
@@ -491,12 +518,21 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
Future<List<SpaceModel>> saveSpacesHierarchically( Future<List<SpaceModel>> saveSpacesHierarchically(
BuildContext context, List<SpaceModel> spaces, String communityUuid) async { BuildContext context, List<SpaceModel> spaces, String communityUuid) async {
final orderedSpaces = flattenHierarchy(spaces); final orderedSpaces = flattenHierarchy(spaces);
final projectUuid = await ProjectManager.getProjectUUID() ?? ''; final projectUuid = await ProjectManager.getProjectUUID() ?? '';
var spaceBloc = context.read<SpaceTreeBloc>(); CommunityModel? selectedCommunity;
List<CommunityModel> communities = spaceBloc.state.communityList; try {
CommunityModel? selectedCommunity = communities.firstWhere( final spaceTreeState = context.read<SpaceTreeBloc>().state;
(community) => community.uuid == communityUuid, final filteredCommunities = spaceTreeState.searchQuery.isNotEmpty
); ? spaceTreeState.filteredCommunity
: spaceTreeState.communityList;
selectedCommunity = filteredCommunities.firstWhere(
(community) => community.uuid == communityUuid,
);
} catch (e) {
return [];
}
final parentsToDelete = orderedSpaces.where((space) => final parentsToDelete = orderedSpaces.where((space) =>
space.status == SpaceStatus.deleted && space.status == SpaceStatus.deleted &&
@@ -605,11 +641,11 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
projectId: projectUuid); projectId: projectUuid);
} else { } else {
// Call create if the space does not have a UUID // Call create if the space does not have a UUID
final List<CreateTagBodyModel> tagBodyModels = space.tags != null List<CreateTagBodyModel> tagBodyModels = space.tags != null
? space.tags!.map((tag) => tag.toCreateTagBodyModel()).toList() ? space.tags!.map((tag) => tag.toCreateTagBodyModel()).toList()
: []; : [];
final createSubspaceBodyModels = space.subspaces?.map((subspace) { var createSubspaceBodyModels = space.subspaces?.map((subspace) {
final tagBodyModels = final tagBodyModels =
subspace.tags?.map((tag) => tag.toCreateTagBodyModel()).toList() ?? []; subspace.tags?.map((tag) => tag.toCreateTagBodyModel()).toList() ?? [];
return CreateSubspaceModel() return CreateSubspaceModel()
@@ -618,6 +654,11 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
}).toList() ?? }).toList() ??
[]; [];
if (space.spaceModel?.uuid != null) {
tagBodyModels = [];
createSubspaceBodyModels = [];
}
final response = await _api.createSpace( final response = await _api.createSpace(
communityId: communityUuid, communityId: communityUuid,
name: space.name, name: space.name,
@@ -669,9 +710,12 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
try { try {
await fetchTags(); await fetchTags();
final projectUuid = await ProjectManager.getProjectUUID() ?? ''; final spaceTreeState = event.context.read<SpaceTreeBloc>().state;
var spaceBloc = event.context.read<SpaceTreeBloc>(); final filteredCommunities = spaceTreeState.searchQuery.isNotEmpty
List<CommunityModel> communities = spaceBloc.state.communityList; ? spaceTreeState.filteredCommunity
: spaceTreeState.communityList;
List<CommunityModel> communities = filteredCommunities;
var prevSpaceModels = await fetchSpaceModels(); var prevSpaceModels = await fetchSpaceModels();
@@ -1,4 +1,6 @@
// Flutter imports // Flutter imports
import 'dart:math';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
@@ -70,6 +72,8 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
_nameController = TextEditingController( _nameController = TextEditingController(
text: widget.selectedCommunity?.name ?? '', text: widget.selectedCommunity?.name ?? '',
); );
realignTree();
_transformationController = TransformationController(); _transformationController = TransformationController();
if (widget.selectedSpace != null) { if (widget.selectedSpace != null) {
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
@@ -94,6 +98,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
spaces = widget.spaces.isNotEmpty ? flattenSpaces(widget.spaces) : []; spaces = widget.spaces.isNotEmpty ? flattenSpaces(widget.spaces) : [];
connections = widget.spaces.isNotEmpty ? createConnections(widget.spaces) : []; connections = widget.spaces.isNotEmpty ? createConnections(widget.spaces) : [];
_adjustCanvasSizeForSpaces(); _adjustCanvasSizeForSpaces();
realignTree();
}); });
} }
@@ -336,6 +341,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
} }
spaces.add(newSpace); spaces.add(newSpace);
_updateNodePosition(newSpace, newSpace.position); _updateNodePosition(newSpace, newSpace.position);
realignTree();
}); });
}, },
); );
@@ -401,12 +407,15 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
List<SpaceModel> flattenSpaces(List<SpaceModel> spaces) { List<SpaceModel> flattenSpaces(List<SpaceModel> spaces) {
List<SpaceModel> result = []; List<SpaceModel> result = [];
Map<String, SpaceModel> idToSpace = {};
void flatten(SpaceModel space) { void flatten(SpaceModel space) {
if (space.status == SpaceStatus.deleted || space.status == SpaceStatus.parentDeleted) { if (space.status == SpaceStatus.deleted || space.status == SpaceStatus.parentDeleted) {
return; return;
} }
result.add(space); result.add(space);
idToSpace[space.internalId] = space;
for (var child in space.children) { for (var child in space.children) {
flatten(child); flatten(child);
} }
@@ -416,6 +425,12 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
flatten(space); flatten(space);
} }
for (var space in result) {
if (space.parent != null) {
space.parent = idToSpace[space.parent!.internalId];
}
}
return result; return result;
} }
@@ -450,7 +465,6 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
void _saveSpaces() { void _saveSpaces() {
if (widget.selectedCommunity == null) { if (widget.selectedCommunity == null) {
debugPrint("No community selected for saving spaces.");
return; return;
} }
@@ -465,7 +479,6 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
} }
String communityUuid = widget.selectedCommunity!.uuid; String communityUuid = widget.selectedCommunity!.uuid;
context.read<SpaceManagementBloc>().add(SaveSpacesEvent( context.read<SpaceManagementBloc>().add(SaveSpacesEvent(
context, context,
spaces: spacesToSave, spaces: spacesToSave,
@@ -530,35 +543,83 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
} }
Offset getBalancedChildPosition(SpaceModel parent) { Offset getBalancedChildPosition(SpaceModel parent) {
int totalSiblings = parent.children.length + 1; const double nodeWidth = 200;
double totalWidth = (totalSiblings - 1) * 250; // Horizontal spacing const double verticalGap = 180;
double startX = parent.position.dx - (totalWidth / 2);
Offset position = Offset(startX + (parent.children.length * 250), parent.position.dy + 180); if (parent.children.isEmpty) {
// First child → exactly center
return Offset(parent.position.dx, parent.position.dy + verticalGap);
} else {
// More children → arrange them spaced horizontally
double totalWidth = (parent.children.length) * (nodeWidth + 60);
double startX = parent.position.dx - (totalWidth / 2);
// Check for overlaps & adjust double childX = startX + (parent.children.length * (nodeWidth + 60));
while (spaces.any((s) => (s.position - position).distance < 250)) { return Offset(childX, parent.position.dy + verticalGap);
position = Offset(position.dx + 250, position.dy);
} }
return position;
} }
void realignTree() { void realignTree() {
void updatePositions(SpaceModel node, double x, double y) { const double nodeWidth = 200;
node.position = Offset(x, y); const double nodeHeight = 100;
const double horizontalGap = 60;
const double verticalGap = 180;
const double rootGap = 400; // extra space between different roots
int numChildren = node.children.length; double canvasRightEdge = 1000;
double childStartX = x - ((numChildren - 1) * 250) / 2; double canvasBottomEdge = 1000;
for (int i = 0; i < numChildren; i++) { double calculateSubtreeWidth(SpaceModel node) {
updatePositions(node.children[i], childStartX + (i * 250), y + 180); if (node.children.isEmpty) return nodeWidth;
double totalWidth = 0;
for (var child in node.children) {
totalWidth += calculateSubtreeWidth(child) + horizontalGap;
}
return totalWidth - horizontalGap;
}
void layoutSubtree(SpaceModel node, double startX, double y) {
double subtreeWidth = calculateSubtreeWidth(node);
double centerX = startX + subtreeWidth / 2 - nodeWidth / 2;
node.position = Offset(centerX, y);
canvasRightEdge = max(canvasRightEdge, centerX + nodeWidth);
canvasBottomEdge = max(canvasBottomEdge, y + nodeHeight);
if (node.children.length == 1) {
final child = node.children.first;
layoutSubtree(child, centerX, y + verticalGap);
} else {
double childX = startX;
for (var child in node.children) {
double childWidth = calculateSubtreeWidth(child);
layoutSubtree(child, childX, y + verticalGap);
childX += childWidth + horizontalGap;
}
} }
} }
if (spaces.isNotEmpty) { // ⚡ New: layout each root separately
updatePositions(spaces.first, spaces.first.position.dx, spaces.first.position.dy); final List<SpaceModel> roots = spaces
.where((s) =>
s.parent == null &&
s.status != SpaceStatus.deleted &&
s.status != SpaceStatus.parentDeleted)
.toList();
double currentX = 100; // start some margin from left
double currentY = 100; // top margin
for (var root in roots) {
layoutSubtree(root, currentX, currentY);
double rootWidth = calculateSubtreeWidth(root);
currentX += rootWidth + rootGap;
} }
setState(() {
canvasWidth = canvasRightEdge + 400;
canvasHeight = canvasBottomEdge + 400;
});
} }
void _onDuplicate(BuildContext parentContext) { void _onDuplicate(BuildContext parentContext) {
@@ -642,116 +703,72 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
} }
void _duplicateSpace(SpaceModel space) { void _duplicateSpace(SpaceModel space) {
final Map<SpaceModel, SpaceModel> originalToDuplicate = {}; setState(() {
double horizontalGap = 250.0; // Increased spacing SpaceModel? parent = space.parent;
double verticalGap = 180.0; // Adjusted for better visualization
print("🟢 Duplicating: ${space.name}"); SpaceModel duplicated = _deepCloneSpaceTree(space, parent: parent);
/// **Find a new position ensuring no overlap** duplicated.position = Offset(space.position.dx + 300, space.position.dy + 100);
Offset getBalancedChildPosition(SpaceModel parent) { List<SpaceModel> duplicatedSubtree = [];
int totalSiblings = parent.children.length + 1; void collectSubtree(SpaceModel node) {
double totalWidth = (totalSiblings - 1) * horizontalGap; duplicatedSubtree.add(node);
double startX = parent.position.dx - (totalWidth / 2); for (var child in node.children) {
Offset position = Offset( collectSubtree(child);
startX + (parent.children.length * horizontalGap), parent.position.dy + verticalGap);
// **Check for overlaps & adjust**
while (spaces.any((s) => (s.position - position).distance < horizontalGap)) {
position = Offset(position.dx + horizontalGap, position.dy);
}
print("🔹 New position for ${parent.name}: (${position.dx}, ${position.dy})");
return position;
}
/// **Realign the entire tree after duplication**
void realignTree() {
void updatePositions(SpaceModel node, double x, double y) {
node.position = Offset(x, y);
print("✅ Adjusted ${node.name} to (${x}, ${y})");
int numChildren = node.children.length;
double childStartX = x - ((numChildren - 1) * horizontalGap) / 2;
for (int i = 0; i < numChildren; i++) {
updatePositions(node.children[i], childStartX + (i * horizontalGap), y + verticalGap);
} }
} }
if (spaces.isNotEmpty) { collectSubtree(duplicated);
print("🔄 Realigning tree..."); spaces.addAll(duplicatedSubtree);
updatePositions(spaces.first, spaces.first.position.dx, spaces.first.position.dy);
if (parent != null) {
parent.children.add(duplicated);
final newConnection = Connection(
startSpace: parent,
endSpace: duplicated,
direction: "down",
);
connections.add(newConnection);
duplicated.incomingConnection = newConnection;
parent.addOutgoingConnection(newConnection);
} }
}
/// **Recursive duplication logic** realignTree();
SpaceModel duplicateRecursive(SpaceModel original, SpaceModel? duplicatedParent) { connections = createConnections(spaces);
Offset newPosition = duplicatedParent == null });
? Offset(original.position.dx + horizontalGap, original.position.dy) }
: getBalancedChildPosition(duplicatedParent);
final duplicatedName = SpaceHelper.generateUniqueSpaceName(original.name, spaces); SpaceModel _deepCloneSpaceTree(SpaceModel original, {SpaceModel? parent}) {
print( final duplicatedName = SpaceHelper.generateUniqueSpaceName(original.name, spaces);
"🟡 Duplicating ${original.name} → ${duplicatedName} at (${newPosition.dx}, ${newPosition.dy})");
final duplicated = SpaceModel( final newSpace = SpaceModel(
name: duplicatedName, name: duplicatedName,
icon: original.icon, icon: original.icon,
position: newPosition, position: original.position,
isPrivate: original.isPrivate, isPrivate: original.isPrivate,
children: [], children: [],
status: SpaceStatus.newSpace, status: SpaceStatus.newSpace,
parent: duplicatedParent, spaceModel: original.spaceModel,
spaceModel: original.spaceModel, subspaces: original.subspaces,
subspaces: original.subspaces, tags: original.tags,
tags: original.tags, parent: parent,
);
for (var child in original.children) {
final duplicatedChild = _deepCloneSpaceTree(child, parent: newSpace);
newSpace.children.add(duplicatedChild);
final newConnection = Connection(
startSpace: newSpace,
endSpace: duplicatedChild,
direction: "down",
); );
connections.add(newConnection);
setState(() { duplicatedChild.incomingConnection = newConnection;
spaces.add(duplicated); newSpace.addOutgoingConnection(newConnection);
_updateNodePosition(duplicated, duplicated.position);
if (duplicatedParent != null) {
final newConnection = Connection(
startSpace: duplicatedParent,
endSpace: duplicated,
direction: "down",
);
connections.add(newConnection);
duplicated.incomingConnection = newConnection;
duplicatedParent.addOutgoingConnection(newConnection);
duplicatedParent.children.add(duplicated);
print("🔗 Created connection: ${duplicatedParent.name} → ${duplicated.name}");
}
// **Recalculate the whole tree to avoid overlaps**
realignTree();
});
// Recursively duplicate children
for (var child in original.children) {
duplicateRecursive(child, duplicated);
}
return duplicated;
} }
/// **Handle root duplication** return newSpace;
if (space.parent == null) {
print("🟠 Duplicating root node: ${space.name}");
SpaceModel duplicatedRoot = duplicateRecursive(space, null);
setState(() {
spaces.add(duplicatedRoot);
realignTree();
});
print("✅ Root duplication successful: ${duplicatedRoot.name}");
} else {
duplicateRecursive(space, space.parent);
}
print("🟢 Finished duplication process for: ${space.name}");
} }
} }
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart'; import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_event.dart';
import 'package:syncrow_web/pages/space_tree/view/space_tree_view.dart'; import 'package:syncrow_web/pages/space_tree/view/space_tree_view.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_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/bloc/space_management_event.dart';
@@ -45,7 +46,6 @@ class _LoadedSpaceViewState extends State<LoadedSpaceView> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_spaceModels = List.from(widget.spaceModels ?? []); _spaceModels = List.from(widget.spaceModels ?? []);
} }
@@ -106,9 +106,8 @@ class _LoadedSpaceViewState extends State<LoadedSpaceView> {
children: [ children: [
SidebarWidget( SidebarWidget(
communities: widget.communities, communities: widget.communities,
selectedSpaceUuid: widget.selectedSpace?.uuid ?? selectedSpaceUuid:
widget.selectedCommunity?.uuid ?? widget.selectedSpace?.uuid ?? widget.selectedCommunity?.uuid ?? '',
'',
onCreateCommunity: (name, description) { onCreateCommunity: (name, description) {
context.read<SpaceManagementBloc>().add( context.read<SpaceManagementBloc>().add(
CreateCommunityEvent(name, description, context), CreateCommunityEvent(name, description, context),
@@ -1,8 +1,11 @@
import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/common/widgets/empty_search_result_widget.dart'; import 'package:syncrow_web/common/widgets/empty_search_result_widget.dart';
import 'package:syncrow_web/common/widgets/search_bar.dart'; import 'package:syncrow_web/common/widgets/search_bar.dart';
import 'package:syncrow_web/common/widgets/sidebar_communities_list.dart'; import 'package:syncrow_web/common/widgets/sidebar_communities_list.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.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_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/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/community_model.dart';
@@ -15,6 +18,8 @@ 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/pages/spaces_management/structure_selector/bloc/center_body_event.dart';
import 'package:syncrow_web/utils/style.dart'; import 'package:syncrow_web/utils/style.dart';
import '../../../space_tree/bloc/space_tree_event.dart';
class SidebarWidget extends StatefulWidget { class SidebarWidget extends StatefulWidget {
final List<CommunityModel> communities; final List<CommunityModel> communities;
final String? selectedSpaceUuid; final String? selectedSpaceUuid;
@@ -33,6 +38,7 @@ class SidebarWidget extends StatefulWidget {
class _SidebarWidgetState extends State<SidebarWidget> { class _SidebarWidgetState extends State<SidebarWidget> {
late final ScrollController _scrollController; late final ScrollController _scrollController;
Timer? _debounce;
String _searchQuery = ''; String _searchQuery = '';
String? _selectedSpaceUuid; String? _selectedSpaceUuid;
@@ -40,17 +46,48 @@ class _SidebarWidgetState extends State<SidebarWidget> {
@override @override
void initState() { void initState() {
_selectedId = widget.selectedSpaceUuid;
_scrollController = ScrollController();
super.initState(); super.initState();
_scrollController = ScrollController();
_scrollController.addListener(_onScroll);
_selectedId = widget.selectedSpaceUuid;
_searchQuery = '';
context.read<SpaceTreeBloc>().add(ClearCachedData());
}
void _onScroll() {
if (_scrollController.position.pixels >= _scrollController.position.maxScrollExtent - 100) {
// Trigger pagination event
final bloc = context.read<SpaceTreeBloc>();
if (!bloc.state.paginationIsLoading && bloc.state.paginationModel?.hasNext == true) {
bloc.add(
PaginationEvent(
bloc.state.paginationModel!,
bloc.state.communityList,
),
);
}
}
} }
@override @override
void dispose() { void dispose() {
_scrollController.removeListener(_onScroll);
_scrollController.dispose(); _scrollController.dispose();
_debounce?.cancel();
super.dispose(); super.dispose();
} }
void _onSearchChanged(String query) {
if (_debounce?.isActive ?? false) _debounce?.cancel();
_debounce = Timer(const Duration(milliseconds: 500), () {
setState(() {
_searchQuery = query;
});
context.read<SpaceTreeBloc>().add(SearchQueryEvent(query));
});
}
@override @override
void didUpdateWidget(covariant SidebarWidget oldWidget) { void didUpdateWidget(covariant SidebarWidget oldWidget) {
if (widget.selectedSpaceUuid != oldWidget.selectedSpaceUuid) { if (widget.selectedSpaceUuid != oldWidget.selectedSpaceUuid) {
@@ -59,38 +96,6 @@ class _SidebarWidgetState extends State<SidebarWidget> {
super.didUpdateWidget(oldWidget); super.didUpdateWidget(oldWidget);
} }
List<CommunityModel> _filteredCommunities() {
if (_searchQuery.isEmpty) {
_selectedSpaceUuid = null;
return widget.communities;
}
return widget.communities.where((community) {
final containsQueryInCommunity =
community.name.toLowerCase().contains(_searchQuery.toLowerCase());
final containsQueryInSpaces = community.spaces.any((space) =>
_containsQuery(space: space, query: _searchQuery.toLowerCase()));
return containsQueryInCommunity || containsQueryInSpaces;
}).toList();
}
bool _containsQuery({
required SpaceModel space,
required String query,
}) {
final matchesSpace = space.name.toLowerCase().contains(query);
final matchesChildren = space.children.any(
(child) => _containsQuery(space: child, query: query),
);
if (matchesSpace || matchesChildren) {
_selectedSpaceUuid = space.uuid;
}
return matchesSpace || matchesChildren;
}
bool _isSpaceOrChildSelected(SpaceModel space) { bool _isSpaceOrChildSelected(SpaceModel space) {
final isSpaceSelected = _selectedSpaceUuid == space.uuid; final isSpaceSelected = _selectedSpaceUuid == space.uuid;
final anySubSpaceIsSelected = space.children.any(_isSpaceOrChildSelected); final anySubSpaceIsSelected = space.children.any(_isSpaceOrChildSelected);
@@ -101,7 +106,10 @@ class _SidebarWidgetState extends State<SidebarWidget> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final filteredCommunities = _filteredCommunities(); final spaceTreeState = context.watch<SpaceTreeBloc>().state;
final filteredCommunities = spaceTreeState.searchQuery.isNotEmpty
? spaceTreeState.filteredCommunity
: spaceTreeState.communityList;
return Container( return Container(
width: _width, width: _width,
@@ -112,7 +120,7 @@ class _SidebarWidgetState extends State<SidebarWidget> {
children: [ children: [
SidebarHeader(onAddCommunity: _onAddCommunity), SidebarHeader(onAddCommunity: _onAddCommunity),
CustomSearchBar( CustomSearchBar(
onSearchChanged: (query) => setState(() => _searchQuery = query), onSearchChanged: _onSearchChanged,
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
Expanded( Expanded(
@@ -120,14 +128,23 @@ class _SidebarWidgetState extends State<SidebarWidget> {
visible: filteredCommunities.isNotEmpty, visible: filteredCommunities.isNotEmpty,
replacement: const EmptySearchResultWidget(), replacement: const EmptySearchResultWidget(),
child: SidebarCommunitiesList( child: SidebarCommunitiesList(
scrollController: _scrollController, scrollController: _scrollController,
onScrollToEnd: () {}, onScrollToEnd: () {},
communities: filteredCommunities, communities: filteredCommunities,
itemBuilder: (context, index) => _buildCommunityTile( itemBuilder: (context, index) {
context, if (index == filteredCommunities.length) {
filteredCommunities[index], final spaceTreeState = context.read<SpaceTreeBloc>().state;
), if (spaceTreeState.paginationIsLoading) {
), return const Padding(
padding: EdgeInsets.all(8.0),
child: Center(child: CircularProgressIndicator()),
);
} else {
return const SizedBox.shrink();
}
}
return _buildCommunityTile(context, filteredCommunities[index]);
}),
), ),
), ),
], ],
@@ -205,9 +222,8 @@ class _SidebarWidgetState extends State<SidebarWidget> {
); );
} }
void _onAddCommunity() => _selectedId?.isNotEmpty ?? true void _onAddCommunity() =>
? _clearSelection() _selectedId?.isNotEmpty ?? true ? _clearSelection() : _showCreateCommunityDialog();
: _showCreateCommunityDialog();
void _clearSelection() { void _clearSelection() {
setState(() => _selectedId = ''); setState(() => _selectedId = '');
@@ -1,5 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_event.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/bloc/space_model_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/space_model_bloc.dart';
@@ -27,6 +29,8 @@ class SpaceModelPage extends StatelessWidget {
return const Center(child: CircularProgressIndicator()); return const Center(child: CircularProgressIndicator());
} else if (state is SpaceModelLoaded) { } else if (state is SpaceModelLoaded) {
final spaceModels = state.spaceModels; final spaceModels = state.spaceModels;
context.read<SpaceTreeBloc>().add(ClearCachedData());
final allTagValues = _getAllTagValues(spaceModels); final allTagValues = _getAllTagValues(spaceModels);
final allSpaceModelNames = _getAllSpaceModelName(spaceModels); final allSpaceModelNames = _getAllSpaceModelName(spaceModels);
+20 -10
View File
@@ -46,14 +46,14 @@ final class DebouncedBatchControlDevicesService
final BatchControlDevicesService decoratee; final BatchControlDevicesService decoratee;
final Duration debounceDuration; final Duration debounceDuration;
final _pendingRequests = <(List<String> uuids, String code, Object value)>[];
var _isProcessing = false;
DebouncedBatchControlDevicesService({ DebouncedBatchControlDevicesService({
required this.decoratee, required this.decoratee,
this.debounceDuration = const Duration(milliseconds: 1500), this.debounceDuration = const Duration(milliseconds: 1500),
}); });
final _pendingRequests = <(List<String> uuids, String code, Object value)>[];
var _isProcessing = false;
@override @override
Future<bool> batchControlDevices({ Future<bool> batchControlDevices({
required List<String> uuids, required List<String> uuids,
@@ -68,16 +68,26 @@ final class DebouncedBatchControlDevicesService
await Future.delayed(debounceDuration); await Future.delayed(debounceDuration);
final lastRequest = _pendingRequests.last; final groupedRequests =
<String, (List<String> uuids, String code, Object value)>{};
for (final request in _pendingRequests) {
final (_, requestCode, requestValue) = request;
groupedRequests[requestCode] = request;
}
_pendingRequests.clear(); _pendingRequests.clear();
try { try {
final (lastRequestUuids, lastRequestCode, lastRequestValue) = lastRequest; var allSuccessful = true;
return decoratee.batchControlDevices( for (final request in groupedRequests.values) {
uuids: lastRequestUuids, final (lastRequestUuids, lastRequestCode, lastRequestValue) = request;
code: lastRequestCode, final success = await decoratee.batchControlDevices(
value: lastRequestValue, uuids: lastRequestUuids,
); code: lastRequestCode,
value: lastRequestValue,
);
if (!success) allSuccessful = false;
}
return allSuccessful;
} finally { } finally {
_isProcessing = false; _isProcessing = false;
} }
+15 -6
View File
@@ -59,15 +59,24 @@ final class DebouncedControlDeviceService implements ControlDeviceService {
await Future.delayed(debounceDuration); await Future.delayed(debounceDuration);
final lastRequest = _pendingRequests.last; final groupedRequests = <String, (String deviceUuid, Status status)>{};
for (final request in _pendingRequests) {
final (_, requestStatus) = request;
groupedRequests[requestStatus.code] = request;
}
_pendingRequests.clear(); _pendingRequests.clear();
try { try {
final (lastRequestDeviceUuid, lastRequestStatus) = lastRequest; var allSuccessful = true;
return decoratee.controlDevice( for (final request in groupedRequests.values) {
deviceUuid: lastRequestDeviceUuid, final (lastRequestDeviceUuid, lastRequestStatus) = request;
status: lastRequestStatus, final success = await decoratee.controlDevice(
); deviceUuid: lastRequestDeviceUuid,
status: lastRequestStatus,
);
if (!success) allSuccessful = false;
}
return allSuccessful;
} finally { } finally {
_isProcessing = false; _isProcessing = false;
} }
+1
View File
@@ -281,6 +281,7 @@ class CommunitySpaceManagementApi {
return json['success'] ?? false; return json['success'] ?? false;
}, },
); );
return response; return response;
} catch (e) { } catch (e) {
debugPrint('Error updating space: $e'); debugPrint('Error updating space: $e');
+6
View File
@@ -1,5 +1,6 @@
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:syncrow_web/pages/access_management/view/access_management.dart'; import 'package:syncrow_web/pages/access_management/view/access_management.dart';
import 'package:syncrow_web/pages/analytics/modules/analytics/views/analytics_page.dart';
import 'package:syncrow_web/pages/auth/view/login_page.dart'; import 'package:syncrow_web/pages/auth/view/login_page.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/view/device_managment_page.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/view/device_managment_page.dart';
import 'package:syncrow_web/pages/home/view/home_page.dart'; import 'package:syncrow_web/pages/home/view/home_page.dart';
@@ -37,6 +38,11 @@ class AppRoutes {
GoRoute( GoRoute(
path: RoutesConst.rolesAndPermissions, path: RoutesConst.rolesAndPermissions,
builder: (context, state) => const RolesAndPermissionPage()), builder: (context, state) => const RolesAndPermissionPage()),
GoRoute(
path: RoutesConst.analytics,
name: 'analytics',
builder: (context, state) => const AnalyticsPage(),
),
]; ];
} }
} }
+1
View File
@@ -480,4 +480,5 @@ class Assets {
static const String DisappeDelayIcon = 'assets/icons/disappe_delay_icon.svg'; static const String DisappeDelayIcon = 'assets/icons/disappe_delay_icon.svg';
static const String indentLevelIcon = 'assets/icons/indent_level_icon.svg'; static const String indentLevelIcon = 'assets/icons/indent_level_icon.svg';
static const String triggerLevelIcon = 'assets/icons/trigger_level_icon.svg'; static const String triggerLevelIcon = 'assets/icons/trigger_level_icon.svg';
static const String blankCalendar = 'assets/icons/blank_calendar.svg';
} }
+1
View File
@@ -6,4 +6,5 @@ class RoutesConst {
static const String deviceManagementPage = '/device-management-page'; static const String deviceManagementPage = '/device-management-page';
static const String spacesManagementPage = '/spaces_management-page'; static const String spacesManagementPage = '/spaces_management-page';
static const String rolesAndPermissions = '/roles_and_Permissions-page'; static const String rolesAndPermissions = '/roles_and_Permissions-page';
static const String analytics = '/syncrow_analytics';
} }