fix(home): 首切频道预取并保留播放画面
This commit is contained in:
1 parent
dfa79bed88
commit
677e49fa6e
5 files changed
+242
-27
No files matched your search
@@ -52,6 +52,14 @@ class HomeFeedController extends ChangeNotifier {
|
||||
_notifyIfCurrent(HomeFeedChannel.recommend);
|
||||
}
|
||||
|
||||
/// 后台预取漫剧与真人剧首屏,不切换频道,也不影响当前列表。
|
||||
Future<void> prefetchChannels() async {
|
||||
await Future.wait([
|
||||
_prefetchChannel(HomeFeedChannel.anime),
|
||||
_prefetchChannel(HomeFeedChannel.live),
|
||||
]);
|
||||
}
|
||||
|
||||
/// 同步切到目标频道;有缓存先渲染,无缓存时复用或等待在飞请求。
|
||||
Future<void> selectChannel(HomeFeedChannel next) async {
|
||||
if (next == _channel) return;
|
||||
@@ -59,7 +67,9 @@ class HomeFeedController extends ChangeNotifier {
|
||||
_notify();
|
||||
final state = _state(next);
|
||||
if (state.dramas.isNotEmpty) {
|
||||
if (!state.loading) {
|
||||
if (state.skipNextRefresh) {
|
||||
state.skipNextRefresh = false;
|
||||
} else if (!state.loading) {
|
||||
unawaited(_refresh(next, keepData: true));
|
||||
}
|
||||
return;
|
||||
@@ -67,6 +77,7 @@ class HomeFeedController extends ChangeNotifier {
|
||||
if (state.loading) {
|
||||
final inFlight = state.inFlight;
|
||||
if (inFlight != null) await inFlight;
|
||||
state.skipNextRefresh = false;
|
||||
return;
|
||||
}
|
||||
await _refresh(next, keepData: false);
|
||||
@@ -126,6 +137,13 @@ class HomeFeedController extends ChangeNotifier {
|
||||
return operation;
|
||||
}
|
||||
|
||||
Future<void> _prefetchChannel(HomeFeedChannel channel) async {
|
||||
final state = _state(channel);
|
||||
if (state.loaded || state.loading) return;
|
||||
final success = await _refresh(channel, keepData: false);
|
||||
if (success && !_disposed) state.skipNextRefresh = true;
|
||||
}
|
||||
|
||||
Future<bool> _performRefresh(
|
||||
HomeFeedChannel channel, {
|
||||
required bool keepData,
|
||||
@@ -200,4 +218,5 @@ class _ChannelFeedState {
|
||||
String? lastError;
|
||||
int requestSeq = 0;
|
||||
Future<bool>? inFlight;
|
||||
bool skipNextRefresh = false;
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import 'package:flutter/services.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../../core/config/app_config.dart';
|
||||
import '../../core/data/models/drama.dart';
|
||||
import '../../core/data/services/home_feed_controller.dart';
|
||||
import '../../core/data/services/catalog_controller.dart';
|
||||
import '../../core/data/services/catalog_retry.dart';
|
||||
@@ -141,7 +142,7 @@ class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
|
||||
_recommendSeeded = true;
|
||||
homeFeed.seedRecommend(catalog.dramas);
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (mounted) unawaited(homeFeed.refreshCurrent());
|
||||
if (mounted) unawaited(_refreshRecommendAndPrefetch(homeFeed));
|
||||
});
|
||||
}
|
||||
return _HomeTabs(key: const Key('home-tabs'), feedController: homeFeed);
|
||||
@@ -151,6 +152,12 @@ class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
|
||||
return _homeFeed ??= HomeFeedController(catalog.repository);
|
||||
}
|
||||
|
||||
Future<void> _refreshRecommendAndPrefetch(HomeFeedController homeFeed) async {
|
||||
if (await homeFeed.refreshCurrent()) {
|
||||
await homeFeed.prefetchChannels();
|
||||
}
|
||||
}
|
||||
|
||||
Widget _startupSplash() {
|
||||
final isAndroid = defaultTargetPlatform == TargetPlatform.android;
|
||||
final size = isAndroid ? _androidSplashSize : _defaultSplashSize;
|
||||
@@ -222,11 +229,15 @@ class _HomeTabs extends StatefulWidget {
|
||||
|
||||
class _HomeTabsState extends State<_HomeTabs> {
|
||||
late final HomeLogic logic;
|
||||
List<Drama>? _visibleDramas;
|
||||
HomeFeedChannel? _visibleChannel;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
logic = HomeBinding().create(widget.feedController.dramas);
|
||||
_visibleDramas = widget.feedController.dramas;
|
||||
_visibleChannel = widget.feedController.channel;
|
||||
widget.feedController.addListener(_onFeedChanged);
|
||||
}
|
||||
|
||||
@@ -291,26 +302,70 @@ class _HomeTabsState extends State<_HomeTabs> {
|
||||
}
|
||||
|
||||
Widget _home(BuildContext context) {
|
||||
final dramas = widget.feedController.dramas;
|
||||
if (dramas.isEmpty) return _feedChannelPlaceholder(context);
|
||||
return PlayerPage(
|
||||
homeMode: true,
|
||||
embedded: true,
|
||||
active: logic.tab == 0,
|
||||
drama: dramas.first,
|
||||
initialIndex: 0,
|
||||
feedDramas: dramas,
|
||||
feedChannel: widget.feedController.channel,
|
||||
onOpenFullPlayer: (drama, episode) =>
|
||||
logic.playDrama(context, drama, episodeId: episode.id),
|
||||
onRefreshFeed: widget.feedController.refreshCurrent,
|
||||
onLoadMore: widget.feedController.loadMore,
|
||||
onChannelSelected: _selectFeedChannel,
|
||||
onDramaChanged: logic.onDramaChanged,
|
||||
onImmersiveChanged: logic.onImmersiveChanged,
|
||||
onSearch: () => logic.select(1, search: true),
|
||||
onDiscover: () => logic.select(1),
|
||||
onMy: () => logic.select(3),
|
||||
final currentDramas = widget.feedController.dramas;
|
||||
final currentChannel = widget.feedController.channel;
|
||||
if (currentDramas.isNotEmpty) {
|
||||
_visibleDramas = currentDramas;
|
||||
_visibleChannel = currentChannel;
|
||||
}
|
||||
if (currentDramas.isEmpty && !widget.feedController.loadingFirstPage) {
|
||||
return _feedChannelPlaceholder(context);
|
||||
}
|
||||
final visibleDramas = _visibleDramas;
|
||||
final visibleChannel = _visibleChannel;
|
||||
final switching =
|
||||
currentDramas.isEmpty && widget.feedController.loadingFirstPage;
|
||||
if (visibleDramas == null ||
|
||||
visibleDramas.isEmpty ||
|
||||
visibleChannel == null) {
|
||||
return _feedChannelPlaceholder(context);
|
||||
}
|
||||
final playerDramas = currentDramas.isEmpty ? visibleDramas : currentDramas;
|
||||
final playerChannel = currentDramas.isEmpty
|
||||
? visibleChannel
|
||||
: currentChannel;
|
||||
return Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
PlayerPage(
|
||||
homeMode: true,
|
||||
embedded: true,
|
||||
active: logic.tab == 0,
|
||||
drama: playerDramas.first,
|
||||
initialIndex: 0,
|
||||
feedDramas: playerDramas,
|
||||
feedChannel: playerChannel,
|
||||
headerChannel: currentChannel,
|
||||
onOpenFullPlayer: (drama, episode) =>
|
||||
logic.playDrama(context, drama, episodeId: episode.id),
|
||||
onRefreshFeed: widget.feedController.refreshCurrent,
|
||||
onLoadMore: widget.feedController.loadMore,
|
||||
onChannelSelected: _selectFeedChannel,
|
||||
onDramaChanged: logic.onDramaChanged,
|
||||
onImmersiveChanged: logic.onImmersiveChanged,
|
||||
onSearch: () => logic.select(1, search: true),
|
||||
onDiscover: () => logic.select(1),
|
||||
onMy: () => logic.select(3),
|
||||
),
|
||||
if (switching)
|
||||
Positioned.fill(
|
||||
child: IgnorePointer(
|
||||
key: const Key('home-feed-channel-loading'),
|
||||
child: ColoredBox(
|
||||
color: Colors.black26,
|
||||
child: Center(
|
||||
child: SizedBox.square(
|
||||
dimension: 24,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2.4,
|
||||
color: Colors.white70,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -59,6 +59,7 @@ class PlayerPage extends StatefulWidget {
|
||||
this.onMenu,
|
||||
this.feedDramas,
|
||||
this.feedChannel,
|
||||
this.headerChannel,
|
||||
this.onDramaChanged,
|
||||
this.onImmersiveChanged,
|
||||
this.onOpenFullPlayer,
|
||||
@@ -82,6 +83,7 @@ class PlayerPage extends StatefulWidget {
|
||||
final VoidCallback? onMenu;
|
||||
final List<Drama>? feedDramas;
|
||||
final HomeFeedChannel? feedChannel;
|
||||
final HomeFeedChannel? headerChannel;
|
||||
final ValueChanged<Drama>? onDramaChanged;
|
||||
final ValueChanged<bool>? onImmersiveChanged;
|
||||
final Future<void> Function(Drama drama, Episode episode)? onOpenFullPlayer;
|
||||
@@ -295,7 +297,10 @@ class _PlayerPageState extends State<PlayerPage>
|
||||
bottom: false,
|
||||
child: HomeFeedHeader(
|
||||
l10n: l10n,
|
||||
channel: widget.feedChannel ?? HomeFeedChannel.recommend,
|
||||
channel:
|
||||
widget.headerChannel ??
|
||||
widget.feedChannel ??
|
||||
HomeFeedChannel.recommend,
|
||||
onChannelSelected: widget.onChannelSelected ?? (_) {},
|
||||
onRefresh: () async {
|
||||
final ok = await widget.onRefreshFeed?.call() ?? true;
|
||||
|
||||
Reference in new issue
Block a user