feat(player): 倍速改为底部弹窗选择
This commit is contained in:
1 parent
5ca5846a26
commit
3a7e0bc4b7
10 files changed
+194
-6
No files matched your search
@@ -72,6 +72,7 @@ class PlayerPage extends StatefulWidget {
|
||||
class _PlayerPageState extends State<PlayerPage> with WidgetsBindingObserver {
|
||||
late final PlayerLogic logic;
|
||||
bool _chromeVisible = true;
|
||||
double _playbackSpeed = 1.0;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -384,6 +385,8 @@ class _PlayerPageState extends State<PlayerPage> with WidgetsBindingObserver {
|
||||
? () => unawaited(logic.playNextEpisode())
|
||||
: null,
|
||||
playing: logic.session.userWantsPlay,
|
||||
playbackSpeed: _playbackSpeed,
|
||||
onSpeedTap: () => unawaited(_openSpeedSheet(context)),
|
||||
controlsVisible: _chromeVisible,
|
||||
onClear: () => setState(() => _chromeVisible = false),
|
||||
onExitClear: () => setState(() => _chromeVisible = true),
|
||||
@@ -469,6 +472,18 @@ class _PlayerPageState extends State<PlayerPage> with WidgetsBindingObserver {
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _openSpeedSheet(BuildContext context) async {
|
||||
final selected = await showModalBottomSheet<double>(
|
||||
context: context,
|
||||
backgroundColor: AppColors.card,
|
||||
showDragHandle: true,
|
||||
builder: (_) => _PlaybackSpeedSheet(selected: _playbackSpeed),
|
||||
);
|
||||
if (selected == null || !mounted) return;
|
||||
setState(() => _playbackSpeed = selected);
|
||||
await logic.setPlaybackSpeed(selected);
|
||||
}
|
||||
}
|
||||
|
||||
/// 不可播放提示:文案由后端返回的 `accessType` 驱动(契约 §1.4),前端不自行推断。
|
||||
@@ -602,6 +617,8 @@ class _PlaybackOverlay extends StatefulWidget {
|
||||
required this.liked,
|
||||
required this.saved,
|
||||
required this.playing,
|
||||
required this.playbackSpeed,
|
||||
required this.onSpeedTap,
|
||||
required this.controlsVisible,
|
||||
required this.onTogglePlay,
|
||||
required this.onClear,
|
||||
@@ -626,6 +643,8 @@ class _PlaybackOverlay extends StatefulWidget {
|
||||
final bool liked;
|
||||
final bool saved;
|
||||
final bool playing;
|
||||
final double playbackSpeed;
|
||||
final VoidCallback onSpeedTap;
|
||||
final bool controlsVisible;
|
||||
final VoidCallback onTogglePlay;
|
||||
final VoidCallback onClear;
|
||||
@@ -667,14 +686,28 @@ class _PlaybackOverlayState extends State<_PlaybackOverlay> {
|
||||
Expanded(
|
||||
child: Stack(
|
||||
children: [
|
||||
const Positioned(
|
||||
Positioned(
|
||||
right: 10,
|
||||
bottom: 14,
|
||||
child: Text(
|
||||
'1.0x',
|
||||
style: TextStyle(
|
||||
color: Color(0xFFDDDDDD),
|
||||
fontSize: 9,
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
key: const Key('player-speed'),
|
||||
onTap: widget.onSpeedTap,
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 4,
|
||||
vertical: 2,
|
||||
),
|
||||
child: Text(
|
||||
_speedLabel(widget.playbackSpeed),
|
||||
style: const TextStyle(
|
||||
color: Color(0xFFDDDDDD),
|
||||
fontSize: 9,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -920,6 +953,69 @@ class _PlaybackOverlayState extends State<_PlaybackOverlay> {
|
||||
}
|
||||
}
|
||||
|
||||
const List<double> _speedOptions = [0.5, 0.75, 1.0, 1.25, 1.5, 2.0];
|
||||
|
||||
String _speedLabel(double value) => '${value.toString()}x';
|
||||
|
||||
class _PlaybackSpeedSheet extends StatelessWidget {
|
||||
const _PlaybackSpeedSheet({required this.selected});
|
||||
|
||||
final double selected;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
return SafeArea(
|
||||
child: Column(
|
||||
key: const Key('player-speed-sheet'),
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(20, 2, 20, 8),
|
||||
child: Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
l10n.playbackSpeed,
|
||||
style: const TextStyle(
|
||||
color: AppColors.onSurface,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
for (final value in _speedOptions)
|
||||
ListTile(
|
||||
key: Key('player-speed-${value.toString()}'),
|
||||
dense: true,
|
||||
tileColor: value == selected
|
||||
? AppColors.blue.withValues(alpha: 0.12)
|
||||
: null,
|
||||
title: Text(
|
||||
_speedLabel(value),
|
||||
style: TextStyle(
|
||||
color: value == selected ? AppColors.blue : AppColors.cream,
|
||||
fontSize: 13,
|
||||
fontWeight: value == selected
|
||||
? FontWeight.w700
|
||||
: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
trailing: value == selected
|
||||
? const Icon(
|
||||
Icons.check_rounded,
|
||||
color: AppColors.blue,
|
||||
size: 18,
|
||||
)
|
||||
: null,
|
||||
onTap: () => Navigator.pop(context, value),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _TopAction extends StatelessWidget {
|
||||
const _TopAction({
|
||||
super.key,
|
||||
|
||||
Reference in new issue
Block a user