This commit is contained in:
Faris Armoush
2025-04-29 10:39:54 +03:00
201 changed files with 11547 additions and 2722 deletions
@@ -0,0 +1,26 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
class EmptySearchResultWidget extends StatelessWidget {
const EmptySearchResultWidget({
this.message = 'No results found',
super.key,
});
final String message;
@override
Widget build(BuildContext context) {
return Center(
child: Text(
message,
textAlign: TextAlign.center,
style: context.textTheme.bodySmall?.copyWith(
color: ColorsManager.lightGreyColor,
fontWeight: FontWeight.w400,
),
),
);
}
}
@@ -0,0 +1,53 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
class SidebarCommunitiesList extends StatelessWidget {
const SidebarCommunitiesList({
required this.communities,
required this.itemBuilder,
required this.scrollController,
required this.onScrollToEnd,
super.key,
});
final List<CommunityModel> communities;
final Widget Function(BuildContext context, int index) itemBuilder;
final ScrollController scrollController;
final void Function() onScrollToEnd;
bool _onNotification(ScrollEndNotification notification) {
final hasReachedEnd = notification.metrics.extentAfter == 0;
if (hasReachedEnd) {
onScrollToEnd.call();
return true;
}
return false;
}
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: SizedBox(
width: context.screenWidth * 0.5,
child: Scrollbar(
scrollbarOrientation: ScrollbarOrientation.left,
thumbVisibility: true,
controller: scrollController,
child: NotificationListener<ScrollEndNotification>(
onNotification: _onNotification,
child: ListView.builder(
shrinkWrap: true,
padding: const EdgeInsetsDirectional.only(start: 16),
itemCount: communities.length,
controller: scrollController,
itemBuilder: itemBuilder,
),
),
),
),
);
}
}
+4 -1
View File
@@ -8,6 +8,7 @@ import 'package:syncrow_web/firebase_options_prod.dart';
import 'package:syncrow_web/pages/auth/bloc/auth_bloc.dart';
import 'package:syncrow_web/pages/home/bloc/home_bloc.dart';
import 'package:syncrow_web/pages/home/bloc/home_event.dart';
import 'package:syncrow_web/pages/routines/bloc/create_routine_bloc/create_routine_bloc.dart';
import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_event.dart';
@@ -33,7 +34,6 @@ Future<void> main() async {
}
class MyApp extends StatelessWidget {
MyApp({super.key});
final GoRouter _router = GoRouter(
@@ -55,6 +55,9 @@ class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider<CreateRoutineBloc>(
create: (context) => CreateRoutineBloc(),
),
BlocProvider(
create: (context) => HomeBloc()..add(const FetchUserInfo())),
BlocProvider<VisitorPasswordBloc>(
+10 -7
View File
@@ -8,6 +8,7 @@ import 'package:syncrow_web/firebase_options_dev.dart';
import 'package:syncrow_web/pages/auth/bloc/auth_bloc.dart';
import 'package:syncrow_web/pages/home/bloc/home_bloc.dart';
import 'package:syncrow_web/pages/home/bloc/home_event.dart';
import 'package:syncrow_web/pages/routines/bloc/create_routine_bloc/create_routine_bloc.dart';
import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_event.dart';
@@ -15,12 +16,12 @@ import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_bloc.da
import 'package:syncrow_web/services/locator.dart';
import 'package:syncrow_web/utils/app_routes.dart';
import 'package:syncrow_web/utils/constants/routes_const.dart';
import 'package:syncrow_web/utils/navigation_service.dart';
import 'package:syncrow_web/utils/theme/theme.dart';
Future<void> main() async {
try {
const environment =
String.fromEnvironment('FLAVOR', defaultValue: 'development');
const environment = String.fromEnvironment('FLAVOR', defaultValue: 'development');
await dotenv.load(fileName: '.env.$environment');
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
@@ -32,9 +33,7 @@ Future<void> main() async {
}
class MyApp extends StatelessWidget {
MyApp({
super.key,
});
MyApp({super.key});
final GoRouter _router = GoRouter(
initialLocation: RoutesConst.auth,
@@ -55,8 +54,10 @@ class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider(
create: (context) => HomeBloc()..add(const FetchUserInfo())),
BlocProvider<CreateRoutineBloc>(
create: (context) => CreateRoutineBloc(),
),
BlocProvider(create: (context) => HomeBloc()..add(const FetchUserInfo())),
BlocProvider<VisitorPasswordBloc>(
create: (context) => VisitorPasswordBloc(),
),
@@ -77,6 +78,8 @@ class MyApp extends StatelessWidget {
PointerDeviceKind.unknown,
},
),
key: NavigationService.navigatorKey,
// scaffoldMessengerKey: NavigationService.snackbarKey,
theme: myTheme,
routerConfig: _router,
));
+87
View File
@@ -0,0 +1,87 @@
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:go_router/go_router.dart';
import 'package:syncrow_web/firebase_options_prod.dart';
import 'package:syncrow_web/pages/auth/bloc/auth_bloc.dart';
import 'package:syncrow_web/pages/home/bloc/home_bloc.dart';
import 'package:syncrow_web/pages/home/bloc/home_event.dart';
import 'package:syncrow_web/pages/routines/bloc/create_routine_bloc/create_routine_bloc.dart';
import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_event.dart';
import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_bloc.dart';
import 'package:syncrow_web/services/locator.dart';
import 'package:syncrow_web/utils/app_routes.dart';
import 'package:syncrow_web/utils/constants/routes_const.dart';
import 'package:syncrow_web/utils/navigation_service.dart';
import 'package:syncrow_web/utils/theme/theme.dart';
Future<void> main() async {
try {
const environment = String.fromEnvironment('FLAVOR', defaultValue: 'staging');
await dotenv.load(fileName: '.env.$environment');
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: DefaultFirebaseOptionsStaging.currentPlatform,
);
initialSetup();
} catch (_) {}
runApp(MyApp());
}
class MyApp extends StatelessWidget {
MyApp({super.key});
final GoRouter _router = GoRouter(
initialLocation: RoutesConst.auth,
routes: AppRoutes.getRoutes(),
redirect: (context, state) async {
String checkToken = await AuthBloc.getTokenAndValidate();
final loggedIn = checkToken == 'Success';
final goingToLogin = state.uri.toString() == RoutesConst.auth;
if (!loggedIn && !goingToLogin) return RoutesConst.auth;
if (loggedIn && goingToLogin) return RoutesConst.home;
return null;
},
);
@override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider<CreateRoutineBloc>(
create: (context) => CreateRoutineBloc(),
),
BlocProvider(create: (context) => HomeBloc()..add(const FetchUserInfo())),
BlocProvider<VisitorPasswordBloc>(
create: (context) => VisitorPasswordBloc(),
),
BlocProvider<RoutineBloc>(
create: (context) => RoutineBloc(),
),
BlocProvider<SpaceTreeBloc>(
create: (context) => SpaceTreeBloc()..add(InitialEvent()),
),
],
child: MaterialApp.router(
debugShowCheckedModeBanner: false,
scrollBehavior: const MaterialScrollBehavior().copyWith(
dragDevices: {
PointerDeviceKind.mouse,
PointerDeviceKind.touch,
PointerDeviceKind.stylus,
PointerDeviceKind.unknown,
},
),
key: NavigationService.navigatorKey,
// scaffoldMessengerKey: NavigationService.snackbarKey,
theme: myTheme,
routerConfig: _router,
));
}
}
+8 -3
View File
@@ -10,6 +10,7 @@ import 'package:syncrow_web/pages/auth/model/region_model.dart';
import 'package:syncrow_web/pages/auth/model/token.dart';
import 'package:syncrow_web/pages/auth/model/user_model.dart';
import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
import 'package:syncrow_web/pages/home/bloc/home_bloc.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_event.dart';
import 'package:syncrow_web/services/auth_api.dart';
@@ -432,9 +433,13 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
}
static Future<void> logout(BuildContext context) async {
final storage = FlutterSecureStorage();
ProjectManager.clearProjectUUID();
const storage = FlutterSecureStorage();
context.read<SpaceTreeBloc>().add(ClearAllData());
storage.deleteAll();
user = null;
context.read<HomeBloc>().user = null;
await Future.wait<void>([
ProjectManager.clearProjectUUID(),
storage.deleteAll(),
]);
}
}
@@ -201,20 +201,17 @@ class ForgetPasswordWebPage extends StatelessWidget {
!state.isButtonEnabled &&
state.remainingTime != 1
? null
: () {
:
() {
if (forgetBloc
.forgetEmailKey.currentState!
.validate() ||
forgetBloc
.forgetRegionKey.currentState!
.validate()) {
if (forgetBloc
.forgetRegionKey.currentState!
.validate()) {
forgetBloc.add(StartTimerEvent());
}
.forgetEmailKey
.currentState!
.validate()) {
forgetBloc.add(
StartTimerEvent());
}
},
child: Text(
'Get Code ${state is TimerState && !state.isButtonEnabled && state.remainingTime != 1 ? "(${forgetBloc.formattedTime(state.remainingTime)}) " : ""}',
style: TextStyle(
+19 -9
View File
@@ -55,12 +55,12 @@ class _LoginWebPageState extends State<LoginWebPage> with HelperResponsiveLayout
final isSmallScreen = isSmallScreenSize(context);
final isMediumScreen = isMediumScreenSize(context);
Size size = MediaQuery.of(context).size;
late ScrollController _scrollController;
_scrollController = ScrollController();
late ScrollController scrollController;
scrollController = ScrollController();
void _scrollToCenter() {
final double middlePosition = _scrollController.position.maxScrollExtent / 2;
_scrollController.animateTo(
void scrollToCenter() {
final double middlePosition = scrollController.position.maxScrollExtent / 2;
scrollController.animateTo(
middlePosition,
duration: const Duration(seconds: 1),
curve: Curves.easeInOut,
@@ -68,7 +68,7 @@ class _LoginWebPageState extends State<LoginWebPage> with HelperResponsiveLayout
}
WidgetsBinding.instance.addPostFrameCallback((_) {
_scrollToCenter();
scrollToCenter();
});
return Stack(
@@ -76,7 +76,7 @@ class _LoginWebPageState extends State<LoginWebPage> with HelperResponsiveLayout
FirstLayer(
second: Center(
child: ListView(
controller: _scrollController,
controller: scrollController,
shrinkWrap: true,
children: [
Container(
@@ -199,7 +199,7 @@ class _LoginWebPageState extends State<LoginWebPage> with HelperResponsiveLayout
width: size.width * 0.9,
child: DropdownButtonHideUnderline(
child: DropdownButton2<String>(
style: TextStyle(color: Colors.black),
style: const TextStyle(color: Colors.black),
isExpanded: true,
hint: Text(
'Select your region/country',
@@ -336,6 +336,16 @@ class _LoginWebPageState extends State<LoginWebPage> with HelperResponsiveLayout
obscureText: loginBloc.obscureText,
keyboardType: TextInputType.visiblePassword,
controller: loginBloc.loginPasswordController,
onFieldSubmitted: (value) {
if (loginBloc.loginFormKey.currentState!.validate()) {
loginBloc.add(LoginButtonPressed(
username: loginBloc.loginEmailController.text,
password: value,
));
} else {
loginBloc.add(ChangeValidateEvent());
}
},
decoration: textBoxDecoration()!.copyWith(
hintText: 'At least 8 characters',
hintStyle: Theme.of(context)
@@ -393,7 +403,7 @@ class _LoginWebPageState extends State<LoginWebPage> with HelperResponsiveLayout
Transform.scale(
scale: 1.2,
child: Checkbox(
fillColor: MaterialStateProperty.all<Color>(Colors.white),
fillColor: WidgetStateProperty.all<Color>(Colors.white),
activeColor: Colors.white,
value: loginBloc.isChecked,
checkColor: Colors.black,
@@ -1,8 +1,8 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/common/buttons/default_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';
import 'package:syncrow_web/utils/color_manager.dart';
class SearchResetButtons extends StatelessWidget {
const SearchResetButtons({
@@ -17,8 +17,10 @@ class SearchResetButtons extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const SizedBox(height: 25),
+179 -22
View File
@@ -13,6 +13,7 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
late AcStatusModel deviceStatus;
final String deviceId;
Timer? _timer;
Timer? _countdownTimer;
AcBloc({required this.deviceId}) : super(AcsInitialState()) {
on<AcFetchDeviceStatusEvent>(_onFetchAcStatus);
@@ -21,7 +22,16 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
on<AcBatchControlEvent>(_onAcBatchControl);
on<AcFactoryResetEvent>(_onFactoryReset);
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(
AcFetchDeviceStatusEvent event, Emitter<AcsState> emit) async {
@@ -30,8 +40,23 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
final status =
await DevicesManagementApi().getDeviceStatus(event.deviceId);
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);
emit(ACStatusLoaded(deviceStatus));
} catch (e) {
emit(AcsFailedState(error: e.toString()));
}
@@ -70,31 +95,16 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
void _onAcStatusUpdated(AcStatusUpdated event, Emitter<AcsState> emit) {
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(
AcControlEvent event, Emitter<AcsState> emit) async {
final oldValue = _getValueByCode(event.code);
_updateLocalValue(event.code, event.value, emit);
emit(ACStatusLoaded(deviceStatus));
emit(ACStatusLoaded(status: deviceStatus));
await _runDebounce(
isBatch: false,
@@ -151,7 +161,7 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
void _revertValueAndEmit(
String deviceId, String code, dynamic oldValue, Emitter<AcsState> emit) {
_updateLocalValue(code, oldValue, emit);
emit(ACStatusLoaded(deviceStatus));
emit(ACStatusLoaded(status: deviceStatus));
}
void _updateLocalValue(String code, dynamic value, Emitter<AcsState> emit) {
@@ -184,11 +194,16 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
if (value is bool) {
deviceStatus = deviceStatus.copyWith(childLock: value);
}
case 'countdown_time':
if (value is int) {
deviceStatus = deviceStatus.copyWith(countdown1: value);
}
break;
default:
break;
}
emit(ACStatusLoaded(deviceStatus));
emit(ACStatusLoaded(status: deviceStatus));
}
dynamic _getValueByCode(String code) {
@@ -203,6 +218,8 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
return deviceStatus.fanSpeedsString;
case 'child_lock':
return deviceStatus.childLock;
case 'countdown_time':
return deviceStatus.countdown1;
default:
return null;
}
@@ -216,7 +233,7 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
await DevicesManagementApi().getBatchStatus(event.devicesIds);
deviceStatus =
AcStatusModel.fromJson(event.devicesIds.first, status.status);
emit(ACStatusLoaded(deviceStatus));
emit(ACStatusLoaded(status: deviceStatus));
} catch (e) {
emit(AcsFailedState(error: e.toString()));
}
@@ -228,7 +245,7 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
_updateLocalValue(event.code, event.value, emit);
emit(ACStatusLoaded(deviceStatus));
emit(ACStatusLoaded(status: deviceStatus));
await _runDebounce(
isBatch: true,
@@ -257,4 +274,144 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
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();
}
}
@@ -8,6 +8,7 @@ sealed class AcsEvent extends Equatable {
@override
List<Object> get props => [];
}
class AcUpdated extends AcsEvent {}
class AcFetchDeviceStatusEvent extends AcsEvent {
@@ -18,10 +19,12 @@ class AcFetchDeviceStatusEvent extends AcsEvent {
@override
List<Object> get props => [deviceId];
}
class AcStatusUpdated extends AcsEvent {
final AcStatusModel deviceStatus;
AcStatusUpdated(this.deviceStatus);
}
class AcFetchBatchStatusEvent extends AcsEvent {
final List<String> devicesIds;
@@ -73,3 +76,30 @@ class AcFactoryResetEvent extends AcsEvent {
@override
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);
}
@@ -2,8 +2,9 @@ import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/device_managment/ac/model/ac_model.dart';
abstract class AcsState extends Equatable {
const AcsState();
final bool isTimerActive;
const AcsState({this.isTimerActive = false});
@override
List<Object> get props => [];
}
@@ -15,8 +16,30 @@ class AcsLoadingState extends AcsState {}
class ACStatusLoaded extends AcsState {
final AcStatusModel status;
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
List<Object> get props => [status, timestamp];
@@ -40,3 +63,14 @@ class AcsFailedState extends AcsState {
@override
List<Object> get props => [error];
}
class TimerRunInProgress extends AcsState {
final int remainingTime;
const TimerRunInProgress(this.remainingTime);
@override
List<Object> get props => [remainingTime];
}
@@ -11,6 +11,7 @@ class AcStatusModel {
final bool childLock;
final TempModes acMode;
final FanSpeeds acFanSpeed;
final int countdown1;
AcStatusModel({
required this.uuid,
@@ -18,6 +19,7 @@ class AcStatusModel {
required this.modeString,
required this.tempSet,
required this.currentTemp,
required this.countdown1,
required this.fanSpeedsString,
required this.childLock,
}) : acMode = getACMode(modeString),
@@ -30,6 +32,7 @@ class AcStatusModel {
late int currentTemp;
late String fanSpeeds;
late bool childLock;
late int _countdown1 = 0;
for (var status in jsonList) {
switch (status.code) {
@@ -51,6 +54,9 @@ class AcStatusModel {
case 'child_lock':
childLock = status.value ?? false;
break;
case 'countdown_time':
_countdown1 = status.value ?? 0;
break;
}
}
@@ -62,6 +68,7 @@ class AcStatusModel {
currentTemp: currentTemp,
fanSpeedsString: fanSpeeds,
childLock: childLock,
countdown1: _countdown1,
);
}
@@ -73,6 +80,7 @@ class AcStatusModel {
int? currentTemp,
String? fanSpeedsString,
bool? childLock,
int? countdown1,
}) {
return AcStatusModel(
uuid: uuid ?? this.uuid,
@@ -82,6 +90,7 @@ class AcStatusModel {
currentTemp: currentTemp ?? this.currentTemp,
fanSpeedsString: fanSpeedsString ?? this.fanSpeedsString,
childLock: childLock ?? this.childLock,
countdown1: countdown1 ?? this.countdown1,
);
}
@@ -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/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
class AcDeviceControlsView extends StatelessWidget with HelperResponsiveLayout {
const AcDeviceControlsView({super.key, required this.device});
const AcDeviceControlsView({super.key, required this.device});
final AllDevicesModel device;
@@ -23,11 +22,13 @@ class AcDeviceControlsView extends StatelessWidget with HelperResponsiveLayout {
final isExtraLarge = isExtraLargeScreenSize(context);
final isLarge = isLargeScreenSize(context);
final isMedium = isMediumScreenSize(context);
return BlocProvider(
create: (context) => AcBloc(deviceId: device.uuid!)
..add(AcFetchDeviceStatusEvent(device.uuid!)),
child: BlocBuilder<AcBloc, AcsState>(
builder: (context, state) {
final acBloc = BlocProvider.of<AcBloc>(context);
if (state is ACStatusLoaded) {
return GridView(
padding: const EdgeInsets.symmetric(horizontal: 50),
@@ -78,56 +79,101 @@ class AcDeviceControlsView extends StatelessWidget with HelperResponsiveLayout {
),
ToggleWidget(
label: '',
labelWidget: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
labelWidget: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
IconButton(
padding: const EdgeInsets.all(0),
onPressed: () {},
icon: const Icon(
Icons.remove,
size: 28,
color: ColorsManager.greyColor,
Container(
width: MediaQuery.of(context).size.width,
decoration: const ShapeDecoration(
color: ColorsManager.primaryColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(30)),
),
),
),
Text(
'06',
style: context.textTheme.titleLarge!.copyWith(
color: ColorsManager.dialogBlueTitle,
fontWeight: FontWeight.bold,
),
),
Text(
'h',
style: context.textTheme.bodySmall!
.copyWith(color: ColorsManager.blackColor),
),
Text(
'30',
style: context.textTheme.titleLarge!.copyWith(
color: ColorsManager.dialogBlueTitle,
fontWeight: FontWeight.bold,
),
),
Text('m',
style: context.textTheme.bodySmall!
.copyWith(color: ColorsManager.blackColor)),
IconButton(
padding: const EdgeInsets.all(0),
onPressed: () {},
icon: const Icon(
Icons.add,
size: 28,
color: ColorsManager.greyColor,
Center(
child: SizedBox(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
IconButton(
onPressed: () {
if (acBloc.timerActive == false) {
context
.read<AcBloc>()
.add(DecreaseTimeEvent());
}
},
icon: const Icon(Icons.remove,
color: ColorsManager.greyColor),
),
Text(
acBloc.scheduledHours
.toString()
.padLeft(2, '0'),
style: Theme.of(context)
.textTheme
.titleLarge!
.copyWith(
color: ColorsManager.dialogBlueTitle,
fontWeight: FontWeight.bold,
),
),
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',
deviceId: device.uuid!,
icon: Assets.acSchedule,
onChange: (value) {},
onChange: (value) {
context.read<AcBloc>().add(ToggleScheduleEvent());
},
),
ToggleWidget(
deviceId: device.uuid!,
@@ -60,7 +60,15 @@ class _CurrentTempState extends State<CurrentTemp> {
);
});
}
@override
void didUpdateWidget(CurrentTemp oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.tempSet != widget.tempSet) {
setState(() {
_adjustedValue = _initialAdjustedValue(widget.tempSet);
});
}
}
@override
void dispose() {
_debounce?.cancel();
@@ -43,8 +43,7 @@ class DeviceManagementBloc
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
if (spaceBloc.state.selectedCommunities.isEmpty) {
devices = await DevicesManagementApi()
.fetchDevices('', '', projectUuid );
devices = await DevicesManagementApi().fetchDevices('', '', projectUuid);
} else {
for (var community in spaceBloc.state.selectedCommunities) {
List<String> spacesList =
@@ -9,6 +9,8 @@ import 'package:syncrow_web/pages/device_managment/curtain/view/curtain_batch_st
import 'package:syncrow_web/pages/device_managment/curtain/view/curtain_status_view.dart';
import 'package:syncrow_web/pages/device_managment/door_lock/view/door_lock_batch_control_view.dart';
import 'package:syncrow_web/pages/device_managment/door_lock/view/door_lock_control_view.dart';
import 'package:syncrow_web/pages/device_managment/flush_mounted_presence_sensor/views/flush_mounted_presence_sensor_batch_control_view.dart';
import 'package:syncrow_web/pages/device_managment/flush_mounted_presence_sensor/views/flush_mounted_presence_sensor_control_view.dart';
import 'package:syncrow_web/pages/device_managment/garage_door/view/garage_door_batch_control_view.dart';
import 'package:syncrow_web/pages/device_managment/garage_door/view/garage_door_control_view.dart';
import 'package:syncrow_web/pages/device_managment/gateway/view/gateway_batch_control.dart';
@@ -104,6 +106,9 @@ mixin RouteControlsBasedCode {
);
case 'SOS':
return SosDeviceControlsView(device: device);
case 'NCPS':
return FlushMountedPresenceSensorControlView(device: device);
default:
return const SizedBox();
}
@@ -194,6 +199,10 @@ mixin RouteControlsBasedCode {
return SOSBatchControlView(
deviceIds: devices.where((e) => (e.productType == 'SOS')).map((e) => e.uuid!).toList(),
);
case 'NCPS':
return FlushMountedPresenceSensorBatchControlView(
devicesIds: devices.where((e) => (e.productType == 'NCPS')).map((e) => e.uuid!).toList(),
);
default:
return const SizedBox();
}
@@ -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;
}
}
@@ -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() ?? '';
}
}
@@ -1,13 +1,19 @@
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_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_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/unit.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/flush/flush_functions.dart';
import 'package:syncrow_web/pages/routines/models/gang_switches/one_gang_switch/one_gang_switch.dart';
import 'package:syncrow_web/pages/routines/models/gang_switches/three_gang_switch/three_gang_switch.dart';
import 'package:syncrow_web/pages/routines/models/gang_switches/two_gang_switch/two_gang_switch.dart';
import 'package:syncrow_web/pages/routines/models/gateway.dart';
import 'package:syncrow_web/pages/routines/models/wps/wps_functions.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_helper.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/enum/device_types.dart';
@@ -76,38 +82,41 @@ class AllDevicesModel {
int? batteryLevel;
String? productName;
List<DeviceSpaceModel>? spaces;
List<DeviceTagModel>? deviceTags;
DeviceSubSpace? deviceSubSpace;
AllDevicesModel({
this.room,
this.subspace,
this.unit,
this.community,
this.productUuid,
this.productType,
this.permissionType,
this.activeTime,
this.category,
this.categoryName,
this.createTime,
this.gatewayId,
this.icon,
this.ip,
this.lat,
this.localKey,
this.lon,
this.model,
this.name,
this.nodeId,
this.online,
this.ownerId,
this.sub,
this.timeZone,
this.updateTime,
this.uuid,
this.batteryLevel,
this.productName,
this.spaces,
});
AllDevicesModel(
{this.room,
this.subspace,
this.unit,
this.community,
this.productUuid,
this.productType,
this.permissionType,
this.activeTime,
this.category,
this.categoryName,
this.createTime,
this.gatewayId,
this.icon,
this.ip,
this.lat,
this.localKey,
this.lon,
this.model,
this.name,
this.nodeId,
this.online,
this.ownerId,
this.sub,
this.timeZone,
this.updateTime,
this.uuid,
this.batteryLevel,
this.productName,
this.spaces,
this.deviceTags,
this.deviceSubSpace});
AllDevicesModel.fromJson(Map<String, dynamic> json) {
room = (json['room'] != null && (json['room'] is Map))
@@ -145,10 +154,19 @@ class AllDevicesModel {
updateTime = int.tryParse(json['updateTime']?.toString() ?? '');
uuid = json['uuid']?.toString();
batteryLevel = int.tryParse(json['battery']?.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) {
spaces = (json['spaces'] as List).map((space) => DeviceSpaceModel.fromJson(space)).toList();
spaces = (json['spaces'] as List)
.map((space) => DeviceSpaceModel.fromJson(space))
.toList();
}
}
@@ -196,7 +214,8 @@ SOS
String tempIcon = '';
if (type == DeviceType.LightBulb) {
tempIcon = Assets.lightBulb;
} else if (type == DeviceType.CeilingSensor || type == DeviceType.WallSensor) {
} else if (type == DeviceType.CeilingSensor ||
type == DeviceType.WallSensor) {
tempIcon = Assets.sensors;
} else if (type == DeviceType.AC) {
tempIcon = Assets.ac;
@@ -226,6 +245,8 @@ SOS
// tempIcon = Assets.gang3touch;
} else if (type == DeviceType.WaterLeak) {
tempIcon = Assets.waterLeakNormal;
} else if (type == DeviceType.NCPS) {
tempIcon = Assets.sensors;
} else {
tempIcon = Assets.logoHorizontal;
}
@@ -241,36 +262,121 @@ SOS
switch (productType) {
case 'AC':
return [
SwitchFunction(deviceId: uuid ?? '', deviceName: name ?? ''),
ModeFunction(deviceId: uuid ?? '', deviceName: name ?? ''),
TempSetFunction(deviceId: uuid ?? '', deviceName: name ?? ''),
CurrentTempFunction(deviceId: uuid ?? '', deviceName: name ?? ''),
LevelFunction(deviceId: uuid ?? '', deviceName: name ?? ''),
ChildLockFunction(deviceId: uuid ?? '', deviceName: name ?? ''),
SwitchFunction(
deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'),
ModeFunction(
deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'),
TempSetFunction(
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':
return [
OneGangSwitchFunction(deviceId: uuid ?? '', deviceName: name ?? ''),
OneGangCountdownFunction(deviceId: uuid ?? '', deviceName: name ?? ''),
OneGangCountdownFunction(
deviceId: uuid ?? '', deviceName: name ?? ''),
];
case '2G':
return [
TwoGangSwitch1Function(deviceId: uuid ?? '', deviceName: name ?? ''),
TwoGangSwitch2Function(deviceId: uuid ?? '', deviceName: name ?? ''),
TwoGangCountdown1Function(deviceId: uuid ?? '', deviceName: name ?? ''),
TwoGangCountdown2Function(deviceId: uuid ?? '', deviceName: name ?? ''),
TwoGangCountdown1Function(
deviceId: uuid ?? '', deviceName: name ?? ''),
TwoGangCountdown2Function(
deviceId: uuid ?? '', deviceName: name ?? ''),
];
case '3G':
return [
ThreeGangSwitch1Function(deviceId: uuid ?? '', deviceName: name ?? ''),
ThreeGangSwitch2Function(deviceId: uuid ?? '', deviceName: name ?? ''),
ThreeGangSwitch3Function(deviceId: uuid ?? '', deviceName: name ?? ''),
ThreeGangCountdown1Function(deviceId: uuid ?? '', deviceName: name ?? ''),
ThreeGangCountdown2Function(deviceId: uuid ?? '', deviceName: name ?? ''),
ThreeGangCountdown3Function(deviceId: uuid ?? '', deviceName: name ?? ''),
ThreeGangSwitch1Function(
deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'),
ThreeGangSwitch2Function(
deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'),
ThreeGangSwitch3Function(
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':
return [
//IF Functions
PresenceStateFunction(
deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'),
CurrentDistanceFunction(
deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'),
IlluminanceValueFunction(
deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'),
PresenceTimeFunction(
deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'),
//THEN Functions
FarDetectionFunction(
deviceId: uuid ?? '', deviceName: name ?? '', type: 'THEN'),
MotionSensitivityFunction(
deviceId: uuid ?? '', deviceName: name ?? '', type: 'THEN'),
MotionLessSensitivityFunction(
deviceId: uuid ?? '', deviceName: name ?? '', type: 'THEN'),
IndicatorFunction(
deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'),
NoOneTimeFunction(
deviceId: uuid ?? '', deviceName: name ?? '', type: 'THEN'),
];
case 'GW':
return [
GatewaySwitchAlarmSound(
deviceId: uuid ?? '',
deviceName: name ?? '',
type: 'BOTH',
),
GatewayMasterState(
deviceId: uuid ?? '',
deviceName: name ?? '',
type: 'BOTH',
),
GatewayFactoryReset(
deviceId: uuid ?? '',
deviceName: name ?? '',
type: 'BOTH',
),
];
case 'CPS':
return CeilingSensorHelper.getCeilingSensorFunctions(
uuid: uuid ?? '',
name: name ?? '',
);
case 'NCPS':
return [
FlushPresenceDelayFunction(
deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF',),
FlushIlluminanceFunction(
deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'),
//THEN Functions
FlushSensitivityFunction(
deviceId: uuid ?? '', deviceName: name ?? '', type: 'THEN'),
FlushNearDetectionFunction(
deviceId: uuid ?? '', deviceName: name ?? '', type: 'THEN'),
FlushMaxDetectDistFunction(
deviceId: uuid ?? '', deviceName: name ?? '', type: 'THEN'),
FlushTargetConfirmTimeFunction(
deviceId: uuid ?? '', deviceName: name ?? '', type: 'THEN'),
FlushDisappeDelayFunction(
deviceId: uuid ?? '', deviceName: name ?? '', type: 'THEN'),
FlushIndentLevelFunction(
deviceId: uuid ?? '', deviceName: name ?? '', type: 'THEN'),
FlushTriggerLevelFunction(
deviceId: uuid ?? '', deviceName: name ?? '', type: 'THEN'),
];
default:
@@ -404,5 +510,6 @@ SOS
"3GT": DeviceType.ThreeTouch,
"GD": DeviceType.GarageDoor,
"WL": DeviceType.WaterLeak,
"NCPS": DeviceType.NCPS,
};
}
@@ -2,20 +2,24 @@ import 'package:flutter/foundation.dart';
class FactoryResetModel {
final List<String> devicesUuid;
final String operationType;
FactoryResetModel({
required this.devicesUuid,
this.operationType = "RESET",
});
factory FactoryResetModel.fromJson(Map<String, dynamic> json) {
return FactoryResetModel(
devicesUuid: List<String>.from(json['devicesUuid']),
operationType: "RESET",
);
}
Map<String, dynamic> toJson() {
return {
'devicesUuid': devicesUuid,
'operationType': operationType,
};
}
@@ -30,6 +34,7 @@ class FactoryResetModel {
Map<String, dynamic> toMap() {
return {
'devicesUuid': devicesUuid,
'operationType': operationType,
};
}
@@ -53,3 +58,4 @@ class FactoryResetModel {
@override
int get hashCode => devicesUuid.hashCode;
}
@@ -3,6 +3,8 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/widgets/device_managment_body.dart';
import 'package:syncrow_web/pages/device_managment/shared/navigate_home_grid_view.dart';
import 'package:syncrow_web/pages/routines/bloc/create_routine_bloc/create_routine_bloc.dart';
import 'package:syncrow_web/pages/routines/bloc/create_routine_bloc/create_routine_event.dart';
import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routines/view/create_new_routine_view.dart';
import 'package:syncrow_web/pages/routines/view/routines_view.dart';
@@ -39,10 +41,15 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout {
backgroundColor: null,
),
onPressed: () {
BlocProvider.of<CreateRoutineBloc>(context)
.add(const ResetSelectedEvent());
context
.read<RoutineBloc>()
.add(const TriggerSwitchTabsEvent(isRoutineTab: false));
context.read<DeviceManagementBloc>().add(FetchDevices(context));
context
.read<DeviceManagementBloc>()
.add(FetchDevices(context));
},
child: Text(
'Devices',
@@ -60,6 +67,9 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout {
backgroundColor: null,
),
onPressed: () {
BlocProvider.of<CreateRoutineBloc>(context)
.add(const ResetSelectedEvent());
context
.read<RoutineBloc>()
.add(const TriggerSwitchTabsEvent(isRoutineTab: true));
@@ -98,7 +108,7 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout {
return DeviceManagementBody(
devices: deviceState.filteredDevices);
} else {
return const Center(child: Text('Error fetching Devices'));
return const DeviceManagementBody(devices: []);
}
},
);
@@ -72,6 +72,7 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
child: state is DeviceManagementLoading
? const Center(child: CircularProgressIndicator())
: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
padding: isLargeScreenSize(context)
@@ -12,77 +12,68 @@ class DeviceSearchFilters extends StatefulWidget {
State<DeviceSearchFilters> createState() => _DeviceSearchFiltersState();
}
class _DeviceSearchFiltersState extends State<DeviceSearchFilters> with HelperResponsiveLayout {
final TextEditingController communityController = TextEditingController();
final TextEditingController unitNameController = TextEditingController();
final TextEditingController productNameController = TextEditingController();
class _DeviceSearchFiltersState extends State<DeviceSearchFilters>
with HelperResponsiveLayout {
late final TextEditingController _unitNameController;
late final TextEditingController _productNameController;
@override
void initState() {
_unitNameController = TextEditingController();
_productNameController = TextEditingController();
super.initState();
}
@override
Widget build(BuildContext context) {
return isExtraLargeScreenSize(context)
? Row(
children: [
_buildSearchField("Community", communityController, 200),
const SizedBox(width: 20),
_buildSearchField("Space Name", unitNameController, 200),
const SizedBox(width: 20),
_buildSearchField("Device Name / Product Name", productNameController, 300),
const SizedBox(width: 20),
_buildSearchResetButtons(),
],
)
: Wrap(
spacing: 20,
runSpacing: 10,
children: [
_buildSearchField(
"Community",
communityController,
200,
),
_buildSearchField("Space Name", unitNameController, 200),
_buildSearchField(
"Device Name / Product Name",
productNameController,
300,
),
_buildSearchResetButtons(),
],
);
return Wrap(
alignment: WrapAlignment.start,
runAlignment: WrapAlignment.start,
crossAxisAlignment: WrapCrossAlignment.center,
spacing: 20,
runSpacing: 10,
children: [
_buildSearchField("Space Name", _unitNameController, 200),
_buildSearchField("Device Name / Product Name", _productNameController, 300),
_buildSearchResetButtons(),
],
);
}
Widget _buildSearchField(String title, TextEditingController controller, double width) {
return Container(
child: StatefulTextField(
title: title,
width: width,
elevation: 2,
controller: controller,
onSubmitted: () {
context.read<DeviceManagementBloc>().add(SearchDevices(
productName: productNameController.text,
unitName: unitNameController.text,
community: communityController.text,
searchField: true));
},
onChanged: (p0) {},
),
Widget _buildSearchField(
String title,
TextEditingController controller,
double width,
) {
return StatefulTextField(
title: title,
width: width,
elevation: 2,
controller: controller,
onSubmitted: () {
final searchDevicesEvent = SearchDevices(
productName: _productNameController.text,
unitName: _unitNameController.text,
searchField: true,
);
context.read<DeviceManagementBloc>().add(searchDevicesEvent);
},
onChanged: (p0) {},
);
}
Widget _buildSearchResetButtons() {
return SearchResetButtons(
onSearch: () {
context.read<DeviceManagementBloc>().add(SearchDevices(
community: communityController.text,
unitName: unitNameController.text,
productName: productNameController.text,
searchField: true));
},
onSearch: () => context.read<DeviceManagementBloc>().add(
SearchDevices(
unitName: _unitNameController.text,
productName: _productNameController.text,
searchField: true,
),
),
onReset: () {
communityController.clear();
unitNameController.clear();
productNameController.clear();
_unitNameController.clear();
_productNameController.clear();
context.read<DeviceManagementBloc>()
..add(ResetFilters())
..add(FetchDevices(context));
@@ -16,7 +16,8 @@ import 'package:syncrow_web/pages/device_managment/shared/table/report_table.dar
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
class CeilingSensorControlsView extends StatelessWidget with HelperResponsiveLayout {
class CeilingSensorControlsView extends StatelessWidget
with HelperResponsiveLayout {
const CeilingSensorControlsView({super.key, required this.device});
final AllDevicesModel device;
@@ -31,29 +32,35 @@ class CeilingSensorControlsView extends StatelessWidget with HelperResponsiveLay
..add(CeilingInitialEvent(device.uuid ?? '')),
child: BlocBuilder<CeilingSensorBloc, CeilingSensorState>(
builder: (context, state) {
if (state is CeilingLoadingInitialState || state is CeilingReportsLoadingState) {
if (state is CeilingLoadingInitialState ||
state is CeilingReportsLoadingState) {
return const Center(child: CircularProgressIndicator());
} else if (state is CeilingUpdateState) {
return _buildGridView(
context, state.ceilingSensorModel, isExtraLarge, isLarge, isMedium);
return _buildGridView(context, state.ceilingSensorModel,
isExtraLarge, isLarge, isMedium);
} else if (state is CeilingReportsState) {
return ReportsTable(
report: state.deviceReport,
onRowTap: (index) {},
onClose: () {
context.read<CeilingSensorBloc>().add(BackToCeilingGridViewEvent());
context
.read<CeilingSensorBloc>()
.add(BackToCeilingGridViewEvent());
},
);
} else if (state is ShowCeilingDescriptionState) {
return DescriptionView(
description: state.description,
onClose: () {
context.read<CeilingSensorBloc>().add(BackToCeilingGridViewEvent());
context
.read<CeilingSensorBloc>()
.add(BackToCeilingGridViewEvent());
},
);
} else if (state is CeilingReportsFailedState) {
final model = context.read<CeilingSensorBloc>().deviceStatus;
return _buildGridView(context, model, isExtraLarge, isLarge, isMedium);
return _buildGridView(
context, model, isExtraLarge, isLarge, isMedium);
}
return const Center(child: Text('Error fetching status'));
},
@@ -61,8 +68,8 @@ class CeilingSensorControlsView extends StatelessWidget with HelperResponsiveLay
);
}
Widget _buildGridView(BuildContext context, CeilingSensorModel model, bool isExtraLarge,
bool isLarge, bool isMedium) {
Widget _buildGridView(BuildContext context, CeilingSensorModel model,
bool isExtraLarge, bool isLarge, bool isMedium) {
return GridView(
padding: const EdgeInsets.symmetric(horizontal: 50),
shrinkWrap: true,
@@ -143,8 +150,8 @@ class CeilingSensorControlsView extends StatelessWidget with HelperResponsiveLay
),
GestureDetector(
onTap: () {
context.read<CeilingSensorBloc>().add(
GetCeilingDeviceReportsEvent(code: 'presence_state', deviceUuid: device.uuid!));
context.read<CeilingSensorBloc>().add(GetCeilingDeviceReportsEvent(
code: 'presence_state', deviceUuid: device.uuid!));
},
child: const PresenceStaticWidget(
icon: Assets.illuminanceRecordIcon,
@@ -153,9 +160,8 @@ class CeilingSensorControlsView extends StatelessWidget with HelperResponsiveLay
),
GestureDetector(
onTap: () {
context
.read<CeilingSensorBloc>()
.add(GetCeilingDeviceReportsEvent(code: '', deviceUuid: device.uuid!));
context.read<CeilingSensorBloc>().add(GetCeilingDeviceReportsEvent(
code: '', deviceUuid: device.uuid!));
},
child: const PresenceStaticWidget(
icon: Assets.helpDescriptionIcon,
@@ -0,0 +1,250 @@
import 'dart:async';
import 'dart:developer';
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:firebase_database/firebase_database.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_reports.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart';
import 'package:syncrow_web/pages/device_managment/flush_mounted_presence_sensor/models/flush_mounted_presence_sensor_model.dart';
import 'package:syncrow_web/services/batch_control_devices_service.dart';
import 'package:syncrow_web/services/control_device_service.dart';
import 'package:syncrow_web/services/devices_mang_api.dart';
part 'flush_mounted_presence_sensor_event.dart';
part 'flush_mounted_presence_sensor_state.dart';
class FlushMountedPresenceSensorBloc
extends Bloc<FlushMountedPresenceSensorEvent, FlushMountedPresenceSensorState> {
final String deviceId;
final ControlDeviceService controlDeviceService;
final BatchControlDevicesService batchControlDevicesService;
late FlushMountedPresenceSensorModel deviceStatus;
FlushMountedPresenceSensorBloc({
required this.deviceId,
required this.controlDeviceService,
required this.batchControlDevicesService,
}) : super(FlushMountedPresenceSensorInitialState()) {
on<FlushMountedPresenceSensorFetchStatusEvent>(
_onFlushMountedPresenceSensorFetchStatusEvent,
);
on<FlushMountedPresenceSensorFetchBatchStatusEvent>(
_onFlushMountedPresenceSensorFetchBatchStatusEvent);
on<FlushMountedPresenceSensorChangeValueEvent>(
_onFlushMountedPresenceSensorChangeValueEvent,
);
on<FlushMountedPresenceSensorBatchControlEvent>(
_onFlushMountedPresenceSensorBatchControlEvent,
);
on<FlushMountedPresenceSensorGetDeviceReportsEvent>(
_onFlushMountedPresenceSensorGetDeviceReportsEvent);
on<FlushMountedPresenceSensorShowDescriptionEvent>(
_onFlushMountedPresenceSensorShowDescriptionEvent,
);
on<FlushMountedPresenceSensorBackToGridViewEvent>(
_onFlushMountedPresenceSensorBackToGridViewEvent,
);
on<FlushMountedPresenceSensorFactoryResetEvent>(
_onFlushMountedPresenceSensorFactoryResetEvent,
);
on<FlushMountedPresenceSensorStatusUpdatedEvent>(
_onFlushMountedPresenceSensorStatusUpdatedEvent,
);
}
void _onFlushMountedPresenceSensorFetchStatusEvent(
FlushMountedPresenceSensorFetchStatusEvent event,
Emitter<FlushMountedPresenceSensorState> emit,
) async {
emit(FlushMountedPresenceSensorLoadingInitialState());
try {
final response = await DevicesManagementApi().getDeviceStatus(deviceId);
deviceStatus = FlushMountedPresenceSensorModel.fromJson(response.status);
emit(FlushMountedPresenceSensorUpdateState(model: deviceStatus));
_listenToChanges(deviceId);
} catch (e) {
emit(FlushMountedPresenceSensorFailedState(error: e.toString()));
return;
}
}
Future<void> _onFlushMountedPresenceSensorFetchBatchStatusEvent(
FlushMountedPresenceSensorFetchBatchStatusEvent event,
Emitter<FlushMountedPresenceSensorState> emit,
) async {
emit(FlushMountedPresenceSensorLoadingInitialState());
try {
final response = await DevicesManagementApi().getBatchStatus(event.devicesIds);
deviceStatus = FlushMountedPresenceSensorModel.fromJson(response.status);
emit(FlushMountedPresenceSensorUpdateState(model: deviceStatus));
} catch (e) {
emit(FlushMountedPresenceSensorFailedState(error: e.toString()));
}
}
void _listenToChanges(String deviceId) {
try {
final ref = FirebaseDatabase.instance.ref(
'device-status/$deviceId',
);
ref.onValue.listen((event) {
final eventsMap = event.snapshot.value as Map<dynamic, dynamic>;
List<Status> statusList = [];
eventsMap['status'].forEach((element) {
statusList.add(
Status(code: element['code'], value: element['value']),
);
});
deviceStatus = FlushMountedPresenceSensorModel.fromJson(statusList);
if (!isClosed) {
add(FlushMountedPresenceSensorStatusUpdatedEvent(deviceStatus));
}
});
} catch (_) {
log(
'Error listening to changes',
name: 'FlushMountedPresenceSensorBloc._listenToChanges',
);
}
}
void _onFlushMountedPresenceSensorChangeValueEvent(
FlushMountedPresenceSensorChangeValueEvent event,
Emitter<FlushMountedPresenceSensorState> emit,
) async {
emit(FlushMountedPresenceSensorLoadingNewSate(model: deviceStatus));
_updateDeviceFunctionFromCode(event.code, event.value);
emit(FlushMountedPresenceSensorUpdateState(model: deviceStatus));
try {
await controlDeviceService.controlDevice(
deviceUuid: deviceId,
status: Status(code: event.code, value: event.value),
);
} catch (_) {
await _reloadDeviceStatus();
}
}
Future<void> _onFlushMountedPresenceSensorBatchControlEvent(
FlushMountedPresenceSensorBatchControlEvent event,
Emitter<FlushMountedPresenceSensorState> emit,
) async {
emit(FlushMountedPresenceSensorLoadingNewSate(model: deviceStatus));
_updateDeviceFunctionFromCode(event.code, event.value);
emit(FlushMountedPresenceSensorUpdateState(model: deviceStatus));
try {
await batchControlDevicesService.batchControlDevices(
uuids: event.deviceIds,
code: event.code,
value: event.value,
);
} catch (_) {
await _reloadDeviceStatus();
}
}
void _updateDeviceFunctionFromCode(String code, int value) {
switch (code) {
case FlushMountedPresenceSensorModel.codeFarDetection:
deviceStatus.farDetection = value;
break;
case FlushMountedPresenceSensorModel.codeSensitivity:
deviceStatus.sensitivity = value;
break;
case FlushMountedPresenceSensorModel.codeNoneDelay:
deviceStatus.noneDelay = value;
break;
case FlushMountedPresenceSensorModel.codePresenceDelay:
deviceStatus.presenceDelay = value;
break;
case FlushMountedPresenceSensorModel.codeNearDetection:
deviceStatus.nearDetection = value;
break;
case FlushMountedPresenceSensorModel.codeOccurDistReduce:
deviceStatus.occurDistReduce = value;
break;
case FlushMountedPresenceSensorModel.codeSensiReduce:
deviceStatus.sensiReduce = value;
break;
default:
return;
}
}
Future<void> _reloadDeviceStatus() async {
await Future.delayed(const Duration(milliseconds: 500), () {
add(FlushMountedPresenceSensorFetchStatusEvent());
});
}
Future<void> _onFlushMountedPresenceSensorGetDeviceReportsEvent(
FlushMountedPresenceSensorGetDeviceReportsEvent event,
Emitter<FlushMountedPresenceSensorState> emit,
) async {
emit(FlushMountedPresenceSensorDeviceReportsLoadingState());
try {
await DevicesManagementApi.getDeviceReports(deviceId, event.code)
.then((value) {
emit(FlushMountedPresenceSensorDeviceReportsState(
deviceReport: value, code: event.code));
});
} catch (e) {
emit(FlushMountedPresenceSensorDeviceReportsFailedState(error: e.toString()));
return;
}
}
void _onFlushMountedPresenceSensorShowDescriptionEvent(
FlushMountedPresenceSensorShowDescriptionEvent event,
Emitter<FlushMountedPresenceSensorState> emit,
) {
emit(FlushMountedPresenceSensorShowDescriptionState(
description: event.description));
}
void _onFlushMountedPresenceSensorBackToGridViewEvent(
FlushMountedPresenceSensorBackToGridViewEvent event,
Emitter<FlushMountedPresenceSensorState> emit,
) {
emit(FlushMountedPresenceSensorUpdateState(model: deviceStatus));
}
Future<void> _onFlushMountedPresenceSensorFactoryResetEvent(
FlushMountedPresenceSensorFactoryResetEvent event,
Emitter<FlushMountedPresenceSensorState> emit,
) async {
emit(FlushMountedPresenceSensorLoadingNewSate(model: deviceStatus));
try {
final response = await DevicesManagementApi().factoryReset(
event.factoryReset,
event.deviceId,
);
if (!response) {
emit(
const FlushMountedPresenceSensorFailedState(
error: 'Something went wrong with factory reset, please try again',
),
);
} else {
emit(FlushMountedPresenceSensorUpdateState(model: deviceStatus));
}
} catch (e) {
emit(FlushMountedPresenceSensorFailedState(error: e.toString()));
}
}
void _onFlushMountedPresenceSensorStatusUpdatedEvent(
FlushMountedPresenceSensorStatusUpdatedEvent event,
Emitter<FlushMountedPresenceSensorState> emit,
) {
deviceStatus = event.model;
emit(FlushMountedPresenceSensorUpdateState(model: deviceStatus));
}
}
@@ -0,0 +1,93 @@
part of 'flush_mounted_presence_sensor_bloc.dart';
sealed class FlushMountedPresenceSensorEvent extends Equatable {
const FlushMountedPresenceSensorEvent();
@override
List<Object> get props => [];
}
class FlushMountedPresenceSensorFetchStatusEvent
extends FlushMountedPresenceSensorEvent {}
class FlushMountedPresenceSensorStatusUpdatedEvent
extends FlushMountedPresenceSensorEvent {
const FlushMountedPresenceSensorStatusUpdatedEvent(this.model);
final FlushMountedPresenceSensorModel model;
@override
List<Object> get props => [model];
}
class FlushMountedPresenceSensorChangeValueEvent
extends FlushMountedPresenceSensorEvent {
final int value;
final String code;
const FlushMountedPresenceSensorChangeValueEvent({
required this.value,
required this.code,
});
@override
List<Object> get props => [value, code];
}
class FlushMountedPresenceSensorFetchBatchStatusEvent
extends FlushMountedPresenceSensorEvent {
final List<String> devicesIds;
const FlushMountedPresenceSensorFetchBatchStatusEvent(this.devicesIds);
@override
List<Object> get props => [devicesIds];
}
class FlushMountedPresenceSensorGetDeviceReportsEvent
extends FlushMountedPresenceSensorEvent {
final String deviceUuid;
final String code;
const FlushMountedPresenceSensorGetDeviceReportsEvent({
required this.deviceUuid,
required this.code,
});
@override
List<Object> get props => [deviceUuid, code];
}
class FlushMountedPresenceSensorShowDescriptionEvent
extends FlushMountedPresenceSensorEvent {
final String description;
const FlushMountedPresenceSensorShowDescriptionEvent({required this.description});
}
class FlushMountedPresenceSensorBackToGridViewEvent
extends FlushMountedPresenceSensorEvent {}
class FlushMountedPresenceSensorBatchControlEvent
extends FlushMountedPresenceSensorEvent {
final List<String> deviceIds;
final String code;
final dynamic value;
const FlushMountedPresenceSensorBatchControlEvent({
required this.deviceIds,
required this.code,
required this.value,
});
@override
List<Object> get props => [deviceIds, code, value];
}
class FlushMountedPresenceSensorFactoryResetEvent
extends FlushMountedPresenceSensorEvent {
final String deviceId;
final FactoryResetModel factoryReset;
const FlushMountedPresenceSensorFactoryResetEvent({
required this.deviceId,
required this.factoryReset,
});
}
@@ -0,0 +1,76 @@
part of 'flush_mounted_presence_sensor_bloc.dart';
sealed class FlushMountedPresenceSensorState extends Equatable {
const FlushMountedPresenceSensorState();
@override
List<Object> get props => [];
}
class FlushMountedPresenceSensorInitialState
extends FlushMountedPresenceSensorState {}
class FlushMountedPresenceSensorLoadingInitialState
extends FlushMountedPresenceSensorState {}
class FlushMountedPresenceSensorUpdateState extends FlushMountedPresenceSensorState {
final FlushMountedPresenceSensorModel model;
const FlushMountedPresenceSensorUpdateState({required this.model});
@override
List<Object> get props => [model];
}
class FlushMountedPresenceSensorLoadingNewSate
extends FlushMountedPresenceSensorState {
final FlushMountedPresenceSensorModel model;
const FlushMountedPresenceSensorLoadingNewSate({required this.model});
@override
List<Object> get props => [model];
}
class FlushMountedPresenceSensorFailedState extends FlushMountedPresenceSensorState {
final String error;
const FlushMountedPresenceSensorFailedState({required this.error});
@override
List<Object> get props => [error];
}
class FlushMountedPresenceSensorDeviceReportsLoadingState
extends FlushMountedPresenceSensorState {}
class FlushMountedPresenceSensorDeviceReportsState
extends FlushMountedPresenceSensorState {
const FlushMountedPresenceSensorDeviceReportsState({
required this.deviceReport,
required this.code,
});
final DeviceReport deviceReport;
final String code;
@override
List<Object> get props => [deviceReport, code];
}
class FlushMountedPresenceSensorDeviceReportsFailedState
extends FlushMountedPresenceSensorState {
const FlushMountedPresenceSensorDeviceReportsFailedState({required this.error});
final String error;
@override
List<Object> get props => [error];
}
class FlushMountedPresenceSensorShowDescriptionState
extends FlushMountedPresenceSensorState {
const FlushMountedPresenceSensorShowDescriptionState({required this.description});
final String description;
@override
List<Object> get props => [description];
}
@@ -0,0 +1,21 @@
import 'package:syncrow_web/pages/device_managment/flush_mounted_presence_sensor/bloc/flush_mounted_presence_sensor_bloc.dart';
import 'package:syncrow_web/services/batch_control_devices_service.dart';
import 'package:syncrow_web/services/control_device_service.dart';
abstract final class FlushMountedPresenceSensorBlocFactory {
const FlushMountedPresenceSensorBlocFactory._();
static FlushMountedPresenceSensorBloc create({
required String deviceId,
}) {
return FlushMountedPresenceSensorBloc(
deviceId: deviceId,
controlDeviceService: DebouncedControlDeviceService(
decoratee: RemoteControlDeviceService(),
),
batchControlDevicesService: DebouncedBatchControlDevicesService(
decoratee: RemoteBatchControlDevicesService(),
),
);
}
}
@@ -0,0 +1,103 @@
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
class FlushMountedPresenceSensorModel {
FlushMountedPresenceSensorModel({
required this.presenceState,
required this.farDetection,
required this.illuminance,
required this.sensitivity,
required this.occurDistReduce,
required this.noneDelay,
required this.presenceDelay,
required this.nearDetection,
required this.sensiReduce,
required this.checkingResult,
});
static const String codePresenceState = 'presence_state';
static const String codeSensitivity = 'sensitivity';
static const String codeNearDetection = 'near_detection';
static const String codeFarDetection = 'far_detection';
static const String codeCheckingResult = 'checking_result';
static const String codePresenceDelay = 'presence_delay';
static const String codeNoneDelay = 'none_delay';
static const String codeOccurDistReduce = 'occur_dist_reduce';
static const String codeIlluminance = 'illum_value';
static const String codeSensiReduce = 'sensi_reduce';
String presenceState;
int sensitivity;
int nearDetection;
int farDetection;
String checkingResult;
int presenceDelay;
int noneDelay;
int occurDistReduce;
int illuminance;
int sensiReduce;
factory FlushMountedPresenceSensorModel.fromJson(List<Status> jsonList) {
String presenceState = 'none';
int sensitivity = 0;
int nearDetection = 0;
int farDetection = 0;
String checkingResult = 'none';
int presenceDelay = 0;
int noneDelay = 0;
int occurDistReduce = 0;
int illuminance = 0;
int sensiReduce = 0;
for (var status in jsonList) {
switch (status.code) {
case codePresenceState:
presenceState = status.value ?? 'presence';
break;
case codeSensitivity:
sensitivity = status.value ?? 0;
break;
case codeNearDetection:
nearDetection = status.value ?? 0;
break;
case codeFarDetection:
farDetection = status.value ?? 0;
break;
case codeCheckingResult:
checkingResult = status.value ?? 'check_success';
break;
case codePresenceDelay:
presenceDelay = status.value ?? 0;
break;
case codeNoneDelay:
noneDelay = status.value ?? 0;
break;
case codeOccurDistReduce:
occurDistReduce = status.value ?? 0;
break;
case codeIlluminance:
illuminance = status.value ?? 0;
break;
case codeSensiReduce:
sensiReduce = status.value ?? 0;
break;
}
}
return FlushMountedPresenceSensorModel(
presenceState: presenceState,
sensitivity: sensitivity,
nearDetection: nearDetection,
farDetection: farDetection,
checkingResult: checkingResult,
presenceDelay: presenceDelay,
noneDelay: noneDelay,
occurDistReduce: occurDistReduce,
illuminance: illuminance,
sensiReduce: sensiReduce,
);
}
}
@@ -0,0 +1,185 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart';
import 'package:syncrow_web/pages/device_managment/flush_mounted_presence_sensor/bloc/flush_mounted_presence_sensor_bloc.dart';
import 'package:syncrow_web/pages/device_managment/flush_mounted_presence_sensor/factories/flush_mounted_presence_sensor_bloc_factory.dart';
import 'package:syncrow_web/pages/device_managment/flush_mounted_presence_sensor/models/flush_mounted_presence_sensor_model.dart';
import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart';
import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_update_data.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
class FlushMountedPresenceSensorBatchControlView extends StatelessWidget
with HelperResponsiveLayout {
const FlushMountedPresenceSensorBatchControlView({
required this.devicesIds,
super.key,
});
final List<String> devicesIds;
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => FlushMountedPresenceSensorBlocFactory.create(
deviceId: devicesIds.first,
)..add(FlushMountedPresenceSensorFetchBatchStatusEvent(devicesIds)),
child: BlocBuilder<FlushMountedPresenceSensorBloc,
FlushMountedPresenceSensorState>(
builder: (context, state) {
if (state is FlushMountedPresenceSensorLoadingInitialState ||
state is FlushMountedPresenceSensorDeviceReportsLoadingState) {
return const Center(child: CircularProgressIndicator());
} else if (state is FlushMountedPresenceSensorUpdateState) {
return _buildGridView(context, state.model);
}
return const Center(child: Text('Error fetching status'));
},
),
);
}
Widget _buildGridView(
BuildContext context,
FlushMountedPresenceSensorModel model,
) {
final isExtraLarge = isExtraLargeScreenSize(context);
final isLarge = isLargeScreenSize(context);
final isMedium = isMediumScreenSize(context);
return GridView(
padding: const EdgeInsets.symmetric(horizontal: 50),
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: isLarge || isExtraLarge
? 3
: isMedium
? 2
: 1,
mainAxisExtent: 140,
crossAxisSpacing: 12,
mainAxisSpacing: 12,
),
children: [
PresenceUpdateData(
value: model.sensitivity.toDouble(),
title: 'Sensitivity:',
minValue: 0,
maxValue: 9,
steps: 1,
action: (int value) => context.read<FlushMountedPresenceSensorBloc>().add(
FlushMountedPresenceSensorBatchControlEvent(
deviceIds: devicesIds,
code: FlushMountedPresenceSensorModel.codeSensitivity,
value: value,
),
),
),
PresenceUpdateData(
value: (model.nearDetection / 100).clamp(0.0, double.infinity),
title: 'Nearest Detect Dist:',
description: 'm',
minValue: 0.0,
maxValue: 9.5,
steps: 0.1,
valuesPercision: 1,
action: (double value) =>
context.read<FlushMountedPresenceSensorBloc>().add(
FlushMountedPresenceSensorBatchControlEvent(
deviceIds: devicesIds,
code: FlushMountedPresenceSensorModel.codeNearDetection,
value: (value * 100).toInt(),
),
),
),
PresenceUpdateData(
value: (model.farDetection / 100).clamp(0.0, double.infinity),
title: 'Max Detect Dist:',
description: 'm',
minValue: 0.0,
maxValue: 9.5,
steps: 0.1,
valuesPercision: 1,
action: (double value) =>
context.read<FlushMountedPresenceSensorBloc>().add(
FlushMountedPresenceSensorBatchControlEvent(
deviceIds: devicesIds,
code: FlushMountedPresenceSensorModel.codeFarDetection,
value: (value * 100).toInt(),
),
),
),
PresenceUpdateData(
value: model.sensiReduce.toDouble(),
title: 'Trigger Level:',
minValue: 0,
maxValue: 3,
steps: 1,
action: (int value) => context.read<FlushMountedPresenceSensorBloc>().add(
FlushMountedPresenceSensorBatchControlEvent(
deviceIds: devicesIds,
code: FlushMountedPresenceSensorModel.codeSensiReduce,
value: value,
),
),
),
PresenceUpdateData(
value: model.occurDistReduce.toDouble(),
title: 'Indent Level:',
minValue: 0,
maxValue: 3,
steps: 1,
action: (int value) => context.read<FlushMountedPresenceSensorBloc>().add(
FlushMountedPresenceSensorBatchControlEvent(
deviceIds: devicesIds,
code: FlushMountedPresenceSensorModel.codeOccurDistReduce,
value: value,
),
),
),
PresenceUpdateData(
value: (model.presenceDelay / 10).toDouble(),
title: 'Target Confirm Time:',
description: 's',
minValue: 0.0,
maxValue: 0.5,
steps: 0.1,
valuesPercision: 1,
action: (double value) =>
context.read<FlushMountedPresenceSensorBloc>().add(
FlushMountedPresenceSensorBatchControlEvent(
deviceIds: devicesIds,
code: FlushMountedPresenceSensorModel.codePresenceDelay,
value: (value * 10).toInt(),
),
),
),
PresenceUpdateData(
value: ((model.noneDelay / 10).toDouble()),
description: 's',
title: 'Disappe Delay:',
minValue: 20,
maxValue: 300,
steps: 1,
action: (double value) =>
context.read<FlushMountedPresenceSensorBloc>().add(
FlushMountedPresenceSensorBatchControlEvent(
deviceIds: devicesIds,
code: FlushMountedPresenceSensorModel.codeNoneDelay,
value: (value * 10).round(),
),
),
),
FactoryResetWidget(
callFactoryReset: () {
context.read<FlushMountedPresenceSensorBloc>().add(
FlushMountedPresenceSensorFactoryResetEvent(
deviceId: devicesIds.first,
factoryReset: FactoryResetModel(devicesUuid: devicesIds),
),
);
},
),
],
);
}
}
@@ -0,0 +1,219 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
import 'package:syncrow_web/pages/device_managment/flush_mounted_presence_sensor/bloc/flush_mounted_presence_sensor_bloc.dart';
import 'package:syncrow_web/pages/device_managment/flush_mounted_presence_sensor/factories/flush_mounted_presence_sensor_bloc_factory.dart';
import 'package:syncrow_web/pages/device_managment/flush_mounted_presence_sensor/models/flush_mounted_presence_sensor_model.dart';
import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_display_data.dart';
import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_static_widget.dart';
import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_status.dart';
import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_update_data.dart';
import 'package:syncrow_web/pages/device_managment/shared/table/description_view.dart';
import 'package:syncrow_web/pages/device_managment/shared/table/report_table.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
class FlushMountedPresenceSensorControlView extends StatelessWidget
with HelperResponsiveLayout {
const FlushMountedPresenceSensorControlView({required this.device, super.key});
final AllDevicesModel device;
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => FlushMountedPresenceSensorBlocFactory.create(
deviceId: device.uuid ?? '-1',
)..add(FlushMountedPresenceSensorFetchStatusEvent()),
child: BlocBuilder<FlushMountedPresenceSensorBloc,
FlushMountedPresenceSensorState>(
builder: (context, state) {
if (state is FlushMountedPresenceSensorLoadingInitialState ||
state is FlushMountedPresenceSensorDeviceReportsLoadingState) {
return const Center(child: CircularProgressIndicator());
} else if (state is FlushMountedPresenceSensorUpdateState) {
return _buildGridView(context, state.model);
} else if (state is FlushMountedPresenceSensorDeviceReportsState) {
return ReportsTable(
report: state.deviceReport,
thirdColumnTitle:
state.code == 'illuminance_value' ? "Value" : 'Status',
thirdColumnDescription:
state.code == 'illuminance_value' ? "Lux" : null,
onRowTap: (index) {},
onClose: () {
context
.read<FlushMountedPresenceSensorBloc>()
.add(FlushMountedPresenceSensorBackToGridViewEvent());
},
);
} else if (state is FlushMountedPresenceSensorShowDescriptionState) {
return DescriptionView(
description: state.description,
onClose: () {
context
.read<FlushMountedPresenceSensorBloc>()
.add(FlushMountedPresenceSensorBackToGridViewEvent());
},
);
} else if (state is FlushMountedPresenceSensorDeviceReportsFailedState) {
final model =
context.read<FlushMountedPresenceSensorBloc>().deviceStatus;
return _buildGridView(context, model);
}
return const Center(
child: Text('Error fetching status', textAlign: TextAlign.center),
);
},
),
);
}
Widget _buildGridView(
BuildContext context,
FlushMountedPresenceSensorModel model,
) {
final isExtraLarge = isExtraLargeScreenSize(context);
final isLarge = isLargeScreenSize(context);
final isMedium = isMediumScreenSize(context);
return GridView(
padding: const EdgeInsets.symmetric(horizontal: 50),
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: isLarge || isExtraLarge
? 3
: isMedium
? 2
: 1,
mainAxisExtent: 140,
crossAxisSpacing: 12,
mainAxisSpacing: 12,
),
children: [
PresenceState(
value: model.presenceState,
),
PresenceDisplayValue(
value: model.illuminance.toString(),
postfix: 'Lux',
description: 'Illuminance Value',
),
PresenceUpdateData(
value: model.sensitivity.toDouble(),
title: 'Sensitivity:',
minValue: 0,
maxValue: 9,
steps: 1,
action: (int value) => context.read<FlushMountedPresenceSensorBloc>().add(
FlushMountedPresenceSensorChangeValueEvent(
code: FlushMountedPresenceSensorModel.codeSensitivity,
value: value,
),
),
),
PresenceUpdateData(
value: (model.nearDetection / 100).clamp(0.0, double.infinity),
title: 'Nearest Detect Dist:',
description: 'm',
minValue: 0.0,
maxValue: 9.5,
steps: 0.1,
valuesPercision: 1,
action: (double value) =>
context.read<FlushMountedPresenceSensorBloc>().add(
FlushMountedPresenceSensorChangeValueEvent(
code: FlushMountedPresenceSensorModel.codeNearDetection,
value: (value * 100).toInt(),
),
),
),
PresenceUpdateData(
value: (model.farDetection / 100).clamp(0.0, double.infinity),
title: 'Max Detect Dist:',
description: 'm',
minValue: 0.0,
maxValue: 9.5,
steps: 0.1,
valuesPercision: 1,
action: (double value) =>
context.read<FlushMountedPresenceSensorBloc>().add(
FlushMountedPresenceSensorChangeValueEvent(
code: FlushMountedPresenceSensorModel.codeFarDetection,
value: (value * 100).toInt(),
),
),
),
PresenceUpdateData(
value: model.sensiReduce.toDouble(),
title: 'Trigger Level:',
minValue: 0,
maxValue: 3,
steps: 1,
action: (int value) => context.read<FlushMountedPresenceSensorBloc>().add(
FlushMountedPresenceSensorChangeValueEvent(
code: FlushMountedPresenceSensorModel.codeSensiReduce,
value: value,
),
),
),
PresenceUpdateData(
value: model.occurDistReduce.toDouble(),
title: 'Indent Level:',
minValue: 0,
maxValue: 3,
steps: 1,
action: (int value) => context.read<FlushMountedPresenceSensorBloc>().add(
FlushMountedPresenceSensorChangeValueEvent(
code: FlushMountedPresenceSensorModel.codeOccurDistReduce,
value: value,
),
),
),
PresenceUpdateData(
value: (model.presenceDelay / 10).toDouble(),
valuesPercision: 1,
title: 'Target Confirm Time:',
description: 's',
minValue: 0.0,
maxValue: 0.5,
steps: 0.1,
action: (double value) =>
context.read<FlushMountedPresenceSensorBloc>().add(
FlushMountedPresenceSensorChangeValueEvent(
code: FlushMountedPresenceSensorModel.codePresenceDelay,
value: (value * 10).toInt(),
),
),
),
PresenceUpdateData(
value: (model.noneDelay / 10).toDouble(),
description: 's',
title: 'Disappe Delay:',
minValue: 20,
maxValue: 300,
steps: 1,
action: (double value) =>
context.read<FlushMountedPresenceSensorBloc>().add(
FlushMountedPresenceSensorChangeValueEvent(
code: FlushMountedPresenceSensorModel.codeNoneDelay,
value: (value * 10).round(),
),
),
),
GestureDetector(
onTap: () => context.read<FlushMountedPresenceSensorBloc>().add(
FlushMountedPresenceSensorGetDeviceReportsEvent(
code: 'presence_state',
deviceUuid: device.uuid!,
),
),
child: const PresenceStaticWidget(
icon: Assets.presenceRecordIcon,
description: 'Presence Record',
),
),
],
);
}
}
@@ -106,14 +106,14 @@ class GarageDoorBloc extends Bloc<GarageDoorEvent, GarageDoorState> {
ScheduleEntry newSchedule = ScheduleEntry(
category: event.category,
time: formatTimeOfDayToISO(event.time),
function: Status(code: 'switch_1', value: event.functionOn),
function: Status(code: 'doorcontact_state', value: event.functionOn),
days: ScheduleModel.convertSelectedDaysToStrings(event.selectedDays),
);
bool success =
await DevicesManagementApi().addScheduleRecord(newSchedule, deviceId);
if (success) {
add(FetchGarageDoorSchedulesEvent(
deviceId: deviceId, category: 'switch_1'));
deviceId: deviceId, category: 'doorcontact_state'));
} else {
emit(GarageDoorLoadedState(status: deviceStatus));
}
@@ -149,7 +149,8 @@ class GarageDoorBloc extends Bloc<GarageDoorEvent, GarageDoorState> {
final updatedSchedules = deviceStatus.schedules?.map((schedule) {
if (schedule.scheduleId == event.scheduleId) {
return schedule.copyWith(
function: Status(code: 'switch_1', value: event.functionOn),
function:
Status(code: 'doorcontact_state', value: event.functionOn),
enable: event.enable,
);
}
@@ -274,7 +275,8 @@ class GarageDoorBloc extends Bloc<GarageDoorEvent, GarageDoorState> {
Future<void> _onBatchControl(
GarageDoorBatchControlEvent event, Emitter<GarageDoorState> emit) async {
final oldValue = event.code == 'switch_1' ? deviceStatus.switch1 : false;
final oldValue =
event.code == 'doorcontact_state' ? deviceStatus.switch1 : false;
_updateLocalValue(event.code, event.value);
emit(GarageDoorBatchStatusLoaded(deviceStatus));
@@ -409,7 +411,7 @@ class GarageDoorBloc extends Bloc<GarageDoorEvent, GarageDoorState> {
void _revertValue(
String code, dynamic oldValue, Emitter<GarageDoorState> emit) {
switch (code) {
case 'switch_1':
case 'doorcontact_state':
if (oldValue is bool) {
deviceStatus = deviceStatus.copyWith(switch1: oldValue);
}
@@ -468,10 +470,11 @@ class GarageDoorBloc extends Bloc<GarageDoorEvent, GarageDoorState> {
deviceStatus = deviceStatus.copyWith(voiceControl1: value);
}
break;
case 'door_contact_state':
case 'doorcontact_state':
if (value is bool) {
deviceStatus = deviceStatus.copyWith(doorContactState: value);
}
default:
break;
}
@@ -490,14 +493,14 @@ class GarageDoorBloc extends Bloc<GarageDoorEvent, GarageDoorState> {
scheduleId: event.scheduleId,
category: event.category,
time: formatTimeOfDayToISO(event.time),
function: Status(code: 'switch_1', value: event.functionOn),
function: Status(code: 'doorcontact_state', value: event.functionOn),
days: ScheduleModel.convertSelectedDaysToStrings(event.selectedDays),
);
bool success = await DevicesManagementApi()
.editScheduleRecord(deviceId, newSchedule);
if (success) {
add(FetchGarageDoorSchedulesEvent(
deviceId: deviceId, category: 'switch_1'));
deviceId: deviceId, category: 'doorcontact_state'));
} else {
emit(GarageDoorLoadedState(status: deviceStatus));
}
@@ -14,7 +14,8 @@ import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
class GarageDoorControlView extends StatelessWidget with HelperResponsiveLayout {
class GarageDoorControlView extends StatelessWidget
with HelperResponsiveLayout {
final String deviceId;
const GarageDoorControlView({required this.deviceId, super.key});
@@ -22,7 +23,8 @@ class GarageDoorControlView extends StatelessWidget with HelperResponsiveLayout
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => GarageDoorBloc(deviceId: deviceId)..add(GarageDoorInitialEvent(deviceId)),
create: (context) => GarageDoorBloc(deviceId: deviceId)
..add(GarageDoorInitialEvent(deviceId)),
child: BlocBuilder<GarageDoorBloc, GarageDoorState>(
builder: (context, state) {
if (state is GarageDoorLoadingState) {
@@ -34,7 +36,9 @@ class GarageDoorControlView extends StatelessWidget with HelperResponsiveLayout
garageDoorSensor: true,
onRowTap: (index) {},
onClose: () {
context.read<GarageDoorBloc>().add(BackToGarageDoorGridViewEvent());
context
.read<GarageDoorBloc>()
.add(BackToGarageDoorGridViewEvent());
},
);
} else if (state is GarageDoorLoadedState) {
@@ -71,11 +75,14 @@ class GarageDoorControlView extends StatelessWidget with HelperResponsiveLayout
children: [
IconNameStatusContainer(
isFullIcon: false,
name: status.switch1 ? 'Opened' : 'Closed',
icon: status.switch1 ? Assets.openedDoor : Assets.closedDoor,
name: status.doorContactState ? 'Opened' : 'Closed',
icon: status.doorContactState ? Assets.openedDoor : Assets.closedDoor,
onTap: () {
context.read<GarageDoorBloc>().add(
GarageDoorControlEvent(deviceId: status.uuid, value: !status.switch1, code: 'switch_1'),
GarageDoorControlEvent(
deviceId: status.uuid,
value: !status.switch1,
code: 'doorcontact_state'),
);
},
status: status.switch1,
@@ -84,7 +91,8 @@ class GarageDoorControlView extends StatelessWidget with HelperResponsiveLayout
IconNameStatusContainer(
onTap: () {
context.read<GarageDoorBloc>().add(
FetchGarageDoorSchedulesEvent(deviceId: deviceId, category: 'switch_1'),
FetchGarageDoorSchedulesEvent(
deviceId: deviceId, category: 'doorcontact_state'),
);
showDialog(
context: context,
@@ -107,7 +115,9 @@ class GarageDoorControlView extends StatelessWidget with HelperResponsiveLayout
children: [
IconButton(
onPressed: () {
context.read<GarageDoorBloc>().add(DecreaseGarageDoorDelayEvent(deviceId: status.uuid));
context
.read<GarageDoorBloc>()
.add(DecreaseGarageDoorDelayEvent(deviceId: status.uuid));
},
icon: const Icon(
Icons.remove,
@@ -125,7 +135,8 @@ class GarageDoorControlView extends StatelessWidget with HelperResponsiveLayout
),
Text(
'h',
style: context.textTheme.bodySmall!.copyWith(color: ColorsManager.blackColor),
style: context.textTheme.bodySmall!
.copyWith(color: ColorsManager.blackColor),
),
Text(
(status.delay.inMinutes % 60).toString().padLeft(2, '0'),
@@ -136,11 +147,14 @@ class GarageDoorControlView extends StatelessWidget with HelperResponsiveLayout
),
Text(
'm',
style: context.textTheme.bodySmall!.copyWith(color: ColorsManager.blackColor),
style: context.textTheme.bodySmall!
.copyWith(color: ColorsManager.blackColor),
),
IconButton(
onPressed: () {
context.read<GarageDoorBloc>().add(IncreaseGarageDoorDelayEvent(deviceId: status.uuid));
context
.read<GarageDoorBloc>()
.add(IncreaseGarageDoorDelayEvent(deviceId: status.uuid));
},
icon: const Icon(
Icons.add,
@@ -158,7 +172,9 @@ class GarageDoorControlView extends StatelessWidget with HelperResponsiveLayout
onChange: (value) {
context.read<GarageDoorBloc>().add(
GarageDoorControlEvent(
deviceId: status.uuid, value: value ? status.delay.inSeconds : 0, code: 'countdown_1'),
deviceId: status.uuid,
value: value ? status.delay.inSeconds : 0,
code: 'countdown_1'),
);
},
),
@@ -167,7 +183,8 @@ class GarageDoorControlView extends StatelessWidget with HelperResponsiveLayout
name: 'Records',
icon: Assets.records,
onTap: () {
context.read<GarageDoorBloc>().add(FetchGarageDoorRecordsEvent(code: 'switch_1', deviceId: status.uuid));
context.read<GarageDoorBloc>().add(FetchGarageDoorRecordsEvent(
code: 'doorcontact_state', deviceId: status.uuid));
},
status: false,
textColor: ColorsManager.blackColor,
@@ -0,0 +1,49 @@
class GatewayModel {
final String uuid;
final bool switchAlarmSound;
final String masterState;
final bool factoryReset;
final String alarmActive;
GatewayModel({
required this.uuid,
required this.switchAlarmSound,
required this.masterState,
required this.factoryReset,
required this.alarmActive,
});
factory GatewayModel.fromJson(Map<String, dynamic> json) {
final status = json['status'] as List<dynamic>? ?? <dynamic>[];
bool? switchAlarmSound;
String? masterState;
bool? factoryReset;
String? alarmActive;
for (final item in status) {
switch (item['code']) {
case 'switch_alarm_sound':
switchAlarmSound = item['value'] as bool;
break;
case 'master_state':
masterState = item['value'] as String;
break;
case 'factory_reset':
factoryReset = item['value'] as bool;
break;
case 'alarm_active':
alarmActive = item['value'] as String;
break;
}
}
return GatewayModel(
uuid: json['uuid'] as String? ?? '',
switchAlarmSound: switchAlarmSound ?? false,
masterState: masterState ?? '',
factoryReset: factoryReset ?? false,
alarmActive: alarmActive ?? '',
);
}
}
@@ -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_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/firmware_update.dart';
class MainDoorSensorBatchView extends StatelessWidget {
const MainDoorSensorBatchView({super.key, required this.devicesIds});
@@ -13,35 +12,31 @@ class MainDoorSensorBatchView extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// SizedBox(
// width: 170,
// height: 140,
// child: FirmwareUpdateWidget(
// deviceId: devicesIds.first,
// version: 12,
// ),
// ),
// const SizedBox(
// width: 12,
// ),
SizedBox(
width: 170,
height: 140,
child: FactoryResetWidget(
callFactoryReset: () {
BlocProvider.of<MainDoorSensorBloc>(context).add(
MainDoorSensorFactoryReset(
deviceId: devicesIds.first,
factoryReset: FactoryResetModel(devicesUuid: devicesIds),
return BlocProvider(
create: (context) => MainDoorSensorBloc(),
child: Builder(
builder: (innerContext) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
width: 170,
height: 140,
child: FactoryResetWidget(
callFactoryReset: () {
BlocProvider.of<MainDoorSensorBloc>(innerContext).add(
MainDoorSensorFactoryReset(
deviceId: devicesIds.first,
factoryReset: FactoryResetModel(devicesUuid: devicesIds),
),
);
},
),
);
},
),
),
],
),
],
);
},
),
);
}
}
@@ -217,29 +217,31 @@ class SmartPowerBloc extends Bloc<SmartPowerEvent, SmartPowerState> {
try {
var status =
await DevicesManagementApi().getPowerClampInfo(event.deviceId);
deviceStatus = PowerClampModel.fromJson(status);
deviceStatus = PowerClampModel.fromJson(status as Map<String, Object?>? ??{});
final phaseADataPoints = deviceStatus.status.phaseA.dataPoints;
final phaseBDataPoints = deviceStatus.status.phaseB.dataPoints;
final phaseCDataPoints = deviceStatus.status.phaseC.dataPoints;
phaseData = [
{
'name': 'Phase A',
'voltage': '${deviceStatus.status.phaseA.dataPoints[0].value / 10} V',
'current': '${deviceStatus.status.phaseA.dataPoints[1].value / 10} A',
'activePower': '${deviceStatus.status.phaseA.dataPoints[2].value} W',
'powerFactor': '${deviceStatus.status.phaseA.dataPoints[3].value}',
'voltage': '${(phaseADataPoints.elementAtOrNull(0)?.value as num? ?? 0) / 10} V',
'current': '${(phaseADataPoints.elementAtOrNull(1)?.value as num? ?? 0) / 10} A',
'activePower': '${phaseADataPoints.elementAtOrNull(2)?.value??'N/A'} W',
'powerFactor': '${phaseADataPoints.elementAtOrNull(3)?.value??'N/A'}',
},
{
'name': 'Phase B',
'voltage': '${deviceStatus.status.phaseB.dataPoints[0].value / 10} V',
'current': '${deviceStatus.status.phaseB.dataPoints[1].value / 10} A',
'activePower': '${deviceStatus.status.phaseB.dataPoints[2].value} W',
'powerFactor': '${deviceStatus.status.phaseB.dataPoints[3].value}',
'voltage': '${(phaseBDataPoints .elementAtOrNull(0)?.value as num? ?? 0) / 10} V',
'current': '${(phaseBDataPoints .elementAtOrNull(1)?.value as num? ?? 0) / 10} A',
'activePower': '${phaseBDataPoints.elementAtOrNull(2)?.value??'N/A'} W',
'powerFactor': '${phaseBDataPoints.elementAtOrNull(3)?.value??'N/A'}',
},
{
'name': 'Phase C',
'voltage': '${deviceStatus.status.phaseC.dataPoints[0].value / 10} V',
'current': '${deviceStatus.status.phaseC.dataPoints[1].value / 10} A',
'activePower': '${deviceStatus.status.phaseC.dataPoints[2].value} W',
'powerFactor': '${deviceStatus.status.phaseC.dataPoints[3].value}',
'voltage': '${(phaseCDataPoints.elementAtOrNull(0)?.value as num? ?? 0) / 10} V',
'current': '${(phaseCDataPoints.elementAtOrNull(1)?.value as num? ?? 0) / 10} A',
'activePower': '${phaseCDataPoints.elementAtOrNull(2)?.value ?? 'N/A'} W',
'powerFactor': '${phaseCDataPoints.elementAtOrNull(3)?.value ?? 'N/A'}',
},
];
emit(GetDeviceStatus());
@@ -785,7 +787,7 @@ class SmartPowerBloc extends Bloc<SmartPowerEvent, SmartPowerState> {
void selectDateRange() async {
DateTime startDate = dateTime!;
DateTime endDate = DateTime(startDate.year, startDate.month + 1, 1)
.subtract(Duration(days: 1));
.subtract(const Duration(days: 1));
String formattedEndDate = DateFormat('dd/MM/yyyy').format(endDate);
endChartDate = ' - $formattedEndDate';
}
@@ -12,9 +12,9 @@ class PowerClampModel {
factory PowerClampModel.fromJson(Map<String, dynamic> json) {
return PowerClampModel(
productUuid: json['productUuid'],
productType: json['productType'],
status: PowerStatus.fromJson(json['status']),
productUuid: json['productUuid'] as String? ?? '',
productType: json['productType'] as String? ?? '',
status: PowerStatus.fromJson(json['status'] as Map<String, dynamic>? ?? {}),
);
}
@@ -26,7 +26,7 @@ class PowerClampModel {
return PowerClampModel(
productUuid: productUuid ?? this.productUuid,
productType: productType ?? this.productType,
status: statusPower ?? this.status,
status: statusPower ?? status,
);
}
}
@@ -46,12 +46,10 @@ class PowerStatus {
factory PowerStatus.fromJson(Map<String, dynamic> json) {
return PowerStatus(
phaseA: Phase.fromJson(json['phaseA']),
phaseB: Phase.fromJson(json['phaseB']),
phaseC: Phase.fromJson(json['phaseC']),
general: Phase.fromJson(json['general']
// List<DataPoint>.from(
// json['general'].map((x) => DataPoint.fromJson(x))),
phaseA: Phase.fromJson(json['phaseA']as List<dynamic>? ?? []),
phaseB: Phase.fromJson(json['phaseB']as List<dynamic>? ?? []),
phaseC: Phase.fromJson(json['phaseC']as List<dynamic>? ?? []),
general: Phase.fromJson(json['general']as List<dynamic>? ?? []
));
}
}
@@ -69,30 +67,30 @@ class Phase {
}
class DataPoint {
dynamic code;
dynamic customName;
dynamic dpId;
dynamic time;
dynamic type;
dynamic value;
final String? code;
final String? customName;
final int? dpId;
final int? time;
final String? type;
final dynamic value;
DataPoint({
required this.code,
required this.customName,
required this.dpId,
required this.time,
required this.type,
required this.value,
this.code,
this.customName,
this.dpId,
this.time,
this.type,
this.value,
});
factory DataPoint.fromJson(Map<String, dynamic> json) {
return DataPoint(
code: json['code'],
customName: json['customName'],
dpId: json['dpId'],
time: json['time'],
type: json['type'],
value: json['value'],
code: json['code'] as String?,
customName: json['customName'] as String?,
dpId: json['dpId'] as int?,
time: json['time'] as int?,
type: json['type'] as String?,
value: json['value'] as dynamic,
);
}
}
@@ -1,5 +1,5 @@
import 'package:flutter/material.dart';
import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class EnergyConsumptionPage extends StatefulWidget {
@@ -10,7 +10,8 @@ class EnergyConsumptionPage extends StatefulWidget {
final Widget widget;
final Function()? onTap;
EnergyConsumptionPage({
const EnergyConsumptionPage({
super.key,
required this.chartData,
required this.totalConsumption,
required this.date,
@@ -91,11 +92,12 @@ class _EnergyConsumptionPageState extends State<EnergyConsumptionPage> {
],
),
Column(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsets.only(top: 10),
child: SizedBox(
height: MediaQuery.of(context).size.height * 0.11,
height: MediaQuery.sizeOf(context).height * 0.09,
child: LineChart(
LineChartData(
lineTouchData: LineTouchData(
@@ -151,7 +153,7 @@ class _EnergyConsumptionPageState extends State<EnergyConsumptionPage> {
child: RotatedBox(
quarterTurns: -1,
child: Text(_chartData[index].time,
style: TextStyle(fontSize: 10)),
style: const TextStyle(fontSize: 10)),
),
);
}
@@ -190,8 +192,8 @@ class _EnergyConsumptionPageState extends State<EnergyConsumptionPage> {
spots: _chartData
.asMap()
.entries
.map((entry) => FlSpot(entry.key.toDouble(),
entry.value.consumption))
.map((entry) => FlSpot(
entry.key.toDouble(), entry.value.consumption))
.toList(),
isCurved: true,
color: ColorsManager.primaryColor.withOpacity(0.6),
@@ -218,7 +220,7 @@ class _EnergyConsumptionPageState extends State<EnergyConsumptionPage> {
borderData: FlBorderData(
show: false,
border: Border.all(
color: Color(0xff023DFE).withOpacity(0.7),
color: const Color(0xff023DFE).withOpacity(0.7),
width: 10,
),
),
@@ -253,11 +255,9 @@ class _EnergyConsumptionPageState extends State<EnergyConsumptionPage> {
child: InkWell(
onTap: widget.onTap,
child: Center(
child: SizedBox(
child: Padding(
padding: const EdgeInsets.all(5),
child: Text(widget.date),
),
child: Padding(
padding: const EdgeInsets.all(5),
child: Text(widget.date),
),
),
),
@@ -12,8 +12,7 @@ import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
//Smart Power Clamp
class SmartPowerDeviceControl extends StatelessWidget
with HelperResponsiveLayout {
class SmartPowerDeviceControl extends StatelessWidget with HelperResponsiveLayout {
final String deviceId;
const SmartPowerDeviceControl({super.key, required this.deviceId});
@@ -25,27 +24,27 @@ class SmartPowerDeviceControl extends StatelessWidget
..add(SmartPowerFetchDeviceEvent(deviceId)),
child: BlocBuilder<SmartPowerBloc, SmartPowerState>(
builder: (context, state) {
final _blocProvider = BlocProvider.of<SmartPowerBloc>(context);
final blocProvider = BlocProvider.of<SmartPowerBloc>(context);
if (state is SmartPowerLoading) {
return const Center(child: CircularProgressIndicator());
} else if (state is FakeState) {
return _buildStatusControls(
currentPage: _blocProvider.currentPage,
currentPage: blocProvider.currentPage,
context: context,
blocProvider: _blocProvider,
blocProvider: blocProvider,
);
} else if (state is GetDeviceStatus) {
return _buildStatusControls(
currentPage: _blocProvider.currentPage,
currentPage: blocProvider.currentPage,
context: context,
blocProvider: _blocProvider,
blocProvider: blocProvider,
);
} else if (state is FilterRecordsState) {
return _buildStatusControls(
currentPage: _blocProvider.currentPage,
currentPage: blocProvider.currentPage,
context: context,
blocProvider: _blocProvider,
blocProvider: blocProvider,
);
}
return const Center(child: CircularProgressIndicator());
@@ -60,7 +59,7 @@ class SmartPowerDeviceControl extends StatelessWidget
required SmartPowerBloc blocProvider,
required int currentPage,
}) {
PageController _pageController = PageController(initialPage: currentPage);
PageController pageController = PageController(initialPage: currentPage);
return Container(
padding: const EdgeInsets.symmetric(horizontal: 50),
child: DeviceControlsContainer(
@@ -85,25 +84,31 @@ class SmartPowerDeviceControl extends StatelessWidget
PowerClampInfoCard(
iconPath: Assets.powerActiveIcon,
title: 'Active',
value: blocProvider
.deviceStatus.status.general.dataPoints[2].value
.toString(),
value: blocProvider.deviceStatus.status.general.dataPoints
.elementAtOrNull(2)
?.value
.toString() ??
'',
unit: '',
),
PowerClampInfoCard(
iconPath: Assets.voltMeterIcon,
title: 'Current',
value: blocProvider
.deviceStatus.status.general.dataPoints[1].value
.toString(),
value: blocProvider.deviceStatus.status.general.dataPoints
.elementAtOrNull(1)
?.value
.toString() ??
'',
unit: ' A',
),
PowerClampInfoCard(
iconPath: Assets.frequencyIcon,
title: 'Frequency',
value: blocProvider
.deviceStatus.status.general.dataPoints[4].value
.toString(),
value: blocProvider.deviceStatus.status.general.dataPoints
.elementAtOrNull(4)
?.value
.toString() ??
'',
unit: ' Hz',
),
],
@@ -142,7 +147,7 @@ class SmartPowerDeviceControl extends StatelessWidget
icon: const Icon(Icons.arrow_left),
onPressed: () {
blocProvider.add(SmartPowerArrowPressedEvent(-1));
_pageController.previousPage(
pageController.previousPage(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
);
@@ -162,7 +167,7 @@ class SmartPowerDeviceControl extends StatelessWidget
icon: const Icon(Icons.arrow_right),
onPressed: () {
blocProvider.add(SmartPowerArrowPressedEvent(1));
_pageController.nextPage(
pageController.nextPage(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
);
@@ -177,7 +182,7 @@ class SmartPowerDeviceControl extends StatelessWidget
Expanded(
flex: 2,
child: PageView(
controller: _pageController,
controller: pageController,
onPageChanged: (int page) {
blocProvider.add(SmartPowerPageChangedEvent(page));
},
@@ -190,8 +195,8 @@ class SmartPowerDeviceControl extends StatelessWidget
blocProvider.add(SelectDateEvent(context: context));
blocProvider.add(FilterRecordsByDateEvent(
selectedDate: blocProvider.dateTime!,
viewType: blocProvider
.views[blocProvider.currentIndex]));
viewType:
blocProvider.views[blocProvider.currentIndex]));
},
widget: blocProvider.dateSwitcher(),
chartData: blocProvider.energyDataList.isNotEmpty
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/device_managment/shared/device_controls_container.dart';
import 'package:syncrow_web/pages/device_managment/shared/increament_decreament.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
class PresenceUpdateData extends StatefulWidget {
const PresenceUpdateData({
@@ -13,6 +14,7 @@ class PresenceUpdateData extends StatefulWidget {
required this.maxValue,
required this.steps,
this.description,
this.valuesPercision = 0,
});
final String title;
@@ -22,6 +24,7 @@ class PresenceUpdateData extends StatefulWidget {
final double steps;
final Function action;
final String? description;
final int valuesPercision;
@override
State<PresenceUpdateData> createState() => _CurrentTempState();
@@ -45,7 +48,7 @@ class _CurrentTempState extends State<PresenceUpdateData> {
}
void _onValueChanged(double newValue) {
widget.action(newValue.toInt());
widget.action(newValue);
}
@override
@@ -62,11 +65,14 @@ class _CurrentTempState extends State<PresenceUpdateData> {
children: [
Text(
widget.title,
style: Theme.of(context).textTheme.bodySmall!.copyWith(
color: ColorsManager.blackColor, fontWeight: FontWeight.w400, fontSize: 10),
style: context.textTheme.bodySmall?.copyWith(
color: ColorsManager.blackColor,
fontWeight: FontWeight.w400,
fontSize: 10,
),
),
IncrementDecrementWidget(
value: widget.value.toString(),
value: widget.value.toStringAsFixed(widget.valuesPercision),
description: widget.description ?? '',
descriptionColor: ColorsManager.blackColor,
onIncrement: () {
@@ -84,6 +84,16 @@ class _PresenceUpdateDataState extends State<PresenceNoBodyTime> {
}
}
@override
void didUpdateWidget(PresenceNoBodyTime oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.value != widget.value) {
setState(() {
_currentValue = widget.value;
});
}
}
@override
Widget build(BuildContext context) {
return DeviceControlsContainer(
@@ -62,9 +62,6 @@ class ToggleWidget extends StatelessWidget {
)),
if (showToggle)
Container(
height: 20,
width: 35,
padding: const EdgeInsets.only(right: 16, top: 10),
child: CupertinoSwitch(
value: value,
activeColor: ColorsManager.dialogBlueTitle,
@@ -13,7 +13,8 @@ import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_la
import '../models/sos_status_model.dart';
class SosDeviceControlsView extends StatelessWidget with HelperResponsiveLayout {
class SosDeviceControlsView extends StatelessWidget
with HelperResponsiveLayout {
const SosDeviceControlsView({
super.key,
required this.device,
@@ -24,7 +25,8 @@ class SosDeviceControlsView extends StatelessWidget with HelperResponsiveLayout
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => SosDeviceBloc()..add(GetDeviceStatus(device.uuid!)),
create: (context) =>
SosDeviceBloc()..add(GetDeviceStatus(device.uuid!)),
child: BlocBuilder<SosDeviceBloc, SosDeviceState>(
builder: (context, state) {
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 isLarge = isLargeScreenSize(context);
final isMedium = isMediumScreenSize(context);
@@ -85,7 +88,7 @@ class SosDeviceControlsView extends StatelessWidget with HelperResponsiveLayout
IconNameStatusContainer(
isFullIcon: false,
name: deviceStatus.sosStatus == 'sos' ? 'SOS' : 'Normal',
icon: deviceStatus.sosStatus == 'sos' ? Assets.sos : Assets.sosNormal,
icon: deviceStatus.sosStatus == 'sos' ? Assets.sosNormal : Assets.sos,
onTap: () {},
status: false,
textColor: ColorsManager.blackColor,
@@ -21,6 +21,7 @@ class WallSensorBloc extends Bloc<WallSensorEvent, WallSensorState> {
on<ShowDescriptionEvent>(_showDescription);
on<BackToGridViewEvent>(_backToGridView);
on<WallSensorFactoryResetEvent>(_onFactoryReset);
on<WallSensorRealtimeUpdateEvent>(_onRealtimeUpdate);
}
void _fetchWallSensorStatus(
@@ -30,7 +31,7 @@ class WallSensorBloc extends Bloc<WallSensorEvent, WallSensorState> {
var response = await DevicesManagementApi().getDeviceStatus(deviceId);
deviceStatus = WallSensorModel.fromJson(response.status);
emit(WallSensorUpdateState(wallSensorModel: deviceStatus));
_listenToChanges(emit, deviceId);
_listenToChanges(deviceId);
} catch (e) {
emit(WallSensorFailedState(error: e.toString()));
return;
@@ -52,28 +53,27 @@ class WallSensorBloc extends Bloc<WallSensorEvent, WallSensorState> {
}
}
_listenToChanges(Emitter<WallSensorState> emit, deviceId) {
try {
DatabaseReference ref =
FirebaseDatabase.instance.ref('device-status/$deviceId');
Stream<DatabaseEvent> stream = ref.onValue;
void _listenToChanges(String deviceId) {
DatabaseReference ref =
FirebaseDatabase.instance.ref('device-status/$deviceId');
ref.onValue.listen((DatabaseEvent event) {
final data = event.snapshot.value as Map<dynamic, dynamic>?;
if (data == null) return;
stream.listen((DatabaseEvent event) {
Map<dynamic, dynamic> usersMap =
event.snapshot.value as Map<dynamic, dynamic>;
List<Status> statusList = [];
final statusList = (data['status'] as List?)
?.map((e) => Status(code: e['code'], value: e['value']))
.toList();
usersMap['status'].forEach((element) {
statusList
.add(Status(code: element['code'], value: element['value']));
});
deviceStatus = WallSensorModel.fromJson(statusList);
emit(WallSensorLoadingNewSate(wallSensorModel: deviceStatus));
});
} catch (_) {}
if (statusList != null) {
final updatedDeviceStatus = WallSensorModel.fromJson(statusList);
if (!isClosed) {
add(WallSensorRealtimeUpdateEvent(updatedDeviceStatus));
}
}
});
}
void _changeValue(
WallSensorChangeValueEvent event, Emitter<WallSensorState> emit) async {
emit(WallSensorLoadingNewSate(wallSensorModel: deviceStatus));
@@ -195,4 +195,12 @@ class WallSensorBloc extends Bloc<WallSensorEvent, WallSensorState> {
emit(WallSensorFailedState(error: e.toString()));
}
}
void _onRealtimeUpdate(
WallSensorRealtimeUpdateEvent event,
Emitter<WallSensorState> emit,
) {
deviceStatus = event.deviceStatus;
emit(WallSensorUpdateState(wallSensorModel: deviceStatus));
}
}
@@ -1,5 +1,6 @@
import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart';
import 'package:syncrow_web/pages/device_managment/wall_sensor/model/wall_sensor_model.dart';
abstract class WallSensorEvent extends Equatable {
const WallSensorEvent();
@@ -70,3 +71,8 @@ class WallSensorFactoryResetEvent extends WallSensorEvent {
required this.factoryReset,
});
}
class WallSensorRealtimeUpdateEvent extends WallSensorEvent {
final WallSensorModel deviceStatus;
const WallSensorRealtimeUpdateEvent(this.deviceStatus);
}
@@ -95,6 +95,77 @@ class CountdownInchingView extends StatelessWidget {
);
}
Row _hourMinutesSecondWheel(
BuildContext context, WaterHeaterDeviceStatusLoaded state) {
final isCountDown =
state.scheduleMode?.name == ScheduleModes.countdown.name;
late bool isActive;
if (isCountDown &&
state.countdownRemaining != null &&
state.isCountdownActive == true) {
isActive = true;
} else if (!isCountDown &&
state.countdownRemaining != null &&
state.isInchingActive == true) {
isActive = true;
} else {
isActive = false;
}
return Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
_buildPickerColumn(
context,
'h',
isCountDown
? (state.countdownHours ?? 0)
: (state.inchingHours ?? 0),
24, (value) {
context.read<WaterHeaterBloc>().add(UpdateScheduleEvent(
scheduleMode: state.scheduleMode ?? ScheduleModes.countdown,
hours: value,
minutes: isCountDown
? (state.countdownMinutes ?? 0)
: (state.inchingMinutes ?? 0),
));
}, isActive: isActive),
const SizedBox(width: 10),
_buildPickerColumn(
context,
'm',
isCountDown
? (state.countdownMinutes ?? 0)
: (state.inchingMinutes ?? 0),
60, (value) {
context.read<WaterHeaterBloc>().add(UpdateScheduleEvent(
scheduleMode: state.scheduleMode ?? ScheduleModes.countdown,
hours: isCountDown
? (state.countdownHours ?? 0)
: (state.inchingHours ?? 0),
minutes: value,
));
}, isActive: isActive),
const SizedBox(width: 10),
_buildPickerColumn(
context,
'S',
isCountDown
? (state.countdownMinutes ?? 0)
: (state.inchingMinutes ?? 0),
60, (value) {
context.read<WaterHeaterBloc>().add(UpdateScheduleEvent(
scheduleMode: state.scheduleMode ?? ScheduleModes.countdown,
hours: isCountDown
? (state.countdownHours ?? 0)
: (state.inchingHours ?? 0),
minutes: value,
));
}, isActive: isActive),
],
);
}
Widget _buildPickerColumn(
BuildContext context,
String label,
@@ -0,0 +1,39 @@
import 'dart:convert';
class AutomationStatusUpdate {
final String spaceUuid;
final bool isEnable;
AutomationStatusUpdate({
required this.spaceUuid,
required this.isEnable,
});
factory AutomationStatusUpdate.fromRawJson(String str) =>
AutomationStatusUpdate.fromJson(json.decode(str));
String toRawJson() => json.encode(toJson());
factory AutomationStatusUpdate.fromJson(Map<String, dynamic> json) =>
AutomationStatusUpdate(
spaceUuid: json["spaceUuid"],
isEnable: json["isEnable"],
);
Map<String, dynamic> toJson() => {
"spaceUuid": spaceUuid,
"isEnable": isEnable,
};
factory AutomationStatusUpdate.fromMap(Map<String, dynamic> map) =>
AutomationStatusUpdate(
spaceUuid: map["spaceUuid"],
isEnable: map["isEnable"],
);
Map<String, dynamic> toMap() => {
"spaceUuid": spaceUuid,
"isEnable": isEnable,
};
}
@@ -0,0 +1,67 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
import 'package:syncrow_web/pages/routines/bloc/create_routine_bloc/create_routine_event.dart';
import 'package:syncrow_web/pages/routines/bloc/create_routine_bloc/create_routine_state.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
import 'package:syncrow_web/services/space_mana_api.dart';
class CreateRoutineBloc extends Bloc<CreateRoutineEvent, CreateRoutineState> {
CreateRoutineBloc() : super(const CreateRoutineInitial()) {
on<SpaceOnlyWithDevicesEvent>(_fetchSpaceOnlyWithDevices);
on<SaveCommunityIdAndSpaceIdEvent>(saveSpaceIdCommunityId);
on<ResetSelectedEvent>(resetSelected);
on<FetchCommunityEvent>(_fetchCommunity);
}
String selectedSpaceId = '';
String selectedCommunityId = '';
List<CommunityModel> communities = [];
List<SpaceModel> spacesOnlyWithDevices = [];
Future<void> _fetchSpaceOnlyWithDevices(
SpaceOnlyWithDevicesEvent event, Emitter<CreateRoutineState> emit) async {
emit(const SpaceWithDeviceLoadingState());
try {
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
spacesOnlyWithDevices = await CommunitySpaceManagementApi()
.getSpaceOnlyWithDevices(
communityId: event.communityID, projectId: projectUuid);
emit(SpaceWithDeviceLoadedState(spacesOnlyWithDevices));
} catch (e) {
emit(SpaceTreeErrorState('Error loading spaces: $e'));
}
}
saveSpaceIdCommunityId(
SaveCommunityIdAndSpaceIdEvent event, Emitter<CreateRoutineState> emit) {
emit(const SpaceWithDeviceLoadingState());
selectedSpaceId = event.spaceID!;
selectedCommunityId = event.communityID!;
emit(const SelectedState());
}
resetSelected(ResetSelectedEvent event, Emitter<CreateRoutineState> emit) {
emit(const SpaceWithDeviceLoadingState());
selectedSpaceId = '';
selectedCommunityId = '';
emit(const ResetSelectedState());
}
Future<void> _fetchCommunity(
FetchCommunityEvent event, Emitter<CreateRoutineState> emit) async {
emit(const CommunitiesLoadingState());
try {
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
communities =
await CommunitySpaceManagementApi().fetchCommunities(projectUuid);
emit(const CommunityLoadedState());
} catch (e) {
emit(SpaceTreeErrorState('Error loading communities $e'));
}
}
}
@@ -0,0 +1,51 @@
import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
abstract class CreateRoutineEvent extends Equatable {
const CreateRoutineEvent();
@override
List<Object?> get props => [];
}
class AddToIfContainer extends CreateRoutineEvent {
final SpaceModel spaceModel;
const AddToIfContainer(this.spaceModel);
@override
List<Object?> get props => [spaceModel];
}
class SpaceOnlyWithDevicesEvent extends CreateRoutineEvent {
final String communityID;
const SpaceOnlyWithDevicesEvent(this.communityID);
@override
List<Object> get props => [communityID];
}
class SaveCommunityIdAndSpaceIdEvent extends CreateRoutineEvent {
final String? communityID;
final String? spaceID;
const SaveCommunityIdAndSpaceIdEvent({this.communityID, this.spaceID});
@override
List<Object> get props => [communityID!, spaceID!];
}
class ResetSelectedEvent extends CreateRoutineEvent {
const ResetSelectedEvent();
@override
List<Object> get props => [];
}
class FetchCommunityEvent extends CreateRoutineEvent {
const FetchCommunityEvent();
@override
List<Object> get props => [];
}
@@ -0,0 +1,53 @@
import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
abstract class CreateRoutineState extends Equatable {
const CreateRoutineState();
@override
List<Object?> get props => [];
}
class CreateRoutineInitial extends CreateRoutineState {
const CreateRoutineInitial();
}
class SpaceWithDeviceLoadingState extends CreateRoutineState {
const SpaceWithDeviceLoadingState();
}
class SpaceWithDeviceLoadedState extends CreateRoutineState {
final List<SpaceModel> spaces;
const SpaceWithDeviceLoadedState(this.spaces);
@override
List<Object?> get props => [spaces];
}
class SpaceTreeErrorState extends CreateRoutineState {
final String errorMessage;
const SpaceTreeErrorState(this.errorMessage);
@override
List<Object?> get props => [errorMessage];
}
class SelectedState extends CreateRoutineState {
const SelectedState();
}
class ResetSelectedState extends CreateRoutineState {
const ResetSelectedState();
}
class CommunityLoadedState extends CreateRoutineState {
const CommunityLoadedState();
}
class CommunitiesLoadingState extends CreateRoutineState {
const CommunitiesLoadingState();
}
@@ -30,6 +30,7 @@ class FunctionBloc extends Bloc<FunctionBlocEvent, FunctionBlocState> {
condition: event.functionData.condition ?? existingData.condition,
);
} else {
functions.clear();
functions.add(event.functionData);
}
@@ -1,11 +1,12 @@
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
import 'package:syncrow_web/pages/routines/bloc/automation_scene_trigger_bloc/automation_status_update.dart';
import 'package:syncrow_web/pages/routines/bloc/create_routine_bloc/create_routine_bloc.dart';
import 'package:syncrow_web/pages/routines/models/create_scene_and_autoamtion/create_automation_model.dart';
import 'package:syncrow_web/pages/routines/models/create_scene_and_autoamtion/create_scene_model.dart';
import 'package:syncrow_web/pages/routines/models/delay/delay_fucntions.dart';
@@ -16,9 +17,6 @@ import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
import 'package:syncrow_web/services/devices_mang_api.dart';
import 'package:syncrow_web/services/routines_api.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/constants/strings_manager.dart';
import 'package:syncrow_web/utils/constants/temp_const.dart';
import 'package:syncrow_web/utils/helpers/shared_preferences_helper.dart';
import 'package:syncrow_web/utils/navigation_service.dart';
import 'package:syncrow_web/utils/snack_bar.dart';
import 'package:uuid/uuid.dart';
@@ -55,7 +53,11 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
on<TriggerSwitchTabsEvent>(_triggerSwitchTabsEvent);
on<CreateNewRoutineViewEvent>(_createNewRoutineViewEvent);
on<ResetErrorMessage>(_resetErrorMessage);
on<SceneTrigger>(_onSceneTrigger);
on<UpdateAutomationStatus>(_onUpdateAutomationStatus);
}
String selectedSpaceId = '';
String selectedCommunityId = '';
FutureOr<void> _triggerSwitchTabsEvent(
TriggerSwitchTabsEvent event,
@@ -87,8 +89,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
final updatedIfItems = List<Map<String, dynamic>>.from(state.ifItems);
// Find the index of the item in teh current itemsList
int index =
updatedIfItems.indexWhere((map) => map['uniqueCustomId'] == event.item['uniqueCustomId']);
int index = updatedIfItems
.indexWhere((map) => map['uniqueCustomId'] == event.item['uniqueCustomId']);
// Replace the map if the index is valid
if (index != -1) {
updatedIfItems[index] = event.item;
@@ -97,9 +99,11 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
}
if (event.isTabToRun) {
emit(state.copyWith(ifItems: updatedIfItems, isTabToRun: true, isAutomation: false));
emit(state.copyWith(
ifItems: updatedIfItems, isTabToRun: true, isAutomation: false));
} else {
emit(state.copyWith(ifItems: updatedIfItems, isTabToRun: false, isAutomation: true));
emit(state.copyWith(
ifItems: updatedIfItems, isTabToRun: false, isAutomation: true));
}
}
@@ -107,8 +111,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
final currentItems = List<Map<String, dynamic>>.from(state.thenItems);
// Find the index of the item in teh current itemsList
int index =
currentItems.indexWhere((map) => map['uniqueCustomId'] == event.item['uniqueCustomId']);
int index = currentItems
.indexWhere((map) => map['uniqueCustomId'] == event.item['uniqueCustomId']);
// Replace the map if the index is valid
if (index != -1) {
currentItems[index] = event.item;
@@ -119,39 +123,43 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
emit(state.copyWith(thenItems: currentItems));
}
void _onAddFunctionsToRoutine(AddFunctionToRoutine event, Emitter<RoutineState> emit) {
void _onAddFunctionsToRoutine(
AddFunctionToRoutine event, Emitter<RoutineState> emit) {
try {
if (event.functions.isEmpty) return;
List<DeviceFunctionData> selectedFunction = List<DeviceFunctionData>.from(event.functions);
// List<DeviceFunctionData> selectedFunction = List<DeviceFunctionData>.from(event.functions);
Map<String, List<DeviceFunctionData>> currentSelectedFunctions =
Map<String, List<DeviceFunctionData>>.from(state.selectedFunctions);
if (currentSelectedFunctions.containsKey(event.uniqueCustomId)) {
List<DeviceFunctionData> currentFunctions =
List<DeviceFunctionData>.from(currentSelectedFunctions[event.uniqueCustomId] ?? []);
List<String> functionCode = [];
for (int i = 0; i < selectedFunction.length; 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);
}
}
}
}
// if (currentSelectedFunctions.containsKey(event.uniqueCustomId)) {
// List<DeviceFunctionData> currentFunctions =
// List<DeviceFunctionData>.from(currentSelectedFunctions[event.uniqueCustomId] ?? []);
for (int i = 0; i < functionCode.length; i++) {
selectedFunction.removeWhere((code) => code.functionCode == functionCode[i]);
}
// List<String> functionCode = [];
// for (int i = 0; i < selectedFunction.length; 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] = List.from(currentFunctions)
..addAll(selectedFunction);
} else {
currentSelectedFunctions[event.uniqueCustomId] = List.from(event.functions);
}
// for (int i = 0; i < functionCode.length; i++) {
// selectedFunction.removeWhere((code) => code.functionCode == functionCode[i]);
// }
// 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));
} catch (e) {
@@ -163,15 +171,23 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
emit(state.copyWith(isLoading: true, errorMessage: null));
List<ScenesModel> scenes = [];
try {
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
BuildContext context = NavigationService.navigatorKey.currentContext!;
var spaceBloc = context.read<SpaceTreeBloc>();
for (var communityId in spaceBloc.state.selectedCommunities) {
List<String> spacesList = spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? [];
for (var spaceId in spacesList) {
scenes.addAll(await SceneApi.getScenes(spaceId, communityId, projectUuid));
var createRoutineBloc = context.read<CreateRoutineBloc>();
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
if (createRoutineBloc.selectedSpaceId == '' &&
createRoutineBloc.selectedCommunityId == '') {
var spaceBloc = context.read<SpaceTreeBloc>();
for (var communityId in spaceBloc.state.selectedCommunities) {
List<String> spacesList =
spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? [];
for (var spaceId in spacesList) {
scenes
.addAll(await SceneApi.getScenes(spaceId, communityId, projectUuid));
}
}
} else {
scenes.addAll(await SceneApi.getScenes(createRoutineBloc.selectedSpaceId,
createRoutineBloc.selectedCommunityId, projectUuid));
}
emit(state.copyWith(
@@ -188,18 +204,31 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
}
}
Future<void> _onLoadAutomation(LoadAutomation event, Emitter<RoutineState> emit) async {
Future<void> _onLoadAutomation(
LoadAutomation event, Emitter<RoutineState> emit) async {
emit(state.copyWith(isLoading: true, errorMessage: null));
List<ScenesModel> automations = [];
final projectId = await ProjectManager.getProjectUUID() ?? '';
BuildContext context = NavigationService.navigatorKey.currentContext!;
var createRoutineBloc = context.read<CreateRoutineBloc>();
try {
BuildContext context = NavigationService.navigatorKey.currentContext!;
var spaceBloc = context.read<SpaceTreeBloc>();
for (var communityId in spaceBloc.state.selectedCommunities) {
List<String> spacesList = spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? [];
for (var spaceId in spacesList) {
automations.addAll(await SceneApi.getAutomation(spaceId));
if (createRoutineBloc.selectedSpaceId == '' &&
createRoutineBloc.selectedCommunityId == '') {
var spaceBloc = context.read<SpaceTreeBloc>();
for (var communityId in spaceBloc.state.selectedCommunities) {
List<String> spacesList =
spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? [];
for (var spaceId in spacesList) {
automations.addAll(
await SceneApi.getAutomation(spaceId, communityId, projectId));
}
}
} else {
automations.addAll(await SceneApi.getAutomation(
createRoutineBloc.selectedSpaceId,
createRoutineBloc.selectedCommunityId,
projectId));
}
emit(state.copyWith(
automations: automations,
@@ -215,14 +244,16 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
}
}
FutureOr<void> _onSearchRoutines(SearchRoutines event, Emitter<RoutineState> emit) async {
FutureOr<void> _onSearchRoutines(
SearchRoutines event, Emitter<RoutineState> emit) async {
emit(state.copyWith(isLoading: true, errorMessage: null));
await Future.delayed(const Duration(seconds: 1));
emit(state.copyWith(isLoading: false, errorMessage: null));
emit(state.copyWith(searchText: event.query));
}
FutureOr<void> _onAddSelectedIcon(AddSelectedIcon event, Emitter<RoutineState> emit) {
FutureOr<void> _onAddSelectedIcon(
AddSelectedIcon event, Emitter<RoutineState> emit) {
emit(state.copyWith(selectedIcon: event.icon));
}
@@ -236,7 +267,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
return actions.last['deviceId'] == 'delay';
}
Future<void> _onCreateScene(CreateSceneEvent event, Emitter<RoutineState> emit) async {
Future<void> _onCreateScene(
CreateSceneEvent event, Emitter<RoutineState> emit) async {
try {
// Check if first action is delay
// if (_isFirstActionDelay(state.thenItems)) {
@@ -295,10 +327,10 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
}).toList();
BuildContext context = NavigationService.navigatorKey.currentContext!;
var spaceBloc = context.read<SpaceTreeBloc>();
var createRoutineBloc = context.read<CreateRoutineBloc>();
final createSceneModel = CreateSceneModel(
spaceUuid: spaceBloc.state.selectedSpaces[0],
spaceUuid: createRoutineBloc.selectedSpaceId,
iconId: state.selectedIcon ?? '',
showInDevice: true,
sceneName: state.routineName ?? '',
@@ -325,8 +357,10 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
}
}
Future<void> _onCreateAutomation(CreateAutomationEvent event, Emitter<RoutineState> emit) async {
Future<void> _onCreateAutomation(
CreateAutomationEvent event, Emitter<RoutineState> emit) async {
try {
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
if (state.routineName == null || state.routineName!.isEmpty) {
emit(state.copyWith(
errorMessage: 'Automation name is required',
@@ -422,10 +456,10 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
});
}).toList();
BuildContext context = NavigationService.navigatorKey.currentContext!;
var spaceBloc = context.read<SpaceTreeBloc>();
var createRoutineBloc = context.read<CreateRoutineBloc>();
final createAutomationModel = CreateAutomationModel(
spaceUuid: spaceBloc.state.selectedSpaces[0],
spaceUuid: createRoutineBloc.selectedSpaceId,
automationName: state.routineName ?? '',
decisionExpr: state.selectedAutomationOperator,
effectiveTime: EffectiveTime(
@@ -437,7 +471,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
actions: actions,
);
final result = await SceneApi.createAutomation(createAutomationModel);
final result =
await SceneApi.createAutomation(createAutomationModel, projectUuid);
if (result['success']) {
add(ResetRoutineState());
add(const LoadAutomation());
@@ -458,17 +493,21 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
}
}
FutureOr<void> _onRemoveDragCard(RemoveDragCard event, Emitter<RoutineState> emit) {
FutureOr<void> _onRemoveDragCard(
RemoveDragCard event, Emitter<RoutineState> emit) {
if (event.isFromThen) {
final thenItems = List<Map<String, dynamic>>.from(state.thenItems);
final selectedFunctions = Map<String, List<DeviceFunctionData>>.from(state.selectedFunctions);
final selectedFunctions =
Map<String, List<DeviceFunctionData>>.from(state.selectedFunctions);
thenItems.removeAt(event.index);
selectedFunctions.remove(event.key);
emit(state.copyWith(thenItems: thenItems, selectedFunctions: selectedFunctions));
emit(state.copyWith(
thenItems: thenItems, selectedFunctions: selectedFunctions));
} else {
final ifItems = List<Map<String, dynamic>>.from(state.ifItems);
final selectedFunctions = Map<String, List<DeviceFunctionData>>.from(state.selectedFunctions);
final selectedFunctions =
Map<String, List<DeviceFunctionData>>.from(state.selectedFunctions);
ifItems.removeAt(event.index);
selectedFunctions.remove(event.key);
@@ -491,131 +530,141 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
));
}
FutureOr<void> _onEffectiveTimeEvent(EffectiveTimePeriodEvent event, Emitter<RoutineState> emit) {
FutureOr<void> _onEffectiveTimeEvent(
EffectiveTimePeriodEvent event, Emitter<RoutineState> emit) {
emit(state.copyWith(effectiveTime: event.effectiveTime));
}
FutureOr<void> _onSetRoutineName(SetRoutineName event, Emitter<RoutineState> emit) {
FutureOr<void> _onSetRoutineName(
SetRoutineName event, Emitter<RoutineState> emit) {
emit(state.copyWith(
routineName: event.name,
));
}
(List<Map<String, dynamic>>, List<Map<String, dynamic>>, Map<String, List<DeviceFunctionData>>)
_createCardData(
List<RoutineAction> actions,
List<RoutineCondition>? conditions,
Map<String, List<DeviceFunctionData>> currentFunctions,
bool isTabToRun,
) {
final ifItems = isTabToRun
? [
{
'entityId': 'tab_to_run',
'uniqueCustomId': const Uuid().v4(),
'deviceId': 'tab_to_run',
'title': 'Tab to run',
'productType': 'tab_to_run',
'imagePath': Assets.tabToRun,
}
]
: conditions?.map((condition) {
final matchingDevice = state.devices.firstWhere(
(device) => device.uuid == condition.entityId,
orElse: () => AllDevicesModel(
uuid: condition.entityId,
name: condition.entityId,
productType: condition.entityType,
),
);
// (
// List<Map<String, dynamic>>,
// List<Map<String, dynamic>>,
// Map<String, List<DeviceFunctionData>>
// ) _createCardData(
// List<RoutineAction> actions,
// List<RoutineCondition>? conditions,
// Map<String, List<DeviceFunctionData>> currentFunctions,
// bool isTabToRun,
// ) {
// final ifItems = isTabToRun
// ? [
// {
// 'entityId': 'tab_to_run',
// 'uniqueCustomId': const Uuid().v4(),
// 'deviceId': 'tab_to_run',
// 'title': 'Tab to run',
// 'productType': 'tab_to_run',
// 'imagePath': Assets.tabToRun,
// }
// ]
// : conditions?.map((condition) {
// final matchingDevice = state.devices.firstWhere(
// (device) => device.uuid == condition.entityId,
// orElse: () => AllDevicesModel(
// uuid: condition.entityId,
// name: condition.entityId,
// productType: condition.entityType,
// ),
// );
final cardData = {
'entityId': condition.entityId,
'uniqueCustomId': const Uuid().v4(),
'deviceId': condition.entityId,
'title': matchingDevice.name ?? condition.entityId,
'productType': condition.entityType,
'imagePath': matchingDevice.getDefaultIcon(condition.entityType),
};
// final cardData = {
// 'entityId': condition.entityId,
// 'uniqueCustomId': const Uuid().v4(),
// 'deviceId': condition.entityId,
// 'title': matchingDevice.name ?? condition.entityId,
// 'productType': condition.entityType,
// 'imagePath':
// matchingDevice.getDefaultIcon(condition.entityType),
// };
final functions = matchingDevice.functions;
// final functions = matchingDevice.functions;
for (var function in functions) {
if (function.code == condition.expr.statusCode) {
currentFunctions[cardData['uniqueCustomId'] ?? ''] = [
DeviceFunctionData(
entityId: condition.entityId,
functionCode: condition.expr.statusCode,
value: condition.expr.statusValue,
operationName: function.operationName,
),
];
break;
}
}
// for (var function in functions) {
// if (function.code == condition.expr.statusCode) {
// currentFunctions[cardData['uniqueCustomId'] ?? ''] = [
// DeviceFunctionData(
// entityId: condition.entityId,
// functionCode: condition.expr.statusCode,
// value: condition.expr.statusValue,
// operationName: function.operationName,
// ),
// ];
// break;
// }
// }
return cardData;
}).toList() ??
[];
// return cardData;
// }).toList() ??
// [];
final thenItems = actions.map((action) {
final matchingDevice = state.devices.firstWhere(
(device) => device.uuid == action.entityId,
orElse: () => AllDevicesModel(
uuid: action.entityId,
name: action.entityId,
productType: action.productType,
),
);
// final thenItems = actions.map((action) {
// final matchingDevice = state.devices.firstWhere(
// (device) => device.uuid == action.entityId,
// orElse: () => AllDevicesModel(
// uuid: action.entityId,
// name: action.entityId,
// productType: action.productType,
// ),
// );
final cardData = {
'entityId': action.entityId,
'uniqueCustomId': const Uuid().v4(),
'deviceId': action.actionExecutor == 'delay' ? 'delay' : action.entityId,
'title': action.actionExecutor == 'delay' ? 'Delay' : (matchingDevice.name ?? 'Device'),
'productType': action.productType,
'imagePath': matchingDevice.getDefaultIcon(action.productType),
};
// final cardData = {
// 'entityId': action.entityId,
// 'uniqueCustomId': const Uuid().v4(),
// 'deviceId':
// action.actionExecutor == 'delay' ? 'delay' : action.entityId,
// 'title': action.actionExecutor == 'delay'
// ? 'Delay'
// : (matchingDevice.name ?? 'Device'),
// 'productType': action.productType,
// 'imagePath': matchingDevice.getDefaultIcon(action.productType),
// };
final functions = matchingDevice.functions;
// final functions = matchingDevice.functions;
if (action.executorProperty != null && action.actionExecutor != 'delay') {
final functionCode = action.executorProperty!.functionCode;
for (var function in functions) {
if (function.code == functionCode) {
currentFunctions[cardData['uniqueCustomId'] ?? ''] = [
DeviceFunctionData(
entityId: action.entityId,
functionCode: functionCode ?? '',
value: action.executorProperty!.functionValue,
operationName: function.operationName,
),
];
break;
}
}
} else if (action.actionExecutor == 'delay') {
final delayFunction = DelayFunction(
deviceId: action.entityId,
deviceName: 'Delay',
);
currentFunctions[cardData['uniqueCustomId'] ?? ''] = [
DeviceFunctionData(
entityId: action.entityId,
functionCode: 'delay',
value: action.executorProperty?.delaySeconds ?? 0,
operationName: delayFunction.operationName,
),
];
}
// if (action.executorProperty != null && action.actionExecutor != 'delay') {
// final functionCode = action.executorProperty!.functionCode;
// for (var function in functions) {
// if (function.code == functionCode) {
// currentFunctions[cardData['uniqueCustomId'] ?? ''] = [
// DeviceFunctionData(
// entityId: action.entityId,
// functionCode: functionCode ?? '',
// value: action.executorProperty!.functionValue,
// operationName: function.operationName,
// ),
// ];
// break;
// }
// }
// } else if (action.actionExecutor == 'delay') {
// final delayFunction = DelayFunction(
// deviceId: action.entityId,
// deviceName: 'Delay',
// );
// currentFunctions[cardData['uniqueCustomId'] ?? ''] = [
// DeviceFunctionData(
// entityId: action.entityId,
// functionCode: 'delay',
// value: action.executorProperty?.delaySeconds ?? 0,
// operationName: delayFunction.operationName,
// ),
// ];
// }
return cardData;
}).toList();
// return cardData;
// }).toList();
return (thenItems, ifItems, currentFunctions);
}
// return (thenItems, ifItems, currentFunctions);
// }
Future<void> _onGetSceneDetails(GetSceneDetails event, Emitter<RoutineState> emit) async {
Future<void> _onGetSceneDetails(
GetSceneDetails event, Emitter<RoutineState> emit) async {
try {
emit(state.copyWith(
isLoading: true,
@@ -658,40 +707,46 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
? '${action.entityId}_automation'
: action.actionExecutor == 'delay'
? '${action.entityId}_delay'
: action.entityId;
: const Uuid().v4();
if (!deviceCards.containsKey(deviceId)) {
deviceCards[deviceId] = {
'entityId': action.entityId,
'deviceId': action.actionExecutor == 'delay' ? 'delay' : action.entityId,
'uniqueCustomId': action.type == 'automation' || action.actionExecutor == 'delay'
? const Uuid().v4()
: action.entityId,
'title': action.actionExecutor == 'delay'
? 'Delay'
: action.type == 'automation'
? action.name ?? 'Automation'
: (matchingDevice?.name ?? 'Device'),
'productType': action.productType,
'functions': matchingDevice?.functions,
'imagePath': action.type == 'automation'
? Assets.automation
: action.actionExecutor == 'delay'
? Assets.delay
: matchingDevice?.getDefaultIcon(action.productType),
'device': matchingDevice,
'name': action.name,
'type': action.type,
};
}
// if (!deviceCards.containsKey(deviceId)) {
deviceCards[deviceId] = {
'entityId': action.entityId,
'deviceId': action.actionExecutor == 'delay' ? 'delay' : action.entityId,
'uniqueCustomId':
action.type == 'automation' || action.actionExecutor == 'delay'
? action.entityId
: const Uuid().v4(),
'title': action.actionExecutor == 'delay'
? 'Delay'
: action.type == 'automation'
? action.name ?? 'Automation'
: (matchingDevice?.name ?? 'Device'),
'productType': action.productType,
'functions': matchingDevice?.functions,
'imagePath': action.type == 'automation'
? Assets.automation
: action.actionExecutor == 'delay'
? Assets.delay
: matchingDevice?.getDefaultIcon(action.productType),
'device': matchingDevice,
'name': action.name,
'type': action.type,
'tag': matchingDevice?.deviceTags?.isNotEmpty ?? false
? matchingDevice?.deviceTags![0].name ?? ''
: '',
'subSpace': matchingDevice?.deviceSubSpace?.subspaceName ?? '',
};
// }
final cardData = deviceCards[deviceId]!;
final uniqueCustomId = cardData['uniqueCustomId'].toString();
if (!updatedFunctions.containsKey(uniqueCustomId)) {
updatedFunctions[uniqueCustomId] = [];
}
if (action.type == 'automation') {
if (!updatedFunctions.containsKey(uniqueCustomId)) {
updatedFunctions[uniqueCustomId] = [];
}
updatedFunctions[uniqueCustomId]!.add(
DeviceFunctionData(
entityId: action.entityId,
@@ -701,13 +756,11 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
),
);
// emit(state.copyWith(automationActionExecutor: action.actionExecutor));
} else if (action.executorProperty != null && action.actionExecutor != 'delay') {
if (!updatedFunctions.containsKey(uniqueCustomId)) {
updatedFunctions[uniqueCustomId] = [];
}
final functions = matchingDevice?.functions;
} else if (action.executorProperty != null &&
action.actionExecutor != 'delay') {
final functions = matchingDevice?.functions ?? [];
final functionCode = action.executorProperty?.functionCode;
for (DeviceFunction function in functions ?? []) {
for (DeviceFunction function in functions) {
if (function.code == functionCode) {
updatedFunctions[uniqueCustomId]!.add(
DeviceFunctionData(
@@ -721,9 +774,6 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
}
}
} else if (action.actionExecutor == 'delay') {
if (!updatedFunctions.containsKey(uniqueCustomId)) {
updatedFunctions[uniqueCustomId] = [];
}
final delayFunction = DelayFunction(
deviceId: action.entityId,
deviceName: 'Delay',
@@ -773,7 +823,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
}
}
FutureOr<void> _onResetRoutineState(ResetRoutineState event, Emitter<RoutineState> emit) {
FutureOr<void> _onResetRoutineState(
ResetRoutineState event, Emitter<RoutineState> emit) {
emit(state.copyWith(
ifItems: [],
thenItems: [],
@@ -799,16 +850,32 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
FutureOr<void> _deleteScene(DeleteScene event, Emitter<RoutineState> emit) async {
try {
final projectId = await ProjectManager.getProjectUUID() ?? '';
emit(state.copyWith(isLoading: true));
BuildContext context = NavigationService.navigatorKey.currentContext!;
var spaceBloc = context.read<SpaceTreeBloc>();
if (state.isTabToRun) {
await SceneApi.deleteScene(
unitUuid: spaceBloc.state.selectedSpaces[0], sceneId: state.sceneId ?? '');
unitUuid: spaceBloc.state.selectedSpaces[0],
sceneId: state.sceneId ?? '');
} else {
await SceneApi.deleteAutomation(
unitUuid: spaceBloc.state.selectedSpaces[0], automationId: state.automationId ?? '');
unitUuid: spaceBloc.state.selectedSpaces[0],
automationId: state.automationId ?? '',
projectId: projectId);
}
// var createRoutineBloc = context.read<CreateRoutineBloc>();
// if (state.isTabToRun) {
// await SceneApi.deleteScene(
// unitUuid: createRoutineBloc.selectedSpaceId,
// sceneId: state.sceneId ?? '');
// } else {
// await SceneApi.deleteAutomation(
// projectId: projectId,
// unitUuid: createRoutineBloc.selectedSpaceId,
// automationId: state.automationId ?? '');
// }
add(const LoadScenes());
add(const LoadAutomation());
@@ -836,21 +903,31 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
// }
// }
FutureOr<void> _fetchDevices(FetchDevicesInRoutine event, Emitter<RoutineState> emit) async {
FutureOr<void> _fetchDevices(
FetchDevicesInRoutine event, Emitter<RoutineState> emit) async {
emit(state.copyWith(isLoading: true));
try {
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
List<AllDevicesModel> devices = [];
BuildContext context = NavigationService.navigatorKey.currentContext!;
var createRoutineBloc = context.read<CreateRoutineBloc>();
var spaceBloc = context.read<SpaceTreeBloc>();
for (var communityId in spaceBloc.state.selectedCommunities) {
List<String> spacesList = spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? [];
for (var spaceId in spacesList) {
devices
.addAll(await DevicesManagementApi().fetchDevices(communityId, spaceId, projectUuid));
if (createRoutineBloc.selectedSpaceId == '' &&
createRoutineBloc.selectedCommunityId == '') {
for (var communityId in spaceBloc.state.selectedCommunities) {
List<String> spacesList =
spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? [];
for (var spaceId in spacesList) {
devices.addAll(await DevicesManagementApi()
.fetchDevices(communityId, spaceId, projectUuid));
}
}
} else {
devices.addAll(await DevicesManagementApi().fetchDevices(
createRoutineBloc.selectedCommunityId,
createRoutineBloc.selectedSpaceId,
projectUuid));
}
emit(state.copyWith(isLoading: false, devices: devices));
@@ -859,7 +936,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
}
}
FutureOr<void> _onUpdateScene(UpdateScene event, Emitter<RoutineState> emit) async {
FutureOr<void> _onUpdateScene(
UpdateScene event, Emitter<RoutineState> emit) async {
try {
// Check if first action is delay
// if (_isFirstActionDelay(state.thenItems)) {
@@ -926,7 +1004,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
actions: actions,
);
final result = await SceneApi.updateScene(createSceneModel, state.sceneId ?? '');
final result =
await SceneApi.updateScene(createSceneModel, state.sceneId ?? '');
if (result['success']) {
add(ResetRoutineState());
add(const LoadScenes());
@@ -945,7 +1024,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
}
}
FutureOr<void> _onUpdateAutomation(UpdateAutomation event, Emitter<RoutineState> emit) async {
FutureOr<void> _onUpdateAutomation(
UpdateAutomation event, Emitter<RoutineState> emit) async {
try {
if (state.routineName == null || state.routineName!.isEmpty) {
emit(state.copyWith(
@@ -1041,10 +1121,10 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
}).toList();
BuildContext context = NavigationService.navigatorKey.currentContext!;
var spaceBloc = context.read<SpaceTreeBloc>();
var spaceBloc = context.read<CreateRoutineBloc>();
final createAutomationModel = CreateAutomationModel(
spaceUuid: spaceBloc.state.selectedSpaces[0],
spaceUuid: spaceBloc.selectedSpaceId,
automationName: state.routineName ?? '',
decisionExpr: state.selectedAutomationOperator,
effectiveTime: EffectiveTime(
@@ -1055,14 +1135,14 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
conditions: conditions,
actions: actions,
);
final result =
await SceneApi.updateAutomation(createAutomationModel, state.automationId ?? '');
final projectId = await ProjectManager.getProjectUUID() ?? '';
final result = await SceneApi.updateAutomation(
createAutomationModel, state.automationId ?? '', projectId);
if (result['success']) {
add(ResetRoutineState());
add(LoadAutomation());
add(LoadScenes());
add(const LoadAutomation());
add(const LoadScenes());
} else {
emit(state.copyWith(
isLoading: false,
@@ -1080,6 +1160,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
Future<void> _onGetAutomationDetails(
GetAutomationDetails event, Emitter<RoutineState> emit) async {
try {
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
emit(state.copyWith(
isLoading: true,
isUpdate: true,
@@ -1090,7 +1171,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
thenItems: [],
));
final automationDetails = await SceneApi.getAutomationDetails(event.automationId);
final automationDetails =
await SceneApi.getAutomationDetails(event.automationId, projectUuid);
final Map<String, Map<String, dynamic>> deviceIfCards = {};
final Map<String, Map<String, dynamic>> deviceThenCards = {};
@@ -1108,21 +1190,25 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
),
);
final deviceId = condition.entityId;
final deviceId = const Uuid().v4();
if (!deviceIfCards.containsKey(deviceId)) {
deviceIfCards[deviceId] = {
'entityId': condition.entityId,
'deviceId': condition.entityId,
'uniqueCustomId': const Uuid().v4(),
'title': matchingDevice.name ?? 'Device',
'productType': condition.productType,
'functions': matchingDevice.functions,
'imagePath': matchingDevice.getDefaultIcon(condition.productType),
'device': matchingDevice,
'type': 'condition',
};
}
// if (!deviceIfCards.containsKey(deviceId)) {
deviceIfCards[deviceId] = {
'entityId': condition.entityId,
'deviceId': condition.entityId,
'uniqueCustomId': const Uuid().v4(),
'title': matchingDevice.name ?? 'Device',
'productType': condition.productType,
'functions': matchingDevice.functions,
'imagePath': matchingDevice.getDefaultIcon(condition.productType),
'device': matchingDevice,
'type': 'condition',
'tag': matchingDevice.deviceTags?.isNotEmpty ?? false
? matchingDevice.deviceTags![0].name
: '',
'subSpace': matchingDevice.deviceSubSpace?.subspaceName ?? '',
};
// }
final cardData = deviceIfCards[deviceId]!;
final uniqueCustomId = cardData['uniqueCustomId'].toString();
@@ -1158,35 +1244,38 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
),
);
final deviceId =
action.actionExecutor == 'delay' ? '${action.entityId}_delay' : action.entityId;
final deviceId = const Uuid().v4();
if (!deviceThenCards.containsKey(deviceId)) {
deviceThenCards[deviceId] = {
'entityId': action.entityId,
'deviceId': action.actionExecutor == 'delay' ? 'delay' : action.entityId,
'uniqueCustomId': const Uuid().v4(),
'title': action.actionExecutor == 'delay'
? 'Delay'
: (action.type == 'scene' || action.type == 'automation')
? action.name
: (matchingDevice.name ?? 'Device'),
'productType': action.productType,
'functions': matchingDevice.functions,
'imagePath': action.actionExecutor == 'delay'
? Assets.delay
: action.type == 'automation'
? Assets.automation
: matchingDevice.getDefaultIcon(action.productType),
'device': matchingDevice,
'type': action.type == 'scene'
? 'scene'
: action.type == 'automation'
? 'automation'
: 'action',
'icon': action.icon ?? ''
};
}
// if (!deviceThenCards.containsKey(deviceId)) {
deviceThenCards[deviceId] = {
'entityId': action.entityId,
'deviceId': action.actionExecutor == 'delay' ? 'delay' : action.entityId,
'uniqueCustomId': const Uuid().v4(),
'title': action.actionExecutor == 'delay'
? 'Delay'
: (action.type == 'scene' || action.type == 'automation')
? action.name
: (matchingDevice.name ?? 'Device'),
'productType': action.productType,
'functions': matchingDevice.functions,
'imagePath': action.actionExecutor == 'delay'
? Assets.delay
: action.type == 'automation'
? Assets.automation
: matchingDevice.getDefaultIcon(action.productType),
'device': matchingDevice,
'type': action.type == 'scene'
? 'scene'
: action.type == 'automation'
? 'automation'
: 'action',
'icon': action.icon ?? '',
'tag': matchingDevice.deviceTags?.isNotEmpty ?? false
? matchingDevice.deviceTags![0].name
: '',
'subSpace': matchingDevice.deviceSubSpace?.subspaceName ?? '',
};
// }
final cardData = deviceThenCards[deviceId]!;
final uniqueCustomId = cardData['uniqueCustomId'].toString();
@@ -1237,10 +1326,13 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
}
}
final ifItems = deviceIfCards.values.where((card) => card['type'] == 'condition').toList();
final ifItems =
deviceIfCards.values.where((card) => card['type'] == 'condition').toList();
final thenItems = deviceThenCards.values
.where((card) =>
card['type'] == 'action' || card['type'] == 'automation' || card['type'] == 'scene')
card['type'] == 'action' ||
card['type'] == 'automation' ||
card['type'] == 'scene')
.toList();
emit(state.copyWith(
@@ -1261,4 +1353,75 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
));
}
}
Future<void> _onSceneTrigger(
SceneTrigger event, Emitter<RoutineState> emit) async {
emit(state.copyWith(loadingSceneId: event.sceneId));
try {
final success = await SceneApi.triggerScene(event.sceneId!);
if (success) {
emit(state.copyWith(
loadingSceneId: null,
// Add success state if needed
));
// Optional: Add delay to show success feedback
await Future.delayed(const Duration(milliseconds: 500));
} else {
emit(state.copyWith(
loadingSceneId: null,
errorMessage: 'Trigger failed',
));
}
} catch (e) {
emit(state.copyWith(
loadingSceneId: null,
errorMessage: 'Trigger error: ${e.toString()}',
));
}
}
Future<void> _onUpdateAutomationStatus(
UpdateAutomationStatus event, Emitter<RoutineState> emit) async {
// Create a new set safely
final currentLoadingIds = state.loadingAutomationIds;
final newLoadingIds = {...currentLoadingIds!}..add(event.automationId);
emit(state.copyWith(loadingAutomationIds: newLoadingIds));
try {
final projectId = await ProjectManager.getProjectUUID() ?? '';
final success = await SceneApi.updateAutomationStatus(
event.automationId, event.automationStatusUpdate, projectId);
if (success) {
final updatedAutomations = await SceneApi.getAutomationByUnitId(
event.automationStatusUpdate.spaceUuid, event.communityId, projectId);
// Remove from loading set safely
final updatedLoadingIds = {...state.loadingAutomationIds!}
..remove(event.automationId);
emit(state.copyWith(
automations: updatedAutomations,
loadingAutomationIds: updatedLoadingIds,
));
} else {
final updatedLoadingIds = {...state.loadingAutomationIds!}
..remove(event.automationId);
emit(state.copyWith(
loadingAutomationIds: updatedLoadingIds,
errorMessage: 'Update failed',
));
}
} catch (e) {
final updatedLoadingIds = {...state.loadingAutomationIds!}
..remove(event.automationId);
emit(state.copyWith(
loadingAutomationIds: updatedLoadingIds,
errorMessage: 'Update error: ${e.toString()}',
));
}
}
}
@@ -210,3 +210,28 @@ class ResetRoutineState extends RoutineEvent {}
class ClearFunctions extends RoutineEvent {}
class ResetErrorMessage extends RoutineEvent {}
class SceneTrigger extends RoutineEvent {
final String? sceneId;
final String? name;
const SceneTrigger({this.sceneId, this.name});
@override
List<Object> get props => [sceneId!,name!];
}
//updateAutomationStatus
class UpdateAutomationStatus extends RoutineEvent {
final String automationId;
final AutomationStatusUpdate automationStatusUpdate;
final String communityId;
const UpdateAutomationStatus({required this.automationStatusUpdate, required this.automationId, required this.communityId});
@override
List<Object> get props => [automationStatusUpdate];
}
@@ -1,6 +1,7 @@
part of 'routine_bloc.dart';
class RoutineState extends Equatable {
final String? loadingSceneId;
final List<Map<String, dynamic>> ifItems;
final List<Map<String, dynamic>> thenItems;
final List<Map<String, String>> availableCards;
@@ -25,6 +26,7 @@ class RoutineState extends Equatable {
// final String? automationActionExecutor;
final bool routineTab;
final bool createRoutineView;
final Set<String>? loadingAutomationIds; // Track loading automations
const RoutineState(
{this.ifItems = const [],
@@ -47,12 +49,16 @@ class RoutineState extends Equatable {
this.sceneId,
this.automationId,
this.isUpdate,
this.loadingAutomationIds = const <String>{}, // Initialize with empty set
this.loadingSceneId,
this.devices = const [],
// this.automationActionExecutor,
this.routineTab = false,
this.createRoutineView = false});
RoutineState copyWith({
String? loadingSceneId,
Set<String>? loadingAutomationIds,
List<Map<String, dynamic>>? ifItems,
List<Map<String, dynamic>>? thenItems,
List<ScenesModel>? scenes,
@@ -79,6 +85,8 @@ class RoutineState extends Equatable {
bool? createRoutineView,
}) {
return RoutineState(
loadingSceneId: loadingSceneId,
loadingAutomationIds: loadingAutomationIds ?? this.loadingAutomationIds,
ifItems: ifItems ?? this.ifItems,
thenItems: thenItems ?? this.thenItems,
scenes: scenes ?? this.scenes,
@@ -109,6 +117,7 @@ class RoutineState extends Equatable {
@override
List<Object?> get props => [
loadingAutomationIds,
ifItems,
thenItems,
scenes,
@@ -134,3 +143,38 @@ class RoutineState extends Equatable {
createRoutineView
];
}
class SceneInitial extends RoutineState {}
class SceneLoading extends RoutineState {}
class SceneLoaded extends RoutineState {
final List<ScenesModel>? scenesOrAutomation;
const SceneLoaded({this.scenesOrAutomation});
@override
List<Object?> get props => [
scenesOrAutomation,
];
}
class SceneError extends RoutineState {
final String message;
const SceneError({required this.message});
@override
List<Object> get props => [message];
}
class SceneTriggerSuccess extends RoutineState {
final String sceneName;
const SceneTriggerSuccess(this.sceneName);
@override
List<Object> get props => [sceneName];
}
class UpdateAutomationStatusLoading extends RoutineState {
const UpdateAutomationStatusLoading();
}
@@ -0,0 +1,156 @@
import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class CommunityDropdown extends StatelessWidget {
final String? selectedValue;
final List<CommunityModel> communities;
final Function(String?) onChanged;
final TextEditingController _searchController = TextEditingController();
CommunityDropdown({
Key? key,
required this.selectedValue,
required this.onChanged,
required this.communities,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(10.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Community",
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
fontWeight: FontWeight.w400,
fontSize: 13,
color: ColorsManager.blackColor,
),
),
const SizedBox(height: 8),
SizedBox(
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
),
child: DropdownButton2<String>(
underline: const SizedBox(),
value: selectedValue,
items: communities.map((community) {
return DropdownMenuItem<String>(
value: community.uuid,
child: Text(
' ${community.name}',
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
);
}).toList(),
onChanged: onChanged,
style: const TextStyle(color: Colors.black),
hint: Padding(
padding: const EdgeInsets.only(left: 10),
child: Text(
" Please Select",
style: Theme.of(context).textTheme.bodySmall!.copyWith(
color: ColorsManager.textGray,
),
),
),
customButton: Container(
height: 45,
decoration: BoxDecoration(
border: Border.all(color: ColorsManager.textGray, width: 1.0),
borderRadius: BorderRadius.circular(10),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
flex: 5,
child: Text(
selectedValue != null
? " ${communities.firstWhere((element) => element.uuid == selectedValue).name}"
: ' Please Select',
style: Theme.of(context).textTheme.bodySmall!.copyWith(
color: selectedValue != null
? Colors.black
: ColorsManager.textGray,
),
overflow: TextOverflow.ellipsis,
),
),
Expanded(
child: Container(
decoration: BoxDecoration(
color: Colors.grey[100],
borderRadius: const BorderRadius.only(
topRight: Radius.circular(10),
bottomRight: Radius.circular(10),
),
),
height: 45,
child: const Icon(
Icons.keyboard_arrow_down,
color: ColorsManager.textGray,
),
),
),
],
),
),
dropdownStyleData: DropdownStyleData(
maxHeight: MediaQuery.of(context).size.height * 0.4,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
),
),
dropdownSearchData: DropdownSearchData(
searchController: _searchController,
searchInnerWidgetHeight: 50,
searchInnerWidget: Container(
height: 50,
padding:
const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
child: TextFormField(
style: const TextStyle(color: Colors.black),
controller: _searchController,
decoration: InputDecoration(
isDense: true,
contentPadding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 12,
),
hintText: 'Search for community...',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
),
),
),
),
searchMatchFn: (item, searchValue) {
final communityName =
(item.child as Text).data?.toLowerCase() ?? '';
return communityName
.contains(searchValue.toLowerCase().trim());
},
),
onMenuStateChange: (isOpen) {
if (!isOpen) {
_searchController.clear();
}
},
menuItemStyleData: const MenuItemStyleData(
height: 40,
),
),
))
],
),
);
}
}
@@ -0,0 +1,181 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/routines/bloc/create_routine_bloc/create_routine_event.dart';
import 'package:syncrow_web/pages/routines/bloc/create_routine_bloc/create_routine_state.dart';
import 'package:syncrow_web/pages/routines/bloc/create_routine_bloc/create_routine_bloc.dart';
import 'package:syncrow_web/pages/routines/create_new_routines/commu_dropdown.dart';
import 'package:syncrow_web/pages/routines/create_new_routines/space_dropdown.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class CreateNewRoutinesDialog extends StatefulWidget {
const CreateNewRoutinesDialog({super.key});
@override
State<CreateNewRoutinesDialog> createState() =>
_CreateNewRoutinesDialogState();
}
class _CreateNewRoutinesDialogState extends State<CreateNewRoutinesDialog> {
String? _selectedCommunity;
String? _selectedSpace;
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (BuildContext context) =>
CreateRoutineBloc()..add(const FetchCommunityEvent()),
child: BlocBuilder<CreateRoutineBloc, CreateRoutineState>(
builder: (context, state) {
final _bloc = BlocProvider.of<CreateRoutineBloc>(context);
final spaces = _bloc.spacesOnlyWithDevices;
final isLoadingCommunities = state is CommunitiesLoadingState;
final isLoadingSpaces = state is SpaceWithDeviceLoadingState;
String spaceHint = 'Select a community first';
if (_selectedCommunity != null) {
if (isLoadingSpaces) {
spaceHint = 'Loading spaces...';
} else if (spaces.isEmpty) {
spaceHint = 'No spaces available';
} else {
spaceHint = 'Select Space';
}
}
return AlertDialog(
backgroundColor: Colors.white,
insetPadding: EdgeInsets.zero,
contentPadding: EdgeInsets.zero,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12)),
title: Text(
'Create New Routines',
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: ColorsManager.primaryColor,
),
),
content: Stack(
children: [
Column(
mainAxisSize: MainAxisSize.min,
children: [
const Divider(),
Padding(
padding: const EdgeInsets.only(left: 15, right: 15),
child: CommunityDropdown(
communities: _bloc.communities..sort(
(a, b) => a.name.toLowerCase().compareTo(
b.name.toLowerCase(),
),
),
selectedValue: _selectedCommunity,
onChanged: (String? newValue) {
setState(() {
_selectedCommunity = newValue;
_selectedSpace = null;
});
if (newValue != null) {
_bloc.add(SpaceOnlyWithDevicesEvent(newValue));
}
},
),
),
const SizedBox(height: 5),
Padding(
padding: const EdgeInsets.only(left: 15, right: 15),
child: SpaceDropdown(
hintMessage: spaceHint,
spaces: spaces,
selectedValue: _selectedSpace,
onChanged: (String? newValue) {
setState(() {
_selectedSpace = newValue;
});
},
),
),
const Divider(),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Padding(
padding: const EdgeInsets.only(
left: 20,
right: 20,
),
child: TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text(
'Cancel',
style: Theme.of(context)
.textTheme
.bodyMedium!
.copyWith(
fontWeight: FontWeight.w400,
fontSize: 14,
color: ColorsManager.blackColor,
),
),
),
),
Padding(
padding: const EdgeInsets.only(
left: 20,
right: 20,
),
child: TextButton(
onPressed: _selectedCommunity != null &&
_selectedSpace != null
? () {
Navigator.of(context).pop({
'community': _selectedCommunity,
'space': _selectedSpace,
});
}
: null,
child: Text(
'Next',
style: Theme.of(context)
.textTheme
.bodyMedium!
.copyWith(
fontWeight: FontWeight.w400,
fontSize: 14,
color: _selectedCommunity != null &&
_selectedSpace != null
? ColorsManager.blueColor
: Colors.blue.shade100,
),
),
),
),
],
),
const SizedBox(height: 10),
],
),
if (isLoadingCommunities)
const SizedBox(
height: 200,
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Center(
child: CircularProgressIndicator(
color: ColorsManager.primaryColor,
),
),
],
),
),
],
),
);
},
));
}
}
@@ -0,0 +1,147 @@
import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class SpaceDropdown extends StatelessWidget {
final List<SpaceModel> spaces;
final String? selectedValue;
final Function(String?)? onChanged;
final String hintMessage;
const SpaceDropdown({
Key? key,
required this.spaces,
required this.selectedValue,
required this.onChanged,
required this.hintMessage,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(10.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Space",
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
fontWeight: FontWeight.w400,
fontSize: 13,
color: ColorsManager.blackColor,
),
),
const SizedBox(height: 8),
SizedBox(
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
),
child: DropdownButton2<String>(
underline: const SizedBox(),
value: selectedValue,
items: spaces.map((space) {
return DropdownMenuItem<String>(
value: space.uuid,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
' ${space.name}',
style:
Theme.of(context).textTheme.bodyMedium!.copyWith(
fontSize: 12,
color: ColorsManager.blackColor,
),
),
Text(
' ${space.lastThreeParents}',
style:
Theme.of(context).textTheme.bodyMedium!.copyWith(
fontSize: 12,
),
),
],
),
);
}).toList(),
onChanged: onChanged,
style: TextStyle(color: Colors.black),
hint: Padding(
padding: const EdgeInsets.only(left: 10),
child: Text(
hintMessage,
style: Theme.of(context).textTheme.bodySmall!.copyWith(
color: ColorsManager.textGray,
),
),
),
customButton: Container(
height: 45,
decoration: BoxDecoration(
border:
Border.all(color: ColorsManager.textGray, width: 1.0),
borderRadius: BorderRadius.circular(10),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
flex: 5,
child: Padding(
padding: const EdgeInsets.only(left: 10),
child: Text(
selectedValue != null
? spaces
.firstWhere((e) => e.uuid == selectedValue)
.name
: hintMessage,
style:
Theme.of(context).textTheme.bodySmall!.copyWith(
color: selectedValue != null
? Colors.black
: ColorsManager.textGray,
),
overflow: TextOverflow.ellipsis,
),
),
),
Expanded(
child: Container(
decoration: BoxDecoration(
color: Colors.grey[200],
borderRadius: const BorderRadius.only(
topRight: Radius.circular(10),
bottomRight: Radius.circular(10),
),
),
height: 45,
child: const Icon(
Icons.keyboard_arrow_down,
color: ColorsManager.textGray,
),
),
),
],
),
),
dropdownStyleData: DropdownStyleData(
maxHeight: MediaQuery.of(context).size.height * 0.4,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
),
),
menuItemStyleData: const MenuItemStyleData(
height: 60,
),
),
),
),
],
),
);
}
}
@@ -1,26 +1,32 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routines/models/device_functions.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/ac_dialog.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_helper.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/flush_presence_sensor/flush_presence_sensor.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/gateway/gateway_helper.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/one_gang_switch_dialog.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/three_gang_switch_dialog.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/two_gang_switch_dialog.dart';
import 'package:syncrow_web/pages/routines/models/device_functions.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/wall_sensor/wall_presence_sensor.dart';
class DeviceDialogHelper {
static Future<Map<String, dynamic>?> showDeviceDialog(
BuildContext context,
Map<String, dynamic> data, {
static Future<Map<String, dynamic>?> showDeviceDialog({
required BuildContext context,
required String dialogType,
required Map<String, dynamic> data,
required bool removeComparetors,
}) async {
final functions = data['functions'] as List<DeviceFunction>;
try {
final result = await _getDialogForDeviceType(
context,
data['productType'],
data,
functions,
dialogType: dialogType,
context: context,
productType: data['productType'],
data: data,
functions: functions,
removeComparetors: removeComparetors,
);
@@ -34,32 +40,93 @@ class DeviceDialogHelper {
return null;
}
static Future<Map<String, dynamic>?> _getDialogForDeviceType(BuildContext context,
String productType, Map<String, dynamic> data, List<DeviceFunction> functions,
{required bool removeComparetors}) async {
static Future<Map<String, dynamic>?> _getDialogForDeviceType({
required String dialogType,
required BuildContext context,
required String productType,
required Map<String, dynamic> data,
required List<DeviceFunction> functions,
required bool removeComparetors,
}) async {
final routineBloc = context.read<RoutineBloc>();
final deviceSelectedFunctions =
routineBloc.state.selectedFunctions[data['uniqueCustomId']] ?? [];
if (removeComparetors) {
//remove the current temp function in the 'if container'
functions.removeAt(3);
}
switch (productType) {
case 'AC':
return ACHelper.showACFunctionsDialog(context, functions, data['device'],
deviceSelectedFunctions, data['uniqueCustomId'], removeComparetors);
return ACHelper.showACFunctionsDialog(
context: context,
functions: functions,
device: data['device'],
deviceSelectedFunctions: deviceSelectedFunctions,
uniqueCustomId: data['uniqueCustomId'],
removeComparetors: removeComparetors,
dialogType: dialogType,
);
case '1G':
return OneGangSwitchHelper.showSwitchFunctionsDialog(context, functions, data['device'],
deviceSelectedFunctions, data['uniqueCustomId'], removeComparetors);
return OneGangSwitchHelper.showSwitchFunctionsDialog(
dialogType: dialogType,
context: context,
functions: functions,
device: data['device'],
deviceSelectedFunctions: deviceSelectedFunctions,
uniqueCustomId: data['uniqueCustomId'],
removeComparetors: removeComparetors);
case '2G':
return TwoGangSwitchHelper.showSwitchFunctionsDialog(context, functions, data['device'],
deviceSelectedFunctions, data['uniqueCustomId'], removeComparetors);
return TwoGangSwitchHelper.showSwitchFunctionsDialog(
dialogType: dialogType,
context: context,
functions: functions,
device: data['device'],
deviceSelectedFunctions: deviceSelectedFunctions,
uniqueCustomId: data['uniqueCustomId'],
removeComparetors: removeComparetors);
case '3G':
return ThreeGangSwitchHelper.showSwitchFunctionsDialog(context, functions, data['device'],
deviceSelectedFunctions, data['uniqueCustomId'], removeComparetors);
return ThreeGangSwitchHelper.showSwitchFunctionsDialog(
dialogType: dialogType,
context: context,
functions: functions,
device: data['device'],
deviceSelectedFunctions: deviceSelectedFunctions,
uniqueCustomId: data['uniqueCustomId'],
removeComparetors: removeComparetors);
case 'WPS':
return WallPresenceSensor.showWPSFunctionsDialog(
dialogType: dialogType,
context: context,
functions: functions,
device: data['device'],
deviceSelectedFunctions: deviceSelectedFunctions,
uniqueCustomId: data['uniqueCustomId'],
removeComparetors: removeComparetors);
case 'CPS':
return CeilingSensorHelper.showCeilingSensorDialog(
context: context,
functions: functions,
device: data['device'],
deviceSelectedFunctions: deviceSelectedFunctions,
uniqueCustomId: data['uniqueCustomId'],
dialogType: dialogType,
);
case 'GW':
return GatewayHelper.showGatewayFunctionsDialog(
context: context,
functions: functions,
uniqueCustomId: data['uniqueCustomId'],
deviceSelectedFunctions: deviceSelectedFunctions,
device: data['device'],
);
case 'NCPS':
return FlushPresenceSensor.showFlushFunctionsDialog(
context: context,
functions: functions,
uniqueCustomId: data['uniqueCustomId'],
deviceSelectedFunctions: deviceSelectedFunctions,
dialogType: dialogType,
device: data['device'],
);
default:
return null;
}
+281 -139
View File
@@ -4,7 +4,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.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/widgets/dialog_header.dart';
import 'package:syncrow_web/pages/routines/models/device_functions.dart';
import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
@@ -14,13 +14,18 @@ class SaveRoutineHelper {
static Future<void> showSaveRoutineDialog(BuildContext context) async {
return showDialog<void>(
context: context,
builder: (BuildContext context) {
builder: (context) {
return BlocBuilder<RoutineBloc, RoutineState>(
builder: (context, state) {
final selectedConditionLabel = state.selectedAutomationOperator == 'and'
? 'All Conditions are met'
: 'Any Condition is met';
return AlertDialog(
contentPadding: EdgeInsets.zero,
content: Container(
width: 600,
width: context.screenWidth * 0.5,
height: 500,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
@@ -28,146 +33,42 @@ class SaveRoutineHelper {
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
DialogHeader('Create a scene: ${state.routineName ?? ""}'),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Left side - IF
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'IF:',
style: TextStyle(
fontSize: 16,
),
),
const SizedBox(height: 8),
if (state.isTabToRun)
ListTile(
leading: SvgPicture.asset(
Assets.tabToRun,
width: 24,
height: 24,
),
title: const Text('Tab to run'),
),
if (state.isAutomation)
...state.ifItems.map((item) {
final functions =
state.selectedFunctions[item['uniqueCustomId']] ?? [];
return ListTile(
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(),
),
);
}),
],
),
const SizedBox(height: 18),
Text(
'Create a scene: ${state.routineName ?? ""}',
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.headlineMedium!.copyWith(
color: ColorsManager.primaryColorWithOpacity,
fontWeight: FontWeight.bold,
),
const SizedBox(width: 16),
// Right side - THEN items
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'THEN:',
style: TextStyle(
fontSize: 16,
),
),
const SizedBox(height: 8),
...state.thenItems.map((item) {
final functions =
state.selectedFunctions[item['uniqueCustomId']] ?? [];
return ListTile(
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(),
),
);
}),
],
),
const SizedBox(height: 18),
_buildDivider(),
_buildListsLabelRow(selectedConditionLabel),
Expanded(
child: Padding(
padding: const EdgeInsetsDirectional.symmetric(
horizontal: 16,
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
spacing: 24,
children: [
_buildIfConditions(state, context),
Container(
width: 1,
color: ColorsManager.greyColor.withValues(alpha: 0.8),
),
),
],
_buildThenActions(state, context),
],
),
),
),
// if (state.errorMessage != null || state.errorMessage!.isNotEmpty)
// Padding(
// padding: const EdgeInsets.all(8.0),
// child: Text(
// state.errorMessage!,
// style: const TextStyle(color: Colors.red),
// ),
// ),
DialogFooter(
onCancel: () => Navigator.pop(context),
onConfirm: () async {
if (state.isAutomation) {
if (state.isUpdate ?? false) {
context.read<RoutineBloc>().add(const UpdateAutomation());
} else {
context.read<RoutineBloc>().add(const CreateAutomationEvent());
}
} else {
if (state.isUpdate ?? false) {
context.read<RoutineBloc>().add(const UpdateScene());
} else {
context.read<RoutineBloc>().add(const CreateSceneEvent());
}
}
// if (state.errorMessage == null || state.errorMessage!.isEmpty) {
Navigator.pop(context);
// }
},
isConfirmEnabled: true,
),
_buildDivider(),
const SizedBox(height: 8),
_buildDialogFooter(context, state),
const SizedBox(height: 8),
],
),
),
@@ -177,4 +78,245 @@ class SaveRoutineHelper {
},
);
}
static Container _buildDivider() {
return Container(
height: 1,
width: double.infinity,
color: ColorsManager.greyColor,
);
}
static Widget _buildListsLabelRow(String selectedConditionLabel) {
const textStyle = TextStyle(
fontSize: 16,
);
return Container(
color: ColorsManager.backgroundColor.withValues(alpha: 0.5),
padding: const EdgeInsetsDirectional.all(20),
child: Row(
spacing: 16,
children: [
Expanded(child: Text('IF: $selectedConditionLabel', style: textStyle)),
const Expanded(child: Text('THEN:', style: textStyle)),
],
),
);
}
static Widget _buildDialogFooter(BuildContext context, RoutineState state) {
return Row(
spacing: 16,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
DialogFooterButton(
text: 'Cancel',
onTap: () => Navigator.pop(context),
),
DialogFooterButton(
text: 'Confirm',
onTap: () {
if (state.isAutomation) {
if (state.isUpdate ?? false) {
context.read<RoutineBloc>().add(const UpdateAutomation());
} else {
context.read<RoutineBloc>().add(const CreateAutomationEvent());
}
} else {
if (state.isUpdate ?? false) {
context.read<RoutineBloc>().add(const UpdateScene());
} else {
context.read<RoutineBloc>().add(const CreateSceneEvent());
}
}
Navigator.pop(context);
},
textColor: ColorsManager.primaryColorWithOpacity,
),
],
);
}
static Widget _buildThenActions(RoutineState state, BuildContext context) {
return Expanded(
child: ListView(
// shrinkWrap: true,
children: state.thenItems.map((item) {
final functions = state.selectedFunctions[item['uniqueCustomId']] ?? [];
return functionRow(item, context, functions);
}).toList(),
),
);
}
static Widget _buildIfConditions(RoutineState state, BuildContext context) {
return Expanded(
child: ListView(
// shrinkWrap: true,
children: [
if (state.isTabToRun)
ListTile(
leading: SvgPicture.asset(
Assets.tabToRun,
width: 24,
height: 24,
),
title: const Text('Tab to run'),
),
if (state.isAutomation)
...state.ifItems.map((item) {
final functions =
state.selectedFunctions[item['uniqueCustomId']] ?? [];
return functionRow(item, context, functions);
}),
],
),
);
}
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: 17,
children: [
Container(
width: 22,
height: 22,
padding: const EdgeInsetsDirectional.all(4),
decoration: BoxDecoration(
shape: BoxShape.circle,
color: ColorsManager.textFieldGreyColor,
border: Border.all(
color: ColorsManager.neutralGray,
width: 1.5,
),
),
child: Center(
child: item['type'] == 'tap_to_run' || item['type'] == 'scene'
? Image.memory(
base64Decode(item['icon']),
width: 12,
height: 22,
fit: BoxFit.scaleDown,
)
: SvgPicture.asset(
item['imagePath'],
width: 12,
height: 12,
fit: BoxFit.scaleDown,
),
),
),
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: 15,
color: ColorsManager.textPrimaryColor,
),
),
Wrap(
runSpacing: 16,
spacing: 4,
children: functions
.map(
(function) => Text(
'${function.operationName}: ${function.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,
),
),
],
),
),
],
),
],
),
);
}
}
+21 -6
View File
@@ -5,23 +5,28 @@ import 'package:syncrow_web/utils/constants/app_enum.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
abstract class ACFunction extends DeviceFunction<AcStatusModel> {
final String type;
ACFunction({
required super.deviceId,
required super.deviceName,
required super.code,
required super.operationName,
required super.icon,
required this.type,
});
List<ACOperationalValue> getOperationalValues();
}
class SwitchFunction extends ACFunction {
SwitchFunction({required super.deviceId, required super.deviceName})
SwitchFunction(
{required super.deviceId, required super.deviceName, required type})
: super(
code: 'switch',
operationName: 'Power',
icon: Assets.assetsAcPower,
type: type,
);
@override
@@ -40,11 +45,13 @@ class SwitchFunction extends ACFunction {
}
class ModeFunction extends ACFunction {
ModeFunction({required super.deviceId, required super.deviceName})
ModeFunction(
{required super.deviceId, required super.deviceName, required type})
: super(
code: 'mode',
operationName: 'Mode',
icon: Assets.assetsFreezing,
type: type,
);
@override
@@ -72,7 +79,8 @@ class TempSetFunction extends ACFunction {
final int max;
final int step;
TempSetFunction({required super.deviceId, required super.deviceName})
TempSetFunction(
{required super.deviceId, required super.deviceName, required type})
: min = 160,
max = 300,
step = 1,
@@ -80,6 +88,7 @@ class TempSetFunction extends ACFunction {
code: 'temp_set',
operationName: 'Set Temperature',
icon: Assets.assetsTempreture,
type: type,
);
@override
@@ -97,8 +106,10 @@ class TempSetFunction extends ACFunction {
}
class LevelFunction extends ACFunction {
LevelFunction({required super.deviceId, required super.deviceName})
LevelFunction(
{required super.deviceId, required super.deviceName, required type})
: super(
type: type,
code: 'level',
operationName: 'Fan Speed',
icon: Assets.assetsFanSpeed,
@@ -130,8 +141,10 @@ class LevelFunction extends ACFunction {
}
class ChildLockFunction extends ACFunction {
ChildLockFunction({required super.deviceId, required super.deviceName})
ChildLockFunction(
{required super.deviceId, required super.deviceName, required type})
: super(
type: type,
code: 'child_lock',
operationName: 'Child Lock',
icon: Assets.assetsChildLock,
@@ -157,11 +170,13 @@ class CurrentTempFunction extends ACFunction {
final int max;
final int step;
CurrentTempFunction({required super.deviceId, required super.deviceName})
CurrentTempFunction(
{required super.deviceId, required super.deviceName, required type})
: min = -100,
max = 990,
step = 1,
super(
type: type,
code: 'temp_current',
operationName: 'Current Temperature',
icon: Assets.currentTemp,
@@ -0,0 +1,889 @@
import 'package:syncrow_web/pages/routines/models/device_functions.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
class CpsOperationalValue {
final String icon;
final String description;
final dynamic value;
CpsOperationalValue({
required this.icon,
required this.description,
required this.value,
});
}
abstract class CpsFunctions extends DeviceFunction<CpsOperationalValue> {
CpsFunctions({
required super.deviceId,
required super.deviceName,
required super.code,
required super.operationName,
required super.icon,
required this.type,
});
final String type;
List<CpsOperationalValue> getOperationalValues();
}
final class CpsRadarSwitchFunction extends CpsFunctions {
CpsRadarSwitchFunction({
required super.deviceId,
required super.deviceName,
required super.type,
}) : super(
code: 'radar_switch',
operationName: 'Radar Switch',
icon: Assets.acPower,
);
@override
List<CpsOperationalValue> getOperationalValues() => [
CpsOperationalValue(
icon: Assets.assetsAcPower,
description: "ON",
value: true,
),
CpsOperationalValue(
icon: Assets.assetsAcPowerOFF,
description: "OFF",
value: false,
),
];
}
final class CpsSpatialParameterSwitchFunction extends CpsFunctions {
CpsSpatialParameterSwitchFunction({
required super.deviceId,
required super.deviceName,
required super.type,
}) : super(
code: 'space_para_switch',
operationName: 'Spatial Parameter Switch',
icon: Assets.acPower,
);
@override
List<CpsOperationalValue> getOperationalValues() => [
CpsOperationalValue(
icon: Assets.assetsAcPower,
description: "ON",
value: true,
),
CpsOperationalValue(
icon: Assets.assetsAcPowerOFF,
description: "OFF",
value: false,
),
];
}
final class CpsSensitivityFunction extends CpsFunctions {
CpsSensitivityFunction({
required super.deviceId,
required super.deviceName,
required super.type,
}) : min = 1,
max = 10,
step = 1,
super(
code: 'sensitivity',
operationName: 'Sensitivity',
icon: Assets.sensitivity,
);
final int min;
final int max;
final int step;
static const _images = <String>[
Assets.sensitivityFeature1,
Assets.sensitivityFeature1,
Assets.sensitivityFeature2,
Assets.sensitivityFeature3,
Assets.sensitivityFeature4,
Assets.sensitivityFeature5,
Assets.sensitivityFeature6,
Assets.sensitivityFeature7,
Assets.sensitivityFeature8,
Assets.sensitivityFeature9,
Assets.sensitivityFeature9,
];
@override
List<CpsOperationalValue> getOperationalValues() {
final values = <CpsOperationalValue>[];
for (var value = min; value <= max; value += step) {
values.add(
CpsOperationalValue(
icon: _images[value],
description: '$value',
value: value,
),
);
}
return values;
}
}
final class CpsMovingSpeedFunction extends CpsFunctions {
CpsMovingSpeedFunction({
required super.deviceId,
required super.deviceName,
required super.type,
}) : min = 0,
max = 32,
step = 1,
super(
code: 'moving_speed',
operationName: 'Moving Speed',
icon: Assets.speedoMeter,
);
final int min;
final int max;
final int step;
@override
List<CpsOperationalValue> getOperationalValues() {
return List.generate(
(max - min) ~/ step + 1,
(index) => CpsOperationalValue(
icon: Assets.speedoMeter,
description: '${min + (index * step)}',
value: min + (index * step),
),
);
}
}
final class CpsSpatialStaticValueFunction extends CpsFunctions {
CpsSpatialStaticValueFunction({
required super.deviceId,
required super.deviceName,
required super.type,
}) : min = 0,
max = 255,
step = 1,
super(
code: 'space_static_val',
operationName: 'Spacial Static Value',
icon: Assets.spatialStaticValue,
);
final int min;
final int max;
final int step;
@override
List<CpsOperationalValue> getOperationalValues() {
return List.generate(
(max - min) ~/ step + 1,
(index) => CpsOperationalValue(
icon: Assets.spatialStaticValue,
description: '${min + (index * step)}',
value: min + (index * step),
),
);
}
}
final class CpsSpatialMotionValueFunction extends CpsFunctions {
CpsSpatialMotionValueFunction({
required super.deviceId,
required super.deviceName,
required super.type,
}) : min = 0,
max = 255,
step = 1,
super(
code: 'space_move_val',
operationName: 'Spatial Motion Value',
icon: Assets.spatialMotionValue,
);
final int min;
final int max;
final int step;
@override
List<CpsOperationalValue> getOperationalValues() {
return List.generate(
(max - min) ~/ step + 1,
(index) => CpsOperationalValue(
icon: Assets.spatialMotionValue,
description: '${min + (index * step)}',
value: min + (index * step),
),
);
}
}
final class CpsMaxDistanceOfDetectionFunction extends CpsFunctions {
CpsMaxDistanceOfDetectionFunction({
required super.deviceId,
required super.deviceName,
required super.type,
}) : min = 0.0,
max = 10.0,
step = 0.5,
super(
code: 'moving_max_dis',
operationName: 'Maximum Distance Of Detection',
icon: Assets.currentDistanceIcon,
);
final double min;
final double max;
final double step;
@override
List<CpsOperationalValue> getOperationalValues() {
final count = ((max - min) / step).round() + 1;
return List.generate(
count,
(index) {
final value = (min + (index * step));
return CpsOperationalValue(
icon: Assets.currentDistanceIcon,
description: '${value.toStringAsFixed(1)} M',
value: value,
);
},
);
}
}
final class CpsMaxDistanceOfStaticDetectionFunction extends CpsFunctions {
CpsMaxDistanceOfStaticDetectionFunction({
required super.deviceId,
required super.deviceName,
required super.type,
}) : min = 0.0,
max = 10.0,
step = 0.5,
super(
code: 'static_max_dis',
operationName: 'Maximum Distance Of Static Detection',
icon: Assets.currentDistanceIcon,
);
final double min;
final double max;
final double step;
@override
List<CpsOperationalValue> getOperationalValues() {
final count = ((max - min) / step).round() + 1;
return List.generate(
count,
(index) {
final value = (min + (index * step));
return CpsOperationalValue(
icon: Assets.currentDistanceIcon,
description: '${value.toStringAsFixed(1)} M',
value: value,
);
},
);
}
}
final class CpsDetectionRangeFunction extends CpsFunctions {
CpsDetectionRangeFunction({
required super.deviceId,
required super.deviceName,
required super.type,
}) : min = 0.0,
max = 25.5,
step = 0.1,
super(
code: 'moving_range',
operationName: 'Detection Range',
icon: Assets.farDetection,
);
final double min;
final double max;
final double step;
@override
List<CpsOperationalValue> getOperationalValues() {
final count = ((max - min) / step).round() + 1;
return List.generate(
count,
(index) {
final value = (min + (index * step));
return CpsOperationalValue(
icon: Assets.farDetection,
description: '${value.toStringAsFixed(1)} M',
value: value,
);
},
);
}
}
final class CpsDistanceOfMovingObjectsFunction extends CpsFunctions {
CpsDistanceOfMovingObjectsFunction({
required super.deviceId,
required super.deviceName,
required super.type,
}) : min = 0.0,
max = 25.5,
step = 0.1,
super(
code: 'presence_range',
operationName: 'Distance Of Moving Objects',
icon: Assets.currentDistanceIcon,
);
final double min;
final double max;
final double step;
@override
List<CpsOperationalValue> getOperationalValues() {
final count = ((max - min) / step).round() + 1;
return List.generate(
count,
(index) {
final value = (min + (index * step));
return CpsOperationalValue(
icon: Assets.currentDistanceIcon,
description: '${value.toStringAsFixed(1)} M',
value: value,
);
},
);
}
}
final class CpsPresenceJudgementThrsholdFunction extends CpsFunctions {
CpsPresenceJudgementThrsholdFunction({
required super.deviceId,
required super.deviceName,
required super.type,
}) : min = 0,
max = 255,
step = 5,
super(
code: 'presence_reference',
operationName: 'Presence Judgement Threshold',
icon: Assets.presenceJudgementThrshold,
);
final int min;
final int max;
final int step;
@override
List<CpsOperationalValue> getOperationalValues() {
return List.generate(
(max - min) ~/ step + 1,
(index) => CpsOperationalValue(
icon: Assets.presenceJudgementThrshold,
description: '${min + (index * step)}',
value: min + (index * step),
),
);
}
}
final class CpsMotionAmplitudeTriggerThresholdFunction extends CpsFunctions {
CpsMotionAmplitudeTriggerThresholdFunction({
required super.deviceId,
required super.deviceName,
required super.type,
}) : min = 0,
max = 255,
step = 5,
super(
code: 'moving_reference',
operationName: 'Motion Amplitude Trigger Threshold',
icon: Assets.presenceJudgementThrshold,
);
final int min;
final int max;
final int step;
@override
List<CpsOperationalValue> getOperationalValues() {
return List.generate(
(max - min) ~/ step + 1,
(index) => CpsOperationalValue(
icon: Assets.presenceJudgementThrshold,
description: '${min + (index * step)}',
value: min + (index * step),
),
);
}
}
final class CpsPerpetualBoundaryFunction extends CpsFunctions {
CpsPerpetualBoundaryFunction({
required super.deviceId,
required super.deviceName,
required super.type,
}) : min = 0.00,
max = 5.00,
step = 0.50,
super(
code: 'perceptual_boundary',
operationName: 'Perpetual Boundary',
icon: Assets.boundary,
);
final double min;
final double max;
final double step;
@override
List<CpsOperationalValue> getOperationalValues() {
final count = ((max - min) / step).round() + 1;
return List.generate(
count,
(index) {
final value = (min + (index * step));
return CpsOperationalValue(
icon: Assets.boundary,
description: '${value.toStringAsFixed(1)}M',
value: value + 1200,
);
},
);
}
}
final class CpsMotionTriggerBoundaryFunction extends CpsFunctions {
CpsMotionTriggerBoundaryFunction({
required super.deviceId,
required super.deviceName,
required super.type,
}) : min = 0.0,
max = 5.0,
step = 0.5,
super(
code: 'moving_boundary',
operationName: 'Motion Trigger Boundary',
icon: Assets.motionMeter,
);
final double min;
final double max;
final double step;
@override
List<CpsOperationalValue> getOperationalValues() {
final count = ((max - min) / step).round() + 1;
return List.generate(
count,
(index) {
final value = (min + (index * step));
return CpsOperationalValue(
icon: Assets.motionMeter,
description: '${value.toStringAsFixed(1)} M',
value: value,
);
},
);
}
}
final class CpsMotionTriggerTimeFunction extends CpsFunctions {
CpsMotionTriggerTimeFunction({
required super.deviceId,
required super.deviceName,
required super.type,
}) : min = 0.0,
max = 2.0,
step = 0.1,
super(
code: 'moving_rigger_time',
operationName: 'Motion Trigger Time',
icon: Assets.motionMeter,
);
final double min;
final double max;
final double step;
@override
List<CpsOperationalValue> getOperationalValues() {
final count = ((max - min) / step).round() + 1;
return List.generate(
count,
(index) {
final value = (min + (index * step));
return CpsOperationalValue(
icon: Assets.motionMeter,
description: '${value.toStringAsFixed(3)} sec',
value: value,
);
},
);
}
}
final class CpsMotionToStaticTimeFunction extends CpsFunctions {
CpsMotionToStaticTimeFunction({
required super.deviceId,
required super.deviceName,
required super.type,
}) : min = 0.0,
max = 50.0,
step = 1.0,
super(
code: 'moving_static_time',
operationName: 'Motion To Static Time',
icon: Assets.motionMeter,
);
final double min;
final double max;
final double step;
@override
List<CpsOperationalValue> getOperationalValues() {
final count = ((max - min) / step).round() + 1;
return List.generate(
count,
(index) {
final value = (min + (index * step));
return CpsOperationalValue(
icon: Assets.motionMeter,
description: '${value.toStringAsFixed(0)} sec',
value: value,
);
},
);
}
}
final class CpsEnteringNoBodyStateTimeFunction extends CpsFunctions {
CpsEnteringNoBodyStateTimeFunction({
required super.deviceId,
required super.deviceName,
required super.type,
}) : min = 0.0,
max = 300.0,
step = 5.0,
super(
code: 'none_body_time',
operationName: 'Entering Nobody State Time',
icon: Assets.motionMeter,
);
final double min;
final double max;
final double step;
@override
List<CpsOperationalValue> getOperationalValues() {
final count = ((max - min) / step).round() + 1;
return List.generate(
count,
(index) {
final value = (min + (index * step));
return CpsOperationalValue(
icon: Assets.motionMeter,
description: '${value.toStringAsFixed(0)} sec',
value: value,
);
},
);
}
}
final class CpsSelfTestResultFunctions extends CpsFunctions {
CpsSelfTestResultFunctions({
required super.deviceId,
required super.deviceName,
required super.type,
}) : super(
code: 'checking_result',
operationName: 'Self-Test Result',
icon: Assets.selfTestResult,
);
@override
List<CpsOperationalValue> getOperationalValues() {
return [
CpsOperationalValue(
description: 'Self Testing',
icon: Assets.selfTestResult,
value: 'check',
),
CpsOperationalValue(
description: 'Self Testing Success',
icon: Assets.selfTestingSuccess,
value: 'check_success',
),
CpsOperationalValue(
description: 'Self Testing Failure',
icon: Assets.selfTestingFailure,
value: 'check_failure',
),
CpsOperationalValue(
description: 'Self Testing Timeout',
icon: Assets.selfTestingTimeout,
value: 'check_timeout',
),
CpsOperationalValue(
description: 'Communication Fault',
icon: Assets.communicationFault,
value: 'communication_fault',
),
CpsOperationalValue(
description: 'Radar Fault',
icon: Assets.radarFault,
value: 'radar_fault',
),
];
}
}
final class CpsNobodyTimeFunction extends CpsFunctions {
CpsNobodyTimeFunction({
required super.deviceId,
required super.deviceName,
required super.type,
}) : super(
code: 'nobody_time',
operationName: 'Entering Nobody Time',
icon: Assets.assetsNobodyTime,
);
@override
List<CpsOperationalValue> getOperationalValues() {
return [
CpsOperationalValue(
icon: Assets.assetsNobodyTime,
description: 'None',
value: 'none',
),
CpsOperationalValue(
icon: Assets.assetsNobodyTime,
description: '10sec',
value: '10s',
),
CpsOperationalValue(
icon: Assets.assetsNobodyTime,
description: '30sec',
value: '30s',
),
CpsOperationalValue(
icon: Assets.assetsNobodyTime,
description: '1min',
value: '1min',
),
CpsOperationalValue(
icon: Assets.assetsNobodyTime,
description: '2min',
value: '2min',
),
CpsOperationalValue(
icon: Assets.assetsNobodyTime,
description: '5min',
value: '5min',
),
CpsOperationalValue(
icon: Assets.assetsNobodyTime,
description: '10min',
value: '10min',
),
CpsOperationalValue(
icon: Assets.assetsNobodyTime,
description: '30min',
value: '30min',
),
CpsOperationalValue(
icon: Assets.assetsNobodyTime,
description: '1hour',
value: '1hr',
),
];
}
}
final class CpsMovementFunctions extends CpsFunctions {
CpsMovementFunctions({
required super.deviceId,
required super.deviceName,
required super.type,
}) : super(
code: 'body_movement',
operationName: 'Movement',
icon: Assets.motion,
);
@override
List<CpsOperationalValue> getOperationalValues() {
return [
CpsOperationalValue(
description: 'None',
icon: Assets.nobodyTime,
value: 'none',
),
CpsOperationalValue(
description: 'Close To',
icon: Assets.closeToMotion,
value: 'close_to',
),
CpsOperationalValue(
description: 'Far Away',
icon: Assets.farAwayMotion,
value: 'far_away',
),
];
}
}
final class CpsCustomModeFunction extends CpsFunctions {
CpsCustomModeFunction({
required super.deviceId,
required super.deviceName,
required super.type,
}) : super(
code: 'custom_mode',
operationName: 'Custom Mode',
icon: Assets.cpsCustomMode,
);
@override
List<CpsOperationalValue> getOperationalValues() {
return [
CpsOperationalValue(
icon: Assets.cpsMode1,
description: 'Mode 1',
value: 'mode1',
),
CpsOperationalValue(
icon: Assets.cpsMode2,
description: 'Mode 2',
value: 'mode2',
),
CpsOperationalValue(
icon: Assets.cpsMode3,
description: 'Mode 3',
value: 'mode3',
),
CpsOperationalValue(
icon: Assets.cpsMode4,
description: 'Mode 4',
value: 'mode4',
),
];
}
}
final class CpsSpaceTypeFunctions extends CpsFunctions {
CpsSpaceTypeFunctions({
required super.deviceId,
required super.deviceName,
required super.type,
}) : super(
code: 'scene',
operationName: 'Space Type',
icon: Assets.spaceType,
);
@override
List<CpsOperationalValue> getOperationalValues() {
return [
CpsOperationalValue(
description: 'Office',
icon: Assets.office,
value: 'office',
),
CpsOperationalValue(
description: 'Parlour',
icon: Assets.parlour,
value: 'parlour',
),
CpsOperationalValue(
description: 'Bathroom',
icon: Assets.bathroom,
value: 'bathroom',
),
CpsOperationalValue(
description: 'Bedroom',
icon: Assets.bedroom,
value: 'bedroom',
),
CpsOperationalValue(
description: 'DIY',
icon: Assets.dyi,
value: 'dyi',
),
];
}
}
class CpsPresenceStatusFunctions extends CpsFunctions {
CpsPresenceStatusFunctions({
required super.deviceId,
required super.deviceName,
required super.type,
}) : super(
code: 'presence_state',
operationName: 'Presence Status',
icon: Assets.presenceSensor,
);
@override
List<CpsOperationalValue> getOperationalValues() {
return [
CpsOperationalValue(
icon: Assets.nobodyTime,
description: 'None',
value: 'none',
),
CpsOperationalValue(
icon: Assets.presenceState,
description: 'Presence',
value: 'presence',
),
CpsOperationalValue(
icon: Assets.motion,
description: 'Motion',
value: 'motion',
),
];
}
}
final class CpsSportsParaFunction extends CpsFunctions {
CpsSportsParaFunction({
required super.deviceId,
required super.deviceName,
required super.type,
}) : min = 1,
max = 100,
step = 1,
super(
code: 'sports_para',
operationName: 'Sports Para',
icon: Assets.sportsPara,
);
final double min;
final double max;
final double step;
@override
List<CpsOperationalValue> getOperationalValues() {
final count = ((max - min) / step).round() + 1;
return List.generate(
count,
(index) {
final value = (min + (index * step));
return CpsOperationalValue(
icon: Assets.motionMeter,
description: value.toStringAsFixed(0),
value: value,
);
},
);
}
}
@@ -0,0 +1,407 @@
import 'package:syncrow_web/pages/device_managment/flush_mounted_presence_sensor/models/flush_mounted_presence_sensor_model.dart';
import 'package:syncrow_web/pages/routines/models/device_functions.dart';
import 'package:syncrow_web/pages/routines/models/flush/flush_operational_value.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
abstract class FlushFunctions
extends DeviceFunction<FlushMountedPresenceSensorModel> {
final String type;
FlushFunctions({
required super.deviceId,
required super.deviceName,
required super.code,
required super.operationName,
required super.icon,
required this.type,
});
List<FlushOperationalValue> getOperationalValues();
}
class FlushPresenceDelayFunction extends FlushFunctions {
final int min;
FlushPresenceDelayFunction({
required super.deviceId,
required super.deviceName,
required super.type,
}) : min = 0,
super(
code: FlushMountedPresenceSensorModel.codePresenceState,
operationName: 'Presence State',
icon: Assets.presenceStateIcon,
);
@override
List<FlushOperationalValue> getOperationalValues() {
return [
FlushOperationalValue(
icon: Assets.nobodyTime,
description: 'None',
value: "none",
),
FlushOperationalValue(
icon: Assets.presenceStateIcon,
description: 'Presence',
value: 'presence',
),
];
}
}
class FlushSensiReduceFunction extends FlushFunctions {
final int min;
final int max;
final int step;
FlushSensiReduceFunction({
required super.deviceId,
required super.deviceName,
required super.type,
}) : min = 1,
max = 5,
step = 1,
super(
code: FlushMountedPresenceSensorModel.codeSensiReduce,
operationName: 'Sensitivity Reduction',
icon: Assets.motionlessDetectionSensitivityIcon,
);
@override
List<FlushOperationalValue> getOperationalValues() {
return List.generate(
(max - min) ~/ step + 1,
(index) => FlushOperationalValue(
icon: Assets.currentDistanceIcon,
description: '${min + (index * step)}',
value: min + (index * step),
));
}
}
class FlushNoneDelayFunction extends FlushFunctions {
final int min;
final int max;
final String unit;
FlushNoneDelayFunction({
required super.deviceId,
required super.deviceName,
required super.type,
}) : min = 10,
max = 10000,
unit = '秒',
super(
code: FlushMountedPresenceSensorModel.codeNoneDelay,
operationName: 'None Delay',
icon: Assets.nobodyTime,
);
@override
List<FlushOperationalValue> getOperationalValues() {
return [
FlushOperationalValue(
icon: icon,
description: 'Custom $unit',
value: null,
)
];
}
}
class FlushIlluminanceFunction extends FlushFunctions {
final int min;
final int max;
final int step;
FlushIlluminanceFunction({
required super.deviceId,
required super.deviceName,
required super.type,
}) : min = 0,
max = 2000,
step = 0,
super(
code: FlushMountedPresenceSensorModel.codeIlluminance,
operationName: 'Illuminance',
icon: Assets.IlluminanceIcon,
);
@override
List<FlushOperationalValue> getOperationalValues() {
List<FlushOperationalValue> values = [];
for (int lux = min; lux <= max; lux += step) {
values.add(FlushOperationalValue(
icon: Assets.IlluminanceIcon,
description: "$lux Lux",
value: lux,
));
}
return values;
}
}
class FlushOccurDistReduceFunction extends FlushFunctions {
final int min;
final int max;
final int step;
FlushOccurDistReduceFunction({
required super.deviceId,
required super.deviceName,
required super.type,
}) : min = 0,
max = 100,
step = 1,
super(
code: FlushMountedPresenceSensorModel.codeOccurDistReduce,
operationName: 'Occurrence Distance Reduction',
icon: Assets.assetsTempreture,
);
@override
List<FlushOperationalValue> getOperationalValues() {
return List.generate(
(max - min) ~/ step + 1,
(index) => FlushOperationalValue(
icon: Assets.assetsTempreture,
description: '${min + (index * step)}',
value: min + (index * step),
));
}
}
// ==== then functions ====
class FlushSensitivityFunction extends FlushFunctions {
final int min;
final int max;
final int step;
FlushSensitivityFunction({
required super.deviceId,
required super.deviceName,
required super.type,
}) : min = 1,
max = 9,
step = 1,
super(
code: FlushMountedPresenceSensorModel.codeSensitivity,
operationName: 'Sensitivity',
icon: Assets.sensitivity,
);
@override
List<FlushOperationalValue> getOperationalValues() {
return List.generate(
(max - min) ~/ step + 1,
(index) => FlushOperationalValue(
icon: Assets.motionDetectionSensitivityValueIcon,
description: '${min + (index * step)}',
value: min + (index * step),
));
}
}
class FlushNearDetectionFunction extends FlushFunctions {
final int min;
final double max;
final int step;
final String unit;
FlushNearDetectionFunction({
required super.deviceId,
required super.deviceName,
required super.type,
}) : min = 0,
max = 9.5,
step = 1,
unit = 'm',
super(
code: FlushMountedPresenceSensorModel.codeNearDetection,
operationName: 'Nearest Detect Dist',
icon: Assets.currentDistanceIcon,
);
@override
List<FlushOperationalValue> getOperationalValues() {
final values = <FlushOperationalValue>[];
for (var value = min; value <= max; value += step) {
values.add(FlushOperationalValue(
icon: Assets.nobodyTime,
description: '$value $unit',
value: value * 10,
));
}
return values;
}
}
class FlushMaxDetectDistFunction extends FlushFunctions {
final int min;
final int max;
final int step;
final String unit;
FlushMaxDetectDistFunction({
required super.deviceId,
required super.deviceName,
required super.type,
}) : min = 75,
max = 600,
step = 75,
unit = 'cm',
super(
code: FlushMountedPresenceSensorModel.codeFarDetection,
operationName: 'Max Detect Dist',
icon: Assets.currentDistanceIcon,
);
@override
List<FlushOperationalValue> getOperationalValues() {
final values = <FlushOperationalValue>[];
for (var value = min; value <= max; value += step) {
values.add(FlushOperationalValue(
icon: Assets.nobodyTime,
description: '$value $unit',
value: value,
));
}
return values;
}
}
class FlushTargetConfirmTimeFunction extends FlushFunctions {
final int min;
final int max;
final int step;
final String unit;
FlushTargetConfirmTimeFunction({
required super.deviceId,
required super.deviceName,
required super.type,
}) : min = 75,
max = 600,
step = 75,
unit = 'cm',
super(
code: FlushMountedPresenceSensorModel.codePresenceDelay,
operationName: 'Target Confirm Time',
icon: Assets.targetConfirmTimeIcon,
);
@override
List<FlushOperationalValue> getOperationalValues() {
final values = <FlushOperationalValue>[];
for (var value = min; value <= max; value += step) {
values.add(FlushOperationalValue(
icon: Assets.nobodyTime,
description: '$value $unit',
value: value,
));
}
return values;
}
}
class FlushDisappeDelayFunction extends FlushFunctions {
final int min;
final int max;
final int step;
final String unit;
FlushDisappeDelayFunction({
required super.deviceId,
required super.deviceName,
required super.type,
}) : min = 75,
max = 600,
step = 75,
unit = 'cm',
super(
code: FlushMountedPresenceSensorModel.codeNoneDelay,
operationName: 'Disappe Delay',
icon: Assets.DisappeDelayIcon,
);
@override
List<FlushOperationalValue> getOperationalValues() {
final values = <FlushOperationalValue>[];
for (var value = min; value <= max; value += step) {
values.add(FlushOperationalValue(
icon: Assets.nobodyTime,
description: '$value $unit',
value: value,
));
}
return values;
}
}
class FlushIndentLevelFunction extends FlushFunctions {
final int min;
final int max;
final int step;
final String unit;
FlushIndentLevelFunction({
required super.deviceId,
required super.deviceName,
required super.type,
}) : min = 75,
max = 600,
step = 75,
unit = 'cm',
super(
code: FlushMountedPresenceSensorModel.codeOccurDistReduce,
operationName: 'Indent Level',
icon: Assets.indentLevelIcon,
);
@override
List<FlushOperationalValue> getOperationalValues() {
final values = <FlushOperationalValue>[];
for (var value = min; value <= max; value += step) {
values.add(FlushOperationalValue(
icon: Assets.nobodyTime,
description: '$value $unit',
value: value,
));
}
return values;
}
}
class FlushTriggerLevelFunction extends FlushFunctions {
final int min;
final int max;
final int step;
final String unit;
FlushTriggerLevelFunction({
required super.deviceId,
required super.deviceName,
required super.type,
}) : min = 75,
max = 600,
step = 75,
unit = 'cm',
super(
code: FlushMountedPresenceSensorModel.codeSensiReduce,
operationName: 'Trigger Level',
icon: Assets.triggerLevelIcon,
);
@override
List<FlushOperationalValue> getOperationalValues() {
final values = <FlushOperationalValue>[];
for (var value = min; value <= max; value += step) {
values.add(FlushOperationalValue(
icon: Assets.nobodyTime,
description: '$value $unit',
value: value,
));
}
return values;
}
}
@@ -0,0 +1,11 @@
class FlushOperationalValue {
final String icon;
final String description;
final dynamic value;
FlushOperationalValue({
required this.icon,
required this.description,
required this.value,
});
}
@@ -3,7 +3,7 @@ import 'package:syncrow_web/pages/routines/models/gang_switches/switch_operation
import 'package:syncrow_web/utils/constants/assets.dart';
class ThreeGangSwitch1Function extends BaseSwitchFunction {
ThreeGangSwitch1Function({required super.deviceId, required super.deviceName})
ThreeGangSwitch1Function({required super.deviceId, required super.deviceName ,required type})
: super(
code: 'switch_1',
operationName: 'Light 1 Switch',
@@ -26,7 +26,7 @@ class ThreeGangSwitch1Function extends BaseSwitchFunction {
}
class ThreeGangCountdown1Function extends BaseSwitchFunction {
ThreeGangCountdown1Function({required super.deviceId, required super.deviceName})
ThreeGangCountdown1Function({required super.deviceId, required super.deviceName ,required type})
: super(
code: 'countdown_1',
operationName: 'Light 1 Countdown',
@@ -47,7 +47,7 @@ class ThreeGangCountdown1Function extends BaseSwitchFunction {
}
class ThreeGangSwitch2Function extends BaseSwitchFunction {
ThreeGangSwitch2Function({required super.deviceId, required super.deviceName})
ThreeGangSwitch2Function({required super.deviceId, required super.deviceName, required type})
: super(
code: 'switch_2',
operationName: 'Light 2 Switch',
@@ -70,7 +70,7 @@ class ThreeGangSwitch2Function extends BaseSwitchFunction {
}
class ThreeGangCountdown2Function extends BaseSwitchFunction {
ThreeGangCountdown2Function({required super.deviceId, required super.deviceName})
ThreeGangCountdown2Function({required super.deviceId, required super.deviceName ,required type})
: super(
code: 'countdown_2',
operationName: 'Light 2 Countdown',
@@ -91,7 +91,7 @@ class ThreeGangCountdown2Function extends BaseSwitchFunction {
}
class ThreeGangSwitch3Function extends BaseSwitchFunction {
ThreeGangSwitch3Function({required super.deviceId, required super.deviceName})
ThreeGangSwitch3Function({required super.deviceId, required super.deviceName ,required type})
: super(
code: 'switch_3',
operationName: 'Light 3 Switch',
@@ -114,7 +114,7 @@ class ThreeGangSwitch3Function extends BaseSwitchFunction {
}
class ThreeGangCountdown3Function extends BaseSwitchFunction {
ThreeGangCountdown3Function({required super.deviceId, required super.deviceName})
ThreeGangCountdown3Function({required super.deviceId, required super.deviceName ,required type})
: super(
code: 'countdown_3',
operationName: 'Light 3 Countdown',
+111
View File
@@ -0,0 +1,111 @@
import 'package:syncrow_web/pages/routines/models/device_functions.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
class GatewayOperationalValue {
final String icon;
final String description;
final dynamic value;
GatewayOperationalValue({
required this.icon,
required this.description,
required this.value,
});
}
abstract class GatewayFunctions extends DeviceFunction<GatewayOperationalValue> {
final String type;
GatewayFunctions({
required super.deviceId,
required super.deviceName,
required super.code,
required super.operationName,
required super.icon,
required this.type,
});
List<GatewayOperationalValue> getOperationalValues();
}
final class GatewaySwitchAlarmSound extends GatewayFunctions {
GatewaySwitchAlarmSound({
required super.deviceId,
required super.deviceName,
required super.type,
}) : super(
code: 'switch_alarm_sound',
operationName: 'Switch Alarm Sound',
icon: Assets.activeBell,
);
@override
List<GatewayOperationalValue> getOperationalValues() => [
GatewayOperationalValue(
icon: Assets.assetsAcPower,
description: "ON",
value: true,
),
GatewayOperationalValue(
icon: Assets.assetsAcPowerOFF,
description: "OFF",
value: false,
),
];
}
final class GatewayMasterState extends GatewayFunctions {
GatewayMasterState({
required super.deviceId,
required super.deviceName,
required super.type,
}) : super(
code: 'master_state',
operationName: 'Master State',
icon: Assets.gear,
);
@override
List<GatewayOperationalValue> getOperationalValues() {
return [
GatewayOperationalValue(
icon: Assets.assetsAcPower,
description: "Normal",
value: 'normal',
),
GatewayOperationalValue(
icon: Assets.assetsAcPowerOFF,
description: "Alarm",
value: 'alarm',
),
];
}
}
final class GatewayFactoryReset extends GatewayFunctions {
GatewayFactoryReset({
required super.deviceId,
required super.deviceName,
required super.type,
}) : super(
code: 'factory_reset',
operationName: 'Factory Reset',
icon: Assets.factoryReset,
);
@override
List<GatewayOperationalValue> getOperationalValues() {
return [
GatewayOperationalValue(
icon: Assets.assetsAcPower,
description: "ON",
value: true,
),
GatewayOperationalValue(
icon: Assets.assetsAcPowerOFF,
description: "OFF",
value: false,
),
];
}
}
@@ -9,6 +9,9 @@ class ScenesModel {
final String status;
final String type;
final String? icon;
final String spaceName;
final String spaceId;
final String communityId;
ScenesModel({
required this.id,
@@ -16,6 +19,9 @@ class ScenesModel {
required this.name,
required this.status,
required this.type,
required this.spaceName,
required this.spaceId,
required this.communityId,
this.icon,
});
@@ -41,6 +47,9 @@ class ScenesModel {
name: json["name"] ?? '',
status: json["status"] ?? '',
type: json["type"] ?? '',
spaceName: json["spaceName"] ?? '',
spaceId: json["spaceId"] ?? '',
communityId: json["communityId"] ?? '',
icon:
isAutomation == true ? Assets.automation : (json["icon"] as String?),
);
@@ -52,5 +61,8 @@ class ScenesModel {
"name": name,
"status": status,
"type": type,
"spaceName": spaceName,
"spaceId": spaceId,
"communityId": communityId,
};
}
@@ -0,0 +1,289 @@
import 'package:syncrow_web/pages/device_managment/wall_sensor/model/wall_sensor_model.dart';
import 'package:syncrow_web/pages/routines/models/device_functions.dart';
import 'package:syncrow_web/pages/routines/models/wps/wps_operational_value.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
abstract class WpsFunctions extends DeviceFunction<WallSensorModel> {
final String type;
WpsFunctions({
required super.deviceId,
required super.deviceName,
required super.code,
required super.operationName,
required super.icon,
required this.type,
});
List<WpsOperationalValue> getOperationalValues();
}
// For far_detection (75-600cm in 75cm steps)
class FarDetectionFunction extends WpsFunctions {
final int min;
final int max;
final int step;
final String unit;
FarDetectionFunction(
{required super.deviceId, required super.deviceName, required type})
: min = 75,
max = 600,
step = 75,
unit = 'cm',
super(
type: type,
code: 'far_detection',
operationName: 'Far Detection',
icon: Assets.farDetectionIcon,
);
@override
List<WpsOperationalValue> getOperationalValues() {
final values = <WpsOperationalValue>[];
for (var value = min; value <= max; value += step) {
values.add(WpsOperationalValue(
icon: Assets.currentDistanceIcon,
description: '$value $unit',
value: value,
));
}
return values;
}
}
// For presence_time (0-65535 minutes)
class PresenceTimeFunction extends WpsFunctions {
final int min;
final int max;
final int step;
final String unit;
PresenceTimeFunction(
{required super.deviceId, required super.deviceName, required type})
: min = 0,
max = 65535,
step = 1,
unit = 'Min',
super(
type: type,
code: 'presence_time',
operationName: 'Presence Time',
icon: Assets.presenceTimeIcon,
);
@override
List<WpsOperationalValue> getOperationalValues() {
return [
WpsOperationalValue(
icon: icon,
description: 'Custom $unit',
value: null,
)
];
}
}
// For motion_sensitivity_value (1-5 levels)
class MotionSensitivityFunction extends WpsFunctions {
final int min;
final int max;
final int step;
MotionSensitivityFunction(
{required super.deviceId, required super.deviceName, required type})
: min = 1,
max = 5,
step = 1,
super(
type: type,
code: 'motion_sensitivity_value',
operationName: 'Motion Detection Sensitivity',
icon: Assets.motionDetectionSensitivityIcon,
);
@override
List<WpsOperationalValue> getOperationalValues() {
return List.generate(
(max - min) ~/ step + 1,
(index) => WpsOperationalValue(
icon: Assets.motionDetectionSensitivityValueIcon,
description: '${min + (index * step)}',
value: min + (index * step),
),
);
}
}
class MotionLessSensitivityFunction extends WpsFunctions {
final int min;
final int max;
final int step;
MotionLessSensitivityFunction(
{required super.deviceId, required super.deviceName, required type})
: min = 1,
max = 5,
step = 1,
super(
type: type,
code: 'motionless_sensitivity',
operationName: 'Motionless Detection Sensitivity',
icon: Assets.motionlessDetectionSensitivityIcon,
);
@override
List<WpsOperationalValue> getOperationalValues() {
return List.generate(
(max - min) ~/ step + 1,
(index) => WpsOperationalValue(
icon: Assets.currentDistanceIcon,
description: '${min + (index * step)}',
value: min + (index * step),
),
);
}
}
class IndicatorFunction extends WpsFunctions {
IndicatorFunction(
{required super.deviceId, required super.deviceName, required type})
: super(
type: type,
code: 'indicator',
operationName: 'Indicator',
icon: Assets.IndicatorIcon,
);
@override
List<WpsOperationalValue> getOperationalValues() => [
WpsOperationalValue(
icon: Assets.assetsAcPower,
description: "ON",
value: true,
),
WpsOperationalValue(
icon: Assets.assetsAcPowerOFF,
description: "OFF",
value: false,
),
];
}
class NoOneTimeFunction extends WpsFunctions {
final int min;
final int max;
final String unit;
NoOneTimeFunction(
{required super.deviceId, required super.deviceName, required type})
: min = 10,
max = 10000,
unit = '秒',
super(
type: type,
code: 'no_one_time',
operationName: 'Nobody Time',
icon: Assets.nobodyTime,
);
@override
List<WpsOperationalValue> getOperationalValues() {
return [
WpsOperationalValue(
icon: icon,
description: 'Custom $unit',
value: null,
)
];
}
}
class PresenceStateFunction extends WpsFunctions {
PresenceStateFunction(
{required super.deviceId, required super.deviceName, required type})
: super(
type: type,
code: 'presence_state',
operationName: 'Presence State',
icon: Assets.presenceStateIcon,
);
@override
List<WpsOperationalValue> getOperationalValues() => [
WpsOperationalValue(
icon: Assets.assetsAcPower,
description: "None",
value: 'none',
),
WpsOperationalValue(
icon: Assets.presenceStateIcon,
description: "Presence",
value: 'presence',
),
];
}
class CurrentDistanceFunction extends WpsFunctions {
final int min;
final int max;
final int step;
CurrentDistanceFunction(
{required super.deviceId, required super.deviceName, required type})
: min = 1,
max = 600,
step = 1,
super(
type: type,
code: 'dis_current',
operationName: 'Current Distance',
icon: Assets.currentDistanceIcon,
);
@override
List<WpsOperationalValue> getOperationalValues() {
List<WpsOperationalValue> values = [];
for (int cm = min; cm <= max; cm += step) {
values.add(WpsOperationalValue(
icon: Assets.assetsTempreture,
description: "${cm}CM",
value: cm,
));
}
return values;
}
}
class IlluminanceValueFunction extends WpsFunctions {
final int min;
final int max;
final int step;
IlluminanceValueFunction({
required super.deviceId,
required super.deviceName,
required super.type,
}) : min = 0,
max = 10000,
step = 10,
super(
code: 'illuminance_value',
operationName: 'Illuminance Value',
icon: Assets.IlluminanceIcon,
);
@override
List<WpsOperationalValue> getOperationalValues() {
List<WpsOperationalValue> values = [];
for (int lux = min; lux <= max; lux += step) {
values.add(WpsOperationalValue(
icon: Assets.IlluminanceIcon,
description: "$lux Lux",
value: lux,
));
}
return values;
}
}
@@ -0,0 +1,11 @@
class WpsOperationalValue {
final String icon;
final String description;
final dynamic value;
WpsOperationalValue({
required this.icon,
required this.description,
required this.value,
});
}
+66 -65
View File
@@ -1,12 +1,15 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/routines/bloc/create_routine_bloc/create_routine_bloc.dart';
import 'package:syncrow_web/pages/routines/bloc/create_routine_bloc/create_routine_event.dart';
import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routines/create_new_routines/create_new_routines.dart';
import 'package:syncrow_web/pages/routines/view/create_new_routine_view.dart';
import 'package:syncrow_web/pages/routines/widgets/main_routine_view/fetch_routine_scenes_automation.dart';
import 'package:syncrow_web/pages/routines/widgets/main_routine_view/routine_view_card.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
import 'package:syncrow_web/pages/space_tree/view/space_tree_view.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
class RoutinesView extends StatefulWidget {
const RoutinesView({super.key});
@@ -16,10 +19,21 @@ class RoutinesView extends StatefulWidget {
}
class _RoutinesViewState extends State<RoutinesView> {
@override
void initState() {
super.initState();
// context.read<RoutineBloc>().add(FetchDevicesInRoutine());
void _handleRoutineCreation(BuildContext context) async {
final result = await showDialog<Map<String, dynamic>>(
context: context,
builder: (context) => const CreateNewRoutinesDialog(),
);
if (result == null) return;
final communityId = result['community'];
final spaceId = result['space'];
final bloc = BlocProvider.of<CreateRoutineBloc>(context);
final routineBloc = context.read<RoutineBloc>();
bloc.add(SaveCommunityIdAndSpaceIdEvent(
communityID: communityId, spaceID: spaceId));
await Future.delayed(const Duration(seconds: 1));
routineBloc.add(const CreateNewRoutineViewEvent(createRoutineView: true));
}
@override
@@ -29,73 +43,60 @@ class _RoutinesViewState extends State<RoutinesView> {
if (state.createRoutineView) {
return const CreateNewRoutineView();
}
return Row(
children: [
Expanded(child: SpaceTreeView(
onSelect: () {
context.read<RoutineBloc>()
Expanded(
child: SpaceTreeView(
onSelect: () => context.read<RoutineBloc>()
..add(const LoadScenes())
..add(const LoadAutomation());
},
)),
..add(const LoadAutomation())
..add(FetchDevicesInRoutine()),
),
),
Expanded(
flex: 4,
child: ListView(children: [
Container(
padding: const EdgeInsets.all(16),
height: MediaQuery.sizeOf(context).height,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
"Create New Routines",
style: Theme.of(context).textTheme.titleLarge?.copyWith(
color: ColorsManager.grayColor,
fontWeight: FontWeight.bold,
),
),
const SizedBox(
height: 10,
),
RoutineViewCard(
onTap: () {
if (context.read<SpaceTreeBloc>().state.selectedCommunities.length == 1 &&
context.read<SpaceTreeBloc>().state.selectedSpaces.length == 1) {
context.read<RoutineBloc>().add(
(ResetRoutineState()),
);
BlocProvider.of<RoutineBloc>(context).add(
const CreateNewRoutineViewEvent(createRoutineView: true),
);
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
context.read<SpaceTreeBloc>().state.selectedSpaces.isEmpty
? 'Please select a space'
: 'Please select only one space to proceed'),
),
);
// CustomSnackBar.redSnackBar(
// context.read<SpaceTreeBloc>().state.selectedSpaces.isEmpty
// ? 'Please select a space'
// : 'Please select only one space to proceed');
}
},
icon: Icons.add,
textString: '',
),
const SizedBox(
height: 15,
),
const Expanded(child: FetchRoutineScenesAutomation()),
],
child: SizedBox(
height: context.screenHeight,
width: context.screenWidth,
child: SingleChildScrollView(
padding: const EdgeInsetsDirectional.all(16),
child: Padding(
padding: const EdgeInsets.only(left: 20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
spacing: 16,
children: [
Text(
"Create New Routines",
style:
Theme.of(context).textTheme.titleLarge?.copyWith(
color: ColorsManager.grayColor,
fontWeight: FontWeight.bold,
),
),
RoutineViewCard(
isLoading: false,
onChanged: (v) {},
status: '',
spaceId: '',
automationId: '',
communityId: '',
sceneId: '',
cardType: '',
spaceName: '',
onTap: () => _handleRoutineCreation(context),
icon: Icons.add,
textString: '',
),
const FetchRoutineScenesAutomation(),
],
),
),
),
]),
),
),
)
],
);
},
@@ -0,0 +1,33 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class ConditionToggle extends StatelessWidget {
final String? currentCondition;
final void Function(String condition) onChanged;
const ConditionToggle({
required this.onChanged,
this.currentCondition,
super.key,
});
static const _conditions = ["<", "==", ">"];
@override
Widget build(BuildContext context) {
return ToggleButtons(
onPressed: (index) => onChanged(_conditions[index]),
borderRadius: const BorderRadius.all(Radius.circular(8)),
selectedBorderColor: ColorsManager.primaryColorWithOpacity,
selectedColor: Colors.white,
fillColor: ColorsManager.primaryColorWithOpacity,
color: ColorsManager.primaryColorWithOpacity,
constraints: const BoxConstraints(
minHeight: 40.0,
minWidth: 40.0,
),
isSelected: _conditions.map((c) => c == (currentCondition ?? "==")).toList(),
children: _conditions.map((c) => Text(c)).toList(),
);
}
}
+38 -32
View File
@@ -8,12 +8,12 @@ class DialogFooter extends StatelessWidget {
final int? dialogWidth;
const DialogFooter({
Key? key,
super.key,
required this.onCancel,
required this.onConfirm,
required this.isConfirmEnabled,
this.dialogWidth,
}) : super(key: key);
});
@override
Widget build(BuildContext context) {
@@ -28,46 +28,52 @@ class DialogFooter extends StatelessWidget {
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Expanded(
child: _buildFooterButton(
context,
'Cancel',
onCancel,
),
DialogFooterButton(
text: 'Cancel',
onTap: onCancel,
),
if (isConfirmEnabled) ...[
Container(width: 1, height: 50, color: ColorsManager.greyColor),
Expanded(
child: _buildFooterButton(
context,
'Confirm',
onConfirm,
),
DialogFooterButton(
text: 'Confirm',
onTap: onConfirm,
textColor: isConfirmEnabled
? ColorsManager.primaryColorWithOpacity
: Colors.red,
),
],
],
),
);
}
}
Widget _buildFooterButton(
BuildContext context,
String text,
VoidCallback? onTap,
) {
return GestureDetector(
onTap: onTap,
child: SizedBox(
height: 50,
child: Center(
child: Text(
text,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: text == 'Confirm'
? ColorsManager.primaryColorWithOpacity
: ColorsManager.textGray,
),
),
class DialogFooterButton extends StatelessWidget {
const DialogFooterButton({
required this.text,
required this.onTap,
this.textColor,
super.key,
});
final String text;
final VoidCallback? onTap;
final Color? textColor;
@override
Widget build(BuildContext context) {
return Expanded(
child: TextButton(
style: TextButton.styleFrom(
foregroundColor: ColorsManager.primaryColorWithOpacity,
disabledForegroundColor: ColorsManager.primaryColor,
),
onPressed: onTap,
child: Text(
text,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: textColor ?? ColorsManager.textGray,
),
),
),
);
@@ -16,6 +16,7 @@ class DialogHeader extends StatelessWidget {
),
Text(
title,
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: ColorsManager.primaryColorWithOpacity,
fontWeight: FontWeight.bold,
+67 -4
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/models/device_functions.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';
class DraggableCard extends StatelessWidget {
@@ -68,9 +69,9 @@ class DraggableCard extends StatelessWidget {
Card(
color: ColorsManager.whiteColors,
child: Container(
padding: padding ?? const EdgeInsets.all(16),
padding: const EdgeInsets.all(16),
width: 110,
height: deviceFunctions.isEmpty ? 123 : null,
height: deviceFunctions.isEmpty ? 160 : 180,
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
@@ -78,6 +79,7 @@ class DraggableCard extends StatelessWidget {
children: [
Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Container(
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)
...deviceFunctions.map((function) => Row(
mainAxisSize: MainAxisSize.min,
@@ -123,7 +186,7 @@ class DraggableCard extends StatelessWidget {
'${function.operationName}: ${_formatFunctionValue(function)}',
style: context.textTheme.bodySmall?.copyWith(
fontSize: 9,
color: ColorsManager.textGray,
color: ColorsManager.lightGreyColor,
height: 1.2,
),
maxLines: 2,
@@ -162,7 +225,7 @@ class DraggableCard extends StatelessWidget {
if (function.functionCode == 'temp_set' || function.functionCode == 'temp_current') {
return '${(function.value / 10).toStringAsFixed(0)}°C';
} else if (function.functionCode.contains('countdown')) {
final seconds = function.value.toInt();
final seconds = function.value?.toInt() ?? 0;
if (seconds >= 3600) {
final hours = (seconds / 3600).floor();
final remainingMinutes = ((seconds % 3600) / 60).floor();
@@ -0,0 +1,35 @@
import 'package:flutter/material.dart';
class FunctionSlider extends StatelessWidget {
final dynamic initialValue;
final (double min, double max) range;
final void Function(double value) onChanged;
final double dividendOfRange;
const FunctionSlider({
required this.onChanged,
required this.initialValue,
required this.range,
required this.dividendOfRange,
super.key,
});
@override
Widget build(BuildContext context) {
final (min, max) = range;
final bool isValidRange = max > min;
final double value = initialValue is int
? (initialValue as int).toDouble()
: (initialValue as double);
final int? divisions = isValidRange ? ((max - min) / dividendOfRange).round() : null;
return Slider(
value: value.clamp(min, max),
min: min,
max: max,
divisions: divisions,
onChanged: isValidRange ? onChanged : null,
);
}
}
+106 -71
View File
@@ -17,81 +17,102 @@ class IfContainer extends StatelessWidget {
builder: (context, state) {
return DragTarget<Map<String, dynamic>>(
builder: (context, candidateData, rejectedData) {
return Container(
width: double.infinity,
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
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,
return SingleChildScrollView(
child: Container(
width: double.infinity,
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
DraggableCard(
imagePath: Assets.tabToRun,
title: 'Tab to run',
deviceData: {},
),
const Text('IF',
style: TextStyle(
fontSize: 18, fontWeight: FontWeight.bold)),
if (state.isAutomation && state.ifItems.isNotEmpty)
AutomationOperatorSelector(
selectedOperator:
state.selectedAutomationOperator),
],
),
if (!state.isTabToRun)
Wrap(
spacing: 8,
runSpacing: 8,
children: List.generate(
state.ifItems.length,
(index) => GestureDetector(
onTap: () async {
if (!state.isTabToRun) {
final result = await DeviceDialogHelper.showDeviceDialog(
context, state.ifItems[index],
removeComparetors: false);
const SizedBox(height: 16),
if (state.isTabToRun)
const Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
DraggableCard(
imagePath: Assets.tabToRun,
title: 'Tab to run',
deviceData: {},
),
],
),
if (!state.isTabToRun)
Wrap(
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) {
context
.read<RoutineBloc>()
.add(AddToIfContainer(state.ifItems[index], false));
} else if (!['AC', '1G', '2G', '3G']
.contains(state.ifItems[index]['productType'])) {
context
.read<RoutineBloc>()
.add(AddToIfContainer(state.ifItems[index], false));
if (result != null) {
context.read<RoutineBloc>().add(
AddToIfContainer(
state.ifItems[index], false));
} else if (![
'AC',
'1G',
'2G',
'3G',
'WPS',
'GW',
'CPS',
'NCPS'
].contains(state.ifItems[index]
['productType'])) {
context.read<RoutineBloc>().add(
AddToIfContainer(
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']));
},
),
)),
),
],
),
),
);
},
onAcceptWithDetails: (data) async {
final uniqueCustomId = const Uuid().v4();
final mutableData = Map<String, dynamic>.from(data.data);
mutableData['uniqueCustomId'] = uniqueCustomId;
@@ -101,15 +122,25 @@ class IfContainer extends StatelessWidget {
if (!state.isTabToRun) {
if (mutableData['deviceId'] == 'tab_to_run') {
context.read<RoutineBloc>().add(AddToIfContainer(mutableData, true));
context
.read<RoutineBloc>()
.add(AddToIfContainer(mutableData, true));
} else {
final result = await DeviceDialogHelper.showDeviceDialog(context, mutableData,
final result = await DeviceDialogHelper.showDeviceDialog(
dialogType: 'IF',
context: context,
data: mutableData,
removeComparetors: false);
if (result != null) {
context.read<RoutineBloc>().add(AddToIfContainer(mutableData, false));
} else if (!['AC', '1G', '2G', '3G'].contains(mutableData['productType'])) {
context.read<RoutineBloc>().add(AddToIfContainer(mutableData, false));
context
.read<RoutineBloc>()
.add(AddToIfContainer(mutableData, false));
} else if (!['AC', '1G', '2G', '3G', 'WPS', 'GW', 'CPS', 'NCPS']
.contains(mutableData['productType'])) {
context
.read<RoutineBloc>()
.add(AddToIfContainer(mutableData, false));
}
}
}
@@ -155,7 +186,9 @@ class AutomationOperatorSelector extends StatelessWidget {
),
),
onPressed: () {
context.read<RoutineBloc>().add(const ChangeAutomationOperator(operator: 'or'));
context
.read<RoutineBloc>()
.add(const ChangeAutomationOperator(operator: 'or'));
},
),
Container(
@@ -181,7 +214,9 @@ class AutomationOperatorSelector extends StatelessWidget {
),
),
onPressed: () {
context.read<RoutineBloc>().add(const ChangeAutomationOperator(operator: 'and'));
context
.read<RoutineBloc>()
.add(const ChangeAutomationOperator(operator: 'and'));
},
),
],
@@ -1,139 +1,195 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/routines/bloc/automation_scene_trigger_bloc/automation_status_update.dart';
import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routines/widgets/main_routine_view/routine_view_card.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
class FetchRoutineScenesAutomation extends StatefulWidget {
const FetchRoutineScenesAutomation({super.key});
@override
State<FetchRoutineScenesAutomation> createState() => _FetchRoutineScenesState();
}
class _FetchRoutineScenesState extends State<FetchRoutineScenesAutomation>
class FetchRoutineScenesAutomation extends StatelessWidget
with HelperResponsiveLayout {
@override
void initState() {
super.initState();
}
const FetchRoutineScenesAutomation({super.key});
@override
Widget build(BuildContext context) {
return BlocBuilder<RoutineBloc, RoutineState>(
builder: (context, state) {
return state.isLoading
? const Center(
child: CircularProgressIndicator(),
)
: Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
"Scenes (Tab to Run)",
style: Theme.of(context).textTheme.titleLarge?.copyWith(
color: ColorsManager.grayColor,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 10),
if (state.scenes.isEmpty)
Text(
"No scenes found",
style: context.textTheme.bodyMedium?.copyWith(
color: ColorsManager.grayColor,
),
),
if (state.scenes.isNotEmpty)
ConstrainedBox(
constraints: BoxConstraints(
maxHeight: isSmallScreenSize(context) ? 160 : 170,
maxWidth: MediaQuery.sizeOf(context).width * 0.7),
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: state.scenes.length,
itemBuilder: (context, index) => Padding(
padding: EdgeInsets.only(
right: isSmallScreenSize(context) ? 4.0 : 8.0,
),
child: RoutineViewCard(
onTap: () {
BlocProvider.of<RoutineBloc>(context).add(
const CreateNewRoutineViewEvent(createRoutineView: true),
);
context.read<RoutineBloc>().add(
GetSceneDetails(
sceneId: state.scenes[index].id,
isTabToRun: true,
isUpdate: true,
),
);
},
textString: state.scenes[index].name,
icon: state.scenes[index].icon ?? Assets.logoHorizontal,
isFromScenes: true,
iconInBytes: state.scenes[index].iconInBytes,
),
),
),
),
const SizedBox(height: 15),
Text(
"Automations",
style: Theme.of(context).textTheme.titleLarge?.copyWith(
color: ColorsManager.grayColor,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 10),
if (state.automations.isEmpty)
Text(
"No automations found",
style: context.textTheme.bodyMedium?.copyWith(
color: ColorsManager.grayColor,
),
),
if (state.automations.isNotEmpty)
ConstrainedBox(
constraints: BoxConstraints(
maxHeight: isSmallScreenSize(context) ? 160 : 170,
maxWidth: MediaQuery.sizeOf(context).width * 0.7),
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: state.automations.length,
itemBuilder: (context, index) => Padding(
padding: EdgeInsets.only(
right: isSmallScreenSize(context) ? 4.0 : 8.0,
),
child: RoutineViewCard(
onTap: () {
BlocProvider.of<RoutineBloc>(context).add(
const CreateNewRoutineViewEvent(createRoutineView: true),
);
context.read<RoutineBloc>().add(
GetAutomationDetails(
automationId: state.automations[index].id,
isAutomation: true,
isUpdate: true),
);
},
textString: state.automations[index].name,
icon: state.automations[index].icon ?? Assets.automation,
),
),
),
),
],
if (state.isLoading)
return const Center(child: CircularProgressIndicator());
return SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
_buildListTitle(context, "Scenes (Tab to Run)"),
const SizedBox(height: 10),
Visibility(
visible: state.scenes.isNotEmpty,
replacement: _buildEmptyState(context, "No scenes found"),
child: SizedBox(
height: 200,
child: _buildScenes(state),
),
),
);
const SizedBox(height: 10),
_buildListTitle(context, "Automations"),
const SizedBox(height: 3),
Visibility(
visible: state.automations.isNotEmpty,
replacement:
_buildEmptyState(context, "No automations found"),
child: SizedBox(
height: 200,
child: _buildAutomations(state),
),
)
],
),
),
);
},
);
}
Widget _buildAutomations(RoutineState state) {
return ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: state.automations.length,
itemBuilder: (context, index) {
final isLoading =
state.automations.contains(state.automations[index].id);
return Column(
children: [
Padding(
padding: EdgeInsets.only(
right: isSmallScreenSize(context) ? 4.0 : 8.0,
),
child: RoutineViewCard(
isLoading: isLoading,
onChanged: (v) {
context.read<RoutineBloc>().add(
UpdateAutomationStatus(
automationId: state.automations[index].id,
automationStatusUpdate: AutomationStatusUpdate(
spaceUuid: state.automations[index].spaceId,
isEnable: v,
),
communityId: state.automations[index].communityId,
),
);
},
status: state.automations[index].status,
communityId: '',
spaceId: state.automations[index].spaceId,
sceneId: '',
automationId: state.automations[index].id,
cardType: 'automations',
spaceName: state.automations[index].spaceName,
onTap: () {
BlocProvider.of<RoutineBloc>(context).add(
const CreateNewRoutineViewEvent(
createRoutineView: true,
),
);
context.read<RoutineBloc>().add(
GetAutomationDetails(
automationId: state.automations[index].id,
isAutomation: true,
isUpdate: true,
),
);
},
textString: state.automations[index].name,
icon: state.automations[index].icon ?? Assets.automation,
),
),
],
);
},
);
}
Widget _buildScenes(RoutineState state) {
return ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: state.scenes.length,
itemBuilder: (context, index) {
final scene = state.scenes[index];
final isLoading = state.loadingSceneId == scene.id;
return Padding(
padding: EdgeInsets.only(
right: isSmallScreenSize(context) ? 4.0 : 8.0,
),
child: Column(
children: [
RoutineViewCard(
isLoading: isLoading,
sceneOnTap: () {
context.read<RoutineBloc>().add(
SceneTrigger(
sceneId: scene.id,
name: scene.name,
),
);
},
status: state.scenes[index].status,
communityId: state.scenes[index].communityId,
spaceId: state.scenes[index].spaceId,
sceneId: state.scenes[index].sceneTuyaId!,
automationId: state.scenes[index].id,
cardType: 'scenes',
spaceName: state.scenes[index].spaceName,
onTap: () {
BlocProvider.of<RoutineBloc>(context).add(
const CreateNewRoutineViewEvent(
createRoutineView: true,
),
);
context.read<RoutineBloc>().add(
GetSceneDetails(
sceneId: state.scenes[index].id,
isTabToRun: true,
isUpdate: true,
),
);
},
textString: state.scenes[index].name,
icon: state.scenes[index].icon ?? Assets.logoHorizontal,
isFromScenes: true,
iconInBytes: state.scenes[index].iconInBytes,
),
],
),
);
});
}
Widget _buildListTitle(BuildContext context, String title) {
return Text(
title,
style: Theme.of(context).textTheme.titleLarge?.copyWith(
color: ColorsManager.grayColor,
fontWeight: FontWeight.bold,
),
);
}
Widget _buildEmptyState(BuildContext context, String title) {
return Padding(
padding: const EdgeInsets.only(bottom: 100),
child: Text(
title,
style: context.textTheme.bodyMedium?.copyWith(
color: ColorsManager.grayColor,
),
),
);
}
}
@@ -1,3 +1,6 @@
import 'dart:async';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_svg/flutter_svg.dart';
@@ -6,37 +9,77 @@ import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
class RoutineViewCard extends StatelessWidget with HelperResponsiveLayout {
class RoutineViewCard extends StatefulWidget with HelperResponsiveLayout {
const RoutineViewCard({
super.key,
required this.onTap,
this.sceneOnTap,
required this.icon,
required this.textString,
required this.spaceName,
required this.cardType,
this.isFromScenes,
this.iconInBytes,
required this.sceneId,
required this.communityId,
required this.spaceId,
required this.automationId,
required this.status,
this.onChanged,
required this.isLoading,
});
final Function() onTap;
final Function()? sceneOnTap;
final dynamic icon;
final String textString;
final String spaceName;
final String cardType;
final String sceneId;
final String spaceId;
final String status;
final bool isLoading;
final void Function(bool)? onChanged;
final String automationId;
final String communityId;
final bool? isFromScenes;
final Uint8List? iconInBytes;
@override
State<RoutineViewCard> createState() => _RoutineViewCardState();
}
class _RoutineViewCardState extends State<RoutineViewCard> {
bool _showTemporaryCheck = false;
void _handleSceneTap() {
if (!_showTemporaryCheck) {
setState(() => _showTemporaryCheck = true);
widget.sceneOnTap?.call();
Timer(const Duration(seconds: 3), () {
if (mounted) setState(() => _showTemporaryCheck = false);
});
}
}
@override
Widget build(BuildContext context) {
final double cardWidth = isSmallScreenSize(context)
final double cardWidth = widget.isSmallScreenSize(context)
? 120
: isMediumScreenSize(context)
: widget.isMediumScreenSize(context)
? 135
: 150;
final double cardHeight = isSmallScreenSize(context) ? 160 : 170;
final double cardHeight = widget.isSmallScreenSize(context) ? 190 : 200;
final double iconSize = isSmallScreenSize(context)
? 50
: isMediumScreenSize(context)
? 60
: 70;
final double iconSize = widget.isSmallScreenSize(context)
? 70
: widget.isMediumScreenSize(context)
? 80
: 90;
return ConstrainedBox(
constraints: BoxConstraints(
@@ -50,70 +93,145 @@ class RoutineViewCard extends StatelessWidget with HelperResponsiveLayout {
),
color: ColorsManager.whiteColors,
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(10),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Center(
child: Container(
decoration: BoxDecoration(
color: ColorsManager.graysColor,
borderRadius: BorderRadius.circular(120),
border: Border.all(
color: ColorsManager.greyColor,
width: 2.0,
widget.cardType != ''
? Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
if (widget.isFromScenes ?? false)
InkWell(
onTap: _handleSceneTap,
child: Image.asset(
_showTemporaryCheck
? Assets.scenesPlayIcon
: Assets.scenesPlayIconCheck,
fit: BoxFit.contain,
),
)
else if (widget.isLoading)
const SizedBox(
width: 49,
height: 20,
child: Center(
child: SizedBox(
width: 16,
height: 16,
child: CircularProgressIndicator(strokeWidth: 2),
),
),
)
else
CupertinoSwitch(
activeTrackColor: ColorsManager.primaryColor,
value: widget.status == 'enable',
onChanged: widget.onChanged,
)
],
)
: const SizedBox(),
Column(
children: [
Center(
child: InkWell(
customBorder: const CircleBorder(),
onTap: widget.onTap,
child: Container(
decoration: BoxDecoration(
color: ColorsManager.graysColor,
shape: BoxShape.circle,
border: Border.all(
color: ColorsManager.greyColor,
width: 2.0,
),
),
height: iconSize,
width: iconSize,
child: (widget.isFromScenes ?? false)
? (widget.iconInBytes != null &&
widget.iconInBytes?.isNotEmpty == true)
? Image.memory(
widget.iconInBytes!,
height: iconSize,
width: iconSize,
fit: BoxFit.contain,
errorBuilder: (context, error, stackTrace) =>
Image.asset(
Assets.logo,
height: iconSize,
width: iconSize,
fit: BoxFit.contain,
),
)
: Image.asset(
Assets.logo,
height: iconSize,
width: iconSize,
fit: BoxFit.contain,
)
: (widget.icon is String &&
widget.icon.endsWith('.svg'))
? SvgPicture.asset(
height: iconSize,
width: iconSize,
widget.icon,
fit: BoxFit.contain,
)
: Icon(
widget.icon,
color: ColorsManager.dialogBlueTitle,
size: widget.isSmallScreenSize(context)
? 30
: 40,
),
),
),
),
height: iconSize,
width: iconSize,
child: (isFromScenes ?? false)
? (iconInBytes != null && iconInBytes?.isNotEmpty == true)
? Image.memory(
iconInBytes!,
height: iconSize,
width: iconSize,
fit: BoxFit.contain,
errorBuilder: (context, error, stackTrace) => Image.asset(
Assets.logo,
height: iconSize,
width: iconSize,
const SizedBox(height: 8),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 3),
child: Column(
children: [
Text(
widget.textString,
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
maxLines: 1,
style: context.textTheme.bodySmall?.copyWith(
color: ColorsManager.blackColor,
fontSize: widget.isSmallScreenSize(context) ? 10 : 12,
),
),
if (widget.spaceName != '')
Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
SvgPicture.asset(
Assets.spaceLocationIcon,
fit: BoxFit.contain,
),
)
: Image.asset(
Assets.logo,
height: iconSize,
width: iconSize,
fit: BoxFit.contain,
)
: (icon is String && icon.endsWith('.svg'))
? SvgPicture.asset(
icon,
fit: BoxFit.contain,
)
: Icon(
icon,
color: ColorsManager.dialogBlueTitle,
size: isSmallScreenSize(context) ? 30 : 40,
),
),
),
const SizedBox(height: 8),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 3),
child: Text(
textString,
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
maxLines: 2,
style: context.textTheme.bodySmall?.copyWith(
color: ColorsManager.blackColor,
fontSize: isSmallScreenSize(context) ? 10 : 12,
Text(
widget.spaceName,
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
maxLines: 1,
style: context.textTheme.bodySmall?.copyWith(
color: ColorsManager.blackColor,
fontSize:
widget.isSmallScreenSize(context) ? 10 : 12,
),
),
],
),
],
),
),
),
],
),
],
),
+39 -31
View File
@@ -1,6 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routines/widgets/dragable_card.dart';
@@ -18,6 +17,17 @@ class _RoutineDevicesState extends State<RoutineDevices> {
context.read<RoutineBloc>().add(FetchDevicesInRoutine());
}
static const _allowedProductTypes = {
'AC',
'1G',
'2G',
'3G',
'WPS',
'GW',
'CPS',
'NCPS'
};
@override
Widget build(BuildContext context) {
return BlocBuilder<RoutineBloc, RoutineState>(
@@ -32,12 +42,9 @@ class _RoutineDevicesState extends State<RoutineDevices> {
}
});
List<AllDevicesModel> deviceList = state.devices
.where((device) =>
device.productType == 'AC' ||
device.productType == '1G' ||
device.productType == '2G' ||
device.productType == '3G')
final deviceList = state.devices
.where(
(device) => _allowedProductTypes.contains(device.productType))
.toList();
return Wrap(
@@ -45,35 +52,36 @@ class _RoutineDevicesState extends State<RoutineDevices> {
runSpacing: 10,
children: deviceList.asMap().entries.map((entry) {
final device = entry.value;
final deviceData = {
'device': device,
'imagePath': device.getDefaultIcon(device.productType),
'title': device.name ?? '',
'deviceId': device.uuid,
'productType': device.productType,
'functions': device.functions,
'uniqueCustomId': '',
'tag': device.deviceTags!.isNotEmpty
? device.deviceTags![0].name
: '',
'subSpace': device.deviceSubSpace?.subspaceName ?? '',
};
if (state.searchText != null && state.searchText!.isNotEmpty) {
return device.name!.toLowerCase().contains(state.searchText!.toLowerCase())
return device.name!
.toLowerCase()
.contains(state.searchText!.toLowerCase())
? DraggableCard(
imagePath: device.getDefaultIcon(device.productType),
title: device.name ?? '',
deviceData: {
'device': device,
'imagePath': device.getDefaultIcon(device.productType),
'title': device.name ?? '',
'deviceId': device.uuid,
'productType': device.productType,
'functions': device.functions,
'uniqueCustomId': '',
},
imagePath: deviceData['imagePath'] as String,
title: deviceData['title'] as String,
deviceData: deviceData,
)
: Container();
: const SizedBox.shrink();
} else {
return DraggableCard(
imagePath: device.getDefaultIcon(device.productType),
title: device.name ?? '',
deviceData: {
'device': device,
'imagePath': device.getDefaultIcon(device.productType),
'title': device.name ?? '',
'deviceId': device.uuid,
'productType': device.productType,
'functions': device.functions,
'uniqueCustomId': '',
},
imagePath: deviceData['imagePath'] as String,
title: deviceData['title'] as String,
deviceData: deviceData,
);
}
}).toList(),
@@ -0,0 +1,42 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
class RoutineDialogFunctionListTile extends StatelessWidget {
const RoutineDialogFunctionListTile({
super.key,
required this.iconPath,
required this.operationName,
required this.onTap,
});
final String iconPath;
final String operationName;
final void Function() onTap;
@override
Widget build(BuildContext context) {
return ListTile(
leading: SvgPicture.asset(
iconPath,
width: 24,
height: 24,
placeholderBuilder: (context) => const SizedBox(
width: 24,
height: 24,
),
),
title: Text(
operationName,
style: context.textTheme.bodyMedium,
),
trailing: const Icon(
Icons.arrow_forward_ios,
size: 16,
color: ColorsManager.textGray,
),
onTap: onTap,
);
}
}
@@ -0,0 +1,47 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
class RoutineDialogSelectionListTile extends StatelessWidget {
const RoutineDialogSelectionListTile({
required this.iconPath,
required this.description,
required this.isSelected,
required this.onTap,
super.key,
});
final bool isSelected;
final String iconPath;
final String description;
final void Function() onTap;
@override
Widget build(BuildContext context) {
return ListTile(
leading: SvgPicture.asset(
iconPath,
width: 24,
height: 24,
placeholderBuilder: (context) => Container(
width: 24,
height: 24,
color: Colors.transparent,
),
),
title: Text(
description,
style: context.textTheme.bodyMedium,
),
trailing: Icon(
isSelected ? Icons.radio_button_checked : Icons.radio_button_unchecked,
size: 24,
color: isSelected
? ColorsManager.primaryColorWithOpacity
: ColorsManager.textGray,
),
onTap: onTap,
);
}
}
@@ -1,41 +1,51 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart';
import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routines/models/ac/ac_function.dart';
import 'package:syncrow_web/pages/routines/models/ac/ac_operational_value.dart';
import 'package:syncrow_web/pages/routines/models/device_functions.dart';
import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart';
import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/helpers/routine_tap_function_helper.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart';
class ACHelper {
static Future<Map<String, dynamic>?> showACFunctionsDialog(
BuildContext context,
List<DeviceFunction> functions,
AllDevicesModel? device,
List<DeviceFunctionData>? deviceSelectedFunctions,
String uniqueCustomId,
bool? removeComparetors,
) async {
List<ACFunction> acFunctions = functions.whereType<ACFunction>().toList();
static Future<Map<String, dynamic>?> showACFunctionsDialog({
required BuildContext context,
required List<DeviceFunction> functions,
required AllDevicesModel? device,
required List<DeviceFunctionData>? deviceSelectedFunctions,
required String uniqueCustomId,
required bool? removeComparetors,
required String dialogType,
}) async {
List<ACFunction> acFunctions =
functions.whereType<ACFunction>().where((function) {
if (dialogType == 'THEN') {
return function.type == 'THEN' || function.type == 'BOTH';
}
return function.type == 'IF' || function.type == 'BOTH';
}).toList();
// List<ACFunction> acFunctions = functions.whereType<ACFunction>().toList();
return showDialog<Map<String, dynamic>?>(
context: context,
builder: (BuildContext context) {
return BlocProvider(
create: (_) => FunctionBloc()..add(InitializeFunctions(deviceSelectedFunctions ?? [])),
create: (_) => FunctionBloc()
..add(InitializeFunctions(deviceSelectedFunctions ?? [])),
child: AlertDialog(
contentPadding: EdgeInsets.zero,
content: BlocBuilder<FunctionBloc, FunctionBlocState>(
builder: (context, state) {
final selectedFunction = state.selectedFunction;
final selectedOperationName = state.selectedOperationName;
final selectedFunctionData =
state.addedFunctions.firstWhere((f) => f.functionCode == selectedFunction,
final selectedFunctionData = state.addedFunctions
.firstWhere((f) => f.functionCode == selectedFunction,
orElse: () => DeviceFunctionData(
entityId: '',
functionCode: selectedFunction ?? '',
@@ -65,11 +75,26 @@ class ACHelper {
child: _buildFunctionsList(
context: context,
acFunctions: acFunctions,
onFunctionSelected: (functionCode, operationName) =>
context.read<FunctionBloc>().add(SelectFunction(
functionCode: functionCode,
operationName: operationName,
)),
device: device,
onFunctionSelected: (functionCode, operationName) {
RoutineTapFunctionHelper.onTapFunction(
context,
functionCode: functionCode,
functionOperationName: operationName,
functionValueDescription:
selectedFunctionData.valueDescription,
deviceUuid: device?.uuid,
codesToAddIntoFunctionsWithDefaultValue: [
'temp_set',
'temp_current',
],
defaultValue: functionCode == 'temp_set'
? 200
: functionCode == 'temp_current'
? -100
: 0,
);
},
),
),
// Value selector
@@ -128,6 +153,7 @@ class ACHelper {
required BuildContext context,
required List<ACFunction> acFunctions,
required Function(String, String) onFunctionSelected,
required AllDevicesModel? device,
}) {
return ListView.separated(
shrinkWrap: false,
@@ -180,8 +206,9 @@ class ACHelper {
required String operationName,
bool? removeComparators,
}) {
final initialVal = selectedFunction == 'temp_set' ? 200 : -100;
if (selectedFunction == 'temp_set' || selectedFunction == 'temp_current') {
final initialValue = selectedFunctionData?.value ?? 250;
final initialValue = selectedFunctionData?.value ?? initialVal;
return _buildTemperatureSelector(
context: context,
initialValue: initialValue,
@@ -275,7 +302,9 @@ class ACHelper {
functionCode: selectCode,
operationName: operationName,
condition: conditions[index],
value: selectedFunctionData?.value,
value: selectedFunctionData?.value ?? selectCode == 'temp_set'
? 200
: -100,
valueDescription: selectedFunctionData?.valueDescription,
),
),
@@ -304,6 +333,7 @@ class ACHelper {
DeviceFunctionData? selectedFunctionData,
String selectCode,
) {
final initialVal = selectCode == 'temp_set' ? 200 : -100;
return Container(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
decoration: BoxDecoration(
@@ -311,7 +341,7 @@ class ACHelper {
borderRadius: BorderRadius.circular(10),
),
child: Text(
'${(initialValue ?? 200) / 10}°C',
'${(initialValue ?? initialVal) / 10}°C',
style: context.textTheme.headlineMedium!.copyWith(
color: ColorsManager.primaryColorWithOpacity,
),
@@ -386,7 +416,9 @@ class ACHelper {
trailing: Icon(
isSelected ? Icons.radio_button_checked : Icons.radio_button_unchecked,
size: 24,
color: isSelected ? ColorsManager.primaryColorWithOpacity : ColorsManager.textGray,
color: isSelected
? ColorsManager.primaryColorWithOpacity
: ColorsManager.textGray,
),
onTap: () {
if (!isSelected) {
@@ -0,0 +1,224 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart';
import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routines/models/ceiling_presence_sensor_functions.dart';
import 'package:syncrow_web/pages/routines/models/device_functions.dart';
import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart';
import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_helper.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_functions_list.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_slider_helpers.dart';
class CeilingSensorDialog extends StatefulWidget {
const CeilingSensorDialog({
required this.uniqueCustomId,
required this.functions,
required this.deviceSelectedFunctions,
required this.device,
required this.dialogType,
super.key,
});
final String? uniqueCustomId;
final List<DeviceFunction> functions;
final List<DeviceFunctionData> deviceSelectedFunctions;
final AllDevicesModel? device;
final String dialogType;
@override
State<CeilingSensorDialog> createState() => _CeilingSensorDialogState();
}
class _CeilingSensorDialogState extends State<CeilingSensorDialog> {
late final List<CpsFunctions> _cpsFunctions;
late final String _dialogHeaderText;
@override
void initState() {
super.initState();
_cpsFunctions = widget.functions.whereType<CpsFunctions>().where((function) {
if (widget.dialogType == 'THEN') {
return function.type == 'THEN' || function.type == 'BOTH';
}
return function.type == 'IF' || function.type == 'BOTH';
}).toList();
final isIfDialog = widget.dialogType == 'IF';
_dialogHeaderText = isIfDialog ? 'Conditions' : 'Functions';
}
@override
Widget build(BuildContext context) {
return AlertDialog(
contentPadding: EdgeInsets.zero,
content: BlocBuilder<FunctionBloc, FunctionBlocState>(
builder: (context, state) {
final selectedFunction = state.selectedFunction;
return Container(
width: selectedFunction != null ? 600 : 360,
height: 450,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
),
padding: const EdgeInsets.only(top: 20),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
DialogHeader('Presence Sensor $_dialogHeaderText'),
Expanded(child: _buildMainContent(context, state)),
DialogFooter(
onCancel: () => Navigator.pop(context),
onConfirm: state.addedFunctions.isNotEmpty
? () {
final functions = _updateValuesForAddedFunctions(
state.addedFunctions,
);
context.read<RoutineBloc>().add(
AddFunctionToRoutine(
functions,
'${widget.uniqueCustomId}',
),
);
Navigator.pop(context, {
'deviceId': widget.functions.first.deviceId,
});
}
: null,
isConfirmEnabled: selectedFunction != null,
),
],
),
);
},
),
);
}
Widget _buildMainContent(BuildContext context, FunctionBlocState state) {
final selectedFunction = state.selectedFunction;
final selectedOperationName = state.selectedOperationName;
final selectedFunctionData = state.addedFunctions.firstWhere(
(f) => f.functionCode == selectedFunction,
orElse: () => DeviceFunctionData(
entityId: '',
functionCode: selectedFunction ?? '',
operationName: '',
value: null,
),
);
final selectedCpsFunctions = _cpsFunctions.firstWhere(
(f) => f.code == selectedFunction,
orElse: () => CpsMovementFunctions(
deviceId: '',
deviceName: '',
type: '',
),
);
final operations = selectedCpsFunctions.getOperationalValues();
final isSensitivityFunction = selectedFunction == 'sensitivity';
final isToggleFunction = isSensitivityFunction
? widget.dialogType == 'THEN'
: CeilingSensorHelper.toggleCodes.contains(selectedFunction);
return Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
CpsFunctionsList(
cpsFunctions: _cpsFunctions,
device: widget.device,
selectedFunctionData: selectedFunctionData,
dialogType: widget.dialogType,
),
if (state.selectedFunction != null)
Expanded(
child: isToggleFunction
? CpsDialogValueSelector(
operations: operations,
selectedFunction: selectedFunction ?? '',
selectedFunctionData: selectedFunctionData,
cpsFunctions: _cpsFunctions,
operationName: selectedOperationName ?? '',
device: widget.device,
)
: CpsDialogSliderSelector(
operations: operations,
selectedFunction: selectedFunction ?? '',
selectedFunctionData: selectedFunctionData,
cpsFunctions: _cpsFunctions,
operationName: selectedOperationName ?? '',
device: widget.device,
dialogType: widget.dialogType,
),
),
],
);
}
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();
}
}
@@ -0,0 +1,176 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
import 'package:syncrow_web/pages/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/device_functions.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_dialog.dart';
abstract final class CeilingSensorHelper {
const CeilingSensorHelper._();
static Future<Map<String, dynamic>?> showCeilingSensorDialog({
required BuildContext context,
required String? uniqueCustomId,
required List<DeviceFunction> functions,
required List<DeviceFunctionData> deviceSelectedFunctions,
required AllDevicesModel? device,
required String dialogType,
}) {
return showDialog(
context: context,
builder: (context) => BlocProvider(
create: (context) => FunctionBloc()
..add(
InitializeFunctions(deviceSelectedFunctions),
),
child: CeilingSensorDialog(
uniqueCustomId: uniqueCustomId,
functions: functions,
deviceSelectedFunctions: deviceSelectedFunctions,
device: device,
dialogType: dialogType,
),
),
);
}
static List<DeviceFunction<CpsOperationalValue>> getCeilingSensorFunctions({
required String uuid,
required String name,
}) {
return [
CpsRadarSwitchFunction(
deviceName: name,
deviceId: uuid,
type: 'BOTH',
),
CpsSpatialParameterSwitchFunction(
deviceName: name,
deviceId: uuid,
type: 'BOTH',
),
CpsSensitivityFunction(
deviceName: name,
deviceId: uuid,
type: 'BOTH',
),
CpsMovingSpeedFunction(
deviceName: name,
deviceId: uuid,
type: 'IF',
),
CpsSpatialStaticValueFunction(
deviceName: name,
deviceId: uuid,
type: 'IF',
),
CpsSpatialMotionValueFunction(
deviceName: name,
deviceId: uuid,
type: 'IF',
),
CpsMaxDistanceOfDetectionFunction(
deviceName: name,
deviceId: uuid,
type: 'BOTH',
),
CpsMaxDistanceOfStaticDetectionFunction(
deviceName: name,
deviceId: uuid,
type: 'BOTH',
),
CpsDetectionRangeFunction(
deviceName: name,
deviceId: uuid,
type: 'IF',
),
CpsDistanceOfMovingObjectsFunction(
deviceName: name,
deviceId: uuid,
type: 'IF',
),
CpsPresenceJudgementThrsholdFunction(
deviceName: name,
deviceId: uuid,
type: 'BOTH',
),
CpsMotionAmplitudeTriggerThresholdFunction(
deviceName: name,
deviceId: uuid,
type: 'BOTH',
),
CpsPerpetualBoundaryFunction(
deviceName: name,
deviceId: uuid,
type: 'BOTH',
),
CpsMotionTriggerBoundaryFunction(
deviceName: name,
deviceId: uuid,
type: 'BOTH',
),
CpsMotionTriggerTimeFunction(
deviceName: name,
deviceId: uuid,
type: 'BOTH',
),
CpsMotionToStaticTimeFunction(
deviceName: name,
deviceId: uuid,
type: 'BOTH',
),
CpsEnteringNoBodyStateTimeFunction(
deviceName: name,
deviceId: uuid,
type: 'BOTH',
),
CpsSelfTestResultFunctions(
deviceName: name,
deviceId: uuid,
type: 'IF',
),
CpsNobodyTimeFunction(
deviceName: name,
deviceId: uuid,
type: 'BOTH',
),
CpsMovementFunctions(
deviceName: name,
deviceId: uuid,
type: 'IF',
),
CpsCustomModeFunction(
deviceName: name,
deviceId: uuid,
type: 'BOTH',
),
CpsSpaceTypeFunctions(
deviceName: name,
deviceId: uuid,
type: 'BOTH',
),
CpsPresenceStatusFunctions(
deviceName: name,
deviceId: uuid,
type: 'IF',
),
CpsSportsParaFunction(
deviceName: name,
deviceId: uuid,
type: 'IF',
),
];
}
static const toggleCodes = <String>{
'radar_switch',
'space_para_switch',
'checking_result',
'nobody_time',
'body_movement',
'custom_mode',
'scene',
'presence_state',
};
}
@@ -0,0 +1,69 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
import 'package:syncrow_web/pages/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/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';
class CpsDialogSliderSelector extends StatelessWidget {
const CpsDialogSliderSelector({
required this.operations,
required this.selectedFunction,
required this.selectedFunctionData,
required this.cpsFunctions,
required this.device,
required this.operationName,
required this.dialogType,
super.key,
});
final List<CpsOperationalValue> operations;
final String selectedFunction;
final DeviceFunctionData selectedFunctionData;
final List<CpsFunctions> cpsFunctions;
final AllDevicesModel? device;
final String operationName;
final String dialogType;
@override
Widget build(BuildContext context) {
return SliderValueSelector(
currentCondition: selectedFunctionData.condition,
dialogType: dialogType,
sliderRange: CpsSliderHelpers.sliderRange(selectedFunctionData.functionCode),
displayedValue: CpsSliderHelpers.displayText(
value: selectedFunctionData.value,
functionCode: selectedFunctionData.functionCode,
),
initialValue: selectedFunctionData.value ?? 0,
unit: CpsSliderHelpers.unit(selectedFunctionData.functionCode),
onConditionChanged: (condition) => context.read<FunctionBloc>().add(
AddFunction(
functionData: DeviceFunctionData(
entityId: device?.uuid ?? '',
functionCode: selectedFunction,
operationName: operationName,
condition: condition,
value: selectedFunctionData.value ?? 0,
),
),
),
onSliderChanged: (value) => context.read<FunctionBloc>().add(
AddFunction(
functionData: DeviceFunctionData(
entityId: device?.uuid ?? '',
functionCode: selectedFunction,
operationName: operationName,
value: double.parse(value.toStringAsFixed(2)),
condition: selectedFunctionData.condition,
),
),
),
dividendOfRange: CpsSliderHelpers.dividendOfRange(
selectedFunctionData.functionCode,
),
);
}
}
@@ -0,0 +1,58 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
import 'package:syncrow_web/pages/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/device_functions.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialog_selection_list_tile.dart';
class CpsDialogValueSelector extends StatelessWidget {
const CpsDialogValueSelector({
required this.operations,
required this.selectedFunction,
required this.selectedFunctionData,
required this.cpsFunctions,
required this.device,
required this.operationName,
super.key,
});
final List<CpsOperationalValue> operations;
final String selectedFunction;
final DeviceFunctionData? selectedFunctionData;
final List<CpsFunctions> cpsFunctions;
final AllDevicesModel? device;
final String operationName;
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: operations.length,
itemBuilder: (context, index) {
final operation = operations[index];
final isSelected = selectedFunctionData?.value == operation.value;
return RoutineDialogSelectionListTile(
iconPath: operation.icon,
description: operation.description,
isSelected: isSelected,
onTap: () {
if (!isSelected) {
context.read<FunctionBloc>().add(
AddFunction(
functionData: DeviceFunctionData(
entityId: device?.uuid ?? '',
functionCode: selectedFunction,
operationName: operationName,
value: operation.value,
condition: selectedFunctionData?.condition,
valueDescription: selectedFunctionData?.valueDescription,
),
),
);
}
},
);
},
);
}
}
@@ -0,0 +1,65 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.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/widgets/routine_dialog_function_list_tile.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/helpers/routine_tap_function_helper.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class CpsFunctionsList extends StatelessWidget {
const CpsFunctionsList({
required this.cpsFunctions,
required this.device,
required this.selectedFunctionData,
required this.dialogType,
super.key,
});
final List<CpsFunctions> cpsFunctions;
final AllDevicesModel? device;
final DeviceFunctionData? selectedFunctionData;
final String dialogType;
@override
Widget build(BuildContext context) {
return SizedBox(
width: 360,
child: ListView.separated(
shrinkWrap: false,
itemCount: cpsFunctions.length,
separatorBuilder: (context, index) => const Padding(
padding: EdgeInsets.symmetric(horizontal: 40.0),
child: Divider(color: ColorsManager.dividerColor),
),
itemBuilder: (context, index) {
final function = cpsFunctions[index];
return RoutineDialogFunctionListTile(
iconPath: function.icon,
operationName: function.operationName,
onTap: () => RoutineTapFunctionHelper.onTapFunction(
context,
functionCode: function.code,
functionOperationName: function.operationName,
functionValueDescription: selectedFunctionData?.valueDescription,
deviceUuid: device?.uuid,
codesToAddIntoFunctionsWithDefaultValue: [
'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',
if (dialogType == "IF") 'sensitivity',
],
),
);
},
),
);
}
}
@@ -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}',
};
}
}
@@ -0,0 +1,70 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart';
import 'package:syncrow_web/pages/routines/models/device_functions.dart';
import 'package:syncrow_web/pages/routines/models/flush/flush_operational_value.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/wall_sensor/time_wheel.dart';
class FlushOperationalValuesList extends StatelessWidget {
final List<FlushOperationalValue> values;
final dynamic selectedValue;
final AllDevicesModel? device;
final String operationName;
final String selectCode;
final ValueChanged<FlushOperationalValue> onSelect;
const FlushOperationalValuesList({
required this.values,
required this.selectedValue,
required this.device,
required this.operationName,
required this.selectCode,
required this.onSelect,
super.key,
});
@override
Widget build(BuildContext context) {
return ListView.builder(
padding: const EdgeInsets.all(20),
itemCount: values.length,
itemBuilder: (context, index) =>
_buildValueItem(context, values[index]),
);
}
Widget _buildValueItem(BuildContext context, FlushOperationalValue value) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(child: _buildValueDescription(value)),
_buildValueRadio(context, value),
],
),
);
}
Widget _buildValueDescription(FlushOperationalValue value) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: Text(value.description),
);
}
Widget _buildValueRadio(context, FlushOperationalValue value) {
return Radio<dynamic>(
value: value.value,
groupValue: selectedValue,
onChanged: (_) => onSelect(value));
}
}
@@ -0,0 +1,208 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart';
import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routines/models/device_functions.dart';
import 'package:syncrow_web/pages/routines/models/flush/flush_functions.dart';
import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart';
import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/flush_presence_sensor/flush_value_selector_widget.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
class FlushPresenceSensor extends StatefulWidget {
final List<DeviceFunction> functions;
final AllDevicesModel? device;
final List<DeviceFunctionData>? deviceSelectedFunctions;
final String? uniqueCustomId;
final String dialogType;
final bool removeComparetors;
const FlushPresenceSensor({
super.key,
required this.functions,
this.device,
this.deviceSelectedFunctions,
this.uniqueCustomId,
required this.dialogType,
this.removeComparetors = false,
});
static Future<Map<String, dynamic>?> showFlushFunctionsDialog({
required BuildContext context,
required List<DeviceFunction> functions,
AllDevicesModel? device,
List<DeviceFunctionData>? deviceSelectedFunctions,
String? uniqueCustomId,
required String dialogType,
bool removeComparetors = false,
}) async {
return showDialog<Map<String, dynamic>?>(
context: context,
builder: (context) => FlushPresenceSensor(
functions: functions,
device: device,
deviceSelectedFunctions: deviceSelectedFunctions,
uniqueCustomId: uniqueCustomId,
removeComparetors: removeComparetors,
dialogType: dialogType,
),
);
}
@override
State<FlushPresenceSensor> createState() => _WallPresenceSensorState();
}
class _WallPresenceSensorState extends State<FlushPresenceSensor> {
late final List<FlushFunctions> _flushFunctions;
@override
void initState() {
super.initState();
_flushFunctions =
widget.functions.whereType<FlushFunctions>().where((function) {
if (widget.dialogType == 'THEN') {
return function.type == 'THEN' || function.type == 'BOTH';
}
return function.type == 'IF' || function.type == 'BOTH';
}).toList();
}
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => FunctionBloc()
..add(InitializeFunctions(widget.deviceSelectedFunctions ?? [])),
child: _buildDialogContent(),
);
}
Widget _buildDialogContent() {
return AlertDialog(
contentPadding: EdgeInsets.zero,
content: BlocBuilder<FunctionBloc, FunctionBlocState>(
builder: (context, state) {
final selectedFunction = state.selectedFunction;
return Container(
width: selectedFunction != null ? 600 : 360,
height: 450,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
),
padding: const EdgeInsets.only(top: 20),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const DialogHeader('Presence Sensor Condition'),
Expanded(child: _buildMainContent(context, state)),
_buildDialogFooter(context, state),
],
),
);
},
),
);
}
Widget _buildMainContent(BuildContext context, FunctionBlocState state) {
return Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_buildFunctionList(context),
if (state.selectedFunction != null) _buildValueSelector(context, state),
],
);
}
Widget _buildFunctionList(BuildContext context) {
return SizedBox(
width: 360,
child: ListView.separated(
shrinkWrap: false,
physics: const AlwaysScrollableScrollPhysics(),
itemCount: _flushFunctions.length,
separatorBuilder: (context, index) => const Padding(
padding: EdgeInsets.symmetric(horizontal: 40.0),
child: Divider(color: ColorsManager.dividerColor),
),
itemBuilder: (context, index) {
final function = _flushFunctions[index];
return ListTile(
leading: SvgPicture.asset(
function.icon,
width: 24,
height: 24,
placeholderBuilder: (context) => const SizedBox(
width: 24,
height: 24,
),
),
title: Text(
function.operationName,
style: context.textTheme.bodyMedium,
),
trailing: const Icon(
Icons.arrow_forward_ios,
size: 16,
color: ColorsManager.textGray,
),
onTap: () => context.read<FunctionBloc>().add(
SelectFunction(
functionCode: function.code,
operationName: function.operationName,
),
),
);
},
),
);
}
Widget _buildValueSelector(BuildContext context, FunctionBlocState state) {
final selectedFunction = state.selectedFunction ?? '';
final functionData = state.addedFunctions.firstWhere(
(f) => f.functionCode == selectedFunction,
orElse: () => DeviceFunctionData(
entityId: '',
functionCode: selectedFunction,
operationName: state.selectedOperationName ?? '',
value: null,
),
);
return Expanded(
child: FlushValueSelectorWidget(
selectedFunction: selectedFunction,
functionData: functionData,
flushFunctions: _flushFunctions,
device: widget.device,
dialogType: widget.dialogType,
removeComparators: widget.removeComparetors,
),
);
}
Widget _buildDialogFooter(BuildContext context, FunctionBlocState state) {
return DialogFooter(
onCancel: () => Navigator.pop(context),
onConfirm: state.addedFunctions.isNotEmpty
? () {
context.read<RoutineBloc>().add(
AddFunctionToRoutine(
state.addedFunctions,
widget.uniqueCustomId!,
),
);
Navigator.pop(
context,
{'deviceId': widget.functions.first.deviceId},
);
}
: null,
isConfirmEnabled: state.selectedFunction != null,
);
}
}
@@ -0,0 +1,178 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
import 'package:syncrow_web/pages/device_managment/flush_mounted_presence_sensor/models/flush_mounted_presence_sensor_model.dart';
import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart';
import 'package:syncrow_web/pages/routines/models/device_functions.dart';
import 'package:syncrow_web/pages/routines/models/flush/flush_functions.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/flush_presence_sensor/flush_operational_values_list.dart';
import 'package:syncrow_web/pages/routines/widgets/slider_value_selector.dart';
class FlushValueSelectorWidget extends StatelessWidget {
final String selectedFunction;
final DeviceFunctionData functionData;
final List<FlushFunctions> flushFunctions;
final AllDevicesModel? device;
final String dialogType;
final bool removeComparators;
const FlushValueSelectorWidget({
required this.selectedFunction,
required this.functionData,
required this.flushFunctions,
required this.device,
required this.dialogType,
required this.removeComparators,
super.key,
});
(double, double) get sliderRange => switch (functionData.functionCode) {
FlushMountedPresenceSensorModel.codeOccurDistReduce => (0, 3),
FlushMountedPresenceSensorModel.codeNoneDelay => (200, 3000),
FlushMountedPresenceSensorModel.codeSensiReduce => (0, 3),
FlushMountedPresenceSensorModel.codePresenceDelay => (0, 5),
FlushMountedPresenceSensorModel.codeIlluminance => (0.0, 2000.0),
FlushMountedPresenceSensorModel.codeFarDetection => (0.0, 9.5),
FlushMountedPresenceSensorModel.codeNearDetection => (0.0, 9.5),
_ => (0.0, 100.0),
};
double get stepSize => switch (functionData.functionCode) {
FlushMountedPresenceSensorModel.codeNoneDelay => 10.0,
FlushMountedPresenceSensorModel.codeFarDetection => 0.5,
FlushMountedPresenceSensorModel.codeNearDetection => 0.5,
FlushMountedPresenceSensorModel.codePresenceDelay => 1.0,
FlushMountedPresenceSensorModel.codeOccurDistReduce => 1.0,
FlushMountedPresenceSensorModel.codeSensiReduce => 1.0,
_ => 1.0,
};
@override
Widget build(BuildContext context) {
final selectedFn = flushFunctions.firstWhere(
(f) => f.code == selectedFunction,
orElse: () => throw Exception('Function $selectedFunction not found'),
);
if (_isSliderFunction(selectedFunction)) {
final isNearDetection =
selectedFunction == FlushMountedPresenceSensorModel.codeNearDetection;
final isFarDetection =
selectedFunction == FlushMountedPresenceSensorModel.codeFarDetection;
final isDistanceDetection = isNearDetection || isFarDetection;
double initialValue = (functionData.value as num?)?.toDouble() ?? 0.0;
if (isDistanceDetection) {
initialValue = initialValue / 100;
}
return SliderValueSelector(
currentCondition: functionData.condition,
dialogType: dialogType,
sliderRange: sliderRange,
displayedValue: getDisplayText,
initialValue: initialValue,
onConditionChanged: (condition) => context.read<FunctionBloc>().add(
AddFunction(
functionData: DeviceFunctionData(
entityId: device?.uuid ?? '',
functionCode: selectedFunction,
operationName: functionData.operationName,
condition: condition,
value: functionData.value ?? 0,
),
),
),
onSliderChanged: (value) {
final roundedValue = _roundToStep(value, stepSize);
final finalValue =
isDistanceDetection ? (roundedValue * 100).toInt() : roundedValue;
context.read<FunctionBloc>().add(
AddFunction(
functionData: DeviceFunctionData(
entityId: device?.uuid ?? '',
functionCode: selectedFunction,
operationName: functionData.operationName,
value: finalValue,
condition: functionData.condition,
),
),
);
},
unit: _unit,
dividendOfRange: stepSize,
);
}
return FlushOperationalValuesList(
values: selectedFn.getOperationalValues(),
selectedValue: functionData.value,
device: device,
operationName: selectedFn.operationName,
selectCode: selectedFunction,
onSelect: (selectedValue) async {
context.read<FunctionBloc>().add(
AddFunction(
functionData: DeviceFunctionData(
entityId: device?.uuid ?? '',
functionCode: selectedFunction,
operationName: functionData.operationName,
value: selectedValue.value,
condition: functionData.condition,
),
),
);
},
);
}
double _roundToStep(double value, double step) {
return (value / step).roundToDouble() * step;
}
bool _isSliderFunction(String function) => [
FlushMountedPresenceSensorModel.codeOccurDistReduce,
FlushMountedPresenceSensorModel.codeSensiReduce,
FlushMountedPresenceSensorModel.codeNoneDelay,
FlushMountedPresenceSensorModel.codeIlluminance,
FlushMountedPresenceSensorModel.codePresenceDelay,
FlushMountedPresenceSensorModel.codeFarDetection,
FlushMountedPresenceSensorModel.codeNearDetection,
].contains(function);
String get _unit => switch (functionData.functionCode) {
FlushMountedPresenceSensorModel.codeOccurDistReduce => 'Min',
FlushMountedPresenceSensorModel.codeSensiReduce => 'Sec',
FlushMountedPresenceSensorModel.codeNoneDelay => 'Sec',
FlushMountedPresenceSensorModel.codePresenceDelay => 'Sec',
FlushMountedPresenceSensorModel.codeIlluminance => 'Lux',
FlushMountedPresenceSensorModel.codeFarDetection => 'm',
FlushMountedPresenceSensorModel.codeNearDetection => 'm',
_ => '',
};
String get getDisplayText {
final num? value = functionData.value;
double displayValue = value?.toDouble() ?? 0.0;
if (functionData.functionCode ==
FlushMountedPresenceSensorModel.codeNearDetection ||
functionData.functionCode ==
FlushMountedPresenceSensorModel.codeFarDetection) {
displayValue = displayValue / 100;
}
switch (functionData.functionCode) {
case FlushMountedPresenceSensorModel.codeFarDetection:
case FlushMountedPresenceSensorModel.codeNearDetection:
return displayValue.toStringAsFixed(1);
case FlushMountedPresenceSensorModel.codeOccurDistReduce:
case FlushMountedPresenceSensorModel.codeSensiReduce:
case FlushMountedPresenceSensorModel.codePresenceDelay:
return displayValue.toStringAsFixed(0);
default:
return displayValue.toStringAsFixed(0);
}
}
}
@@ -0,0 +1,169 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class TimeWheelPicker extends StatefulWidget {
final int initialHours;
final int initialMinutes;
final int initialSeconds;
final Function(int, int, int) onTimeChanged;
const TimeWheelPicker({
super.key,
required this.initialHours,
required this.initialMinutes,
required this.initialSeconds,
required this.onTimeChanged,
});
@override
State<TimeWheelPicker> createState() => _TimeWheelPickerState();
}
class _TimeWheelPickerState extends State<TimeWheelPicker> {
late FixedExtentScrollController _hoursController;
late FixedExtentScrollController _minutesController;
late FixedExtentScrollController _secondsController;
@override
void initState() {
super.initState();
_hoursController =
FixedExtentScrollController(initialItem: widget.initialHours);
_minutesController =
FixedExtentScrollController(initialItem: widget.initialMinutes);
_secondsController =
FixedExtentScrollController(initialItem: widget.initialSeconds);
}
@override
void didUpdateWidget(TimeWheelPicker oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.initialHours != widget.initialHours) {
_hoursController.jumpToItem(widget.initialHours);
}
if (oldWidget.initialMinutes != widget.initialMinutes) {
_minutesController.jumpToItem(widget.initialMinutes);
}
if (oldWidget.initialSeconds != widget.initialSeconds) {
_secondsController.jumpToItem(widget.initialSeconds);
}
}
@override
void dispose() {
_hoursController.dispose();
_minutesController.dispose();
_secondsController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
_buildPickerColumn(
label: 'h',
controller: _hoursController,
itemCount: 3,
onChanged: (value) {
_handleTimeChange(
value,
_minutesController.selectedItem,
_secondsController.selectedItem,
);
}),
const SizedBox(width: 5),
_buildPickerColumn(
label: 'm',
controller: _minutesController,
itemCount: 60,
onChanged: (value) {
_handleTimeChange(
_hoursController.selectedItem,
value,
_secondsController.selectedItem,
);
}),
const SizedBox(width: 5),
_buildPickerColumn(
label: 's',
controller: _secondsController,
itemCount: 60,
onChanged: (value) => _handleTimeChange(
_hoursController.selectedItem,
_minutesController.selectedItem,
value,
),
),
],
);
}
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 _buildPickerColumn({
required String label,
required FixedExtentScrollController controller,
required int itemCount,
required Function(int) onChanged,
}) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
height: 40,
width: 40,
padding: const EdgeInsets.symmetric(horizontal: 5),
decoration: BoxDecoration(
color: ColorsManager.boxColor,
borderRadius: BorderRadius.circular(8),
),
child: ListWheelScrollView.useDelegate(
controller: controller,
itemExtent: 40.0,
physics: const FixedExtentScrollPhysics(),
onSelectedItemChanged: onChanged,
childDelegate: ListWheelChildBuilderDelegate(
builder: (context, index) => Center(
child: Text(
index.toString().padLeft(2),
style: const TextStyle(
fontSize: 18,
color: ColorsManager.blue1,
),
),
),
childCount: itemCount,
),
),
),
const SizedBox(width: 5),
Text(
label,
style: const TextStyle(
color: ColorsManager.blackColor,
fontSize: 18,
),
),
],
);
}
}
@@ -0,0 +1,130 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart';
import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routines/models/device_functions.dart';
import 'package:syncrow_web/pages/routines/models/gateway.dart';
import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart';
import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/gateway/gateway_dialog_value_selector.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/gateway/gateway_functions_list.dart';
class GatewayDialog extends StatefulWidget {
const GatewayDialog({
required this.uniqueCustomId,
required this.functions,
required this.deviceSelectedFunctions,
required this.device,
super.key,
});
final String? uniqueCustomId;
final List<DeviceFunction> functions;
final List<DeviceFunctionData> deviceSelectedFunctions;
final AllDevicesModel? device;
@override
State<GatewayDialog> createState() => _GatewayDialogState();
}
class _GatewayDialogState extends State<GatewayDialog> {
late final List<GatewayFunctions> _gatewayFunctions;
@override
void initState() {
super.initState();
_gatewayFunctions = widget.functions.whereType<GatewayFunctions>().toList();
}
@override
Widget build(BuildContext context) {
return AlertDialog(
contentPadding: EdgeInsets.zero,
content: BlocBuilder<FunctionBloc, FunctionBlocState>(
builder: (context, state) {
final selectedFunction = state.selectedFunction;
return Container(
width: selectedFunction != null ? 600 : 360,
height: 450,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
),
padding: const EdgeInsets.only(top: 20),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const DialogHeader('Gateway Conditions'),
Expanded(child: _buildMainContent(context, state)),
_buildDialogFooter(context, state),
],
),
);
},
),
);
}
Widget _buildMainContent(BuildContext context, FunctionBlocState state) {
final selectedFunction = state.selectedFunction;
final selectedOperationName = state.selectedOperationName;
final selectedFunctionData = state.addedFunctions.firstWhere(
(f) => f.functionCode == selectedFunction,
orElse: () => DeviceFunctionData(
entityId: '',
functionCode: selectedFunction ?? '',
operationName: '',
value: null,
),
);
final selectedGatewayFunctions = _gatewayFunctions.firstWhere(
(f) => f.code == selectedFunction,
orElse: () => GatewaySwitchAlarmSound(
deviceId: '',
deviceName: '',
type: '',
),
);
final operations = selectedGatewayFunctions.getOperationalValues();
return Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
GatewayFunctionsList(gatewayFunctions: _gatewayFunctions),
if (state.selectedFunction != null)
Expanded(
child: GatewayDialogValueSelector(
operations: operations,
selectedFunction: selectedFunction ?? '',
selectedFunctionData: selectedFunctionData,
gatewayFunctions: _gatewayFunctions,
operationName: selectedOperationName ?? '',
device: widget.device,
),
),
],
);
}
Widget _buildDialogFooter(BuildContext context, FunctionBlocState state) {
return DialogFooter(
onCancel: () => Navigator.pop(context),
onConfirm: state.addedFunctions.isNotEmpty
? () {
context.read<RoutineBloc>().add(
AddFunctionToRoutine(
state.addedFunctions,
widget.uniqueCustomId ?? '-1',
),
);
Navigator.pop(
context,
{'deviceId': widget.functions.firstOrNull?.deviceId},
);
}
: null,
isConfirmEnabled: state.selectedFunction != null,
);
}
}
@@ -0,0 +1,58 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart';
import 'package:syncrow_web/pages/routines/models/device_functions.dart';
import 'package:syncrow_web/pages/routines/models/gateway.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialog_selection_list_tile.dart';
class GatewayDialogValueSelector extends StatelessWidget {
const GatewayDialogValueSelector({
required this.operations,
required this.selectedFunction,
required this.selectedFunctionData,
required this.gatewayFunctions,
required this.device,
required this.operationName,
super.key,
});
final List<GatewayOperationalValue> operations;
final String selectedFunction;
final DeviceFunctionData? selectedFunctionData;
final List<GatewayFunctions> gatewayFunctions;
final AllDevicesModel? device;
final String operationName;
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: operations.length,
itemBuilder: (context, index) {
final operation = operations[index];
final isSelected = selectedFunctionData?.value == operation.value;
return RoutineDialogSelectionListTile(
iconPath: operation.icon,
description: operation.description,
isSelected: isSelected,
onTap: () {
if (!isSelected) {
context.read<FunctionBloc>().add(
AddFunction(
functionData: DeviceFunctionData(
entityId: device?.uuid ?? '',
functionCode: selectedFunction,
operationName: operationName,
value: operation.value,
condition: selectedFunctionData?.condition,
valueDescription: selectedFunctionData?.valueDescription,
),
),
);
}
},
);
},
);
}
}
@@ -0,0 +1,43 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart';
import 'package:syncrow_web/pages/routines/models/gateway.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialog_function_list_tile.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class GatewayFunctionsList extends StatelessWidget {
const GatewayFunctionsList({
required this.gatewayFunctions,
super.key,
});
final List<GatewayFunctions> gatewayFunctions;
@override
Widget build(BuildContext context) {
return SizedBox(
width: 360,
child: ListView.separated(
shrinkWrap: false,
itemCount: gatewayFunctions.length,
separatorBuilder: (context, index) => const Padding(
padding: EdgeInsets.symmetric(horizontal: 40.0),
child: Divider(color: ColorsManager.dividerColor),
),
itemBuilder: (context, index) {
final function = gatewayFunctions[index];
return RoutineDialogFunctionListTile(
iconPath: function.icon,
operationName: function.operationName,
onTap: () => context.read<FunctionBloc>().add(
SelectFunction(
functionCode: function.code,
operationName: function.operationName,
),
),
);
},
),
);
}
}
@@ -0,0 +1,34 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart';
import 'package:syncrow_web/pages/routines/models/device_functions.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/gateway/gateway_dialog.dart';
abstract final class GatewayHelper {
const GatewayHelper._();
static Future<Map<String, dynamic>?> showGatewayFunctionsDialog({
required BuildContext context,
required List<DeviceFunction> functions,
required String? uniqueCustomId,
required List<DeviceFunctionData> deviceSelectedFunctions,
required AllDevicesModel? device,
}) async {
return showDialog(
context: context,
builder: (context) => BlocProvider<FunctionBloc>(
create: (context) => FunctionBloc()
..add(
InitializeFunctions(deviceSelectedFunctions),
),
child: GatewayDialog(
uniqueCustomId: uniqueCustomId,
functions: functions,
deviceSelectedFunctions: deviceSelectedFunctions,
device: device,
),
),
);
}
}
@@ -0,0 +1,46 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart';
import 'package:syncrow_web/pages/routines/models/device_functions.dart';
abstract final class RoutineTapFunctionHelper {
const RoutineTapFunctionHelper._();
static void onTapFunction(
BuildContext context, {
required String functionCode,
required String functionOperationName,
required String? functionValueDescription,
required String? deviceUuid,
required List<String> codesToAddIntoFunctionsWithDefaultValue,
int defaultValue = 0,
}) {
final functionsBloc = context.read<FunctionBloc>();
functionsBloc.add(
SelectFunction(
functionCode: functionCode,
operationName: functionOperationName,
),
);
final addedFunctions = functionsBloc.state.addedFunctions;
final isFunctionAlreadyAdded = addedFunctions.any(
(e) => e.functionCode == functionCode,
);
final shouldAddFunction =
codesToAddIntoFunctionsWithDefaultValue.contains(functionCode);
if (!isFunctionAlreadyAdded && shouldAddFunction) {
context.read<FunctionBloc>().add(
AddFunction(
functionData: DeviceFunctionData(
entityId: deviceUuid ?? '',
functionCode: functionCode,
operationName: functionOperationName,
value: defaultValue,
condition: '==',
valueDescription: functionValueDescription,
),
),
);
}
}
}
@@ -7,36 +7,41 @@ import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routines/helper/duration_format_helper.dart';
import 'package:syncrow_web/pages/routines/models/device_functions.dart';
import 'package:syncrow_web/pages/routines/models/gang_switches/base_switch_function.dart';
import 'package:syncrow_web/pages/routines/models/gang_switches/one_gang_switch/one_gang_switch.dart';
import 'package:syncrow_web/pages/routines/models/gang_switches/switch_operational_value.dart';
import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart';
import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/helpers/routine_tap_function_helper.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
class OneGangSwitchHelper {
static Future<Map<String, dynamic>?> showSwitchFunctionsDialog(
BuildContext context,
List<DeviceFunction> functions,
AllDevicesModel? device,
List<DeviceFunctionData>? deviceSelectedFunctions,
String uniqueCustomId,
bool removeComparetors,
) async {
List<BaseSwitchFunction> acFunctions = functions.whereType<BaseSwitchFunction>().toList();
static Future<Map<String, dynamic>?> showSwitchFunctionsDialog({
required String dialogType,
required BuildContext context,
required List<DeviceFunction> functions,
required AllDevicesModel? device,
required List<DeviceFunctionData>? deviceSelectedFunctions,
required String uniqueCustomId,
required bool removeComparetors,
}) async {
List<BaseSwitchFunction> oneGangFunctions =
functions.whereType<BaseSwitchFunction>().toList();
return showDialog<Map<String, dynamic>?>(
context: context,
builder: (BuildContext context) {
return BlocProvider(
create: (_) => FunctionBloc()..add(InitializeFunctions(deviceSelectedFunctions ?? [])),
create: (_) => FunctionBloc()
..add(InitializeFunctions(deviceSelectedFunctions ?? [])),
child: AlertDialog(
contentPadding: EdgeInsets.zero,
content: BlocBuilder<FunctionBloc, FunctionBlocState>(
builder: (context, state) {
final selectedFunction = state.selectedFunction;
final selectedOperationName = state.selectedOperationName;
final selectedFunctionData =
state.addedFunctions.firstWhere((f) => f.functionCode == selectedFunction,
final selectedFunctionData = state.addedFunctions
.firstWhere((f) => f.functionCode == selectedFunction,
orElse: () => DeviceFunctionData(
entityId: '',
functionCode: selectedFunction ?? '',
@@ -61,12 +66,12 @@ class OneGangSwitchHelper {
// Left side: Function list
Expanded(
child: ListView.separated(
itemCount: acFunctions.length,
itemCount: oneGangFunctions.length,
separatorBuilder: (_, __) => const Divider(
color: ColorsManager.dividerColor,
),
itemBuilder: (context, index) {
final function = acFunctions[index];
final function = oneGangFunctions[index];
return ListTile(
leading: SvgPicture.asset(
function.icon,
@@ -82,12 +87,19 @@ class OneGangSwitchHelper {
size: 16,
color: ColorsManager.textGray,
),
onTap: () {
context.read<FunctionBloc>().add(SelectFunction(
functionCode: function.code,
operationName: function.operationName,
));
},
onTap: () =>
RoutineTapFunctionHelper.onTapFunction(
context,
functionCode: function.code,
functionOperationName:
function.operationName,
functionValueDescription:
selectedFunctionData.valueDescription,
deviceUuid: device?.uuid,
codesToAddIntoFunctionsWithDefaultValue: [
'countdown_1',
],
),
);
},
),
@@ -99,7 +111,7 @@ class OneGangSwitchHelper {
context: context,
selectedFunction: selectedFunction,
selectedFunctionData: selectedFunctionData,
acFunctions: acFunctions,
acFunctions: oneGangFunctions,
device: device,
operationName: selectedOperationName ?? '',
removeComparetors: removeComparetors,
@@ -162,7 +174,7 @@ class OneGangSwitchHelper {
required bool removeComparetors,
}) {
if (selectedFunction == 'countdown_1') {
final initialValue = selectedFunctionData?.value ?? 200;
final initialValue = selectedFunctionData?.value ?? 0;
return _buildCountDownSelector(
context: context,
initialValue: initialValue,
@@ -174,8 +186,14 @@ class OneGangSwitchHelper {
removeComparetors: removeComparetors,
);
}
final selectedFn = acFunctions.firstWhere(
(f) => f.code == selectedFunction,
orElse: () => OneGangSwitchFunction(
deviceId: '',
deviceName: '',
),
);
final selectedFn = acFunctions.firstWhere((f) => f.code == selectedFunction);
final values = selectedFn.getOperationalValues();
return _buildOperationalValuesList(
@@ -212,11 +230,11 @@ class OneGangSwitchHelper {
selectedFunctionData,
),
const SizedBox(height: 20),
_buildCountDownDisplay(
context, initialValue, device, operationName, selectedFunctionData, selectCode),
_buildCountDownDisplay(context, initialValue, device, operationName,
selectedFunctionData, selectCode),
const SizedBox(height: 20),
_buildCountDownSlider(
context, initialValue, device, operationName, selectedFunctionData, selectCode),
_buildCountDownSlider(context, initialValue, device, operationName,
selectedFunctionData, selectCode),
],
);
}
@@ -242,7 +260,7 @@ class OneGangSwitchHelper {
functionCode: selectCode,
operationName: operationName,
condition: conditions[index],
value: selectedFunctionData?.value,
value: selectedFunctionData?.value ?? 0,
valueDescription: selectedFunctionData?.valueDescription,
),
),
@@ -293,21 +311,23 @@ class OneGangSwitchHelper {
DeviceFunctionData? selectedFunctionData,
String selectCode,
) {
const twelveHoursInSeconds = 43200.0;
final operationalValues = SwitchOperationalValue(
icon: '',
description: "sec",
value: 0.0,
minValue: 0,
maxValue: 86400,
maxValue: twelveHoursInSeconds,
stepValue: 1,
);
return Slider(
value: (initialValue ?? 0).toDouble(),
min: operationalValues.minValue?.toDouble() ?? 0.0,
max: operationalValues.maxValue?.toDouble() ?? 0.0,
divisions: (((operationalValues.maxValue ?? 0) - (operationalValues.minValue ?? 0)) /
(operationalValues.stepValue ?? 1))
.round(),
divisions:
(((operationalValues.maxValue ?? 0) - (operationalValues.minValue ?? 0)) /
(operationalValues.stepValue ?? 1))
.round(),
onChanged: (value) {
context.read<FunctionBloc>().add(
AddFunction(
@@ -359,7 +379,9 @@ class OneGangSwitchHelper {
trailing: Icon(
isSelected ? Icons.radio_button_checked : Icons.radio_button_unchecked,
size: 24,
color: isSelected ? ColorsManager.primaryColorWithOpacity : ColorsManager.textGray,
color: isSelected
? ColorsManager.primaryColorWithOpacity
: ColorsManager.textGray,
),
onTap: () {
if (!isSelected) {
@@ -10,33 +10,37 @@ import 'package:syncrow_web/pages/routines/models/gang_switches/base_switch_func
import 'package:syncrow_web/pages/routines/models/gang_switches/switch_operational_value.dart';
import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart';
import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/helpers/routine_tap_function_helper.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
class ThreeGangSwitchHelper {
static Future<Map<String, dynamic>?> showSwitchFunctionsDialog(
BuildContext context,
List<DeviceFunction> functions,
AllDevicesModel? device,
List<DeviceFunctionData>? deviceSelectedFunctions,
String uniqueCustomId,
bool removeComparetors,
) async {
List<BaseSwitchFunction> switchFunctions = functions.whereType<BaseSwitchFunction>().toList();
static Future<Map<String, dynamic>?> showSwitchFunctionsDialog({
required BuildContext context,
required List<DeviceFunction> functions,
required AllDevicesModel? device,
required List<DeviceFunctionData>? deviceSelectedFunctions,
required String uniqueCustomId,
required String dialogType,
required bool removeComparetors,
}) async {
List<BaseSwitchFunction> switchFunctions =
functions.whereType<BaseSwitchFunction>().toList();
return showDialog<Map<String, dynamic>?>(
context: context,
builder: (BuildContext context) {
return BlocProvider(
create: (_) => FunctionBloc()..add(InitializeFunctions(deviceSelectedFunctions ?? [])),
create: (_) => FunctionBloc()
..add(InitializeFunctions(deviceSelectedFunctions ?? [])),
child: AlertDialog(
contentPadding: EdgeInsets.zero,
content: BlocBuilder<FunctionBloc, FunctionBlocState>(
builder: (context, state) {
final selectedFunction = state.selectedFunction;
final selectedOperationName = state.selectedOperationName;
final selectedFunctionData =
state.addedFunctions.firstWhere((f) => f.functionCode == selectedFunction,
final selectedFunctionData = state.addedFunctions
.firstWhere((f) => f.functionCode == selectedFunction,
orElse: () => DeviceFunctionData(
entityId: '',
functionCode: selectedFunction ?? '',
@@ -82,12 +86,21 @@ class ThreeGangSwitchHelper {
size: 16,
color: ColorsManager.textGray,
),
onTap: () {
context.read<FunctionBloc>().add(SelectFunction(
functionCode: function.code,
operationName: function.operationName,
));
},
onTap: () =>
RoutineTapFunctionHelper.onTapFunction(
context,
functionCode: function.code,
functionOperationName:
function.operationName,
functionValueDescription:
selectedFunctionData.valueDescription,
deviceUuid: device?.uuid,
codesToAddIntoFunctionsWithDefaultValue: [
'countdown_1',
'countdown_2',
'countdown_3',
],
),
);
},
),
@@ -164,7 +177,7 @@ class ThreeGangSwitchHelper {
if (selectedFunction == 'countdown_1' ||
selectedFunction == 'countdown_2' ||
selectedFunction == 'countdown_3') {
final initialValue = selectedFunctionData?.value ?? 200;
final initialValue = selectedFunctionData?.value ?? 0;
return _buildTemperatureSelector(
context: context,
initialValue: initialValue,
@@ -214,11 +227,11 @@ class ThreeGangSwitchHelper {
selectedFunctionData,
),
const SizedBox(height: 20),
_buildCountDownDisplay(
context, initialValue, device, operationName, selectedFunctionData, selectCode),
_buildCountDownDisplay(context, initialValue, device, operationName,
selectedFunctionData, selectCode),
const SizedBox(height: 20),
_buildCountDownSlider(
context, initialValue, device, operationName, selectedFunctionData, selectCode),
_buildCountDownSlider(context, initialValue, device, operationName,
selectedFunctionData, selectCode),
],
);
}
@@ -244,7 +257,7 @@ class ThreeGangSwitchHelper {
functionCode: selectCode,
operationName: operationName,
condition: conditions[index],
value: selectedFunctionData?.value,
value: selectedFunctionData?.value ?? 0,
valueDescription: selectedFunctionData?.valueDescription,
),
),
@@ -295,21 +308,23 @@ class ThreeGangSwitchHelper {
DeviceFunctionData? selectedFunctionData,
String selectCode,
) {
const twelveHoursInSeconds = 43200.0;
final operationalValues = SwitchOperationalValue(
icon: '',
description: "sec",
value: 0.0,
minValue: 0,
maxValue: 86400,
maxValue: twelveHoursInSeconds,
stepValue: 1,
);
return Slider(
value: (initialValue ?? 0).toDouble(),
min: operationalValues.minValue?.toDouble() ?? 0.0,
max: operationalValues.maxValue?.toDouble() ?? 0.0,
divisions: (((operationalValues.maxValue ?? 0) - (operationalValues.minValue ?? 0)) /
(operationalValues.stepValue ?? 1))
.round(),
divisions:
(((operationalValues.maxValue ?? 0) - (operationalValues.minValue ?? 0)) /
(operationalValues.stepValue ?? 1))
.round(),
onChanged: (value) {
context.read<FunctionBloc>().add(
AddFunction(
@@ -361,7 +376,9 @@ class ThreeGangSwitchHelper {
trailing: Icon(
isSelected ? Icons.radio_button_checked : Icons.radio_button_unchecked,
size: 24,
color: isSelected ? ColorsManager.primaryColorWithOpacity : ColorsManager.textGray,
color: isSelected
? ColorsManager.primaryColorWithOpacity
: ColorsManager.textGray,
),
onTap: () {
if (!isSelected) {
@@ -10,33 +10,37 @@ import 'package:syncrow_web/pages/routines/models/gang_switches/base_switch_func
import 'package:syncrow_web/pages/routines/models/gang_switches/switch_operational_value.dart';
import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart';
import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/helpers/routine_tap_function_helper.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
class TwoGangSwitchHelper {
static Future<Map<String, dynamic>?> showSwitchFunctionsDialog(
BuildContext context,
List<DeviceFunction> functions,
AllDevicesModel? device,
List<DeviceFunctionData>? deviceSelectedFunctions,
String uniqueCustomId,
bool removeComparetors,
) async {
List<BaseSwitchFunction> switchFunctions = functions.whereType<BaseSwitchFunction>().toList();
static Future<Map<String, dynamic>?> showSwitchFunctionsDialog({
required BuildContext context,
required List<DeviceFunction> functions,
required AllDevicesModel? device,
required List<DeviceFunctionData>? deviceSelectedFunctions,
required String uniqueCustomId,
required bool removeComparetors,
required String dialogType,
}) async {
List<BaseSwitchFunction> switchFunctions =
functions.whereType<BaseSwitchFunction>().toList();
return showDialog<Map<String, dynamic>?>(
context: context,
builder: (BuildContext context) {
return BlocProvider(
create: (_) => FunctionBloc()..add(InitializeFunctions(deviceSelectedFunctions ?? [])),
create: (_) => FunctionBloc()
..add(InitializeFunctions(deviceSelectedFunctions ?? [])),
child: AlertDialog(
contentPadding: EdgeInsets.zero,
content: BlocBuilder<FunctionBloc, FunctionBlocState>(
builder: (context, state) {
final selectedFunction = state.selectedFunction;
final selectedOperationName = state.selectedOperationName;
final selectedFunctionData =
state.addedFunctions.firstWhere((f) => f.functionCode == selectedFunction,
final selectedFunctionData = state.addedFunctions
.firstWhere((f) => f.functionCode == selectedFunction,
orElse: () => DeviceFunctionData(
entityId: '',
functionCode: selectedFunction ?? '',
@@ -82,12 +86,20 @@ class TwoGangSwitchHelper {
size: 16,
color: ColorsManager.textGray,
),
onTap: () {
context.read<FunctionBloc>().add(SelectFunction(
functionCode: function.code,
operationName: function.operationName,
));
},
onTap: () =>
RoutineTapFunctionHelper.onTapFunction(
context,
functionCode: function.code,
functionOperationName:
function.operationName,
functionValueDescription:
selectedFunctionData.valueDescription,
deviceUuid: device?.uuid,
codesToAddIntoFunctionsWithDefaultValue: [
'countdown_1',
'countdown_2',
],
),
);
},
),
@@ -162,7 +174,7 @@ class TwoGangSwitchHelper {
required bool removeComparetors,
}) {
if (selectedFunction == 'countdown_1' || selectedFunction == 'countdown_2') {
final initialValue = selectedFunctionData?.value ?? 200;
final initialValue = selectedFunctionData?.value ?? 0;
return _buildTemperatureSelector(
context: context,
initialValue: initialValue,
@@ -212,11 +224,11 @@ class TwoGangSwitchHelper {
selectedFunctionData,
),
const SizedBox(height: 20),
_buildCountDownDisplay(
context, initialValue, device, operationName, selectedFunctionData, selectCode),
_buildCountDownDisplay(context, initialValue, device, operationName,
selectedFunctionData, selectCode),
const SizedBox(height: 20),
_buildCountDownSlider(
context, initialValue, device, operationName, selectedFunctionData, selectCode),
_buildCountDownSlider(context, initialValue, device, operationName,
selectedFunctionData, selectCode),
],
);
}
@@ -242,7 +254,7 @@ class TwoGangSwitchHelper {
functionCode: selectCode,
operationName: operationName,
condition: conditions[index],
value: selectedFunctionData?.value,
value: selectedFunctionData?.value ?? 0,
valueDescription: selectedFunctionData?.valueDescription,
),
),
@@ -293,21 +305,23 @@ class TwoGangSwitchHelper {
DeviceFunctionData? selectedFunctionData,
String selectCode,
) {
const twelveHoursInSeconds = 43200.0;
final operationalValues = SwitchOperationalValue(
icon: '',
description: "sec",
value: 0.0,
minValue: 0,
maxValue: 86400,
maxValue: twelveHoursInSeconds,
stepValue: 1,
);
return Slider(
value: (initialValue ?? 0).toDouble(),
min: operationalValues.minValue?.toDouble() ?? 0.0,
max: operationalValues.maxValue?.toDouble() ?? 0.0,
divisions: (((operationalValues.maxValue ?? 0) - (operationalValues.minValue ?? 0)) /
(operationalValues.stepValue ?? 1))
.round(),
divisions:
(((operationalValues.maxValue ?? 0) - (operationalValues.minValue ?? 0)) /
(operationalValues.stepValue ?? 1))
.round(),
onChanged: (value) {
context.read<FunctionBloc>().add(
AddFunction(
@@ -359,7 +373,9 @@ class TwoGangSwitchHelper {
trailing: Icon(
isSelected ? Icons.radio_button_checked : Icons.radio_button_unchecked,
size: 24,
color: isSelected ? ColorsManager.primaryColorWithOpacity : ColorsManager.textGray,
color: isSelected
? ColorsManager.primaryColorWithOpacity
: ColorsManager.textGray,
),
onTap: () {
if (!isSelected) {

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