Compare commits

..

32 Commits

Author SHA1 Message Date
132cafcaa2 Enhanced the side tree design 2025-02-05 11:52:44 +03:00
572520eed5 Fixed issues 2025-02-04 01:54:18 +03:00
506531e16a Fetch devices based on selection 2025-02-03 11:15:36 +03:00
5ae07688cb fixed issues in the space tree 2025-02-02 02:46:13 +03:00
6e546a4831 Merged with dev 2025-01-30 12:17:06 +03:00
b098202fd8 Merge pull request #74 from SyncrowIOT/bugfix/subspace-name-validatio
add subspace validation
2025-01-30 13:13:10 +04:00
a294988558 add subspace and space information 2025-01-30 10:09:21 +04:00
43c17d1c18 Implemented the selection behavior of the side tree 2025-01-30 04:03:54 +03:00
09c1a785e5 add subspace validation 2025-01-29 22:03:56 +04:00
e70b9ea9e2 Merge pull request #73 from SyncrowIOT/bugfix/edit-space
Bugfix/edit space
2025-01-29 12:46:19 +04:00
c173df934d added all tags 2025-01-29 12:24:12 +04:00
e4262d08a5 fixed ok button 2025-01-29 11:40:53 +04:00
4bae7bb9fb fixed update space 2025-01-29 11:14:12 +04:00
073916d4ac updated text theme 2025-01-29 10:21:06 +04:00
8870467fe4 fixed the edit flow of space 2025-01-29 09:56:10 +04:00
9331193e90 added tags and subspace to update space 2025-01-29 09:55:36 +04:00
45b0b67fe0 make counter widget work only for add device 2025-01-29 09:53:06 +04:00
9e0184f19d Merge pull request #72 from SyncrowIOT/bugfix/edit-subspace
Bugfix/edit-subspace
2025-01-28 13:30:03 +04:00
ea5b6597f5 Merge pull request #71 from SyncrowIOT/feat/update-create-edit-space
Feat/update-create-edit-space
2025-01-27 17:02:01 +04:00
2221d9ae7b Merged with dev 2025-01-26 21:00:05 +03:00
921d352207 Merge pull request #70 from SyncrowIOT/chore/remove-unsupported-param
Chore/remove unsupported param
2025-01-23 10:46:23 +04:00
c5871be990 removed unmatched alignment 2025-01-23 10:21:31 +04:00
2fb6f30ccb Updated pubsepc file 2025-01-23 01:21:13 +03:00
508d8bbaa8 Merged with dev 2025-01-23 01:18:30 +03:00
97bdb1bbb7 Merge pull request #68 from SyncrowIOT/user_agreement_privacy
user_agreement_dialog
2025-01-23 00:40:08 +03:00
7ce0a27af0 Merge pull request #69 from SyncrowIOT/bugfix/space-edit
Bugfix/space edit
2025-01-23 00:37:03 +03:00
bc4af6a237 user_agreement 2025-01-22 17:24:19 +03:00
513175ed1e user_agreement_dialog 2025-01-22 15:44:46 +03:00
5060d2a66d Updated side tree branch 2025-01-21 15:28:59 +03:00
540f569b1f Added spaces devices 2025-01-05 00:21:10 +03:00
a98f7e77a3 Implemented side tree to devices and rountines screen 2025-01-04 17:45:15 +03:00
0341844ea9 SP-859 2024-12-26 12:25:37 +03:00
121 changed files with 3030 additions and 1611 deletions

View File

@ -46,14 +46,13 @@ class CustomSearchBar extends StatelessWidget {
filled: true, filled: true,
fillColor: ColorsManager.textFieldGreyColor, fillColor: ColorsManager.textFieldGreyColor,
hintText: hintText, hintText: hintText,
hintStyle: TextStyle( hintStyle: Theme.of(context).textTheme.bodyLarge!.copyWith(
color: Color(0xB2999999), color: ColorsManager.lightGrayColor,
fontSize: 12, fontSize: 12,
fontFamily: 'Aftika', fontWeight: FontWeight.w400,
fontWeight: FontWeight.w400, height: 0,
height: 0, letterSpacing: -0.24,
letterSpacing: -0.24, ),
),
suffixIcon: Padding( suffixIcon: Padding(
padding: const EdgeInsets.only(right: 16), padding: const EdgeInsets.only(right: 16),
child: SvgPicture.asset( child: SvgPicture.asset(

View File

@ -0,0 +1,25 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart';
class SpacesSideTree extends StatefulWidget {
final List<CommunityModel> communities;
final String? selectedSpaceUuid;
const SpacesSideTree({
super.key,
required this.communities,
this.selectedSpaceUuid,
});
@override
State<SpacesSideTree> createState() => _SpacesSideTreeState();
}
class _SpacesSideTreeState extends State<SpacesSideTree> {
String _searchQuery = '';
String? _selectedSpaceUuid;
String? _selectedId;
@override
Widget build(BuildContext context) {
return const Placeholder();
}
}

View File

@ -6,7 +6,9 @@ import 'package:go_router/go_router.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/home/bloc/home_event.dart';
import 'package:syncrow_web/pages/routiens/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_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';
@ -15,8 +17,7 @@ import 'package:syncrow_web/utils/theme/theme.dart';
Future<void> main() async { Future<void> main() async {
try { try {
const environment = const environment = String.fromEnvironment('FLAVOR', defaultValue: 'development');
String.fromEnvironment('FLAVOR', defaultValue: 'development');
await dotenv.load(fileName: '.env.$environment'); await dotenv.load(fileName: '.env.$environment');
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
initialSetup(); initialSetup();
@ -48,14 +49,16 @@ class MyApp extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MultiBlocProvider( return MultiBlocProvider(
providers: [ providers: [
BlocProvider( BlocProvider(create: (context) => HomeBloc()..add(const FetchUserInfo())),
create: (context) => HomeBloc()..add(const FetchUserInfo())),
BlocProvider<VisitorPasswordBloc>( BlocProvider<VisitorPasswordBloc>(
create: (context) => VisitorPasswordBloc(), create: (context) => VisitorPasswordBloc(),
), ),
BlocProvider<RoutineBloc>( BlocProvider<RoutineBloc>(
create: (context) => RoutineBloc(), create: (context) => RoutineBloc(),
), ),
BlocProvider<SpaceTreeBloc>(
create: (context) => SpaceTreeBloc()..add(InitialEvent()),
),
], ],
child: MaterialApp.router( child: MaterialApp.router(
debugShowCheckedModeBanner: false, debugShowCheckedModeBanner: false,

View File

@ -10,6 +10,10 @@ class UserModel {
final String? phoneNumber; final String? phoneNumber;
final bool? isEmailVerified; final bool? isEmailVerified;
final bool? isAgreementAccepted; final bool? isAgreementAccepted;
final bool? hasAcceptedWebAgreement;
final DateTime? webAgreementAcceptedAt;
final UserRole? role;
UserModel({ UserModel({
required this.uuid, required this.uuid,
required this.email, required this.email,
@ -19,6 +23,9 @@ class UserModel {
required this.phoneNumber, required this.phoneNumber,
required this.isEmailVerified, required this.isEmailVerified,
required this.isAgreementAccepted, required this.isAgreementAccepted,
required this.hasAcceptedWebAgreement,
required this.webAgreementAcceptedAt,
required this.role,
}); });
factory UserModel.fromJson(Map<String, dynamic> json) { factory UserModel.fromJson(Map<String, dynamic> json) {
@ -31,6 +38,11 @@ class UserModel {
phoneNumber: json['phoneNumber'], phoneNumber: json['phoneNumber'],
isEmailVerified: json['isEmailVerified'], isEmailVerified: json['isEmailVerified'],
isAgreementAccepted: json['isAgreementAccepted'], isAgreementAccepted: json['isAgreementAccepted'],
hasAcceptedWebAgreement: json['hasAcceptedWebAgreement'],
webAgreementAcceptedAt: json['webAgreementAcceptedAt'] != null
? DateTime.parse(json['webAgreementAcceptedAt'])
: null,
role: json['role'] != null ? UserRole.fromJson(json['role']) : null,
); );
} }
@ -41,6 +53,9 @@ class UserModel {
Map<String, dynamic> tempJson = Token.decodeToken(token.accessToken); Map<String, dynamic> tempJson = Token.decodeToken(token.accessToken);
return UserModel( return UserModel(
hasAcceptedWebAgreement: null,
role: null,
webAgreementAcceptedAt: null,
uuid: tempJson['uuid'].toString(), uuid: tempJson['uuid'].toString(),
email: tempJson['email'], email: tempJson['email'],
firstName: null, firstName: null,
@ -65,3 +80,26 @@ class UserModel {
}; };
} }
} }
class UserRole {
final String uuid;
final DateTime createdAt;
final DateTime updatedAt;
final String type;
UserRole({
required this.uuid,
required this.createdAt,
required this.updatedAt,
required this.type,
});
factory UserRole.fromJson(Map<String, dynamic> json) {
return UserRole(
uuid: json['uuid'],
createdAt: DateTime.parse(json['createdAt']),
updatedAt: DateTime.parse(json['updatedAt']),
type: json['type'],
);
}
}

View File

@ -1,13 +1,14 @@
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
import 'package:syncrow_web/services/devices_mang_api.dart'; import 'package:syncrow_web/services/devices_mang_api.dart';
part 'device_managment_event.dart'; part 'device_managment_event.dart';
part 'device_managment_state.dart'; part 'device_managment_state.dart';
class DeviceManagementBloc class DeviceManagementBloc extends Bloc<DeviceManagementEvent, DeviceManagementState> {
extends Bloc<DeviceManagementEvent, DeviceManagementState> {
int _selectedIndex = 0; int _selectedIndex = 0;
List<AllDevicesModel> _devices = []; List<AllDevicesModel> _devices = [];
int _onlineCount = 0; int _onlineCount = 0;
@ -30,11 +31,23 @@ class DeviceManagementBloc
on<UpdateSelection>(_onUpdateSelection); on<UpdateSelection>(_onUpdateSelection);
} }
Future<void> _onFetchDevices( Future<void> _onFetchDevices(FetchDevices event, Emitter<DeviceManagementState> emit) async {
FetchDevices event, Emitter<DeviceManagementState> emit) async {
emit(DeviceManagementLoading()); emit(DeviceManagementLoading());
try { try {
final devices = await DevicesManagementApi().fetchDevices(); List<AllDevicesModel> devices = [];
_devices.clear();
var spaceBloc = event.context.read<SpaceTreeBloc>();
if (spaceBloc.state.selectedCommunities.isEmpty) {
devices = await DevicesManagementApi().fetchDevices('', '');
} else {
for (var community in spaceBloc.state.selectedCommunities) {
List<String> spacesList = spaceBloc.state.selectedCommunityAndSpaces[community] ?? [];
for (var space in spacesList) {
devices.addAll(await DevicesManagementApi().fetchDevices(community, space));
}
}
}
_selectedDevices.clear(); _selectedDevices.clear();
_devices = devices; _devices = devices;
_filteredDevices = devices; _filteredDevices = devices;
@ -53,8 +66,7 @@ class DeviceManagementBloc
} }
} }
void _onFilterDevices( void _onFilterDevices(FilterDevices event, Emitter<DeviceManagementState> emit) async {
FilterDevices event, Emitter<DeviceManagementState> emit) async {
if (_devices.isNotEmpty) { if (_devices.isNotEmpty) {
_filteredDevices = List.from(_devices.where((device) { _filteredDevices = List.from(_devices.where((device) {
switch (event.filter) { switch (event.filter) {
@ -85,8 +97,7 @@ class DeviceManagementBloc
} }
} }
Future<void> _onResetFilters( Future<void> _onResetFilters(ResetFilters event, Emitter<DeviceManagementState> emit) async {
ResetFilters event, Emitter<DeviceManagementState> emit) async {
currentProductName = ''; currentProductName = '';
_selectedDevices.clear(); _selectedDevices.clear();
_filteredDevices = List.from(_devices); _filteredDevices = List.from(_devices);
@ -102,8 +113,7 @@ class DeviceManagementBloc
)); ));
} }
void _onResetSelectedDevices( void _onResetSelectedDevices(ResetSelectedDevices event, Emitter<DeviceManagementState> emit) {
ResetSelectedDevices event, Emitter<DeviceManagementState> emit) {
_selectedDevices.clear(); _selectedDevices.clear();
if (state is DeviceManagementLoaded) { if (state is DeviceManagementLoaded) {
@ -129,14 +139,12 @@ class DeviceManagementBloc
} }
} }
void _onSelectedFilterChanged( void _onSelectedFilterChanged(SelectedFilterChanged event, Emitter<DeviceManagementState> emit) {
SelectedFilterChanged event, Emitter<DeviceManagementState> emit) {
_selectedIndex = event.selectedIndex; _selectedIndex = event.selectedIndex;
add(FilterDevices(_getFilterFromIndex(_selectedIndex))); add(FilterDevices(_getFilterFromIndex(_selectedIndex)));
} }
void _onSelectDevice( void _onSelectDevice(SelectDevice event, Emitter<DeviceManagementState> emit) {
SelectDevice event, Emitter<DeviceManagementState> emit) {
final selectedUuid = event.selectedDevice.uuid; final selectedUuid = event.selectedDevice.uuid;
if (_selectedDevices.any((device) => device.uuid == selectedUuid)) { if (_selectedDevices.any((device) => device.uuid == selectedUuid)) {
@ -147,8 +155,7 @@ class DeviceManagementBloc
List<AllDevicesModel> clonedSelectedDevices = List.from(_selectedDevices); List<AllDevicesModel> clonedSelectedDevices = List.from(_selectedDevices);
bool isControlButtonEnabled = bool isControlButtonEnabled = _checkIfControlButtonEnabled(clonedSelectedDevices);
_checkIfControlButtonEnabled(clonedSelectedDevices);
if (state is DeviceManagementLoaded) { if (state is DeviceManagementLoaded) {
emit(DeviceManagementLoaded( emit(DeviceManagementLoaded(
@ -157,8 +164,7 @@ class DeviceManagementBloc
onlineCount: _onlineCount, onlineCount: _onlineCount,
offlineCount: _offlineCount, offlineCount: _offlineCount,
lowBatteryCount: _lowBatteryCount, lowBatteryCount: _lowBatteryCount,
selectedDevice: selectedDevice: clonedSelectedDevices.isNotEmpty ? clonedSelectedDevices : null,
clonedSelectedDevices.isNotEmpty ? clonedSelectedDevices : null,
isControlButtonEnabled: isControlButtonEnabled, isControlButtonEnabled: isControlButtonEnabled,
)); ));
} else if (state is DeviceManagementFiltered) { } else if (state is DeviceManagementFiltered) {
@ -168,15 +174,13 @@ class DeviceManagementBloc
onlineCount: _onlineCount, onlineCount: _onlineCount,
offlineCount: _offlineCount, offlineCount: _offlineCount,
lowBatteryCount: _lowBatteryCount, lowBatteryCount: _lowBatteryCount,
selectedDevice: selectedDevice: clonedSelectedDevices.isNotEmpty ? clonedSelectedDevices : null,
clonedSelectedDevices.isNotEmpty ? clonedSelectedDevices : null,
isControlButtonEnabled: isControlButtonEnabled, isControlButtonEnabled: isControlButtonEnabled,
)); ));
} }
} }
void _onUpdateSelection( void _onUpdateSelection(UpdateSelection event, Emitter<DeviceManagementState> emit) {
UpdateSelection event, Emitter<DeviceManagementState> emit) {
List<AllDevicesModel> selectedDevices = []; List<AllDevicesModel> selectedDevices = [];
List<AllDevicesModel> devicesToSelectFrom = []; List<AllDevicesModel> devicesToSelectFrom = [];
@ -219,8 +223,7 @@ class DeviceManagementBloc
bool _checkIfControlButtonEnabled(List<AllDevicesModel> selectedDevices) { bool _checkIfControlButtonEnabled(List<AllDevicesModel> selectedDevices) {
if (selectedDevices.length > 1) { if (selectedDevices.length > 1) {
final productTypes = final productTypes = selectedDevices.map((device) => device.productType).toSet();
selectedDevices.map((device) => device.productType).toSet();
return productTypes.length == 1; return productTypes.length == 1;
} else if (selectedDevices.length == 1) { } else if (selectedDevices.length == 1) {
return true; return true;
@ -231,10 +234,8 @@ class DeviceManagementBloc
void _calculateDeviceCounts() { void _calculateDeviceCounts() {
_onlineCount = _devices.where((device) => device.online == true).length; _onlineCount = _devices.where((device) => device.online == true).length;
_offlineCount = _devices.where((device) => device.online == false).length; _offlineCount = _devices.where((device) => device.online == false).length;
_lowBatteryCount = _devices _lowBatteryCount =
.where((device) => _devices.where((device) => device.batteryLevel != null && device.batteryLevel! < 20).length;
device.batteryLevel != null && device.batteryLevel! < 20)
.length;
} }
String _getFilterFromIndex(int index) { String _getFilterFromIndex(int index) {
@ -250,8 +251,7 @@ class DeviceManagementBloc
} }
} }
void _onSearchDevices( void _onSearchDevices(SearchDevices event, Emitter<DeviceManagementState> emit) {
SearchDevices event, Emitter<DeviceManagementState> emit) {
if ((event.community == null || event.community!.isEmpty) && if ((event.community == null || event.community!.isEmpty) &&
(event.unitName == null || event.unitName!.isEmpty) && (event.unitName == null || event.unitName!.isEmpty) &&
(event.productName == null || event.productName!.isEmpty)) { (event.productName == null || event.productName!.isEmpty)) {
@ -280,33 +280,22 @@ class DeviceManagementBloc
final filteredDevices = devicesToSearch.where((device) { final filteredDevices = devicesToSearch.where((device) {
final matchesCommunity = event.community == null || final matchesCommunity = event.community == null ||
event.community!.isEmpty || event.community!.isEmpty ||
(device.community?.name (device.community?.name?.toLowerCase().contains(event.community!.toLowerCase()) ??
?.toLowerCase()
.contains(event.community!.toLowerCase()) ??
false); false);
final matchesUnit = event.unitName == null || final matchesUnit = event.unitName == null ||
event.unitName!.isEmpty || event.unitName!.isEmpty ||
(device.spaces != null && (device.spaces != null &&
device.spaces!.isNotEmpty && device.spaces!.isNotEmpty &&
device.spaces![0].spaceName device.spaces![0].spaceName!.toLowerCase().contains(event.unitName!.toLowerCase()));
!.toLowerCase()
.contains(event.unitName!.toLowerCase()));
final matchesProductName = event.productName == null || final matchesProductName = event.productName == null ||
event.productName!.isEmpty || event.productName!.isEmpty ||
(device.name (device.name?.toLowerCase().contains(event.productName!.toLowerCase()) ?? false);
?.toLowerCase()
.contains(event.productName!.toLowerCase()) ??
false);
final matchesDeviceName = event.productName == null || final matchesDeviceName = event.productName == null ||
event.productName!.isEmpty || event.productName!.isEmpty ||
(device.categoryName (device.categoryName?.toLowerCase().contains(event.productName!.toLowerCase()) ??
?.toLowerCase()
.contains(event.productName!.toLowerCase()) ??
false); false);
return matchesCommunity && return matchesCommunity && matchesUnit && (matchesProductName || matchesDeviceName);
matchesUnit &&
(matchesProductName || matchesDeviceName);
}).toList(); }).toList();
emit(DeviceManagementFiltered( emit(DeviceManagementFiltered(

View File

@ -7,7 +7,15 @@ abstract class DeviceManagementEvent extends Equatable {
List<Object?> get props => []; List<Object?> get props => [];
} }
class FetchDevices extends DeviceManagementEvent {} class FetchDevices extends DeviceManagementEvent {
// final Map<String, List<String>> selectedCommunitiesSpaces;
// final String spaceId;
final BuildContext context;
const FetchDevices(this.context);
@override
List<Object?> get props => [context];
}
class FilterDevices extends DeviceManagementEvent { class FilterDevices extends DeviceManagementEvent {
final String filter; final String filter;

View File

@ -0,0 +1,47 @@
class DeviceSubspace {
final String uuid;
final DateTime? createdAt;
final DateTime? updatedAt;
final String subspaceName;
final bool disabled;
DeviceSubspace({
required this.uuid,
this.createdAt,
this.updatedAt,
required this.subspaceName,
required this.disabled,
});
factory DeviceSubspace.fromJson(Map<String, dynamic> json) {
return DeviceSubspace(
uuid: json['uuid'] as String,
createdAt: json['createdAt'] != null
? DateTime.tryParse(json['createdAt'].toString())
: null,
updatedAt: json['updatedAt'] != null
? DateTime.tryParse(json['updatedAt'].toString())
: null,
subspaceName: json['subspaceName'] as String,
disabled: json['disabled'] as bool,
);
}
Map<String, dynamic> toJson() {
return {
'uuid': uuid,
'createdAt': createdAt?.toIso8601String(),
'updatedAt': updatedAt?.toIso8601String(),
'subspaceName': subspaceName,
'disabled': disabled,
};
}
static List<DeviceSubspace> listFromJson(List<dynamic> jsonList) {
return jsonList.map((json) => DeviceSubspace.fromJson(json)).toList();
}
static List<Map<String, dynamic>> listToJson(List<DeviceSubspace> subspaces) {
return subspaces.map((subspace) => subspace.toJson()).toList();
}
}

View File

@ -1,12 +1,13 @@
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_community.model.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/device_community.model.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_space_model.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/device_space_model.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_subspace.model.dart';
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/routiens/models/ac/ac_function.dart'; import 'package:syncrow_web/pages/routines/models/ac/ac_function.dart';
import 'package:syncrow_web/pages/routiens/models/device_functions.dart'; import 'package:syncrow_web/pages/routines/models/device_functions.dart';
import 'package:syncrow_web/pages/routiens/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';
import 'package:syncrow_web/pages/routiens/models/gang_switches/three_gang_switch/three_gang_switch.dart'; import 'package:syncrow_web/pages/routines/models/gang_switches/three_gang_switch/three_gang_switch.dart';
import 'package:syncrow_web/pages/routiens/models/gang_switches/two_gang_switch/two_gang_switch.dart'; import 'package:syncrow_web/pages/routines/models/gang_switches/two_gang_switch/two_gang_switch.dart';
import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/enum/device_types.dart'; import 'package:syncrow_web/utils/enum/device_types.dart';
@ -47,6 +48,7 @@ class AllDevicesModel {
*/ */
DevicesModelRoom? room; DevicesModelRoom? room;
DeviceSubspace? subspace;
DevicesModelUnit? unit; DevicesModelUnit? unit;
DeviceCommunityModel? community; DeviceCommunityModel? community;
String? productUuid; String? productUuid;
@ -77,6 +79,7 @@ class AllDevicesModel {
AllDevicesModel({ AllDevicesModel({
this.room, this.room,
this.subspace,
this.unit, this.unit,
this.community, this.community,
this.productUuid, this.productUuid,
@ -110,6 +113,9 @@ class AllDevicesModel {
room = (json['room'] != null && (json['room'] is Map)) room = (json['room'] != null && (json['room'] is Map))
? DevicesModelRoom.fromJson(json['room']) ? DevicesModelRoom.fromJson(json['room'])
: null; : null;
subspace = (json['subspace'] != null && (json['subspace'] is Map))
? DeviceSubspace.fromJson(json['subspace'])
: null;
unit = (json['unit'] != null && (json['unit'] is Map)) unit = (json['unit'] != null && (json['unit'] is Map))
? DevicesModelUnit.fromJson(json['unit']) ? DevicesModelUnit.fromJson(json['unit'])
: null; : null;
@ -142,9 +148,7 @@ class AllDevicesModel {
productName = json['productName']?.toString(); productName = json['productName']?.toString();
if (json['spaces'] != null && json['spaces'] is List) { if (json['spaces'] != null && json['spaces'] is List) {
spaces = (json['spaces'] as List) spaces = (json['spaces'] as List).map((space) => DeviceSpaceModel.fromJson(space)).toList();
.map((space) => DeviceSpaceModel.fromJson(space))
.toList();
} }
} }
@ -192,8 +196,7 @@ SOS
String tempIcon = ''; String tempIcon = '';
if (type == DeviceType.LightBulb) { if (type == DeviceType.LightBulb) {
tempIcon = Assets.lightBulb; tempIcon = Assets.lightBulb;
} else if (type == DeviceType.CeilingSensor || } else if (type == DeviceType.CeilingSensor || type == DeviceType.WallSensor) {
type == DeviceType.WallSensor) {
tempIcon = Assets.sensors; tempIcon = Assets.sensors;
} else if (type == DeviceType.AC) { } else if (type == DeviceType.AC) {
tempIcon = Assets.ac; tempIcon = Assets.ac;
@ -248,34 +251,25 @@ SOS
case '1G': case '1G':
return [ return [
OneGangSwitchFunction(deviceId: uuid ?? '', deviceName: name ?? ''), OneGangSwitchFunction(deviceId: uuid ?? '', deviceName: name ?? ''),
OneGangCountdownFunction( OneGangCountdownFunction(deviceId: uuid ?? '', deviceName: name ?? ''),
deviceId: uuid ?? '', deviceName: name ?? ''),
]; ];
case '2G': case '2G':
return [ return [
TwoGangSwitch1Function(deviceId: uuid ?? '', deviceName: name ?? ''), TwoGangSwitch1Function(deviceId: uuid ?? '', deviceName: name ?? ''),
TwoGangSwitch2Function(deviceId: uuid ?? '', deviceName: name ?? ''), TwoGangSwitch2Function(deviceId: uuid ?? '', deviceName: name ?? ''),
TwoGangCountdown1Function( TwoGangCountdown1Function(deviceId: uuid ?? '', deviceName: name ?? ''),
deviceId: uuid ?? '', deviceName: name ?? ''), TwoGangCountdown2Function(deviceId: uuid ?? '', deviceName: name ?? ''),
TwoGangCountdown2Function(
deviceId: uuid ?? '', deviceName: name ?? ''),
]; ];
case '3G': case '3G':
return [ return [
ThreeGangSwitch1Function( ThreeGangSwitch1Function(deviceId: uuid ?? '', deviceName: name ?? ''),
deviceId: uuid ?? '', deviceName: name ?? ''), ThreeGangSwitch2Function(deviceId: uuid ?? '', deviceName: name ?? ''),
ThreeGangSwitch2Function( ThreeGangSwitch3Function(deviceId: uuid ?? '', deviceName: name ?? ''),
deviceId: uuid ?? '', deviceName: name ?? ''), ThreeGangCountdown1Function(deviceId: uuid ?? '', deviceName: name ?? ''),
ThreeGangSwitch3Function( ThreeGangCountdown2Function(deviceId: uuid ?? '', deviceName: name ?? ''),
deviceId: uuid ?? '', deviceName: name ?? ''), ThreeGangCountdown3Function(deviceId: uuid ?? '', deviceName: name ?? ''),
ThreeGangCountdown1Function(
deviceId: uuid ?? '', deviceName: name ?? ''),
ThreeGangCountdown2Function(
deviceId: uuid ?? '', deviceName: name ?? ''),
ThreeGangCountdown3Function(
deviceId: uuid ?? '', deviceName: name ?? ''),
]; ];
default: default:
@ -288,6 +282,9 @@ SOS
if (room != null) { if (room != null) {
data['room'] = room!.toJson(); data['room'] = room!.toJson();
} }
if (subspace != null) {
data['subspace'] = subspace!.toJson();
}
if (unit != null) { if (unit != null) {
data['unit'] = unit!.toJson(); data['unit'] = unit!.toJson();
} }
@ -330,6 +327,7 @@ SOS
return other is AllDevicesModel && return other is AllDevicesModel &&
other.room == room && other.room == room &&
other.subspace == subspace &&
other.unit == unit && other.unit == unit &&
other.productUuid == productUuid && other.productUuid == productUuid &&
other.productType == productType && other.productType == productType &&
@ -360,6 +358,7 @@ SOS
@override @override
int get hashCode { int get hashCode {
return room.hashCode ^ return room.hashCode ^
subspace.hashCode ^
unit.hashCode ^ unit.hashCode ^
productUuid.hashCode ^ productUuid.hashCode ^
productType.hashCode ^ productType.hashCode ^

View File

@ -3,9 +3,9 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/widgets/device_managment_body.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/widgets/device_managment_body.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/routiens/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routiens/view/create_new_routine_view.dart'; import 'package:syncrow_web/pages/routines/view/create_new_routine_view.dart';
import 'package:syncrow_web/pages/routiens/view/routines_view.dart'; import 'package:syncrow_web/pages/routines/view/routines_view.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';
@ -19,7 +19,7 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout {
return MultiBlocProvider( return MultiBlocProvider(
providers: [ providers: [
BlocProvider( BlocProvider(
create: (context) => DeviceManagementBloc()..add(FetchDevices()), create: (context) => DeviceManagementBloc()..add(FetchDevices(context)),
), ),
], ],
child: WebScaffold( child: WebScaffold(
@ -80,7 +80,7 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout {
return BlocBuilder<DeviceManagementBloc, DeviceManagementState>( return BlocBuilder<DeviceManagementBloc, DeviceManagementState>(
builder: (context, deviceState) { builder: (context, deviceState) {
if (deviceState is DeviceManagementLoading) { if (deviceState is DeviceManagementLoading) {
return const Center(child: CircularProgressIndicator()); return const DeviceManagementBody(devices: []);
} else if (deviceState is DeviceManagementLoaded) { } else if (deviceState is DeviceManagementLoaded) {
return DeviceManagementBody(devices: deviceState.devices); return DeviceManagementBody(devices: deviceState.devices);
} else if (deviceState is DeviceManagementFiltered) { } else if (deviceState is DeviceManagementFiltered) {

View File

@ -8,6 +8,8 @@ import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_mo
import 'package:syncrow_web/pages/device_managment/all_devices/widgets/device_search_filters.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/widgets/device_search_filters.dart';
import 'package:syncrow_web/pages/device_managment/shared/device_batch_control_dialog.dart'; import 'package:syncrow_web/pages/device_managment/shared/device_batch_control_dialog.dart';
import 'package:syncrow_web/pages/device_managment/shared/device_control_dialog.dart'; import 'package:syncrow_web/pages/device_managment/shared/device_control_dialog.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
import 'package:syncrow_web/pages/space_tree/view/space_tree_view.dart';
import 'package:syncrow_web/utils/format_date_time.dart'; import 'package:syncrow_web/utils/format_date_time.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/style.dart'; import 'package:syncrow_web/utils/style.dart';
@ -59,118 +61,153 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
final buttonLabel = (selectedDevices.length > 1) ? 'Batch Control' : 'Control'; final buttonLabel = (selectedDevices.length > 1) ? 'Batch Control' : 'Control';
return Column( return Row(
children: [ children: [
Container( Expanded(child: SpaceTreeView(
padding: isLargeScreenSize(context) ? const EdgeInsets.all(30) : const EdgeInsets.all(15), onSelect: () {
child: Column( context.read<DeviceManagementBloc>().add(FetchDevices(context));
crossAxisAlignment: CrossAxisAlignment.start, },
children: [ )),
FilterWidget( Expanded(
size: MediaQuery.of(context).size, flex: 4,
tabs: tabs, child: state is DeviceManagementLoading
selectedIndex: selectedIndex, ? const Center(child: CircularProgressIndicator())
onTabChanged: (index) { : Column(
context.read<DeviceManagementBloc>().add(SelectedFilterChanged(index)); children: [
}, Container(
), padding: isLargeScreenSize(context)
const SizedBox(height: 20), ? const EdgeInsets.all(30)
const DeviceSearchFilters(), : const EdgeInsets.all(15),
const SizedBox(height: 12), child: Column(
Container( crossAxisAlignment: CrossAxisAlignment.start,
height: 45, children: [
width: 125, FilterWidget(
decoration: containerDecoration, size: MediaQuery.of(context).size,
child: Center( tabs: tabs,
child: DefaultButton( selectedIndex: selectedIndex,
onPressed: isControlButtonEnabled onTabChanged: (index) {
? () { context
if (selectedDevices.length == 1) { .read<DeviceManagementBloc>()
showDialog( .add(SelectedFilterChanged(index));
context: context, },
builder: (context) => DeviceControlDialog( ),
device: selectedDevices.first, const SizedBox(height: 20),
), const DeviceSearchFilters(),
); const SizedBox(height: 12),
} else if (selectedDevices.length > 1) { Container(
final productTypes = selectedDevices.map((device) => device.productType).toSet(); height: 45,
if (productTypes.length == 1) { width: 125,
showDialog( decoration: containerDecoration,
context: context, child: Center(
builder: (context) => DeviceBatchControlDialog( child: DefaultButton(
devices: selectedDevices, onPressed: isControlButtonEnabled
? () {
if (selectedDevices.length == 1) {
showDialog(
context: context,
builder: (context) => DeviceControlDialog(
device: selectedDevices.first,
),
);
} else if (selectedDevices.length > 1) {
final productTypes = selectedDevices
.map((device) => device.productType)
.toSet();
if (productTypes.length == 1) {
showDialog(
context: context,
builder: (context) => DeviceBatchControlDialog(
devices: selectedDevices,
),
);
}
}
}
: null,
borderRadius: 9,
child: Text(
buttonLabel,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 12,
color: isControlButtonEnabled ? Colors.white : Colors.grey,
), ),
); ),
} ),
} ),
} ),
: null, ],
borderRadius: 9,
child: Text(
buttonLabel,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 12,
color: isControlButtonEnabled ? Colors.white : Colors.grey,
), ),
), ),
), Expanded(
), child: Padding(
), padding: isLargeScreenSize(context)
], ? const EdgeInsets.all(30)
), : const EdgeInsets.all(15),
), child: DynamicTable(
Expanded( withSelectAll: true,
child: Padding( cellDecoration: containerDecoration,
padding: isLargeScreenSize(context) ? const EdgeInsets.all(30) : const EdgeInsets.all(15), onRowSelected: (index, isSelected, row) {
child: DynamicTable( final selectedDevice = devicesToShow[index];
withSelectAll: true, context
cellDecoration: containerDecoration, .read<DeviceManagementBloc>()
onRowSelected: (index, isSelected, row) { .add(SelectDevice(selectedDevice));
final selectedDevice = devicesToShow[index]; },
context.read<DeviceManagementBloc>().add(SelectDevice(selectedDevice)); withCheckBox: true,
}, size: MediaQuery.of(context).size,
withCheckBox: true, uuidIndex: 2,
size: MediaQuery.of(context).size, headers: const [
uuidIndex: 2, 'Device Name',
headers: const [ 'Product Name',
'Device Name', 'Device ID',
'Product Name', 'Space Name',
'Device ID', 'location',
'Space Name', 'Battery Level',
'location', 'Installation Date and Time',
'Battery Level', 'Status',
'Installation Date and Time', 'Last Offline Date and Time',
'Status', ],
'Last Offline Date and Time', data: devicesToShow.map((device) {
], final combinedSpaceNames = device.spaces != null
data: devicesToShow.map((device) { ? device.spaces!.map((space) => space.spaceName).join(' > ') +
final combinedSpaceNames = device.spaces != null (device.community != null
? device.spaces!.map((space) => space.spaceName).join(' > ') + ? ' > ${device.community!.name}'
(device.community != null ? ' > ${device.community!.name}' : '') : '')
: (device.community != null ? device.community!.name : ''); : (device.community != null ? device.community!.name : '');
return [ return [
device.name ?? '', device.name ?? '',
device.productName ?? '', device.productName ?? '',
device.uuid ?? '', device.uuid ?? '',
(device.spaces != null && device.spaces!.isNotEmpty) ? device.spaces![0].spaceName : '', (device.spaces != null && device.spaces!.isNotEmpty)
combinedSpaceNames, ? device.spaces![0].spaceName
device.batteryLevel != null ? '${device.batteryLevel}%' : '-', : '',
formatDateTime(DateTime.fromMillisecondsSinceEpoch((device.createTime ?? 0) * 1000)), combinedSpaceNames,
device.online == true ? 'Online' : 'Offline', device.batteryLevel != null ? '${device.batteryLevel}%' : '-',
formatDateTime(DateTime.fromMillisecondsSinceEpoch((device.updateTime ?? 0) * 1000)), formatDateTime(DateTime.fromMillisecondsSinceEpoch(
]; (device.createTime ?? 0) * 1000)),
}).toList(), device.online == true ? 'Online' : 'Offline',
onSelectionChanged: (selectedRows) { formatDateTime(DateTime.fromMillisecondsSinceEpoch(
context.read<DeviceManagementBloc>().add(UpdateSelection(selectedRows)); (device.updateTime ?? 0) * 1000)),
}, ];
initialSelectedIds: }).toList(),
context.read<DeviceManagementBloc>().selectedDevices.map((device) => device.uuid!).toList(), onSelectionChanged: (selectedRows) {
isEmpty: devicesToShow.isEmpty, context
), .read<DeviceManagementBloc>()
), .add(UpdateSelection(selectedRows));
) },
initialSelectedIds: context
.read<DeviceManagementBloc>()
.selectedDevices
.map((device) => device.uuid!)
.toList(),
isEmpty: devicesToShow.isEmpty,
),
),
)
],
),
),
], ],
); );
}, },

View File

@ -12,8 +12,7 @@ class DeviceSearchFilters extends StatefulWidget {
State<DeviceSearchFilters> createState() => _DeviceSearchFiltersState(); State<DeviceSearchFilters> createState() => _DeviceSearchFiltersState();
} }
class _DeviceSearchFiltersState extends State<DeviceSearchFilters> class _DeviceSearchFiltersState extends State<DeviceSearchFilters> with HelperResponsiveLayout {
with HelperResponsiveLayout {
final TextEditingController communityController = TextEditingController(); final TextEditingController communityController = TextEditingController();
final TextEditingController unitNameController = TextEditingController(); final TextEditingController unitNameController = TextEditingController();
final TextEditingController productNameController = TextEditingController(); final TextEditingController productNameController = TextEditingController();
@ -27,8 +26,7 @@ class _DeviceSearchFiltersState extends State<DeviceSearchFilters>
const SizedBox(width: 20), const SizedBox(width: 20),
_buildSearchField("Space Name", unitNameController, 200), _buildSearchField("Space Name", unitNameController, 200),
const SizedBox(width: 20), const SizedBox(width: 20),
_buildSearchField( _buildSearchField("Device Name / Product Name", productNameController, 300),
"Device Name / Product Name", productNameController, 300),
const SizedBox(width: 20), const SizedBox(width: 20),
_buildSearchResetButtons(), _buildSearchResetButtons(),
], ],
@ -53,8 +51,7 @@ class _DeviceSearchFiltersState extends State<DeviceSearchFilters>
); );
} }
Widget _buildSearchField( Widget _buildSearchField(String title, TextEditingController controller, double width) {
String title, TextEditingController controller, double width) {
return Container( return Container(
child: StatefulTextField( child: StatefulTextField(
title: title, title: title,
@ -88,7 +85,7 @@ class _DeviceSearchFiltersState extends State<DeviceSearchFilters>
productNameController.clear(); productNameController.clear();
context.read<DeviceManagementBloc>() context.read<DeviceManagementBloc>()
..add(ResetFilters()) ..add(ResetFilters())
..add(FetchDevices()); ..add(FetchDevices(context));
}, },
); );
} }

View File

@ -95,8 +95,9 @@ class DeviceControlDialog extends StatelessWidget with RouteControlsBasedCode {
]), ]),
TableRow( TableRow(
children: [ children: [
_buildInfoRow('Space Name:', device.unit?.name ?? 'N/A'), _buildInfoRow('Space Name:',
_buildInfoRow('Room:', device.room?.name ?? 'N/A'), device.spaces?.firstOrNull?.spaceName ?? 'N/A'),
_buildInfoRow('Room:', device.subspace?.subspaceName ?? 'N/A'),
], ],
), ),
TableRow( TableRow(
@ -111,9 +112,13 @@ class DeviceControlDialog extends StatelessWidget with RouteControlsBasedCode {
), ),
_buildInfoRow( _buildInfoRow(
'Battery Level:', 'Battery Level:',
device.batteryLevel != null ? '${device.batteryLevel ?? 0}%' : "-", device.batteryLevel != null
? '${device.batteryLevel ?? 0}%'
: "-",
statusColor: device.batteryLevel != null statusColor: device.batteryLevel != null
? (device.batteryLevel! < 20 ? ColorsManager.red : ColorsManager.green) ? (device.batteryLevel! < 20
? ColorsManager.red
: ColorsManager.green)
: null, : null,
), ),
], ],

View File

@ -1,56 +1,94 @@
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:graphview/GraphView.dart'; // import 'package:graphview/GraphView.dart';
import 'package:syncrow_web/pages/auth/model/user_model.dart'; import 'package:syncrow_web/pages/auth/model/user_model.dart';
import 'package:syncrow_web/pages/home/bloc/home_event.dart'; import 'package:syncrow_web/pages/home/bloc/home_event.dart';
import 'package:syncrow_web/pages/home/bloc/home_state.dart'; import 'package:syncrow_web/pages/home/bloc/home_state.dart';
import 'package:syncrow_web/pages/home/home_model/home_item_model.dart'; import 'package:syncrow_web/pages/home/home_model/home_item_model.dart';
import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/services/home_api.dart'; import 'package:syncrow_web/services/home_api.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/constants/routes_const.dart'; import 'package:syncrow_web/utils/constants/routes_const.dart';
class HomeBloc extends Bloc<HomeEvent, HomeState> { class HomeBloc extends Bloc<HomeEvent, HomeState> {
final Graph graph = Graph()..isTree = true; // final Graph graph = Graph()..isTree = true;
final BuchheimWalkerConfiguration builder = BuchheimWalkerConfiguration(); // final BuchheimWalkerConfiguration builder = BuchheimWalkerConfiguration();
List<Node> sourcesList = []; // List<Node> sourcesList = [];
List<Node> destinationsList = []; // List<Node> destinationsList = [];
UserModel? user; UserModel? user;
String terms = '';
String policy = '';
HomeBloc() : super((HomeInitial())) { HomeBloc() : super((HomeInitial())) {
on<CreateNewNode>(_createNode); // on<CreateNewNode>(_createNode);
on<FetchUserInfo>(_fetchUserInfo); on<FetchUserInfo>(_fetchUserInfo);
on<FetchTermEvent>(_fetchTerms);
on<FetchPolicyEvent>(_fetchPolicy);
on<ConfirmUserAgreementEvent>(_confirmUserAgreement);
} }
void _createNode(CreateNewNode event, Emitter<HomeState> emit) async { // void _createNode(CreateNewNode event, Emitter<HomeState> emit) async {
emit(HomeInitial()); // emit(HomeInitial());
sourcesList.add(event.sourceNode); // sourcesList.add(event.sourceNode);
destinationsList.add(event.destinationNode); // destinationsList.add(event.destinationNode);
for (int i = 0; i < sourcesList.length; i++) { // for (int i = 0; i < sourcesList.length; i++) {
graph.addEdge(sourcesList[i], destinationsList[i]); // graph.addEdge(sourcesList[i], destinationsList[i]);
} // }
builder // builder
..siblingSeparation = (100) // ..siblingSeparation = (100)
..levelSeparation = (150) // ..levelSeparation = (150)
..subtreeSeparation = (150) // ..subtreeSeparation = (150)
..orientation = (BuchheimWalkerConfiguration.ORIENTATION_TOP_BOTTOM); // ..orientation = (BuchheimWalkerConfiguration.ORIENTATION_TOP_BOTTOM);
emit(HomeUpdateTree(graph: graph, builder: builder)); // emit(HomeUpdateTree(graph: graph, builder: builder));
} // }
Future _fetchUserInfo(FetchUserInfo event, Emitter<HomeState> emit) async { Future _fetchUserInfo(FetchUserInfo event, Emitter<HomeState> emit) async {
try { try {
var uuid = var uuid =
await const FlutterSecureStorage().read(key: UserModel.userUuidKey); await const FlutterSecureStorage().read(key: UserModel.userUuidKey);
user = await HomeApi().fetchUserInfo(uuid); user = await HomeApi().fetchUserInfo(uuid);
add(FetchTermEvent());
emit(HomeInitial()); emit(HomeInitial());
} catch (e) { } catch (e) {
return; return;
} }
} }
Future _fetchTerms(FetchTermEvent event, Emitter<HomeState> emit) async {
try {
emit(LoadingHome());
terms = await HomeApi().fetchTerms();
add(FetchPolicyEvent());
} catch (e) {
return;
}
}
Future _fetchPolicy(FetchPolicyEvent event, Emitter<HomeState> emit) async {
try {
emit(LoadingHome());
policy = await HomeApi().fetchPolicy();
} catch (e) {
return;
}
}
Future _confirmUserAgreement(
ConfirmUserAgreementEvent event, Emitter<HomeState> emit) async {
try {
emit(LoadingHome());
var uuid =
await const FlutterSecureStorage().read(key: UserModel.userUuidKey);
policy = await HomeApi().confirmUserAgreements(uuid);
emit(PolicyAgreement());
} catch (e) {
return;
}
}
// static Future fetchUserInfo() async { // static Future fetchUserInfo() async {
// try { // try {
// var uuid = // var uuid =

View File

@ -1,5 +1,5 @@
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:graphview/GraphView.dart'; // import 'package:graphview/GraphView.dart';
abstract class HomeEvent extends Equatable { abstract class HomeEvent extends Equatable {
const HomeEvent(); const HomeEvent();
@ -8,16 +8,22 @@ abstract class HomeEvent extends Equatable {
List<Object> get props => []; List<Object> get props => [];
} }
class CreateNewNode extends HomeEvent { // class CreateNewNode extends HomeEvent {
final Node sourceNode; // final Node sourceNode;
final Node destinationNode; // final Node destinationNode;
const CreateNewNode( // const CreateNewNode(
{required this.sourceNode, required this.destinationNode}); // {required this.sourceNode, required this.destinationNode});
@override // @override
List<Object> get props => [sourceNode, destinationNode]; // List<Object> get props => [sourceNode, destinationNode];
} // }
class FetchUserInfo extends HomeEvent { class FetchUserInfo extends HomeEvent {
const FetchUserInfo(); const FetchUserInfo();
} }
class FetchTermEvent extends HomeEvent {}
class FetchPolicyEvent extends HomeEvent {}
class ConfirmUserAgreementEvent extends HomeEvent {}

View File

@ -1,5 +1,5 @@
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:graphview/GraphView.dart'; // import 'package:graphview/GraphView.dart';
abstract class HomeState extends Equatable { abstract class HomeState extends Equatable {
const HomeState(); const HomeState();
@ -8,19 +8,25 @@ abstract class HomeState extends Equatable {
List<Object> get props => []; List<Object> get props => [];
} }
class LoadingHome extends HomeState {}
class HomeInitial extends HomeState {} class HomeInitial extends HomeState {}
class HomeCounterState extends HomeState { class TermsAgreement extends HomeState {}
final int counter;
const HomeCounterState(this.counter);
}
class HomeUpdateTree extends HomeState { class PolicyAgreement extends HomeState {}
final Graph graph;
final BuchheimWalkerConfiguration builder;
const HomeUpdateTree({required this.graph, required this.builder}); // class HomeCounterState extends HomeState {
// final int counter;
// const HomeCounterState(this.counter);
// }
@override // class HomeUpdateTree extends HomeState {
List<Object> get props => [graph, builder]; // final Graph graph;
} // final BuchheimWalkerConfiguration builder;
// const HomeUpdateTree({required this.graph, required this.builder});
// @override
// List<Object> get props => [graph, builder];
// }

View File

@ -0,0 +1,176 @@
import 'package:flutter/material.dart';
import 'package:flutter_html/flutter_html.dart';
import 'package:go_router/go_router.dart';
import 'package:syncrow_web/pages/auth/bloc/auth_bloc.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/routes_const.dart';
import 'package:url_launcher/url_launcher.dart';
class AgreementAndPrivacyDialog extends StatefulWidget {
final String terms;
final String policy;
const AgreementAndPrivacyDialog({
super.key,
required this.terms,
required this.policy,
});
@override
_AgreementAndPrivacyDialogState createState() =>
_AgreementAndPrivacyDialogState();
}
class _AgreementAndPrivacyDialogState extends State<AgreementAndPrivacyDialog> {
final ScrollController _scrollController = ScrollController();
bool _isAtEnd = false;
int _currentPage = 1;
@override
void initState() {
super.initState();
_scrollController.addListener(_onScroll);
WidgetsBinding.instance
.addPostFrameCallback((_) => _checkScrollRequirement());
}
void _checkScrollRequirement() {
final scrollPosition = _scrollController.position;
if (scrollPosition.maxScrollExtent <= 0) {
setState(() {
_isAtEnd = true;
});
}
}
@override
void dispose() {
_scrollController.removeListener(_onScroll);
_scrollController.dispose();
super.dispose();
}
void _onScroll() {
if (_scrollController.position.atEdge) {
final isAtBottom = _scrollController.position.pixels ==
_scrollController.position.maxScrollExtent;
if (isAtBottom && !_isAtEnd) {
setState(() {
_isAtEnd = true;
});
}
}
}
String get _dialogTitle =>
_currentPage == 2 ? 'User Agreement' : 'Privacy Policy';
String get _dialogContent => _currentPage == 2 ? widget.terms : widget.policy;
Widget _buildScrollableContent() {
return Container(
padding: const EdgeInsets.all(40),
width: MediaQuery.of(context).size.width * 0.8,
height: MediaQuery.of(context).size.height * 0.75,
decoration: BoxDecoration(
color: Colors.grey[200],
borderRadius: const BorderRadius.all(Radius.circular(20)),
),
child: Scrollbar(
thumbVisibility: true,
trackVisibility: true,
interactive: true,
controller: _scrollController,
child: SingleChildScrollView(
controller: _scrollController,
padding: const EdgeInsets.all(25),
child: Html(
data: _dialogContent,
onLinkTap: (url, attributes, element) async {
if (url != null) {
final uri = Uri.parse(url);
await launchUrl(uri, mode: LaunchMode.externalApplication);
}
},
style: {
"body": Style(
fontSize: FontSize(14),
color: Colors.black87,
lineHeight: LineHeight(1.5),
),
},
),
),
),
);
}
Widget _buildActionButton() {
final String buttonText = _currentPage == 2 ? "I Agree" : "Next";
return InkWell(
onTap: _isAtEnd
? () {
if (_currentPage == 1) {
setState(() {
_currentPage = 2;
_isAtEnd = false;
_scrollController.jumpTo(0);
WidgetsBinding.instance
.addPostFrameCallback((_) => _checkScrollRequirement());
});
} else {
Navigator.of(context).pop(true);
}
}
: null,
child: Text(
buttonText,
style: TextStyle(
color: _isAtEnd ? ColorsManager.secondaryColor : Colors.grey,
),
),
);
}
@override
Widget build(BuildContext context) {
return Dialog(
child: Column(
children: [
Padding(
padding: const EdgeInsets.all(16.0),
child: Text(
_dialogTitle,
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.w700,
color: ColorsManager.secondaryColor,
),
),
),
const Divider(),
_buildScrollableContent(),
const Divider(),
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
InkWell(
onTap: () {
AuthBloc.logout();
context.go(RoutesConst.auth);
},
child: const Text("Cancel"),
),
_buildActionButton(),
],
),
),
],
),
);
}
}

View File

@ -41,8 +41,7 @@ class HomeMobilePage extends StatelessWidget {
SizedBox(height: size.height * 0.05), SizedBox(height: size.height * 0.05),
const Text( const Text(
'ACCESS YOUR APPS', 'ACCESS YOUR APPS',
style: style: TextStyle(fontSize: 20, fontWeight: FontWeight.w700),
TextStyle(fontSize: 20, fontWeight: FontWeight.w700),
), ),
const SizedBox(height: 30), const SizedBox(height: 30),
Expanded( Expanded(
@ -51,9 +50,8 @@ class HomeMobilePage extends StatelessWidget {
height: size.height * 0.6, height: size.height * 0.6,
width: size.width * 0.68, width: size.width * 0.68,
child: GridView.builder( child: GridView.builder(
itemCount: 8, itemCount: 3,
gridDelegate: gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2, crossAxisCount: 2,
crossAxisSpacing: 20.0, crossAxisSpacing: 20.0,
mainAxisSpacing: 20.0, mainAxisSpacing: 20.0,
@ -65,8 +63,7 @@ class HomeMobilePage extends StatelessWidget {
active: homeItems[index]['active'], active: homeItems[index]['active'],
name: homeItems[index]['title'], name: homeItems[index]['title'],
img: homeItems[index]['icon'], img: homeItems[index]['icon'],
onTap: () => onTap: () => homeBloc.homeItems[index].onPress(context),
homeBloc.homeItems[index].onPress(context),
); );
}, },
), ),
@ -97,33 +94,33 @@ class HomeMobilePage extends StatelessWidget {
'icon': Assets.devicesIcon, 'icon': Assets.devicesIcon,
'active': true, 'active': true,
}, },
{ // {
'title': 'Move in', // 'title': 'Move in',
'icon': Assets.moveinIcon, // 'icon': Assets.moveinIcon,
'active': false, // 'active': false,
}, // },
{ // {
'title': 'Construction', // 'title': 'Construction',
'icon': Assets.constructionIcon, // 'icon': Assets.constructionIcon,
'active': false, // 'active': false,
}, // },
{ // {
'title': 'Energy', // 'title': 'Energy',
'icon': Assets.energyIcon, // 'icon': Assets.energyIcon,
'color': ColorsManager.slidingBlueColor.withOpacity(0.2), // 'color': ColorsManager.slidingBlueColor.withOpacity(0.2),
'active': false, // 'active': false,
}, // },
{ // {
'title': 'Integrations', // 'title': 'Integrations',
'icon': Assets.integrationsIcon, // 'icon': Assets.integrationsIcon,
'color': ColorsManager.slidingBlueColor.withOpacity(0.2), // 'color': ColorsManager.slidingBlueColor.withOpacity(0.2),
'active': false, // 'active': false,
}, // },
{ // {
'title': 'Asset', // 'title': 'Asset',
'icon': Assets.assetIcon, // 'icon': Assets.assetIcon,
'color': ColorsManager.slidingBlueColor.withOpacity(0.2), // 'color': ColorsManager.slidingBlueColor.withOpacity(0.2),
'active': false, // 'active': false,
}, // },
]; ];
} }

View File

@ -1,6 +1,8 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/svg.dart'; import 'package:flutter_svg/svg.dart';
import 'package:syncrow_web/pages/home/bloc/home_event.dart';
import 'package:syncrow_web/pages/home/view/agreement_and_privacy_dialog.dart';
import 'package:syncrow_web/pages/home/bloc/home_bloc.dart'; import 'package:syncrow_web/pages/home/bloc/home_bloc.dart';
import 'package:syncrow_web/pages/home/bloc/home_state.dart'; import 'package:syncrow_web/pages/home/bloc/home_state.dart';
import 'package:syncrow_web/pages/home/view/home_card.dart'; import 'package:syncrow_web/pages/home/view/home_card.dart';
@ -9,16 +11,40 @@ import 'package:syncrow_web/web_layout/web_scaffold.dart';
class HomeWebPage extends StatelessWidget { class HomeWebPage extends StatelessWidget {
const HomeWebPage({super.key}); const HomeWebPage({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Size size = MediaQuery.of(context).size; Size size = MediaQuery.of(context).size;
final homeBloc = BlocProvider.of<HomeBloc>(context);
return PopScope( return PopScope(
canPop: false, canPop: false,
onPopInvoked: (didPop) => false, onPopInvoked: (didPop) => false,
child: BlocConsumer<HomeBloc, HomeState>( child: BlocConsumer<HomeBloc, HomeState>(
listener: (BuildContext context, state) {}, listener: (BuildContext context, state) {
if (state is HomeInitial) {
if (homeBloc.user!.hasAcceptedWebAgreement == false) {
Future.delayed(const Duration(seconds: 1), () {
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return AgreementAndPrivacyDialog(
terms: homeBloc.terms,
policy: homeBloc.policy,
);
},
).then((v) {
if (v != null) {
homeBloc.add(ConfirmUserAgreementEvent());
homeBloc.add(const FetchUserInfo());
}
});
});
}
}
},
builder: (context, state) { builder: (context, state) {
final homeBloc = BlocProvider.of<HomeBloc>(context);
return WebScaffold( return WebScaffold(
enableMenuSidebar: false, enableMenuSidebar: false,
appBarTitle: Row( appBarTitle: Row(
@ -32,43 +58,48 @@ class HomeWebPage extends StatelessWidget {
scaffoldBody: SizedBox( scaffoldBody: SizedBox(
height: size.height, height: size.height,
width: size.width, width: size.width,
child: Column( child: Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
SizedBox(height: size.height * 0.1), Column(
Text( mainAxisAlignment: MainAxisAlignment.center,
'ACCESS YOUR APPS', crossAxisAlignment: CrossAxisAlignment.center,
style: Theme.of(context) children: [
.textTheme SizedBox(height: size.height * 0.1),
.headlineLarge! Text(
.copyWith(color: Colors.black, fontSize: 40), 'ACCESS YOUR APPS',
), style: Theme.of(context)
const SizedBox(height: 30), .textTheme
Expanded( .headlineLarge!
flex: 4, .copyWith(color: Colors.black, fontSize: 40),
child: SizedBox(
height: size.height * 0.6,
width: size.width * 0.68,
child: GridView.builder(
itemCount: 3, //8
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 4,
crossAxisSpacing: 20.0,
mainAxisSpacing: 20.0,
childAspectRatio: 1.5,
),
itemBuilder: (context, index) {
return HomeCard(
index: index,
active: homeBloc.homeItems[index].active!,
name: homeBloc.homeItems[index].title!,
img: homeBloc.homeItems[index].icon!,
onTap: () => homeBloc.homeItems[index].onPress(context),
);
},
), ),
), const SizedBox(height: 30),
Expanded(
flex: 4,
child: SizedBox(
height: size.height * 0.6,
width: size.width * 0.68,
child: GridView.builder(
itemCount: 3, //8
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3, //4
crossAxisSpacing: 20.0,
mainAxisSpacing: 20.0,
childAspectRatio: 1.5,
),
itemBuilder: (context, index) {
return HomeCard(
index: index,
active: homeBloc.homeItems[index].active!,
name: homeBloc.homeItems[index].title!,
img: homeBloc.homeItems[index].icon!,
onTap: () => homeBloc.homeItems[index].onPress(context),
);
},
),
),
),
],
), ),
], ],
), ),

View File

@ -1,185 +1,185 @@
import 'package:flutter/material.dart'; // import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; // import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:graphview/GraphView.dart'; // import 'package:graphview/GraphView.dart';
import 'package:syncrow_web/pages/home/bloc/home_bloc.dart'; // import 'package:syncrow_web/pages/home/bloc/home_bloc.dart';
import 'package:syncrow_web/pages/home/bloc/home_event.dart'; // import 'package:syncrow_web/pages/home/bloc/home_event.dart';
import 'package:syncrow_web/pages/home/bloc/home_state.dart'; // import 'package:syncrow_web/pages/home/bloc/home_state.dart';
class TreeWidget extends StatelessWidget { // class TreeWidget extends StatelessWidget {
const TreeWidget({super.key}); // const TreeWidget({super.key});
@override // @override
Widget build(BuildContext context) { // Widget build(BuildContext context) {
// final HomeBloc homeBloc = BlocProvider.of<HomeBloc>(context); // // final HomeBloc homeBloc = BlocProvider.of<HomeBloc>(context);
String firstNodeName = ''; // String firstNodeName = '';
String secondNodeName = ''; // String secondNodeName = '';
return SafeArea( // return SafeArea(
child: Container( // child: Container(
padding: const EdgeInsets.all(24), // padding: const EdgeInsets.all(24),
width: MediaQuery.sizeOf(context).width, // width: MediaQuery.sizeOf(context).width,
height: MediaQuery.sizeOf(context).height, // height: MediaQuery.sizeOf(context).height,
alignment: AlignmentDirectional.center, // alignment: AlignmentDirectional.center,
child: Column( // child: Column(
mainAxisSize: MainAxisSize.max, // mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.center, // crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center, // mainAxisAlignment: MainAxisAlignment.center,
children: [ // children: [
BlocBuilder<HomeBloc, HomeState>(builder: (context, state) { // BlocBuilder<HomeBloc, HomeState>(builder: (context, state) {
if (state is HomeInitial) { // if (state is HomeInitial) {
return Wrap( // return Wrap(
children: [ // children: [
SizedBox( // SizedBox(
width: 100, // width: 100,
child: TextFormField( // child: TextFormField(
decoration: const InputDecoration( // decoration: const InputDecoration(
labelText: "Subtree separation"), // labelText: "Subtree separation"),
onChanged: (text) { // onChanged: (text) {
firstNodeName = text; // firstNodeName = text;
}, // },
), // ),
), // ),
const SizedBox( // const SizedBox(
width: 8, // width: 8,
), // ),
Container( // Container(
width: 100, // width: 100,
child: TextFormField( // child: TextFormField(
decoration: InputDecoration(labelText: "Node Name"), // decoration: InputDecoration(labelText: "Node Name"),
onChanged: (text) { // onChanged: (text) {
secondNodeName = text; // secondNodeName = text;
}, // },
), // ),
), // ),
ElevatedButton( // ElevatedButton(
onPressed: () { // onPressed: () {
final node1 = Node.Id(firstNodeName); // final node1 = Node.Id(firstNodeName);
final node2 = Node.Id(secondNodeName); // final node2 = Node.Id(secondNodeName);
context.read<HomeBloc>().add(CreateNewNode( // context.read<HomeBloc>().add(CreateNewNode(
sourceNode: node1, destinationNode: node2)); // sourceNode: node1, destinationNode: node2));
}, // },
child: Text("Add"), // child: Text("Add"),
) // )
], // ],
); // );
} // }
if (state is HomeUpdateTree) { // if (state is HomeUpdateTree) {
return Expanded( // return Expanded(
child: InteractiveViewer( // child: InteractiveViewer(
constrained: false, // constrained: false,
boundaryMargin: const EdgeInsets.all(100), // boundaryMargin: const EdgeInsets.all(100),
minScale: 0.01, // minScale: 0.01,
maxScale: 5.6, // maxScale: 5.6,
child: GraphView( // child: GraphView(
graph: state.graph, // graph: state.graph,
algorithm: BuchheimWalkerAlgorithm( // algorithm: BuchheimWalkerAlgorithm(
state.builder, TreeEdgeRenderer(state.builder)), // state.builder, TreeEdgeRenderer(state.builder)),
paint: Paint() // paint: Paint()
..color = Colors.green // ..color = Colors.green
..strokeWidth = 1 // ..strokeWidth = 1
..style = PaintingStyle.stroke, // ..style = PaintingStyle.stroke,
builder: (Node node) { // builder: (Node node) {
// I can decide what widget should be shown here based on the id // // I can decide what widget should be shown here based on the id
var nodeName = node.key!.value; // var nodeName = node.key!.value;
return rectangleWidget(nodeName, node, context); // return rectangleWidget(nodeName, node, context);
}, // },
)), // )),
); // );
} else { // } else {
return Container(); // return Container();
} // }
}) // })
], // ],
), // ),
), // ),
); // );
} // }
} // }
Widget rectangleWidget(String text, Node node, BuildContext blocContext) { // Widget rectangleWidget(String text, Node node, BuildContext blocContext) {
String nodeName = ''; // String nodeName = '';
return InkWell( // return InkWell(
onTap: () { // onTap: () {
showDialog( // showDialog(
context: blocContext, // context: blocContext,
builder: (BuildContext context) { // builder: (BuildContext context) {
return AlertDialog( // return AlertDialog(
title: const Text('Add a child'), // title: const Text('Add a child'),
content: TextField( // content: TextField(
decoration: // decoration:
const InputDecoration(hintText: 'Enter your text here'), // const InputDecoration(hintText: 'Enter your text here'),
onChanged: (value) { // onChanged: (value) {
nodeName = value; // nodeName = value;
}, // },
), // ),
actions: <Widget>[ // actions: <Widget>[
TextButton( // TextButton(
onPressed: () { // onPressed: () {
Navigator.of(context).pop(); // Navigator.of(context).pop();
}, // },
child: Text('Close'), // child: Text('Close'),
), // ),
TextButton( // TextButton(
onPressed: () { // onPressed: () {
if (nodeName.isNotEmpty) { // if (nodeName.isNotEmpty) {
final newNode = Node.Id(nodeName); // final newNode = Node.Id(nodeName);
blocContext.read<HomeBloc>().add(CreateNewNode( // blocContext.read<HomeBloc>().add(CreateNewNode(
sourceNode: node, destinationNode: newNode)); // sourceNode: node, destinationNode: newNode));
} // }
Navigator.of(context).pop(); // Navigator.of(context).pop();
}, // },
child: Text('Add'), // child: Text('Add'),
), // ),
], // ],
); // );
}, // },
); // );
}, // },
child: Container( // child: Container(
width: MediaQuery.of(blocContext).size.width * 0.2, // width: MediaQuery.of(blocContext).size.width * 0.2,
margin: EdgeInsets.symmetric(vertical: 10.0, horizontal: 20.0), // margin: EdgeInsets.symmetric(vertical: 10.0, horizontal: 20.0),
padding: EdgeInsets.all(20.0), // padding: EdgeInsets.all(20.0),
decoration: BoxDecoration( // decoration: BoxDecoration(
color: Colors.white, // color: Colors.white,
borderRadius: BorderRadius.circular(10.0), // borderRadius: BorderRadius.circular(10.0),
boxShadow: [ // boxShadow: [
BoxShadow( // BoxShadow(
color: Colors.grey.withOpacity(0.5), // color: Colors.grey.withOpacity(0.5),
spreadRadius: 2, // spreadRadius: 2,
blurRadius: 5, // blurRadius: 5,
offset: Offset(0, 3), // changes position of shadow // offset: Offset(0, 3), // changes position of shadow
), // ),
], // ],
), // ),
child: Row( // child: Row(
children: [ // children: [
const SizedBox( // const SizedBox(
child: Icon( // child: Icon(
Icons.location_on, // Icons.location_on,
color: Colors.blue, // color: Colors.blue,
size: 40.0, // size: 40.0,
), // ),
), // ),
const SizedBox(width: 10.0), // const SizedBox(width: 10.0),
SizedBox( // SizedBox(
child: Text( // child: Text(
text, // text,
style: const TextStyle( // style: const TextStyle(
fontSize: 24.0, // fontSize: 24.0,
fontWeight: FontWeight.bold, // fontWeight: FontWeight.bold,
), // ),
), // ),
), // ),
const Spacer(), // const Spacer(),
Container( // Container(
child: const Icon( // child: const Icon(
Icons.add_circle_outline, // Icons.add_circle_outline,
color: Colors.grey, // color: Colors.grey,
size: 24.0, // size: 24.0,
), // ),
), // ),
], // ],
), // ),
), // ),
); // );
} // }

View File

@ -1,85 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routiens/widgets/routine_dialogs/ac_dialog.dart';
import 'package:syncrow_web/pages/routiens/widgets/routine_dialogs/one_gang_switch_dialog.dart';
import 'package:syncrow_web/pages/routiens/widgets/routine_dialogs/three_gang_switch_dialog.dart';
import 'package:syncrow_web/pages/routiens/widgets/routine_dialogs/two_gang_switch_dialog.dart';
import 'package:syncrow_web/pages/routiens/models/device_functions.dart';
class DeviceDialogHelper {
static Future<Map<String, dynamic>?> showDeviceDialog(
BuildContext context,
Map<String, dynamic> data, {
required bool removeComparetors,
}) async {
final functions = data['functions'] as List<DeviceFunction>;
try {
final result = await _getDialogForDeviceType(
context,
data['productType'],
data,
functions,
removeComparetors: removeComparetors,
);
if (result != null) {
return result;
}
} catch (e) {
debugPrint('Error: $e');
}
return null;
}
static Future<Map<String, dynamic>?> _getDialogForDeviceType(
BuildContext context,
String productType,
Map<String, dynamic> data,
List<DeviceFunction> functions,
{required bool removeComparetors}) async {
final routineBloc = context.read<RoutineBloc>();
final deviceSelectedFunctions =
routineBloc.state.selectedFunctions[data['uniqueCustomId']] ?? [];
switch (productType) {
case 'AC':
return ACHelper.showACFunctionsDialog(
context,
functions,
data['device'],
deviceSelectedFunctions,
data['uniqueCustomId'],
removeComparetors);
case '1G':
return OneGangSwitchHelper.showSwitchFunctionsDialog(
context,
functions,
data['device'],
deviceSelectedFunctions,
data['uniqueCustomId'],
removeComparetors);
case '2G':
return TwoGangSwitchHelper.showSwitchFunctionsDialog(
context,
functions,
data['device'],
deviceSelectedFunctions,
data['uniqueCustomId'],
removeComparetors);
case '3G':
return ThreeGangSwitchHelper.showSwitchFunctionsDialog(
context,
functions,
data['device'],
deviceSelectedFunctions,
data['uniqueCustomId'],
removeComparetors);
default:
return null;
}
}
}

View File

@ -1,69 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routiens/view/create_new_routine_view.dart';
import 'package:syncrow_web/pages/routiens/widgets/main_routine_view/fetch_routine_scenes_automation.dart';
import 'package:syncrow_web/pages/routiens/widgets/main_routine_view/routine_view_card.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class RoutinesView extends StatefulWidget {
const RoutinesView({super.key});
@override
State<RoutinesView> createState() => _RoutinesViewState();
}
class _RoutinesViewState extends State<RoutinesView> {
@override
void initState() {
super.initState();
context.read<RoutineBloc>().add(FetchDevicesInRoutine());
}
@override
Widget build(BuildContext context) {
return BlocBuilder<RoutineBloc, RoutineState>(
builder: (context, state) {
if (state.createRoutineView) {
return const CreateNewRoutineView();
}
return Padding(
padding: const EdgeInsets.all(16),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
"Create New Routines",
style: Theme.of(context).textTheme.titleLarge?.copyWith(
color: ColorsManager.grayColor,
fontWeight: FontWeight.bold,
),
),
const SizedBox(
height: 10,
),
RoutineViewCard(
onTap: () {
context.read<RoutineBloc>().add(
(ResetRoutineState()),
);
BlocProvider.of<RoutineBloc>(context).add(
const CreateNewRoutineViewEvent(createRoutineView: true),
);
},
icon: Icons.add,
textString: '',
),
const SizedBox(
height: 15,
),
const Expanded(child: FetchRoutineScenesAutomation()),
],
),
);
},
);
}
}

View File

@ -1,143 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routiens/widgets/main_routine_view/routine_view_card.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
class FetchRoutineScenesAutomation extends StatefulWidget {
const FetchRoutineScenesAutomation({super.key});
@override
State<FetchRoutineScenesAutomation> createState() => _FetchRoutineScenesState();
}
class _FetchRoutineScenesState extends State<FetchRoutineScenesAutomation>
with HelperResponsiveLayout {
@override
void initState() {
super.initState();
context.read<RoutineBloc>()
..add(const LoadScenes(spaceId, communityId))
..add(const LoadAutomation(spaceId));
}
@override
Widget build(BuildContext context) {
return BlocBuilder<RoutineBloc, RoutineState>(
builder: (context, state) {
return state.isLoading
? const Center(
child: CircularProgressIndicator(),
)
: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
"Scenes (Tab to Run)",
style: Theme.of(context).textTheme.titleLarge?.copyWith(
color: ColorsManager.grayColor,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 10),
if (state.scenes.isEmpty)
Text(
"No scenes found",
style: context.textTheme.bodyMedium?.copyWith(
color: ColorsManager.grayColor,
),
),
if (state.scenes.isNotEmpty)
ConstrainedBox(
constraints: BoxConstraints(
maxHeight: isSmallScreenSize(context) ? 160 : 170,
),
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: state.scenes.length,
itemBuilder: (context, index) => Padding(
padding: EdgeInsets.only(
right: isSmallScreenSize(context) ? 4.0 : 8.0,
),
child: RoutineViewCard(
onTap: () {
BlocProvider.of<RoutineBloc>(context).add(
const CreateNewRoutineViewEvent(createRoutineView: true),
);
context.read<RoutineBloc>().add(
GetSceneDetails(
sceneId: state.scenes[index].id,
isTabToRun: true,
isUpdate: true,
),
);
},
textString: state.scenes[index].name,
icon: state.scenes[index].icon ?? Assets.logoHorizontal,
isFromScenes: true,
iconInBytes: state.scenes[index].iconInBytes,
),
),
),
),
const SizedBox(height: 15),
Text(
"Automations",
style: Theme.of(context).textTheme.titleLarge?.copyWith(
color: ColorsManager.grayColor,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 10),
if (state.automations.isEmpty)
Text(
"No automations found",
style: context.textTheme.bodyMedium?.copyWith(
color: ColorsManager.grayColor,
),
),
if (state.automations.isNotEmpty)
ConstrainedBox(
constraints: BoxConstraints(
maxHeight: isSmallScreenSize(context) ? 160 : 170,
),
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: state.automations.length,
itemBuilder: (context, index) => Padding(
padding: EdgeInsets.only(
right: isSmallScreenSize(context) ? 4.0 : 8.0,
),
child: RoutineViewCard(
onTap: () {
BlocProvider.of<RoutineBloc>(context).add(
const CreateNewRoutineViewEvent(createRoutineView: true),
);
context.read<RoutineBloc>().add(
GetAutomationDetails(
automationId: state.automations[index].id,
isAutomation: true,
isUpdate: true),
);
},
textString: state.automations[index].name,
icon: state.automations[index].icon ?? Assets.automation,
),
),
),
),
],
),
),
);
},
);
}
}

View File

@ -1,7 +1,7 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/routiens/bloc/effective_period/effect_period_event.dart'; import 'package:syncrow_web/pages/routines/bloc/effective_period/effect_period_event.dart';
import 'package:syncrow_web/pages/routiens/bloc/effective_period/effect_period_state.dart'; import 'package:syncrow_web/pages/routines/bloc/effective_period/effect_period_state.dart';
import 'package:syncrow_web/utils/constants/app_enum.dart'; import 'package:syncrow_web/utils/constants/app_enum.dart';
class EffectPeriodBloc extends Bloc<EffectPeriodEvent, EffectPeriodState> { class EffectPeriodBloc extends Bloc<EffectPeriodEvent, EffectPeriodState> {

View File

@ -1,5 +1,5 @@
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/routiens/models/create_scene_and_autoamtion/create_automation_model.dart'; import 'package:syncrow_web/pages/routines/models/create_scene_and_autoamtion/create_automation_model.dart';
import 'package:syncrow_web/utils/constants/app_enum.dart'; import 'package:syncrow_web/utils/constants/app_enum.dart';
abstract class EffectPeriodEvent extends Equatable { abstract class EffectPeriodEvent extends Equatable {

View File

@ -2,7 +2,7 @@ import 'dart:async';
import 'package:bloc/bloc.dart'; import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/routiens/models/device_functions.dart'; import 'package:syncrow_web/pages/routines/models/device_functions.dart';
part 'functions_bloc_event.dart'; part 'functions_bloc_event.dart';
part 'functions_bloc_state.dart'; part 'functions_bloc_state.dart';
@ -26,8 +26,7 @@ class FunctionBloc extends Bloc<FunctionBlocEvent, FunctionBlocState> {
functionCode: event.functionData.functionCode, functionCode: event.functionData.functionCode,
operationName: event.functionData.operationName, operationName: event.functionData.operationName,
value: event.functionData.value ?? existingData.value, value: event.functionData.value ?? existingData.value,
valueDescription: event.functionData.valueDescription ?? valueDescription: event.functionData.valueDescription ?? existingData.valueDescription,
existingData.valueDescription,
condition: event.functionData.condition ?? existingData.condition, condition: event.functionData.condition ?? existingData.condition,
); );
} else { } else {
@ -59,10 +58,8 @@ class FunctionBloc extends Bloc<FunctionBlocEvent, FunctionBlocState> {
); );
} }
FutureOr<void> _onSelectFunction( FutureOr<void> _onSelectFunction(SelectFunction event, Emitter<FunctionBlocState> emit) {
SelectFunction event, Emitter<FunctionBlocState> emit) {
emit(state.copyWith( emit(state.copyWith(
selectedFunction: event.functionCode, selectedFunction: event.functionCode, selectedOperationName: event.operationName));
selectedOperationName: event.operationName));
} }
} }

View File

@ -4,12 +4,12 @@ import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
import 'package:syncrow_web/pages/routiens/models/create_scene_and_autoamtion/create_automation_model.dart'; import 'package:syncrow_web/pages/routines/models/create_scene_and_autoamtion/create_automation_model.dart';
import 'package:syncrow_web/pages/routiens/models/create_scene_and_autoamtion/create_scene_model.dart'; import 'package:syncrow_web/pages/routines/models/create_scene_and_autoamtion/create_scene_model.dart';
import 'package:syncrow_web/pages/routiens/models/delay/delay_fucntions.dart'; import 'package:syncrow_web/pages/routines/models/delay/delay_fucntions.dart';
import 'package:syncrow_web/pages/routiens/models/device_functions.dart'; import 'package:syncrow_web/pages/routines/models/device_functions.dart';
import 'package:syncrow_web/pages/routiens/models/routine_details_model.dart'; import 'package:syncrow_web/pages/routines/models/routine_details_model.dart';
import 'package:syncrow_web/pages/routiens/models/routine_model.dart'; import 'package:syncrow_web/pages/routines/models/routine_model.dart';
import 'package:syncrow_web/services/devices_mang_api.dart'; import 'package:syncrow_web/services/devices_mang_api.dart';
import 'package:syncrow_web/services/routines_api.dart'; import 'package:syncrow_web/services/routines_api.dart';
import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/constants/assets.dart';
@ -19,8 +19,8 @@ import 'package:uuid/uuid.dart';
part 'routine_event.dart'; part 'routine_event.dart';
part 'routine_state.dart'; part 'routine_state.dart';
const spaceId = '25c96044-fadf-44bb-93c7-3c079e527ce6'; String spaceId = '25c96044-fadf-44bb-93c7-3c079e527ce6';
const communityId = 'aff21a57-2f91-4e5c-b99b-0182c3ab65a9'; String communityId = 'aff21a57-2f91-4e5c-b99b-0182c3ab65a9';
class RoutineBloc extends Bloc<RoutineEvent, RoutineState> { class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
RoutineBloc() : super(const RoutineState()) { RoutineBloc() : super(const RoutineState()) {
@ -57,8 +57,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
emit(state.copyWith(routineTab: event.isRoutineTab, createRoutineView: false)); emit(state.copyWith(routineTab: event.isRoutineTab, createRoutineView: false));
add(ResetRoutineState()); add(ResetRoutineState());
if (event.isRoutineTab) { if (event.isRoutineTab) {
add(const LoadScenes(spaceId, communityId)); add(LoadScenes(spaceId, communityId));
add(const LoadAutomation(spaceId)); add(LoadAutomation(spaceId));
} }
} }
@ -156,18 +156,25 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
emit(state.copyWith(isLoading: true, errorMessage: null)); emit(state.copyWith(isLoading: true, errorMessage: null));
try { try {
final scenes = await SceneApi.getScenesByUnitId(event.unitId, event.communityId); spaceId = event.spaceId;
communityId = event.communityId;
List<ScenesModel> scenes = [];
if (communityId.isNotEmpty && spaceId.isNotEmpty) {
scenes = await SceneApi.getScenes(event.spaceId, event.communityId);
}
emit(state.copyWith( emit(state.copyWith(
scenes: scenes, scenes: scenes,
isLoading: false, isLoading: false,
)); ));
} catch (e) { } catch (e) {
emit(state.copyWith( emit(state.copyWith(
isLoading: false, isLoading: false,
loadScenesErrorMessage: 'Failed to load scenes', loadScenesErrorMessage: 'Failed to load scenes',
errorMessage: '', errorMessage: '',
loadAutomationErrorMessage: '', loadAutomationErrorMessage: '',
)); scenes: []));
} }
} }
@ -175,27 +182,22 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
emit(state.copyWith(isLoading: true, errorMessage: null)); emit(state.copyWith(isLoading: true, errorMessage: null));
try { try {
final automations = await SceneApi.getAutomationByUnitId(event.unitId); spaceId = event.spaceId;
if (automations.isNotEmpty) { List<ScenesModel> automations = [];
emit(state.copyWith( if (spaceId.isNotEmpty) {
automations: automations, automations = await SceneApi.getAutomation(event.spaceId);
isLoading: false, }
)); emit(state.copyWith(
} else { automations: automations,
emit(state.copyWith( isLoading: false,
));
} catch (e) {
emit(state.copyWith(
isLoading: false, isLoading: false,
loadAutomationErrorMessage: 'Failed to load automations', loadAutomationErrorMessage: 'Failed to load automations',
errorMessage: '', errorMessage: '',
loadScenesErrorMessage: '', loadScenesErrorMessage: '',
)); automations: []));
}
} catch (e) {
emit(state.copyWith(
isLoading: false,
loadAutomationErrorMessage: 'Failed to load automations',
errorMessage: '',
loadScenesErrorMessage: '',
));
} }
} }
@ -290,8 +292,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
final result = await SceneApi.createScene(createSceneModel); final result = await SceneApi.createScene(createSceneModel);
if (result['success']) { if (result['success']) {
add(ResetRoutineState()); add(ResetRoutineState());
add(const LoadScenes(spaceId, communityId)); add(LoadScenes(spaceId, communityId));
add(const LoadAutomation(spaceId)); add(LoadAutomation(spaceId));
} else { } else {
emit(state.copyWith( emit(state.copyWith(
isLoading: false, isLoading: false,
@ -419,8 +421,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
final result = await SceneApi.createAutomation(createAutomationModel); final result = await SceneApi.createAutomation(createAutomationModel);
if (result['success']) { if (result['success']) {
add(ResetRoutineState()); add(ResetRoutineState());
add(const LoadAutomation(spaceId)); add(LoadAutomation(spaceId));
add(const LoadScenes(spaceId, communityId)); add(LoadScenes(spaceId, communityId));
} else { } else {
emit(state.copyWith( emit(state.copyWith(
isLoading: false, isLoading: false,
@ -785,8 +787,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
SceneApi.deleteAutomation(unitUuid: spaceId, automationId: state.automationId ?? ''); SceneApi.deleteAutomation(unitUuid: spaceId, automationId: state.automationId ?? '');
} }
add(const LoadScenes(spaceId, communityId)); add(LoadScenes(spaceId, communityId));
add(const LoadAutomation(spaceId)); add(LoadAutomation(spaceId));
add(ResetRoutineState()); add(ResetRoutineState());
emit(state.copyWith(isLoading: false, createRoutineView: false)); emit(state.copyWith(isLoading: false, createRoutineView: false));
} catch (e) { } catch (e) {
@ -814,7 +816,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
FutureOr<void> _fetchDevices(FetchDevicesInRoutine event, Emitter<RoutineState> emit) async { FutureOr<void> _fetchDevices(FetchDevicesInRoutine event, Emitter<RoutineState> emit) async {
emit(state.copyWith(isLoading: true)); emit(state.copyWith(isLoading: true));
try { try {
final devices = await DevicesManagementApi().fetchDevices(); final devices = await DevicesManagementApi().fetchDevices('', '');
emit(state.copyWith(isLoading: false, devices: devices)); emit(state.copyWith(isLoading: false, devices: devices));
} catch (e) { } catch (e) {
@ -892,8 +894,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
final result = await SceneApi.updateScene(createSceneModel, state.sceneId ?? ''); final result = await SceneApi.updateScene(createSceneModel, state.sceneId ?? '');
if (result['success']) { if (result['success']) {
add(ResetRoutineState()); add(ResetRoutineState());
add(const LoadScenes(spaceId, communityId)); add(LoadScenes(spaceId, communityId));
add(const LoadAutomation(spaceId)); add(LoadAutomation(spaceId));
} else { } else {
emit(state.copyWith( emit(state.copyWith(
isLoading: false, isLoading: false,
@ -1021,8 +1023,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
if (result['success']) { if (result['success']) {
add(ResetRoutineState()); add(ResetRoutineState());
add(const LoadAutomation(spaceId)); add(LoadAutomation(spaceId));
add(const LoadScenes(spaceId, communityId)); add(LoadScenes(spaceId, communityId));
} else { } else {
emit(state.copyWith( emit(state.copyWith(
isLoading: false, isLoading: false,

View File

@ -27,22 +27,22 @@ class AddToThenContainer extends RoutineEvent {
} }
class LoadScenes extends RoutineEvent { class LoadScenes extends RoutineEvent {
final String unitId; final String spaceId;
final String communityId; final String communityId;
const LoadScenes(this.unitId, this.communityId); const LoadScenes(this.spaceId, this.communityId);
@override @override
List<Object> get props => [unitId, communityId]; List<Object> get props => [spaceId, communityId];
} }
class LoadAutomation extends RoutineEvent { class LoadAutomation extends RoutineEvent {
final String unitId; final String spaceId;
const LoadAutomation(this.unitId); const LoadAutomation(this.spaceId);
@override @override
List<Object> get props => [unitId]; List<Object> get props => [spaceId];
} }
class AddFunctionToRoutine extends RoutineEvent { class AddFunctionToRoutine extends RoutineEvent {

View File

@ -1,7 +1,7 @@
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/routiens/bloc/setting_bloc/setting_event.dart'; import 'package:syncrow_web/pages/routines/bloc/setting_bloc/setting_event.dart';
import 'package:syncrow_web/pages/routiens/bloc/setting_bloc/setting_state.dart'; import 'package:syncrow_web/pages/routines/bloc/setting_bloc/setting_state.dart';
import 'package:syncrow_web/pages/routiens/models/icon_model.dart'; import 'package:syncrow_web/pages/routines/models/icon_model.dart';
import 'package:syncrow_web/services/routines_api.dart'; import 'package:syncrow_web/services/routines_api.dart';
class SettingBloc extends Bloc<SettingEvent, SettingState> { class SettingBloc extends Bloc<SettingEvent, SettingState> {

View File

@ -1,5 +1,5 @@
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/routiens/models/icon_model.dart'; import 'package:syncrow_web/pages/routines/models/icon_model.dart';
abstract class SettingState extends Equatable { abstract class SettingState extends Equatable {
const SettingState(); const SettingState();

View File

@ -0,0 +1,62 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/ac_dialog.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/one_gang_switch_dialog.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/three_gang_switch_dialog.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/two_gang_switch_dialog.dart';
import 'package:syncrow_web/pages/routines/models/device_functions.dart';
class DeviceDialogHelper {
static Future<Map<String, dynamic>?> showDeviceDialog(
BuildContext context,
Map<String, dynamic> data, {
required bool removeComparetors,
}) async {
final functions = data['functions'] as List<DeviceFunction>;
try {
final result = await _getDialogForDeviceType(
context,
data['productType'],
data,
functions,
removeComparetors: removeComparetors,
);
if (result != null) {
return result;
}
} catch (e) {
debugPrint('Error: $e');
}
return null;
}
static Future<Map<String, dynamic>?> _getDialogForDeviceType(BuildContext context,
String productType, Map<String, dynamic> data, List<DeviceFunction> functions,
{required bool removeComparetors}) async {
final routineBloc = context.read<RoutineBloc>();
final deviceSelectedFunctions =
routineBloc.state.selectedFunctions[data['uniqueCustomId']] ?? [];
switch (productType) {
case 'AC':
return ACHelper.showACFunctionsDialog(context, functions, data['device'],
deviceSelectedFunctions, data['uniqueCustomId'], removeComparetors);
case '1G':
return OneGangSwitchHelper.showSwitchFunctionsDialog(context, functions, data['device'],
deviceSelectedFunctions, data['uniqueCustomId'], removeComparetors);
case '2G':
return TwoGangSwitchHelper.showSwitchFunctionsDialog(context, functions, data['device'],
deviceSelectedFunctions, data['uniqueCustomId'], removeComparetors);
case '3G':
return ThreeGangSwitchHelper.showSwitchFunctionsDialog(context, functions, data['device'],
deviceSelectedFunctions, data['uniqueCustomId'], removeComparetors);
default:
return null;
}
}
}

View File

@ -3,9 +3,9 @@ import 'dart:convert';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routiens/widgets/dialog_header.dart'; import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart';
import 'package:syncrow_web/pages/routiens/widgets/dialog_footer.dart'; import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart';

View File

@ -1,6 +1,6 @@
import 'package:syncrow_web/pages/device_managment/ac/model/ac_model.dart'; import 'package:syncrow_web/pages/device_managment/ac/model/ac_model.dart';
import 'package:syncrow_web/pages/routiens/models/ac/ac_operational_value.dart'; import 'package:syncrow_web/pages/routines/models/ac/ac_operational_value.dart';
import 'package:syncrow_web/pages/routiens/models/device_functions.dart'; import 'package:syncrow_web/pages/routines/models/device_functions.dart';
import 'package:syncrow_web/utils/constants/app_enum.dart'; import 'package:syncrow_web/utils/constants/app_enum.dart';
import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/constants/assets.dart';

View File

@ -1,5 +1,5 @@
import 'package:syncrow_web/pages/routiens/models/gang_switches/base_switch_function.dart'; import 'package:syncrow_web/pages/routines/models/gang_switches/base_switch_function.dart';
import 'package:syncrow_web/pages/routiens/models/gang_switches/switch_operational_value.dart'; import 'package:syncrow_web/pages/routines/models/gang_switches/switch_operational_value.dart';
import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/constants/assets.dart';
class DelayFunction extends BaseSwitchFunction { class DelayFunction extends BaseSwitchFunction {

View File

@ -1,5 +1,5 @@
import 'package:syncrow_web/pages/routiens/models/device_functions.dart'; import 'package:syncrow_web/pages/routines/models/device_functions.dart';
import 'package:syncrow_web/pages/routiens/models/gang_switches/switch_operational_value.dart'; import 'package:syncrow_web/pages/routines/models/gang_switches/switch_operational_value.dart';
abstract class BaseSwitchFunction extends DeviceFunction<bool> { abstract class BaseSwitchFunction extends DeviceFunction<bool> {
BaseSwitchFunction({ BaseSwitchFunction({

View File

@ -1,5 +1,5 @@
import 'package:syncrow_web/pages/routiens/models/gang_switches/base_switch_function.dart'; import 'package:syncrow_web/pages/routines/models/gang_switches/base_switch_function.dart';
import 'package:syncrow_web/pages/routiens/models/gang_switches/switch_operational_value.dart'; import 'package:syncrow_web/pages/routines/models/gang_switches/switch_operational_value.dart';
import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/constants/assets.dart';
class OneGangSwitchFunction extends BaseSwitchFunction { class OneGangSwitchFunction extends BaseSwitchFunction {

View File

@ -1,5 +1,5 @@
import 'package:syncrow_web/pages/routiens/models/gang_switches/base_switch_function.dart'; import 'package:syncrow_web/pages/routines/models/gang_switches/base_switch_function.dart';
import 'package:syncrow_web/pages/routiens/models/gang_switches/switch_operational_value.dart'; import 'package:syncrow_web/pages/routines/models/gang_switches/switch_operational_value.dart';
import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/constants/assets.dart';
class ThreeGangSwitch1Function extends BaseSwitchFunction { class ThreeGangSwitch1Function extends BaseSwitchFunction {
@ -26,8 +26,7 @@ class ThreeGangSwitch1Function extends BaseSwitchFunction {
} }
class ThreeGangCountdown1Function extends BaseSwitchFunction { class ThreeGangCountdown1Function extends BaseSwitchFunction {
ThreeGangCountdown1Function( ThreeGangCountdown1Function({required super.deviceId, required super.deviceName})
{required super.deviceId, required super.deviceName})
: super( : super(
code: 'countdown_1', code: 'countdown_1',
operationName: 'Light 1 Countdown', operationName: 'Light 1 Countdown',
@ -71,8 +70,7 @@ class ThreeGangSwitch2Function extends BaseSwitchFunction {
} }
class ThreeGangCountdown2Function extends BaseSwitchFunction { class ThreeGangCountdown2Function extends BaseSwitchFunction {
ThreeGangCountdown2Function( ThreeGangCountdown2Function({required super.deviceId, required super.deviceName})
{required super.deviceId, required super.deviceName})
: super( : super(
code: 'countdown_2', code: 'countdown_2',
operationName: 'Light 2 Countdown', operationName: 'Light 2 Countdown',
@ -116,8 +114,7 @@ class ThreeGangSwitch3Function extends BaseSwitchFunction {
} }
class ThreeGangCountdown3Function extends BaseSwitchFunction { class ThreeGangCountdown3Function extends BaseSwitchFunction {
ThreeGangCountdown3Function( ThreeGangCountdown3Function({required super.deviceId, required super.deviceName})
{required super.deviceId, required super.deviceName})
: super( : super(
code: 'countdown_3', code: 'countdown_3',
operationName: 'Light 3 Countdown', operationName: 'Light 3 Countdown',

View File

@ -1,5 +1,5 @@
import 'package:syncrow_web/pages/routiens/models/gang_switches/base_switch_function.dart'; import 'package:syncrow_web/pages/routines/models/gang_switches/base_switch_function.dart';
import 'package:syncrow_web/pages/routiens/models/gang_switches/switch_operational_value.dart'; import 'package:syncrow_web/pages/routines/models/gang_switches/switch_operational_value.dart';
import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/constants/assets.dart';
class TwoGangSwitch1Function extends BaseSwitchFunction { class TwoGangSwitch1Function extends BaseSwitchFunction {
@ -49,8 +49,7 @@ class TwoGangSwitch2Function extends BaseSwitchFunction {
} }
class TwoGangCountdown1Function extends BaseSwitchFunction { class TwoGangCountdown1Function extends BaseSwitchFunction {
TwoGangCountdown1Function( TwoGangCountdown1Function({required super.deviceId, required super.deviceName})
{required super.deviceId, required super.deviceName})
: super( : super(
code: 'countdown_1', code: 'countdown_1',
operationName: 'Light 1 Countdown', operationName: 'Light 1 Countdown',
@ -71,8 +70,7 @@ class TwoGangCountdown1Function extends BaseSwitchFunction {
} }
class TwoGangCountdown2Function extends BaseSwitchFunction { class TwoGangCountdown2Function extends BaseSwitchFunction {
TwoGangCountdown2Function( TwoGangCountdown2Function({required super.deviceId, required super.deviceName})
{required super.deviceId, required super.deviceName})
: super( : super(
code: 'countdown_2', code: 'countdown_2',
operationName: 'Light 2 Countdown', operationName: 'Light 2 Countdown',

View File

@ -1,7 +1,7 @@
import 'dart:convert'; import 'dart:convert';
import 'package:syncrow_web/pages/routiens/models/create_scene_and_autoamtion/create_automation_model.dart'; import 'package:syncrow_web/pages/routines/models/create_scene_and_autoamtion/create_automation_model.dart';
import 'package:syncrow_web/pages/routiens/models/create_scene_and_autoamtion/create_scene_model.dart'; import 'package:syncrow_web/pages/routines/models/create_scene_and_autoamtion/create_scene_model.dart';
class RoutineDetailsModel { class RoutineDetailsModel {
final String spaceUuid; final String spaceUuid;

View File

@ -1,8 +1,8 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/routiens/widgets/conditions_routines_devices_view.dart'; import 'package:syncrow_web/pages/routines/widgets/conditions_routines_devices_view.dart';
import 'package:syncrow_web/pages/routiens/widgets/if_container.dart'; import 'package:syncrow_web/pages/routines/widgets/if_container.dart';
import 'package:syncrow_web/pages/routiens/widgets/routine_search_and_buttons.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_search_and_buttons.dart';
import 'package:syncrow_web/pages/routiens/widgets/then_container.dart'; import 'package:syncrow_web/pages/routines/widgets/then_container.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
class CreateNewRoutineView extends StatelessWidget { class CreateNewRoutineView extends StatelessWidget {

View File

@ -1,7 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/routiens/widgets/routine_dialogs/effictive_period_dialog.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/effictive_period_dialog.dart';
import 'package:syncrow_web/pages/routiens/widgets/period_option.dart'; import 'package:syncrow_web/pages/routines/widgets/period_option.dart';
import 'package:syncrow_web/pages/routiens/widgets/repeat_days.dart'; import 'package:syncrow_web/pages/routines/widgets/repeat_days.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
class EffectivePeriodView extends StatelessWidget { class EffectivePeriodView extends StatelessWidget {

View File

@ -0,0 +1,91 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_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/widgets/main_routine_view/fetch_routine_scenes_automation.dart';
import 'package:syncrow_web/pages/routines/widgets/main_routine_view/routine_view_card.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
import 'package:syncrow_web/pages/space_tree/view/space_tree_view.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/snack_bar.dart';
class RoutinesView extends StatefulWidget {
const RoutinesView({super.key});
@override
State<RoutinesView> createState() => _RoutinesViewState();
}
class _RoutinesViewState extends State<RoutinesView> {
@override
void initState() {
super.initState();
context.read<RoutineBloc>().add(FetchDevicesInRoutine());
}
@override
Widget build(BuildContext context) {
return BlocBuilder<RoutineBloc, RoutineState>(
builder: (context, state) {
if (state.createRoutineView) {
return const CreateNewRoutineView();
}
return Row(
children: [
Expanded(
child: SpaceTreeView(
onSelect: () {},
)),
Expanded(
flex: 4,
child: ListView(children: [
Container(
padding: const EdgeInsets.all(16),
height: MediaQuery.sizeOf(context).height,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
"Create New Routines",
style: Theme.of(context).textTheme.titleLarge?.copyWith(
color: ColorsManager.grayColor,
fontWeight: FontWeight.bold,
),
),
const SizedBox(
height: 10,
),
RoutineViewCard(
onTap: () {
if (context.read<SpaceTreeBloc>().selectedCommunityId.isNotEmpty &&
context.read<SpaceTreeBloc>().selectedSpaceId.isNotEmpty) {
context.read<RoutineBloc>().add(
(ResetRoutineState()),
);
BlocProvider.of<RoutineBloc>(context).add(
const CreateNewRoutineViewEvent(createRoutineView: true),
);
} else {
CustomSnackBar.redSnackBar('Please select a space');
}
},
icon: Icons.add,
textString: '',
),
const SizedBox(
height: 15,
),
const Expanded(child: FetchRoutineScenesAutomation()),
],
),
),
]),
),
],
);
},
);
}
}

View File

@ -1,11 +1,11 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routiens/widgets/dragable_card.dart'; import 'package:syncrow_web/pages/routines/widgets/dragable_card.dart';
import 'package:syncrow_web/pages/routiens/widgets/routine_devices.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_devices.dart';
import 'package:syncrow_web/pages/routiens/widgets/routines_title_widget.dart'; import 'package:syncrow_web/pages/routines/widgets/routines_title_widget.dart';
import 'package:syncrow_web/pages/routiens/widgets/scenes_and_automations.dart'; import 'package:syncrow_web/pages/routines/widgets/scenes_and_automations.dart';
import 'package:syncrow_web/pages/routiens/widgets/search_bar_condition_title.dart'; import 'package:syncrow_web/pages/routines/widgets/search_bar_condition_title.dart';
import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/constants/assets.dart';
class ConditionsRoutinesDevicesView extends StatelessWidget { class ConditionsRoutinesDevicesView extends StatelessWidget {

View File

@ -1,7 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/common/custom_dialog.dart'; import 'package:syncrow_web/pages/common/custom_dialog.dart';
import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
class DeleteSceneWidget extends StatelessWidget { class DeleteSceneWidget extends StatelessWidget {

View File

@ -3,8 +3,8 @@ import 'dart:convert';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routiens/models/device_functions.dart'; import 'package:syncrow_web/pages/routines/models/device_functions.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';

View File

@ -1,8 +1,8 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routiens/helper/dialog_helper/device_dialog_helper.dart'; import 'package:syncrow_web/pages/routines/helper/dialog_helper/device_dialog_helper.dart';
import 'package:syncrow_web/pages/routiens/widgets/dragable_card.dart'; import 'package:syncrow_web/pages/routines/widgets/dragable_card.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart';
@ -26,9 +26,7 @@ class IfContainer extends StatelessWidget {
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
const Text('IF', const Text('IF', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
style: TextStyle(
fontSize: 18, fontWeight: FontWeight.bold)),
if (state.isAutomation && state.ifItems.isNotEmpty) if (state.isAutomation && state.ifItems.isNotEmpty)
AutomationOperatorSelector( AutomationOperatorSelector(
selectedOperator: state.selectedAutomationOperator), selectedOperator: state.selectedAutomationOperator),
@ -55,44 +53,34 @@ class IfContainer extends StatelessWidget {
(index) => GestureDetector( (index) => GestureDetector(
onTap: () async { onTap: () async {
if (!state.isTabToRun) { if (!state.isTabToRun) {
final result = await DeviceDialogHelper final result = await DeviceDialogHelper.showDeviceDialog(
.showDeviceDialog( context, state.ifItems[index],
context, state.ifItems[index], removeComparetors: false);
removeComparetors: false);
if (result != null) { if (result != null) {
context.read<RoutineBloc>().add( context
AddToIfContainer( .read<RoutineBloc>()
state.ifItems[index], false)); .add(AddToIfContainer(state.ifItems[index], false));
} else if (![ } else if (!['AC', '1G', '2G', '3G']
'AC', .contains(state.ifItems[index]['productType'])) {
'1G', context
'2G', .read<RoutineBloc>()
'3G' .add(AddToIfContainer(state.ifItems[index], false));
].contains(
state.ifItems[index]['productType'])) {
context.read<RoutineBloc>().add(
AddToIfContainer(
state.ifItems[index], false));
} }
} }
}, },
child: DraggableCard( child: DraggableCard(
imagePath: imagePath: state.ifItems[index]['imagePath'] ?? '',
state.ifItems[index]['imagePath'] ?? '',
title: state.ifItems[index]['title'] ?? '', title: state.ifItems[index]['title'] ?? '',
deviceData: state.ifItems[index], deviceData: state.ifItems[index],
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 8),
horizontal: 4, vertical: 8),
isFromThen: false, isFromThen: false,
isFromIf: true, isFromIf: true,
onRemove: () { onRemove: () {
context.read<RoutineBloc>().add( context.read<RoutineBloc>().add(RemoveDragCard(
RemoveDragCard( index: index,
index: index, isFromThen: false,
isFromThen: false, key: state.ifItems[index]['uniqueCustomId']));
key: state.ifItems[index]
['uniqueCustomId']));
}, },
), ),
)), )),
@ -113,23 +101,15 @@ class IfContainer extends StatelessWidget {
if (!state.isTabToRun) { if (!state.isTabToRun) {
if (mutableData['deviceId'] == 'tab_to_run') { if (mutableData['deviceId'] == 'tab_to_run') {
context context.read<RoutineBloc>().add(AddToIfContainer(mutableData, true));
.read<RoutineBloc>()
.add(AddToIfContainer(mutableData, true));
} else { } else {
final result = await DeviceDialogHelper.showDeviceDialog( final result = await DeviceDialogHelper.showDeviceDialog(context, mutableData,
context, mutableData,
removeComparetors: false); removeComparetors: false);
if (result != null) { if (result != null) {
context context.read<RoutineBloc>().add(AddToIfContainer(mutableData, false));
.read<RoutineBloc>() } else if (!['AC', '1G', '2G', '3G'].contains(mutableData['productType'])) {
.add(AddToIfContainer(mutableData, false)); context.read<RoutineBloc>().add(AddToIfContainer(mutableData, false));
} else if (!['AC', '1G', '2G', '3G']
.contains(mutableData['productType'])) {
context
.read<RoutineBloc>()
.add(AddToIfContainer(mutableData, false));
} }
} }
} }
@ -175,9 +155,7 @@ class AutomationOperatorSelector extends StatelessWidget {
), ),
), ),
onPressed: () { onPressed: () {
context context.read<RoutineBloc>().add(const ChangeAutomationOperator(operator: 'or'));
.read<RoutineBloc>()
.add(const ChangeAutomationOperator(operator: 'or'));
}, },
), ),
Container( Container(
@ -203,9 +181,7 @@ class AutomationOperatorSelector extends StatelessWidget {
), ),
), ),
onPressed: () { onPressed: () {
context context.read<RoutineBloc>().add(const ChangeAutomationOperator(operator: 'and'));
.read<RoutineBloc>()
.add(const ChangeAutomationOperator(operator: 'and'));
}, },
), ),
], ],

View File

@ -0,0 +1,143 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routines/widgets/main_routine_view/routine_view_card.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
class FetchRoutineScenesAutomation extends StatefulWidget {
const FetchRoutineScenesAutomation({super.key});
@override
State<FetchRoutineScenesAutomation> createState() => _FetchRoutineScenesState();
}
class _FetchRoutineScenesState extends State<FetchRoutineScenesAutomation>
with HelperResponsiveLayout {
@override
void initState() {
super.initState();
context.read<RoutineBloc>()
..add(LoadScenes(context.read<SpaceTreeBloc>().selectedSpaceId,
context.read<SpaceTreeBloc>().selectedCommunityId))
..add(LoadAutomation(context.read<SpaceTreeBloc>().selectedSpaceId));
}
@override
Widget build(BuildContext context) {
return BlocBuilder<RoutineBloc, RoutineState>(
builder: (context, state) {
return state.isLoading
? const Center(
child: CircularProgressIndicator(),
)
: Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
"Scenes (Tab to Run)",
style: Theme.of(context).textTheme.titleLarge?.copyWith(
color: ColorsManager.grayColor,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 10),
if (state.scenes.isEmpty)
Text(
"No scenes found",
style: context.textTheme.bodyMedium?.copyWith(
color: ColorsManager.grayColor,
),
),
if (state.scenes.isNotEmpty)
ConstrainedBox(
constraints: BoxConstraints(
maxHeight: isSmallScreenSize(context) ? 160 : 170,
maxWidth: MediaQuery.sizeOf(context).width * 0.7),
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: state.scenes.length,
itemBuilder: (context, index) => Padding(
padding: EdgeInsets.only(
right: isSmallScreenSize(context) ? 4.0 : 8.0,
),
child: RoutineViewCard(
onTap: () {
BlocProvider.of<RoutineBloc>(context).add(
const CreateNewRoutineViewEvent(createRoutineView: true),
);
context.read<RoutineBloc>().add(
GetSceneDetails(
sceneId: state.scenes[index].id,
isTabToRun: true,
isUpdate: true,
),
);
},
textString: state.scenes[index].name,
icon: state.scenes[index].icon ?? Assets.logoHorizontal,
isFromScenes: true,
iconInBytes: state.scenes[index].iconInBytes,
),
),
),
),
const SizedBox(height: 15),
Text(
"Automations",
style: Theme.of(context).textTheme.titleLarge?.copyWith(
color: ColorsManager.grayColor,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 10),
if (state.automations.isEmpty)
Text(
"No automations found",
style: context.textTheme.bodyMedium?.copyWith(
color: ColorsManager.grayColor,
),
),
if (state.automations.isNotEmpty)
ConstrainedBox(
constraints: BoxConstraints(
maxHeight: isSmallScreenSize(context) ? 160 : 170,
maxWidth: MediaQuery.sizeOf(context).width * 0.7),
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: state.automations.length,
itemBuilder: (context, index) => Padding(
padding: EdgeInsets.only(
right: isSmallScreenSize(context) ? 4.0 : 8.0,
),
child: RoutineViewCard(
onTap: () {
BlocProvider.of<RoutineBloc>(context).add(
const CreateNewRoutineViewEvent(createRoutineView: true),
);
context.read<RoutineBloc>().add(
GetAutomationDetails(
automationId: state.automations[index].id,
isAutomation: true,
isUpdate: true),
);
},
textString: state.automations[index].name,
icon: state.automations[index].icon ?? Assets.automation,
),
),
),
),
],
),
);
},
);
}
}

View File

@ -70,15 +70,13 @@ class RoutineViewCard extends StatelessWidget with HelperResponsiveLayout {
height: iconSize, height: iconSize,
width: iconSize, width: iconSize,
child: (isFromScenes ?? false) child: (isFromScenes ?? false)
? (iconInBytes != null && ? (iconInBytes != null && iconInBytes?.isNotEmpty == true)
iconInBytes?.isNotEmpty == true)
? Image.memory( ? Image.memory(
iconInBytes!, iconInBytes!,
height: iconSize, height: iconSize,
width: iconSize, width: iconSize,
fit: BoxFit.contain, fit: BoxFit.contain,
errorBuilder: (context, error, stackTrace) => errorBuilder: (context, error, stackTrace) => Image.asset(
Image.asset(
Assets.logo, Assets.logo,
height: iconSize, height: iconSize,
width: iconSize, width: iconSize,

View File

@ -1,9 +1,9 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/routiens/bloc/effective_period/effect_period_bloc.dart'; import 'package:syncrow_web/pages/routines/bloc/effective_period/effect_period_bloc.dart';
import 'package:syncrow_web/pages/routiens/bloc/effective_period/effect_period_event.dart'; import 'package:syncrow_web/pages/routines/bloc/effective_period/effect_period_event.dart';
import 'package:syncrow_web/pages/routiens/bloc/effective_period/effect_period_state.dart'; import 'package:syncrow_web/pages/routines/bloc/effective_period/effect_period_state.dart';
import 'package:syncrow_web/pages/routiens/widgets/routine_dialogs/effictive_period_dialog.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/effictive_period_dialog.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/app_enum.dart'; import 'package:syncrow_web/utils/constants/app_enum.dart';

View File

@ -1,8 +1,8 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/routiens/bloc/effective_period/effect_period_bloc.dart'; import 'package:syncrow_web/pages/routines/bloc/effective_period/effect_period_bloc.dart';
import 'package:syncrow_web/pages/routiens/bloc/effective_period/effect_period_event.dart'; import 'package:syncrow_web/pages/routines/bloc/effective_period/effect_period_event.dart';
import 'package:syncrow_web/pages/routiens/bloc/effective_period/effect_period_state.dart'; import 'package:syncrow_web/pages/routines/bloc/effective_period/effect_period_state.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
class RepeatDays extends StatelessWidget { class RepeatDays extends StatelessWidget {

View File

@ -1,8 +1,8 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routiens/widgets/dragable_card.dart'; import 'package:syncrow_web/pages/routines/widgets/dragable_card.dart';
class RoutineDevices extends StatelessWidget { class RoutineDevices extends StatelessWidget {
const RoutineDevices({super.key}); const RoutineDevices({super.key});
@ -35,9 +35,7 @@ class RoutineDevices extends StatelessWidget {
children: deviceList.asMap().entries.map((entry) { children: deviceList.asMap().entries.map((entry) {
final device = entry.value; final device = entry.value;
if (state.searchText != null && state.searchText!.isNotEmpty) { if (state.searchText != null && state.searchText!.isNotEmpty) {
return device.name! return device.name!.toLowerCase().contains(state.searchText!.toLowerCase())
.toLowerCase()
.contains(state.searchText!.toLowerCase())
? DraggableCard( ? DraggableCard(
imagePath: device.getDefaultIcon(device.productType), imagePath: device.getDefaultIcon(device.productType),
title: device.name ?? '', title: device.name ?? '',

View File

@ -1,16 +1,16 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routiens/models/ac/ac_function.dart'; import 'package:syncrow_web/pages/routines/models/ac/ac_function.dart';
import 'package:syncrow_web/pages/routiens/models/ac/ac_operational_value.dart'; import 'package:syncrow_web/pages/routines/models/ac/ac_operational_value.dart';
import 'package:syncrow_web/pages/routiens/models/device_functions.dart'; import 'package:syncrow_web/pages/routines/models/device_functions.dart';
import 'package:syncrow_web/pages/routiens/widgets/dialog_footer.dart'; import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart';
import 'package:syncrow_web/pages/routiens/widgets/dialog_header.dart'; import 'package:syncrow_web/pages/routines/widgets/dialog_header.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:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/routiens/bloc/functions_bloc/functions_bloc_bloc.dart'; import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart';
class ACHelper { class ACHelper {
static Future<Map<String, dynamic>?> showACFunctionsDialog( static Future<Map<String, dynamic>?> showACFunctionsDialog(
@ -27,16 +27,15 @@ class ACHelper {
context: context, context: context,
builder: (BuildContext context) { builder: (BuildContext context) {
return BlocProvider( return BlocProvider(
create: (_) => FunctionBloc() create: (_) => FunctionBloc()..add(InitializeFunctions(deviceSelectedFunctions ?? [])),
..add(InitializeFunctions(deviceSelectedFunctions ?? [])),
child: AlertDialog( child: AlertDialog(
contentPadding: EdgeInsets.zero, contentPadding: EdgeInsets.zero,
content: BlocBuilder<FunctionBloc, FunctionBlocState>( content: BlocBuilder<FunctionBloc, FunctionBlocState>(
builder: (context, state) { builder: (context, state) {
final selectedFunction = state.selectedFunction; final selectedFunction = state.selectedFunction;
final selectedOperationName = state.selectedOperationName; final selectedOperationName = state.selectedOperationName;
final selectedFunctionData = state.addedFunctions final selectedFunctionData =
.firstWhere((f) => f.functionCode == selectedFunction, state.addedFunctions.firstWhere((f) => f.functionCode == selectedFunction,
orElse: () => DeviceFunctionData( orElse: () => DeviceFunctionData(
entityId: '', entityId: '',
functionCode: selectedFunction ?? '', functionCode: selectedFunction ?? '',
@ -66,10 +65,8 @@ class ACHelper {
child: _buildFunctionsList( child: _buildFunctionsList(
context: context, context: context,
acFunctions: acFunctions, acFunctions: acFunctions,
onFunctionSelected: onFunctionSelected: (functionCode, operationName) =>
(functionCode, operationName) => context context.read<FunctionBloc>().add(SelectFunction(
.read<FunctionBloc>()
.add(SelectFunction(
functionCode: functionCode, functionCode: functionCode,
operationName: operationName, operationName: operationName,
)), )),
@ -184,7 +181,7 @@ class ACHelper {
bool? removeComparators, bool? removeComparators,
}) { }) {
if (selectedFunction == 'temp_set' || selectedFunction == 'temp_current') { if (selectedFunction == 'temp_set' || selectedFunction == 'temp_current') {
final initialValue = selectedFunctionData?.value ?? 200; final initialValue = selectedFunctionData?.value ?? 250;
return _buildTemperatureSelector( return _buildTemperatureSelector(
context: context, context: context,
initialValue: initialValue, initialValue: initialValue,
@ -197,8 +194,7 @@ class ACHelper {
); );
} }
final selectedFn = final selectedFn = acFunctions.firstWhere((f) => f.code == selectedFunction);
acFunctions.firstWhere((f) => f.code == selectedFunction);
final values = selectedFn.getOperationalValues(); final values = selectedFn.getOperationalValues();
return _buildOperationalValuesList( return _buildOperationalValuesList(
@ -294,8 +290,7 @@ class ACHelper {
minHeight: 40.0, minHeight: 40.0,
minWidth: 40.0, minWidth: 40.0,
), ),
isSelected: isSelected: conditions.map((c) => c == (currentCondition ?? "==")).toList(),
conditions.map((c) => c == (currentCondition ?? "==")).toList(),
children: conditions.map((c) => Text(c)).toList(), children: conditions.map((c) => Text(c)).toList(),
); );
} }
@ -333,10 +328,10 @@ class ACHelper {
String selectCode, String selectCode,
) { ) {
return Slider( return Slider(
value: initialValue is int ? initialValue.toDouble() : 160.0, value: initialValue is int ? initialValue.toDouble() : 200.0,
min: 160, min: 200,
max: 300, max: 300,
divisions: 14, divisions: 10,
label: '${((initialValue ?? 160) / 10).toInt()}°C', label: '${((initialValue ?? 160) / 10).toInt()}°C',
onChanged: (value) { onChanged: (value) {
context.read<FunctionBloc>().add( context.read<FunctionBloc>().add(
@ -389,13 +384,9 @@ class ACHelper {
style: context.textTheme.bodyMedium, style: context.textTheme.bodyMedium,
), ),
trailing: Icon( trailing: Icon(
isSelected isSelected ? Icons.radio_button_checked : Icons.radio_button_unchecked,
? Icons.radio_button_checked
: Icons.radio_button_unchecked,
size: 24, size: 24,
color: isSelected color: isSelected ? ColorsManager.primaryColorWithOpacity : ColorsManager.textGray,
? ColorsManager.primaryColorWithOpacity
: ColorsManager.textGray,
), ),
onTap: () { onTap: () {
if (!isSelected) { if (!isSelected) {
@ -407,8 +398,7 @@ class ACHelper {
operationName: operationName, operationName: operationName,
value: value.value, value: value.value,
condition: selectedFunctionData?.condition, condition: selectedFunctionData?.condition,
valueDescription: valueDescription: selectedFunctionData?.valueDescription,
selectedFunctionData?.valueDescription,
), ),
), ),
); );

View File

@ -1,10 +1,10 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routiens/models/device_functions.dart'; import 'package:syncrow_web/pages/routines/models/device_functions.dart';
import 'package:syncrow_web/pages/routiens/widgets/dialog_header.dart'; import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart';
import 'package:syncrow_web/pages/routiens/widgets/dialog_footer.dart'; import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart';
import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/constants/assets.dart';
class AutomationDialog extends StatefulWidget { class AutomationDialog extends StatefulWidget {
@ -31,10 +31,8 @@ class _AutomationDialogState extends State<AutomationDialog> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
List<DeviceFunctionData>? functions = context List<DeviceFunctionData>? functions =
.read<RoutineBloc>() context.read<RoutineBloc>().state.selectedFunctions[widget.uniqueCustomId];
.state
.selectedFunctions[widget.uniqueCustomId];
for (DeviceFunctionData data in functions ?? []) { for (DeviceFunctionData data in functions ?? []) {
if (data.entityId == widget.automationId) { if (data.entityId == widget.automationId) {
selectedAutomationActionExecutor = data.value; selectedAutomationActionExecutor = data.value;
@ -67,8 +65,7 @@ class _AutomationDialogState extends State<AutomationDialog> {
}), }),
), ),
ListTile( ListTile(
leading: leading: SvgPicture.asset(Assets.acPowerOff, width: 24, height: 24),
SvgPicture.asset(Assets.acPowerOff, width: 24, height: 24),
title: const Text('Disable'), title: const Text('Disable'),
trailing: Radio<String?>( trailing: Radio<String?>(
value: 'rule_disable', value: 'rule_disable',

View File

@ -1,10 +1,10 @@
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routiens/models/device_functions.dart'; import 'package:syncrow_web/pages/routines/models/device_functions.dart';
import 'package:syncrow_web/pages/routiens/widgets/dialog_footer.dart'; import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart';
import 'package:syncrow_web/pages/routiens/widgets/dialog_header.dart'; import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart';
class DelayHelper { class DelayHelper {
static Future<Map<String, dynamic>?> showDelayPickerDialog( static Future<Map<String, dynamic>?> showDelayPickerDialog(

View File

@ -1,6 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.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';

View File

@ -1,7 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/routiens/bloc/effective_period/effect_period_bloc.dart'; import 'package:syncrow_web/pages/routines/bloc/effective_period/effect_period_bloc.dart';
import 'package:syncrow_web/pages/routiens/bloc/effective_period/effect_period_event.dart'; import 'package:syncrow_web/pages/routines/bloc/effective_period/effect_period_event.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/app_enum.dart'; import 'package:syncrow_web/utils/constants/app_enum.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart';

View File

@ -2,14 +2,14 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
import 'package:syncrow_web/pages/routiens/bloc/functions_bloc/functions_bloc_bloc.dart'; import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart';
import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routiens/helper/duration_format_helper.dart'; import 'package:syncrow_web/pages/routines/helper/duration_format_helper.dart';
import 'package:syncrow_web/pages/routiens/models/device_functions.dart'; import 'package:syncrow_web/pages/routines/models/device_functions.dart';
import 'package:syncrow_web/pages/routiens/models/gang_switches/base_switch_function.dart'; import 'package:syncrow_web/pages/routines/models/gang_switches/base_switch_function.dart';
import 'package:syncrow_web/pages/routiens/models/gang_switches/switch_operational_value.dart'; import 'package:syncrow_web/pages/routines/models/gang_switches/switch_operational_value.dart';
import 'package:syncrow_web/pages/routiens/widgets/dialog_footer.dart'; import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart';
import 'package:syncrow_web/pages/routiens/widgets/dialog_header.dart'; import 'package:syncrow_web/pages/routines/widgets/dialog_header.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';
@ -22,23 +22,21 @@ class OneGangSwitchHelper {
String uniqueCustomId, String uniqueCustomId,
bool removeComparetors, bool removeComparetors,
) async { ) async {
List<BaseSwitchFunction> acFunctions = List<BaseSwitchFunction> acFunctions = functions.whereType<BaseSwitchFunction>().toList();
functions.whereType<BaseSwitchFunction>().toList();
return showDialog<Map<String, dynamic>?>( return showDialog<Map<String, dynamic>?>(
context: context, context: context,
builder: (BuildContext context) { builder: (BuildContext context) {
return BlocProvider( return BlocProvider(
create: (_) => FunctionBloc() create: (_) => FunctionBloc()..add(InitializeFunctions(deviceSelectedFunctions ?? [])),
..add(InitializeFunctions(deviceSelectedFunctions ?? [])),
child: AlertDialog( child: AlertDialog(
contentPadding: EdgeInsets.zero, contentPadding: EdgeInsets.zero,
content: BlocBuilder<FunctionBloc, FunctionBlocState>( content: BlocBuilder<FunctionBloc, FunctionBlocState>(
builder: (context, state) { builder: (context, state) {
final selectedFunction = state.selectedFunction; final selectedFunction = state.selectedFunction;
final selectedOperationName = state.selectedOperationName; final selectedOperationName = state.selectedOperationName;
final selectedFunctionData = state.addedFunctions final selectedFunctionData =
.firstWhere((f) => f.functionCode == selectedFunction, state.addedFunctions.firstWhere((f) => f.functionCode == selectedFunction,
orElse: () => DeviceFunctionData( orElse: () => DeviceFunctionData(
entityId: '', entityId: '',
functionCode: selectedFunction ?? '', functionCode: selectedFunction ?? '',
@ -85,12 +83,9 @@ class OneGangSwitchHelper {
color: ColorsManager.textGray, color: ColorsManager.textGray,
), ),
onTap: () { onTap: () {
context context.read<FunctionBloc>().add(SelectFunction(
.read<FunctionBloc>()
.add(SelectFunction(
functionCode: function.code, functionCode: function.code,
operationName: operationName: function.operationName,
function.operationName,
)); ));
}, },
); );
@ -180,8 +175,7 @@ class OneGangSwitchHelper {
); );
} }
final selectedFn = final selectedFn = acFunctions.firstWhere((f) => f.code == selectedFunction);
acFunctions.firstWhere((f) => f.code == selectedFunction);
final values = selectedFn.getOperationalValues(); final values = selectedFn.getOperationalValues();
return _buildOperationalValuesList( return _buildOperationalValuesList(
@ -218,11 +212,11 @@ class OneGangSwitchHelper {
selectedFunctionData, selectedFunctionData,
), ),
const SizedBox(height: 20), const SizedBox(height: 20),
_buildCountDownDisplay(context, initialValue, device, operationName, _buildCountDownDisplay(
selectedFunctionData, selectCode), context, initialValue, device, operationName, selectedFunctionData, selectCode),
const SizedBox(height: 20), const SizedBox(height: 20),
_buildCountDownSlider(context, initialValue, device, operationName, _buildCountDownSlider(
selectedFunctionData, selectCode), context, initialValue, device, operationName, selectedFunctionData, selectCode),
], ],
); );
} }
@ -263,8 +257,7 @@ class OneGangSwitchHelper {
minHeight: 40.0, minHeight: 40.0,
minWidth: 40.0, minWidth: 40.0,
), ),
isSelected: isSelected: conditions.map((c) => c == (currentCondition ?? "==")).toList(),
conditions.map((c) => c == (currentCondition ?? "==")).toList(),
children: conditions.map((c) => Text(c)).toList(), children: conditions.map((c) => Text(c)).toList(),
); );
} }
@ -312,8 +305,7 @@ class OneGangSwitchHelper {
value: (initialValue ?? 0).toDouble(), value: (initialValue ?? 0).toDouble(),
min: operationalValues.minValue?.toDouble() ?? 0.0, min: operationalValues.minValue?.toDouble() ?? 0.0,
max: operationalValues.maxValue?.toDouble() ?? 0.0, max: operationalValues.maxValue?.toDouble() ?? 0.0,
divisions: (((operationalValues.maxValue ?? 0) - divisions: (((operationalValues.maxValue ?? 0) - (operationalValues.minValue ?? 0)) /
(operationalValues.minValue ?? 0)) /
(operationalValues.stepValue ?? 1)) (operationalValues.stepValue ?? 1))
.round(), .round(),
onChanged: (value) { onChanged: (value) {
@ -365,13 +357,9 @@ class OneGangSwitchHelper {
style: context.textTheme.bodyMedium, style: context.textTheme.bodyMedium,
), ),
trailing: Icon( trailing: Icon(
isSelected isSelected ? Icons.radio_button_checked : Icons.radio_button_unchecked,
? Icons.radio_button_checked
: Icons.radio_button_unchecked,
size: 24, size: 24,
color: isSelected color: isSelected ? ColorsManager.primaryColorWithOpacity : ColorsManager.textGray,
? ColorsManager.primaryColorWithOpacity
: ColorsManager.textGray,
), ),
onTap: () { onTap: () {
if (!isSelected) { if (!isSelected) {
@ -383,8 +371,7 @@ class OneGangSwitchHelper {
operationName: operationName, operationName: operationName,
value: value.value, value: value.value,
condition: selectedFunctionData?.condition, condition: selectedFunctionData?.condition,
valueDescription: valueDescription: selectedFunctionData?.valueDescription,
selectedFunctionData?.valueDescription,
), ),
), ),
); );

View File

@ -1,17 +1,17 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/routiens/bloc/effective_period/effect_period_bloc.dart'; import 'package:syncrow_web/pages/routines/bloc/effective_period/effect_period_bloc.dart';
import 'package:syncrow_web/pages/routiens/bloc/effective_period/effect_period_event.dart'; import 'package:syncrow_web/pages/routines/bloc/effective_period/effect_period_event.dart';
import 'package:syncrow_web/pages/routiens/bloc/effective_period/effect_period_state.dart'; import 'package:syncrow_web/pages/routines/bloc/effective_period/effect_period_state.dart';
import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routiens/bloc/setting_bloc/setting_bloc.dart'; import 'package:syncrow_web/pages/routines/bloc/setting_bloc/setting_bloc.dart';
import 'package:syncrow_web/pages/routiens/bloc/setting_bloc/setting_event.dart'; import 'package:syncrow_web/pages/routines/bloc/setting_bloc/setting_event.dart';
import 'package:syncrow_web/pages/routiens/bloc/setting_bloc/setting_state.dart'; import 'package:syncrow_web/pages/routines/bloc/setting_bloc/setting_state.dart';
import 'package:syncrow_web/pages/routiens/models/create_scene_and_autoamtion/create_automation_model.dart'; import 'package:syncrow_web/pages/routines/models/create_scene_and_autoamtion/create_automation_model.dart';
import 'package:syncrow_web/pages/routiens/models/icon_model.dart'; import 'package:syncrow_web/pages/routines/models/icon_model.dart';
import 'package:syncrow_web/pages/routiens/view/effective_period_view.dart'; import 'package:syncrow_web/pages/routines/view/effective_period_view.dart';
import 'package:syncrow_web/pages/routiens/widgets/delete_scene.dart'; import 'package:syncrow_web/pages/routines/widgets/delete_scene.dart';
import 'package:syncrow_web/pages/routiens/widgets/dialog_header.dart'; import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';

View File

@ -2,14 +2,14 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
import 'package:syncrow_web/pages/routiens/bloc/functions_bloc/functions_bloc_bloc.dart'; import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart';
import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routiens/helper/duration_format_helper.dart'; import 'package:syncrow_web/pages/routines/helper/duration_format_helper.dart';
import 'package:syncrow_web/pages/routiens/models/device_functions.dart'; import 'package:syncrow_web/pages/routines/models/device_functions.dart';
import 'package:syncrow_web/pages/routiens/models/gang_switches/base_switch_function.dart'; import 'package:syncrow_web/pages/routines/models/gang_switches/base_switch_function.dart';
import 'package:syncrow_web/pages/routiens/models/gang_switches/switch_operational_value.dart'; import 'package:syncrow_web/pages/routines/models/gang_switches/switch_operational_value.dart';
import 'package:syncrow_web/pages/routiens/widgets/dialog_footer.dart'; import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart';
import 'package:syncrow_web/pages/routiens/widgets/dialog_header.dart'; import 'package:syncrow_web/pages/routines/widgets/dialog_header.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';
@ -22,23 +22,21 @@ class ThreeGangSwitchHelper {
String uniqueCustomId, String uniqueCustomId,
bool removeComparetors, bool removeComparetors,
) async { ) async {
List<BaseSwitchFunction> switchFunctions = List<BaseSwitchFunction> switchFunctions = functions.whereType<BaseSwitchFunction>().toList();
functions.whereType<BaseSwitchFunction>().toList();
return showDialog<Map<String, dynamic>?>( return showDialog<Map<String, dynamic>?>(
context: context, context: context,
builder: (BuildContext context) { builder: (BuildContext context) {
return BlocProvider( return BlocProvider(
create: (_) => FunctionBloc() create: (_) => FunctionBloc()..add(InitializeFunctions(deviceSelectedFunctions ?? [])),
..add(InitializeFunctions(deviceSelectedFunctions ?? [])),
child: AlertDialog( child: AlertDialog(
contentPadding: EdgeInsets.zero, contentPadding: EdgeInsets.zero,
content: BlocBuilder<FunctionBloc, FunctionBlocState>( content: BlocBuilder<FunctionBloc, FunctionBlocState>(
builder: (context, state) { builder: (context, state) {
final selectedFunction = state.selectedFunction; final selectedFunction = state.selectedFunction;
final selectedOperationName = state.selectedOperationName; final selectedOperationName = state.selectedOperationName;
final selectedFunctionData = state.addedFunctions final selectedFunctionData =
.firstWhere((f) => f.functionCode == selectedFunction, state.addedFunctions.firstWhere((f) => f.functionCode == selectedFunction,
orElse: () => DeviceFunctionData( orElse: () => DeviceFunctionData(
entityId: '', entityId: '',
functionCode: selectedFunction ?? '', functionCode: selectedFunction ?? '',
@ -85,12 +83,9 @@ class ThreeGangSwitchHelper {
color: ColorsManager.textGray, color: ColorsManager.textGray,
), ),
onTap: () { onTap: () {
context context.read<FunctionBloc>().add(SelectFunction(
.read<FunctionBloc>()
.add(SelectFunction(
functionCode: function.code, functionCode: function.code,
operationName: operationName: function.operationName,
function.operationName,
)); ));
}, },
); );
@ -182,8 +177,7 @@ class ThreeGangSwitchHelper {
); );
} }
final selectedFn = final selectedFn = switchFunctions.firstWhere((f) => f.code == selectedFunction);
switchFunctions.firstWhere((f) => f.code == selectedFunction);
final values = selectedFn.getOperationalValues(); final values = selectedFn.getOperationalValues();
return _buildOperationalValuesList( return _buildOperationalValuesList(
@ -220,11 +214,11 @@ class ThreeGangSwitchHelper {
selectedFunctionData, selectedFunctionData,
), ),
const SizedBox(height: 20), const SizedBox(height: 20),
_buildCountDownDisplay(context, initialValue, device, operationName, _buildCountDownDisplay(
selectedFunctionData, selectCode), context, initialValue, device, operationName, selectedFunctionData, selectCode),
const SizedBox(height: 20), const SizedBox(height: 20),
_buildCountDownSlider(context, initialValue, device, operationName, _buildCountDownSlider(
selectedFunctionData, selectCode), context, initialValue, device, operationName, selectedFunctionData, selectCode),
], ],
); );
} }
@ -265,8 +259,7 @@ class ThreeGangSwitchHelper {
minHeight: 40.0, minHeight: 40.0,
minWidth: 40.0, minWidth: 40.0,
), ),
isSelected: isSelected: conditions.map((c) => c == (currentCondition ?? "==")).toList(),
conditions.map((c) => c == (currentCondition ?? "==")).toList(),
children: conditions.map((c) => Text(c)).toList(), children: conditions.map((c) => Text(c)).toList(),
); );
} }
@ -314,8 +307,7 @@ class ThreeGangSwitchHelper {
value: (initialValue ?? 0).toDouble(), value: (initialValue ?? 0).toDouble(),
min: operationalValues.minValue?.toDouble() ?? 0.0, min: operationalValues.minValue?.toDouble() ?? 0.0,
max: operationalValues.maxValue?.toDouble() ?? 0.0, max: operationalValues.maxValue?.toDouble() ?? 0.0,
divisions: (((operationalValues.maxValue ?? 0) - divisions: (((operationalValues.maxValue ?? 0) - (operationalValues.minValue ?? 0)) /
(operationalValues.minValue ?? 0)) /
(operationalValues.stepValue ?? 1)) (operationalValues.stepValue ?? 1))
.round(), .round(),
onChanged: (value) { onChanged: (value) {
@ -367,13 +359,9 @@ class ThreeGangSwitchHelper {
style: context.textTheme.bodyMedium, style: context.textTheme.bodyMedium,
), ),
trailing: Icon( trailing: Icon(
isSelected isSelected ? Icons.radio_button_checked : Icons.radio_button_unchecked,
? Icons.radio_button_checked
: Icons.radio_button_unchecked,
size: 24, size: 24,
color: isSelected color: isSelected ? ColorsManager.primaryColorWithOpacity : ColorsManager.textGray,
? ColorsManager.primaryColorWithOpacity
: ColorsManager.textGray,
), ),
onTap: () { onTap: () {
if (!isSelected) { if (!isSelected) {
@ -385,8 +373,7 @@ class ThreeGangSwitchHelper {
operationName: operationName, operationName: operationName,
value: value.value, value: value.value,
condition: selectedFunctionData?.condition, condition: selectedFunctionData?.condition,
valueDescription: valueDescription: selectedFunctionData?.valueDescription,
selectedFunctionData?.valueDescription,
), ),
), ),
); );

View File

@ -2,14 +2,14 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
import 'package:syncrow_web/pages/routiens/bloc/functions_bloc/functions_bloc_bloc.dart'; import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart';
import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routiens/helper/duration_format_helper.dart'; import 'package:syncrow_web/pages/routines/helper/duration_format_helper.dart';
import 'package:syncrow_web/pages/routiens/models/device_functions.dart'; import 'package:syncrow_web/pages/routines/models/device_functions.dart';
import 'package:syncrow_web/pages/routiens/models/gang_switches/base_switch_function.dart'; import 'package:syncrow_web/pages/routines/models/gang_switches/base_switch_function.dart';
import 'package:syncrow_web/pages/routiens/models/gang_switches/switch_operational_value.dart'; import 'package:syncrow_web/pages/routines/models/gang_switches/switch_operational_value.dart';
import 'package:syncrow_web/pages/routiens/widgets/dialog_footer.dart'; import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart';
import 'package:syncrow_web/pages/routiens/widgets/dialog_header.dart'; import 'package:syncrow_web/pages/routines/widgets/dialog_header.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';
@ -22,23 +22,21 @@ class TwoGangSwitchHelper {
String uniqueCustomId, String uniqueCustomId,
bool removeComparetors, bool removeComparetors,
) async { ) async {
List<BaseSwitchFunction> switchFunctions = List<BaseSwitchFunction> switchFunctions = functions.whereType<BaseSwitchFunction>().toList();
functions.whereType<BaseSwitchFunction>().toList();
return showDialog<Map<String, dynamic>?>( return showDialog<Map<String, dynamic>?>(
context: context, context: context,
builder: (BuildContext context) { builder: (BuildContext context) {
return BlocProvider( return BlocProvider(
create: (_) => FunctionBloc() create: (_) => FunctionBloc()..add(InitializeFunctions(deviceSelectedFunctions ?? [])),
..add(InitializeFunctions(deviceSelectedFunctions ?? [])),
child: AlertDialog( child: AlertDialog(
contentPadding: EdgeInsets.zero, contentPadding: EdgeInsets.zero,
content: BlocBuilder<FunctionBloc, FunctionBlocState>( content: BlocBuilder<FunctionBloc, FunctionBlocState>(
builder: (context, state) { builder: (context, state) {
final selectedFunction = state.selectedFunction; final selectedFunction = state.selectedFunction;
final selectedOperationName = state.selectedOperationName; final selectedOperationName = state.selectedOperationName;
final selectedFunctionData = state.addedFunctions final selectedFunctionData =
.firstWhere((f) => f.functionCode == selectedFunction, state.addedFunctions.firstWhere((f) => f.functionCode == selectedFunction,
orElse: () => DeviceFunctionData( orElse: () => DeviceFunctionData(
entityId: '', entityId: '',
functionCode: selectedFunction ?? '', functionCode: selectedFunction ?? '',
@ -85,12 +83,9 @@ class TwoGangSwitchHelper {
color: ColorsManager.textGray, color: ColorsManager.textGray,
), ),
onTap: () { onTap: () {
context context.read<FunctionBloc>().add(SelectFunction(
.read<FunctionBloc>()
.add(SelectFunction(
functionCode: function.code, functionCode: function.code,
operationName: operationName: function.operationName,
function.operationName,
)); ));
}, },
); );
@ -166,8 +161,7 @@ class TwoGangSwitchHelper {
required String operationName, required String operationName,
required bool removeComparetors, required bool removeComparetors,
}) { }) {
if (selectedFunction == 'countdown_1' || if (selectedFunction == 'countdown_1' || selectedFunction == 'countdown_2') {
selectedFunction == 'countdown_2') {
final initialValue = selectedFunctionData?.value ?? 200; final initialValue = selectedFunctionData?.value ?? 200;
return _buildTemperatureSelector( return _buildTemperatureSelector(
context: context, context: context,
@ -181,8 +175,7 @@ class TwoGangSwitchHelper {
); );
} }
final selectedFn = final selectedFn = switchFunctions.firstWhere((f) => f.code == selectedFunction);
switchFunctions.firstWhere((f) => f.code == selectedFunction);
final values = selectedFn.getOperationalValues(); final values = selectedFn.getOperationalValues();
return _buildOperationalValuesList( return _buildOperationalValuesList(
@ -219,11 +212,11 @@ class TwoGangSwitchHelper {
selectedFunctionData, selectedFunctionData,
), ),
const SizedBox(height: 20), const SizedBox(height: 20),
_buildCountDownDisplay(context, initialValue, device, operationName, _buildCountDownDisplay(
selectedFunctionData, selectCode), context, initialValue, device, operationName, selectedFunctionData, selectCode),
const SizedBox(height: 20), const SizedBox(height: 20),
_buildCountDownSlider(context, initialValue, device, operationName, _buildCountDownSlider(
selectedFunctionData, selectCode), context, initialValue, device, operationName, selectedFunctionData, selectCode),
], ],
); );
} }
@ -264,8 +257,7 @@ class TwoGangSwitchHelper {
minHeight: 40.0, minHeight: 40.0,
minWidth: 40.0, minWidth: 40.0,
), ),
isSelected: isSelected: conditions.map((c) => c == (currentCondition ?? "==")).toList(),
conditions.map((c) => c == (currentCondition ?? "==")).toList(),
children: conditions.map((c) => Text(c)).toList(), children: conditions.map((c) => Text(c)).toList(),
); );
} }
@ -313,8 +305,7 @@ class TwoGangSwitchHelper {
value: (initialValue ?? 0).toDouble(), value: (initialValue ?? 0).toDouble(),
min: operationalValues.minValue?.toDouble() ?? 0.0, min: operationalValues.minValue?.toDouble() ?? 0.0,
max: operationalValues.maxValue?.toDouble() ?? 0.0, max: operationalValues.maxValue?.toDouble() ?? 0.0,
divisions: (((operationalValues.maxValue ?? 0) - divisions: (((operationalValues.maxValue ?? 0) - (operationalValues.minValue ?? 0)) /
(operationalValues.minValue ?? 0)) /
(operationalValues.stepValue ?? 1)) (operationalValues.stepValue ?? 1))
.round(), .round(),
onChanged: (value) { onChanged: (value) {
@ -366,13 +357,9 @@ class TwoGangSwitchHelper {
style: context.textTheme.bodyMedium, style: context.textTheme.bodyMedium,
), ),
trailing: Icon( trailing: Icon(
isSelected isSelected ? Icons.radio_button_checked : Icons.radio_button_unchecked,
? Icons.radio_button_checked
: Icons.radio_button_unchecked,
size: 24, size: 24,
color: isSelected color: isSelected ? ColorsManager.primaryColorWithOpacity : ColorsManager.textGray,
? ColorsManager.primaryColorWithOpacity
: ColorsManager.textGray,
), ),
onTap: () { onTap: () {
if (!isSelected) { if (!isSelected) {
@ -384,8 +371,7 @@ class TwoGangSwitchHelper {
operationName: operationName, operationName: operationName,
value: value.value, value: value.value,
condition: selectedFunctionData?.condition, condition: selectedFunctionData?.condition,
valueDescription: valueDescription: selectedFunctionData?.valueDescription,
selectedFunctionData?.valueDescription,
), ),
), ),
); );

View File

@ -1,10 +1,10 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/common/buttons/default_button.dart'; import 'package:syncrow_web/pages/common/buttons/default_button.dart';
import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routiens/helper/save_routine_helper.dart'; import 'package:syncrow_web/pages/routines/helper/save_routine_helper.dart';
import 'package:syncrow_web/pages/routiens/widgets/routine_dialogs/discard_dialog.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/discard_dialog.dart';
import 'package:syncrow_web/pages/routiens/widgets/routine_dialogs/setting_dialog.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/setting_dialog.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/style.dart'; import 'package:syncrow_web/utils/style.dart';

View File

@ -1,7 +1,8 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routiens/widgets/dragable_card.dart'; import 'package:syncrow_web/pages/routines/widgets/dragable_card.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/constants/assets.dart';
class ScenesAndAutomations extends StatefulWidget { class ScenesAndAutomations extends StatefulWidget {
@ -18,8 +19,9 @@ class _ScenesAndAutomationsState extends State<ScenesAndAutomations> {
void initState() { void initState() {
super.initState(); super.initState();
context.read<RoutineBloc>() context.read<RoutineBloc>()
..add(const LoadScenes(spaceId, communityId)) ..add(LoadScenes(context.read<SpaceTreeBloc>().selectedSpaceId,
..add(const LoadAutomation(spaceId)); context.read<SpaceTreeBloc>().selectedCommunityId))
..add(LoadAutomation(context.read<SpaceTreeBloc>().selectedSpaceId));
} }
@override @override
@ -34,9 +36,7 @@ class _ScenesAndAutomationsState extends State<ScenesAndAutomations> {
children: scenes.asMap().entries.map((entry) { children: scenes.asMap().entries.map((entry) {
final scene = entry.value; final scene = entry.value;
if (state.searchText != null && state.searchText!.isNotEmpty) { if (state.searchText != null && state.searchText!.isNotEmpty) {
return scene.name return scene.name.toLowerCase().contains(state.searchText!.toLowerCase())
.toLowerCase()
.contains(state.searchText!.toLowerCase())
? DraggableCard( ? DraggableCard(
imagePath: scene.icon ?? Assets.loginLogo, imagePath: scene.icon ?? Assets.loginLogo,
title: scene.name, title: scene.name,

View File

@ -1,8 +1,8 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/common/text_field/custom_text_field.dart'; import 'package:syncrow_web/pages/common/text_field/custom_text_field.dart';
import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routiens/widgets/routines_title_widget.dart'; import 'package:syncrow_web/pages/routines/widgets/routines_title_widget.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.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';

View File

@ -2,11 +2,11 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routiens/widgets/routine_dialogs/automation_dialog.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/automation_dialog.dart';
import 'package:syncrow_web/pages/routiens/widgets/routine_dialogs/delay_dialog.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/delay_dialog.dart';
import 'package:syncrow_web/pages/routiens/helper/dialog_helper/device_dialog_helper.dart'; import 'package:syncrow_web/pages/routines/helper/dialog_helper/device_dialog_helper.dart';
import 'package:syncrow_web/pages/routiens/widgets/dragable_card.dart'; import 'package:syncrow_web/pages/routines/widgets/dragable_card.dart';
import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:uuid/uuid.dart'; import 'package:uuid/uuid.dart';

View File

@ -0,0 +1,291 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_event.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_state.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
import 'package:syncrow_web/services/space_mana_api.dart';
class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
String selectedCommunityId = 'aff21a57-2f91-4e5c-b99b-0182c3ab65a9';
String selectedSpaceId = '25c96044-fadf-44bb-93c7-3c079e527ce6';
SpaceTreeBloc() : super(const SpaceTreeState()) {
on<InitialEvent>(_fetchSpaces);
on<OnCommunityExpanded>(_onCommunityExpanded);
on<OnSpaceExpanded>(_onSpaceExpanded);
on<OnCommunitySelected>(_onCommunitySelected);
on<OnSpaceSelected>(_onSpaceSelected);
on<SearchQueryEvent>(_onSearch);
}
_fetchSpaces(InitialEvent event, Emitter<SpaceTreeState> emit) async {
emit(SpaceTreeLoadingState());
try {
List<CommunityModel> communities = await CommunitySpaceManagementApi().fetchCommunities();
List<CommunityModel> updatedCommunities = await Future.wait(
communities.map((community) async {
List<SpaceModel> spaces =
await CommunitySpaceManagementApi().getSpaceHierarchy(community.uuid);
return CommunityModel(
uuid: community.uuid,
createdAt: community.createdAt,
updatedAt: community.updatedAt,
name: community.name,
description: community.description,
spaces: spaces,
region: community.region,
);
}).toList(),
);
emit(state.copyWith(
communitiesList: updatedCommunities, expandedCommunity: [], expandedSpaces: []));
} catch (e) {
emit(SpaceTreeErrorState('Error loading communities and spaces: $e'));
}
}
_onCommunityExpanded(OnCommunityExpanded event, Emitter<SpaceTreeState> emit) async {
try {
List<String> updatedExpandedCommunityList = List.from(state.expandedCommunities);
if (updatedExpandedCommunityList.contains(event.communityId)) {
updatedExpandedCommunityList.remove(event.communityId);
} else {
updatedExpandedCommunityList.add(event.communityId);
}
emit(state.copyWith(
expandedCommunity: updatedExpandedCommunityList,
));
} catch (e) {
emit(const SpaceTreeErrorState('Something went wrong'));
}
}
_onSpaceExpanded(OnSpaceExpanded event, Emitter<SpaceTreeState> emit) async {
try {
List<String> updatedExpandedSpacesList = List.from(state.expandedSpaces);
if (updatedExpandedSpacesList.contains(event.spaceId)) {
updatedExpandedSpacesList.remove(event.spaceId);
} else {
updatedExpandedSpacesList.add(event.spaceId);
}
emit(state.copyWith(expandedSpaces: updatedExpandedSpacesList));
} catch (e) {
emit(const SpaceTreeErrorState('Something went wrong'));
}
}
_onCommunitySelected(OnCommunitySelected event, Emitter<SpaceTreeState> emit) async {
try {
List<String> updatedSelectedCommunities =
List.from(state.selectedCommunities.toSet().toList());
List<String> updatedSelectedSpaces = List.from(state.selectedSpaces.toSet().toList());
List<String> updatedSoldChecks = List.from(state.soldCheck.toSet().toList());
Map<String, List<String>> communityAndSpaces = Map.from(state.selectedCommunityAndSpaces);
List<String> childrenIds = _getAllChildIds(event.children);
if (!updatedSelectedCommunities.contains(event.communityId)) {
// Select the community and all its children
updatedSelectedCommunities.add(event.communityId);
updatedSelectedSpaces.addAll(childrenIds);
} else {
// Unselect the community and all its children
updatedSelectedCommunities.remove(event.communityId);
updatedSelectedSpaces.removeWhere(childrenIds.contains);
updatedSoldChecks.removeWhere(childrenIds.contains);
}
communityAndSpaces[event.communityId] = updatedSelectedSpaces;
emit(state.copyWith(
selectedCommunities: updatedSelectedCommunities,
selectedSpaces: updatedSelectedSpaces,
soldCheck: updatedSoldChecks,
selectedCommunityAndSpaces: communityAndSpaces));
} catch (e) {
emit(const SpaceTreeErrorState('Something went wrong'));
}
}
_onSpaceSelected(OnSpaceSelected event, Emitter<SpaceTreeState> emit) async {
try {
List<String> updatedSelectedCommunities =
List.from(state.selectedCommunities.toSet().toList());
List<String> updatedSelectedSpaces = List.from(state.selectedSpaces.toSet().toList());
List<String> updatedSoldChecks = List.from(state.soldCheck.toSet().toList());
Map<String, List<String>> communityAndSpaces = Map.from(state.selectedCommunityAndSpaces);
List<String> childrenIds = _getAllChildIds(event.children);
bool isChildSelected = false;
for (String id in childrenIds) {
if (updatedSelectedSpaces.contains(id)) {
isChildSelected = true;
}
}
if (!updatedSelectedSpaces.contains(event.spaceId) &&
!updatedSoldChecks.contains(event.spaceId)) {
// First click: Select the space and all its children
updatedSelectedSpaces.add(event.spaceId);
updatedSelectedCommunities.add(event.communityId);
if (childrenIds.isNotEmpty) {
updatedSelectedSpaces.addAll(childrenIds);
}
List<String> spaces = _getThePathToChild(event.communityId, event.spaceId);
for (String space in spaces) {
if (!updatedSelectedSpaces.contains(space) && !updatedSoldChecks.contains(space)) {
updatedSoldChecks.add(space);
}
}
} else if (!updatedSoldChecks.contains(event.spaceId) &&
childrenIds.isNotEmpty &&
isChildSelected) {
// Second click: Unselect space but keep children
updatedSelectedSpaces.remove(event.spaceId);
updatedSoldChecks.add(event.spaceId);
} else {
// Third click: Unselect space and all its children
updatedSelectedSpaces.remove(event.spaceId);
if (childrenIds.isNotEmpty) {
updatedSelectedSpaces.removeWhere(childrenIds.contains);
updatedSoldChecks.removeWhere(childrenIds.contains);
}
updatedSoldChecks.remove(event.spaceId);
List<String> parents = _getThePathToChild(event.communityId, event.spaceId);
if (!_parentSelected(parents, updatedSelectedSpaces)) {
updatedSoldChecks.removeWhere(parents.contains);
}
if (!_anySpacesSelectedInCommunity(
event.communityId, updatedSelectedSpaces, updatedSoldChecks)) {
updatedSelectedCommunities.remove(event.communityId);
}
}
communityAndSpaces[event.communityId] = updatedSelectedSpaces;
emit(state.copyWith(
selectedCommunities: updatedSelectedCommunities,
selectedSpaces: updatedSelectedSpaces,
soldCheck: updatedSoldChecks,
selectedCommunityAndSpaces: communityAndSpaces));
emit(state.copyWith(selectedSpaces: updatedSelectedSpaces));
} catch (e) {
emit(const SpaceTreeErrorState('Something went wrong'));
}
}
_parentSelected(List<String> parents, List<String> selectedSpaces) {
for (String space in parents) {
if (selectedSpaces.contains(space)) {
return true;
}
}
return false;
}
_onSearch(SearchQueryEvent event, Emitter<SpaceTreeState> emit) async {
try {
List<CommunityModel> communities = List.from(state.communityList);
List<CommunityModel> filteredCommunity = [];
// Filter communities and expand only those that match the query
filteredCommunity = communities.where((community) {
final containsQueryInCommunity =
community.name.toLowerCase().contains(event.searchQuery.toLowerCase());
final containsQueryInSpaces =
community.spaces.any((space) => _containsQuery(space, event.searchQuery.toLowerCase()));
return containsQueryInCommunity || containsQueryInSpaces;
}).toList();
emit(state.copyWith(
filteredCommunity: filteredCommunity, isSearching: event.searchQuery.isNotEmpty));
} catch (e) {
emit(const SpaceTreeErrorState('Something went wrong'));
}
}
// Helper function to determine if any space or its children match the search query
bool _containsQuery(SpaceModel space, String query) {
final matchesSpace = space.name.toLowerCase().contains(query);
final matchesChildren =
space.children.any((child) => _containsQuery(child, query)); // Recursive check for children
return matchesSpace || matchesChildren;
}
List<String> _getAllChildIds(List<SpaceModel> spaces) {
List<String> ids = [];
for (var child in spaces) {
ids.add(child.uuid!);
ids.addAll(_getAllChildIds(child.children));
}
return ids;
}
bool _anySpacesSelectedInCommunity(
String communityId, List<String> selectedSpaces, List<String> partialCheckedList) {
bool result = false;
for (var community in state.communityList) {
if (community.uuid == communityId) {
List<String> ids = _getAllChildIds(community.spaces);
for (var id in ids) {
result = selectedSpaces.contains(id) || partialCheckedList.contains(id);
if (result) {
return result;
}
}
}
}
return result;
}
List<String> _getThePathToChild(String communityId, String selectedSpaceId) {
List<String> ids = [];
for (var community in state.communityList) {
if (community.uuid == communityId) {
for (var space in community.spaces) {
List<String> list = [];
list.add(space.uuid!);
ids = _getAllParentsIds(space, selectedSpaceId, List.from(list));
if (ids.isNotEmpty) {
return ids;
}
}
}
}
return ids;
}
List<String> _getAllParentsIds(SpaceModel child, String spaceId, List<String> listIds) {
List<String> ids = listIds;
ids.add(child.uuid ?? '');
if (child.uuid == spaceId) {
return ids;
}
if (child.children.isNotEmpty) {
for (var space in child.children) {
var result = _getAllParentsIds(space, spaceId, List.from(ids));
if (result.isNotEmpty) {
return result;
}
}
}
ids.removeLast();
return [];
}
}

View File

@ -0,0 +1,69 @@
import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
class SpaceTreeEvent extends Equatable {
const SpaceTreeEvent();
@override
List<Object> get props => [];
}
class InitialEvent extends SpaceTreeEvent {}
class SearchForSpace extends SpaceTreeEvent {
final String searchQuery;
const SearchForSpace(this.searchQuery);
@override
List<Object> get props => [searchQuery];
}
class OnCommunityExpanded extends SpaceTreeEvent {
final String communityId;
const OnCommunityExpanded(this.communityId);
@override
List<Object> get props => [communityId];
}
class OnCommunitySelected extends SpaceTreeEvent {
final String communityId;
final List<SpaceModel> children;
const OnCommunitySelected(this.communityId, this.children);
@override
List<Object> get props => [communityId, children];
}
class OnSpaceExpanded extends SpaceTreeEvent {
final String communityId;
final String spaceId;
const OnSpaceExpanded(this.communityId, this.spaceId);
@override
List<Object> get props => [communityId, spaceId];
}
class OnSpaceSelected extends SpaceTreeEvent {
final String communityId;
final String spaceId;
final List<SpaceModel> children;
const OnSpaceSelected(this.communityId, this.spaceId, this.children);
@override
List<Object> get props => [communityId, spaceId, children];
}
class SearchQueryEvent extends SpaceTreeEvent {
final String searchQuery;
const SearchQueryEvent(this.searchQuery);
@override
List<Object> get props => [searchQuery];
}

View File

@ -0,0 +1,70 @@
import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart';
class SpaceTreeState extends Equatable {
final Map<String, List<String>> selectedCommunityAndSpaces;
final List<CommunityModel> communityList;
final List<CommunityModel> filteredCommunity;
final List<String> expandedCommunities;
final List<String> expandedSpaces;
final List<String> selectedCommunities;
final List<String> selectedSpaces;
final List<String> soldCheck;
final bool isSearching;
const SpaceTreeState(
{this.communityList = const [],
this.filteredCommunity = const [],
this.expandedCommunities = const [],
this.expandedSpaces = const [],
this.selectedCommunities = const [],
this.selectedSpaces = const [],
this.soldCheck = const [],
this.isSearching = false,
this.selectedCommunityAndSpaces = const {}});
SpaceTreeState copyWith(
{List<CommunityModel>? communitiesList,
List<CommunityModel>? filteredCommunity,
List<String>? expandedSpaces,
List<String>? expandedCommunity,
List<String>? selectedCommunities,
List<String>? selectedSpaces,
List<String>? soldCheck,
bool? isSearching,
Map<String, List<String>>? selectedCommunityAndSpaces}) {
return SpaceTreeState(
communityList: communitiesList ?? this.communityList,
filteredCommunity: filteredCommunity ?? this.filteredCommunity,
expandedSpaces: expandedSpaces ?? this.expandedSpaces,
expandedCommunities: expandedCommunity ?? this.expandedCommunities,
selectedCommunities: selectedCommunities ?? this.selectedCommunities,
selectedSpaces: selectedSpaces ?? this.selectedSpaces,
soldCheck: soldCheck ?? this.soldCheck,
isSearching: isSearching ?? this.isSearching,
selectedCommunityAndSpaces: selectedCommunityAndSpaces ?? this.selectedCommunityAndSpaces);
}
@override
List<Object?> get props => [
communityList,
filteredCommunity,
expandedSpaces,
expandedCommunities,
selectedCommunities,
selectedSpaces,
soldCheck,
isSearching,
selectedCommunityAndSpaces
];
}
class SpaceTreeLoadingState extends SpaceTreeState {}
class SpaceTreeErrorState extends SpaceTreeState {
final String message;
const SpaceTreeErrorState(this.message);
@override
List<Object?> get props => [message];
}

View File

@ -0,0 +1,99 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class CustomExpansionTileSpaceTree extends StatelessWidget {
final String? spaceId;
final String title;
final List<Widget>? children;
final bool isSelected;
final bool isSoldCheck;
final bool isExpanded;
final Function? onExpansionChanged;
final Function? onItemSelected;
const CustomExpansionTileSpaceTree(
{super.key,
this.spaceId,
required this.title,
this.children,
this.isExpanded = false,
this.onExpansionChanged,
this.onItemSelected,
required this.isSelected,
this.isSoldCheck = false});
@override
Widget build(BuildContext context) {
return Column(
children: [
Row(
children: [
Checkbox(
value: isSoldCheck ? null : isSelected,
onChanged: (bool? value) {
if (onItemSelected != null) {
onItemSelected!();
}
},
tristate: true,
side: WidgetStateBorderSide.resolveWith((states) {
return const BorderSide(color: ColorsManager.grayBorder);
}),
fillColor: WidgetStateProperty.resolveWith((states) {
if (states.contains(WidgetState.selected)) {
return ColorsManager.blue1;
} else {
return ColorsManager.checkBoxFillColor;
}
}),
checkColor: ColorsManager.whiteColors,
),
if (children != null && children!.isNotEmpty)
InkWell(
onTap: () {
if (onExpansionChanged != null) {
onExpansionChanged!();
}
},
child: Icon(
isExpanded ? Icons.keyboard_arrow_down : Icons.keyboard_arrow_right,
color: ColorsManager.lightGrayColor,
size: 16.0,
),
),
Expanded(
child: GestureDetector(
onTap: () {
if (onItemSelected != null) {
onItemSelected!();
}
},
child: Text(
_capitalizeFirstLetter(title),
style: Theme.of(context).textTheme.bodySmall!.copyWith(
color: isSelected
? ColorsManager.blackColor // Change color to black when selected
: ColorsManager.lightGrayColor, // Gray when not selected
fontWeight: FontWeight.w400,
),
),
),
),
],
),
if (isExpanded && children != null && children!.isNotEmpty)
Padding(
padding: const EdgeInsets.only(left: 48.0),
child: Column(
children: children ?? [],
),
),
],
);
}
String _capitalizeFirstLetter(String text) {
if (text.isEmpty) return text;
return text[0].toUpperCase() + text.substring(1);
}
}

View File

@ -0,0 +1,220 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/common/widgets/search_bar.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_event.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_state.dart';
import 'package:syncrow_web/pages/space_tree/view/custom_expansion.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/style.dart';
class SpaceTreeView extends StatefulWidget {
final Function onSelect;
const SpaceTreeView({required this.onSelect, super.key});
@override
State<SpaceTreeView> createState() => _SpaceTreeViewState();
}
class _SpaceTreeViewState extends State<SpaceTreeView> {
final ScrollController _scrollController = ScrollController();
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return BlocBuilder<SpaceTreeBloc, SpaceTreeState>(builder: (context, state) {
List<CommunityModel> list = state.isSearching ? state.filteredCommunity : state.communityList;
return Container(
height: MediaQuery.sizeOf(context).height,
decoration: subSectionContainerDecoration,
child: state is SpaceTreeLoadingState
? const Center(child: CircularProgressIndicator())
: Column(
children: [
CustomSearchBar(
onSearchChanged: (query) {
context.read<SpaceTreeBloc>().add(SearchQueryEvent(query));
},
),
const SizedBox(height: 16),
Expanded(
child: ListView(
shrinkWrap: true,
scrollDirection: Axis.horizontal,
children: [
Container(
width: MediaQuery.sizeOf(context).width * 0.5,
padding: const EdgeInsets.all(8.0),
child: list.isEmpty
? Center(
child: Text(
'No results found',
style: Theme.of(context).textTheme.bodySmall!.copyWith(
color: ColorsManager.lightGrayColor,
fontWeight: FontWeight.w400,
),
),
)
: Scrollbar(
scrollbarOrientation: ScrollbarOrientation.left,
thumbVisibility: true,
controller: _scrollController,
child: Padding(
padding: const EdgeInsets.only(left: 16),
child: ListView(
controller: _scrollController,
shrinkWrap: true,
children: list
.map(
(community) => CustomExpansionTileSpaceTree(
title: community.name,
isSelected: state.selectedCommunities
.contains(community.uuid),
isSoldCheck: state.selectedCommunities
.contains(community.uuid),
onExpansionChanged: () {
context
.read<SpaceTreeBloc>()
.add(OnCommunityExpanded(community.uuid));
},
isExpanded: state.expandedCommunities
.contains(community.uuid),
onItemSelected: () {
context.read<SpaceTreeBloc>().add(
OnCommunitySelected(
community.uuid, community.spaces));
widget.onSelect();
},
children: community.spaces.map((space) {
return CustomExpansionTileSpaceTree(
title: space.name,
isExpanded:
state.expandedSpaces.contains(space.uuid),
onItemSelected: () {
context.read<SpaceTreeBloc>().add(
OnSpaceSelected(community.uuid,
space.uuid ?? '', space.children));
widget.onSelect();
},
onExpansionChanged: () {
context.read<SpaceTreeBloc>().add(
OnSpaceExpanded(
community.uuid, space.uuid ?? ''));
},
isSelected:
state.selectedSpaces.contains(space.uuid) ||
state.soldCheck.contains(space.uuid),
isSoldCheck: state.soldCheck.contains(space.uuid),
children: _buildNestedSpaces(
context, state, space, community.uuid),
);
}).toList(),
),
)
.toList(),
),
),
),
),
],
),
),
// Expanded(
// child: Padding(
// padding: const EdgeInsets.all(8.0),
// child: list.isEmpty
// ? Center(
// child: Text(
// 'No results found',
// style: Theme.of(context).textTheme.bodySmall!.copyWith(
// color: ColorsManager.lightGrayColor, // Gray when not selected
// fontWeight: FontWeight.w400,
// ),
// ),
// )
// : ListView(
// shrinkWrap: true,
// children: list
// .map(
// (community) => CustomExpansionTileSpaceTree(
// title: community.name,
// isSelected:
// state.selectedCommunities.contains(community.uuid),
// isSoldCheck:
// state.selectedCommunities.contains(community.uuid),
// onExpansionChanged: () {
// context
// .read<SpaceTreeBloc>()
// .add(OnCommunityExpanded(community.uuid));
// },
// isExpanded:
// state.expandedCommunities.contains(community.uuid),
// onItemSelected: () {
// context.read<SpaceTreeBloc>().add(
// OnCommunitySelected(community.uuid, community.spaces));
// onSelect();
// },
// children: community.spaces.map((space) {
// return CustomExpansionTileSpaceTree(
// title: space.name,
// isExpanded: state.expandedSpaces.contains(space.uuid),
// onItemSelected: () {
// context.read<SpaceTreeBloc>().add(OnSpaceSelected(
// community.uuid, space.uuid ?? '', space.children));
// onSelect();
// },
// onExpansionChanged: () {
// context.read<SpaceTreeBloc>().add(
// OnSpaceExpanded(community.uuid, space.uuid ?? ''));
// },
// isSelected: state.selectedSpaces.contains(space.uuid) ||
// state.soldCheck.contains(space.uuid),
// isSoldCheck: state.soldCheck.contains(space.uuid),
// children: _buildNestedSpaces(
// context, state, space, community.uuid),
// );
// }).toList(),
// ),
// )
// .toList(),
// ),
// ),
// ),
],
),
);
});
}
List<Widget> _buildNestedSpaces(
BuildContext context, SpaceTreeState state, SpaceModel space, String communityId) {
return space.children.map((child) {
return CustomExpansionTileSpaceTree(
isSelected:
state.selectedSpaces.contains(child.uuid) || state.soldCheck.contains(child.uuid),
isSoldCheck: state.soldCheck.contains(child.uuid),
title: child.name,
isExpanded: state.expandedSpaces.contains(child.uuid),
onItemSelected: () {
context
.read<SpaceTreeBloc>()
.add(OnSpaceSelected(communityId, child.uuid ?? '', child.children));
widget.onSelect();
},
onExpansionChanged: () {
context.read<SpaceTreeBloc>().add(OnSpaceExpanded(communityId, child.uuid ?? ''));
},
children: _buildNestedSpaces(context, state, child, communityId),
);
}).toList();
}
}

View File

@ -12,7 +12,6 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_mod
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart';
import 'package:syncrow_web/pages/spaces_management/assign_tag/views/assign_tag_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/assign_tag/views/assign_tag_dialog.dart';
import 'package:syncrow_web/pages/spaces_management/helper/tag_helper.dart'; import 'package:syncrow_web/pages/spaces_management/helper/tag_helper.dart';
import 'package:syncrow_web/pages/spaces_management/tag_model/widgets/action_button_widget.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
class AddDeviceTypeWidget extends StatelessWidget { class AddDeviceTypeWidget extends StatelessWidget {
@ -23,10 +22,12 @@ class AddDeviceTypeWidget extends StatelessWidget {
final List<Tag>? spaceTags; final List<Tag>? spaceTags;
final List<String>? allTags; final List<String>? allTags;
final String spaceName; final String spaceName;
final bool isCreate;
final Function(List<Tag>, List<SubspaceModel>?)? onSave; final Function(List<Tag>, List<SubspaceModel>?)? onSave;
const AddDeviceTypeWidget( const AddDeviceTypeWidget(
{super.key, {super.key,
required this.isCreate,
this.products, this.products,
this.initialSelectedProducts, this.initialSelectedProducts,
this.onProductsSelected, this.onProductsSelected,
@ -74,7 +75,9 @@ class AddDeviceTypeWidget extends StatelessWidget {
padding: padding:
const EdgeInsets.symmetric(horizontal: 20.0), const EdgeInsets.symmetric(horizontal: 20.0),
child: ScrollableGridViewWidget( child: ScrollableGridViewWidget(
initialProductCounts: state.selectedProducts,
products: products, products: products,
isCreate: isCreate,
crossAxisCount: crossAxisCount), crossAxisCount: crossAxisCount),
), ),
), ),
@ -124,17 +127,14 @@ class AddDeviceTypeWidget extends StatelessWidget {
barrierDismissible: false, barrierDismissible: false,
context: context, context: context,
builder: (context) => AssignTagDialog( builder: (context) => AssignTagDialog(
products: products, products: products,
subspaces: subspaces, subspaces: subspaces,
addedProducts: state.selectedProducts, addedProducts: state.selectedProducts,
allTags: allTags, allTags: allTags,
spaceName: spaceName, spaceName: spaceName,
initialTags: initialTags, initialTags: initialTags,
title: dialogTitle, title: dialogTitle,
onSave: (tags, subspaces) { onSave: onSave),
onSave!(tags, subspaces);
},
),
); );
} }
}, },
@ -148,29 +148,4 @@ class AddDeviceTypeWidget extends StatelessWidget {
), ),
)); ));
} }
List<Tag> generateInitialTags({
List<Tag>? spaceTags,
List<SubspaceModel>? subspaces,
}) {
final List<Tag> initialTags = [];
if (spaceTags != null) {
initialTags.addAll(spaceTags);
}
if (subspaces != null) {
for (var subspace in subspaces) {
if (subspace.tags != null) {
initialTags.addAll(
subspace.tags!.map(
(tag) => tag.copyWith(location: subspace.subspaceName),
),
);
}
}
}
return initialTags;
}
} }

View File

@ -13,12 +13,13 @@ import 'package:syncrow_web/utils/constants/assets.dart';
class DeviceTypeTileWidget extends StatelessWidget { class DeviceTypeTileWidget extends StatelessWidget {
final ProductModel product; final ProductModel product;
final List<SelectedProduct> productCounts; final List<SelectedProduct> productCounts;
final bool isCreate;
const DeviceTypeTileWidget({ const DeviceTypeTileWidget(
super.key, {super.key,
required this.product, required this.product,
required this.productCounts, required this.productCounts,
}); required this.isCreate});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -48,7 +49,7 @@ class DeviceTypeTileWidget extends StatelessWidget {
DeviceNameWidget(name: product.name), DeviceNameWidget(name: product.name),
const SizedBox(height: 4), const SizedBox(height: 4),
CounterWidget( CounterWidget(
isCreate: false, isCreate: isCreate,
initialCount: selectedProduct.count, initialCount: selectedProduct.count,
onCountChanged: (newCount) { onCountChanged: (newCount) {
context.read<AddDeviceTypeBloc>().add( context.read<AddDeviceTypeBloc>().add(

View File

@ -10,13 +10,14 @@ class ScrollableGridViewWidget extends StatelessWidget {
final List<ProductModel>? products; final List<ProductModel>? products;
final int crossAxisCount; final int crossAxisCount;
final List<SelectedProduct>? initialProductCounts; final List<SelectedProduct>? initialProductCounts;
final bool isCreate;
const ScrollableGridViewWidget({ const ScrollableGridViewWidget(
super.key, {super.key,
required this.products, required this.products,
required this.crossAxisCount, required this.crossAxisCount,
this.initialProductCounts, this.initialProductCounts,
}); required this.isCreate});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -46,6 +47,7 @@ class ScrollableGridViewWidget extends StatelessWidget {
return DeviceTypeTileWidget( return DeviceTypeTileWidget(
product: product, product: product,
isCreate: isCreate,
productCounts: initialProductCount != null productCounts: initialProductCount != null
? [...productCounts, initialProductCount] ? [...productCounts, initialProductCount]
: productCounts, : productCounts,

View File

@ -5,12 +5,15 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_mod
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_event.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_event.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_state.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_state.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_body_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_body_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_update_model.dart';
import 'package:syncrow_web/services/product_api.dart'; import 'package:syncrow_web/services/product_api.dart';
import 'package:syncrow_web/services/space_mana_api.dart'; import 'package:syncrow_web/services/space_mana_api.dart';
import 'package:syncrow_web/services/space_model_mang_api.dart'; import 'package:syncrow_web/services/space_model_mang_api.dart';
import 'package:syncrow_web/utils/constants/action_enum.dart';
class SpaceManagementBloc class SpaceManagementBloc
extends Bloc<SpaceManagementEvent, SpaceManagementState> { extends Bloc<SpaceManagementEvent, SpaceManagementState> {
@ -341,7 +344,7 @@ class SpaceManagementBloc
products: _cachedProducts ?? [], products: _cachedProducts ?? [],
selectedCommunity: selectedCommunity, selectedCommunity: selectedCommunity,
selectedSpace: selectedSpace, selectedSpace: selectedSpace,
spaceModels: spaceModels ?? [])); spaceModels: spaceModels));
} }
} catch (e) { } catch (e) {
emit(SpaceManagementError('Error updating state: $e')); emit(SpaceManagementError('Error updating state: $e'));
@ -428,6 +431,76 @@ class SpaceManagementBloc
for (var space in orderedSpaces) { for (var space in orderedSpaces) {
try { try {
if (space.uuid != null && space.uuid!.isNotEmpty) { if (space.uuid != null && space.uuid!.isNotEmpty) {
List<TagModelUpdate> tagUpdates = [];
final prevSpace = await _api.getSpace(communityUuid, space.uuid!);
final List<UpdateSubspaceTemplateModel> subspaceUpdates = [];
final List<SubspaceModel>? prevSubspaces = prevSpace?.subspaces;
final List<SubspaceModel>? newSubspaces = space.subspaces;
tagUpdates = processTagUpdates(prevSpace?.tags, space.tags);
if (prevSubspaces != null || newSubspaces != null) {
if (prevSubspaces != null && newSubspaces != null) {
for (var prevSubspace in prevSubspaces) {
final existsInNew = newSubspaces
.any((subspace) => subspace.uuid == prevSubspace.uuid);
if (!existsInNew) {
subspaceUpdates.add(UpdateSubspaceTemplateModel(
action: Action.delete, uuid: prevSubspace.uuid));
}
}
} else if (prevSubspaces != null && newSubspaces == null) {
for (var prevSubspace in prevSubspaces) {
subspaceUpdates.add(UpdateSubspaceTemplateModel(
action: Action.delete, uuid: prevSubspace.uuid));
}
}
if (newSubspaces != null) {
for (var newSubspace in newSubspaces) {
// Tag without UUID
if ((newSubspace.uuid == null || newSubspace.uuid!.isEmpty)) {
final List<TagModelUpdate> tagUpdates = [];
if (newSubspace.tags != null) {
for (var tag in newSubspace.tags!) {
tagUpdates.add(TagModelUpdate(
action: Action.add,
uuid: tag.uuid == '' ? null : tag.uuid,
tag: tag.tag,
productUuid: tag.product?.uuid));
}
}
subspaceUpdates.add(UpdateSubspaceTemplateModel(
action: Action.add,
subspaceName: newSubspace.subspaceName,
tags: tagUpdates));
}
}
}
if (prevSubspaces != null && newSubspaces != null) {
final newSubspaceMap = {
for (var subspace in newSubspaces) subspace.uuid: subspace
};
for (var prevSubspace in prevSubspaces) {
final newSubspace = newSubspaceMap[prevSubspace.uuid];
if (newSubspace != null) {
final List<TagModelUpdate> tagSubspaceUpdates =
processTagUpdates(prevSubspace.tags, newSubspace.tags);
subspaceUpdates.add(UpdateSubspaceTemplateModel(
action: Action.update,
uuid: newSubspace.uuid,
subspaceName: newSubspace.subspaceName,
tags: tagSubspaceUpdates));
}
}
}
}
final response = await _api.updateSpace( final response = await _api.updateSpace(
communityId: communityUuid, communityId: communityUuid,
spaceId: space.uuid!, spaceId: space.uuid!,
@ -436,6 +509,8 @@ class SpaceManagementBloc
isPrivate: space.isPrivate, isPrivate: space.isPrivate,
position: space.position, position: space.position,
icon: space.icon, icon: space.icon,
subspaces: subspaceUpdates,
tags: tagUpdates,
direction: space.incomingConnection?.direction, direction: space.incomingConnection?.direction,
); );
} else { } else {
@ -535,4 +610,79 @@ class SpaceManagementBloc
emit(SpaceManagementError('Error loading communities and spaces: $e')); emit(SpaceManagementError('Error loading communities and spaces: $e'));
} }
} }
List<TagModelUpdate> processTagUpdates(
List<Tag>? prevTags,
List<Tag>? newTags,
) {
final List<TagModelUpdate> tagUpdates = [];
final processedTags = <String?>{};
if (prevTags == null && newTags != null) {
for (var newTag in newTags) {
tagUpdates.add(TagModelUpdate(
action: Action.add,
tag: newTag.tag,
uuid: newTag.uuid,
productUuid: newTag.product?.uuid,
));
}
return tagUpdates;
}
if (newTags != null || prevTags != null) {
// Case 1: Tags deleted
if (prevTags != null && newTags != null) {
for (var prevTag in prevTags) {
final existsInNew =
newTags.any((newTag) => newTag.uuid == prevTag.uuid);
if (!existsInNew) {
tagUpdates
.add(TagModelUpdate(action: Action.delete, uuid: prevTag.uuid));
}
}
} else if (prevTags != null && newTags == null) {
for (var prevTag in prevTags) {
tagUpdates
.add(TagModelUpdate(action: Action.delete, uuid: prevTag.uuid));
}
}
// Case 2: Tags added
if (newTags != null) {
final prevTagUuids = prevTags?.map((t) => t.uuid).toSet() ?? {};
for (var newTag in newTags) {
// Tag without UUID
if ((newTag.uuid == null || !prevTagUuids.contains(newTag.uuid)) &&
!processedTags.contains(newTag.tag)) {
tagUpdates.add(TagModelUpdate(
action: Action.add,
tag: newTag.tag,
uuid: newTag.uuid == '' ? null : newTag.uuid,
productUuid: newTag.product?.uuid));
processedTags.add(newTag.tag);
}
}
}
// Case 3: Tags updated
if (prevTags != null && newTags != null) {
final newTagMap = {for (var tag in newTags) tag.uuid: tag};
for (var prevTag in prevTags) {
final newTag = newTagMap[prevTag.uuid];
if (newTag != null) {
tagUpdates.add(TagModelUpdate(
action: Action.update,
uuid: newTag.uuid,
tag: newTag.tag,
));
} else {}
}
}
}
return tagUpdates;
}
} }

View File

@ -66,7 +66,6 @@ class SpaceModel {
final instance = SpaceModel( final instance = SpaceModel(
internalId: internalId, internalId: internalId,
uuid: json['uuid'] ?? '', uuid: json['uuid'] ?? '',
spaceTuyaUuid: json['spaceTuyaUuid'],
name: json['spaceName'], name: json['spaceName'],
isPrivate: json['isPrivate'] ?? false, isPrivate: json['isPrivate'] ?? false,
invitationCode: json['invitationCode'], invitationCode: json['invitationCode'],

View File

@ -1,5 +1,6 @@
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
import 'package:syncrow_web/utils/constants/action_enum.dart'; import 'package:syncrow_web/utils/constants/action_enum.dart';
import 'package:uuid/uuid.dart';
import 'tag.dart'; import 'tag.dart';
@ -8,19 +9,24 @@ class SubspaceModel {
String subspaceName; String subspaceName;
final bool disabled; final bool disabled;
List<Tag>? tags; List<Tag>? tags;
String internalId;
SubspaceModel({ SubspaceModel({
this.uuid, this.uuid,
required this.subspaceName, required this.subspaceName,
required this.disabled, required this.disabled,
this.tags, this.tags,
}); String? internalId,
}) : internalId = internalId ?? const Uuid().v4();
factory SubspaceModel.fromJson(Map<String, dynamic> json) { factory SubspaceModel.fromJson(Map<String, dynamic> json) {
final String internalId = json['internalId'] ?? const Uuid().v4();
return SubspaceModel( return SubspaceModel(
uuid: json['uuid'] ?? '', uuid: json['uuid'] ?? '',
subspaceName: json['subspaceName'] ?? '', subspaceName: json['subspaceName'] ?? '',
disabled: json['disabled'] ?? false, disabled: json['disabled'] ?? false,
internalId: internalId,
tags: (json['tags'] as List<dynamic>?) tags: (json['tags'] as List<dynamic>?)
?.map((item) => Tag.fromJson(item)) ?.map((item) => Tag.fromJson(item))
.toList() ?? .toList() ??
@ -43,7 +49,7 @@ class UpdateSubspaceModel {
final Action action; final Action action;
final String? subspaceName; final String? subspaceName;
final List<UpdateTag>? tags; final List<UpdateTag>? tags;
UpdateSubspaceModel({ UpdateSubspaceModel({
required this.action, required this.action,
required this.uuid, required this.uuid,
this.subspaceName, this.subspaceName,

View File

@ -195,6 +195,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
screenSize, screenSize,
position: position:
spaces[index].position + newPosition, spaces[index].position + newPosition,
parentIndex: index, parentIndex: index,
direction: direction, direction: direction,
); );
@ -294,6 +295,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
return CreateSpaceDialog( return CreateSpaceDialog(
products: widget.products, products: widget.products,
spaceModels: widget.spaceModels, spaceModels: widget.spaceModels,
allTags: _getAllTagValues(spaces),
parentSpace: parentIndex != null ? spaces[parentIndex] : null, parentSpace: parentIndex != null ? spaces[parentIndex] : null,
onCreateSpace: (String name, onCreateSpace: (String name,
String icon, String icon,
@ -350,7 +352,10 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
name: widget.selectedSpace!.name, name: widget.selectedSpace!.name,
icon: widget.selectedSpace!.icon, icon: widget.selectedSpace!.icon,
editSpace: widget.selectedSpace, editSpace: widget.selectedSpace,
tags: widget.selectedSpace?.tags,
subspaces: widget.selectedSpace?.subspaces,
isEdit: true, isEdit: true,
allTags: _getAllTagValues(spaces),
onCreateSpace: (String name, onCreateSpace: (String name,
String icon, String icon,
List<SelectedProduct> selectedProducts, List<SelectedProduct> selectedProducts,
@ -742,4 +747,14 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
duplicateRecursive(space, space.position, duplicatedParent); duplicateRecursive(space, space.position, duplicatedParent);
} }
} }
List<String> _getAllTagValues(List<SpaceModel> spaces) {
final List<String> allTags = [];
for (final space in spaces) {
if (space.tags != null) {
allTags.addAll(space.listAllTagValues());
}
}
return allTags;
}
} }

View File

@ -1,5 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:syncrow_web/common/custom_expansion_tile.dart'; import 'package:syncrow_web/common/widgets/custom_expansion_tile.dart';
class CommunityTile extends StatelessWidget { class CommunityTile extends StatelessWidget {
final String title; final String title;

View File

@ -39,6 +39,7 @@ class CreateSpaceDialog extends StatefulWidget {
final List<SpaceTemplateModel>? spaceModels; final List<SpaceTemplateModel>? spaceModels;
final List<SubspaceModel>? subspaces; final List<SubspaceModel>? subspaces;
final List<Tag>? tags; final List<Tag>? tags;
final List<String>? allTags;
const CreateSpaceDialog( const CreateSpaceDialog(
{super.key, {super.key,
@ -49,6 +50,7 @@ class CreateSpaceDialog extends StatefulWidget {
this.icon, this.icon,
this.isEdit = false, this.isEdit = false,
this.editSpace, this.editSpace,
this.allTags,
this.selectedProducts = const [], this.selectedProducts = const [],
this.spaceModels, this.spaceModels,
this.subspaces, this.subspaces,
@ -79,6 +81,8 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
widget.selectedProducts.isNotEmpty ? widget.selectedProducts : []; widget.selectedProducts.isNotEmpty ? widget.selectedProducts : [];
isOkButtonEnabled = isOkButtonEnabled =
enteredName.isNotEmpty || nameController.text.isNotEmpty; enteredName.isNotEmpty || nameController.text.isNotEmpty;
tags = widget.tags ?? [];
subspaces = widget.subspaces ?? [];
} }
@override @override
@ -171,14 +175,13 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
} }
}); });
}, },
style: const TextStyle(color: Colors.black), style: Theme.of(context).textTheme.bodyMedium,
decoration: InputDecoration( decoration: InputDecoration(
hintText: 'Please enter the name', hintText: 'Please enter the name',
hintStyle: const TextStyle( hintStyle: Theme.of(context)
fontSize: 14, .textTheme
color: ColorsManager.lightGrayColor, .bodyMedium!
fontWeight: FontWeight.w400, .copyWith(color: ColorsManager.lightGrayColor),
),
filled: true, filled: true,
fillColor: ColorsManager.boxColor, fillColor: ColorsManager.boxColor,
enabledBorder: OutlineInputBorder( enabledBorder: OutlineInputBorder(
@ -237,7 +240,7 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
), ),
) )
: Container( : Container(
width: screenWidth * 0.35, width: screenWidth * 0.25,
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
vertical: 10.0, horizontal: 16.0), vertical: 10.0, horizontal: 16.0),
decoration: BoxDecoration( decoration: BoxDecoration(
@ -251,8 +254,11 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
Chip( Chip(
label: Text( label: Text(
selectedSpaceModel?.modelName ?? '', selectedSpaceModel?.modelName ?? '',
style: const TextStyle( style: Theme.of(context)
color: ColorsManager.spaceColor), .textTheme
.bodyMedium!
.copyWith(
color: ColorsManager.spaceColor),
), ),
backgroundColor: ColorsManager.whiteColors, backgroundColor: ColorsManager.whiteColors,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
@ -285,25 +291,25 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
), ),
), ),
const SizedBox(height: 25), const SizedBox(height: 25),
const Row( Row(
children: [ children: [
Expanded( const Expanded(
child: Divider( child: Divider(
color: ColorsManager.neutralGray, color: ColorsManager.neutralGray,
thickness: 1.0, thickness: 1.0,
), ),
), ),
Padding( Padding(
padding: EdgeInsets.symmetric(horizontal: 6.0), padding: const EdgeInsets.symmetric(horizontal: 6.0),
child: Text( child: Text(
'OR', 'OR',
style: TextStyle( style: Theme.of(context)
color: Colors.black, .textTheme
fontWeight: FontWeight.bold, .bodyMedium
), ?.copyWith(fontWeight: FontWeight.bold),
), ),
), ),
Expanded( const Expanded(
child: Divider( child: Divider(
color: ColorsManager.neutralGray, color: ColorsManager.neutralGray,
thickness: 1.0, thickness: 1.0,
@ -312,7 +318,7 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
], ],
), ),
const SizedBox(height: 25), const SizedBox(height: 25),
subspaces == null subspaces == null || subspaces!.isEmpty
? TextButton( ? TextButton(
style: TextButton.styleFrom( style: TextButton.styleFrom(
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
@ -344,21 +350,40 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
runSpacing: 8.0, runSpacing: 8.0,
children: [ children: [
if (subspaces != null) if (subspaces != null)
...subspaces!.map((subspace) => ...subspaces!.map((subspace) {
SubspaceNameDisplayWidget( return Column(
validateName: (updatedName) { crossAxisAlignment:
return !subspaces!.any((s) => CrossAxisAlignment.start,
s != subspace && children: [
s.subspaceName == updatedName); SubspaceNameDisplayWidget(
}, text: subspace.subspaceName,
text: subspace.subspaceName, validateName: (updatedName) {
onNameChanged: (updatedName) { bool nameExists =
setState(() { subspaces!.any((s) {
subspace.subspaceName = bool isSameId = s.internalId ==
updatedName; subspace.internalId;
}); bool isSameName = s.subspaceName
}, .trim()
)), .toLowerCase() ==
updatedName
.trim()
.toLowerCase();
return !isSameId && isSameName;
});
return !nameExists;
},
onNameChanged: (updatedName) {
setState(() {
subspace.subspaceName =
updatedName;
});
},
),
],
);
}),
EditChip( EditChip(
onTap: () async { onTap: () async {
_showSubSpaceDialog(context, enteredName, _showSubSpaceDialog(context, enteredName,
@ -408,9 +433,12 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
), ),
label: Text( label: Text(
'x${entry.value}', // Show count 'x${entry.value}', // Show count
style: const TextStyle( style: Theme.of(context)
color: ColorsManager.spaceColor, .textTheme
), .bodySmall
?.copyWith(
color: ColorsManager
.spaceColor),
), ),
backgroundColor: backgroundColor:
ColorsManager.whiteColors, ColorsManager.whiteColors,
@ -425,14 +453,29 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
), ),
EditChip(onTap: () async { EditChip(onTap: () async {
_showTagCreateDialog( final result = await showDialog(
context, context: context,
enteredName, builder: (context) => AssignTagDialog(
widget.isEdit, products: widget.products,
widget.products, subspaces: widget.subspaces,
subspaces, addedProducts: TagHelper
.createInitialSelectedProductsForTags(
tags ?? [], subspaces),
title: 'Edit Device',
initialTags:
TagHelper.generateInitialForTags(
spaceTags: tags,
subspaces: subspaces),
spaceName: widget.name ?? '',
onSave:
(updatedTags, updatedSubspaces) {
setState(() {
tags = updatedTags;
subspaces = updatedSubspaces;
});
},
),
); );
// Edit action
}) })
], ],
), ),
@ -547,6 +590,7 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
setState(() { setState(() {
selectedSpaceModel = selectedModel; selectedSpaceModel = selectedModel;
subspaces = null; subspaces = null;
tags = null;
}); });
} }
}, },
@ -597,8 +641,26 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
spaceName: name, spaceName: name,
products: products, products: products,
subspaces: subspaces, subspaces: subspaces,
allTags: [], allTags: widget.allTags,
onSave: (selectedSpaceTags, selectedSubspaces) {}, onSave: (selectedSpaceTags, selectedSubspaces) {
setState(() {
tags = selectedSpaceTags;
selectedSpaceModel = null;
if (selectedSubspaces != null) {
if (subspaces != null) {
for (final subspace in subspaces!) {
for (final selectedSubspace in selectedSubspaces) {
if (subspace.subspaceName ==
selectedSubspace.subspaceName) {
subspace.tags = selectedSubspace.tags;
}
}
}
}
}
});
},
); );
}, },
) )
@ -610,7 +672,8 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
products: products, products: products,
subspaces: subspaces, subspaces: subspaces,
spaceTags: tags, spaceTags: tags,
allTags: [], isCreate: true,
allTags: widget.allTags,
initialSelectedProducts: initialSelectedProducts:
TagHelper.createInitialSelectedProductsForTags( TagHelper.createInitialSelectedProductsForTags(
tags, subspaces), tags, subspaces),

View File

@ -1,7 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/svg.dart'; import 'package:flutter_svg/svg.dart';
import 'package:syncrow_web/common/search_bar.dart'; import 'package:syncrow_web/common/widgets/search_bar.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_event.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_event.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart';
@ -36,8 +36,7 @@ class _SidebarWidgetState extends State<SidebarWidget> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_selectedId = widget _selectedId = widget.selectedSpaceUuid; // Initialize with the passed selected space UUID
.selectedSpaceUuid; // Initialize with the passed selected space UUID
} }
@override @override
@ -62,8 +61,8 @@ class _SidebarWidgetState extends State<SidebarWidget> {
return widget.communities.where((community) { return widget.communities.where((community) {
final containsQueryInCommunity = final containsQueryInCommunity =
community.name.toLowerCase().contains(_searchQuery.toLowerCase()); community.name.toLowerCase().contains(_searchQuery.toLowerCase());
final containsQueryInSpaces = community.spaces final containsQueryInSpaces =
.any((space) => _containsQuery(space, _searchQuery.toLowerCase())); community.spaces.any((space) => _containsQuery(space, _searchQuery.toLowerCase()));
return containsQueryInCommunity || containsQueryInSpaces; return containsQueryInCommunity || containsQueryInSpaces;
}).toList(); }).toList();
@ -72,8 +71,8 @@ class _SidebarWidgetState extends State<SidebarWidget> {
// Helper function to determine if any space or its children match the search query // Helper function to determine if any space or its children match the search query
bool _containsQuery(SpaceModel space, String query) { bool _containsQuery(SpaceModel space, String query) {
final matchesSpace = space.name.toLowerCase().contains(query); final matchesSpace = space.name.toLowerCase().contains(query);
final matchesChildren = space.children.any((child) => final matchesChildren =
_containsQuery(child, query)); // Recursive check for children space.children.any((child) => _containsQuery(child, query)); // Recursive check for children
// If the space or any of its children match the query, expand this space // If the space or any of its children match the query, expand this space
if (matchesSpace || matchesChildren) { if (matchesSpace || matchesChildren) {
@ -107,8 +106,7 @@ class _SidebarWidgetState extends State<SidebarWidget> {
width: 300, width: 300,
decoration: subSectionContainerDecoration, decoration: subSectionContainerDecoration,
child: Column( child: Column(
mainAxisSize: mainAxisSize: MainAxisSize.min, // Ensures the Column only takes necessary height
MainAxisSize.min, // Ensures the Column only takes necessary height
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
// Communities title with the add button // Communities title with the add button
@ -224,14 +222,11 @@ class _SidebarWidgetState extends State<SidebarWidget> {
}); });
context.read<SpaceManagementBloc>().add( context.read<SpaceManagementBloc>().add(
SelectSpaceEvent( SelectSpaceEvent(selectedCommunity: community, selectedSpace: space),
selectedCommunity: community, selectedSpace: space),
); );
}, },
children: space.children.isNotEmpty children: space.children.isNotEmpty
? space.children ? space.children.map((childSpace) => _buildSpaceTile(childSpace, community)).toList()
.map((childSpace) => _buildSpaceTile(childSpace, community))
.toList()
: [], // Recursively render child spaces if available : [], // Recursively render child spaces if available
); );
} }

View File

@ -1,5 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:syncrow_web/common/custom_expansion_tile.dart'; import 'package:syncrow_web/common/widgets/custom_expansion_tile.dart';
class SpaceTile extends StatefulWidget { class SpaceTile extends StatefulWidget {
final String title; final String title;

View File

@ -20,8 +20,7 @@ class SpaceWidget extends StatelessWidget {
top: position.dy, top: position.dy,
child: GestureDetector( child: GestureDetector(
onTap: onTap, onTap: onTap,
child: child: Container(
Container(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
decoration: BoxDecoration( decoration: BoxDecoration(
color: ColorsManager.whiteColors, color: ColorsManager.whiteColors,
@ -39,11 +38,10 @@ class SpaceWidget extends StatelessWidget {
children: [ children: [
const Icon(Icons.location_on, color: ColorsManager.spaceColor), const Icon(Icons.location_on, color: ColorsManager.spaceColor),
const SizedBox(width: 8), const SizedBox(width: 8),
Text(name, style: const TextStyle(fontSize: 16)), Text(name, style: Theme.of(context).textTheme.bodyMedium),
], ],
), ),
), ),
), ),
); );
} }

View File

@ -12,6 +12,7 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
import 'package:syncrow_web/pages/spaces_management/assign_tag/bloc/assign_tag_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/assign_tag/bloc/assign_tag_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/assign_tag/bloc/assign_tag_event.dart'; import 'package:syncrow_web/pages/spaces_management/assign_tag/bloc/assign_tag_event.dart';
import 'package:syncrow_web/pages/spaces_management/assign_tag/bloc/assign_tag_state.dart'; import 'package:syncrow_web/pages/spaces_management/assign_tag/bloc/assign_tag_state.dart';
import 'package:syncrow_web/pages/spaces_management/helper/tag_helper.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
class AssignTagDialog extends StatelessWidget { class AssignTagDialog extends StatelessWidget {
@ -40,8 +41,11 @@ class AssignTagDialog extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final List<String> locations = final List<String> locations = (subspaces ?? [])
(subspaces ?? []).map((subspace) => subspace.subspaceName).toList(); .map((subspace) => subspace.subspaceName)
.toList()
..add('Main Space');
return BlocProvider( return BlocProvider(
create: (_) => AssignTagBloc() create: (_) => AssignTagBloc()
..add(InitializeTags( ..add(InitializeTags(
@ -93,21 +97,22 @@ class AssignTagDialog extends StatelessWidget {
], ],
rows: state.tags.isEmpty rows: state.tags.isEmpty
? [ ? [
const DataRow(cells: [ DataRow(cells: [
DataCell( DataCell(
Center( Center(
child: Text( child: Text('No Data Available',
'No Data Available', style: Theme.of(context)
style: TextStyle( .textTheme
fontSize: 14, .bodyMedium
color: ColorsManager.lightGrayColor, ?.copyWith(
), color: ColorsManager
), .lightGrayColor,
)),
), ),
), ),
DataCell(SizedBox()), const DataCell(SizedBox()),
DataCell(SizedBox()), const DataCell(SizedBox()),
DataCell(SizedBox()), const DataCell(SizedBox()),
]) ])
] ]
: List.generate(state.tags.length, (index) { : List.generate(state.tags.length, (index) {
@ -209,10 +214,11 @@ class AssignTagDialog extends StatelessWidget {
), ),
), ),
if (state.errorMessage != null) if (state.errorMessage != null)
Text( Text(state.errorMessage!,
state.errorMessage!, style: Theme.of(context)
style: const TextStyle(color: ColorsManager.warningRed), .textTheme
), .bodySmall
?.copyWith(color: ColorsManager.warningRed)),
], ],
), ),
), ),
@ -235,16 +241,18 @@ class AssignTagDialog extends StatelessWidget {
Navigator.of(context).pop(); Navigator.of(context).pop();
await showDialog<bool>( await showDialog(
barrierDismissible: false,
context: context, context: context,
builder: (dialogContext) => AddDeviceTypeWidget( builder: (context) => AddDeviceTypeWidget(
products: products, products: products,
subspaces: processedSubspaces, subspaces: processedSubspaces,
initialSelectedProducts: addedProducts, initialSelectedProducts: TagHelper
allTags: allTags, .createInitialSelectedProductsForTags(
processedTags, processedSubspaces),
spaceName: spaceName, spaceName: spaceName,
spaceTags: processedTags, spaceTags: processedTags,
isCreate: false,
onSave: onSave,
), ),
); );
}, },
@ -261,7 +269,6 @@ class AssignTagDialog extends StatelessWidget {
foregroundColor: ColorsManager.whiteColors, foregroundColor: ColorsManager.whiteColors,
onPressed: state.isSaveEnabled onPressed: state.isSaveEnabled
? () async { ? () async {
Navigator.of(context).pop();
final updatedTags = List<Tag>.from(state.tags); final updatedTags = List<Tag>.from(state.tags);
final result = final result =
processTags(updatedTags, subspaces); processTags(updatedTags, subspaces);
@ -270,8 +277,8 @@ class AssignTagDialog extends StatelessWidget {
result['updatedTags'] as List<Tag>; result['updatedTags'] as List<Tag>;
final processedSubspaces = final processedSubspaces =
result['subspaces'] as List<SubspaceModel>; result['subspaces'] as List<SubspaceModel>;
onSave?.call(processedTags, processedSubspaces);
onSave!(processedTags, processedSubspaces); Navigator.of(context).pop();
} }
: null, : null,
child: const Text('Save'), child: const Text('Save'),
@ -307,6 +314,14 @@ class AssignTagDialog extends StatelessWidget {
final modifiedTags = List<Tag>.from(updatedTags); final modifiedTags = List<Tag>.from(updatedTags);
final modifiedSubspaces = List<SubspaceModel>.from(subspaces ?? []); final modifiedSubspaces = List<SubspaceModel>.from(subspaces ?? []);
if (subspaces != null) {
for (var subspace in subspaces) {
subspace.tags?.removeWhere(
(tag) => !modifiedTags
.any((updatedTag) => updatedTag.internalId == tag.internalId),
);
}
}
for (var tag in modifiedTags.toList()) { for (var tag in modifiedTags.toList()) {
if (modifiedSubspaces.isEmpty) continue; if (modifiedSubspaces.isEmpty) continue;

View File

@ -112,22 +112,22 @@ class AssignTagModelsDialog extends StatelessWidget {
], ],
rows: state.tags.isEmpty rows: state.tags.isEmpty
? [ ? [
const DataRow(cells: [ DataRow(cells: [
DataCell( DataCell(
Center( Center(
child: Text( child: Text('No Devices Available',
'No Data Available', style: Theme.of(context)
style: TextStyle( .textTheme
fontSize: 14, .bodyMedium
color: ?.copyWith(
ColorsManager.lightGrayColor, color: ColorsManager
), .lightGrayColor,
), )),
), ),
), ),
DataCell(SizedBox()), const DataCell(SizedBox()),
DataCell(SizedBox()), const DataCell(SizedBox()),
DataCell(SizedBox()), const DataCell(SizedBox()),
]) ])
] ]
: List.generate(state.tags.length, (index) { : List.generate(state.tags.length, (index) {
@ -233,11 +233,11 @@ class AssignTagModelsDialog extends StatelessWidget {
), ),
), ),
if (state.errorMessage != null) if (state.errorMessage != null)
Text( Text(state.errorMessage!,
state.errorMessage!, style: Theme.of(context)
style: const TextStyle( .textTheme
color: ColorsManager.warningRed), .bodySmall
), ?.copyWith(color: ColorsManager.warningRed)),
], ],
), ),
), ),
@ -268,7 +268,7 @@ class AssignTagModelsDialog extends StatelessWidget {
builder: (dialogContext) => builder: (dialogContext) =>
AddDeviceTypeModelWidget( AddDeviceTypeModelWidget(
products: products, products: products,
subspaces: subspaces, subspaces: processedSubspaces,
isCreate: false, isCreate: false,
initialSelectedProducts: TagHelper initialSelectedProducts: TagHelper
.createInitialSelectedProducts( .createInitialSelectedProducts(

Some files were not shown because too many files have changed in this diff Show More