one touch

This commit is contained in:
mohammad
2024-09-29 16:06:05 +03:00
parent e1cd9e55c2
commit c4ff19d471
11 changed files with 1467 additions and 4 deletions

View File

@ -0,0 +1,503 @@
import 'dart:async';
import 'package:dio/dio.dart';
import 'package:firebase_database/firebase_database.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_app/features/app_layout/bloc/home_cubit.dart';
import 'package:syncrow_app/features/devices/bloc/one_touch_bloc/one_touch_state.dart';
import 'package:syncrow_app/features/devices/bloc/one_touch_bloc/one_touch_event.dart';
import 'package:syncrow_app/features/devices/model/device_control_model.dart';
import 'package:syncrow_app/features/devices/model/device_model.dart';
import 'package:syncrow_app/features/devices/model/group_one_touch_model.dart';
import 'package:syncrow_app/features/devices/model/one_touch_model.dart';
import 'package:syncrow_app/features/devices/model/schedule_model.dart';
import 'package:syncrow_app/features/devices/model/status_model.dart';
import 'package:syncrow_app/services/api/devices_api.dart';
import 'package:syncrow_app/utils/helpers/snack_bar.dart';
class OneTouchBloc extends Bloc<OneTouchEvent, OneTouchState> {
final String oneTouchId;
final String switchCode;
OneTouchModel deviceStatus = OneTouchModel(
firstSwitch: false,
firstCountDown: 0,
);
Timer? _timer;
bool oneTouchGroup = false;
List<DeviceModel> devicesList = [];
OneTouchBloc({required this.oneTouchId, required this.switchCode})
: super(InitialState()) {
on<InitialEvent>(_fetchOneTouchStatus);
on<OneTouchUpdated>(_oneTouchUpdated);
on<ChangeFirstSwitchStatusEvent>(_changeFirstSwitch);
on<ChangeSlidingSegment>(_changeSliding);
on<SetCounterValue>(_setCounterValue);
on<GetCounterEvent>(_getCounterValue);
on<TickTimer>(_onTickTimer);
on<OnClose>(_onClose);
on<ToggleDaySelectionEvent>(toggleDaySelection);
on<ScheduleSave>(saveSchedule);
on<GetScheduleEvent>(getSchedule);
on<ToggleScheduleEvent>(toggleChange);
on<DeleteScheduleEvent>(deleteSchedule);
on<ToggleSelectedEvent>(toggleSelectedIndex);
on<ToggleCreateScheduleEvent>(toggleCreateSchedule);
on<InitialWizardEvent>(_fetchOneTouchWizardStatus);
on<ChangeFirstWizardSwitchStatusEvent>(_changeFirstWizardSwitch);
on<GroupAllOnEvent>(_groupAllOn);
on<GroupAllOffEvent>(_groupAllOff);
}
void _fetchOneTouchStatus(
InitialEvent event, Emitter<OneTouchState> emit) async {
emit(LoadingInitialState());
try {
var response = await DevicesAPI.getDeviceStatus(oneTouchId);
List<StatusModel> statusModelList = [];
for (var status in response['status']) {
statusModelList.add(StatusModel.fromJson(status));
}
deviceStatus = OneTouchModel.fromJson(statusModelList);
emit(UpdateState(oneTouchModel: deviceStatus));
_listenToChanges();
} catch (e) {
emit(FailedState(error: e.toString()));
return;
}
}
_listenToChanges() {
try {
DatabaseReference ref =
FirebaseDatabase.instance.ref('device-status/$oneTouchId');
Stream<DatabaseEvent> stream = ref.onValue;
stream.listen((DatabaseEvent event) async {
if (_timer != null) {
await Future.delayed(const Duration(seconds: 2));
}
Map<dynamic, dynamic> usersMap =
event.snapshot.value as Map<dynamic, dynamic>;
List<StatusModel> statusList = [];
usersMap['status'].forEach((element) {
statusList
.add(StatusModel(code: element['code'], value: element['value']));
});
deviceStatus = OneTouchModel.fromJson(statusList);
if (!isClosed) {
add(OneTouchUpdated());
}
});
} catch (_) {}
}
_oneTouchUpdated(OneTouchUpdated event, Emitter<OneTouchState> emit) {
emit(UpdateState(oneTouchModel: deviceStatus));
}
void _changeFirstSwitch(
ChangeFirstSwitchStatusEvent event, Emitter<OneTouchState> emit) async {
emit(LoadingNewSate(oneTouchModel: deviceStatus));
try {
deviceStatus.firstSwitch = !event.value;
emit(UpdateState(oneTouchModel: deviceStatus));
if (_timer != null) {
_timer!.cancel();
}
_timer = Timer(const Duration(milliseconds: 500), () async {
final response = await DevicesAPI.controlDevice(
DeviceControlModel(
deviceId: oneTouchGroup ? event.deviceId : oneTouchId,
code: 'switch_1',
value: !event.value),
oneTouchGroup ? event.deviceId : oneTouchId);
if (!response['success']) {
add(InitialEvent(groupScreen: oneTouchGroup));
}
});
} catch (_) {
add(InitialEvent(groupScreen: oneTouchGroup));
}
}
void _changeSliding(
ChangeSlidingSegment event, Emitter<OneTouchState> emit) async {
emit(ChangeSlidingSegmentState(value: event.value));
}
void _setCounterValue(
SetCounterValue event, Emitter<OneTouchState> emit) async {
emit(LoadingNewSate(oneTouchModel: deviceStatus));
int seconds = 0;
try {
seconds = event.duration.inSeconds;
final response = await DevicesAPI.controlDevice(
DeviceControlModel(
deviceId: oneTouchId, code: event.deviceCode, value: seconds),
oneTouchId);
if (response['success'] ?? false) {
if (event.deviceCode == 'countdown_1') {
deviceStatus.firstCountDown = seconds;
}
} else {
emit(const FailedState(error: 'Something went wrong'));
return;
}
} catch (e) {
emit(FailedState(error: e.toString()));
return;
}
if (seconds > 0) {
_onStartTimer(seconds);
} else {
_timer?.cancel();
emit(TimerRunComplete());
}
}
void _getCounterValue(
GetCounterEvent event, Emitter<OneTouchState> emit) async {
emit(LoadingInitialState());
try {
var response = await DevicesAPI.getDeviceStatus(oneTouchId);
List<StatusModel> statusModelList = [];
for (var status in response['status']) {
statusModelList.add(StatusModel.fromJson(status));
}
deviceStatus = OneTouchModel.fromJson(statusModelList);
if (event.deviceCode == 'countdown_1') {
deviceStatus.firstCountDown > 0
? _onStartTimer(deviceStatus.firstCountDown)
: emit(UpdateTimerState(seconds: deviceStatus.firstCountDown));
}
} catch (e) {
emit(FailedState(error: e.toString()));
return;
}
}
void _onClose(OnClose event, Emitter<OneTouchState> emit) {
_timer?.cancel();
}
void _onStartTimer(int seconds) {
_timer?.cancel();
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
add(TickTimer(seconds - timer.tick));
});
}
void _onTickTimer(TickTimer event, Emitter<OneTouchState> emit) {
if (event.remainingTime > 0) {
emit(TimerRunInProgress(event.remainingTime));
} else {
_timer?.cancel();
emit(TimerRunComplete());
}
}
List<Map<String, String>> days = [
{"day": "Sun", "key": "Sun"},
{"day": "Mon", "key": "Mon"},
{"day": "Tue", "key": "Tue"},
{"day": "Wed", "key": "Wed"},
{"day": "Thu", "key": "Thu"},
{"day": "Fri", "key": "Fri"},
{"day": "Sat", "key": "Sat"},
];
Future<void> saveSchedule(
ScheduleSave event,
Emitter<OneTouchState> emit,
) async {
try {
if (selectedDays.isNotEmpty) {
emit(LoadingInitialState());
await DevicesAPI.postSchedule(
category: switchCode,
deviceId: oneTouchId,
time: getTimeStampWithoutSeconds(selectedTime).toString(),
code: switchCode,
value: toggleSchedule,
days: selectedDays);
CustomSnackBar.displaySnackBar('Save Successfully');
add(GetScheduleEvent());
emit(SaveSchedule());
add(const ToggleCreateScheduleEvent(index: 1));
} else {
CustomSnackBar.displaySnackBar('Please select days');
}
} catch (e) {
emit(FailedState(error: e.toString()));
}
}
Future<void> getSchedule(
GetScheduleEvent event,
Emitter<OneTouchState> emit,
) async {
try {
emit(LoadingInitialState());
final response = await DevicesAPI.getSchedule(
category: switchCode,
deviceId: oneTouchId,
);
List<dynamic> jsonData = response;
listSchedule =
jsonData.map((item) => ScheduleModel.fromJson(item)).toList();
emit(InitialState());
} on DioException catch (e) {
final errorData = e.response!.data;
String errorMessage = errorData['message'];
emit(FailedState(error: errorMessage.toString()));
}
}
int? getTimeStampWithoutSeconds(DateTime? dateTime) {
if (dateTime == null) return null;
DateTime dateTimeWithoutSeconds = DateTime(dateTime.year, dateTime.month,
dateTime.day, dateTime.hour, dateTime.minute);
return dateTimeWithoutSeconds.millisecondsSinceEpoch ~/ 1000;
}
Future toggleChange(
ToggleScheduleEvent event, Emitter<OneTouchState> emit) async {
try {
emit(LoadingInitialState());
final response = await DevicesAPI.changeSchedule(
scheduleId: event.id, deviceUuid: oneTouchId, enable: event.toggle);
if (response == true) {
add(GetScheduleEvent());
toggleSchedule = event.toggle;
return toggleSchedule;
}
emit(IsToggleState(onOff: toggleSchedule));
add(const ChangeSlidingSegment(value: 1));
} on DioException catch (e) {
final errorData = e.response!.data;
String errorMessage = errorData['message'];
emit(FailedState(error: errorMessage.toString()));
}
}
Future deleteSchedule(
DeleteScheduleEvent event, Emitter<OneTouchState> emit) async {
try {
emit(LoadingInitialState());
final response = await DevicesAPI.deleteSchedule(
scheduleId: event.id,
deviceUuid: oneTouchId,
);
if (response == true) {
add(GetScheduleEvent());
return toggleSchedule;
}
emit(IsToggleState(onOff: toggleSchedule));
add(const ChangeSlidingSegment(value: 1));
} on DioException catch (e) {
final errorData = e.response!.data;
String errorMessage = errorData['message'];
emit(FailedState(error: errorMessage.toString()));
}
}
void toggleCreateSchedule(
ToggleCreateScheduleEvent event, Emitter<OneTouchState> emit) {
emit(LoadingInitialState());
createSchedule = !createSchedule;
selectedDays.clear();
selectedTime = DateTime.now();
emit(UpdateCreateScheduleState(createSchedule));
}
bool toggleSchedule = true;
List<String> selectedDays = [];
bool createSchedule = false;
List<ScheduleModel> listSchedule = [];
DateTime? selectedTime = DateTime.now();
Future<void> toggleDaySelection(
ToggleDaySelectionEvent event,
Emitter<OneTouchState> emit,
) async {
emit(LoadingInitialState());
if (selectedDays.contains(event.key)) {
selectedDays.remove(event.key);
} else {
selectedDays.add(event.key);
}
emit(ChangeTimeState());
add(ChangeSlidingSegment(value: 1));
}
int selectedTabIndex = 0;
void toggleSelectedIndex(
ToggleSelectedEvent event, Emitter<OneTouchState> emit) {
emit(LoadingInitialState());
selectedTabIndex = event.index;
emit(ChangeSlidingSegmentState(value: selectedTabIndex));
}
List<GroupOneTouchModel> groupOneTouchList = [];
bool allSwitchesOn = true;
void _fetchOneTouchWizardStatus(
InitialWizardEvent event, Emitter<OneTouchState> emit) async {
emit(LoadingInitialState());
try {
devicesList = [];
groupOneTouchList = [];
allSwitchesOn = true;
devicesList = await DevicesAPI.getDeviceByGroupName(
HomeCubit.getInstance().selectedSpace?.id ?? '', '1GT');
for (int i = 0; i < devicesList.length; i++) {
var response =
await DevicesAPI.getDeviceStatus(devicesList[i].uuid ?? '');
List<StatusModel> statusModelList = [];
for (var status in response['status']) {
statusModelList.add(StatusModel.fromJson(status));
}
deviceStatus = OneTouchModel.fromJson(statusModelList);
groupOneTouchList.add(GroupOneTouchModel(
deviceId: devicesList[i].uuid ?? '',
deviceName: devicesList[i].name ?? '',
firstSwitch: deviceStatus.firstSwitch,
));
}
if (groupOneTouchList.isNotEmpty) {
groupOneTouchList.firstWhere((element) {
if (!element.firstSwitch) {
allSwitchesOn = false;
}
return true;
});
}
emit(UpdateGroupState(
oneTouchList: groupOneTouchList, allSwitches: allSwitchesOn));
} catch (e) {
emit(FailedState(error: e.toString()));
return;
}
}
void _changeFirstWizardSwitch(ChangeFirstWizardSwitchStatusEvent event,
Emitter<OneTouchState> emit) async {
emit(LoadingNewSate(oneTouchModel: deviceStatus));
try {
bool allSwitchesValue = true;
groupOneTouchList.forEach((element) {
if (element.deviceId == event.deviceId) {
element.firstSwitch = !event.value;
}
if (!element.firstSwitch) {
allSwitchesValue = false;
}
});
final response = await DevicesAPI.deviceBatchController(
code: 'switch_1',
devicesUuid: [event.deviceId],
value: !event.value,
);
emit(UpdateGroupState(
oneTouchList: groupOneTouchList, allSwitches: allSwitchesValue));
if (!response['success']) {
add(InitialEvent(groupScreen: oneTouchGroup));
}
} catch (_) {
add(InitialEvent(groupScreen: oneTouchGroup));
}
}
void _groupAllOn(GroupAllOnEvent event, Emitter<OneTouchState> emit) async {
emit(LoadingNewSate(oneTouchModel: deviceStatus));
try {
// Set all switches (firstSwitch and secondSwitch) based on the event value (on/off)
for (int i = 0; i < groupOneTouchList.length; i++) {
groupOneTouchList[i].firstSwitch = true;
}
// Emit the state with updated values
emit(
UpdateGroupState(oneTouchList: groupOneTouchList, allSwitches: true));
// Get a list of all device IDs
List<String> allDeviceIds =
groupOneTouchList.map((device) => device.deviceId).toList();
// First call for switch_1
final response1 = await DevicesAPI.deviceBatchController(
code: 'switch_1', // Controls first switch for all devices
devicesUuid: allDeviceIds,
value: true, // true (on) or false (off) depending on the event value
);
// Second call for switch_2
final response2 = await DevicesAPI.deviceBatchController(
code: 'switch_2', // Controls second switch for all devices
devicesUuid: allDeviceIds,
value: true, // true (on) or false (off) depending on the event value
);
// Check if either response is unsuccessful, then reset to initial state
if (!response1['success'] || !response2['success']) {
await Future.delayed(const Duration(milliseconds: 500));
add(const InitialEvent(groupScreen: true));
}
} catch (_) {
// In case of an error, delay and reset the screen to initial state
await Future.delayed(const Duration(milliseconds: 500));
add(const InitialEvent(groupScreen: true));
}
}
void _groupAllOff(GroupAllOffEvent event, Emitter<OneTouchState> emit) async {
emit(LoadingNewSate(oneTouchModel: deviceStatus));
try {
// Set all switches (firstSwitch and secondSwitch) based on the event value (on/off)
for (int i = 0; i < groupOneTouchList.length; i++) {
groupOneTouchList[i].firstSwitch = false;
}
// Emit the state with updated values
emit(UpdateGroupState(
oneTouchList: groupOneTouchList, allSwitches: false));
// Get a list of all device IDs
List<String> allDeviceIds =
groupOneTouchList.map((device) => device.deviceId).toList();
// First call for switch_1
final response1 = await DevicesAPI.deviceBatchController(
code: 'switch_1', // Controls first switch for all devices
devicesUuid: allDeviceIds,
value: true, // true (on) or false (off) depending on the event value
);
// Second call for switch_2
final response2 = await DevicesAPI.deviceBatchController(
code: 'switch_2', // Controls second switch for all devices
devicesUuid: allDeviceIds,
value: true, // true (on) or false (off) depending on the event value
);
// Check if either response is unsuccessful, then reset to initial state
if (!response1['success'] || !response2['success']) {
await Future.delayed(const Duration(milliseconds: 500));
add(const InitialEvent(groupScreen: true));
}
} catch (_) {
// In case of an error, delay and reset the screen to initial state
await Future.delayed(const Duration(milliseconds: 500));
add(const InitialEvent(groupScreen: true));
}
}
}

View File

@ -0,0 +1,141 @@
import 'package:equatable/equatable.dart';
abstract class OneTouchEvent extends Equatable {
const OneTouchEvent();
@override
List<Object> get props => [];
}
class LoadingEvent extends OneTouchEvent {}
class OneTouchUpdated extends OneTouchEvent {}
class InitialEvent extends OneTouchEvent {
final bool groupScreen;
const InitialEvent({required this.groupScreen});
@override
List<Object> get props => [groupScreen];
}
class ChangeFirstSwitchStatusEvent extends OneTouchEvent {
final bool value;
final String deviceId;
const ChangeFirstSwitchStatusEvent({required this.value, this.deviceId = ''});
@override
List<Object> get props => [value, deviceId];
}
class ChangeSecondSwitchStatusEvent extends OneTouchEvent {
final bool value;
final String deviceId;
const ChangeSecondSwitchStatusEvent(
{required this.value, this.deviceId = ''});
@override
List<Object> get props => [value, deviceId];
}
class AllOffEvent extends OneTouchEvent {}
class AllOnEvent extends OneTouchEvent {}
class GroupAllOnEvent extends OneTouchEvent {}
class GroupAllOffEvent extends OneTouchEvent {}
class ChangeSlidingSegment extends OneTouchEvent {
final int value;
const ChangeSlidingSegment({required this.value});
@override
List<Object> get props => [value];
}
class GetCounterEvent extends OneTouchEvent {
final String deviceCode;
const GetCounterEvent({required this.deviceCode});
@override
List<Object> get props => [deviceCode];
}
class SetCounterValue extends OneTouchEvent {
final Duration duration;
final String deviceCode;
const SetCounterValue({required this.duration, required this.deviceCode});
@override
List<Object> get props => [duration, deviceCode];
}
class StartTimer extends OneTouchEvent {
final int duration;
const StartTimer(this.duration);
@override
List<Object> get props => [duration];
}
class TickTimer extends OneTouchEvent {
final int remainingTime;
const TickTimer(this.remainingTime);
@override
List<Object> get props => [remainingTime];
}
class StopTimer extends OneTouchEvent {}
class OnClose extends OneTouchEvent {}
class InitialWizardEvent extends OneTouchEvent {}
//------------------- Schedule ----------=---------
class GetScheduleEvent extends OneTouchEvent {}
class ScheduleSave extends OneTouchEvent {}
class ToggleScheduleEvent extends OneTouchEvent {
final String id;
final bool toggle;
const ToggleScheduleEvent({required this.toggle, required this.id});
@override
List<Object> get props => [toggle, id];
}
class ToggleDaySelectionEvent extends OneTouchEvent {
final String key;
const ToggleDaySelectionEvent({required this.key});
@override
List<Object> get props => [key];
}
class DeleteScheduleEvent extends OneTouchEvent {
final String id;
const DeleteScheduleEvent({required this.id});
@override
List<Object> get props => [id];
}
class ToggleSelectedEvent extends OneTouchEvent {
final int index;
const ToggleSelectedEvent({required this.index});
@override
List<Object> get props => [index];
}
class ToggleCreateScheduleEvent extends OneTouchEvent {
final int index;
const ToggleCreateScheduleEvent({required this.index});
@override
List<Object> get props => [index];
}
class ChangeFirstWizardSwitchStatusEvent extends OneTouchEvent {
final bool value;
final String deviceId;
const ChangeFirstWizardSwitchStatusEvent(
{required this.value, this.deviceId = ''});
@override
List<Object> get props => [value, deviceId];
}

View File

@ -0,0 +1,92 @@
import 'package:equatable/equatable.dart';
import 'package:syncrow_app/features/devices/model/group_one_touch_model.dart';
import 'package:syncrow_app/features/devices/model/one_touch_model.dart';
class OneTouchState extends Equatable {
const OneTouchState();
@override
List<Object> get props => [];
}
class InitialState extends OneTouchState {}
class LoadingInitialState extends OneTouchState {}
class UpdateState extends OneTouchState {
final OneTouchModel oneTouchModel;
const UpdateState({required this.oneTouchModel});
@override
List<Object> get props => [oneTouchModel];
}
class LoadingNewSate extends OneTouchState {
final OneTouchModel oneTouchModel;
const LoadingNewSate({required this.oneTouchModel});
@override
List<Object> get props => [OneTouchModel];
}
class UpdateGroupState extends OneTouchState {
final List<GroupOneTouchModel> oneTouchList;
final bool allSwitches;
const UpdateGroupState({required this.oneTouchList, required this.allSwitches});
@override
List<Object> get props => [oneTouchList, allSwitches];
}
class FailedState extends OneTouchState {
final String error;
const FailedState({required this.error});
@override
List<Object> get props => [error];
}
class ChangeSlidingSegmentState extends OneTouchState {
final int value;
const ChangeSlidingSegmentState({required this.value});
@override
List<Object> get props => [value];
}
class UpdateTimerState extends OneTouchState {
final int seconds;
const UpdateTimerState({required this.seconds});
@override
List<Object> get props => [seconds];
}
class TimerRunInProgress extends OneTouchState {
final int remainingTime;
const TimerRunInProgress(this.remainingTime);
@override
List<Object> get props => [remainingTime];
}
class TimerRunComplete extends OneTouchState {}
class SaveSchedule extends OneTouchState {}
class IsToggleState extends OneTouchState {
final bool? onOff;
const IsToggleState({this.onOff});
}
class ChangeTimeState extends OneTouchState {}
class UpdateCreateScheduleState extends OneTouchState {
final bool createSchedule;
UpdateCreateScheduleState(this.createSchedule);
}

View File

@ -0,0 +1,11 @@
class GroupOneTouchModel {
final String deviceId;
final String deviceName;
bool firstSwitch;
GroupOneTouchModel({
required this.deviceId,
required this.deviceName,
required this.firstSwitch,
});
}

View File

@ -0,0 +1,28 @@
import 'package:syncrow_app/features/devices/model/status_model.dart';
class OneTouchModel {
bool firstSwitch;
int firstCountDown;
OneTouchModel({
required this.firstSwitch,
required this.firstCountDown,
});
factory OneTouchModel.fromJson(List<StatusModel> jsonList) {
late bool _switch;
late int _count;
for (int i = 0; i < jsonList.length; i++) {
if (jsonList[i].code == 'switch_1') {
_switch = jsonList[i].value ?? false;
} else if (jsonList[i].code == 'countdown_1') {
_count = jsonList[i].value ?? 0;
}
}
return OneTouchModel(
firstSwitch: _switch,
firstCountDown: _count,
);
}
}

View File

@ -0,0 +1,86 @@
import 'package:flutter/material.dart';
import 'package:syncrow_app/features/devices/model/device_model.dart';
import 'package:syncrow_app/features/shared_widgets/text_widgets/body_small.dart';
import 'package:syncrow_app/utils/context_extension.dart';
import 'package:syncrow_app/utils/resource_manager/color_manager.dart';
class CircularButton extends StatelessWidget {
const CircularButton({
super.key,
required this.device,
required this.label,
required this.onTap,
required this.icons,
});
final DeviceModel? device;
final String label;
final Function()? onTap;
final IconData icons;
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Card(
elevation: 3,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(100),
),
child: GestureDetector(
onTap: onTap,
// () {
// Navigator.push(
// context,
// PageRouteBuilder(
// pageBuilder: (context, animation1, animation2) =>
// TimerScheduleScreen(
// switchCode: 'switch_1',
// device: device!,
// deviceCode: 'countdown_1',
// )));
// },
child: Stack(
alignment: Alignment.center,
children: [
Container(
width: 60,
height: 60,
decoration: BoxDecoration(
color: Colors.grey[300],
borderRadius: BorderRadius.circular(100),
),
),
Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(100),
),
child: Center(
child: Icon(
icons,
color: ColorsManager.primaryColorWithOpacity,
size: 25,
),
),
),
],
),
),
),
const SizedBox(height: 10),
BodySmall(
// text: "Timer",
text: label,
style: context.bodyMedium.copyWith(
color: ColorsManager.textPrimaryColor,
),
),
],
);
}
}

View File

@ -0,0 +1,223 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_app/features/devices/bloc/one_touch_bloc/one_touch_bloc.dart';
import 'package:syncrow_app/features/devices/bloc/one_touch_bloc/one_touch_event.dart';
import 'package:syncrow_app/features/devices/bloc/one_touch_bloc/one_touch_state.dart';
import 'package:syncrow_app/features/devices/model/device_model.dart';
import 'package:syncrow_app/features/devices/model/group_one_touch_model.dart';
import 'package:syncrow_app/features/devices/model/one_touch_model.dart';
import 'package:syncrow_app/features/devices/view/widgets/circular_button.dart';
import 'package:syncrow_app/features/devices/view/widgets/device_appbar.dart';
import 'package:syncrow_app/features/devices/view/widgets/one_touch/one_touch_timer_screen.dart';
import 'package:syncrow_app/features/devices/view/widgets/three_gang/gang_switch.dart';
import 'package:syncrow_app/features/shared_widgets/default_container.dart';
import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart';
import 'package:syncrow_app/features/shared_widgets/text_widgets/body_small.dart';
import 'package:syncrow_app/generated/assets.dart';
import 'package:syncrow_app/utils/context_extension.dart';
import 'package:syncrow_app/utils/resource_manager/color_manager.dart';
import 'package:syncrow_app/utils/resource_manager/constants.dart';
import 'package:syncrow_app/utils/resource_manager/font_manager.dart';
class OneTouchScreen extends StatelessWidget {
const OneTouchScreen({super.key, this.device});
final DeviceModel? device;
@override
Widget build(BuildContext context) {
return AnnotatedRegion(
value: SystemUiOverlayStyle(
statusBarColor: ColorsManager.primaryColor.withOpacity(0.5),
statusBarIconBrightness: Brightness.light,
),
child: Scaffold(
backgroundColor: ColorsManager.backgroundColor,
extendBodyBehindAppBar: true,
extendBody: true,
appBar: device != null
? DeviceAppbar(
deviceName: device!.name!,
deviceUuid: device!.uuid!,
)
: AppBar(
backgroundColor: Colors.transparent,
centerTitle: true,
title: BodyLarge(
text: device?.name ?? 'Lights',
fontColor: ColorsManager.primaryColor,
fontWeight: FontsManager.bold,
),
),
body: Container(
width: MediaQuery.sizeOf(context).width,
height: MediaQuery.sizeOf(context).height,
decoration: const BoxDecoration(
image: DecorationImage(
image: AssetImage(
Assets.assetsImagesBackground,
),
fit: BoxFit.cover,
opacity: 0.4,
),
),
child: SafeArea(
child: Padding(
padding: EdgeInsets.only(
left: Constants.defaultPadding,
right: Constants.defaultPadding,
bottom: Constants.bottomNavBarHeight,
),
child: BlocProvider(
create: (context) => OneTouchBloc(
switchCode: 'switch_1', oneTouchId: device?.uuid ?? '')
..add(const InitialEvent(groupScreen: false)),
child: BlocBuilder<OneTouchBloc, OneTouchState>(
builder: (context, state) {
OneTouchModel oneTouchModel = OneTouchModel(
firstSwitch: false,
firstCountDown: 0,
);
List<GroupOneTouchModel> groupOneTouchModel = [];
bool allSwitchesOn = false;
if (state is LoadingNewSate) {
oneTouchModel = state.oneTouchModel;
} else if (state is UpdateState) {
oneTouchModel = state.oneTouchModel;
} else if (state is UpdateGroupState) {
groupOneTouchModel = state.oneTouchList;
allSwitchesOn = state.allSwitches;
}
return state is LoadingInitialState
? const Center(
child: DefaultContainer(
width: 50,
height: 50,
child: CircularProgressIndicator()),
)
: RefreshIndicator(
onRefresh: () async {
BlocProvider.of<OneTouchBloc>(context).add(
InitialEvent(
groupScreen:
device != null ? false : true));
},
child: ListView(
children: [
SizedBox(
height: MediaQuery.of(context).size.height,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment:
CrossAxisAlignment.stretch,
children: [
const Expanded(child: SizedBox.shrink()),
Expanded(
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceAround,
crossAxisAlignment:
CrossAxisAlignment.center,
children: [
Column(
children: [
GangSwitch(
threeGangSwitch: device!,
value:
oneTouchModel.firstSwitch,
action: () {
BlocProvider.of<
OneTouchBloc>(
context)
.add(ChangeFirstSwitchStatusEvent(
value: oneTouchModel
.firstSwitch));
},
),
const SizedBox(height: 20),
const SizedBox(
width: 70,
child: BodySmall(
text: " Entrance Light",
fontColor: ColorsManager
.textPrimaryColor,
textAlign: TextAlign.center,
),
),
],
),
],
),
),
Center(
child: Row(
mainAxisAlignment:
MainAxisAlignment.center,
crossAxisAlignment:
CrossAxisAlignment.center,
children: [
CircularButton(
device: device,
icons: Icons.access_time,
label: 'Timer',
onTap: () {
Navigator.push(
context,
PageRouteBuilder(
pageBuilder: (context,
animation1,
animation2) =>
TimerScheduleScreen(
switchCode:
'switch_1',
device: device!,
deviceCode:
'countdown_1',
)));
},
),
const SizedBox(
width: 30,
),
CircularButton(
device: device,
icons: Icons.access_time,
label: 'Setting',
onTap: () {
Navigator.push(
context,
PageRouteBuilder(
pageBuilder: (context,
animation1,
animation2) =>
TimerScheduleScreen(
switchCode:
'switch_1',
device: device!,
deviceCode:
'countdown_1',
)));
},
),
],
),
),
const Expanded(child: SizedBox())
],
),
),
],
),
);
},
),
),
),
),
),
),
);
}
}

View File

@ -0,0 +1,296 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/svg.dart';
import 'package:syncrow_app/features/devices/bloc/one_touch_bloc/one_touch_bloc.dart';
import 'package:syncrow_app/features/devices/bloc/one_touch_bloc/one_touch_event.dart';
import 'package:syncrow_app/features/devices/bloc/one_touch_bloc/one_touch_state.dart';
import 'package:syncrow_app/features/devices/model/device_model.dart';
import 'package:syncrow_app/features/shared_widgets/create_schedule.dart';
import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart';
import 'package:syncrow_app/features/shared_widgets/schedule_list.dart';
import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart';
import 'package:syncrow_app/features/shared_widgets/text_widgets/body_small.dart';
import 'package:syncrow_app/generated/assets.dart';
import 'package:syncrow_app/utils/context_extension.dart';
import 'package:syncrow_app/utils/resource_manager/color_manager.dart';
import 'package:syncrow_app/utils/resource_manager/font_manager.dart';
class TimerScheduleScreen extends StatelessWidget {
final DeviceModel device;
final String deviceCode;
final String switchCode;
const TimerScheduleScreen(
{required this.device,
required this.deviceCode,
required this.switchCode,
super.key});
@override
Widget build(BuildContext context) {
return AnnotatedRegion(
value: SystemUiOverlayStyle(
statusBarColor: ColorsManager.primaryColor.withOpacity(0.5),
statusBarIconBrightness: Brightness.light,
),
child: BlocProvider(
create: (context) =>
OneTouchBloc(switchCode: switchCode, oneTouchId: device.uuid ?? '')
..add(GetCounterEvent(deviceCode: deviceCode))
..add(GetScheduleEvent()),
child: BlocBuilder<OneTouchBloc, OneTouchState>(
builder: (context, state) {
final oneTouchBloc = BlocProvider.of<OneTouchBloc>(context);
Duration duration = Duration.zero;
int countNum = 0;
if (state is UpdateTimerState) {
countNum = state.seconds;
} else if (state is TimerRunInProgress) {
countNum = state.remainingTime;
} else if (state is TimerRunComplete) {
countNum = 0;
} else if (state is LoadingNewSate) {
countNum = 0;
}
return PopScope(
canPop: false,
onPopInvoked: (didPop) {
if (!didPop) {
oneTouchBloc.add(OnClose());
Navigator.pop(context);
}
},
child: DefaultTabController(
length: 2,
child: DefaultScaffold(
appBar: AppBar(
backgroundColor: Colors.transparent,
centerTitle: true,
title: const BodyLarge(
text: 'Schedule',
fontColor: ColorsManager.primaryColor,
fontWeight: FontsManager.bold,
),
actions: [
oneTouchBloc.createSchedule == true
? TextButton(
onPressed: () {
oneTouchBloc.add(ScheduleSave());
},
child: const Text('Save'))
: oneTouchBloc.selectedTabIndex == 1
? IconButton(
onPressed: () {
// oneTouchBloc.toggleCreateSchedule();
oneTouchBloc.add(
const ToggleCreateScheduleEvent(
index: 1));
},
icon: const Icon(Icons.add),
)
: const SizedBox(),
],
),
child: state is LoadingInitialState
? const Center(child: CircularProgressIndicator())
: Column(
children: [
Container(
width: MediaQuery.of(context).size.width,
decoration: const ShapeDecoration(
color: ColorsManager.onPrimaryColor,
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.all(Radius.circular(30)),
),
),
child: TabBar(
onTap: (value) {
if (value == 0) {
if (oneTouchBloc.createSchedule ==
true) {
// oneTouchBloc.toggleCreateSchedule();
oneTouchBloc.add(
const ToggleCreateScheduleEvent(
index: 0));
}
oneTouchBloc.add(
const ToggleSelectedEvent(
index: 0));
} else {
oneTouchBloc.add(
const ToggleSelectedEvent(
index: 1));
}
},
indicatorColor:
Colors.white, // Customize the indicator
dividerHeight: 0,
indicatorSize: TabBarIndicatorSize.tab,
indicator: const ShapeDecoration(
color: ColorsManager.slidingBlueColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(20)),
),
),
tabs: [
Tab(
child: Container(
padding: const EdgeInsets.symmetric(
vertical: 10),
child: BodySmall(
text: 'Countdown',
style: context.bodySmall.copyWith(
color: ColorsManager.blackColor,
fontSize: 12,
fontWeight: FontWeight.w400,
),
),
),
),
Tab(
child: Container(
padding: const EdgeInsets.symmetric(
vertical: 10),
child: Text(
'Schedule',
style: context.bodySmall.copyWith(
color: ColorsManager.blackColor,
fontSize: 12,
fontWeight: FontWeight.w400,
),
),
),
),
],
),
),
Expanded(
child: TabBarView(
physics:
const NeverScrollableScrollPhysics(), // Disable swiping
children: [
Center(
child: Container(
child: Column(
mainAxisAlignment:
MainAxisAlignment.center,
children: [
countNum > 0
? BodyLarge(
text: _formatDuration(
countNum),
fontColor: ColorsManager
.slidingBlueColor,
fontSize: 40,
)
: CupertinoTimerPicker(
mode:
CupertinoTimerPickerMode
.hm,
onTimerDurationChanged:
(Duration
newDuration) {
duration = newDuration;
},
),
GestureDetector(
onTap: () {
if (state
is LoadingNewSate) {
return;
}
if (countNum > 0) {
oneTouchBloc.add(
SetCounterValue(
deviceCode:
deviceCode,
duration: Duration
.zero));
} else if (duration !=
Duration.zero) {
oneTouchBloc.add(
SetCounterValue(
deviceCode:
deviceCode,
duration:
duration));
}
},
child: SvgPicture.asset(
countNum > 0
? Assets.pauseIcon
: Assets.playIcon)),
],
),
),
),
SizedBox(
child: oneTouchBloc.createSchedule ==
true
? CreateSchedule(
onToggleChanged: (bool isOn) {
oneTouchBloc.toggleSchedule =
isOn;
},
onDateTimeChanged:
(DateTime dateTime) {
oneTouchBloc.selectedTime =
dateTime;
},
days: oneTouchBloc.days,
selectDays: (List<String>
selectedDays) {
oneTouchBloc.selectedDays =
selectedDays;
},
)
: Padding(
padding: const EdgeInsets.only(
top: 10),
child: ScheduleListView(
listSchedule: oneTouchBloc
.listSchedule, // Pass the schedule list here
onDismissed: (scheduleId) {
oneTouchBloc.listSchedule
.removeWhere((schedule) =>
schedule
.scheduleId ==
scheduleId);
oneTouchBloc.add(
DeleteScheduleEvent(
id: scheduleId));
},
onToggleSchedule:
(scheduleId, isEnabled) {
oneTouchBloc.add(
ToggleScheduleEvent(
id: scheduleId,
toggle: isEnabled,
));
},
),
),
),
],
),
),
],
),
)));
},
),
),
);
}
String _formatDuration(int seconds) {
final hours = (seconds ~/ 3600).toString().padLeft(2, '0');
final minutes = ((seconds % 3600) ~/ 60).toString().padLeft(2, '0');
final secs = (seconds % 60).toString().padLeft(2, '0');
return '$hours:$minutes:$secs';
}
}

View File

@ -0,0 +1,46 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_app/features/devices/bloc/one_gang_bloc/one_gang_bloc.dart';
import 'package:syncrow_app/features/devices/bloc/one_gang_bloc/one_gang_event.dart';
import 'package:syncrow_app/features/devices/bloc/one_gang_bloc/one_gang_state.dart';
import 'package:syncrow_app/features/devices/model/device_model.dart';
import 'package:syncrow_app/features/devices/model/group_one_gang_model.dart';
import 'package:syncrow_app/features/devices/view/widgets/one_gang/one_gang_list.dart';
import 'package:syncrow_app/features/shared_widgets/default_container.dart';
import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart';
class OneGangWizard extends StatelessWidget {
const OneGangWizard({super.key, this.device});
final DeviceModel? device;
@override
Widget build(BuildContext context) {
List<GroupOneGangModel> groupOneGangModel = [];
return DefaultScaffold(
child: BlocProvider(
create: (context) =>
OneGangBloc(switchCode: '', oneGangId: device?.uuid ?? '')..add(InitialWizardEvent()),
child: BlocBuilder<OneGangBloc, OneGangState>(
builder: (context, state) {
bool allSwitchesOn = false;
if (state is UpdateGroupState) {
groupOneGangModel = state.oneGangList;
allSwitchesOn = state.allSwitches;
}
return state is LoadingInitialState
? const Center(
child:
DefaultContainer(width: 50, height: 50, child: CircularProgressIndicator()),
)
: OneGangList(
oneGangList: groupOneGangModel,
allSwitches: allSwitchesOn,
);
},
),
));
}
}

View File

@ -13,6 +13,7 @@ import 'package:syncrow_app/features/devices/view/widgets/door_sensor/door_senso
import 'package:syncrow_app/features/devices/view/widgets/gateway/gateway_view.dart';
import 'package:syncrow_app/features/devices/view/widgets/lights/light_interface.dart';
import 'package:syncrow_app/features/devices/view/widgets/one_gang/one_gang_Interface.dart';
import 'package:syncrow_app/features/devices/view/widgets/one_touch/one_touch_screen.dart';
import 'package:syncrow_app/features/devices/view/widgets/two_gang/two_gang_Interface.dart';
import 'package:syncrow_app/features/devices/view/widgets/wall_sensor/wall_sensor_interface.dart';
import 'package:syncrow_app/features/devices/view/widgets/ceiling_sensor/ceiling_sensor_interface.dart';
@ -164,6 +165,13 @@ void showDeviceInterface(DeviceModel device, BuildContext context) {
PageRouteBuilder(
pageBuilder: (context, animation1, animation2) =>
DoorSensorScreen(device: device)));
case DeviceType.OneTouch:
Navigator.push(
context,
PageRouteBuilder(
pageBuilder: (context, animation1, animation2) =>
OneTouchScreen(device: device)));
break;
default:
}

View File

@ -50,6 +50,7 @@ enum DeviceType {
WallSensor,
WH,
DS,
OneTouch,
Other,
}
@ -76,6 +77,7 @@ Map<String, DeviceType> devicesTypesMap = {
"CUR": DeviceType.Curtain,
"WH": DeviceType.WH,
"DS": DeviceType.DS,
"1GT": DeviceType.OneTouch,
};
Map<DeviceType, List<FunctionModel>> devicesFunctionsMap = {
DeviceType.AC: [
@ -284,13 +286,40 @@ Map<DeviceType, List<FunctionModel>> devicesFunctionsMap = {
FunctionModel(
code: 'doorcontact_state',
type: functionTypesMap['Raw'],
values: ValueModel.fromJson({})
),
values: ValueModel.fromJson({})),
FunctionModel(
code: 'battery_percentage',
type: functionTypesMap['Integer'],
values: ValueModel.fromJson({})
),
values: ValueModel.fromJson({})),
],
DeviceType.OneTouch: [
FunctionModel(
code: 'switch_1',
type: functionTypesMap['Boolean'],
values: ValueModel.fromJson({})),
FunctionModel(
code: 'countdown_1',
type: functionTypesMap['Integer'],
values: ValueModel.fromJson(
{"unit": "s", "min": 0, "max": 43200, "scale": 0, "step": 1})),
FunctionModel(
code: 'relay_status',
type: functionTypesMap['Enum'],
values: ValueModel.fromJson({
"range": ['power_off', 'power_on', 'last']
})),
FunctionModel(
code: 'light_mode',
type: functionTypesMap['Enum'],
values: ValueModel.fromJson({
"range": ['none', 'relay', 'pos']
})),
FunctionModel(
code: 'relay_status_1',
type: functionTypesMap['Enum'],
values: ValueModel.fromJson({
"range": ['power_off', 'power_on', 'last']
})),
],
};