Compare commits

..

12 Commits

75 changed files with 2005 additions and 1251 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 290 KiB

View File

@ -0,0 +1,10 @@
import 'package:flutter/material.dart';
class AppLoadingIndicator extends StatelessWidget {
const AppLoadingIndicator({super.key});
@override
Widget build(BuildContext context) {
return const Center(child: CircularProgressIndicator());
}
}

View File

@ -7,10 +7,10 @@ import 'package:go_router/go_router.dart';
import 'package:syncrow_web/firebase_options_prod.dart'; import 'package:syncrow_web/firebase_options_prod.dart';
import 'package:syncrow_web/pages/auth/bloc/auth_bloc.dart'; import 'package:syncrow_web/pages/auth/bloc/auth_bloc.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/routines/bloc/create_routine_bloc/create_routine_bloc.dart'; import 'package:syncrow_web/pages/routines/bloc/create_routine_bloc/create_routine_bloc.dart';
import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_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/visitor_password/bloc/visitor_password_bloc.dart'; import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_bloc.dart';
import 'package:syncrow_web/services/locator.dart'; import 'package:syncrow_web/services/locator.dart';
import 'package:syncrow_web/utils/app_routes.dart'; import 'package:syncrow_web/utils/app_routes.dart';
@ -59,7 +59,7 @@ class MyApp extends StatelessWidget {
BlocProvider<CreateRoutineBloc>( BlocProvider<CreateRoutineBloc>(
create: (context) => CreateRoutineBloc(), create: (context) => CreateRoutineBloc(),
), ),
BlocProvider(create: (context) => HomeBloc()..add(FetchUserInfo())), BlocProvider(create: (context) => HomeBloc()),
BlocProvider<VisitorPasswordBloc>( BlocProvider<VisitorPasswordBloc>(
create: (context) => VisitorPasswordBloc(), create: (context) => VisitorPasswordBloc(),
), ),
@ -67,7 +67,7 @@ class MyApp extends StatelessWidget {
create: (context) => RoutineBloc(), create: (context) => RoutineBloc(),
), ),
BlocProvider<SpaceTreeBloc>( BlocProvider<SpaceTreeBloc>(
create: (context) => SpaceTreeBloc(), create: (context) => SpaceTreeBloc()..add(InitialEvent()),
), ),
], ],
child: MaterialApp.router( child: MaterialApp.router(

View File

@ -7,10 +7,10 @@ import 'package:go_router/go_router.dart';
import 'package:syncrow_web/firebase_options_dev.dart'; import 'package:syncrow_web/firebase_options_dev.dart';
import 'package:syncrow_web/pages/auth/bloc/auth_bloc.dart'; import 'package:syncrow_web/pages/auth/bloc/auth_bloc.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/routines/bloc/create_routine_bloc/create_routine_bloc.dart'; import 'package:syncrow_web/pages/routines/bloc/create_routine_bloc/create_routine_bloc.dart';
import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_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/visitor_password/bloc/visitor_password_bloc.dart'; import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_bloc.dart';
import 'package:syncrow_web/services/locator.dart'; import 'package:syncrow_web/services/locator.dart';
import 'package:syncrow_web/utils/app_routes.dart'; import 'package:syncrow_web/utils/app_routes.dart';
@ -59,7 +59,7 @@ class MyApp extends StatelessWidget {
BlocProvider<CreateRoutineBloc>( BlocProvider<CreateRoutineBloc>(
create: (context) => CreateRoutineBloc(), create: (context) => CreateRoutineBloc(),
), ),
BlocProvider(create: (context) => HomeBloc()..add(FetchUserInfo())), BlocProvider(create: (context) => HomeBloc()),
BlocProvider<VisitorPasswordBloc>( BlocProvider<VisitorPasswordBloc>(
create: (context) => VisitorPasswordBloc(), create: (context) => VisitorPasswordBloc(),
), ),
@ -67,7 +67,7 @@ class MyApp extends StatelessWidget {
create: (context) => RoutineBloc(), create: (context) => RoutineBloc(),
), ),
BlocProvider<SpaceTreeBloc>( BlocProvider<SpaceTreeBloc>(
create: (context) => SpaceTreeBloc(), create: (context) => SpaceTreeBloc()..add(InitialEvent()),
), ),
], ],
child: MaterialApp.router( child: MaterialApp.router(

View File

@ -7,10 +7,10 @@ import 'package:go_router/go_router.dart';
import 'package:syncrow_web/firebase_options_prod.dart'; import 'package:syncrow_web/firebase_options_prod.dart';
import 'package:syncrow_web/pages/auth/bloc/auth_bloc.dart'; import 'package:syncrow_web/pages/auth/bloc/auth_bloc.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/routines/bloc/create_routine_bloc/create_routine_bloc.dart'; import 'package:syncrow_web/pages/routines/bloc/create_routine_bloc/create_routine_bloc.dart';
import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_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/visitor_password/bloc/visitor_password_bloc.dart'; import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_bloc.dart';
import 'package:syncrow_web/services/locator.dart'; import 'package:syncrow_web/services/locator.dart';
import 'package:syncrow_web/utils/app_routes.dart'; import 'package:syncrow_web/utils/app_routes.dart';
@ -56,7 +56,7 @@ class MyApp extends StatelessWidget {
BlocProvider<CreateRoutineBloc>( BlocProvider<CreateRoutineBloc>(
create: (context) => CreateRoutineBloc(), create: (context) => CreateRoutineBloc(),
), ),
BlocProvider(create: (context) => HomeBloc()..add(FetchUserInfo())), BlocProvider(create: (context) => HomeBloc()),
BlocProvider<VisitorPasswordBloc>( BlocProvider<VisitorPasswordBloc>(
create: (context) => VisitorPasswordBloc(), create: (context) => VisitorPasswordBloc(),
), ),
@ -64,7 +64,7 @@ class MyApp extends StatelessWidget {
create: (context) => RoutineBloc(), create: (context) => RoutineBloc(),
), ),
BlocProvider<SpaceTreeBloc>( BlocProvider<SpaceTreeBloc>(
create: (context) => SpaceTreeBloc(), create: (context) => SpaceTreeBloc()..add(InitialEvent()),
), ),
], ],
child: MaterialApp.router( child: MaterialApp.router(

View File

@ -31,8 +31,6 @@ import 'package:syncrow_web/pages/analytics/services/range_of_aqi/remote_range_o
import 'package:syncrow_web/pages/analytics/services/realtime_device_service/firebase_realtime_device_service.dart'; import 'package:syncrow_web/pages/analytics/services/realtime_device_service/firebase_realtime_device_service.dart';
import 'package:syncrow_web/pages/analytics/services/total_energy_consumption/remote_total_energy_consumption_service.dart'; import 'package:syncrow_web/pages/analytics/services/total_energy_consumption/remote_total_energy_consumption_service.dart';
import 'package:syncrow_web/pages/device_managment/shared/navigate_home_grid_view.dart'; import 'package:syncrow_web/pages/device_managment/shared/navigate_home_grid_view.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/services/api/http_service.dart'; import 'package:syncrow_web/services/api/http_service.dart';
import 'package:syncrow_web/utils/theme/responsive_text_theme.dart'; import 'package:syncrow_web/utils/theme/responsive_text_theme.dart';
import 'package:syncrow_web/web_layout/web_scaffold.dart'; import 'package:syncrow_web/web_layout/web_scaffold.dart';
@ -132,19 +130,9 @@ class _AnalyticsPageState extends State<AnalyticsPage> {
} }
} }
class AnalyticsPageForm extends StatefulWidget { class AnalyticsPageForm extends StatelessWidget {
const AnalyticsPageForm({super.key}); const AnalyticsPageForm({super.key});
@override
State<AnalyticsPageForm> createState() => _AnalyticsPageFormState();
}
class _AnalyticsPageFormState extends State<AnalyticsPageForm> {
@override
void initState() {
context.read<SpaceTreeBloc>().add(InitialEvent());
super.initState();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return WebScaffold( return WebScaffold(

View File

@ -46,7 +46,7 @@ class AnalyticsEnergyManagementView extends StatelessWidget {
spacing: 32, spacing: 32,
children: [ children: [
Expanded( Expanded(
flex: 7, flex: 2,
child: Column( child: Column(
spacing: 20, spacing: 20,
children: [ children: [
@ -55,7 +55,7 @@ class AnalyticsEnergyManagementView extends StatelessWidget {
], ],
), ),
), ),
Expanded(flex: 4, child: PowerClampEnergyDataWidget()), Expanded(child: PowerClampEnergyDataWidget()),
], ],
), ),
), ),

View File

@ -31,12 +31,12 @@ class AnalyticsOccupancyView extends StatelessWidget {
return SingleChildScrollView( return SingleChildScrollView(
child: Container( child: Container(
padding: _padding, padding: _padding,
height: height * 1, height: height * 0.9,
child: const Row( child: const Row(
spacing: 32, spacing: 32,
children: [ children: [
Expanded( Expanded(
flex: 7, flex: 5,
child: Column( child: Column(
spacing: 20, spacing: 20,
children: [ children: [
@ -45,7 +45,7 @@ class AnalyticsOccupancyView extends StatelessWidget {
], ],
), ),
), ),
Expanded(flex: 4, child: OccupancyEndSideBar()), Expanded(flex: 2, child: OccupancyEndSideBar()),
], ],
), ),
), ),

View File

@ -24,45 +24,37 @@ class OccupancyEndSideBar extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const AnalyticsSidebarHeader(title: 'Presnce Sensor'), const AnalyticsSidebarHeader(title: 'Presnce Sensor'),
Expanded( SizedBox(
child: SizedBox( height: MediaQuery.sizeOf(context).height * 0.2,
// height: MediaQuery.sizeOf(context).height * 0.2, child: PowerClampEnergyStatusWidget(
child: PowerClampEnergyStatusWidget( status: [
status: [ PowerClampEnergyStatus(
PowerClampEnergyStatus( iconPath: Assets.presenceState,
iconPath: Assets.presenceState, title: 'Presence Status',
title: 'Presence Status', value: _valueFromCode(
value: _valueFromCode( 'presence_state',
'presence_state', state.deviceStatusList,
state.deviceStatusList,
),
unit: '',
), ),
PowerClampEnergyStatus( unit: '',
iconPath: Assets.presenceTimeIcon, ),
title: 'Presence Time', PowerClampEnergyStatus(
value: iconPath: Assets.presenceTimeIcon,
'${_valueFromCode('none_body_time', state.deviceStatusList)} Min', title: 'Presence Time',
unit: '', value:
), '${_valueFromCode('none_body_time', state.deviceStatusList)} Min',
PowerClampEnergyStatus( unit: '',
iconPath: Assets.currentDistanceIcon, ),
title: 'Detection Distance', PowerClampEnergyStatus(
value: iconPath: Assets.currentDistanceIcon,
'${_valueFromCode('space_move_val', state.deviceStatusList)} M', title: 'Detection Distance',
unit: '', value:
), '${_valueFromCode('space_move_val', state.deviceStatusList)} M',
], unit: '',
), ),
],
), ),
), ),
const SizedBox(height: 20), const SizedBox(height: 20),
Expanded(
flex: 2,
child: FittedBox(
child: Image.asset(Assets.autocadOccupancyImage),
),
),
], ],
), ),
); );

View File

@ -50,11 +50,20 @@ class _DynamicTableState extends State<DynamicTable> {
bool _selectAll = false; bool _selectAll = false;
final ScrollController _verticalScrollController = ScrollController(); final ScrollController _verticalScrollController = ScrollController();
final ScrollController _horizontalScrollController = ScrollController(); final ScrollController _horizontalScrollController = ScrollController();
late ScrollController _horizontalHeaderScrollController;
late ScrollController _horizontalBodyScrollController;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_initializeSelection(); _initializeSelection();
_horizontalHeaderScrollController = ScrollController();
_horizontalBodyScrollController = ScrollController();
// Synchronize horizontal scrolling
_horizontalBodyScrollController.addListener(() {
_horizontalHeaderScrollController
.jumpTo(_horizontalBodyScrollController.offset);
});
} }
@override @override
@ -104,78 +113,108 @@ class _DynamicTableState extends State<DynamicTable> {
context.read<DeviceManagementBloc>().add(UpdateSelection(_selectedRows)); context.read<DeviceManagementBloc>().add(UpdateSelection(_selectedRows));
} }
@override
void dispose() {
_horizontalHeaderScrollController.dispose();
_horizontalBodyScrollController.dispose();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return Container(
decoration: widget.cellDecoration, decoration: widget.cellDecoration,
child: Scrollbar( child: Column(
controller: _verticalScrollController, children: [
thumbVisibility: true, Container(
trackVisibility: true, decoration: widget.headerDecoration ??
child: Scrollbar( const BoxDecoration(color: ColorsManager.boxColor),
//fixed the horizontal scrollbar issue
controller: _horizontalScrollController,
thumbVisibility: true,
trackVisibility: true,
notificationPredicate: (notif) => notif.depth == 1,
child: SingleChildScrollView(
controller: _verticalScrollController,
child: SingleChildScrollView( child: SingleChildScrollView(
controller: _horizontalScrollController,
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
physics: const NeverScrollableScrollPhysics(),
controller: _horizontalHeaderScrollController,
child: SizedBox( child: SizedBox(
width: widget.size.width, width: widget.size.width,
child: Column( child: Row(
children: [ children: [
Container( if (widget.withCheckBox) _buildSelectAllCheckbox(),
decoration: widget.headerDecoration ?? ...List.generate(widget.headers.length, (index) {
const BoxDecoration( return _buildTableHeaderCell(
color: ColorsManager.boxColor, widget.headers[index], index);
), }),
child: Row(
children: [
if (widget.withCheckBox) _buildSelectAllCheckbox(),
...List.generate(widget.headers.length, (index) {
return _buildTableHeaderCell(
widget.headers[index], index);
})
//...widget.headers.map((header) => _buildTableHeaderCell(header)),
],
),
),
SizedBox(
width: widget.size.width,
child: widget.isEmpty
? _buildEmptyState()
: Column(
children:
List.generate(widget.data.length, (rowIndex) {
final row = widget.data[rowIndex];
return Row(
children: [
if (widget.withCheckBox)
_buildRowCheckbox(
rowIndex, widget.size.height * 0.08),
...row.asMap().entries.map((entry) {
return _buildTableCell(
entry.value.toString(),
widget.size.height * 0.08,
rowIndex: rowIndex,
columnIndex: entry.key,
);
}).toList(),
],
);
}),
),
),
], ],
), ),
), ),
), ),
), ),
Expanded(
child: Scrollbar(
controller: _verticalScrollController,
thumbVisibility: true,
trackVisibility: true,
child: SingleChildScrollView(
controller: _verticalScrollController,
child: Scrollbar(
controller: _horizontalBodyScrollController,
thumbVisibility: false,
trackVisibility: false,
notificationPredicate: (notif) => notif.depth == 1,
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
controller: _horizontalBodyScrollController,
child: Container(
color: ColorsManager.whiteColors,
child: SizedBox(
width: widget.size.width,
child: widget.isEmpty
? _buildEmptyState()
: Column(
children: List.generate(widget.data.length,
(rowIndex) {
final row = widget.data[rowIndex];
return Row(
children: [
if (widget.withCheckBox)
_buildRowCheckbox(rowIndex,
widget.size.height * 0.08),
...row.asMap().entries.map((entry) {
return _buildTableCell(
entry.value.toString(),
widget.size.height * 0.08,
rowIndex: rowIndex,
columnIndex: entry.key,
);
}).toList(),
],
);
}),
),
),
),
),
),
),
),
),
],
),
);
}
Widget _buildSelectAllCheckbox() {
return Container(
width: 50,
decoration: const BoxDecoration(
border: Border.symmetric(
vertical: BorderSide(color: ColorsManager.boxDivider),
), ),
), ),
child: Checkbox(
value: _selectAll,
onChanged: widget.withSelectAll && widget.data.isNotEmpty
? _toggleSelectAll
: null,
),
); );
} }
@ -205,23 +244,6 @@ class _DynamicTableState extends State<DynamicTable> {
), ),
], ],
); );
Widget _buildSelectAllCheckbox() {
return Container(
width: 50,
decoration: const BoxDecoration(
border: Border.symmetric(
vertical: BorderSide(color: ColorsManager.boxDivider),
),
),
child: Checkbox(
value: _selectAll,
onChanged: widget.withSelectAll && widget.data.isNotEmpty
? _toggleSelectAll
: null,
),
);
}
Widget _buildRowCheckbox(int index, double size) { Widget _buildRowCheckbox(int index, double size) {
return Container( return Container(
width: 50, width: 50,
@ -276,8 +298,12 @@ class _DynamicTableState extends State<DynamicTable> {
); );
} }
Widget _buildTableCell(String content, double size, Widget _buildTableCell(
{required int rowIndex, required int columnIndex}) { String content,
double size, {
required int rowIndex,
required int columnIndex,
}) {
bool isBatteryLevel = content.endsWith('%'); bool isBatteryLevel = content.endsWith('%');
double? batteryLevel; double? batteryLevel;
@ -285,6 +311,7 @@ class _DynamicTableState extends State<DynamicTable> {
batteryLevel = double.tryParse(content.replaceAll('%', '').trim()); batteryLevel = double.tryParse(content.replaceAll('%', '').trim());
} }
bool isSettingsColumn = widget.headers[columnIndex] == 'Settings'; bool isSettingsColumn = widget.headers[columnIndex] == 'Settings';
if (isSettingsColumn) { if (isSettingsColumn) {
return buildSettingsIcon( return buildSettingsIcon(
width: 120, width: 120,
@ -389,10 +416,11 @@ class _DynamicTableState extends State<DynamicTable> {
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: Center( child: Center(
child: SvgPicture.asset( child: SvgPicture.asset(
Assets.settings, Assets.settings, // ضع المسار الصحيح هنا
width: 40, width: 40,
height: 22, height: 22,
color: ColorsManager.primaryColor, color: ColorsManager
.primaryColor, // نفس لون الأيقونة في الصورة
), ),
), ),
), ),

View File

@ -6,7 +6,6 @@ import 'package:syncrow_web/pages/device_managment/all_devices/models/device_tag
import 'package:syncrow_web/pages/device_managment/all_devices/models/room.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/room.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/unit.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/unit.dart';
import 'package:syncrow_web/pages/routines/models/ac/ac_function.dart'; import 'package:syncrow_web/pages/routines/models/ac/ac_function.dart';
import 'package:syncrow_web/pages/routines/models/curtain/curtain_function.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/flush/flush_functions.dart'; import 'package:syncrow_web/pages/routines/models/flush/flush_functions.dart';
import 'package:syncrow_web/pages/routines/models/gang_switches/one_gang_switch/one_gang_switch.dart'; import 'package:syncrow_web/pages/routines/models/gang_switches/one_gang_switch/one_gang_switch.dart';
@ -360,14 +359,6 @@ SOS
uuid: uuid ?? '', uuid: uuid ?? '',
name: name ?? '', name: name ?? '',
); );
case 'CUR':
return [
ControlCurtainFunction(
deviceId: uuid ?? '',
deviceName: name ?? '',
type: 'BOTH',
)
];
case 'NCPS': case 'NCPS':
return [ return [
FlushPresenceDelayFunction( FlushPresenceDelayFunction(
@ -450,10 +441,15 @@ SOS
VoltageCStatusFunction( VoltageCStatusFunction(
deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'), deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'),
CurrentCStatusFunction( CurrentCStatusFunction(
deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'), deviceId: uuid ?? '',
deviceName: name ?? '',
type: 'IF'),
PowerFactorCStatusFunction( PowerFactorCStatusFunction(
deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'), deviceId: uuid ?? '',
deviceName: name ?? '',
type: 'IF'),
]; ];
default: default:
return []; return [];
} }

View File

@ -8,28 +8,15 @@ import 'package:syncrow_web/pages/routines/bloc/create_routine_bloc/create_routi
import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routines/view/create_new_routine_view.dart'; import 'package:syncrow_web/pages/routines/view/create_new_routine_view.dart';
import 'package:syncrow_web/pages/routines/view/routines_view.dart'; import 'package:syncrow_web/pages/routines/view/routines_view.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/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';
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';
import 'package:syncrow_web/utils/theme/responsive_text_theme.dart'; import 'package:syncrow_web/utils/theme/responsive_text_theme.dart';
import 'package:syncrow_web/web_layout/web_scaffold.dart'; import 'package:syncrow_web/web_layout/web_scaffold.dart';
class DeviceManagementPage extends StatefulWidget with HelperResponsiveLayout { class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout {
const DeviceManagementPage({super.key}); const DeviceManagementPage({super.key});
@override
State<DeviceManagementPage> createState() => _DeviceManagementPageState();
}
class _DeviceManagementPageState extends State<DeviceManagementPage> {
@override
void initState() {
context.read<SpaceTreeBloc>().add(InitialEvent());
super.initState();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MultiBlocProvider( return MultiBlocProvider(

View File

@ -34,9 +34,17 @@ class HomeCard extends StatelessWidget {
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
children: [ children: [
Expanded( Flexible(
child: SpliteNameHelperWidget( child: FittedBox(
name: name, fit: BoxFit.scaleDown,
child: Text(
name,
style: const TextStyle(
fontSize: 30,
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
), ),
), ),
], ],
@ -55,72 +63,3 @@ class HomeCard extends StatelessWidget {
); );
} }
} }
class SpliteNameHelperWidget extends StatelessWidget {
final String name;
const SpliteNameHelperWidget({
super.key,
required this.name,
});
@override
Widget build(BuildContext context) {
List<String> parts = name.split(' ');
if (parts.length == 2) {
// Two-word string
return Padding(
padding: const EdgeInsetsGeometry.only(top: 10, left: 10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: FittedBox(
fit: BoxFit.scaleDown,
child: Text(
parts[0],
style: const TextStyle(
fontSize: 30,
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
),
),
Expanded(
child: FittedBox(
fit: BoxFit.scaleDown,
child: Text(
parts[1],
style: const TextStyle(
fontSize: 30,
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
),
),
],
),
);
} else {
// One-word string
return Text(
name,
style: const TextStyle(
fontSize: 30,
color: Colors.white,
fontWeight: FontWeight.bold,
),
);
}
}
}
// Text(
// name,
// style: const TextStyle(
// fontSize: 32,
// color: Colors.white,
// fontWeight: FontWeight.bold,
// ),
// )

View File

@ -13,25 +13,19 @@ class HomePage extends StatefulWidget {
State<HomePage> createState() => _HomePageState(); State<HomePage> createState() => _HomePageState();
} }
class _HomePageState extends State<HomePage> with HelperResponsiveLayout { class _HomePageState extends State<HomePage> with HelperResponsiveLayout{
@override @override
void initState() { void initState() {
_fetchUserInfo(); context.read<HomeBloc>().add(const FetchUserInfo());
super.initState(); super.initState();
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (isSmallScreenSize(context) || isMediumScreenSize(context)) { final isSmallScreen = isSmallScreenSize(context);
return HomeMobilePage(); final isMediumScreen = isMediumScreenSize(context);
} return isSmallScreen || isMediumScreen
? HomeMobilePage()
return const HomeWebPage(); : const HomeWebPage();
}
void _fetchUserInfo() {
final bloc = context.read<HomeBloc>();
if (bloc.user == null) bloc.add(const FetchUserInfo());
} }
} }

View File

@ -92,7 +92,7 @@ class _HomeWebPageState extends State<HomeWebPage> {
flex: 4, flex: 4,
child: SizedBox( child: SizedBox(
height: size.height * 0.6, height: size.height * 0.6,
width: size.width * 0.8, width: size.width * 0.68,
child: GridView.builder( child: GridView.builder(
itemCount: homeBloc.homeItems.length, itemCount: homeBloc.homeItems.length,
gridDelegate: gridDelegate:

View File

@ -13,35 +13,6 @@ class FunctionBloc extends Bloc<FunctionBlocEvent, FunctionBlocState> {
on<AddFunction>(_onAddFunction); on<AddFunction>(_onAddFunction);
on<SelectFunction>(_onSelectFunction); on<SelectFunction>(_onSelectFunction);
} }
// void _onAddFunction(AddFunction event, Emitter<FunctionBlocState> emit) {
// final functions = List<DeviceFunctionData>.from(state.addedFunctions);
// final existingIndex = functions.indexWhere(
// (f) => f.functionCode == event.functionData.functionCode,
// );
// if (existingIndex != -1) {
// final existingData = functions[existingIndex];
// functions[existingIndex] = DeviceFunctionData(
// entityId: event.functionData.entityId,
// functionCode: event.functionData.functionCode,
// operationName: event.functionData.operationName,
// value: event.functionData.value ?? existingData.value,
// valueDescription: event.functionData.valueDescription ??
// existingData.valueDescription,
// condition: event.functionData.condition ?? existingData.condition,
// step: event.functionData.step ?? existingData.step,
// );
// } else {
// functions.clear();
// functions.add(event.functionData);
// }
// emit(state.copyWith(
// addedFunctions: functions,
// selectedFunction: event.functionData.functionCode,
// ));
// }
void _onAddFunction(AddFunction event, Emitter<FunctionBlocState> emit) { void _onAddFunction(AddFunction event, Emitter<FunctionBlocState> emit) {
final functions = List<DeviceFunctionData>.from(state.addedFunctions); final functions = List<DeviceFunctionData>.from(state.addedFunctions);
final existingIndex = functions.indexWhere( final existingIndex = functions.indexWhere(
@ -49,10 +20,19 @@ class FunctionBloc extends Bloc<FunctionBlocEvent, FunctionBlocState> {
); );
if (existingIndex != -1) { if (existingIndex != -1) {
// Update the function value final existingData = functions[existingIndex];
functions[existingIndex] = event.functionData; functions[existingIndex] = DeviceFunctionData(
entityId: event.functionData.entityId,
functionCode: event.functionData.functionCode,
operationName: event.functionData.operationName,
value: event.functionData.value ?? existingData.value,
valueDescription: event.functionData.valueDescription ??
existingData.valueDescription,
condition: event.functionData.condition ?? existingData.condition,
step: event.functionData.step ?? existingData.step,
);
} else { } else {
// Add new function value functions.clear();
functions.add(event.functionData); functions.add(event.functionData);
} }

View File

@ -1419,17 +1419,15 @@ Future<void> _onLoadScenes(
event.automationId, event.automationStatusUpdate, projectId); event.automationId, event.automationStatusUpdate, projectId);
if (success) { if (success) {
// await SceneApi.getAutomationByUnitId( final updatedAutomations = await SceneApi.getAutomationByUnitId(
// event.automationStatusUpdate.spaceUuid, event.automationStatusUpdate.spaceUuid,
// event.communityId, event.communityId,
// projectId); projectId);
// Remove from loading set safely // Remove from loading set safely
final updatedLoadingIds = {...state.loadingAutomationIds!} final updatedLoadingIds = {...state.loadingAutomationIds!}
..remove(event.automationId); ..remove(event.automationId);
final updatedAutomations = changeItemStateOnToggelingSceen(
state.automations, event.automationId);
emit(state.copyWith( emit(state.copyWith(
automations: updatedAutomations, automations: updatedAutomations,
loadingAutomationIds: updatedLoadingIds, loadingAutomationIds: updatedLoadingIds,
@ -1451,24 +1449,4 @@ Future<void> _onLoadScenes(
)); ));
} }
} }
List<ScenesModel> changeItemStateOnToggelingSceen(
List<ScenesModel> oldSceen, String automationId) {
return oldSceen.map((scene) {
if (scene.id == automationId) {
return ScenesModel(
id: scene.id,
sceneTuyaId: scene.sceneTuyaId,
name: scene.name,
status: scene.status == 'enable' ? 'disable' : 'enable',
type: scene.type,
spaceName: scene.spaceName,
spaceId: scene.spaceId,
communityId: scene.communityId,
icon: scene.icon,
);
}
return scene;
}).toList();
}
} }

View File

@ -117,7 +117,7 @@ class _DropdownContentState extends State<_DropdownContent> {
final selectedCommunity = _findCommunity(state, state.selectedSpaceId); final selectedCommunity = _findCommunity(state, state.selectedSpaceId);
return Container( return Container(
height: 40, height: 46,
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border.all(color: Colors.grey.shade300), border: Border.all(color: Colors.grey.shade300),
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
@ -149,7 +149,7 @@ class _DropdownContentState extends State<_DropdownContent> {
), ),
), ),
height: 45, height: 45,
width: 44, width: 33,
child: const Icon( child: const Icon(
Icons.keyboard_arrow_down, Icons.keyboard_arrow_down,
color: ColorsManager.textGray, color: ColorsManager.textGray,

View File

@ -44,156 +44,144 @@ class _CreateNewRoutinesDialogState extends State<CreateNewRoutinesDialog> {
_selectedSpace = null; _selectedSpace = null;
_selectedCommunity = _selectedId; _selectedCommunity = _selectedId;
} }
return Dialog( return AlertDialog(
backgroundColor: Colors.white, backgroundColor: Colors.white,
insetPadding: const EdgeInsets.symmetric( insetPadding: EdgeInsets.zero,
horizontal: 20, contentPadding: EdgeInsets.zero,
),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12)), borderRadius: BorderRadius.circular(12)),
child: Container( title: Text(
width: 450, 'Create New Routines',
child: Stack( textAlign: TextAlign.center,
children: [ style: Theme.of(context).textTheme.bodyMedium!.copyWith(
Column( color: ColorsManager.spaceColor,
mainAxisSize: MainAxisSize.min, fontSize: 20,
children: [ fontWeight: FontWeight.w700,
const SizedBox(height: 20), ),
Text( ),
'Create New Routines', content: Stack(
textAlign: TextAlign.center, children: [
style: Column(
Theme.of(context).textTheme.bodyMedium!.copyWith( mainAxisSize: MainAxisSize.min,
color: ColorsManager.spaceColor, children: [
fontSize: 20, const Divider(),
fontWeight: FontWeight.w700, const SizedBox(height: 20),
), Column(
), children: [
const Divider(), Padding(
const SizedBox(height: 20), padding:
Column( const EdgeInsets.only(left: 13, right: 8),
children: [ child: Column(
Column( children: [
children: [ SpaceTreeDropdown(
Padding( selectedSpaceId: _selectedId,
padding: const EdgeInsets.only(
left: 13, right: 10),
child: Column(
children: [
SpaceTreeDropdown(
selectedSpaceId: _selectedId,
onChanged: (String? newValue) {
setState(
() => _selectedId = newValue!);
if (_selectedId != null) {
_bloc.add(
SpaceOnlyWithDevicesEvent(
_selectedId!));
}
},
),
],
)),
const SizedBox(height: 21),
Padding(
padding: const EdgeInsets.only(
left: 15, right: 20),
child: SpaceDropdown(
hintMessage: spaceHint,
spaces: spaces,
selectedValue: _selectedSpace,
onChanged: (String? newValue) { onChanged: (String? newValue) {
setState(() { setState(() => _selectedId = newValue!);
_selectedSpace = newValue; if (_selectedId != null) {
}); _bloc.add(SpaceOnlyWithDevicesEvent(
_selectedId!));
}
}, },
), ),
), ],
], )),
const SizedBox(height: 5),
const SizedBox(height: 8),
Padding(
padding: const EdgeInsets.only(left: 15, right: 15),
child: SpaceDropdown(
hintMessage: spaceHint,
spaces: spaces,
selectedValue: _selectedSpace,
onChanged: (String? newValue) {
setState(() {
_selectedSpace = newValue;
});
},
), ),
], ),
), ],
const SizedBox(height: 20),
const Divider(),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Padding(
padding: const EdgeInsets.only(
left: 20,
right: 20,
),
child: TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text(
'Cancel',
style: Theme.of(context)
.textTheme
.bodyMedium!
.copyWith(
fontWeight: FontWeight.w400,
fontSize: 14,
color: ColorsManager.blackColor,
),
),
),
),
Padding(
padding: const EdgeInsets.only(
left: 20,
right: 20,
),
child: TextButton(
onPressed: _selectedCommunity != null &&
_selectedSpace != null
? () {
Navigator.of(context).pop({
'community': _selectedCommunity,
'space': _selectedSpace,
});
}
: null,
child: Text(
'Next',
style: Theme.of(context)
.textTheme
.bodyMedium!
.copyWith(
fontWeight: FontWeight.w400,
fontSize: 14,
color: _selectedCommunity != null &&
_selectedSpace != null
? ColorsManager.blueColor
: Colors.blue.shade100,
),
),
),
),
],
),
const SizedBox(height: 10),
],
),
if (isLoadingCommunities)
const SizedBox(
height: 200,
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Center(
child: CircularProgressIndicator(
color: ColorsManager.primaryColor,
),
),
],
),
), ),
], const SizedBox(height: 20),
), const Divider(),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Padding(
padding: const EdgeInsets.only(
left: 20,
right: 20,
),
child: TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text(
'Cancel',
style: Theme.of(context)
.textTheme
.bodyMedium!
.copyWith(
fontWeight: FontWeight.w400,
fontSize: 14,
color: ColorsManager.blackColor,
),
),
),
),
Padding(
padding: const EdgeInsets.only(
left: 20,
right: 20,
),
child: TextButton(
onPressed: _selectedCommunity != null &&
_selectedSpace != null
? () {
Navigator.of(context).pop({
'community': _selectedCommunity,
'space': _selectedSpace,
});
}
: null,
child: Text(
'Next',
style: Theme.of(context)
.textTheme
.bodyMedium!
.copyWith(
fontWeight: FontWeight.w400,
fontSize: 14,
color: _selectedCommunity != null &&
_selectedSpace != null
? ColorsManager.blueColor
: Colors.blue.shade100,
),
),
),
),
],
),
const SizedBox(height: 10),
],
),
if (isLoadingCommunities)
const SizedBox(
height: 200,
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Center(
child: CircularProgressIndicator(
color: ColorsManager.primaryColor,
),
),
],
),
),
],
), ),
); );
}, },

View File

@ -34,9 +34,7 @@ class SpaceDropdown extends StatelessWidget {
), ),
SizedBox( SizedBox(
child: Container( child: Container(
height: 40,
decoration: BoxDecoration( decoration: BoxDecoration(
color: ColorsManager.whiteColors,
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
), ),
child: DropdownButton2<String>( child: DropdownButton2<String>(
@ -47,7 +45,7 @@ class SpaceDropdown extends StatelessWidget {
value: space.uuid, value: space.uuid,
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.start,
children: [ children: [
Text( Text(
' ${space.name}', ' ${space.name}',
@ -90,7 +88,7 @@ class SpaceDropdown extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Expanded( Expanded(
flex: 8, flex: 6,
child: Padding( child: Padding(
padding: const EdgeInsets.only(left: 10), padding: const EdgeInsets.only(left: 10),
child: Text( child: Text(
@ -131,7 +129,6 @@ class SpaceDropdown extends StatelessWidget {
dropdownStyleData: DropdownStyleData( dropdownStyleData: DropdownStyleData(
maxHeight: MediaQuery.of(context).size.height * 0.4, maxHeight: MediaQuery.of(context).size.height * 0.4,
decoration: BoxDecoration( decoration: BoxDecoration(
color: ColorsManager.whiteColors,
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
), ),
), ),

View File

@ -4,7 +4,6 @@ import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.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/routine_dialogs/ac_dialog.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/ac_dialog.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_helper.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_helper.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/curtain_dialog.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/flush_presence_sensor/flush_presence_sensor.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/flush_presence_sensor/flush_presence_sensor.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/gateway/gateway_helper.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/gateway/gateway_helper.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/one_gang_switch_dialog.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/one_gang_switch_dialog.dart';
@ -27,7 +26,7 @@ class DeviceDialogHelper {
final result = await _getDialogForDeviceType( final result = await _getDialogForDeviceType(
dialogType: dialogType, dialogType: dialogType,
context: context, context: context,
productType: data['productType'] as String, productType: data['productType'],
data: data, data: data,
functions: functions, functions: functions,
removeComparetors: removeComparetors, removeComparetors: removeComparetors,
@ -66,14 +65,7 @@ class DeviceDialogHelper {
removeComparetors: removeComparetors, removeComparetors: removeComparetors,
dialogType: dialogType, dialogType: dialogType,
); );
case 'CUR':
return CurtainHelper.showControlDialog(
dialogType: dialogType,
context: context,
functions: functions,
uniqueCustomId: data['uniqueCustomId'],
device: data['device'],
);
case '1G': case '1G':
return OneGangSwitchHelper.showSwitchFunctionsDialog( return OneGangSwitchHelper.showSwitchFunctionsDialog(
dialogType: dialogType, dialogType: dialogType,

View File

@ -17,10 +17,9 @@ class SaveRoutineHelper {
builder: (context) { builder: (context) {
return BlocBuilder<RoutineBloc, RoutineState>( return BlocBuilder<RoutineBloc, RoutineState>(
builder: (context, state) { builder: (context, state) {
final selectedConditionLabel = final selectedConditionLabel = state.selectedAutomationOperator == 'and'
state.selectedAutomationOperator == 'and' ? 'All Conditions are met'
? 'All Conditions are met' : 'Any Condition is met';
: 'Any Condition is met';
return AlertDialog( return AlertDialog(
contentPadding: EdgeInsets.zero, contentPadding: EdgeInsets.zero,
@ -38,11 +37,10 @@ class SaveRoutineHelper {
Text( Text(
'Create a scene: ${state.routineName ?? ""}', 'Create a scene: ${state.routineName ?? ""}',
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: style: Theme.of(context).textTheme.headlineMedium!.copyWith(
Theme.of(context).textTheme.headlineMedium!.copyWith( color: ColorsManager.primaryColorWithOpacity,
color: ColorsManager.primaryColorWithOpacity, fontWeight: FontWeight.bold,
fontWeight: FontWeight.bold, ),
),
), ),
const SizedBox(height: 18), const SizedBox(height: 18),
_buildDivider(), _buildDivider(),
@ -60,8 +58,7 @@ class SaveRoutineHelper {
_buildIfConditions(state, context), _buildIfConditions(state, context),
Container( Container(
width: 1, width: 1,
color: ColorsManager.greyColor color: ColorsManager.greyColor.withValues(alpha: 0.8),
.withValues(alpha: 0.8),
), ),
_buildThenActions(state, context), _buildThenActions(state, context),
], ],
@ -100,8 +97,7 @@ class SaveRoutineHelper {
child: Row( child: Row(
spacing: 16, spacing: 16,
children: [ children: [
Expanded( Expanded(child: Text('IF: $selectedConditionLabel', style: textStyle)),
child: Text('IF: $selectedConditionLabel', style: textStyle)),
const Expanded(child: Text('THEN:', style: textStyle)), const Expanded(child: Text('THEN:', style: textStyle)),
], ],
), ),
@ -113,7 +109,7 @@ class SaveRoutineHelper {
spacing: 16, spacing: 16,
mainAxisAlignment: MainAxisAlignment.spaceEvenly, mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [ children: [
DialogFooterButton( DialogFooterButton(
text: 'Back', text: 'Back',
onTap: () => Navigator.pop(context), onTap: () => Navigator.pop(context),
), ),
@ -147,8 +143,7 @@ class SaveRoutineHelper {
child: ListView( child: ListView(
// shrinkWrap: true, // shrinkWrap: true,
children: state.thenItems.map((item) { children: state.thenItems.map((item) {
final functions = final functions = state.selectedFunctions[item['uniqueCustomId']] ?? [];
state.selectedFunctions[item['uniqueCustomId']] ?? [];
return functionRow(item, context, functions); return functionRow(item, context, functions);
}).toList(), }).toList(),
), ),
@ -208,20 +203,19 @@ class SaveRoutineHelper {
), ),
), ),
child: Center( child: Center(
child: child: item['type'] == 'tap_to_run' || item['type'] == 'scene'
item['type'] == 'tap_to_run' || item['type'] == 'scene' ? Image.memory(
? Image.memory( base64Decode(item['icon']),
base64Decode(item['icon']), width: 12,
width: 12, height: 22,
height: 22, fit: BoxFit.scaleDown,
fit: BoxFit.scaleDown, )
) : SvgPicture.asset(
: SvgPicture.asset( item['imagePath'],
item['imagePath'], width: 12,
width: 12, height: 12,
height: 12, fit: BoxFit.scaleDown,
fit: BoxFit.scaleDown, ),
),
), ),
), ),
Flexible( Flexible(

View File

@ -1,49 +0,0 @@
import 'package:syncrow_web/pages/device_managment/curtain/model/curtain_model.dart';
import 'package:syncrow_web/pages/routines/models/curtain/curtain_opertion_value.dart';
import 'package:syncrow_web/pages/routines/models/device_functions.dart'
show DeviceFunction;
import 'package:syncrow_web/utils/constants/app_enum.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
abstract class CurtainFunction extends DeviceFunction<CurtainModel> {
final String type;
CurtainFunction({
required super.deviceId,
required super.deviceName,
required this.type,
required super.code,
required super.operationName,
required super.icon,
});
List<CurtainOperationalValue> getOperationalValues();
}
class ControlCurtainFunction extends CurtainFunction {
ControlCurtainFunction({
required super.deviceId,
required super.deviceName,
required super.type,
super.code = 'control',
super.operationName = 'Control',
super.icon = Assets.curtain,
});
@override
List<CurtainOperationalValue> getOperationalValues() => [
CurtainOperationalValue(
icon: Assets.curtain,
description: 'OPEN',
value: 'open',
),
CurtainOperationalValue(
icon: Assets.curtain,
description: 'STOP',
value: 'stop',
),
CurtainOperationalValue(
icon: Assets.curtain,
description: 'CLOSE',
value: 'close',
)
];
}

View File

@ -1,11 +0,0 @@
class CurtainOperationalValue {
final String icon;
final String description;
final String value;
CurtainOperationalValue({
required this.icon,
required this.description,
required this.value,
});
}

View File

@ -405,8 +405,8 @@ class PowerFactorCStatusFunction extends EnergyClampFunctions {
code: 'PowerFactorC', code: 'PowerFactorC',
operationName: 'Power Factor C', operationName: 'Power Factor C',
icon: Assets.speedoMeter, icon: Assets.speedoMeter,
min: 0.0, min: 0.00,
max: 1.0, max: 1.00,
step: 0.1, step: 0.1,
unit: "", unit: "",
); );

View File

@ -148,7 +148,6 @@ class IfContainer extends StatelessWidget {
'NCPS', 'NCPS',
'WH', 'WH',
'PC', 'PC',
'CUR',
].contains(mutableData['productType'])) { ].contains(mutableData['productType'])) {
context context
.read<RoutineBloc>() .read<RoutineBloc>()

View File

@ -28,7 +28,6 @@ class _RoutineDevicesState extends State<RoutineDevices> {
'NCPS', 'NCPS',
'WH', 'WH',
'PC', 'PC',
'CUR',
}; };
@override @override

View File

@ -117,22 +117,10 @@ class ACHelper {
}, },
onConfirm: state.addedFunctions.isNotEmpty onConfirm: state.addedFunctions.isNotEmpty
? () { ? () {
final selectedFunctionData =
state.addedFunctions.firstWhere(
(f) =>
f.functionCode == state.selectedFunction,
orElse: () => DeviceFunctionData(
entityId: '',
functionCode: state.selectedFunction ?? '',
operationName: '',
value: null,
),
);
/// add the functions to the routine bloc /// add the functions to the routine bloc
context.read<RoutineBloc>().add( context.read<RoutineBloc>().add(
AddFunctionToRoutine( AddFunctionToRoutine(
[selectedFunctionData], state.addedFunctions,
uniqueCustomId, uniqueCustomId,
), ),
); );

View File

@ -78,22 +78,12 @@ class _CeilingSensorDialogState extends State<CeilingSensorDialog> {
onCancel: () => Navigator.pop(context), onCancel: () => Navigator.pop(context),
onConfirm: state.addedFunctions.isNotEmpty onConfirm: state.addedFunctions.isNotEmpty
? () { ? () {
final selectedFunctionData =
state.addedFunctions.firstWhere(
(f) => f.functionCode == state.selectedFunction,
orElse: () => DeviceFunctionData(
entityId: '',
functionCode: state.selectedFunction ?? '',
operationName: '',
value: null,
),
);
final functions = _updateValuesForAddedFunctions( final functions = _updateValuesForAddedFunctions(
state.addedFunctions, state.addedFunctions,
); );
context.read<RoutineBloc>().add( context.read<RoutineBloc>().add(
AddFunctionToRoutine( AddFunctionToRoutine(
[selectedFunctionData], functions,
'${widget.uniqueCustomId}', '${widget.uniqueCustomId}',
), ),
); );

View File

@ -1,270 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/svg.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/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routines/models/curtain/curtain_function.dart';
import 'package:syncrow_web/pages/routines/models/curtain/curtain_opertion_value.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_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/extension/build_context_x.dart';
class CurtainHelper {
static Future<Map<String, dynamic>?> showControlDialog({
required String dialogType,
required BuildContext context,
required List<DeviceFunction> functions,
required String uniqueCustomId,
required AllDevicesModel? device,
}) async {
List<ControlCurtainFunction> curtainFunctions =
functions.whereType<ControlCurtainFunction>().where((function) {
if (dialogType == 'THEN') {
return function.type == 'THEN' || function.type == 'BOTH';
}
return function.type == 'IF' || function.type == 'BOTH';
}).toList();
return showDialog<Map<String, dynamic>?>(
context: context,
builder: (context) => BlocProvider(
create: (_) => FunctionBloc()..add(const InitializeFunctions([])),
child: AlertDialog(
contentPadding: EdgeInsets.zero,
content: BlocBuilder<FunctionBloc, FunctionBlocState>(
builder: (context, state) {
final selectedFunction = state.selectedFunction;
final selectedOperationName = state.selectedOperationName;
final selectedFunctionData = state.addedFunctions
.firstWhere((f) => f.functionCode == selectedFunction,
orElse: () => DeviceFunctionData(
entityId: '',
functionCode: selectedFunction ?? '',
operationName: '',
value: null,
));
return Container(
width: selectedFunction != null ? 600 : 360,
height: 450,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
),
padding: const EdgeInsets.only(top: 20),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const DialogHeader('AC Functions'),
Expanded(
child: Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Function list
SizedBox(
width: selectedFunction != null ? 320 : 360,
child: _buildFunctionsList(
context: context,
curtainFunctions: curtainFunctions,
onFunctionSelected:
(functionCode, operationName) {
RoutineTapFunctionHelper.onTapFunction(
context,
functionCode: functionCode,
functionOperationName: operationName,
functionValueDescription:
selectedFunctionData.valueDescription,
deviceUuid: device?.uuid,
codesToAddIntoFunctionsWithDefaultValue: [
'temp_set',
'temp_current',
],
defaultValue: 0);
}),
),
// Value selector
if (selectedFunction != null)
Expanded(
child: _buildValueSelector(
context: context,
selectedFunction: selectedFunction,
selectedFunctionData: selectedFunctionData,
controlFunctions: curtainFunctions,
device: device,
operationName: selectedOperationName ?? '',
),
),
],
),
),
DialogFooter(
onCancel: () {
Navigator.pop(context);
},
onConfirm: state.addedFunctions.isNotEmpty
? () {
/// add the functions to the routine bloc
context.read<RoutineBloc>().add(
AddFunctionToRoutine(
state.addedFunctions,
uniqueCustomId,
),
);
// Return the device data to be added to the container
Navigator.pop(context, {
'deviceId': functions.first.deviceId,
});
}
: null,
isConfirmEnabled: selectedFunction != null,
),
],
),
);
},
),
),
),
).then((value) {
return value;
});
}
static Widget _buildFunctionsList({
required BuildContext context,
required List<ControlCurtainFunction> curtainFunctions,
required Function(String, String) onFunctionSelected,
}) {
return ListView.separated(
shrinkWrap: false,
physics: const AlwaysScrollableScrollPhysics(),
itemCount: curtainFunctions.length,
separatorBuilder: (context, index) => const Padding(
padding: EdgeInsets.symmetric(horizontal: 40.0),
child: Divider(
color: ColorsManager.dividerColor,
),
),
itemBuilder: (context, index) {
final function = curtainFunctions[index];
return ListTile(
leading: SvgPicture.asset(
function.icon,
width: 24,
height: 24,
placeholderBuilder: (BuildContext context) => Container(
width: 24,
height: 24,
color: Colors.transparent,
),
),
title: Text(
function.operationName,
style: context.textTheme.bodyMedium,
),
trailing: const Icon(
Icons.arrow_forward_ios,
size: 16,
color: ColorsManager.textGray,
),
onTap: () => onFunctionSelected(
function.code,
function.operationName,
),
);
},
);
}
static Widget _buildValueSelector({
required BuildContext context,
required String selectedFunction,
required DeviceFunctionData? selectedFunctionData,
required List<ControlCurtainFunction> controlFunctions,
AllDevicesModel? device,
required String operationName,
}) {
final selectedFn =
controlFunctions.firstWhere((f) => f.code == selectedFunction);
// Rest of your existing code for other value selectors
final values = selectedFn.getOperationalValues();
return _buildOperationalValuesList(
context: context,
values: values,
selectedValue: selectedFunctionData?.value,
device: device,
operationName: operationName,
selectCode: selectedFunction,
selectedFunctionData: selectedFunctionData,
);
}
static Widget _buildOperationalValuesList({
required BuildContext context,
required List<CurtainOperationalValue> values,
required dynamic selectedValue,
AllDevicesModel? device,
required String operationName,
required String selectCode,
DeviceFunctionData? selectedFunctionData,
// required Function(dynamic) onValueChanged,
}) {
return ListView.builder(
shrinkWrap: false,
physics: const AlwaysScrollableScrollPhysics(),
itemCount: values.length,
itemBuilder: (context, index) {
final value = values[index];
final isSelected = selectedValue == value.value;
return ListTile(
leading: SvgPicture.asset(
value.icon,
width: 24,
height: 24,
placeholderBuilder: (BuildContext context) => Container(
width: 24,
height: 24,
color: Colors.transparent,
),
),
title: Text(
value.description,
style: context.textTheme.bodyMedium,
),
trailing: Icon(
isSelected
? Icons.radio_button_checked
: Icons.radio_button_unchecked,
size: 24,
color: isSelected
? ColorsManager.primaryColorWithOpacity
: ColorsManager.textGray,
),
onTap: () {
if (!isSelected) {
context.read<FunctionBloc>().add(
AddFunction(
functionData: DeviceFunctionData(
entityId: device?.uuid ?? '',
functionCode: selectCode,
operationName: operationName,
value: value.value,
condition: selectedFunctionData?.condition,
valueDescription:
selectedFunctionData?.valueDescription,
),
),
);
}
},
);
},
);
}
}

View File

@ -192,18 +192,9 @@ class _WallPresenceSensorState extends State<FlushPresenceSensor> {
onCancel: () => Navigator.pop(context), onCancel: () => Navigator.pop(context),
onConfirm: state.addedFunctions.isNotEmpty onConfirm: state.addedFunctions.isNotEmpty
? () { ? () {
final selectedFunctionData = state.addedFunctions.firstWhere(
(f) => f.functionCode == state.selectedFunction,
orElse: () => DeviceFunctionData(
entityId: '',
functionCode: state.selectedFunction ?? '',
operationName: '',
value: null,
),
);
context.read<RoutineBloc>().add( context.read<RoutineBloc>().add(
AddFunctionToRoutine( AddFunctionToRoutine(
[selectedFunctionData], state.addedFunctions,
widget.uniqueCustomId!, widget.uniqueCustomId!,
), ),
); );

View File

@ -115,18 +115,9 @@ class _GatewayDialogState extends State<GatewayDialog> {
onCancel: () => Navigator.pop(context), onCancel: () => Navigator.pop(context),
onConfirm: state.addedFunctions.isNotEmpty onConfirm: state.addedFunctions.isNotEmpty
? () { ? () {
final selectedFunctionData = state.addedFunctions.firstWhere(
(f) => f.functionCode == state.selectedFunction,
orElse: () => DeviceFunctionData(
entityId: '',
functionCode: state.selectedFunction ?? '',
operationName: '',
value: null,
),
);
context.read<RoutineBloc>().add( context.read<RoutineBloc>().add(
AddFunctionToRoutine( AddFunctionToRoutine(
[selectedFunctionData], state.addedFunctions,
widget.uniqueCustomId ?? '-1', widget.uniqueCustomId ?? '-1',
), ),
); );

View File

@ -147,7 +147,7 @@ class OneGangSwitchHelper {
// } // }
context.read<RoutineBloc>().add( context.read<RoutineBloc>().add(
AddFunctionToRoutine( AddFunctionToRoutine(
[selectedFunctionData], state.addedFunctions,
uniqueCustomId, uniqueCustomId,
), ),
); );

View File

@ -250,18 +250,9 @@ class _EnergyClampDialogState extends State<EnergyClampDialog> {
onCancel: () => Navigator.pop(context), onCancel: () => Navigator.pop(context),
onConfirm: state.addedFunctions.isNotEmpty onConfirm: state.addedFunctions.isNotEmpty
? () { ? () {
final selectedFunctionData = state.addedFunctions.firstWhere(
(f) => f.functionCode == state.selectedFunction,
orElse: () => DeviceFunctionData(
entityId: '',
functionCode: state.selectedFunction ?? '',
operationName: '',
value: null,
),
);
context.read<RoutineBloc>().add( context.read<RoutineBloc>().add(
AddFunctionToRoutine( AddFunctionToRoutine(
[selectedFunctionData], state.addedFunctions,
widget.uniqueCustomId!, widget.uniqueCustomId!,
), ),
); );

View File

@ -27,16 +27,17 @@ class EnergyValueSelectorWidget extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final selectedFn = functions.firstWhere((f) => f.code == selectedFunction); final selectedFn =
functions.firstWhere((f) => f.code == selectedFunction);
final values = selectedFn.getOperationalValues(); final values = selectedFn.getOperationalValues();
final step = selectedFn.step; final step = selectedFn.step ?? 1.0;
final _unit = selectedFn.unit ?? ''; final _unit = selectedFn.unit ?? '';
final (double, double) sliderRange = final (double, double) sliderRange =
(selectedFn.min ?? 0.0, selectedFn.max ?? 100.0); (selectedFn.min ?? 0.0, selectedFn.max ?? 100.0);
if (_isSliderFunction(selectedFunction)) { if (_isSliderFunction(selectedFunction)) {
return CustomRoutinesTextbox( return CustomRoutinesTextbox(
withSpecialChar: true, withSpecialChar: false,
currentCondition: functionData.condition, currentCondition: functionData.condition,
dialogType: dialogType, dialogType: dialogType,
sliderRange: sliderRange, sliderRange: sliderRange,
@ -59,14 +60,14 @@ class EnergyValueSelectorWidget extends StatelessWidget {
entityId: device?.uuid ?? '', entityId: device?.uuid ?? '',
functionCode: selectedFunction, functionCode: selectedFunction,
operationName: functionData.operationName, operationName: functionData.operationName,
value: value, value: value.toInt(),
condition: functionData.condition, condition: functionData.condition,
), ),
), ),
), ),
unit: _unit, unit: _unit,
dividendOfRange: 1, dividendOfRange: 1,
stepIncreaseAmount: step!, stepIncreaseAmount: step,
); );
} }

View File

@ -145,22 +145,9 @@ class TwoGangSwitchHelper {
// ), // ),
// ); // );
// } // }
final selectedFunctionData =
state.addedFunctions.firstWhere(
(f) =>
f.functionCode ==
state.selectedFunction,
orElse: () => DeviceFunctionData(
entityId: '',
functionCode:
state.selectedFunction ?? '',
operationName: '',
value: null,
),
);
context.read<RoutineBloc>().add( context.read<RoutineBloc>().add(
AddFunctionToRoutine( AddFunctionToRoutine(
[selectedFunctionData], state.addedFunctions,
uniqueCustomId, uniqueCustomId,
), ),
); );

View File

@ -210,18 +210,9 @@ class _WallPresenceSensorState extends State<WallPresenceSensor> {
onCancel: () => Navigator.pop(context), onCancel: () => Navigator.pop(context),
onConfirm: state.addedFunctions.isNotEmpty onConfirm: state.addedFunctions.isNotEmpty
? () { ? () {
final selectedFunctionData = state.addedFunctions.firstWhere(
(f) => f.functionCode == state.selectedFunction,
orElse: () => DeviceFunctionData(
entityId: '',
functionCode: state.selectedFunction ?? '',
operationName: '',
value: null,
),
);
context.read<RoutineBloc>().add( context.read<RoutineBloc>().add(
AddFunctionToRoutine( AddFunctionToRoutine(
[selectedFunctionData], state.addedFunctions,
widget.uniqueCustomId!, widget.uniqueCustomId!,
), ),
); );

View File

@ -188,18 +188,9 @@ class _WaterHeaterDialogRoutinesState extends State<WaterHeaterDialogRoutines> {
onCancel: () => Navigator.pop(context), onCancel: () => Navigator.pop(context),
onConfirm: state.addedFunctions.isNotEmpty onConfirm: state.addedFunctions.isNotEmpty
? () { ? () {
final selectedFunctionData = state.addedFunctions.firstWhere(
(f) => f.functionCode == state.selectedFunction,
orElse: () => DeviceFunctionData(
entityId: '',
functionCode: state.selectedFunction ?? '',
operationName: '',
value: null,
),
);
context.read<RoutineBloc>().add( context.read<RoutineBloc>().add(
AddFunctionToRoutine( AddFunctionToRoutine(
[selectedFunctionData], state.addedFunctions,
widget.uniqueCustomId!, widget.uniqueCustomId!,
), ),
); );

View File

@ -30,121 +30,123 @@ class ThenContainer extends StatelessWidget {
style: TextStyle( style: TextStyle(
fontSize: 18, fontWeight: FontWeight.bold)), fontSize: 18, fontWeight: FontWeight.bold)),
const SizedBox(height: 16), const SizedBox(height: 16),
if (state.isLoading && state.isUpdate == true) state.isLoading && state.isUpdate == true
const Center( ? const Center(
child: CircularProgressIndicator(), child: CircularProgressIndicator(),
) )
else : Wrap(
Wrap( spacing: 8,
spacing: 8, runSpacing: 8,
runSpacing: 8, children: List.generate(
children: List.generate( state.thenItems.length,
state.thenItems.length, (index) => GestureDetector(
(index) => GestureDetector( onTap: () async {
onTap: () async { if (state.thenItems[index]
if (state.thenItems[index]['deviceId'] == ['deviceId'] ==
'delay') { 'delay') {
final result = await DelayHelper final result = await DelayHelper
.showDelayPickerDialog(context, .showDelayPickerDialog(context,
state.thenItems[index]); state.thenItems[index]);
if (result != null) {
context
.read<RoutineBloc>()
.add(AddToThenContainer({
...state.thenItems[index],
'imagePath': Assets.delay,
'title': 'Delay',
}));
}
return;
}
if (state.thenItems[index]['type'] ==
'automation') {
final result = await showDialog<bool>(
context: context,
builder: (BuildContext context) =>
AutomationDialog(
automationName:
state.thenItems[index]
['name'] ??
'Automation',
automationId:
state.thenItems[index]
['deviceId'] ??
'',
uniqueCustomId:
state.thenItems[index]
['uniqueCustomId'],
),
);
if (result != null) {
context
.read<RoutineBloc>()
.add(AddToThenContainer({
...state.thenItems[index],
'imagePath':
Assets.automation,
'title':
state.thenItems[index]
['name'] ??
state.thenItems[index]
['title'],
}));
}
return;
}
final result = await DeviceDialogHelper
.showDeviceDialog(
context: context,
data: state.thenItems[index],
removeComparetors: true,
dialogType: "THEN");
if (result != null) { if (result != null) {
context context.read<RoutineBloc>().add(
.read<RoutineBloc>() AddToThenContainer(
.add(AddToThenContainer({ state.thenItems[index]));
...state.thenItems[index], } else if (![
'imagePath': Assets.delay, 'AC',
'title': 'Delay', '1G',
})); '2G',
'3G',
'WPS',
'CPS',
"GW",
"NCPS",
'WH',
].contains(state.thenItems[index]
['productType'])) {
context.read<RoutineBloc>().add(
AddToThenContainer(
state.thenItems[index]));
} }
return;
}
if (state.thenItems[index]['type'] ==
'automation') {
final result = await showDialog<bool>(
context: context,
builder: (BuildContext context) =>
AutomationDialog(
automationName:
state.thenItems[index]['name']
as String? ??
'Automation',
automationId: state.thenItems[index]
['deviceId'] as String? ??
'',
uniqueCustomId: state
.thenItems[index]
['uniqueCustomId'] as String,
),
);
if (result != null) {
context
.read<RoutineBloc>()
.add(AddToThenContainer({
...state.thenItems[index],
'imagePath': Assets.automation,
'title': state.thenItems[index]
['name'] ??
state.thenItems[index]
['title'],
}));
}
return;
}
final result = await DeviceDialogHelper
.showDeviceDialog(
context: context,
data: state.thenItems[index],
removeComparetors: true,
dialogType: 'THEN');
if (result != null) {
context.read<RoutineBloc>().add(
AddToThenContainer(
state.thenItems[index]));
} else if (![
'AC',
'1G',
'2G',
'3G',
'WPS',
'CPS',
'GW',
'NCPS',
'WH',
'CUR',
].contains(state.thenItems[index]
['productType'])) {
context.read<RoutineBloc>().add(
AddToThenContainer(
state.thenItems[index]));
}
},
child: DraggableCard(
imagePath: state.thenItems[index]
['imagePath'] as String? ??
'',
title: state.thenItems[index]['title']
as String? ??
'',
deviceData: state.thenItems[index],
padding: const EdgeInsets.symmetric(
horizontal: 4, vertical: 8),
isFromThen: true,
isFromIf: false,
onRemove: () {
context.read<RoutineBloc>().add(
RemoveDragCard(
index: index,
isFromThen: true,
key: state.thenItems[index]
['uniqueCustomId']
as String));
}, },
), child: DraggableCard(
))), imagePath: state.thenItems[index]
['imagePath'] ??
'',
title: state.thenItems[index]
['title'] ??
'',
deviceData: state.thenItems[index],
padding: const EdgeInsets.symmetric(
horizontal: 4, vertical: 8),
isFromThen: true,
isFromIf: false,
onRemove: () {
context.read<RoutineBloc>().add(
RemoveDragCard(
index: index,
isFromThen: true,
key: state.thenItems[index]
['uniqueCustomId']));
},
),
))),
], ],
), ),
), ),
@ -228,7 +230,7 @@ class ThenContainer extends StatelessWidget {
context: context, context: context,
data: mutableData, data: mutableData,
removeComparetors: true, removeComparetors: true,
dialogType: 'THEN'); dialogType: "THEN");
if (result != null) { if (result != null) {
context.read<RoutineBloc>().add(AddToThenContainer(mutableData)); context.read<RoutineBloc>().add(AddToThenContainer(mutableData));
} else if (![ } else if (![
@ -239,10 +241,9 @@ class ThenContainer extends StatelessWidget {
'WPS', 'WPS',
'GW', 'GW',
'CPS', 'CPS',
'NCPS', "NCPS",
'WH', "WH",
'PC', 'PC',
'CUR',
].contains(mutableData['productType'])) { ].contains(mutableData['productType'])) {
context.read<RoutineBloc>().add(AddToThenContainer(mutableData)); context.read<RoutineBloc>().add(AddToThenContainer(mutableData));
} }

View File

@ -0,0 +1,43 @@
import 'package:equatable/equatable.dart';
class PaginatedDataModel<T> extends Equatable {
const PaginatedDataModel({
required this.data,
required this.page,
required this.size,
required this.hasNext,
required this.totalItems,
required this.totalPages,
});
final List<T> data;
final int page;
final int size;
final bool hasNext;
final int totalItems;
final int totalPages;
factory PaginatedDataModel.fromJson(
Map<String, dynamic> json,
List<T> Function(List<dynamic>) fromJsonList,
) {
return PaginatedDataModel<T>(
data: fromJsonList(json['data'] as List<dynamic>),
page: json['page'] as int? ?? 1,
size: json['size'] as int? ?? 25,
hasNext: json['hasNext'] as bool? ?? false,
totalItems: json['totalItem'] as int? ?? 0,
totalPages: json['totalPage'] as int? ?? 0,
);
}
@override
List<Object?> get props => [
data,
page,
size,
hasNext,
totalItems,
totalPages,
];
}

View File

@ -0,0 +1,47 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/shared/navigate_home_grid_view.dart';
import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/space_management_body.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/data/services/debounced_communities_service.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/data/services/remote_communities_service.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/params/load_communities_param.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/bloc/communities_bloc.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/communities_tree_selection_bloc/communities_tree_selection_bloc.dart';
import 'package:syncrow_web/services/api/http_service.dart';
import 'package:syncrow_web/utils/theme/responsive_text_theme.dart';
import 'package:syncrow_web/web_layout/web_scaffold.dart';
class SpaceManagementPage extends StatelessWidget {
const SpaceManagementPage({super.key});
@override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider(
create: (context) => CommunitiesBloc(
communitiesService: DebouncedCommunitiesService(
RemoteCommunitiesService(HTTPService()),
),
)..add(const LoadCommunities(LoadCommunitiesParam())),
),
BlocProvider(create: (context) => CommunitiesTreeSelectionBloc()),
],
child: WebScaffold(
appBarTitle: Text(
'Space Management',
style: ResponsiveTextTheme.of(context).deviceManagementTitle,
),
enableMenuSidebar: false,
centerBody: Text(
'Community Structure',
style: Theme.of(context).textTheme.bodyLarge!.copyWith(
fontWeight: FontWeight.bold,
),
),
rightBody: const NavigateHomeGridView(),
scaffoldBody: const SpaceManagementBody(),
),
);
}
}

View File

@ -0,0 +1,15 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/widgets/space_management_communities_tree.dart';
class SpaceManagementBody extends StatelessWidget {
const SpaceManagementBody({super.key});
@override
Widget build(BuildContext context) {
return const Row(
children: [
SpaceManagementCommunitiesTree(),
],
);
}
}

View File

@ -0,0 +1,42 @@
import 'dart:async';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/params/load_communities_param.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/services/communities_service.dart';
final class DebouncedCommunitiesService implements CommunitiesService {
DebouncedCommunitiesService(
this._decoratee, {
this.debounceDuration = const Duration(milliseconds: 500),
});
final CommunitiesService _decoratee;
final Duration debounceDuration;
Timer? _debounceTimer;
late Completer<CommunitiesPaginationModel>? _completer;
@override
Future<CommunitiesPaginationModel> getCommunity(
LoadCommunitiesParam param,
) async {
_debounceTimer?.cancel();
_completer = Completer<CommunitiesPaginationModel>();
final currentCompleter = _completer!;
_debounceTimer = Timer(debounceDuration, () async {
try {
final result = await _decoratee.getCommunity(param);
if (!currentCompleter.isCompleted) {
currentCompleter.complete(result);
}
} catch (error) {
if (!currentCompleter.isCompleted) {
currentCompleter.completeError(error);
}
}
});
return currentCompleter.future;
}
}

View File

@ -1,9 +1,11 @@
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/community_model.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/community_model.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/params/load_communities_param.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/params/load_communities_param.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/services/communities_service.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/services/communities_service.dart';
import 'package:syncrow_web/services/api/api_exception.dart'; import 'package:syncrow_web/services/api/api_exception.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';
class RemoteCommunitiesService implements CommunitiesService { class RemoteCommunitiesService implements CommunitiesService {
const RemoteCommunitiesService(this._httpService); const RemoteCommunitiesService(this._httpService);
@ -13,14 +15,26 @@ class RemoteCommunitiesService implements CommunitiesService {
static const _defaultErrorMessage = 'Failed to load communities'; static const _defaultErrorMessage = 'Failed to load communities';
@override @override
Future<List<CommunityModel>> getCommunity(LoadCommunitiesParam param) async { Future<CommunitiesPaginationModel> getCommunity(
LoadCommunitiesParam param,
) async {
try { try {
return _httpService.get( final response = await _httpService.get(
path: '/api/communities/', path: await _makeUrl(),
expectedResponseModel: (json) => (json as List<dynamic>) queryParameters: {
.map((e) => CommunityModel.fromJson(e as Map<String, dynamic>)) 'page': param.page,
.toList(), 'size': param.size,
'includeSpaces': param.includeSpaces,
if (param.search.isNotEmpty && param.search != 'null')
'search': param.search,
},
expectedResponseModel: (json) => CommunitiesPaginationModel.fromJson(
json as Map<String, dynamic>,
CommunityModel.fromJsonList,
),
); );
return response;
} on DioException catch (e) { } on DioException catch (e) {
final message = e.response?.data as Map<String, dynamic>?; final message = e.response?.data as Map<String, dynamic>?;
final error = message?['error'] as Map<String, dynamic>?; final error = message?['error'] as Map<String, dynamic>?;
@ -31,4 +45,13 @@ class RemoteCommunitiesService implements CommunitiesService {
throw APIException(formattedErrorMessage); throw APIException(formattedErrorMessage);
} }
} }
Future<String> _makeUrl() async {
final projectUuid = await ProjectManager.getProjectUUID();
if (projectUuid == null) throw APIException('Project UUID is required');
return ApiEndpoints.getCommunityListv2.replaceAll(
'{projectId}',
projectUuid,
);
}
} }

View File

@ -4,11 +4,19 @@ import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain
class CommunityModel extends Equatable { class CommunityModel extends Equatable {
final String uuid; final String uuid;
final String name; final String name;
final DateTime createdAt;
final DateTime updatedAt;
final String description;
final String externalId;
final List<SpaceModel> spaces; final List<SpaceModel> spaces;
const CommunityModel({ const CommunityModel({
required this.uuid, required this.uuid,
required this.name, required this.name,
required this.createdAt,
required this.updatedAt,
required this.description,
required this.externalId,
required this.spaces, required this.spaces,
}); });
@ -16,11 +24,20 @@ class CommunityModel extends Equatable {
return CommunityModel( return CommunityModel(
uuid: json['uuid'] as String, uuid: json['uuid'] as String,
name: json['name'] as String, name: json['name'] as String,
spaces: (json['spaces'] as List<dynamic>) createdAt: DateTime.parse(json['createdAt'] as String),
updatedAt: DateTime.parse(json['updatedAt'] as String),
description: json['description'] as String,
externalId: json['externalId']?.toString() ?? '',
spaces: (json['spaces'] as List<dynamic>? ?? <dynamic>[])
.map((e) => SpaceModel.fromJson(e as Map<String, dynamic>)) .map((e) => SpaceModel.fromJson(e as Map<String, dynamic>))
.toList(), .toList(),
); );
} }
static List<CommunityModel> fromJsonList(List<dynamic> json) {
return json
.map((e) => CommunityModel.fromJson(e as Map<String, dynamic>))
.toList();
}
@override @override
List<Object?> get props => [uuid, name, spaces]; List<Object?> get props => [uuid, name, spaces];

View File

@ -2,26 +2,37 @@ import 'package:equatable/equatable.dart';
class SpaceModel extends Equatable { class SpaceModel extends Equatable {
final String uuid; final String uuid;
final DateTime? createdAt;
final DateTime? updatedAt;
final String spaceName; final String spaceName;
final String icon; final String icon;
final List<SpaceModel> children; final List<SpaceModel> children;
final SpaceModel? parent;
const SpaceModel({ const SpaceModel({
required this.uuid, required this.uuid,
required this.createdAt,
required this.updatedAt,
required this.spaceName, required this.spaceName,
required this.icon, required this.icon,
required this.children, required this.children,
required this.parent,
}); });
factory SpaceModel.fromJson(Map<String, dynamic> json) { factory SpaceModel.fromJson(Map<String, dynamic> json) {
return SpaceModel( return SpaceModel(
uuid: json['uuid'] as String, uuid: json['uuid'] as String? ?? '',
spaceName: json['spaceName'] as String, createdAt: DateTime.tryParse(json['createdAt'] as String? ?? ''),
icon: json['icon'] as String, updatedAt: DateTime.tryParse(json['updatedAt'] as String? ?? ''),
spaceName: json['spaceName'] as String? ?? '',
icon: json['icon'] as String? ?? 'assets/icons/location_icon.svg',
children: (json['children'] as List<dynamic>?) children: (json['children'] as List<dynamic>?)
?.map((e) => SpaceModel.fromJson(e as Map<String, dynamic>)) ?.map((e) => SpaceModel.fromJson(e as Map<String, dynamic>))
.toList() ?? .toList() ??
[], [],
parent: json['parent'] != null
? SpaceModel.fromJson(json['parent'] as Map<String, dynamic>)
: null,
); );
} }

View File

@ -1,3 +1,32 @@
class LoadCommunitiesParam { import 'package:equatable/equatable.dart';
const LoadCommunitiesParam();
class LoadCommunitiesParam extends Equatable {
const LoadCommunitiesParam({
this.page = 1,
this.size = 25,
this.search = '',
this.includeSpaces = true,
});
final int page;
final int size;
final String search;
final bool includeSpaces;
LoadCommunitiesParam copyWith({
int? page,
int? size,
String? search,
bool? includeSpaces,
}) {
return LoadCommunitiesParam(
page: page ?? this.page,
size: size ?? this.size,
search: search ?? this.search,
includeSpaces: includeSpaces ?? this.includeSpaces,
);
}
@override
List<Object?> get props => [page, size, search, includeSpaces];
} }

View File

@ -1,6 +1,9 @@
import 'package:syncrow_web/pages/space_management_v2/main_module/shared/models/paginated_data_model.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/community_model.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/community_model.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/params/load_communities_param.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/params/load_communities_param.dart';
typedef CommunitiesPaginationModel = PaginatedDataModel<CommunityModel>;
abstract class CommunitiesService { abstract class CommunitiesService {
Future<List<CommunityModel>> getCommunity(LoadCommunitiesParam param); Future<CommunitiesPaginationModel> getCommunity(LoadCommunitiesParam param);
} }

View File

@ -14,6 +14,8 @@ class CommunitiesBloc extends Bloc<CommunitiesEvent, CommunitiesState> {
}) : _communitiesService = communitiesService, }) : _communitiesService = communitiesService,
super(const CommunitiesState()) { super(const CommunitiesState()) {
on<LoadCommunities>(_onLoadCommunities); on<LoadCommunities>(_onLoadCommunities);
on<LoadMoreCommunities>(_onLoadMoreCommunities);
on<InsertCommunity>(_onInsertCommunity);
} }
final CommunitiesService _communitiesService; final CommunitiesService _communitiesService;
@ -23,28 +25,93 @@ class CommunitiesBloc extends Bloc<CommunitiesEvent, CommunitiesState> {
Emitter<CommunitiesState> emit, Emitter<CommunitiesState> emit,
) async { ) async {
try { try {
emit(const CommunitiesState(status: CommunitiesStatus.loading)); emit(
final communities = await _communitiesService.getCommunity(event.param); state.copyWith(status: CommunitiesStatus.loading),
);
final paginationResponse = await _communitiesService.getCommunity(
event.param,
);
emit( emit(
CommunitiesState( CommunitiesState(
status: CommunitiesStatus.success, status: CommunitiesStatus.success,
communities: communities, communities: paginationResponse.data,
hasNext: paginationResponse.hasNext,
currentPage: paginationResponse.page,
searchQuery: event.param.search,
isLoadingMore: false,
), ),
); );
} on APIException catch (e) { } on APIException catch (e) {
emit( _onApiException(e, emit);
CommunitiesState(
status: CommunitiesStatus.failure,
errorMessage: e.message,
),
);
} catch (e) { } catch (e) {
emit( _onError(e, emit);
CommunitiesState(
status: CommunitiesStatus.failure,
errorMessage: e.toString(),
),
);
} }
} }
Future<void> _onLoadMoreCommunities(
LoadMoreCommunities event,
Emitter<CommunitiesState> emit,
) async {
if (!state.hasNext || state.isLoadingMore) return;
try {
emit(state.copyWith(isLoadingMore: true));
final param = LoadCommunitiesParam(
page: state.currentPage + 1,
search: state.searchQuery,
);
final paginationResponse = await _communitiesService.getCommunity(param);
final updatedCommunities = List<CommunityModel>.from(state.communities)
..addAll(paginationResponse.data);
emit(
state.copyWith(
status: CommunitiesStatus.success,
communities: updatedCommunities,
hasNext: paginationResponse.hasNext,
currentPage: paginationResponse.page,
isLoadingMore: false,
),
);
} on APIException catch (e) {
_onApiException(e, emit);
} catch (e) {
_onError(e, emit);
}
}
void _onApiException(
APIException e,
Emitter<CommunitiesState> emit,
) {
emit(
state.copyWith(
status: CommunitiesStatus.failure,
isLoadingMore: false,
errorMessage: e.message,
),
);
}
void _onError(Object e, Emitter<CommunitiesState> emit) {
emit(
state.copyWith(
status: CommunitiesStatus.failure,
isLoadingMore: false,
errorMessage: e.toString(),
),
);
}
void _onInsertCommunity(
InsertCommunity event,
Emitter<CommunitiesState> emit,
) {
emit(state.copyWith(communities: [event.community, ...state.communities]));
}
} }

View File

@ -15,3 +15,19 @@ class LoadCommunities extends CommunitiesEvent {
@override @override
List<Object?> get props => [param]; List<Object?> get props => [param];
} }
class LoadMoreCommunities extends CommunitiesEvent {
const LoadMoreCommunities();
@override
List<Object?> get props => [];
}
final class InsertCommunity extends CommunitiesEvent {
const InsertCommunity(this.community);
final CommunityModel community;
@override
List<Object?> get props => [community];
}

View File

@ -7,12 +7,48 @@ final class CommunitiesState extends Equatable {
this.status = CommunitiesStatus.initial, this.status = CommunitiesStatus.initial,
this.communities = const [], this.communities = const [],
this.errorMessage, this.errorMessage,
this.isLoadingMore = false,
this.hasNext = false,
this.currentPage = 1,
this.searchQuery = '',
}); });
final CommunitiesStatus status; final CommunitiesStatus status;
final List<CommunityModel> communities; final List<CommunityModel> communities;
final String? errorMessage; final String? errorMessage;
final bool isLoadingMore;
final bool hasNext;
final int currentPage;
final String searchQuery;
CommunitiesState copyWith({
CommunitiesStatus? status,
List<CommunityModel>? communities,
String? errorMessage,
bool? isLoadingMore,
bool? hasNext,
int? currentPage,
String? searchQuery,
}) {
return CommunitiesState(
status: status ?? this.status,
communities: communities ?? this.communities,
errorMessage: errorMessage ?? this.errorMessage,
isLoadingMore: isLoadingMore ?? this.isLoadingMore,
hasNext: hasNext ?? this.hasNext,
currentPage: currentPage ?? this.currentPage,
searchQuery: searchQuery ?? this.searchQuery,
);
}
@override @override
List<Object?> get props => [status, communities, errorMessage]; List<Object?> get props => [
status,
communities,
errorMessage,
isLoadingMore,
hasNext,
currentPage,
searchQuery,
];
} }

View File

@ -0,0 +1,47 @@
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/community_model.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/space_model.dart';
part 'communities_tree_selection_event.dart';
part 'communities_tree_selection_state.dart';
class CommunitiesTreeSelectionBloc
extends Bloc<CommunitiesTreeSelectionEvent, CommunitiesTreeSelectionState> {
CommunitiesTreeSelectionBloc() : super(const CommunitiesTreeSelectionState()) {
on<SelectCommunityEvent>(_onSelectCommunity);
on<SelectSpaceEvent>(_onSelectSpace);
on<ClearCommunitiesTreeSelectionEvent>(_onClearSelection);
}
void _onSelectCommunity(
SelectCommunityEvent event,
Emitter<CommunitiesTreeSelectionState> emit,
) {
emit(
CommunitiesTreeSelectionState(
selectedCommunity: event.community,
selectedSpace: null,
),
);
}
void _onSelectSpace(
SelectSpaceEvent event,
Emitter<CommunitiesTreeSelectionState> emit,
) {
emit(
CommunitiesTreeSelectionState(
selectedCommunity: null,
selectedSpace: event.space,
),
);
}
void _onClearSelection(
ClearCommunitiesTreeSelectionEvent event,
Emitter<CommunitiesTreeSelectionState> emit,
) {
emit(const CommunitiesTreeSelectionState());
}
}

View File

@ -0,0 +1,30 @@
part of 'communities_tree_selection_bloc.dart';
sealed class CommunitiesTreeSelectionEvent extends Equatable {
const CommunitiesTreeSelectionEvent();
@override
List<Object?> get props => [];
}
final class SelectCommunityEvent extends CommunitiesTreeSelectionEvent {
final CommunityModel? community;
const SelectCommunityEvent({required this.community});
@override
List<Object?> get props => [community];
}
final class SelectSpaceEvent extends CommunitiesTreeSelectionEvent {
final SpaceModel? space;
const SelectSpaceEvent({required this.space});
@override
List<Object?> get props => [space];
}
final class ClearCommunitiesTreeSelectionEvent
extends CommunitiesTreeSelectionEvent {
const ClearCommunitiesTreeSelectionEvent();
}

View File

@ -0,0 +1,29 @@
part of 'communities_tree_selection_bloc.dart';
final class CommunitiesTreeSelectionState extends Equatable {
const CommunitiesTreeSelectionState({
this.selectedCommunity,
this.selectedSpace,
});
final CommunityModel? selectedCommunity;
final SpaceModel? selectedSpace;
CommunitiesTreeSelectionState copyWith({
CommunityModel? selectedCommunity,
SpaceModel? selectedSpace,
List<CommunityModel>? expandedCommunities,
List<SpaceModel>? expandedSpaces,
}) {
return CommunitiesTreeSelectionState(
selectedCommunity: selectedCommunity ?? this.selectedCommunity,
selectedSpace: selectedSpace ?? this.selectedSpace,
);
}
@override
List<Object?> get props => [
selectedCommunity,
selectedSpace,
];
}

View File

@ -0,0 +1,38 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/params/load_communities_param.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/bloc/communities_bloc.dart';
class CommunitiesTreeFailureWidget extends StatelessWidget {
const CommunitiesTreeFailureWidget({super.key, this.errorMessage});
final String? errorMessage;
@override
Widget build(BuildContext context) {
return Expanded(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
errorMessage ?? 'Something went wrong',
textAlign: TextAlign.center,
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: () => context.read<CommunitiesBloc>().add(
LoadCommunities(
LoadCommunitiesParam(
search: context.read<CommunitiesBloc>().state.searchQuery,
),
),
),
child: const Text('Retry'),
),
],
),
),
);
}
}

View File

@ -0,0 +1,37 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/common/widgets/custom_expansion_tile.dart';
class CommunityTile extends StatelessWidget {
final String title;
final List<Widget>? children;
final bool isExpanded;
final bool isSelected;
final void Function(String, bool isExpanded) onExpansionChanged;
final void Function() onItemSelected;
const CommunityTile({
super.key,
required this.title,
required this.isExpanded,
required this.onExpansionChanged,
required this.onItemSelected,
required this.isSelected,
this.children,
});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: CustomExpansionTile(
title: title,
initiallyExpanded: isExpanded,
isSelected: isSelected,
onExpansionChanged: (bool expanded) {
onExpansionChanged(title, expanded);
},
onItemSelected: onItemSelected,
children: children ?? [],
));
}
}

View File

@ -0,0 +1,22 @@
import 'package:flutter/material.dart';
class EmptyCommunitiesTreeSearchResultWidget extends StatelessWidget {
const EmptyCommunitiesTreeSearchResultWidget({
required this.searchQuery,
super.key,
});
final String searchQuery;
@override
Widget build(BuildContext context) {
return Center(
child: Text(
searchQuery.isEmpty
? 'No communities found'
: 'No communities found for "$searchQuery"',
textAlign: TextAlign.center,
),
);
}
}

View File

@ -0,0 +1,112 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/common/widgets/app_loading_indicator.dart';
import 'package:syncrow_web/common/widgets/search_bar.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/params/load_communities_param.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/bloc/communities_bloc.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/widgets/communities_tree_failure_widget.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/widgets/empty_communities_tree_search_result_widget.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/widgets/space_management_communities_tree_community_tile.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/widgets/space_management_sidebar_communities_list.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/widgets/space_management_sidebar_header.dart';
import 'package:syncrow_web/utils/style.dart';
class SpaceManagementCommunitiesTree extends StatefulWidget {
const SpaceManagementCommunitiesTree({super.key});
@override
State<SpaceManagementCommunitiesTree> createState() =>
_SpaceManagementCommunitiesTreeState();
}
class _SpaceManagementCommunitiesTreeState
extends State<SpaceManagementCommunitiesTree> {
@override
void initState() {
context.read<CommunitiesBloc>().add(
const LoadCommunities(LoadCommunitiesParam()),
);
super.initState();
}
void _onSearchChanged(String searchQuery) {
context
.read<CommunitiesBloc>()
.add(LoadCommunities(LoadCommunitiesParam(search: searchQuery.trim())));
}
void _onLoadMore() {
context.read<CommunitiesBloc>().add(const LoadMoreCommunities());
}
@override
Widget build(BuildContext context) {
return BlocBuilder<CommunitiesBloc, CommunitiesState>(
builder: (context, state) => Container(
width: 320,
decoration: subSectionContainerDecoration,
child: Column(
children: [
const SpaceManagementSidebarHeader(),
CustomSearchBar(
onSearchChanged: _onSearchChanged,
),
const SizedBox(height: 16),
switch (state.status) {
CommunitiesStatus.initial => const AppLoadingIndicator(),
CommunitiesStatus.loading => state.communities.isEmpty
? const AppLoadingIndicator()
: _buildCommunitiesTree(context, state),
CommunitiesStatus.success => _buildCommunitiesTree(context, state),
CommunitiesStatus.failure => CommunitiesTreeFailureWidget(
errorMessage: state.errorMessage,
),
},
Visibility(
visible: state.isLoadingMore,
child: const AppLoadingIndicator(),
),
],
),
),
);
}
Widget _buildCommunitiesTree(
BuildContext context,
CommunitiesState state,
) {
final communitiesIsEmpty = state.communities.isEmpty;
final statusIsSuccess = state.status == CommunitiesStatus.success;
return Expanded(
child: Visibility(
visible: statusIsSuccess && communitiesIsEmpty,
replacement: Stack(
children: [
SpaceManagementSidebarCommunitiesList(
communities: state.communities,
onLoadMore: state.hasNext ? _onLoadMore : null,
isLoadingMore: state.isLoadingMore,
hasNext: state.hasNext,
itemBuilder: (context, index) {
return SpaceManagementCommunitiesTreeCommunityTile(
community: state.communities[index],
);
},
),
if (state.status == CommunitiesStatus.loading &&
state.communities.isNotEmpty)
ColoredBox(
color: Colors.white.withValues(alpha: 0.7),
child: const AppLoadingIndicator(),
),
],
),
child: EmptyCommunitiesTreeSearchResultWidget(
searchQuery: state.searchQuery,
),
),
);
}
}

View File

@ -0,0 +1,45 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/community_model.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/communities_tree_selection_bloc/communities_tree_selection_bloc.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/widgets/community_tile.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/widgets/space_management_communities_tree_space_tile.dart';
class SpaceManagementCommunitiesTreeCommunityTile extends StatelessWidget {
const SpaceManagementCommunitiesTreeCommunityTile({
required this.community,
super.key,
});
final CommunityModel community;
@override
Widget build(BuildContext context) {
final spaces = community.spaces
.map(
(space) => SpaceManagementCommunitiesTreeSpaceTile(
space: space,
community: community,
),
)
.toList();
return CommunityTile(
title: community.name,
key: ValueKey(community.uuid),
isSelected: context
.watch<CommunitiesTreeSelectionBloc>()
.state
.selectedCommunity
?.uuid ==
community.uuid,
isExpanded: false,
onItemSelected: () {
context.read<CommunitiesTreeSelectionBloc>().add(
SelectCommunityEvent(community: community),
);
},
onExpansionChanged: (title, expanded) {},
children: spaces,
);
}
}

View File

@ -0,0 +1,56 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/community_model.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/space_model.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/communities_tree_selection_bloc/communities_tree_selection_bloc.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/widgets/space_tile.dart';
class SpaceManagementCommunitiesTreeSpaceTile extends StatelessWidget {
const SpaceManagementCommunitiesTreeSpaceTile({
required this.space,
required this.community,
super.key,
});
final SpaceModel space;
final CommunityModel community;
@override
Widget build(BuildContext context) {
final spaceIsExpanded = _isSpaceOrChildSelected(context, space);
final isSelected =
context.watch<CommunitiesTreeSelectionBloc>().state.selectedSpace?.uuid ==
space.uuid;
return Padding(
padding: const EdgeInsetsDirectional.only(start: 16.0),
child: SpaceTile(
title: space.spaceName,
key: ValueKey(space.uuid),
isSelected: isSelected,
initiallyExpanded: spaceIsExpanded,
onExpansionChanged: (expanded) {},
onItemSelected: () => context.read<CommunitiesTreeSelectionBloc>().add(
SelectSpaceEvent(space: space),
),
children: space.children
.map(
(childSpace) => SpaceManagementCommunitiesTreeSpaceTile(
space: childSpace,
community: community,
),
)
.toList(),
),
);
}
bool _isSpaceOrChildSelected(BuildContext context, SpaceModel space) {
final selectedSpace =
context.read<CommunitiesTreeSelectionBloc>().state.selectedSpace;
final isSpaceSelected = selectedSpace?.uuid == space.uuid;
final anySubSpaceIsSelected = space.children.any(
(child) => _isSpaceOrChildSelected(context, child),
);
return isSpaceSelected || anySubSpaceIsSelected;
}
}

View File

@ -0,0 +1,34 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
class SpaceManagementSidebarAddCommunityButton extends StatelessWidget {
const SpaceManagementSidebarAddCommunityButton({
required this.onTap,
super.key,
});
final void Function() onTap;
@override
Widget build(BuildContext context) {
return SizedBox.square(
dimension: 30,
child: IconButton(
style: IconButton.styleFrom(
iconSize: 20,
backgroundColor: ColorsManager.circleImageBackground,
shape: const CircleBorder(
side: BorderSide(
color: ColorsManager.lightGrayBorderColor,
width: 3,
),
),
),
onPressed: onTap,
icon: SvgPicture.asset(Assets.addIcon),
),
);
}
}

View File

@ -0,0 +1,104 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/community_model.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
class SpaceManagementSidebarCommunitiesList extends StatefulWidget {
const SpaceManagementSidebarCommunitiesList({
required this.communities,
required this.itemBuilder,
this.onLoadMore,
this.isLoadingMore = false,
this.hasNext = false,
super.key,
});
final List<CommunityModel> communities;
final Widget Function(BuildContext context, int index) itemBuilder;
final VoidCallback? onLoadMore;
final bool isLoadingMore;
final bool hasNext;
@override
State<SpaceManagementSidebarCommunitiesList> createState() =>
_SpaceManagementSidebarCommunitiesListState();
}
class _SpaceManagementSidebarCommunitiesListState
extends State<SpaceManagementSidebarCommunitiesList> {
late final ScrollController _scrollController;
@override
void initState() {
super.initState();
_scrollController = ScrollController();
_scrollController.addListener(_onScroll);
}
void _onScroll() {
if (_scrollController.position.pixels >=
_scrollController.position.maxScrollExtent - 100) {
if (widget.hasNext && !widget.isLoadingMore && widget.onLoadMore != null) {
widget.onLoadMore!();
}
}
}
bool _onNotification(ScrollEndNotification notification) {
final hasReachedEnd = notification.metrics.extentAfter == 0;
if (hasReachedEnd &&
widget.hasNext &&
!widget.isLoadingMore &&
widget.onLoadMore != null) {
widget.onLoadMore!();
return true;
}
return false;
}
@override
void dispose() {
_scrollController
..removeListener(_onScroll)
..dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final itemCount = widget.communities.length + (widget.isLoadingMore ? 1 : 0);
return SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: SizedBox(
width: context.screenWidth * 0.5,
child: Scrollbar(
scrollbarOrientation: ScrollbarOrientation.left,
thumbVisibility: true,
controller: _scrollController,
child: NotificationListener<ScrollEndNotification>(
onNotification: _onNotification,
child: ListView.builder(
shrinkWrap: true,
padding: const EdgeInsetsDirectional.only(start: 16),
itemCount: itemCount,
controller: _scrollController,
itemBuilder: (context, index) {
if (index == widget.communities.length) {
return const Padding(
padding: EdgeInsets.all(16.0),
child: Center(
child: CircularProgressIndicator(),
),
);
}
return widget.itemBuilder(context, index);
},
),
),
),
),
);
}
}

View File

@ -0,0 +1,68 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/bloc/communities_bloc.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/communities_tree_selection_bloc/communities_tree_selection_bloc.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/widgets/space_management_sidebar_add_community_button.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/create_community/presentation/create_community_dialog.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:syncrow_web/utils/style.dart';
class SpaceManagementSidebarHeader extends StatelessWidget {
const SpaceManagementSidebarHeader({super.key});
@override
Widget build(BuildContext context) {
return Container(
decoration: subSectionContainerDecoration,
padding: const EdgeInsets.all(16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Communities',
style: context.textTheme.titleMedium?.copyWith(
color: ColorsManager.blackColor,
),
),
SpaceManagementSidebarAddCommunityButton(
onTap: () => _onAddCommunity(context),
),
],
),
);
}
void _onAddCommunity(BuildContext context) {
final bloc = context.read<CommunitiesTreeSelectionBloc>();
final selectedCommunity = bloc.state.selectedCommunity;
final isSelected = selectedCommunity?.uuid.isNotEmpty ?? false;
if (isSelected) {
_clearSelection(context);
} else {
_showCreateCommunityDialog(context);
}
}
void _clearSelection(BuildContext context) {
context.read<CommunitiesTreeSelectionBloc>().add(
const ClearCommunitiesTreeSelectionEvent(),
);
}
void _showCreateCommunityDialog(BuildContext context) => showDialog<void>(
context: context,
builder: (_) => CreateCommunityDialog(
title: const Text('Community Name'),
onCreateCommunity: (community) {
context.read<CommunitiesBloc>().add(
InsertCommunity(community),
);
context.read<CommunitiesTreeSelectionBloc>().add(
SelectCommunityEvent(community: community),
);
},
),
);
}

View File

@ -0,0 +1,54 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/common/widgets/custom_expansion_tile.dart';
class SpaceTile extends StatefulWidget {
final String title;
final bool isSelected;
final bool initiallyExpanded;
final ValueChanged<bool> onExpansionChanged;
final List<Widget>? children;
final void Function() onItemSelected;
const SpaceTile({
super.key,
required this.title,
required this.initiallyExpanded,
required this.onExpansionChanged,
required this.onItemSelected,
required this.isSelected,
this.children,
});
@override
State<SpaceTile> createState() => _SpaceTileState();
}
class _SpaceTileState extends State<SpaceTile> {
late bool _isExpanded;
@override
void initState() {
super.initState();
_isExpanded = widget.initiallyExpanded;
}
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(left: 8.0, right: 8.0, top: 8.0),
child: CustomExpansionTile(
isSelected: widget.isSelected,
title: widget.title,
initiallyExpanded: _isExpanded,
onItemSelected: widget.onItemSelected,
onExpansionChanged: (bool expanded) {
setState(() {
_isExpanded = expanded;
});
widget.onExpansionChanged(expanded);
},
children: widget.children ?? [],
),
);
}
}

View File

@ -1,4 +1,5 @@
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/community_model.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/community_model.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/create_community/domain/param/create_community_param.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/create_community/domain/param/create_community_param.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/create_community/domain/services/create_community_service.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/create_community/domain/services/create_community_service.dart';
@ -16,24 +17,51 @@ class RemoteCreateCommunityService implements CreateCommunityService {
Future<CommunityModel> createCommunity(CreateCommunityParam param) async { Future<CommunityModel> createCommunity(CreateCommunityParam param) async {
try { try {
final response = await _httpService.post( final response = await _httpService.post(
path: 'endpoint', path: await _makeUrl(),
expectedResponseModel: (data) => CommunityModel.fromJson( body: {
data as Map<String, dynamic>, 'name': param.name,
), 'description': param.description,
},
expectedResponseModel: (data) {
final json = data as Map<String, dynamic>;
if (json['success'] == true) {
return CommunityModel.fromJson(
json['data'] as Map<String, dynamic>,
);
}
return null;
},
); );
if (response == null) {
throw APIException(
_getErrorMessageFromBody(response as Map<String, dynamic>?),
);
}
return response; return response;
} on DioException catch (e) { } on DioException catch (e) {
final message = e.response?.data as Map<String, dynamic>?; final message = e.response?.data as Map<String, dynamic>?;
final error = message?['error'] as Map<String, dynamic>?; throw APIException(_getErrorMessageFromBody(message));
final errorMessage = error?['error'] as String? ?? '';
final formattedErrorMessage = [
_defaultErrorMessage,
errorMessage,
].join(': ');
throw APIException(formattedErrorMessage);
} catch (e) { } catch (e) {
final formattedErrorMessage = [_defaultErrorMessage, '$e'].join(': '); final formattedErrorMessage = [_defaultErrorMessage, '$e'].join(': ');
throw APIException(formattedErrorMessage); throw APIException(formattedErrorMessage);
} }
} }
String _getErrorMessageFromBody(Map<String, dynamic>? body) {
if (body == null) {
return _defaultErrorMessage;
}
final error = body['error'] as Map<String, dynamic>?;
final errorMessage = error?['error'] as String? ?? '';
return errorMessage;
}
Future<String> _makeUrl() async {
final projectUuid = await ProjectManager.getProjectUUID();
if (projectUuid == null) {
throw APIException('Project UUID is not set');
}
return '/projects/$projectUuid/communities';
}
} }

View File

@ -1,9 +1,13 @@
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
class CreateCommunityParam extends Equatable { class CreateCommunityParam extends Equatable {
const CreateCommunityParam({required this.name}); const CreateCommunityParam({
required this.name,
this.description = '',
});
final String name; final String name;
final String description;
@override @override
List<Object> get props => [name]; List<Object> get props => [name];

View File

@ -0,0 +1,61 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/community_model.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/create_community/data/services/remote_create_community_service.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/create_community/presentation/bloc/create_community_bloc.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/create_community/presentation/create_community_dialog_widget.dart';
import 'package:syncrow_web/services/api/http_service.dart';
class CreateCommunityDialog extends StatelessWidget {
final void Function(CommunityModel community) onCreateCommunity;
final String? initialName;
final Widget title;
const CreateCommunityDialog({
super.key,
required this.onCreateCommunity,
required this.title,
this.initialName,
});
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => CreateCommunityBloc(RemoteCreateCommunityService(HTTPService())),
child: BlocListener<CreateCommunityBloc, CreateCommunityState>(
listener: (context, state) {
switch (state) {
case CreateCommunityLoading():
showDialog<void>(
context: context,
builder: (context) => const Center(
child: CircularProgressIndicator(),
),
);
break;
case CreateCommunitySuccess(:final community):
Navigator.of(context).pop();
Navigator.of(context).pop();
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Community created successfully')),
);
onCreateCommunity.call(community);
break;
case CreateCommunityFailure(:final message):
Navigator.of(context).pop();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(message)),
);
break;
default:
break;
}
},
child: CreateCommunityDialogWidget(
title: title,
initialName: initialName,
),
),
);
}
}

View File

@ -0,0 +1,144 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/common/buttons/cancel_button.dart';
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/create_community/domain/param/create_community_param.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/create_community/presentation/bloc/create_community_bloc.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/create_community/presentation/create_community_name_text_field.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class CreateCommunityDialogWidget extends StatefulWidget {
final String? initialName;
final Widget title;
const CreateCommunityDialogWidget({
super.key,
required this.title,
this.initialName,
});
@override
State<CreateCommunityDialogWidget> createState() =>
_CreateCommunityDialogWidgetState();
}
class _CreateCommunityDialogWidgetState extends State<CreateCommunityDialogWidget> {
late final TextEditingController _nameController;
@override
void initState() {
_nameController = TextEditingController(text: widget.initialName ?? '');
super.initState();
}
@override
void dispose() {
_nameController.dispose();
super.dispose();
}
final _formKey = GlobalKey<FormState>();
@override
Widget build(BuildContext context) {
return Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
backgroundColor: ColorsManager.transparentColor,
child: Container(
width: MediaQuery.of(context).size.width * 0.3,
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: ColorsManager.whiteColors,
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(
color: ColorsManager.blackColor.withValues(alpha: 0.25),
blurRadius: 20,
spreadRadius: 5,
offset: const Offset(0, 5),
),
],
),
child: Form(
key: _formKey,
child: SingleChildScrollView(
child: BlocBuilder<CreateCommunityBloc, CreateCommunityState>(
builder: (context, state) {
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
DefaultTextStyle(
style: Theme.of(context).textTheme.headlineMedium!,
child: widget.title,
),
const SizedBox(height: 18),
CreateCommunityNameTextField(
nameController: _nameController,
),
if (state case CreateCommunityFailure(:final message))
Padding(
padding: const EdgeInsets.only(top: 18),
child: SelectableText(
'* $message',
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(context).colorScheme.error,
),
),
),
const SizedBox(height: 24),
_buildActionButtons(context),
],
);
},
),
),
),
),
);
}
Widget _buildActionButtons(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: CancelButton(
label: 'Cancel',
onPressed: () => Navigator.of(context).pop(),
),
),
const SizedBox(width: 16),
_buildCreateCommunityButton(context),
],
);
}
Widget _buildCreateCommunityButton(BuildContext context) {
return Expanded(
child: DefaultButton(
onPressed: () {
if (_formKey.currentState?.validate() ?? false) {
_onSubmit(context);
}
},
borderRadius: 10,
foregroundColor: ColorsManager.whiteColors,
child: const Text('OK'),
),
);
}
void _onSubmit(BuildContext context) {
if (_formKey.currentState?.validate() ?? false) {
context.read<CreateCommunityBloc>().add(
CreateCommunity(
CreateCommunityParam(
name: _nameController.text.trim(),
),
),
);
}
}
}

View File

@ -0,0 +1,48 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
class CreateCommunityNameTextField extends StatelessWidget {
const CreateCommunityNameTextField({
required this.nameController,
super.key,
});
final TextEditingController nameController;
@override
Widget build(BuildContext context) {
return TextFormField(
controller: nameController,
validator: _validator,
style: context.textTheme.bodyMedium,
decoration: InputDecoration(
hintText: 'Please enter the community name',
filled: true,
fillColor: ColorsManager.boxColor,
enabledBorder: _buildBorder(ColorsManager.boxColor),
focusedBorder: _buildBorder(),
focusedErrorBorder: _buildBorder(Theme.of(context).colorScheme.error),
errorBorder: _buildBorder(Theme.of(context).colorScheme.error),
),
);
}
String? _validator(String? value) {
if (value == null || value.isEmpty) {
return '*Name should not be empty.';
}
return null;
}
InputBorder _buildBorder([Color? color]) {
return OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide: BorderSide(
color: color ?? ColorsManager.vividBlue.withValues(alpha: 0.5),
width: 1,
),
);
}
}

View File

@ -289,6 +289,7 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
selectedSpaces: updatedSelectedSpaces, selectedSpaces: updatedSelectedSpaces,
soldCheck: updatedSoldChecks, soldCheck: updatedSoldChecks,
selectedCommunityAndSpaces: communityAndSpaces)); selectedCommunityAndSpaces: communityAndSpaces));
emit(state.copyWith(selectedSpaces: updatedSelectedSpaces));
} catch (e) { } catch (e) {
emit(const SpaceTreeErrorState('Something went wrong')); emit(const SpaceTreeErrorState('Something went wrong'));
} }
@ -444,12 +445,10 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
List<String> _getThePathToChild(String communityId, String selectedSpaceId) { List<String> _getThePathToChild(String communityId, String selectedSpaceId) {
List<String> ids = []; List<String> ids = [];
final communityDataSource = for (var community in state.communityList) {
state.searchQuery.isNotEmpty ? state.filteredCommunity : state.communityList;
for (final community in communityDataSource) {
if (community.uuid == communityId) { if (community.uuid == communityId) {
for (final space in community.spaces) { for (var space in community.spaces) {
final list = <String>[]; List<String> list = [];
list.add(space.uuid!); list.add(space.uuid!);
ids = _getAllParentsIds(space, selectedSpaceId, List.from(list)); ids = _getAllParentsIds(space, selectedSpaceId, List.from(list));
if (ids.isNotEmpty) { if (ids.isNotEmpty) {

View File

@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/shared/navigate_home_grid_view.dart'; import 'package:syncrow_web/pages/device_managment/shared/navigate_home_grid_view.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/spaces_management/structure_selector/bloc/center_body_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/structure_selector/bloc/center_body_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';
@ -26,12 +25,6 @@ class SpaceManagementPageState extends State<SpaceManagementPage> {
final CommunitySpaceManagementApi _api = CommunitySpaceManagementApi(); final CommunitySpaceManagementApi _api = CommunitySpaceManagementApi();
final ProductApi _productApi = ProductApi(); final ProductApi _productApi = ProductApi();
final SpaceModelManagementApi _spaceModelApi = SpaceModelManagementApi(); final SpaceModelManagementApi _spaceModelApi = SpaceModelManagementApi();
@override
void initState() {
context.read<SpaceTreeBloc>().add(InitialEvent());
super.initState();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MultiBlocProvider( return MultiBlocProvider(

View File

@ -49,12 +49,12 @@ class CommunityStructureHeaderActionButtons extends StatelessWidget {
onPressed: onEdit, onPressed: onEdit,
theme: theme, theme: theme,
), ),
// CommunityStructureHeaderButton( CommunityStructureHeaderButton(
// label: "Duplicate", label: "Duplicate",
// svgAsset: Assets.duplicate, svgAsset: Assets.duplicate,
// onPressed: onDuplicate, onPressed: onDuplicate,
// theme: theme, theme: theme,
// ), ),
CommunityStructureHeaderButton( CommunityStructureHeaderButton(
label: "Delete", label: "Delete",
svgAsset: Assets.spaceDelete, svgAsset: Assets.spaceDelete,

View File

@ -4,11 +4,10 @@ import 'package:syncrow_web/common/widgets/custom_expansion_tile.dart';
class SpaceTile extends StatefulWidget { class SpaceTile extends StatefulWidget {
final String title; final String title;
final bool isSelected; final bool isSelected;
final bool initiallyExpanded; final bool initiallyExpanded;
final ValueChanged<bool> onExpansionChanged; final ValueChanged<bool> onExpansionChanged;
final List<Widget>? children; final List<Widget>? children;
final Function() onItemSelected; final void Function() onItemSelected;
const SpaceTile({ const SpaceTile({
super.key, super.key,

View File

@ -46,6 +46,7 @@ abstract class ApiEndpoints {
// Community Module // Community Module
static const String createCommunity = '/projects/{projectId}/communities'; static const String createCommunity = '/projects/{projectId}/communities';
static const String getCommunityList = '/projects/{projectId}/communities'; static const String getCommunityList = '/projects/{projectId}/communities';
static const String getCommunityListv2 = '/projects/{projectId}/communities/v2';
static const String getCommunityById = static const String getCommunityById =
'/projects/{projectId}/communities/{communityId}'; '/projects/{projectId}/communities/{communityId}';
static const String updateCommunity = static const String updateCommunity =

View File

@ -1,138 +1,138 @@
class Assets { class Assets {
Assets._(); Assets._();
static const String background = 'assets/images/Background.png'; static const String background = "assets/images/Background.png";
static const String webBackground = 'assets/images/web_Background.svg'; static const String webBackground = "assets/images/web_Background.svg";
static const String webBackgroundPng = 'assets/images/web_Background.png'; static const String webBackgroundPng = "assets/images/web_Background.png";
static const String blackLogo = 'assets/images/black-logo.png'; static const String blackLogo = "assets/images/black-logo.png";
static const String logo = 'assets/images/Logo.svg'; static const String logo = "assets/images/Logo.svg";
static const String logoHorizontal = 'assets/images/logo_horizontal.png'; static const String logoHorizontal = "assets/images/logo_horizontal.png";
static const String vector = 'assets/images/Vector.png'; static const String vector = "assets/images/Vector.png";
static const String loginLogo = 'assets/images/login_logo.svg'; static const String loginLogo = "assets/images/login_logo.svg";
static const String whiteLogo = 'assets/images/white-logo.png'; static const String whiteLogo = "assets/images/white-logo.png";
static const String window = 'assets/images/Window.png'; static const String window = "assets/images/Window.png";
static const String liftLine = 'assets/images/lift_line.png'; static const String liftLine = "assets/images/lift_line.png";
static const String rightLine = 'assets/images/right_line.png'; static const String rightLine = "assets/images/right_line.png";
static const String google = 'assets/images/google.svg'; static const String google = "assets/images/google.svg";
static const String facebook = 'assets/images/facebook.svg'; static const String facebook = "assets/images/facebook.svg";
static const String invisiblePassword = static const String invisiblePassword =
'assets/images/Password_invisible.svg'; "assets/images/Password_invisible.svg";
static const String visiblePassword = 'assets/images/password_visible.svg'; static const String visiblePassword = "assets/images/password_visible.svg";
static const String accessIcon = 'assets/images/access_icon.svg'; static const String accessIcon = "assets/images/access_icon.svg";
static const String spaseManagementIcon = static const String spaseManagementIcon =
'assets/images/spase_management_icon.svg'; "assets/images/spase_management_icon.svg";
static const String devicesIcon = 'assets/images/devices_icon.svg'; static const String devicesIcon = "assets/images/devices_icon.svg";
static const String analyticsIcon = 'assets/icons/landing_analytics.svg'; static const String analyticsIcon = "assets/icons/landing_analytics.svg";
static const String moveinIcon = 'assets/images/movein_icon.svg'; static const String moveinIcon = "assets/images/movein_icon.svg";
static const String constructionIcon = 'assets/images/construction_icon.svg'; static const String constructionIcon = "assets/images/construction_icon.svg";
static const String energyIcon = 'assets/images/energy_icon.svg'; static const String energyIcon = "assets/images/energy_icon.svg";
static const String integrationsIcon = 'assets/images/Integrations_icon.svg'; static const String integrationsIcon = "assets/images/Integrations_icon.svg";
static const String assetIcon = 'assets/images/asset_icon.svg'; static const String assetIcon = "assets/images/asset_icon.svg";
static const String calendarIcon = 'assets/images/calendar_icon.svg'; static const String calendarIcon = "assets/images/calendar_icon.svg";
static const String deviceNoteIcon = 'assets/images/device_note.svg'; static const String deviceNoteIcon = "assets/images/device_note.svg";
static const String timeIcon = 'assets/images/time_icon.svg'; static const String timeIcon = "assets/images/time_icon.svg";
static const String emptyTable = 'assets/images/empty_table.svg'; static const String emptyTable = "assets/images/empty_table.svg";
// General assets // General assets
static const String motionlessDetection = static const String motionlessDetection =
'assets/icons/motionless_detection.svg'; "assets/icons/motionless_detection.svg";
static const String acHeating = 'assets/icons/ac_heating.svg'; static const String acHeating = "assets/icons/ac_heating.svg";
static const String acPowerOff = 'assets/icons/ac_power_off.svg'; static const String acPowerOff = "assets/icons/ac_power_off.svg";
static const String acFanMiddle = 'assets/icons/ac_fan_middle.svg'; static const String acFanMiddle = "assets/icons/ac_fan_middle.svg";
static const String switchAlarmSound = 'assets/icons/switch_alarm_sound.svg'; static const String switchAlarmSound = "assets/icons/switch_alarm_sound.svg";
static const String resetOff = 'assets/icons/reset_off.svg'; static const String resetOff = "assets/icons/reset_off.svg";
static const String sensitivityOperationIcon = static const String sensitivityOperationIcon =
'assets/icons/sesitivity_operation_icon.svg'; "assets/icons/sesitivity_operation_icon.svg";
static const String motionDetection = 'assets/icons/motion_detection.svg'; static const String motionDetection = "assets/icons/motion_detection.svg";
static const String freezing = 'assets/icons/freezing.svg'; static const String freezing = "assets/icons/freezing.svg";
static const String indicator = 'assets/icons/indicator.svg'; static const String indicator = "assets/icons/indicator.svg";
static const String sceneRefresh = 'assets/icons/scene_refresh.svg'; static const String sceneRefresh = "assets/icons/scene_refresh.svg";
static const String temperature = 'assets/icons/tempreture.svg'; static const String temperature = "assets/icons/tempreture.svg";
static const String acFanHigh = 'assets/icons/ac_fan_high.svg'; static const String acFanHigh = "assets/icons/ac_fan_high.svg";
static const String fanSpeed = 'assets/icons/fan_speed.svg'; static const String fanSpeed = "assets/icons/fan_speed.svg";
static const String acFanLow = 'assets/icons/ac_fan_low.svg'; static const String acFanLow = "assets/icons/ac_fan_low.svg";
static const String sensitivity = 'assets/icons/sensitivity.svg'; static const String sensitivity = "assets/icons/sensitivity.svg";
static const String lightCountdown = 'assets/icons/light_countdown.svg'; static const String lightCountdown = "assets/icons/light_countdown.svg";
static const String farDetection = 'assets/icons/far_detection.svg'; static const String farDetection = "assets/icons/far_detection.svg";
static const String sceneChildUnlock = 'assets/icons/scene_child_unlock.svg'; static const String sceneChildUnlock = "assets/icons/scene_child_unlock.svg";
static const String acFanAuto = 'assets/icons/ac_fan_auto.svg'; static const String acFanAuto = "assets/icons/ac_fan_auto.svg";
static const String childLock = 'assets/icons/child_lock.svg'; static const String childLock = "assets/icons/child_lock.svg";
static const String factoryReset = 'assets/icons/factory_reset.svg'; static const String factoryReset = "assets/icons/factory_reset.svg";
static const String acCooling = 'assets/icons/ac_cooling.svg'; static const String acCooling = "assets/icons/ac_cooling.svg";
static const String sceneChildLock = 'assets/icons/scene_child_lock.svg'; static const String sceneChildLock = "assets/icons/scene_child_lock.svg";
static const String celsiusDegrees = 'assets/icons/celsius_degrees.svg'; static const String celsiusDegrees = "assets/icons/celsius_degrees.svg";
static const String masterState = 'assets/icons/master_state.svg'; static const String masterState = "assets/icons/master_state.svg";
static const String acPower = 'assets/icons/ac_power.svg'; static const String acPower = "assets/icons/ac_power.svg";
static const String farDetectionFunction = static const String farDetectionFunction =
'assets/icons/far_detection_function.svg'; "assets/icons/far_detection_function.svg";
static const String nobodyTime = 'assets/icons/nobody_time.svg'; static const String nobodyTime = "assets/icons/nobody_time.svg";
// Automation functions // Automation functions
static const String tempPasswordUnlock = static const String tempPasswordUnlock =
'assets/icons/automation_functions/temp_password_unlock.svg'; "assets/icons/automation_functions/temp_password_unlock.svg";
static const String doorlockNormalOpen = static const String doorlockNormalOpen =
'assets/icons/automation_functions/doorlock_normal_open.svg'; "assets/icons/automation_functions/doorlock_normal_open.svg";
static const String doorbell = static const String doorbell =
'assets/icons/automation_functions/doorbell.svg'; "assets/icons/automation_functions/doorbell.svg";
static const String remoteUnlockViaApp = static const String remoteUnlockViaApp =
'assets/icons/automation_functions/remote_unlock_via_app.svg'; "assets/icons/automation_functions/remote_unlock_via_app.svg";
static const String doubleLock = static const String doubleLock =
'assets/icons/automation_functions/double_lock.svg'; "assets/icons/automation_functions/double_lock.svg";
static const String selfTestResult = static const String selfTestResult =
'assets/icons/automation_functions/self_test_result.svg'; "assets/icons/automation_functions/self_test_result.svg";
static const String lockAlarm = static const String lockAlarm =
'assets/icons/automation_functions/lock_alarm.svg'; "assets/icons/automation_functions/lock_alarm.svg";
static const String presenceState = static const String presenceState =
'assets/icons/automation_functions/presence_state.svg'; "assets/icons/automation_functions/presence_state.svg";
static const String currentTemp = static const String currentTemp =
'assets/icons/automation_functions/current_temp.svg'; "assets/icons/automation_functions/current_temp.svg";
static const String presence = static const String presence =
'assets/icons/automation_functions/presence.svg'; "assets/icons/automation_functions/presence.svg";
static const String residualElectricity = static const String residualElectricity =
'assets/icons/automation_functions/residual_electricity.svg'; "assets/icons/automation_functions/residual_electricity.svg";
static const String hijackAlarm = static const String hijackAlarm =
'assets/icons/automation_functions/hijack_alarm.svg'; "assets/icons/automation_functions/hijack_alarm.svg";
static const String passwordUnlock = static const String passwordUnlock =
'assets/icons/automation_functions/password_unlock.svg'; "assets/icons/automation_functions/password_unlock.svg";
static const String remoteUnlockRequest = static const String remoteUnlockRequest =
'assets/icons/automation_functions/remote_unlock_req.svg'; "assets/icons/automation_functions/remote_unlock_req.svg";
static const String cardUnlock = static const String cardUnlock =
'assets/icons/automation_functions/card_unlock.svg'; "assets/icons/automation_functions/card_unlock.svg";
static const String motion = 'assets/icons/automation_functions/motion.svg'; static const String motion = "assets/icons/automation_functions/motion.svg";
static const String fingerprintUnlock = static const String fingerprintUnlock =
'assets/icons/automation_functions/fingerprint_unlock.svg'; "assets/icons/automation_functions/fingerprint_unlock.svg";
// Presence Sensor Assets // Presence Sensor Assets
static const String sensorMotionIcon = 'assets/icons/sensor_motion_ic.svg'; static const String sensorMotionIcon = "assets/icons/sensor_motion_ic.svg";
static const String sensorPresenceIcon = static const String sensorPresenceIcon =
'assets/icons/sensor_presence_ic.svg'; "assets/icons/sensor_presence_ic.svg";
static const String sensorVacantIcon = 'assets/icons/sensor_vacant_ic.svg'; static const String sensorVacantIcon = "assets/icons/sensor_vacant_ic.svg";
static const String illuminanceRecordIcon = static const String illuminanceRecordIcon =
'assets/icons/illuminance_record_ic.svg'; "assets/icons/illuminance_record_ic.svg";
static const String presenceRecordIcon = static const String presenceRecordIcon =
'assets/icons/presence_record_ic.svg'; "assets/icons/presence_record_ic.svg";
static const String helpDescriptionIcon = static const String helpDescriptionIcon =
'assets/icons/help_description_ic.svg'; "assets/icons/help_description_ic.svg";
static const String lightPulp = 'assets/icons/light_pulb.svg'; static const String lightPulp = "assets/icons/light_pulb.svg";
static const String acDevice = 'assets/icons/ac_device.svg'; static const String acDevice = "assets/icons/ac_device.svg";
static const String acAirConditioner = 'assets/icons/ac_air.svg'; static const String acAirConditioner = "assets/icons/ac_air.svg";
static const String acSun = 'assets/icons/ac_sun.svg'; static const String acSun = "assets/icons/ac_sun.svg";
//assets/icons/3GangSwitch.svg //assets/icons/3GangSwitch.svg
static const String gangSwitch = 'assets/icons/3GangSwitch.svg'; static const String gangSwitch = "assets/icons/3GangSwitch.svg";
//assets/icons/AC.svg //assets/icons/AC.svg
static const String ac = 'assets/icons/AC.svg'; static const String ac = "assets/icons/AC.svg";
//assets/icons/Curtain.svg //assets/icons/Curtain.svg
static const String curtain = 'assets/icons/Curtain.svg'; static const String curtain = "assets/icons/Curtain.svg";
//assets/icons/doorLock.svg //assets/icons/doorLock.svg
static const String doorLock = 'assets/icons/doorLock.svg'; static const String doorLock = "assets/icons/doorLock.svg";
//assets/icons/Gateway.svg //assets/icons/Gateway.svg
static const String gateway = 'assets/icons/Gateway.svg'; static const String gateway = "assets/icons/Gateway.svg";
//assets/icons/Light.svg //assets/icons/Light.svg
static const String lightBulb = 'assets/icons/Light.svg'; static const String lightBulb = "assets/icons/Light.svg";
//assets/icons/sensors.svg //assets/icons/sensors.svg
static const String sensors = 'assets/icons/sensors.svg'; static const String sensors = "assets/icons/sensors.svg";
//assets/icons/door_un_look_ic.svg //assets/icons/door_un_look_ic.svg
static const String doorUnlock = 'assets/icons/door_un_look_ic.svg'; static const String doorUnlock = 'assets/icons/door_un_look_ic.svg';
@ -175,7 +175,7 @@ class Assets {
static const String Gang1SwitchIcon = 'assets/icons/1_Gang_switch_icon.svg'; static const String Gang1SwitchIcon = 'assets/icons/1_Gang_switch_icon.svg';
static const String DoorLockIcon = 'assets/icons/door_lock.svg'; static const String DoorLockIcon = 'assets/icons/door_lock.svg';
static const String SmartGatewayIcon = 'assets/icons/smart_gateway_icon.svg'; static const String SmartGatewayIcon = 'assets/icons/smart_gateway_icon.svg';
static const String curtainIcon = 'assets/images/curtain.svg'; static const String curtainIcon = "assets/images/curtain.svg";
static const String unlock = 'assets/icons/unlock_ic.svg'; static const String unlock = 'assets/icons/unlock_ic.svg';
static const String firmware = 'assets/icons/firmware.svg'; static const String firmware = 'assets/icons/firmware.svg';
//assets/images/scheduling.svg //assets/images/scheduling.svg
@ -227,12 +227,12 @@ class Assets {
//assets/icons/2gang.svg //assets/icons/2gang.svg
static const String twoGang = 'assets/icons/2gang.svg'; static const String twoGang = 'assets/icons/2gang.svg';
static const String frequencyIcon = 'assets/icons/frequency_icon.svg'; static const String frequencyIcon = "assets/icons/frequency_icon.svg";
static const String voltMeterIcon = 'assets/icons/volt_meter_icon.svg'; static const String voltMeterIcon = "assets/icons/volt_meter_icon.svg";
static const String powerActiveIcon = 'assets/icons/power_active_icon.svg'; static const String powerActiveIcon = "assets/icons/power_active_icon.svg";
static const String searchIcon = 'assets/icons/search_icon.svg'; static const String searchIcon = "assets/icons/search_icon.svg";
static const String voltageIcon = 'assets/icons/voltage_icon.svg'; static const String voltageIcon = "assets/icons/voltage_icon.svg";
static const String speedoMeter = 'assets/icons/speedo_meter.svg'; static const String speedoMeter = "assets/icons/speedo_meter.svg";
//assets/icons/account_setting.svg //assets/icons/account_setting.svg
static const String accountSetting = 'assets/icons/account_setting.svg'; static const String accountSetting = 'assets/icons/account_setting.svg';
@ -284,99 +284,99 @@ class Assets {
// Assets for functions_icons // Assets for functions_icons
static const String assetsSensitivityFunction = static const String assetsSensitivityFunction =
'assets/icons/functions_icons/sensitivity.svg'; "assets/icons/functions_icons/sensitivity.svg";
static const String assetsSensitivityOperationIcon = static const String assetsSensitivityOperationIcon =
'assets/icons/functions_icons/sesitivity_operation_icon.svg'; "assets/icons/functions_icons/sesitivity_operation_icon.svg";
static const String assetsAcPower = static const String assetsAcPower =
'assets/icons/functions_icons/ac_power.svg'; "assets/icons/functions_icons/ac_power.svg";
static const String assetsAcPowerOFF = static const String assetsAcPowerOFF =
'assets/icons/functions_icons/ac_power_off.svg'; "assets/icons/functions_icons/ac_power_off.svg";
static const String assetsChildLock = static const String assetsChildLock =
'assets/icons/functions_icons/child_lock.svg'; "assets/icons/functions_icons/child_lock.svg";
static const String assetsFreezing = static const String assetsFreezing =
'assets/icons/functions_icons/freezing.svg'; "assets/icons/functions_icons/freezing.svg";
static const String assetsFanSpeed = static const String assetsFanSpeed =
'assets/icons/functions_icons/fan_speed.svg'; "assets/icons/functions_icons/fan_speed.svg";
static const String assetsAcCooling = static const String assetsAcCooling =
'assets/icons/functions_icons/ac_cooling.svg'; "assets/icons/functions_icons/ac_cooling.svg";
static const String assetsAcHeating = static const String assetsAcHeating =
'assets/icons/functions_icons/ac_heating.svg'; "assets/icons/functions_icons/ac_heating.svg";
static const String assetsCelsiusDegrees = static const String assetsCelsiusDegrees =
'assets/icons/functions_icons/celsius_degrees.svg'; "assets/icons/functions_icons/celsius_degrees.svg";
static const String assetsTempreture = static const String assetsTempreture =
'assets/icons/functions_icons/tempreture.svg'; "assets/icons/functions_icons/tempreture.svg";
static const String assetsAcFanLow = static const String assetsAcFanLow =
'assets/icons/functions_icons/ac_fan_low.svg'; "assets/icons/functions_icons/ac_fan_low.svg";
static const String assetsAcFanMiddle = static const String assetsAcFanMiddle =
'assets/icons/functions_icons/ac_fan_middle.svg'; "assets/icons/functions_icons/ac_fan_middle.svg";
static const String assetsAcFanHigh = static const String assetsAcFanHigh =
'assets/icons/functions_icons/ac_fan_high.svg'; "assets/icons/functions_icons/ac_fan_high.svg";
static const String assetsAcFanAuto = static const String assetsAcFanAuto =
'assets/icons/functions_icons/ac_fan_auto.svg'; "assets/icons/functions_icons/ac_fan_auto.svg";
static const String assetsSceneChildLock = static const String assetsSceneChildLock =
'assets/icons/functions_icons/scene_child_lock.svg'; "assets/icons/functions_icons/scene_child_lock.svg";
static const String assetsSceneChildUnlock = static const String assetsSceneChildUnlock =
'assets/icons/functions_icons/scene_child_unlock.svg'; "assets/icons/functions_icons/scene_child_unlock.svg";
static const String assetsSceneRefresh = static const String assetsSceneRefresh =
'assets/icons/functions_icons/scene_refresh.svg'; "assets/icons/functions_icons/scene_refresh.svg";
static const String assetsLightCountdown = static const String assetsLightCountdown =
'assets/icons/functions_icons/light_countdown.svg'; "assets/icons/functions_icons/light_countdown.svg";
static const String assetsFarDetection = static const String assetsFarDetection =
'assets/icons/functions_icons/far_detection.svg'; "assets/icons/functions_icons/far_detection.svg";
static const String assetsFarDetectionFunction = static const String assetsFarDetectionFunction =
'assets/icons/functions_icons/far_detection_function.svg'; "assets/icons/functions_icons/far_detection_function.svg";
static const String assetsIndicator = static const String assetsIndicator =
'assets/icons/functions_icons/indicator.svg'; "assets/icons/functions_icons/indicator.svg";
static const String assetsMotionDetection = static const String assetsMotionDetection =
'assets/icons/functions_icons/motion_detection.svg'; "assets/icons/functions_icons/motion_detection.svg";
static const String assetsMotionlessDetection = static const String assetsMotionlessDetection =
'assets/icons/functions_icons/motionless_detection.svg'; "assets/icons/functions_icons/motionless_detection.svg";
static const String assetsNobodyTime = static const String assetsNobodyTime =
'assets/icons/functions_icons/nobody_time.svg'; "assets/icons/functions_icons/nobody_time.svg";
static const String assetsFactoryReset = static const String assetsFactoryReset =
'assets/icons/functions_icons/factory_reset.svg'; "assets/icons/functions_icons/factory_reset.svg";
static const String assetsMasterState = static const String assetsMasterState =
'assets/icons/functions_icons/master_state.svg'; "assets/icons/functions_icons/master_state.svg";
static const String assetsSwitchAlarmSound = static const String assetsSwitchAlarmSound =
'assets/icons/functions_icons/switch_alarm_sound.svg'; "assets/icons/functions_icons/switch_alarm_sound.svg";
static const String assetsResetOff = static const String assetsResetOff =
'assets/icons/functions_icons/reset_off.svg'; "assets/icons/functions_icons/reset_off.svg";
// Assets for automation_functions // Assets for automation_functions
static const String assetsCardUnlock = static const String assetsCardUnlock =
'assets/icons/functions_icons/automation_functions/card_unlock.svg'; "assets/icons/functions_icons/automation_functions/card_unlock.svg";
static const String assetsDoorbell = static const String assetsDoorbell =
'assets/icons/functions_icons/automation_functions/doorbell.svg'; "assets/icons/functions_icons/automation_functions/doorbell.svg";
static const String assetsDoorlockNormalOpen = static const String assetsDoorlockNormalOpen =
'assets/icons/functions_icons/automation_functions/doorlock_normal_open.svg'; "assets/icons/functions_icons/automation_functions/doorlock_normal_open.svg";
static const String assetsDoubleLock = static const String assetsDoubleLock =
'assets/icons/functions_icons/automation_functions/double_lock.svg'; "assets/icons/functions_icons/automation_functions/double_lock.svg";
static const String assetsFingerprintUnlock = static const String assetsFingerprintUnlock =
'assets/icons/functions_icons/automation_functions/fingerprint_unlock.svg'; "assets/icons/functions_icons/automation_functions/fingerprint_unlock.svg";
static const String assetsHijackAlarm = static const String assetsHijackAlarm =
'assets/icons/functions_icons/automation_functions/hijack_alarm.svg'; "assets/icons/functions_icons/automation_functions/hijack_alarm.svg";
static const String assetsLockAlarm = static const String assetsLockAlarm =
'assets/icons/functions_icons/automation_functions/lock_alarm.svg'; "assets/icons/functions_icons/automation_functions/lock_alarm.svg";
static const String assetsPasswordUnlock = static const String assetsPasswordUnlock =
'assets/icons/functions_icons/automation_functions/password_unlock.svg'; "assets/icons/functions_icons/automation_functions/password_unlock.svg";
static const String assetsRemoteUnlockReq = static const String assetsRemoteUnlockReq =
'assets/icons/functions_icons/automation_functions/remote_unlock_req.svg'; "assets/icons/functions_icons/automation_functions/remote_unlock_req.svg";
static const String assetsRemoteUnlockViaApp = static const String assetsRemoteUnlockViaApp =
'assets/icons/functions_icons/automation_functions/remote_unlock_via_app.svg'; "assets/icons/functions_icons/automation_functions/remote_unlock_via_app.svg";
static const String assetsResidualElectricity = static const String assetsResidualElectricity =
'assets/icons/functions_icons/automation_functions/residual_electricity.svg'; "assets/icons/functions_icons/automation_functions/residual_electricity.svg";
static const String assetsTempPasswordUnlock = static const String assetsTempPasswordUnlock =
'assets/icons/functions_icons/automation_functions/temp_password_unlock.svg'; "assets/icons/functions_icons/automation_functions/temp_password_unlock.svg";
static const String assetsSelfTestResult = static const String assetsSelfTestResult =
'assets/icons/functions_icons/automation_functions/self_test_result.svg'; "assets/icons/functions_icons/automation_functions/self_test_result.svg";
static const String assetsPresence = static const String assetsPresence =
'assets/icons/functions_icons/automation_functions/presence.svg'; "assets/icons/functions_icons/automation_functions/presence.svg";
static const String assetsMotion = static const String assetsMotion =
'assets/icons/functions_icons/automation_functions/motion.svg'; "assets/icons/functions_icons/automation_functions/motion.svg";
static const String assetsCurrentTemp = static const String assetsCurrentTemp =
'assets/icons/functions_icons/automation_functions/current_temp.svg'; "assets/icons/functions_icons/automation_functions/current_temp.svg";
static const String assetsPresenceState = static const String assetsPresenceState =
'assets/icons/functions_icons/automation_functions/presence_state.svg'; "assets/icons/functions_icons/automation_functions/presence_state.svg";
//assets/icons/routine/automation.svg //assets/icons/routine/automation.svg
static const String automation = 'assets/icons/routine/automation.svg'; static const String automation = 'assets/icons/routine/automation.svg';
static const String searchIconUser = 'assets/icons/search_icon_user.svg'; static const String searchIconUser = 'assets/icons/search_icon_user.svg';
@ -501,5 +501,4 @@ class Assets {
static const String aqiAirQuality = 'assets/icons/aqi_air_quality.svg'; static const String aqiAirQuality = 'assets/icons/aqi_air_quality.svg';
static const String temperatureAqiSidebar = 'assets/icons/thermometer.svg'; static const String temperatureAqiSidebar = 'assets/icons/thermometer.svg';
static const String humidityAqiSidebar = 'assets/icons/humidity.svg'; static const String humidityAqiSidebar = 'assets/icons/humidity.svg';
static const String autocadOccupancyImage = 'assets/images/autocad_occupancy_image.png';
} }