fix(recommend): 延后推荐数据刷新
This commit is contained in:
1 parent
6be024f86f
commit
4b97f89c0b
2 files changed
+218
-5
No files matched your search
@@ -14,6 +14,7 @@ import '../../core/data/services/playlist_controller.dart';
|
|||||||
import '../../core/theme/app_colors.dart';
|
import '../../core/theme/app_colors.dart';
|
||||||
import '../../core/widgets/empty_state.dart';
|
import '../../core/widgets/empty_state.dart';
|
||||||
import '../../l10n/app_localizations.dart';
|
import '../../l10n/app_localizations.dart';
|
||||||
|
import '../home/home_feed_channel.dart';
|
||||||
import '../player/open_player.dart';
|
import '../player/open_player.dart';
|
||||||
import '../player/player_view.dart';
|
import '../player/player_view.dart';
|
||||||
import 'recommend_types.dart';
|
import 'recommend_types.dart';
|
||||||
@@ -51,6 +52,8 @@ class _RecommendPageState extends State<RecommendPage>
|
|||||||
RecommendInnerTab _tab = RecommendInnerTab.feed;
|
RecommendInnerTab _tab = RecommendInnerTab.feed;
|
||||||
final PageController _pageController = PageController();
|
final PageController _pageController = PageController();
|
||||||
int _scrubCancelRequest = 0;
|
int _scrubCancelRequest = 0;
|
||||||
|
bool _recommendDataRefreshScheduled = false;
|
||||||
|
HomeFeedChannel? _observedFeedChannel;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool get wantKeepAlive => true;
|
bool get wantKeepAlive => true;
|
||||||
@@ -58,22 +61,48 @@ class _RecommendPageState extends State<RecommendPage>
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
_observedFeedChannel = widget.feedController.channel;
|
||||||
|
widget.feedController.addListener(_handleFeedControllerChanged);
|
||||||
if (widget.active) _scheduleRecommendData();
|
if (widget.active) _scheduleRecommendData();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void didUpdateWidget(RecommendPage oldWidget) {
|
void didUpdateWidget(RecommendPage oldWidget) {
|
||||||
super.didUpdateWidget(oldWidget);
|
super.didUpdateWidget(oldWidget);
|
||||||
if (widget.active && !oldWidget.active) _scheduleRecommendData();
|
if (oldWidget.feedController != widget.feedController) {
|
||||||
|
oldWidget.feedController.removeListener(_handleFeedControllerChanged);
|
||||||
|
_observedFeedChannel = widget.feedController.channel;
|
||||||
|
widget.feedController.addListener(_handleFeedControllerChanged);
|
||||||
|
}
|
||||||
|
if (_requiresRecommendDataRefresh(oldWidget)) {
|
||||||
|
_scheduleRecommendData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _requiresRecommendDataRefresh(RecommendPage oldWidget) {
|
||||||
|
if (!widget.active) return false;
|
||||||
|
return !oldWidget.active ||
|
||||||
|
oldWidget.feedController != widget.feedController ||
|
||||||
|
oldWidget.catalogApi != widget.catalogApi;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleFeedControllerChanged() {
|
||||||
|
final channel = widget.feedController.channel;
|
||||||
|
if (channel == _observedFeedChannel) return;
|
||||||
|
_observedFeedChannel = channel;
|
||||||
|
if (widget.active) _scheduleRecommendData();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _scheduleRecommendData() {
|
void _scheduleRecommendData() {
|
||||||
final feed = widget.feedController;
|
if (_recommendDataRefreshScheduled) return;
|
||||||
if (!feed.loaded && !feed.loadingFirstPage) {
|
_recommendDataRefreshScheduled = true;
|
||||||
unawaited(feed.refreshCurrent());
|
|
||||||
}
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
_recommendDataRefreshScheduled = false;
|
||||||
if (!mounted || !widget.active) return;
|
if (!mounted || !widget.active) return;
|
||||||
|
final feed = widget.feedController;
|
||||||
|
if (!feed.loaded && !feed.loadingFirstPage) {
|
||||||
|
unawaited(feed.refreshCurrent());
|
||||||
|
}
|
||||||
final playlists = _readPlaylists();
|
final playlists = _readPlaylists();
|
||||||
if (playlists != null && !playlists.loaded && !playlists.loading) {
|
if (playlists != null && !playlists.loaded && !playlists.loading) {
|
||||||
unawaited(playlists.load());
|
unawaited(playlists.load());
|
||||||
@@ -83,6 +112,7 @@ class _RecommendPageState extends State<RecommendPage>
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
widget.feedController.removeListener(_handleFeedControllerChanged);
|
||||||
_pageController.dispose();
|
_pageController.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,183 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:fruitshorts/core/data/services/category_controller.dart';
|
||||||
|
import 'package:fruitshorts/core/data/services/home_feed_controller.dart';
|
||||||
|
import 'package:fruitshorts/l10n/app_localizations.dart';
|
||||||
|
import 'package:fruitshorts/pages/home/home_feed_channel.dart';
|
||||||
|
import 'package:fruitshorts/pages/recommend/recommend_page.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
import '../support/fake_catalog.dart';
|
||||||
|
import '../support/fake_category.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
testWidgets('should_refresh_once_when_parent_rebuilds_repeatedly', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
|
final fixture = await _pumpRecommend(tester, active: true);
|
||||||
|
|
||||||
|
expect(fixture.controller.refreshCalls, 1);
|
||||||
|
for (var index = 0; index < 20; index++) {
|
||||||
|
fixture.harnessKey.currentState!.rebuildSameInputs();
|
||||||
|
await tester.pump();
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(tester.takeException(), isNull);
|
||||||
|
expect(fixture.controller.refreshCalls, 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('should_refresh_once_when_active_turns_on', (tester) async {
|
||||||
|
final fixture = await _pumpRecommend(tester, active: false);
|
||||||
|
expect(fixture.controller.refreshCalls, 0);
|
||||||
|
|
||||||
|
fixture.harnessKey.currentState!.update(active: true);
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
expect(fixture.controller.refreshCalls, 1);
|
||||||
|
fixture.harnessKey.currentState!.rebuildSameInputs();
|
||||||
|
await tester.pump();
|
||||||
|
expect(fixture.controller.refreshCalls, 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('should_refresh_again_when_data_source_changes', (tester) async {
|
||||||
|
final fixture = await _pumpRecommend(tester, active: true);
|
||||||
|
expect(fixture.controller.refreshCalls, 1);
|
||||||
|
|
||||||
|
fixture.harnessKey.currentState!.update(active: false);
|
||||||
|
await tester.pump();
|
||||||
|
expect(fixture.controller.refreshCalls, 1);
|
||||||
|
|
||||||
|
fixture.harnessKey.currentState!.update(active: true);
|
||||||
|
await tester.pump();
|
||||||
|
expect(fixture.controller.refreshCalls, 2);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('should_not_refresh_after_dispose_when_channel_changes', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
|
final fixture = await _pumpRecommend(tester, active: true);
|
||||||
|
expect(fixture.controller.refreshCalls, 1);
|
||||||
|
|
||||||
|
fixture.harnessKey.currentState!.update(active: true);
|
||||||
|
final selection = fixture.controller.selectChannel(HomeFeedChannel.anime);
|
||||||
|
await tester.pumpWidget(const MaterialApp(home: SizedBox.shrink()));
|
||||||
|
await selection;
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
expect(fixture.controller.refreshCalls, 1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<_RecommendFixture> _pumpRecommend(
|
||||||
|
WidgetTester tester, {
|
||||||
|
required bool active,
|
||||||
|
}) async {
|
||||||
|
tester.view.physicalSize = const Size(390, 844);
|
||||||
|
tester.view.devicePixelRatio = 1;
|
||||||
|
addTearDown(tester.view.resetPhysicalSize);
|
||||||
|
addTearDown(tester.view.resetDevicePixelRatio);
|
||||||
|
|
||||||
|
final catalog = FakeCatalogRepository();
|
||||||
|
final controller = _RecordingHomeFeedController(catalog);
|
||||||
|
final categories = CategoryController(
|
||||||
|
FakeCategoryRepository(catalog: catalog),
|
||||||
|
);
|
||||||
|
final harnessKey = GlobalKey<_RecommendHarnessState>();
|
||||||
|
final fixture = _RecommendFixture(
|
||||||
|
controller: controller,
|
||||||
|
categoryController: categories,
|
||||||
|
harnessKey: harnessKey,
|
||||||
|
);
|
||||||
|
addTearDown(controller.dispose);
|
||||||
|
addTearDown(categories.dispose);
|
||||||
|
|
||||||
|
await tester.pumpWidget(fixture.app(active: active));
|
||||||
|
await tester.pump();
|
||||||
|
return fixture;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _RecommendFixture {
|
||||||
|
const _RecommendFixture({
|
||||||
|
required this.controller,
|
||||||
|
required this.categoryController,
|
||||||
|
required this.harnessKey,
|
||||||
|
});
|
||||||
|
|
||||||
|
final _RecordingHomeFeedController controller;
|
||||||
|
final CategoryController categoryController;
|
||||||
|
final GlobalKey<_RecommendHarnessState> harnessKey;
|
||||||
|
|
||||||
|
Widget app({required bool active}) {
|
||||||
|
return MaterialApp(
|
||||||
|
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||||
|
supportedLocales: AppLocalizations.supportedLocales,
|
||||||
|
home: ChangeNotifierProvider<CategoryController>.value(
|
||||||
|
value: categoryController,
|
||||||
|
child: _RecommendHarness(
|
||||||
|
key: harnessKey,
|
||||||
|
controller: controller,
|
||||||
|
active: active,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _RecommendHarness extends StatefulWidget {
|
||||||
|
const _RecommendHarness({
|
||||||
|
super.key,
|
||||||
|
required this.controller,
|
||||||
|
required this.active,
|
||||||
|
});
|
||||||
|
|
||||||
|
final HomeFeedController controller;
|
||||||
|
final bool active;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<_RecommendHarness> createState() => _RecommendHarnessState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _RecommendHarnessState extends State<_RecommendHarness> {
|
||||||
|
late HomeFeedController _controller;
|
||||||
|
late bool _active;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_controller = widget.controller;
|
||||||
|
_active = widget.active;
|
||||||
|
}
|
||||||
|
|
||||||
|
void rebuildSameInputs() {
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
|
||||||
|
void update({required bool active}) {
|
||||||
|
setState(() {
|
||||||
|
_active = active;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return RecommendPage(
|
||||||
|
feedController: _controller,
|
||||||
|
active: _active,
|
||||||
|
videoOnly: true,
|
||||||
|
onSearch: () {},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _RecordingHomeFeedController extends HomeFeedController {
|
||||||
|
_RecordingHomeFeedController(super.repository);
|
||||||
|
|
||||||
|
int refreshCalls = 0;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> refreshCurrent() async {
|
||||||
|
refreshCalls++;
|
||||||
|
notifyListeners();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user