Implement Two-Gang & One-Gang

This commit is contained in:
mohammad
2024-09-12 16:59:03 +03:00
parent 76be98354b
commit b5842194ff
23 changed files with 2308 additions and 4 deletions

5
assets/icons/1gang.svg Normal file
View File

@ -0,0 +1,5 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M38.0142 39.2553L35.3682 40H20.9308H19.9999H19.0691H1.24111C0.555643 40 0 39.4444 0 38.7589V1.24111C0 0.555643 0.555643 0 1.24111 0H19.0682H20.1226H20.9543H35.4255L38.2625 1.24111C38.9479 1.24111 39.5036 1.79675 39.5036 2.48221L39.2553 38.0142C39.2553 38.6997 38.6997 39.2553 38.0142 39.2553Z" fill="#D1D1D1"/>
<path d="M38.7589 0H35.0356C35.721 0 36.2767 0.555643 36.2767 1.24111V38.7589C36.2767 39.4444 35.721 40 35.0356 40H38.7589C39.4444 40 40 39.4444 40 38.7589V1.24111C40 0.555643 39.4444 0 38.7589 0Z" fill="#A0A0A0"/>
<path opacity="0.6" d="M20.875 19.2411V20.7304C20.875 20.9746 20.7996 21.1767 20.7014 21.3067C20.6035 21.4364 20.5053 21.4715 20.4375 21.4715H16.3125C16.2447 21.4715 16.1465 21.4364 16.0486 21.3067C15.9504 21.1767 15.875 20.9746 15.875 20.7304V19.2411C15.875 18.9969 15.9504 18.7949 16.0486 18.6649C16.1465 18.5352 16.2447 18.5 16.3125 18.5H20.4375C20.5053 18.5 20.6035 18.5352 20.7014 18.6649C20.7996 18.7949 20.875 18.9969 20.875 19.2411Z" stroke="#023DFE" stroke-opacity="0.6"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,179 @@
import 'dart:async';
import 'package:firebase_database/firebase_database.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_app/features/devices/bloc/one_gang_bloc/one_gang_state.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/one_gang_model.dart';
import 'package:syncrow_app/features/devices/model/status_model.dart';
import 'package:syncrow_app/services/api/devices_api.dart';
import 'one_gang_event.dart';
class OneGangBloc extends Bloc<OneGangEvent, OneGangState> {
final String oneGangId;
OneGangModel deviceStatus = OneGangModel(
firstSwitch: false,
firstCountDown: 0,
);
Timer? _timer;
bool oneGangGroup = false;
List<DeviceModel> devicesList = [];
bool allSwitchesOn = true;
OneGangBloc({required this.oneGangId}) : super(InitialState()) {
on<InitialEvent>(_fetchTwoGangStatus);
on<OneGangUpdated>(_oneGangUpdated);
on<ChangeFirstSwitchStatusEvent>(_changeFirstSwitch);
on<ChangeSlidingSegment>(_changeSliding);
on<SetCounterValue>(_setCounterValue);
on<GetCounterEvent>(_getCounterValue);
on<TickTimer>(_onTickTimer);
on<OnClose>(_onClose);
}
void _fetchTwoGangStatus(InitialEvent event, Emitter<OneGangState> emit) async {
emit(LoadingInitialState());
try {
var response = await DevicesAPI.getDeviceStatus(oneGangId);
List<StatusModel> statusModelList = [];
for (var status in response['status']) {
statusModelList.add(StatusModel.fromJson(status));
}
deviceStatus = OneGangModel.fromJson(statusModelList);
emit(UpdateState(oneGangModel: deviceStatus));
_listenToChanges();
} catch (e) {
emit(FailedState(error: e.toString()));
return;
}
}
_listenToChanges() {
try {
DatabaseReference ref = FirebaseDatabase.instance.ref('device-status/$oneGangId');
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 = OneGangModel.fromJson(statusList);
if (!isClosed) {
add(OneGangUpdated());
}
});
} catch (_) {}
}
_oneGangUpdated(OneGangUpdated event, Emitter<OneGangState> emit) {
emit(UpdateState(oneGangModel: deviceStatus));
}
void _changeFirstSwitch(ChangeFirstSwitchStatusEvent event, Emitter<OneGangState> emit) async {
emit(LoadingNewSate(oneGangModel: deviceStatus));
try {
deviceStatus.firstSwitch = !event.value;
emit(UpdateState(oneGangModel: deviceStatus));
if (_timer != null) {
_timer!.cancel();
}
_timer = Timer(const Duration(milliseconds: 500), () async {
final response = await DevicesAPI.controlDevice(
DeviceControlModel(
deviceId: oneGangGroup ? event.deviceId : oneGangId,
code: 'switch_1',
value: !event.value),
oneGangGroup ? event.deviceId : oneGangId);
if (!response['success']) {
add(InitialEvent(groupScreen: oneGangGroup));
}
});
} catch (_) {
add(InitialEvent(groupScreen: oneGangGroup));
}
}
void _changeSliding(ChangeSlidingSegment event, Emitter<OneGangState> emit) async {
emit(ChangeSlidingSegmentState(value: event.value));
}
void _setCounterValue(SetCounterValue event, Emitter<OneGangState> emit) async {
emit(LoadingNewSate(oneGangModel: deviceStatus));
int seconds = 0;
try {
seconds = event.duration.inSeconds;
final response = await DevicesAPI.controlDevice(
DeviceControlModel(deviceId: oneGangId, code: event.deviceCode, value: seconds),
oneGangId);
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<OneGangState> emit) async {
emit(LoadingInitialState());
try {
var response = await DevicesAPI.getDeviceStatus(oneGangId);
List<StatusModel> statusModelList = [];
for (var status in response['status']) {
statusModelList.add(StatusModel.fromJson(status));
}
deviceStatus = OneGangModel.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<OneGangState> 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<OneGangState> emit) {
if (event.remainingTime > 0) {
emit(TimerRunInProgress(event.remainingTime));
} else {
_timer?.cancel();
emit(TimerRunComplete());
}
}
}

View File

@ -0,0 +1,88 @@
import 'package:equatable/equatable.dart';
abstract class OneGangEvent extends Equatable {
const OneGangEvent();
@override
List<Object> get props => [];
}
class LoadingEvent extends OneGangEvent {}
class OneGangUpdated extends OneGangEvent {}
class InitialEvent extends OneGangEvent {
final bool groupScreen;
const InitialEvent({required this.groupScreen});
@override
List<Object> get props => [groupScreen];
}
class ChangeFirstSwitchStatusEvent extends OneGangEvent {
final bool value;
final String deviceId;
const ChangeFirstSwitchStatusEvent({required this.value, this.deviceId = ''});
@override
List<Object> get props => [value, deviceId];
}
class ChangeSecondSwitchStatusEvent extends OneGangEvent {
final bool value;
final String deviceId;
const ChangeSecondSwitchStatusEvent({required this.value, this.deviceId = ''});
@override
List<Object> get props => [value, deviceId];
}
class AllOffEvent extends OneGangEvent {}
class AllOnEvent extends OneGangEvent {}
class GroupAllOnEvent extends OneGangEvent {}
class GroupAllOffEvent extends OneGangEvent {}
class ChangeSlidingSegment extends OneGangEvent {
final int value;
const ChangeSlidingSegment({required this.value});
@override
List<Object> get props => [value];
}
class GetCounterEvent extends OneGangEvent {
final String deviceCode;
const GetCounterEvent({required this.deviceCode});
@override
List<Object> get props => [deviceCode];
}
class SetCounterValue extends OneGangEvent {
final Duration duration;
final String deviceCode;
const SetCounterValue({required this.duration, required this.deviceCode});
@override
List<Object> get props => [duration, deviceCode];
}
class StartTimer extends OneGangEvent {
final int duration;
const StartTimer(this.duration);
@override
List<Object> get props => [duration];
}
class TickTimer extends OneGangEvent {
final int remainingTime;
const TickTimer(this.remainingTime);
@override
List<Object> get props => [remainingTime];
}
class StopTimer extends OneGangEvent {}
class OnClose extends OneGangEvent {}

View File

@ -0,0 +1,79 @@
import 'package:equatable/equatable.dart';
import 'package:syncrow_app/features/devices/model/groupTwoGangModel.dart';
import 'package:syncrow_app/features/devices/model/one_gang_model.dart';
import 'package:syncrow_app/features/devices/model/two_gang_model.dart';
class OneGangState extends Equatable {
const OneGangState();
@override
List<Object> get props => [];
}
class InitialState extends OneGangState {}
class LoadingInitialState extends OneGangState {}
class UpdateState extends OneGangState {
final OneGangModel oneGangModel;
const UpdateState({required this.oneGangModel});
@override
List<Object> get props => [TwoGangModel];
}
class LoadingNewSate extends OneGangState {
final OneGangModel oneGangModel;
const LoadingNewSate({required this.oneGangModel});
@override
List<Object> get props => [OneGangModel];
}
class UpdateGroupState extends OneGangState {
final List<GroupTwoGangModel> twoGangList;
final bool allSwitches;
const UpdateGroupState({required this.twoGangList, required this.allSwitches});
@override
List<Object> get props => [twoGangList, allSwitches];
}
class FailedState extends OneGangState {
final String error;
const FailedState({required this.error});
@override
List<Object> get props => [error];
}
class ChangeSlidingSegmentState extends OneGangState {
final int value;
const ChangeSlidingSegmentState({required this.value});
@override
List<Object> get props => [value];
}
class UpdateTimerState extends OneGangState {
final int seconds;
const UpdateTimerState({required this.seconds});
@override
List<Object> get props => [seconds];
}
class TimerRunInProgress extends OneGangState {
final int remainingTime;
const TimerRunInProgress(this.remainingTime);
@override
List<Object> get props => [remainingTime];
}
class TimerRunComplete extends OneGangState {}

View File

@ -190,7 +190,6 @@ class ThreeGangBloc extends Bloc<ThreeGangEvent, ThreeGangState> {
if (_timer != null) {
_timer!.cancel();
}
_timer = Timer(const Duration(milliseconds: 500), () async {
final response = await DevicesAPI.controlDevice(
DeviceControlModel(

View File

@ -0,0 +1,419 @@
import 'dart:async';
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/two_gang_bloc/two_gang_event.dart';
import 'package:syncrow_app/features/devices/bloc/two_gang_bloc/two_gang_state.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/groupTwoGangModel.dart';
import 'package:syncrow_app/features/devices/model/status_model.dart';
import 'package:syncrow_app/features/devices/model/two_gang_model.dart';
import 'package:syncrow_app/services/api/devices_api.dart';
class TwoGangBloc extends Bloc<TwoGangEvent, TwoGangState> {
final String twoGangId;
TwoGangModel deviceStatus = TwoGangModel(
firstSwitch: false,
secondSwitch: false,
firstCountDown: 0,
secondCountDown: 0,
);
Timer? _timer;
// Timer? _firstSwitchTimer;
// Timer? _secondSwitchTimer;
// Timer? _thirdSwitchTimer;
bool twoGangGroup = false;
List<DeviceModel> devicesList = [];
List<GroupTwoGangModel> groupTwoGangList = [];
bool allSwitchesOn = true;
TwoGangBloc({required this.twoGangId}) : super(InitialState()) {
on<InitialEvent>(_fetchTwoGangStatus);
on<TwoGangUpdated>(_twoGangUpdated);
on<ChangeFirstSwitchStatusEvent>(_changeFirstSwitch);
on<ChangeSecondSwitchStatusEvent>(_changeSecondSwitch);
on<AllOffEvent>(_allOff);
on<AllOnEvent>(_allOn);
on<ChangeSlidingSegment>(_changeSliding);
on<SetCounterValue>(_setCounterValue);
on<GetCounterEvent>(_getCounterValue);
on<TickTimer>(_onTickTimer);
on<OnClose>(_onClose);
on<GroupAllOnEvent>(_groupAllOn);
on<GroupAllOffEvent>(_groupAllOff);
}
void _fetchTwoGangStatus(InitialEvent event, Emitter<TwoGangState> emit) async {
emit(LoadingInitialState());
try {
twoGangGroup = event.groupScreen;
if (twoGangGroup) {
devicesList = [];
groupTwoGangList = [];
allSwitchesOn = true;
devicesList = await DevicesAPI.getDeviceByGroupName(
HomeCubit.getInstance().selectedSpace?.id ?? '', '2G');
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 = TwoGangModel.fromJson(statusModelList);
groupTwoGangList.add(GroupTwoGangModel(
deviceId: devicesList[i].uuid ?? '',
deviceName: devicesList[i].name ?? '',
firstSwitch: deviceStatus.firstSwitch,
secondSwitch: deviceStatus.secondSwitch,
));
}
if (groupTwoGangList.isNotEmpty) {
groupTwoGangList.firstWhere((element) {
if (!element.firstSwitch || !element.secondSwitch ) {
allSwitchesOn = false;
}
return true;
});
}
emit(UpdateGroupState(twoGangList: groupTwoGangList, allSwitches: allSwitchesOn));
} else {
var response = await DevicesAPI.getDeviceStatus(twoGangId);
List<StatusModel> statusModelList = [];
for (var status in response['status']) {
statusModelList.add(StatusModel.fromJson(status));
}
deviceStatus = TwoGangModel.fromJson(statusModelList);
emit(UpdateState(twoGangModel: deviceStatus));
_listenToChanges();
}
} catch (e) {
emit(FailedState(error: e.toString()));
return;
}
}
_listenToChanges() {
try {
DatabaseReference ref = FirebaseDatabase.instance.ref('device-status/$twoGangId');
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 = TwoGangModel.fromJson(statusList);
if (!isClosed) {
add(TwoGangUpdated());
}
});
} catch (_) {}
}
_twoGangUpdated(TwoGangUpdated event, Emitter<TwoGangState> emit) {
emit(UpdateState(twoGangModel: deviceStatus));
}
void _changeFirstSwitch(ChangeFirstSwitchStatusEvent event, Emitter<TwoGangState> emit) async {
emit(LoadingNewSate(twoGangModel: deviceStatus));
try {
if (twoGangGroup) {
bool allSwitchesValue = true;
groupTwoGangList.forEach((element) {
if (element.deviceId == event.deviceId) {
element.firstSwitch = !event.value;
}
if (!element.firstSwitch || !element.secondSwitch ) {
allSwitchesValue = false;
}
});
emit(UpdateGroupState(twoGangList: groupTwoGangList, allSwitches: allSwitchesValue));
} else {
deviceStatus.firstSwitch = !event.value;
emit(UpdateState(twoGangModel: deviceStatus));
}
if (_timer != null) {
_timer!.cancel();
}
_timer = Timer(const Duration(milliseconds: 500), () async {
final response = await DevicesAPI.controlDevice(
DeviceControlModel(
deviceId: twoGangGroup ? event.deviceId : twoGangId,
code: 'switch_1',
value: !event.value),
twoGangGroup ? event.deviceId : twoGangId);
if (!response['success']) {
add(InitialEvent(groupScreen: twoGangGroup));
}
});
} catch (_) {
add(InitialEvent(groupScreen: twoGangGroup));
}
}
void _changeSecondSwitch(
ChangeSecondSwitchStatusEvent event, Emitter<TwoGangState> emit) async {
emit(LoadingNewSate(twoGangModel: deviceStatus));
try {
if (twoGangGroup) {
bool allSwitchesValue = true;
groupTwoGangList.forEach((element) {
if (element.deviceId == event.deviceId) {
element.secondSwitch = !event.value;
}
if (!element.firstSwitch || !element.secondSwitch ) {
allSwitchesValue = false;
}
});
emit(UpdateGroupState(twoGangList: groupTwoGangList, allSwitches: allSwitchesValue));
} else {
deviceStatus.secondSwitch = !event.value;
emit(UpdateState(twoGangModel: deviceStatus));
}
if (_timer != null) {
_timer!.cancel();
}
_timer = Timer(const Duration(milliseconds: 500), () async {
final response = await DevicesAPI.controlDevice(
DeviceControlModel(
deviceId: twoGangGroup ? event.deviceId : twoGangId,
code: 'switch_2',
value: !event.value),
twoGangGroup ? event.deviceId : twoGangId);
if (!response['success']) {
add(InitialEvent(groupScreen: twoGangGroup));
}
});
} catch (_) {
add(InitialEvent(groupScreen: twoGangGroup));
}
}
void _allOff(AllOffEvent event, Emitter<TwoGangState> emit) async {
emit(LoadingNewSate(twoGangModel: deviceStatus));
try {
deviceStatus.firstSwitch = false;
deviceStatus.secondSwitch = false;
emit(UpdateState(twoGangModel: deviceStatus));
final response = await Future.wait([
DevicesAPI.controlDevice(
DeviceControlModel(
deviceId: twoGangId, code: 'switch_1', value: deviceStatus.firstSwitch),
twoGangId),
DevicesAPI.controlDevice(
DeviceControlModel(
deviceId: twoGangId, code: 'switch_2', value: deviceStatus.secondSwitch),
twoGangId),
]);
if (response.every((element) => !element['success'])) {
await Future.delayed(const Duration(milliseconds: 500));
add(const InitialEvent(groupScreen: false));
}
} catch (_) {
await Future.delayed(const Duration(milliseconds: 500));
add(const InitialEvent(groupScreen: false));
}
}
void _allOn(AllOnEvent event, Emitter<TwoGangState> emit) async {
emit(LoadingNewSate(twoGangModel: deviceStatus));
try {
deviceStatus.firstSwitch = true;
deviceStatus.secondSwitch = true;
emit(UpdateState(twoGangModel: deviceStatus));
final response = await Future.wait([
DevicesAPI.controlDevice(
DeviceControlModel(
deviceId: twoGangId, code: 'switch_1', value: deviceStatus.firstSwitch),
twoGangId),
DevicesAPI.controlDevice(
DeviceControlModel(
deviceId: twoGangId, code: 'switch_2', value: deviceStatus.secondSwitch),
twoGangId),
]);
if (response.every((element) => !element['success'])) {
await Future.delayed(const Duration(milliseconds: 500));
add(const InitialEvent(groupScreen: false));
}
} catch (_) {
await Future.delayed(const Duration(milliseconds: 500));
add(const InitialEvent(groupScreen: false));
}
}
void _groupAllOn(GroupAllOnEvent event, Emitter<TwoGangState> emit) async {
emit(LoadingNewSate(twoGangModel: deviceStatus));
try {
for (int i = 0; i < groupTwoGangList.length; i++) {
groupTwoGangList[i].firstSwitch = true;
groupTwoGangList[i].secondSwitch = true;
}
emit(UpdateGroupState(twoGangList: groupTwoGangList, allSwitches: true));
for (int i = 0; i < groupTwoGangList.length; i++) {
final response = await Future.wait([
DevicesAPI.controlDevice(
DeviceControlModel(
deviceId: groupTwoGangList[i].deviceId, code: 'switch_1', value: true),
groupTwoGangList[i].deviceId),
DevicesAPI.controlDevice(
DeviceControlModel(
deviceId: groupTwoGangList[i].deviceId, code: 'switch_2', value: true),
groupTwoGangList[i].deviceId),
]);
if (response.every((element) => !element['success'])) {
await Future.delayed(const Duration(milliseconds: 500));
add(const InitialEvent(groupScreen: true));
break;
}
}
} catch (_) {
await Future.delayed(const Duration(milliseconds: 500));
add(const InitialEvent(groupScreen: true));
}
}
void _groupAllOff(GroupAllOffEvent event, Emitter<TwoGangState> emit) async {
emit(LoadingNewSate(twoGangModel: deviceStatus));
try {
for (int i = 0; i < groupTwoGangList.length; i++) {
groupTwoGangList[i].firstSwitch = false;
groupTwoGangList[i].secondSwitch = false;
}
emit(UpdateGroupState(twoGangList: groupTwoGangList, allSwitches: false));
for (int i = 0; i < groupTwoGangList.length; i++) {
final response = await Future.wait([
DevicesAPI.controlDevice(
DeviceControlModel(
deviceId: groupTwoGangList[i].deviceId, code: 'switch_1', value: false),
groupTwoGangList[i].deviceId),
DevicesAPI.controlDevice(
DeviceControlModel(
deviceId: groupTwoGangList[i].deviceId, code: 'switch_2', value: false),
groupTwoGangList[i].deviceId),
]);
if (response.every((element) => !element['success'])) {
await Future.delayed(const Duration(milliseconds: 500));
add(const InitialEvent(groupScreen: true));
break;
}
}
} catch (_) {
await Future.delayed(const Duration(milliseconds: 500));
add(const InitialEvent(groupScreen: true));
}
}
void _changeSliding(ChangeSlidingSegment event, Emitter<TwoGangState> emit) async {
emit(ChangeSlidingSegmentState(value: event.value));
}
void _setCounterValue(SetCounterValue event, Emitter<TwoGangState> emit) async {
emit(LoadingNewSate(twoGangModel: deviceStatus));
int seconds = 0;
try {
seconds = event.duration.inSeconds;
final response = await DevicesAPI.controlDevice(
DeviceControlModel(deviceId: twoGangId, code: event.deviceCode, value: seconds),
twoGangId);
if (response['success'] ?? false) {
if (event.deviceCode == 'countdown_1') {
deviceStatus.firstCountDown = seconds;
} else if (event.deviceCode == 'countdown_2') {
deviceStatus.secondCountDown = 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<TwoGangState> emit) async {
emit(LoadingInitialState());
try {
var response = await DevicesAPI.getDeviceStatus(twoGangId);
List<StatusModel> statusModelList = [];
for (var status in response['status']) {
statusModelList.add(StatusModel.fromJson(status));
}
deviceStatus = TwoGangModel.fromJson(statusModelList);
if (event.deviceCode == 'countdown_1') {
deviceStatus.firstCountDown > 0
? _onStartTimer(deviceStatus.firstCountDown)
: emit(UpdateTimerState(seconds: deviceStatus.firstCountDown));
} else if (event.deviceCode == 'countdown_2') {
deviceStatus.secondCountDown > 0
? _onStartTimer(deviceStatus.secondCountDown)
: emit(UpdateTimerState(seconds: deviceStatus.secondCountDown));
}
} catch (e) {
emit(FailedState(error: e.toString()));
return;
}
}
void _onClose(OnClose event, Emitter<TwoGangState> emit) {
_timer?.cancel();
// _firstSwitchTimer?.cancel();
// _secondSwitchTimer?.cancel();
// _thirdSwitchTimer?.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<TwoGangState> emit) {
if (event.remainingTime > 0) {
emit(TimerRunInProgress(event.remainingTime));
} else {
_timer?.cancel();
emit(TimerRunComplete());
}
}
}

View File

@ -0,0 +1,88 @@
import 'package:equatable/equatable.dart';
abstract class TwoGangEvent extends Equatable {
const TwoGangEvent();
@override
List<Object> get props => [];
}
class LoadingEvent extends TwoGangEvent {}
class TwoGangUpdated extends TwoGangEvent {}
class InitialEvent extends TwoGangEvent {
final bool groupScreen;
const InitialEvent({required this.groupScreen});
@override
List<Object> get props => [groupScreen];
}
class ChangeFirstSwitchStatusEvent extends TwoGangEvent {
final bool value;
final String deviceId;
const ChangeFirstSwitchStatusEvent({required this.value, this.deviceId = ''});
@override
List<Object> get props => [value, deviceId];
}
class ChangeSecondSwitchStatusEvent extends TwoGangEvent {
final bool value;
final String deviceId;
const ChangeSecondSwitchStatusEvent({required this.value, this.deviceId = ''});
@override
List<Object> get props => [value, deviceId];
}
class AllOffEvent extends TwoGangEvent {}
class AllOnEvent extends TwoGangEvent {}
class GroupAllOnEvent extends TwoGangEvent {}
class GroupAllOffEvent extends TwoGangEvent {}
class ChangeSlidingSegment extends TwoGangEvent {
final int value;
const ChangeSlidingSegment({required this.value});
@override
List<Object> get props => [value];
}
class GetCounterEvent extends TwoGangEvent {
final String deviceCode;
const GetCounterEvent({required this.deviceCode});
@override
List<Object> get props => [deviceCode];
}
class SetCounterValue extends TwoGangEvent {
final Duration duration;
final String deviceCode;
const SetCounterValue({required this.duration, required this.deviceCode});
@override
List<Object> get props => [duration, deviceCode];
}
class StartTimer extends TwoGangEvent {
final int duration;
const StartTimer(this.duration);
@override
List<Object> get props => [duration];
}
class TickTimer extends TwoGangEvent {
final int remainingTime;
const TickTimer(this.remainingTime);
@override
List<Object> get props => [remainingTime];
}
class StopTimer extends TwoGangEvent {}
class OnClose extends TwoGangEvent {}

View File

@ -0,0 +1,78 @@
import 'package:equatable/equatable.dart';
import 'package:syncrow_app/features/devices/model/groupTwoGangModel.dart';
import 'package:syncrow_app/features/devices/model/two_gang_model.dart';
class TwoGangState extends Equatable {
const TwoGangState();
@override
List<Object> get props => [];
}
class InitialState extends TwoGangState {}
class LoadingInitialState extends TwoGangState {}
class UpdateState extends TwoGangState {
final TwoGangModel twoGangModel;
const UpdateState({required this.twoGangModel});
@override
List<Object> get props => [TwoGangModel];
}
class LoadingNewSate extends TwoGangState {
final TwoGangModel twoGangModel;
const LoadingNewSate({required this.twoGangModel});
@override
List<Object> get props => [TwoGangModel];
}
class UpdateGroupState extends TwoGangState {
final List<GroupTwoGangModel> twoGangList;
final bool allSwitches;
const UpdateGroupState({required this.twoGangList, required this.allSwitches});
@override
List<Object> get props => [twoGangList, allSwitches];
}
class FailedState extends TwoGangState {
final String error;
const FailedState({required this.error});
@override
List<Object> get props => [error];
}
class ChangeSlidingSegmentState extends TwoGangState {
final int value;
const ChangeSlidingSegmentState({required this.value});
@override
List<Object> get props => [value];
}
class UpdateTimerState extends TwoGangState {
final int seconds;
const UpdateTimerState({required this.seconds});
@override
List<Object> get props => [seconds];
}
class TimerRunInProgress extends TwoGangState {
final int remainingTime;
const TimerRunInProgress(this.remainingTime);
@override
List<Object> get props => [remainingTime];
}
class TimerRunComplete extends TwoGangState {}

View File

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

View File

@ -0,0 +1,30 @@
import 'package:syncrow_app/features/devices/model/status_model.dart';
class OneGangModel {
bool firstSwitch;
int firstCountDown;
OneGangModel(
{required this.firstSwitch,
required this.firstCountDown,
});
factory OneGangModel.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 OneGangModel(
firstSwitch: _switch,
firstCountDown: _count,
);
}
}

View File

@ -0,0 +1,40 @@
import 'package:syncrow_app/features/devices/model/status_model.dart';
class TwoGangModel {
bool firstSwitch;
bool secondSwitch;
int firstCountDown;
int secondCountDown;
TwoGangModel(
{required this.firstSwitch,
required this.secondSwitch,
required this.firstCountDown,
required this.secondCountDown,
});
factory TwoGangModel.fromJson(List<StatusModel> jsonList) {
late bool _firstSwitch;
late bool _secondSwitch;
late int _firstCount;
late int _secondCount;
for (int i = 0; i < jsonList.length; i++) {
if (jsonList[i].code == 'switch_1') {
_firstSwitch = jsonList[i].value ?? false;
} else if (jsonList[i].code == 'switch_2') {
_secondSwitch = jsonList[i].value ?? false;
}else if (jsonList[i].code == 'countdown_1') {
_firstCount = jsonList[i].value ?? 0;
} else if (jsonList[i].code == 'countdown_2') {
_secondCount = jsonList[i].value ?? 0;
}
}
return TwoGangModel(
firstSwitch: _firstSwitch,
secondSwitch: _secondSwitch,
firstCountDown: _firstCount,
secondCountDown: _secondCount,
);
}
}

View File

@ -0,0 +1,68 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:syncrow_app/features/devices/model/device_model.dart';
import 'package:syncrow_app/features/devices/view/widgets/device_appbar.dart';
import 'package:syncrow_app/features/devices/view/widgets/one_gang/one_gang_screen.dart';
import 'package:syncrow_app/features/devices/view/widgets/two_gang/two_gang_screen.dart';
import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart';
import 'package:syncrow_app/generated/assets.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 OneGangInterface extends StatelessWidget {
const OneGangInterface({super.key, this.gangSwitch});
final DeviceModel? gangSwitch;
@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: gangSwitch != null
? DeviceAppbar(
deviceName: gangSwitch!.name!,
deviceUuid: gangSwitch!.uuid!,
)
: AppBar(
backgroundColor: Colors.transparent,
centerTitle: true,
title: BodyLarge(
text: gangSwitch?.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: OneGangScreen(device: gangSwitch),
),
),
),
),
);
}
}

View File

@ -0,0 +1,168 @@
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_state.dart';
import 'package:syncrow_app/features/devices/model/device_model.dart';
import 'package:syncrow_app/features/devices/model/one_gang_model.dart';
import 'package:syncrow_app/features/devices/view/widgets/one_gang/one_gang_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_small.dart';
import 'package:syncrow_app/utils/context_extension.dart';
import 'package:syncrow_app/utils/resource_manager/color_manager.dart';
import '../../../bloc/one_gang_bloc/one_gang_event.dart';
class OneGangScreen extends StatelessWidget {
const OneGangScreen({super.key, this.device});
final DeviceModel? device;
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => OneGangBloc(oneGangId : device?.uuid ?? '')
..add(const InitialEvent(groupScreen:false)),
child: BlocBuilder<OneGangBloc, OneGangState>(
builder: (context, state) {
OneGangModel oneGangModel = OneGangModel(
firstSwitch: false,
firstCountDown: 0,
);
if (state is LoadingNewSate) {
oneGangModel = state.oneGangModel;
} else if (state is UpdateState) {
oneGangModel = state.oneGangModel;
}
return state is LoadingInitialState ?
const Center(
child:
DefaultContainer(width: 50, height: 50, child: CircularProgressIndicator()),
):
RefreshIndicator(
onRefresh: () async {
BlocProvider.of<OneGangBloc>(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: oneGangModel.firstSwitch,
action: () {
BlocProvider.of<OneGangBloc>(context).add(
ChangeFirstSwitchStatusEvent(
value: oneGangModel.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: [
const SizedBox(
width: 20,
),
Column(
mainAxisSize: MainAxisSize.min,
children: [
Card(
elevation: 3,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(100),
),
child: GestureDetector(
onTap: () {
Navigator.push(
context,
PageRouteBuilder(
pageBuilder: (context, animation1, animation2) => OneGangTimerScreen(
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.access_time,
color:
ColorsManager.primaryColorWithOpacity,
size: 25,
),
),
),
],
),
),
),
const SizedBox(height: 10),
BodySmall(
text: "Timer",
style: context.bodyMedium.copyWith(
color: ColorsManager.textPrimaryColor,
),
),
],
),
],
),
),
Expanded(child: SizedBox())
],
),
),
],
),
);
},
),
);
}
}

View File

@ -0,0 +1,184 @@
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_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/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/font_manager.dart';
class OneGangTimerScreen extends StatelessWidget {
final DeviceModel? device;
final String? deviceCode;
const OneGangTimerScreen({super.key,this.device,this.deviceCode});
@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: AppBar(
backgroundColor: Colors.transparent,
centerTitle: true,
title: const BodyLarge(
text: 'Schedule',
fontColor: ColorsManager.primaryColor,
fontWeight: FontsManager.bold,
),
),
body: SafeArea(
child: BlocProvider(
create: (context) => OneGangBloc(oneGangId: device!.uuid ?? '')
..add(GetCounterEvent(deviceCode: deviceCode!)),
child: BlocBuilder<OneGangBloc, OneGangState>(
builder: (context, state) {
Duration duration = Duration.zero;
int countNum = 0;
int tabNum = 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;
}
if (state is ChangeSlidingSegmentState) {
tabNum = state.value;
}
return PopScope(
canPop: false,
onPopInvoked: (didPop) {
if (!didPop) {
BlocProvider.of<OneGangBloc>(context).add(OnClose());
Navigator.pop(context);
}
},
child: Container(
width: MediaQuery.sizeOf(context).width,
height: MediaQuery.sizeOf(context).height,
padding: const EdgeInsets.all(24),
decoration: const BoxDecoration(
image: DecorationImage(
image: AssetImage(
Assets.assetsImagesBackground,
),
fit: BoxFit.cover,
opacity: 0.4,
),
),
child: state is LoadingInitialState || state is LoadingNewSate
? const Center(
child: DefaultContainer(
width: 50, height: 50, child: CircularProgressIndicator()),
)
: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Container(
width: MediaQuery.sizeOf(context).width,
decoration: const ShapeDecoration(
color: ColorsManager.slidingBlueColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(20)),
),
),
child: CupertinoSlidingSegmentedControl<int>(
thumbColor: ColorsManager.slidingBlueColor,
onValueChanged: (value) {
BlocProvider.of<OneGangBloc>(context)
.add(ChangeSlidingSegment(value: value ?? 0));
},
groupValue:
state is ChangeSlidingSegmentState ? state.value : 0,
backgroundColor: Colors.white,
children: <int, Widget>{
0: Container(
padding: const EdgeInsets.symmetric(vertical: 10),
child: BodySmall(
text: 'Countdown',
style: context.bodySmall.copyWith(
color: ColorsManager.blackColor,
fontSize: 12,
fontWeight: FontWeight.w400,
),
),
),
1: Container(
padding: const EdgeInsets.symmetric(vertical: 10),
child: Text(
'Schedule',
style: context.bodySmall.copyWith(
color: ColorsManager.blackColor,
fontSize: 12,
fontWeight: FontWeight.w400,
),
),
),
},
),
),
if (tabNum == 0)
countNum > 0
? BodyLarge(
text: _formatDuration(countNum),
fontColor: ColorsManager.slidingBlueColor,
fontSize: 40,
)
: CupertinoTimerPicker(
mode: CupertinoTimerPickerMode.hm,
onTimerDurationChanged: (Duration newDuration) {
duration = newDuration;
},
),
if (tabNum == 0)
GestureDetector(
onTap: () {
if (state is LoadingNewSate) {
return;
}
if (countNum > 0) {
BlocProvider.of<OneGangBloc>(context).add(
SetCounterValue(
deviceCode: deviceCode!,
duration: Duration.zero));
} else if (duration != Duration.zero) {
BlocProvider.of<OneGangBloc>(context).add(
SetCounterValue(
deviceCode: deviceCode!, duration: duration));
}
},
child: SvgPicture.asset(
countNum > 0 ? Assets.pauseIcon : Assets.playIcon))
],
)));
},
),
),
),
),
);
}
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

@ -11,6 +11,10 @@ import 'package:syncrow_app/features/devices/view/widgets/ACs/acs_view.dart';
import 'package:syncrow_app/features/devices/view/widgets/curtains/curtain_view.dart';
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_gang/one_gang_screen.dart';
import 'package:syncrow_app/features/devices/view/widgets/two_gang/two_gang_Interface.dart';
import 'package:syncrow_app/features/devices/view/widgets/two_gang/two_gang_screen.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';
import 'package:syncrow_app/features/devices/view/widgets/smart_door/door_interface.dart';
@ -126,13 +130,24 @@ void showDeviceInterface(DeviceModel device, BuildContext context) {
break;
case DeviceType.LightBulb:
navigateToInterface(LightInterface(light: device), context);
case DeviceType.OneGang:
Navigator.push(
context,
PageRouteBuilder(
pageBuilder: (context, animation1, animation2) =>
OneGangInterface(gangSwitch: device)));
case DeviceType.TwoGang:
Navigator.push(
context,
PageRouteBuilder(
pageBuilder: (context, animation1, animation2) =>
TwoGangInterface(gangSwitch: device)));
case DeviceType.ThreeGang:
Navigator.push(
context,
PageRouteBuilder(
pageBuilder: (context, animation1, animation2) =>
ThreeGangInterface(gangSwitch: device)));
// navigateToInterface(ThreeGangInterface(gangSwitch: device), context);
break;
default:
}

View File

@ -1,8 +1,8 @@
import 'package:flutter/material.dart';
// import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/svg.dart';
// import 'package:syncrow_app/features/devices/bloc/three_gang_bloc/three_gang_bloc.dart';
// import 'package:syncrow_app/features/devices/bloc/three_gang_bloc/three_gang_state.dart';
// import 'package:syncrow_app/features/devices/bloc/three_gang_bloc/two_gang_bloc.dart';
// import 'package:syncrow_app/features/devices/bloc/three_gang_bloc/two_gang_state.dart';
import 'package:syncrow_app/features/devices/model/device_model.dart';
import 'package:syncrow_app/generated/assets.dart';
import 'package:syncrow_app/utils/resource_manager/color_manager.dart';

View File

@ -0,0 +1,67 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:syncrow_app/features/devices/model/device_model.dart';
import 'package:syncrow_app/features/devices/view/widgets/device_appbar.dart';
import 'package:syncrow_app/features/devices/view/widgets/two_gang/two_gang_screen.dart';
import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart';
import 'package:syncrow_app/generated/assets.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 TwoGangInterface extends StatelessWidget {
const TwoGangInterface({super.key, this.gangSwitch});
final DeviceModel? gangSwitch;
@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: gangSwitch != null
? DeviceAppbar(
deviceName: gangSwitch!.name!,
deviceUuid: gangSwitch!.uuid!,
)
: AppBar(
backgroundColor: Colors.transparent,
centerTitle: true,
title: BodyLarge(
text: gangSwitch?.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: TwoGangScreen(device: gangSwitch),
),
),
),
),
);
}
}

View File

@ -0,0 +1,81 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_app/features/devices/bloc/three_gang_bloc/three_gang_bloc.dart';
import 'package:syncrow_app/features/devices/bloc/three_gang_bloc/three_gang_event.dart';
import 'package:syncrow_app/features/devices/bloc/three_gang_bloc/three_gang_state.dart';
import 'package:syncrow_app/features/devices/model/groupTwoGangModel.dart';
import 'package:syncrow_app/features/devices/model/group_three_gang_model.dart';
import 'package:syncrow_app/features/shared_widgets/devices_default_switch.dart';
import 'package:syncrow_app/features/shared_widgets/text_widgets/body_small.dart';
class TwoGangList extends StatelessWidget {
const TwoGangList({super.key, required this.twoGangList, required this.allSwitches});
final List<GroupTwoGangModel> twoGangList;
final bool allSwitches;
@override
Widget build(BuildContext context) {
return BlocBuilder<ThreeGangBloc, ThreeGangState>(
builder: (context, state) {
return SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const SizedBox(height: 10),
const BodySmall(text: 'All Lights'),
const SizedBox(height: 5),
DevicesDefaultSwitch(
switchValue: allSwitches,
action: () {
BlocProvider.of<ThreeGangBloc>(context).add(GroupAllOnEvent());
},
secondAction: () {
BlocProvider.of<ThreeGangBloc>(context).add(GroupAllOffEvent());
},
),
ListView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
padding: const EdgeInsets.all(0),
itemCount: twoGangList.length,
itemBuilder: (context, index) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 10),
BodySmall(text: '${twoGangList[index].deviceName} beside light'),
const SizedBox(height: 5),
DevicesDefaultSwitch(
switchValue: twoGangList[index].firstSwitch,
action: () {
BlocProvider.of<ThreeGangBloc>(context).add(ChangeFirstSwitchStatusEvent(
value: twoGangList[index].firstSwitch,
deviceId: twoGangList[index].deviceId));
},
),
const SizedBox(height: 10),
BodySmall(text: '${twoGangList[index].deviceName} ceiling light'),
const SizedBox(height: 5),
DevicesDefaultSwitch(
switchValue: twoGangList[index].secondSwitch,
action: () {
BlocProvider.of<ThreeGangBloc>(context).add(ChangeSecondSwitchStatusEvent(
value: twoGangList[index].secondSwitch,
deviceId: twoGangList[index].deviceId));
},
),
const SizedBox(height: 10),
BodySmall(text: '${twoGangList[index].deviceName} spotlight'),
const SizedBox(height: 5),
],
);
},
),
],
),
);
},
);
}
}

View File

@ -0,0 +1,340 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_app/features/devices/bloc/two_gang_bloc/two_gang_bloc.dart';
import 'package:syncrow_app/features/devices/bloc/two_gang_bloc/two_gang_event.dart';
import 'package:syncrow_app/features/devices/bloc/two_gang_bloc/two_gang_state.dart';
import 'package:syncrow_app/features/devices/model/device_model.dart';
import 'package:syncrow_app/features/devices/model/groupTwoGangModel.dart';
import 'package:syncrow_app/features/devices/model/two_gang_model.dart';
import 'package:syncrow_app/features/devices/view/widgets/three_gang/gang_switch.dart';
import 'package:syncrow_app/features/devices/view/widgets/two_gang/two_gang_list.dart';
import 'package:syncrow_app/features/devices/view/widgets/two_gang/two_schedule_screen.dart';
import 'package:syncrow_app/features/shared_widgets/default_container.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 TwoGangScreen extends StatelessWidget {
const TwoGangScreen({super.key, this.device});
final DeviceModel? device;
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => TwoGangBloc(twoGangId: device?.uuid ?? '')
..add(InitialEvent(groupScreen: device != null ? false : true)),
child: BlocBuilder<TwoGangBloc, TwoGangState>(
builder: (context, state) {
TwoGangModel twoGangModel = TwoGangModel(
firstSwitch: false,
secondSwitch: false,
firstCountDown: 0,
secondCountDown: 0,
);
List<GroupTwoGangModel> groupTwoGangModel = [];
bool allSwitchesOn = false;
if (state is LoadingNewSate) {
twoGangModel = state.twoGangModel;
} else if (state is UpdateState) {
twoGangModel = state.twoGangModel;
} else if (state is UpdateGroupState) {
groupTwoGangModel = state.twoGangList;
allSwitchesOn = state.allSwitches;
}
return state is LoadingInitialState
? const Center(
child:
DefaultContainer(width: 50, height: 50, child: CircularProgressIndicator()),
)
: device == null
? TwoGangList(
twoGangList: groupTwoGangModel,
allSwitches: allSwitchesOn,
)
: RefreshIndicator(
onRefresh: () async {
BlocProvider.of<TwoGangBloc>(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: twoGangModel.firstSwitch,
action: () {
BlocProvider.of<TwoGangBloc>(context).add(
ChangeFirstSwitchStatusEvent(
value: twoGangModel.firstSwitch));
},
),
const SizedBox(height: 20),
const SizedBox(
width: 77,
child: BodySmall(
text: "Cove Light",
fontColor: ColorsManager.textPrimaryColor,
textAlign: TextAlign.center,
),
),
],
),
Column(
children: [
GangSwitch(
threeGangSwitch: device!,
value: twoGangModel.secondSwitch,
action: () {
BlocProvider.of<TwoGangBloc>(context).add(
ChangeSecondSwitchStatusEvent(
value: twoGangModel.secondSwitch));
},
),
const SizedBox(height: 20),
const SizedBox(
width: 77,
child: BodySmall(
text: "Chandelier",
fontColor: ColorsManager.textPrimaryColor,
textAlign: TextAlign.center,
),
),
],
),
],
),
),
Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Column(
mainAxisSize: MainAxisSize.min,
children: [
Card(
elevation: 3,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(100),
),
child: GestureDetector(
onTap: () {
BlocProvider.of<TwoGangBloc>(context)
.add(AllOnEvent());
},
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: BodySmall(
text: "On",
style: context.bodyMedium.copyWith(
color: ColorsManager
.primaryColorWithOpacity,
fontWeight: FontWeight.bold),
),
),
),
],
),
),
),
const SizedBox(height: 10),
BodySmall(
text: "All On",
style: context.bodyMedium.copyWith(
color: ColorsManager.textPrimaryColor,
),
),
],
),
const SizedBox(
width: 20,
),
Column(
mainAxisSize: MainAxisSize.min,
children: [
Card(
elevation: 3,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(100),
),
child: GestureDetector(
onTap: () {
Navigator.push(
context,
PageRouteBuilder(
pageBuilder:
(context, animation1, animation2) =>
TwoGangScheduleScreen(
device: device!,
)));
},
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.access_time,
color:
ColorsManager.primaryColorWithOpacity,
size: 25,
),
),
),
],
),
),
),
const SizedBox(height: 10),
BodySmall(
text: "Timer",
style: context.bodyMedium.copyWith(
color: ColorsManager.textPrimaryColor,
),
),
],
),
const SizedBox(
width: 20,
),
Column(
mainAxisSize: MainAxisSize.min,
children: [
Card(
elevation: 3,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(100),
),
child: GestureDetector(
onTap: () {
// DevicesCubit.getInstance().deviceControl(
// DeviceControlModel(
// deviceId: device.uuid,
// code: 'switch_1',
// value: false,
// ),
// device.uuid!,
// );
// DevicesCubit.getInstance().deviceControl(
// DeviceControlModel(
// deviceId: device.uuid,
// code: 'switch_2',
// value: false,
// ),
// device.uuid!,
// );
// DevicesCubit.getInstance().deviceControl(
// DeviceControlModel(
// deviceId: device.uuid,
// code: 'switch_3',
// value: false,
// ),
// device.uuid!,
// );
BlocProvider.of<TwoGangBloc>(context)
.add(AllOffEvent());
},
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: BodySmall(
text: "Off",
style: context.bodyMedium.copyWith(
color: ColorsManager
.primaryColorWithOpacity,
fontWeight: FontWeight.bold),
),
),
),
],
),
),
),
const SizedBox(height: 10),
BodySmall(
text: "All Off",
style: context.bodyMedium.copyWith(
color: ColorsManager.textPrimaryColor,
),
),
],
),
],
),
),
Expanded(child: SizedBox())
],
),
),
],
),
);
},
),
);
}
}

View File

@ -0,0 +1,139 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:syncrow_app/features/devices/model/device_model.dart';
import 'package:syncrow_app/features/devices/view/widgets/two_gang/two_timer_screen.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/font_manager.dart';
class TwoGangScheduleScreen extends StatelessWidget {
final DeviceModel device;
const TwoGangScheduleScreen({required this.device, super.key});
@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: AppBar(
backgroundColor: Colors.transparent,
centerTitle: true,
title: const BodyLarge(
text: 'Schedule',
fontColor: ColorsManager.primaryColor,
fontWeight: FontsManager.bold,
),
),
body: SafeArea(
child: 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: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(
height: 48,
),
DefaultContainer(
width: MediaQuery.sizeOf(context).width,
child: Column(
children: [
InkWell(
onTap: () {
Navigator.push(
context,
PageRouteBuilder(
pageBuilder: (context, animation1, animation2) => TwoTimerScreen(
device: device,
deviceCode: 'countdown_1',
)));
},
child: Container(
padding:
const EdgeInsets.only(left: 25, right: 15, top: 20, bottom: 20),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
BodySmall(
text: "Bedside Light",
style: context.bodyMedium.copyWith(
color: ColorsManager.textPrimaryColor,
fontSize: 15,
fontWeight: FontWeight.w400,
),
),
const Icon(
Icons.arrow_forward_ios,
color: ColorsManager.greyColor,
size: 18,
)
],
),
),
),
const Divider(
color: ColorsManager.dividerColor,
),
InkWell(
onTap: () {
Navigator.push(
context,
PageRouteBuilder(
pageBuilder: (context, animation1, animation2) => TwoTimerScreen(
device: device,
deviceCode: 'countdown_2',
)));
},
child: Container(
padding:
const EdgeInsets.only(left: 25, right: 15, top: 20, bottom: 20),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
BodySmall(
text: "Ceiling Light",
style: context.bodyMedium.copyWith(
color: ColorsManager.textPrimaryColor,
fontSize: 15,
fontWeight: FontWeight.w400,
),
),
const Icon(
Icons.arrow_forward_ios,
color: ColorsManager.greyColor,
size: 18,
)
],
),
),
),
],
)),
],
),
),
),
));
}
}

View File

@ -0,0 +1,187 @@
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/flutter_svg.dart';
import 'package:syncrow_app/features/devices/bloc/two_gang_bloc/two_gang_bloc.dart';
import 'package:syncrow_app/features/devices/bloc/two_gang_bloc/two_gang_state.dart';
import 'package:syncrow_app/features/devices/model/device_model.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/font_manager.dart';
import '../../../bloc/two_gang_bloc/two_gang_event.dart';
class TwoTimerScreen extends StatelessWidget {
final DeviceModel device;
final String deviceCode;
const TwoTimerScreen({required this.device, required this.deviceCode, super.key});
@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: AppBar(
backgroundColor: Colors.transparent,
centerTitle: true,
title: const BodyLarge(
text: 'Schedule',
fontColor: ColorsManager.primaryColor,
fontWeight: FontsManager.bold,
),
),
body: SafeArea(
child: BlocProvider(
create: (context) => TwoGangBloc(twoGangId: device.uuid ?? '')
..add(GetCounterEvent(deviceCode: deviceCode)),
child: BlocBuilder<TwoGangBloc, TwoGangState>(
builder: (context, state) {
Duration duration = Duration.zero;
int countNum = 0;
int tabNum = 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;
}
if (state is ChangeSlidingSegmentState) {
tabNum = state.value;
}
return PopScope(
canPop: false,
onPopInvoked: (didPop) {
if (!didPop) {
BlocProvider.of<TwoGangBloc>(context).add(OnClose());
Navigator.pop(context);
}
},
child: Container(
width: MediaQuery.sizeOf(context).width,
height: MediaQuery.sizeOf(context).height,
padding: const EdgeInsets.all(24),
decoration: const BoxDecoration(
image: DecorationImage(
image: AssetImage(
Assets.assetsImagesBackground,
),
fit: BoxFit.cover,
opacity: 0.4,
),
),
child: state is LoadingInitialState || state is LoadingNewSate
? const Center(
child: DefaultContainer(
width: 50, height: 50, child: CircularProgressIndicator()),
)
: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Container(
width: MediaQuery.sizeOf(context).width,
decoration: const ShapeDecoration(
color: ColorsManager.slidingBlueColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(20)),
),
),
child: CupertinoSlidingSegmentedControl<int>(
thumbColor: ColorsManager.slidingBlueColor,
onValueChanged: (value) {
BlocProvider.of<TwoGangBloc>(context)
.add(ChangeSlidingSegment(value: value ?? 0));
},
groupValue:
state is ChangeSlidingSegmentState ? state.value : 0,
backgroundColor: Colors.white,
children: <int, Widget>{
0: Container(
padding: const EdgeInsets.symmetric(vertical: 10),
child: BodySmall(
text: 'Countdown',
style: context.bodySmall.copyWith(
color: ColorsManager.blackColor,
fontSize: 12,
fontWeight: FontWeight.w400,
),
),
),
1: Container(
padding: const EdgeInsets.symmetric(vertical: 10),
child: Text(
'Schedule',
style: context.bodySmall.copyWith(
color: ColorsManager.blackColor,
fontSize: 12,
fontWeight: FontWeight.w400,
),
),
),
},
),
),
if (tabNum == 0)
countNum > 0
? BodyLarge(
text: _formatDuration(countNum),
fontColor: ColorsManager.slidingBlueColor,
fontSize: 40,
)
: CupertinoTimerPicker(
mode: CupertinoTimerPickerMode.hm,
onTimerDurationChanged: (Duration newDuration) {
duration = newDuration;
},
),
if (tabNum == 0)
GestureDetector(
onTap: () {
if (state is LoadingNewSate) {
return;
}
if (countNum > 0) {
BlocProvider.of<TwoGangBloc>(context).add(
SetCounterValue(
deviceCode: deviceCode,
duration: Duration.zero));
} else if (duration != Duration.zero) {
BlocProvider.of<TwoGangBloc>(context).add(
SetCounterValue(
deviceCode: deviceCode, duration: duration));
}
},
child: SvgPicture.asset(
countNum > 0 ? Assets.pauseIcon : Assets.playIcon))
],
)));
},
),
),
),
),
);
}
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

@ -1029,4 +1029,10 @@ class Assets {
static const String assetsPresenceState =
"assets/icons/functions_icons/automation_functions/presence_state.svg";
//assets/icons/functions_icons/automation_functions/presence_state.svg
static const String oneGang =
"assets/icons/1gang.svg";
}

View File

@ -43,6 +43,8 @@ enum DeviceType {
DoorLock,
Curtain,
Blind,
OneGang,
TwoGang,
ThreeGang,
Gateway,
CeilingSensor,
@ -68,6 +70,8 @@ Map<String, DeviceType> devicesTypesMap = {
"DL": DeviceType.DoorLock,
"WPS": DeviceType.WallSensor,
"3G": DeviceType.ThreeGang,
"2G": DeviceType.TwoGang,
"1G": DeviceType.OneGang,
"CUR": DeviceType.Curtain,
};
Map<DeviceType, List<FunctionModel>> devicesFunctionsMap = {
@ -155,6 +159,33 @@ Map<DeviceType, List<FunctionModel>> devicesFunctionsMap = {
FunctionModel(
code: 'indicator', type: functionTypesMap['Boolean'], values: ValueModel.fromJson({})),
],
DeviceType.OneGang:[
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})),
],
DeviceType.TwoGang:[
FunctionModel(
code: 'switch_1', type: functionTypesMap['Boolean'], values: ValueModel.fromJson({})
),
FunctionModel(
code: 'switch_2', 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: 'countdown_2',
type: functionTypesMap['Integer'],
values: ValueModel.fromJson({"unit": "s", "min": 0, "max": 43200, "scale": 0, "step": 1})
),
],
DeviceType.ThreeGang: [
FunctionModel(
code: 'switch_1', type: functionTypesMap['Boolean'], values: ValueModel.fromJson({})),