189 lines
6.0 KiB
Dart
189 lines
6.0 KiB
Dart
// Author: fruit.com.cn
|
|
|
|
part of 'player_view.dart';
|
|
|
|
extension _PlayerViewActions on _PlayerPageState {
|
|
Future<void> _openSpeedSheet(BuildContext context) async {
|
|
final selected = await showModalBottomSheet<double>(
|
|
context: context,
|
|
backgroundColor: AppColors.transparent,
|
|
showDragHandle: false,
|
|
builder: (_) => PlayerSpeedSheet(selected: _playbackSpeed),
|
|
);
|
|
if (selected == null || !mounted) return;
|
|
_updatePlaybackSpeed(selected);
|
|
await logic.setPlaybackSpeed(selected);
|
|
}
|
|
|
|
Future<void> _openMore(BuildContext context, Episode episode) async {
|
|
final l10n = AppLocalizations.of(context)!;
|
|
await showModalBottomSheet<void>(
|
|
context: context,
|
|
backgroundColor: AppColors.transparent,
|
|
showDragHandle: false,
|
|
isScrollControlled: true,
|
|
builder: (_) => PlayerMoreSheet(
|
|
l10n: l10n,
|
|
state: PlayerMoreState(
|
|
speed: _playbackSpeed,
|
|
qualities: logic.qualities,
|
|
selectedQuality: logic.selectedQuality,
|
|
),
|
|
actions: PlayerMoreActions(
|
|
onSpeedChanged: (value) {
|
|
_updatePlaybackSpeed(value);
|
|
unawaited(logic.setPlaybackSpeed(value));
|
|
},
|
|
onQualityChanged: (quality) => _switchQuality(context, l10n, quality),
|
|
onClear: _hideChrome,
|
|
onDownload: () => requestEpisodeDownload(context, episode.id),
|
|
// iOS 不显示手动「画中画」入口(走下方「退出后自动小窗」开关)。
|
|
onPip: logic.pictureInPicture.isPlatformSupported && !logic.iosDevice
|
|
? () => unawaited(logic.enterPip(context))
|
|
: null,
|
|
autoPipEnabled: logic.iosAutoPipEnabled,
|
|
onAutoPipChanged: logic.iosDevice
|
|
? (value) => unawaited(logic.setIosAutoPip(value))
|
|
: null,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Future<bool> _switchQuality(
|
|
BuildContext context,
|
|
AppLocalizations l10n,
|
|
String quality,
|
|
) async {
|
|
Navigator.of(context).pop();
|
|
final switched = await logic.selectQuality(quality);
|
|
if (!context.mounted || !switched) return false;
|
|
final label = quality.toUpperCase() == 'AUTO' ? l10n.qualityAuto : quality;
|
|
showCenteredToast(context, l10n.qualitySwitched(label));
|
|
return true;
|
|
}
|
|
|
|
Future<void> _openUnlockSheet(
|
|
BuildContext context,
|
|
int index,
|
|
Episode episode,
|
|
) async {
|
|
final wallet = logic.wallet;
|
|
if (wallet == null) return;
|
|
if (!context.read<AuthController>().isSignedIn &&
|
|
!await ensureSignedIn(context)) {
|
|
return;
|
|
}
|
|
if (!context.mounted) return;
|
|
await showModalBottomSheet<bool>(
|
|
context: context,
|
|
backgroundColor: AppColors.transparent,
|
|
showDragHandle: false,
|
|
isScrollControlled: true,
|
|
builder: (_) => PlayerUnlockSheet(
|
|
l10n: AppLocalizations.of(context)!,
|
|
episodeNumber: episode.number,
|
|
coinCost: logic.blockedTicket?.unlockCoin ?? 0,
|
|
balance: wallet.snapshot == null ? null : wallet.balance,
|
|
onUnlock: () => _unlockEpisode(context, index),
|
|
),
|
|
);
|
|
}
|
|
|
|
Future<bool> _unlockEpisode(BuildContext context, int index) async {
|
|
if (!context.read<AuthController>().isSignedIn) return false;
|
|
final ok = await logic.unlockEpisode(index);
|
|
if (ok || !context.mounted) return ok;
|
|
final l10n = AppLocalizations.of(context)!;
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(
|
|
content: Text(errorText(l10n, logic.unlockError, l10n.unlockFailed)),
|
|
),
|
|
);
|
|
return false;
|
|
}
|
|
|
|
Future<void> _toggleLike(
|
|
BuildContext context,
|
|
InteractionController interactions,
|
|
Episode episode, {
|
|
bool likeOnly = false,
|
|
}) async {
|
|
final busyBefore = interactions.isEpisodeBusy(episode.id);
|
|
final likedBefore = interactions.isEpisodeLiked(episode.id);
|
|
if (kDebugMode) {
|
|
debugPrint(
|
|
'[player-like] phase=toggle-start '
|
|
'episodeId=${episode.id} likeOnly=$likeOnly '
|
|
'busy=$busyBefore liked=$likedBefore',
|
|
);
|
|
}
|
|
unawaited(context.read<Haptics>().lightImpact());
|
|
if (busyBefore || (likeOnly && likedBefore)) {
|
|
if (kDebugMode) {
|
|
debugPrint(
|
|
'[player-like] phase=toggle-skipped '
|
|
'episodeId=${episode.id} '
|
|
'reason=${busyBefore ? 'busy' : 'already-liked'}',
|
|
);
|
|
}
|
|
return;
|
|
}
|
|
final result = await interactions.toggleEpisodeLike(episode.id);
|
|
if (kDebugMode) {
|
|
debugPrint(
|
|
'[player-like] phase=toggle-result '
|
|
'episodeId=${episode.id} success=${result.isSuccess} '
|
|
'liked=${interactions.isEpisodeLiked(episode.id)} '
|
|
'count=${interactions.episodeLikeCount(episode.id)} '
|
|
'error=${result.errorMessage ?? '-'}',
|
|
);
|
|
}
|
|
if (result.isSuccess) {
|
|
final likeCount = interactions.episodeLikeCount(episode.id);
|
|
if (likeCount != null) {
|
|
logic.applyEpisodeLikeState(
|
|
episode.id,
|
|
liked: interactions.isEpisodeLiked(episode.id),
|
|
likeCount: likeCount,
|
|
);
|
|
}
|
|
_refreshLikeState();
|
|
return;
|
|
}
|
|
if (!context.mounted) return;
|
|
if (result.errorCode == 200007) {
|
|
await ensureSignedIn(context);
|
|
return;
|
|
}
|
|
showCenteredToast(
|
|
context,
|
|
result.errorMessage ?? AppLocalizations.of(context)!.likeFailed,
|
|
);
|
|
}
|
|
|
|
Future<void> _toggleSave(BuildContext context, Drama drama) async {
|
|
final favorites = context.read<FavoriteController>();
|
|
unawaited(context.read<Haptics>().lightImpact());
|
|
final wasSaved = favorites.isSaved(drama.id);
|
|
final ok = await favorites.toggle(drama.id, card: drama);
|
|
if (!context.mounted) return;
|
|
if (ok) {
|
|
final nowSaved = favorites.isSaved(drama.id);
|
|
if (nowSaved != wasSaved) {
|
|
logic.markDramaFavoriteChanged(drama.id);
|
|
}
|
|
return;
|
|
}
|
|
if (favorites.lastErrorCode == 200007) {
|
|
await ensureSignedIn(context);
|
|
return;
|
|
}
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(
|
|
content: Text(AppLocalizations.of(context)!.favoriteToggleFailed),
|
|
),
|
|
);
|
|
}
|
|
}
|