From 3d79cd41fff4984b993fbc5f34586464f2f33cb6 Mon Sep 17 00:00:00 2001 From: Tommy Chow Date: Tue, 22 Mar 2022 17:07:49 -0400 Subject: [PATCH] Implement sleep timer functionality (#84) --- lib/screens/channel/stores/video_store.dart | 51 ++++++++++++++ lib/screens/channel/stores/video_store.g.dart | 48 ++++++++++++++ lib/screens/channel/video/video_overlay.dart | 66 ++++++++++++++++++- 3 files changed, 164 insertions(+), 1 deletion(-) diff --git a/lib/screens/channel/stores/video_store.dart b/lib/screens/channel/stores/video_store.dart index 167d91d2..a861bdd6 100644 --- a/lib/screens/channel/stores/video_store.dart +++ b/lib/screens/channel/stores/video_store.dart @@ -1,4 +1,6 @@ import 'dart:async'; +import 'dart:io'; + import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:frosty/api/twitch_api.dart'; @@ -19,6 +21,17 @@ abstract class _VideoStoreBase with Store { late Timer _overlayTimer; + Timer? sleepTimer; + + @observable + var sleepHours = 0; + + @observable + var sleepMinutes = 0; + + @observable + var timeRemaining = const Duration(); + @readonly var _paused = true; @@ -149,4 +162,42 @@ abstract class _VideoStoreBase with Store { debugPrint(e.toString()); } } + + /// Updates the sleep timer with [sleepHours] and [sleepMinutes]. + void updateSleepTimer() { + // If hours and minutes are 0, do nothing. + if (sleepHours == 0 && sleepMinutes == 0) return; + + // If there is an ongoing timer, cancel it since it'll be replaced. + if (sleepTimer != null) cancelSleepTimer(); + + // Update the new time remaining + timeRemaining = Duration(hours: sleepHours, minutes: sleepMinutes); + + // Reset the hours and minutes in the dropdown buttons. + sleepHours = 0; + sleepMinutes = 0; + + // Set a periodic timer that will update the time remaining every second. + sleepTimer = Timer.periodic( + const Duration(seconds: 1), + (timer) { + // If the timer is up, cancel the timer and exit the app. + if (timeRemaining.inSeconds == 0) { + timer.cancel(); + Platform.isIOS ? exit(0) : SystemNavigator.pop(); + return; + } + + // Decrement the time remaining. + timeRemaining = Duration(seconds: timeRemaining.inSeconds - 1); + }, + ); + } + + /// Cancels the sleep timer and resets the time remaining. + void cancelSleepTimer() { + sleepTimer?.cancel(); + timeRemaining = const Duration(); + } } diff --git a/lib/screens/channel/stores/video_store.g.dart b/lib/screens/channel/stores/video_store.g.dart index a3ea06e4..3563099e 100644 --- a/lib/screens/channel/stores/video_store.g.dart +++ b/lib/screens/channel/stores/video_store.g.dart @@ -17,6 +17,51 @@ mixin _$VideoStore on _VideoStoreBase, Store { name: '_VideoStoreBase.videoUrl')) .value; + final _$sleepHoursAtom = Atom(name: '_VideoStoreBase.sleepHours'); + + @override + int get sleepHours { + _$sleepHoursAtom.reportRead(); + return super.sleepHours; + } + + @override + set sleepHours(int value) { + _$sleepHoursAtom.reportWrite(value, super.sleepHours, () { + super.sleepHours = value; + }); + } + + final _$sleepMinutesAtom = Atom(name: '_VideoStoreBase.sleepMinutes'); + + @override + int get sleepMinutes { + _$sleepMinutesAtom.reportRead(); + return super.sleepMinutes; + } + + @override + set sleepMinutes(int value) { + _$sleepMinutesAtom.reportWrite(value, super.sleepMinutes, () { + super.sleepMinutes = value; + }); + } + + final _$timeRemainingAtom = Atom(name: '_VideoStoreBase.timeRemaining'); + + @override + Duration get timeRemaining { + _$timeRemainingAtom.reportRead(); + return super.timeRemaining; + } + + @override + set timeRemaining(Duration value) { + _$timeRemainingAtom.reportWrite(value, super.timeRemaining, () { + super.timeRemaining = value; + }); + } + final _$_pausedAtom = Atom(name: '_VideoStoreBase._paused'); bool get paused { @@ -124,6 +169,9 @@ mixin _$VideoStore on _VideoStoreBase, Store { @override String toString() { return ''' +sleepHours: ${sleepHours}, +sleepMinutes: ${sleepMinutes}, +timeRemaining: ${timeRemaining}, videoUrl: ${videoUrl} '''; } diff --git a/lib/screens/channel/video/video_overlay.dart b/lib/screens/channel/video/video_overlay.dart index 8cfe8714..52995da9 100644 --- a/lib/screens/channel/video/video_overlay.dart +++ b/lib/screens/channel/video/video_overlay.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:io'; import 'package:flutter/material.dart'; @@ -12,6 +13,69 @@ class VideoOverlay extends StatelessWidget { const VideoOverlay({Key? key, required this.videoStore}) : super(key: key); + Future _showSleepTimerDialog(BuildContext context) { + return showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('Sleep Timer'), + content: Observer( + builder: (context) => Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + DropdownButton( + value: videoStore.sleepHours, + items: List.generate(24, (index) => index).map((e) => DropdownMenuItem(value: e, child: Text(e.toString()))).toList(), + onChanged: (int? hours) => videoStore.sleepHours = hours!, + menuMaxHeight: 200, + ), + const SizedBox(width: 10.0), + const Text('Hours'), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + DropdownButton( + value: videoStore.sleepMinutes, + items: List.generate(60, (index) => index).map((e) => DropdownMenuItem(value: e, child: Text(e.toString()))).toList(), + onChanged: (int? minutes) => videoStore.sleepMinutes = minutes!, + menuMaxHeight: 200, + ), + const SizedBox(width: 10.0), + const Text('Minutes'), + ], + ), + Row( + children: [ + Text('Timer: ${videoStore.timeRemaining.toString().split('.')[0]}'), + const Spacer(), + IconButton( + onPressed: videoStore.cancelSleepTimer, + icon: const Icon(Icons.cancel), + ), + ], + ), + ], + ), + ), + actions: [ + TextButton( + onPressed: Navigator.of(context).pop, + child: const Text('Close'), + ), + TextButton( + onPressed: videoStore.updateSleepTimer, + child: const Text('Set Timer'), + style: TextButton.styleFrom(primary: Colors.green), + ), + ], + ), + ); + } + @override Widget build(BuildContext context) { final orientation = MediaQuery.of(context).orientation; @@ -70,7 +134,7 @@ class VideoOverlay extends StatelessWidget { final sleepTimerButton = IconButton( tooltip: 'Sleep Timer', icon: const Icon(Icons.timer), - onPressed: () {}, + onPressed: () => _showSleepTimerDialog(context), ); final streamInfo = videoStore.streamInfo;