// Copyright 2020 Ben Hills and the project contributors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:pinepods_mobile/bloc/podcast/audio_bloc.dart'; import 'package:pinepods_mobile/bloc/settings/settings_bloc.dart'; import 'package:pinepods_mobile/entities/app_settings.dart'; import 'package:pinepods_mobile/entities/sleep.dart'; import 'package:pinepods_mobile/l10n/L.dart'; import 'package:pinepods_mobile/ui/widgets/slider_handle.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; /// This widget allows the user to change the playback speed and toggle audio effects. /// /// The two audio effects, trim silence and volume boost, are currently Android only. class SleepSelectorWidget extends StatefulWidget { const SleepSelectorWidget({ super.key, }); @override State createState() => _SleepSelectorWidgetState(); } class _SleepSelectorWidgetState extends State { @override Widget build(BuildContext context) { final audioBloc = Provider.of(context, listen: false); final settingsBloc = Provider.of(context); var theme = Theme.of(context); return StreamBuilder( stream: settingsBloc.settings, initialData: AppSettings.sensibleDefaults(), builder: (context, snapshot) { return Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, mainAxisSize: MainAxisSize.min, children: [ SizedBox( height: 48.0, width: 48.0, child: Center( child: StreamBuilder( stream: audioBloc.sleepStream, initialData: Sleep(type: SleepType.none), builder: (context, sleepSnapshot) { var sl = ''; if (sleepSnapshot.hasData) { var s = sleepSnapshot.data!; switch(s.type) { case SleepType.none: sl = ''; case SleepType.time: sl = '${L.of(context)!.now_playing_episode_time_remaining} ${SleepSlider.formatDuration(s.timeRemaining)}'; case SleepType.episode: sl = '${L.of(context)!.semantic_current_value_label} ${L.of(context)!.sleep_episode_label}'; } } return IconButton( icon: sleepSnapshot.data?.type != SleepType.none ? Icon( Icons.bedtime, semanticLabel: '${L.of(context)!.sleep_timer_label}. $sl', size: 20.0, ) : Icon( Icons.bedtime_outlined, semanticLabel: L.of(context)!.sleep_timer_label, size: 20.0, ), onPressed: () { showModalBottomSheet( isScrollControlled: true, context: context, backgroundColor: theme.secondaryHeaderColor, barrierLabel: L.of(context)!.scrim_sleep_timer_selector, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.only( topLeft: Radius.circular(16.0), topRight: Radius.circular(16.0), ), ), builder: (context) { return const SleepSlider(); }); }, ); } ), ), ), ], ); }); } } class SleepSlider extends StatefulWidget { const SleepSlider({super.key}); static String formatDuration(Duration duration) { String twoDigits(int n) { if (n >= 10) return '$n'; return '0$n'; } var twoDigitMinutes = twoDigits(duration.inMinutes.remainder(60).toInt()); var twoDigitSeconds = twoDigits(duration.inSeconds.remainder(60).toInt()); return '${twoDigits(duration.inHours)}:$twoDigitMinutes:$twoDigitSeconds'; } @override State createState() => _SleepSliderState(); } class _SleepSliderState extends State { @override Widget build(BuildContext context) { final audioBloc = Provider.of(context, listen: false); return StreamBuilder( stream: audioBloc.sleepStream, initialData: Sleep(type: SleepType.none), builder: (context, snapshot) { var s = snapshot.data; return Column( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.center, children: [ const SliderHandle(), Padding( padding: const EdgeInsets.only(top: 8.0, bottom: 8.0), child: Semantics( header: true, child: Text( L.of(context)!.sleep_timer_label, style: Theme.of(context).textTheme.titleLarge, ), ), ), if (s != null && s.type == SleepType.none) Text( '(${L.of(context)!.sleep_off_label})', semanticsLabel: '${L.of(context)!.semantic_current_value_label} ${L.of(context)!.sleep_off_label}', style: Theme.of(context).textTheme.bodyLarge, ), if (s != null && s.type == SleepType.time) Text( '(${SleepSlider.formatDuration(s.timeRemaining)})', semanticsLabel: '${L.of(context)!.semantic_current_value_label} ${SleepSlider.formatDuration(s.timeRemaining)}', style: Theme.of(context).textTheme.bodyLarge, ), if (s != null && s.type == SleepType.episode) Text( '(${L.of(context)!.sleep_episode_label})', semanticsLabel: '${L.of(context)!.semantic_current_value_label} ${L.of(context)!.sleep_episode_label}', style: Theme.of(context).textTheme.bodyLarge, ), Padding( padding: const EdgeInsets.all(16.0), child: ListView( shrinkWrap: true, children: [ SleepSelectorEntry( sleep: Sleep(type: SleepType.none), current: s, ), const Divider(), SleepSelectorEntry( sleep: Sleep( type: SleepType.time, duration: const Duration(minutes: 5), ), current: s, ), const Divider(), SleepSelectorEntry( sleep: Sleep( type: SleepType.time, duration: const Duration(minutes: 10), ), current: s, ), const Divider(), SleepSelectorEntry( sleep: Sleep( type: SleepType.time, duration: const Duration(minutes: 15), ), current: s, ), const Divider(), SleepSelectorEntry( sleep: Sleep( type: SleepType.time, duration: const Duration(minutes: 30), ), current: s, ), const Divider(), SleepSelectorEntry( sleep: Sleep( type: SleepType.time, duration: const Duration(minutes: 45), ), current: s, ), const Divider(), SleepSelectorEntry( sleep: Sleep( type: SleepType.time, duration: const Duration(minutes: 60), ), current: s, ), const Divider(), SleepSelectorEntry( sleep: Sleep( type: SleepType.episode, ), current: s, ), ], ), ) ]); }); } } class SleepSelectorEntry extends StatelessWidget { const SleepSelectorEntry({ super.key, required this.sleep, required this.current, }); final Sleep sleep; final Sleep? current; @override Widget build(BuildContext context) { final audioBloc = Provider.of(context, listen: false); return GestureDetector( behavior: HitTestBehavior.translucent, onTap: () { audioBloc.sleep(Sleep( type: sleep.type, duration: sleep.duration, )); Navigator.pop(context); }, child: Padding( padding: const EdgeInsets.only( top: 4.0, bottom: 4.0, ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisSize: MainAxisSize.max, children: [ if (sleep.type == SleepType.none) Text( L.of(context)!.sleep_off_label, style: Theme.of(context).textTheme.bodyLarge, ), if (sleep.type == SleepType.time) Text( L.of(context)!.sleep_minute_label(sleep.duration.inMinutes.toString()), style: Theme.of(context).textTheme.bodyLarge, ), if (sleep.type == SleepType.episode) Text( L.of(context)!.sleep_episode_label, style: Theme.of(context).textTheme.bodyLarge, ), if (sleep == current) const Icon( Icons.check, size: 18.0, ), ], ), ), ); } }