Compare commits

...
Author SHA1 Message Date
mohammad 8dc4081a89 save presence state and edit selected value 2025-04-06 16:38:34 +03:00
Faris Armoush 973ae82bec Merge pull request #125 from SyncrowIOT/SP-1271-rework
SP-1271-rework
2025-04-06 11:59:46 +03:00
mohammadnemer1 8c4dec4483 Merge pull request #129 from SyncrowIOT/SP-1279
add WallPresenceSensor and add new icons for presence state and selec…
2025-04-06 11:57:54 +03:00
Abdullah Alassaf 64f61a6c3d Merged with dev 2025-04-06 01:17:30 +03:00
Abdullah Alassaf ab3f268f29 Added pagination and search logic in space tree 2025-04-06 01:14:16 +03:00
hannathkadher b345822366 Merge pull request #128 from SyncrowIOT:SP-1200
fixing duplicate issue
2025-04-03 10:50:31 +04:00
hannathkadher 76793a3c0b Merge branch 'dev' of https://github.com/SyncrowIOT/web into SP-1200 2025-04-03 10:48:56 +04:00
hannathkadher c330f11206 Merge pull request #127 from SyncrowIOT/SP-1246
updated endpoints for devices
2025-04-03 10:23:34 +04:00
hannathkadher 9cb62795a6 Merge branch 'dev' of https://github.com/SyncrowIOT/web into SP-1246 2025-04-03 10:17:06 +04:00
hannathkadher a034202d76 updated endpoints 2025-04-03 10:14:24 +04:00
mohammadnemer1 869799f08e Merge pull request #126 from SyncrowIOT/fix_routine_popup
Refactor routine creation UI for improved code organization
2025-03-27 15:06:38 +03:00
mohammad 573e7b42ed Refactor routine creation UI for improved code organization 2025-03-27 15:04:33 +03:00
fkarmoush 22935f7007 SP-1271-rework 2025-03-27 14:52:26 +03:00
Faris Armoush 5bf5120883 Merge pull request #124 from SyncrowIOT/SP-1271-Fix-Deployment
SP-1271-Fix-Deployment
2025-03-27 13:45:59 +03:00
fkarmoush f307aaff9d Removed the usage of spacing property because the GitHub action doesnt support the version that has it, so instead opted for using Padding. 2025-03-27 11:08:57 +03:00
Faris Armoush aea18fb293 Merge pull request #123 from SyncrowIOT/SP-1271-FE-Remove-Community-Field-from-Device-Management-Screen
Sp 1271 fe remove community field from device management screen
2025-03-26 12:57:16 +03:00
fkarmoush 835dfe8785 Rename controller variables in DeviceSearchFilters to be private to control access control. 2025-03-26 12:40:25 +03:00
fkarmoush f670ae78aa Refactor device search filters layout for improved code organization and to remove any code duplication. 2025-03-26 12:39:51 +03:00
fkarmoush d3128a9c9c Refactor search button callback for improved readability 2025-03-26 12:37:03 +03:00
fkarmoush b65f172f9d Removed memory leak bug from DeviceSearchFilters widget. 2025-03-26 12:36:26 +03:00
fkarmoush dad18b77de Removed community filter field from DeviceSearchFilters. 2025-03-26 12:35:04 +03:00
Abdullah Alassaf 77a9aa2f19 Added include spaces to the communites api 2025-03-26 04:29:33 +03:00
hannathkadher de5030d9f0 Merge branch 'dev' into SP-1200 2025-03-19 14:12:13 +04:00
hannathkadher 8c15fffd42 duplicate 2025-03-19 12:25:37 +04:00
hannathkadher 3750fa8329 removed left and right button 2025-03-14 15:02:22 +04:00
23 changed files with 802 additions and 560 deletions
@@ -291,25 +291,25 @@ SOS
return [ return [
//IF Functions //IF Functions
PresenceStateFunction( PresenceStateFunction(
deviceId: uuid ?? '', deviceName: name ?? '', type: 'THEN'), deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'),
CurrentDistanceFunction( CurrentDistanceFunction(
deviceId: uuid ?? '', deviceName: name ?? '', type: 'THEN'), deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'),
IlluminanceValueFunction( IlluminanceValueFunction(
deviceId: uuid ?? '', deviceName: name ?? '', type: 'THEN'), deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'),
PresenceTimeFunction( PresenceTimeFunction(
deviceId: uuid ?? '', deviceName: name ?? '', type: 'THEN'), deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'),
//THEN Functions //THEN Functions
FarDetectionFunction( FarDetectionFunction(
deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'), deviceId: uuid ?? '', deviceName: name ?? '', type: 'THEN'),
MotionSensitivityFunction( MotionSensitivityFunction(
deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'), deviceId: uuid ?? '', deviceName: name ?? '', type: 'THEN'),
MotionLessSensitivityFunction( MotionLessSensitivityFunction(
deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'), deviceId: uuid ?? '', deviceName: name ?? '', type: 'THEN'),
IndicatorFunction( IndicatorFunction(
deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'), deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'),
NoOneTimeFunction( NoOneTimeFunction(
deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'), deviceId: uuid ?? '', deviceName: name ?? '', type: 'THEN'),
// FarDetectionSliderFunction( // FarDetectionSliderFunction(
// deviceId: uuid ?? '', deviceName: name ?? '', type: 'THEN') // deviceId: uuid ?? '', deviceName: name ?? '', type: 'THEN')
@@ -2,14 +2,17 @@ import 'package:flutter/foundation.dart';
class FactoryResetModel { class FactoryResetModel {
final List<String> devicesUuid; final List<String> devicesUuid;
final String operationType;
FactoryResetModel({ FactoryResetModel({
required this.devicesUuid, required this.devicesUuid,
this.operationType = "RESET",
}); });
factory FactoryResetModel.fromJson(Map<String, dynamic> json) { factory FactoryResetModel.fromJson(Map<String, dynamic> json) {
return FactoryResetModel( return FactoryResetModel(
devicesUuid: List<String>.from(json['devicesUuid']), devicesUuid: List<String>.from(json['devicesUuid']),
operationType: "RESET",
); );
} }
@@ -12,77 +12,68 @@ class DeviceSearchFilters extends StatefulWidget {
State<DeviceSearchFilters> createState() => _DeviceSearchFiltersState(); State<DeviceSearchFilters> createState() => _DeviceSearchFiltersState();
} }
class _DeviceSearchFiltersState extends State<DeviceSearchFilters> with HelperResponsiveLayout { class _DeviceSearchFiltersState extends State<DeviceSearchFilters>
final TextEditingController communityController = TextEditingController(); with HelperResponsiveLayout {
final TextEditingController unitNameController = TextEditingController(); final _unitNameController = TextEditingController();
final TextEditingController productNameController = TextEditingController(); final _productNameController = TextEditingController();
List<Widget> get _widgets => [
_buildSearchField("Space Name", _unitNameController, 200),
_buildSearchField("Device Name / Product Name", _productNameController, 300),
_buildSearchResetButtons(),
];
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return isExtraLargeScreenSize(context) if (isExtraLargeScreenSize(context)) {
? Row( return Row(
children: [ children: _widgets
_buildSearchField("Community", communityController, 200), .map((e) => Padding(padding: const EdgeInsets.all(10), child: e))
const SizedBox(width: 20), .toList(),
_buildSearchField("Space Name", unitNameController, 200), );
const SizedBox(width: 20), }
_buildSearchField("Device Name / Product Name", productNameController, 300),
const SizedBox(width: 20), return Wrap(
_buildSearchResetButtons(), spacing: 20,
], runSpacing: 10,
) children: _widgets,
: Wrap( );
spacing: 20,
runSpacing: 10,
children: [
_buildSearchField(
"Community",
communityController,
200,
),
_buildSearchField("Space Name", unitNameController, 200),
_buildSearchField(
"Device Name / Product Name",
productNameController,
300,
),
_buildSearchResetButtons(),
],
);
} }
Widget _buildSearchField(String title, TextEditingController controller, double width) { Widget _buildSearchField(
return Container( String title,
child: StatefulTextField( TextEditingController controller,
title: title, double width,
width: width, ) {
elevation: 2, return StatefulTextField(
controller: controller, title: title,
onSubmitted: () { width: width,
context.read<DeviceManagementBloc>().add(SearchDevices( elevation: 2,
productName: productNameController.text, controller: controller,
unitName: unitNameController.text, onSubmitted: () {
community: communityController.text, final searchDevicesEvent = SearchDevices(
searchField: true)); productName: _productNameController.text,
}, unitName: _unitNameController.text,
onChanged: (p0) {}, searchField: true,
), );
context.read<DeviceManagementBloc>().add(searchDevicesEvent);
},
onChanged: (p0) {},
); );
} }
Widget _buildSearchResetButtons() { Widget _buildSearchResetButtons() {
return SearchResetButtons( return SearchResetButtons(
onSearch: () { onSearch: () => context.read<DeviceManagementBloc>().add(
context.read<DeviceManagementBloc>().add(SearchDevices( SearchDevices(
community: communityController.text, unitName: _unitNameController.text,
unitName: unitNameController.text, productName: _productNameController.text,
productName: productNameController.text, searchField: true,
searchField: true)); ),
}, ),
onReset: () { onReset: () {
communityController.clear(); _unitNameController.clear();
unitNameController.clear(); _productNameController.clear();
productNameController.clear();
context.read<DeviceManagementBloc>() context.read<DeviceManagementBloc>()
..add(ResetFilters()) ..add(ResetFilters())
..add(FetchDevices(context)); ..add(FetchDevices(context));
@@ -1,16 +1,16 @@
import 'package:dropdown_button2/dropdown_button2.dart';
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_state.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/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/style.dart';
class CommunityDropdown extends StatelessWidget { class CommunityDropdown extends StatelessWidget {
final String? selectedValue; final String? selectedValue;
final Function(String?) onChanged; final Function(String?) onChanged;
final TextEditingController _searchController = TextEditingController();
const CommunityDropdown({ CommunityDropdown({
Key? key, Key? key,
required this.selectedValue, required this.selectedValue,
required this.onChanged, required this.onChanged,
@@ -34,59 +34,119 @@ class CommunityDropdown extends StatelessWidget {
const SizedBox(height: 8), const SizedBox(height: 8),
BlocBuilder<SpaceTreeBloc, SpaceTreeState>( BlocBuilder<SpaceTreeBloc, SpaceTreeState>(
builder: (context, state) { builder: (context, state) {
List<CommunityModel> communities = state.isSearching
? state.filteredCommunity
: state.communityList;
return SizedBox( return SizedBox(
child: DropdownButtonFormField<String>( child: Container(
dropdownColor: ColorsManager.whiteColors, decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
),
child: DropdownButton2<String>(
underline: SizedBox(),
value: selectedValue, value: selectedValue,
items: communities.map((community) { items: state.communityList.map((community) {
return DropdownMenuItem<String>( return DropdownMenuItem<String>(
value: community.uuid, value: community.uuid,
child: Text(' ${community.name}'), child: Text(
' ${community.name}',
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
); );
}).toList(), }).toList(),
onChanged: onChanged, onChanged: onChanged,
icon: const SizedBox.shrink(), style: TextStyle(color: Colors.black),
borderRadius: const BorderRadius.all(Radius.circular(10)),
hint: Padding( hint: Padding(
padding: EdgeInsets.only(left: 10), padding: EdgeInsets.only(left: 10),
child: Text( child: Text(
"Please Select", " Please Select",
style: Theme.of(context).textTheme.bodySmall!.copyWith( style: Theme.of(context).textTheme.bodySmall!.copyWith(
color: ColorsManager.textGray, color: ColorsManager.textGray,
), ),
), ),
), ),
decoration: inputTextFormDeco().copyWith( customButton: Container(
contentPadding: EdgeInsets.zero, height: 45,
suffixIcon: Container( decoration: BoxDecoration(
padding: EdgeInsets.zero, border: Border.all(color: ColorsManager.textGray, width: 1.0),
width: 70, borderRadius: BorderRadius.circular(10),
height: 45, ),
decoration: BoxDecoration( child: Row(
color: Colors.grey[100], mainAxisAlignment: MainAxisAlignment.spaceBetween,
borderRadius: const BorderRadius.only( children: [
bottomRight: Radius.circular(10), Expanded(
topRight: Radius.circular(10), flex: 5,
child: Text(
selectedValue != null
? " ${state.communityList.firstWhere((element) => element.uuid == selectedValue).name}"
: ' Please Select',
style: Theme.of(context).textTheme.bodySmall!.copyWith(
color:
selectedValue != null ? Colors.black : ColorsManager.textGray,
),
overflow: TextOverflow.ellipsis,
),
), ),
border: Border.all( Expanded(
color: ColorsManager.textGray, child: Container(
width: 1.0, decoration: BoxDecoration(
color: Colors.grey[100],
borderRadius: const BorderRadius.only(
topRight: Radius.circular(10),
bottomRight: Radius.circular(10),
),
),
height: 45,
child: const Icon(
Icons.keyboard_arrow_down,
color: ColorsManager.textGray,
),
),
), ),
), ],
child: const Center( ),
child: Icon( ),
Icons.keyboard_arrow_down, dropdownStyleData: DropdownStyleData(
color: ColorsManager.textGray, maxHeight: MediaQuery.of(context).size.height * 0.4,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
),
),
dropdownSearchData: DropdownSearchData(
searchController: _searchController,
searchInnerWidgetHeight: 50,
searchInnerWidget: Container(
height: 50,
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
child: TextFormField(
style: const TextStyle(color: Colors.black),
controller: _searchController,
decoration: InputDecoration(
isDense: true,
contentPadding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 12,
),
hintText: 'Search for community...',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
),
), ),
), ),
), ),
searchMatchFn: (item, searchValue) {
final communityName = (item.child as Text).data?.toLowerCase() ?? '';
return communityName.contains(searchValue.toLowerCase().trim());
},
),
onMenuStateChange: (isOpen) {
if (!isOpen) {
_searchController.clear();
}
},
menuItemStyleData: const MenuItemStyleData(
height: 40,
), ),
), ),
); ));
}, },
), ),
], ],
@@ -62,28 +62,34 @@ class _CreateNewRoutinesDialogState extends State<CreateNewRoutinesDialog> {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
const Divider(), const Divider(),
CommunityDropdown( Padding(
selectedValue: _selectedCommunity, padding: const EdgeInsets.only(left: 15, right: 15),
onChanged: (String? newValue) { child: CommunityDropdown(
setState(() { selectedValue: _selectedCommunity,
_selectedCommunity = newValue; onChanged: (String? newValue) {
_selectedSpace = null; setState(() {
}); _selectedCommunity = newValue;
if (newValue != null) { _selectedSpace = null;
_fetchSpaces(newValue); });
} if (newValue != null) {
}, _fetchSpaces(newValue);
}
},
),
), ),
const SizedBox(height: 16), const SizedBox(height: 5),
SpaceDropdown( Padding(
hintMessage: spaceHint, padding: const EdgeInsets.only(left: 15, right: 15),
spaces: spaces, child: SpaceDropdown(
selectedValue: _selectedSpace, hintMessage: spaceHint,
onChanged: (String? newValue) { spaces: spaces,
setState(() { selectedValue: _selectedSpace,
_selectedSpace = newValue; onChanged: (String? newValue) {
}); setState(() {
}, _selectedSpace = newValue;
});
},
),
), ),
const Divider(), const Divider(),
Row( Row(
@@ -96,7 +102,6 @@ class _CreateNewRoutinesDialogState extends State<CreateNewRoutinesDialog> {
), ),
child: TextButton( child: TextButton(
onPressed: () { onPressed: () {
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
child: Text( child: Text(
@@ -139,6 +144,7 @@ class _CreateNewRoutinesDialogState extends State<CreateNewRoutinesDialog> {
), ),
], ],
), ),
SizedBox(height: 10),
], ],
), ),
); );
@@ -1,7 +1,8 @@
import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/style.dart';
class SpaceDropdown extends StatelessWidget { class SpaceDropdown extends StatelessWidget {
final List<SpaceModel> spaces; final List<SpaceModel> spaces;
@@ -33,62 +34,108 @@ class SpaceDropdown extends StatelessWidget {
), ),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
DropdownButtonFormField<String>( SizedBox(
value: selectedValue, child: Container(
items: spaces.map((space) { decoration: BoxDecoration(
return DropdownMenuItem<String>( borderRadius: BorderRadius.circular(10),
value: space.uuid,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
' ${space.name}',
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
fontSize: 12,
color: ColorsManager.blackColor,
),
),
Text(
' ${space.lastThreeParents}',
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
fontSize: 12,
),
),
],
));
}).toList(),
onChanged: onChanged,
icon: const SizedBox.shrink(),
borderRadius: const BorderRadius.all(Radius.circular(10)),
hint: Padding(
padding: const EdgeInsets.only(left: 10),
child: Text(
hintMessage,
style: Theme.of(context).textTheme.bodySmall!.copyWith(
color: ColorsManager.textGray,
),
), ),
), child: DropdownButton2<String>(
decoration: inputTextFormDeco().copyWith( underline: const SizedBox(),
contentPadding: EdgeInsets.zero, value: selectedValue,
suffixIcon: Container( items: spaces.map((space) {
width: 70, return DropdownMenuItem<String>(
height: 45, value: space.uuid,
decoration: BoxDecoration( child: Column(
color: Colors.grey[200], crossAxisAlignment: CrossAxisAlignment.start,
borderRadius: const BorderRadius.only( mainAxisAlignment: MainAxisAlignment.start,
bottomRight: Radius.circular(10), children: [
topRight: Radius.circular(10), Text(
), ' ${space.name}',
border: Border.all( style:
color: ColorsManager.textGray, Theme.of(context).textTheme.bodyMedium!.copyWith(
width: 1.0, fontSize: 12,
color: ColorsManager.blackColor,
),
),
Text(
' ${space.lastThreeParents}',
style:
Theme.of(context).textTheme.bodyMedium!.copyWith(
fontSize: 12,
),
),
],
),
);
}).toList(),
onChanged: onChanged,
style: TextStyle(color: Colors.black),
hint: Padding(
padding: const EdgeInsets.only(left: 10),
child: Text(
hintMessage,
style: Theme.of(context).textTheme.bodySmall!.copyWith(
color: ColorsManager.textGray,
),
), ),
), ),
child: const Icon( customButton: Container(
Icons.keyboard_arrow_down, height: 45,
color: ColorsManager.textGray, decoration: BoxDecoration(
border:
Border.all(color: ColorsManager.textGray, width: 1.0),
borderRadius: BorderRadius.circular(10),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
flex: 5,
child: Padding(
padding: const EdgeInsets.only(left: 10),
child: Text(
selectedValue != null
? spaces
.firstWhere((e) => e.uuid == selectedValue)
.name
: hintMessage,
style:
Theme.of(context).textTheme.bodySmall!.copyWith(
color: selectedValue != null
? Colors.black
: ColorsManager.textGray,
),
overflow: TextOverflow.ellipsis,
),
),
),
Expanded(
child: Container(
decoration: BoxDecoration(
color: Colors.grey[200],
borderRadius: const BorderRadius.only(
topRight: Radius.circular(10),
bottomRight: Radius.circular(10),
),
),
height: 45,
child: const Icon(
Icons.keyboard_arrow_down,
color: ColorsManager.textGray,
),
),
),
],
),
),
dropdownStyleData: DropdownStyleData(
maxHeight: MediaQuery.of(context).size.height * 0.4,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
),
),
menuItemStyleData: const MenuItemStyleData(
height: 60,
), ),
), ),
), ),
@@ -1,5 +1,3 @@
import 'package:syncrow_web/pages/device_managment/wall_sensor/model/wall_sensor_model.dart'; import 'package:syncrow_web/pages/device_managment/wall_sensor/model/wall_sensor_model.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/models/wps/wps_operational_value.dart'; import 'package:syncrow_web/pages/routines/models/wps/wps_operational_value.dart';
@@ -195,7 +193,7 @@ class NoOneTimeFunction extends WpsFunctions {
WpsOperationalValue( WpsOperationalValue(
icon: icon, icon: icon,
description: 'Custom $unit', description: 'Custom $unit',
value: null, value: null,
) )
]; ];
} }
@@ -216,12 +214,12 @@ class PresenceStateFunction extends WpsFunctions {
WpsOperationalValue( WpsOperationalValue(
icon: Assets.assetsAcPower, icon: Assets.assetsAcPower,
description: "None", description: "None",
value: true, value: 'none',
), ),
WpsOperationalValue( WpsOperationalValue(
icon: Assets.presenceStateIcon, icon: Assets.presenceStateIcon,
description: "Presence", description: "Presence",
value: false, value: 'presence',
), ),
]; ];
} }
@@ -238,7 +236,7 @@ class CurrentDistanceFunction extends WpsFunctions {
step = 1, step = 1,
super( super(
type: type, type: type,
code: 'current_distance', code: 'dis_current',
operationName: 'Current Distance', operationName: 'Current Distance',
icon: Assets.currentDistanceIcon, icon: Assets.currentDistanceIcon,
); );
@@ -33,8 +33,6 @@ class _RoutinesViewState extends State<RoutinesView> {
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));
await Future.delayed(const Duration(milliseconds:500));
_bloc.add(const ResetSelectedEvent());
} }
@override @override
@@ -252,7 +252,7 @@ class _ValueSelector extends StatelessWidget {
} }
bool _isSliderFunction(String function) => [ bool _isSliderFunction(String function) => [
'current_distance', 'dis_current',
'presence_time', 'presence_time',
'illuminance_value' 'illuminance_value'
].contains(function); ].contains(function);
@@ -382,7 +382,7 @@ class _ValueDisplay extends StatelessWidget {
switch (functionCode) { switch (functionCode) {
case 'presence_time': case 'presence_time':
return '$intValue Min'; return '$intValue Min';
case 'current_distance': case 'dis_current':
return '$intValue CM'; return '$intValue CM';
case 'illuminance_value': case 'illuminance_value':
return '$intValue Lux'; return '$intValue Lux';
@@ -421,7 +421,7 @@ class _FunctionSlider extends StatelessWidget {
switch (functionCode) { switch (functionCode) {
case 'presence_time': case 'presence_time':
return (0, 65535); return (0, 65535);
case 'current_distance': case 'dis_current':
return (1, 600); return (1, 600);
case 'illuminance_value': case 'illuminance_value':
return (0, 10000); return (0, 10000);
+117 -83
View File
@@ -1,7 +1,10 @@
import 'dart:async';
import 'package:flutter_bloc/flutter_bloc.dart'; 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_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/space_tree/bloc/space_tree_state.dart';
import 'package:syncrow_web/pages/space_tree/model/pagination_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/community_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
import 'package:syncrow_web/services/space_mana_api.dart'; import 'package:syncrow_web/services/space_mana_api.dart';
@@ -18,7 +21,10 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
on<ClearCachedData>(_clearCachedData); on<ClearCachedData>(_clearCachedData);
on<OnCommunityAdded>(_onCommunityAdded); on<OnCommunityAdded>(_onCommunityAdded);
on<OnCommunityUpdated>(_onCommunityUpdate); on<OnCommunityUpdated>(_onCommunityUpdate);
on<PaginationEvent>(_fetchPaginationSpaces);
on<DebouncedSearchEvent>(_onDebouncedSearch);
} }
Timer _timer = Timer(const Duration(microseconds: 0), () {});
void _onCommunityUpdate( void _onCommunityUpdate(
OnCommunityUpdated event, OnCommunityUpdated event,
@@ -28,17 +34,16 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
try { try {
final updatedCommunity = event.updatedCommunity; final updatedCommunity = event.updatedCommunity;
final updatedCommunities = final updatedCommunities = List<CommunityModel>.from(state.communityList);
List<CommunityModel>.from(state.communityList);
final index = updatedCommunities final index =
.indexWhere((community) => community.uuid == updatedCommunity.uuid); updatedCommunities.indexWhere((community) => community.uuid == updatedCommunity.uuid);
if (index != -1) { if (index != -1) {
updatedCommunities[index] = updatedCommunity; updatedCommunities[index] = updatedCommunity;
emit(state.copyWith(communitiesList: updatedCommunities)); emit(state.copyWith(communitiesList: updatedCommunities));
} else { } else {
emit(SpaceTreeErrorState('Community not found in the list.')); emit(const SpaceTreeErrorState('Community not found in the list.'));
} }
} catch (e) { } catch (e) {
emit(SpaceTreeErrorState('Error updating community: $e')); emit(SpaceTreeErrorState('Error updating community: $e'));
@@ -50,48 +55,68 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
try { try {
final projectUuid = await ProjectManager.getProjectUUID() ?? ''; final projectUuid = await ProjectManager.getProjectUUID() ?? '';
List<CommunityModel> communities = PaginationModel paginationModel = await CommunitySpaceManagementApi()
await CommunitySpaceManagementApi().fetchCommunities(projectUuid); .fetchCommunitiesAndSpaces(projectId: projectUuid, page: 1);
List<CommunityModel> updatedCommunities = await Future.wait( // List<CommunityModel> updatedCommunities = await Future.wait(
communities.map((community) async { // communities.map((community) async {
List<SpaceModel> spaces = await CommunitySpaceManagementApi() // List<SpaceModel> spaces =
.getSpaceHierarchy(community.uuid, projectUuid); // await CommunitySpaceManagementApi().getSpaceHierarchy(community.uuid, projectUuid);
return CommunityModel( // return CommunityModel(
uuid: community.uuid, // uuid: community.uuid,
createdAt: community.createdAt, // createdAt: community.createdAt,
updatedAt: community.updatedAt, // updatedAt: community.updatedAt,
name: community.name, // name: community.name,
description: community.description, // description: community.description,
spaces: spaces, // spaces: spaces,
region: community.region, // region: community.region,
); // );
}).toList(), // }).toList(),
); // );
emit(state.copyWith( emit(state.copyWith(
communitiesList: updatedCommunities, communitiesList: paginationModel.communities,
expandedCommunity: [], expandedCommunity: [],
expandedSpaces: [])); expandedSpaces: [],
paginationModel: paginationModel));
} catch (e) { } catch (e) {
emit(SpaceTreeErrorState('Error loading communities and spaces: $e')); emit(SpaceTreeErrorState('Error loading communities and spaces: $e'));
} }
} }
void _onCommunityAdded( _fetchPaginationSpaces(PaginationEvent event, Emitter<SpaceTreeState> emit) async {
OnCommunityAdded event, Emitter<SpaceTreeState> emit) async { emit(state.copyWith(paginationIsLoading: true));
PaginationModel paginationModel = event.paginationModel;
List<CommunityModel> communities = List<CommunityModel>.from(event.communities);
try {
if (paginationModel.hasNext && state.searchQuery.isEmpty) {
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
paginationModel = await CommunitySpaceManagementApi().fetchCommunitiesAndSpaces(
projectId: projectUuid, page: paginationModel.pageNum, search: state.searchQuery);
communities.addAll(paginationModel.communities);
}
} catch (e) {
emit(SpaceTreeErrorState('Error loading communities and spaces: $e'));
}
emit(state.copyWith(
communitiesList: communities,
paginationModel: paginationModel,
paginationIsLoading: false));
}
void _onCommunityAdded(OnCommunityAdded event, Emitter<SpaceTreeState> emit) async {
final updatedCommunities = List<CommunityModel>.from(state.communityList); final updatedCommunities = List<CommunityModel>.from(state.communityList);
updatedCommunities.add(event.newCommunity); updatedCommunities.add(event.newCommunity);
emit(state.copyWith(communitiesList: updatedCommunities)); emit(state.copyWith(communitiesList: updatedCommunities));
} }
_onCommunityExpanded( _onCommunityExpanded(OnCommunityExpanded event, Emitter<SpaceTreeState> emit) async {
OnCommunityExpanded event, Emitter<SpaceTreeState> emit) async {
try { try {
List<String> updatedExpandedCommunityList = List<String> updatedExpandedCommunityList = List.from(state.expandedCommunities);
List.from(state.expandedCommunities);
if (updatedExpandedCommunityList.contains(event.communityId)) { if (updatedExpandedCommunityList.contains(event.communityId)) {
updatedExpandedCommunityList.remove(event.communityId); updatedExpandedCommunityList.remove(event.communityId);
@@ -123,19 +148,14 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
} }
} }
_onCommunitySelected( _onCommunitySelected(OnCommunitySelected event, Emitter<SpaceTreeState> emit) async {
OnCommunitySelected event, Emitter<SpaceTreeState> emit) async {
try { try {
List<String> updatedSelectedCommunities = List<String> updatedSelectedCommunities =
List.from(state.selectedCommunities.toSet().toList()); List.from(state.selectedCommunities.toSet().toList());
List<String> updatedSelectedSpaces = List<String> updatedSelectedSpaces = List.from(state.selectedSpaces.toSet().toList());
List.from(state.selectedSpaces.toSet().toList()); List<String> updatedSoldChecks = List.from(state.soldCheck.toSet().toList());
List<String> updatedSoldChecks = Map<String, List<String>> communityAndSpaces = Map.from(state.selectedCommunityAndSpaces);
List.from(state.soldCheck.toSet().toList()); List<String> selectedSpacesInCommunity = communityAndSpaces[event.communityId] ?? [];
Map<String, List<String>> communityAndSpaces =
Map.from(state.selectedCommunityAndSpaces);
List<String> selectedSpacesInCommunity =
communityAndSpaces[event.communityId] ?? [];
List<String> childrenIds = _getAllChildIds(event.children); List<String> childrenIds = _getAllChildIds(event.children);
@@ -168,15 +188,11 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
try { try {
List<String> updatedSelectedCommunities = List<String> updatedSelectedCommunities =
List.from(state.selectedCommunities.toSet().toList()); List.from(state.selectedCommunities.toSet().toList());
List<String> updatedSelectedSpaces = List<String> updatedSelectedSpaces = List.from(state.selectedSpaces.toSet().toList());
List.from(state.selectedSpaces.toSet().toList()); List<String> updatedSoldChecks = List.from(state.soldCheck.toSet().toList());
List<String> updatedSoldChecks = Map<String, List<String>> communityAndSpaces = Map.from(state.selectedCommunityAndSpaces);
List.from(state.soldCheck.toSet().toList());
Map<String, List<String>> communityAndSpaces =
Map.from(state.selectedCommunityAndSpaces);
List<String> selectedSpacesInCommunity = List<String> selectedSpacesInCommunity = communityAndSpaces[event.communityModel.uuid] ?? [];
communityAndSpaces[event.communityModel.uuid] ?? [];
List<String> childrenIds = _getAllChildIds(event.children); List<String> childrenIds = _getAllChildIds(event.children);
bool isChildSelected = false; bool isChildSelected = false;
@@ -199,11 +215,9 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
selectedSpacesInCommunity.addAll(childrenIds); selectedSpacesInCommunity.addAll(childrenIds);
} }
List<String> spaces = List<String> spaces = _getThePathToChild(event.communityModel.uuid, event.spaceId);
_getThePathToChild(event.communityModel.uuid, event.spaceId);
for (String space in spaces) { for (String space in spaces) {
if (!updatedSelectedSpaces.contains(space) && if (!updatedSelectedSpaces.contains(space) && !updatedSoldChecks.contains(space)) {
!updatedSoldChecks.contains(space)) {
updatedSoldChecks.add(space); updatedSoldChecks.add(space);
} }
} }
@@ -226,9 +240,7 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
updatedSoldChecks.remove(event.spaceId); updatedSoldChecks.remove(event.spaceId);
List<String> parents = List<String> parents =
_getThePathToChild(event.communityModel.uuid, event.spaceId) _getThePathToChild(event.communityModel.uuid, event.spaceId).toSet().toList();
.toSet()
.toList();
if (updatedSelectedSpaces.isEmpty) { if (updatedSelectedSpaces.isEmpty) {
updatedSoldChecks.removeWhere(parents.contains); updatedSoldChecks.removeWhere(parents.contains);
@@ -236,8 +248,7 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
} else { } else {
// Check if any parent has selected children // Check if any parent has selected children
for (String space in parents) { for (String space in parents) {
if (!_noChildrenSelected( if (!_noChildrenSelected(event.communityModel, space, updatedSelectedSpaces, parents)) {
event.communityModel, space, updatedSelectedSpaces, parents)) {
updatedSoldChecks.remove(space); updatedSoldChecks.remove(space);
} }
} }
@@ -262,8 +273,8 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
} }
} }
_noChildrenSelected(CommunityModel community, String spaceId, _noChildrenSelected(
List<String> selectedSpaces, List<String> parents) { CommunityModel community, String spaceId, List<String> selectedSpaces, List<String> parents) {
if (selectedSpaces.contains(spaceId)) { if (selectedSpaces.contains(spaceId)) {
return true; return true;
} }
@@ -285,29 +296,52 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
_onSearch(SearchQueryEvent event, Emitter<SpaceTreeState> emit) async { _onSearch(SearchQueryEvent event, Emitter<SpaceTreeState> emit) async {
try { try {
List<CommunityModel> communities = List.from(state.communityList); const duration = Duration(seconds: 1);
List<CommunityModel> filteredCommunity = []; if (_timer.isActive) {
_timer.cancel(); // clear timer
}
_timer = Timer(duration, () async => add(DebouncedSearchEvent(event.searchQuery)));
// List<CommunityModel> communities = List.from(state.communityList);
// List<CommunityModel> filteredCommunity = [];
// Filter communities and expand only those that match the query // Filter communities and expand only those that match the query
filteredCommunity = communities.where((community) { // filteredCommunity = communities.where((community) {
final containsQueryInCommunity = community.name // final containsQueryInCommunity =
.toLowerCase() // community.name.toLowerCase().contains(event.searchQuery.toLowerCase());
.contains(event.searchQuery.toLowerCase()); // final containsQueryInSpaces =
final containsQueryInSpaces = community.spaces.any( // community.spaces.any((space) => _containsQuery(space, event.searchQuery.toLowerCase()));
(space) => _containsQuery(space, event.searchQuery.toLowerCase()));
return containsQueryInCommunity || containsQueryInSpaces; // return containsQueryInCommunity || containsQueryInSpaces;
}).toList(); // }).toList();
emit(state.copyWith( // emit(state.copyWith(
filteredCommunity: filteredCommunity, // filteredCommunity: filteredCommunity,
isSearching: event.searchQuery.isNotEmpty, // isSearching: event.searchQuery.isNotEmpty,
searchQuery: event.searchQuery)); // searchQuery: event.searchQuery));
} catch (e) { } catch (e) {
emit(const SpaceTreeErrorState('Something went wrong')); emit(const SpaceTreeErrorState('Something went wrong'));
} }
} }
_onDebouncedSearch(DebouncedSearchEvent event, Emitter<SpaceTreeState> emit) async {
emit(state.copyWith(
isSearching: true,
));
PaginationModel paginationModel = const PaginationModel.emptyConstructor();
try {
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
paginationModel = await CommunitySpaceManagementApi()
.fetchCommunitiesAndSpaces(projectId: projectUuid, page: 1, search: event.searchQuery);
} catch (_) {}
emit(state.copyWith(
filteredCommunity: paginationModel.communities,
isSearching: false,
searchQuery: event.searchQuery));
}
_clearAllData(ClearAllData event, Emitter<SpaceTreeState> emit) async { _clearAllData(ClearAllData event, Emitter<SpaceTreeState> emit) async {
try { try {
emit(state.copyWith( emit(state.copyWith(
@@ -345,13 +379,13 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
} }
// Helper function to determine if any space or its children match the search query // Helper function to determine if any space or its children match the search query
bool _containsQuery(SpaceModel space, String query) { // bool _containsQuery(SpaceModel space, String query) {
final matchesSpace = space.name.toLowerCase().contains(query); // final matchesSpace = space.name.toLowerCase().contains(query);
final matchesChildren = space.children.any((child) => // final matchesChildren =
_containsQuery(child, query)); // Recursive check for children // space.children.any((child) => _containsQuery(child, query)); // Recursive check for children
return matchesSpace || matchesChildren; // return matchesSpace || matchesChildren;
} // }
List<String> _getAllChildIds(List<SpaceModel> spaces) { List<String> _getAllChildIds(List<SpaceModel> spaces) {
List<String> ids = []; List<String> ids = [];
@@ -371,8 +405,8 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
return children; return children;
} }
bool _anySpacesSelectedInCommunity(CommunityModel community, bool _anySpacesSelectedInCommunity(
List<String> selectedSpaces, List<String> partialCheckedList) { CommunityModel community, List<String> selectedSpaces, List<String> partialCheckedList) {
bool result = false; bool result = false;
List<String> ids = _getAllChildIds(community.spaces); List<String> ids = _getAllChildIds(community.spaces);
for (var id in ids) { for (var id in ids) {
@@ -401,8 +435,7 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
return ids; return ids;
} }
List<String> _getAllParentsIds( List<String> _getAllParentsIds(SpaceModel child, String spaceId, List<String> listIds) {
SpaceModel child, String spaceId, List<String> listIds) {
List<String> ids = listIds; List<String> ids = listIds;
ids.add(child.uuid ?? ''); ids.add(child.uuid ?? '');
@@ -426,6 +459,7 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
@override @override
Future<void> close() async { Future<void> close() async {
_timer.cancel();
super.close(); super.close();
} }
} }
@@ -1,4 +1,5 @@
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/space_tree/model/pagination_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/community_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
@@ -11,6 +12,16 @@ class SpaceTreeEvent extends Equatable {
class InitialEvent extends SpaceTreeEvent {} class InitialEvent extends SpaceTreeEvent {}
class PaginationEvent extends SpaceTreeEvent {
final PaginationModel paginationModel;
final List<CommunityModel> communities;
const PaginationEvent(this.paginationModel, this.communities);
@override
List<Object> get props => [paginationModel, communities];
}
class SearchForSpace extends SpaceTreeEvent { class SearchForSpace extends SpaceTreeEvent {
final String searchQuery; final String searchQuery;
@@ -69,6 +80,15 @@ class SearchQueryEvent extends SpaceTreeEvent {
List<Object> get props => [searchQuery]; List<Object> get props => [searchQuery];
} }
class DebouncedSearchEvent extends SpaceTreeEvent {
final String searchQuery;
const DebouncedSearchEvent(this.searchQuery);
@override
List<Object> get props => [searchQuery];
}
class OnCommunityAdded extends SpaceTreeEvent { class OnCommunityAdded extends SpaceTreeEvent {
final CommunityModel newCommunity; final CommunityModel newCommunity;
const OnCommunityAdded(this.newCommunity); const OnCommunityAdded(this.newCommunity);
@@ -85,7 +105,6 @@ class OnCommunityUpdated extends SpaceTreeEvent {
List<Object> get props => [updatedCommunity]; List<Object> get props => [updatedCommunity];
} }
class ClearAllData extends SpaceTreeEvent {} class ClearAllData extends SpaceTreeEvent {}
class ClearCachedData extends SpaceTreeEvent {} class ClearCachedData extends SpaceTreeEvent {}
@@ -1,4 +1,5 @@
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/space_tree/model/pagination_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/community_model.dart';
class SpaceTreeState extends Equatable { class SpaceTreeState extends Equatable {
@@ -12,6 +13,8 @@ class SpaceTreeState extends Equatable {
final List<String> soldCheck; final List<String> soldCheck;
final bool isSearching; final bool isSearching;
final String searchQuery; final String searchQuery;
final PaginationModel paginationModel;
final bool paginationIsLoading;
const SpaceTreeState( const SpaceTreeState(
{this.communityList = const [], {this.communityList = const [],
@@ -23,7 +26,9 @@ class SpaceTreeState extends Equatable {
this.soldCheck = const [], this.soldCheck = const [],
this.isSearching = false, this.isSearching = false,
this.selectedCommunityAndSpaces = const {}, this.selectedCommunityAndSpaces = const {},
this.searchQuery = ''}); this.searchQuery = '',
this.paginationModel = const PaginationModel.emptyConstructor(),
this.paginationIsLoading = false});
SpaceTreeState copyWith( SpaceTreeState copyWith(
{List<CommunityModel>? communitiesList, {List<CommunityModel>? communitiesList,
@@ -35,7 +40,9 @@ class SpaceTreeState extends Equatable {
List<String>? soldCheck, List<String>? soldCheck,
bool? isSearching, bool? isSearching,
Map<String, List<String>>? selectedCommunityAndSpaces, Map<String, List<String>>? selectedCommunityAndSpaces,
String? searchQuery}) { String? searchQuery,
PaginationModel? paginationModel,
bool? paginationIsLoading}) {
return SpaceTreeState( return SpaceTreeState(
communityList: communitiesList ?? this.communityList, communityList: communitiesList ?? this.communityList,
filteredCommunity: filteredCommunity ?? this.filteredCommunity, filteredCommunity: filteredCommunity ?? this.filteredCommunity,
@@ -46,7 +53,9 @@ class SpaceTreeState extends Equatable {
soldCheck: soldCheck ?? this.soldCheck, soldCheck: soldCheck ?? this.soldCheck,
isSearching: isSearching ?? this.isSearching, isSearching: isSearching ?? this.isSearching,
selectedCommunityAndSpaces: selectedCommunityAndSpaces ?? this.selectedCommunityAndSpaces, selectedCommunityAndSpaces: selectedCommunityAndSpaces ?? this.selectedCommunityAndSpaces,
searchQuery: searchQuery ?? this.searchQuery); searchQuery: searchQuery ?? this.searchQuery,
paginationModel: paginationModel ?? this.paginationModel,
paginationIsLoading: paginationIsLoading ?? this.paginationIsLoading);
} }
@override @override
@@ -60,7 +69,9 @@ class SpaceTreeState extends Equatable {
soldCheck, soldCheck,
isSearching, isSearching,
selectedCommunityAndSpaces, selectedCommunityAndSpaces,
searchQuery searchQuery,
paginationModel,
paginationIsLoading
]; ];
} }
@@ -0,0 +1,20 @@
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart';
class PaginationModel {
final int pageNum;
final bool hasNext;
final int size;
final List<CommunityModel> communities;
const PaginationModel(
{required this.pageNum,
required this.hasNext,
required this.size,
required this.communities});
const PaginationModel.emptyConstructor()
: pageNum = 1,
hasNext = true,
size = 10,
communities = const [];
}
+97 -82
View File
@@ -34,7 +34,8 @@ 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) {
List<CommunityModel> list = state.isSearching ? state.filteredCommunity : state.communityList; List<CommunityModel> list =
state.searchQuery.isNotEmpty ? 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
@@ -61,7 +62,8 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
borderRadius: const BorderRadius.all(Radius.circular(20)), borderRadius: const BorderRadius.all(Radius.circular(20)),
border: Border.all(color: ColorsManager.grayBorder)), border: Border.all(color: ColorsManager.grayBorder)),
child: TextFormField( child: TextFormField(
style: const TextStyle(color: Colors.black), style: context.textTheme.bodyMedium
?.copyWith(color: ColorsManager.blackColor),
onChanged: (value) { onChanged: (value) {
context.read<SpaceTreeBloc>().add(SearchQueryEvent(value)); context.read<SpaceTreeBloc>().add(SearchQueryEvent(value));
}, },
@@ -94,88 +96,101 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
Expanded( Expanded(
child: ListView( child: state.isSearching
shrinkWrap: true, ? const Center(child: CircularProgressIndicator())
scrollDirection: Axis.horizontal, : ListView(
children: [ shrinkWrap: true,
Container( scrollDirection: Axis.horizontal,
width: MediaQuery.sizeOf(context).width * 0.5, children: [
padding: const EdgeInsets.all(8.0), Container(
child: list.isEmpty width: MediaQuery.sizeOf(context).width * 0.5,
? Center( padding: const EdgeInsets.all(8.0),
child: Text( child: list.isEmpty
'No results found', ? Center(
style: Theme.of(context).textTheme.bodySmall!.copyWith( child: Text(
color: ColorsManager.lightGrayColor, 'No results found',
fontWeight: FontWeight.w400, style: Theme.of(context).textTheme.bodySmall!.copyWith(
color: ColorsManager.lightGrayColor,
fontWeight: FontWeight.w400,
),
), ),
), )
) : Scrollbar(
: Scrollbar( scrollbarOrientation: ScrollbarOrientation.left,
scrollbarOrientation: ScrollbarOrientation.left, thumbVisibility: true,
thumbVisibility: true, controller: _scrollController,
controller: _scrollController, child: NotificationListener(
child: Padding( onNotification: (notification) {
padding: const EdgeInsets.only(left: 16), if (notification is ScrollEndNotification &&
child: ListView( notification.metrics.extentAfter == 0) {
controller: _scrollController, // If the user has reached the end of the list Load more data
shrinkWrap: true, context.read<SpaceTreeBloc>().add(PaginationEvent(
children: list state.paginationModel, state.communityList));
.map( }
(community) => CustomExpansionTileSpaceTree( return false;
title: community.name, },
isSelected: state.selectedCommunities child: Padding(
.contains(community.uuid), padding: const EdgeInsets.only(left: 16),
isSoldCheck: state.selectedCommunities child: ListView.builder(
.contains(community.uuid), shrinkWrap: true,
onExpansionChanged: () { itemCount: list.length,
context controller: _scrollController,
.read<SpaceTreeBloc>() itemBuilder: (context, index) {
.add(OnCommunityExpanded(community.uuid)); return CustomExpansionTileSpaceTree(
}, title: list[index].name,
isExpanded: state.expandedCommunities isSelected: state.selectedCommunities
.contains(community.uuid), .contains(list[index].uuid),
onItemSelected: () { isSoldCheck: state.selectedCommunities
context.read<SpaceTreeBloc>().add( .contains(list[index].uuid),
OnCommunitySelected( onExpansionChanged: () {
community.uuid, community.spaces)); context.read<SpaceTreeBloc>().add(
widget.onSelect(); OnCommunityExpanded(list[index].uuid));
}, },
children: community.spaces.map((space) { isExpanded: state.expandedCommunities
return CustomExpansionTileSpaceTree( .contains(list[index].uuid),
title: space.name, onItemSelected: () {
isExpanded: context.read<SpaceTreeBloc>().add(
state.expandedSpaces.contains(space.uuid), OnCommunitySelected(list[index].uuid,
onItemSelected: () { list[index].spaces));
context.read<SpaceTreeBloc>().add( widget.onSelect();
OnSpaceSelected(community, space.uuid ?? '', },
space.children)); children: list[index].spaces.map((space) {
widget.onSelect(); return CustomExpansionTileSpaceTree(
}, title: space.name,
onExpansionChanged: () { isExpanded: state.expandedSpaces
context.read<SpaceTreeBloc>().add( .contains(space.uuid),
OnSpaceExpanded( onItemSelected: () {
community.uuid, space.uuid ?? '')); context.read<SpaceTreeBloc>().add(
}, OnSpaceSelected(
isSelected: list[index],
state.selectedSpaces.contains(space.uuid) || space.uuid ?? '',
state.soldCheck.contains(space.uuid), space.children));
isSoldCheck: state.soldCheck.contains(space.uuid), widget.onSelect();
children: _buildNestedSpaces( },
context, state, space, community), onExpansionChanged: () {
); context.read<SpaceTreeBloc>().add(
}).toList(), OnSpaceExpanded(list[index].uuid,
), space.uuid ?? ''));
) },
.toList(), isSelected: state.selectedSpaces
), .contains(space.uuid) ||
), state.soldCheck.contains(space.uuid),
), isSoldCheck:
), state.soldCheck.contains(space.uuid),
], children: _buildNestedSpaces(
), context, state, space, list[index]),
);
}).toList(),
);
}),
),
),
),
),
],
),
), ),
if (state.paginationIsLoading) const CircularProgressIndicator(),
// Expanded( // Expanded(
// child: Padding( // child: Padding(
// padding: const EdgeInsets.all(8.0), // padding: const EdgeInsets.all(8.0),
@@ -22,11 +22,11 @@ class CommunityModel {
factory CommunityModel.fromJson(Map<String, dynamic> json) { factory CommunityModel.fromJson(Map<String, dynamic> json) {
return CommunityModel( return CommunityModel(
uuid: json['uuid'], uuid: json['uuid'] ?? '',
createdAt: DateTime.parse(json['createdAt']), createdAt: DateTime.parse(json['createdAt'] ?? ''),
updatedAt: DateTime.parse(json['updatedAt']), updatedAt: DateTime.parse(json['updatedAt'] ?? ''),
name: json['name'], name: json['name'] ?? '',
description: json['description'], description: json['description'] ?? '',
region: json['region'] != null ? RegionModel.fromJson(json['region']) : null, region: json['region'] != null ? RegionModel.fromJson(json['region']) : null,
spaces: json['spaces'] != null spaces: json['spaces'] != null
? (json['spaces'] as List).map((space) => SpaceModel.fromJson(space)).toList() ? (json['spaces'] as List).map((space) => SpaceModel.fromJson(space)).toList()
@@ -301,11 +301,18 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
SpaceTemplateModel? spaceModel, List<SubspaceModel>? subspaces, List<Tag>? tags) { SpaceTemplateModel? spaceModel, List<SubspaceModel>? subspaces, List<Tag>? tags) {
setState(() { setState(() {
// Set the first space in the center or use passed position // Set the first space in the center or use passed position
Offset centerPosition = position ?? ConnectionHelper.getCenterPosition(screenSize); Offset newPosition;
if (parentIndex != null) {
newPosition =
getBalancedChildPosition(spaces[parentIndex]); // Ensure balanced position
} else {
newPosition = position ?? ConnectionHelper.getCenterPosition(screenSize);
}
SpaceModel newSpace = SpaceModel( SpaceModel newSpace = SpaceModel(
name: name, name: name,
icon: icon, icon: icon,
position: centerPosition, position: newPosition,
isPrivate: false, isPrivate: false,
children: [], children: [],
status: SpaceStatus.newSpace, status: SpaceStatus.newSpace,
@@ -425,7 +432,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
Connection( Connection(
startSpace: parent, startSpace: parent,
endSpace: child, endSpace: child,
direction: child.incomingConnection?.direction ?? "down", direction: "down",
), ),
); );
@@ -522,6 +529,38 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
); );
} }
Offset getBalancedChildPosition(SpaceModel parent) {
int totalSiblings = parent.children.length + 1;
double totalWidth = (totalSiblings - 1) * 250; // Horizontal spacing
double startX = parent.position.dx - (totalWidth / 2);
Offset position = Offset(startX + (parent.children.length * 250), parent.position.dy + 180);
// Check for overlaps & adjust
while (spaces.any((s) => (s.position - position).distance < 250)) {
position = Offset(position.dx + 250, position.dy);
}
return position;
}
void realignTree() {
void updatePositions(SpaceModel node, double x, double y) {
node.position = Offset(x, y);
int numChildren = node.children.length;
double childStartX = x - ((numChildren - 1) * 250) / 2;
for (int i = 0; i < numChildren; i++) {
updatePositions(node.children[i], childStartX + (i * 250), y + 180);
}
}
if (spaces.isNotEmpty) {
updatePositions(spaces.first, spaces.first.position.dx, spaces.first.position.dy);
}
}
void _onDuplicate(BuildContext parentContext) { void _onDuplicate(BuildContext parentContext) {
final screenWidth = MediaQuery.of(context).size.width; final screenWidth = MediaQuery.of(context).size.width;
@@ -604,29 +643,57 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
void _duplicateSpace(SpaceModel space) { void _duplicateSpace(SpaceModel space) {
final Map<SpaceModel, SpaceModel> originalToDuplicate = {}; final Map<SpaceModel, SpaceModel> originalToDuplicate = {};
const double horizontalGap = 200.0; double horizontalGap = 250.0; // Increased spacing
const double verticalGap = 100.0; double verticalGap = 180.0; // Adjusted for better visualization
SpaceModel duplicateRecursive( print("🟢 Duplicating: ${space.name}");
SpaceModel original, Offset parentPosition, SpaceModel? duplicatedParent) {
Offset newPosition = Offset(parentPosition.dx + horizontalGap, original.position.dy);
while (spaces.any((s) => /// **Find a new position ensuring no overlap**
(s.position - newPosition).distance < horizontalGap && s.status != SpaceStatus.deleted)) { Offset getBalancedChildPosition(SpaceModel parent) {
newPosition += Offset(horizontalGap, 0); int totalSiblings = parent.children.length + 1;
double totalWidth = (totalSiblings - 1) * horizontalGap;
double startX = parent.position.dx - (totalWidth / 2);
Offset position = Offset(
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) {
print("🔄 Realigning tree...");
updatePositions(spaces.first, spaces.first.position.dx, spaces.first.position.dy);
}
}
/// **Recursive duplication logic**
SpaceModel duplicateRecursive(SpaceModel original, SpaceModel? duplicatedParent) {
Offset newPosition = duplicatedParent == null
? Offset(original.position.dx + horizontalGap, original.position.dy)
: getBalancedChildPosition(duplicatedParent);
final duplicatedName = SpaceHelper.generateUniqueSpaceName(original.name, spaces); final duplicatedName = SpaceHelper.generateUniqueSpaceName(original.name, spaces);
print(
final List<SubspaceModel>? duplicatedSubspaces; "🟡 Duplicating ${original.name} → ${duplicatedName} at (${newPosition.dx}, ${newPosition.dy})");
final List<Tag>? duplicatedTags;
if (original.spaceModel != null) {
duplicatedTags = [];
duplicatedSubspaces = [];
} else {
duplicatedTags = original.tags;
duplicatedSubspaces = original.subspaces;
}
final duplicated = SpaceModel( final duplicated = SpaceModel(
name: duplicatedName, name: duplicatedName,
@@ -637,12 +704,10 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
status: SpaceStatus.newSpace, status: SpaceStatus.newSpace,
parent: duplicatedParent, parent: duplicatedParent,
spaceModel: original.spaceModel, spaceModel: original.spaceModel,
subspaces: duplicatedSubspaces, subspaces: original.subspaces,
tags: duplicatedTags, tags: original.tags,
); );
originalToDuplicate[original] = duplicated;
setState(() { setState(() {
spaces.add(duplicated); spaces.add(duplicated);
_updateNodePosition(duplicated, duplicated.position); _updateNodePosition(duplicated, duplicated.position);
@@ -651,60 +716,42 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
final newConnection = Connection( final newConnection = Connection(
startSpace: duplicatedParent, startSpace: duplicatedParent,
endSpace: duplicated, endSpace: duplicated,
direction: original.incomingConnection?.direction ?? 'down', direction: "down",
); );
connections.add(newConnection); connections.add(newConnection);
duplicated.incomingConnection = newConnection; duplicated.incomingConnection = newConnection;
duplicatedParent.addOutgoingConnection(newConnection); duplicatedParent.addOutgoingConnection(newConnection);
duplicatedParent.children.add(duplicated);
print("🔗 Created connection: ${duplicatedParent.name} → ${duplicated.name}");
} }
if (original.parent != null && duplicatedParent == null) { // **Recalculate the whole tree to avoid overlaps**
final originalParent = original.parent!; realignTree();
final duplicatedParent = originalToDuplicate[originalParent] ?? originalParent;
final parentConnection = Connection(
startSpace: duplicatedParent,
endSpace: duplicated,
direction: original.incomingConnection?.direction ?? "down",
);
connections.add(parentConnection);
duplicated.incomingConnection = parentConnection;
duplicatedParent.addOutgoingConnection(parentConnection);
}
}); });
final childrenWithDownDirection = original.children // Recursively duplicate children
.where((child) => for (var child in original.children) {
child.incomingConnection?.direction == "down" && child.status != SpaceStatus.deleted) duplicateRecursive(child, duplicated);
.toList();
Offset childStartPosition = childrenWithDownDirection.length == 1
? duplicated.position
: newPosition + Offset(0, verticalGap);
for (final child in original.children) {
final isDownDirection = child.incomingConnection?.direction == "down" ?? false;
if (isDownDirection && childrenWithDownDirection.length == 1) {
childStartPosition = duplicated.position + Offset(0, verticalGap);
} else if (!isDownDirection) {
childStartPosition = duplicated.position + Offset(horizontalGap, 0);
}
final duplicatedChild = duplicateRecursive(child, childStartPosition, duplicated);
duplicated.children.add(duplicatedChild);
childStartPosition += Offset(0, verticalGap);
} }
return duplicated; return duplicated;
} }
/// **Handle root duplication**
if (space.parent == null) { if (space.parent == null) {
duplicateRecursive(space, space.position, null); print("🟠 Duplicating root node: ${space.name}");
SpaceModel duplicatedRoot = duplicateRecursive(space, null);
setState(() {
spaces.add(duplicatedRoot);
realignTree();
});
print("✅ Root duplication successful: ${duplicatedRoot.name}");
} else { } else {
final duplicatedParent = originalToDuplicate[space.parent!] ?? space.parent!; duplicateRecursive(space, space.parent);
duplicateRecursive(space, space.position, duplicatedParent);
} }
print("🟢 Finished duplication process for: ${space.name}");
} }
} }
@@ -47,18 +47,6 @@ class SpaceCardWidget extends StatelessWidget {
children: [ children: [
buildSpaceContainer(index), // Build the space container buildSpaceContainer(index), // Build the space container
if (isHovered) ...[ if (isHovered) ...[
PlusButtonWidget(
index: index,
direction: 'left',
offset: const Offset(-21, 20),
onButtonTap: onButtonTap,
),
PlusButtonWidget(
index: index,
direction: 'right',
offset: const Offset(140, 20),
onButtonTap: onButtonTap,
),
PlusButtonWidget( PlusButtonWidget(
index: index, index: index,
direction: 'down', direction: 'down',
@@ -2,8 +2,7 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_m
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
class SpaceHelper { class SpaceHelper {
static SpaceModel? findSpaceByUuid( static SpaceModel? findSpaceByUuid(String? uuid, List<CommunityModel> communities) {
String? uuid, List<CommunityModel> communities) {
for (var community in communities) { for (var community in communities) {
for (var space in community.spaces) { for (var space in community.spaces) {
if (space.uuid == uuid) return space; if (space.uuid == uuid) return space;
@@ -12,8 +11,7 @@ class SpaceHelper {
return null; return null;
} }
static SpaceModel? findSpaceByInternalId( static SpaceModel? findSpaceByInternalId(String? internalId, List<SpaceModel> spaces) {
String? internalId, List<SpaceModel> spaces) {
if (internalId != null) { if (internalId != null) {
for (var space in spaces) { for (var space in spaces) {
if (space.internalId == internalId) return space; if (space.internalId == internalId) return space;
@@ -23,8 +21,7 @@ class SpaceHelper {
return null; return null;
} }
static String generateUniqueSpaceName( static String generateUniqueSpaceName(String originalName, List<SpaceModel> spaces) {
String originalName, List<SpaceModel> spaces) {
final baseName = originalName.replaceAll(RegExp(r'\(\d+\)$'), '').trim(); final baseName = originalName.replaceAll(RegExp(r'\(\d+\)$'), '').trim();
int maxNumber = 0; int maxNumber = 0;
@@ -54,13 +51,10 @@ class SpaceHelper {
return space == selectedSpace || return space == selectedSpace ||
selectedSpace.parent?.internalId == space.internalId || selectedSpace.parent?.internalId == space.internalId ||
selectedSpace.children selectedSpace.children?.any((child) => child.internalId == space.internalId) == true;
?.any((child) => child.internalId == space.internalId) ==
true;
} }
static bool isNameConflict( static bool isNameConflict(String value, SpaceModel? parentSpace, SpaceModel? editSpace) {
String value, SpaceModel? parentSpace, SpaceModel? editSpace) {
final siblings = parentSpace?.children final siblings = parentSpace?.children
.where((child) => child.internalId != editSpace?.internalId) .where((child) => child.internalId != editSpace?.internalId)
.toList() ?? .toList() ??
@@ -71,19 +65,17 @@ class SpaceHelper {
.toList() ?? .toList() ??
[]; [];
final editSiblingConflict = final editSiblingConflict = editSiblings.any((child) => child.name == value);
editSiblings.any((child) => child.name == value);
final siblingConflict = siblings.any((child) => child.name == value); final siblingConflict = siblings.any((child) => child.name == value);
final parentConflict = parentSpace?.name == value && final parentConflict =
parentSpace?.internalId != editSpace?.internalId; parentSpace?.name == value && parentSpace?.internalId != editSpace?.internalId;
final parentOfEditSpaceConflict = editSpace?.parent?.name == value && final parentOfEditSpaceConflict =
editSpace?.parent?.internalId != editSpace?.internalId; editSpace?.parent?.name == value && editSpace?.parent?.internalId != editSpace?.internalId;
final childConflict = final childConflict = editSpace?.children.any((child) => child.name == value) ?? false;
editSpace?.children.any((child) => child.name == value) ?? false;
return siblingConflict || return siblingConflict ||
parentConflict || parentConflict ||
@@ -194,9 +194,10 @@ class VisitorPasswordBloc
emit(DeviceLoaded()); emit(DeviceLoaded());
final projectUuid = await ProjectManager.getProjectUUID() ?? ''; final projectUuid = await ProjectManager.getProjectUUID() ?? '';
data = await AccessMangApi().fetchDevices(projectUuid); data = await AccessMangApi().fetchDoorLockDeviceList(projectUuid);
emit(TableLoaded(data)); emit(TableLoaded(data));
} catch (e) { } catch (e) {
print("error: $e");
emit(FailedState(e.toString())); emit(FailedState(e.toString()));
} }
} }
+31 -13
View File
@@ -6,13 +6,23 @@ import 'package:syncrow_web/services/api/http_service.dart';
import 'package:syncrow_web/utils/constants/api_const.dart'; import 'package:syncrow_web/utils/constants/api_const.dart';
class AccessMangApi { class AccessMangApi {
AccessMangApi() {
_validateEndpoints();
}
void _validateEndpoints() {
if (!ApiEndpoints.getDevices.contains('{projectId}')) {
throw Exception("Endpoint 'getDevices' must contain '{projectId}' placeholder.");
}
}
Future<List<PasswordModel>> fetchVisitorPassword(String projectId) async { Future<List<PasswordModel>> fetchVisitorPassword(String projectId) async {
try { try {
final response = await HTTPService().get( final response = await HTTPService().get(
path: ApiEndpoints.visitorPassword.replaceAll('{projectId}', projectId), path: ApiEndpoints.visitorPassword,
showServerMessage: true, showServerMessage: true,
expectedResponseModel: (json) { expectedResponseModel: (json) {
List<dynamic> jsonData = json; List<dynamic> jsonData = json['data'] ?? [];
List<PasswordModel> passwordList = jsonData.map((jsonItem) { List<PasswordModel> passwordList = jsonData.map((jsonItem) {
return PasswordModel.fromJson(jsonItem); return PasswordModel.fromJson(jsonItem);
}).toList(); }).toList();
@@ -25,17 +35,22 @@ class AccessMangApi {
} }
} }
Future fetchDevices(String projectId) async { Future fetchDoorLockDeviceList(String projectId) async {
try { try {
// The endpoint structure is already validated during initialization.
final response = await HTTPService().get( final response = await HTTPService().get(
path: ApiEndpoints.getDevices.replaceAll('{projectId}', projectId), path: ApiEndpoints.getDevices.replaceAll('{projectId}', projectId),
queryParameters: {
'deviceType': 'DOOR_LOCK',
},
showServerMessage: true, showServerMessage: true,
expectedResponseModel: (json) { expectedResponseModel: (json) {
List<dynamic> jsonData = json; List<dynamic> jsonData = json['data'] ?? [];
List<DeviceModel> passwordList = jsonData.map((jsonItem) { List<DeviceModel> deviceList = jsonData.map((jsonItem) {
return DeviceModel.fromJson(jsonItem); return DeviceModel.fromJson(jsonItem);
}).toList(); }).toList();
return passwordList; return deviceList;
}, },
); );
return response; return response;
@@ -52,14 +67,15 @@ class AccessMangApi {
String? invalidTime, String? invalidTime,
List<String>? devicesUuid}) async { List<String>? devicesUuid}) async {
final response = await HTTPService().post( final response = await HTTPService().post(
path: ApiEndpoints.sendOnlineOneTime, path: ApiEndpoints.visitorPassword,
body: jsonEncode({ body: jsonEncode({
"email": email, "email": email,
"passwordName": passwordName, "passwordName": passwordName,
"password": password, "password": password,
"devicesUuid": devicesUuid, "devicesUuid": devicesUuid,
"effectiveTime": effectiveTime, "effectiveTime": effectiveTime,
"invalidTime": invalidTime "invalidTime": invalidTime,
"operationType": "ONLINE_ONE_TIME",
}), }),
showServerMessage: true, showServerMessage: true,
expectedResponseModel: (json) { expectedResponseModel: (json) {
@@ -84,13 +100,13 @@ class AccessMangApi {
"password": password, "password": password,
"effectiveTime": effectiveTime, "effectiveTime": effectiveTime,
"invalidTime": invalidTime, "invalidTime": invalidTime,
"operationType": "ONLINE_MULTIPLE_TIME",
}; };
if (scheduleList != null) { if (scheduleList != null) {
body["scheduleList"] = body["scheduleList"] = scheduleList.map((schedule) => schedule.toJson()).toList();
scheduleList.map((schedule) => schedule.toJson()).toList();
} }
final response = await HTTPService().post( final response = await HTTPService().post(
path: ApiEndpoints.sendOnlineMultipleTime, path: ApiEndpoints.visitorPassword,
body: jsonEncode(body), body: jsonEncode(body),
showServerMessage: true, showServerMessage: true,
expectedResponseModel: (json) { expectedResponseModel: (json) {
@@ -105,8 +121,9 @@ class AccessMangApi {
Future postOffLineOneTime( Future postOffLineOneTime(
{String? email, String? passwordName, List<String>? devicesUuid}) async { {String? email, String? passwordName, List<String>? devicesUuid}) async {
final response = await HTTPService().post( final response = await HTTPService().post(
path: ApiEndpoints.sendOffLineOneTime, path: ApiEndpoints.visitorPassword,
body: jsonEncode({ body: jsonEncode({
"operationType": "OFFLINE_ONE_TIME",
"email": email, "email": email,
"passwordName": passwordName, "passwordName": passwordName,
"devicesUuid": devicesUuid "devicesUuid": devicesUuid
@@ -126,13 +143,14 @@ class AccessMangApi {
String? invalidTime, String? invalidTime,
List<String>? devicesUuid}) async { List<String>? devicesUuid}) async {
final response = await HTTPService().post( final response = await HTTPService().post(
path: ApiEndpoints.sendOffLineMultipleTime, path: ApiEndpoints.visitorPassword,
body: jsonEncode({ body: jsonEncode({
"email": email, "email": email,
"devicesUuid": devicesUuid, "devicesUuid": devicesUuid,
"passwordName": passwordName, "passwordName": passwordName,
"effectiveTime": effectiveTime, "effectiveTime": effectiveTime,
"invalidTime": invalidTime, "invalidTime": invalidTime,
"operationType": "OFFLINE_MULTIPLE_TIME",
}), }),
showServerMessage: true, showServerMessage: true,
expectedResponseModel: (json) { expectedResponseModel: (json) {
+17 -27
View File
@@ -23,9 +23,7 @@ class DevicesManagementApi {
: ApiEndpoints.getAllDevices.replaceAll('{projectId}', projectId), : ApiEndpoints.getAllDevices.replaceAll('{projectId}', projectId),
showServerMessage: true, showServerMessage: true,
expectedResponseModel: (json) { expectedResponseModel: (json) {
List<dynamic> jsonData = communityId.isNotEmpty && spaceId.isNotEmpty List<dynamic> jsonData = json['data'];
? json['data']
: json;
List<AllDevicesModel> devicesList = jsonData.map((jsonItem) { List<AllDevicesModel> devicesList = jsonData.map((jsonItem) {
return AllDevicesModel.fromJson(jsonItem); return AllDevicesModel.fromJson(jsonItem);
}).toList(); }).toList();
@@ -34,7 +32,7 @@ class DevicesManagementApi {
); );
return response; return response;
} catch (e) { } catch (e) {
debugPrint('fetchDevices Error fetching $e'); debugPrint('Error fetching device $e');
return []; return [];
} }
} }
@@ -45,7 +43,7 @@ class DevicesManagementApi {
path: ApiEndpoints.getDeviceStatus.replaceAll('{uuid}', uuid), path: ApiEndpoints.getDeviceStatus.replaceAll('{uuid}', uuid),
showServerMessage: true, showServerMessage: true,
expectedResponseModel: (json) { expectedResponseModel: (json) {
return DeviceStatus.fromJson(json); return DeviceStatus.fromJson(json['data']);
}, },
); );
return response; return response;
@@ -62,7 +60,7 @@ class DevicesManagementApi {
Future getPowerClampInfo(String deviceId) async { Future getPowerClampInfo(String deviceId) async {
try { try {
final response = await HTTPService().get( final response = await HTTPService().get(
path: ApiEndpoints.powerClamp.replaceAll('{powerClampUuid}', deviceId), path: ApiEndpoints.getDeviceStatus.replaceAll('{uuid}', deviceId),
showServerMessage: true, showServerMessage: true,
expectedResponseModel: (json) { expectedResponseModel: (json) {
return json; return json;
@@ -93,13 +91,13 @@ class DevicesManagementApi {
} }
} }
Future<bool> deviceBatchControl( Future<bool> deviceBatchControl(List<String> uuids, String code, dynamic value) async {
List<String> uuids, String code, dynamic value) async {
try { try {
final body = { final body = {
'devicesUuid': uuids, 'devicesUuid': uuids,
'code': code, 'code': code,
'value': value, 'value': value,
'operationType': 'COMMAND',
}; };
final response = await HTTPService().post( final response = await HTTPService().post(
@@ -107,7 +105,7 @@ class DevicesManagementApi {
body: body, body: body,
showServerMessage: true, showServerMessage: true,
expectedResponseModel: (json) { expectedResponseModel: (json) {
return (json['successResults'] as List).isNotEmpty; return json['success'] ?? false;
}, },
); );
@@ -118,8 +116,7 @@ class DevicesManagementApi {
} }
} }
static Future<List<DeviceModel>> getDevicesByGatewayId( static Future<List<DeviceModel>> getDevicesByGatewayId(String gatewayId) async {
String gatewayId) async {
final response = await HTTPService().get( final response = await HTTPService().get(
path: ApiEndpoints.gatewayApi.replaceAll('{gatewayUuid}', gatewayId), path: ApiEndpoints.gatewayApi.replaceAll('{gatewayUuid}', gatewayId),
showServerMessage: false, showServerMessage: false,
@@ -128,7 +125,7 @@ class DevicesManagementApi {
if (json == null || json.isEmpty || json == []) { if (json == null || json.isEmpty || json == []) {
return devices; return devices;
} }
for (var device in json['devices']) { for (var device in json['data']['devices']) {
devices.add(DeviceModel.fromJson(device)); devices.add(DeviceModel.fromJson(device));
} }
return devices; return devices;
@@ -153,12 +150,10 @@ class DevicesManagementApi {
String code, String code,
) async { ) async {
final response = await HTTPService().get( final response = await HTTPService().get(
path: ApiEndpoints.getDeviceLogs path: ApiEndpoints.getDeviceLogs.replaceAll('{uuid}', uuid).replaceAll('{code}', code),
.replaceAll('{uuid}', uuid)
.replaceAll('{code}', code),
showServerMessage: false, showServerMessage: false,
expectedResponseModel: (json) { expectedResponseModel: (json) {
return DeviceReport.fromJson(json); return DeviceReport.fromJson(json['data']);
}, },
); );
return response; return response;
@@ -174,7 +169,7 @@ class DevicesManagementApi {
.replaceAll('{endTime}', to ?? ''), .replaceAll('{endTime}', to ?? ''),
showServerMessage: false, showServerMessage: false,
expectedResponseModel: (json) { expectedResponseModel: (json) {
return DeviceReport.fromJson(json); return DeviceReport.fromJson(json['data']);
}, },
); );
return response; return response;
@@ -190,7 +185,7 @@ class DevicesManagementApi {
queryParameters: queryParameters, queryParameters: queryParameters,
showServerMessage: true, showServerMessage: true,
expectedResponseModel: (json) { expectedResponseModel: (json) {
return DeviceStatus.fromJson(json['status']); return DeviceStatus.fromJson(json['data']['status']);
}, },
); );
return response; return response;
@@ -228,8 +223,7 @@ class DevicesManagementApi {
} }
} }
Future<bool> addScheduleRecord( Future<bool> addScheduleRecord(ScheduleEntry sendSchedule, String uuid) async {
ScheduleEntry sendSchedule, String uuid) async {
try { try {
final response = await HTTPService().post( final response = await HTTPService().post(
path: ApiEndpoints.scheduleByDeviceId.replaceAll('{deviceUuid}', uuid), path: ApiEndpoints.scheduleByDeviceId.replaceAll('{deviceUuid}', uuid),
@@ -246,8 +240,7 @@ class DevicesManagementApi {
} }
} }
Future<List<ScheduleModel>> getDeviceSchedules( Future<List<ScheduleModel>> getDeviceSchedules(String uuid, String category) async {
String uuid, String category) async {
try { try {
final response = await HTTPService().get( final response = await HTTPService().get(
path: ApiEndpoints.getScheduleByDeviceId path: ApiEndpoints.getScheduleByDeviceId
@@ -270,9 +263,7 @@ class DevicesManagementApi {
} }
Future<bool> updateScheduleRecord( Future<bool> updateScheduleRecord(
{required bool enable, {required bool enable, required String uuid, required String scheduleId}) async {
required String uuid,
required String scheduleId}) async {
try { try {
final response = await HTTPService().put( final response = await HTTPService().put(
path: ApiEndpoints.updateScheduleByDeviceId path: ApiEndpoints.updateScheduleByDeviceId
@@ -293,8 +284,7 @@ class DevicesManagementApi {
} }
} }
Future<bool> editScheduleRecord( Future<bool> editScheduleRecord(String uuid, ScheduleEntry newSchedule) async {
String uuid, ScheduleEntry newSchedule) async {
try { try {
final response = await HTTPService().put( final response = await HTTPService().put(
path: ApiEndpoints.scheduleByDeviceId.replaceAll('{deviceUuid}', uuid), path: ApiEndpoints.scheduleByDeviceId.replaceAll('{deviceUuid}', uuid),
+51 -29
View File
@@ -1,29 +1,28 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/space_tree/model/pagination_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/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/space_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_response_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_response_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/create_space_template_body_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_body_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_body_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_update_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_update_model.dart';
import 'package:syncrow_web/services/api/http_service.dart'; import 'package:syncrow_web/services/api/http_service.dart';
import 'package:syncrow_web/utils/constants/api_const.dart'; import 'package:syncrow_web/utils/constants/api_const.dart';
import 'package:syncrow_web/utils/constants/temp_const.dart';
class CommunitySpaceManagementApi { class CommunitySpaceManagementApi {
// Community Management APIs // Community Management APIs
Future<List<CommunityModel>> fetchCommunities(String projectId, Future<List<CommunityModel>> fetchCommunities(String projectId, {int page = 1}) async {
{int page = 1}) async {
try { try {
List<CommunityModel> allCommunities = []; List<CommunityModel> allCommunities = [];
bool hasNext = true; bool hasNext = true;
while (hasNext) { while (hasNext) {
await HTTPService().get( await HTTPService().get(
path: ApiEndpoints.getCommunityList path: ApiEndpoints.getCommunityList.replaceAll('{projectId}', projectId),
.replaceAll('{projectId}', projectId), queryParameters: {
queryParameters: {'page': page}, 'page': page,
},
expectedResponseModel: (json) { expectedResponseModel: (json) {
try { try {
List<dynamic> jsonData = json['data'] ?? []; List<dynamic> jsonData = json['data'] ?? [];
@@ -49,11 +48,42 @@ class CommunitySpaceManagementApi {
} }
} }
Future<PaginationModel> fetchCommunitiesAndSpaces(
{required String projectId, int page = 1, String search = ''}) async {
PaginationModel paginationModel = const PaginationModel.emptyConstructor();
try {
bool hasNext = false;
await HTTPService().get(
path: ApiEndpoints.getCommunityList.replaceAll('{projectId}', projectId),
queryParameters: {'page': page, 'includeSpaces': true, 'size': 25, 'search': search},
expectedResponseModel: (json) {
try {
List<dynamic> jsonData = json['data'] ?? [];
hasNext = json['hasNext'] ?? false;
int currentPage = json['page'] ?? 1;
List<CommunityModel> communityList = jsonData.map((jsonItem) {
return CommunityModel.fromJson(jsonItem);
}).toList();
page = currentPage + 1;
paginationModel = PaginationModel(
pageNum: page, hasNext: hasNext, size: 25, communities: communityList);
return paginationModel;
} catch (_) {
hasNext = false;
return [];
}
},
);
} catch (_) {}
return paginationModel;
}
Future<CommunityModel?> getCommunityById(String communityId) async { Future<CommunityModel?> getCommunityById(String communityId) async {
try { try {
final response = await HTTPService().get( final response = await HTTPService().get(
path: ApiEndpoints.getCommunityById path: ApiEndpoints.getCommunityById.replaceAll('{communityId}', communityId),
.replaceAll('{communityId}', communityId),
expectedResponseModel: (json) { expectedResponseModel: (json) {
return CommunityModel.fromJson(json['data']); return CommunityModel.fromJson(json['data']);
}, },
@@ -65,8 +95,7 @@ class CommunitySpaceManagementApi {
} }
} }
Future<CommunityModel?> createCommunity( Future<CommunityModel?> createCommunity(String name, String description, String projectId) async {
String name, String description, String projectId) async {
try { try {
final response = await HTTPService().post( final response = await HTTPService().post(
path: ApiEndpoints.createCommunity.replaceAll('{projectId}', projectId), path: ApiEndpoints.createCommunity.replaceAll('{projectId}', projectId),
@@ -85,8 +114,7 @@ class CommunitySpaceManagementApi {
} }
} }
Future<bool> updateCommunity( Future<bool> updateCommunity(String communityId, String name, String projectId) async {
String communityId, String name, String projectId) async {
try { try {
final response = await HTTPService().put( final response = await HTTPService().put(
path: ApiEndpoints.updateCommunity path: ApiEndpoints.updateCommunity
@@ -123,8 +151,7 @@ class CommunitySpaceManagementApi {
} }
} }
Future<SpacesResponse> fetchSpaces( Future<SpacesResponse> fetchSpaces(String communityId, String projectId) async {
String communityId, String projectId) async {
try { try {
final response = await HTTPService().get( final response = await HTTPService().get(
path: ApiEndpoints.listSpaces path: ApiEndpoints.listSpaces
@@ -150,8 +177,7 @@ class CommunitySpaceManagementApi {
} }
} }
Future<SpaceModel?> getSpace( Future<SpaceModel?> getSpace(String communityId, String spaceId, String projectId) async {
String communityId, String spaceId, String projectId) async {
try { try {
final response = await HTTPService().get( final response = await HTTPService().get(
path: ApiEndpoints.getSpace path: ApiEndpoints.getSpace
@@ -262,8 +288,7 @@ class CommunitySpaceManagementApi {
} }
} }
Future<bool> deleteSpace( Future<bool> deleteSpace(String communityId, String spaceId, String projectId) async {
String communityId, String spaceId, String projectId) async {
try { try {
final response = await HTTPService().delete( final response = await HTTPService().delete(
path: ApiEndpoints.deleteSpace path: ApiEndpoints.deleteSpace
@@ -281,17 +306,15 @@ class CommunitySpaceManagementApi {
} }
} }
Future<List<SpaceModel>> getSpaceHierarchy( Future<List<SpaceModel>> getSpaceHierarchy(String communityId, String projectId) async {
String communityId, String projectId) async {
try { try {
final response = await HTTPService().get( final response = await HTTPService().get(
path: ApiEndpoints.getSpaceHierarchy path: ApiEndpoints.getSpaceHierarchy
.replaceAll('{communityId}', communityId) .replaceAll('{communityId}', communityId)
.replaceAll('{projectId}', projectId), .replaceAll('{projectId}', projectId),
expectedResponseModel: (json) { expectedResponseModel: (json) {
final spaceModels = (json['data'] as List) final spaceModels =
.map((spaceJson) => SpaceModel.fromJson(spaceJson)) (json['data'] as List).map((spaceJson) => SpaceModel.fromJson(spaceJson)).toList();
.toList();
return spaceModels; return spaceModels;
}, },
@@ -302,17 +325,16 @@ class CommunitySpaceManagementApi {
return []; return [];
} }
} }
Future<List<SpaceModel>> getSpaceOnlyWithDevices(
{String? communityId, String? projectId}) async { Future<List<SpaceModel>> getSpaceOnlyWithDevices({String? communityId, String? projectId}) async {
try { try {
final response = await HTTPService().get( final response = await HTTPService().get(
path: ApiEndpoints.spaceOnlyWithDevices path: ApiEndpoints.spaceOnlyWithDevices
.replaceAll('{communityId}', communityId!) .replaceAll('{communityId}', communityId!)
.replaceAll('{projectId}', projectId!), .replaceAll('{projectId}', projectId!),
expectedResponseModel: (json) { expectedResponseModel: (json) {
final spaceModels = (json['data'] as List) final spaceModels =
.map((spaceJson) => SpaceModel.fromJson(spaceJson)) (json['data'] as List).map((spaceJson) => SpaceModel.fromJson(spaceJson)).toList();
.toList();
return spaceModels; return spaceModels;
}, },
); );
+13 -31
View File
@@ -9,21 +9,8 @@ abstract class ApiEndpoints {
static const String sendOtp = '/authentication/user/send-otp'; static const String sendOtp = '/authentication/user/send-otp';
static const String verifyOtp = '/authentication/user/verify-otp'; static const String verifyOtp = '/authentication/user/verify-otp';
static const String getRegion = '/region'; static const String getRegion = '/region';
static const String visitorPassword = static const String visitorPassword = '/visitor-passwords';
'/projects/{projectId}/visitor-password'; static const String getDevices = '/projects/{projectId}/devices';
static const String getDevices =
'/projects/{projectId}/visitor-password/devices';
static const String sendOnlineOneTime =
'/visitor-password/temporary-password/online/one-time';
static const String sendOnlineMultipleTime =
'/visitor-password/temporary-password/online/multiple-time';
//offline Password
static const String sendOffLineOneTime =
'/visitor-password/temporary-password/offline/one-time';
static const String sendOffLineMultipleTime =
'/visitor-password/temporary-password/offline/multiple-time';
static const String getUser = '/user/{userUuid}'; static const String getUser = '/user/{userUuid}';
@@ -32,15 +19,15 @@ abstract class ApiEndpoints {
static const String getAllDevices = '/projects/{projectId}/devices'; static const String getAllDevices = '/projects/{projectId}/devices';
static const String getSpaceDevices = static const String getSpaceDevices =
'/projects/{projectId}/communities/{communityUuid}/spaces/{spaceUuid}/devices'; '/projects/{projectId}/communities/{communityUuid}/spaces/{spaceUuid}/devices';
static const String getDeviceStatus = '/device/{uuid}/functions/status'; static const String getDeviceStatus = '/devices/{uuid}/functions/status';
static const String getBatchStatus = '/device/status/batch'; static const String getBatchStatus = '/devices/batch';
static const String deviceControl = '/device/{uuid}/control'; static const String deviceControl = '/devices/{uuid}/command';
static const String deviceBatchControl = '/device/control/batch'; static const String deviceBatchControl = '/devices/batch';
static const String gatewayApi = '/device/gateway/{gatewayUuid}/devices'; static const String gatewayApi = '/devices/gateway/{gatewayUuid}/devices';
static const String openDoorLock = '/door-lock/open/{doorLockUuid}'; static const String openDoorLock = '/door-lock/open/{doorLockUuid}';
static const String getDeviceLogs = '/device/report-logs/{uuid}?code={code}'; static const String getDeviceLogs = '/devices/{uuid}/report-logs?code={code}';
// Space Module // Space Module
static const String createSpace = static const String createSpace =
@@ -70,18 +57,13 @@ abstract class ApiEndpoints {
static const String createUserCommunity = static const String createUserCommunity =
'/projects/{projectId}/communities/user'; '/projects/{projectId}/communities/user';
static const String getDeviceLogsByDate = static const String getDeviceLogsByDate =
'/device/report-logs/{uuid}?code={code}&startTime={startTime}&endTime={endTime}'; '/devices/{uuid}/report-logs?code={code}&startTime={startTime}&endTime={endTime}';
static const String scheduleByDeviceId = '/schedule/{deviceUuid}'; static const String scheduleByDeviceId = '/schedule/{deviceUuid}';
static const String getScheduleByDeviceId = static const String getScheduleByDeviceId = '/schedule/{deviceUuid}?category={category}';
'/schedule/{deviceUuid}?category={category}'; static const String deleteScheduleByDeviceId = '/schedule/{deviceUuid}/{scheduleUuid}';
static const String deleteScheduleByDeviceId = static const String updateScheduleByDeviceId = '/schedule/enable/{deviceUuid}';
'/schedule/{deviceUuid}/{scheduleUuid}'; static const String factoryReset = '/devices/batch';
static const String updateScheduleByDeviceId =
'/schedule/enable/{deviceUuid}';
static const String factoryReset = '/device/factory/reset/{deviceUuid}';
static const String powerClamp =
'/device/{powerClampUuid}/power-clamp/status';
//product //product
static const String listProducts = '/products'; static const String listProducts = '/products';