feat: custom schedule duration

opt: bottom sheet

Closes #44

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
bggRGjQaUbCoE
2024-12-23 11:12:47 +08:00
parent 9e8d34e0dc
commit a9e4f2081d
3 changed files with 319 additions and 204 deletions

View File

@@ -221,8 +221,8 @@ class _RecommendSettingState extends State<RecommendSetting> {
...defDurations, ...defDurations,
if (defDurations.contains(minDurationForRcmd).not) if (defDurations.contains(minDurationForRcmd).not)
minDurationForRcmd, minDurationForRcmd,
-1
]..sort(), ]..sort(),
-1,
].map((e) { ].map((e) {
if (e == -1) { if (e == -1) {
return {'title': '自定义', 'value': e}; return {'title': '自定义', 'value': e};

View File

@@ -3,6 +3,7 @@ import 'dart:io';
import 'dart:math'; import 'dart:math';
import 'package:PiliPalaX/pages/setting/widgets/switch_item.dart'; import 'package:PiliPalaX/pages/setting/widgets/switch_item.dart';
import 'package:PiliPalaX/utils/extension.dart';
import 'package:PiliPalaX/utils/id_utils.dart'; import 'package:PiliPalaX/utils/id_utils.dart';
import 'package:canvas_danmaku/canvas_danmaku.dart'; import 'package:canvas_danmaku/canvas_danmaku.dart';
import 'package:floating/floating.dart'; import 'package:floating/floating.dart';
@@ -139,7 +140,7 @@ class _HeaderControlState extends State<HeaderControl> {
bottom: 12 + MediaQuery.paddingOf(context).bottom, bottom: 12 + MediaQuery.paddingOf(context).bottom,
), ),
child: Column( child: Column(
children: <Widget>[ children: [
SizedBox( SizedBox(
height: 35, height: 35,
child: Center( child: Center(
@@ -513,6 +514,7 @@ class _HeaderControlState extends State<HeaderControl> {
); );
}, },
), ),
const SizedBox(height: 14),
], ],
), ),
), ),
@@ -529,20 +531,14 @@ class _HeaderControlState extends State<HeaderControl> {
/// 定时关闭 /// 定时关闭
void scheduleExit() async { void scheduleExit() async {
const List<int> scheduleTimeChoices = [ const List<int> scheduleTimeChoices = [0, 15, 30, 45, 60];
-1,
15,
30,
45,
60,
];
showModalBottomSheet( showModalBottomSheet(
context: context, context: context,
elevation: 0, elevation: 0,
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
builder: (BuildContext context) { builder: (context) {
return StatefulBuilder( return StatefulBuilder(
builder: (BuildContext context, StateSetter setState) { builder: (context, setState) {
return Container( return Container(
width: double.infinity, width: double.infinity,
height: 500, height: 500,
@@ -551,7 +547,12 @@ class _HeaderControlState extends State<HeaderControl> {
color: Theme.of(context).colorScheme.surface, color: Theme.of(context).colorScheme.surface,
borderRadius: const BorderRadius.all(Radius.circular(12)), 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,
),
padding: const EdgeInsets.only(left: 14, right: 14), padding: const EdgeInsets.only(left: 14, right: 14),
child: SingleChildScrollView( child: SingleChildScrollView(
child: Padding( child: Padding(
@@ -559,21 +560,86 @@ class _HeaderControlState extends State<HeaderControl> {
const EdgeInsets.symmetric(vertical: 0, horizontal: 20), const EdgeInsets.symmetric(vertical: 0, horizontal: 20),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[ children: [
const SizedBox(height: 30), const SizedBox(height: 30),
const Center(child: Text('定时关闭', style: titleStyle)), const Center(child: Text('定时关闭', style: titleStyle)),
const SizedBox(height: 10), const SizedBox(height: 10),
for (final int choice in scheduleTimeChoices) ...<Widget>[ ...[
ListTile( ...[
...scheduleTimeChoices,
if (scheduleTimeChoices
.contains(
shutdownTimerService.scheduledExitInMinutes)
.not)
shutdownTimerService.scheduledExitInMinutes,
]..sort(),
-1,
].map(
(choice) => ListTile(
dense: true, dense: true,
onTap: () { onTap: () {
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 = shutdownTimerService.scheduledExitInMinutes =
choice; choice;
shutdownTimerService.startShutdownTimer(); shutdownTimerService.startShutdownTimer();
Get.back(); }
}, },
contentPadding: const EdgeInsets.only(), contentPadding: const EdgeInsets.only(),
title: Text(choice == -1 ? "禁用" : "$choice分钟后"), title: Text(choice == -1
? '自定义'
: choice == 0
? "禁用"
: "$choice分钟后"),
trailing: shutdownTimerService trailing: shutdownTimerService
.scheduledExitInMinutes == .scheduledExitInMinutes ==
choice choice
@@ -581,15 +647,16 @@ class _HeaderControlState extends State<HeaderControl> {
Icons.done, Icons.done,
color: Theme.of(context).colorScheme.primary, color: Theme.of(context).colorScheme.primary,
) )
: const SizedBox(), : null,
) ),
], ),
const SizedBox(height: 6), const SizedBox(height: 6),
const Center( const Center(
child: SizedBox( child: SizedBox(
width: 100, width: 125,
child: Divider(height: 1), child: Divider(height: 1),
)), ),
),
const SizedBox(height: 10), const SizedBox(height: 10),
ListTile( ListTile(
dense: true, dense: true,
@@ -600,27 +667,29 @@ class _HeaderControlState extends State<HeaderControl> {
}, },
contentPadding: const EdgeInsets.only(), contentPadding: const EdgeInsets.only(),
title: const Text("额外等待视频播放完毕", style: titleStyle), title: const Text("额外等待视频播放完毕", style: titleStyle),
trailing: Switch( trailing: Transform.scale(
// thumb color (round icon) alignment: Alignment
activeColor: Theme.of(context).colorScheme.primary, .centerRight, // 缩放Switch的大小后保持右侧对齐, 避免右侧空隙过大
activeTrackColor: scale: 0.8,
Theme.of(context).colorScheme.primaryContainer, child: Switch(
inactiveThumbColor: thumbIcon: WidgetStateProperty.resolveWith<Icon?>(
Theme.of(context).colorScheme.primaryContainer, (Set<WidgetState> states) {
inactiveTrackColor: if (states.isNotEmpty &&
Theme.of(context).colorScheme.surface, states.first == WidgetState.selected) {
splashRadius: 10.0, return const Icon(Icons.done);
// boolean variable value }
return null;
}),
value: shutdownTimerService.waitForPlayingCompleted, value: shutdownTimerService.waitForPlayingCompleted,
// changes the state of the switch
onChanged: (value) => setState(() => onChanged: (value) => setState(() =>
shutdownTimerService.waitForPlayingCompleted = shutdownTimerService.waitForPlayingCompleted =
value), value),
), ),
), ),
),
const SizedBox(height: 10), const SizedBox(height: 10),
Row( Row(
children: <Widget>[ children: [
const Text('倒计时结束:', style: titleStyle), const Text('倒计时结束:', style: titleStyle),
const Spacer(), const Spacer(),
ActionRowLineItem( ActionRowLineItem(
@@ -645,11 +714,14 @@ class _HeaderControlState extends State<HeaderControl> {
) )
], ],
), ),
]), const SizedBox(height: 10),
],
),
), ),
), ),
); );
}); },
);
}, },
); );
} }
@@ -691,9 +763,14 @@ class _HeaderControlState extends State<HeaderControl> {
color: Theme.of(context).colorScheme.surface, color: Theme.of(context).colorScheme.surface,
borderRadius: const BorderRadius.all(Radius.circular(12)), 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( child: Column(
children: <Widget>[ children: [
SizedBox( SizedBox(
height: 45, height: 45,
child: GestureDetector( child: GestureDetector(
@@ -718,6 +795,9 @@ class _HeaderControlState extends State<HeaderControl> {
Expanded( Expanded(
child: Material( child: Material(
child: Scrollbar( child: Scrollbar(
child: MediaQuery.removePadding(
context: context,
removeBottom: true,
child: ListView( child: ListView(
children: [ children: [
for (int i = 0; i < totalQaSam; i++) ...[ for (int i = 0; i < totalQaSam; i++) ...[
@@ -731,8 +811,9 @@ class _HeaderControlState extends State<HeaderControl> {
final int quality = videoFormat[i].quality!; final int quality = videoFormat[i].quality!;
widget.videoDetailCtr!.currentVideoQa = widget.videoDetailCtr!.currentVideoQa =
VideoQualityCode.fromCode(quality)!; VideoQualityCode.fromCode(quality)!;
String oldQualityDesc = VideoQualityCode.fromCode( String oldQualityDesc =
setting.get(SettingBoxKey.defaultVideoQa, VideoQualityCode.fromCode(setting.get(
SettingBoxKey.defaultVideoQa,
defaultValue: defaultValue:
VideoQuality.values.last.code))! VideoQuality.values.last.code))!
.description; .description;
@@ -766,6 +847,7 @@ class _HeaderControlState extends State<HeaderControl> {
), ),
), ),
), ),
),
], ],
), ),
); );
@@ -790,17 +872,25 @@ class _HeaderControlState extends State<HeaderControl> {
color: Theme.of(context).colorScheme.surface, color: Theme.of(context).colorScheme.surface,
borderRadius: const BorderRadius.all(Radius.circular(12)), 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( child: Column(
children: <Widget>[ children: [
const SizedBox( const SizedBox(
height: 45, height: 45,
child: Center(child: Text('选择音质', style: titleStyle))), child: Center(child: Text('选择音质', style: titleStyle))),
Expanded( Expanded(
child: Material( child: Material(
child: MediaQuery.removePadding(
context: context,
removeBottom: true,
child: ListView( child: ListView(
children: <Widget>[ children: [
for (final AudioItem i in audio) ...<Widget>[ for (final AudioItem i in audio) ...[
ListTile( ListTile(
dense: true, dense: true,
onTap: () { onTap: () {
@@ -815,7 +905,8 @@ class _HeaderControlState extends State<HeaderControl> {
defaultValue: defaultValue:
AudioQuality.values.last.code))! AudioQuality.values.last.code))!
.description; .description;
setting.put(SettingBoxKey.defaultAudioQa, quality); setting.put(
SettingBoxKey.defaultAudioQa, quality);
SmartDialog.showToast( SmartDialog.showToast(
"默认音质由:$oldQualityDesc 变为:${AudioQualityCode.fromCode(quality)!.description}"); "默认音质由:$oldQualityDesc 变为:${AudioQualityCode.fromCode(quality)!.description}");
widget.videoDetailCtr!.updatePlayer(); widget.videoDetailCtr!.updatePlayer();
@@ -831,7 +922,8 @@ class _HeaderControlState extends State<HeaderControl> {
trailing: currentAudioQa.code == i.id trailing: currentAudioQa.code == i.id
? Icon( ? Icon(
Icons.done, Icons.done,
color: Theme.of(context).colorScheme.primary, color:
Theme.of(context).colorScheme.primary,
) )
: const SizedBox(), : const SizedBox(),
), ),
@@ -840,6 +932,7 @@ class _HeaderControlState extends State<HeaderControl> {
), ),
), ),
), ),
),
], ],
), ),
); );
@@ -876,7 +969,12 @@ class _HeaderControlState extends State<HeaderControl> {
color: Theme.of(context).colorScheme.surface, color: Theme.of(context).colorScheme.surface,
borderRadius: const BorderRadius.all(Radius.circular(12)), 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( child: Column(
children: [ children: [
const SizedBox( const SizedBox(
@@ -884,13 +982,18 @@ class _HeaderControlState extends State<HeaderControl> {
child: Center(child: Text('选择解码格式', style: titleStyle))), child: Center(child: Text('选择解码格式', style: titleStyle))),
Expanded( Expanded(
child: Material( child: Material(
child: MediaQuery.removePadding(
context: context,
removeBottom: true,
child: ListView( child: ListView(
children: [ children: [
for (var i in list) ...[ for (var i in list) ...[
ListTile( ListTile(
dense: true, dense: true,
onTap: () { onTap: () {
if (i.startsWith(currentDecodeFormats.code)) return; if (i.startsWith(currentDecodeFormats.code)) {
return;
}
widget.videoDetailCtr!.currentDecodeFormats = widget.videoDetailCtr!.currentDecodeFormats =
VideoDecodeFormatsCode.fromString(i)!; VideoDecodeFormatsCode.fromString(i)!;
widget.videoDetailCtr!.updatePlayer(); widget.videoDetailCtr!.updatePlayer();
@@ -907,7 +1010,8 @@ class _HeaderControlState extends State<HeaderControl> {
trailing: i.startsWith(currentDecodeFormats.code) trailing: i.startsWith(currentDecodeFormats.code)
? Icon( ? Icon(
Icons.done, Icons.done,
color: Theme.of(context).colorScheme.primary, color:
Theme.of(context).colorScheme.primary,
) )
: const SizedBox(), : const SizedBox(),
), ),
@@ -916,6 +1020,7 @@ class _HeaderControlState extends State<HeaderControl> {
), ),
), ),
), ),
),
], ],
), ),
); );
@@ -1050,9 +1155,9 @@ class _HeaderControlState extends State<HeaderControl> {
Padding( Padding(
padding: const EdgeInsets.only(top: 12, bottom: 18), padding: const EdgeInsets.only(top: 12, bottom: 18),
child: Row( child: Row(
children: <Widget>[ children: [
for (final Map<String, dynamic> i for (final Map<String, dynamic> i
in blockTypesList) ...<Widget>[ in blockTypesList) ...[
ActionRowLineItem( ActionRowLineItem(
onTap: () async { onTap: () async {
final bool isChoose = final bool isChoose =
@@ -1458,7 +1563,12 @@ class _HeaderControlState extends State<HeaderControl> {
color: Theme.of(context).colorScheme.surface, color: Theme.of(context).colorScheme.surface,
borderRadius: const BorderRadius.all(Radius.circular(12)), 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( child: Column(
children: [ children: [
const SizedBox( const SizedBox(
@@ -1466,8 +1576,11 @@ class _HeaderControlState extends State<HeaderControl> {
child: Center(child: Text('选择播放顺序', style: titleStyle))), child: Center(child: Text('选择播放顺序', style: titleStyle))),
Expanded( Expanded(
child: Material( child: Material(
child: MediaQuery.removePadding(
context: context,
removeBottom: true,
child: ListView( child: ListView(
children: <Widget>[ children: [
for (final PlayRepeat i in PlayRepeat.values) ...[ for (final PlayRepeat i in PlayRepeat.values) ...[
ListTile( ListTile(
dense: true, dense: true,
@@ -1481,7 +1594,8 @@ class _HeaderControlState extends State<HeaderControl> {
trailing: widget.controller!.playRepeat == i trailing: widget.controller!.playRepeat == i
? Icon( ? Icon(
Icons.done, Icons.done,
color: Theme.of(context).colorScheme.primary, color:
Theme.of(context).colorScheme.primary,
) )
: const SizedBox(), : const SizedBox(),
) )
@@ -1490,6 +1604,7 @@ class _HeaderControlState extends State<HeaderControl> {
), ),
), ),
), ),
),
], ],
), ),
); );
@@ -1738,9 +1853,9 @@ class _HeaderControlState extends State<HeaderControl> {
// }); // });
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
content: Column(children: <Widget>[ content: Column(children: [
const Row( const Row(
children: <Widget>[ children: [
Icon( Icon(
Icons.check, Icons.check,
color: Colors.green, color: Colors.green,

View File

@@ -11,7 +11,7 @@ class ShutdownTimerService with WidgetsBindingObserver {
Timer? _shutdownTimer; Timer? _shutdownTimer;
Timer? _autoCloseDialogTimer; Timer? _autoCloseDialogTimer;
//定时退出 //定时退出
int scheduledExitInMinutes = -1; int scheduledExitInMinutes = 0;
bool exitApp = false; bool exitApp = false;
bool waitForPlayingCompleted = false; bool waitForPlayingCompleted = false;
bool isWaiting = false; bool isWaiting = false;
@@ -36,7 +36,7 @@ class ShutdownTimerService with WidgetsBindingObserver {
void startShutdownTimer() { void startShutdownTimer() {
cancelShutdownTimer(); // Cancel any previous timer cancelShutdownTimer(); // Cancel any previous timer
if (scheduledExitInMinutes == -1) { if (scheduledExitInMinutes == 0) {
//使用toast提示用户已取消 //使用toast提示用户已取消
SmartDialog.showToast("取消定时关闭"); SmartDialog.showToast("取消定时关闭");
return; return;