Compare commits

..

7 Commits

28 changed files with 1702 additions and 389 deletions

View File

@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 24C8.79469 24 5.78123 22.7518 3.51469 20.4853C1.24823 18.2188 0 15.2053 0 12C0 8.79469 1.24823 5.78123 3.51469 3.51469C5.78123 1.24823 8.79469 0 12 0C15.2053 0 18.2188 1.24823 20.4853 3.51469C22.7518 5.78123 24 8.79469 24 12C24 15.2053 22.7518 18.2188 20.4853 20.4853C18.2188 22.7518 15.2053 24 12 24ZM12 1.875C9.2955 1.875 6.75291 2.92819 4.84055 4.84055C2.92819 6.75291 1.875 9.2955 1.875 12C1.875 14.7045 2.92819 17.2471 4.84055 19.1595C6.75291 21.0718 9.2955 22.125 12 22.125C14.7045 22.125 17.2471 21.0718 19.1595 19.1595C21.0718 17.2471 22.125 14.7045 22.125 12C22.125 9.2955 21.0718 6.75291 19.1595 4.84055C17.2471 2.92819 14.7045 1.875 12 1.875ZM15.9775 17.3033L12 13.3258L8.02252 17.3033L6.6967 15.9775L10.6742 12L6.6967 8.02252L8.02252 6.6967L12 10.6742L15.9775 6.6967L17.3033 8.02252L13.3258 12L17.3033 15.9775L15.9775 17.3033Z" fill="#999999" fill-opacity="0.7"/>
</svg>

After

Width:  |  Height:  |  Size: 992 B

View File

@ -0,0 +1,3 @@
<svg width="13" height="13" viewBox="0 0 13 13" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12.4854 3.1332L9.8668 0.515228C9.64704 0.295536 9.34903 0.172119 9.03829 0.172119C8.72755 0.172119 8.42953 0.295536 8.20977 0.515228L0.983989 7.74042C0.874797 7.84895 0.788225 7.97806 0.729285 8.12027C0.670346 8.26249 0.640212 8.41499 0.640629 8.56894V11.1875C0.640629 11.4983 0.764094 11.7964 0.983864 12.0161C1.20363 12.2359 1.5017 12.3594 1.8125 12.3594H11.6563C11.8427 12.3594 12.0216 12.2853 12.1534 12.1534C12.2853 12.0216 12.3594 11.8427 12.3594 11.6562C12.3594 11.4698 12.2853 11.2909 12.1534 11.1591C12.0216 11.0272 11.8427 10.9531 11.6563 10.9531H6.32422L12.4854 4.79081C12.5942 4.68199 12.6806 4.55278 12.7395 4.41057C12.7984 4.26836 12.8288 4.11594 12.8288 3.96201C12.8288 3.80807 12.7984 3.65565 12.7395 3.51344C12.6806 3.37123 12.5942 3.24202 12.4854 3.1332ZM4.33204 10.9531H2.04688V8.66796L6.96875 3.74609L9.25391 6.03124L4.33204 10.9531ZM10.25 5.03515L7.96485 2.74999L9.03946 1.67538L11.3246 3.96054L10.25 5.03515Z" fill="#D5D5D5"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -13,7 +13,6 @@ import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
import 'package:syncrow_web/pages/home/bloc/home_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/services/api/api_exception.dart';
import 'package:syncrow_web/services/auth_api.dart';
import 'package:syncrow_web/utils/constants/strings_manager.dart';
import 'package:syncrow_web/utils/helpers/shared_preferences_helper.dart';
@ -100,8 +99,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
emit(const TimerState(isButtonEnabled: true, remainingTime: 0));
}
Future<void> changePassword(
ChangePasswordEvent event, Emitter<AuthState> emit) async {
Future<void> changePassword(ChangePasswordEvent event, Emitter<AuthState> emit) async {
emit(LoadingForgetState());
try {
var response = await AuthenticationAPI.verifyOtp(
@ -115,14 +113,14 @@ Future<void> changePassword(
emit(const TimerState(isButtonEnabled: true, remainingTime: 0));
emit(SuccessForgetState());
}
} on APIException catch (e) {
final errorMessage = e.message;
} on DioException catch (e) {
final errorData = e.response!.data;
String errorMessage = errorData['error']['message'] ?? 'something went wrong';
validate = errorMessage;
emit(AuthInitialState());
}
}
String? validateCode(String? value) {
if (value == null || value.isEmpty) {
return 'Code is required';
@ -151,7 +149,6 @@ Future<void> changePassword(
static UserModel? user;
bool showValidationMessage = false;
void _login(LoginButtonPressed event, Emitter<AuthState> emit) async {
emit(AuthLoading());
if (isChecked) {
@ -168,20 +165,21 @@ Future<void> changePassword(
password: event.password,
),
);
} on APIException catch (e) {
validate = e.message;
emit(LoginInitial());
return;
} catch (e) {
validate = 'Something went wrong';
} on DioException catch (e) {
final errorData = e.response!.data;
String errorMessage = errorData['error']['message'];
if (errorMessage == "Access denied for web platform") {
validate = errorMessage;
} else {
validate = 'Invalid Credentials!';
}
emit(LoginInitial());
return;
}
if (token.accessTokenIsNotEmpty) {
FlutterSecureStorage storage = const FlutterSecureStorage();
await storage.write(
key: Token.loginAccessTokenKey, value: token.accessToken);
await storage.write(key: Token.loginAccessTokenKey, value: token.accessToken);
const FlutterSecureStorage().write(
key: UserModel.userUuidKey,
value: Token.decodeToken(token.accessToken)['uuid'].toString());
@ -197,7 +195,6 @@ Future<void> changePassword(
}
}
checkBoxToggle(
CheckBoxEvent event,
Emitter<AuthState> emit,

View File

@ -211,6 +211,7 @@ class _DynamicTableState extends State<DynamicTable> {
onChanged: widget.withSelectAll && widget.data.isNotEmpty
? _toggleSelectAll
: null,
),
);
}
@ -281,6 +282,7 @@ class _DynamicTableState extends State<DynamicTable> {
padding: EdgeInsets.symmetric(
horizontal: index == widget.headers.length - 1 ? 12 : 8.0,
vertical: 4),
child: Text(
title,
style: context.textTheme.titleSmall!.copyWith(
@ -301,6 +303,7 @@ class _DynamicTableState extends State<DynamicTable> {
required int rowIndex,
required int columnIndex,
}) {
bool isBatteryLevel = content.endsWith('%');
double? batteryLevel;
@ -312,6 +315,7 @@ class _DynamicTableState extends State<DynamicTable> {
if (isSettingsColumn) {
return _buildSettingsIcon(rowIndex, size);
}
Color? statusColor;
switch (content) {

View File

@ -95,7 +95,7 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout {
return const RoutinesView();
}
if (state.createRoutineView) {
return CreateNewRoutineView();
return const CreateNewRoutineView();
}
return BlocBuilder<DeviceManagementBloc, DeviceManagementState>(

View File

@ -6,9 +6,11 @@ import 'package:syncrow_web/pages/common/filter/filter_widget.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/models/devices_model.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/widgets/device_search_filters.dart';
import 'package:syncrow_web/pages/device_managment/device_setting/device_settings_panel.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/view/space_tree_view.dart';
import 'package:syncrow_web/utils/color_manager.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';
@ -58,7 +60,8 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
'Low Battery ($lowBatteryCount)',
];
final buttonLabel = (selectedDevices.length > 1) ? 'Batch Control' : 'Control';
final buttonLabel =
(selectedDevices.length > 1) ? 'Batch Control' : 'Control';
return Row(
children: [
@ -105,18 +108,23 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
if (selectedDevices.length == 1) {
showDialog(
context: context,
builder: (context) => DeviceControlDialog(
builder: (context) =>
DeviceControlDialog(
device: selectedDevices.first,
),
);
} else if (selectedDevices.length > 1) {
final productTypes = selectedDevices
.map((device) => device.productType)
.toSet();
} else if (selectedDevices.length >
1) {
final productTypes =
selectedDevices
.map((device) =>
device.productType)
.toSet();
if (productTypes.length == 1) {
showDialog(
context: context,
builder: (context) => DeviceBatchControlDialog(
builder: (context) =>
DeviceBatchControlDialog(
devices: selectedDevices,
),
);
@ -130,7 +138,9 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 12,
color: isControlButtonEnabled ? Colors.white : Colors.grey,
color: isControlButtonEnabled
? Colors.white
: Colors.grey,
),
),
),
@ -166,29 +176,40 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
'Installation Date and Time',
'Status',
'Last Offline Date and Time',
'Settings'
],
data: devicesToShow.map((device) {
final combinedSpaceNames = device.spaces != null
? device.spaces!.map((space) => space.spaceName).join(' > ') +
? 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 != null &&
device.spaces!.isNotEmpty)
? device.spaces![0].spaceName
: '',
combinedSpaceNames,
device.batteryLevel != null ? '${device.batteryLevel}%' : '-',
formatDateTime(DateTime.fromMillisecondsSinceEpoch(
(device.createTime ?? 0) * 1000)),
device.batteryLevel != null
? '${device.batteryLevel}%'
: '-',
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)),
'Settings',
];
}).toList(),
onSelectionChanged: (selectedRows) {
@ -202,6 +223,10 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
.map((device) => device.uuid!)
.toList(),
isEmpty: devicesToShow.isEmpty,
onSettingsPressed: (rowIndex) {
final device = devicesToShow[rowIndex];
showDeviceSettingsSidebar(context, device);
},
),
),
)
@ -213,4 +238,37 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
},
);
}
void showDeviceSettingsSidebar(BuildContext context, AllDevicesModel device) {
showGeneralDialog(
context: context,
barrierDismissible: true,
barrierLabel: "Device Settings",
transitionDuration: const Duration(milliseconds: 300),
pageBuilder: (context, anim1, anim2) {
return Align(
alignment: Alignment.centerRight,
child: Material(
child: Container(
width: MediaQuery.of(context).size.width * 0.3,
color: ColorsManager.whiteColors,
child: DeviceSettingsPanel(
device: device,
onClose: () => Navigator.of(context).pop(),
),
),
),
);
},
transitionBuilder: (context, anim1, anim2, child) {
return SlideTransition(
position: Tween<Offset>(
begin: const Offset(1, 0),
end: Offset.zero,
).animate(anim1),
child: child,
);
},
);
}
}

View File

@ -0,0 +1,165 @@
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
import 'package:syncrow_web/pages/device_managment/device_setting/settings_model/device_info_model.dart';
import 'package:syncrow_web/pages/device_managment/device_setting/bloc/setting_bloc_state.dart';
import 'package:syncrow_web/pages/device_managment/device_setting/settings_model/sub_space_model.dart';
import 'package:syncrow_web/services/devices_mang_api.dart';
import 'package:syncrow_web/services/space_mana_api.dart';
import 'package:syncrow_web/utils/snack_bar.dart';
part 'setting_bloc_event.dart';
class SettingDeviceBloc extends Bloc<DeviceSettingEvent, DeviceSettingsState> {
final String deviceId;
SettingDeviceBloc({
required this.deviceId,
}) : super(const DeviceSettingsInitial()) {
on<DeviceSettingInitialInfo>(_fetchDeviceInfo);
on<SettingBlocSaveName>(_saveName);
on<ChangeNameEvent>(_changeName);
on<SettingBlocDeleteDevice>(_deleteDevice);
on<SettingBlocFetchRooms>(_fetchRooms);
on<SettingBlocAssignRoom>(_onAssignDevice);
}
final TextEditingController nameController = TextEditingController();
List<SubSpaceModel> roomsList = [];
bool isEditingName = false;
bool _validateInputs() {
final nameError = _fullNameValidator(nameController.text);
if (nameError != null) {
CustomSnackBar.displaySnackBar(nameError);
return true;
}
return false;
}
String? _fullNameValidator(String? value) {
if (value == null) return 'name is required';
final withoutExtraSpaces = value.replaceAll(RegExp(r"\s+"), ' ').trim();
if (withoutExtraSpaces.length < 2 || withoutExtraSpaces.length > 30) {
return 'name must be between 2 and 30 characters long';
}
if (RegExp(r"/[^ a-zA-Z0-9-\']/").hasMatch(withoutExtraSpaces)) {
return 'Only alphanumeric characters, space, dash and single quote are allowed';
}
return null;
}
Future<void> _saveName(
SettingBlocSaveName event, Emitter<DeviceSettingsState> emit) async {
if (_validateInputs()) return;
try {
emit(DeviceSettingsLoading());
await DevicesManagementApi.putDeviceName(
deviceId: deviceId, deviceName: nameController.text);
add(DeviceSettingInitialInfo());
CustomSnackBar.displaySnackBar('Save Successfully');
emit(DeviceSettingsUpdate(deviceName: nameController.text));
} catch (e) {
emit(DeviceSettingsError(message: e.toString()));
}
}
Future<void> _fetchDeviceInfo(
DeviceSettingInitialInfo event, Emitter<DeviceSettingsState> emit) async {
try {
emit(DeviceSettingsLoading());
var response = await DevicesManagementApi.getDeviceInfo(deviceId);
DeviceInfoModel deviceInfo = DeviceInfoModel.fromJson(response);
nameController.text = deviceInfo.name;
emit(DeviceSettingsUpdate(
deviceName: nameController.text,
deviceInfo: deviceInfo,
roomsList: roomsList,
));
} catch (e) {
emit(DeviceSettingsError(message: e.toString()));
}
}
bool editName = false;
final FocusNode focusNode = FocusNode();
void _changeName(ChangeNameEvent event, Emitter<DeviceSettingsState> emit) {
emit(DeviceSettingsInitial(
deviceName: nameController.text,
deviceId: deviceId,
isEditingName: event.value ?? false,
editingNameValue: event.value?.toString() ?? '',
deviceInfo: state.deviceInfo,
));
editName = event.value!;
if (editName) {
Future.delayed(const Duration(milliseconds: 500), () {
focusNode.requestFocus();
});
} else {
add(const SettingBlocSaveName());
focusNode.unfocus();
}
emit(DeviceSettingsUpdate(
deviceName: nameController.text,
deviceInfo: state.deviceInfo,
roomsList: roomsList,
));
}
void _deleteDevice(
SettingBlocDeleteDevice event, Emitter<DeviceSettingsState> emit) async {
try {
emit(DeviceSettingsLoading());
await DevicesManagementApi.resetDevice(devicesUuid: deviceId);
CustomSnackBar.displaySnackBar('Reset Successfully');
emit(DeviceSettingsUpdate(
deviceName: nameController.text,
deviceInfo: state.deviceInfo,
roomsList: roomsList,
));
} catch (e) {
emit(DeviceSettingsError(message: e.toString()));
return;
}
}
void _onAssignDevice(
SettingBlocAssignRoom event, Emitter<DeviceSettingsState> emit) async {
try {
emit(DeviceSettingsLoading());
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
await CommunitySpaceManagementApi.assignDeviceToRoom(
communityId: event.communityUuid,
spaceId: event.spaceUuid,
subSpaceId: event.subSpaceUuid,
deviceId: deviceId,
projectId: projectUuid);
add(DeviceSettingInitialInfo());
CustomSnackBar.displaySnackBar('Save Successfully');
emit(DeviceSettingsSaveSelectionSuccess());
} catch (e) {
emit(DeviceSettingsError(message: e.toString()));
return;
}
}
void _fetchRooms(
SettingBlocFetchRooms event, Emitter<DeviceSettingsState> emit) async {
try {
emit(DeviceSettingsLoading());
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
roomsList = await CommunitySpaceManagementApi.getSubSpaceBySpaceId(
communityId: event.communityUuid,
spaceId: event.spaceUuid,
projectId: projectUuid);
emit(DeviceSettingsUpdate(
deviceName: nameController.text,
deviceInfo: state.deviceInfo,
roomsList: roomsList,
));
} catch (e) {
emit(DeviceSettingsError(message: e.toString()));
return;
}
}
}

View File

@ -0,0 +1,69 @@
part of 'setting_bloc_bloc.dart';
abstract class DeviceSettingEvent extends Equatable {
const DeviceSettingEvent();
@override
List<Object?> get props => [];
}
class SettingBlocSaveDeviceName extends DeviceSettingEvent {
final String deviceName;
final String deviceId;
const SettingBlocSaveDeviceName(
{required this.deviceName, required this.deviceId});
@override
List<Object?> get props => [deviceName, deviceId];
}
class SettingBlocStartEditingName extends DeviceSettingEvent {}
class SettingBlocCancelEditingName extends DeviceSettingEvent {}
class SettingBlocChangeEditingNameValue extends DeviceSettingEvent {
final String value;
const SettingBlocChangeEditingNameValue(this.value);
@override
List<Object?> get props => [value];
}
class SettingBlocFetchRooms extends DeviceSettingEvent {
final String communityUuid;
final String spaceUuid;
const SettingBlocFetchRooms(
{required this.communityUuid, required this.spaceUuid});
@override
List<Object?> get props => [communityUuid, spaceUuid];
}
class SettingBlocSaveName extends DeviceSettingEvent {
const SettingBlocSaveName();
}
class DeviceSettingInitialInfo extends DeviceSettingEvent {}
class ChangeNameEvent extends DeviceSettingEvent {
final bool? value;
const ChangeNameEvent({this.value});
}
class SettingBlocDeleteDevice extends DeviceSettingEvent {}
class SettingBlocAssignRoom extends DeviceSettingEvent {
final String communityUuid;
final String spaceUuid;
final String subSpaceUuid;
const SettingBlocAssignRoom({
required this.communityUuid,
required this.spaceUuid,
required this.subSpaceUuid,
});
@override
List<Object?> get props => [spaceUuid, communityUuid, subSpaceUuid];
}

View File

@ -0,0 +1,81 @@
import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/device_managment/device_setting/settings_model/device_info_model.dart';
import 'package:syncrow_web/pages/device_managment/device_setting/settings_model/sub_space_model.dart';
abstract class DeviceSettingsState extends Equatable {
const DeviceSettingsState({this.deviceInfo});
final DeviceInfoModel? deviceInfo;
@override
List<Object?> get props => [deviceInfo];
}
class DeviceSettingsInitial extends DeviceSettingsState {
final String deviceName;
final String deviceId;
final bool isEditingName;
final String editingNameValue;
const DeviceSettingsInitial({
this.deviceName = '',
this.deviceId = '',
this.isEditingName = false,
this.editingNameValue = '',
super.deviceInfo,
});
DeviceSettingsInitial copyWith({
String? deviceName,
String? deviceId,
bool? isEditingName,
String? editingNameValue,
}) =>
DeviceSettingsInitial(
deviceName: deviceName ?? this.deviceName,
deviceId: deviceId ?? this.deviceId,
isEditingName: isEditingName ?? this.isEditingName,
editingNameValue: editingNameValue ?? this.editingNameValue,
);
@override
List<Object?> get props =>
[deviceName, deviceId, isEditingName, editingNameValue];
}
class DeviceSettingsLoading extends DeviceSettingsState {}
class DeviceSettingsUpdate extends DeviceSettingsState {
final String? deviceName;
final List<SubSpaceModel> roomsList;
const DeviceSettingsUpdate({
this.deviceName,
super.deviceInfo,
this.roomsList = const [],
});
@override
List<Object?> get props => [deviceName, deviceInfo, roomsList];
}
class DeviceSettingsError extends DeviceSettingsState {
final String message;
const DeviceSettingsError({required this.message});
@override
List<Object?> get props => [message];
}
class DeviceSettingsFetchRooms extends DeviceSettingsState {
final List<SubSpaceModel> roomsList;
const DeviceSettingsFetchRooms({required this.roomsList});
@override
List<Object?> get props => [roomsList];
}
class DeviceSettingsSaveSelectionSuccess extends DeviceSettingsState {}
class ChangeNameState extends DeviceSettingsState {}

View File

@ -0,0 +1,28 @@
import 'package:syncrow_web/utils/constants/assets.dart';
class DeviceIconTypeHelper {
static const Map<String, String> _iconMap = {
'AC': Assets.ac,
'GW': Assets.gateway,
'CPS': Assets.sensors,
'DL': Assets.doorLock,
'WPS': Assets.sensors,
'3G': Assets.gangSwitch,
'2G': Assets.twoGang,
'1G': Assets.oneGang,
'CUR': Assets.curtain,
'WH': Assets.waterHeater,
'DS': Assets.doorSensor,
'1GT': Assets.oneTouchSwitch,
'2GT': Assets.twoTouchSwitch,
'3GT': Assets.threeTouchSwitch,
'GD': Assets.garageDoor,
'WL': Assets.waterLeakNormal,
'NCPS': Assets.sensors,
};
static String getDeviceIconByTypeCode(String? typeCode) {
if (typeCode == null) return Assets.logoHorizontal;
return _iconMap[typeCode] ?? Assets.logoHorizontal;
}
}

View File

@ -0,0 +1,127 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
import 'package:syncrow_web/pages/device_managment/device_setting/settings_model/device_info_model.dart';
import 'package:syncrow_web/pages/device_managment/device_setting/settings_model/sub_space_model.dart';
import 'package:syncrow_web/pages/device_managment/device_setting/sub_space_dialog.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:syncrow_web/web_layout/default_container.dart';
class DeviceManagementContent extends StatelessWidget {
const DeviceManagementContent({
super.key,
required this.device,
required this.subSpaces,
required this.deviceInfo,
});
final AllDevicesModel device;
final List<SubSpaceModel> subSpaces;
final DeviceInfoModel deviceInfo;
@override
Widget build(BuildContext context) {
Widget infoRow(
{required String label,
required String value,
Widget? trailing,
required Color? valueColor}) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 6.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
label,
style: context.theme.textTheme.bodyMedium!.copyWith(
fontSize: 14,
color: ColorsManager.grayColor,
),
),
Expanded(
child: Text(
value,
textAlign: TextAlign.end,
style: context.theme.textTheme.bodyMedium!
.copyWith(fontSize: 14, color: valueColor),
overflow: TextOverflow.ellipsis,
),
),
const SizedBox(width: 8),
trailing ?? const SizedBox.shrink(),
],
),
);
}
return DefaultContainer(
padding: EdgeInsets.zero,
child: Column(
children: [
const SizedBox(height: 5),
Padding(
padding: const EdgeInsets.all(10.0),
child: InkWell(
onTap: () {
showSubSpaceDialog(
context,
communityUuid: device.community!.uuid!,
spaceUuid: device.spaces!.first.uuid!,
subSpaces: subSpaces,
selected: device.subspace!.uuid,
);
},
child: infoRow(
label: 'Sub-Space:',
value: deviceInfo.subspace.subspaceName,
valueColor: ColorsManager.textGray,
trailing: const Icon(
Icons.arrow_forward_ios,
size: 16,
color: ColorsManager.greyColor,
),
),
),
),
const Divider(color: ColorsManager.dividerColor),
Padding(
padding: const EdgeInsets.all(10.0),
child: infoRow(
label: 'Virtual Address:',
value: deviceInfo.productUuid,
valueColor: ColorsManager.blackColor,
trailing: InkWell(
onTap: () {
Clipboard.setData(
ClipboardData(text: device.productUuid ?? ''),
);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Virtual Address copied to clipboard'),
),
);
},
child: const Icon(
Icons.copy,
size: 16,
color: ColorsManager.greyColor,
),
),
),
),
const Divider(color: ColorsManager.dividerColor),
Padding(
padding: const EdgeInsets.all(10.0),
child: infoRow(
label: 'MAC Address:',
valueColor: ColorsManager.blackColor,
value: deviceInfo.macAddress,
),
),
const SizedBox(height: 5),
],
),
);
}
}

View File

@ -0,0 +1,184 @@
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/device_managment/device_setting/device_icon_type_helper.dart';
import 'package:syncrow_web/pages/device_managment/device_setting/device_management_content.dart';
import 'package:syncrow_web/pages/device_managment/device_setting/remove_device_widget.dart';
import 'package:syncrow_web/pages/device_managment/device_setting/settings_model/device_info_model.dart';
import 'package:syncrow_web/pages/device_managment/device_setting/bloc/setting_bloc_bloc.dart';
import 'package:syncrow_web/pages/device_managment/device_setting/bloc/setting_bloc_state.dart';
import 'package:syncrow_web/pages/device_managment/device_setting/settings_model/sub_space_model.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/web_layout/default_container.dart';
class DeviceSettingsPanel extends StatelessWidget {
final VoidCallback? onClose;
final AllDevicesModel device;
const DeviceSettingsPanel({super.key, this.onClose, required this.device});
@override
Widget build(BuildContext context) {
final sectionTitle = context.theme.textTheme.titleMedium!.copyWith(
fontWeight: FontWeight.bold,
color: ColorsManager.grayColor,
);
return BlocProvider(
create: (context) => SettingDeviceBloc(
deviceId: device.uuid ?? '',
)
..add(DeviceSettingInitialInfo())
..add(SettingBlocFetchRooms(
communityUuid: device.community!.uuid!,
spaceUuid: device.spaces!.first.uuid!,
)),
child: Builder(
builder: (context) {
return BlocBuilder<SettingDeviceBloc, DeviceSettingsState>(
builder: (context, state) {
final _bloc = context.read<SettingDeviceBloc>();
final iconPath = DeviceIconTypeHelper.getDeviceIconByTypeCode(
device.productType);
final deviceInfo = state is DeviceSettingsUpdate
? state.deviceInfo ?? DeviceInfoModel.empty()
: DeviceInfoModel.empty();
final subSpaces =
state is DeviceSettingsUpdate ? state.roomsList ?? [] : [];
return Stack(
children: [
Container(
width: MediaQuery.of(context).size.width * 0.3,
color: ColorsManager.grey25,
padding: const EdgeInsets.symmetric(
horizontal: 20, vertical: 24),
child: ListView(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
IconButton(
icon: SvgPicture.asset(Assets.closeSettingsIcon),
onPressed:
onClose ?? () => Navigator.of(context).pop(),
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Device Settings',
style:
context.theme.textTheme.titleLarge!.copyWith(
fontWeight: FontWeight.bold,
color: ColorsManager.primaryColor,
),
),
],
),
const SizedBox(height: 24),
DefaultContainer(
child: Row(
children: [
CircleAvatar(
radius: 40,
backgroundColor:
const Color.fromARGB(177, 213, 213, 213),
child: CircleAvatar(
backgroundColor: ColorsManager.whiteColors,
radius: 36,
child: SvgPicture.asset(
iconPath,
fit: BoxFit.cover,
),
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Device Name:',
style: context.textTheme.bodyMedium!
.copyWith(
color: ColorsManager.grayColor,
),
),
TextFormField(
maxLength: 30,
style: const TextStyle(
color: ColorsManager.blackColor,
),
textAlign: TextAlign.start,
focusNode: _bloc.focusNode,
controller: _bloc.nameController,
enabled: _bloc.editName,
onFieldSubmitted: (value) {
_bloc.add(const ChangeNameEvent(
value: false));
},
decoration: const InputDecoration(
border: InputBorder.none,
fillColor: Colors.white10,
counterText: '',
),
),
],
),
),
const SizedBox(width: 8),
Visibility(
visible: _bloc.editName != true,
replacement: const SizedBox(),
child: GestureDetector(
onTap: () {
_bloc.add(
const ChangeNameEvent(value: true));
},
child: SvgPicture.asset(
Assets.editNameIconSettings,
color: ColorsManager.grayColor,
height: 20,
width: 20,
),
),
)
],
),
),
const SizedBox(height: 32),
Text('Device Management', style: sectionTitle),
DeviceManagementContent(
device: device,
subSpaces: subSpaces.cast<SubSpaceModel>(),
deviceInfo: deviceInfo,
),
const SizedBox(height: 32),
RemoveDeviceWidget(bloc: _bloc),
],
),
),
if (state is DeviceSettingsLoading)
Positioned.fill(
child: Container(
color: Colors.black.withOpacity(0.1),
child: const Center(
child: CircularProgressIndicator(
color: ColorsManager.primaryColor,
),
),
),
),
],
);
},
);
},
),
);
}
}

View File

@ -0,0 +1,82 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/device_managment/device_setting/bloc/setting_bloc_bloc.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:syncrow_web/web_layout/default_container.dart';
class RemoveDeviceWidget extends StatelessWidget {
const RemoveDeviceWidget({
super.key,
required SettingDeviceBloc bloc,
}) : _bloc = bloc;
final SettingDeviceBloc _bloc;
@override
Widget build(BuildContext context) {
return SizedBox(
width: double.infinity,
child: InkWell(
onTap: () {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text(
'Remove Device',
style: context.textTheme.bodyMedium!.copyWith(
fontWeight: FontWeight.w700,
color: ColorsManager.red,
),
),
content: Text(
'Are you sure you want to remove this device?',
style: context.textTheme.bodyMedium!.copyWith(
color: ColorsManager.grayColor,
),
),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text(
'Cancel',
style: context.textTheme.bodyMedium!.copyWith(
color: ColorsManager.grayColor,
),
),
),
TextButton(
onPressed: () {
_bloc.add(SettingBlocDeleteDevice());
Navigator.of(context).pop();
},
child: Text(
'Remove',
style: context.textTheme.bodyMedium!.copyWith(
color: ColorsManager.red,
),
),
),
],
);
},
);
},
child: DefaultContainer(
padding: const EdgeInsets.all(25),
child: Center(
child: Text(
'Remove Device',
style: context.textTheme.bodyMedium!.copyWith(
fontSize: 14,
color: ColorsManager.red,
fontWeight: FontWeight.w700),
),
),
),
),
);
}
}

View File

@ -0,0 +1,183 @@
class DeviceInfoModel {
final int activeTime;
final String category;
final String categoryName;
final int createTime;
final String gatewayId;
final String icon;
final String ip;
final String lat;
final String localKey;
final String lon;
final String model;
final String name;
final String nodeId;
final bool online;
final String ownerId;
final String productName;
final bool sub;
final String timeZone;
final int updateTime;
final String uuid;
final String productUuid;
final String productType;
final String permissionType;
final String macAddress;
final Subspace subspace;
DeviceInfoModel({
required this.activeTime,
required this.category,
required this.categoryName,
required this.createTime,
required this.gatewayId,
required this.icon,
required this.ip,
required this.lat,
required this.localKey,
required this.lon,
required this.model,
required this.name,
required this.nodeId,
required this.online,
required this.ownerId,
required this.productName,
required this.sub,
required this.timeZone,
required this.updateTime,
required this.uuid,
required this.productUuid,
required this.productType,
required this.permissionType,
required this.macAddress,
required this.subspace,
});
factory DeviceInfoModel.fromJson(Map<String, dynamic> json) {
return DeviceInfoModel(
activeTime: json['activeTime'] as int? ?? 0,
category: json['category'] ?? '',
categoryName: json['categoryName'] as String? ?? '',
createTime: json['createTime'] as int? ?? 0,
gatewayId: json['gatewayId'] as String? ?? '',
icon: json['icon'] as String? ?? '',
ip: json['ip'] as String? ?? '',
lat: json['lat'] as String? ?? '',
localKey: json['localKey'] as String? ?? '',
lon: json['lon'] as String? ?? '',
model: json['model'] as String? ?? '',
name: json['name'] as String? ?? '',
nodeId: json['nodeId'] as String? ?? '',
online: json['online'] as bool? ?? false,
ownerId: json['ownerId'] as String? ?? '',
productName: json['productName'] as String? ?? '',
sub: json['sub'] as bool? ?? false,
timeZone: json['timeZone'] as String? ?? '',
updateTime: json['updateTime'] as int? ?? 0,
uuid: json['uuid'] as String? ?? '',
productUuid: json['productUuid'] as String? ?? '',
productType: json['productType'] as String? ?? '',
permissionType: json['permissionType'] as String? ?? '',
macAddress: json['macAddress'] as String? ?? '',
subspace:
Subspace.fromJson(json['subspace'] as Map<String, dynamic>? ?? {}),
);
}
Map<String, dynamic> toJson() {
return {
'activeTime': activeTime,
'category': category,
'categoryName': categoryName,
'createTime': createTime,
'gatewayId': gatewayId,
'icon': icon,
'ip': ip,
'lat': lat,
'localKey': localKey,
'lon': lon,
'model': model,
'name': name,
'nodeId': nodeId,
'online': online,
'ownerId': ownerId,
'productName': productName,
'sub': sub,
'timeZone': timeZone,
'updateTime': updateTime,
'uuid': uuid,
'productUuid': productUuid,
'productType': productType,
'permissionType': permissionType,
'macAddress': macAddress,
'subspace': subspace.toJson(),
};
}
static DeviceInfoModel empty() {
return DeviceInfoModel(
activeTime: 0,
category: '',
categoryName: '',
createTime: 0,
gatewayId: '',
icon: '',
ip: '',
lat: '',
localKey: '',
lon: '',
model: '',
name: '',
nodeId: '',
online: false,
ownerId: '',
productName: '',
sub: false,
timeZone: '',
updateTime: 0,
uuid: '',
productUuid: '',
productType: '',
permissionType: '',
macAddress: '',
subspace: Subspace(
uuid: '',
createdAt: '',
updatedAt: '',
subspaceName: '',
),
);
}
}
class Subspace {
final String uuid;
final String createdAt;
final String updatedAt;
final String subspaceName;
Subspace({
required this.uuid,
required this.createdAt,
required this.updatedAt,
required this.subspaceName,
});
factory Subspace.fromJson(Map<String, dynamic> json) {
return Subspace(
uuid: json['uuid'] as String? ?? '',
createdAt: json['createdAt'] as String? ?? '',
updatedAt: json['updatedAt'] as String? ?? '',
subspaceName: json['subspaceName'] as String? ?? '',
);
}
Map<String, dynamic> toJson() {
return {
'uuid': uuid,
'createdAt': createdAt,
'updatedAt': updatedAt,
'subspaceName': subspaceName,
};
}
}

View File

@ -0,0 +1,35 @@
import 'package:syncrow_web/pages/visitor_password/model/device_model.dart';
class SubSpaceModel {
final String? id;
final String? name;
List<DeviceModel>? devices;
SubSpaceModel({
required this.id,
required this.name,
required this.devices,
});
Map<String, dynamic> toJson() {
return {
'id': id,
'name': name,
'devices': devices?.map((device) => device.toJson()).toList(),
};
}
factory SubSpaceModel.fromJson(Map<String, dynamic> json) {
List<DeviceModel> devices = [];
if (json['devices'] != null) {
for (var device in json['devices']) {
devices.add(DeviceModel.fromJson(device));
}
}
return SubSpaceModel(
id: json['uuid'] as String? ?? '',
name: json['subspaceName'] as String? ?? '',
devices: devices.isNotEmpty ? devices : null as List<DeviceModel>?,
);
}
}

View File

@ -0,0 +1,115 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/device_setting/bloc/setting_bloc_bloc.dart';
import 'package:syncrow_web/pages/device_managment/device_setting/settings_model/sub_space_model.dart';
import 'package:syncrow_web/pages/device_managment/device_setting/subspace_dialog_buttons.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
class SubSpaceDialog extends StatefulWidget {
final List<SubSpaceModel> subSpaces;
final String? selected;
final void Function(SubSpaceModel?) onConfirmed;
const SubSpaceDialog({
Key? key,
required this.subSpaces,
this.selected,
required this.onConfirmed,
}) : super(key: key);
@override
State<SubSpaceDialog> createState() => _SubSpaceDialogState();
}
class _SubSpaceDialogState extends State<SubSpaceDialog> {
String? _selectedId;
@override
void initState() {
super.initState();
_selectedId = widget.selected;
}
@override
Widget build(BuildContext context) {
return Dialog(
backgroundColor: ColorsManager.whiteColors,
insetPadding: const EdgeInsets.symmetric(horizontal: 24, vertical: 60),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(28),
),
child: Container(
width: MediaQuery.of(context).size.width * 0.35,
padding: const EdgeInsets.fromLTRB(0, 24, 0, 0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
'Sub-Space',
style: context.textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w700,
color: ColorsManager.blueColor,
fontSize: 20),
),
const Divider(),
const SizedBox(height: 10),
...widget.subSpaces.map((space) {
return RadioListTile<String>(
value: space.id!,
groupValue: _selectedId,
onChanged: (value) {
setState(() {
_selectedId = value;
});
},
activeColor: Color(0xFF2962FF),
title: Text(
space.name ?? 'Unnamed Sub-Space',
style: context.textTheme.bodyMedium?.copyWith(
fontSize: 15,
color: ColorsManager.grayColor,
fontWeight: FontWeight.w400,
),
),
controlAffinity: ListTileControlAffinity.trailing,
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
);
}).toList(),
const SizedBox(height: 12),
const Divider(height: 1, thickness: 1),
SubSpaceDialogButtons(selectedId: _selectedId, widget: widget),
],
),
),
);
}
}
void showSubSpaceDialog(
BuildContext context, {
required List<SubSpaceModel> subSpaces,
String? selected,
required String communityUuid,
required String spaceUuid,
}) {
showDialog(
context: context,
barrierDismissible: true,
builder: (ctx) => SubSpaceDialog(
subSpaces: subSpaces,
selected: selected,
onConfirmed: (selectedModel) {
if (selectedModel != null) {
context.read<SettingDeviceBloc>().add(
SettingBlocAssignRoom(
communityUuid: communityUuid,
spaceUuid: spaceUuid,
subSpaceUuid: selectedModel.id ?? '',
),
);
}
},
),
);
}

View File

@ -0,0 +1,114 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/device_setting/bloc/setting_bloc_bloc.dart';
import 'package:syncrow_web/pages/device_managment/device_setting/settings_model/sub_space_model.dart';
import 'package:syncrow_web/pages/device_managment/device_setting/sub_space_dialog.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
class SubSpaceDialogButtons extends StatelessWidget {
const SubSpaceDialogButtons({
super.key,
required String? selectedId,
required this.widget,
}) : _selectedId = selectedId;
final String? _selectedId;
final SubSpaceDialog widget;
@override
Widget build(BuildContext context) {
return SizedBox(
height: 50,
child: Row(
children: [
Expanded(
child: Container(
decoration: const BoxDecoration(
border: Border(
right: BorderSide(
color: ColorsManager.dividerColor,
width: 0.5,
),
),
),
child: TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text(
'Cancel',
style: context.textTheme.bodyMedium?.copyWith(
color: ColorsManager.textGray,
fontSize: 18,
fontWeight: FontWeight.w500,
),
),
),
),
),
Expanded(
child: Container(
decoration: const BoxDecoration(
border: Border(
left: BorderSide(
color: ColorsManager.dividerColor,
width: 0.5,
),
),
),
child: TextButton(
onPressed: _selectedId == null
? null
: () {
final selectedModel = widget.subSpaces.firstWhere(
(space) => space.id == _selectedId,
orElse: () =>
SubSpaceModel(id: null, name: '', devices: []));
widget.onConfirmed(selectedModel);
Navigator.of(context).pop();
},
child: Text(
'Confirm',
style: context.textTheme.bodyMedium?.copyWith(
color: ColorsManager.secondaryColor,
fontSize: 14,
fontWeight: FontWeight.w400,
),
),
),
),
),
],
),
);
}
}
void showSubSpaceDialog(
BuildContext context, {
required List<SubSpaceModel> subSpaces,
String? selected,
required String communityUuid,
required String spaceUuid,
}) {
showDialog(
context: context,
barrierDismissible: true,
builder: (ctx) => SubSpaceDialog(
subSpaces: subSpaces,
selected: selected,
onConfirmed: (selectedModel) {
if (selectedModel != null) {
context.read<SettingDeviceBloc>().add(
SettingBlocAssignRoom(
communityUuid: communityUuid,
spaceUuid: spaceUuid,
subSpaceUuid: selectedModel.id ?? '',
),
);
}
},
),
);
}

View File

@ -1,7 +1,6 @@
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:dio/dio.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
@ -16,7 +15,6 @@ 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/pages/space_tree/bloc/space_tree_bloc.dart';
import 'package:syncrow_web/services/api/api_exception.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';
@ -66,8 +64,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
TriggerSwitchTabsEvent event,
Emitter<RoutineState> emit,
) {
emit(state.copyWith(
routineTab: event.isRoutineTab, createRoutineView: false));
emit(state.copyWith(routineTab: event.isRoutineTab, createRoutineView: false));
add(ResetRoutineState());
if (event.isRoutineTab) {
add(const LoadScenes());
@ -93,8 +90,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
final updatedIfItems = List<Map<String, dynamic>>.from(state.ifItems);
// Find the index of the item in teh current itemsList
int index = updatedIfItems.indexWhere(
(map) => map['uniqueCustomId'] == event.item['uniqueCustomId']);
int index =
updatedIfItems.indexWhere((map) => map['uniqueCustomId'] == event.item['uniqueCustomId']);
// Replace the map if the index is valid
if (index != -1) {
updatedIfItems[index] = event.item;
@ -103,21 +100,18 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
}
if (event.isTabToRun) {
emit(state.copyWith(
ifItems: updatedIfItems, isTabToRun: true, isAutomation: false));
emit(state.copyWith(ifItems: updatedIfItems, isTabToRun: true, isAutomation: false));
} else {
emit(state.copyWith(
ifItems: updatedIfItems, isTabToRun: false, isAutomation: true));
emit(state.copyWith(ifItems: updatedIfItems, isTabToRun: false, isAutomation: true));
}
}
void _onAddToThenContainer(
AddToThenContainer event, Emitter<RoutineState> emit) {
void _onAddToThenContainer(AddToThenContainer event, Emitter<RoutineState> emit) {
final currentItems = List<Map<String, dynamic>>.from(state.thenItems);
// Find the index of the item in teh current itemsList
int index = currentItems.indexWhere(
(map) => map['uniqueCustomId'] == event.item['uniqueCustomId']);
int index =
currentItems.indexWhere((map) => map['uniqueCustomId'] == event.item['uniqueCustomId']);
// Replace the map if the index is valid
if (index != -1) {
currentItems[index] = event.item;
@ -128,8 +122,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
emit(state.copyWith(thenItems: currentItems));
}
void _onAddFunctionsToRoutine(
AddFunctionToRoutine event, Emitter<RoutineState> emit) {
void _onAddFunctionsToRoutine(AddFunctionToRoutine event, Emitter<RoutineState> emit) {
try {
if (event.functions.isEmpty) return;
@ -164,8 +157,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
// currentSelectedFunctions[event.uniqueCustomId] = List.from(event.functions);
// }
currentSelectedFunctions[event.uniqueCustomId] =
List.from(event.functions);
currentSelectedFunctions[event.uniqueCustomId] = List.from(event.functions);
emit(state.copyWith(selectedFunctions: currentSelectedFunctions));
} catch (e) {
@ -173,30 +165,24 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
}
}
Future<void> _onLoadScenes(
LoadScenes event, Emitter<RoutineState> emit) async {
Future<void> _onLoadScenes(LoadScenes event, Emitter<RoutineState> emit) async {
emit(state.copyWith(isLoading: true, errorMessage: null));
List<ScenesModel> scenes = [];
try {
BuildContext context = NavigationService.navigatorKey.currentContext!;
var createRoutineBloc = context.read<CreateRoutineBloc>();
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
if (createRoutineBloc.selectedSpaceId == '' &&
createRoutineBloc.selectedCommunityId == '') {
if (createRoutineBloc.selectedSpaceId == '' && createRoutineBloc.selectedCommunityId == '') {
var spaceBloc = context.read<SpaceTreeBloc>();
for (var communityId in spaceBloc.state.selectedCommunities) {
List<String> spacesList =
spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? [];
List<String> spacesList = spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? [];
for (var spaceId in spacesList) {
scenes.addAll(
await SceneApi.getScenes(spaceId, communityId, projectUuid));
scenes.addAll(await SceneApi.getScenes(spaceId, communityId, projectUuid));
}
}
} else {
scenes.addAll(await SceneApi.getScenes(
createRoutineBloc.selectedSpaceId,
createRoutineBloc.selectedCommunityId,
projectUuid));
createRoutineBloc.selectedSpaceId, createRoutineBloc.selectedCommunityId, projectUuid));
}
emit(state.copyWith(
@ -213,8 +199,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
}
}
Future<void> _onLoadAutomation(
LoadAutomation event, Emitter<RoutineState> emit) async {
Future<void> _onLoadAutomation(LoadAutomation event, Emitter<RoutineState> emit) async {
emit(state.copyWith(isLoading: true, errorMessage: null));
List<ScenesModel> automations = [];
final projectId = await ProjectManager.getProjectUUID() ?? '';
@ -222,22 +207,17 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
BuildContext context = NavigationService.navigatorKey.currentContext!;
var createRoutineBloc = context.read<CreateRoutineBloc>();
try {
if (createRoutineBloc.selectedSpaceId == '' &&
createRoutineBloc.selectedCommunityId == '') {
if (createRoutineBloc.selectedSpaceId == '' && createRoutineBloc.selectedCommunityId == '') {
var spaceBloc = context.read<SpaceTreeBloc>();
for (var communityId in spaceBloc.state.selectedCommunities) {
List<String> spacesList =
spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? [];
List<String> spacesList = spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? [];
for (var spaceId in spacesList) {
automations.addAll(
await SceneApi.getAutomation(spaceId, communityId, projectId));
automations.addAll(await SceneApi.getAutomation(spaceId, communityId, projectId));
}
}
} else {
automations.addAll(await SceneApi.getAutomation(
createRoutineBloc.selectedSpaceId,
createRoutineBloc.selectedCommunityId,
projectId));
createRoutineBloc.selectedSpaceId, createRoutineBloc.selectedCommunityId, projectId));
}
emit(state.copyWith(
automations: automations,
@ -253,16 +233,14 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
}
}
FutureOr<void> _onSearchRoutines(
SearchRoutines event, Emitter<RoutineState> emit) async {
FutureOr<void> _onSearchRoutines(SearchRoutines event, Emitter<RoutineState> emit) async {
emit(state.copyWith(isLoading: true, errorMessage: null));
await Future.delayed(const Duration(seconds: 1));
emit(state.copyWith(isLoading: false, errorMessage: null));
emit(state.copyWith(searchText: event.query));
}
FutureOr<void> _onAddSelectedIcon(
AddSelectedIcon event, Emitter<RoutineState> emit) {
FutureOr<void> _onAddSelectedIcon(AddSelectedIcon event, Emitter<RoutineState> emit) {
emit(state.copyWith(selectedIcon: event.icon));
}
@ -276,8 +254,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
return actions.last['deviceId'] == 'delay';
}
Future<void> _onCreateScene(
CreateSceneEvent event, Emitter<RoutineState> emit) async {
Future<void> _onCreateScene(CreateSceneEvent event, Emitter<RoutineState> emit) async {
try {
// Check if first action is delay
// if (_isFirstActionDelay(state.thenItems)) {
@ -290,8 +267,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
if (_isLastActionDelay(state.thenItems)) {
emit(state.copyWith(
errorMessage:
'A delay condition cannot be the only or the last action',
errorMessage: 'A delay condition cannot be the only or the last action',
isLoading: false,
));
return;
@ -359,18 +335,15 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
errorMessage: 'Something went wrong',
));
}
} on APIException catch (e) {
final errorData = e.message;
String errorMessage = errorData;
} catch (e) {
emit(state.copyWith(
isLoading: false,
errorMessage: errorMessage,
errorMessage: 'Something went wrong',
));
}
}
Future<void> _onCreateAutomation(
CreateAutomationEvent event, Emitter<RoutineState> emit) async {
Future<void> _onCreateAutomation(CreateAutomationEvent event, Emitter<RoutineState> emit) async {
try {
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
if (state.routineName == null || state.routineName!.isEmpty) {
@ -392,8 +365,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
if (_isLastActionDelay(state.thenItems)) {
emit(state.copyWith(
errorMessage:
'A delay condition cannot be the only or the last action',
errorMessage: 'A delay condition cannot be the only or the last action',
isLoading: false,
));
CustomSnackBar.redSnackBar('Cannot have delay as the last action');
@ -484,8 +456,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
actions: actions,
);
final result =
await SceneApi.createAutomation(createAutomationModel, projectUuid);
final result = await SceneApi.createAutomation(createAutomationModel, projectUuid);
if (result['success']) {
add(ResetRoutineState());
add(const LoadAutomation());
@ -497,32 +468,26 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
));
CustomSnackBar.redSnackBar('Something went wrong');
}
} on APIException catch (e) {
final errorData = e.message;
String errorMessage = errorData;
} catch (e) {
emit(state.copyWith(
isLoading: false,
errorMessage: errorMessage,
errorMessage: 'Something went wrong',
));
CustomSnackBar.redSnackBar(errorMessage);
CustomSnackBar.redSnackBar('Something went wrong');
}
}
FutureOr<void> _onRemoveDragCard(
RemoveDragCard event, Emitter<RoutineState> emit) {
FutureOr<void> _onRemoveDragCard(RemoveDragCard event, Emitter<RoutineState> emit) {
if (event.isFromThen) {
final thenItems = List<Map<String, dynamic>>.from(state.thenItems);
final selectedFunctions =
Map<String, List<DeviceFunctionData>>.from(state.selectedFunctions);
final selectedFunctions = Map<String, List<DeviceFunctionData>>.from(state.selectedFunctions);
thenItems.removeAt(event.index);
selectedFunctions.remove(event.key);
emit(state.copyWith(
thenItems: thenItems, selectedFunctions: selectedFunctions));
emit(state.copyWith(thenItems: thenItems, selectedFunctions: selectedFunctions));
} else {
final ifItems = List<Map<String, dynamic>>.from(state.ifItems);
final selectedFunctions =
Map<String, List<DeviceFunctionData>>.from(state.selectedFunctions);
final selectedFunctions = Map<String, List<DeviceFunctionData>>.from(state.selectedFunctions);
ifItems.removeAt(event.index);
selectedFunctions.remove(event.key);
@ -533,8 +498,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
isAutomation: false,
isTabToRun: false));
} else {
emit(state.copyWith(
ifItems: ifItems, selectedFunctions: selectedFunctions));
emit(state.copyWith(ifItems: ifItems, selectedFunctions: selectedFunctions));
}
}
}
@ -546,13 +510,11 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
));
}
FutureOr<void> _onEffectiveTimeEvent(
EffectiveTimePeriodEvent event, Emitter<RoutineState> emit) {
FutureOr<void> _onEffectiveTimeEvent(EffectiveTimePeriodEvent event, Emitter<RoutineState> emit) {
emit(state.copyWith(effectiveTime: event.effectiveTime));
}
FutureOr<void> _onSetRoutineName(
SetRoutineName event, Emitter<RoutineState> emit) {
FutureOr<void> _onSetRoutineName(SetRoutineName event, Emitter<RoutineState> emit) {
emit(state.copyWith(
routineName: event.name,
));
@ -679,8 +641,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
// return (thenItems, ifItems, currentFunctions);
// }
Future<void> _onGetSceneDetails(
GetSceneDetails event, Emitter<RoutineState> emit) async {
Future<void> _onGetSceneDetails(GetSceneDetails event, Emitter<RoutineState> emit) async {
try {
emit(state.copyWith(
isLoading: true,
@ -728,12 +689,10 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
// if (!deviceCards.containsKey(deviceId)) {
deviceCards[deviceId] = {
'entityId': action.entityId,
'deviceId':
action.actionExecutor == 'delay' ? 'delay' : action.entityId,
'uniqueCustomId':
action.type == 'automation' || action.actionExecutor == 'delay'
? action.entityId
: const Uuid().v4(),
'deviceId': action.actionExecutor == 'delay' ? 'delay' : action.entityId,
'uniqueCustomId': action.type == 'automation' || action.actionExecutor == 'delay'
? action.entityId
: const Uuid().v4(),
'title': action.actionExecutor == 'delay'
? 'Delay'
: action.type == 'automation'
@ -773,8 +732,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
),
);
// emit(state.copyWith(automationActionExecutor: action.actionExecutor));
} else if (action.executorProperty != null &&
action.actionExecutor != 'delay') {
} else if (action.executorProperty != null && action.actionExecutor != 'delay') {
final functions = matchingDevice?.functions ?? [];
final functionCode = action.executorProperty?.functionCode;
for (DeviceFunction function in functions) {
@ -840,8 +798,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
}
}
FutureOr<void> _onResetRoutineState(
ResetRoutineState event, Emitter<RoutineState> emit) {
FutureOr<void> _onResetRoutineState(ResetRoutineState event, Emitter<RoutineState> emit) {
emit(state.copyWith(
ifItems: [],
thenItems: [],
@ -865,8 +822,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
createRoutineView: false));
}
FutureOr<void> _deleteScene(
DeleteScene event, Emitter<RoutineState> emit) async {
FutureOr<void> _deleteScene(DeleteScene event, Emitter<RoutineState> emit) async {
try {
final projectId = await ProjectManager.getProjectUUID() ?? '';
@ -875,8 +831,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
var spaceBloc = context.read<SpaceTreeBloc>();
if (state.isTabToRun) {
await SceneApi.deleteScene(
unitUuid: spaceBloc.state.selectedSpaces[0],
sceneId: state.sceneId ?? '');
unitUuid: spaceBloc.state.selectedSpaces[0], sceneId: state.sceneId ?? '');
} else {
await SceneApi.deleteAutomation(
unitUuid: spaceBloc.state.selectedSpaces[0],
@ -899,14 +854,11 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
add(const LoadAutomation());
add(ResetRoutineState());
emit(state.copyWith(isLoading: false, createRoutineView: false));
} on APIException catch (e) {
final errorData = e.message;
String errorMessage = errorData;
} catch (e) {
emit(state.copyWith(
isLoading: false,
errorMessage: errorMessage,
errorMessage: 'Failed to delete scene',
));
CustomSnackBar.redSnackBar(errorMessage);
}
}
@ -924,8 +876,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
// }
// }
FutureOr<void> _fetchDevices(
FetchDevicesInRoutine event, Emitter<RoutineState> emit) async {
FutureOr<void> _fetchDevices(FetchDevicesInRoutine event, Emitter<RoutineState> emit) async {
emit(state.copyWith(isLoading: true));
try {
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
@ -934,21 +885,17 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
var createRoutineBloc = context.read<CreateRoutineBloc>();
var spaceBloc = context.read<SpaceTreeBloc>();
if (createRoutineBloc.selectedSpaceId == '' &&
createRoutineBloc.selectedCommunityId == '') {
if (createRoutineBloc.selectedSpaceId == '' && createRoutineBloc.selectedCommunityId == '') {
for (var communityId in spaceBloc.state.selectedCommunities) {
List<String> spacesList =
spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? [];
List<String> spacesList = spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? [];
for (var spaceId in spacesList) {
devices.addAll(await DevicesManagementApi()
.fetchDevices(communityId, spaceId, projectUuid));
devices.addAll(
await DevicesManagementApi().fetchDevices(communityId, spaceId, projectUuid));
}
}
} else {
devices.addAll(await DevicesManagementApi().fetchDevices(
createRoutineBloc.selectedCommunityId,
createRoutineBloc.selectedSpaceId,
projectUuid));
createRoutineBloc.selectedCommunityId, createRoutineBloc.selectedSpaceId, projectUuid));
}
emit(state.copyWith(isLoading: false, devices: devices));
@ -957,8 +904,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
}
}
FutureOr<void> _onUpdateScene(
UpdateScene event, Emitter<RoutineState> emit) async {
FutureOr<void> _onUpdateScene(UpdateScene event, Emitter<RoutineState> emit) async {
try {
// Check if first action is delay
// if (_isFirstActionDelay(state.thenItems)) {
@ -972,8 +918,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
if (_isLastActionDelay(state.thenItems)) {
emit(state.copyWith(
errorMessage:
'A delay condition cannot be the only or the last action',
errorMessage: 'A delay condition cannot be the only or the last action',
isLoading: false,
));
return;
@ -1026,8 +971,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
actions: actions,
);
final result =
await SceneApi.updateScene(createSceneModel, state.sceneId ?? '');
final result = await SceneApi.updateScene(createSceneModel, state.sceneId ?? '');
if (result['success']) {
add(ResetRoutineState());
add(const LoadScenes());
@ -1046,8 +990,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
}
}
FutureOr<void> _onUpdateAutomation(
UpdateAutomation event, Emitter<RoutineState> emit) async {
FutureOr<void> _onUpdateAutomation(UpdateAutomation event, Emitter<RoutineState> emit) async {
try {
if (state.routineName == null || state.routineName!.isEmpty) {
emit(state.copyWith(
@ -1171,11 +1114,10 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
errorMessage: result['message'],
));
}
} on APIException catch (e) {
final errorData = e.message;
} catch (e) {
emit(state.copyWith(
isLoading: false,
errorMessage: errorData,
errorMessage: 'Something went wrong',
));
}
}
@ -1272,8 +1214,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
// if (!deviceThenCards.containsKey(deviceId)) {
deviceThenCards[deviceId] = {
'entityId': action.entityId,
'deviceId':
action.actionExecutor == 'delay' ? 'delay' : action.entityId,
'deviceId': action.actionExecutor == 'delay' ? 'delay' : action.entityId,
'uniqueCustomId': const Uuid().v4(),
'title': action.actionExecutor == 'delay'
? 'Delay'
@ -1308,8 +1249,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
updatedFunctions[uniqueCustomId] = [];
}
if (action.executorProperty != null &&
action.actionExecutor != 'delay') {
if (action.executorProperty != null && action.actionExecutor != 'delay') {
final functions = matchingDevice.functions;
final functionCode = action.executorProperty!.functionCode;
for (var function in functions) {
@ -1351,14 +1291,10 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
}
}
final ifItems = deviceIfCards.values
.where((card) => card['type'] == 'condition')
.toList();
final ifItems = deviceIfCards.values.where((card) => card['type'] == 'condition').toList();
final thenItems = deviceThenCards.values
.where((card) =>
card['type'] == 'action' ||
card['type'] == 'automation' ||
card['type'] == 'scene')
card['type'] == 'action' || card['type'] == 'automation' || card['type'] == 'scene')
.toList();
emit(state.copyWith(
@ -1380,8 +1316,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
}
}
Future<void> _onSceneTrigger(
SceneTrigger event, Emitter<RoutineState> emit) async {
Future<void> _onSceneTrigger(SceneTrigger event, Emitter<RoutineState> emit) async {
emit(state.copyWith(loadingSceneId: event.sceneId));
try {
@ -1423,29 +1358,24 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
if (success) {
final updatedAutomations = await SceneApi.getAutomationByUnitId(
event.automationStatusUpdate.spaceUuid,
event.communityId,
projectId);
event.automationStatusUpdate.spaceUuid, event.communityId, projectId);
// Remove from loading set safely
final updatedLoadingIds = {...state.loadingAutomationIds!}
..remove(event.automationId);
final updatedLoadingIds = {...state.loadingAutomationIds!}..remove(event.automationId);
emit(state.copyWith(
automations: updatedAutomations,
loadingAutomationIds: updatedLoadingIds,
));
} else {
final updatedLoadingIds = {...state.loadingAutomationIds!}
..remove(event.automationId);
final updatedLoadingIds = {...state.loadingAutomationIds!}..remove(event.automationId);
emit(state.copyWith(
loadingAutomationIds: updatedLoadingIds,
errorMessage: 'Update failed',
));
}
} catch (e) {
final updatedLoadingIds = {...state.loadingAutomationIds!}
..remove(event.automationId);
final updatedLoadingIds = {...state.loadingAutomationIds!}..remove(event.automationId);
emit(state.copyWith(
loadingAutomationIds: updatedLoadingIds,
errorMessage: 'Update error: ${e.toString()}',

View File

@ -48,8 +48,7 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
@override
Widget build(BuildContext context) {
return BlocBuilder<SpaceTreeBloc, SpaceTreeState>(
builder: (context, state) {
return BlocBuilder<SpaceTreeBloc, SpaceTreeState>(builder: (context, state) {
final communities = state.searchQuery.isNotEmpty
? state.filteredCommunity
: state.communityList;
@ -133,118 +132,104 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
)
else
CustomSearchBar(
onSearchChanged: (query) =>
context.read<SpaceTreeBloc>().add(
SearchQueryEvent(query),
),
onSearchChanged: (query) => context.read<SpaceTreeBloc>().add(
SearchQueryEvent(query),
),
),
const SizedBox(height: 16),
Expanded(
child: state.isSearching
? const Center(child: CircularProgressIndicator())
: communities.isEmpty
? Center(
child: Text(
'No communities found',
style: context.textTheme.bodyMedium?.copyWith(
color: ColorsManager.textGray,
),
),
)
: SidebarCommunitiesList(
onScrollToEnd: () {
if (!state.paginationIsLoading) {
: SidebarCommunitiesList(
onScrollToEnd: () {
if (!state.paginationIsLoading) {
context.read<SpaceTreeBloc>().add(
PaginationEvent(
state.paginationModel,
state.communityList,
),
);
}
},
scrollController: _scrollController,
communities: communities,
itemBuilder: (context, index) {
return CustomExpansionTileSpaceTree(
title: communities[index].name,
isSelected: state.selectedCommunities
.contains(communities[index].uuid),
isSoldCheck: state.selectedCommunities
.contains(communities[index].uuid),
onExpansionChanged: () =>
context.read<SpaceTreeBloc>().add(
PaginationEvent(
state.paginationModel,
state.communityList,
OnCommunityExpanded(
communities[index].uuid,
),
);
}
),
isExpanded: state.expandedCommunities.contains(
communities[index].uuid,
),
onItemSelected: () {
widget.onSelect();
context.read<SpaceTreeBloc>().add(
OnCommunitySelected(
communities[index].uuid,
communities[index].spaces,
),
);
},
scrollController: _scrollController,
communities: communities,
itemBuilder: (context, index) {
return CustomExpansionTileSpaceTree(
title: communities[index].name,
isSelected: state.selectedCommunities
.contains(communities[index].uuid),
isSoldCheck: state.selectedCommunities
.contains(communities[index].uuid),
onExpansionChanged: () =>
children: communities[index].spaces.map(
(space) {
return CustomExpansionTileSpaceTree(
title: space.name,
isExpanded:
state.expandedSpaces.contains(space.uuid),
onItemSelected: () {
final isParentSelected = _isParentSelected(
state,
communities[index],
space,
);
if (widget
.shouldDisableDeselectingChildrenOfSelectedParent &&
isParentSelected) {
return;
}
widget.onSelect();
context.read<SpaceTreeBloc>().add(
OnCommunityExpanded(
communities[index].uuid,
OnSpaceSelected(
communities[index],
space.uuid ?? '',
space.children,
),
),
isExpanded:
state.expandedCommunities.contains(
communities[index].uuid,
),
onItemSelected: () {
widget.onSelect();
context.read<SpaceTreeBloc>().add(
OnCommunitySelected(
communities[index].uuid,
communities[index].spaces,
),
);
},
children: communities[index].spaces.map(
(space) {
return CustomExpansionTileSpaceTree(
title: space.name,
isExpanded: state.expandedSpaces
.contains(space.uuid),
onItemSelected: () {
final isParentSelected =
_isParentSelected(
state,
communities[index],
space,
);
if (widget
.shouldDisableDeselectingChildrenOfSelectedParent &&
isParentSelected) {
return;
}
widget.onSelect();
context.read<SpaceTreeBloc>().add(
OnSpaceSelected(
communities[index],
space.uuid ?? '',
space.children,
),
);
},
onExpansionChanged: () =>
context.read<SpaceTreeBloc>().add(
OnSpaceExpanded(
communities[index].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,
communities[index],
),
);
},
).toList(),
);
},
),
onExpansionChanged: () =>
context.read<SpaceTreeBloc>().add(
OnSpaceExpanded(
communities[index].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,
communities[index],
),
);
},
).toList(),
);
},
),
),
if (state.paginationIsLoading)
const CircularProgressIndicator(),
if (state.paginationIsLoading) const CircularProgressIndicator(),
],
),
);

View File

@ -1,10 +0,0 @@
class APIException implements Exception {
final String message;
APIException(this.message);
@override
String toString() {
return message;
}
}

View File

@ -1,26 +1,18 @@
import 'package:dio/dio.dart';
import 'package:syncrow_web/pages/auth/model/region_model.dart';
import 'package:syncrow_web/pages/auth/model/token.dart';
import 'package:syncrow_web/services/api/api_exception.dart';
import 'package:syncrow_web/services/api/http_service.dart';
import 'package:syncrow_web/utils/constants/api_const.dart';
class AuthenticationAPI {
static Future<Token> loginWithEmail({required var model}) async {
try {
final response = await HTTPService().post(
path: ApiEndpoints.login,
body: model.toJson(),
showServerMessage: true,
expectedResponseModel: (json) {
return Token.fromJson(json['data']);
});
return response;
} on DioException catch (e) {
final message = e.response?.data['error']['message'] ??
'An error occurred while logging in';
throw APIException(message);
}
final response = await HTTPService().post(
path: ApiEndpoints.login,
body: model.toJson(),
showServerMessage: true,
expectedResponseModel: (json) {
return Token.fromJson(json['data']);
});
return response;
}
static Future forgetPassword({
@ -28,18 +20,12 @@ class AuthenticationAPI {
required var password,
required var otpCode,
}) async {
try {
final response = await HTTPService().post(
path: ApiEndpoints.forgetPassword,
body: {"email": email, "password": password, "otpCode": otpCode},
showServerMessage: true,
expectedResponseModel: (json) {});
return response;
} on DioException catch (e) {
final message = e.response?.data['error']['message'] ??
'An error occurred while resetting the password';
throw APIException(message);
}
final response = await HTTPService().post(
path: ApiEndpoints.forgetPassword,
body: {"email": email, "password": password, "otpCode": otpCode},
showServerMessage: true,
expectedResponseModel: (json) {});
return response;
}
static Future<int?> sendOtp({required String email}) async {
@ -53,26 +39,19 @@ class AuthenticationAPI {
return response;
}
static Future verifyOtp(
{required String email, required String otpCode}) async {
try {
final response = await HTTPService().post(
path: ApiEndpoints.verifyOtp,
body: {"email": email, "type": "PASSWORD", "otpCode": otpCode},
showServerMessage: true,
expectedResponseModel: (json) {
if (json['message'] == 'Otp Verified Successfully') {
return true;
} else {
return false;
}
});
return response;
} on APIException catch (e) {
throw APIException(e.message);
} catch (e) {
throw APIException('An error occurred while verifying the OTP');
}
static Future verifyOtp({required String email, required String otpCode}) async {
final response = await HTTPService().post(
path: ApiEndpoints.verifyOtp,
body: {"email": email, "type": "PASSWORD", "otpCode": otpCode},
showServerMessage: true,
expectedResponseModel: (json) {
if (json['message'] == 'Otp Verified Successfully') {
return true;
} else {
return false;
}
});
return response;
}
static Future<List<RegionModel>> fetchRegion() async {
@ -80,9 +59,7 @@ class AuthenticationAPI {
path: ApiEndpoints.getRegion,
showServerMessage: true,
expectedResponseModel: (json) {
return (json as List)
.map((zone) => RegionModel.fromJson(zone))
.toList();
return (json as List).map((zone) => RegionModel.fromJson(zone)).toList();
});
return response;
}

View File

@ -91,7 +91,8 @@ class DevicesManagementApi {
}
}
Future<bool> deviceBatchControl(List<String> uuids, String code, dynamic value) async {
Future<bool> deviceBatchControl(
List<String> uuids, String code, dynamic value) async {
try {
final body = {
'devicesUuid': uuids,
@ -116,7 +117,8 @@ class DevicesManagementApi {
}
}
static Future<List<DeviceModel>> getDevicesByGatewayId(String gatewayId) async {
static Future<List<DeviceModel>> getDevicesByGatewayId(
String gatewayId) async {
final response = await HTTPService().get(
path: ApiEndpoints.gatewayApi.replaceAll('{gatewayUuid}', gatewayId),
showServerMessage: false,
@ -150,7 +152,9 @@ class DevicesManagementApi {
String code,
) async {
final response = await HTTPService().get(
path: ApiEndpoints.getDeviceLogs.replaceAll('{uuid}', uuid).replaceAll('{code}', code),
path: ApiEndpoints.getDeviceLogs
.replaceAll('{uuid}', uuid)
.replaceAll('{code}', code),
showServerMessage: false,
expectedResponseModel: (json) {
return DeviceReport.fromJson(json['data']);
@ -223,7 +227,8 @@ class DevicesManagementApi {
}
}
Future<bool> addScheduleRecord(ScheduleEntry sendSchedule, String uuid) async {
Future<bool> addScheduleRecord(
ScheduleEntry sendSchedule, String uuid) async {
try {
final response = await HTTPService().post(
path: ApiEndpoints.scheduleByDeviceId.replaceAll('{deviceUuid}', uuid),
@ -240,7 +245,8 @@ class DevicesManagementApi {
}
}
Future<List<ScheduleModel>> getDeviceSchedules(String uuid, String category) async {
Future<List<ScheduleModel>> getDeviceSchedules(
String uuid, String category) async {
try {
final response = await HTTPService().get(
path: ApiEndpoints.getScheduleByDeviceId
@ -263,7 +269,9 @@ class DevicesManagementApi {
}
Future<bool> updateScheduleRecord(
{required bool enable, required String uuid, required String scheduleId}) async {
{required bool enable,
required String uuid,
required String scheduleId}) async {
try {
final response = await HTTPService().put(
path: ApiEndpoints.updateScheduleByDeviceId
@ -284,7 +292,8 @@ class DevicesManagementApi {
}
}
Future<bool> editScheduleRecord(String uuid, ScheduleEntry newSchedule) async {
Future<bool> editScheduleRecord(
String uuid, ScheduleEntry newSchedule) async {
try {
final response = await HTTPService().put(
path: ApiEndpoints.scheduleByDeviceId.replaceAll('{deviceUuid}', uuid),
@ -335,4 +344,46 @@ class DevicesManagementApi {
return false;
}
}
static Future<Map<String, dynamic>> putDeviceName(
{required String deviceId, required String deviceName}) async {
try {
final response = await HTTPService().put(
path: ApiEndpoints.deviceByUuid.replaceAll('{deviceUuid}', deviceId),
body: {"deviceName": deviceName},
expectedResponseModel: (json) {
return json['data'];
},
);
return response;
} catch (e) {
rethrow;
}
}
static Future getDeviceInfo(String deviceId) async {
final response = await HTTPService().get(
path: ApiEndpoints.deviceByUuid.replaceAll('{deviceUuid}', deviceId),
showServerMessage: false,
expectedResponseModel: (json) {
return json['data'] as Map<String, dynamic>;
});
return response;
}
static Future resetDevice({
String? devicesUuid,
}) async {
final response = await HTTPService().post(
path: ApiEndpoints.resetDevice.replaceAll('{deviceUuid}', devicesUuid!),
showServerMessage: false,
body: {
"devicesUuid": [devicesUuid]
},
expectedResponseModel: (json) {
return json;
},
);
return response;
}
}

View File

@ -1,4 +1,3 @@
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/routines/bloc/automation_scene_trigger_bloc/automation_status_update.dart';
import 'package:syncrow_web/pages/routines/models/create_scene_and_autoamtion/create_automation_model.dart';
@ -6,7 +5,6 @@ import 'package:syncrow_web/pages/routines/models/create_scene_and_autoamtion/cr
import 'package:syncrow_web/pages/routines/models/icon_model.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/api/api_exception.dart';
import 'package:syncrow_web/services/api/http_service.dart';
import 'package:syncrow_web/utils/constants/api_const.dart';
@ -28,10 +26,9 @@ class SceneApi {
);
debugPrint('create scene response: $response');
return response;
} on DioException catch (e) {
String errorMessage =
e.response?.data['error']['message'][0] ?? 'something went wrong';
throw APIException(errorMessage);
} catch (e) {
debugPrint(e.toString());
rethrow;
}
}
@ -51,10 +48,9 @@ class SceneApi {
);
debugPrint('create automation response: $response');
return response;
} on DioException catch (e) {
String errorMessage =
e.response?.data['error']['message'][0] ?? 'something went wrong';
throw APIException(errorMessage);
} catch (e) {
debugPrint(e.toString());
rethrow;
}
}
@ -169,10 +165,8 @@ class SceneApi {
},
);
return response;
} on DioException catch (e) {
String errorMessage =
e.response?.data['error']['message'][0] ?? 'something went wrong';
throw APIException(errorMessage);
} catch (e) {
rethrow;
}
}
@ -191,10 +185,8 @@ class SceneApi {
},
);
return response;
} on DioException catch (e) {
String errorMessage =
e.response?.data['error']['message'][0] ?? 'something went wrong';
throw APIException(errorMessage);
} catch (e) {
rethrow;
}
}
@ -225,10 +217,8 @@ class SceneApi {
expectedResponseModel: (json) => json['statusCode'] == 200,
);
return response;
} on DioException catch (e) {
String errorMessage =
e.response?.data['error']['message'][0] ?? 'something went wrong';
throw APIException(errorMessage);
} catch (e) {
rethrow;
}
}
@ -246,10 +236,8 @@ class SceneApi {
expectedResponseModel: (json) => json['statusCode'] == 200,
);
return response;
} on DioException catch (e) {
String errorMessage =
e.response?.data['error']['message'][0] ?? 'something went wrong';
throw APIException(errorMessage);
} catch (e) {
rethrow;
}
}

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/device_managment/device_setting/settings_model/sub_space_model.dart';
import 'package:syncrow_web/pages/space_tree/model/pagination_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/create_subspace_model.dart';
@ -12,14 +13,16 @@ import 'package:syncrow_web/utils/constants/api_const.dart';
class CommunitySpaceManagementApi {
// Community Management APIs
Future<List<CommunityModel>> fetchCommunities(String projectId, {int page = 1}) async {
Future<List<CommunityModel>> fetchCommunities(String projectId,
{int page = 1}) async {
try {
List<CommunityModel> allCommunities = [];
bool hasNext = true;
while (hasNext) {
await HTTPService().get(
path: ApiEndpoints.getCommunityList.replaceAll('{projectId}', projectId),
path: ApiEndpoints.getCommunityList
.replaceAll('{projectId}', projectId),
queryParameters: {
'page': page,
},
@ -55,8 +58,14 @@ class CommunitySpaceManagementApi {
try {
bool hasNext = false;
await HTTPService().get(
path: ApiEndpoints.getCommunityList.replaceAll('{projectId}', projectId),
queryParameters: {'page': page, 'includeSpaces': true, 'size': 25, 'search': search},
path:
ApiEndpoints.getCommunityList.replaceAll('{projectId}', projectId),
queryParameters: {
'page': page,
'includeSpaces': true,
'size': 25,
'search': search
},
expectedResponseModel: (json) {
try {
List<dynamic> jsonData = json['data'] ?? [];
@ -68,7 +77,10 @@ class CommunitySpaceManagementApi {
page = currentPage + 1;
paginationModel = PaginationModel(
pageNum: page, hasNext: hasNext, size: 25, communities: communityList);
pageNum: page,
hasNext: hasNext,
size: 25,
communities: communityList);
return paginationModel;
} catch (_) {
hasNext = false;
@ -83,7 +95,8 @@ class CommunitySpaceManagementApi {
Future<CommunityModel?> getCommunityById(String communityId) async {
try {
final response = await HTTPService().get(
path: ApiEndpoints.getCommunityById.replaceAll('{communityId}', communityId),
path: ApiEndpoints.getCommunityById
.replaceAll('{communityId}', communityId),
expectedResponseModel: (json) {
return CommunityModel.fromJson(json['data']);
},
@ -95,7 +108,8 @@ class CommunitySpaceManagementApi {
}
}
Future<CommunityModel?> createCommunity(String name, String description, String projectId) async {
Future<CommunityModel?> createCommunity(
String name, String description, String projectId) async {
try {
final response = await HTTPService().post(
path: ApiEndpoints.createCommunity.replaceAll('{projectId}', projectId),
@ -114,7 +128,8 @@ class CommunitySpaceManagementApi {
}
}
Future<bool> updateCommunity(String communityId, String name, String projectId) async {
Future<bool> updateCommunity(
String communityId, String name, String projectId) async {
try {
final response = await HTTPService().put(
path: ApiEndpoints.updateCommunity
@ -151,7 +166,8 @@ class CommunitySpaceManagementApi {
}
}
Future<SpacesResponse> fetchSpaces(String communityId, String projectId) async {
Future<SpacesResponse> fetchSpaces(
String communityId, String projectId) async {
try {
final response = await HTTPService().get(
path: ApiEndpoints.listSpaces
@ -177,7 +193,8 @@ class CommunitySpaceManagementApi {
}
}
Future<SpaceModel?> getSpace(String communityId, String spaceId, String projectId) async {
Future<SpaceModel?> getSpace(
String communityId, String spaceId, String projectId) async {
try {
final response = await HTTPService().get(
path: ApiEndpoints.getSpace
@ -289,7 +306,8 @@ class CommunitySpaceManagementApi {
}
}
Future<bool> deleteSpace(String communityId, String spaceId, String projectId) async {
Future<bool> deleteSpace(
String communityId, String spaceId, String projectId) async {
try {
final response = await HTTPService().delete(
path: ApiEndpoints.deleteSpace
@ -307,15 +325,17 @@ class CommunitySpaceManagementApi {
}
}
Future<List<SpaceModel>> getSpaceHierarchy(String communityId, String projectId) async {
Future<List<SpaceModel>> getSpaceHierarchy(
String communityId, String projectId) async {
try {
final response = await HTTPService().get(
path: ApiEndpoints.getSpaceHierarchy
.replaceAll('{communityId}', communityId)
.replaceAll('{projectId}', projectId),
expectedResponseModel: (json) {
final spaceModels =
(json['data'] as List).map((spaceJson) => SpaceModel.fromJson(spaceJson)).toList();
final spaceModels = (json['data'] as List)
.map((spaceJson) => SpaceModel.fromJson(spaceJson))
.toList();
return spaceModels;
},
@ -327,15 +347,17 @@ class CommunitySpaceManagementApi {
}
}
Future<List<SpaceModel>> getSpaceOnlyWithDevices({String? communityId, String? projectId}) async {
Future<List<SpaceModel>> getSpaceOnlyWithDevices(
{String? communityId, String? projectId}) async {
try {
final response = await HTTPService().get(
path: ApiEndpoints.spaceOnlyWithDevices
.replaceAll('{communityId}', communityId!)
.replaceAll('{projectId}', projectId!),
expectedResponseModel: (json) {
final spaceModels =
(json['data'] as List).map((spaceJson) => SpaceModel.fromJson(spaceJson)).toList();
final spaceModels = (json['data'] as List)
.map((spaceJson) => SpaceModel.fromJson(spaceJson))
.toList();
return spaceModels;
},
);
@ -345,4 +367,59 @@ class CommunitySpaceManagementApi {
return [];
}
}
static Future<List<SubSpaceModel>> getSubSpaceBySpaceId(
{required String communityId,
required String spaceId,
required String projectId}) async {
try {
final path = ApiEndpoints.listSubspace
.replaceFirst('{communityUuid}', communityId)
.replaceFirst('{spaceUuid}', spaceId)
.replaceAll('{projectUuid}', projectId);
final response = await HTTPService().get(
path: path,
queryParameters: {"page": 1, "pageSize": 10},
showServerMessage: false,
expectedResponseModel: (json) {
List<SubSpaceModel> rooms = [];
if (json['data'] != null) {
for (var subspace in json['data']) {
rooms.add(SubSpaceModel.fromJson(subspace));
}
}
return rooms;
},
);
return response;
} catch (error, stackTrace) {
return [];
}
}
static Future<Map<String, dynamic>> assignDeviceToRoom(
{required String communityId,
required String spaceId,
required String subSpaceId,
required String deviceId,
required String projectId}) async {
try {
final response = await HTTPService().post(
path: ApiEndpoints.assignDeviceToRoom
.replaceAll('{projectUuid}', projectId)
.replaceAll('{communityUuid}', communityId)
.replaceAll('{spaceUuid}', spaceId)
.replaceAll('{subSpaceUuid}', subSpaceId)
.replaceAll('{deviceUuid}', deviceId),
expectedResponseModel: (json) {
return json;
},
);
return response;
} catch (e) {
rethrow;
}
}
}

View File

@ -83,4 +83,7 @@ abstract class ColorsManager {
static const Color maxPurpleDot = Color(0xFF5F00BD);
static const Color minBlue = Color(0xFF93AAFD);
static const Color minBlueDot = Color(0xFF023DFE);
static const Color grey25 = Color(0xFFF9F9F9);
}

View File

@ -60,9 +60,12 @@ abstract class ApiEndpoints {
'/devices/{uuid}/report-logs?code={code}&startTime={startTime}&endTime={endTime}';
static const String scheduleByDeviceId = '/schedule/{deviceUuid}';
static const String getScheduleByDeviceId = '/schedule/{deviceUuid}?category={category}';
static const String deleteScheduleByDeviceId = '/schedule/{deviceUuid}/{scheduleUuid}';
static const String updateScheduleByDeviceId = '/schedule/enable/{deviceUuid}';
static const String getScheduleByDeviceId =
'/schedule/{deviceUuid}?category={category}';
static const String deleteScheduleByDeviceId =
'/schedule/{deviceUuid}/{scheduleUuid}';
static const String updateScheduleByDeviceId =
'/schedule/enable/{deviceUuid}';
static const String factoryReset = '/devices/batch';
//product
@ -124,4 +127,13 @@ abstract class ApiEndpoints {
'/projects/{projectId}/communities/{communityId}/spaces/{unitUuid}/automations';
static const String spaceOnlyWithDevices =
'/projects/{projectId}/communities/{communityId}/spaces?onlyWithDevices=true';
static const String listSubspace =
'/projects/{projectUuid}/communities/{communityUuid}/spaces/{spaceUuid}/subspaces';
static const String deviceByUuid = '/devices/{deviceUuid}';
static const String resetDevice = '/factory/reset/{deviceUuid}';
static const String assignDeviceToRoom =
'/projects/{projectUuid}/communities/{communityUuid}/spaces/{spaceUuid}/subspaces/{subSpaceUuid}/devices/{deviceUuid}';
}

View File

@ -452,6 +452,13 @@ class Assets {
'assets/icons/refresh_status_icon.svg';
static const String energyConsumedIcon =
'assets/icons/energy_consumed_icon.svg';
static const String closeSettingsIcon =
'assets/icons/close_settings_icon.svg';
static const String editNameIconSettings =
'assets/icons/edit_name_icon_settings.svg';
static const String locationPin = 'assets/icons/location_pin.svg';
static const String aqiTemperature = 'assets/icons/aqi_temperature.svg';
static const String aqiHumidity = 'assets/icons/aqi_humidity.svg';

View File

@ -0,0 +1,45 @@
import 'package:flutter/material.dart';
class DefaultContainer extends StatelessWidget {
const DefaultContainer({
super.key,
required this.child,
this.height,
this.width,
this.color,
this.boxConstraints,
this.margin,
this.padding,
this.onTap,
this.borderRadius,
});
final double? height;
final double? width;
final Widget child;
final BoxConstraints? boxConstraints;
final EdgeInsets? margin;
final EdgeInsets? padding;
final Color? color;
final Function()? onTap;
final BorderRadius? borderRadius;
@override
Widget build(BuildContext context) {
return InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(20),
child: Container(
height: height,
width: width,
margin: margin ?? const EdgeInsets.only(right: 3, bottom: 3),
constraints: boxConstraints,
decoration: BoxDecoration(
color: color ?? Colors.white,
borderRadius: borderRadius ?? BorderRadius.circular(20),
),
padding: padding ?? const EdgeInsets.all(10),
child: child,
),
);
}
}