Files
ReelShort/lib/pages/player/widgets/player_overlay_parts.dart

210 lines
5.4 KiB
Dart

// Author: fruit.com.cn
part of 'player_overlay.dart';
/// 顶部按钮:直接压在画面上,不加底色,靠文字/图标阴影保证可读性。
class _TopButton extends StatelessWidget {
const _TopButton({
super.key,
required this.tooltip,
required this.icon,
required this.onPressed,
this.iconSize = 22,
});
final String tooltip;
final IconData icon;
final VoidCallback onPressed;
final double iconSize;
@override
Widget build(BuildContext context) {
return SizedBox.square(
dimension: _topButtonSize,
child: IconButton(
tooltip: tooltip,
padding: EdgeInsets.zero,
style: IconButton.styleFrom(
backgroundColor: AppColors.transparent,
foregroundColor: AppColors.onPrimary,
),
onPressed: onPressed,
icon: Icon(icon, size: iconSize, shadows: _playerTextShadows),
),
);
}
}
class _SpeedEntry extends StatelessWidget {
const _SpeedEntry({super.key, required this.speed, required this.onTap});
final double speed;
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
return Material(
color: AppColors.transparent,
borderRadius: BorderRadius.circular(999),
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(999),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 7),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(
Icons.speed_rounded,
color: AppColors.onPrimary,
size: 15,
shadows: _playerTextShadows,
),
const SizedBox(width: 4),
Text(
'${speed}x',
style: const TextStyle(
color: AppColors.onPrimary,
fontSize: 12,
fontWeight: FontWeight.w700,
shadows: _playerTextShadows,
),
),
],
),
),
),
);
}
}
class _BottomIconButton extends StatelessWidget {
const _BottomIconButton({
super.key,
required this.tooltip,
required this.icon,
required this.onPressed,
this.height = 40,
});
final String tooltip;
final IconData icon;
final VoidCallback onPressed;
/// 与左侧选集条等高,两个控件底部齐平。
final double height;
@override
Widget build(BuildContext context) {
return SizedBox(
width: 40,
height: height,
child: IconButton(
tooltip: tooltip,
padding: EdgeInsets.zero,
style: IconButton.styleFrom(
backgroundColor: AppColors.transparent,
foregroundColor: AppColors.onPrimary,
),
onPressed: onPressed,
icon: Icon(icon, size: 24, shadows: _playerTextShadows),
),
);
}
}
class _EpisodeSynopsis extends StatefulWidget {
const _EpisodeSynopsis({
super.key,
required this.l10n,
required this.drama,
required this.synopsis,
required this.authorNote,
});
final AppLocalizations l10n;
final Drama drama;
final String synopsis;
final String authorNote;
@override
State<_EpisodeSynopsis> createState() => _EpisodeSynopsisState();
}
class _EpisodeSynopsisState extends State<_EpisodeSynopsis> {
String? _fullSynopsis;
int _detailRequest = 0;
bool _detailRequested = false;
static const int _authorNoteLines = 2;
static const double _authorNoteGap = 4;
static const double _authorNoteFont = 10;
String get _synopsis => _fullSynopsis ?? widget.synopsis;
@override
void didChangeDependencies() {
super.didChangeDependencies();
if (_detailRequested || widget.drama.detailLoaded) return;
_detailRequested = true;
unawaited(_loadFullSynopsis());
}
@override
void didUpdateWidget(covariant _EpisodeSynopsis oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.drama.id == widget.drama.id &&
oldWidget.synopsis == widget.synopsis) {
return;
}
_fullSynopsis = null;
_detailRequested = !widget.drama.detailLoaded;
if (_detailRequested) unawaited(_loadFullSynopsis());
}
Future<void> _loadFullSynopsis() async {
if (widget.drama.detailLoaded) return;
final request = ++_detailRequest;
final CatalogController catalog;
try {
catalog = context.read<CatalogController>();
} catch (_) {
return;
}
try {
final detail = await catalog.requireDetail(widget.drama);
if (!mounted || request != _detailRequest) return;
final full = localizedDramaSynopsis(widget.l10n, detail);
if (full == widget.synopsis) return;
setState(() => _fullSynopsis = full);
} catch (_) {
// 详情失败时继续用列表里的简介。
}
}
@override
Widget build(BuildContext context) {
return ExpandableSynopsis(
key: ValueKey(widget.drama.id),
keyPrefix: 'player',
synopsis: _synopsis,
expandedFooter: widget.authorNote.isEmpty ? null : _authorNote(),
);
}
Widget _authorNote() => Padding(
padding: const EdgeInsets.only(top: _authorNoteGap),
child: Text(
widget.authorNote,
key: const Key('player-author-note'),
maxLines: _authorNoteLines,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: AppColors.playerMuted,
fontSize: _authorNoteFont,
shadows: _playerTextShadows,
),
),
);
}