diff --git a/lib/pages/setting/recommend_setting.dart b/lib/pages/setting/recommend_setting.dart index fdd413d74..472f202a1 100644 --- a/lib/pages/setting/recommend_setting.dart +++ b/lib/pages/setting/recommend_setting.dart @@ -221,8 +221,8 @@ class _RecommendSettingState extends State { ...defDurations, if (defDurations.contains(minDurationForRcmd).not) minDurationForRcmd, - -1 ]..sort(), + -1, ].map((e) { if (e == -1) { return {'title': '自定义', 'value': e}; diff --git a/lib/pages/video/detail/widgets/header_control.dart b/lib/pages/video/detail/widgets/header_control.dart index e43472794..4cab2897f 100644 --- a/lib/pages/video/detail/widgets/header_control.dart +++ b/lib/pages/video/detail/widgets/header_control.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'dart:math'; import 'package:PiliPalaX/pages/setting/widgets/switch_item.dart'; +import 'package:PiliPalaX/utils/extension.dart'; import 'package:PiliPalaX/utils/id_utils.dart'; import 'package:canvas_danmaku/canvas_danmaku.dart'; import 'package:floating/floating.dart'; @@ -139,7 +140,7 @@ class _HeaderControlState extends State { bottom: 12 + MediaQuery.paddingOf(context).bottom, ), child: Column( - children: [ + children: [ SizedBox( height: 35, child: Center( @@ -513,6 +514,7 @@ class _HeaderControlState extends State { ); }, ), + const SizedBox(height: 14), ], ), ), @@ -529,51 +531,115 @@ class _HeaderControlState extends State { /// 定时关闭 void scheduleExit() async { - const List scheduleTimeChoices = [ - -1, - 15, - 30, - 45, - 60, - ]; + const List scheduleTimeChoices = [0, 15, 30, 45, 60]; showModalBottomSheet( context: context, elevation: 0, backgroundColor: Colors.transparent, - builder: (BuildContext context) { + builder: (context) { return StatefulBuilder( - builder: (BuildContext context, StateSetter setState) { - return Container( - width: double.infinity, - height: 500, - clipBehavior: Clip.hardEdge, - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surface, - borderRadius: const BorderRadius.all(Radius.circular(12)), - ), - margin: const EdgeInsets.all(12), - padding: const EdgeInsets.only(left: 14, right: 14), - child: SingleChildScrollView( - child: Padding( - padding: - const EdgeInsets.symmetric(vertical: 0, horizontal: 20), - child: Column( + builder: (context, setState) { + return Container( + width: double.infinity, + height: 500, + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surface, + borderRadius: const BorderRadius.all(Radius.circular(12)), + ), + margin: EdgeInsets.only( + left: 12, + top: 12, + right: 12, + bottom: 12 + MediaQuery.paddingOf(context).bottom, + ), + padding: const EdgeInsets.only(left: 14, right: 14), + child: SingleChildScrollView( + child: Padding( + padding: + const EdgeInsets.symmetric(vertical: 0, horizontal: 20), + child: Column( crossAxisAlignment: CrossAxisAlignment.start, - children: [ + children: [ const SizedBox(height: 30), const Center(child: Text('定时关闭', style: titleStyle)), const SizedBox(height: 10), - for (final int choice in scheduleTimeChoices) ...[ - ListTile( + ...[ + ...[ + ...scheduleTimeChoices, + if (scheduleTimeChoices + .contains( + shutdownTimerService.scheduledExitInMinutes) + .not) + shutdownTimerService.scheduledExitInMinutes, + ]..sort(), + -1, + ].map( + (choice) => ListTile( dense: true, onTap: () { - shutdownTimerService.scheduledExitInMinutes = - choice; - shutdownTimerService.startShutdownTimer(); - Get.back(); + if (choice == -1) { + showDialog( + context: context, + builder: (context) { + String duration = ''; + return AlertDialog( + title: Text( + '自定义时长', + style: TextStyle(fontSize: 18), + ), + content: TextField( + autofocus: true, + onChanged: (value) => duration = value, + keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter.allow( + RegExp(r'\d+')), + ], + decoration: const InputDecoration( + suffixText: 'min'), + ), + actions: [ + TextButton( + onPressed: Get.back, + child: Text( + '取消', + style: TextStyle( + color: Theme.of(context) + .colorScheme + .outline), + ), + ), + TextButton( + onPressed: () { + Get.back(); + int choice = + int.tryParse(duration) ?? 0; + shutdownTimerService + .scheduledExitInMinutes = choice; + shutdownTimerService + .startShutdownTimer(); + setState(() {}); + }, + child: Text('确定'), + ), + ], + ); + }, + ); + } else { + Get.back(); + shutdownTimerService.scheduledExitInMinutes = + choice; + shutdownTimerService.startShutdownTimer(); + } }, contentPadding: const EdgeInsets.only(), - title: Text(choice == -1 ? "禁用" : "$choice分钟后"), + title: Text(choice == -1 + ? '自定义' + : choice == 0 + ? "禁用" + : "$choice分钟后"), trailing: shutdownTimerService .scheduledExitInMinutes == choice @@ -581,15 +647,16 @@ class _HeaderControlState extends State { Icons.done, color: Theme.of(context).colorScheme.primary, ) - : const SizedBox(), - ) - ], + : null, + ), + ), const SizedBox(height: 6), const Center( - child: SizedBox( - width: 100, - child: Divider(height: 1), - )), + child: SizedBox( + width: 125, + child: Divider(height: 1), + ), + ), const SizedBox(height: 10), ListTile( dense: true, @@ -600,27 +667,29 @@ class _HeaderControlState extends State { }, contentPadding: const EdgeInsets.only(), title: const Text("额外等待视频播放完毕", style: titleStyle), - trailing: Switch( - // thumb color (round icon) - activeColor: Theme.of(context).colorScheme.primary, - activeTrackColor: - Theme.of(context).colorScheme.primaryContainer, - inactiveThumbColor: - Theme.of(context).colorScheme.primaryContainer, - inactiveTrackColor: - Theme.of(context).colorScheme.surface, - splashRadius: 10.0, - // boolean variable value - value: shutdownTimerService.waitForPlayingCompleted, - // changes the state of the switch - onChanged: (value) => setState(() => - shutdownTimerService.waitForPlayingCompleted = - value), + trailing: Transform.scale( + alignment: Alignment + .centerRight, // 缩放Switch的大小后保持右侧对齐, 避免右侧空隙过大 + scale: 0.8, + child: Switch( + thumbIcon: WidgetStateProperty.resolveWith( + (Set states) { + if (states.isNotEmpty && + states.first == WidgetState.selected) { + return const Icon(Icons.done); + } + return null; + }), + value: shutdownTimerService.waitForPlayingCompleted, + onChanged: (value) => setState(() => + shutdownTimerService.waitForPlayingCompleted = + value), + ), ), ), const SizedBox(height: 10), Row( - children: [ + children: [ const Text('倒计时结束:', style: titleStyle), const Spacer(), ActionRowLineItem( @@ -645,11 +714,14 @@ class _HeaderControlState extends State { ) ], ), - ]), + const SizedBox(height: 10), + ], + ), + ), ), - ), - ); - }); + ); + }, + ); }, ); } @@ -691,9 +763,14 @@ class _HeaderControlState extends State { color: Theme.of(context).colorScheme.surface, borderRadius: const BorderRadius.all(Radius.circular(12)), ), - margin: const EdgeInsets.all(12), + margin: EdgeInsets.only( + left: 12, + top: 12, + right: 12, + bottom: 12 + MediaQuery.paddingOf(context).bottom, + ), child: Column( - children: [ + children: [ SizedBox( height: 45, child: GestureDetector( @@ -718,50 +795,55 @@ class _HeaderControlState extends State { Expanded( child: Material( child: Scrollbar( - child: ListView( - children: [ - for (int i = 0; i < totalQaSam; i++) ...[ - ListTile( - dense: true, - onTap: () { - if (currentVideoQa.code == - videoFormat[i].quality) { - return; - } - final int quality = videoFormat[i].quality!; - widget.videoDetailCtr!.currentVideoQa = - VideoQualityCode.fromCode(quality)!; - String oldQualityDesc = VideoQualityCode.fromCode( - setting.get(SettingBoxKey.defaultVideoQa, - defaultValue: - VideoQuality.values.last.code))! - .description; - setting.put( - SettingBoxKey.defaultVideoQa, quality); - SmartDialog.showToast( - "默认画质由:$oldQualityDesc 变为:${VideoQualityCode.fromCode(quality)!.description}"); - widget.videoDetailCtr!.updatePlayer(); - Get.back(); - }, - // 可能包含会员解锁画质 - enabled: i >= totalQaSam - userfulQaSam, - contentPadding: - const EdgeInsets.only(left: 20, right: 20), - title: Text(videoFormat[i].newDesc!), - trailing: currentVideoQa.code == - videoFormat[i].quality - ? Icon( - Icons.done, - color: - Theme.of(context).colorScheme.primary, - ) - : Text( - videoFormat[i].format!, - style: subTitleStyle, - ), - ), - ] - ], + child: MediaQuery.removePadding( + context: context, + removeBottom: true, + child: ListView( + children: [ + for (int i = 0; i < totalQaSam; i++) ...[ + ListTile( + dense: true, + onTap: () { + if (currentVideoQa.code == + videoFormat[i].quality) { + return; + } + final int quality = videoFormat[i].quality!; + widget.videoDetailCtr!.currentVideoQa = + VideoQualityCode.fromCode(quality)!; + String oldQualityDesc = + VideoQualityCode.fromCode(setting.get( + SettingBoxKey.defaultVideoQa, + defaultValue: + VideoQuality.values.last.code))! + .description; + setting.put( + SettingBoxKey.defaultVideoQa, quality); + SmartDialog.showToast( + "默认画质由:$oldQualityDesc 变为:${VideoQualityCode.fromCode(quality)!.description}"); + widget.videoDetailCtr!.updatePlayer(); + Get.back(); + }, + // 可能包含会员解锁画质 + enabled: i >= totalQaSam - userfulQaSam, + contentPadding: + const EdgeInsets.only(left: 20, right: 20), + title: Text(videoFormat[i].newDesc!), + trailing: currentVideoQa.code == + videoFormat[i].quality + ? Icon( + Icons.done, + color: + Theme.of(context).colorScheme.primary, + ) + : Text( + videoFormat[i].format!, + style: subTitleStyle, + ), + ), + ] + ], + ), ), ), ), @@ -790,53 +872,64 @@ class _HeaderControlState extends State { color: Theme.of(context).colorScheme.surface, borderRadius: const BorderRadius.all(Radius.circular(12)), ), - margin: const EdgeInsets.all(12), + margin: EdgeInsets.only( + left: 12, + top: 12, + right: 12, + bottom: 12 + MediaQuery.paddingOf(context).bottom, + ), child: Column( - children: [ + children: [ const SizedBox( height: 45, child: Center(child: Text('选择音质', style: titleStyle))), Expanded( child: Material( - child: ListView( - children: [ - for (final AudioItem i in audio) ...[ - ListTile( - dense: true, - onTap: () { - if (currentAudioQa.code == i.id) { - return; - } - final int quality = i.id!; - widget.videoDetailCtr!.currentAudioQa = - AudioQualityCode.fromCode(quality)!; - String oldQualityDesc = AudioQualityCode.fromCode( - setting.get(SettingBoxKey.defaultAudioQa, - defaultValue: - AudioQuality.values.last.code))! - .description; - setting.put(SettingBoxKey.defaultAudioQa, quality); - SmartDialog.showToast( - "默认音质由:$oldQualityDesc 变为:${AudioQualityCode.fromCode(quality)!.description}"); - widget.videoDetailCtr!.updatePlayer(); - Get.back(); - }, - contentPadding: - const EdgeInsets.only(left: 20, right: 20), - title: Text(i.quality!), - subtitle: Text( - i.codecs!, - style: subTitleStyle, + child: MediaQuery.removePadding( + context: context, + removeBottom: true, + child: ListView( + children: [ + for (final AudioItem i in audio) ...[ + ListTile( + dense: true, + onTap: () { + if (currentAudioQa.code == i.id) { + return; + } + final int quality = i.id!; + widget.videoDetailCtr!.currentAudioQa = + AudioQualityCode.fromCode(quality)!; + String oldQualityDesc = AudioQualityCode.fromCode( + setting.get(SettingBoxKey.defaultAudioQa, + defaultValue: + AudioQuality.values.last.code))! + .description; + setting.put( + SettingBoxKey.defaultAudioQa, quality); + SmartDialog.showToast( + "默认音质由:$oldQualityDesc 变为:${AudioQualityCode.fromCode(quality)!.description}"); + widget.videoDetailCtr!.updatePlayer(); + Get.back(); + }, + contentPadding: + const EdgeInsets.only(left: 20, right: 20), + title: Text(i.quality!), + subtitle: Text( + i.codecs!, + style: subTitleStyle, + ), + trailing: currentAudioQa.code == i.id + ? Icon( + Icons.done, + color: + Theme.of(context).colorScheme.primary, + ) + : const SizedBox(), ), - trailing: currentAudioQa.code == i.id - ? Icon( - Icons.done, - color: Theme.of(context).colorScheme.primary, - ) - : const SizedBox(), - ), - ] - ], + ] + ], + ), ), ), ), @@ -876,7 +969,12 @@ class _HeaderControlState extends State { color: Theme.of(context).colorScheme.surface, borderRadius: const BorderRadius.all(Radius.circular(12)), ), - margin: const EdgeInsets.all(12), + margin: EdgeInsets.only( + left: 12, + top: 12, + right: 12, + bottom: 12 + MediaQuery.paddingOf(context).bottom, + ), child: Column( children: [ const SizedBox( @@ -884,35 +982,42 @@ class _HeaderControlState extends State { child: Center(child: Text('选择解码格式', style: titleStyle))), Expanded( child: Material( - child: ListView( - children: [ - for (var i in list) ...[ - ListTile( - dense: true, - onTap: () { - if (i.startsWith(currentDecodeFormats.code)) return; - widget.videoDetailCtr!.currentDecodeFormats = - VideoDecodeFormatsCode.fromString(i)!; - widget.videoDetailCtr!.updatePlayer(); - Get.back(); - }, - contentPadding: - const EdgeInsets.only(left: 20, right: 20), - title: Text(VideoDecodeFormatsCode.fromString(i)! - .description!), - subtitle: Text( - i!, - style: subTitleStyle, + child: MediaQuery.removePadding( + context: context, + removeBottom: true, + child: ListView( + children: [ + for (var i in list) ...[ + ListTile( + dense: true, + onTap: () { + if (i.startsWith(currentDecodeFormats.code)) { + return; + } + widget.videoDetailCtr!.currentDecodeFormats = + VideoDecodeFormatsCode.fromString(i)!; + widget.videoDetailCtr!.updatePlayer(); + Get.back(); + }, + contentPadding: + const EdgeInsets.only(left: 20, right: 20), + title: Text(VideoDecodeFormatsCode.fromString(i)! + .description!), + subtitle: Text( + i!, + style: subTitleStyle, + ), + trailing: i.startsWith(currentDecodeFormats.code) + ? Icon( + Icons.done, + color: + Theme.of(context).colorScheme.primary, + ) + : const SizedBox(), ), - trailing: i.startsWith(currentDecodeFormats.code) - ? Icon( - Icons.done, - color: Theme.of(context).colorScheme.primary, - ) - : const SizedBox(), - ), - ] - ], + ] + ], + ), ), ), ), @@ -1050,9 +1155,9 @@ class _HeaderControlState extends State { Padding( padding: const EdgeInsets.only(top: 12, bottom: 18), child: Row( - children: [ + children: [ for (final Map i - in blockTypesList) ...[ + in blockTypesList) ...[ ActionRowLineItem( onTap: () async { final bool isChoose = @@ -1458,7 +1563,12 @@ class _HeaderControlState extends State { color: Theme.of(context).colorScheme.surface, borderRadius: const BorderRadius.all(Radius.circular(12)), ), - margin: const EdgeInsets.all(12), + margin: EdgeInsets.only( + left: 12, + top: 12, + right: 12, + bottom: 12 + MediaQuery.paddingOf(context).bottom, + ), child: Column( children: [ const SizedBox( @@ -1466,27 +1576,32 @@ class _HeaderControlState extends State { child: Center(child: Text('选择播放顺序', style: titleStyle))), Expanded( child: Material( - child: ListView( - children: [ - for (final PlayRepeat i in PlayRepeat.values) ...[ - ListTile( - dense: true, - onTap: () { - widget.controller!.setPlayRepeat(i); - Get.back(); - }, - contentPadding: - const EdgeInsets.only(left: 20, right: 20), - title: Text(i.description), - trailing: widget.controller!.playRepeat == i - ? Icon( - Icons.done, - color: Theme.of(context).colorScheme.primary, - ) - : const SizedBox(), - ) + child: MediaQuery.removePadding( + context: context, + removeBottom: true, + child: ListView( + children: [ + for (final PlayRepeat i in PlayRepeat.values) ...[ + ListTile( + dense: true, + onTap: () { + widget.controller!.setPlayRepeat(i); + Get.back(); + }, + contentPadding: + const EdgeInsets.only(left: 20, right: 20), + title: Text(i.description), + trailing: widget.controller!.playRepeat == i + ? Icon( + Icons.done, + color: + Theme.of(context).colorScheme.primary, + ) + : const SizedBox(), + ) + ], ], - ], + ), ), ), ), @@ -1738,9 +1853,9 @@ class _HeaderControlState extends State { // }); ScaffoldMessenger.of(context).showSnackBar( SnackBar( - content: Column(children: [ + content: Column(children: [ const Row( - children: [ + children: [ Icon( Icons.check, color: Colors.green, diff --git a/lib/services/shutdown_timer_service.dart b/lib/services/shutdown_timer_service.dart index 35d0acc58..46e630ed3 100644 --- a/lib/services/shutdown_timer_service.dart +++ b/lib/services/shutdown_timer_service.dart @@ -11,7 +11,7 @@ class ShutdownTimerService with WidgetsBindingObserver { Timer? _shutdownTimer; Timer? _autoCloseDialogTimer; //定时退出 - int scheduledExitInMinutes = -1; + int scheduledExitInMinutes = 0; bool exitApp = false; bool waitForPlayingCompleted = false; bool isWaiting = false; @@ -36,7 +36,7 @@ class ShutdownTimerService with WidgetsBindingObserver { void startShutdownTimer() { cancelShutdownTimer(); // Cancel any previous timer - if (scheduledExitInMinutes == -1) { + if (scheduledExitInMinutes == 0) { //使用toast提示用户已取消 SmartDialog.showToast("取消定时关闭"); return;