Compare commits

..

51 Commits

Author SHA1 Message Date
72ae3b1727 Refactor FactoryResetModel and MainDoorSensorBatchView
- Refactor FactoryResetModel to include 'operationType' in toJson and toMap methods.
- Refactor MainDoorSensorBatchView to use BlocProvider and Builder for better state management.
2025-04-16 15:06:50 +03:00
e0be44a507 Merged with dev 2025-04-16 04:00:07 +03:00
d4a7dd5854 Fixed design issues, added tag and location to the save dialog 2025-04-16 03:46:10 +03:00
50eb890d18 Merge pull request #142 from SyncrowIOT/SP-1278-FE-Allow-Simple-Edit-Delete
Added Ceiling Presence Sensor Device To Routine
2025-04-15 16:56:05 +03:00
9eefd522b7 bump flutter version on production github action. 2025-04-15 16:25:00 +03:00
4989a0e95c removed the use of Flexible that was causing an exception. 2025-04-15 16:24:46 +03:00
3c6b9f9ef4 Merge branch 'dev' of https://github.com/SyncrowIOT/web into dev 2025-04-15 16:12:03 +03:00
86b8771694 bump-v of web deployment action. 2025-04-15 16:12:01 +03:00
ea1d3d18c8 Merge pull request #143 from SyncrowIOT/SP-1189-Rework-Add-Button-Not-clickable-Opening-Pop-up-in-Community-Screen
Sp 1189 rework add button not clickable opening pop up in community screen
2025-04-15 15:55:36 +03:00
1bfab8cc76 SP-1189-Fix tapping ok and nothing happening bug by taking the action out of the widget. 2025-04-15 14:38:06 +03:00
7dcaa20da1 Enhanced the code and look of DialogFooter buttons. 2025-04-15 13:06:30 +03:00
616adccfdd Applied the correct scenario of tapping add community icon button. 2025-04-15 12:58:20 +03:00
abf6555485 Merge branch 'dev' of https://github.com/SyncrowIOT/web into SP-1278-FE-Allow-Simple-Edit-Delete 2025-04-15 12:03:25 +03:00
be0533645e Refactor dialog header text assignment in CeilingSensorDialog for clarity and readability. 2025-04-15 11:20:15 +03:00
254e03e3c7 Add mapping for 'sports_para' in slider range function 2025-04-15 11:19:58 +03:00
db1f29e2b2 Merged with dev 2025-04-15 11:18:43 +03:00
dba89027e3 Updated the if statement for the tag and the location 2025-04-15 11:09:48 +03:00
6bea4c2f4a Add mappings for moving_range and presence_range in slider helpers 2025-04-15 10:34:05 +03:00
e2ec986bb9 Refactor mappable stepped functions to a static constant for improved readability and maintainability 2025-04-15 10:03:47 +03:00
ceb1e1d23a Remove TODO comments. 2025-04-15 10:03:37 +03:00
ee12980b47 Fix value parsing in CpsDialogSliderSelector to ensure two decimal precision 2025-04-15 10:03:21 +03:00
4849bb41ba Added function to single card, included tag and location to cards 2025-04-15 02:13:00 +03:00
ebcd89d2a5 Refactor ceiling sensor functions and update slider helper mappings for improved value handling 2025-04-14 16:24:50 +03:00
a7bdbfe3ec Merge pull request #141 from SyncrowIOT/fix-timer-toggle-issue
Refactor AC device controls and toggle widget
2025-04-14 16:06:37 +03:00
db84a9aa5e fix a logic 2025-04-14 16:03:34 +03:00
1493e35f6a removed unused method. 2025-04-14 14:35:24 +03:00
f19cc616be moved CpsSliderHelpers to its own file. 2025-04-14 14:32:49 +03:00
06383018b9 refactored helpers into a helper class to release some complexity out of the widget. 2025-04-14 14:32:22 +03:00
9e3a78f6b7 Update dialog header to reflect sensor condition type dynamically 2025-04-14 12:10:42 +03:00
a27b2e758c Merge branch 'dev' of https://github.com/SyncrowIOT/web into SP-1278-FE-Allow-Simple-Edit-Delete 2025-04-14 12:06:42 +03:00
1023170788 Sort communities in create new routine dropdown. 2025-04-14 11:25:36 +03:00
140f4ff5e2 Refactor AC device controls and toggle widget 2025-04-14 09:57:25 +03:00
cbaeecc968 Merge pull request #139 from SyncrowIOT/SP-1189-FE-Add-Button-Not-clickable-Opening-Pop-up-in-Community-Screen
Sp 1189 fe add button not clickable opening pop up in community screen
2025-04-13 16:22:37 +03:00
2a95720cb0 Merge pull request #140 from SyncrowIOT/SP-1345-FE-SOS-Button-UI-Issue
Refactor SosDeviceControlsView
2025-04-13 16:20:55 +03:00
4fae2d6be0 Refactor SosDeviceControlsView 2025-04-13 16:19:16 +03:00
5b84076572 Merge pull request #138 from SyncrowIOT/SP-1192-FE-Implement-the-schedule-on-the-web-for-the-AC-device
Sp 1192 fe implement the schedule on the web for the ac device
2025-04-13 15:02:02 +03:00
9bf37243a6 remove unused code and make a limitation for the nobody time picker 2025-04-13 14:55:55 +03:00
acad0e8c9c SP-1189-FE-Add-Button-Not-clickable-Opening-Pop-up-in-Community-Screen 2025-04-13 14:50:07 +03:00
79f5ef7871 simplify space selection logic. 2025-04-13 13:12:48 +03:00
6493f02bcc simplify if statements readabaility. 2025-04-13 13:08:23 +03:00
c7c8898763 removed redundant code. 2025-04-13 12:52:38 +03:00
62ee9a72d6 moved SidebarHeader and SidebarAddCommunityButton to their own files. 2025-04-13 12:47:05 +03:00
55695ca5db refactor: improve readability and structure in SidebarWidget's space tile handling 2025-04-13 12:33:50 +03:00
c2f5a8df10 refactor: streamline context usage and improve readability in SidebarWidget 2025-04-13 12:31:17 +03:00
cd9821679e added trailing commas. 2025-04-13 12:29:38 +03:00
bfd3d4542e refactor: simplify onSearchChanged callback to use expressions. 2025-04-13 12:28:50 +03:00
35a99ccda7 removed unnecessary comments from SidebarWidget. 2025-04-13 12:27:04 +03:00
978934399e fixed invalid use of a private type in a public API in SidebarWidget. 2025-04-13 12:25:28 +03:00
bc32fe7941 removed unused method and its use. 2025-04-13 12:25:03 +03:00
cb6d50d367 remove unnecessary print statement 2025-04-13 12:21:35 +03:00
97eb1c152b Implement a countdown timer for the AC and fix bugs in the 'Forgot Password' 2025-04-13 12:18:56 +03:00
43 changed files with 1848 additions and 1282 deletions

View File

@ -25,7 +25,7 @@ jobs:
- name: Set up Flutter - name: Set up Flutter
uses: subosito/flutter-action@v2 uses: subosito/flutter-action@v2
with: with:
flutter-version: '3.22.2' # Specify the Flutter version you want to use flutter-version: '3.27.3' # Specify the Flutter version you want to use
- name: Install dependencies - name: Install dependencies
run: flutter pub get run: flutter pub get

View File

@ -25,7 +25,7 @@ jobs:
- name: Set up Flutter - name: Set up Flutter
uses: subosito/flutter-action@v2 uses: subosito/flutter-action@v2
with: with:
flutter-version: '3.22.2' # Specify the Flutter version you want to use flutter-version: '3.27.3' # Specify the Flutter version you want to use
- name: Install dependencies - name: Install dependencies
run: flutter pub get run: flutter pub get

View File

@ -0,0 +1,4 @@
<svg width="8" height="9" viewBox="0 0 8 9" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.13918 0.5H4.10294C4.0408 0.5 3.98117 0.524721 3.93722 0.568668L0.251783 4.25398C-0.0839278 4.58982 -0.0839278 5.13617 0.251783 5.47188L3.02848 8.24852C3.1906 8.41064 3.40686 8.49994 3.63734 8.5H3.6374C3.86794 8.5 4.0842 8.41064 4.24638 8.24846L7.9317 4.56308C7.97565 4.51914 8.00037 4.4595 8.00037 4.39736L8.00043 1.36113C8.00037 0.886312 7.61399 0.5 7.13918 0.5ZM7.53159 4.30031L3.91488 7.91702C3.84127 7.9907 3.74269 8.03122 3.6374 8.03122C3.53205 8.03122 3.43353 7.9907 3.35992 7.91708L0.583222 5.14045C0.43026 4.98748 0.43026 4.73845 0.583222 4.58542L4.19999 0.968775H7.13918C7.35556 0.968775 7.53165 1.14481 7.53165 1.36119L7.53159 4.30031Z" fill="#999999"/>
<path d="M5.93455 1.8291C5.73782 1.8291 5.55288 1.90577 5.41377 2.04487C5.27466 2.18392 5.19806 2.36886 5.19806 2.56559C5.19806 2.76232 5.27466 2.94726 5.41377 3.08637C5.55288 3.22548 5.73782 3.30208 5.93455 3.30208C6.13121 3.30208 6.31616 3.22548 6.45527 3.08637C6.59437 2.94726 6.67098 2.76232 6.67098 2.56559C6.67098 2.36886 6.59437 2.18392 6.45533 2.04487C6.31622 1.90577 6.13128 1.8291 5.93455 1.8291ZM6.12383 2.75487C6.07329 2.80547 6.00602 2.83331 5.93455 2.83331C5.86301 2.83331 5.79581 2.80547 5.74527 2.75487C5.69467 2.70433 5.66683 2.63707 5.66683 2.56559C5.66683 2.49412 5.69467 2.42685 5.74527 2.37631C5.79581 2.32571 5.86307 2.29788 5.93455 2.29788C6.00602 2.29788 6.07323 2.32571 6.12383 2.37631C6.17443 2.42685 6.20226 2.49412 6.20226 2.56559C6.20226 2.63707 6.17437 2.70433 6.12383 2.75487Z" fill="#999999"/>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -201,20 +201,17 @@ class ForgetPasswordWebPage extends StatelessWidget {
!state.isButtonEnabled && !state.isButtonEnabled &&
state.remainingTime != 1 state.remainingTime != 1
? null ? null
: () { :
() {
if (forgetBloc if (forgetBloc
.forgetEmailKey.currentState! .forgetEmailKey
.validate() || .currentState!
forgetBloc .validate()) {
.forgetRegionKey.currentState! forgetBloc.add(
.validate()) { StartTimerEvent());
if (forgetBloc
.forgetRegionKey.currentState!
.validate()) {
forgetBloc.add(StartTimerEvent());
}
} }
}, },
child: Text( child: Text(
'Get Code ${state is TimerState && !state.isButtonEnabled && state.remainingTime != 1 ? "(${forgetBloc.formattedTime(state.remainingTime)}) " : ""}', 'Get Code ${state is TimerState && !state.isButtonEnabled && state.remainingTime != 1 ? "(${forgetBloc.formattedTime(state.remainingTime)}) " : ""}',
style: TextStyle( style: TextStyle(

View File

@ -13,6 +13,7 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
late AcStatusModel deviceStatus; late AcStatusModel deviceStatus;
final String deviceId; final String deviceId;
Timer? _timer; Timer? _timer;
Timer? _countdownTimer;
AcBloc({required this.deviceId}) : super(AcsInitialState()) { AcBloc({required this.deviceId}) : super(AcsInitialState()) {
on<AcFetchDeviceStatusEvent>(_onFetchAcStatus); on<AcFetchDeviceStatusEvent>(_onFetchAcStatus);
@ -21,7 +22,16 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
on<AcBatchControlEvent>(_onAcBatchControl); on<AcBatchControlEvent>(_onAcBatchControl);
on<AcFactoryResetEvent>(_onFactoryReset); on<AcFactoryResetEvent>(_onFactoryReset);
on<AcStatusUpdated>(_onAcStatusUpdated); on<AcStatusUpdated>(_onAcStatusUpdated);
on<OnClose>(_onClose);
on<IncreaseTimeEvent>(_handleIncreaseTime);
on<DecreaseTimeEvent>(_handleDecreaseTime);
on<UpdateTimerEvent>(_handleUpdateTimer);
on<ToggleScheduleEvent>(_handleToggleTimer);
on<ApiCountdownValueEvent>(_handleApiCountdownValue);
} }
bool timerActive = false;
int scheduledHours = 0;
int scheduledMinutes = 0;
FutureOr<void> _onFetchAcStatus( FutureOr<void> _onFetchAcStatus(
AcFetchDeviceStatusEvent event, Emitter<AcsState> emit) async { AcFetchDeviceStatusEvent event, Emitter<AcsState> emit) async {
@ -30,8 +40,23 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
final status = final status =
await DevicesManagementApi().getDeviceStatus(event.deviceId); await DevicesManagementApi().getDeviceStatus(event.deviceId);
deviceStatus = AcStatusModel.fromJson(event.deviceId, status.status); deviceStatus = AcStatusModel.fromJson(event.deviceId, status.status);
if (deviceStatus.countdown1 != 0) {
// Convert API value to minutes
final totalMinutes = deviceStatus.countdown1 * 6;
scheduledHours = totalMinutes ~/ 60;
scheduledMinutes = totalMinutes % 60;
timerActive = true;
_startCountdownTimer(emit);
}
emit(ACStatusLoaded(
status: deviceStatus,
scheduledHours: scheduledHours,
scheduledMinutes: scheduledMinutes,
isTimerActive: timerActive,
));
_listenToChanges(event.deviceId); _listenToChanges(event.deviceId);
emit(ACStatusLoaded(deviceStatus));
} catch (e) { } catch (e) {
emit(AcsFailedState(error: e.toString())); emit(AcsFailedState(error: e.toString()));
} }
@ -70,31 +95,16 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
void _onAcStatusUpdated(AcStatusUpdated event, Emitter<AcsState> emit) { void _onAcStatusUpdated(AcStatusUpdated event, Emitter<AcsState> emit) {
deviceStatus = event.deviceStatus; deviceStatus = event.deviceStatus;
emit(ACStatusLoaded(deviceStatus)); emit(ACStatusLoaded(status: deviceStatus));
} }
// Future<void> testFirebaseConnection() async {
// // Reference to a test node in your database
// final testRef = FirebaseDatabase.instance.ref("test");
// // Write a test value
// await testRef.set("Hello, Firebase!");
// // Listen for changes on the test node
// testRef.onValue.listen((DatabaseEvent event) {
// final data = event.snapshot.value;
// print("Data from Firebase: $data");
// // If you see "Hello, Firebase!" printed in your console, it means the connection works.
// });
// }
FutureOr<void> _onAcControl( FutureOr<void> _onAcControl(
AcControlEvent event, Emitter<AcsState> emit) async { AcControlEvent event, Emitter<AcsState> emit) async {
final oldValue = _getValueByCode(event.code); final oldValue = _getValueByCode(event.code);
_updateLocalValue(event.code, event.value, emit); _updateLocalValue(event.code, event.value, emit);
emit(ACStatusLoaded(deviceStatus)); emit(ACStatusLoaded(status: deviceStatus));
await _runDebounce( await _runDebounce(
isBatch: false, isBatch: false,
@ -151,7 +161,7 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
void _revertValueAndEmit( void _revertValueAndEmit(
String deviceId, String code, dynamic oldValue, Emitter<AcsState> emit) { String deviceId, String code, dynamic oldValue, Emitter<AcsState> emit) {
_updateLocalValue(code, oldValue, emit); _updateLocalValue(code, oldValue, emit);
emit(ACStatusLoaded(deviceStatus)); emit(ACStatusLoaded(status: deviceStatus));
} }
void _updateLocalValue(String code, dynamic value, Emitter<AcsState> emit) { void _updateLocalValue(String code, dynamic value, Emitter<AcsState> emit) {
@ -184,11 +194,16 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
if (value is bool) { if (value is bool) {
deviceStatus = deviceStatus.copyWith(childLock: value); deviceStatus = deviceStatus.copyWith(childLock: value);
} }
case 'countdown_time':
if (value is int) {
deviceStatus = deviceStatus.copyWith(countdown1: value);
}
break; break;
default: default:
break; break;
} }
emit(ACStatusLoaded(deviceStatus)); emit(ACStatusLoaded(status: deviceStatus));
} }
dynamic _getValueByCode(String code) { dynamic _getValueByCode(String code) {
@ -203,6 +218,8 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
return deviceStatus.fanSpeedsString; return deviceStatus.fanSpeedsString;
case 'child_lock': case 'child_lock':
return deviceStatus.childLock; return deviceStatus.childLock;
case 'countdown_time':
return deviceStatus.countdown1;
default: default:
return null; return null;
} }
@ -216,7 +233,7 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
await DevicesManagementApi().getBatchStatus(event.devicesIds); await DevicesManagementApi().getBatchStatus(event.devicesIds);
deviceStatus = deviceStatus =
AcStatusModel.fromJson(event.devicesIds.first, status.status); AcStatusModel.fromJson(event.devicesIds.first, status.status);
emit(ACStatusLoaded(deviceStatus)); emit(ACStatusLoaded(status: deviceStatus));
} catch (e) { } catch (e) {
emit(AcsFailedState(error: e.toString())); emit(AcsFailedState(error: e.toString()));
} }
@ -228,7 +245,7 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
_updateLocalValue(event.code, event.value, emit); _updateLocalValue(event.code, event.value, emit);
emit(ACStatusLoaded(deviceStatus)); emit(ACStatusLoaded(status: deviceStatus));
await _runDebounce( await _runDebounce(
isBatch: true, isBatch: true,
@ -257,4 +274,144 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
emit(AcsFailedState(error: e.toString())); emit(AcsFailedState(error: e.toString()));
} }
} }
void _onClose(OnClose event, Emitter<AcsState> emit) {
_countdownTimer?.cancel();
_timer?.cancel();
}
void _handleIncreaseTime(IncreaseTimeEvent event, Emitter<AcsState> emit) {
if (state is! ACStatusLoaded) return;
final currentState = state as ACStatusLoaded;
int newHours = scheduledHours;
int newMinutes = scheduledMinutes + 30;
newHours += newMinutes ~/ 60;
newMinutes = newMinutes % 60;
if (newHours > 23) {
newHours = 23;
newMinutes = 59;
}
scheduledHours = newHours;
scheduledMinutes = newMinutes;
emit(currentState.copyWith(
scheduledHours: scheduledHours,
scheduledMinutes: scheduledMinutes,
));
}
void _handleDecreaseTime(DecreaseTimeEvent event, Emitter<AcsState> emit) {
if (state is! ACStatusLoaded) return;
final currentState = state as ACStatusLoaded;
int totalMinutes = (scheduledHours * 60) + scheduledMinutes;
totalMinutes = (totalMinutes - 30).clamp(0, 1440);
scheduledHours = totalMinutes ~/ 60;
scheduledMinutes = totalMinutes % 60;
emit(currentState.copyWith(
scheduledHours: scheduledHours,
scheduledMinutes: scheduledMinutes,
));
}
Future<void> _handleToggleTimer(
ToggleScheduleEvent event, Emitter<AcsState> emit) async {
if (state is! ACStatusLoaded) return;
final currentState = state as ACStatusLoaded;
timerActive = !timerActive;
if (timerActive) {
final totalMinutes = scheduledHours * 60 + scheduledMinutes;
if (totalMinutes <= 0) {
timerActive = false;
emit(currentState.copyWith(isTimerActive: timerActive));
return;
}
try {
final scaledValue = totalMinutes ~/ 6;
await _runDebounce(
isBatch: false,
deviceId: deviceId,
code: 'countdown_time',
value: scaledValue,
oldValue: scaledValue,
emit: emit,
);
_startCountdownTimer(emit);
emit(currentState.copyWith(isTimerActive: timerActive));
} catch (e) {
timerActive = false;
emit(AcsFailedState(error: e.toString()));
}
} else {
await _runDebounce(
isBatch: false,
deviceId: deviceId,
code: 'countdown_time',
value: 0,
oldValue: 0,
emit: emit,
);
_countdownTimer?.cancel();
scheduledHours = 0;
scheduledMinutes = 0;
emit(currentState.copyWith(
isTimerActive: timerActive,
scheduledHours: 0,
scheduledMinutes: 0,
));
}
}
void _startCountdownTimer(Emitter<AcsState> emit) {
_countdownTimer?.cancel();
int totalSeconds = (scheduledHours * 3600) + (scheduledMinutes * 60);
_countdownTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
if (totalSeconds > 0) {
totalSeconds--;
scheduledHours = totalSeconds ~/ 3600;
scheduledMinutes = (totalSeconds % 3600) ~/ 60;
add(UpdateTimerEvent());
} else {
_countdownTimer?.cancel();
timerActive = false;
scheduledHours = 0;
scheduledMinutes = 0;
add(TimerCompletedEvent());
}
});
}
void _handleUpdateTimer(UpdateTimerEvent event, Emitter<AcsState> emit) {
if (state is ACStatusLoaded) {
final currentState = state as ACStatusLoaded;
emit(currentState.copyWith(
scheduledHours: scheduledHours,
scheduledMinutes: scheduledMinutes,
isTimerActive: timerActive,
));
}
}
void _handleApiCountdownValue(
ApiCountdownValueEvent event, Emitter<AcsState> emit) {
if (state is ACStatusLoaded) {
final totalMinutes = event.apiValue * 6;
final scheduledHours = totalMinutes ~/ 60;
scheduledMinutes = totalMinutes % 60;
_startCountdownTimer(
emit,
);
add(UpdateTimerEvent());
}
}
@override
Future<void> close() {
add(OnClose());
return super.close();
}
} }

View File

@ -8,6 +8,7 @@ sealed class AcsEvent extends Equatable {
@override @override
List<Object> get props => []; List<Object> get props => [];
} }
class AcUpdated extends AcsEvent {} class AcUpdated extends AcsEvent {}
class AcFetchDeviceStatusEvent extends AcsEvent { class AcFetchDeviceStatusEvent extends AcsEvent {
@ -18,10 +19,12 @@ class AcFetchDeviceStatusEvent extends AcsEvent {
@override @override
List<Object> get props => [deviceId]; List<Object> get props => [deviceId];
} }
class AcStatusUpdated extends AcsEvent { class AcStatusUpdated extends AcsEvent {
final AcStatusModel deviceStatus; final AcStatusModel deviceStatus;
AcStatusUpdated(this.deviceStatus); AcStatusUpdated(this.deviceStatus);
} }
class AcFetchBatchStatusEvent extends AcsEvent { class AcFetchBatchStatusEvent extends AcsEvent {
final List<String> devicesIds; final List<String> devicesIds;
@ -73,3 +76,30 @@ class AcFactoryResetEvent extends AcsEvent {
@override @override
List<Object> get props => [deviceId, factoryResetModel]; List<Object> get props => [deviceId, factoryResetModel];
} }
class OnClose extends AcsEvent {}
class IncreaseTimeEvent extends AcsEvent {
@override
List<Object> get props => [];
}
class DecreaseTimeEvent extends AcsEvent {
@override
List<Object> get props => [];
}
class ToggleScheduleEvent extends AcsEvent {}
class TimerCompletedEvent extends AcsEvent {}
class UpdateTimerEvent extends AcsEvent {
}
class ApiCountdownValueEvent extends AcsEvent {
final int apiValue;
const ApiCountdownValueEvent(this.apiValue);
}

View File

@ -2,8 +2,9 @@ import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/device_managment/ac/model/ac_model.dart'; import 'package:syncrow_web/pages/device_managment/ac/model/ac_model.dart';
abstract class AcsState extends Equatable { abstract class AcsState extends Equatable {
const AcsState(); final bool isTimerActive;
const AcsState({this.isTimerActive = false});
@override @override
List<Object> get props => []; List<Object> get props => [];
} }
@ -15,8 +16,30 @@ class AcsLoadingState extends AcsState {}
class ACStatusLoaded extends AcsState { class ACStatusLoaded extends AcsState {
final AcStatusModel status; final AcStatusModel status;
final DateTime timestamp; final DateTime timestamp;
final int scheduledHours;
final int scheduledMinutes;
final bool isTimerActive;
ACStatusLoaded(this.status) : timestamp = DateTime.now(); ACStatusLoaded({
required this.status,
this.scheduledHours = 0,
this.scheduledMinutes = 0,
this.isTimerActive = false,
}) : timestamp = DateTime.now();
ACStatusLoaded copyWith({
AcStatusModel? status,
int? scheduledHours,
int? scheduledMinutes,
bool? isTimerActive,
int? remainingTime,
}) {
return ACStatusLoaded(
status: status ?? this.status,
scheduledHours: scheduledHours ?? this.scheduledHours,
scheduledMinutes: scheduledMinutes ?? this.scheduledMinutes,
isTimerActive: isTimerActive ?? this.isTimerActive,
);
}
@override @override
List<Object> get props => [status, timestamp]; List<Object> get props => [status, timestamp];
@ -40,3 +63,14 @@ class AcsFailedState extends AcsState {
@override @override
List<Object> get props => [error]; List<Object> get props => [error];
} }
class TimerRunInProgress extends AcsState {
final int remainingTime;
const TimerRunInProgress(this.remainingTime);
@override
List<Object> get props => [remainingTime];
}

View File

@ -11,6 +11,7 @@ class AcStatusModel {
final bool childLock; final bool childLock;
final TempModes acMode; final TempModes acMode;
final FanSpeeds acFanSpeed; final FanSpeeds acFanSpeed;
final int countdown1;
AcStatusModel({ AcStatusModel({
required this.uuid, required this.uuid,
@ -18,6 +19,7 @@ class AcStatusModel {
required this.modeString, required this.modeString,
required this.tempSet, required this.tempSet,
required this.currentTemp, required this.currentTemp,
required this.countdown1,
required this.fanSpeedsString, required this.fanSpeedsString,
required this.childLock, required this.childLock,
}) : acMode = getACMode(modeString), }) : acMode = getACMode(modeString),
@ -30,6 +32,7 @@ class AcStatusModel {
late int currentTemp; late int currentTemp;
late String fanSpeeds; late String fanSpeeds;
late bool childLock; late bool childLock;
late int _countdown1 = 0;
for (var status in jsonList) { for (var status in jsonList) {
switch (status.code) { switch (status.code) {
@ -51,6 +54,9 @@ class AcStatusModel {
case 'child_lock': case 'child_lock':
childLock = status.value ?? false; childLock = status.value ?? false;
break; break;
case 'countdown_time':
_countdown1 = status.value ?? 0;
break;
} }
} }
@ -62,6 +68,7 @@ class AcStatusModel {
currentTemp: currentTemp, currentTemp: currentTemp,
fanSpeedsString: fanSpeeds, fanSpeedsString: fanSpeeds,
childLock: childLock, childLock: childLock,
countdown1: _countdown1,
); );
} }
@ -73,6 +80,7 @@ class AcStatusModel {
int? currentTemp, int? currentTemp,
String? fanSpeedsString, String? fanSpeedsString,
bool? childLock, bool? childLock,
int? countdown1,
}) { }) {
return AcStatusModel( return AcStatusModel(
uuid: uuid ?? this.uuid, uuid: uuid ?? this.uuid,
@ -82,6 +90,7 @@ class AcStatusModel {
currentTemp: currentTemp ?? this.currentTemp, currentTemp: currentTemp ?? this.currentTemp,
fanSpeedsString: fanSpeedsString ?? this.fanSpeedsString, fanSpeedsString: fanSpeedsString ?? this.fanSpeedsString,
childLock: childLock ?? this.childLock, childLock: childLock ?? this.childLock,
countdown1: countdown1 ?? this.countdown1,
); );
} }

View File

@ -10,11 +10,10 @@ import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_mo
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart'; import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
class AcDeviceControlsView extends StatelessWidget with HelperResponsiveLayout { class AcDeviceControlsView extends StatelessWidget with HelperResponsiveLayout {
const AcDeviceControlsView({super.key, required this.device}); const AcDeviceControlsView({super.key, required this.device});
final AllDevicesModel device; final AllDevicesModel device;
@ -23,11 +22,13 @@ class AcDeviceControlsView extends StatelessWidget with HelperResponsiveLayout {
final isExtraLarge = isExtraLargeScreenSize(context); final isExtraLarge = isExtraLargeScreenSize(context);
final isLarge = isLargeScreenSize(context); final isLarge = isLargeScreenSize(context);
final isMedium = isMediumScreenSize(context); final isMedium = isMediumScreenSize(context);
return BlocProvider( return BlocProvider(
create: (context) => AcBloc(deviceId: device.uuid!) create: (context) => AcBloc(deviceId: device.uuid!)
..add(AcFetchDeviceStatusEvent(device.uuid!)), ..add(AcFetchDeviceStatusEvent(device.uuid!)),
child: BlocBuilder<AcBloc, AcsState>( child: BlocBuilder<AcBloc, AcsState>(
builder: (context, state) { builder: (context, state) {
final acBloc = BlocProvider.of<AcBloc>(context);
if (state is ACStatusLoaded) { if (state is ACStatusLoaded) {
return GridView( return GridView(
padding: const EdgeInsets.symmetric(horizontal: 50), padding: const EdgeInsets.symmetric(horizontal: 50),
@ -78,56 +79,101 @@ class AcDeviceControlsView extends StatelessWidget with HelperResponsiveLayout {
), ),
ToggleWidget( ToggleWidget(
label: '', label: '',
labelWidget: Row( labelWidget: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly, crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
IconButton( Container(
padding: const EdgeInsets.all(0), width: MediaQuery.of(context).size.width,
onPressed: () {}, decoration: const ShapeDecoration(
icon: const Icon( color: ColorsManager.primaryColor,
Icons.remove, shape: RoundedRectangleBorder(
size: 28, borderRadius: BorderRadius.all(Radius.circular(30)),
color: ColorsManager.greyColor, ),
), ),
), ),
Text( Center(
'06', child: SizedBox(
style: context.textTheme.titleLarge!.copyWith( child: Row(
color: ColorsManager.dialogBlueTitle, mainAxisAlignment: MainAxisAlignment.spaceEvenly,
fontWeight: FontWeight.bold, children: [
), IconButton(
), onPressed: () {
Text( if (acBloc.timerActive == false) {
'h', context
style: context.textTheme.bodySmall! .read<AcBloc>()
.copyWith(color: ColorsManager.blackColor), .add(DecreaseTimeEvent());
), }
Text( },
'30', icon: const Icon(Icons.remove,
style: context.textTheme.titleLarge!.copyWith( color: ColorsManager.greyColor),
color: ColorsManager.dialogBlueTitle, ),
fontWeight: FontWeight.bold, Text(
), acBloc.scheduledHours
), .toString()
Text('m', .padLeft(2, '0'),
style: context.textTheme.bodySmall! style: Theme.of(context)
.copyWith(color: ColorsManager.blackColor)), .textTheme
IconButton( .titleLarge!
padding: const EdgeInsets.all(0), .copyWith(
onPressed: () {}, color: ColorsManager.dialogBlueTitle,
icon: const Icon( fontWeight: FontWeight.bold,
Icons.add, ),
size: 28, ),
color: ColorsManager.greyColor, Text(
'h',
style: Theme.of(context)
.textTheme
.bodySmall!
.copyWith(
color: ColorsManager.blackColor,
),
),
Text(
acBloc.scheduledMinutes
.toString()
.padLeft(2, '0'),
style: Theme.of(context)
.textTheme
.titleLarge!
.copyWith(
color: ColorsManager.dialogBlueTitle,
fontWeight: FontWeight.bold,
),
),
Text(
'm',
style: Theme.of(context)
.textTheme
.bodySmall!
.copyWith(
color: ColorsManager.blackColor,
),
),
IconButton(
onPressed: () {
if (acBloc.timerActive == false) {
context
.read<AcBloc>()
.add(IncreaseTimeEvent());
}
},
icon: const Icon(Icons.add,
color: ColorsManager.greyColor),
),
],
),
), ),
), ),
], ],
), ),
value: false, value: acBloc.timerActive,
code: 'ac_schedule', code: 'ac_schedule',
deviceId: device.uuid!, deviceId: device.uuid!,
icon: Assets.acSchedule, icon: Assets.acSchedule,
onChange: (value) {}, onChange: (value) {
context.read<AcBloc>().add(ToggleScheduleEvent());
},
), ),
ToggleWidget( ToggleWidget(
deviceId: device.uuid!, deviceId: device.uuid!,

View File

@ -0,0 +1,18 @@
class DeviceSubSpace {
String? id;
String? createdAt;
String? updatedAt;
String? subspaceName;
bool? disabled;
DeviceSubSpace({this.id, this.createdAt, this.updatedAt, this.subspaceName, this.disabled});
DeviceSubSpace.fromJson(Map<String, dynamic> json) {
id = json['uuid']?.toString() ?? '';
createdAt = json['createdAt']?.toString() ?? '';
updatedAt = json['updatedAt']?.toString() ?? '';
subspaceName = json['subspaceName']?.toString() ?? '';
subspaceName = json['subspaceName']?.toString() ?? '';
disabled = json['disabled'] ?? false;
}
}

View File

@ -0,0 +1,20 @@
class DeviceTagModel {
String? id;
String? createdAt;
String? updatedAt;
String? name;
DeviceTagModel({
this.id,
this.createdAt,
this.updatedAt,
this.name,
});
DeviceTagModel.fromJson(Map<String, dynamic> json) {
id = json['uuid']?.toString() ?? '';
createdAt = json['createdAt']?.toString() ?? '';
updatedAt = json['updatedAt']?.toString() ?? '';
name = json['name']?.toString() ?? '';
}
}

View File

@ -1,6 +1,8 @@
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_community.model.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/device_community.model.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_space_model.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/device_space_model.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_sub_space.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/device_subspace.model.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_tag_model.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/room.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/room.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/unit.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/unit.dart';
import 'package:syncrow_web/pages/routines/models/ac/ac_function.dart'; import 'package:syncrow_web/pages/routines/models/ac/ac_function.dart';
@ -79,38 +81,41 @@ class AllDevicesModel {
int? batteryLevel; int? batteryLevel;
String? productName; String? productName;
List<DeviceSpaceModel>? spaces; List<DeviceSpaceModel>? spaces;
List<DeviceTagModel>? deviceTags;
DeviceSubSpace? deviceSubSpace;
AllDevicesModel({ AllDevicesModel(
this.room, {this.room,
this.subspace, this.subspace,
this.unit, this.unit,
this.community, this.community,
this.productUuid, this.productUuid,
this.productType, this.productType,
this.permissionType, this.permissionType,
this.activeTime, this.activeTime,
this.category, this.category,
this.categoryName, this.categoryName,
this.createTime, this.createTime,
this.gatewayId, this.gatewayId,
this.icon, this.icon,
this.ip, this.ip,
this.lat, this.lat,
this.localKey, this.localKey,
this.lon, this.lon,
this.model, this.model,
this.name, this.name,
this.nodeId, this.nodeId,
this.online, this.online,
this.ownerId, this.ownerId,
this.sub, this.sub,
this.timeZone, this.timeZone,
this.updateTime, this.updateTime,
this.uuid, this.uuid,
this.batteryLevel, this.batteryLevel,
this.productName, this.productName,
this.spaces, this.spaces,
}); this.deviceTags,
this.deviceSubSpace});
AllDevicesModel.fromJson(Map<String, dynamic> json) { AllDevicesModel.fromJson(Map<String, dynamic> json) {
room = (json['room'] != null && (json['room'] is Map)) room = (json['room'] != null && (json['room'] is Map))
@ -148,12 +153,15 @@ class AllDevicesModel {
updateTime = int.tryParse(json['updateTime']?.toString() ?? ''); updateTime = int.tryParse(json['updateTime']?.toString() ?? '');
uuid = json['uuid']?.toString(); uuid = json['uuid']?.toString();
batteryLevel = int.tryParse(json['battery']?.toString() ?? ''); batteryLevel = int.tryParse(json['battery']?.toString() ?? '');
productName = json['productName']?.toString(); productName = json['productName']?.toString();
deviceTags = json['deviceTag'] != null && json['deviceTag'] is List
? (json['deviceTag'] as List).map((tag) => DeviceTagModel.fromJson(tag)).toList()
: [];
deviceSubSpace = json['subspace'] != null
? DeviceSubSpace.fromJson(json['subspace'])
: DeviceSubSpace(subspaceName: '');
if (json['spaces'] != null && json['spaces'] is List) { if (json['spaces'] != null && json['spaces'] is List) {
spaces = (json['spaces'] as List) spaces = (json['spaces'] as List).map((space) => DeviceSpaceModel.fromJson(space)).toList();
.map((space) => DeviceSpaceModel.fromJson(space))
.toList();
} }
} }
@ -201,8 +209,7 @@ SOS
String tempIcon = ''; String tempIcon = '';
if (type == DeviceType.LightBulb) { if (type == DeviceType.LightBulb) {
tempIcon = Assets.lightBulb; tempIcon = Assets.lightBulb;
} else if (type == DeviceType.CeilingSensor || } else if (type == DeviceType.CeilingSensor || type == DeviceType.WallSensor) {
type == DeviceType.WallSensor) {
tempIcon = Assets.sensors; tempIcon = Assets.sensors;
} else if (type == DeviceType.AC) { } else if (type == DeviceType.AC) {
tempIcon = Assets.ac; tempIcon = Assets.ac;
@ -232,8 +239,6 @@ SOS
// tempIcon = Assets.gang3touch; // tempIcon = Assets.gang3touch;
} else if (type == DeviceType.WaterLeak) { } else if (type == DeviceType.WaterLeak) {
tempIcon = Assets.waterLeakNormal; tempIcon = Assets.waterLeakNormal;
} else if (type == DeviceType.WaterLeak) {
tempIcon = Assets.waterLeakNormal;
} else { } else {
tempIcon = Assets.logoHorizontal; tempIcon = Assets.logoHorizontal;
} }
@ -249,76 +254,51 @@ SOS
switch (productType) { switch (productType) {
case 'AC': case 'AC':
return [ return [
SwitchFunction( SwitchFunction(deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'),
deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'), ModeFunction(deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'),
ModeFunction( TempSetFunction(deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'),
deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'), CurrentTempFunction(deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'),
TempSetFunction( LevelFunction(deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'),
deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'), ChildLockFunction(deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'),
CurrentTempFunction(
deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'),
LevelFunction(
deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'),
ChildLockFunction(
deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'),
]; ];
case '1G': case '1G':
return [ return [
OneGangSwitchFunction(deviceId: uuid ?? '', deviceName: name ?? ''), OneGangSwitchFunction(deviceId: uuid ?? '', deviceName: name ?? ''),
OneGangCountdownFunction( OneGangCountdownFunction(deviceId: uuid ?? '', deviceName: name ?? ''),
deviceId: uuid ?? '', deviceName: name ?? ''),
]; ];
case '2G': case '2G':
return [ return [
TwoGangSwitch1Function(deviceId: uuid ?? '', deviceName: name ?? ''), TwoGangSwitch1Function(deviceId: uuid ?? '', deviceName: name ?? ''),
TwoGangSwitch2Function(deviceId: uuid ?? '', deviceName: name ?? ''), TwoGangSwitch2Function(deviceId: uuid ?? '', deviceName: name ?? ''),
TwoGangCountdown1Function( TwoGangCountdown1Function(deviceId: uuid ?? '', deviceName: name ?? ''),
deviceId: uuid ?? '', deviceName: name ?? ''), TwoGangCountdown2Function(deviceId: uuid ?? '', deviceName: name ?? ''),
TwoGangCountdown2Function(
deviceId: uuid ?? '', deviceName: name ?? ''),
]; ];
case '3G': case '3G':
return [ return [
ThreeGangSwitch1Function( ThreeGangSwitch1Function(deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'),
deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'), ThreeGangSwitch2Function(deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'),
ThreeGangSwitch2Function( ThreeGangSwitch3Function(deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'),
deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'), ThreeGangCountdown1Function(deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'),
ThreeGangSwitch3Function( ThreeGangCountdown2Function(deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'),
deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'), ThreeGangCountdown3Function(deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'),
ThreeGangCountdown1Function(
deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'),
ThreeGangCountdown2Function(
deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'),
ThreeGangCountdown3Function(
deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'),
]; ];
case 'WPS': case 'WPS':
return [ return [
//IF Functions //IF Functions
PresenceStateFunction( PresenceStateFunction(deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'),
deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'), CurrentDistanceFunction(deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'),
CurrentDistanceFunction( IlluminanceValueFunction(deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'),
deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'), PresenceTimeFunction(deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'),
IlluminanceValueFunction(
deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'),
PresenceTimeFunction(
deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'),
//THEN Functions //THEN Functions
FarDetectionFunction( FarDetectionFunction(deviceId: uuid ?? '', deviceName: name ?? '', type: 'THEN'),
deviceId: uuid ?? '', deviceName: name ?? '', type: 'THEN'), MotionSensitivityFunction(deviceId: uuid ?? '', deviceName: name ?? '', type: 'THEN'),
MotionSensitivityFunction( MotionLessSensitivityFunction(deviceId: uuid ?? '', deviceName: name ?? '', type: 'THEN'),
deviceId: uuid ?? '', deviceName: name ?? '', type: 'THEN'), IndicatorFunction(deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'),
MotionLessSensitivityFunction( NoOneTimeFunction(deviceId: uuid ?? '', deviceName: name ?? '', type: 'THEN'),
deviceId: uuid ?? '', deviceName: name ?? '', type: 'THEN'),
IndicatorFunction(
deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'),
NoOneTimeFunction(
deviceId: uuid ?? '', deviceName: name ?? '', type: 'THEN'),
]; ];
case 'GW': case 'GW':
return [ return [

View File

@ -19,6 +19,7 @@ class FactoryResetModel {
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
return { return {
'devicesUuid': devicesUuid, 'devicesUuid': devicesUuid,
'operationType': operationType,
}; };
} }
@ -33,6 +34,7 @@ class FactoryResetModel {
Map<String, dynamic> toMap() { Map<String, dynamic> toMap() {
return { return {
'devicesUuid': devicesUuid, 'devicesUuid': devicesUuid,
'operationType': operationType,
}; };
} }
@ -56,3 +58,4 @@ class FactoryResetModel {
@override @override
int get hashCode => devicesUuid.hashCode; int get hashCode => devicesUuid.hashCode;
} }

View File

@ -4,7 +4,6 @@ import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_re
import 'package:syncrow_web/pages/device_managment/main_door_sensor/bloc/main_door_sensor_bloc.dart'; import 'package:syncrow_web/pages/device_managment/main_door_sensor/bloc/main_door_sensor_bloc.dart';
import 'package:syncrow_web/pages/device_managment/main_door_sensor/bloc/main_door_sensor_event.dart'; import 'package:syncrow_web/pages/device_managment/main_door_sensor/bloc/main_door_sensor_event.dart';
import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart'; import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart';
// import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
class MainDoorSensorBatchView extends StatelessWidget { class MainDoorSensorBatchView extends StatelessWidget {
const MainDoorSensorBatchView({super.key, required this.devicesIds}); const MainDoorSensorBatchView({super.key, required this.devicesIds});
@ -13,35 +12,31 @@ class MainDoorSensorBatchView extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Row( return BlocProvider(
mainAxisAlignment: MainAxisAlignment.center, create: (context) => MainDoorSensorBloc(),
children: [ child: Builder(
// SizedBox( builder: (innerContext) {
// width: 170, return Row(
// height: 140, mainAxisAlignment: MainAxisAlignment.center,
// child: FirmwareUpdateWidget( children: [
// deviceId: devicesIds.first, SizedBox(
// version: 12, width: 170,
// ), height: 140,
// ), child: FactoryResetWidget(
// const SizedBox( callFactoryReset: () {
// width: 12, BlocProvider.of<MainDoorSensorBloc>(innerContext).add(
// ), MainDoorSensorFactoryReset(
SizedBox( deviceId: devicesIds.first,
width: 170, factoryReset: FactoryResetModel(devicesUuid: devicesIds),
height: 140, ),
child: FactoryResetWidget( );
callFactoryReset: () { },
BlocProvider.of<MainDoorSensorBloc>(context).add(
MainDoorSensorFactoryReset(
deviceId: devicesIds.first,
factoryReset: FactoryResetModel(devicesUuid: devicesIds),
), ),
); ),
}, ],
), );
), },
], ),
); );
} }
} }

View File

@ -62,9 +62,6 @@ class ToggleWidget extends StatelessWidget {
)), )),
if (showToggle) if (showToggle)
Container( Container(
height: 20,
width: 35,
padding: const EdgeInsets.only(right: 16, top: 10),
child: CupertinoSwitch( child: CupertinoSwitch(
value: value, value: value,
activeColor: ColorsManager.dialogBlueTitle, activeColor: ColorsManager.dialogBlueTitle,

View File

@ -13,7 +13,8 @@ import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_la
import '../models/sos_status_model.dart'; import '../models/sos_status_model.dart';
class SosDeviceControlsView extends StatelessWidget with HelperResponsiveLayout { class SosDeviceControlsView extends StatelessWidget
with HelperResponsiveLayout {
const SosDeviceControlsView({ const SosDeviceControlsView({
super.key, super.key,
required this.device, required this.device,
@ -24,7 +25,8 @@ class SosDeviceControlsView extends StatelessWidget with HelperResponsiveLayout
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider( return BlocProvider(
create: (context) => SosDeviceBloc()..add(GetDeviceStatus(device.uuid!)), create: (context) =>
SosDeviceBloc()..add(GetDeviceStatus(device.uuid!)),
child: BlocBuilder<SosDeviceBloc, SosDeviceState>( child: BlocBuilder<SosDeviceBloc, SosDeviceState>(
builder: (context, state) { builder: (context, state) {
if (state is SosDeviceLoadingState) { if (state is SosDeviceLoadingState) {
@ -63,7 +65,8 @@ class SosDeviceControlsView extends StatelessWidget with HelperResponsiveLayout
)); ));
} }
Widget _buildStatusControls(BuildContext context, SosStatusModel deviceStatus) { Widget _buildStatusControls(
BuildContext context, SosStatusModel deviceStatus) {
final isExtraLarge = isExtraLargeScreenSize(context); final isExtraLarge = isExtraLargeScreenSize(context);
final isLarge = isLargeScreenSize(context); final isLarge = isLargeScreenSize(context);
final isMedium = isMediumScreenSize(context); final isMedium = isMediumScreenSize(context);
@ -85,7 +88,7 @@ class SosDeviceControlsView extends StatelessWidget with HelperResponsiveLayout
IconNameStatusContainer( IconNameStatusContainer(
isFullIcon: false, isFullIcon: false,
name: deviceStatus.sosStatus == 'sos' ? 'SOS' : 'Normal', name: deviceStatus.sosStatus == 'sos' ? 'SOS' : 'Normal',
icon: deviceStatus.sosStatus == 'sos' ? Assets.sos : Assets.sosNormal, icon: deviceStatus.sosStatus == 'sos' ? Assets.sosNormal : Assets.sos,
onTap: () {}, onTap: () {},
status: false, status: false,
textColor: ColorsManager.blackColor, textColor: ColorsManager.blackColor,

View File

@ -30,6 +30,7 @@ class FunctionBloc extends Bloc<FunctionBlocEvent, FunctionBlocState> {
condition: event.functionData.condition ?? existingData.condition, condition: event.functionData.condition ?? existingData.condition,
); );
} else { } else {
functions.clear();
functions.add(event.functionData); functions.add(event.functionData);
} }

View File

@ -64,8 +64,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
TriggerSwitchTabsEvent event, TriggerSwitchTabsEvent event,
Emitter<RoutineState> emit, Emitter<RoutineState> emit,
) { ) {
emit(state.copyWith( emit(state.copyWith(routineTab: event.isRoutineTab, createRoutineView: false));
routineTab: event.isRoutineTab, createRoutineView: false));
add(ResetRoutineState()); add(ResetRoutineState());
if (event.isRoutineTab) { if (event.isRoutineTab) {
add(const LoadScenes()); add(const LoadScenes());
@ -91,8 +90,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
final updatedIfItems = List<Map<String, dynamic>>.from(state.ifItems); final updatedIfItems = List<Map<String, dynamic>>.from(state.ifItems);
// Find the index of the item in teh current itemsList // Find the index of the item in teh current itemsList
int index = updatedIfItems.indexWhere( int index =
(map) => map['uniqueCustomId'] == event.item['uniqueCustomId']); updatedIfItems.indexWhere((map) => map['uniqueCustomId'] == event.item['uniqueCustomId']);
// Replace the map if the index is valid // Replace the map if the index is valid
if (index != -1) { if (index != -1) {
updatedIfItems[index] = event.item; updatedIfItems[index] = event.item;
@ -101,21 +100,18 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
} }
if (event.isTabToRun) { if (event.isTabToRun) {
emit(state.copyWith( emit(state.copyWith(ifItems: updatedIfItems, isTabToRun: true, isAutomation: false));
ifItems: updatedIfItems, isTabToRun: true, isAutomation: false));
} else { } else {
emit(state.copyWith( emit(state.copyWith(ifItems: updatedIfItems, isTabToRun: false, isAutomation: true));
ifItems: updatedIfItems, isTabToRun: false, isAutomation: true));
} }
} }
void _onAddToThenContainer( void _onAddToThenContainer(AddToThenContainer event, Emitter<RoutineState> emit) {
AddToThenContainer event, Emitter<RoutineState> emit) {
final currentItems = List<Map<String, dynamic>>.from(state.thenItems); final currentItems = List<Map<String, dynamic>>.from(state.thenItems);
// Find the index of the item in teh current itemsList // Find the index of the item in teh current itemsList
int index = currentItems.indexWhere( int index =
(map) => map['uniqueCustomId'] == event.item['uniqueCustomId']); currentItems.indexWhere((map) => map['uniqueCustomId'] == event.item['uniqueCustomId']);
// Replace the map if the index is valid // Replace the map if the index is valid
if (index != -1) { if (index != -1) {
currentItems[index] = event.item; currentItems[index] = event.item;
@ -126,45 +122,42 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
emit(state.copyWith(thenItems: currentItems)); emit(state.copyWith(thenItems: currentItems));
} }
void _onAddFunctionsToRoutine( void _onAddFunctionsToRoutine(AddFunctionToRoutine event, Emitter<RoutineState> emit) {
AddFunctionToRoutine event, Emitter<RoutineState> emit) {
try { try {
if (event.functions.isEmpty) return; if (event.functions.isEmpty) return;
List<DeviceFunctionData> selectedFunction = // List<DeviceFunctionData> selectedFunction = List<DeviceFunctionData>.from(event.functions);
List<DeviceFunctionData>.from(event.functions);
Map<String, List<DeviceFunctionData>> currentSelectedFunctions = Map<String, List<DeviceFunctionData>> currentSelectedFunctions =
Map<String, List<DeviceFunctionData>>.from(state.selectedFunctions); Map<String, List<DeviceFunctionData>>.from(state.selectedFunctions);
if (currentSelectedFunctions.containsKey(event.uniqueCustomId)) {
List<DeviceFunctionData> currentFunctions =
List<DeviceFunctionData>.from(
currentSelectedFunctions[event.uniqueCustomId] ?? []);
List<String> functionCode = []; // if (currentSelectedFunctions.containsKey(event.uniqueCustomId)) {
for (int i = 0; i < selectedFunction.length; i++) { // List<DeviceFunctionData> currentFunctions =
for (int j = 0; j < currentFunctions.length; j++) { // List<DeviceFunctionData>.from(currentSelectedFunctions[event.uniqueCustomId] ?? []);
if (selectedFunction[i].functionCode ==
currentFunctions[j].functionCode) {
currentFunctions[j] = selectedFunction[i];
if (!functionCode.contains(currentFunctions[j].functionCode)) {
functionCode.add(currentFunctions[j].functionCode);
}
}
}
}
for (int i = 0; i < functionCode.length; i++) { // List<String> functionCode = [];
selectedFunction // for (int i = 0; i < selectedFunction.length; i++) {
.removeWhere((code) => code.functionCode == functionCode[i]); // for (int j = 0; j < currentFunctions.length; j++) {
} // if (selectedFunction[i].functionCode == currentFunctions[j].functionCode) {
// currentFunctions[j] = selectedFunction[i];
// if (!functionCode.contains(currentFunctions[j].functionCode)) {
// functionCode.add(currentFunctions[j].functionCode);
// }
// }
// }
// }
currentSelectedFunctions[event.uniqueCustomId] = // for (int i = 0; i < functionCode.length; i++) {
List.from(currentFunctions)..addAll(selectedFunction); // selectedFunction.removeWhere((code) => code.functionCode == functionCode[i]);
} else { // }
currentSelectedFunctions[event.uniqueCustomId] =
List.from(event.functions); // currentSelectedFunctions[event.uniqueCustomId] = List.from(currentFunctions)
} // ..addAll(selectedFunction);
// } else {
// currentSelectedFunctions[event.uniqueCustomId] = List.from(event.functions);
// }
currentSelectedFunctions[event.uniqueCustomId] = List.from(event.functions);
emit(state.copyWith(selectedFunctions: currentSelectedFunctions)); emit(state.copyWith(selectedFunctions: currentSelectedFunctions));
} catch (e) { } catch (e) {
@ -172,30 +165,24 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
} }
} }
Future<void> _onLoadScenes( Future<void> _onLoadScenes(LoadScenes event, Emitter<RoutineState> emit) async {
LoadScenes event, Emitter<RoutineState> emit) async {
emit(state.copyWith(isLoading: true, errorMessage: null)); emit(state.copyWith(isLoading: true, errorMessage: null));
List<ScenesModel> scenes = []; List<ScenesModel> scenes = [];
try { try {
BuildContext context = NavigationService.navigatorKey.currentContext!; BuildContext context = NavigationService.navigatorKey.currentContext!;
var createRoutineBloc = context.read<CreateRoutineBloc>(); var createRoutineBloc = context.read<CreateRoutineBloc>();
final projectUuid = await ProjectManager.getProjectUUID() ?? ''; final projectUuid = await ProjectManager.getProjectUUID() ?? '';
if (createRoutineBloc.selectedSpaceId == '' && if (createRoutineBloc.selectedSpaceId == '' && createRoutineBloc.selectedCommunityId == '') {
createRoutineBloc.selectedCommunityId == '') {
var spaceBloc = context.read<SpaceTreeBloc>(); var spaceBloc = context.read<SpaceTreeBloc>();
for (var communityId in spaceBloc.state.selectedCommunities) { for (var communityId in spaceBloc.state.selectedCommunities) {
List<String> spacesList = List<String> spacesList = spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? [];
spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? [];
for (var spaceId in spacesList) { for (var spaceId in spacesList) {
scenes.addAll( scenes.addAll(await SceneApi.getScenes(spaceId, communityId, projectUuid));
await SceneApi.getScenes(spaceId, communityId, projectUuid));
} }
} }
} else { } else {
scenes.addAll(await SceneApi.getScenes( scenes.addAll(await SceneApi.getScenes(
createRoutineBloc.selectedSpaceId, createRoutineBloc.selectedSpaceId, createRoutineBloc.selectedCommunityId, projectUuid));
createRoutineBloc.selectedCommunityId,
projectUuid));
} }
emit(state.copyWith( emit(state.copyWith(
@ -212,8 +199,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
} }
} }
Future<void> _onLoadAutomation( Future<void> _onLoadAutomation(LoadAutomation event, Emitter<RoutineState> emit) async {
LoadAutomation event, Emitter<RoutineState> emit) async {
emit(state.copyWith(isLoading: true, errorMessage: null)); emit(state.copyWith(isLoading: true, errorMessage: null));
List<ScenesModel> automations = []; List<ScenesModel> automations = [];
final projectId = await ProjectManager.getProjectUUID() ?? ''; final projectId = await ProjectManager.getProjectUUID() ?? '';
@ -221,23 +207,17 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
BuildContext context = NavigationService.navigatorKey.currentContext!; BuildContext context = NavigationService.navigatorKey.currentContext!;
var createRoutineBloc = context.read<CreateRoutineBloc>(); var createRoutineBloc = context.read<CreateRoutineBloc>();
try { try {
if (createRoutineBloc.selectedSpaceId == '' && createRoutineBloc.selectedCommunityId == '') {
if (createRoutineBloc.selectedSpaceId == '' &&
createRoutineBloc.selectedCommunityId == '') {
var spaceBloc = context.read<SpaceTreeBloc>(); var spaceBloc = context.read<SpaceTreeBloc>();
for (var communityId in spaceBloc.state.selectedCommunities) { for (var communityId in spaceBloc.state.selectedCommunities) {
List<String> spacesList = List<String> spacesList = spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? [];
spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? [];
for (var spaceId in spacesList) { for (var spaceId in spacesList) {
automations.addAll( automations.addAll(await SceneApi.getAutomation(spaceId, communityId, projectId));
await SceneApi.getAutomation(spaceId, communityId, projectId));
} }
} }
} else { } else {
automations.addAll(await SceneApi.getAutomation( automations.addAll(await SceneApi.getAutomation(
createRoutineBloc.selectedSpaceId, createRoutineBloc.selectedSpaceId, createRoutineBloc.selectedCommunityId, projectId));
createRoutineBloc.selectedCommunityId,
projectId));
} }
emit(state.copyWith( emit(state.copyWith(
automations: automations, automations: automations,
@ -253,16 +233,14 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
} }
} }
FutureOr<void> _onSearchRoutines( FutureOr<void> _onSearchRoutines(SearchRoutines event, Emitter<RoutineState> emit) async {
SearchRoutines event, Emitter<RoutineState> emit) async {
emit(state.copyWith(isLoading: true, errorMessage: null)); emit(state.copyWith(isLoading: true, errorMessage: null));
await Future.delayed(const Duration(seconds: 1)); await Future.delayed(const Duration(seconds: 1));
emit(state.copyWith(isLoading: false, errorMessage: null)); emit(state.copyWith(isLoading: false, errorMessage: null));
emit(state.copyWith(searchText: event.query)); emit(state.copyWith(searchText: event.query));
} }
FutureOr<void> _onAddSelectedIcon( FutureOr<void> _onAddSelectedIcon(AddSelectedIcon event, Emitter<RoutineState> emit) {
AddSelectedIcon event, Emitter<RoutineState> emit) {
emit(state.copyWith(selectedIcon: event.icon)); emit(state.copyWith(selectedIcon: event.icon));
} }
@ -276,8 +254,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
return actions.last['deviceId'] == 'delay'; return actions.last['deviceId'] == 'delay';
} }
Future<void> _onCreateScene( Future<void> _onCreateScene(CreateSceneEvent event, Emitter<RoutineState> emit) async {
CreateSceneEvent event, Emitter<RoutineState> emit) async {
try { try {
// Check if first action is delay // Check if first action is delay
// if (_isFirstActionDelay(state.thenItems)) { // if (_isFirstActionDelay(state.thenItems)) {
@ -290,8 +267,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
if (_isLastActionDelay(state.thenItems)) { if (_isLastActionDelay(state.thenItems)) {
emit(state.copyWith( emit(state.copyWith(
errorMessage: errorMessage: 'A delay condition cannot be the only or the last action',
'A delay condition cannot be the only or the last action',
isLoading: false, isLoading: false,
)); ));
return; return;
@ -367,8 +343,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
} }
} }
Future<void> _onCreateAutomation( Future<void> _onCreateAutomation(CreateAutomationEvent event, Emitter<RoutineState> emit) async {
CreateAutomationEvent event, Emitter<RoutineState> emit) async {
try { try {
final projectUuid = await ProjectManager.getProjectUUID() ?? ''; final projectUuid = await ProjectManager.getProjectUUID() ?? '';
if (state.routineName == null || state.routineName!.isEmpty) { if (state.routineName == null || state.routineName!.isEmpty) {
@ -390,8 +365,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
if (_isLastActionDelay(state.thenItems)) { if (_isLastActionDelay(state.thenItems)) {
emit(state.copyWith( emit(state.copyWith(
errorMessage: errorMessage: 'A delay condition cannot be the only or the last action',
'A delay condition cannot be the only or the last action',
isLoading: false, isLoading: false,
)); ));
CustomSnackBar.redSnackBar('Cannot have delay as the last action'); CustomSnackBar.redSnackBar('Cannot have delay as the last action');
@ -482,8 +456,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
actions: actions, actions: actions,
); );
final result = final result = await SceneApi.createAutomation(createAutomationModel, projectUuid);
await SceneApi.createAutomation(createAutomationModel, projectUuid);
if (result['success']) { if (result['success']) {
add(ResetRoutineState()); add(ResetRoutineState());
add(const LoadAutomation()); add(const LoadAutomation());
@ -504,21 +477,17 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
} }
} }
FutureOr<void> _onRemoveDragCard( FutureOr<void> _onRemoveDragCard(RemoveDragCard event, Emitter<RoutineState> emit) {
RemoveDragCard event, Emitter<RoutineState> emit) {
if (event.isFromThen) { if (event.isFromThen) {
final thenItems = List<Map<String, dynamic>>.from(state.thenItems); final thenItems = List<Map<String, dynamic>>.from(state.thenItems);
final selectedFunctions = final selectedFunctions = Map<String, List<DeviceFunctionData>>.from(state.selectedFunctions);
Map<String, List<DeviceFunctionData>>.from(state.selectedFunctions);
thenItems.removeAt(event.index); thenItems.removeAt(event.index);
selectedFunctions.remove(event.key); selectedFunctions.remove(event.key);
emit(state.copyWith( emit(state.copyWith(thenItems: thenItems, selectedFunctions: selectedFunctions));
thenItems: thenItems, selectedFunctions: selectedFunctions));
} else { } else {
final ifItems = List<Map<String, dynamic>>.from(state.ifItems); final ifItems = List<Map<String, dynamic>>.from(state.ifItems);
final selectedFunctions = final selectedFunctions = Map<String, List<DeviceFunctionData>>.from(state.selectedFunctions);
Map<String, List<DeviceFunctionData>>.from(state.selectedFunctions);
ifItems.removeAt(event.index); ifItems.removeAt(event.index);
selectedFunctions.remove(event.key); selectedFunctions.remove(event.key);
@ -529,8 +498,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
isAutomation: false, isAutomation: false,
isTabToRun: false)); isTabToRun: false));
} else { } else {
emit(state.copyWith( emit(state.copyWith(ifItems: ifItems, selectedFunctions: selectedFunctions));
ifItems: ifItems, selectedFunctions: selectedFunctions));
} }
} }
} }
@ -542,141 +510,138 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
)); ));
} }
FutureOr<void> _onEffectiveTimeEvent( FutureOr<void> _onEffectiveTimeEvent(EffectiveTimePeriodEvent event, Emitter<RoutineState> emit) {
EffectiveTimePeriodEvent event, Emitter<RoutineState> emit) {
emit(state.copyWith(effectiveTime: event.effectiveTime)); emit(state.copyWith(effectiveTime: event.effectiveTime));
} }
FutureOr<void> _onSetRoutineName( FutureOr<void> _onSetRoutineName(SetRoutineName event, Emitter<RoutineState> emit) {
SetRoutineName event, Emitter<RoutineState> emit) {
emit(state.copyWith( emit(state.copyWith(
routineName: event.name, routineName: event.name,
)); ));
} }
( // (
List<Map<String, dynamic>>, // List<Map<String, dynamic>>,
List<Map<String, dynamic>>, // List<Map<String, dynamic>>,
Map<String, List<DeviceFunctionData>> // Map<String, List<DeviceFunctionData>>
) _createCardData( // ) _createCardData(
List<RoutineAction> actions, // List<RoutineAction> actions,
List<RoutineCondition>? conditions, // List<RoutineCondition>? conditions,
Map<String, List<DeviceFunctionData>> currentFunctions, // Map<String, List<DeviceFunctionData>> currentFunctions,
bool isTabToRun, // bool isTabToRun,
) { // ) {
final ifItems = isTabToRun // final ifItems = isTabToRun
? [ // ? [
{ // {
'entityId': 'tab_to_run', // 'entityId': 'tab_to_run',
'uniqueCustomId': const Uuid().v4(), // 'uniqueCustomId': const Uuid().v4(),
'deviceId': 'tab_to_run', // 'deviceId': 'tab_to_run',
'title': 'Tab to run', // 'title': 'Tab to run',
'productType': 'tab_to_run', // 'productType': 'tab_to_run',
'imagePath': Assets.tabToRun, // 'imagePath': Assets.tabToRun,
} // }
] // ]
: conditions?.map((condition) { // : conditions?.map((condition) {
final matchingDevice = state.devices.firstWhere( // final matchingDevice = state.devices.firstWhere(
(device) => device.uuid == condition.entityId, // (device) => device.uuid == condition.entityId,
orElse: () => AllDevicesModel( // orElse: () => AllDevicesModel(
uuid: condition.entityId, // uuid: condition.entityId,
name: condition.entityId, // name: condition.entityId,
productType: condition.entityType, // productType: condition.entityType,
), // ),
); // );
final cardData = { // final cardData = {
'entityId': condition.entityId, // 'entityId': condition.entityId,
'uniqueCustomId': const Uuid().v4(), // 'uniqueCustomId': const Uuid().v4(),
'deviceId': condition.entityId, // 'deviceId': condition.entityId,
'title': matchingDevice.name ?? condition.entityId, // 'title': matchingDevice.name ?? condition.entityId,
'productType': condition.entityType, // 'productType': condition.entityType,
'imagePath': // 'imagePath':
matchingDevice.getDefaultIcon(condition.entityType), // matchingDevice.getDefaultIcon(condition.entityType),
}; // };
final functions = matchingDevice.functions; // final functions = matchingDevice.functions;
for (var function in functions) { // for (var function in functions) {
if (function.code == condition.expr.statusCode) { // if (function.code == condition.expr.statusCode) {
currentFunctions[cardData['uniqueCustomId'] ?? ''] = [ // currentFunctions[cardData['uniqueCustomId'] ?? ''] = [
DeviceFunctionData( // DeviceFunctionData(
entityId: condition.entityId, // entityId: condition.entityId,
functionCode: condition.expr.statusCode, // functionCode: condition.expr.statusCode,
value: condition.expr.statusValue, // value: condition.expr.statusValue,
operationName: function.operationName, // operationName: function.operationName,
), // ),
]; // ];
break; // break;
} // }
} // }
return cardData; // return cardData;
}).toList() ?? // }).toList() ??
[]; // [];
final thenItems = actions.map((action) { // final thenItems = actions.map((action) {
final matchingDevice = state.devices.firstWhere( // final matchingDevice = state.devices.firstWhere(
(device) => device.uuid == action.entityId, // (device) => device.uuid == action.entityId,
orElse: () => AllDevicesModel( // orElse: () => AllDevicesModel(
uuid: action.entityId, // uuid: action.entityId,
name: action.entityId, // name: action.entityId,
productType: action.productType, // productType: action.productType,
), // ),
); // );
final cardData = { // final cardData = {
'entityId': action.entityId, // 'entityId': action.entityId,
'uniqueCustomId': const Uuid().v4(), // 'uniqueCustomId': const Uuid().v4(),
'deviceId': // 'deviceId':
action.actionExecutor == 'delay' ? 'delay' : action.entityId, // action.actionExecutor == 'delay' ? 'delay' : action.entityId,
'title': action.actionExecutor == 'delay' // 'title': action.actionExecutor == 'delay'
? 'Delay' // ? 'Delay'
: (matchingDevice.name ?? 'Device'), // : (matchingDevice.name ?? 'Device'),
'productType': action.productType, // 'productType': action.productType,
'imagePath': matchingDevice.getDefaultIcon(action.productType), // 'imagePath': matchingDevice.getDefaultIcon(action.productType),
}; // };
final functions = matchingDevice.functions; // final functions = matchingDevice.functions;
if (action.executorProperty != null && action.actionExecutor != 'delay') { // if (action.executorProperty != null && action.actionExecutor != 'delay') {
final functionCode = action.executorProperty!.functionCode; // final functionCode = action.executorProperty!.functionCode;
for (var function in functions) { // for (var function in functions) {
if (function.code == functionCode) { // if (function.code == functionCode) {
currentFunctions[cardData['uniqueCustomId'] ?? ''] = [ // currentFunctions[cardData['uniqueCustomId'] ?? ''] = [
DeviceFunctionData( // DeviceFunctionData(
entityId: action.entityId, // entityId: action.entityId,
functionCode: functionCode ?? '', // functionCode: functionCode ?? '',
value: action.executorProperty!.functionValue, // value: action.executorProperty!.functionValue,
operationName: function.operationName, // operationName: function.operationName,
), // ),
]; // ];
break; // break;
} // }
} // }
} else if (action.actionExecutor == 'delay') { // } else if (action.actionExecutor == 'delay') {
final delayFunction = DelayFunction( // final delayFunction = DelayFunction(
deviceId: action.entityId, // deviceId: action.entityId,
deviceName: 'Delay', // deviceName: 'Delay',
); // );
currentFunctions[cardData['uniqueCustomId'] ?? ''] = [ // currentFunctions[cardData['uniqueCustomId'] ?? ''] = [
DeviceFunctionData( // DeviceFunctionData(
entityId: action.entityId, // entityId: action.entityId,
functionCode: 'delay', // functionCode: 'delay',
value: action.executorProperty?.delaySeconds ?? 0, // value: action.executorProperty?.delaySeconds ?? 0,
operationName: delayFunction.operationName, // operationName: delayFunction.operationName,
), // ),
]; // ];
} // }
return cardData; // return cardData;
}).toList(); // }).toList();
return (thenItems, ifItems, currentFunctions); // return (thenItems, ifItems, currentFunctions);
} // }
Future<void> _onGetSceneDetails( Future<void> _onGetSceneDetails(GetSceneDetails event, Emitter<RoutineState> emit) async {
GetSceneDetails event, Emitter<RoutineState> emit) async {
try { try {
emit(state.copyWith( emit(state.copyWith(
isLoading: true, isLoading: true,
@ -719,42 +684,45 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
? '${action.entityId}_automation' ? '${action.entityId}_automation'
: action.actionExecutor == 'delay' : action.actionExecutor == 'delay'
? '${action.entityId}_delay' ? '${action.entityId}_delay'
: action.entityId; : const Uuid().v4();
if (!deviceCards.containsKey(deviceId)) { // if (!deviceCards.containsKey(deviceId)) {
deviceCards[deviceId] = { deviceCards[deviceId] = {
'entityId': action.entityId, 'entityId': action.entityId,
'deviceId': 'deviceId': action.actionExecutor == 'delay' ? 'delay' : action.entityId,
action.actionExecutor == 'delay' ? 'delay' : action.entityId, 'uniqueCustomId': action.type == 'automation' || action.actionExecutor == 'delay'
'uniqueCustomId': ? action.entityId
action.type == 'automation' || action.actionExecutor == 'delay' : const Uuid().v4(),
? const Uuid().v4() 'title': action.actionExecutor == 'delay'
: action.entityId, ? 'Delay'
'title': action.actionExecutor == 'delay' : action.type == 'automation'
? 'Delay' ? action.name ?? 'Automation'
: action.type == 'automation' : (matchingDevice?.name ?? 'Device'),
? action.name ?? 'Automation' 'productType': action.productType,
: (matchingDevice?.name ?? 'Device'), 'functions': matchingDevice?.functions,
'productType': action.productType, 'imagePath': action.type == 'automation'
'functions': matchingDevice?.functions, ? Assets.automation
'imagePath': action.type == 'automation' : action.actionExecutor == 'delay'
? Assets.automation ? Assets.delay
: action.actionExecutor == 'delay' : matchingDevice?.getDefaultIcon(action.productType),
? Assets.delay 'device': matchingDevice,
: matchingDevice?.getDefaultIcon(action.productType), 'name': action.name,
'device': matchingDevice, 'type': action.type,
'name': action.name, 'tag': matchingDevice?.deviceTags?.isNotEmpty ?? false
'type': action.type, ? matchingDevice?.deviceTags![0].name ?? ''
}; : '',
} 'subSpace': matchingDevice?.deviceSubSpace?.subspaceName ?? '',
};
// }
final cardData = deviceCards[deviceId]!; final cardData = deviceCards[deviceId]!;
final uniqueCustomId = cardData['uniqueCustomId'].toString(); final uniqueCustomId = cardData['uniqueCustomId'].toString();
if (!updatedFunctions.containsKey(uniqueCustomId)) {
updatedFunctions[uniqueCustomId] = [];
}
if (action.type == 'automation') { if (action.type == 'automation') {
if (!updatedFunctions.containsKey(uniqueCustomId)) {
updatedFunctions[uniqueCustomId] = [];
}
updatedFunctions[uniqueCustomId]!.add( updatedFunctions[uniqueCustomId]!.add(
DeviceFunctionData( DeviceFunctionData(
entityId: action.entityId, entityId: action.entityId,
@ -764,14 +732,10 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
), ),
); );
// emit(state.copyWith(automationActionExecutor: action.actionExecutor)); // emit(state.copyWith(automationActionExecutor: action.actionExecutor));
} else if (action.executorProperty != null && } else if (action.executorProperty != null && action.actionExecutor != 'delay') {
action.actionExecutor != 'delay') { final functions = matchingDevice?.functions ?? [];
if (!updatedFunctions.containsKey(uniqueCustomId)) {
updatedFunctions[uniqueCustomId] = [];
}
final functions = matchingDevice?.functions;
final functionCode = action.executorProperty?.functionCode; final functionCode = action.executorProperty?.functionCode;
for (DeviceFunction function in functions ?? []) { for (DeviceFunction function in functions) {
if (function.code == functionCode) { if (function.code == functionCode) {
updatedFunctions[uniqueCustomId]!.add( updatedFunctions[uniqueCustomId]!.add(
DeviceFunctionData( DeviceFunctionData(
@ -785,9 +749,6 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
} }
} }
} else if (action.actionExecutor == 'delay') { } else if (action.actionExecutor == 'delay') {
if (!updatedFunctions.containsKey(uniqueCustomId)) {
updatedFunctions[uniqueCustomId] = [];
}
final delayFunction = DelayFunction( final delayFunction = DelayFunction(
deviceId: action.entityId, deviceId: action.entityId,
deviceName: 'Delay', deviceName: 'Delay',
@ -837,8 +798,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
} }
} }
FutureOr<void> _onResetRoutineState( FutureOr<void> _onResetRoutineState(ResetRoutineState event, Emitter<RoutineState> emit) {
ResetRoutineState event, Emitter<RoutineState> emit) {
emit(state.copyWith( emit(state.copyWith(
ifItems: [], ifItems: [],
thenItems: [], thenItems: [],
@ -861,6 +821,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
isUpdate: false, isUpdate: false,
createRoutineView: false)); createRoutineView: false));
} }
FutureOr<void> _deleteScene(DeleteScene event, Emitter<RoutineState> emit) async { FutureOr<void> _deleteScene(DeleteScene event, Emitter<RoutineState> emit) async {
try { try {
final projectId = await ProjectManager.getProjectUUID() ?? ''; final projectId = await ProjectManager.getProjectUUID() ?? '';
@ -900,7 +861,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
)); ));
} }
} }
// FutureOr<void> _deleteAutomation(DeleteAutomation event, Emitter<RoutineState> emit) { // FutureOr<void> _deleteAutomation(DeleteAutomation event, Emitter<RoutineState> emit) {
// try { // try {
// emit(state.copyWith(isLoading: true)); // emit(state.copyWith(isLoading: true));
@ -915,8 +876,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
// } // }
// } // }
FutureOr<void> _fetchDevices( FutureOr<void> _fetchDevices(FetchDevicesInRoutine event, Emitter<RoutineState> emit) async {
FetchDevicesInRoutine event, Emitter<RoutineState> emit) async {
emit(state.copyWith(isLoading: true)); emit(state.copyWith(isLoading: true));
try { try {
final projectUuid = await ProjectManager.getProjectUUID() ?? ''; final projectUuid = await ProjectManager.getProjectUUID() ?? '';
@ -925,21 +885,17 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
var createRoutineBloc = context.read<CreateRoutineBloc>(); var createRoutineBloc = context.read<CreateRoutineBloc>();
var spaceBloc = context.read<SpaceTreeBloc>(); var spaceBloc = context.read<SpaceTreeBloc>();
if (createRoutineBloc.selectedSpaceId == '' && if (createRoutineBloc.selectedSpaceId == '' && createRoutineBloc.selectedCommunityId == '') {
createRoutineBloc.selectedCommunityId == '') {
for (var communityId in spaceBloc.state.selectedCommunities) { for (var communityId in spaceBloc.state.selectedCommunities) {
List<String> spacesList = List<String> spacesList = spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? [];
spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? [];
for (var spaceId in spacesList) { for (var spaceId in spacesList) {
devices.addAll(await DevicesManagementApi() devices.addAll(
.fetchDevices(communityId, spaceId, projectUuid)); await DevicesManagementApi().fetchDevices(communityId, spaceId, projectUuid));
} }
} }
} else { } else {
devices.addAll(await DevicesManagementApi().fetchDevices( devices.addAll(await DevicesManagementApi().fetchDevices(
createRoutineBloc.selectedCommunityId, createRoutineBloc.selectedCommunityId, createRoutineBloc.selectedSpaceId, projectUuid));
createRoutineBloc.selectedSpaceId,
projectUuid));
} }
emit(state.copyWith(isLoading: false, devices: devices)); emit(state.copyWith(isLoading: false, devices: devices));
@ -948,8 +904,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
} }
} }
FutureOr<void> _onUpdateScene( FutureOr<void> _onUpdateScene(UpdateScene event, Emitter<RoutineState> emit) async {
UpdateScene event, Emitter<RoutineState> emit) async {
try { try {
// Check if first action is delay // Check if first action is delay
// if (_isFirstActionDelay(state.thenItems)) { // if (_isFirstActionDelay(state.thenItems)) {
@ -963,8 +918,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
if (_isLastActionDelay(state.thenItems)) { if (_isLastActionDelay(state.thenItems)) {
emit(state.copyWith( emit(state.copyWith(
errorMessage: errorMessage: 'A delay condition cannot be the only or the last action',
'A delay condition cannot be the only or the last action',
isLoading: false, isLoading: false,
)); ));
return; return;
@ -1017,8 +971,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
actions: actions, actions: actions,
); );
final result = final result = await SceneApi.updateScene(createSceneModel, state.sceneId ?? '');
await SceneApi.updateScene(createSceneModel, state.sceneId ?? '');
if (result['success']) { if (result['success']) {
add(ResetRoutineState()); add(ResetRoutineState());
add(const LoadScenes()); add(const LoadScenes());
@ -1037,8 +990,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
} }
} }
FutureOr<void> _onUpdateAutomation( FutureOr<void> _onUpdateAutomation(UpdateAutomation event, Emitter<RoutineState> emit) async {
UpdateAutomation event, Emitter<RoutineState> emit) async {
try { try {
if (state.routineName == null || state.routineName!.isEmpty) { if (state.routineName == null || state.routineName!.isEmpty) {
emit(state.copyWith( emit(state.copyWith(
@ -1203,21 +1155,25 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
), ),
); );
final deviceId = condition.entityId; final deviceId = const Uuid().v4();
if (!deviceIfCards.containsKey(deviceId)) { // if (!deviceIfCards.containsKey(deviceId)) {
deviceIfCards[deviceId] = { deviceIfCards[deviceId] = {
'entityId': condition.entityId, 'entityId': condition.entityId,
'deviceId': condition.entityId, 'deviceId': condition.entityId,
'uniqueCustomId': const Uuid().v4(), 'uniqueCustomId': const Uuid().v4(),
'title': matchingDevice.name ?? 'Device', 'title': matchingDevice.name ?? 'Device',
'productType': condition.productType, 'productType': condition.productType,
'functions': matchingDevice.functions, 'functions': matchingDevice.functions,
'imagePath': matchingDevice.getDefaultIcon(condition.productType), 'imagePath': matchingDevice.getDefaultIcon(condition.productType),
'device': matchingDevice, 'device': matchingDevice,
'type': 'condition', 'type': 'condition',
}; 'tag': matchingDevice.deviceTags?.isNotEmpty ?? false
} ? matchingDevice.deviceTags![0].name
: '',
'subSpace': matchingDevice.deviceSubSpace?.subspaceName ?? '',
};
// }
final cardData = deviceIfCards[deviceId]!; final cardData = deviceIfCards[deviceId]!;
final uniqueCustomId = cardData['uniqueCustomId'].toString(); final uniqueCustomId = cardData['uniqueCustomId'].toString();
@ -1253,37 +1209,38 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
), ),
); );
final deviceId = action.actionExecutor == 'delay' final deviceId = const Uuid().v4();
? '${action.entityId}_delay'
: action.entityId;
if (!deviceThenCards.containsKey(deviceId)) { // if (!deviceThenCards.containsKey(deviceId)) {
deviceThenCards[deviceId] = { deviceThenCards[deviceId] = {
'entityId': action.entityId, 'entityId': action.entityId,
'deviceId': 'deviceId': action.actionExecutor == 'delay' ? 'delay' : action.entityId,
action.actionExecutor == 'delay' ? 'delay' : action.entityId, 'uniqueCustomId': const Uuid().v4(),
'uniqueCustomId': const Uuid().v4(), 'title': action.actionExecutor == 'delay'
'title': action.actionExecutor == 'delay' ? 'Delay'
? 'Delay' : (action.type == 'scene' || action.type == 'automation')
: (action.type == 'scene' || action.type == 'automation') ? action.name
? action.name : (matchingDevice.name ?? 'Device'),
: (matchingDevice.name ?? 'Device'), 'productType': action.productType,
'productType': action.productType, 'functions': matchingDevice.functions,
'functions': matchingDevice.functions, 'imagePath': action.actionExecutor == 'delay'
'imagePath': action.actionExecutor == 'delay' ? Assets.delay
? Assets.delay : action.type == 'automation'
: action.type == 'automation' ? Assets.automation
? Assets.automation : matchingDevice.getDefaultIcon(action.productType),
: matchingDevice.getDefaultIcon(action.productType), 'device': matchingDevice,
'device': matchingDevice, 'type': action.type == 'scene'
'type': action.type == 'scene' ? 'scene'
? 'scene' : action.type == 'automation'
: action.type == 'automation' ? 'automation'
? 'automation' : 'action',
: 'action', 'icon': action.icon ?? '',
'icon': action.icon ?? '' 'tag': matchingDevice.deviceTags?.isNotEmpty ?? false
}; ? matchingDevice.deviceTags![0].name
} : '',
'subSpace': matchingDevice.deviceSubSpace?.subspaceName ?? '',
};
// }
final cardData = deviceThenCards[deviceId]!; final cardData = deviceThenCards[deviceId]!;
final uniqueCustomId = cardData['uniqueCustomId'].toString(); final uniqueCustomId = cardData['uniqueCustomId'].toString();
@ -1292,8 +1249,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
updatedFunctions[uniqueCustomId] = []; updatedFunctions[uniqueCustomId] = [];
} }
if (action.executorProperty != null && if (action.executorProperty != null && action.actionExecutor != 'delay') {
action.actionExecutor != 'delay') {
final functions = matchingDevice.functions; final functions = matchingDevice.functions;
final functionCode = action.executorProperty!.functionCode; final functionCode = action.executorProperty!.functionCode;
for (var function in functions) { for (var function in functions) {
@ -1335,14 +1291,10 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
} }
} }
final ifItems = deviceIfCards.values final ifItems = deviceIfCards.values.where((card) => card['type'] == 'condition').toList();
.where((card) => card['type'] == 'condition')
.toList();
final thenItems = deviceThenCards.values final thenItems = deviceThenCards.values
.where((card) => .where((card) =>
card['type'] == 'action' || card['type'] == 'action' || card['type'] == 'automation' || card['type'] == 'scene')
card['type'] == 'automation' ||
card['type'] == 'scene')
.toList(); .toList();
emit(state.copyWith( emit(state.copyWith(
@ -1364,8 +1316,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
} }
} }
Future<void> _onSceneTrigger( Future<void> _onSceneTrigger(SceneTrigger event, Emitter<RoutineState> emit) async {
SceneTrigger event, Emitter<RoutineState> emit) async {
emit(state.copyWith(loadingSceneId: event.sceneId)); emit(state.copyWith(loadingSceneId: event.sceneId));
try { try {
@ -1407,29 +1358,24 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
if (success) { if (success) {
final updatedAutomations = await SceneApi.getAutomationByUnitId( final updatedAutomations = await SceneApi.getAutomationByUnitId(
event.automationStatusUpdate.spaceUuid, event.automationStatusUpdate.spaceUuid, event.communityId, projectId);
event.communityId,
projectId);
// Remove from loading set safely // Remove from loading set safely
final updatedLoadingIds = {...state.loadingAutomationIds!} final updatedLoadingIds = {...state.loadingAutomationIds!}..remove(event.automationId);
..remove(event.automationId);
emit(state.copyWith( emit(state.copyWith(
automations: updatedAutomations, automations: updatedAutomations,
loadingAutomationIds: updatedLoadingIds, loadingAutomationIds: updatedLoadingIds,
)); ));
} else { } else {
final updatedLoadingIds = {...state.loadingAutomationIds!} final updatedLoadingIds = {...state.loadingAutomationIds!}..remove(event.automationId);
..remove(event.automationId);
emit(state.copyWith( emit(state.copyWith(
loadingAutomationIds: updatedLoadingIds, loadingAutomationIds: updatedLoadingIds,
errorMessage: 'Update failed', errorMessage: 'Update failed',
)); ));
} }
} catch (e) { } catch (e) {
final updatedLoadingIds = {...state.loadingAutomationIds!} final updatedLoadingIds = {...state.loadingAutomationIds!}..remove(event.automationId);
..remove(event.automationId);
emit(state.copyWith( emit(state.copyWith(
loadingAutomationIds: updatedLoadingIds, loadingAutomationIds: updatedLoadingIds,
errorMessage: 'Update error: ${e.toString()}', errorMessage: 'Update error: ${e.toString()}',

View File

@ -63,7 +63,11 @@ class _CreateNewRoutinesDialogState extends State<CreateNewRoutinesDialog> {
Padding( Padding(
padding: const EdgeInsets.only(left: 15, right: 15), padding: const EdgeInsets.only(left: 15, right: 15),
child: CommunityDropdown( child: CommunityDropdown(
communities: _bloc.communities, communities: _bloc.communities..sort(
(a, b) => a.name.toLowerCase().compareTo(
b.name.toLowerCase(),
),
),
selectedValue: _selectedCommunity, selectedValue: _selectedCommunity,
onChanged: (String? newValue) { onChanged: (String? newValue) {
setState(() { setState(() {

View File

@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routines/models/device_functions.dart';
import 'package:syncrow_web/pages/routines/widgets/dialog_header.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/pages/routines/widgets/dialog_footer.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
@ -20,7 +21,7 @@ class SaveRoutineHelper {
return AlertDialog( return AlertDialog(
contentPadding: EdgeInsets.zero, contentPadding: EdgeInsets.zero,
content: Container( content: Container(
width: 600, width: MediaQuery.sizeOf(context).width * 0.5,
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: Colors.white,
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),
@ -30,14 +31,15 @@ class SaveRoutineHelper {
children: [ children: [
DialogHeader('Create a scene: ${state.routineName ?? ""}'), DialogHeader('Create a scene: ${state.routineName ?? ""}'),
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
// Left side - IF // Left side - IF
Expanded( Expanded(
child: Column( child: ListView(
crossAxisAlignment: CrossAxisAlignment.start, // crossAxisAlignment: CrossAxisAlignment.start,
shrinkWrap: true,
children: [ children: [
const Text( const Text(
'IF:', 'IF:',
@ -59,26 +61,7 @@ class SaveRoutineHelper {
...state.ifItems.map((item) { ...state.ifItems.map((item) {
final functions = final functions =
state.selectedFunctions[item['uniqueCustomId']] ?? []; state.selectedFunctions[item['uniqueCustomId']] ?? [];
return ListTile( return functionRow(item, context, functions);
leading: SvgPicture.asset(
item['imagePath'],
width: 22,
height: 22,
),
title:
Text(item['title'], style: const TextStyle(fontSize: 14)),
subtitle: Wrap(
children: functions
.map((f) => Text(
'${f.operationName}: ${f.value}, ',
style: const TextStyle(
color: ColorsManager.grayColor, fontSize: 8),
overflow: TextOverflow.ellipsis,
maxLines: 3,
))
.toList(),
),
);
}), }),
], ],
), ),
@ -87,8 +70,9 @@ class SaveRoutineHelper {
// Right side - THEN items // Right side - THEN items
Expanded( Expanded(
child: Column( child: ListView(
crossAxisAlignment: CrossAxisAlignment.start, // crossAxisAlignment: CrossAxisAlignment.start,
shrinkWrap: true,
children: [ children: [
const Text( const Text(
'THEN:', 'THEN:',
@ -100,37 +84,7 @@ class SaveRoutineHelper {
...state.thenItems.map((item) { ...state.thenItems.map((item) {
final functions = final functions =
state.selectedFunctions[item['uniqueCustomId']] ?? []; state.selectedFunctions[item['uniqueCustomId']] ?? [];
return ListTile( return functionRow(item, context, functions);
leading: item['type'] == 'tap_to_run' || item['type'] == 'scene'
? Image.memory(
base64Decode(item['icon']),
width: 22,
height: 22,
)
: SvgPicture.asset(
item['imagePath'],
width: 22,
height: 22,
),
title: Text(
item['title'],
style: context.textTheme.bodySmall?.copyWith(
fontSize: 14,
color: ColorsManager.grayColor,
),
),
subtitle: Wrap(
children: functions
.map((f) => Text(
'${f.operationName}: ${f.value}, ',
style: context.textTheme.bodySmall?.copyWith(
color: ColorsManager.grayColor, fontSize: 8),
overflow: TextOverflow.ellipsis,
maxLines: 3,
))
.toList(),
),
);
}), }),
], ],
), ),
@ -177,4 +131,112 @@ class SaveRoutineHelper {
}, },
); );
} }
static Widget functionRow(
dynamic item, BuildContext context, List<DeviceFunctionData> functions) {
return Padding(
padding: const EdgeInsets.only(top: 6),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
spacing: 8,
children: [
item['type'] == 'tap_to_run' || item['type'] == 'scene'
? Image.memory(
base64Decode(item['icon']),
width: 22,
height: 22,
)
: SvgPicture.asset(
item['imagePath'],
width: 22,
height: 22,
),
Flexible(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 2,
children: [
Text(
item['title'],
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: context.textTheme.bodySmall?.copyWith(
fontSize: 14,
color: ColorsManager.textPrimaryColor,
),
),
Wrap(
children: functions
.map((f) => Text(
'${f.operationName}: ${f.value}',
style: context.textTheme.bodySmall
?.copyWith(color: ColorsManager.grayColor, fontSize: 8),
overflow: TextOverflow.ellipsis,
maxLines: 3,
))
.toList(),
),
],
),
),
],
),
),
Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 2,
children: [
Visibility(
visible: item['tag'] != null && item['tag'] != '',
child: Row(
spacing: 2,
children: [
SizedBox(width: 8, height: 8, child: SvgPicture.asset(Assets.deviceTagIcon)),
Text(
item['tag'] ?? '',
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
maxLines: 1,
style: context.textTheme.bodySmall?.copyWith(
color: ColorsManager.lightGreyColor,
fontSize: 9,
fontWeight: FontWeight.w400,
),
),
],
),
),
Visibility(
visible: item['subSpace'] != null && item['subSpace'] != '',
child: Row(
spacing: 2,
children: [
SizedBox(
width: 8, height: 8, child: SvgPicture.asset(Assets.spaceLocationIcon)),
Text(
item['subSpace'] ?? '',
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
maxLines: 1,
style: context.textTheme.bodySmall?.copyWith(
color: ColorsManager.lightGreyColor,
fontSize: 9,
fontWeight: FontWeight.w400,
),
),
],
),
),
],
),
],
),
);
}
} }

View File

@ -1,7 +1,6 @@
import 'package:syncrow_web/pages/routines/models/device_functions.dart'; import 'package:syncrow_web/pages/routines/models/device_functions.dart';
import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/constants/assets.dart';
// TODO: functions in conditions / status in actions
class CpsOperationalValue { class CpsOperationalValue {
final String icon; final String icon;
final String description; final String description;
@ -223,7 +222,6 @@ final class CpsSpatialMotionValueFunction extends CpsFunctions {
} }
final class CpsMaxDistanceOfDetectionFunction extends CpsFunctions { final class CpsMaxDistanceOfDetectionFunction extends CpsFunctions {
// confirm with BE
CpsMaxDistanceOfDetectionFunction({ CpsMaxDistanceOfDetectionFunction({
required super.deviceId, required super.deviceId,
required super.deviceName, required super.deviceName,
@ -267,7 +265,7 @@ final class CpsMaxDistanceOfStaticDetectionFunction extends CpsFunctions {
max = 10.0, max = 10.0,
step = 0.5, step = 0.5,
super( super(
code: 'static_max_dis', // 0 / 500 code: 'static_max_dis',
operationName: 'Maximum Distance Of Static Detection', operationName: 'Maximum Distance Of Static Detection',
icon: Assets.currentDistanceIcon, icon: Assets.currentDistanceIcon,
); );
@ -302,7 +300,7 @@ final class CpsDetectionRangeFunction extends CpsFunctions {
max = 25.5, max = 25.5,
step = 0.1, step = 0.1,
super( super(
code: 'moving_range', // just then code: 'moving_range',
operationName: 'Detection Range', operationName: 'Detection Range',
icon: Assets.farDetection, icon: Assets.farDetection,
); );
@ -372,7 +370,7 @@ final class CpsPresenceJudgementThrsholdFunction extends CpsFunctions {
max = 255, max = 255,
step = 5, step = 5,
super( super(
code: 'presence_reference', // max 255 // change widget code: 'presence_reference',
operationName: 'Presence Judgement Threshold', operationName: 'Presence Judgement Threshold',
icon: Assets.presenceJudgementThrshold, icon: Assets.presenceJudgementThrshold,
); );
@ -403,7 +401,7 @@ final class CpsMotionAmplitudeTriggerThresholdFunction extends CpsFunctions {
max = 255, max = 255,
step = 5, step = 5,
super( super(
code: 'moving_reference', // max 255 // change widget code: 'moving_reference',
operationName: 'Motion Amplitude Trigger Threshold', operationName: 'Motion Amplitude Trigger Threshold',
icon: Assets.presenceJudgementThrshold, icon: Assets.presenceJudgementThrshold,
); );
@ -434,7 +432,7 @@ final class CpsPerpetualBoundaryFunction extends CpsFunctions {
max = 5.00, max = 5.00,
step = 0.50, step = 0.50,
super( super(
code: 'perceptual_boundary', // 0 / 500 code: 'perceptual_boundary',
operationName: 'Perpetual Boundary', operationName: 'Perpetual Boundary',
icon: Assets.boundary, icon: Assets.boundary,
); );
@ -469,7 +467,7 @@ final class CpsMotionTriggerBoundaryFunction extends CpsFunctions {
max = 5.0, max = 5.0,
step = 0.5, step = 0.5,
super( super(
code: 'moving_boundary', // 0 / 500 / step 50 code: 'moving_boundary',
operationName: 'Motion Trigger Boundary', operationName: 'Motion Trigger Boundary',
icon: Assets.motionMeter, icon: Assets.motionMeter,
); );
@ -504,7 +502,7 @@ final class CpsMotionTriggerTimeFunction extends CpsFunctions {
max = 2.0, max = 2.0,
step = 0.1, step = 0.1,
super( super(
code: 'moving_rigger_time', // 0 / 2000 steps 10 code: 'moving_rigger_time',
operationName: 'Motion Trigger Time', operationName: 'Motion Trigger Time',
icon: Assets.motionMeter, icon: Assets.motionMeter,
); );
@ -539,7 +537,7 @@ final class CpsMotionToStaticTimeFunction extends CpsFunctions {
max = 50.0, max = 50.0,
step = 1.0, step = 1.0,
super( super(
code: 'moving_static_time', // 0 / 6000 steps 100 code: 'moving_static_time',
operationName: 'Motion To Static Time', operationName: 'Motion To Static Time',
icon: Assets.motionMeter, icon: Assets.motionMeter,
); );
@ -574,7 +572,7 @@ final class CpsEnteringNoBodyStateTimeFunction extends CpsFunctions {
max = 300.0, max = 300.0,
step = 5.0, step = 5.0,
super( super(
code: 'none_body_time', // 0 / 300000 / steps 500 code: 'none_body_time',
operationName: 'Entering Nobody State Time', operationName: 'Entering Nobody State Time',
icon: Assets.motionMeter, icon: Assets.motionMeter,
); );
@ -606,7 +604,7 @@ final class CpsSelfTestResultFunctions extends CpsFunctions {
required super.deviceName, required super.deviceName,
required super.type, required super.type,
}) : super( }) : super(
code: 'checking_result', // just in action code: 'checking_result',
operationName: 'Self-Test Result', operationName: 'Self-Test Result',
icon: Assets.selfTestResult, icon: Assets.selfTestResult,
); );
@ -828,7 +826,7 @@ class CpsPresenceStatusFunctions extends CpsFunctions {
required super.deviceName, required super.deviceName,
required super.type, required super.type,
}) : super( }) : super(
code: 'presence_state', // just in action code: 'presence_state',
operationName: 'Presence Status', operationName: 'Presence Status',
icon: Assets.presenceSensor, icon: Assets.presenceSensor,
); );
@ -864,7 +862,7 @@ final class CpsSportsParaFunction extends CpsFunctions {
max = 100, max = 100,
step = 1, step = 1,
super( super(
code: 'sports_para', // just in action code: 'sports_para',
operationName: 'Sports Para', operationName: 'Sports Para',
icon: Assets.sportsPara, icon: Assets.sportsPara,
); );

View File

@ -29,8 +29,7 @@ class _RoutinesViewState extends State<RoutinesView> {
final spaceId = result['space']; final spaceId = result['space'];
final _bloc = BlocProvider.of<CreateRoutineBloc>(context); final _bloc = BlocProvider.of<CreateRoutineBloc>(context);
final routineBloc = context.read<RoutineBloc>(); final routineBloc = context.read<RoutineBloc>();
_bloc.add(SaveCommunityIdAndSpaceIdEvent( _bloc.add(SaveCommunityIdAndSpaceIdEvent(communityID: communityId, spaceID: spaceId));
communityID: communityId, spaceID: spaceId));
await Future.delayed(const Duration(seconds: 1)); await Future.delayed(const Duration(seconds: 1));
routineBloc.add(const CreateNewRoutineViewEvent(createRoutineView: true)); routineBloc.add(const CreateNewRoutineViewEvent(createRoutineView: true));
} }
@ -49,7 +48,8 @@ class _RoutinesViewState extends State<RoutinesView> {
child: SpaceTreeView( child: SpaceTreeView(
onSelect: () => context.read<RoutineBloc>() onSelect: () => context.read<RoutineBloc>()
..add(const LoadScenes()) ..add(const LoadScenes())
..add(const LoadAutomation()), ..add(const LoadAutomation())
..add(FetchDevicesInRoutine()),
), ),
), ),
Expanded( Expanded(
@ -64,11 +64,10 @@ class _RoutinesViewState extends State<RoutinesView> {
children: [ children: [
Text( Text(
"Create New Routines", "Create New Routines",
style: style: Theme.of(context).textTheme.titleLarge?.copyWith(
Theme.of(context).textTheme.titleLarge?.copyWith( color: ColorsManager.grayColor,
color: ColorsManager.grayColor, fontWeight: FontWeight.bold,
fontWeight: FontWeight.bold, ),
),
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
RoutineViewCard( RoutineViewCard(

View File

@ -8,12 +8,12 @@ class DialogFooter extends StatelessWidget {
final int? dialogWidth; final int? dialogWidth;
const DialogFooter({ const DialogFooter({
Key? key, super.key,
required this.onCancel, required this.onCancel,
required this.onConfirm, required this.onConfirm,
required this.isConfirmEnabled, required this.isConfirmEnabled,
this.dialogWidth, this.dialogWidth,
}) : super(key: key); });
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -28,21 +28,19 @@ class DialogFooter extends StatelessWidget {
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly, mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [ children: [
Expanded( _buildFooterButton(
child: _buildFooterButton( context: context,
context, text: 'Cancel',
'Cancel', onTap: onCancel,
onCancel,
),
), ),
if (isConfirmEnabled) ...[ if (isConfirmEnabled) ...[
Container(width: 1, height: 50, color: ColorsManager.greyColor), Container(width: 1, height: 50, color: ColorsManager.greyColor),
Expanded( _buildFooterButton(
child: _buildFooterButton( context: context,
context, text: 'Confirm',
'Confirm', onTap: onConfirm,
onConfirm, textColor:
), isConfirmEnabled ? ColorsManager.primaryColorWithOpacity : Colors.red,
), ),
], ],
], ],
@ -50,24 +48,24 @@ class DialogFooter extends StatelessWidget {
); );
} }
Widget _buildFooterButton( Widget _buildFooterButton({
BuildContext context, required BuildContext context,
String text, required String text,
VoidCallback? onTap, required VoidCallback? onTap,
) { Color? textColor,
return GestureDetector( }) {
onTap: onTap, return Expanded(
child: SizedBox( child: TextButton(
height: 50, style: TextButton.styleFrom(
child: Center( foregroundColor: ColorsManager.primaryColorWithOpacity,
child: Text( disabledForegroundColor: ColorsManager.primaryColor,
text, ),
style: Theme.of(context).textTheme.bodyMedium!.copyWith( onPressed: onTap,
color: text == 'Confirm' child: Text(
? ColorsManager.primaryColorWithOpacity text,
: ColorsManager.textGray, style: Theme.of(context).textTheme.bodyMedium?.copyWith(
), color: textColor ?? ColorsManager.textGray,
), ),
), ),
), ),
); );

View File

@ -6,6 +6,7 @@ import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routines/models/device_functions.dart'; import 'package:syncrow_web/pages/routines/models/device_functions.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart';
class DraggableCard extends StatelessWidget { class DraggableCard extends StatelessWidget {
@ -68,9 +69,9 @@ class DraggableCard extends StatelessWidget {
Card( Card(
color: ColorsManager.whiteColors, color: ColorsManager.whiteColors,
child: Container( child: Container(
padding: padding ?? const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
width: 110, width: 110,
height: deviceFunctions.isEmpty ? 123 : null, height: deviceFunctions.isEmpty ? 160 : 180,
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
@ -78,6 +79,7 @@ class DraggableCard extends StatelessWidget {
children: [ children: [
Column( Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
Container( Container(
height: 50, height: 50,
@ -112,8 +114,69 @@ class DraggableCard extends StatelessWidget {
), ),
), ),
), ),
const SizedBox(
height: 4,
),
Visibility(
visible: deviceData['tag'] != null && deviceData['tag'] != '',
child: Row(
spacing: 2,
children: [
SizedBox(
width: 8, height: 8, child: SvgPicture.asset(Assets.deviceTagIcon)),
Flexible(
child: Text(
deviceData['tag'] ?? '',
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
maxLines: 1,
style: context.textTheme.bodySmall?.copyWith(
color: ColorsManager.lightGreyColor,
fontSize: 9,
fontWeight: FontWeight.w400,
),
),
),
],
),
),
Visibility(
visible: deviceData['subSpace'] != null && deviceData['subSpace'] != '',
child: const SizedBox(
height: 4,
),
),
Visibility(
visible: deviceData['subSpace'] != null && deviceData['subSpace'] != '',
child: Row(
spacing: 2,
children: [
SizedBox(
width: 8,
height: 8,
child: SvgPicture.asset(Assets.spaceLocationIcon)),
Flexible(
child: Text(
deviceData['subSpace'] ?? '',
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
maxLines: 1,
style: context.textTheme.bodySmall?.copyWith(
color: ColorsManager.lightGreyColor,
fontSize: 9,
fontWeight: FontWeight.w400,
),
),
),
],
),
),
], ],
), ),
if (deviceFunctions.isNotEmpty)
const SizedBox(
height: 4,
),
if (deviceFunctions.isNotEmpty) if (deviceFunctions.isNotEmpty)
...deviceFunctions.map((function) => Row( ...deviceFunctions.map((function) => Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
@ -123,7 +186,7 @@ class DraggableCard extends StatelessWidget {
'${function.operationName}: ${_formatFunctionValue(function)}', '${function.operationName}: ${_formatFunctionValue(function)}',
style: context.textTheme.bodySmall?.copyWith( style: context.textTheme.bodySmall?.copyWith(
fontSize: 9, fontSize: 9,
color: ColorsManager.textGray, color: ColorsManager.lightGreyColor,
height: 1.2, height: 1.2,
), ),
maxLines: 2, maxLines: 2,

View File

@ -17,90 +17,87 @@ class IfContainer extends StatelessWidget {
builder: (context, state) { builder: (context, state) {
return DragTarget<Map<String, dynamic>>( return DragTarget<Map<String, dynamic>>(
builder: (context, candidateData, rejectedData) { builder: (context, candidateData, rejectedData) {
return Container( return SingleChildScrollView(
width: double.infinity, child: Container(
padding: const EdgeInsets.all(16), width: double.infinity,
child: Column( padding: const EdgeInsets.all(16),
crossAxisAlignment: CrossAxisAlignment.start, child: Column(
children: [ crossAxisAlignment: CrossAxisAlignment.start,
Row( children: [
mainAxisAlignment: MainAxisAlignment.spaceBetween, Row(
children: [ mainAxisAlignment: MainAxisAlignment.spaceBetween,
const Text('IF',
style:
TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
if (state.isAutomation && state.ifItems.isNotEmpty)
AutomationOperatorSelector(
selectedOperator: state.selectedAutomationOperator),
],
),
const SizedBox(height: 16),
if (state.isTabToRun)
const Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
DraggableCard( const Text('IF',
imagePath: Assets.tabToRun, style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
title: 'Tab to run', if (state.isAutomation && state.ifItems.isNotEmpty)
deviceData: {}, AutomationOperatorSelector(
), selectedOperator: state.selectedAutomationOperator),
], ],
), ),
if (!state.isTabToRun) const SizedBox(height: 16),
Wrap( if (state.isTabToRun)
spacing: 8, const Row(
runSpacing: 8, mainAxisAlignment: MainAxisAlignment.center,
children: List.generate( children: [
state.ifItems.length, DraggableCard(
(index) => GestureDetector( imagePath: Assets.tabToRun,
onTap: () async { title: 'Tab to run',
if (!state.isTabToRun) { deviceData: {},
final result = ),
await DeviceDialogHelper.showDeviceDialog( ],
context: context, ),
data: state.ifItems[index], if (!state.isTabToRun)
removeComparetors: false, Wrap(
dialogType: "IF"); spacing: 8,
runSpacing: 8,
children: List.generate(
state.ifItems.length,
(index) => GestureDetector(
onTap: () async {
if (!state.isTabToRun) {
final result = await DeviceDialogHelper.showDeviceDialog(
context: context,
data: state.ifItems[index],
removeComparetors: false,
dialogType: "IF");
if (result != null) { if (result != null) {
context.read<RoutineBloc>().add( context
AddToIfContainer( .read<RoutineBloc>()
state.ifItems[index], false)); .add(AddToIfContainer(state.ifItems[index], false));
} else if (![ } else if (![
'AC', 'AC',
'1G', '1G',
'2G', '2G',
'3G', '3G',
'WPS', 'WPS',
'GW', 'GW',
'CPS', 'CPS',
].contains( ].contains(state.ifItems[index]['productType'])) {
state.ifItems[index]['productType'])) { context
context.read<RoutineBloc>().add( .read<RoutineBloc>()
AddToIfContainer( .add(AddToIfContainer(state.ifItems[index], false));
state.ifItems[index], false)); }
} }
}
},
child: DraggableCard(
imagePath: state.ifItems[index]['imagePath'] ?? '',
title: state.ifItems[index]['title'] ?? '',
deviceData: state.ifItems[index],
padding: const EdgeInsets.symmetric(
horizontal: 4, vertical: 8),
isFromThen: false,
isFromIf: true,
onRemove: () {
context.read<RoutineBloc>().add(RemoveDragCard(
index: index,
isFromThen: false,
key: state.ifItems[index]
['uniqueCustomId']));
}, },
), child: DraggableCard(
)), imagePath: state.ifItems[index]['imagePath'] ?? '',
), title: state.ifItems[index]['title'] ?? '',
], deviceData: state.ifItems[index],
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 8),
isFromThen: false,
isFromIf: true,
onRemove: () {
context.read<RoutineBloc>().add(RemoveDragCard(
index: index,
isFromThen: false,
key: state.ifItems[index]['uniqueCustomId']));
},
),
)),
),
],
),
), ),
); );
}, },
@ -124,14 +121,10 @@ class IfContainer extends StatelessWidget {
removeComparetors: false); removeComparetors: false);
if (result != null) { if (result != null) {
context context.read<RoutineBloc>().add(AddToIfContainer(mutableData, false));
.read<RoutineBloc>()
.add(AddToIfContainer(mutableData, false));
} else if (!['AC', '1G', '2G', '3G', 'WPS', 'GW', 'CPS'] } else if (!['AC', '1G', '2G', '3G', 'WPS', 'GW', 'CPS']
.contains(mutableData['productType'])) { .contains(mutableData['productType'])) {
context context.read<RoutineBloc>().add(AddToIfContainer(mutableData, false));
.read<RoutineBloc>()
.add(AddToIfContainer(mutableData, false));
} }
} }
} }
@ -177,9 +170,7 @@ class AutomationOperatorSelector extends StatelessWidget {
), ),
), ),
onPressed: () { onPressed: () {
context context.read<RoutineBloc>().add(const ChangeAutomationOperator(operator: 'or'));
.read<RoutineBloc>()
.add(const ChangeAutomationOperator(operator: 'or'));
}, },
), ),
Container( Container(
@ -205,9 +196,7 @@ class AutomationOperatorSelector extends StatelessWidget {
), ),
), ),
onPressed: () { onPressed: () {
context context.read<RoutineBloc>().add(const ChangeAutomationOperator(operator: 'and'));
.read<RoutineBloc>()
.add(const ChangeAutomationOperator(operator: 'and'));
}, },
), ),
], ],

View File

@ -121,8 +121,7 @@ class _RoutineViewCardState extends State<RoutineViewCard> {
child: SizedBox( child: SizedBox(
width: 16, width: 16,
height: 16, height: 16,
child: child: CircularProgressIndicator(strokeWidth: 2),
CircularProgressIndicator(strokeWidth: 2),
), ),
), ),
) )
@ -159,9 +158,7 @@ class _RoutineViewCardState extends State<RoutineViewCard> {
height: iconSize, height: iconSize,
width: iconSize, width: iconSize,
fit: BoxFit.contain, fit: BoxFit.contain,
errorBuilder: errorBuilder: (context, error, stackTrace) => Image.asset(
(context, error, stackTrace) =>
Image.asset(
Assets.logo, Assets.logo,
height: iconSize, height: iconSize,
width: iconSize, width: iconSize,
@ -174,8 +171,7 @@ class _RoutineViewCardState extends State<RoutineViewCard> {
width: iconSize, width: iconSize,
fit: BoxFit.contain, fit: BoxFit.contain,
) )
: (widget.icon is String && : (widget.icon is String && widget.icon.endsWith('.svg'))
widget.icon.endsWith('.svg'))
? SvgPicture.asset( ? SvgPicture.asset(
height: iconSize, height: iconSize,
width: iconSize, width: iconSize,
@ -185,9 +181,7 @@ class _RoutineViewCardState extends State<RoutineViewCard> {
: Icon( : Icon(
widget.icon, widget.icon,
color: ColorsManager.dialogBlueTitle, color: ColorsManager.dialogBlueTitle,
size: widget.isSmallScreenSize(context) size: widget.isSmallScreenSize(context) ? 30 : 40,
? 30
: 40,
), ),
), ),
), ),
@ -200,11 +194,10 @@ class _RoutineViewCardState extends State<RoutineViewCard> {
widget.textString, widget.textString,
textAlign: TextAlign.center, textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
maxLines: 2, maxLines: 1,
style: context.textTheme.bodySmall?.copyWith( style: context.textTheme.bodySmall?.copyWith(
color: ColorsManager.blackColor, color: ColorsManager.blackColor,
fontSize: fontSize: widget.isSmallScreenSize(context) ? 10 : 12,
widget.isSmallScreenSize(context) ? 10 : 12,
), ),
), ),
if (widget.spaceName != '') if (widget.spaceName != '')
@ -220,14 +213,10 @@ class _RoutineViewCardState extends State<RoutineViewCard> {
widget.spaceName, widget.spaceName,
textAlign: TextAlign.center, textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
maxLines: 2, maxLines: 1,
style: style: context.textTheme.bodySmall?.copyWith(
context.textTheme.bodySmall?.copyWith(
color: ColorsManager.blackColor, color: ColorsManager.blackColor,
fontSize: fontSize: widget.isSmallScreenSize(context) ? 10 : 12,
widget.isSmallScreenSize(context)
? 10
: 12,
), ),
), ),
], ],

View File

@ -51,12 +51,12 @@ class _RoutineDevicesState extends State<RoutineDevices> {
'productType': device.productType, 'productType': device.productType,
'functions': device.functions, 'functions': device.functions,
'uniqueCustomId': '', 'uniqueCustomId': '',
'tag': device.deviceTags!.isNotEmpty ? device.deviceTags![0].name : '',
'subSpace': device.deviceSubSpace?.subspaceName ?? '',
}; };
if (state.searchText != null && state.searchText!.isNotEmpty) { if (state.searchText != null && state.searchText!.isNotEmpty) {
return device.name! return device.name!.toLowerCase().contains(state.searchText!.toLowerCase())
.toLowerCase()
.contains(state.searchText!.toLowerCase())
? DraggableCard( ? DraggableCard(
imagePath: deviceData['imagePath'] as String, imagePath: deviceData['imagePath'] as String,
title: deviceData['title'] as String, title: deviceData['title'] as String,

View File

@ -11,6 +11,7 @@ import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/ceiling_senso
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_dialog_slider_selector.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_dialog_slider_selector.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_dialog_value_selector.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_dialog_value_selector.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_functions_list.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_functions_list.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_slider_helpers.dart';
class CeilingSensorDialog extends StatefulWidget { class CeilingSensorDialog extends StatefulWidget {
const CeilingSensorDialog({ const CeilingSensorDialog({
@ -34,6 +35,7 @@ class CeilingSensorDialog extends StatefulWidget {
class _CeilingSensorDialogState extends State<CeilingSensorDialog> { class _CeilingSensorDialogState extends State<CeilingSensorDialog> {
late final List<CpsFunctions> _cpsFunctions; late final List<CpsFunctions> _cpsFunctions;
late final String _dialogHeaderText;
@override @override
void initState() { void initState() {
@ -45,6 +47,9 @@ class _CeilingSensorDialogState extends State<CeilingSensorDialog> {
} }
return function.type == 'IF' || function.type == 'BOTH'; return function.type == 'IF' || function.type == 'BOTH';
}).toList(); }).toList();
final isIfDialog = widget.dialogType == 'IF';
_dialogHeaderText = isIfDialog ? 'Conditions' : 'Functions';
} }
@override @override
@ -66,15 +71,18 @@ class _CeilingSensorDialogState extends State<CeilingSensorDialog> {
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
const DialogHeader('Presence Sensor Condition'), DialogHeader('Presence Sensor $_dialogHeaderText'),
Expanded(child: _buildMainContent(context, state)), Expanded(child: _buildMainContent(context, state)),
DialogFooter( DialogFooter(
onCancel: () => Navigator.pop(context), onCancel: () => Navigator.pop(context),
onConfirm: state.addedFunctions.isNotEmpty onConfirm: state.addedFunctions.isNotEmpty
? () { ? () {
final functions = _updateValuesForAddedFunctions(
state.addedFunctions,
);
context.read<RoutineBloc>().add( context.read<RoutineBloc>().add(
AddFunctionToRoutine( AddFunctionToRoutine(
state.addedFunctions, functions,
'${widget.uniqueCustomId}', '${widget.uniqueCustomId}',
), ),
); );
@ -148,4 +156,64 @@ class _CeilingSensorDialogState extends State<CeilingSensorDialog> {
], ],
); );
} }
static const _mappableSteppedFunctions = <String>{
'static_max_dis',
'presence_reference',
'moving_reference',
'perceptual_boundary',
'moving_boundary',
'moving_rigger_time',
'moving_static_time',
'none_body_time',
'moving_max_dis',
'moving_range',
'presence_range',
};
List<DeviceFunctionData> _updateValuesForAddedFunctions(
List<DeviceFunctionData> addedFunctions,
) {
return addedFunctions.map((function) {
final shouldMapValue = _mappableSteppedFunctions.contains(
function.functionCode,
);
if (shouldMapValue) {
final mappedValue = _mapSteppedValue(
value: function.value,
inputStep: CpsSliderHelpers.dividendOfRange(function.functionCode),
inputRange: CpsSliderHelpers.sliderRange(function.functionCode),
outputRange: CpsSliderHelpers.mappedRange(function.functionCode),
);
return DeviceFunctionData(
value: mappedValue,
entityId: function.entityId,
functionCode: function.functionCode,
operationName: function.operationName,
condition: function.condition,
actionExecutor: function.actionExecutor,
valueDescription: function.valueDescription,
);
}
return function;
}).toList();
}
int _mapSteppedValue({
required (double min, double max) inputRange,
required double inputStep,
required (double min, double max, double dividend) outputRange,
required double value,
}) {
final (inputMin, inputMax) = inputRange;
final (outputMin, outputMax, outputStep) = outputRange;
final clampedValue = value.clamp(inputMin, inputMax);
final stepsFromMin = ((clampedValue - inputMin) / inputStep).round();
final mappedValue = outputMin + (stepsFromMin * outputStep);
return mappedValue.clamp(outputMin, outputMax).round();
}
} }

View File

@ -4,6 +4,7 @@ import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_mo
import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart'; import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart';
import 'package:syncrow_web/pages/routines/models/ceiling_presence_sensor_functions.dart'; import 'package:syncrow_web/pages/routines/models/ceiling_presence_sensor_functions.dart';
import 'package:syncrow_web/pages/routines/models/device_functions.dart'; import 'package:syncrow_web/pages/routines/models/device_functions.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_slider_helpers.dart';
import 'package:syncrow_web/pages/routines/widgets/slider_value_selector.dart'; import 'package:syncrow_web/pages/routines/widgets/slider_value_selector.dart';
class CpsDialogSliderSelector extends StatelessWidget { class CpsDialogSliderSelector extends StatelessWidget {
@ -31,10 +32,13 @@ class CpsDialogSliderSelector extends StatelessWidget {
return SliderValueSelector( return SliderValueSelector(
currentCondition: selectedFunctionData.condition, currentCondition: selectedFunctionData.condition,
dialogType: dialogType, dialogType: dialogType,
sliderRange: _sliderRange, sliderRange: CpsSliderHelpers.sliderRange(selectedFunctionData.functionCode),
displayedValue: _displayText, displayedValue: CpsSliderHelpers.displayText(
value: selectedFunctionData.value,
functionCode: selectedFunctionData.functionCode,
),
initialValue: selectedFunctionData.value ?? 0, initialValue: selectedFunctionData.value ?? 0,
unit: _unit, unit: CpsSliderHelpers.unit(selectedFunctionData.functionCode),
onConditionChanged: (condition) => context.read<FunctionBloc>().add( onConditionChanged: (condition) => context.read<FunctionBloc>().add(
AddFunction( AddFunction(
functionData: DeviceFunctionData( functionData: DeviceFunctionData(
@ -52,85 +56,14 @@ class CpsDialogSliderSelector extends StatelessWidget {
entityId: device?.uuid ?? '', entityId: device?.uuid ?? '',
functionCode: selectedFunction, functionCode: selectedFunction,
operationName: operationName, operationName: operationName,
value: value, value: double.parse(value.toStringAsFixed(2)),
condition: selectedFunctionData.condition, condition: selectedFunctionData.condition,
), ),
), ),
), ),
dividendOfRange: _dividendOfRange, dividendOfRange: CpsSliderHelpers.dividendOfRange(
selectedFunctionData.functionCode,
),
); );
} }
(double, double) get _sliderRange => switch (selectedFunctionData.functionCode) {
'moving_speed' => (0, 32),
'sensitivity' => (0, 10),
'space_static_val' => (0, 255),
'space_move_val' => (0, 255),
'moving_max_dis' => (0, 10),
'static_max_dis' => (0, 10),
'moving_range' => (0, 25.5),
'presence_range' => (0, 25.5),
'presence_judgement_threshold' => (0, 255),
'motion_amplitude_trigger_threshold' => (0, 255),
'perceptual_boundary' => (0, 5),
'moving_boundary' => (0, 5),
'moving_rigger_time' => (0, 2),
'moving_static_time' => (0, 50),
'none_body_time' => (0, 300),
_ => (0, 300),
};
String get _displayText {
final value = selectedFunctionData.value;
final parsedValue = double.tryParse('$value');
return switch (selectedFunctionData.functionCode) {
'moving_max_dis' ||
'static_max_dis' ||
'moving_range' ||
'presence_range' ||
'perceptual_boundary' ||
'moving_boundary' =>
parsedValue?.toStringAsFixed(1) ?? '0',
'moving_rigger_time' => parsedValue?.toStringAsFixed(2) ?? '0',
'moving_static_time' ||
'none_body_time' =>
parsedValue?.toStringAsFixed(2) ?? '0',
_ => '${parsedValue?.toStringAsFixed(0) ?? 0}',
};
}
String get _unit {
return switch (selectedFunctionData.functionCode) {
'moving_max_dis' ||
'static_max_dis' ||
'moving_range' ||
'presence_range' ||
'perceptual_boundary' ||
'moving_boundary' =>
'M',
'moving_rigger_time' || 'moving_static_time' || 'none_body_time' => 'sec',
_ => '',
};
}
double get _dividendOfRange => switch (selectedFunctionData.functionCode) {
'sensitivity' => 1,
'moving_speed' => 1,
'space_static_val' => 1,
'space_move_val' => 1,
'presence_reference' => 5,
'moving_reference' => 5,
'moving_max_dis' => 0.5,
'static_max_dis' => 0.5,
'moving_range' => 0.1,
'presence_range' => 0.1,
'perceptual_boundary' => 0.5,
'moving_boundary' => 0.5,
'moving_rigger_time' => 0.1,
'moving_static_time' => 1.0,
'none_body_time' => 5.0,
'sports_para' => 1.0,
_ => 1,
};
} }

View File

@ -0,0 +1,90 @@
abstract final class CpsSliderHelpers {
static (double min, double max, double step) mappedRange(String functionCode) {
final (defaultMin, defaultMax) = sliderRange(functionCode);
final defaultDivdidend = dividendOfRange(functionCode);
return switch (functionCode) {
'static_max_dis' => (0, 500, 50),
'presence_reference' => (0, 255, 5),
'moving_reference' => (0, 255, 5),
'perceptual_boundary' => (0, 500, 50),
'moving_boundary' => (0, 500, 50),
'moving_rigger_time' => (0, 2000, 100),
'moving_static_time' => (0, 60000, 1000),
'none_body_time' => (0, 300000, 5000),
'moving_max_dis' => (0, 500, 50),
'moving_range' => (0, 255, 5),
'presence_range' => (0, 255, 5),
_ => (defaultMin, defaultMax, defaultDivdidend),
};
}
static (double min, double max) sliderRange(String functionCode) =>
switch (functionCode) {
'moving_speed' => (0, 32),
'sensitivity' => (0, 10),
'space_static_val' => (0, 255),
'space_move_val' => (0, 255),
'moving_max_dis' => (0, 10),
'static_max_dis' => (0, 10),
'moving_range' => (0, 25.5),
'presence_range' => (0, 25.5),
'presence_judgement_threshold' => (0, 255),
'motion_amplitude_trigger_threshold' => (0, 255),
'perceptual_boundary' => (0, 5),
'moving_boundary' => (0, 5),
'moving_rigger_time' => (0, 2),
'moving_static_time' => (0, 50),
'none_body_time' => (0, 300),
'sports_para' => (0, 100),
_ => (0, 300),
};
static double dividendOfRange(String functionCode) => switch (functionCode) {
'presence_reference' => 5,
'moving_reference' => 5,
'moving_max_dis' => 0.5,
'static_max_dis' => 0.5,
'moving_range' => 0.1,
'presence_range' => 0.1,
'perceptual_boundary' => 0.5,
'moving_boundary' => 0.5,
'moving_rigger_time' => 0.1,
'moving_static_time' => 1.0,
'none_body_time' => 5.0,
_ => 1,
};
static String unit(String functionCode) => switch (functionCode) {
'moving_max_dis' ||
'static_max_dis' ||
'moving_range' ||
'presence_range' ||
'perceptual_boundary' ||
'moving_boundary' =>
'M',
'moving_rigger_time' || 'moving_static_time' || 'none_body_time' => 'sec',
_ => '',
};
static String displayText({
required dynamic value,
required String functionCode,
}) {
final parsedValue = double.tryParse('$value');
return switch (functionCode) {
'moving_max_dis' ||
'static_max_dis' ||
'moving_range' ||
'presence_range' ||
'perceptual_boundary' ||
'moving_boundary' =>
parsedValue?.toStringAsFixed(1) ?? '0',
'moving_rigger_time' => parsedValue?.toStringAsFixed(2) ?? '0',
'moving_static_time' ||
'none_body_time' =>
parsedValue?.toStringAsFixed(2) ?? '0',
_ => '${parsedValue?.toStringAsFixed(0) ?? 0}',
};
}
}

View File

@ -5,7 +5,6 @@ import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_mo
import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart'; import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart';
import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routines/helper/duration_format_helper.dart'; import 'package:syncrow_web/pages/routines/helper/duration_format_helper.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/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/base_switch_function.dart';
import 'package:syncrow_web/pages/routines/models/gang_switches/one_gang_switch/one_gang_switch.dart'; import 'package:syncrow_web/pages/routines/models/gang_switches/one_gang_switch/one_gang_switch.dart';
@ -25,23 +24,21 @@ class OneGangSwitchHelper {
required String uniqueCustomId, required String uniqueCustomId,
required bool removeComparetors, required bool removeComparetors,
}) async { }) async {
List<BaseSwitchFunction> oneGangFunctions = List<BaseSwitchFunction> oneGangFunctions = functions.whereType<BaseSwitchFunction>().toList();
functions.whereType<BaseSwitchFunction>().toList();
return showDialog<Map<String, dynamic>?>( return showDialog<Map<String, dynamic>?>(
context: context, context: context,
builder: (BuildContext context) { builder: (BuildContext context) {
return BlocProvider( return BlocProvider(
create: (_) => FunctionBloc() create: (_) => FunctionBloc()..add(InitializeFunctions(deviceSelectedFunctions ?? [])),
..add(InitializeFunctions(deviceSelectedFunctions ?? [])),
child: AlertDialog( child: AlertDialog(
contentPadding: EdgeInsets.zero, contentPadding: EdgeInsets.zero,
content: BlocBuilder<FunctionBloc, FunctionBlocState>( content: BlocBuilder<FunctionBloc, FunctionBlocState>(
builder: (context, state) { builder: (context, state) {
final selectedFunction = state.selectedFunction; final selectedFunction = state.selectedFunction;
final selectedOperationName = state.selectedOperationName; final selectedOperationName = state.selectedOperationName;
final selectedFunctionData = state.addedFunctions final selectedFunctionData =
.firstWhere((f) => f.functionCode == selectedFunction, state.addedFunctions.firstWhere((f) => f.functionCode == selectedFunction,
orElse: () => DeviceFunctionData( orElse: () => DeviceFunctionData(
entityId: '', entityId: '',
functionCode: selectedFunction ?? '', functionCode: selectedFunction ?? '',
@ -88,12 +85,9 @@ class OneGangSwitchHelper {
color: ColorsManager.textGray, color: ColorsManager.textGray,
), ),
onTap: () { onTap: () {
context context.read<FunctionBloc>().add(SelectFunction(
.read<FunctionBloc>()
.add(SelectFunction(
functionCode: function.code, functionCode: function.code,
operationName: operationName: function.operationName,
function.operationName,
)); ));
}, },
); );
@ -226,11 +220,11 @@ class OneGangSwitchHelper {
selectedFunctionData, selectedFunctionData,
), ),
const SizedBox(height: 20), const SizedBox(height: 20),
_buildCountDownDisplay(context, initialValue, device, operationName, _buildCountDownDisplay(
selectedFunctionData, selectCode), context, initialValue, device, operationName, selectedFunctionData, selectCode),
const SizedBox(height: 20), const SizedBox(height: 20),
_buildCountDownSlider(context, initialValue, device, operationName, _buildCountDownSlider(
selectedFunctionData, selectCode), context, initialValue, device, operationName, selectedFunctionData, selectCode),
], ],
); );
} }
@ -271,8 +265,7 @@ class OneGangSwitchHelper {
minHeight: 40.0, minHeight: 40.0,
minWidth: 40.0, minWidth: 40.0,
), ),
isSelected: isSelected: conditions.map((c) => c == (currentCondition ?? "==")).toList(),
conditions.map((c) => c == (currentCondition ?? "==")).toList(),
children: conditions.map((c) => Text(c)).toList(), children: conditions.map((c) => Text(c)).toList(),
); );
} }
@ -320,8 +313,7 @@ class OneGangSwitchHelper {
value: (initialValue ?? 0).toDouble(), value: (initialValue ?? 0).toDouble(),
min: operationalValues.minValue?.toDouble() ?? 0.0, min: operationalValues.minValue?.toDouble() ?? 0.0,
max: operationalValues.maxValue?.toDouble() ?? 0.0, max: operationalValues.maxValue?.toDouble() ?? 0.0,
divisions: (((operationalValues.maxValue ?? 0) - divisions: (((operationalValues.maxValue ?? 0) - (operationalValues.minValue ?? 0)) /
(operationalValues.minValue ?? 0)) /
(operationalValues.stepValue ?? 1)) (operationalValues.stepValue ?? 1))
.round(), .round(),
onChanged: (value) { onChanged: (value) {
@ -373,13 +365,9 @@ class OneGangSwitchHelper {
style: context.textTheme.bodyMedium, style: context.textTheme.bodyMedium,
), ),
trailing: Icon( trailing: Icon(
isSelected isSelected ? Icons.radio_button_checked : Icons.radio_button_unchecked,
? Icons.radio_button_checked
: Icons.radio_button_unchecked,
size: 24, size: 24,
color: isSelected color: isSelected ? ColorsManager.primaryColorWithOpacity : ColorsManager.textGray,
? ColorsManager.primaryColorWithOpacity
: ColorsManager.textGray,
), ),
onTap: () { onTap: () {
if (!isSelected) { if (!isSelected) {
@ -391,8 +379,7 @@ class OneGangSwitchHelper {
operationName: operationName, operationName: operationName,
value: value.value, value: value.value,
condition: selectedFunctionData?.condition, condition: selectedFunctionData?.condition,
valueDescription: valueDescription: selectedFunctionData?.valueDescription,
selectedFunctionData?.valueDescription,
), ),
), ),
); );

View File

@ -1,4 +1,3 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
@ -28,9 +27,12 @@ class _TimeWheelPickerState extends State<TimeWheelPicker> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_hoursController = FixedExtentScrollController(initialItem: widget.initialHours); _hoursController =
_minutesController = FixedExtentScrollController(initialItem: widget.initialMinutes); FixedExtentScrollController(initialItem: widget.initialHours);
_secondsController = FixedExtentScrollController(initialItem: widget.initialSeconds); _minutesController =
FixedExtentScrollController(initialItem: widget.initialMinutes);
_secondsController =
FixedExtentScrollController(initialItem: widget.initialSeconds);
} }
@override @override
@ -47,6 +49,8 @@ class _TimeWheelPickerState extends State<TimeWheelPicker> {
} }
} }
@override @override
void dispose() { void dispose() {
_hoursController.dispose(); _hoursController.dispose();
@ -61,26 +65,28 @@ class _TimeWheelPickerState extends State<TimeWheelPicker> {
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
_buildPickerColumn( _buildPickerColumn(
label: 'h', label: 'h',
controller: _hoursController, controller: _hoursController,
itemCount: 24, itemCount: 3,
onChanged: (value) => _handleTimeChange( onChanged: (value) {
value, _handleTimeChange(
_minutesController.selectedItem, value,
_secondsController.selectedItem, _minutesController.selectedItem,
), _secondsController.selectedItem,
), );
}),
const SizedBox(width: 5), const SizedBox(width: 5),
_buildPickerColumn( _buildPickerColumn(
label: 'm', label: 'm',
controller: _minutesController, controller: _minutesController,
itemCount: 60, itemCount: 60,
onChanged: (value) => _handleTimeChange( onChanged: (value) {
_hoursController.selectedItem, _handleTimeChange(
value, _hoursController.selectedItem,
_secondsController.selectedItem, value,
), _secondsController.selectedItem,
), );
}),
const SizedBox(width: 5), const SizedBox(width: 5),
_buildPickerColumn( _buildPickerColumn(
label: 's', label: 's',
@ -97,6 +103,19 @@ class _TimeWheelPickerState extends State<TimeWheelPicker> {
} }
void _handleTimeChange(int hours, int minutes, int seconds) { void _handleTimeChange(int hours, int minutes, int seconds) {
int total = hours * 3600 + minutes * 60 + seconds;
if (total > 10000) {
hours = 2;
minutes = 46;
seconds = 40;
total = 10000;
WidgetsBinding.instance.addPostFrameCallback((_) {
_hoursController.jumpToItem(hours);
_minutesController.jumpToItem(minutes);
_secondsController.jumpToItem(seconds);
});
}
widget.onTimeChanged(hours, minutes, seconds); widget.onTimeChanged(hours, minutes, seconds);
} }
@ -147,4 +166,4 @@ class _TimeWheelPickerState extends State<TimeWheelPicker> {
], ],
); );
} }
} }

View File

@ -2,6 +2,8 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart'; import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
import 'package:syncrow_web/pages/space_tree/view/space_tree_view.dart'; import 'package:syncrow_web/pages/space_tree/view/space_tree_view.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'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
@ -107,6 +109,11 @@ class _LoadedSpaceViewState extends State<LoadedSpaceView> {
selectedSpaceUuid: widget.selectedSpace?.uuid ?? selectedSpaceUuid: widget.selectedSpace?.uuid ??
widget.selectedCommunity?.uuid ?? widget.selectedCommunity?.uuid ??
'', '',
onCreateCommunity: (name, description) {
context.read<SpaceManagementBloc>().add(
CreateCommunityEvent(name, description, context),
);
},
), ),
CommunityStructureArea( CommunityStructureArea(
selectedCommunity: widget.selectedCommunity, selectedCommunity: widget.selectedCommunity,

View File

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

View File

@ -0,0 +1,36 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/sidebar_add_community_button.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:syncrow_web/utils/style.dart';
class SidebarHeader extends StatelessWidget {
const SidebarHeader({
required this.onAddCommunity,
super.key,
});
final void Function() onAddCommunity;
@override
Widget build(BuildContext context) {
return Container(
decoration: subSectionContainerDecoration,
padding: const EdgeInsets.all(16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Communities',
style: context.textTheme.titleMedium?.copyWith(
color: ColorsManager.blackColor,
),
),
SidebarAddCommunityButton(
onTap: onAddCommunity,
),
],
),
);
}
}

View File

@ -1,80 +1,78 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/svg.dart';
import 'package:syncrow_web/common/widgets/search_bar.dart'; import 'package:syncrow_web/common/widgets/search_bar.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_event.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_event.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/community_tile.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/community_tile.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/sidebar_header.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/space_tile_widget.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/space_tile_widget.dart';
import 'package:syncrow_web/pages/spaces_management/create_community/view/create_community_dialog.dart';
import 'package:syncrow_web/pages/spaces_management/structure_selector/bloc/center_body_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/structure_selector/bloc/center_body_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/structure_selector/bloc/center_body_event.dart'; import 'package:syncrow_web/pages/spaces_management/structure_selector/bloc/center_body_event.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/style.dart'; import 'package:syncrow_web/utils/style.dart';
class SidebarWidget extends StatefulWidget { class SidebarWidget extends StatefulWidget {
final List<CommunityModel> communities; final List<CommunityModel> communities;
final String? selectedSpaceUuid; final String? selectedSpaceUuid;
final void Function(String name, String description) onCreateCommunity;
const SidebarWidget({ const SidebarWidget({
super.key,
required this.communities, required this.communities,
required this.onCreateCommunity,
this.selectedSpaceUuid, this.selectedSpaceUuid,
super.key,
}); });
@override @override
_SidebarWidgetState createState() => _SidebarWidgetState(); State<SidebarWidget> createState() => _SidebarWidgetState();
} }
class _SidebarWidgetState extends State<SidebarWidget> { class _SidebarWidgetState extends State<SidebarWidget> {
String _searchQuery = ''; // Track search query String _searchQuery = '';
String? _selectedSpaceUuid; String? _selectedSpaceUuid;
String? _selectedId; String? _selectedId;
@override @override
void initState() { void initState() {
_selectedId = widget.selectedSpaceUuid;
super.initState(); super.initState();
_selectedId = widget.selectedSpaceUuid; // Initialize with the passed selected space UUID
} }
@override @override
void didUpdateWidget(covariant SidebarWidget oldWidget) { void didUpdateWidget(covariant SidebarWidget oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.selectedSpaceUuid != oldWidget.selectedSpaceUuid) { if (widget.selectedSpaceUuid != oldWidget.selectedSpaceUuid) {
setState(() { setState(() => _selectedId = widget.selectedSpaceUuid);
_selectedId = widget.selectedSpaceUuid;
});
} }
super.didUpdateWidget(oldWidget);
} }
// Function to filter communities based on the search query List<CommunityModel> _filteredCommunities() {
List<CommunityModel> _filterCommunities() {
if (_searchQuery.isEmpty) { if (_searchQuery.isEmpty) {
// Reset the selected community and space UUIDs if there's no query
_selectedSpaceUuid = null; _selectedSpaceUuid = null;
return widget.communities; return widget.communities;
} }
// Filter communities and expand only those that match the query
return widget.communities.where((community) { return widget.communities.where((community) {
final containsQueryInCommunity = final containsQueryInCommunity =
community.name.toLowerCase().contains(_searchQuery.toLowerCase()); community.name.toLowerCase().contains(_searchQuery.toLowerCase());
final containsQueryInSpaces = final containsQueryInSpaces = community.spaces.any((space) =>
community.spaces.any((space) => _containsQuery(space, _searchQuery.toLowerCase())); _containsQuery(space: space, query: _searchQuery.toLowerCase()));
return containsQueryInCommunity || containsQueryInSpaces; return containsQueryInCommunity || containsQueryInSpaces;
}).toList(); }).toList();
} }
// Helper function to determine if any space or its children match the search query bool _containsQuery({
bool _containsQuery(SpaceModel space, String query) { required SpaceModel space,
required String query,
}) {
final matchesSpace = space.name.toLowerCase().contains(query); final matchesSpace = space.name.toLowerCase().contains(query);
final matchesChildren = final matchesChildren = space.children.any(
space.children.any((child) => _containsQuery(child, query)); // Recursive check for children (child) => _containsQuery(space: child, query: query),
);
// If the space or any of its children match the query, expand this space
if (matchesSpace || matchesChildren) { if (matchesSpace || matchesChildren) {
_selectedSpaceUuid = space.uuid; _selectedSpaceUuid = space.uuid;
} }
@ -83,79 +81,32 @@ class _SidebarWidgetState extends State<SidebarWidget> {
} }
bool _isSpaceOrChildSelected(SpaceModel space) { bool _isSpaceOrChildSelected(SpaceModel space) {
// Return true if the current space or any of its child spaces is selected final isSpaceSelected = _selectedSpaceUuid == space.uuid;
if (_selectedSpaceUuid == space.uuid) { final anySubSpaceIsSelected = space.children.any(_isSpaceOrChildSelected);
return true; return isSpaceSelected || anySubSpaceIsSelected;
}
// Recursively check if any child spaces match the query
for (var child in space.children) {
if (_isSpaceOrChildSelected(child)) {
return true;
}
}
return false;
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final filteredCommunities = _filterCommunities(); final filteredCommunities = _filteredCommunities();
return Container( return Container(
width: 300, width: 300,
decoration: subSectionContainerDecoration, decoration: subSectionContainerDecoration,
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, // Ensures the Column only takes necessary height mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
// Communities title with the add button SidebarHeader(onAddCommunity: _onAddCommunity),
Container(
decoration: subSectionContainerDecoration,
padding: const EdgeInsets.all(16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('Communities',
style: Theme.of(context).textTheme.titleMedium?.copyWith(
color: ColorsManager.blackColor,
)),
GestureDetector(
onTap: () => _navigateToBlank(context),
child: Container(
width: 30,
height: 30,
decoration: const BoxDecoration(
color: ColorsManager.whiteColors,
shape: BoxShape.circle,
),
child: Center(
child: SvgPicture.asset(
Assets.roundedAddIcon,
width: 24,
height: 24,
),
),
),
),
],
),
),
// Search bar
CustomSearchBar( CustomSearchBar(
onSearchChanged: (query) { onSearchChanged: (query) => setState(() => _searchQuery = query),
setState(() {
_searchQuery = query;
});
},
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
// Community list
Expanded( Expanded(
child: ListView( child: ListView(
children: filteredCommunities.map((community) { children: filteredCommunities
return _buildCommunityTile(context, community); .map((community) => _buildCommunityTile(context, community))
}).toList(), .toList(),
), ),
), ),
], ],
@ -163,18 +114,7 @@ class _SidebarWidgetState extends State<SidebarWidget> {
); );
} }
void _navigateToBlank(BuildContext context) {
setState(() {
_selectedId = '';
});
context.read<SpaceManagementBloc>().add(
NewCommunityEvent(communities: widget.communities),
);
}
Widget _buildCommunityTile(BuildContext context, CommunityModel community) { Widget _buildCommunityTile(BuildContext context, CommunityModel community) {
bool hasChildren = community.spaces.isNotEmpty;
return CommunityTile( return CommunityTile(
title: community.name, title: community.name,
key: ValueKey(community.uuid), key: ValueKey(community.uuid),
@ -183,7 +123,7 @@ class _SidebarWidgetState extends State<SidebarWidget> {
onItemSelected: () { onItemSelected: () {
setState(() { setState(() {
_selectedId = community.uuid; _selectedId = community.uuid;
_selectedSpaceUuid = null; // Update the selected community _selectedSpaceUuid = null;
}); });
context.read<CenterBodyBloc>().add(CommunitySelectedEvent()); context.read<CenterBodyBloc>().add(CommunitySelectedEvent());
@ -192,46 +132,73 @@ class _SidebarWidgetState extends State<SidebarWidget> {
SelectCommunityEvent(selectedCommunity: community), SelectCommunityEvent(selectedCommunity: community),
); );
}, },
onExpansionChanged: (String title, bool expanded) { onExpansionChanged: (title, expanded) {},
_handleExpansionChange(community.uuid, expanded); children: community.spaces
}, .where((space) {
children: hasChildren final isDeleted = space.status != SpaceStatus.deleted;
? community.spaces final isParentDeleted = space.status != SpaceStatus.parentDeleted;
.where((space) => (space.status != SpaceStatus.deleted || return (isDeleted || isParentDeleted);
space.status != SpaceStatus.parentDeleted)) })
.map((space) => _buildSpaceTile(space, community)) .map((space) => _buildSpaceTile(space: space, community: community))
.toList() .toList(),
: null,
); );
} }
Widget _buildSpaceTile(SpaceModel space, CommunityModel community, {int depth = 1}) { Widget _buildSpaceTile({
bool isExpandedSpace = _isSpaceOrChildSelected(space); required SpaceModel space,
required CommunityModel community,
}) {
final spaceIsExpanded = _isSpaceOrChildSelected(space);
final isSelected = _selectedId == space.uuid;
return Padding( return Padding(
padding: EdgeInsets.only(left: depth * 16.0), padding: const EdgeInsetsDirectional.only(start: 16.0),
child: SpaceTile( child: SpaceTile(
title: space.name, title: space.name,
key: ValueKey(space.uuid), key: ValueKey(space.uuid),
isSelected: _selectedId == space.uuid, isSelected: isSelected,
initiallyExpanded: isExpandedSpace, initiallyExpanded: spaceIsExpanded,
onExpansionChanged: (bool expanded) { onExpansionChanged: (expanded) {},
_handleExpansionChange(space.uuid ?? '', expanded); onItemSelected: () {
}, setState(() {
onItemSelected: () { _selectedId = space.uuid;
setState(() { _selectedSpaceUuid = space.uuid;
_selectedId = space.uuid; });
_selectedSpaceUuid = space.uuid;
});
context.read<SpaceManagementBloc>().add( context.read<SpaceManagementBloc>().add(
SelectSpaceEvent(selectedCommunity: community, selectedSpace: space), SelectSpaceEvent(selectedCommunity: community, selectedSpace: space),
); );
}, },
children: space.children.isNotEmpty children: space.children
? space.children.map((childSpace) => _buildSpaceTile(childSpace, community)).toList() .map(
: [], // Recursively render child spaces if available (childSpace) => _buildSpaceTile(
)); space: childSpace,
community: community,
),
)
.toList(),
),
);
} }
void _handleExpansionChange(String uuid, bool expanded) {} void _onAddCommunity() => _selectedId?.isNotEmpty ?? true
? _clearSelection()
: _showCreateCommunityDialog();
void _clearSelection() {
setState(() => _selectedId = '');
context.read<SpaceManagementBloc>().add(
NewCommunityEvent(communities: widget.communities),
);
}
void _showCreateCommunityDialog() {
showDialog<void>(
context: context,
builder: (context) => CreateCommunityDialog(
isEditMode: false,
existingCommunityNames: widget.communities.map((e) => e.name).toList(),
onCreateCommunity: widget.onCreateCommunity,
),
);
}
} }

View File

@ -1,12 +1,13 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/common/buttons/cancel_button.dart';
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
import 'package:syncrow_web/pages/spaces_management/create_subspace_model/bloc/subspace_model_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/create_subspace_model/bloc/subspace_model_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/create_subspace_model/bloc/subspace_model_event.dart'; import 'package:syncrow_web/pages/spaces_management/create_subspace_model/bloc/subspace_model_event.dart';
import 'package:syncrow_web/pages/spaces_management/create_subspace_model/bloc/subspace_model_state.dart'; import 'package:syncrow_web/pages/spaces_management/create_subspace_model/bloc/subspace_model_state.dart';
import 'package:syncrow_web/pages/spaces_management/create_subspace_model/widgets/create_subspace_model_chips_box.dart';
import 'package:syncrow_web/pages/spaces_management/create_subspace_model/widgets/create_subspace_model_footer_buttons.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
class CreateSubSpaceModelDialog extends StatelessWidget { class CreateSubSpaceModelDialog extends StatelessWidget {
final bool isEdit; final bool isEdit;
@ -14,211 +15,67 @@ class CreateSubSpaceModelDialog extends StatelessWidget {
final List<SubspaceTemplateModel>? existingSubSpaces; final List<SubspaceTemplateModel>? existingSubSpaces;
final void Function(List<SubspaceTemplateModel> newSubspaces)? onUpdate; final void Function(List<SubspaceTemplateModel> newSubspaces)? onUpdate;
const CreateSubSpaceModelDialog( const CreateSubSpaceModelDialog({
{Key? key, required this.isEdit,
required this.isEdit, required this.dialogTitle,
required this.dialogTitle, this.existingSubSpaces,
this.existingSubSpaces, this.onUpdate,
this.onUpdate}) super.key,
: super(key: key); });
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final screenWidth = MediaQuery.of(context).size.width; final screenWidth = MediaQuery.of(context).size.width;
final textController = TextEditingController();
return Dialog( return Dialog(
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),
), ),
child: BlocProvider( child: BlocProvider(
create: (_) { create: (context) {
final bloc = SubSpaceModelBloc(); final bloc = SubSpaceModelBloc();
if (existingSubSpaces != null) { if (existingSubSpaces != null) {
for (var subSpace in existingSubSpaces!) { for (final subSpace in existingSubSpaces ?? []) {
bloc.add(AddSubSpaceModel(subSpace)); bloc.add(AddSubSpaceModel(subSpace));
} }
} }
return bloc; return bloc;
}, },
child: BlocBuilder<SubSpaceModelBloc, SubSpaceModelState>( child: BlocBuilder<SubSpaceModelBloc, SubSpaceModelState>(
builder: (context, state) { builder: (context, state) => Container(
return Container( color: ColorsManager.whiteColors,
color: ColorsManager.whiteColors, width: screenWidth * 0.3,
child: SizedBox( padding: const EdgeInsets.all(16),
width: screenWidth * 0.3, child: Column(
child: Padding( crossAxisAlignment: CrossAxisAlignment.start,
padding: const EdgeInsets.all(16.0), mainAxisSize: MainAxisSize.min,
child: Column( children: [
crossAxisAlignment: CrossAxisAlignment.start, Text(
mainAxisSize: MainAxisSize.min, dialogTitle,
children: [ style: context.textTheme.headlineLarge?.copyWith(
Text( color: ColorsManager.blackColor,
dialogTitle, ),
style: Theme.of(context) ),
.textTheme const SizedBox(height: 16),
.headlineLarge CreateSubspaceModelChipsBox(subSpaces: state.subSpaces),
?.copyWith(color: ColorsManager.blackColor), if (state.errorMessage.isNotEmpty)
), Padding(
const SizedBox(height: 16), padding: const EdgeInsets.only(bottom: 16),
Container( child: Text(
width: screenWidth * 0.35, state.errorMessage,
padding: const EdgeInsets.symmetric( style: context.textTheme.bodySmall?.copyWith(
vertical: 10.0, horizontal: 16.0), color: ColorsManager.red,
decoration: BoxDecoration( ),
color: ColorsManager.boxColor,
borderRadius: BorderRadius.circular(10),
),
child: Wrap(
spacing: 8.0,
runSpacing: 8.0,
children: [
...state.subSpaces.asMap().entries.map(
(entry) {
final index = entry.key;
final subSpace = entry.value;
final lowerName =
subSpace.subspaceName.toLowerCase();
final duplicateIndices = state.subSpaces
.asMap()
.entries
.where((e) =>
e.value.subspaceName.toLowerCase() ==
lowerName)
.map((e) => e.key)
.toList();
final isDuplicate =
duplicateIndices.length > 1 &&
duplicateIndices.indexOf(index) != 0;
return Chip(
label: Text(subSpace.subspaceName,
style: Theme.of(context)
.textTheme
.bodySmall
?.copyWith(
color: ColorsManager.spaceColor,
)),
backgroundColor: ColorsManager.whiteColors,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
side: BorderSide(
color: isDuplicate
? ColorsManager.red
: ColorsManager.transparentColor,
width: 0,
),
),
deleteIcon: Container(
width: 24,
height: 24,
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
color: ColorsManager.lightGrayColor,
width: 1.5,
),
),
child: const Icon(
Icons.close,
size: 16,
color: ColorsManager.lightGrayColor,
),
),
onDeleted: () => context
.read<SubSpaceModelBloc>()
.add(RemoveSubSpaceModel(subSpace)),
);
},
),
SizedBox(
width: 200,
child: TextField(
controller: textController,
decoration: InputDecoration(
border: InputBorder.none,
hintText: state.subSpaces.isEmpty
? 'Please enter the name'
: null,
hintStyle: Theme.of(context)
.textTheme
.bodySmall!
.copyWith(
color: ColorsManager
.lightGrayColor)),
onSubmitted: (value) {
if (value.trim().isNotEmpty) {
context.read<SubSpaceModelBloc>().add(
AddSubSpaceModel(
SubspaceTemplateModel(
subspaceName: value.trim(),
disabled: false)));
textController.clear();
}
},
style: Theme.of(context)
.textTheme
.bodyMedium
?.copyWith(
color: ColorsManager.blackColor)),
),
],
),
),
if (state.errorMessage.isNotEmpty)
Padding(
padding: const EdgeInsets.only(bottom: 16.0),
child: Text(state.errorMessage,
style: Theme.of(context)
.textTheme
.bodySmall
?.copyWith(
color: ColorsManager.red,
)),
),
const SizedBox(height: 16),
Row(
children: [
Expanded(
child: CancelButton(
label: 'Cancel',
onPressed: () async {
Navigator.of(context).pop();
},
),
),
const SizedBox(width: 10),
Expanded(
child: DefaultButton(
onPressed: (state.errorMessage.isNotEmpty)
? null
: () async {
final subSpaces = context
.read<SubSpaceModelBloc>()
.state
.subSpaces;
Navigator.of(context).pop();
if (onUpdate != null) {
onUpdate!(subSpaces);
}
},
backgroundColor: ColorsManager.secondaryColor,
borderRadius: 10,
foregroundColor: state.errorMessage.isNotEmpty
? ColorsManager.whiteColorsWithOpacity
: ColorsManager.whiteColors,
child: const Text('OK'),
),
),
],
),
],
), ),
), ),
)); const SizedBox(height: 16),
}, CreateSubspaceModelFooterButtons(
onUpdate: onUpdate,
errorMessage: state.errorMessage,
),
],
),
),
), ),
), ),
); );

View File

@ -0,0 +1,64 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/spaces_management/create_subspace_model/widgets/subspace_chip.dart';
import 'package:syncrow_web/pages/spaces_management/create_subspace_model/widgets/subspaces_textfield.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class CreateSubspaceModelChipsBox extends StatelessWidget {
const CreateSubspaceModelChipsBox({
required this.subSpaces,
super.key,
});
final List<SubspaceTemplateModel> subSpaces;
@override
Widget build(BuildContext context) {
final screenWidth = MediaQuery.of(context).size.width;
return Container(
width: screenWidth * 0.35,
padding: const EdgeInsets.symmetric(
vertical: 10,
horizontal: 16,
),
decoration: BoxDecoration(
color: ColorsManager.boxColor,
borderRadius: BorderRadius.circular(10),
),
child: Wrap(
spacing: 8,
runSpacing: 8,
alignment: WrapAlignment.start,
crossAxisAlignment: WrapCrossAlignment.center,
children: [
...subSpaces.asMap().entries.map(
(entry) {
final index = entry.key;
final subSpace = entry.value;
final lowerName = subSpace.subspaceName.toLowerCase();
final duplicateIndices = subSpaces
.asMap()
.entries
.where((e) => e.value.subspaceName.toLowerCase() == lowerName)
.map((e) => e.key)
.toList();
final isDuplicate = duplicateIndices.length > 1 &&
duplicateIndices.indexOf(index) != 0;
return SubspaceChip(
subSpace: subSpace,
isDuplicate: isDuplicate,
);
},
),
SubspacesTextfield(
hintText: subSpaces.isEmpty ? 'Please enter the name' : null,
),
],
),
);
}
}

View File

@ -0,0 +1,53 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/common/buttons/cancel_button.dart';
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
import 'package:syncrow_web/pages/spaces_management/create_subspace_model/bloc/subspace_model_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class CreateSubspaceModelFooterButtons extends StatelessWidget {
const CreateSubspaceModelFooterButtons({
required this.onUpdate,
required this.errorMessage,
super.key,
});
final void Function(List<SubspaceTemplateModel> newSubspaces)? onUpdate;
final String errorMessage;
@override
Widget build(BuildContext context) {
return Row(
children: [
Expanded(
child: CancelButton(
label: 'Cancel',
onPressed: () => Navigator.of(context).pop(),
),
),
const SizedBox(width: 10),
Expanded(
child: DefaultButton(
onPressed: errorMessage.isEmpty
? () {
Navigator.of(context).pop();
if (onUpdate != null) {
final subSpaces =
context.read<SubSpaceModelBloc>().state.subSpaces;
onUpdate!(subSpaces);
}
}
: null,
backgroundColor: ColorsManager.secondaryColor,
borderRadius: 10,
foregroundColor: errorMessage.isNotEmpty
? ColorsManager.whiteColorsWithOpacity
: ColorsManager.whiteColors,
child: const Text('OK'),
),
),
],
);
}
}

View File

@ -0,0 +1,58 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/create_subspace_model/bloc/subspace_model_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/create_subspace_model/bloc/subspace_model_event.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
class SubspaceChip extends StatelessWidget {
const SubspaceChip({
required this.subSpace,
required this.isDuplicate,
super.key,
});
final SubspaceTemplateModel subSpace;
final bool isDuplicate;
@override
Widget build(BuildContext context) {
return Chip(
label: Text(
subSpace.subspaceName,
style: context.textTheme.bodySmall?.copyWith(
color: isDuplicate ? ColorsManager.red : ColorsManager.spaceColor,
),
),
backgroundColor: ColorsManager.whiteColors,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
side: BorderSide(
color: isDuplicate ? ColorsManager.red : ColorsManager.transparentColor,
width: 0,
),
),
deleteIcon: Container(
padding: const EdgeInsetsDirectional.all(1),
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
color: ColorsManager.lightGrayColor,
width: 1.5,
),
),
child: const FittedBox(
fit: BoxFit.scaleDown,
child: Icon(
Icons.close,
color: ColorsManager.lightGrayColor,
),
),
),
onDeleted: () => context.read<SubSpaceModelBloc>().add(
RemoveSubSpaceModel(subSpace),
),
);
}
}

View File

@ -0,0 +1,68 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/create_subspace_model/bloc/subspace_model_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/create_subspace_model/bloc/subspace_model_event.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
class SubspacesTextfield extends StatefulWidget {
const SubspacesTextfield({
required this.hintText,
super.key,
});
final String? hintText;
@override
State<SubspacesTextfield> createState() => _SubspacesTextfieldState();
}
class _SubspacesTextfieldState extends State<SubspacesTextfield> {
late final TextEditingController _controller;
@override
void initState() {
_controller = TextEditingController();
super.initState();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return SizedBox(
width: 100,
child: TextField(
controller: _controller,
decoration: InputDecoration(
border: InputBorder.none,
hintText: widget.hintText,
hintStyle: context.textTheme.bodySmall?.copyWith(
color: ColorsManager.lightGrayColor,
),
),
onSubmitted: (value) {
final trimmedValue = value.trim();
if (trimmedValue.isNotEmpty) {
context.read<SubSpaceModelBloc>().add(
AddSubSpaceModel(
SubspaceTemplateModel(
subspaceName: trimmedValue,
disabled: false,
),
),
);
_controller.clear();
}
},
style: context.textTheme.bodyMedium?.copyWith(
color: ColorsManager.blackColor,
),
),
);
}
}

View File

@ -321,13 +321,14 @@ class DevicesManagementApi {
Future<bool> factoryReset(FactoryResetModel factoryReset, String uuid) async { Future<bool> factoryReset(FactoryResetModel factoryReset, String uuid) async {
try { try {
final response = await HTTPService().post( final response = await HTTPService().post(
path: ApiEndpoints.factoryReset.replaceAll('{deviceUuid}', uuid), path: ApiEndpoints.factoryReset,
body: factoryReset.toMap(), body: factoryReset.toMap(),
showServerMessage: true, showServerMessage: true,
expectedResponseModel: (json) { expectedResponseModel: (json) {
return json['success'] ?? false; return json['success'] ?? false;
}, },
); );
return response; return response;
} catch (e) { } catch (e) {
debugPrint('Error fetching $e'); debugPrint('Error fetching $e');

View File

@ -13,8 +13,7 @@ class Assets {
static const String rightLine = "assets/images/right_line.png"; static const String rightLine = "assets/images/right_line.png";
static const String google = "assets/images/google.svg"; static const String google = "assets/images/google.svg";
static const String facebook = "assets/images/facebook.svg"; static const String facebook = "assets/images/facebook.svg";
static const String invisiblePassword = static const String invisiblePassword = "assets/images/Password_invisible.svg";
"assets/images/Password_invisible.svg";
static const String visiblePassword = "assets/images/password_visible.svg"; static const String visiblePassword = "assets/images/password_visible.svg";
static const String accessIcon = "assets/images/access_icon.svg"; static const String accessIcon = "assets/images/access_icon.svg";
static const String spaseManagementIcon = static const String spaseManagementIcon =
@ -31,8 +30,7 @@ class Assets {
static const String emptyTable = "assets/images/empty_table.svg"; static const String emptyTable = "assets/images/empty_table.svg";
// General assets // General assets
static const String motionlessDetection = static const String motionlessDetection = "assets/icons/motionless_detection.svg";
"assets/icons/motionless_detection.svg";
static const String acHeating = "assets/icons/ac_heating.svg"; static const String acHeating = "assets/icons/ac_heating.svg";
static const String acPowerOff = "assets/icons/ac_power_off.svg"; static const String acPowerOff = "assets/icons/ac_power_off.svg";
static const String acFanMiddle = "assets/icons/ac_fan_middle.svg"; static const String acFanMiddle = "assets/icons/ac_fan_middle.svg";
@ -69,22 +67,19 @@ class Assets {
"assets/icons/automation_functions/temp_password_unlock.svg"; "assets/icons/automation_functions/temp_password_unlock.svg";
static const String doorlockNormalOpen = static const String doorlockNormalOpen =
"assets/icons/automation_functions/doorlock_normal_open.svg"; "assets/icons/automation_functions/doorlock_normal_open.svg";
static const String doorbell = static const String doorbell = "assets/icons/automation_functions/doorbell.svg";
"assets/icons/automation_functions/doorbell.svg";
static const String remoteUnlockViaApp = static const String remoteUnlockViaApp =
"assets/icons/automation_functions/remote_unlock_via_app.svg"; "assets/icons/automation_functions/remote_unlock_via_app.svg";
static const String doubleLock = static const String doubleLock =
"assets/icons/automation_functions/double_lock.svg"; "assets/icons/automation_functions/double_lock.svg";
static const String selfTestResult = static const String selfTestResult =
"assets/icons/automation_functions/self_test_result.svg"; "assets/icons/automation_functions/self_test_result.svg";
static const String lockAlarm = static const String lockAlarm = "assets/icons/automation_functions/lock_alarm.svg";
"assets/icons/automation_functions/lock_alarm.svg";
static const String presenceState = static const String presenceState =
"assets/icons/automation_functions/presence_state.svg"; "assets/icons/automation_functions/presence_state.svg";
static const String currentTemp = static const String currentTemp =
"assets/icons/automation_functions/current_temp.svg"; "assets/icons/automation_functions/current_temp.svg";
static const String presence = static const String presence = "assets/icons/automation_functions/presence.svg";
"assets/icons/automation_functions/presence.svg";
static const String residualElectricity = static const String residualElectricity =
"assets/icons/automation_functions/residual_electricity.svg"; "assets/icons/automation_functions/residual_electricity.svg";
static const String hijackAlarm = static const String hijackAlarm =
@ -101,15 +96,12 @@ class Assets {
// Presence Sensor Assets // Presence Sensor Assets
static const String sensorMotionIcon = "assets/icons/sensor_motion_ic.svg"; static const String sensorMotionIcon = "assets/icons/sensor_motion_ic.svg";
static const String sensorPresenceIcon = static const String sensorPresenceIcon = "assets/icons/sensor_presence_ic.svg";
"assets/icons/sensor_presence_ic.svg";
static const String sensorVacantIcon = "assets/icons/sensor_vacant_ic.svg"; static const String sensorVacantIcon = "assets/icons/sensor_vacant_ic.svg";
static const String illuminanceRecordIcon = static const String illuminanceRecordIcon =
"assets/icons/illuminance_record_ic.svg"; "assets/icons/illuminance_record_ic.svg";
static const String presenceRecordIcon = static const String presenceRecordIcon = "assets/icons/presence_record_ic.svg";
"assets/icons/presence_record_ic.svg"; static const String helpDescriptionIcon = "assets/icons/help_description_ic.svg";
static const String helpDescriptionIcon =
"assets/icons/help_description_ic.svg";
static const String lightPulp = "assets/icons/light_pulb.svg"; static const String lightPulp = "assets/icons/light_pulb.svg";
static const String acDevice = "assets/icons/ac_device.svg"; static const String acDevice = "assets/icons/ac_device.svg";
@ -159,12 +151,10 @@ class Assets {
static const String unit = 'assets/icons/unit_icon.svg'; static const String unit = 'assets/icons/unit_icon.svg';
static const String villa = 'assets/icons/villa_icon.svg'; static const String villa = 'assets/icons/villa_icon.svg';
static const String iconEdit = 'assets/icons/icon_edit_icon.svg'; static const String iconEdit = 'assets/icons/icon_edit_icon.svg';
static const String textFieldSearch = static const String textFieldSearch = 'assets/icons/textfield_search_icon.svg';
'assets/icons/textfield_search_icon.svg';
static const String roundedAddIcon = 'assets/icons/rounded_add_icon.svg'; static const String roundedAddIcon = 'assets/icons/rounded_add_icon.svg';
static const String addIcon = 'assets/icons/add_icon.svg'; static const String addIcon = 'assets/icons/add_icon.svg';
static const String smartThermostatIcon = static const String smartThermostatIcon = 'assets/icons/smart_thermostat_icon.svg';
'assets/icons/smart_thermostat_icon.svg';
static const String smartLightIcon = 'assets/icons/smart_light_icon.svg'; static const String smartLightIcon = 'assets/icons/smart_light_icon.svg';
static const String presenceSensor = 'assets/icons/presence_sensor.svg'; static const String presenceSensor = 'assets/icons/presence_sensor.svg';
static const String Gang3SwitchIcon = 'assets/icons/3_Gang_switch_icon.svg'; static const String Gang3SwitchIcon = 'assets/icons/3_Gang_switch_icon.svg';
@ -212,8 +202,7 @@ class Assets {
//assets/icons/water_leak_normal.svg //assets/icons/water_leak_normal.svg
static const String waterLeakNormal = 'assets/icons/water_leak_normal.svg'; static const String waterLeakNormal = 'assets/icons/water_leak_normal.svg';
//assets/icons/water_leak_detected.svg //assets/icons/water_leak_detected.svg
static const String waterLeakDetected = static const String waterLeakDetected = 'assets/icons/water_leak_detected.svg';
'assets/icons/water_leak_detected.svg';
//assets/icons/automation_records.svg //assets/icons/automation_records.svg
static const String automationRecords = 'assets/icons/automation_records.svg'; static const String automationRecords = 'assets/icons/automation_records.svg';
@ -284,16 +273,13 @@ class Assets {
"assets/icons/functions_icons/sensitivity.svg"; "assets/icons/functions_icons/sensitivity.svg";
static const String assetsSensitivityOperationIcon = static const String assetsSensitivityOperationIcon =
"assets/icons/functions_icons/sesitivity_operation_icon.svg"; "assets/icons/functions_icons/sesitivity_operation_icon.svg";
static const String assetsAcPower = static const String assetsAcPower = "assets/icons/functions_icons/ac_power.svg";
"assets/icons/functions_icons/ac_power.svg";
static const String assetsAcPowerOFF = static const String assetsAcPowerOFF =
"assets/icons/functions_icons/ac_power_off.svg"; "assets/icons/functions_icons/ac_power_off.svg";
static const String assetsChildLock = static const String assetsChildLock =
"assets/icons/functions_icons/child_lock.svg"; "assets/icons/functions_icons/child_lock.svg";
static const String assetsFreezing = static const String assetsFreezing = "assets/icons/functions_icons/freezing.svg";
"assets/icons/functions_icons/freezing.svg"; static const String assetsFanSpeed = "assets/icons/functions_icons/fan_speed.svg";
static const String assetsFanSpeed =
"assets/icons/functions_icons/fan_speed.svg";
static const String assetsAcCooling = static const String assetsAcCooling =
"assets/icons/functions_icons/ac_cooling.svg"; "assets/icons/functions_icons/ac_cooling.svg";
static const String assetsAcHeating = static const String assetsAcHeating =
@ -302,8 +288,7 @@ class Assets {
"assets/icons/functions_icons/celsius_degrees.svg"; "assets/icons/functions_icons/celsius_degrees.svg";
static const String assetsTempreture = static const String assetsTempreture =
"assets/icons/functions_icons/tempreture.svg"; "assets/icons/functions_icons/tempreture.svg";
static const String assetsAcFanLow = static const String assetsAcFanLow = "assets/icons/functions_icons/ac_fan_low.svg";
"assets/icons/functions_icons/ac_fan_low.svg";
static const String assetsAcFanMiddle = static const String assetsAcFanMiddle =
"assets/icons/functions_icons/ac_fan_middle.svg"; "assets/icons/functions_icons/ac_fan_middle.svg";
static const String assetsAcFanHigh = static const String assetsAcFanHigh =
@ -322,8 +307,7 @@ class Assets {
"assets/icons/functions_icons/far_detection.svg"; "assets/icons/functions_icons/far_detection.svg";
static const String assetsFarDetectionFunction = static const String assetsFarDetectionFunction =
"assets/icons/functions_icons/far_detection_function.svg"; "assets/icons/functions_icons/far_detection_function.svg";
static const String assetsIndicator = static const String assetsIndicator = "assets/icons/functions_icons/indicator.svg";
"assets/icons/functions_icons/indicator.svg";
static const String assetsMotionDetection = static const String assetsMotionDetection =
"assets/icons/functions_icons/motion_detection.svg"; "assets/icons/functions_icons/motion_detection.svg";
static const String assetsMotionlessDetection = static const String assetsMotionlessDetection =
@ -336,8 +320,7 @@ class Assets {
"assets/icons/functions_icons/master_state.svg"; "assets/icons/functions_icons/master_state.svg";
static const String assetsSwitchAlarmSound = static const String assetsSwitchAlarmSound =
"assets/icons/functions_icons/switch_alarm_sound.svg"; "assets/icons/functions_icons/switch_alarm_sound.svg";
static const String assetsResetOff = static const String assetsResetOff = "assets/icons/functions_icons/reset_off.svg";
"assets/icons/functions_icons/reset_off.svg";
// Assets for automation_functions // Assets for automation_functions
static const String assetsCardUnlock = static const String assetsCardUnlock =
@ -381,14 +364,12 @@ class Assets {
static const String activeUser = 'assets/icons/active_user.svg'; static const String activeUser = 'assets/icons/active_user.svg';
static const String deActiveUser = 'assets/icons/deactive_user.svg'; static const String deActiveUser = 'assets/icons/deactive_user.svg';
static const String invitedIcon = 'assets/icons/invited_icon.svg'; static const String invitedIcon = 'assets/icons/invited_icon.svg';
static const String rectangleCheckBox = static const String rectangleCheckBox = 'assets/icons/rectangle_check_box.png';
'assets/icons/rectangle_check_box.png';
static const String CheckBoxChecked = 'assets/icons/box_checked.png'; static const String CheckBoxChecked = 'assets/icons/box_checked.png';
static const String emptyBox = 'assets/icons/empty_box.png'; static const String emptyBox = 'assets/icons/empty_box.png';
static const String completeProcessIcon = static const String completeProcessIcon =
'assets/icons/compleate_process_icon.svg'; 'assets/icons/compleate_process_icon.svg';
static const String currentProcessIcon = static const String currentProcessIcon = 'assets/icons/current_process_icon.svg';
'assets/icons/current_process_icon.svg';
static const String uncomplete_ProcessIcon = static const String uncomplete_ProcessIcon =
'assets/icons/uncompleate_process_icon.svg'; 'assets/icons/uncompleate_process_icon.svg';
static const String wrongProcessIcon = 'assets/icons/wrong_process_icon.svg'; static const String wrongProcessIcon = 'assets/icons/wrong_process_icon.svg';
@ -409,11 +390,9 @@ class Assets {
static const String successIcon = 'assets/icons/success_icon.svg'; static const String successIcon = 'assets/icons/success_icon.svg';
static const String spaceLocationIcon = 'assets/icons/spaseLocationIcon.svg'; static const String spaceLocationIcon = 'assets/icons/spaseLocationIcon.svg';
static const String scenesPlayIcon = 'assets/icons/scenesPlayIcon.png'; static const String scenesPlayIcon = 'assets/icons/scenesPlayIcon.png';
static const String scenesPlayIconCheck = static const String scenesPlayIconCheck = 'assets/icons/scenesPlayIconCheck.png';
'assets/icons/scenesPlayIconCheck.png';
static const String presenceStateIcon = 'assets/icons/presence_state.svg'; static const String presenceStateIcon = 'assets/icons/presence_state.svg';
static const String currentDistanceIcon = static const String currentDistanceIcon = 'assets/icons/current_distance_icon.svg';
'assets/icons/current_distance_icon.svg';
static const String farDetectionIcon = 'assets/icons/far_detection_icon.svg'; static const String farDetectionIcon = 'assets/icons/far_detection_icon.svg';
static const String motionDetectionSensitivityIcon = static const String motionDetectionSensitivityIcon =
@ -423,38 +402,41 @@ class Assets {
'assets/icons/motionless_detection_sensitivity_icon.svg'; 'assets/icons/motionless_detection_sensitivity_icon.svg';
static const String IndicatorIcon = 'assets/icons/Indicator_icon.svg'; static const String IndicatorIcon = 'assets/icons/Indicator_icon.svg';
static const String motionDetectionSensitivityValueIcon = 'assets/icons/motion_detection_sensitivity_value_icon.svg'; static const String motionDetectionSensitivityValueIcon =
static const String presenceTimeIcon = 'assets/icons/presence_time_icon.svg'; 'assets/icons/motion_detection_sensitivity_value_icon.svg';
static const String IlluminanceIcon = 'assets/icons/Illuminance_icon.svg'; static const String presenceTimeIcon = 'assets/icons/presence_time_icon.svg';
static const String gear = 'assets/icons/gear.svg'; static const String IlluminanceIcon = 'assets/icons/Illuminance_icon.svg';
static const String activeBell='assets/icons/active_bell.svg'; static const String gear = 'assets/icons/gear.svg';
static const String cpsCustomMode = 'assets/icons/cps_custom_mode.svg'; static const String activeBell = 'assets/icons/active_bell.svg';
static const String cpsMode1 = 'assets/icons/cps_mode1.svg'; static const String cpsCustomMode = 'assets/icons/cps_custom_mode.svg';
static const String cpsMode2 = 'assets/icons/cps_mode2.svg'; static const String cpsMode1 = 'assets/icons/cps_mode1.svg';
static const String cpsMode3 = 'assets/icons/cps_mode3.svg'; static const String cpsMode2 = 'assets/icons/cps_mode2.svg';
static const String cpsMode4 = 'assets/icons/cps_mode4.svg'; static const String cpsMode3 = 'assets/icons/cps_mode3.svg';
static const String closeToMotion = 'assets/icons/close_to_motion.svg'; static const String cpsMode4 = 'assets/icons/cps_mode4.svg';
static const String farAwayMotion = 'assets/icons/far_away_motion.svg'; static const String closeToMotion = 'assets/icons/close_to_motion.svg';
static const String communicationFault = 'assets/icons/communication_fault.svg'; static const String farAwayMotion = 'assets/icons/far_away_motion.svg';
static const String radarFault = 'assets/icons/radar_fault.svg'; static const String communicationFault = 'assets/icons/communication_fault.svg';
static const String selfTestingSuccess = 'assets/icons/self_testing_success.svg'; static const String radarFault = 'assets/icons/radar_fault.svg';
static const String selfTestingFailure = 'assets/icons/self_testing_failure.svg'; static const String selfTestingSuccess = 'assets/icons/self_testing_success.svg';
static const String selfTestingTimeout = 'assets/icons/self_testing_timeout.svg'; static const String selfTestingFailure = 'assets/icons/self_testing_failure.svg';
static const String movingSpeed = 'assets/icons/moving_speed.svg'; static const String selfTestingTimeout = 'assets/icons/self_testing_timeout.svg';
static const String boundary = 'assets/icons/boundary.svg'; static const String movingSpeed = 'assets/icons/moving_speed.svg';
static const String motionMeter = 'assets/icons/motion_meter.svg'; static const String boundary = 'assets/icons/boundary.svg';
static const String spatialStaticValue = 'assets/icons/spatial_static_value.svg'; static const String motionMeter = 'assets/icons/motion_meter.svg';
static const String spatialMotionValue = 'assets/icons/spatial_motion_value.svg'; static const String spatialStaticValue = 'assets/icons/spatial_static_value.svg';
static const String presenceJudgementThrshold = 'assets/icons/presence_judgement_threshold.svg'; static const String spatialMotionValue = 'assets/icons/spatial_motion_value.svg';
static const String spaceType = 'assets/icons/space_type.svg'; static const String presenceJudgementThrshold =
static const String sportsPara = 'assets/icons/sports_para.svg'; 'assets/icons/presence_judgement_threshold.svg';
static const String sensitivityFeature1 = 'assets/icons/sensitivity_feature_1.svg'; static const String spaceType = 'assets/icons/space_type.svg';
static const String sensitivityFeature2 = 'assets/icons/sensitivity_feature_2.svg'; static const String sportsPara = 'assets/icons/sports_para.svg';
static const String sensitivityFeature3 = 'assets/icons/sensitivity_feature_3.svg'; static const String sensitivityFeature1 = 'assets/icons/sensitivity_feature_1.svg';
static const String sensitivityFeature4 = 'assets/icons/sensitivity_feature_4.svg'; static const String sensitivityFeature2 = 'assets/icons/sensitivity_feature_2.svg';
static const String sensitivityFeature5 = 'assets/icons/sensitivity_feature_5.svg'; static const String sensitivityFeature3 = 'assets/icons/sensitivity_feature_3.svg';
static const String sensitivityFeature6 = 'assets/icons/sensitivity_feature_6.svg'; static const String sensitivityFeature4 = 'assets/icons/sensitivity_feature_4.svg';
static const String sensitivityFeature7 = 'assets/icons/sensitivity_feature_7.svg'; static const String sensitivityFeature5 = 'assets/icons/sensitivity_feature_5.svg';
static const String sensitivityFeature8 = 'assets/icons/sensitivity_feature_8.svg'; static const String sensitivityFeature6 = 'assets/icons/sensitivity_feature_6.svg';
static const String sensitivityFeature9 = 'assets/icons/sensitivity_feature_9.svg'; static const String sensitivityFeature7 = 'assets/icons/sensitivity_feature_7.svg';
static const String sensitivityFeature8 = 'assets/icons/sensitivity_feature_8.svg';
static const String sensitivityFeature9 = 'assets/icons/sensitivity_feature_9.svg';
static const String deviceTagIcon = 'assets/icons/device_tag_ic.svg';
} }