mirror of
https://github.com/SyncrowIOT/web.git
synced 2026-10-24 17:27:55 +00:00
Merged with dev
This commit is contained in:
+9
-3
@@ -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,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,
|
||||
),
|
||||
];
|
||||
}
|
||||
@@ -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),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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}';
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
}
|
||||
|
||||
@@ -11,9 +11,13 @@ enum DeviceType {
|
||||
CeilingSensor,
|
||||
WallSensor,
|
||||
WH,
|
||||
DoorSensor,
|
||||
DS,
|
||||
OneTouch,
|
||||
TowTouch,
|
||||
ThreeTouch,
|
||||
GarageDoor,
|
||||
WaterLeak,
|
||||
DoorSensor,
|
||||
Other,
|
||||
}
|
||||
/*
|
||||
|
||||
@@ -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
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user