// Author: fruit.com.cn part of 'player_view.dart'; extension _PlayerViewActions on _PlayerPageState { Future _openSpeedSheet(BuildContext context) async { final selected = await showModalBottomSheet( context: context, backgroundColor: AppColors.transparent, showDragHandle: false, builder: (_) => PlayerSpeedSheet(selected: _playbackSpeed), ); if (selected == null || !mounted) return; _updatePlaybackSpeed(selected); await logic.setPlaybackSpeed(selected); } Future _openMore(BuildContext context, Episode episode) async { final l10n = AppLocalizations.of(context)!; await showModalBottomSheet( 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 _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 _openUnlockSheet( BuildContext context, int index, Episode episode, ) async { final wallet = logic.wallet; if (wallet == null) return; if (!context.read().isSignedIn && !await ensureSignedIn(context)) { return; } if (!context.mounted) return; await showModalBottomSheet( 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 _unlockEpisode(BuildContext context, int index) async { if (!context.read().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 _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().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 _toggleSave(BuildContext context, Drama drama) async { final favorites = context.read(); unawaited(context.read().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), ), ); } }