fix(flutter): 收紧播放页底部与互动栏

This commit is contained in:
Zhou Weidong committed 2026-10-20 11:12:12 +08:00
1 parent 7b4a792c12
commit cf93d8d794
10 files changed
+167 -240

No files matched your search

-2
View File
@@ -592,9 +592,7 @@
"unlockMethodsVary": "Available unlock methods vary by title.",
"unlockEpisode": "Unlock this episode",
"language": "Language",
"fullScreenWatch": "Full screen",
"freeWatch": "Free watch",
"showMore": "More",
"qualityUnavailable": "Unavailable",
"notInterested": "Not interested",
"clearPlayback": "Clear-screen playback",
-2
View File
@@ -590,9 +590,7 @@
"unlockMethodsVary": "Metode pembukaan tersedia dapat berbeda per judul.",
"unlockEpisode": "Buka episode ini",
"language": "Bahasa",
"fullScreenWatch": "Layar penuh",
"freeWatch": "Tonton gratis",
"showMore": "Selengkapnya",
"qualityUnavailable": "Tidak tersedia",
"notInterested": "Tidak tertarik",
"clearPlayback": "Putar tanpa kontrol",
-12
View File
@@ -2638,24 +2638,12 @@ abstract class AppLocalizations {
/// **'Language'**
String get language;
/// No description provided for @fullScreenWatch.
///
/// In en, this message translates to:
/// **'Full screen'**
String get fullScreenWatch;
/// No description provided for @freeWatch.
///
/// In en, this message translates to:
/// **'Free watch'**
String get freeWatch;
/// No description provided for @showMore.
///
/// In en, this message translates to:
/// **'More'**
String get showMore;
/// No description provided for @qualityUnavailable.
///
/// In en, this message translates to:
-6
View File
@@ -1368,15 +1368,9 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get language => 'Language';
@override
String get fullScreenWatch => 'Full screen';
@override
String get freeWatch => 'Free watch';
@override
String get showMore => 'More';
@override
String get qualityUnavailable => 'Unavailable';
-6
View File
@@ -1368,15 +1368,9 @@ class AppLocalizationsId extends AppLocalizations {
@override
String get language => 'Bahasa';
@override
String get fullScreenWatch => 'Layar penuh';
@override
String get freeWatch => 'Tonton gratis';
@override
String get showMore => 'Selengkapnya';
@override
String get qualityUnavailable => 'Tidak tersedia';
-6
View File
@@ -1344,15 +1344,9 @@ class AppLocalizationsZh extends AppLocalizations {
@override
String get language => '语言';
@override
String get fullScreenWatch => '全屏观看';
@override
String get freeWatch => '免费观看';
@override
String get showMore => '展开';
@override
String get qualityUnavailable => '暂不可用';
-2
View File
@@ -552,9 +552,7 @@
"unlockMethodsVary": "具体解锁方式以作品而定。",
"unlockEpisode": "解锁本集",
"language": "语言",
"fullScreenWatch": "全屏观看",
"freeWatch": "免费观看",
"showMore": "展开",
"qualityUnavailable": "暂不可用",
"notInterested": "不感兴趣",
"clearPlayback": "清屏播放",
+13 -73
View File
@@ -1,8 +1,6 @@
// Author: fruit.com.cn
import 'package:flutter/material.dart';
import 'package:video_player/video_player.dart';
import '../../../core/formatting/localized_content.dart';
import '../../../core/theme/app_colors.dart';
import '../../../core/theme/app_dimens.dart';
@@ -14,11 +12,10 @@ import 'seek_progress_bar.dart';
part 'player_overlay_parts.dart';
const double _topBarHeight = 58;
const double _topButtonSize = 36;
const double _railButtonSize = 36;
const double _railGap = 9;
const double _fullScreenPillBottom = 246;
const double _topBarHeight = 58;
const double _railButtonSize = 34;
const double _railGap = 18;
const double _panelRadius = 16;
/// K29 竖屏播放页浮层。
@@ -57,7 +54,6 @@ class PlayerPlaybackOverlay extends StatelessWidget {
_bottom(context),
_rail(context),
],
_fullScreenPill(),
if (!controlsVisible)
Positioned(
top: AppDimens.spaceSm,
@@ -144,7 +140,7 @@ class PlayerPlaybackOverlay extends StatelessWidget {
final compact = MediaQuery.sizeOf(context).height < 700;
return Positioned(
right: AppDimens.spaceMd,
top: _topBarHeight + (compact ? 18 : 54),
top: MediaQuery.sizeOf(context).height * (compact ? 0.42 : 0.55),
child: _Rail(
l10n: data.l10n,
liked: data.liked,
@@ -156,7 +152,6 @@ class PlayerPlaybackOverlay extends StatelessWidget {
onComments: actions.onComments,
onSave: actions.onSave,
onShare: actions.onShare,
compact: compact,
),
);
}
@@ -170,7 +165,7 @@ class PlayerPlaybackOverlay extends StatelessWidget {
bottom: 0,
child: Container(
key: const Key('player-controls-bottom'),
padding: const EdgeInsets.fromLTRB(14, 10, 14, 10),
padding: const EdgeInsets.fromLTRB(14, 8, 14, 6),
decoration: const BoxDecoration(
color: Color(0xED000000),
borderRadius: BorderRadius.vertical(
@@ -182,27 +177,16 @@ class PlayerPlaybackOverlay extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_detailRow(series),
const SizedBox(height: 6),
const SizedBox(height: 4),
_EpisodeSynopsis(
key: const Key('player-episode-synopsis'),
l10n: data.l10n,
episodeNumber: data.episode.number,
synopsis: synopsis,
authorNote: data.authorNote,
textScale: data.textScale,
),
if (data.authorNote.isNotEmpty) ...[
const SizedBox(height: 6),
Text(
data.authorNote,
key: const Key('player-author-note'),
style: const TextStyle(color: Colors.white60, fontSize: 11),
),
],
const SizedBox(height: 6),
_PlayerTimeLabel(
key: const Key('player-time-label'),
controller: data.controller,
),
const SizedBox(height: 2),
SeekProgressBar(
key: const Key('player-progress'),
valueListenable: data.controller,
@@ -226,7 +210,7 @@ class PlayerPlaybackOverlay extends StatelessWidget {
key: const Key('player-open-detail'),
onTap: actions.onDetail,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 2),
padding: const EdgeInsets.symmetric(vertical: 1),
child: Row(
children: [
Expanded(
@@ -238,7 +222,7 @@ class PlayerPlaybackOverlay extends StatelessWidget {
style: TextStyle(
color: AppColors.onPrimary,
fontFamily: 'Outfit',
fontSize: 19 * data.textScale,
fontSize: 18 * data.textScale,
fontWeight: FontWeight.w800,
),
),
@@ -267,7 +251,7 @@ class PlayerPlaybackOverlay extends StatelessWidget {
onTap: actions.onEpisodes,
borderRadius: BorderRadius.circular(8),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
padding: const EdgeInsets.symmetric(vertical: 4),
child: Row(
children: [
Flexible(
@@ -288,7 +272,7 @@ class PlayerPlaybackOverlay extends StatelessWidget {
const Icon(
Icons.keyboard_arrow_up_rounded,
color: AppColors.onPrimary,
size: 18,
size: 16,
),
],
),
@@ -298,57 +282,13 @@ class PlayerPlaybackOverlay extends StatelessWidget {
),
if (actions.onPip != null) ...[
const SizedBox(width: 8),
_TopButton(
_PipButton(
key: const Key('player-pip'),
tooltip: data.l10n.pictureInPicture,
icon: Icons.picture_in_picture_alt_rounded,
onPressed: actions.onPip!,
),
],
],
);
}
Widget _fullScreenPill() {
return Positioned(
left: 0,
right: 0,
bottom: _fullScreenPillBottom,
child: Center(
child: Material(
color: const Color(0x99000000),
borderRadius: BorderRadius.circular(999),
child: InkWell(
key: const Key('player-fullscreen-toggle'),
borderRadius: BorderRadius.circular(999),
onTap: controlsVisible ? actions.onClear : actions.onExitClear,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 7),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(
Icons.fullscreen_rounded,
color: AppColors.onPrimary,
size: 16,
),
const SizedBox(width: 5),
Text(
controlsVisible
? data.l10n.fullScreenWatch
: data.l10n.exitClearScreen,
style: const TextStyle(
color: AppColors.onPrimary,
fontSize: 12,
fontWeight: FontWeight.w700,
),
),
],
),
),
),
),
),
);
}
}
+103 -113
View File
@@ -73,6 +73,30 @@ class _SpeedEntry extends StatelessWidget {
}
}
class _PipButton extends StatelessWidget {
const _PipButton({super.key, required this.tooltip, required this.onPressed});
final String tooltip;
final VoidCallback onPressed;
@override
Widget build(BuildContext context) {
return SizedBox.square(
dimension: 30,
child: IconButton(
tooltip: tooltip,
padding: EdgeInsets.zero,
style: IconButton.styleFrom(
backgroundColor: const Color(0x80000000),
foregroundColor: AppColors.onPrimary,
),
onPressed: onPressed,
icon: const Icon(Icons.picture_in_picture_alt_rounded, size: 17),
),
);
}
}
class _Rail extends StatelessWidget {
const _Rail({
required this.l10n,
@@ -85,7 +109,6 @@ class _Rail extends StatelessWidget {
required this.onComments,
required this.onSave,
required this.onShare,
required this.compact,
});
final AppLocalizations l10n;
@@ -98,7 +121,6 @@ class _Rail extends StatelessWidget {
final VoidCallback onComments;
final VoidCallback onSave;
final VoidCallback onShare;
final bool compact;
@override
Widget build(BuildContext context) {
@@ -111,29 +133,28 @@ class _Rail extends StatelessWidget {
label: favoriteCount?.toString() ?? l10n.favorite,
active: saved,
onTap: onSave,
compact: compact,
),
const SizedBox(height: _railGap),
_RailButton(
key: const Key('player-rail-comments'),
icon: Icons.chat_bubble_outline_rounded,
label: commentCount?.toString() ?? l10n.comments,
onTap: onComments,
compact: compact,
),
const SizedBox(height: _railGap),
_RailButton(
key: const Key('player-rail-like'),
icon: liked ? Icons.favorite_rounded : Icons.favorite_border_rounded,
label: likeCount?.toString() ?? l10n.like,
active: liked,
onTap: onLike,
compact: compact,
),
const SizedBox(height: _railGap),
_RailButton(
key: const Key('player-rail-share'),
icon: Icons.ios_share_rounded,
label: l10n.share,
onTap: onShare,
compact: compact,
),
],
);
@@ -146,46 +167,41 @@ class _RailButton extends StatelessWidget {
required this.icon,
required this.label,
required this.onTap,
required this.compact,
this.active = false,
});
final IconData icon;
final String label;
final VoidCallback onTap;
final bool compact;
final bool active;
@override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.only(bottom: compact ? 3 : _railGap),
child: Column(
children: [
SizedBox.square(
dimension: _railButtonSize,
child: IconButton(
onPressed: onTap,
padding: EdgeInsets.zero,
style: IconButton.styleFrom(
backgroundColor: const Color(0x80000000),
foregroundColor: active ? AppColors.blue : AppColors.onPrimary,
),
icon: Icon(icon, size: 18),
return Column(
children: [
SizedBox.square(
dimension: _railButtonSize,
child: IconButton(
onPressed: onTap,
padding: EdgeInsets.zero,
style: IconButton.styleFrom(
backgroundColor: const Color(0x80000000),
foregroundColor: active ? AppColors.blue : AppColors.onPrimary,
),
icon: Icon(icon, size: 24),
),
SizedBox(height: compact ? 1 : 3),
Text(
label,
maxLines: 1,
style: TextStyle(
color: AppColors.onPrimary,
fontSize: compact ? 8 : 9,
fontWeight: FontWeight.w600,
),
),
Text(
label,
maxLines: 1,
style: const TextStyle(
color: AppColors.onPrimary,
fontSize: 11,
height: 1,
fontWeight: FontWeight.w600,
),
],
),
),
],
);
}
}
@@ -196,12 +212,14 @@ class _EpisodeSynopsis extends StatefulWidget {
required this.l10n,
required this.episodeNumber,
required this.synopsis,
required this.authorNote,
required this.textScale,
});
final AppLocalizations l10n;
final int episodeNumber;
final String synopsis;
final String authorNote;
final double textScale;
@override
@@ -213,92 +231,64 @@ class _EpisodeSynopsisState extends State<_EpisodeSynopsis> {
@override
Widget build(BuildContext context) {
final canExpand = widget.synopsis.trim().length > 28;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
return Material(
color: Colors.transparent,
child: InkWell(
key: const Key('player-synopsis-toggle'),
onTap: () => setState(() => _expanded = !_expanded),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
widget.l10n.episodeLabel(widget.episodeNumber),
key: const Key('player-episode-label'),
style: TextStyle(
color: AppColors.blue,
fontSize: 11 * widget.textScale,
fontWeight: FontWeight.w700,
),
),
const Padding(
padding: EdgeInsets.symmetric(horizontal: 6),
child: Text('|', style: TextStyle(color: Colors.white38)),
),
Expanded(
child: Text(
widget.synopsis,
key: const Key('player-synopsis'),
maxLines: _expanded ? 4 : 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: Colors.white70,
fontSize: 11 * widget.textScale,
height: 1.45,
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
widget.l10n.episodeLabel(widget.episodeNumber),
key: const Key('player-episode-label'),
style: TextStyle(
color: AppColors.blue,
fontSize: 11 * widget.textScale,
fontWeight: FontWeight.w700,
),
),
),
const Padding(
padding: EdgeInsets.symmetric(horizontal: 6),
child: Text('|', style: TextStyle(color: Colors.white38)),
),
Expanded(
child: Text(
widget.synopsis,
key: const Key('player-synopsis'),
maxLines: _expanded ? 3 : 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: Colors.white70,
fontSize: 11 * widget.textScale,
height: 1.35,
),
),
),
const SizedBox(width: 4),
Icon(
_expanded
? Icons.keyboard_arrow_up_rounded
: Icons.keyboard_arrow_down_rounded,
color: Colors.white54,
size: 16,
),
],
),
if (_expanded && widget.authorNote.isNotEmpty) ...[
const SizedBox(height: 4),
Text(
widget.authorNote,
key: const Key('player-author-note'),
style: const TextStyle(color: Colors.white54, fontSize: 10),
),
],
],
),
if (canExpand)
GestureDetector(
key: const Key('player-synopsis-toggle'),
onTap: () => setState(() => _expanded = !_expanded),
child: Padding(
padding: const EdgeInsets.only(top: 2),
child: Text(
_expanded ? widget.l10n.showLess : widget.l10n.showMore,
style: const TextStyle(
color: Colors.white54,
fontSize: 10,
fontWeight: FontWeight.w600,
),
),
),
),
],
);
}
}
class _PlayerTimeLabel extends StatelessWidget {
const _PlayerTimeLabel({super.key, required this.controller});
final VideoPlayerController? controller;
@override
Widget build(BuildContext context) {
final source = controller;
if (source == null) return _label(Duration.zero, Duration.zero);
return ValueListenableBuilder<VideoPlayerValue>(
valueListenable: source,
builder: (context, value, _) => _label(value.position, value.duration),
);
}
Widget _label(Duration position, Duration duration) => Align(
alignment: Alignment.centerRight,
child: Text(
'${_format(position)} / ${_format(duration)}',
style: const TextStyle(
color: Colors.white60,
fontSize: 9,
fontWeight: FontWeight.w600,
),
),
);
String _format(Duration value) {
final minutes = value.inMinutes.remainder(60).toString().padLeft(2, '0');
final seconds = value.inSeconds.remainder(60).toString().padLeft(2, '0');
return '$minutes:$seconds';
);
}
}