Merged with dev

This commit is contained in:
Abdullah Alassaf
2024-11-28 12:14:19 +03:00
149 changed files with 9889 additions and 248 deletions
+9 -3
View File
@@ -6,6 +6,7 @@ import 'package:go_router/go_router.dart';
import 'package:syncrow_web/pages/auth/bloc/auth_bloc.dart';
import 'package:syncrow_web/pages/home/bloc/home_bloc.dart';
import 'package:syncrow_web/pages/home/bloc/home_event.dart';
import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_bloc.dart';
import 'package:syncrow_web/services/locator.dart';
import 'package:syncrow_web/utils/app_routes.dart';
@@ -14,7 +15,8 @@ import 'package:syncrow_web/utils/theme/theme.dart';
Future<void> main() async {
try {
const environment = String.fromEnvironment('FLAVOR', defaultValue: 'development');
const environment =
String.fromEnvironment('FLAVOR', defaultValue: 'development');
await dotenv.load(fileName: '.env.$environment');
WidgetsFlutterBinding.ensureInitialized();
initialSetup();
@@ -46,10 +48,14 @@ class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider(create: (context) => HomeBloc()..add(const FetchUserInfo())),
BlocProvider(
create: (context) => HomeBloc()..add(const FetchUserInfo())),
BlocProvider<VisitorPasswordBloc>(
create: (context) => VisitorPasswordBloc(),
)
),
BlocProvider<RoutineBloc>(
create: (context) => RoutineBloc(),
),
],
child: MaterialApp.router(
debugShowCheckedModeBanner: false,
+1 -1
View File
@@ -1,7 +1,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/device_managment/all_devices/bloc/device_managment_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
@@ -1,59 +1,107 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:syncrow_web/utils/style.dart';
class StatefulTextField extends StatefulWidget {
const StatefulTextField(
{super.key,
required this.title,
this.hintText = 'Please enter',
required this.width,
this.elevation = 0,
required this.controller,
this.onSubmitted});
const StatefulTextField({
super.key,
required this.title,
this.hintText = 'Please enter',
required this.width,
this.elevation,
required this.controller,
this.onSubmitted,
this.boxDecoration,
this.borderRadius,
this.height,
this.padding,
this.icon,
this.hintColor,
required this.onChanged,
this.isRequired,
});
final String title;
final String hintText;
final double width;
final double elevation;
final double? elevation;
final TextEditingController controller;
final Function? onSubmitted;
final BoxDecoration? boxDecoration;
final double? borderRadius;
final double? height;
final double? padding;
final IconData? icon;
final Color? hintColor;
final Function(String)? onChanged;
final bool? isRequired;
@override
State<StatefulTextField> createState() => _StatefulTextFieldState();
}
class _StatefulTextFieldState extends State<StatefulTextField> {
@override
void dispose() {
widget.controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Container(
child: CustomTextField(
title: widget.title,
controller: widget.controller,
hintText: widget.hintText,
width: widget.width,
elevation: widget.elevation,
onSubmittedFun: widget.onSubmitted),
return CustomTextField(
title: widget.title,
controller: widget.controller,
hintText: widget.hintText,
width: widget.width,
elevation: widget.elevation,
onSubmittedFun: widget.onSubmitted,
boxDecoration: widget.boxDecoration,
borderRadius: widget.borderRadius,
height: widget.height,
padding: widget.padding,
icon: widget.icon,
hintColor: widget.hintColor,
onChanged: widget.onChanged,
isRequired: widget.isRequired,
);
}
}
class CustomTextField extends StatelessWidget {
const CustomTextField(
{super.key,
required this.title,
required this.controller,
this.hintText = 'Please enter',
required this.width,
this.elevation = 0,
this.onSubmittedFun});
const CustomTextField({
super.key,
required this.title,
required this.controller,
this.hintText = 'Please enter',
required this.width,
this.elevation,
this.onSubmittedFun,
this.boxDecoration,
this.borderRadius,
this.height,
this.padding,
this.icon,
this.hintColor,
required this.onChanged,
this.isRequired,
});
final String title;
final TextEditingController controller;
final String hintText;
final double width;
final double elevation;
final double? elevation;
final Function? onSubmittedFun;
final BoxDecoration? boxDecoration;
final double? borderRadius;
final double? height;
final double? padding;
final IconData? icon;
final Color? hintColor;
final Function(String)? onChanged;
final bool? isRequired;
@override
Widget build(BuildContext context) {
@@ -61,40 +109,65 @@ class CustomTextField extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
title,
style: context.textTheme.bodyMedium!.copyWith(
fontSize: 13,
fontWeight: FontWeight.w600,
color: const Color(0xff000000),
),
Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Visibility(
visible: isRequired == true,
child: Text('* ',
style: context.textTheme.bodyMedium!
.copyWith(color: Colors.red, fontSize: 13)),
),
Text(
title,
style: context.textTheme.bodyMedium!.copyWith(
fontSize: 13,
fontWeight: FontWeight.w600,
color: const Color(0xff000000),
),
),
],
),
const SizedBox(height: 8),
Material(
elevation: elevation,
borderRadius: BorderRadius.circular(8),
elevation: elevation ?? 0,
borderRadius: BorderRadius.circular(borderRadius ?? 8),
child: Container(
width: width,
height: 45,
decoration: containerDecoration,
// decoration: BoxDecoration(
// color: Colors.white,
// borderRadius: BorderRadius.circular(8),
// ),
height: height ?? 45,
decoration: boxDecoration ?? containerDecoration,
child: TextFormField(
controller: controller,
style: const TextStyle(color: Colors.black),
decoration: InputDecoration(
hintText: hintText,
hintStyle: const TextStyle(fontSize: 12),
contentPadding:
const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
hintStyle: TextStyle(
fontSize: 12, color: hintColor ?? ColorsManager.blackColor),
contentPadding: EdgeInsets.symmetric(
horizontal: 12, vertical: padding ?? 10),
border: InputBorder.none,
suffixIcon: icon != null
? Icon(icon, color: ColorsManager.greyColor)
: null,
),
onFieldSubmitted: (_) {
onSubmittedFun!();
},
onChanged: (value) {
onChanged!(value);
},
/// required validator
validator: (value) {
if (isRequired == true) {
if (value == null || value.isEmpty) {
return 'This field is required';
}
}
return null;
},
),
),
),
@@ -1,8 +1,5 @@
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
enum TempModes { hot, cold, wind }
enum FanSpeeds { auto, low, middle, high }
import 'package:syncrow_web/utils/constants/app_enum.dart';
class AcStatusModel {
final String uuid;
@@ -3,9 +3,9 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_bloc.dart';
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_event.dart';
import 'package:syncrow_web/pages/device_managment/ac/model/ac_model.dart';
import 'package:syncrow_web/pages/device_managment/shared/device_controls_container.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/app_enum.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
class BatchAcMode extends StatelessWidget {
@@ -27,15 +27,19 @@ class BatchAcMode extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
_buildIconContainer(context, TempModes.cold, Assets.freezing, value == TempModes.cold),
_buildIconContainer(context, TempModes.hot, Assets.acSun, value == TempModes.hot),
_buildIconContainer(context, TempModes.wind, Assets.acAirConditioner, value == TempModes.wind),
_buildIconContainer(context, TempModes.cold, Assets.freezing,
value == TempModes.cold),
_buildIconContainer(
context, TempModes.hot, Assets.acSun, value == TempModes.hot),
_buildIconContainer(context, TempModes.wind, Assets.acAirConditioner,
value == TempModes.wind),
],
),
);
}
Widget _buildIconContainer(BuildContext context, TempModes mode, String assetPath, bool isSelected) {
Widget _buildIconContainer(
BuildContext context, TempModes mode, String assetPath, bool isSelected) {
return Flexible(
child: GestureDetector(
onTap: () {
@@ -3,9 +3,9 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_bloc.dart';
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_event.dart';
import 'package:syncrow_web/pages/device_managment/ac/model/ac_model.dart';
import 'package:syncrow_web/pages/device_managment/shared/device_controls_container.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/app_enum.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
class BatchFanSpeedControl extends StatelessWidget {
@@ -30,8 +30,10 @@ class BatchFanSpeedControl extends StatelessWidget {
runSpacing: 8,
spacing: 8,
children: [
_buildIconContainer(context, FanSpeeds.auto, Assets.acFanAuto, value == FanSpeeds.auto),
_buildIconContainer(context, FanSpeeds.low, Assets.acFanLow, value == FanSpeeds.low),
_buildIconContainer(context, FanSpeeds.auto, Assets.acFanAuto,
value == FanSpeeds.auto),
_buildIconContainer(context, FanSpeeds.low, Assets.acFanLow,
value == FanSpeeds.low),
],
),
const SizedBox(height: 8),
@@ -39,8 +41,10 @@ class BatchFanSpeedControl extends StatelessWidget {
runSpacing: 8,
spacing: 8,
children: [
_buildIconContainer(context, FanSpeeds.middle, Assets.acFanMiddle, value == FanSpeeds.middle),
_buildIconContainer(context, FanSpeeds.high, Assets.acFanHigh, value == FanSpeeds.high),
_buildIconContainer(context, FanSpeeds.middle, Assets.acFanMiddle,
value == FanSpeeds.middle),
_buildIconContainer(context, FanSpeeds.high, Assets.acFanHigh,
value == FanSpeeds.high),
],
)
],
@@ -48,7 +52,8 @@ class BatchFanSpeedControl extends StatelessWidget {
);
}
Widget _buildIconContainer(BuildContext context, FanSpeeds speed, String assetPath, bool isSelected) {
Widget _buildIconContainer(BuildContext context, FanSpeeds speed,
String assetPath, bool isSelected) {
return GestureDetector(
onTap: () {
context.read<AcBloc>().add(
@@ -3,9 +3,9 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_bloc.dart';
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_event.dart';
import 'package:syncrow_web/pages/device_managment/ac/model/ac_model.dart';
import 'package:syncrow_web/pages/device_managment/shared/device_controls_container.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/app_enum.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
class AcMode extends StatelessWidget {
@@ -27,15 +27,19 @@ class AcMode extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
_buildIconContainer(context, TempModes.cold, Assets.freezing, value == TempModes.cold),
_buildIconContainer(context, TempModes.hot, Assets.acSun, value == TempModes.hot),
_buildIconContainer(context, TempModes.wind, Assets.acAirConditioner, value == TempModes.wind),
_buildIconContainer(context, TempModes.cold, Assets.freezing,
value == TempModes.cold),
_buildIconContainer(
context, TempModes.hot, Assets.acSun, value == TempModes.hot),
_buildIconContainer(context, TempModes.wind, Assets.acAirConditioner,
value == TempModes.wind),
],
),
);
}
Widget _buildIconContainer(BuildContext context, TempModes mode, String assetPath, bool isSelected) {
Widget _buildIconContainer(
BuildContext context, TempModes mode, String assetPath, bool isSelected) {
return Flexible(
child: GestureDetector(
onTap: () {
@@ -3,9 +3,9 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_bloc.dart';
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_event.dart';
import 'package:syncrow_web/pages/device_managment/ac/model/ac_model.dart';
import 'package:syncrow_web/pages/device_managment/shared/device_controls_container.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/app_enum.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
class FanSpeedControl extends StatelessWidget {
@@ -29,8 +29,10 @@ class FanSpeedControl extends StatelessWidget {
runSpacing: 8,
spacing: 8,
children: [
_buildIconContainer(context, FanSpeeds.auto, Assets.acFanAuto, value == FanSpeeds.auto),
_buildIconContainer(context, FanSpeeds.low, Assets.acFanLow, value == FanSpeeds.low),
_buildIconContainer(context, FanSpeeds.auto, Assets.acFanAuto,
value == FanSpeeds.auto),
_buildIconContainer(context, FanSpeeds.low, Assets.acFanLow,
value == FanSpeeds.low),
],
),
const SizedBox(height: 8),
@@ -38,8 +40,10 @@ class FanSpeedControl extends StatelessWidget {
runSpacing: 8,
spacing: 8,
children: [
_buildIconContainer(context, FanSpeeds.middle, Assets.acFanMiddle, value == FanSpeeds.middle),
_buildIconContainer(context, FanSpeeds.high, Assets.acFanHigh, value == FanSpeeds.high),
_buildIconContainer(context, FanSpeeds.middle, Assets.acFanMiddle,
value == FanSpeeds.middle),
_buildIconContainer(context, FanSpeeds.high, Assets.acFanHigh,
value == FanSpeeds.high),
],
)
],
@@ -47,7 +51,8 @@ class FanSpeedControl extends StatelessWidget {
);
}
Widget _buildIconContainer(BuildContext context, FanSpeeds speed, String assetPath, bool isSelected) {
Widget _buildIconContainer(BuildContext context, FanSpeeds speed,
String assetPath, bool isSelected) {
return GestureDetector(
onTap: () {
context.read<AcBloc>().add(
@@ -0,0 +1,28 @@
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
part 'switch_tabs_event.dart';
part 'switch_tabs_state.dart';
class SwitchTabsBloc extends Bloc<SwitchTabsEvent, SwitchTabsState> {
SwitchTabsBloc() : super(SwitchTabsInitial()) {
on<TriggerSwitchTabsEvent>(_switchTab);
on<CreateNewRoutineViewEvent>(_newRoutineView);
}
FutureOr<void> _switchTab(
TriggerSwitchTabsEvent event,
Emitter<SwitchTabsState> emit,
) {
emit(SelectedTabState(event.isRoutineView));
}
FutureOr<void> _newRoutineView(
CreateNewRoutineViewEvent event,
Emitter<SwitchTabsState> emit,
) {
emit(ShowCreateRoutineState(event.showCreateNewRoutineView));
}
}
@@ -0,0 +1,21 @@
part of 'switch_tabs_bloc.dart';
sealed class SwitchTabsEvent extends Equatable {
const SwitchTabsEvent();
}
class TriggerSwitchTabsEvent extends SwitchTabsEvent {
final bool isRoutineView;
const TriggerSwitchTabsEvent(this.isRoutineView);
@override
List<Object?> get props => [isRoutineView];
}
class CreateNewRoutineViewEvent extends SwitchTabsEvent {
final bool showCreateNewRoutineView;
const CreateNewRoutineViewEvent(this.showCreateNewRoutineView);
@override
List<Object?> get props => [showCreateNewRoutineView];
}
@@ -0,0 +1,26 @@
part of 'switch_tabs_bloc.dart';
sealed class SwitchTabsState extends Equatable {
const SwitchTabsState();
}
final class SwitchTabsInitial extends SwitchTabsState {
@override
List<Object> get props => [];
}
class SelectedTabState extends SwitchTabsState {
final bool selectedTab;
const SelectedTabState(this.selectedTab);
@override
List<Object?> get props => [selectedTab];
}
class ShowCreateRoutineState extends SwitchTabsState {
final bool showCreateRoutine;
const ShowCreateRoutineState(this.showCreateRoutine);
@override
List<Object?> get props => [showCreateRoutine];
}
@@ -2,7 +2,13 @@ import 'package:syncrow_web/pages/device_managment/all_devices/models/device_com
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_space_model.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/room.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/unit.dart';
import 'package:syncrow_web/pages/routiens/models/ac/ac_function.dart';
import 'package:syncrow_web/pages/routiens/models/device_functions.dart';
import 'package:syncrow_web/pages/routiens/models/gang_switches/one_gang_switch/one_gang_switch.dart';
import 'package:syncrow_web/pages/routiens/models/gang_switches/three_gang_switch/three_gang_switch.dart';
import 'package:syncrow_web/pages/routiens/models/gang_switches/two_gang_switch/two_gang_switch.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/enum/device_types.dart';
class AllDevicesModel {
/*
@@ -99,6 +105,7 @@ class AllDevicesModel {
this.productName,
this.spaces,
});
AllDevicesModel.fromJson(Map<String, dynamic> json) {
room = (json['room'] != null && (json['room'] is Map))
? DevicesModelRoom.fromJson(json['room'])
@@ -117,7 +124,7 @@ class AllDevicesModel {
categoryName = json['categoryName']?.toString();
createTime = int.tryParse(json['createTime']?.toString() ?? '');
gatewayId = json['gatewayId']?.toString();
icon = json['icon'] ?? _getDefaultIcon(productType);
icon = json['icon'] ?? getDefaultIcon(productType);
ip = json['ip']?.toString();
lat = json['lat']?.toString();
localKey = json['localKey']?.toString();
@@ -132,6 +139,7 @@ class AllDevicesModel {
updateTime = int.tryParse(json['updateTime']?.toString() ?? '');
uuid = json['uuid']?.toString();
batteryLevel = int.tryParse(json['battery']?.toString() ?? '');
productName = json['productName']?.toString();
if (json['spaces'] != null && json['spaces'] is List) {
spaces = (json['spaces'] as List)
@@ -140,31 +148,138 @@ class AllDevicesModel {
}
}
String _getDefaultIcon(String? productType) {
String getDefaultIcon(String? productType) {
/*
AC
GD
3G
3G
GW
DL
WPS
CPS
AC
CPS
WPS
GW
AC
CUR
DS
1GT
2GT
3GT
1G
1G
2G
2G
DS
WH
1GT
2GT
3GT
GD
WL
WL
3G
CUR
GW
PC
PC
SOS
*/
DeviceType type = devicesTypesMap[productType] ?? DeviceType.Other;
String tempIcon = '';
if (type == DeviceType.LightBulb) {
tempIcon = Assets.lightBulb;
} else if (type == DeviceType.CeilingSensor ||
type == DeviceType.WallSensor) {
tempIcon = Assets.sensors;
} else if (type == DeviceType.AC) {
tempIcon = Assets.ac;
} else if (type == DeviceType.DoorLock) {
tempIcon = Assets.doorLock;
} else if (type == DeviceType.Curtain) {
tempIcon = Assets.curtain;
} else if (type == DeviceType.ThreeGang) {
tempIcon = Assets.gangSwitch;
} else if (type == DeviceType.Gateway) {
tempIcon = Assets.gateway;
} else if (type == DeviceType.OneGang) {
tempIcon = Assets.oneGang;
} else if (type == DeviceType.TwoGang) {
tempIcon = Assets.twoGang;
} else if (type == DeviceType.WH) {
tempIcon = Assets.waterHeater;
} else if (type == DeviceType.DS) {
// tempIcon = Assets.mainDoor;
} else if (type == DeviceType.OneTouch) {
// tempIcon = Assets.oneGang;
} else if (type == DeviceType.TowTouch) {
// tempIcon = Assets.twoGang;
} else if (type == DeviceType.GarageDoor) {
//tempIcon = Assets.;
} else if (type == DeviceType.ThreeTouch) {
// tempIcon = Assets.gang3touch;
} else if (type == DeviceType.WaterLeak) {
tempIcon = Assets.waterLeakNormal;
} else {
tempIcon = Assets.logoHorizontal;
}
return tempIcon;
}
List<DeviceFunction> get functions {
return _getDeviceFunctions();
}
//! Functions for Devices Types
List<DeviceFunction> _getDeviceFunctions() {
switch (productType) {
case 'LightBulb':
return Assets.lightBulb;
case 'CeilingSensor':
case 'WallSensor':
return Assets.sensors;
case 'AC':
return Assets.ac;
case 'DoorLock':
return Assets.doorLock;
case 'Curtain':
return Assets.curtain;
case '3G':
case '2G':
return [
SwitchFunction(deviceId: uuid ?? '', deviceName: name ?? ''),
ModeFunction(deviceId: uuid ?? '', deviceName: name ?? ''),
TempSetFunction(deviceId: uuid ?? '', deviceName: name ?? ''),
LevelFunction(deviceId: uuid ?? '', deviceName: name ?? ''),
ChildLockFunction(deviceId: uuid ?? '', deviceName: name ?? ''),
];
case '1G':
return Assets.gangSwitch;
case 'Gateway':
return Assets.gateway;
case 'WH':
return Assets.blackLogo;
case 'DS':
return Assets.sensors;
return [
OneGangSwitchFunction(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 ?? ''),
];
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 ?? ''),
];
default:
return Assets.logo;
return [];
}
}
@@ -271,4 +386,23 @@ class AllDevicesModel {
productName.hashCode ^
batteryLevel.hashCode;
}
Map<String, DeviceType> devicesTypesMap = {
"AC": DeviceType.AC,
"GW": DeviceType.Gateway,
"CPS": DeviceType.CeilingSensor,
"DL": DeviceType.DoorLock,
"WPS": DeviceType.WallSensor,
"3G": DeviceType.ThreeGang,
"2G": DeviceType.TwoGang,
"1G": DeviceType.OneGang,
"CUR": DeviceType.Curtain,
"WH": DeviceType.WH,
"DS": DeviceType.DS,
"1GT": DeviceType.OneTouch,
"2GT": DeviceType.TowTouch,
"3GT": DeviceType.ThreeTouch,
"GD": DeviceType.GarageDoor,
"WL": DeviceType.WaterLeak,
};
}
@@ -1,18 +1,31 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/bloc/device_managment_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/bloc/switch_tabs/switch_tabs_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/web_layout/web_scaffold.dart';
import 'package:syncrow_web/pages/routiens/view/create_new_routine_view.dart';
import 'package:syncrow_web/pages/routiens/view/routines_view.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
import 'package:syncrow_web/web_layout/web_scaffold.dart';
class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout {
const DeviceManagementPage({super.key});
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => DeviceManagementBloc()..add(FetchDevices()),
return MultiBlocProvider(
providers: [
BlocProvider(
create: (context) =>
SwitchTabsBloc()..add(const TriggerSwitchTabsEvent(false)),
),
BlocProvider(
create: (context) => DeviceManagementBloc()..add(FetchDevices()),
),
],
child: WebScaffold(
appBarTitle: FittedBox(
child: Text(
@@ -20,26 +33,86 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout {
style: Theme.of(context).textTheme.headlineLarge,
),
),
centerBody: BlocBuilder<SwitchTabsBloc, SwitchTabsState>(
builder: (context, state) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
TextButton(
style: TextButton.styleFrom(
backgroundColor: null,
),
onPressed: () {
context
.read<SwitchTabsBloc>()
.add(const TriggerSwitchTabsEvent(false));
},
child: Text(
'Devices',
style: context.textTheme.titleMedium?.copyWith(
color:
state is SelectedTabState && state.selectedTab == false
? ColorsManager.whiteColors
: ColorsManager.grayColor,
fontWeight: (state is SelectedTabState) &&
state.selectedTab == false
? FontWeight.w700
: FontWeight.w400,
),
),
),
TextButton(
style: TextButton.styleFrom(
backgroundColor: null,
),
onPressed: () {
context
.read<SwitchTabsBloc>()
.add(const TriggerSwitchTabsEvent(true));
},
child: Text(
'Routines',
style: context.textTheme.titleMedium?.copyWith(
color:
(state is SelectedTabState) && state.selectedTab == true
? ColorsManager.whiteColors
: ColorsManager.grayColor,
fontWeight:
(state is SelectedTabState) && state.selectedTab == true
? FontWeight.w700
: FontWeight.w400,
),
),
),
],
);
}),
rightBody: const NavigateHomeGridView(),
scaffoldBody: BlocBuilder<DeviceManagementBloc, DeviceManagementState>(
builder: (context, state) {
if (state is DeviceManagementLoading) {
return const Center(child: CircularProgressIndicator());
} else if (state is DeviceManagementLoaded || state is DeviceManagementFiltered) {
final devices = state is DeviceManagementLoaded
? state.devices
: (state as DeviceManagementFiltered).filteredDevices;
scaffoldBody: BlocBuilder<SwitchTabsBloc, SwitchTabsState>(
builder: (context, state) {
if (state is SelectedTabState && state.selectedTab) {
return const RoutinesView();
}
if (state is ShowCreateRoutineState && state.showCreateRoutine) {
return const CreateNewRoutineView();
}
return DeviceManagementBody(devices: devices);
} else {
return const Center(child: Text('Error fetching Devices'));
}
},
),
return BlocBuilder<DeviceManagementBloc, DeviceManagementState>(
builder: (context, deviceState) {
if (deviceState is DeviceManagementLoading) {
return const Center(child: CircularProgressIndicator());
} else if (deviceState is DeviceManagementLoaded) {
return DeviceManagementBody(devices: deviceState.devices);
} else if (deviceState is DeviceManagementFiltered) {
return DeviceManagementBody(
devices: deviceState.filteredDevices);
} else {
return const Center(child: Text('Error fetching Devices'));
}
},
);
}),
),
);
}
}
@@ -3,7 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
import 'package:syncrow_web/pages/common/custom_table.dart';
import 'package:syncrow_web/pages/common/filter/filter_widget.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/bloc/device_managment_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/widgets/device_search_filters.dart';
import 'package:syncrow_web/pages/device_managment/shared/device_batch_control_dialog.dart';
@@ -57,15 +57,12 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
'Low Battery ($lowBatteryCount)',
];
final buttonLabel =
(selectedDevices.length > 1) ? 'Batch Control' : 'Control';
final buttonLabel = (selectedDevices.length > 1) ? 'Batch Control' : 'Control';
return Column(
children: [
Container(
padding: isLargeScreenSize(context)
? const EdgeInsets.all(30)
: const EdgeInsets.all(15),
padding: isLargeScreenSize(context) ? const EdgeInsets.all(30) : const EdgeInsets.all(15),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@@ -74,9 +71,7 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
tabs: tabs,
selectedIndex: selectedIndex,
onTabChanged: (index) {
context
.read<DeviceManagementBloc>()
.add(SelectedFilterChanged(index));
context.read<DeviceManagementBloc>().add(SelectedFilterChanged(index));
},
),
const SizedBox(height: 20),
@@ -98,14 +93,11 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
),
);
} else if (selectedDevices.length > 1) {
final productTypes = selectedDevices
.map((device) => device.productType)
.toSet();
final productTypes = selectedDevices.map((device) => device.productType).toSet();
if (productTypes.length == 1) {
showDialog(
context: context,
builder: (context) =>
DeviceBatchControlDialog(
builder: (context) => DeviceBatchControlDialog(
devices: selectedDevices,
),
);
@@ -119,9 +111,7 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 12,
color: isControlButtonEnabled
? Colors.white
: Colors.grey,
color: isControlButtonEnabled ? Colors.white : Colors.grey,
),
),
),
@@ -132,17 +122,13 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
),
Expanded(
child: Padding(
padding: isLargeScreenSize(context)
? const EdgeInsets.all(30)
: const EdgeInsets.all(15),
padding: isLargeScreenSize(context) ? const EdgeInsets.all(30) : const EdgeInsets.all(15),
child: DynamicTable(
withSelectAll: true,
cellDecoration: containerDecoration,
onRowSelected: (index, isSelected, row) {
final selectedDevice = devicesToShow[index];
context
.read<DeviceManagementBloc>()
.add(SelectDevice(selectedDevice));
context.read<DeviceManagementBloc>().add(SelectDevice(selectedDevice));
},
withCheckBox: true,
size: MediaQuery.of(context).size,
@@ -160,44 +146,27 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
],
data: devicesToShow.map((device) {
final combinedSpaceNames = device.spaces != null
? device.spaces!
.map((space) => space.spaceName)
.join(' > ') +
(device.community != null
? ' > ${device.community!.name}'
: '')
: (device.community != null
? device.community!.name
: '');
? device.spaces!.map((space) => space.spaceName).join(' > ') +
(device.community != null ? ' > ${device.community!.name}' : '')
: (device.community != null ? device.community!.name : '');
return [
device.name ?? '',
device.productName ?? '',
device.uuid ?? '',
(device.spaces != null && device.spaces!.isNotEmpty)
? device.spaces![0].spaceName
: '',
(device.spaces != null && device.spaces!.isNotEmpty) ? device.spaces![0].spaceName : '',
combinedSpaceNames,
device.batteryLevel != null
? '${device.batteryLevel}%'
: '-',
formatDateTime(DateTime.fromMillisecondsSinceEpoch(
(device.createTime ?? 0) * 1000)),
device.batteryLevel != null ? '${device.batteryLevel}%' : '-',
formatDateTime(DateTime.fromMillisecondsSinceEpoch((device.createTime ?? 0) * 1000)),
device.online == true ? 'Online' : 'Offline',
formatDateTime(DateTime.fromMillisecondsSinceEpoch(
(device.updateTime ?? 0) * 1000)),
formatDateTime(DateTime.fromMillisecondsSinceEpoch((device.updateTime ?? 0) * 1000)),
];
}).toList(),
onSelectionChanged: (selectedRows) {
context
.read<DeviceManagementBloc>()
.add(UpdateSelection(selectedRows));
context.read<DeviceManagementBloc>().add(UpdateSelection(selectedRows));
},
initialSelectedIds: context
.read<DeviceManagementBloc>()
.selectedDevices
.map((device) => device.uuid!)
.toList(),
initialSelectedIds:
context.read<DeviceManagementBloc>().selectedDevices.map((device) => device.uuid!).toList(),
isEmpty: devicesToShow.isEmpty,
),
),
@@ -1,8 +1,8 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/common/text_field/custom_text_field.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/bloc/device_managment_bloc.dart';
import 'package:syncrow_web/pages/common/buttons/search_reset_buttons.dart';
import 'package:syncrow_web/pages/common/text_field/custom_text_field.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
class DeviceSearchFilters extends StatefulWidget {
@@ -18,14 +18,6 @@ class _DeviceSearchFiltersState extends State<DeviceSearchFilters>
final TextEditingController unitNameController = TextEditingController();
final TextEditingController productNameController = TextEditingController();
@override
void dispose() {
communityController.dispose();
unitNameController.dispose();
productNameController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return isExtraLargeScreenSize(context)
@@ -76,6 +68,7 @@ class _DeviceSearchFiltersState extends State<DeviceSearchFilters>
community: communityController.text,
searchField: true));
},
onChanged: (p0) {},
),
);
}
@@ -0,0 +1,108 @@
import 'dart:async';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/routiens/bloc/effective_period/effect_period_event.dart';
import 'package:syncrow_web/pages/routiens/bloc/effective_period/effect_period_state.dart';
import 'package:syncrow_web/utils/constants/app_enum.dart';
class EffectPeriodBloc extends Bloc<EffectPeriodEvent, EffectPeriodState> {
final daysMap = {
'Sun': 'S',
'Mon': 'M',
'Tue': 'T',
'Wed': 'W',
'Thu': 'T',
'Fri': 'F',
'Sat': 'S',
};
EffectPeriodBloc() : super(EffectPeriodState.initial()) {
on<SetPeriod>(_onSetPeriod);
on<ToggleDay>(_onToggleDay);
on<SetCustomTime>(_onSetCustomTime);
on<ResetEffectivePeriod>(_onResetEffectivePeriod);
on<ResetDays>(_onResetDays);
on<SetDays>(_setAllDays);
}
void _onSetPeriod(SetPeriod event, Emitter<EffectPeriodState> emit) {
String startTime = '';
String endTime = '';
switch (event.period) {
case EnumEffectivePeriodOptions.allDay:
startTime = '00:00';
endTime = '23:59';
break;
case EnumEffectivePeriodOptions.daytime:
startTime = '06:00';
endTime = '18:00';
break;
case EnumEffectivePeriodOptions.night:
startTime = '18:00';
endTime = '06:00';
break;
case EnumEffectivePeriodOptions.custom:
startTime = state.customStartTime ?? '00:00';
endTime = state.customEndTime ?? '23:59';
break;
default:
break;
}
emit(state.copyWith(
selectedPeriod: event.period, customStartTime: startTime, customEndTime: endTime));
}
void _onToggleDay(ToggleDay event, Emitter<EffectPeriodState> emit) {
final daysList = state.selectedDaysBinary.split('');
final dayIndex = getDayIndex(event.day);
if (daysList[dayIndex] == '1') {
daysList[dayIndex] = '0';
} else {
daysList[dayIndex] = '1';
}
final newDaysBinary = daysList.join();
emit(state.copyWith(selectedDaysBinary: newDaysBinary));
}
void _onSetCustomTime(SetCustomTime event, Emitter<EffectPeriodState> emit) {
String startTime = event.startTime;
String endTime = event.endTime;
EnumEffectivePeriodOptions period;
// Determine the period based on start and end times
if (startTime == '00:00' && endTime == '23:59') {
period = EnumEffectivePeriodOptions.allDay;
} else if (startTime == '06:00' && endTime == '18:00') {
period = EnumEffectivePeriodOptions.daytime;
} else if (startTime == '18:00' && endTime == '06:00') {
period = EnumEffectivePeriodOptions.night;
} else {
period = EnumEffectivePeriodOptions.custom;
}
emit(
state.copyWith(customStartTime: startTime, customEndTime: endTime, selectedPeriod: period));
}
void _onResetEffectivePeriod(ResetEffectivePeriod event, Emitter<EffectPeriodState> emit) {
emit(state.copyWith(
selectedPeriod: EnumEffectivePeriodOptions.allDay,
customStartTime: '00:00',
customEndTime: '23:59',
selectedDaysBinary: '1111111'));
}
void _onResetDays(ResetDays event, Emitter<EffectPeriodState> emit) {
emit(state.copyWith(selectedDaysBinary: '1111111'));
}
int getDayIndex(String day) {
const days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
return days.indexOf(day);
}
FutureOr<void> _setAllDays(SetDays event, Emitter<EffectPeriodState> emit) {
emit(state.copyWith(selectedDaysBinary: event.daysBinary));
}
}
@@ -0,0 +1,50 @@
import 'package:equatable/equatable.dart';
import 'package:syncrow_web/utils/constants/app_enum.dart';
abstract class EffectPeriodEvent extends Equatable {
const EffectPeriodEvent();
@override
List<Object> get props => [];
}
class SetPeriod extends EffectPeriodEvent {
final EnumEffectivePeriodOptions period;
const SetPeriod(this.period);
@override
List<Object> get props => [period];
}
class ToggleDay extends EffectPeriodEvent {
final String day;
const ToggleDay(this.day);
@override
List<Object> get props => [day];
}
class SetCustomTime extends EffectPeriodEvent {
final String startTime;
final String endTime;
const SetCustomTime(this.startTime, this.endTime);
@override
List<Object> get props => [startTime, endTime];
}
class ResetEffectivePeriod extends EffectPeriodEvent {}
class ResetDays extends EffectPeriodEvent {
@override
List<Object> get props => [];
}
class SetDays extends EffectPeriodEvent {
final String daysBinary;
const SetDays(this.daysBinary);
}
@@ -0,0 +1,54 @@
import 'package:equatable/equatable.dart';
import 'package:syncrow_web/utils/constants/app_enum.dart';
class EffectPeriodState extends Equatable {
final EnumEffectivePeriodOptions selectedPeriod;
final String selectedDaysBinary;
final String? customStartTime;
final String? customEndTime;
const EffectPeriodState({
required this.selectedPeriod,
required this.selectedDaysBinary,
this.customStartTime,
this.customEndTime,
});
factory EffectPeriodState.initial() {
return const EffectPeriodState(
selectedPeriod: EnumEffectivePeriodOptions.allDay,
selectedDaysBinary: "1111111", // All days selected
customStartTime: "00:00",
customEndTime: "23:59",
);
}
EffectPeriodState copyWith({
EnumEffectivePeriodOptions? selectedPeriod,
String? selectedDaysBinary,
String? customStartTime,
String? customEndTime,
}) {
return EffectPeriodState(
selectedPeriod: selectedPeriod ?? this.selectedPeriod,
selectedDaysBinary: selectedDaysBinary ?? this.selectedDaysBinary,
customStartTime: customStartTime ?? this.customStartTime,
customEndTime: customEndTime ?? this.customEndTime,
);
}
EnumEffectivePeriodOptions getEffectivePeriod() {
if (customStartTime == '00:00' && customEndTime == '23:59') {
return EnumEffectivePeriodOptions.allDay;
} else if (customStartTime == '06:00' && customEndTime == '18:00') {
return EnumEffectivePeriodOptions.daytime;
} else if (customStartTime == '18:00' && customEndTime == '06:00') {
return EnumEffectivePeriodOptions.night;
} else {
return EnumEffectivePeriodOptions.custom;
}
}
@override
List<Object?> get props => [selectedPeriod, selectedDaysBinary, customStartTime, customEndTime];
}
@@ -0,0 +1,68 @@
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/routiens/models/device_functions.dart';
part 'functions_bloc_event.dart';
part 'functions_bloc_state.dart';
class FunctionBloc extends Bloc<FunctionBlocEvent, FunctionBlocState> {
FunctionBloc() : super(const FunctionBlocState()) {
on<InitializeFunctions>(_onInitializeFunctions);
on<AddFunction>(_onAddFunction);
on<SelectFunction>(_onSelectFunction);
}
void _onAddFunction(AddFunction event, Emitter<FunctionBlocState> emit) {
final functions = List<DeviceFunctionData>.from(state.addedFunctions);
final existingIndex = functions.indexWhere(
(f) => f.functionCode == event.functionData.functionCode,
);
if (existingIndex != -1) {
final existingData = functions[existingIndex];
functions[existingIndex] = DeviceFunctionData(
entityId: event.functionData.entityId,
functionCode: event.functionData.functionCode,
operationName: event.functionData.operationName,
value: event.functionData.value ?? existingData.value,
valueDescription: event.functionData.valueDescription ??
existingData.valueDescription,
condition: event.functionData.condition ?? existingData.condition,
);
} else {
functions.add(event.functionData);
}
emit(state.copyWith(
addedFunctions: functions,
selectedFunction: event.functionData.functionCode,
));
}
void _onInitializeFunctions(
InitializeFunctions event,
Emitter<FunctionBlocState> emit,
) {
emit(state.copyWith(addedFunctions: event.functions));
}
DeviceFunctionData? getFunction(String functionCode) {
return state.addedFunctions.firstWhere(
(data) => data.functionCode == functionCode,
orElse: () => DeviceFunctionData(
entityId: '',
functionCode: functionCode,
operationName: '',
value: null,
),
);
}
FutureOr<void> _onSelectFunction(
SelectFunction event, Emitter<FunctionBlocState> emit) {
emit(state.copyWith(
selectedFunction: event.functionCode,
selectedOperationName: event.operationName));
}
}
@@ -0,0 +1,41 @@
part of 'functions_bloc_bloc.dart';
abstract class FunctionBlocEvent extends Equatable {
const FunctionBlocEvent();
@override
List<Object?> get props => [];
}
class AddFunction extends FunctionBlocEvent {
final DeviceFunctionData functionData;
const AddFunction({
required this.functionData,
});
@override
List<Object?> get props => [functionData];
}
class SelectFunction extends FunctionBlocEvent {
final String functionCode;
final String operationName;
const SelectFunction({
required this.functionCode,
required this.operationName,
});
@override
List<Object?> get props => [functionCode, operationName];
}
class InitializeFunctions extends FunctionBlocEvent {
final List<DeviceFunctionData> functions;
const InitializeFunctions(this.functions);
@override
List<Object?> get props => [functions];
}
@@ -0,0 +1,29 @@
part of 'functions_bloc_bloc.dart';
class FunctionBlocState extends Equatable {
final List<DeviceFunctionData> addedFunctions;
final String? selectedFunction;
final String? selectedOperationName;
const FunctionBlocState({
this.addedFunctions = const [],
this.selectedFunction,
this.selectedOperationName,
});
FunctionBlocState copyWith({
List<DeviceFunctionData>? addedFunctions,
String? selectedFunction,
String? selectedOperationName,
}) {
return FunctionBlocState(
addedFunctions: addedFunctions ?? this.addedFunctions,
selectedFunction: selectedFunction ?? this.selectedFunction,
selectedOperationName:
selectedOperationName ?? this.selectedOperationName,
);
}
@override
List<Object?> get props =>
[addedFunctions, selectedFunction, selectedOperationName];
}
@@ -0,0 +1,595 @@
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/routiens/models/create_scene_and_autoamtion/create_automation_model.dart';
import 'package:syncrow_web/pages/routiens/models/create_scene_and_autoamtion/create_scene_model.dart';
import 'package:syncrow_web/pages/routiens/models/device_functions.dart';
import 'package:syncrow_web/pages/routiens/models/routine_details_model.dart';
import 'package:syncrow_web/pages/routiens/models/routine_model.dart';
import 'package:syncrow_web/services/routines_api.dart';
part 'routine_event.dart';
part 'routine_state.dart';
const spaceId = '25c96044-fadf-44bb-93c7-3c079e527ce6';
const communityId = 'aff21a57-2f91-4e5c-b99b-0182c3ab65a9';
class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
RoutineBloc() : super(const RoutineState()) {
on<AddToIfContainer>(_onAddToIfContainer);
on<AddToThenContainer>(_onAddToThenContainer);
on<LoadScenes>(_onLoadScenes);
on<LoadAutomation>(_onLoadAutomation);
on<AddFunctionToRoutine>(_onAddFunctionsToRoutine);
on<SearchRoutines>(_onSearchRoutines);
on<AddSelectedIcon>(_onAddSelectedIcon);
on<CreateSceneEvent>(_onCreateScene);
on<RemoveDragCard>(_onRemoveDragCard);
on<ChangeAutomationOperator>(_changeOperatorOperator);
on<EffectiveTimePeriodEvent>(_onEffectiveTimeEvent);
on<CreateAutomationEvent>(_onCreateAutomation);
on<SetRoutineName>(_onSetRoutineName);
on<ResetRoutineState>(_onResetRoutineState);
on<GetSceneDetails>(_onGetSceneDetails);
on<GetAutomationDetails>(_onGetAutomationDetails);
// on<InitializeRoutineState>(_onInitializeRoutineState);
on<DeleteScene>(_deleteScene);
on<DeleteAutomation>(_deleteAutomation);
// on<RemoveFunction>(_onRemoveFunction);
// on<ClearFunctions>(_onClearFunctions);
}
void _onAddToIfContainer(AddToIfContainer event, Emitter<RoutineState> emit) {
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']);
// Replace the map if the index is valid
if (index != -1) {
updatedIfItems[index] = event.item;
} else {
updatedIfItems.add(event.item);
}
if (event.isTabToRun) {
emit(state.copyWith(
ifItems: updatedIfItems, isTabToRun: true, isAutomation: false));
} else {
emit(state.copyWith(
ifItems: updatedIfItems, isTabToRun: false, isAutomation: true));
}
}
void _onAddToThenContainer(
AddToThenContainer event, Emitter<RoutineState> emit) {
final currentItems = List<Map<String, dynamic>>.from(state.thenItems);
// Find the index of the item in teh current itemsList
int index = currentItems.indexWhere(
(map) => map['uniqueCustomId'] == event.item['uniqueCustomId']);
// Replace the map if the index is valid
if (index != -1) {
currentItems[index] = event.item;
} else {
currentItems.add(event.item);
}
emit(state.copyWith(thenItems: currentItems));
}
void _onAddFunctionsToRoutine(
AddFunctionToRoutine event, Emitter<RoutineState> emit) {
try {
if (event.functions.isEmpty) return;
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);
}
}
}
}
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);
}
emit(state.copyWith(selectedFunctions: currentSelectedFunctions));
} catch (e) {
debugPrint('Error adding functions: $e');
}
}
Future<void> _onLoadScenes(
LoadScenes event, Emitter<RoutineState> emit) async {
emit(state.copyWith(isLoading: true, errorMessage: null));
try {
final scenes =
await SceneApi.getScenesByUnitId(event.unitId, event.communityId);
emit(state.copyWith(
scenes: scenes,
isLoading: false,
));
} catch (e) {
emit(state.copyWith(
isLoading: false,
loadScenesErrorMessage: 'Failed to load scenes',
errorMessage: '',
loadAutomationErrorMessage: '',
));
}
}
Future<void> _onLoadAutomation(
LoadAutomation event, Emitter<RoutineState> emit) async {
emit(state.copyWith(isLoading: true, errorMessage: null));
try {
final automations = await SceneApi.getAutomationByUnitId(event.unitId);
if (automations.isNotEmpty) {
emit(state.copyWith(
automations: automations,
isLoading: false,
));
} else {
emit(state.copyWith(
isLoading: false,
loadAutomationErrorMessage: 'Failed to load automations',
errorMessage: '',
loadScenesErrorMessage: '',
));
}
} catch (e) {
emit(state.copyWith(
isLoading: false,
loadAutomationErrorMessage: 'Failed to load automations',
errorMessage: '',
loadScenesErrorMessage: '',
));
}
}
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) {
emit(state.copyWith(selectedIcon: event.icon));
}
bool _isFirstActionDelay(List<Map<String, dynamic>> actions) {
if (actions.isEmpty) return false;
return actions.first['deviceId'] == 'delay';
}
Future<void> _onCreateScene(
CreateSceneEvent event, Emitter<RoutineState> emit) async {
try {
// Check if first action is delay
if (_isFirstActionDelay(state.thenItems)) {
emit(state.copyWith(
errorMessage: 'Cannot have delay as the first action',
isLoading: false,
));
return;
}
emit(state.copyWith(isLoading: true));
final actions = state.thenItems.expand((item) {
final functions = state.selectedFunctions[item['uniqueCustomId']] ?? [];
return functions.map((function) {
if (function.functionCode == 'automation') {
return CreateSceneAction(
entityId: function.entityId,
actionExecutor: function.value,
executorProperty: null,
);
}
if (item['deviceId'] == 'delay') {
return CreateSceneAction(
entityId: function.entityId,
actionExecutor: 'delay',
executorProperty: CreateSceneExecutorProperty(
functionCode: '',
functionValue: '',
delaySeconds: int.tryParse(function.value.toString()) ?? 0,
),
);
}
return CreateSceneAction(
entityId: function.entityId,
actionExecutor: 'device_issue',
executorProperty: CreateSceneExecutorProperty(
functionCode: function.functionCode,
functionValue: function.value,
delaySeconds: 0,
),
);
});
}).toList();
final createSceneModel = CreateSceneModel(
spaceUuid: spaceId,
iconId: state.selectedIcon ?? '',
showInDevice: true,
sceneName: state.routineName!,
decisionExpr: 'and',
actions: actions,
);
final result = await SceneApi.createScene(createSceneModel);
if (result['success']) {
emit(_resetState());
add(const LoadScenes(spaceId, communityId));
} else {
emit(state.copyWith(
isLoading: false,
errorMessage: result['message'],
));
}
} catch (e) {
emit(state.copyWith(
isLoading: false,
errorMessage: 'Something went wrong',
));
}
}
Future<void> _onCreateAutomation(
CreateAutomationEvent event, Emitter<RoutineState> emit) async {
try {
if (state.routineName == null || state.routineName!.isEmpty) {
emit(state.copyWith(
errorMessage: 'Automation name is required',
));
return;
}
emit(state.copyWith(isLoading: true));
final conditions = state.ifItems.expand((item) {
final functions = state.selectedFunctions[item['uniqueCustomId']] ?? [];
return functions.map((function) {
return Condition(
code: state.selectedFunctions[item['uniqueCustomId']]!.indexOf(
function,
) +
1,
entityId: function.entityId,
entityType: 'device_report',
expr: ConditionExpr(
statusCode: function.functionCode,
comparator: function.condition ?? '==',
statusValue: function.value,
),
);
});
}).toList();
if (conditions.isEmpty) {
emit(state.copyWith(
isLoading: false,
errorMessage: 'At least one condition is required',
));
return;
}
final actions = state.thenItems.expand((item) {
final functions = state.selectedFunctions[item['uniqueCustomId']] ?? [];
return functions.map((function) {
if (function.functionCode == 'automation') {
return AutomationAction(
entityId: function.entityId,
actionExecutor: function.value,
);
}
if (item['deviceId'] == 'delay') {
return AutomationAction(
entityId: function.entityId,
actionExecutor: 'delay',
executorProperty: ExecutorProperty(
delaySeconds: int.tryParse(function.value.toString()) ?? 0,
),
);
}
return AutomationAction(
entityId: function.entityId,
actionExecutor: 'device_issue',
executorProperty: ExecutorProperty(
functionCode: function.functionCode,
functionValue: function.value,
),
);
});
}).toList();
final createAutomationModel = CreateAutomationModel(
spaceUuid: spaceId,
automationName: state.routineName!,
decisionExpr: state.selectedAutomationOperator,
effectiveTime: EffectiveTime(
start: state.effectiveTime?.start ?? '00:00',
end: state.effectiveTime?.end ?? '23:59',
loops: state.effectiveTime?.loops ?? '1111111',
),
conditions: conditions,
actions: actions,
);
final result = await SceneApi.createAutomation(createAutomationModel);
if (result['success']) {
emit(_resetState());
add(const LoadAutomation(spaceId));
} else {
emit(state.copyWith(
isLoading: false,
errorMessage: result['message'],
));
}
} catch (e) {
emit(state.copyWith(
isLoading: false,
errorMessage: 'Something went wrong',
));
}
}
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);
thenItems.removeAt(event.index);
selectedFunctions.remove(event.key);
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);
ifItems.removeAt(event.index);
selectedFunctions.remove(event.key);
if (ifItems.isEmpty && state.thenItems.isEmpty) {
emit(state.copyWith(
ifItems: ifItems,
selectedFunctions: selectedFunctions,
isAutomation: false,
isTabToRun: false));
} else {
emit(state.copyWith(
ifItems: ifItems, selectedFunctions: selectedFunctions));
}
}
}
FutureOr<void> _changeOperatorOperator(
ChangeAutomationOperator event, Emitter<RoutineState> emit) {
emit(state.copyWith(
selectedAutomationOperator: event.operator,
));
}
FutureOr<void> _onEffectiveTimeEvent(
EffectiveTimePeriodEvent event, Emitter<RoutineState> emit) {
emit(state.copyWith(effectiveTime: event.effectiveTime));
}
FutureOr<void> _onSetRoutineName(
SetRoutineName event, Emitter<RoutineState> emit) {
emit(state.copyWith(routineName: event.name));
}
Future<void> _onGetSceneDetails(
GetSceneDetails event, Emitter<RoutineState> emit) async {
try {
emit(state.copyWith(
isLoading: true,
isTabToRun: event.isTabToRun,
isUpdate: true,
sceneId: event.sceneId,
isAutomation: false));
final sceneDetails = await SceneApi.getSceneDetails(event.sceneId);
add(InitializeRoutineState(sceneDetails));
} catch (e) {
emit(state.copyWith(
isLoading: false,
errorMessage: 'Failed to load scene details',
));
}
}
Future<void> _onGetAutomationDetails(
GetAutomationDetails event, Emitter<RoutineState> emit) async {
try {
emit(state.copyWith(
isLoading: true,
isAutomation: event.isAutomation,
automationId: event.automationId,
isTabToRun: false,
isUpdate: true,
));
final automationDetails =
await SceneApi.getAutomationDetails(event.automationId);
add(InitializeRoutineState(automationDetails));
} catch (e) {
emit(state.copyWith(
isLoading: false,
errorMessage: 'Failed to load automation details',
));
}
}
// void _onInitializeRoutineState(
// InitializeRoutineState event, Emitter<RoutineState> emit) {
// final routineDetails = event.routineDetails;
// // Convert actions to draggable cards for the THEN container
// final thenItems = routineDetails.actions.map((action) {
// final Map<String, dynamic> cardData = {
// 'entityId': action.entityId,
// 'uniqueCustomId': const Uuid().v4(),
// 'deviceId':
// action.actionExecutor == 'delay' ? 'delay' : action.entityId,
// 'title': action.actionExecutor == 'delay' ? 'Delay' : 'Device',
// // fix this
// 'imagePath':
// action.actionExecutor == 'delay' ? Assets.delay : Assets.logo,
// };
// // Add functions to selectedFunctions
// if (action.executorProperty != null) {
// final functions = <DeviceFunctionData>[
// DeviceFunctionData(
// entityId: action.entityId,
// functionCode: action.executorProperty!.functionCode ?? '',
// value: action.executorProperty!.functionValue,
// /// fix this
// operationName: action.executorProperty?.functionCode ?? ''),
// ];
// state.selectedFunctions[cardData['uniqueCustomId']] = functions;
// }
// return cardData;
// }).toList();
// // Convert conditions to draggable cards for the IF container
// final ifItems = routineDetails.conditions?.map((condition) {
// final Map<String, dynamic> cardData = {
// 'entityId': condition.entityId,
// 'uniqueCustomId': const Uuid().v4(),
// 'deviceId': condition.entityId,
// /// fix this
// 'title': 'Device',
// /// fix this
// 'imagePath': Assets.logo,
// };
// // Add functions to selectedFunctions
// final functions = <DeviceFunctionData>[
// DeviceFunctionData(
// entityId: condition.entityId,
// functionCode: condition.expr.statusCode,
// value: condition.expr.statusValue,
// condition: condition.expr.comparator,
// operationName: condition.expr.comparator,
// ),
// ];
// state.selectedFunctions[cardData['uniqueCustomId']] = functions;
// return cardData;
// }).toList() ??
// [];
// emit(state.copyWith(
// isLoading: false,
// routineName: routineDetails.name,
// selectedIcon: routineDetails.iconId,
// selectedAutomationOperator: routineDetails.decisionExpr,
// effectiveTime: routineDetails.effectiveTime,
// isAutomation: routineDetails.conditions != null,
// isTabToRun: routineDetails.conditions == null,
// thenItems: thenItems,
// ifItems: ifItems,
// selectedFunctions: Map.from(state.selectedFunctions),
// ));
// }
RoutineState _resetState() {
return const RoutineState(
ifItems: [],
thenItems: [],
selectedFunctions: {},
scenes: [],
automations: [],
isLoading: false,
errorMessage: null,
loadScenesErrorMessage: null,
loadAutomationErrorMessage: null,
searchText: '',
selectedIcon: null,
isTabToRun: false,
isAutomation: false,
selectedAutomationOperator: 'or',
effectiveTime: null,
routineName: null,
);
}
FutureOr<void> _onResetRoutineState(
ResetRoutineState event, Emitter<RoutineState> emit) {
emit(_resetState());
}
FutureOr<void> _deleteScene(DeleteScene event, Emitter<RoutineState> emit) {
try {
// emit(state.copyWith(isLoading: true));
SceneApi.deleteScene(unitUuid: spaceId, sceneId: event.sceneId);
add(const LoadScenes(spaceId, communityId));
//emit(_resetState());
} catch (e) {
emit(state.copyWith(
isLoading: false,
errorMessage: 'Failed to delete scene',
));
}
}
FutureOr<void> _deleteAutomation(
DeleteAutomation event, Emitter<RoutineState> emit) {
try {
//emit(state.copyWith(isLoading: true));
SceneApi.deleteAutomation(
unitUuid: spaceId, automationId: event.automationId);
add(const LoadAutomation(spaceId));
// emit(_resetState());
} catch (e) {
emit(state.copyWith(
isLoading: false,
errorMessage: 'Failed to delete automation',
));
}
}
}
@@ -0,0 +1,179 @@
part of 'routine_bloc.dart';
abstract class RoutineEvent extends Equatable {
const RoutineEvent();
@override
List<Object> get props => [];
}
class AddToIfContainer extends RoutineEvent {
final Map<String, dynamic> item;
final bool isTabToRun;
const AddToIfContainer(this.item, this.isTabToRun);
@override
List<Object> get props => [item, isTabToRun];
}
class AddToThenContainer extends RoutineEvent {
final Map<String, dynamic> item;
const AddToThenContainer(this.item);
@override
List<Object> get props => [item];
}
class LoadScenes extends RoutineEvent {
final String unitId;
final String communityId;
const LoadScenes(this.unitId, this.communityId);
@override
List<Object> get props => [unitId, communityId];
}
class LoadAutomation extends RoutineEvent {
final String unitId;
const LoadAutomation(this.unitId);
@override
List<Object> get props => [unitId];
}
class AddFunctionToRoutine extends RoutineEvent {
final List<DeviceFunctionData> functions;
final String uniqueCustomId;
const AddFunctionToRoutine(this.functions, this.uniqueCustomId);
@override
List<Object> get props => [functions, uniqueCustomId];
}
class RemoveFunction extends RoutineEvent {
final DeviceFunctionData function;
const RemoveFunction(this.function);
@override
List<Object> get props => [function];
}
class SearchRoutines extends RoutineEvent {
final String query;
const SearchRoutines(this.query);
@override
List<Object> get props => [query];
}
class AddSelectedIcon extends RoutineEvent {
final String icon;
const AddSelectedIcon(this.icon);
@override
List<Object> get props => [icon];
}
class CreateSceneEvent extends RoutineEvent {
const CreateSceneEvent();
@override
List<Object> get props => [];
}
class RemoveDragCard extends RoutineEvent {
final int index;
final bool isFromThen;
final String key;
const RemoveDragCard(
{required this.index, required this.isFromThen, required this.key});
@override
List<Object> get props => [index, isFromThen, key];
}
class ChangeAutomationOperator extends RoutineEvent {
final String operator;
const ChangeAutomationOperator({required this.operator});
@override
List<Object> get props => [operator];
}
class EffectiveTimePeriodEvent extends RoutineEvent {
final EffectiveTime effectiveTime;
const EffectiveTimePeriodEvent(this.effectiveTime);
@override
List<Object> get props => [effectiveTime];
}
class CreateAutomationEvent extends RoutineEvent {
// final CreateAutomationModel createAutomationModel;
final String? automationId;
final bool updateAutomation;
const CreateAutomationEvent({
//required this.createAutomationModel,
this.automationId,
this.updateAutomation = false,
});
@override
List<Object> get props => [];
}
class SetRoutineName extends RoutineEvent {
final String name;
const SetRoutineName(this.name);
@override
List<Object> get props => [name];
}
class GetSceneDetails extends RoutineEvent {
final String sceneId;
final bool isUpdate;
final bool isTabToRun;
const GetSceneDetails({
required this.sceneId,
required this.isUpdate,
required this.isTabToRun,
});
@override
List<Object> get props => [sceneId];
}
class GetAutomationDetails extends RoutineEvent {
final String automationId;
final bool isUpdate;
final bool isAutomation;
const GetAutomationDetails({
required this.automationId,
this.isUpdate = false,
this.isAutomation = false,
});
@override
List<Object> get props => [automationId];
}
class InitializeRoutineState extends RoutineEvent {
final RoutineDetailsModel routineDetails;
const InitializeRoutineState(this.routineDetails);
@override
List<Object> get props => [routineDetails];
}
class DeleteScene extends RoutineEvent {
final String sceneId;
final String unitUuid;
const DeleteScene({required this.sceneId, required this.unitUuid});
@override
List<Object> get props => [sceneId];
}
class DeleteAutomation extends RoutineEvent {
final String automationId;
final String unitUuid;
const DeleteAutomation({required this.automationId, required this.unitUuid});
@override
List<Object> get props => [automationId];
}
class ResetRoutineState extends RoutineEvent {}
class ClearFunctions extends RoutineEvent {}
@@ -0,0 +1,117 @@
part of 'routine_bloc.dart';
class RoutineState extends Equatable {
final List<Map<String, dynamic>> ifItems;
final List<Map<String, dynamic>> thenItems;
final List<Map<String, String>> availableCards;
final List<ScenesModel> scenes;
final List<ScenesModel> automations;
final Map<String, List<DeviceFunctionData>> selectedFunctions;
final bool isLoading;
final String? errorMessage;
final String? loadScenesErrorMessage;
final String? loadAutomationErrorMessage;
final String? routineName;
final String? selectedIcon;
final String? searchText;
final bool isTabToRun;
final bool isAutomation;
final String selectedAutomationOperator;
final EffectiveTime? effectiveTime;
final String? sceneId;
final String? automationId;
final bool? isUpdate;
const RoutineState({
this.ifItems = const [],
this.thenItems = const [],
this.availableCards = const [],
this.scenes = const [],
this.automations = const [],
this.selectedFunctions = const {},
this.isLoading = false,
this.errorMessage,
this.routineName,
this.selectedIcon,
this.loadScenesErrorMessage,
this.loadAutomationErrorMessage,
this.searchText,
this.isTabToRun = false,
this.isAutomation = false,
this.selectedAutomationOperator = 'or',
this.effectiveTime,
this.sceneId,
this.automationId,
this.isUpdate,
});
RoutineState copyWith({
List<Map<String, dynamic>>? ifItems,
List<Map<String, dynamic>>? thenItems,
List<ScenesModel>? scenes,
List<ScenesModel>? automations,
Map<String, List<DeviceFunctionData>>? selectedFunctions,
bool? isLoading,
String? errorMessage,
String? routineName,
String? selectedIcon,
String? loadAutomationErrorMessage,
String? loadScenesErrorMessage,
String? searchText,
bool? isTabToRun,
bool? isAutomation,
String? selectedAutomationOperator,
EffectiveTime? effectiveTime,
String? sceneId,
String? automationId,
bool? isUpdate,
}) {
return RoutineState(
ifItems: ifItems ?? this.ifItems,
thenItems: thenItems ?? this.thenItems,
scenes: scenes ?? this.scenes,
automations: automations ?? this.automations,
selectedFunctions: selectedFunctions ?? this.selectedFunctions,
isLoading: isLoading ?? this.isLoading,
errorMessage: errorMessage ?? this.errorMessage,
routineName: routineName ?? this.routineName,
selectedIcon: selectedIcon ?? this.selectedIcon,
loadScenesErrorMessage:
loadScenesErrorMessage ?? this.loadScenesErrorMessage,
loadAutomationErrorMessage:
loadAutomationErrorMessage ?? this.loadAutomationErrorMessage,
searchText: searchText ?? this.searchText,
isTabToRun: isTabToRun ?? this.isTabToRun,
isAutomation: isAutomation ?? this.isAutomation,
selectedAutomationOperator:
selectedAutomationOperator ?? this.selectedAutomationOperator,
effectiveTime: effectiveTime ?? this.effectiveTime,
sceneId: sceneId ?? this.sceneId,
automationId: automationId ?? this.automationId,
isUpdate: isUpdate ?? this.isUpdate,
);
}
@override
List<Object?> get props => [
ifItems,
thenItems,
scenes,
automations,
selectedFunctions,
isLoading,
errorMessage,
routineName,
selectedIcon,
loadScenesErrorMessage,
loadAutomationErrorMessage,
searchText,
isTabToRun,
isAutomation,
selectedAutomationOperator,
effectiveTime,
sceneId,
automationId,
isUpdate
];
}
@@ -0,0 +1,53 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/routiens/bloc/setting_bloc/setting_event.dart';
import 'package:syncrow_web/pages/routiens/bloc/setting_bloc/setting_state.dart';
import 'package:syncrow_web/pages/routiens/models/icon_model.dart';
import 'package:syncrow_web/services/routines_api.dart';
class SettingBloc extends Bloc<SettingEvent, SettingState> {
bool isExpanded = false;
String selectedIcon = '';
List<IconModel> iconModelList = [];
SettingBloc() : super(const InitialState()) {
on<InitialEvent>(_initialSetting);
on<FetchIcons>(_fetchIcons);
on<SelectIcon>(_selectIcon);
}
void _initialSetting(InitialEvent event, Emitter<SettingState> emit) async {
try {
emit(const LoadingState());
selectedIcon = event.selectedIcon;
emit(TabToRunSettingLoaded(
showInDevice: true, selectedIcon: event.selectedIcon, iconList: iconModelList));
} catch (e) {
emit(const FailedState(error: 'Something went wrong'));
}
}
void _fetchIcons(FetchIcons event, Emitter<SettingState> emit) async {
try {
isExpanded = event.expanded;
emit(const LoadingState());
if (isExpanded) {
iconModelList = await SceneApi.getIcon();
emit(TabToRunSettingLoaded(
showInDevice: true, selectedIcon: selectedIcon, iconList: iconModelList));
}
} catch (e) {
emit(const FailedState(error: 'Something went wrong'));
}
}
void _selectIcon(SelectIcon event, Emitter<SettingState> emit) async {
try {
emit(const LoadingState());
selectedIcon = event.iconId;
emit(TabToRunSettingLoaded(
showInDevice: true, selectedIcon: event.iconId, iconList: iconModelList));
} catch (e) {
emit(const FailedState(error: 'Something went wrong'));
}
}
}
@@ -0,0 +1,32 @@
import 'package:equatable/equatable.dart';
abstract class SettingEvent extends Equatable {
const SettingEvent();
@override
List<Object> get props => [];
}
class InitialEvent extends SettingEvent {
final String selectedIcon;
const InitialEvent({required this.selectedIcon});
@override
List<Object> get props => [selectedIcon];
}
class FetchIcons extends SettingEvent {
final bool expanded;
const FetchIcons({required this.expanded});
@override
List<Object> get props => [expanded];
}
class SelectIcon extends SettingEvent {
final String iconId;
const SelectIcon({required this.iconId});
@override
List<Object> get props => [iconId];
}
@@ -0,0 +1,56 @@
import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/routiens/models/icon_model.dart';
abstract class SettingState extends Equatable {
const SettingState();
@override
List<Object> get props => [];
}
class LoadingState extends SettingState {
const LoadingState();
@override
List<Object> get props => [];
}
class InitialState extends SettingState {
const InitialState();
@override
List<Object> get props => [];
}
class IconLoadedState extends SettingState {
final List<String> status;
const IconLoadedState(this.status);
@override
List<Object> get props => [status];
}
class TabToRunSettingLoaded extends SettingState {
final String selectedIcon;
final List<IconModel> iconList;
final bool showInDevice;
const TabToRunSettingLoaded({
required this.selectedIcon,
required this.iconList,
required this.showInDevice,
});
@override
List<Object> get props => [selectedIcon, iconList, showInDevice];
}
class FailedState extends SettingState {
final String error;
const FailedState({required this.error});
@override
List<Object> get props => [error];
}
@@ -0,0 +1,85 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routiens/widgets/routine_dialogs/ac_dialog.dart';
import 'package:syncrow_web/pages/routiens/widgets/routine_dialogs/one_gang_switch_dialog.dart';
import 'package:syncrow_web/pages/routiens/widgets/routine_dialogs/three_gang_switch_dialog.dart';
import 'package:syncrow_web/pages/routiens/widgets/routine_dialogs/two_gang_switch_dialog.dart';
import 'package:syncrow_web/pages/routiens/models/device_functions.dart';
class DeviceDialogHelper {
static Future<Map<String, dynamic>?> showDeviceDialog(
BuildContext context,
Map<String, dynamic> data, {
required bool removeComparetors,
}) async {
final functions = data['functions'] as List<DeviceFunction>;
try {
final result = await _getDialogForDeviceType(
context,
data['productType'],
data,
functions,
removeComparetors: removeComparetors,
);
if (result != null) {
return result;
}
} catch (e) {
debugPrint('Error: $e');
}
return null;
}
static Future<Map<String, dynamic>?> _getDialogForDeviceType(
BuildContext context,
String productType,
Map<String, dynamic> data,
List<DeviceFunction> functions,
{required bool removeComparetors}) async {
final routineBloc = context.read<RoutineBloc>();
final deviceSelectedFunctions =
routineBloc.state.selectedFunctions[data['uniqueCustomId']] ?? [];
switch (productType) {
case 'AC':
return ACHelper.showACFunctionsDialog(
context,
functions,
data['device'],
deviceSelectedFunctions,
data['uniqueCustomId'],
removeComparetors);
case '1G':
return OneGangSwitchHelper.showSwitchFunctionsDialog(
context,
functions,
data['device'],
deviceSelectedFunctions,
data['uniqueCustomId'],
removeComparetors);
case '2G':
return TwoGangSwitchHelper.showSwitchFunctionsDialog(
context,
functions,
data['device'],
deviceSelectedFunctions,
data['uniqueCustomId'],
removeComparetors);
case '3G':
return ThreeGangSwitchHelper.showSwitchFunctionsDialog(
context,
functions,
data['device'],
deviceSelectedFunctions,
data['uniqueCustomId'],
removeComparetors);
default:
return null;
}
}
}
@@ -0,0 +1,15 @@
class DurationFormatMixin {
static String formatDuration(int seconds) {
if (seconds >= 3600) {
final hours = (seconds / 3600).floor();
final remainingMinutes = ((seconds % 3600) / 60).floor();
final remainingSeconds = seconds % 60;
return '$hours h ${remainingMinutes}m ${remainingSeconds}s';
} else if (seconds >= 60) {
final minutes = (seconds / 60).floor();
final remainingSeconds = seconds % 60;
return '$minutes m ${remainingSeconds}s';
}
return '${seconds}s';
}
}
@@ -0,0 +1,157 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routiens/widgets/dialog_header.dart';
import 'package:syncrow_web/pages/routiens/widgets/dialog_footer.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
class SaveRoutineHelper {
static Future<bool?> showSaveRoutineDialog(BuildContext context) async {
return showDialog<bool?>(
context: context,
builder: (BuildContext context) {
return BlocBuilder<RoutineBloc, RoutineState>(
builder: (context, state) {
return AlertDialog(
contentPadding: EdgeInsets.zero,
content: Container(
width: 600,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
),
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(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: 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(),
),
);
}),
],
),
),
],
),
),
if (state.errorMessage != null)
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
state.errorMessage!,
style: const TextStyle(color: Colors.red),
),
),
DialogFooter(
onCancel: () => Navigator.pop(context, false),
onConfirm: () {
if (state.isAutomation) {
context.read<RoutineBloc>().add(const CreateAutomationEvent());
} else {
context.read<RoutineBloc>().add(const CreateSceneEvent());
}
Navigator.pop(context, true);
},
isConfirmEnabled: true,
),
],
),
),
);
},
);
},
);
}
}
@@ -0,0 +1,153 @@
import 'package:syncrow_web/pages/device_managment/ac/model/ac_model.dart';
import 'package:syncrow_web/pages/routiens/models/ac/ac_operational_value.dart';
import 'package:syncrow_web/pages/routiens/models/device_functions.dart';
import 'package:syncrow_web/utils/constants/app_enum.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
abstract class ACFunction extends DeviceFunction<AcStatusModel> {
ACFunction({
required super.deviceId,
required super.deviceName,
required super.code,
required super.operationName,
required super.icon,
});
List<ACOperationalValue> getOperationalValues();
}
class SwitchFunction extends ACFunction {
SwitchFunction({required super.deviceId, required super.deviceName})
: super(
code: 'switch',
operationName: 'Power',
icon: Assets.assetsAcPower,
);
@override
List<ACOperationalValue> getOperationalValues() => [
ACOperationalValue(
icon: Assets.assetsAcPower,
description: "ON",
value: true,
),
ACOperationalValue(
icon: Assets.assetsAcPowerOFF,
description: "OFF",
value: false,
),
];
}
class ModeFunction extends ACFunction {
ModeFunction({required super.deviceId, required super.deviceName})
: super(
code: 'mode',
operationName: 'Mode',
icon: Assets.assetsFreezing,
);
@override
List<ACOperationalValue> getOperationalValues() => [
ACOperationalValue(
icon: Assets.assetsAcCooling,
description: "Cooling",
value: TempModes.cold.name,
),
ACOperationalValue(
icon: Assets.assetsAcHeating,
description: "Heating",
value: TempModes.hot.name,
),
ACOperationalValue(
icon: Assets.assetsFanSpeed,
description: "Ventilation",
value: TempModes.wind.name,
),
];
}
class TempSetFunction extends ACFunction {
final int min;
final int max;
final int step;
TempSetFunction({required super.deviceId, required super.deviceName})
: min = 160,
max = 300,
step = 1,
super(
code: 'temp_set',
operationName: 'Set Temperature',
icon: Assets.assetsTempreture,
);
@override
List<ACOperationalValue> getOperationalValues() {
List<ACOperationalValue> values = [];
for (int temp = min; temp <= max; temp += step) {
values.add(ACOperationalValue(
icon: Assets.assetsTempreture,
description: "${temp / 10}°C",
value: temp,
));
}
return values;
}
}
class LevelFunction extends ACFunction {
LevelFunction({required super.deviceId, required super.deviceName})
: super(
code: 'level',
operationName: 'Fan Speed',
icon: Assets.assetsFanSpeed,
);
@override
List<ACOperationalValue> getOperationalValues() => [
ACOperationalValue(
icon: Assets.assetsAcFanLow,
description: "LOW",
value: FanSpeeds.low.name,
),
ACOperationalValue(
icon: Assets.assetsAcFanMiddle,
description: "MIDDLE",
value: FanSpeeds.middle.name,
),
ACOperationalValue(
icon: Assets.assetsAcFanHigh,
description: "HIGH",
value: FanSpeeds.high.name,
),
ACOperationalValue(
icon: Assets.assetsAcFanAuto,
description: "AUTO",
value: FanSpeeds.auto.name,
),
];
}
class ChildLockFunction extends ACFunction {
ChildLockFunction({required super.deviceId, required super.deviceName})
: super(
code: 'child_lock',
operationName: 'Child Lock',
icon: Assets.assetsChildLock,
);
@override
List<ACOperationalValue> getOperationalValues() => [
ACOperationalValue(
icon: Assets.assetsSceneChildLock,
description: "Lock",
value: true,
),
ACOperationalValue(
icon: Assets.assetsSceneChildUnlock,
description: "Unlock",
value: false,
),
];
}
@@ -0,0 +1,11 @@
class ACOperationalValue {
final String icon;
final String description;
final dynamic value;
ACOperationalValue({
required this.icon,
required this.description,
required this.value,
});
}
@@ -0,0 +1,194 @@
import 'dart:convert';
class CreateAutomationModel {
String spaceUuid;
String automationName;
String decisionExpr;
EffectiveTime effectiveTime;
List<Condition> conditions;
List<AutomationAction> actions;
CreateAutomationModel({
required this.spaceUuid,
required this.automationName,
required this.decisionExpr,
required this.effectiveTime,
required this.conditions,
required this.actions,
});
Map<String, dynamic> toMap() {
return {
'spaceUuid': spaceUuid,
'automationName': automationName,
'decisionExpr': decisionExpr,
'effectiveTime': effectiveTime.toMap(),
'conditions': conditions.map((x) => x.toMap()).toList(),
'actions': actions.map((x) => x.toMap()).toList(),
};
}
factory CreateAutomationModel.fromMap(Map<String, dynamic> map) {
return CreateAutomationModel(
spaceUuid: map['spaceUuid'] ?? '',
automationName: map['automationName'] ?? '',
decisionExpr: map['decisionExpr'] ?? '',
effectiveTime: EffectiveTime.fromMap(map['effectiveTime']),
conditions: List<Condition>.from(
map['conditions']?.map((x) => Condition.fromMap(x)) ?? []),
actions: List<AutomationAction>.from(
map['actions']?.map((x) => AutomationAction.fromMap(x)) ?? []),
);
}
String toJson() => json.encode(toMap());
factory CreateAutomationModel.fromJson(String source) =>
CreateAutomationModel.fromMap(json.decode(source));
}
class EffectiveTime {
String start;
String end;
String loops;
EffectiveTime({
required this.start,
required this.end,
required this.loops,
});
Map<String, dynamic> toMap() {
return {
'start': start,
'end': end,
'loops': loops,
};
}
factory EffectiveTime.fromMap(Map<String, dynamic> map) {
return EffectiveTime(
start: map['start'] ?? '',
end: map['end'] ?? '',
loops: map['loops'] ?? '',
);
}
}
class Condition {
int code;
String entityId;
String entityType;
ConditionExpr expr;
Condition({
required this.code,
required this.entityId,
required this.entityType,
required this.expr,
});
Map<String, dynamic> toMap() {
return {
'code': code,
'entityId': entityId,
'entityType': entityType,
'expr': expr.toMap(),
};
}
factory Condition.fromMap(Map<String, dynamic> map) {
return Condition(
code: map['code']?.toInt() ?? 0,
entityId: map['entityId'] ?? '',
entityType: map['entityType'] ?? '',
expr: ConditionExpr.fromMap(map['expr']),
);
}
}
class ConditionExpr {
String statusCode;
String comparator;
dynamic statusValue;
ConditionExpr({
required this.statusCode,
required this.comparator,
required this.statusValue,
});
Map<String, dynamic> toMap() {
return {
'statusCode': statusCode,
'comparator': comparator,
'statusValue': statusValue,
};
}
factory ConditionExpr.fromMap(Map<String, dynamic> map) {
return ConditionExpr(
statusCode: map['statusCode'] ?? '',
comparator: map['comparator'] ?? '',
statusValue: map['statusValue'],
);
}
}
class AutomationAction {
String entityId;
String actionExecutor;
ExecutorProperty? executorProperty;
AutomationAction({
required this.entityId,
required this.actionExecutor,
this.executorProperty,
});
Map<String, dynamic> toMap() {
return {
'entityId': entityId,
'actionExecutor': actionExecutor,
'executorProperty': executorProperty?.toMap(),
};
}
factory AutomationAction.fromMap(Map<String, dynamic> map) {
return AutomationAction(
entityId: map['entityId'] ?? '',
actionExecutor: map['actionExecutor'] ?? '',
executorProperty: map['executorProperty'] != null
? ExecutorProperty.fromMap(map['executorProperty'])
: null,
);
}
}
class ExecutorProperty {
String? functionCode;
dynamic functionValue;
int? delaySeconds;
ExecutorProperty({
this.functionCode,
this.functionValue,
this.delaySeconds,
});
Map<String, dynamic> toMap() {
return {
if (functionCode != null) 'functionCode': functionCode,
if (functionValue != null) 'functionValue': functionValue,
if (delaySeconds != null) 'delaySeconds': delaySeconds,
};
}
factory ExecutorProperty.fromMap(Map<String, dynamic> map) {
return ExecutorProperty(
functionCode: map['functionCode'],
functionValue: map['functionValue'],
delaySeconds: map['delaySeconds']?.toInt(),
);
}
}
@@ -0,0 +1,230 @@
import 'dart:convert';
import 'package:flutter/foundation.dart';
class CreateSceneModel {
String spaceUuid;
String iconId;
bool showInDevice;
String sceneName;
String decisionExpr;
List<CreateSceneAction> actions;
CreateSceneModel({
required this.spaceUuid,
required this.iconId,
required this.showInDevice,
required this.sceneName,
required this.decisionExpr,
required this.actions,
});
CreateSceneModel copyWith({
String? spaceUuid,
String? iconId,
bool? showInDevice,
String? sceneName,
String? decisionExpr,
List<CreateSceneAction>? actions,
bool? showInHomePage,
}) {
return CreateSceneModel(
spaceUuid: spaceUuid ?? this.spaceUuid,
iconId: iconId ?? this.iconId,
showInDevice: showInDevice ?? this.showInDevice,
sceneName: sceneName ?? this.sceneName,
decisionExpr: decisionExpr ?? this.decisionExpr,
actions: actions ?? this.actions,
);
}
Map<String, dynamic> toMap([String? sceneId]) {
return {
if (sceneId == null) 'spaceUuid': spaceUuid,
if (iconId.isNotEmpty) 'iconUuid': iconId,
'showInHomePage': showInDevice,
'sceneName': sceneName,
'decisionExpr': decisionExpr,
'actions': actions.map((x) => x.toMap()).toList(),
};
}
factory CreateSceneModel.fromMap(Map<String, dynamic> map) {
return CreateSceneModel(
spaceUuid: map['spaceUuid'] ?? '',
showInDevice: map['showInHomePage'] ?? false,
iconId: map['iconUuid'] ?? '',
sceneName: map['sceneName'] ?? '',
decisionExpr: map['decisionExpr'] ?? '',
actions: List<CreateSceneAction>.from(
map['actions']?.map((x) => CreateSceneAction.fromMap(x))),
);
}
String toJson([String? sceneId]) => json.encode(toMap(sceneId));
factory CreateSceneModel.fromJson(String source) =>
CreateSceneModel.fromMap(json.decode(source));
@override
String toString() {
return 'CreateSceneModel(unitUuid: $spaceUuid, sceneName: $sceneName, decisionExpr: $decisionExpr, actions: $actions)';
}
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is CreateSceneModel &&
other.spaceUuid == spaceUuid &&
other.iconId == iconId &&
other.showInDevice == showInDevice &&
other.sceneName == sceneName &&
other.decisionExpr == decisionExpr &&
listEquals(other.actions, actions);
}
@override
int get hashCode {
return spaceUuid.hashCode ^
sceneName.hashCode ^
decisionExpr.hashCode ^
actions.hashCode;
}
}
class CreateSceneAction {
String entityId;
String actionExecutor;
CreateSceneExecutorProperty? executorProperty;
CreateSceneAction({
required this.entityId,
required this.actionExecutor,
required this.executorProperty,
});
CreateSceneAction copyWith({
String? entityId,
String? actionExecutor,
CreateSceneExecutorProperty? executorProperty,
}) {
return CreateSceneAction(
entityId: entityId ?? this.entityId,
actionExecutor: actionExecutor ?? this.actionExecutor,
executorProperty: executorProperty ?? this.executorProperty,
);
}
Map<String, dynamic> toMap() {
if (executorProperty != null) {
return {
'entityId': entityId,
'actionExecutor': actionExecutor,
'executorProperty': executorProperty?.toMap(actionExecutor),
};
} else {
return {
'entityId': entityId,
'actionExecutor': actionExecutor,
};
}
}
factory CreateSceneAction.fromMap(Map<String, dynamic> map) {
return CreateSceneAction(
entityId: map['entityId'] ?? '',
actionExecutor: map['actionExecutor'] ?? '',
executorProperty:
CreateSceneExecutorProperty.fromMap(map['executorProperty']),
);
}
String toJson() => json.encode(toMap());
factory CreateSceneAction.fromJson(String source) =>
CreateSceneAction.fromMap(json.decode(source));
@override
String toString() =>
'CreateSceneAction(entityId: $entityId, actionExecutor: $actionExecutor, executorProperty: $executorProperty)';
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is CreateSceneAction &&
other.entityId == entityId &&
other.actionExecutor == actionExecutor &&
other.executorProperty == executorProperty;
}
@override
int get hashCode =>
entityId.hashCode ^ actionExecutor.hashCode ^ executorProperty.hashCode;
}
class CreateSceneExecutorProperty {
String functionCode;
dynamic functionValue;
int delaySeconds;
CreateSceneExecutorProperty({
required this.functionCode,
required this.functionValue,
required this.delaySeconds,
});
CreateSceneExecutorProperty copyWith({
String? functionCode,
dynamic functionValue,
int? delaySeconds,
}) {
return CreateSceneExecutorProperty(
functionCode: functionCode ?? this.functionCode,
functionValue: functionValue ?? this.functionValue,
delaySeconds: delaySeconds ?? this.delaySeconds,
);
}
Map<String, dynamic> toMap(String actionExecutor) {
final map = <String, dynamic>{};
if (functionCode.isNotEmpty) map['functionCode'] = functionCode;
if (functionValue != null) map['functionValue'] = functionValue;
if (actionExecutor == 'delay' && delaySeconds > 0) {
map['delaySeconds'] = delaySeconds;
}
return map;
}
factory CreateSceneExecutorProperty.fromMap(Map<String, dynamic> map) {
return CreateSceneExecutorProperty(
functionCode: map['functionCode'] ?? '',
functionValue: map['functionValue'] ?? '',
delaySeconds: map['delaySeconds']?.toInt() ?? 0,
);
}
String toJson(String actionExecutor) => json.encode(toMap(actionExecutor));
factory CreateSceneExecutorProperty.fromJson(String source) =>
CreateSceneExecutorProperty.fromMap(json.decode(source));
@override
String toString() =>
'CreateSceneExecutorProperty(functionCode: $functionCode, functionValue: $functionValue, delaySeconds: $delaySeconds)';
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is CreateSceneExecutorProperty &&
other.functionCode == functionCode &&
other.functionValue == functionValue &&
other.delaySeconds == delaySeconds;
}
@override
int get hashCode =>
functionCode.hashCode ^ functionValue.hashCode ^ delaySeconds.hashCode;
}
@@ -0,0 +1,28 @@
import 'package:syncrow_web/pages/routiens/models/gang_switches/base_switch_function.dart';
import 'package:syncrow_web/pages/routiens/models/gang_switches/switch_operational_value.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
class DelayFunction extends BaseSwitchFunction {
DelayFunction({required super.deviceId, required super.deviceName})
: super(
code: 'delay',
operationName: 'Delay',
icon: Assets.delay,
);
@override
List<SwitchOperationalValue> getOperationalValues() => [
SwitchOperationalValue(
icon: '',
description: "Duration in seconds",
value: 0.0,
minValue: 0,
maxValue: 43200,
stepValue: 1,
),
];
int convertToSeconds(int hours, int minutes) {
return (hours * 3600) + (minutes * 60);
}
}
@@ -0,0 +1,84 @@
abstract class DeviceFunction<T> {
final String deviceId;
final String deviceName;
final String code;
final String operationName;
final String icon;
DeviceFunction({
required this.deviceId,
required this.deviceName,
required this.code,
required this.operationName,
required this.icon,
});
}
class DeviceFunctionData {
final String entityId;
final String actionExecutor;
final String functionCode;
final String operationName;
final dynamic value;
final String? condition;
final String? valueDescription;
DeviceFunctionData({
required this.entityId,
this.actionExecutor = 'device_issue',
required this.functionCode,
required this.operationName,
required this.value,
this.condition,
this.valueDescription,
});
Map<String, dynamic> toJson() {
return {
'entityId': entityId,
'actionExecutor': actionExecutor,
'function': functionCode,
'operationName': operationName,
'value': value,
if (condition != null) 'condition': condition,
if (valueDescription != null) 'valueDescription': valueDescription,
};
}
factory DeviceFunctionData.fromJson(Map<String, dynamic> json) {
return DeviceFunctionData(
entityId: json['entityId'],
actionExecutor: json['actionExecutor'] ?? 'function',
functionCode: json['function'],
operationName: json['operationName'],
value: json['value'],
condition: json['condition'],
valueDescription: json['valueDescription'],
);
}
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is DeviceFunctionData &&
other.entityId == entityId &&
other.actionExecutor == actionExecutor &&
other.functionCode == functionCode &&
other.operationName == operationName &&
other.value == value &&
other.condition == condition &&
other.valueDescription == valueDescription;
}
@override
int get hashCode {
return entityId.hashCode ^
actionExecutor.hashCode ^
functionCode.hashCode ^
operationName.hashCode ^
value.hashCode ^
condition.hashCode ^
valueDescription.hashCode;
}
}
@@ -0,0 +1,14 @@
import 'package:syncrow_web/pages/routiens/models/device_functions.dart';
import 'package:syncrow_web/pages/routiens/models/gang_switches/switch_operational_value.dart';
abstract class BaseSwitchFunction extends DeviceFunction<bool> {
BaseSwitchFunction({
required super.deviceId,
required super.deviceName,
required super.code,
required super.operationName,
required super.icon,
});
List<SwitchOperationalValue> getOperationalValues();
}
@@ -0,0 +1,47 @@
import 'package:syncrow_web/pages/routiens/models/gang_switches/base_switch_function.dart';
import 'package:syncrow_web/pages/routiens/models/gang_switches/switch_operational_value.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
class OneGangSwitchFunction extends BaseSwitchFunction {
OneGangSwitchFunction({required super.deviceId, required super.deviceName})
: super(
code: 'switch_1',
operationName: 'Light Switch',
icon: Assets.assetsAcPower,
);
@override
List<SwitchOperationalValue> getOperationalValues() => [
SwitchOperationalValue(
icon: Assets.assetsAcPower,
description: "ON",
value: true,
),
SwitchOperationalValue(
icon: Assets.assetsAcPowerOFF,
description: "OFF",
value: false,
),
];
}
class OneGangCountdownFunction extends BaseSwitchFunction {
OneGangCountdownFunction({required super.deviceId, required super.deviceName})
: super(
code: 'countdown_1',
operationName: 'Light Countdown',
icon: Assets.assetsLightCountdown,
);
@override
List<SwitchOperationalValue> getOperationalValues() => [
SwitchOperationalValue(
icon: '',
description: "sec",
value: 0.0,
minValue: 0,
maxValue: 43200,
stepValue: 1,
),
];
}
@@ -0,0 +1,17 @@
class SwitchOperationalValue {
final String icon;
final String description;
final dynamic value;
final double? minValue;
final double? maxValue;
final double? stepValue;
SwitchOperationalValue({
required this.icon,
required this.value,
this.description = '',
this.minValue,
this.maxValue,
this.stepValue,
});
}
@@ -0,0 +1,138 @@
import 'package:syncrow_web/pages/routiens/models/gang_switches/base_switch_function.dart';
import 'package:syncrow_web/pages/routiens/models/gang_switches/switch_operational_value.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
class ThreeGangSwitch1Function extends BaseSwitchFunction {
ThreeGangSwitch1Function({required super.deviceId, required super.deviceName})
: super(
code: 'switch_1',
operationName: 'Light 1 Switch',
icon: Assets.assetsAcPower,
);
@override
List<SwitchOperationalValue> getOperationalValues() => [
SwitchOperationalValue(
icon: Assets.assetsAcPower,
description: "ON",
value: true,
),
SwitchOperationalValue(
icon: Assets.assetsAcPowerOFF,
description: "OFF",
value: false,
),
];
}
class ThreeGangCountdown1Function extends BaseSwitchFunction {
ThreeGangCountdown1Function(
{required super.deviceId, required super.deviceName})
: super(
code: 'countdown_1',
operationName: 'Light 1 Countdown',
icon: Assets.assetsLightCountdown,
);
@override
List<SwitchOperationalValue> getOperationalValues() => [
SwitchOperationalValue(
icon: '',
description: "sec",
value: 0.0,
minValue: 0,
maxValue: 43200,
stepValue: 1,
),
];
}
class ThreeGangSwitch2Function extends BaseSwitchFunction {
ThreeGangSwitch2Function({required super.deviceId, required super.deviceName})
: super(
code: 'switch_2',
operationName: 'Light 2 Switch',
icon: Assets.assetsAcPower,
);
@override
List<SwitchOperationalValue> getOperationalValues() => [
SwitchOperationalValue(
icon: Assets.assetsAcPower,
description: "ON",
value: true,
),
SwitchOperationalValue(
icon: Assets.assetsAcPowerOFF,
description: "OFF",
value: false,
),
];
}
class ThreeGangCountdown2Function extends BaseSwitchFunction {
ThreeGangCountdown2Function(
{required super.deviceId, required super.deviceName})
: super(
code: 'countdown_2',
operationName: 'Light 2 Countdown',
icon: Assets.assetsLightCountdown,
);
@override
List<SwitchOperationalValue> getOperationalValues() => [
SwitchOperationalValue(
icon: '',
description: "sec",
value: 0.0,
minValue: 0,
maxValue: 43200,
stepValue: 1,
),
];
}
class ThreeGangSwitch3Function extends BaseSwitchFunction {
ThreeGangSwitch3Function({required super.deviceId, required super.deviceName})
: super(
code: 'switch_3',
operationName: 'Light 3 Switch',
icon: Assets.assetsAcPower,
);
@override
List<SwitchOperationalValue> getOperationalValues() => [
SwitchOperationalValue(
icon: Assets.assetsAcPower,
description: "ON",
value: true,
),
SwitchOperationalValue(
icon: Assets.assetsAcPowerOFF,
description: "OFF",
value: false,
),
];
}
class ThreeGangCountdown3Function extends BaseSwitchFunction {
ThreeGangCountdown3Function(
{required super.deviceId, required super.deviceName})
: super(
code: 'countdown_3',
operationName: 'Light 3 Countdown',
icon: Assets.assetsLightCountdown,
);
@override
List<SwitchOperationalValue> getOperationalValues() => [
SwitchOperationalValue(
icon: '',
description: "sec",
value: 0.0,
minValue: 0,
maxValue: 43200,
stepValue: 1,
),
];
}
@@ -0,0 +1,93 @@
import 'package:syncrow_web/pages/routiens/models/gang_switches/base_switch_function.dart';
import 'package:syncrow_web/pages/routiens/models/gang_switches/switch_operational_value.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
class TwoGangSwitch1Function extends BaseSwitchFunction {
TwoGangSwitch1Function({required super.deviceId, required super.deviceName})
: super(
code: 'switch_1',
operationName: 'Light 1 Switch',
icon: Assets.assetsAcPower,
);
@override
List<SwitchOperationalValue> getOperationalValues() => [
SwitchOperationalValue(
icon: Assets.assetsAcPower,
description: "ON",
value: true,
),
SwitchOperationalValue(
icon: Assets.assetsAcPowerOFF,
description: "OFF",
value: false,
),
];
}
class TwoGangSwitch2Function extends BaseSwitchFunction {
TwoGangSwitch2Function({required super.deviceId, required super.deviceName})
: super(
code: 'switch_2',
operationName: 'Light 2 Switch',
icon: Assets.assetsAcPower,
);
@override
List<SwitchOperationalValue> getOperationalValues() => [
SwitchOperationalValue(
icon: Assets.assetsAcPower,
description: "ON",
value: true,
),
SwitchOperationalValue(
icon: Assets.assetsAcPowerOFF,
description: "OFF",
value: false,
),
];
}
class TwoGangCountdown1Function extends BaseSwitchFunction {
TwoGangCountdown1Function(
{required super.deviceId, required super.deviceName})
: super(
code: 'countdown_1',
operationName: 'Light 1 Countdown',
icon: Assets.assetsLightCountdown,
);
@override
List<SwitchOperationalValue> getOperationalValues() => [
SwitchOperationalValue(
icon: '',
description: "sec",
value: 0.0,
minValue: 0,
maxValue: 43200,
stepValue: 1,
),
];
}
class TwoGangCountdown2Function extends BaseSwitchFunction {
TwoGangCountdown2Function(
{required super.deviceId, required super.deviceName})
: super(
code: 'countdown_2',
operationName: 'Light 2 Countdown',
icon: Assets.assetsLightCountdown,
);
@override
List<SwitchOperationalValue> getOperationalValues() => [
SwitchOperationalValue(
icon: '',
description: "sec",
value: 0.0,
minValue: 0,
maxValue: 43200,
stepValue: 1,
),
];
}
+39
View File
@@ -0,0 +1,39 @@
import 'dart:convert';
import 'dart:typed_data';
class IconModel {
final String uuid;
final DateTime createdAt;
final DateTime updatedAt;
final String iconBase64;
IconModel({
required this.uuid,
required this.createdAt,
required this.updatedAt,
required this.iconBase64,
});
// Method to decode the icon from Base64 and return as Uint8List
Uint8List get iconBytes => base64Decode(iconBase64);
// Factory constructor to create an instance from JSON
factory IconModel.fromJson(Map<String, dynamic> json) {
return IconModel(
uuid: json['uuid'] as String,
createdAt: DateTime.parse(json['createdAt'] as String),
updatedAt: DateTime.parse(json['updatedAt'] as String),
iconBase64: json['icon'] as String,
);
}
// Method to convert an instance back to JSON
Map<String, dynamic> toJson() {
return {
'uuid': uuid,
'createdAt': createdAt.toIso8601String(),
'updatedAt': updatedAt.toIso8601String(),
'icon': iconBase64,
};
}
}
@@ -0,0 +1,262 @@
import 'dart:convert';
import 'package:syncrow_web/pages/routiens/models/create_scene_and_autoamtion/create_automation_model.dart';
import 'package:syncrow_web/pages/routiens/models/create_scene_and_autoamtion/create_scene_model.dart';
class RoutineDetailsModel {
final String spaceUuid;
final String name;
final String decisionExpr;
final List<RoutineAction> actions;
final String? iconId;
final bool? showInDevice;
final EffectiveTime? effectiveTime;
final List<RoutineCondition>? conditions;
final String? type;
RoutineDetailsModel({
required this.spaceUuid,
required this.name,
required this.decisionExpr,
required this.actions,
this.iconId,
this.showInDevice,
this.effectiveTime,
this.conditions,
this.type,
});
// Convert to CreateSceneModel
CreateSceneModel toCreateSceneModel() {
return CreateSceneModel(
spaceUuid: spaceUuid,
iconId: iconId ?? '',
showInDevice: showInDevice ?? false,
sceneName: name,
decisionExpr: decisionExpr,
actions: actions.map((a) => a.toCreateSceneAction()).toList(),
);
}
// Convert to CreateAutomationModel
CreateAutomationModel toCreateAutomationModel() {
return CreateAutomationModel(
spaceUuid: spaceUuid,
automationName: name,
decisionExpr: decisionExpr,
effectiveTime:
effectiveTime ?? EffectiveTime(start: '', end: '', loops: ''),
conditions: conditions?.map((c) => c.toCondition()).toList() ?? [],
actions: actions.map((a) => a.toAutomationAction()).toList(),
);
}
Map<String, dynamic> toMap() {
return {
'spaceUuid': spaceUuid,
'name': name,
'decisionExpr': decisionExpr,
'actions': actions.map((x) => x.toMap()).toList(),
if (iconId != null) 'iconId': iconId,
if (showInDevice != null) 'showInDevice': showInDevice,
if (effectiveTime != null) 'effectiveTime': effectiveTime!.toMap(),
if (conditions != null)
'conditions': conditions!.map((x) => x.toMap()).toList(),
if (type != null) 'type': type,
};
}
factory RoutineDetailsModel.fromMap(Map<String, dynamic> map) {
return RoutineDetailsModel(
spaceUuid: map['spaceUuid'] ?? '',
name: map['name'] ?? '',
decisionExpr: map['decisionExpr'] ?? '',
actions: List<RoutineAction>.from(
map['actions']?.map((x) => RoutineAction.fromMap(x)) ?? [],
),
iconId: map['iconId'],
showInDevice: map['showInDevice'],
effectiveTime: map['effectiveTime'] != null
? EffectiveTime.fromMap(map['effectiveTime'])
: null,
conditions: map['conditions'] != null
? List<RoutineCondition>.from(
map['conditions'].map((x) => RoutineCondition.fromMap(x)))
: null,
type: map['type'],
);
}
String toJson() => json.encode(toMap());
factory RoutineDetailsModel.fromJson(String source) =>
RoutineDetailsModel.fromMap(json.decode(source));
}
class RoutineAction {
final String entityId;
final String actionExecutor;
final RoutineExecutorProperty? executorProperty;
RoutineAction({
required this.entityId,
required this.actionExecutor,
this.executorProperty,
});
CreateSceneAction toCreateSceneAction() {
return CreateSceneAction(
entityId: entityId,
actionExecutor: actionExecutor,
executorProperty: executorProperty?.toCreateSceneExecutorProperty(),
);
}
AutomationAction toAutomationAction() {
return AutomationAction(
entityId: entityId,
actionExecutor: actionExecutor,
executorProperty: executorProperty?.toExecutorProperty(),
);
}
Map<String, dynamic> toMap() {
return {
'entityId': entityId,
'actionExecutor': actionExecutor,
if (executorProperty != null)
'executorProperty': executorProperty!.toMap(),
};
}
factory RoutineAction.fromMap(Map<String, dynamic> map) {
return RoutineAction(
entityId: map['entityId'] ?? '',
actionExecutor: map['actionExecutor'] ?? '',
executorProperty: map['executorProperty'] != null
? RoutineExecutorProperty.fromMap(map['executorProperty'])
: null,
);
}
}
class RoutineExecutorProperty {
final String? functionCode;
final dynamic functionValue;
final int? delaySeconds;
RoutineExecutorProperty({
this.functionCode,
this.functionValue,
this.delaySeconds,
});
CreateSceneExecutorProperty toCreateSceneExecutorProperty() {
return CreateSceneExecutorProperty(
functionCode: functionCode ?? '',
functionValue: functionValue,
delaySeconds: delaySeconds ?? 0,
);
}
ExecutorProperty toExecutorProperty() {
return ExecutorProperty(
functionCode: functionCode,
functionValue: functionValue,
delaySeconds: delaySeconds,
);
}
Map<String, dynamic> toMap() {
return {
if (functionCode != null) 'functionCode': functionCode,
if (functionValue != null) 'functionValue': functionValue,
if (delaySeconds != null) 'delaySeconds': delaySeconds,
};
}
factory RoutineExecutorProperty.fromMap(Map<String, dynamic> map) {
return RoutineExecutorProperty(
functionCode: map['functionCode'],
functionValue: map['functionValue'],
delaySeconds: map['delaySeconds']?.toInt(),
);
}
}
class RoutineCondition {
final int code;
final String entityId;
final String entityType;
final RoutineConditionExpr expr;
RoutineCondition({
required this.code,
required this.entityId,
required this.entityType,
required this.expr,
});
Condition toCondition() {
return Condition(
code: code,
entityId: entityId,
entityType: entityType,
expr: expr.toConditionExpr(),
);
}
Map<String, dynamic> toMap() {
return {
'code': code,
'entityId': entityId,
'entityType': entityType,
'expr': expr.toMap(),
};
}
factory RoutineCondition.fromMap(Map<String, dynamic> map) {
return RoutineCondition(
code: map['code']?.toInt() ?? 0,
entityId: map['entityId'] ?? '',
entityType: map['entityType'] ?? '',
expr: RoutineConditionExpr.fromMap(map['expr']),
);
}
}
class RoutineConditionExpr {
final String statusCode;
final String comparator;
final dynamic statusValue;
RoutineConditionExpr({
required this.statusCode,
required this.comparator,
required this.statusValue,
});
ConditionExpr toConditionExpr() {
return ConditionExpr(
statusCode: statusCode,
comparator: comparator,
statusValue: statusValue,
);
}
Map<String, dynamic> toMap() {
return {
'statusCode': statusCode,
'comparator': comparator,
'statusValue': statusValue,
};
}
factory RoutineConditionExpr.fromMap(Map<String, dynamic> map) {
return RoutineConditionExpr(
statusCode: map['statusCode'] ?? '',
comparator: map['comparator'] ?? '',
statusValue: map['statusValue'],
);
}
}
@@ -0,0 +1,30 @@
// import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
// class RoutineItem {
// final AllDevicesModel device;
// final String? function;
// final dynamic value;
// RoutineItem({
// required this.device,
// this.function,
// this.value,
// });
// Map<String, dynamic> toMap() {
// return {
// 'device': device,
// 'function': function,
// 'value': value,
// };
// }
// factory RoutineItem.fromMap(Map<String, dynamic> map) {
// return RoutineItem(
// device: map['device'] as AllDevicesModel,
// function: map['function'],
// value: map['value'],
// );
// }
// }
// : uniqueCustomId = uniqueCustomId ?? const Uuid().v4()
@@ -0,0 +1,56 @@
import 'dart:convert';
import 'dart:typed_data';
import 'package:syncrow_web/utils/constants/assets.dart';
class ScenesModel {
final String id;
final String? sceneTuyaId;
final String name;
final String status;
final String type;
final String? icon;
ScenesModel({
required this.id,
this.sceneTuyaId,
required this.name,
required this.status,
required this.type,
this.icon,
});
factory ScenesModel.fromRawJson(String str) =>
ScenesModel.fromJson(json.decode(str));
String toRawJson() => json.encode(toJson());
Uint8List? get iconInBytes {
if (icon == null || icon?.isEmpty == true) return null;
try {
return base64Decode(icon!);
} catch (e) {
return null;
}
}
factory ScenesModel.fromJson(Map<String, dynamic> json,
{bool? isAutomation}) {
return ScenesModel(
id: json["id"] ?? json["uuid"] ?? '',
sceneTuyaId: json["sceneTuyaId"] as String?,
name: json["name"] ?? '',
status: json["status"] ?? '',
type: json["type"] ?? '',
icon:
isAutomation == true ? Assets.automation : (json["icon"] as String?),
);
}
Map<String, dynamic> toJson() => {
"id": id,
"sceneTuyaId": sceneTuyaId ?? '',
"name": name,
"status": status,
"type": type,
};
}
@@ -0,0 +1,97 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/routiens/widgets/conditions_routines_devices_view.dart';
import 'package:syncrow_web/pages/routiens/widgets/if_container.dart';
import 'package:syncrow_web/pages/routiens/widgets/routine_search_and_buttons.dart';
import 'package:syncrow_web/pages/routiens/widgets/then_container.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class CreateNewRoutineView extends StatelessWidget {
final bool isUpdate;
final String? routineId;
final bool isScene;
const CreateNewRoutineView({
super.key,
this.isUpdate = false,
this.routineId,
this.isScene = true,
});
@override
Widget build(BuildContext context) {
return Container(
alignment: Alignment.topLeft,
padding: const EdgeInsets.all(16),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const RoutineSearchAndButtons(),
const SizedBox(height: 20),
Flexible(
child: Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Expanded(
child: Card(
child: Container(
decoration: BoxDecoration(
color: ColorsManager.whiteColors,
borderRadius: BorderRadius.circular(15),
),
child: const ConditionsRoutinesDevicesView()),
),
),
const SizedBox(
width: 10,
),
Expanded(
child: Column(
children: [
/// IF Container
Expanded(
child: Card(
margin: EdgeInsets.zero,
child: Container(
decoration: const BoxDecoration(
color: ColorsManager.whiteColors,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(15),
topRight: Radius.circular(15),
),
),
child: const IfContainer(),
),
),
),
Container(
height: 2,
width: double.infinity,
color: ColorsManager.dialogBlueTitle,
),
/// THEN Container
Expanded(
child: Card(
margin: EdgeInsets.zero,
child: Container(
decoration: const BoxDecoration(
color: ColorsManager.boxColor,
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(15),
bottomRight: Radius.circular(15),
),
),
child: const ThenContainer(),
),
),
),
],
),
),
],
),
),
],
),
);
}
}
@@ -0,0 +1,45 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/routiens/widgets/routine_dialogs/effictive_period_dialog.dart';
import 'package:syncrow_web/pages/routiens/widgets/period_option.dart';
import 'package:syncrow_web/pages/routiens/widgets/repeat_days.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class EffectivePeriodView extends StatelessWidget {
const EffectivePeriodView({super.key});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(16.0),
child: ListView(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Spacer(),
Expanded(
child: Text(
'Effective Period',
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: ColorsManager.textPrimaryColor,
fontWeight: FontWeight.w400,
fontSize: 14),
),
),
const Spacer(),
],
),
const Divider(
color: ColorsManager.backgroundColor,
),
const PeriodOptions(
showCustomTimePicker: EffectPeriodHelper.showCustomTimePicker,
),
const SizedBox(height: 16),
const RepeatDays(),
const SizedBox(height: 24),
],
),
);
}
}
@@ -0,0 +1,59 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/bloc/switch_tabs/switch_tabs_bloc.dart';
import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routiens/view/create_new_routine_view.dart';
import 'package:syncrow_web/pages/routiens/widgets/main_routine_view/fetch_routine_scenes_automation.dart';
import 'package:syncrow_web/pages/routiens/widgets/main_routine_view/routine_view_card.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class RoutinesView extends StatelessWidget {
const RoutinesView({super.key});
@override
Widget build(BuildContext context) {
return BlocBuilder<SwitchTabsBloc, SwitchTabsState>(
builder: (context, state) {
if (state is ShowCreateRoutineState && state.showCreateRoutine) {
return const CreateNewRoutineView();
}
return Padding(
padding: const EdgeInsets.all(16),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
"Create New Routines",
style: Theme.of(context).textTheme.titleLarge?.copyWith(
color: ColorsManager.grayColor,
fontWeight: FontWeight.bold,
),
),
const SizedBox(
height: 10,
),
RoutineViewCard(
onTap: () {
BlocProvider.of<SwitchTabsBloc>(context).add(
const CreateNewRoutineViewEvent(true),
);
context.read<RoutineBloc>().add(
(ResetRoutineState()),
);
},
icon: Icons.add,
textString: '',
),
const SizedBox(
height: 15,
),
const Expanded(child: FetchRoutineScenesAutomation()),
],
),
);
},
);
}
}
@@ -0,0 +1,120 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routiens/widgets/dragable_card.dart';
import 'package:syncrow_web/pages/routiens/widgets/routine_devices.dart';
import 'package:syncrow_web/pages/routiens/widgets/routines_title_widget.dart';
import 'package:syncrow_web/pages/routiens/widgets/scenes_and_automations.dart';
import 'package:syncrow_web/pages/routiens/widgets/search_bar_condition_title.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
class ConditionsRoutinesDevicesView extends StatelessWidget {
const ConditionsRoutinesDevicesView({super.key});
@override
Widget build(BuildContext context) {
return BlocBuilder<RoutineBloc, RoutineState>(
builder: (context, state) {
return const Padding(
padding: EdgeInsets.symmetric(horizontal: 8.0),
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ConditionTitleAndSearchBar(),
SizedBox(height: 10),
Wrap(
spacing: 10,
runSpacing: 10,
children: [
DraggableCard(
imagePath: Assets.tabToRun,
title: 'Tab to run',
deviceData: {
'deviceId': 'tab_to_run',
'type': 'trigger',
'name': 'Tab to run',
},
),
DraggableCard(
imagePath: Assets.map,
title: 'Location',
deviceData: {
'deviceId': 'location',
'type': 'trigger',
'name': 'Location',
},
),
DraggableCard(
imagePath: Assets.weather,
title: 'Weather',
deviceData: {
'deviceId': 'weather',
'type': 'trigger',
'name': 'Weather',
},
),
DraggableCard(
imagePath: Assets.schedule,
title: 'Schedule',
deviceData: {
'deviceId': 'schedule',
'type': 'trigger',
'name': 'Schedule',
},
),
],
),
SizedBox(height: 10),
TitleRoutine(
title: 'Conditions',
subtitle: '(THEN)',
),
SizedBox(height: 10),
Wrap(
spacing: 10,
runSpacing: 10,
children: [
DraggableCard(
imagePath: Assets.notification,
title: 'Send Notification',
deviceData: {
'deviceId': 'notification',
'type': 'action',
'name': 'Send Notification',
},
),
DraggableCard(
imagePath: Assets.delay,
title: 'Delay the action',
deviceData: {
'deviceId': 'delay',
'type': 'action',
'name': 'Delay the action',
'uniqueCustomId': '',
},
),
],
),
SizedBox(height: 10),
TitleRoutine(
title: 'Routines',
subtitle: '(THEN)',
),
SizedBox(height: 10),
ScenesAndAutomations(),
SizedBox(height: 10),
TitleRoutine(
title: 'Devices',
subtitle: '',
),
SizedBox(height: 10),
RoutineDevices(),
],
),
),
);
},
);
}
}
@@ -0,0 +1,75 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class DialogFooter extends StatelessWidget {
final VoidCallback onCancel;
final VoidCallback? onConfirm;
final bool isConfirmEnabled;
final int? dialogWidth;
const DialogFooter({
Key? key,
required this.onCancel,
required this.onConfirm,
required this.isConfirmEnabled,
this.dialogWidth,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
decoration: const BoxDecoration(
border: Border(
top: BorderSide(
color: ColorsManager.greyColor,
),
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Expanded(
child: _buildFooterButton(
context,
'Cancel',
onCancel,
),
),
if (isConfirmEnabled) ...[
Container(width: 1, height: 50, color: ColorsManager.greyColor),
Expanded(
child: _buildFooterButton(
context,
'Confirm',
onConfirm,
),
),
],
],
),
);
}
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,
),
),
),
),
);
}
}
@@ -0,0 +1,35 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class DialogHeader extends StatelessWidget {
final String title;
const DialogHeader(this.title, {super.key});
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const SizedBox(
height: 10,
),
Text(
title,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: ColorsManager.primaryColorWithOpacity,
fontWeight: FontWeight.bold,
),
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 15, horizontal: 50),
child: Container(
height: 1,
width: double.infinity,
color: ColorsManager.greyColor,
),
),
],
);
}
}
@@ -0,0 +1,182 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routiens/models/device_functions.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
class DraggableCard extends StatelessWidget {
final String imagePath;
final String title;
final Map<String, dynamic> deviceData;
final EdgeInsetsGeometry? padding;
final void Function()? onRemove;
final bool? isFromThen;
final bool? isFromIf;
const DraggableCard({
super.key,
required this.imagePath,
required this.title,
required this.deviceData,
this.padding,
this.onRemove,
this.isFromThen,
this.isFromIf,
});
@override
Widget build(BuildContext context) {
return BlocBuilder<RoutineBloc, RoutineState>(
builder: (context, state) {
final deviceFunctions =
state.selectedFunctions[deviceData['uniqueCustomId']] ?? [];
return Draggable<Map<String, dynamic>>(
data: deviceData,
feedback: Transform.rotate(
angle: -0.1,
child:
_buildCardContent(context, deviceFunctions, padding: padding),
),
childWhenDragging: _buildGreyContainer(),
child: _buildCardContent(context, deviceFunctions, padding: padding),
);
},
);
}
Widget _buildCardContent(
BuildContext context, List<DeviceFunctionData> deviceFunctions,
{EdgeInsetsGeometry? padding}) {
return Stack(
children: [
Card(
color: ColorsManager.whiteColors,
child: Container(
padding: padding ?? const EdgeInsets.all(16),
width: 110,
height: deviceFunctions.isEmpty ? 123 : null,
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
height: 50,
width: 50,
decoration: BoxDecoration(
color: ColorsManager.CircleImageBackground,
borderRadius: BorderRadius.circular(90),
border: Border.all(
color: ColorsManager.graysColor,
),
),
padding: const EdgeInsets.all(8),
child: imagePath.contains('.svg')
? SvgPicture.asset(
imagePath,
)
: Image.memory(
base64Decode(imagePath),
),
),
const SizedBox(height: 8),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 3),
child: Text(
title,
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
maxLines: 2,
style: context.textTheme.bodySmall?.copyWith(
color: ColorsManager.blackColor,
fontSize: 12,
),
),
),
],
),
if (deviceFunctions.isNotEmpty)
// const Divider(height: 1),
...deviceFunctions.map((function) => Row(
mainAxisSize: MainAxisSize.min,
children: [
Expanded(
child: Text(
'${function.operationName}: ${_formatFunctionValue(function)}',
style: context.textTheme.bodySmall?.copyWith(
fontSize: 9,
color: ColorsManager.textGray,
height: 1.2,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
),
],
)),
],
),
),
),
Positioned(
top: -4,
right: -6,
child: Visibility(
visible: (isFromIf ?? false) || (isFromThen ?? false),
child: IconButton(
onPressed: onRemove == null
? null
: () {
onRemove!();
},
icon: const Icon(
Icons.close,
color: ColorsManager.boxColor,
),
),
),
),
],
);
}
String _formatFunctionValue(DeviceFunctionData function) {
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();
if (seconds >= 3600) {
final hours = (seconds / 3600).floor();
final remainingMinutes = ((seconds % 3600) / 60).floor();
final remainingSeconds = seconds % 60;
return '$hours h ${remainingMinutes}m ${remainingSeconds}s';
} else if (seconds >= 60) {
final minutes = (seconds / 60).floor();
final remainingSeconds = seconds % 60;
return '$minutes m ${remainingSeconds}s';
}
return '${seconds}s';
}
return function.value.toString();
}
Widget _buildGreyContainer() {
return Container(
height: 123,
width: 90,
decoration: BoxDecoration(
color: Colors.grey[100],
borderRadius: BorderRadius.circular(20),
),
);
}
}
@@ -0,0 +1,215 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routiens/helper/dialog_helper/device_dialog_helper.dart';
import 'package:syncrow_web/pages/routiens/widgets/dragable_card.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:uuid/uuid.dart';
class IfContainer extends StatelessWidget {
const IfContainer({super.key});
@override
Widget build(BuildContext context) {
return BlocBuilder<RoutineBloc, RoutineState>(
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,
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, state.ifItems[index],
removeComparetors: false);
if (result != null) {
context.read<RoutineBloc>().add(
AddToIfContainer(
state.ifItems[index], false));
} else if (![
'AC',
'1G',
'2G',
'3G'
].contains(
state.ifItems[index]['productType'])) {
context.read<RoutineBloc>().add(
AddToIfContainer(
state.ifItems[index], false));
}
}
},
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;
if (state.isAutomation && mutableData['deviceId'] == 'tab_to_run') {
return;
}
if (!state.isTabToRun) {
if (mutableData['deviceId'] == 'tab_to_run') {
context
.read<RoutineBloc>()
.add(AddToIfContainer(mutableData, true));
} else {
final result = await DeviceDialogHelper.showDeviceDialog(
context, mutableData,
removeComparetors: false);
if (result != null) {
context
.read<RoutineBloc>()
.add(AddToIfContainer(mutableData, false));
} else if (!['AC', '1G', '2G', '3G']
.contains(mutableData['productType'])) {
context
.read<RoutineBloc>()
.add(AddToIfContainer(mutableData, false));
}
}
}
},
);
},
);
}
}
class AutomationOperatorSelector extends StatelessWidget {
const AutomationOperatorSelector({
super.key,
required this.selectedOperator,
});
final String selectedOperator;
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
border: Border.all(color: ColorsManager.dividerColor),
borderRadius: BorderRadius.circular(8),
),
child: Row(
children: [
TextButton(
style: TextButton.styleFrom(
backgroundColor: selectedOperator.toLowerCase() == 'or'
? ColorsManager.dialogBlueTitle
: ColorsManager.whiteColors,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(0),
),
),
child: Text(
'Any condition is met',
style: context.textTheme.bodyMedium?.copyWith(
color: selectedOperator.toLowerCase() == 'or'
? ColorsManager.whiteColors
: ColorsManager.blackColor,
),
),
onPressed: () {
context
.read<RoutineBloc>()
.add(const ChangeAutomationOperator(operator: 'or'));
},
),
Container(
width: 3,
height: 24,
color: ColorsManager.dividerColor,
),
TextButton(
style: TextButton.styleFrom(
backgroundColor: selectedOperator.toLowerCase() == 'and'
? ColorsManager.dialogBlueTitle
: ColorsManager.whiteColors,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(0),
),
),
child: Text(
'All condition is met',
style: context.textTheme.bodyMedium?.copyWith(
color: selectedOperator.toLowerCase() == 'and'
? ColorsManager.whiteColors
: ColorsManager.blackColor,
),
),
onPressed: () {
context
.read<RoutineBloc>()
.add(const ChangeAutomationOperator(operator: 'and'));
},
),
],
),
);
}
}
@@ -0,0 +1,196 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routiens/widgets/main_routine_view/routine_view_card.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
class FetchRoutineScenesAutomation extends StatefulWidget {
const FetchRoutineScenesAutomation({super.key});
@override
State<FetchRoutineScenesAutomation> createState() =>
_FetchRoutineScenesState();
}
class _FetchRoutineScenesState extends State<FetchRoutineScenesAutomation>
with HelperResponsiveLayout {
@override
void initState() {
super.initState();
context.read<RoutineBloc>()
..add(const LoadScenes(spaceId, communityId))
..add(const LoadAutomation(spaceId));
}
@override
Widget build(BuildContext context) {
return BlocBuilder<RoutineBloc, RoutineState>(
builder: (context, state) {
return state.isLoading
? const Center(
child: CircularProgressIndicator(),
)
: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
"Scenes (Tab to Run)",
style: Theme.of(context).textTheme.titleLarge?.copyWith(
color: ColorsManager.grayColor,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 10),
if (state.scenes.isEmpty)
Text(
"No scenes found",
style: context.textTheme.bodyMedium?.copyWith(
color: ColorsManager.grayColor,
),
),
if (state.scenes.isNotEmpty)
ConstrainedBox(
constraints: BoxConstraints(
maxHeight: isSmallScreenSize(context) ? 160 : 170,
),
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: state.scenes.length,
itemBuilder: (context, index) => Padding(
padding: EdgeInsets.only(
right: isSmallScreenSize(context) ? 4.0 : 8.0,
),
child: Stack(
children: [
RoutineViewCard(
onTap: () {},
textString: state.scenes[index].name,
icon: state.scenes[index].icon ??
Assets.logoHorizontal,
isFromScenes: true,
iconInBytes:
state.scenes[index].iconInBytes,
),
Positioned(
top: 0,
right: 0,
child: InkWell(
onTap: () => context
.read<RoutineBloc>()
.add(
DeleteScene(
sceneId: state.scenes[index].id,
unitUuid: spaceId,
),
),
child: Container(
height: 20,
width: 20,
decoration: BoxDecoration(
color: ColorsManager.whiteColors,
shape: BoxShape.circle,
border: Border.all(
color: ColorsManager.grayColor,
width: 2.0,
),
),
child: const Center(
child: Icon(Icons.delete,
size: 15,
color: ColorsManager.grayColor),
),
),
),
),
],
),
),
),
),
const SizedBox(height: 15),
Text(
"Automations",
style: Theme.of(context).textTheme.titleLarge?.copyWith(
color: ColorsManager.grayColor,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 10),
if (state.automations.isEmpty)
Text(
"No automations found",
style: context.textTheme.bodyMedium?.copyWith(
color: ColorsManager.grayColor,
),
),
if (state.automations.isNotEmpty)
ConstrainedBox(
constraints: BoxConstraints(
maxHeight: isSmallScreenSize(context) ? 160 : 170,
),
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: state.automations.length,
itemBuilder: (context, index) => Padding(
padding: EdgeInsets.only(
right: isSmallScreenSize(context) ? 4.0 : 8.0,
),
child: Stack(
children: [
RoutineViewCard(
onTap: () {},
textString: state.automations[index].name,
icon: state.automations[index].icon ??
Assets.automation,
),
Positioned(
top: 0,
right: 0,
child: InkWell(
onTap: () =>
context.read<RoutineBloc>().add(
DeleteAutomation(
automationId: state
.automations[index].id,
unitUuid: spaceId,
),
),
child: Container(
height: 20,
width: 20,
decoration: BoxDecoration(
color: ColorsManager.whiteColors,
shape: BoxShape.circle,
border: Border.all(
color: ColorsManager.grayColor,
width: 2.0,
),
),
child: const Center(
child: Icon(Icons.delete,
size: 15,
color: ColorsManager.grayColor),
),
),
),
),
],
),
),
),
),
],
),
),
);
},
);
}
}
@@ -0,0 +1,127 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_svg/flutter_svg.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 RoutineViewCard extends StatelessWidget with HelperResponsiveLayout {
const RoutineViewCard({
super.key,
required this.onTap,
required this.icon,
required this.textString,
this.isFromScenes,
this.iconInBytes,
});
final Function() onTap;
final dynamic icon;
final String textString;
final bool? isFromScenes;
final Uint8List? iconInBytes;
@override
Widget build(BuildContext context) {
final double cardWidth = isSmallScreenSize(context)
? 120
: isMediumScreenSize(context)
? 135
: 150;
final double cardHeight = isSmallScreenSize(context) ? 160 : 170;
final double iconSize = isSmallScreenSize(context)
? 50
: isMediumScreenSize(context)
? 60
: 70;
return ConstrainedBox(
constraints: BoxConstraints(
maxWidth: cardWidth,
maxHeight: cardHeight,
),
child: Card(
elevation: 3,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
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,
),
),
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,
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,
),
),
),
],
),
),
),
),
);
}
}
@@ -0,0 +1,101 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/routiens/bloc/effective_period/effect_period_bloc.dart';
import 'package:syncrow_web/pages/routiens/bloc/effective_period/effect_period_event.dart';
import 'package:syncrow_web/pages/routiens/bloc/effective_period/effect_period_state.dart';
import 'package:syncrow_web/pages/routiens/widgets/routine_dialogs/effictive_period_dialog.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/app_enum.dart';
class PeriodOptions extends StatelessWidget {
final Future<List<String>?> Function(BuildContext) showCustomTimePicker;
const PeriodOptions({
required this.showCustomTimePicker,
super.key,
});
@override
Widget build(BuildContext context) {
return BlocBuilder<EffectPeriodBloc, EffectPeriodState>(
builder: (context, state) {
return Column(
children: [
_buildRadioOption(context, EnumEffectivePeriodOptions.allDay, '24 Hours'),
_buildRadioOption(context, EnumEffectivePeriodOptions.daytime, 'Sunrise to Sunset'),
_buildRadioOption(context, EnumEffectivePeriodOptions.night, 'Sunset to Sunrise'),
ListTile(
contentPadding: EdgeInsets.zero,
onTap: () => showCustomTimePicker(context),
title: Text(
'Custom',
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: ColorsManager.textPrimaryColor,
fontWeight: FontWeight.w400,
fontSize: 14),
),
subtitle: state.customStartTime != null && state.customEndTime != null
? Text(
'${"${state.customStartTime}"} - ${"${state.customEndTime}"}',
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: ColorsManager.textPrimaryColor,
fontWeight: FontWeight.w400,
fontSize: 10),
)
: Text(
'00:00 - 23:59',
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: ColorsManager.textPrimaryColor,
fontWeight: FontWeight.w400,
fontSize: 10),
),
trailing: Radio<EnumEffectivePeriodOptions>(
value: EnumEffectivePeriodOptions.custom,
groupValue: state.selectedPeriod,
onChanged: (value) async {
if (value != null) {
context.read<EffectPeriodBloc>().add(SetPeriod(value));
}
showCustomTimePicker(context);
},
),
),
],
);
},
);
}
Widget _buildRadioOption(
BuildContext context, EnumEffectivePeriodOptions value, String subtitle) {
return BlocBuilder<EffectPeriodBloc, EffectPeriodState>(
builder: (context, state) {
return ListTile(
contentPadding: EdgeInsets.zero,
onTap: () {
context.read<EffectPeriodBloc>().add(SetPeriod(value));
},
title: Text(
EffectPeriodHelper.formatEnumValue(value),
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: ColorsManager.blackColor, fontWeight: FontWeight.w400, fontSize: 12),
),
subtitle: Text(
subtitle,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: ColorsManager.textPrimaryColor, fontWeight: FontWeight.w400, fontSize: 10),
),
trailing: Radio<EnumEffectivePeriodOptions>(
value: value,
groupValue: state.selectedPeriod,
onChanged: (value) {
if (value != null) {
context.read<EffectPeriodBloc>().add(SetPeriod(value));
}
},
),
);
},
);
}
}
@@ -0,0 +1,83 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/routiens/bloc/effective_period/effect_period_bloc.dart';
import 'package:syncrow_web/pages/routiens/bloc/effective_period/effect_period_event.dart';
import 'package:syncrow_web/pages/routiens/bloc/effective_period/effect_period_state.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class RepeatDays extends StatelessWidget {
const RepeatDays({super.key});
@override
Widget build(BuildContext context) {
final effectiveBloc = context.read<EffectPeriodBloc>();
return Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text('Repeat',
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: ColorsManager.textPrimaryColor, fontWeight: FontWeight.w400, fontSize: 14)),
const SizedBox(width: 8),
BlocBuilder<EffectPeriodBloc, EffectPeriodState>(
builder: (context, state) {
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: effectiveBloc.daysMap.entries.map((entry) {
final day = entry.key;
final abbreviation = entry.value;
final dayIndex = effectiveBloc.getDayIndex(day);
final isSelected = state.selectedDaysBinary[dayIndex] == '1';
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 3.0),
child: GestureDetector(
onTap: () {
effectiveBloc.add(ToggleDay(day));
},
child: Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
color: isSelected ? Colors.grey : Colors.grey.shade300,
width: 1,
),
),
child: CircleAvatar(
radius: 15,
backgroundColor: Colors.white,
child: Text(
abbreviation,
style: TextStyle(
fontSize: 16,
color: isSelected ? Colors.grey : Colors.grey.shade300,
),
),
),
),
),
);
}).toList(),
),
const SizedBox(
height: 8,
),
if (state.selectedDaysBinary == '0000000')
Text(
'At least one day must be selected',
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: ColorsManager.textPrimaryColor,
fontWeight: FontWeight.w400,
fontSize: 14),
),
],
);
},
),
],
);
}
}
@@ -0,0 +1,77 @@
import 'package:flutter/material.dart';
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/models/devices_model.dart';
import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routiens/widgets/dragable_card.dart';
class RoutineDevices extends StatelessWidget {
const RoutineDevices({super.key});
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => DeviceManagementBloc()..add(FetchDevices()),
child: BlocBuilder<DeviceManagementBloc, DeviceManagementState>(
builder: (context, state) {
if (state is DeviceManagementLoaded) {
List<AllDevicesModel> deviceList = state.devices
.where((device) =>
device.productType == 'AC' ||
device.productType == '1G' ||
device.productType == '2G' ||
device.productType == '3G')
.toList();
// Provide the RoutineBloc to the child widgets
return BlocBuilder<RoutineBloc, RoutineState>(
builder: (context, routineState) {
return Wrap(
spacing: 10,
runSpacing: 10,
children: deviceList.asMap().entries.map((entry) {
final device = entry.value;
if (routineState.searchText != null && routineState.searchText!.isNotEmpty) {
return device.name!
.toLowerCase()
.contains(routineState.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': '',
},
)
: Container();
} 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': '',
},
);
}
}).toList(),
);
},
);
}
return const Center(child: CircularProgressIndicator());
},
),
);
}
}
@@ -0,0 +1,421 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routiens/models/ac/ac_function.dart';
import 'package:syncrow_web/pages/routiens/models/ac/ac_operational_value.dart';
import 'package:syncrow_web/pages/routiens/models/device_functions.dart';
import 'package:syncrow_web/pages/routiens/widgets/dialog_footer.dart';
import 'package:syncrow_web/pages/routiens/widgets/dialog_header.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/routiens/bloc/functions_bloc/functions_bloc_bloc.dart';
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();
return showDialog<Map<String, dynamic>?>(
context: context,
builder: (BuildContext context) {
return BlocProvider(
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,
orElse: () => DeviceFunctionData(
entityId: '',
functionCode: selectedFunction ?? '',
operationName: '',
value: null,
));
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('AC Functions'),
Expanded(
child: Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Function list
SizedBox(
width: selectedFunction != null ? 320 : 360,
child: _buildFunctionsList(
context: context,
acFunctions: acFunctions,
onFunctionSelected:
(functionCode, operationName) => context
.read<FunctionBloc>()
.add(SelectFunction(
functionCode: functionCode,
operationName: operationName,
)),
),
),
// Value selector
if (selectedFunction != null)
Expanded(
child: _buildValueSelector(
context: context,
selectedFunction: selectedFunction,
selectedFunctionData: selectedFunctionData,
acFunctions: acFunctions,
device: device,
operationName: selectedOperationName ?? '',
removeComparators: removeComparetors,
),
),
],
),
),
DialogFooter(
onCancel: () {
Navigator.pop(context);
},
onConfirm: state.addedFunctions.isNotEmpty
? () {
/// add the functions to the routine bloc
context.read<RoutineBloc>().add(
AddFunctionToRoutine(
state.addedFunctions,
uniqueCustomId,
),
);
// Return the device data to be added to the container
Navigator.pop(context, {
'deviceId': functions.first.deviceId,
});
}
: null,
isConfirmEnabled: selectedFunction != null,
),
],
),
);
},
),
),
);
},
).then((value) {
return value;
});
}
/// Build functions list for AC functions dialog
static Widget _buildFunctionsList({
required BuildContext context,
required List<ACFunction> acFunctions,
required Function(String, String) onFunctionSelected,
}) {
return ListView.separated(
shrinkWrap: false,
physics: const AlwaysScrollableScrollPhysics(),
itemCount: acFunctions.length,
separatorBuilder: (context, index) => const Padding(
padding: EdgeInsets.symmetric(horizontal: 40.0),
child: Divider(
color: ColorsManager.dividerColor,
),
),
itemBuilder: (context, index) {
final function = acFunctions[index];
return ListTile(
leading: SvgPicture.asset(
function.icon,
width: 24,
height: 24,
placeholderBuilder: (BuildContext context) => Container(
width: 24,
height: 24,
color: Colors.transparent,
),
),
title: Text(
function.operationName,
style: context.textTheme.bodyMedium,
),
trailing: const Icon(
Icons.arrow_forward_ios,
size: 16,
color: ColorsManager.textGray,
),
onTap: () => onFunctionSelected(
function.code,
function.operationName,
),
);
},
);
}
/// Build value selector for AC functions dialog
static Widget _buildValueSelector({
required BuildContext context,
required String selectedFunction,
required DeviceFunctionData? selectedFunctionData,
required List<ACFunction> acFunctions,
AllDevicesModel? device,
required String operationName,
bool? removeComparators,
}) {
if (selectedFunction == 'temp_set' || selectedFunction == 'temp_current') {
final initialValue = selectedFunctionData?.value ?? 200;
return _buildTemperatureSelector(
context: context,
initialValue: initialValue,
selectCode: selectedFunction,
currentCondition: selectedFunctionData?.condition,
device: device,
operationName: operationName,
selectedFunctionData: selectedFunctionData,
removeComparators: removeComparators,
);
}
final selectedFn =
acFunctions.firstWhere((f) => f.code == selectedFunction);
final values = selectedFn.getOperationalValues();
return _buildOperationalValuesList(
context: context,
values: values,
selectedValue: selectedFunctionData?.value,
device: device,
operationName: operationName,
selectCode: selectedFunction,
selectedFunctionData: selectedFunctionData,
);
}
/// Build temperature selector for AC functions dialog
static Widget _buildTemperatureSelector({
required BuildContext context,
required dynamic initialValue,
required String? currentCondition,
required String selectCode,
AllDevicesModel? device,
required String operationName,
DeviceFunctionData? selectedFunctionData,
bool? removeComparators,
}) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (removeComparators != true)
_buildConditionToggle(
context,
currentCondition,
selectCode,
device,
operationName,
selectedFunctionData,
),
const SizedBox(height: 20),
_buildTemperatureDisplay(
context,
initialValue,
device,
operationName,
selectedFunctionData,
selectCode,
),
const SizedBox(height: 20),
_buildTemperatureSlider(
context,
initialValue,
device,
operationName,
selectedFunctionData,
selectCode,
),
],
);
}
/// Build condition toggle for AC functions dialog
static Widget _buildConditionToggle(
BuildContext context,
String? currentCondition,
String selectCode,
AllDevicesModel? device,
String operationName,
DeviceFunctionData? selectedFunctionData,
// Function(String) onConditionChanged,
) {
final conditions = ["<", "==", ">"];
return ToggleButtons(
onPressed: (int index) {
context.read<FunctionBloc>().add(
AddFunction(
functionData: DeviceFunctionData(
entityId: device?.uuid ?? '',
functionCode: selectCode,
operationName: operationName,
condition: conditions[index],
value: selectedFunctionData?.value,
valueDescription: selectedFunctionData?.valueDescription,
),
),
);
},
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(),
);
}
/// Build temperature display for AC functions dialog
static Widget _buildTemperatureDisplay(
BuildContext context,
dynamic initialValue,
AllDevicesModel? device,
String operationName,
DeviceFunctionData? selectedFunctionData,
String selectCode,
) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
decoration: BoxDecoration(
color: ColorsManager.primaryColorWithOpacity.withOpacity(0.1),
borderRadius: BorderRadius.circular(10),
),
child: Text(
'${(initialValue ?? 200) / 10}°C',
style: context.textTheme.headlineMedium!.copyWith(
color: ColorsManager.primaryColorWithOpacity,
),
),
);
}
static Widget _buildTemperatureSlider(
BuildContext context,
dynamic initialValue,
AllDevicesModel? device,
String operationName,
DeviceFunctionData? selectedFunctionData,
String selectCode,
) {
return Slider(
value: initialValue is int ? initialValue.toDouble() : 160.0,
min: 160,
max: 300,
divisions: 14,
label: '${((initialValue ?? 160) / 10).toInt()}°C',
onChanged: (value) {
context.read<FunctionBloc>().add(
AddFunction(
functionData: DeviceFunctionData(
entityId: device?.uuid ?? '',
functionCode: selectCode,
operationName: operationName,
value: value,
condition: selectedFunctionData?.condition,
valueDescription: selectedFunctionData?.valueDescription,
),
),
);
},
);
}
static Widget _buildOperationalValuesList({
required BuildContext context,
required List<ACOperationalValue> values,
required dynamic selectedValue,
AllDevicesModel? device,
required String operationName,
required String selectCode,
DeviceFunctionData? selectedFunctionData,
// required Function(dynamic) onValueChanged,
}) {
return ListView.builder(
shrinkWrap: false,
physics: const AlwaysScrollableScrollPhysics(),
itemCount: values.length,
itemBuilder: (context, index) {
final value = values[index];
final isSelected = selectedValue == value.value;
return ListTile(
leading: SvgPicture.asset(
value.icon,
width: 24,
height: 24,
placeholderBuilder: (BuildContext context) => Container(
width: 24,
height: 24,
color: Colors.transparent,
),
),
title: Text(
value.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: () {
if (!isSelected) {
context.read<FunctionBloc>().add(
AddFunction(
functionData: DeviceFunctionData(
entityId: device?.uuid ?? '',
functionCode: selectCode,
operationName: operationName,
value: value.value,
condition: selectedFunctionData?.condition,
valueDescription:
selectedFunctionData?.valueDescription,
),
),
);
}
},
);
},
);
}
}
@@ -0,0 +1,95 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routiens/models/device_functions.dart';
import 'package:syncrow_web/pages/routiens/widgets/dialog_header.dart';
import 'package:syncrow_web/pages/routiens/widgets/dialog_footer.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
class AutomationDialog extends StatefulWidget {
final String automationName;
final String automationId;
final String uniqueCustomId;
const AutomationDialog({
super.key,
required this.automationName,
required this.automationId,
required this.uniqueCustomId,
});
@override
_AutomationDialogState createState() => _AutomationDialogState();
}
class _AutomationDialogState extends State<AutomationDialog> {
bool _isEnabled = true;
@override
Widget build(BuildContext context) {
return Dialog(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
child: Container(
width: 400,
padding: const EdgeInsets.all(16),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
DialogHeader(widget.automationName),
const SizedBox(height: 16),
ListTile(
leading: SvgPicture.asset(Assets.acPower, width: 24, height: 24),
title: const Text('Enable'),
trailing: Radio<bool>(
value: true,
groupValue: _isEnabled,
onChanged: (bool? value) {
setState(() {
_isEnabled = value!;
});
},
),
),
ListTile(
leading:
SvgPicture.asset(Assets.acPowerOff, width: 24, height: 24),
title: const Text('Disable'),
trailing: Radio<bool>(
value: false,
groupValue: _isEnabled,
onChanged: (bool? value) {
setState(() {
_isEnabled = value!;
});
},
),
),
const SizedBox(height: 16),
DialogFooter(
onConfirm: () {
context.read<RoutineBloc>().add(
AddFunctionToRoutine(
[
DeviceFunctionData(
entityId: widget.automationId,
functionCode: 'automation',
value: _isEnabled ? 'rule_enable' : 'rule_disable',
operationName: 'Automation',
),
],
widget.uniqueCustomId,
),
);
Navigator.of(context).pop(true);
},
onCancel: () => Navigator.of(context).pop(false),
isConfirmEnabled: true,
dialogWidth: 400,
),
],
),
),
);
}
}
@@ -0,0 +1,85 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routiens/models/device_functions.dart';
import 'package:syncrow_web/pages/routiens/widgets/dialog_footer.dart';
import 'package:syncrow_web/pages/routiens/widgets/dialog_header.dart';
class DelayHelper {
static Future<Map<String, dynamic>?> showDelayPickerDialog(
BuildContext context, Map<String, dynamic> data) async {
int hours = 0;
int minutes = 0;
return showDialog<Map<String, dynamic>?>(
context: context,
builder: (BuildContext context) {
final routineBloc = context.read<RoutineBloc>();
int totalSec = 0;
final selectedFunctionData =
routineBloc.state.selectedFunctions[data['uniqueCustomId']] ?? [];
if (selectedFunctionData.isNotEmpty) {
totalSec = selectedFunctionData[0].value;
// Convert seconds to hours and minutes
hours = totalSec ~/ 3600;
minutes = (totalSec % 3600) ~/ 60;
}
return AlertDialog(
contentPadding: EdgeInsets.zero,
content: Container(
width: 600,
height: 300,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const DialogHeader('Select Delay Duration'),
Expanded(
child: CupertinoTimerPicker(
mode: CupertinoTimerPickerMode.hm,
initialTimerDuration: Duration(hours: hours, minutes: minutes),
onTimerDurationChanged: (Duration newDuration) {
hours = newDuration.inHours;
minutes = newDuration.inMinutes % 60;
},
),
),
DialogFooter(
onCancel: () {
Navigator.of(context).pop();
},
onConfirm: () {
int totalSeconds = (hours * 3600) + (minutes * 60);
context.read<RoutineBloc>().add(AddFunctionToRoutine(
[
DeviceFunctionData(
entityId: 'delay',
functionCode: 'delay',
operationName: 'Delay',
value: totalSeconds,
)
],
data['uniqueCustomId'],
));
Navigator.pop(context, {
'deviceId': 'delay',
'value': totalSeconds,
});
},
isConfirmEnabled: true,
),
],
),
),
);
},
);
}
}
@@ -0,0 +1,60 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/bloc/switch_tabs/switch_tabs_bloc.dart';
import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
class DiscardDialog {
static void show(BuildContext context) {
context.customAlertDialog(
alertBody: Container(
height: 150,
padding: const EdgeInsets.all(16),
child: Column(
children: [
Text(
'If you close, you will lose all the changes you have made.',
textAlign: TextAlign.center,
style: context.textTheme.bodyMedium!.copyWith(
color: ColorsManager.red,
fontWeight: FontWeight.w400,
fontSize: 14,
),
),
const SizedBox(
height: 20,
),
Text(
'Are you sure you wish to close?',
style: context.textTheme.bodyMedium!.copyWith(
fontWeight: FontWeight.w400,
color: ColorsManager.grayColor,
),
)
],
)),
title: 'Discard',
titleStyle: context.textTheme.titleLarge!.copyWith(
color: ColorsManager.red,
fontWeight: FontWeight.bold,
),
onDismissText: "Don’t Close",
onConfirmText: "Close",
onDismissColor: ColorsManager.grayColor,
onConfirmColor: ColorsManager.red.withOpacity(0.8),
onDismiss: () {
Navigator.pop(context);
},
onConfirm: () {
context.read<RoutineBloc>().add(ResetRoutineState());
Navigator.pop(context);
BlocProvider.of<SwitchTabsBloc>(context).add(
const CreateNewRoutineViewEvent(false),
);
BlocProvider.of<SwitchTabsBloc>(context).add(
const TriggerSwitchTabsEvent(true),
);
});
}
}
@@ -0,0 +1,151 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/routiens/bloc/effective_period/effect_period_bloc.dart';
import 'package:syncrow_web/pages/routiens/bloc/effective_period/effect_period_event.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/app_enum.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:time_picker_spinner/time_picker_spinner.dart';
class EffectPeriodHelper {
static Future<List<String>?> showCustomTimePicker(BuildContext context) async {
String selectedStartTime = "00:00";
String selectedEndTime = "23:59";
PageController pageController = PageController(initialPage: 0);
DateTime startDateTime = DateTime(2022, 1, 1, 0, 0);
DateTime endDateTime = DateTime(2022, 1, 1, 23, 59);
context.customAlertDialog(
alertBody: SizedBox(
height: 250,
child: PageView(
controller: pageController,
physics: const NeverScrollableScrollPhysics(),
children: [
_buildTimePickerPage(
context: context,
pageController: pageController,
isStartTime: true,
time: startDateTime,
onTimeChange: (time) {
selectedStartTime =
"${time.hour.toString().padLeft(2, '0')}:${time.minute.toString().padLeft(2, '0')}";
},
),
_buildTimePickerPage(
context: context,
pageController: pageController,
isStartTime: false,
time: endDateTime,
onTimeChange: (time) {
selectedEndTime =
"${time.hour.toString().padLeft(2, '0')}:${time.minute.toString().padLeft(2, '0')}";
},
),
],
),
),
title: "Custom",
onConfirm: () {
context.read<EffectPeriodBloc>().add(
SetCustomTime(selectedStartTime, selectedEndTime),
);
context.read<EffectPeriodBloc>().add(
const SetPeriod(EnumEffectivePeriodOptions.custom),
);
Navigator.of(context).pop();
},
);
return null;
}
static Widget _buildTimePickerPage({
required BuildContext context,
required PageController pageController,
required bool isStartTime,
required DateTime time,
required Function(DateTime) onTimeChange,
}) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
if (!isStartTime)
TextButton(
onPressed: () {
pageController.previousPage(
duration: const Duration(milliseconds: 300),
curve: Curves.easeIn,
);
},
child: const Text('Start'),
),
TextButton(
onPressed: () {},
child: Text(isStartTime ? "Start" : "End",
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: ColorsManager.textPrimaryColor,
fontWeight: FontWeight.w400,
fontSize: 10)),
),
if (isStartTime)
TextButton(
onPressed: () {
pageController.nextPage(
duration: const Duration(milliseconds: 300),
curve: Curves.easeIn,
);
},
child: Text('End',
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: ColorsManager.textPrimaryColor,
fontWeight: FontWeight.w400,
fontSize: 10)),
),
],
),
),
TimePickerSpinner(
is24HourMode: false,
normalTextStyle: const TextStyle(
fontSize: 24,
color: Colors.grey,
),
highlightedTextStyle: const TextStyle(
fontSize: 24,
color: ColorsManager.primaryColor,
),
spacing: 20,
itemHeight: 50,
isForce2Digits: true,
time: time,
onTimeChange: onTimeChange,
),
const SizedBox(height: 16),
],
);
}
static String formatEnumValue(EnumEffectivePeriodOptions value) {
switch (value) {
case EnumEffectivePeriodOptions.allDay:
return "All Day";
case EnumEffectivePeriodOptions.daytime:
return "Daytime";
case EnumEffectivePeriodOptions.night:
return "Night";
case EnumEffectivePeriodOptions.custom:
return "Custom";
case EnumEffectivePeriodOptions.none:
return "None";
default:
return "";
}
}
}
@@ -0,0 +1,397 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
import 'package:syncrow_web/pages/routiens/bloc/functions_bloc/functions_bloc_bloc.dart';
import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routiens/helper/duration_format_helper.dart';
import 'package:syncrow_web/pages/routiens/models/device_functions.dart';
import 'package:syncrow_web/pages/routiens/models/gang_switches/base_switch_function.dart';
import 'package:syncrow_web/pages/routiens/models/gang_switches/switch_operational_value.dart';
import 'package:syncrow_web/pages/routiens/widgets/dialog_footer.dart';
import 'package:syncrow_web/pages/routiens/widgets/dialog_header.dart';
import 'package:syncrow_web/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();
return showDialog<Map<String, dynamic>?>(
context: context,
builder: (BuildContext context) {
return BlocProvider(
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,
orElse: () => DeviceFunctionData(
entityId: '',
functionCode: selectedFunction ?? '',
operationName: '',
value: null,
));
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('1 Gang Light Switch Condition'),
Expanded(
child: Row(
children: [
// Left side: Function list
Expanded(
child: ListView.separated(
itemCount: acFunctions.length,
separatorBuilder: (_, __) => const Divider(
color: ColorsManager.dividerColor,
),
itemBuilder: (context, index) {
final function = acFunctions[index];
return ListTile(
leading: SvgPicture.asset(
function.icon,
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,
));
},
);
},
),
),
// Right side: Value selector
if (selectedFunction != null)
Expanded(
child: _buildValueSelector(
context: context,
selectedFunction: selectedFunction,
selectedFunctionData: selectedFunctionData,
acFunctions: acFunctions,
device: device,
operationName: selectedOperationName ?? '',
removeComparetors: removeComparetors,
),
),
],
),
),
Container(
height: 1,
width: double.infinity,
color: ColorsManager.greyColor,
),
DialogFooter(
onCancel: () {
Navigator.pop(context);
},
onConfirm: state.addedFunctions.isNotEmpty
? () {
/// add the functions to the routine bloc
// for (var function in state.addedFunctions) {
// context.read<RoutineBloc>().add(
// AddFunctionToRoutine(
// function,
// uniqueCustomId,
// ),
// );
// }
context.read<RoutineBloc>().add(
AddFunctionToRoutine(
state.addedFunctions,
uniqueCustomId,
),
);
// Return the device data to be added to the container
Navigator.pop(context, {
'deviceId': functions.first.deviceId,
});
}
: null,
isConfirmEnabled: selectedFunction != null,
),
],
),
);
},
),
));
},
);
}
static Widget _buildValueSelector({
required BuildContext context,
required String selectedFunction,
required DeviceFunctionData? selectedFunctionData,
required List<BaseSwitchFunction> acFunctions,
AllDevicesModel? device,
required String operationName,
required bool removeComparetors,
}) {
if (selectedFunction == 'countdown_1') {
final initialValue = selectedFunctionData?.value ?? 200;
return _buildCountDownSelector(
context: context,
initialValue: initialValue,
selectCode: selectedFunction,
currentCondition: selectedFunctionData?.condition,
device: device,
operationName: operationName,
selectedFunctionData: selectedFunctionData,
removeComparetors: removeComparetors,
);
}
final selectedFn =
acFunctions.firstWhere((f) => f.code == selectedFunction);
final values = selectedFn.getOperationalValues();
return _buildOperationalValuesList(
context: context,
values: values,
selectedValue: selectedFunctionData?.value,
device: device,
operationName: operationName,
selectCode: selectedFunction,
selectedFunctionData: selectedFunctionData,
);
}
static Widget _buildCountDownSelector({
required BuildContext context,
required dynamic initialValue,
required String? currentCondition,
required String selectCode,
AllDevicesModel? device,
required String operationName,
DeviceFunctionData? selectedFunctionData,
required bool removeComparetors,
}) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (removeComparetors != true)
_buildConditionToggle(
context,
currentCondition,
selectCode,
device,
operationName,
selectedFunctionData,
),
const SizedBox(height: 20),
_buildCountDownDisplay(context, initialValue, device, operationName,
selectedFunctionData, selectCode),
const SizedBox(height: 20),
_buildCountDownSlider(context, initialValue, device, operationName,
selectedFunctionData, selectCode),
],
);
}
/// Build condition toggle for AC functions dialog
static Widget _buildConditionToggle(
BuildContext context,
String? currentCondition,
String selectCode,
AllDevicesModel? device,
String operationName,
DeviceFunctionData? selectedFunctionData,
// Function(String) onConditionChanged,
) {
final conditions = ["<", "==", ">"];
return ToggleButtons(
onPressed: (int index) {
context.read<FunctionBloc>().add(
AddFunction(
functionData: DeviceFunctionData(
entityId: device?.uuid ?? '',
functionCode: selectCode,
operationName: operationName,
condition: conditions[index],
value: selectedFunctionData?.value,
valueDescription: selectedFunctionData?.valueDescription,
),
),
);
},
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(),
);
}
/// Build temperature display for AC functions dialog
static Widget _buildCountDownDisplay(
BuildContext context,
dynamic initialValue,
AllDevicesModel? device,
String operationName,
DeviceFunctionData? selectedFunctionData,
String selectCode) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
decoration: BoxDecoration(
color: ColorsManager.primaryColorWithOpacity.withOpacity(0.1),
borderRadius: BorderRadius.circular(10),
),
child: Text(
DurationFormatMixin.formatDuration(initialValue?.toInt() ?? 0),
style: context.textTheme.headlineMedium!.copyWith(
color: ColorsManager.primaryColorWithOpacity,
),
),
);
}
static Widget _buildCountDownSlider(
BuildContext context,
dynamic initialValue,
AllDevicesModel? device,
String operationName,
DeviceFunctionData? selectedFunctionData,
String selectCode,
) {
final operationalValues = SwitchOperationalValue(
icon: '',
description: "sec",
value: 0.0,
minValue: 0,
maxValue: 86400,
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(),
onChanged: (value) {
context.read<FunctionBloc>().add(
AddFunction(
functionData: DeviceFunctionData(
entityId: device?.uuid ?? '',
functionCode: selectCode,
operationName: operationName,
value: value,
condition: selectedFunctionData?.condition,
valueDescription: selectedFunctionData?.valueDescription,
),
),
);
},
);
}
static Widget _buildOperationalValuesList({
required BuildContext context,
required List<SwitchOperationalValue> values,
required dynamic selectedValue,
AllDevicesModel? device,
required String operationName,
required String selectCode,
DeviceFunctionData? selectedFunctionData,
}) {
return ListView.builder(
shrinkWrap: false,
physics: const AlwaysScrollableScrollPhysics(),
itemCount: values.length,
itemBuilder: (context, index) {
final value = values[index];
final isSelected = selectedValue == value.value;
return ListTile(
leading: SvgPicture.asset(
value.icon,
width: 24,
height: 24,
placeholderBuilder: (BuildContext context) => Container(
width: 24,
height: 24,
color: Colors.transparent,
),
),
title: Text(
value.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: () {
if (!isSelected) {
context.read<FunctionBloc>().add(
AddFunction(
functionData: DeviceFunctionData(
entityId: device?.uuid ?? '',
functionCode: selectCode,
operationName: operationName,
value: value.value,
condition: selectedFunctionData?.condition,
valueDescription:
selectedFunctionData?.valueDescription,
),
),
);
}
},
);
},
);
}
}
@@ -0,0 +1,416 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/routiens/bloc/effective_period/effect_period_bloc.dart';
import 'package:syncrow_web/pages/routiens/bloc/effective_period/effect_period_state.dart';
import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routiens/bloc/setting_bloc/setting_bloc.dart';
import 'package:syncrow_web/pages/routiens/bloc/setting_bloc/setting_event.dart';
import 'package:syncrow_web/pages/routiens/bloc/setting_bloc/setting_state.dart';
import 'package:syncrow_web/pages/routiens/models/create_scene_and_autoamtion/create_automation_model.dart';
import 'package:syncrow_web/pages/routiens/models/icon_model.dart';
import 'package:syncrow_web/pages/routiens/view/effective_period_view.dart';
import 'package:syncrow_web/pages/routiens/widgets/dialog_header.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:flutter/cupertino.dart';
class SettingHelper {
static Future<String?> showSettingDialog({
required BuildContext context,
String? iconId,
}) async {
return showDialog<String>(
context: context,
builder: (BuildContext context) {
final isAutomation = context.read<RoutineBloc>().state.isAutomation;
return MultiBlocProvider(
providers: [
BlocProvider(
create: (_) => EffectPeriodBloc(),
),
BlocProvider(
create: (_) => SettingBloc()..add(InitialEvent(selectedIcon: iconId ?? ''))),
],
child: AlertDialog(
contentPadding: EdgeInsets.zero,
content: BlocBuilder<EffectPeriodBloc, EffectPeriodState>(
builder: (context, effectPeriodState) {
return BlocBuilder<SettingBloc, SettingState>(
builder: (context, settingState) {
String selectedIcon = '';
List<IconModel> list = [];
if (settingState is TabToRunSettingLoaded) {
selectedIcon = settingState.selectedIcon;
list = settingState.iconList;
}
return Container(
width: context.read<SettingBloc>().isExpanded ? 800 : 400,
height: context.read<SettingBloc>().isExpanded && isAutomation ? 500 : 300,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
),
padding: const EdgeInsets.only(top: 20),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const DialogHeader('Settings'),
Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: 400,
child: isAutomation
? Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
padding: const EdgeInsets.only(
top: 10, left: 10, right: 10, bottom: 10),
child: Column(
children: [
InkWell(
onTap: () {},
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Text(
'Validity',
style: Theme.of(context)
.textTheme
.bodyMedium!
.copyWith(
color:
ColorsManager.textPrimaryColor,
fontWeight: FontWeight.w400,
fontSize: 14),
),
const Icon(
Icons.arrow_forward_ios_outlined,
color: ColorsManager.textGray,
size: 15,
)
],
),
),
const SizedBox(
height: 5,
),
const Divider(
color: ColorsManager.graysColor,
),
const SizedBox(
height: 5,
),
InkWell(
onTap: () {
BlocProvider.of<SettingBloc>(context).add(
FetchIcons(
expanded: !context
.read<SettingBloc>()
.isExpanded));
},
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Text(
'Effective Period',
style: Theme.of(context)
.textTheme
.bodyMedium!
.copyWith(
color:
ColorsManager.textPrimaryColor,
fontWeight: FontWeight.w400,
fontSize: 14),
),
const Icon(
Icons.arrow_forward_ios_outlined,
color: ColorsManager.textGray,
size: 15,
)
],
),
),
const SizedBox(
height: 5,
),
const Divider(
color: ColorsManager.graysColor,
),
const SizedBox(
height: 5,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Executed by',
style: Theme.of(context)
.textTheme
.bodyMedium!
.copyWith(
color: ColorsManager.textPrimaryColor,
fontWeight: FontWeight.w400,
fontSize: 14),
),
Text('Cloud',
style: Theme.of(context)
.textTheme
.bodyMedium!
.copyWith(
color: ColorsManager.textGray,
fontWeight: FontWeight.w400,
fontSize: 14)),
],
),
],
)),
],
)
: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
padding: const EdgeInsets.only(
top: 10, left: 10, right: 10, bottom: 10),
child: Column(
children: [
InkWell(
onTap: () {
BlocProvider.of<SettingBloc>(context).add(
FetchIcons(
expanded: !context
.read<SettingBloc>()
.isExpanded));
},
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Text(
'Icons',
style: Theme.of(context)
.textTheme
.bodyMedium!
.copyWith(
color:
ColorsManager.textPrimaryColor,
fontWeight: FontWeight.w400,
fontSize: 14),
),
const Icon(
Icons.arrow_forward_ios_outlined,
color: ColorsManager.textGray,
size: 15,
)
],
),
),
const SizedBox(
height: 5,
),
const Divider(
color: ColorsManager.graysColor,
),
const SizedBox(
height: 5,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Show on devices page',
style: Theme.of(context)
.textTheme
.bodyMedium!
.copyWith(
color: ColorsManager.textPrimaryColor,
fontWeight: FontWeight.w400,
fontSize: 14),
),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Container(
height: 30,
width: 1,
color: ColorsManager.graysColor,
),
Transform.scale(
scale: .8,
child: CupertinoSwitch(
value: true,
onChanged: (value) {},
applyTheme: true,
),
),
],
)
],
),
const SizedBox(
height: 5,
),
const Divider(
color: ColorsManager.graysColor,
),
const SizedBox(
height: 5,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Executed by',
style: Theme.of(context)
.textTheme
.bodyMedium!
.copyWith(
color: ColorsManager.textPrimaryColor,
fontWeight: FontWeight.w400,
fontSize: 14),
),
Text('Cloud',
style: Theme.of(context)
.textTheme
.bodyMedium!
.copyWith(
color: ColorsManager.textGray,
fontWeight: FontWeight.w400,
fontSize: 14)),
],
),
],
)),
],
),
),
if (context.read<SettingBloc>().isExpanded && !isAutomation)
SizedBox(
width: 400,
height: 150,
child: settingState is LoadingState
? const Center(child: CircularProgressIndicator())
: GridView.builder(
gridDelegate:
const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 6,
crossAxisSpacing: 12,
mainAxisSpacing: 12,
),
shrinkWrap: true,
itemCount: list.length,
itemBuilder: (context, index) {
final iconModel = list[index];
return SizedBox(
width: 35,
height: 35,
child: InkWell(
onTap: () {
BlocProvider.of<SettingBloc>(context)
.add(SelectIcon(
iconId: iconModel.uuid,
));
selectedIcon = iconModel.uuid;
},
child: SizedBox(
child: ClipOval(
child: Container(
padding: const EdgeInsets.all(1),
decoration: BoxDecoration(
border: Border.all(
color: selectedIcon == iconModel.uuid
? ColorsManager
.primaryColorWithOpacity
: Colors.transparent,
width: 2,
),
shape: BoxShape.circle,
),
child: Image.memory(
iconModel.iconBytes,
),
),
),
),
),
);
},
)),
if (context.read<SettingBloc>().isExpanded && isAutomation)
const SizedBox(height: 350, width: 400, child: EffectivePeriodView())
],
),
Container(
width: MediaQuery.sizeOf(context).width,
decoration: const BoxDecoration(
border: Border(
top: BorderSide(
color: ColorsManager.greyColor,
),
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: InkWell(
onTap: () {
Navigator.of(context).pop();
},
child: Container(
alignment: AlignmentDirectional.center,
child: Text(
'Cancel',
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: ColorsManager.textGray,
),
),
),
),
),
Container(width: 1, height: 50, color: ColorsManager.greyColor),
Expanded(
child: InkWell(
onTap: () {
if (isAutomation) {
BlocProvider.of<RoutineBloc>(context).add(
EffectiveTimePeriodEvent(EffectiveTime(
start: effectPeriodState.customStartTime!,
end: effectPeriodState.customEndTime!,
loops: effectPeriodState.selectedDaysBinary)));
Navigator.of(context).pop();
} else {
Navigator.of(context).pop(selectedIcon);
}
},
child: Container(
alignment: AlignmentDirectional.center,
child: Text(
'Confirm',
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: ColorsManager.primaryColorWithOpacity,
),
),
),
),
),
],
),
)
],
),
);
},
);
}),
),
);
},
);
}
}
@@ -0,0 +1,399 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
import 'package:syncrow_web/pages/routiens/bloc/functions_bloc/functions_bloc_bloc.dart';
import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routiens/helper/duration_format_helper.dart';
import 'package:syncrow_web/pages/routiens/models/device_functions.dart';
import 'package:syncrow_web/pages/routiens/models/gang_switches/base_switch_function.dart';
import 'package:syncrow_web/pages/routiens/models/gang_switches/switch_operational_value.dart';
import 'package:syncrow_web/pages/routiens/widgets/dialog_footer.dart';
import 'package:syncrow_web/pages/routiens/widgets/dialog_header.dart';
import 'package:syncrow_web/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();
return showDialog<Map<String, dynamic>?>(
context: context,
builder: (BuildContext context) {
return BlocProvider(
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,
orElse: () => DeviceFunctionData(
entityId: '',
functionCode: selectedFunction ?? '',
operationName: '',
value: null,
));
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('3 Gangs Light Switch Condition'),
Expanded(
child: Row(
children: [
// Left side: Function list
Expanded(
child: ListView.separated(
itemCount: switchFunctions.length,
separatorBuilder: (_, __) => const Divider(
color: ColorsManager.dividerColor,
),
itemBuilder: (context, index) {
final function = switchFunctions[index];
return ListTile(
leading: SvgPicture.asset(
function.icon,
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,
));
},
);
},
),
),
// Right side: Value selector
if (selectedFunction != null)
Expanded(
child: _buildValueSelector(
context: context,
selectedFunction: selectedFunction,
selectedFunctionData: selectedFunctionData,
switchFunctions: switchFunctions,
device: device,
operationName: selectedOperationName ?? '',
removeComparetors: removeComparetors,
),
),
],
),
),
Container(
height: 1,
width: double.infinity,
color: ColorsManager.greyColor,
),
DialogFooter(
onCancel: () {
Navigator.pop(context);
},
onConfirm: state.addedFunctions.isNotEmpty
? () {
/// add the functions to the routine bloc
// for (var function in state.addedFunctions) {
// context.read<RoutineBloc>().add(
// AddFunctionToRoutine(
// function,
// uniqueCustomId,
// ),
// );
// }
context.read<RoutineBloc>().add(
AddFunctionToRoutine(
state.addedFunctions,
uniqueCustomId,
),
);
// Return the device data to be added to the container
Navigator.pop(context, {
'deviceId': functions.first.deviceId,
});
}
: null,
isConfirmEnabled: selectedFunction != null,
),
],
),
);
},
),
));
},
);
}
static Widget _buildValueSelector({
required BuildContext context,
required String selectedFunction,
required DeviceFunctionData? selectedFunctionData,
required List<BaseSwitchFunction> switchFunctions,
AllDevicesModel? device,
required String operationName,
required bool removeComparetors,
}) {
if (selectedFunction == 'countdown_1' ||
selectedFunction == 'countdown_2' ||
selectedFunction == 'countdown_3') {
final initialValue = selectedFunctionData?.value ?? 200;
return _buildTemperatureSelector(
context: context,
initialValue: initialValue,
selectCode: selectedFunction,
currentCondition: selectedFunctionData?.condition,
device: device,
operationName: operationName,
selectedFunctionData: selectedFunctionData,
removeComparetors: removeComparetors,
);
}
final selectedFn =
switchFunctions.firstWhere((f) => f.code == selectedFunction);
final values = selectedFn.getOperationalValues();
return _buildOperationalValuesList(
context: context,
values: values,
selectedValue: selectedFunctionData?.value,
device: device,
operationName: operationName,
selectCode: selectedFunction,
selectedFunctionData: selectedFunctionData,
);
}
static Widget _buildTemperatureSelector({
required BuildContext context,
required dynamic initialValue,
required String? currentCondition,
required String selectCode,
AllDevicesModel? device,
required String operationName,
DeviceFunctionData? selectedFunctionData,
bool? removeComparetors,
}) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (removeComparetors != true)
_buildConditionToggle(
context,
currentCondition,
selectCode,
device,
operationName,
selectedFunctionData,
),
const SizedBox(height: 20),
_buildCountDownDisplay(context, initialValue, device, operationName,
selectedFunctionData, selectCode),
const SizedBox(height: 20),
_buildCountDownSlider(context, initialValue, device, operationName,
selectedFunctionData, selectCode),
],
);
}
/// Build condition toggle for AC functions dialog
static Widget _buildConditionToggle(
BuildContext context,
String? currentCondition,
String selectCode,
AllDevicesModel? device,
String operationName,
DeviceFunctionData? selectedFunctionData,
// Function(String) onConditionChanged,
) {
final conditions = ["<", "==", ">"];
return ToggleButtons(
onPressed: (int index) {
context.read<FunctionBloc>().add(
AddFunction(
functionData: DeviceFunctionData(
entityId: device?.uuid ?? '',
functionCode: selectCode,
operationName: operationName,
condition: conditions[index],
value: selectedFunctionData?.value,
valueDescription: selectedFunctionData?.valueDescription,
),
),
);
},
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(),
);
}
/// Build temperature display for AC functions dialog
static Widget _buildCountDownDisplay(
BuildContext context,
dynamic initialValue,
AllDevicesModel? device,
String operationName,
DeviceFunctionData? selectedFunctionData,
String selectCode) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
decoration: BoxDecoration(
color: ColorsManager.primaryColorWithOpacity.withOpacity(0.1),
borderRadius: BorderRadius.circular(10),
),
child: Text(
DurationFormatMixin.formatDuration(initialValue?.toInt() ?? 0),
style: context.textTheme.headlineMedium!.copyWith(
color: ColorsManager.primaryColorWithOpacity,
),
),
);
}
static Widget _buildCountDownSlider(
BuildContext context,
dynamic initialValue,
AllDevicesModel? device,
String operationName,
DeviceFunctionData? selectedFunctionData,
String selectCode,
) {
final operationalValues = SwitchOperationalValue(
icon: '',
description: "sec",
value: 0.0,
minValue: 0,
maxValue: 86400,
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(),
onChanged: (value) {
context.read<FunctionBloc>().add(
AddFunction(
functionData: DeviceFunctionData(
entityId: device?.uuid ?? '',
functionCode: selectCode,
operationName: operationName,
value: value,
condition: selectedFunctionData?.condition,
valueDescription: selectedFunctionData?.valueDescription,
),
),
);
},
);
}
static Widget _buildOperationalValuesList({
required BuildContext context,
required List<SwitchOperationalValue> values,
required dynamic selectedValue,
AllDevicesModel? device,
required String operationName,
required String selectCode,
DeviceFunctionData? selectedFunctionData,
}) {
return ListView.builder(
shrinkWrap: false,
physics: const AlwaysScrollableScrollPhysics(),
itemCount: values.length,
itemBuilder: (context, index) {
final value = values[index];
final isSelected = selectedValue == value.value;
return ListTile(
leading: SvgPicture.asset(
value.icon,
width: 24,
height: 24,
placeholderBuilder: (BuildContext context) => Container(
width: 24,
height: 24,
color: Colors.transparent,
),
),
title: Text(
value.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: () {
if (!isSelected) {
context.read<FunctionBloc>().add(
AddFunction(
functionData: DeviceFunctionData(
entityId: device?.uuid ?? '',
functionCode: selectCode,
operationName: operationName,
value: value.value,
condition: selectedFunctionData?.condition,
valueDescription:
selectedFunctionData?.valueDescription,
),
),
);
}
},
);
},
);
}
}
@@ -0,0 +1,398 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
import 'package:syncrow_web/pages/routiens/bloc/functions_bloc/functions_bloc_bloc.dart';
import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routiens/helper/duration_format_helper.dart';
import 'package:syncrow_web/pages/routiens/models/device_functions.dart';
import 'package:syncrow_web/pages/routiens/models/gang_switches/base_switch_function.dart';
import 'package:syncrow_web/pages/routiens/models/gang_switches/switch_operational_value.dart';
import 'package:syncrow_web/pages/routiens/widgets/dialog_footer.dart';
import 'package:syncrow_web/pages/routiens/widgets/dialog_header.dart';
import 'package:syncrow_web/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();
return showDialog<Map<String, dynamic>?>(
context: context,
builder: (BuildContext context) {
return BlocProvider(
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,
orElse: () => DeviceFunctionData(
entityId: '',
functionCode: selectedFunction ?? '',
operationName: '',
value: null,
));
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('2 Gangs Light Switch Condition'),
Expanded(
child: Row(
children: [
// Left side: Function list
Expanded(
child: ListView.separated(
itemCount: switchFunctions.length,
separatorBuilder: (_, __) => const Divider(
color: ColorsManager.dividerColor,
),
itemBuilder: (context, index) {
final function = switchFunctions[index];
return ListTile(
leading: SvgPicture.asset(
function.icon,
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,
));
},
);
},
),
),
// Right side: Value selector
if (selectedFunction != null)
Expanded(
child: _buildValueSelector(
context: context,
selectedFunction: selectedFunction,
selectedFunctionData: selectedFunctionData,
switchFunctions: switchFunctions,
device: device,
operationName: selectedOperationName ?? '',
removeComparetors: removeComparetors,
),
),
],
),
),
Container(
height: 1,
width: double.infinity,
color: ColorsManager.greyColor,
),
DialogFooter(
onCancel: () {
Navigator.pop(context);
},
onConfirm: state.addedFunctions.isNotEmpty
? () {
/// add the functions to the routine bloc
// for (var function in state.addedFunctions) {
// context.read<RoutineBloc>().add(
// AddFunctionToRoutine(
// function,
// uniqueCustomId
// ),
// );
// }
context.read<RoutineBloc>().add(
AddFunctionToRoutine(
state.addedFunctions,
uniqueCustomId,
),
);
// Return the device data to be added to the container
Navigator.pop(context, {
'deviceId': functions.first.deviceId,
});
}
: null,
isConfirmEnabled: selectedFunction != null,
),
],
),
);
},
),
));
},
);
}
static Widget _buildValueSelector({
required BuildContext context,
required String selectedFunction,
required DeviceFunctionData? selectedFunctionData,
required List<BaseSwitchFunction> switchFunctions,
AllDevicesModel? device,
required String operationName,
required bool removeComparetors,
}) {
if (selectedFunction == 'countdown_1' ||
selectedFunction == 'countdown_2') {
final initialValue = selectedFunctionData?.value ?? 200;
return _buildTemperatureSelector(
context: context,
initialValue: initialValue,
selectCode: selectedFunction,
currentCondition: selectedFunctionData?.condition,
device: device,
operationName: operationName,
selectedFunctionData: selectedFunctionData,
removeComparetors: removeComparetors,
);
}
final selectedFn =
switchFunctions.firstWhere((f) => f.code == selectedFunction);
final values = selectedFn.getOperationalValues();
return _buildOperationalValuesList(
context: context,
values: values,
selectedValue: selectedFunctionData?.value,
device: device,
operationName: operationName,
selectCode: selectedFunction,
selectedFunctionData: selectedFunctionData,
);
}
static Widget _buildTemperatureSelector({
required BuildContext context,
required dynamic initialValue,
required String? currentCondition,
required String selectCode,
AllDevicesModel? device,
required String operationName,
DeviceFunctionData? selectedFunctionData,
bool? removeComparetors,
}) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (removeComparetors != true)
_buildConditionToggle(
context,
currentCondition,
selectCode,
device,
operationName,
selectedFunctionData,
),
const SizedBox(height: 20),
_buildCountDownDisplay(context, initialValue, device, operationName,
selectedFunctionData, selectCode),
const SizedBox(height: 20),
_buildCountDownSlider(context, initialValue, device, operationName,
selectedFunctionData, selectCode),
],
);
}
/// Build condition toggle for AC functions dialog
static Widget _buildConditionToggle(
BuildContext context,
String? currentCondition,
String selectCode,
AllDevicesModel? device,
String operationName,
DeviceFunctionData? selectedFunctionData,
// Function(String) onConditionChanged,
) {
final conditions = ["<", "==", ">"];
return ToggleButtons(
onPressed: (int index) {
context.read<FunctionBloc>().add(
AddFunction(
functionData: DeviceFunctionData(
entityId: device?.uuid ?? '',
functionCode: selectCode,
operationName: operationName,
condition: conditions[index],
value: selectedFunctionData?.value,
valueDescription: selectedFunctionData?.valueDescription,
),
),
);
},
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(),
);
}
/// Build temperature display for AC functions dialog
static Widget _buildCountDownDisplay(
BuildContext context,
dynamic initialValue,
AllDevicesModel? device,
String operationName,
DeviceFunctionData? selectedFunctionData,
String selectCode) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
decoration: BoxDecoration(
color: ColorsManager.primaryColorWithOpacity.withOpacity(0.1),
borderRadius: BorderRadius.circular(10),
),
child: Text(
DurationFormatMixin.formatDuration(initialValue?.toInt() ?? 0),
style: context.textTheme.headlineMedium!.copyWith(
color: ColorsManager.primaryColorWithOpacity,
),
),
);
}
static Widget _buildCountDownSlider(
BuildContext context,
dynamic initialValue,
AllDevicesModel? device,
String operationName,
DeviceFunctionData? selectedFunctionData,
String selectCode,
) {
final operationalValues = SwitchOperationalValue(
icon: '',
description: "sec",
value: 0.0,
minValue: 0,
maxValue: 86400,
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(),
onChanged: (value) {
context.read<FunctionBloc>().add(
AddFunction(
functionData: DeviceFunctionData(
entityId: device?.uuid ?? '',
functionCode: selectCode,
operationName: operationName,
value: value,
condition: selectedFunctionData?.condition,
valueDescription: selectedFunctionData?.valueDescription,
),
),
);
},
);
}
static Widget _buildOperationalValuesList({
required BuildContext context,
required List<SwitchOperationalValue> values,
required dynamic selectedValue,
AllDevicesModel? device,
required String operationName,
required String selectCode,
DeviceFunctionData? selectedFunctionData,
}) {
return ListView.builder(
shrinkWrap: false,
physics: const AlwaysScrollableScrollPhysics(),
itemCount: values.length,
itemBuilder: (context, index) {
final value = values[index];
final isSelected = selectedValue == value.value;
return ListTile(
leading: SvgPicture.asset(
value.icon,
width: 24,
height: 24,
placeholderBuilder: (BuildContext context) => Container(
width: 24,
height: 24,
color: Colors.transparent,
),
),
title: Text(
value.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: () {
if (!isSelected) {
context.read<FunctionBloc>().add(
AddFunction(
functionData: DeviceFunctionData(
entityId: device?.uuid ?? '',
functionCode: selectCode,
operationName: operationName,
value: value.value,
condition: selectedFunctionData?.condition,
valueDescription:
selectedFunctionData?.valueDescription,
),
),
);
}
},
);
},
);
}
}
@@ -0,0 +1,363 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/bloc/switch_tabs/switch_tabs_bloc.dart';
import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routiens/helper/save_routine_helper.dart';
import 'package:syncrow_web/pages/routiens/widgets/routine_dialogs/discard_dialog.dart';
import 'package:syncrow_web/pages/routiens/widgets/routine_dialogs/setting_dialog.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:syncrow_web/utils/style.dart';
class RoutineSearchAndButtons extends StatelessWidget {
const RoutineSearchAndButtons({
super.key,
});
@override
Widget build(BuildContext context) {
return BlocBuilder<RoutineBloc, RoutineState>(
builder: (context, state) {
return LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
return Wrap(
runSpacing: 16,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Expanded(
child: Wrap(
spacing: 12,
runSpacing: 12,
crossAxisAlignment: WrapCrossAlignment.end,
children: [
ConstrainedBox(
constraints: BoxConstraints(
maxWidth:
constraints.maxWidth > 700 ? 450 : constraints.maxWidth - 32),
// child: StatefulTextField(
// title: 'Routine Name',
// initialValue: state.routineName ?? '',
// height: 40,
// // controller: TextEditingController(),
// hintText: 'Please enter the name',
// boxDecoration: containerWhiteDecoration,
// elevation: 0,
// borderRadius: 15,
// isRequired: true,
// width: 450,
// onSubmitted: (value) {
// // context.read<RoutineBloc>().add(SetRoutineName(value));
// },
// onChanged: (value) {
// context.read<RoutineBloc>().add(SetRoutineName(value));
// },
// ),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text('* ',
style: context.textTheme.bodyMedium!
.copyWith(color: ColorsManager.red, fontSize: 13)),
Text(
'Routine Name',
style: context.textTheme.bodyMedium!.copyWith(
fontSize: 13,
fontWeight: FontWeight.w600,
color: ColorsManager.blackColor,
),
),
],
),
Container(
width: 450,
height: 40,
decoration: containerWhiteDecoration,
child: TextFormField(
style: context.textTheme.bodyMedium!
.copyWith(color: ColorsManager.blackColor),
initialValue: state.routineName,
decoration: InputDecoration(
hintText: 'Please enter the name',
hintStyle: context.textTheme.bodyMedium!
.copyWith(fontSize: 12, color: ColorsManager.grayColor),
contentPadding:
const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
border: InputBorder.none,
),
onChanged: (value) {
context.read<RoutineBloc>().add(SetRoutineName(value));
},
validator: (value) {
if (value == null || value.isEmpty) {
return 'This field is required';
}
return null;
},
),
),
],
),
),
(constraints.maxWidth <= 1000)
? const SizedBox()
: SizedBox(
height: 40,
width: 200,
child: Center(
child: DefaultButton(
onPressed: state.isAutomation || state.isTabToRun
? () async {
final result = await SettingHelper.showSettingDialog(
context: context,
);
if (result != null) {
context
.read<RoutineBloc>()
.add(AddSelectedIcon(result));
}
}
: null,
borderRadius: 15,
elevation: 0,
borderColor: ColorsManager.greyColor,
backgroundColor: ColorsManager.boxColor,
child: const Text(
'Settings',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 12,
color: ColorsManager.primaryColor,
),
),
),
),
),
],
),
),
if (constraints.maxWidth > 1000)
Row(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
height: 40,
width: 200,
child: Center(
child: DefaultButton(
onPressed: () {
DiscardDialog.show(context);
},
borderRadius: 15,
elevation: 0,
borderColor: ColorsManager.greyColor,
backgroundColor: ColorsManager.boxColor,
child: const Text(
'Cancel',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 12,
color: ColorsManager.blackColor,
),
),
),
),
),
const SizedBox(width: 12),
SizedBox(
height: 40,
width: 200,
child: Center(
child: DefaultButton(
onPressed: () async {
if (state.routineName == null || state.routineName!.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: const Text('Please enter the routine name'),
duration: const Duration(seconds: 2),
backgroundColor: ColorsManager.red,
action: SnackBarAction(
label: 'Dismiss',
onPressed: () {
// Optional action on Snackbar
},
),
),
);
return;
}
if (state.ifItems.isEmpty || state.thenItems.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: const Text('Please add if and then condition'),
duration: const Duration(seconds: 2),
backgroundColor: ColorsManager.red,
action: SnackBarAction(
label: 'Dismiss',
onPressed: () {
// Optional action on Snackbar
},
),
),
);
return;
}
final result =
await SaveRoutineHelper.showSaveRoutineDialog(context);
if (result != null && result) {
BlocProvider.of<SwitchTabsBloc>(context).add(
const CreateNewRoutineViewEvent(false),
);
BlocProvider.of<SwitchTabsBloc>(context).add(
const TriggerSwitchTabsEvent(true),
);
}
},
borderRadius: 15,
elevation: 0,
backgroundColor: ColorsManager.primaryColor,
child: const Text(
'Save',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 12,
color: ColorsManager.whiteColors,
),
),
),
),
),
],
),
],
),
if (constraints.maxWidth <= 1000)
Wrap(
runSpacing: 12,
children: [
SizedBox(
height: 40,
width: 200,
child: Center(
child: DefaultButton(
onPressed: state.isAutomation || state.isTabToRun
? () async {
final result = await SettingHelper.showSettingDialog(
context: context,
);
if (result != null) {
context.read<RoutineBloc>().add(AddSelectedIcon(result));
}
}
: null,
borderRadius: 15,
elevation: 0,
borderColor: ColorsManager.greyColor,
backgroundColor: ColorsManager.boxColor,
child: const Text(
'Settings',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 12,
color: ColorsManager.primaryColor,
),
),
),
),
),
const SizedBox(width: 12),
SizedBox(
height: 40,
width: 200,
child: Center(
child: DefaultButton(
onPressed: () {
DiscardDialog.show(context);
},
borderRadius: 15,
elevation: 0,
borderColor: ColorsManager.greyColor,
backgroundColor: ColorsManager.boxColor,
child: const Text(
'Cancel',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 12,
color: ColorsManager.blackColor,
),
),
),
),
),
const SizedBox(width: 12),
SizedBox(
height: 40,
width: 200,
child: Center(
child: DefaultButton(
onPressed: () {
if (state.routineName == null || state.routineName!.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: const Text('Please enter the routine name'),
duration: const Duration(seconds: 2),
backgroundColor: ColorsManager.red,
action: SnackBarAction(
label: 'Dismiss',
onPressed: () {
// Optional action on Snackbar
},
),
),
);
return;
}
if (state.ifItems.isEmpty || state.thenItems.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: const Text('Please add if and then condition'),
duration: const Duration(seconds: 2),
backgroundColor: ColorsManager.red,
action: SnackBarAction(
label: 'Dismiss',
onPressed: () {
// Optional action on Snackbar
},
),
),
);
return;
}
SaveRoutineHelper.showSaveRoutineDialog(context);
},
borderRadius: 15,
elevation: 0,
backgroundColor: ColorsManager.primaryColor,
child: const Text(
'Save',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 12,
color: ColorsManager.whiteColors,
),
),
),
),
),
],
),
],
);
},
);
},
);
}
}
@@ -0,0 +1,38 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
class TitleRoutine extends StatelessWidget {
const TitleRoutine({
super.key,
required this.title,
required this.subtitle,
});
final String title;
final String subtitle;
@override
Widget build(BuildContext context) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
title,
style: context.textTheme.titleLarge?.copyWith(
color: ColorsManager.greyColor,
),
),
const SizedBox(
width: 4,
),
Text(
subtitle,
style: context.textTheme.titleLarge?.copyWith(
color: ColorsManager.greyColor,
),
),
],
);
}
}
@@ -0,0 +1,72 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routiens/widgets/dragable_card.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
class ScenesAndAutomations extends StatefulWidget {
const ScenesAndAutomations({
super.key,
});
@override
State<ScenesAndAutomations> createState() => _ScenesAndAutomationsState();
}
class _ScenesAndAutomationsState extends State<ScenesAndAutomations> {
@override
void initState() {
super.initState();
context.read<RoutineBloc>()
..add(const LoadScenes(spaceId, communityId))
..add(const LoadAutomation(spaceId));
}
@override
Widget build(BuildContext context) {
return BlocBuilder<RoutineBloc, RoutineState>(
builder: (context, state) {
if (!state.isLoading) {
var scenes = [...state.scenes, ...state.automations];
return Wrap(
spacing: 10,
runSpacing: 10,
children: scenes.asMap().entries.map((entry) {
final scene = entry.value;
if (state.searchText != null && state.searchText!.isNotEmpty) {
return scene.name
.toLowerCase()
.contains(state.searchText!.toLowerCase())
? DraggableCard(
imagePath: scene.icon ?? Assets.loginLogo,
title: scene.name,
deviceData: {
'deviceId': scene.id,
'name': scene.name,
'status': scene.status,
'type': scene.type,
'icon': scene.icon,
},
)
: Container();
} else {
return DraggableCard(
imagePath: scene.icon ?? Assets.loginLogo,
title: scene.name,
deviceData: {
'deviceId': scene.id,
'name': scene.name,
'status': scene.status,
'type': scene.type,
'icon': scene.icon,
},
);
}
}).toList(),
);
}
return const Center(child: CircularProgressIndicator());
},
);
}
}
@@ -0,0 +1,76 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/common/text_field/custom_text_field.dart';
import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routiens/widgets/routines_title_widget.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
class ConditionTitleAndSearchBar extends StatelessWidget with HelperResponsiveLayout {
const ConditionTitleAndSearchBar({
super.key,
});
@override
Widget build(BuildContext context) {
final isMedium = isMediumScreenSize(context);
final isSmall = isSmallScreenSize(context);
return isMedium || isSmall
? Wrap(
spacing: 10,
runSpacing: 10,
children: [
const TitleRoutine(title: 'Conditions', subtitle: '(IF)'),
StatefulTextField(
title: '',
width: 250,
height: 40,
hintText: 'Search',
elevation: 0,
borderRadius: 15,
icon: Icons.search,
hintColor: ColorsManager.grayColor,
boxDecoration: BoxDecoration(
color: ColorsManager.boxColor,
borderRadius: BorderRadius.circular(15),
),
controller: TextEditingController(),
// onSubmitted: (value) {
// context.read<RoutineBloc>().add(SearchRoutines(value));
// },
onChanged: (value) {
context.read<RoutineBloc>().add(SearchRoutines(value));
},
),
],
)
: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
const TitleRoutine(title: 'Conditions', subtitle: '(IF)'),
StatefulTextField(
title: '',
width: 250,
height: 40,
hintText: 'Search',
elevation: 0,
borderRadius: 15,
icon: Icons.search,
hintColor: ColorsManager.grayColor,
boxDecoration: BoxDecoration(
color: ColorsManager.boxColor,
borderRadius: BorderRadius.circular(15),
),
controller: TextEditingController(),
// onSubmitted: (value) {
// context.read<RoutineBloc>().add(SearchRoutines(value));
// },
onChanged: (value) {
context.read<RoutineBloc>().add(SearchRoutines(value));
},
),
],
);
}
}
@@ -0,0 +1,173 @@
// lib/pages/routiens/widgets/then_container.dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routiens/widgets/routine_dialogs/automation_dialog.dart';
import 'package:syncrow_web/pages/routiens/widgets/routine_dialogs/delay_dialog.dart';
import 'package:syncrow_web/pages/routiens/helper/dialog_helper/device_dialog_helper.dart';
import 'package:syncrow_web/pages/routiens/widgets/dragable_card.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:uuid/uuid.dart';
class ThenContainer extends StatelessWidget {
const ThenContainer({super.key});
@override
Widget build(BuildContext context) {
return BlocBuilder<RoutineBloc, RoutineState>(
builder: (context, state) {
return DragTarget<Map<String, dynamic>>(
builder: (context, candidateData, rejectedData) {
return SingleChildScrollView(
child: Container(
padding: const EdgeInsets.all(16),
width: double.infinity,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('THEN',
style: TextStyle(
fontSize: 18, fontWeight: FontWeight.bold)),
const SizedBox(height: 16),
Wrap(
spacing: 8,
runSpacing: 8,
children: List.generate(
state.thenItems.length,
(index) => GestureDetector(
onTap: () async {
if (state.thenItems[index]['deviceId'] ==
'delay') {
final result = await DelayHelper
.showDelayPickerDialog(
context, state.thenItems[index]);
if (result != null) {
context
.read<RoutineBloc>()
.add(AddToThenContainer({
...state.thenItems[index],
'imagePath': Assets.delay,
'title': 'Delay',
}));
}
return;
}
final result = await DeviceDialogHelper
.showDeviceDialog(
context, state.thenItems[index],
removeComparetors: true);
if (result != null) {
context.read<RoutineBloc>().add(
AddToThenContainer(
state.thenItems[index]));
} else if (!['AC', '1G', '2G', '3G']
.contains(state.thenItems[index]
['productType'])) {
context.read<RoutineBloc>().add(
AddToThenContainer(
state.thenItems[index]));
}
},
child: DraggableCard(
imagePath: state.thenItems[index]
['imagePath'] ??
'',
title:
state.thenItems[index]['title'] ?? '',
deviceData: state.thenItems[index],
padding: const EdgeInsets.symmetric(
horizontal: 4, vertical: 8),
isFromThen: true,
isFromIf: false,
onRemove: () {
context.read<RoutineBloc>().add(
RemoveDragCard(
index: index,
isFromThen: true,
key: state.thenItems[index]
['uniqueCustomId']));
},
),
))),
],
),
),
);
},
onAcceptWithDetails: (data) async {
final uniqueCustomId = const Uuid().v4();
final mutableData = Map<String, dynamic>.from(data.data);
mutableData['uniqueCustomId'] = uniqueCustomId;
if (mutableData['type'] == 'scene') {
context.read<RoutineBloc>().add(AddToThenContainer(mutableData));
return;
}
if (mutableData['type'] == 'automation') {
final result = await showDialog<bool>(
context: context,
builder: (BuildContext context) => AutomationDialog(
automationName: mutableData['name'] ?? 'Automation',
automationId: mutableData['deviceId'] ?? '',
uniqueCustomId: uniqueCustomId,
),
);
if (result != null) {
context.read<RoutineBloc>().add(AddToThenContainer({
...mutableData,
'imagePath': Assets.automation,
'title': mutableData['name'],
}));
}
return;
}
if (mutableData['type'] == 'tap_to_run' && state.isAutomation) {
context.read<RoutineBloc>().add(AddToThenContainer({
...mutableData,
'imagePath': Assets.logo,
'title': mutableData['name'],
}));
return;
}
if (mutableData['type'] == 'tap_to_run' && !state.isAutomation) {
return;
}
if (mutableData['deviceId'] == 'delay') {
final result =
await DelayHelper.showDelayPickerDialog(context, mutableData);
if (result != null) {
context.read<RoutineBloc>().add(AddToThenContainer({
...mutableData,
'imagePath': Assets.delay,
'title': 'Delay',
}));
}
return;
}
final result = await DeviceDialogHelper.showDeviceDialog(
context, mutableData,
removeComparetors: true);
if (result != null) {
context.read<RoutineBloc>().add(AddToThenContainer(mutableData));
} else if (!['AC', '1G', '2G', '3G']
.contains(mutableData['productType'])) {
context.read<RoutineBloc>().add(AddToThenContainer(mutableData));
}
},
);
},
);
}
}
@@ -2,8 +2,7 @@ import 'package:flutter/material.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class CreateCommunityDialog extends StatefulWidget {
final Function(String name, String description)
onCreateCommunity;
final Function(String name, String description) onCreateCommunity;
const CreateCommunityDialog({super.key, required this.onCreateCommunity});
@@ -65,23 +64,20 @@ class CreateCommunityDialogState extends State<CreateCommunityDialog> {
fillColor: ColorsManager.boxColor,
hintStyle: const TextStyle(
fontSize: 14,
color: ColorsManager.boxColor,
color: ColorsManager.grayBorder,
fontWeight: FontWeight.w400,
),
border: OutlineInputBorder(
borderSide:
const BorderSide(color: ColorsManager.boxColor, width: 1),
borderSide: const BorderSide(color: ColorsManager.boxColor, width: 1),
borderRadius: BorderRadius.circular(10),
),
enabledBorder: OutlineInputBorder(
borderSide:
const BorderSide(color: ColorsManager.boxColor, width: 1),
borderSide: const BorderSide(color: ColorsManager.boxColor, width: 1),
borderRadius: BorderRadius.circular(10),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide:
const BorderSide(color: ColorsManager.boxColor, width: 1),
borderSide: const BorderSide(color: ColorsManager.boxColor, width: 1),
),
),
),
+250
View File
@@ -0,0 +1,250 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/routiens/models/create_scene_and_autoamtion/create_automation_model.dart';
import 'package:syncrow_web/pages/routiens/models/create_scene_and_autoamtion/create_scene_model.dart';
import 'package:syncrow_web/pages/routiens/models/icon_model.dart';
import 'package:syncrow_web/pages/routiens/models/routine_details_model.dart';
import 'package:syncrow_web/pages/routiens/models/routine_model.dart';
import 'package:syncrow_web/services/api/http_service.dart';
import 'package:syncrow_web/utils/constants/api_const.dart';
class SceneApi {
static final HTTPService _httpService = HTTPService();
// //create scene
static Future<Map<String, dynamic>> createScene(
CreateSceneModel createSceneModel) async {
try {
debugPrint('create scene model: ${createSceneModel.toMap()}');
final response = await _httpService.post(
path: ApiEndpoints.createScene,
body: createSceneModel.toMap(),
showServerMessage: false,
expectedResponseModel: (json) {
return json;
},
);
debugPrint('create scene response: $response');
return response;
} catch (e) {
debugPrint(e.toString());
rethrow;
}
}
//
// create automation
static Future<Map<String, dynamic>> createAutomation(
CreateAutomationModel createAutomationModel) async {
try {
debugPrint("automation body ${createAutomationModel.toMap()}");
final response = await _httpService.post(
path: ApiEndpoints.createAutomation,
body: createAutomationModel.toMap(),
showServerMessage: false,
expectedResponseModel: (json) {
return json;
},
);
debugPrint('create automation response: $response');
return response;
} catch (e) {
debugPrint(e.toString());
rethrow;
}
}
static Future<List<IconModel>> getIcon() async {
final response = await _httpService.get(
path: ApiEndpoints.getIconScene,
showServerMessage: false,
expectedResponseModel: (json) {
List<IconModel> iconsList = [];
json.forEach((element) {
iconsList.add(IconModel.fromJson(element));
});
return iconsList;
},
);
return response;
}
//get scenes by community id and space id
static Future<List<ScenesModel>> getScenesByUnitId(
String unitId, String communityId,
{showInDevice = false}) async {
try {
final response = await _httpService.get(
path: ApiEndpoints.getUnitScenes
.replaceAll('{spaceUuid}', unitId)
.replaceAll('{communityUuid}', communityId),
queryParameters: {'showInHomePage': showInDevice},
showServerMessage: false,
expectedResponseModel: (json) {
final scenesJson = json['data'] as List;
List<ScenesModel> scenes = [];
for (var scene in scenesJson) {
scenes.add(ScenesModel.fromJson(scene, isAutomation: false));
}
return scenes;
},
);
return response;
} catch (e) {
rethrow;
}
}
//getAutomation
static Future<List<ScenesModel>> getAutomationByUnitId(String unitId) async {
try {
final response = await _httpService.get(
path: ApiEndpoints.getSpaceAutomation.replaceAll('{unitUuid}', unitId),
showServerMessage: false,
expectedResponseModel: (json) {
List<ScenesModel> scenes = [];
for (var scene in json) {
scenes.add(ScenesModel.fromJson(scene, isAutomation: true));
}
return scenes;
},
);
return response;
} catch (e) {
rethrow;
}
}
// static Future<bool> triggerScene(String sceneId) async {
// try {
// final response = await _httpService.post(
// path: ApiEndpoints.triggerScene.replaceAll('{sceneId}', sceneId),
// showServerMessage: false,
// expectedResponseModel: (json) => json['success'],
// );
// return response;
// } catch (e) {
// rethrow;
// }
// }
//automation details
static Future<RoutineDetailsModel> getAutomationDetails(
String automationId) async {
try {
final response = await _httpService.get(
path: ApiEndpoints.getAutomationDetails
.replaceAll('{automationId}', automationId),
showServerMessage: false,
expectedResponseModel: (json) => RoutineDetailsModel.fromJson(json),
);
return response;
} catch (e) {
rethrow;
}
}
//
// //updateAutomationStatus
// static Future<bool> updateAutomationStatus(String automationId,
// AutomationStatusUpdate createAutomationEnable) async {
// try {
// final response = await _httpService.put(
// path: ApiEndpoints.updateAutomationStatus
// .replaceAll('{automationId}', automationId),
// body: createAutomationEnable.toMap(),
// expectedResponseModel: (json) => json['success'],
// );
// return response;
// } catch (e) {
// rethrow;
// }
// }
//getScene
static Future<RoutineDetailsModel> getSceneDetails(String sceneId) async {
try {
final response = await _httpService.get(
path: ApiEndpoints.getScene.replaceAll('{sceneId}', sceneId),
showServerMessage: false,
expectedResponseModel: (json) => RoutineDetailsModel.fromJson(json),
);
return response;
} catch (e) {
rethrow;
}
}
//
// //update Scene
// static updateScene(CreateSceneModel createSceneModel, String sceneId) async {
// try {
// final response = await _httpService.put(
// path: ApiEndpoints.updateScene.replaceAll('{sceneId}', sceneId),
// body: createSceneModel
// .toJson(sceneId.isNotEmpty == true ? sceneId : null),
// expectedResponseModel: (json) {
// return json;
// },
// );
// return response;
// } catch (e) {
// rethrow;
// }
// }
//
// //update automation
// static updateAutomation(
// CreateAutomationModel createAutomationModel, String automationId) async {
// try {
// final response = await _httpService.put(
// path: ApiEndpoints.updateAutomation
// .replaceAll('{automationId}', automationId),
// body: createAutomationModel
// .toJson(automationId.isNotEmpty == true ? automationId : null),
// expectedResponseModel: (json) {
// return json;
// },
// );
// return response;
// } catch (e) {
// rethrow;
// }
// }
//
//delete Scene
static Future<bool> deleteScene(
{required String unitUuid, required String sceneId}) async {
try {
final response = await _httpService.delete(
path: ApiEndpoints.deleteScene
.replaceAll('{sceneId}', sceneId)
.replaceAll('{unitUuid}', unitUuid),
showServerMessage: false,
expectedResponseModel: (json) => json['statusCode'] == 200,
);
return response;
} catch (e) {
rethrow;
}
}
// delete automation
static Future<bool> deleteAutomation(
{required String unitUuid, required String automationId}) async {
try {
final response = await _httpService.delete(
path: ApiEndpoints.deleteAutomation
.replaceAll('{automationId}', automationId)
.replaceAll('{unitUuid}', unitUuid),
showServerMessage: false,
expectedResponseModel: (json) => json['statusCode'] == 200,
);
return response;
} catch (e) {
rethrow;
}
}
}
+1 -1
View File
@@ -53,6 +53,6 @@ abstract class ColorsManager {
static const Color neutralGray = Color(0xFFE5E5E5);
static const Color warningRed = Color(0xFFFF6465);
static const Color borderColor = Color(0xFFE5E5E5);
static const Color CircleImageBackground = Color(0xFFF4F4F4);
}
//background: #background: #5D5D5D;
+29 -36
View File
@@ -11,14 +11,12 @@ abstract class ApiEndpoints {
static const String visitorPassword = '/visitor-password';
static const String getDevices = '/visitor-password/devices';
static const String sendOnlineOneTime =
'/visitor-password/temporary-password/online/one-time';
static const String sendOnlineOneTime = '/visitor-password/temporary-password/online/one-time';
static const String sendOnlineMultipleTime =
'/visitor-password/temporary-password/online/multiple-time';
//offline Password
static const String sendOffLineOneTime =
'/visitor-password/temporary-password/offline/one-time';
static const String sendOffLineOneTime = '/visitor-password/temporary-password/offline/one-time';
static const String sendOffLineMultipleTime =
'/visitor-password/temporary-password/offline/multiple-time';
@@ -35,50 +33,45 @@ abstract class ApiEndpoints {
static const String gatewayApi = '/device/gateway/{gatewayUuid}/devices';
static const String openDoorLock = '/door-lock/open/{doorLockUuid}';
static const String getDeviceLogs =
'/device/report-logs/{uuid}?code={code}';
static const String getDeviceLogs = '/device/report-logs/{uuid}?code={code}';
// Space Module
static const String createSpace =
'/communities/{communityId}/spaces';
static const String listSpaces =
'/communities/{communityId}/spaces';
static const String deleteSpace =
'/communities/{communityId}/spaces/{spaceId}';
static const String updateSpace =
'/communities/{communityId}/spaces/{spaceId}';
static const String getSpace =
'/communities/{communityId}/spaces/{spaceId}';
static const String getSpaceHierarchy =
'/communities/{communityId}/spaces';
static const String createSpace = '/communities/{communityId}/spaces';
static const String listSpaces = '/communities/{communityId}/spaces';
static const String deleteSpace = '/communities/{communityId}/spaces/{spaceId}';
static const String updateSpace = '/communities/{communityId}/spaces/{spaceId}';
static const String getSpace = '/communities/{communityId}/spaces/{spaceId}';
static const String getSpaceHierarchy = '/communities/{communityId}/spaces';
// Community Module
static const String createCommunity = '/communities';
static const String getCommunityList = '/communities';
static const String getCommunityById =
'/communities/{communityId}';
static const String updateCommunity =
'/communities/{communityId}';
static const String deleteCommunity =
'/communities/{communityId}';
static const String getUserCommunities =
'/communities/user/{userUuid}';
static const String getCommunityById = '/communities/{communityId}';
static const String updateCommunity = '/communities/{communityId}';
static const String deleteCommunity = '/communities/{communityId}';
static const String getUserCommunities = '/communities/user/{userUuid}';
static const String createUserCommunity = '/communities/user';
static const String getDeviceLogsByDate =
'/device/report-logs/{uuid}?code={code}&startTime={startTime}&endTime={endTime}';
static const String scheduleByDeviceId = '/schedule/{deviceUuid}';
static const String getScheduleByDeviceId =
'/schedule/{deviceUuid}?category={category}';
static const String deleteScheduleByDeviceId =
'/schedule/{deviceUuid}/{scheduleUuid}';
static const String updateScheduleByDeviceId =
'/schedule/enable/{deviceUuid}';
static const String getScheduleByDeviceId = '/schedule/{deviceUuid}?category={category}';
static const String deleteScheduleByDeviceId = '/schedule/{deviceUuid}/{scheduleUuid}';
static const String updateScheduleByDeviceId = '/schedule/enable/{deviceUuid}';
static const String factoryReset = '/device/factory/reset/{deviceUuid}';
static const String powerClamp =
'/device/{powerClampUuid}/power-clamp/status';
static const String powerClamp = '/device/{powerClampUuid}/power-clamp/status';
//product
//product
static const String listProducts = '/products';
static const String getSpaceScenes = '/scene/tap-to-run/{unitUuid}';
static const String getSpaceAutomation = '/automation/{unitUuid}';
static const String getIconScene = '/scene/icon';
static const String createScene = '/scene/tap-to-run';
static const String createAutomation = '/automation';
static const String getUnitScenes = '/communities/{communityUuid}/spaces/{spaceUuid}/scenes';
static const String getAutomationDetails = '/automation/details/{automationId}';
static const String getScene = '/scene/tap-to-run/{sceneId}';
static const String deleteScene = '/scene/tap-to-run/{sceneId}';
static const String deleteAutomation = '/automation/{automationId}';
}
+14
View File
@@ -97,3 +97,17 @@ extension AccessStatusExtension on AccessStatus {
enum TempModes { hot, cold, wind }
enum FanSpeeds { auto, low, middle, high }
enum AcValuesEnums {
Cooling,
Heating,
Ventilation,
}
enum EnumEffectivePeriodOptions {
allDay,
daytime,
night,
custom,
none,
}
+90
View File
@@ -237,4 +237,94 @@ class Assets {
static const String delete = 'assets/icons/delete.svg';
static const String edit = 'assets/icons/edit.svg';
//assets/icons/routine/tab_to_run.svg
static const String tabToRun = 'assets/icons/routine/tab_to_run.svg';
//assets/icons/routine/schedule.svg
static const String schedule = 'assets/icons/routine/schedule.svg';
//assets/icons/routine/map.svg
static const String map = 'assets/icons/routine/map.svg';
//assets/icons/routine/weather.svg
static const String weather = 'assets/icons/routine/weather.svg';
//assets/icons/routine/notification.svg
static const String notification = 'assets/icons/routine/notification.svg';
//assets/icons/routine/delay.svg
static const String delay = 'assets/icons/routine/delay.svg';
// Assets for functions_icons
static const String assetsSensitivityFunction = "assets/icons/functions_icons/sensitivity.svg";
static const String assetsSensitivityOperationIcon =
"assets/icons/functions_icons/sesitivity_operation_icon.svg";
static const String assetsAcPower = "assets/icons/functions_icons/ac_power.svg";
static const String assetsAcPowerOFF = "assets/icons/functions_icons/ac_power_off.svg";
static const String assetsChildLock = "assets/icons/functions_icons/child_lock.svg";
static const String assetsFreezing = "assets/icons/functions_icons/freezing.svg";
static const String assetsFanSpeed = "assets/icons/functions_icons/fan_speed.svg";
static const String assetsAcCooling = "assets/icons/functions_icons/ac_cooling.svg";
static const String assetsAcHeating = "assets/icons/functions_icons/ac_heating.svg";
static const String assetsCelsiusDegrees = "assets/icons/functions_icons/celsius_degrees.svg";
static const String assetsTempreture = "assets/icons/functions_icons/tempreture.svg";
static const String assetsAcFanLow = "assets/icons/functions_icons/ac_fan_low.svg";
static const String assetsAcFanMiddle = "assets/icons/functions_icons/ac_fan_middle.svg";
static const String assetsAcFanHigh = "assets/icons/functions_icons/ac_fan_high.svg";
static const String assetsAcFanAuto = "assets/icons/functions_icons/ac_fan_auto.svg";
static const String assetsSceneChildLock = "assets/icons/functions_icons/scene_child_lock.svg";
static const String assetsSceneChildUnlock =
"assets/icons/functions_icons/scene_child_unlock.svg";
static const String assetsSceneRefresh = "assets/icons/functions_icons/scene_refresh.svg";
static const String assetsLightCountdown = "assets/icons/functions_icons/light_countdown.svg";
static const String assetsFarDetection = "assets/icons/functions_icons/far_detection.svg";
static const String assetsFarDetectionFunction =
"assets/icons/functions_icons/far_detection_function.svg";
static const String assetsIndicator = "assets/icons/functions_icons/indicator.svg";
static const String assetsMotionDetection = "assets/icons/functions_icons/motion_detection.svg";
static const String assetsMotionlessDetection =
"assets/icons/functions_icons/motionless_detection.svg";
static const String assetsNobodyTime = "assets/icons/functions_icons/nobody_time.svg";
static const String assetsFactoryReset = "assets/icons/functions_icons/factory_reset.svg";
static const String assetsMasterState = "assets/icons/functions_icons/master_state.svg";
static const String assetsSwitchAlarmSound =
"assets/icons/functions_icons/switch_alarm_sound.svg";
static const String assetsResetOff = "assets/icons/functions_icons/reset_off.svg";
// Assets for automation_functions
static const String assetsCardUnlock =
"assets/icons/functions_icons/automation_functions/card_unlock.svg";
static const String assetsDoorbell =
"assets/icons/functions_icons/automation_functions/doorbell.svg";
static const String assetsDoorlockNormalOpen =
"assets/icons/functions_icons/automation_functions/doorlock_normal_open.svg";
static const String assetsDoubleLock =
"assets/icons/functions_icons/automation_functions/double_lock.svg";
static const String assetsFingerprintUnlock =
"assets/icons/functions_icons/automation_functions/fingerprint_unlock.svg";
static const String assetsHijackAlarm =
"assets/icons/functions_icons/automation_functions/hijack_alarm.svg";
static const String assetsLockAlarm =
"assets/icons/functions_icons/automation_functions/lock_alarm.svg";
static const String assetsPasswordUnlock =
"assets/icons/functions_icons/automation_functions/password_unlock.svg";
static const String assetsRemoteUnlockReq =
"assets/icons/functions_icons/automation_functions/remote_unlock_req.svg";
static const String assetsRemoteUnlockViaApp =
"assets/icons/functions_icons/automation_functions/remote_unlock_via_app.svg";
static const String assetsResidualElectricity =
"assets/icons/functions_icons/automation_functions/residual_electricity.svg";
static const String assetsTempPasswordUnlock =
"assets/icons/functions_icons/automation_functions/temp_password_unlock.svg";
static const String assetsSelfTestResult =
"assets/icons/functions_icons/automation_functions/self_test_result.svg";
static const String assetsPresence =
"assets/icons/functions_icons/automation_functions/presence.svg";
static const String assetsMotion = "assets/icons/functions_icons/automation_functions/motion.svg";
static const String assetsCurrentTemp =
"assets/icons/functions_icons/automation_functions/current_temp.svg";
static const String assetsPresenceState =
"assets/icons/functions_icons/automation_functions/presence_state.svg";
//assets/icons/routine/automation.svg
static const String automation = 'assets/icons/routine/automation.svg';
}
+5 -1
View File
@@ -11,9 +11,13 @@ enum DeviceType {
CeilingSensor,
WallSensor,
WH,
DoorSensor,
DS,
OneTouch,
TowTouch,
ThreeTouch,
GarageDoor,
WaterLeak,
DoorSensor,
Other,
}
/*
+16 -9
View File
@@ -23,6 +23,11 @@ extension BuildContextExt on BuildContext {
VoidCallback? onDismiss,
bool? hideConfirmButton,
final double? dialogWidth,
TextStyle? titleStyle,
String? onDismissText,
String? onConfirmText,
Color? onDismissColor,
Color? onConfirmColor,
}) {
showDialog(
context: this,
@@ -42,10 +47,11 @@ extension BuildContextExt on BuildContext {
/// header widget
Text(
title,
style: context.textTheme.bodyMedium!.copyWith(
color: ColorsManager.primaryColorWithOpacity,
fontWeight: FontWeight.bold,
),
style: titleStyle ??
context.textTheme.bodyMedium!.copyWith(
color: ColorsManager.primaryColorWithOpacity,
fontWeight: FontWeight.bold,
),
),
Padding(
padding: const EdgeInsets.symmetric(
@@ -79,9 +85,10 @@ extension BuildContextExt on BuildContext {
},
child: Center(
child: Text(
'Cancel',
style: context.textTheme.bodyMedium!
.copyWith(color: ColorsManager.greyColor),
onDismissText ?? 'Cancel',
style: context.textTheme.bodyMedium!.copyWith(
color: onDismissColor ??
ColorsManager.greyColor),
),
),
),
@@ -94,9 +101,9 @@ extension BuildContextExt on BuildContext {
onTap: onConfirm,
child: Center(
child: Text(
'Confirm',
onConfirmText ?? 'Confirm',
style: context.textTheme.bodyMedium!.copyWith(
color:
color: onConfirmColor ??
ColorsManager.primaryColorWithOpacity),
),
),
+19 -13
View File
@@ -1,8 +1,8 @@
import 'package:flutter/material.dart';
import 'color_manager.dart';
InputDecoration? textBoxDecoration({bool suffixIcon = false}) =>
InputDecoration(
InputDecoration? textBoxDecoration({bool suffixIcon = false}) => InputDecoration(
focusColor: ColorsManager.grayColor,
suffixIcon: suffixIcon ? const Icon(Icons.search) : null,
hintText: 'Search',
@@ -30,17 +30,23 @@ InputDecoration? textBoxDecoration({bool suffixIcon = false}) =>
),
);
BoxDecoration containerDecoration = BoxDecoration(
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.3),
spreadRadius: 2,
blurRadius: 4,
offset: const Offset(0, 5), // changes position of shadow
),
],
color: ColorsManager.boxColor,
borderRadius: const BorderRadius.all(Radius.circular(10)));
BoxDecoration containerDecoration = BoxDecoration(boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.3),
spreadRadius: 2,
blurRadius: 4,
offset: const Offset(0, 5), // changes position of shadow
),
], color: ColorsManager.boxColor, borderRadius: const BorderRadius.all(Radius.circular(10)));
BoxDecoration containerWhiteDecoration = BoxDecoration(boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.3),
spreadRadius: 2,
blurRadius: 4,
offset: const Offset(0, 5), // changes position of shadow
),
], color: ColorsManager.whiteColors, borderRadius: const BorderRadius.all(Radius.circular(15)));
BoxDecoration subSectionContainerDecoration = BoxDecoration(
color: ColorsManager.whiteColors,