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,10 +46,9 @@ class CustomSearchBar extends StatelessWidget {
filled: true,
fillColor: ColorsManager.textFieldGreyColor,
hintText: hintText,
hintStyle: TextStyle(
color: Color(0xB2999999),
hintStyle: Theme.of(context).textTheme.bodyLarge!.copyWith(
color: ColorsManager.lightGrayColor,
fontSize: 12,
fontFamily: 'Aftika',
fontWeight: FontWeight.w400,
height: 0,
letterSpacing: -0.24,

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/home/bloc/home_bloc.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/services/locator.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 {
try {
const environment =
String.fromEnvironment('FLAVOR', defaultValue: 'development');
const environment = String.fromEnvironment('FLAVOR', defaultValue: 'development');
await dotenv.load(fileName: '.env.$environment');
WidgetsFlutterBinding.ensureInitialized();
initialSetup();
@ -48,14 +49,16 @@ class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider(
create: (context) => HomeBloc()..add(const FetchUserInfo())),
BlocProvider(create: (context) => HomeBloc()..add(const FetchUserInfo())),
BlocProvider<VisitorPasswordBloc>(
create: (context) => VisitorPasswordBloc(),
),
BlocProvider<RoutineBloc>(
create: (context) => RoutineBloc(),
),
BlocProvider<SpaceTreeBloc>(
create: (context) => SpaceTreeBloc()..add(InitialEvent()),
),
],
child: MaterialApp.router(
debugShowCheckedModeBanner: false,

View File

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

View File

@ -7,7 +7,15 @@ abstract class DeviceManagementEvent extends Equatable {
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 {
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_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/unit.dart';
import 'package:syncrow_web/pages/routiens/models/ac/ac_function.dart';
import 'package:syncrow_web/pages/routiens/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/routiens/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/ac/ac_function.dart';
import 'package:syncrow_web/pages/routines/models/device_functions.dart';
import 'package:syncrow_web/pages/routines/models/gang_switches/one_gang_switch/one_gang_switch.dart';
import 'package:syncrow_web/pages/routines/models/gang_switches/three_gang_switch/three_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/enum/device_types.dart';
@ -47,6 +48,7 @@ class AllDevicesModel {
*/
DevicesModelRoom? room;
DeviceSubspace? subspace;
DevicesModelUnit? unit;
DeviceCommunityModel? community;
String? productUuid;
@ -77,6 +79,7 @@ class AllDevicesModel {
AllDevicesModel({
this.room,
this.subspace,
this.unit,
this.community,
this.productUuid,
@ -110,6 +113,9 @@ class AllDevicesModel {
room = (json['room'] != null && (json['room'] is Map))
? DevicesModelRoom.fromJson(json['room'])
: null;
subspace = (json['subspace'] != null && (json['subspace'] is Map))
? DeviceSubspace.fromJson(json['subspace'])
: null;
unit = (json['unit'] != null && (json['unit'] is Map))
? DevicesModelUnit.fromJson(json['unit'])
: null;
@ -142,9 +148,7 @@ class AllDevicesModel {
productName = json['productName']?.toString();
if (json['spaces'] != null && json['spaces'] is List) {
spaces = (json['spaces'] as List)
.map((space) => DeviceSpaceModel.fromJson(space))
.toList();
spaces = (json['spaces'] as List).map((space) => DeviceSpaceModel.fromJson(space)).toList();
}
}
@ -192,8 +196,7 @@ SOS
String tempIcon = '';
if (type == DeviceType.LightBulb) {
tempIcon = Assets.lightBulb;
} else if (type == DeviceType.CeilingSensor ||
type == DeviceType.WallSensor) {
} else if (type == DeviceType.CeilingSensor || type == DeviceType.WallSensor) {
tempIcon = Assets.sensors;
} else if (type == DeviceType.AC) {
tempIcon = Assets.ac;
@ -248,34 +251,25 @@ SOS
case '1G':
return [
OneGangSwitchFunction(deviceId: uuid ?? '', deviceName: name ?? ''),
OneGangCountdownFunction(
deviceId: uuid ?? '', deviceName: name ?? ''),
OneGangCountdownFunction(deviceId: uuid ?? '', deviceName: name ?? ''),
];
case '2G':
return [
TwoGangSwitch1Function(deviceId: uuid ?? '', deviceName: name ?? ''),
TwoGangSwitch2Function(deviceId: uuid ?? '', deviceName: name ?? ''),
TwoGangCountdown1Function(
deviceId: uuid ?? '', deviceName: name ?? ''),
TwoGangCountdown2Function(
deviceId: uuid ?? '', deviceName: name ?? ''),
TwoGangCountdown1Function(deviceId: uuid ?? '', deviceName: name ?? ''),
TwoGangCountdown2Function(deviceId: uuid ?? '', deviceName: name ?? ''),
];
case '3G':
return [
ThreeGangSwitch1Function(
deviceId: uuid ?? '', deviceName: name ?? ''),
ThreeGangSwitch2Function(
deviceId: uuid ?? '', deviceName: name ?? ''),
ThreeGangSwitch3Function(
deviceId: uuid ?? '', deviceName: name ?? ''),
ThreeGangCountdown1Function(
deviceId: uuid ?? '', deviceName: name ?? ''),
ThreeGangCountdown2Function(
deviceId: uuid ?? '', deviceName: name ?? ''),
ThreeGangCountdown3Function(
deviceId: uuid ?? '', deviceName: name ?? ''),
ThreeGangSwitch1Function(deviceId: uuid ?? '', deviceName: name ?? ''),
ThreeGangSwitch2Function(deviceId: uuid ?? '', deviceName: name ?? ''),
ThreeGangSwitch3Function(deviceId: uuid ?? '', deviceName: name ?? ''),
ThreeGangCountdown1Function(deviceId: uuid ?? '', deviceName: name ?? ''),
ThreeGangCountdown2Function(deviceId: uuid ?? '', deviceName: name ?? ''),
ThreeGangCountdown3Function(deviceId: uuid ?? '', deviceName: name ?? ''),
];
default:
@ -288,6 +282,9 @@ SOS
if (room != null) {
data['room'] = room!.toJson();
}
if (subspace != null) {
data['subspace'] = subspace!.toJson();
}
if (unit != null) {
data['unit'] = unit!.toJson();
}
@ -330,6 +327,7 @@ SOS
return other is AllDevicesModel &&
other.room == room &&
other.subspace == subspace &&
other.unit == unit &&
other.productUuid == productUuid &&
other.productType == productType &&
@ -360,6 +358,7 @@ SOS
@override
int get hashCode {
return room.hashCode ^
subspace.hashCode ^
unit.hashCode ^
productUuid.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/widgets/device_managment_body.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/routiens/view/create_new_routine_view.dart';
import 'package:syncrow_web/pages/routiens/view/routines_view.dart';
import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routines/view/create_new_routine_view.dart';
import 'package:syncrow_web/pages/routines/view/routines_view.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.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(
providers: [
BlocProvider(
create: (context) => DeviceManagementBloc()..add(FetchDevices()),
create: (context) => DeviceManagementBloc()..add(FetchDevices(context)),
),
],
child: WebScaffold(
@ -80,7 +80,7 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout {
return BlocBuilder<DeviceManagementBloc, DeviceManagementState>(
builder: (context, deviceState) {
if (deviceState is DeviceManagementLoading) {
return const Center(child: CircularProgressIndicator());
return const DeviceManagementBody(devices: []);
} else if (deviceState is DeviceManagementLoaded) {
return DeviceManagementBody(devices: deviceState.devices);
} 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/shared/device_batch_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/helpers/responsice_layout_helper/responsive_layout_helper.dart';
import 'package:syncrow_web/utils/style.dart';
@ -59,10 +61,23 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
final buttonLabel = (selectedDevices.length > 1) ? 'Batch Control' : 'Control';
return Column(
return Row(
children: [
Expanded(child: SpaceTreeView(
onSelect: () {
context.read<DeviceManagementBloc>().add(FetchDevices(context));
},
)),
Expanded(
flex: 4,
child: state is DeviceManagementLoading
? const Center(child: CircularProgressIndicator())
: Column(
children: [
Container(
padding: isLargeScreenSize(context) ? const EdgeInsets.all(30) : const EdgeInsets.all(15),
padding: isLargeScreenSize(context)
? const EdgeInsets.all(30)
: const EdgeInsets.all(15),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@ -71,7 +86,9 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
tabs: tabs,
selectedIndex: selectedIndex,
onTabChanged: (index) {
context.read<DeviceManagementBloc>().add(SelectedFilterChanged(index));
context
.read<DeviceManagementBloc>()
.add(SelectedFilterChanged(index));
},
),
const SizedBox(height: 20),
@ -93,7 +110,9 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
),
);
} else if (selectedDevices.length > 1) {
final productTypes = selectedDevices.map((device) => device.productType).toSet();
final productTypes = selectedDevices
.map((device) => device.productType)
.toSet();
if (productTypes.length == 1) {
showDialog(
context: context,
@ -122,13 +141,17 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
),
Expanded(
child: Padding(
padding: isLargeScreenSize(context) ? const EdgeInsets.all(30) : const EdgeInsets.all(15),
padding: isLargeScreenSize(context)
? const EdgeInsets.all(30)
: const EdgeInsets.all(15),
child: DynamicTable(
withSelectAll: true,
cellDecoration: containerDecoration,
onRowSelected: (index, isSelected, row) {
final selectedDevice = devicesToShow[index];
context.read<DeviceManagementBloc>().add(SelectDevice(selectedDevice));
context
.read<DeviceManagementBloc>()
.add(SelectDevice(selectedDevice));
},
withCheckBox: true,
size: MediaQuery.of(context).size,
@ -147,31 +170,45 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
data: devicesToShow.map((device) {
final combinedSpaceNames = device.spaces != null
? device.spaces!.map((space) => space.spaceName).join(' > ') +
(device.community != null ? ' > ${device.community!.name}' : '')
(device.community != null
? ' > ${device.community!.name}'
: '')
: (device.community != null ? device.community!.name : '');
return [
device.name ?? '',
device.productName ?? '',
device.uuid ?? '',
(device.spaces != null && device.spaces!.isNotEmpty) ? device.spaces![0].spaceName : '',
(device.spaces != null && device.spaces!.isNotEmpty)
? device.spaces![0].spaceName
: '',
combinedSpaceNames,
device.batteryLevel != null ? '${device.batteryLevel}%' : '-',
formatDateTime(DateTime.fromMillisecondsSinceEpoch((device.createTime ?? 0) * 1000)),
formatDateTime(DateTime.fromMillisecondsSinceEpoch(
(device.createTime ?? 0) * 1000)),
device.online == true ? 'Online' : 'Offline',
formatDateTime(DateTime.fromMillisecondsSinceEpoch((device.updateTime ?? 0) * 1000)),
formatDateTime(DateTime.fromMillisecondsSinceEpoch(
(device.updateTime ?? 0) * 1000)),
];
}).toList(),
onSelectionChanged: (selectedRows) {
context.read<DeviceManagementBloc>().add(UpdateSelection(selectedRows));
context
.read<DeviceManagementBloc>()
.add(UpdateSelection(selectedRows));
},
initialSelectedIds:
context.read<DeviceManagementBloc>().selectedDevices.map((device) => device.uuid!).toList(),
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();
}
class _DeviceSearchFiltersState extends State<DeviceSearchFilters>
with HelperResponsiveLayout {
class _DeviceSearchFiltersState extends State<DeviceSearchFilters> with HelperResponsiveLayout {
final TextEditingController communityController = TextEditingController();
final TextEditingController unitNameController = TextEditingController();
final TextEditingController productNameController = TextEditingController();
@ -27,8 +26,7 @@ class _DeviceSearchFiltersState extends State<DeviceSearchFilters>
const SizedBox(width: 20),
_buildSearchField("Space Name", unitNameController, 200),
const SizedBox(width: 20),
_buildSearchField(
"Device Name / Product Name", productNameController, 300),
_buildSearchField("Device Name / Product Name", productNameController, 300),
const SizedBox(width: 20),
_buildSearchResetButtons(),
],
@ -53,8 +51,7 @@ class _DeviceSearchFiltersState extends State<DeviceSearchFilters>
);
}
Widget _buildSearchField(
String title, TextEditingController controller, double width) {
Widget _buildSearchField(String title, TextEditingController controller, double width) {
return Container(
child: StatefulTextField(
title: title,
@ -88,7 +85,7 @@ class _DeviceSearchFiltersState extends State<DeviceSearchFilters>
productNameController.clear();
context.read<DeviceManagementBloc>()
..add(ResetFilters())
..add(FetchDevices());
..add(FetchDevices(context));
},
);
}

View File

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

View File

@ -1,56 +1,94 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.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/home/bloc/home_event.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/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/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/constants/routes_const.dart';
class HomeBloc extends Bloc<HomeEvent, HomeState> {
final Graph graph = Graph()..isTree = true;
final BuchheimWalkerConfiguration builder = BuchheimWalkerConfiguration();
List<Node> sourcesList = [];
List<Node> destinationsList = [];
// final Graph graph = Graph()..isTree = true;
// final BuchheimWalkerConfiguration builder = BuchheimWalkerConfiguration();
// List<Node> sourcesList = [];
// List<Node> destinationsList = [];
UserModel? user;
String terms = '';
String policy = '';
HomeBloc() : super((HomeInitial())) {
on<CreateNewNode>(_createNode);
// on<CreateNewNode>(_createNode);
on<FetchUserInfo>(_fetchUserInfo);
on<FetchTermEvent>(_fetchTerms);
on<FetchPolicyEvent>(_fetchPolicy);
on<ConfirmUserAgreementEvent>(_confirmUserAgreement);
}
void _createNode(CreateNewNode event, Emitter<HomeState> emit) async {
emit(HomeInitial());
sourcesList.add(event.sourceNode);
destinationsList.add(event.destinationNode);
for (int i = 0; i < sourcesList.length; i++) {
graph.addEdge(sourcesList[i], destinationsList[i]);
}
// void _createNode(CreateNewNode event, Emitter<HomeState> emit) async {
// emit(HomeInitial());
// sourcesList.add(event.sourceNode);
// destinationsList.add(event.destinationNode);
// for (int i = 0; i < sourcesList.length; i++) {
// graph.addEdge(sourcesList[i], destinationsList[i]);
// }
builder
..siblingSeparation = (100)
..levelSeparation = (150)
..subtreeSeparation = (150)
..orientation = (BuchheimWalkerConfiguration.ORIENTATION_TOP_BOTTOM);
emit(HomeUpdateTree(graph: graph, builder: builder));
}
// builder
// ..siblingSeparation = (100)
// ..levelSeparation = (150)
// ..subtreeSeparation = (150)
// ..orientation = (BuchheimWalkerConfiguration.ORIENTATION_TOP_BOTTOM);
// emit(HomeUpdateTree(graph: graph, builder: builder));
// }
Future _fetchUserInfo(FetchUserInfo event, Emitter<HomeState> emit) async {
try {
var uuid =
await const FlutterSecureStorage().read(key: UserModel.userUuidKey);
user = await HomeApi().fetchUserInfo(uuid);
add(FetchTermEvent());
emit(HomeInitial());
} catch (e) {
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 {
// try {
// var uuid =

View File

@ -1,5 +1,5 @@
import 'package:equatable/equatable.dart';
import 'package:graphview/GraphView.dart';
// import 'package:graphview/GraphView.dart';
abstract class HomeEvent extends Equatable {
const HomeEvent();
@ -8,16 +8,22 @@ abstract class HomeEvent extends Equatable {
List<Object> get props => [];
}
class CreateNewNode extends HomeEvent {
final Node sourceNode;
final Node destinationNode;
const CreateNewNode(
{required this.sourceNode, required this.destinationNode});
// class CreateNewNode extends HomeEvent {
// final Node sourceNode;
// final Node destinationNode;
// const CreateNewNode(
// {required this.sourceNode, required this.destinationNode});
@override
List<Object> get props => [sourceNode, destinationNode];
}
// @override
// List<Object> get props => [sourceNode, destinationNode];
// }
class FetchUserInfo extends HomeEvent {
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:graphview/GraphView.dart';
// import 'package:graphview/GraphView.dart';
abstract class HomeState extends Equatable {
const HomeState();
@ -8,19 +8,25 @@ abstract class HomeState extends Equatable {
List<Object> get props => [];
}
class LoadingHome extends HomeState {}
class HomeInitial extends HomeState {}
class HomeCounterState extends HomeState {
final int counter;
const HomeCounterState(this.counter);
}
class TermsAgreement extends HomeState {}
class HomeUpdateTree extends HomeState {
final Graph graph;
final BuchheimWalkerConfiguration builder;
class PolicyAgreement extends HomeState {}
const HomeUpdateTree({required this.graph, required this.builder});
// class HomeCounterState extends HomeState {
// final int counter;
// const HomeCounterState(this.counter);
// }
@override
List<Object> get props => [graph, builder];
}
// class HomeUpdateTree extends HomeState {
// 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),
const Text(
'ACCESS YOUR APPS',
style:
TextStyle(fontSize: 20, fontWeight: FontWeight.w700),
style: TextStyle(fontSize: 20, fontWeight: FontWeight.w700),
),
const SizedBox(height: 30),
Expanded(
@ -51,9 +50,8 @@ class HomeMobilePage extends StatelessWidget {
height: size.height * 0.6,
width: size.width * 0.68,
child: GridView.builder(
itemCount: 8,
gridDelegate:
const SliverGridDelegateWithFixedCrossAxisCount(
itemCount: 3,
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: 20.0,
mainAxisSpacing: 20.0,
@ -65,8 +63,7 @@ class HomeMobilePage extends StatelessWidget {
active: homeItems[index]['active'],
name: homeItems[index]['title'],
img: homeItems[index]['icon'],
onTap: () =>
homeBloc.homeItems[index].onPress(context),
onTap: () => homeBloc.homeItems[index].onPress(context),
);
},
),
@ -97,33 +94,33 @@ class HomeMobilePage extends StatelessWidget {
'icon': Assets.devicesIcon,
'active': true,
},
{
'title': 'Move in',
'icon': Assets.moveinIcon,
'active': false,
},
{
'title': 'Construction',
'icon': Assets.constructionIcon,
'active': false,
},
{
'title': 'Energy',
'icon': Assets.energyIcon,
'color': ColorsManager.slidingBlueColor.withOpacity(0.2),
'active': false,
},
{
'title': 'Integrations',
'icon': Assets.integrationsIcon,
'color': ColorsManager.slidingBlueColor.withOpacity(0.2),
'active': false,
},
{
'title': 'Asset',
'icon': Assets.assetIcon,
'color': ColorsManager.slidingBlueColor.withOpacity(0.2),
'active': false,
},
// {
// 'title': 'Move in',
// 'icon': Assets.moveinIcon,
// 'active': false,
// },
// {
// 'title': 'Construction',
// 'icon': Assets.constructionIcon,
// 'active': false,
// },
// {
// 'title': 'Energy',
// 'icon': Assets.energyIcon,
// 'color': ColorsManager.slidingBlueColor.withOpacity(0.2),
// 'active': false,
// },
// {
// 'title': 'Integrations',
// 'icon': Assets.integrationsIcon,
// 'color': ColorsManager.slidingBlueColor.withOpacity(0.2),
// 'active': false,
// },
// {
// 'title': 'Asset',
// 'icon': Assets.assetIcon,
// 'color': ColorsManager.slidingBlueColor.withOpacity(0.2),
// 'active': false,
// },
];
}

View File

@ -1,6 +1,8 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.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_state.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 {
const HomeWebPage({super.key});
@override
Widget build(BuildContext context) {
Size size = MediaQuery.of(context).size;
final homeBloc = BlocProvider.of<HomeBloc>(context);
return PopScope(
canPop: false,
onPopInvoked: (didPop) => false,
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) {
final homeBloc = BlocProvider.of<HomeBloc>(context);
return WebScaffold(
enableMenuSidebar: false,
appBarTitle: Row(
@ -32,7 +58,10 @@ class HomeWebPage extends StatelessWidget {
scaffoldBody: SizedBox(
height: size.height,
width: size.width,
child: Column(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
@ -53,7 +82,7 @@ class HomeWebPage extends StatelessWidget {
child: GridView.builder(
itemCount: 3, //8
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 4,
crossAxisCount: 3, //4
crossAxisSpacing: 20.0,
mainAxisSpacing: 20.0,
childAspectRatio: 1.5,
@ -72,6 +101,8 @@ class HomeWebPage extends StatelessWidget {
),
],
),
],
),
),
);
},

View File

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

View File

@ -1,5 +1,5 @@
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';
abstract class EffectPeriodEvent extends Equatable {

View File

@ -2,7 +2,7 @@ import 'dart:async';
import 'package:bloc/bloc.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_state.dart';
@ -26,8 +26,7 @@ class FunctionBloc extends Bloc<FunctionBlocEvent, FunctionBlocState> {
functionCode: event.functionData.functionCode,
operationName: event.functionData.operationName,
value: event.functionData.value ?? existingData.value,
valueDescription: event.functionData.valueDescription ??
existingData.valueDescription,
valueDescription: event.functionData.valueDescription ?? existingData.valueDescription,
condition: event.functionData.condition ?? existingData.condition,
);
} else {
@ -59,10 +58,8 @@ class FunctionBloc extends Bloc<FunctionBlocEvent, FunctionBlocState> {
);
}
FutureOr<void> _onSelectFunction(
SelectFunction event, Emitter<FunctionBlocState> emit) {
FutureOr<void> _onSelectFunction(SelectFunction event, Emitter<FunctionBlocState> emit) {
emit(state.copyWith(
selectedFunction: event.functionCode,
selectedOperationName: event.operationName));
selectedFunction: event.functionCode, selectedOperationName: event.operationName));
}
}

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
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 {
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_bloc/flutter_bloc.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/routiens/widgets/dialog_header.dart';
import 'package:syncrow_web/pages/routiens/widgets/dialog_footer.dart';
import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart';
import 'package:syncrow_web/pages/routines/widgets/dialog_footer.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';

View File

@ -1,6 +1,6 @@
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/routiens/models/device_functions.dart';
import 'package:syncrow_web/pages/routines/models/ac/ac_operational_value.dart';
import 'package:syncrow_web/pages/routines/models/device_functions.dart';
import 'package:syncrow_web/utils/constants/app_enum.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/routiens/models/gang_switches/switch_operational_value.dart';
import 'package:syncrow_web/pages/routines/models/gang_switches/base_switch_function.dart';
import 'package:syncrow_web/pages/routines/models/gang_switches/switch_operational_value.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
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/routiens/models/gang_switches/switch_operational_value.dart';
import 'package:syncrow_web/pages/routines/models/device_functions.dart';
import 'package:syncrow_web/pages/routines/models/gang_switches/switch_operational_value.dart';
abstract class BaseSwitchFunction extends DeviceFunction<bool> {
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/routiens/models/gang_switches/switch_operational_value.dart';
import 'package:syncrow_web/pages/routines/models/gang_switches/base_switch_function.dart';
import 'package:syncrow_web/pages/routines/models/gang_switches/switch_operational_value.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
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/routiens/models/gang_switches/switch_operational_value.dart';
import 'package:syncrow_web/pages/routines/models/gang_switches/base_switch_function.dart';
import 'package:syncrow_web/pages/routines/models/gang_switches/switch_operational_value.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
class ThreeGangSwitch1Function extends BaseSwitchFunction {
@ -26,8 +26,7 @@ class ThreeGangSwitch1Function extends BaseSwitchFunction {
}
class ThreeGangCountdown1Function extends BaseSwitchFunction {
ThreeGangCountdown1Function(
{required super.deviceId, required super.deviceName})
ThreeGangCountdown1Function({required super.deviceId, required super.deviceName})
: super(
code: 'countdown_1',
operationName: 'Light 1 Countdown',
@ -71,8 +70,7 @@ class ThreeGangSwitch2Function extends BaseSwitchFunction {
}
class ThreeGangCountdown2Function extends BaseSwitchFunction {
ThreeGangCountdown2Function(
{required super.deviceId, required super.deviceName})
ThreeGangCountdown2Function({required super.deviceId, required super.deviceName})
: super(
code: 'countdown_2',
operationName: 'Light 2 Countdown',
@ -116,8 +114,7 @@ class ThreeGangSwitch3Function extends BaseSwitchFunction {
}
class ThreeGangCountdown3Function extends BaseSwitchFunction {
ThreeGangCountdown3Function(
{required super.deviceId, required super.deviceName})
ThreeGangCountdown3Function({required super.deviceId, required super.deviceName})
: super(
code: 'countdown_3',
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/routiens/models/gang_switches/switch_operational_value.dart';
import 'package:syncrow_web/pages/routines/models/gang_switches/base_switch_function.dart';
import 'package:syncrow_web/pages/routines/models/gang_switches/switch_operational_value.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
class TwoGangSwitch1Function extends BaseSwitchFunction {
@ -49,8 +49,7 @@ class TwoGangSwitch2Function extends BaseSwitchFunction {
}
class TwoGangCountdown1Function extends BaseSwitchFunction {
TwoGangCountdown1Function(
{required super.deviceId, required super.deviceName})
TwoGangCountdown1Function({required super.deviceId, required super.deviceName})
: super(
code: 'countdown_1',
operationName: 'Light 1 Countdown',
@ -71,8 +70,7 @@ class TwoGangCountdown1Function extends BaseSwitchFunction {
}
class TwoGangCountdown2Function extends BaseSwitchFunction {
TwoGangCountdown2Function(
{required super.deviceId, required super.deviceName})
TwoGangCountdown2Function({required super.deviceId, required super.deviceName})
: super(
code: 'countdown_2',
operationName: 'Light 2 Countdown',

View File

@ -1,7 +1,7 @@
import 'dart:convert';
import 'package:syncrow_web/pages/routiens/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_automation_model.dart';
import 'package:syncrow_web/pages/routines/models/create_scene_and_autoamtion/create_scene_model.dart';
class RoutineDetailsModel {
final String spaceUuid;

View File

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

View File

@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/routiens/widgets/routine_dialogs/effictive_period_dialog.dart';
import 'package:syncrow_web/pages/routiens/widgets/period_option.dart';
import 'package:syncrow_web/pages/routiens/widgets/repeat_days.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/effictive_period_dialog.dart';
import 'package:syncrow_web/pages/routines/widgets/period_option.dart';
import 'package:syncrow_web/pages/routines/widgets/repeat_days.dart';
import 'package:syncrow_web/utils/color_manager.dart';
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_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routiens/widgets/dragable_card.dart';
import 'package:syncrow_web/pages/routiens/widgets/routine_devices.dart';
import 'package:syncrow_web/pages/routiens/widgets/routines_title_widget.dart';
import 'package:syncrow_web/pages/routiens/widgets/scenes_and_automations.dart';
import 'package:syncrow_web/pages/routiens/widgets/search_bar_condition_title.dart';
import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routines/widgets/dragable_card.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_devices.dart';
import 'package:syncrow_web/pages/routines/widgets/routines_title_widget.dart';
import 'package:syncrow_web/pages/routines/widgets/scenes_and_automations.dart';
import 'package:syncrow_web/pages/routines/widgets/search_bar_condition_title.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
class ConditionsRoutinesDevicesView extends StatelessWidget {

View File

@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.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';
class DeleteSceneWidget extends StatelessWidget {

View File

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

View File

@ -1,9 +1,9 @@
import 'package:flutter/material.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/routiens/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/routiens/widgets/routine_dialogs/effictive_period_dialog.dart';
import 'package:syncrow_web/pages/routines/bloc/effective_period/effect_period_bloc.dart';
import 'package:syncrow_web/pages/routines/bloc/effective_period/effect_period_event.dart';
import 'package:syncrow_web/pages/routines/bloc/effective_period/effect_period_state.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/constants/app_enum.dart';

View File

@ -1,8 +1,8 @@
import 'package:flutter/material.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/routiens/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_bloc.dart';
import 'package:syncrow_web/pages/routines/bloc/effective_period/effect_period_event.dart';
import 'package:syncrow_web/pages/routines/bloc/effective_period/effect_period_state.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class RepeatDays extends StatelessWidget {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,8 +1,8 @@
import 'package:flutter/material.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/routiens/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routiens/widgets/routines_title_widget.dart';
import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.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/helpers/responsice_layout_helper/responsive_layout_helper.dart';

View File

@ -2,11 +2,11 @@
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/automation_dialog.dart';
import 'package:syncrow_web/pages/routiens/widgets/routine_dialogs/delay_dialog.dart';
import 'package:syncrow_web/pages/routiens/helper/dialog_helper/device_dialog_helper.dart';
import 'package:syncrow_web/pages/routiens/widgets/dragable_card.dart';
import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/automation_dialog.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/delay_dialog.dart';
import 'package:syncrow_web/pages/routines/helper/dialog_helper/device_dialog_helper.dart';
import 'package:syncrow_web/pages/routines/widgets/dragable_card.dart';
import 'package:syncrow_web/utils/constants/assets.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/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/tag_model/widgets/action_button_widget.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class AddDeviceTypeWidget extends StatelessWidget {
@ -23,10 +22,12 @@ class AddDeviceTypeWidget extends StatelessWidget {
final List<Tag>? spaceTags;
final List<String>? allTags;
final String spaceName;
final bool isCreate;
final Function(List<Tag>, List<SubspaceModel>?)? onSave;
const AddDeviceTypeWidget(
{super.key,
required this.isCreate,
this.products,
this.initialSelectedProducts,
this.onProductsSelected,
@ -74,7 +75,9 @@ class AddDeviceTypeWidget extends StatelessWidget {
padding:
const EdgeInsets.symmetric(horizontal: 20.0),
child: ScrollableGridViewWidget(
initialProductCounts: state.selectedProducts,
products: products,
isCreate: isCreate,
crossAxisCount: crossAxisCount),
),
),
@ -131,10 +134,7 @@ class AddDeviceTypeWidget extends StatelessWidget {
spaceName: spaceName,
initialTags: initialTags,
title: dialogTitle,
onSave: (tags, subspaces) {
onSave!(tags, subspaces);
},
),
onSave: onSave),
);
}
},
@ -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 {
final ProductModel product;
final List<SelectedProduct> productCounts;
final bool isCreate;
const DeviceTypeTileWidget({
super.key,
const DeviceTypeTileWidget(
{super.key,
required this.product,
required this.productCounts,
});
required this.isCreate});
@override
Widget build(BuildContext context) {
@ -48,7 +49,7 @@ class DeviceTypeTileWidget extends StatelessWidget {
DeviceNameWidget(name: product.name),
const SizedBox(height: 4),
CounterWidget(
isCreate: false,
isCreate: isCreate,
initialCount: selectedProduct.count,
onCountChanged: (newCount) {
context.read<AddDeviceTypeBloc>().add(

View File

@ -10,13 +10,14 @@ class ScrollableGridViewWidget extends StatelessWidget {
final List<ProductModel>? products;
final int crossAxisCount;
final List<SelectedProduct>? initialProductCounts;
final bool isCreate;
const ScrollableGridViewWidget({
super.key,
const ScrollableGridViewWidget(
{super.key,
required this.products,
required this.crossAxisCount,
this.initialProductCounts,
});
required this.isCreate});
@override
Widget build(BuildContext context) {
@ -46,6 +47,7 @@ class ScrollableGridViewWidget extends StatelessWidget {
return DeviceTypeTileWidget(
product: product,
isCreate: isCreate,
productCounts: initialProductCount != null
? [...productCounts, initialProductCount]
: 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/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/model/subspace_model.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/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/space_mana_api.dart';
import 'package:syncrow_web/services/space_model_mang_api.dart';
import 'package:syncrow_web/utils/constants/action_enum.dart';
class SpaceManagementBloc
extends Bloc<SpaceManagementEvent, SpaceManagementState> {
@ -341,7 +344,7 @@ class SpaceManagementBloc
products: _cachedProducts ?? [],
selectedCommunity: selectedCommunity,
selectedSpace: selectedSpace,
spaceModels: spaceModels ?? []));
spaceModels: spaceModels));
}
} catch (e) {
emit(SpaceManagementError('Error updating state: $e'));
@ -428,6 +431,76 @@ class SpaceManagementBloc
for (var space in orderedSpaces) {
try {
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(
communityId: communityUuid,
spaceId: space.uuid!,
@ -436,6 +509,8 @@ class SpaceManagementBloc
isPrivate: space.isPrivate,
position: space.position,
icon: space.icon,
subspaces: subspaceUpdates,
tags: tagUpdates,
direction: space.incomingConnection?.direction,
);
} else {
@ -535,4 +610,79 @@ class SpaceManagementBloc
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(
internalId: internalId,
uuid: json['uuid'] ?? '',
spaceTuyaUuid: json['spaceTuyaUuid'],
name: json['spaceName'],
isPrivate: json['isPrivate'] ?? false,
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/utils/constants/action_enum.dart';
import 'package:uuid/uuid.dart';
import 'tag.dart';
@ -8,19 +9,24 @@ class SubspaceModel {
String subspaceName;
final bool disabled;
List<Tag>? tags;
String internalId;
SubspaceModel({
this.uuid,
required this.subspaceName,
required this.disabled,
this.tags,
});
String? internalId,
}) : internalId = internalId ?? const Uuid().v4();
factory SubspaceModel.fromJson(Map<String, dynamic> json) {
final String internalId = json['internalId'] ?? const Uuid().v4();
return SubspaceModel(
uuid: json['uuid'] ?? '',
subspaceName: json['subspaceName'] ?? '',
disabled: json['disabled'] ?? false,
internalId: internalId,
tags: (json['tags'] as List<dynamic>?)
?.map((item) => Tag.fromJson(item))
.toList() ??

View File

@ -195,6 +195,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
screenSize,
position:
spaces[index].position + newPosition,
parentIndex: index,
direction: direction,
);
@ -294,6 +295,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
return CreateSpaceDialog(
products: widget.products,
spaceModels: widget.spaceModels,
allTags: _getAllTagValues(spaces),
parentSpace: parentIndex != null ? spaces[parentIndex] : null,
onCreateSpace: (String name,
String icon,
@ -350,7 +352,10 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
name: widget.selectedSpace!.name,
icon: widget.selectedSpace!.icon,
editSpace: widget.selectedSpace,
tags: widget.selectedSpace?.tags,
subspaces: widget.selectedSpace?.subspaces,
isEdit: true,
allTags: _getAllTagValues(spaces),
onCreateSpace: (String name,
String icon,
List<SelectedProduct> selectedProducts,
@ -742,4 +747,14 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
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:syncrow_web/common/custom_expansion_tile.dart';
import 'package:syncrow_web/common/widgets/custom_expansion_tile.dart';
class CommunityTile extends StatelessWidget {
final String title;

View File

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

View File

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

View File

@ -1,5 +1,5 @@
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 {
final String title;

View File

@ -20,8 +20,7 @@ class SpaceWidget extends StatelessWidget {
top: position.dy,
child: GestureDetector(
onTap: onTap,
child:
Container(
child: Container(
padding: const EdgeInsets.all(8.0),
decoration: BoxDecoration(
color: ColorsManager.whiteColors,
@ -39,11 +38,10 @@ class SpaceWidget extends StatelessWidget {
children: [
const Icon(Icons.location_on, color: ColorsManager.spaceColor),
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_event.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';
class AssignTagDialog extends StatelessWidget {
@ -40,8 +41,11 @@ class AssignTagDialog extends StatelessWidget {
@override
Widget build(BuildContext context) {
final List<String> locations =
(subspaces ?? []).map((subspace) => subspace.subspaceName).toList();
final List<String> locations = (subspaces ?? [])
.map((subspace) => subspace.subspaceName)
.toList()
..add('Main Space');
return BlocProvider(
create: (_) => AssignTagBloc()
..add(InitializeTags(
@ -93,21 +97,22 @@ class AssignTagDialog extends StatelessWidget {
],
rows: state.tags.isEmpty
? [
const DataRow(cells: [
DataRow(cells: [
DataCell(
Center(
child: Text(
'No Data Available',
style: TextStyle(
fontSize: 14,
color: ColorsManager.lightGrayColor,
child: Text('No Data Available',
style: Theme.of(context)
.textTheme
.bodyMedium
?.copyWith(
color: ColorsManager
.lightGrayColor,
)),
),
),
),
),
DataCell(SizedBox()),
DataCell(SizedBox()),
DataCell(SizedBox()),
const DataCell(SizedBox()),
const DataCell(SizedBox()),
const DataCell(SizedBox()),
])
]
: List.generate(state.tags.length, (index) {
@ -209,10 +214,11 @@ class AssignTagDialog extends StatelessWidget {
),
),
if (state.errorMessage != null)
Text(
state.errorMessage!,
style: const TextStyle(color: ColorsManager.warningRed),
),
Text(state.errorMessage!,
style: Theme.of(context)
.textTheme
.bodySmall
?.copyWith(color: ColorsManager.warningRed)),
],
),
),
@ -235,16 +241,18 @@ class AssignTagDialog extends StatelessWidget {
Navigator.of(context).pop();
await showDialog<bool>(
barrierDismissible: false,
await showDialog(
context: context,
builder: (dialogContext) => AddDeviceTypeWidget(
builder: (context) => AddDeviceTypeWidget(
products: products,
subspaces: processedSubspaces,
initialSelectedProducts: addedProducts,
allTags: allTags,
initialSelectedProducts: TagHelper
.createInitialSelectedProductsForTags(
processedTags, processedSubspaces),
spaceName: spaceName,
spaceTags: processedTags,
isCreate: false,
onSave: onSave,
),
);
},
@ -261,7 +269,6 @@ class AssignTagDialog extends StatelessWidget {
foregroundColor: ColorsManager.whiteColors,
onPressed: state.isSaveEnabled
? () async {
Navigator.of(context).pop();
final updatedTags = List<Tag>.from(state.tags);
final result =
processTags(updatedTags, subspaces);
@ -270,8 +277,8 @@ class AssignTagDialog extends StatelessWidget {
result['updatedTags'] as List<Tag>;
final processedSubspaces =
result['subspaces'] as List<SubspaceModel>;
onSave!(processedTags, processedSubspaces);
onSave?.call(processedTags, processedSubspaces);
Navigator.of(context).pop();
}
: null,
child: const Text('Save'),
@ -307,6 +314,14 @@ class AssignTagDialog extends StatelessWidget {
final modifiedTags = List<Tag>.from(updatedTags);
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()) {
if (modifiedSubspaces.isEmpty) continue;

View File

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

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