diff --git a/assets/icons/pause_ic.svg b/assets/icons/pause_ic.svg new file mode 100644 index 0000000..c6f6d5c --- /dev/null +++ b/assets/icons/pause_ic.svg @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/play_ic.svg b/assets/icons/play_ic.svg new file mode 100644 index 0000000..e9242b0 --- /dev/null +++ b/assets/icons/play_ic.svg @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/features/devices/bloc/three_gang_bloc/three_gang_bloc.dart b/lib/features/devices/bloc/three_gang_bloc/three_gang_bloc.dart index be2df58..e582850 100644 --- a/lib/features/devices/bloc/three_gang_bloc/three_gang_bloc.dart +++ b/lib/features/devices/bloc/three_gang_bloc/three_gang_bloc.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:flutter_bloc/flutter_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'; @@ -9,6 +11,7 @@ import 'package:syncrow_app/services/api/devices_api.dart'; class ThreeGangBloc extends Bloc { final String threeGangId; late ThreeGangModel deviceStatus; + Timer? _timer; ThreeGangBloc({required this.threeGangId}) : super(InitialState()) { on(_fetchThreeGangStatus); @@ -17,6 +20,10 @@ class ThreeGangBloc extends Bloc { on(_changeThirdSwitch); on(_allOff); on(_allOn); + on(_changeSliding); + on(_setCounterValue); + on(_getCounterValue); + on(_onTickTimer); } void _fetchThreeGangStatus(InitialEvent event, Emitter emit) async { @@ -121,4 +128,74 @@ class ThreeGangBloc extends Bloc { } catch (_) {} emit(UpdateState(threeGangModel: deviceStatus)); } + + void _changeSliding(ChangeSlidingSegment event, Emitter emit) async { + emit(ChangeSlidingSegmentState(value: event.value)); + } + + void _setCounterValue(SetCounterValue event, Emitter emit) async { + emit(LoadingNewSate(threeGangModel: deviceStatus)); + try { + int seconds = (event.duration.inHours * 60 * 60) + (event.duration.inMinutes * 60); + final response = await DevicesAPI.controlDevice( + DeviceControlModel(deviceId: threeGangId, code: event.deviceCode, value: seconds), + threeGangId); + + if (response['success'] ?? false) { + if (event.deviceCode == 'countdown_1') { + deviceStatus.firstCountDown = seconds; + } else if (event.deviceCode == 'countdown_2') { + deviceStatus.secondCountDown = seconds; + } else if (event.deviceCode == 'countdown_3') { + deviceStatus.thirdCountDown = seconds; + } + } + } catch (_) {} + emit(UpdateState(threeGangModel: deviceStatus)); + } + + void _getCounterValue(GetCounterEvent event, Emitter emit) async { + emit(LoadingInitialState()); + try { + var response = await DevicesAPI.getDeviceStatus(threeGangId); + List statusModelList = []; + for (var status in response['status']) { + statusModelList.add(StatusModel.fromJson(status)); + } + deviceStatus = ThreeGangModel.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)); + } else if (event.deviceCode == 'countdown_3') { + deviceStatus.thirdCountDown > 0 + ? _onStartTimer(deviceStatus.thirdCountDown) + : emit(UpdateTimerState(seconds: deviceStatus.thirdCountDown)); + } + } catch (e) { + emit(FailedState(error: e.toString())); + return; + } + } + + void _onStartTimer(int seconds) { + _timer?.cancel(); + _timer = Timer.periodic(const Duration(seconds: 1), (timer) { + add(TickTimer(seconds - timer.tick)); + }); + } + + void _onTickTimer(TickTimer event, Emitter emit) { + if (event.remainingTime > 0) { + emit(TimerRunInProgress(event.remainingTime)); + } else { + _timer?.cancel(); + emit(TimerRunComplete()); + } + } } diff --git a/lib/features/devices/bloc/three_gang_bloc/three_gang_event.dart b/lib/features/devices/bloc/three_gang_bloc/three_gang_event.dart index b7e799c..a7ff0eb 100644 --- a/lib/features/devices/bloc/three_gang_bloc/three_gang_event.dart +++ b/lib/features/devices/bloc/three_gang_bloc/three_gang_event.dart @@ -35,3 +35,45 @@ class ChangeThirdSwitchStatusEvent extends ThreeGangEvent { class AllOffEvent extends ThreeGangEvent {} class AllOnEvent extends ThreeGangEvent {} + +class ChangeSlidingSegment extends ThreeGangEvent { + final int value; + const ChangeSlidingSegment({required this.value}); + @override + List get props => [value]; +} + +class GetCounterEvent extends ThreeGangEvent { + final String deviceCode; + const GetCounterEvent({required this.deviceCode}); + @override + List get props => [deviceCode]; +} + +class SetCounterValue extends ThreeGangEvent { + final Duration duration; + final String deviceCode; + const SetCounterValue({required this.duration, required this.deviceCode}); + @override + List get props => [duration, deviceCode]; +} + +class StartTimer extends ThreeGangEvent { + final int duration; + + const StartTimer(this.duration); + + @override + List get props => [duration]; +} + +class TickTimer extends ThreeGangEvent { + final int remainingTime; + + const TickTimer(this.remainingTime); + + @override + List get props => [remainingTime]; +} + +class StopTimer extends ThreeGangEvent {} diff --git a/lib/features/devices/bloc/three_gang_bloc/three_gang_state.dart b/lib/features/devices/bloc/three_gang_bloc/three_gang_state.dart index 41b0a31..a124c7c 100644 --- a/lib/features/devices/bloc/three_gang_bloc/three_gang_state.dart +++ b/lib/features/devices/bloc/three_gang_bloc/three_gang_state.dart @@ -36,3 +36,31 @@ class FailedState extends ThreeGangState { @override List get props => [error]; } + +class ChangeSlidingSegmentState extends ThreeGangState { + final int value; + + const ChangeSlidingSegmentState({required this.value}); + + @override + List get props => [value]; +} + +class UpdateTimerState extends ThreeGangState { + final int seconds; + const UpdateTimerState({required this.seconds}); + + @override + List get props => [seconds]; +} + +class TimerRunInProgress extends ThreeGangState { + final int remainingTime; + + const TimerRunInProgress(this.remainingTime); + + @override + List get props => [remainingTime]; +} + +class TimerRunComplete extends ThreeGangState {} diff --git a/lib/features/devices/view/widgets/smart_door/door_button.dart b/lib/features/devices/view/widgets/smart_door/door_button.dart index fd250d5..393f157 100644 --- a/lib/features/devices/view/widgets/smart_door/door_button.dart +++ b/lib/features/devices/view/widgets/smart_door/door_button.dart @@ -1,14 +1,14 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/flutter_svg.dart'; -import 'package:syncrow_app/features/devices/bloc/acs_bloc/acs_event.dart'; -import 'package:syncrow_app/features/devices/bloc/devices_cubit.dart'; +// import 'package:syncrow_app/features/devices/bloc/acs_bloc/acs_event.dart'; +// import 'package:syncrow_app/features/devices/bloc/devices_cubit.dart'; import 'package:syncrow_app/features/devices/bloc/smart_door_bloc/smart_door_bloc.dart'; import 'package:syncrow_app/features/devices/bloc/smart_door_bloc/smart_door_event.dart'; -import 'package:syncrow_app/features/devices/model/device_control_model.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/smart_door_model.dart'; -import 'package:syncrow_app/features/devices/view/widgets/smart_door/door_status_bar.dart'; +// import 'package:syncrow_app/features/devices/view/widgets/smart_door/door_status_bar.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'; diff --git a/lib/features/devices/view/widgets/three_gang/gang_switch.dart b/lib/features/devices/view/widgets/three_gang/gang_switch.dart index e316f30..c0bd8f9 100644 --- a/lib/features/devices/view/widgets/three_gang/gang_switch.dart +++ b/lib/features/devices/view/widgets/three_gang/gang_switch.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.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/three_gang_bloc.dart'; +// import 'package:syncrow_app/features/devices/bloc/three_gang_bloc/three_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'; diff --git a/lib/features/devices/view/widgets/three_gang/schedule_screen.dart b/lib/features/devices/view/widgets/three_gang/schedule_screen.dart new file mode 100644 index 0000000..1c527e3 --- /dev/null +++ b/lib/features/devices/view/widgets/three_gang/schedule_screen.dart @@ -0,0 +1,174 @@ +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/three_gang/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 ScheduleScreen extends StatelessWidget { + final DeviceModel device; + const ScheduleScreen({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) => TimerScreen( + 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) => TimerScreen( + 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, + ) + ], + ), + ), + ), + const Divider( + color: ColorsManager.dividerColor, + ), + InkWell( + onTap: () { + Navigator.push( + context, + PageRouteBuilder( + pageBuilder: (context, animation1, animation2) => TimerScreen( + device: device, + deviceCode: 'countdown_3', + ))); + }, + child: Container( + padding: + const EdgeInsets.only(left: 25, right: 15, top: 20, bottom: 20), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + BodySmall( + text: "Spotlight", + style: context.bodyMedium.copyWith( + color: ColorsManager.textPrimaryColor, + fontSize: 15, + fontWeight: FontWeight.w400, + ), + ), + const Icon( + Icons.arrow_forward_ios, + color: ColorsManager.greyColor, + size: 18, + ) + ], + ), + ), + ), + ], + )), + ], + ), + ), + ), + )); + } +} diff --git a/lib/features/devices/view/widgets/three_gang/three_gang_screen.dart b/lib/features/devices/view/widgets/three_gang/three_gang_screen.dart index 2af3cdc..89709c3 100644 --- a/lib/features/devices/view/widgets/three_gang/three_gang_screen.dart +++ b/lib/features/devices/view/widgets/three_gang/three_gang_screen.dart @@ -6,6 +6,7 @@ import 'package:syncrow_app/features/devices/bloc/three_gang_bloc/three_gang_sta import 'package:syncrow_app/features/devices/model/device_model.dart'; import 'package:syncrow_app/features/devices/model/three_gang_model.dart'; import 'package:syncrow_app/features/devices/view/widgets/three_gang/gang_switch.dart'; +import 'package:syncrow_app/features/devices/view/widgets/three_gang/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'; @@ -228,7 +229,15 @@ class ThreeGangScreen extends StatelessWidget { borderRadius: BorderRadius.circular(100), ), child: GestureDetector( - onTap: () {}, + onTap: () { + Navigator.push( + context, + PageRouteBuilder( + pageBuilder: (context, animation1, animation2) => + ScheduleScreen( + device: device, + ))); + }, child: Stack( alignment: Alignment.center, children: [ diff --git a/lib/features/devices/view/widgets/three_gang/timer_screen.dart b/lib/features/devices/view/widgets/three_gang/timer_screen.dart new file mode 100644 index 0000000..d7c3aa9 --- /dev/null +++ b/lib/features/devices/view/widgets/three_gang/timer_screen.dart @@ -0,0 +1,159 @@ +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/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/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 TimerScreen extends StatelessWidget { + final DeviceModel device; + final String deviceCode; + const TimerScreen({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: 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: BlocProvider( + create: (context) => ThreeGangBloc(threeGangId: device.uuid ?? '') + ..add(GetCounterEvent(deviceCode: deviceCode)), + child: BlocBuilder( + builder: (context, state) { + Duration duration = Duration(); + int countNum = 0; + if (state is UpdateTimerState) { + countNum = state.seconds; + } else if (state is TimerRunInProgress) { + countNum = state.remainingTime; + } + return state is LoadingInitialState + ? 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( + thumbColor: ColorsManager.slidingBlueColor, + onValueChanged: (value) { + BlocProvider.of(context) + .add(ChangeSlidingSegment(value: value ?? 0)); + }, + groupValue: state is ChangeSlidingSegmentState ? state.value : 0, + backgroundColor: Colors.white, + children: { + 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, + ), + ), + ), + }, + ), + ), + countNum > 0 + ? BodyLarge(text: _formatDuration(countNum)) + : Container( + child: CupertinoTimerPicker( + mode: CupertinoTimerPickerMode.hm, + initialTimerDuration: Duration(hours: 2, minutes: 20), + onTimerDurationChanged: (Duration newDuration) { + duration = newDuration; + }, + ), + ), + GestureDetector( + onTap: () { + if (countNum > 0) { + BlocProvider.of(context).add(SetCounterValue( + deviceCode: deviceCode, duration: Duration.zero)); + } else { + BlocProvider.of(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'; + } +} diff --git a/lib/generated/assets.dart b/lib/generated/assets.dart index b7e5fde..fabad48 100644 --- a/lib/generated/assets.dart +++ b/lib/generated/assets.dart @@ -649,6 +649,8 @@ class Assets { static const String blueCheckboxIcon = "assets/icons/blue_checkbox_ic.svg"; static const String emptyCheckboxIcon = "assets/icons/empty_checkbox_ic.svg"; + static const String pauseIcon = "assets/icons/pause_ic.svg"; + static const String playIcon = "assets/icons/play_ic.svg"; /// Assets for assetsImagesAutomation /// assets/images/automation.jpg diff --git a/lib/utils/resource_manager/color_manager.dart b/lib/utils/resource_manager/color_manager.dart index 6675670..b5edf52 100644 --- a/lib/utils/resource_manager/color_manager.dart +++ b/lib/utils/resource_manager/color_manager.dart @@ -4,8 +4,7 @@ abstract class ColorsManager { static const Color textPrimaryColor = Color(0xFF5D5D5D); static const Color switchOffColor = Color(0x7F8D99AE); static const Color primaryColor = Color(0xFF0030CB); - static Color primaryColorWithOpacity = - const Color(0xFF023DFE).withOpacity(0.6); + static Color primaryColorWithOpacity = const Color(0xFF023DFE).withOpacity(0.6); static const Color onPrimaryColor = Colors.white; static const Color secondaryColor = Color(0xFF023DFE); static const Color onSecondaryColor = Color(0xFF023DFE); @@ -18,4 +17,7 @@ abstract class ColorsManager { static const Color relaxColor = Color(0xFFFBD288); static const Color readingColor = Color(0xFFF7D69C); static const Color energizingColor = Color(0xFFEDEDED); + static const Color dividerColor = Color(0xFFEBEBEB); + static const Color slidingBlueColor = Color(0x99023DFE); + static const Color blackColor = Color(0xFF000000); }