feat(following): 收藏页改为紧凑布局并去掉重复续播横幅
This commit is contained in:
1 parent
fd2ba0bf43
commit
c5b94633f1
3 files changed
+147
-78
No files matched your search
@@ -1,22 +1,18 @@
|
||||
// Author: fruit.com.cn
|
||||
// 职责:追剧收藏网格、分页和编辑选择,不承载顶部续播或重复区块标题。
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../core/data/models/watch_record.dart';
|
||||
import '../../core/data/services/favorite_controller.dart';
|
||||
import '../../core/data/services/watch_records_controller.dart';
|
||||
import '../../core/formatting/error_text.dart';
|
||||
import '../../core/theme/app_colors.dart';
|
||||
import '../../core/theme/app_dimens.dart';
|
||||
import '../../core/widgets/empty_state.dart';
|
||||
import '../../l10n/app_localizations.dart';
|
||||
import '../player/open_player.dart';
|
||||
import 'following_grid_card.dart';
|
||||
import 'following_paged_grid.dart';
|
||||
import 'widgets/continue_watching_banner.dart';
|
||||
|
||||
/// 追剧编辑中的选中状态。
|
||||
class FollowingFavoriteSelection {
|
||||
@@ -36,19 +32,15 @@ class FollowingFavoritesGrid extends StatelessWidget {
|
||||
const FollowingFavoritesGrid({
|
||||
super.key,
|
||||
required this.controller,
|
||||
required this.history,
|
||||
required this.selection,
|
||||
required this.scrollController,
|
||||
required this.onGoHome,
|
||||
required this.onContinueRecord,
|
||||
});
|
||||
|
||||
final FavoriteController controller;
|
||||
final WatchRecordsController history;
|
||||
final FollowingFavoriteSelection selection;
|
||||
final ScrollController scrollController;
|
||||
final VoidCallback onGoHome;
|
||||
final ValueChanged<WatchRecord> onContinueRecord;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -59,7 +51,7 @@ class FollowingFavoritesGrid extends StatelessWidget {
|
||||
valueListenable: selection.selected,
|
||||
builder: (context, picked, _) {
|
||||
return ListenableBuilder(
|
||||
listenable: Listenable.merge([controller, history]),
|
||||
listenable: controller,
|
||||
builder: (context, _) => _grid(context, isEditing, picked),
|
||||
);
|
||||
},
|
||||
@@ -89,7 +81,6 @@ class FollowingFavoritesGrid extends StatelessWidget {
|
||||
onRefresh: controller.load,
|
||||
onLoadMore: controller.loadMore,
|
||||
itemCount: controller.cards.length + extra,
|
||||
topSlivers: _topSlivers(context),
|
||||
empty: EmptyState(
|
||||
key: const Key('following-favorites-empty'),
|
||||
icon: Icons.bookmark_border_rounded,
|
||||
@@ -99,68 +90,6 @@ class FollowingFavoritesGrid extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> _topSlivers(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final continueRecord = _continueRecord();
|
||||
return [
|
||||
if (continueRecord != null)
|
||||
SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(
|
||||
AppDimens.feedPageHorizontal,
|
||||
4,
|
||||
AppDimens.feedPageHorizontal,
|
||||
0,
|
||||
),
|
||||
child: ContinueWatchingBanner(
|
||||
record: continueRecord,
|
||||
languageCode: Localizations.localeOf(context).languageCode,
|
||||
onTap: () => onContinueRecord(continueRecord),
|
||||
),
|
||||
),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(
|
||||
AppDimens.feedPageHorizontal,
|
||||
AppDimens.followingSectionTopGap,
|
||||
AppDimens.feedPageHorizontal,
|
||||
AppDimens.followingSectionBottomGap,
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Text(
|
||||
l10n.favoriteDramas,
|
||||
style: const TextStyle(
|
||||
color: AppColors.textPrimary,
|
||||
fontSize: AppDimens.followingSectionTitleFont,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 2),
|
||||
child: Text(
|
||||
l10n.totalDramasCount(controller.total),
|
||||
style: const TextStyle(
|
||||
color: AppColors.textTabInactive,
|
||||
fontSize: AppDimens.followingSectionCountFont,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
/// 最近一条观看记录(records 已按最近观看排序,与观看历史页首条一致)。
|
||||
WatchRecord? _continueRecord() {
|
||||
return history.records.isEmpty ? null : history.records.first;
|
||||
}
|
||||
|
||||
Widget _item(
|
||||
BuildContext context,
|
||||
int index,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// Author: fruit.com.cn
|
||||
// 职责:追剧收藏与观看历史切换、数据刷新、管理和续播入口。
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
@@ -39,7 +40,7 @@ class FollowingPage extends StatefulWidget {
|
||||
final FavoriteController favoriteController;
|
||||
final int historyRequest;
|
||||
|
||||
/// 当前是否为首页选中的 tab;重新可见时刷新观看历史,保证横幅是最新进度。
|
||||
/// 当前是否为首页选中的 tab;重新可见时刷新观看历史,保证记录是最新进度。
|
||||
final bool visible;
|
||||
|
||||
@override
|
||||
@@ -157,7 +158,6 @@ class _FollowingPageState extends State<FollowingPage> {
|
||||
FollowingKeepAliveTab(
|
||||
child: FollowingFavoritesGrid(
|
||||
controller: widget.favoriteController,
|
||||
history: _history,
|
||||
selection: FollowingFavoriteSelection(
|
||||
editing: _editing,
|
||||
selected: _selected,
|
||||
@@ -165,9 +165,6 @@ class _FollowingPageState extends State<FollowingPage> {
|
||||
),
|
||||
scrollController: _favoriteScroll,
|
||||
onGoHome: widget.onGoHome,
|
||||
onContinueRecord: (record) => unawaited(
|
||||
_openHistory(context, _history, record),
|
||||
),
|
||||
),
|
||||
),
|
||||
FollowingKeepAliveTab(
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
// Author: fruit.com.cn
|
||||
// 职责:验证追剧页移除顶部横幅/重复收藏标题后,卡片直达及历史续播能力。
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:fruitshorts/app.dart';
|
||||
import 'package:fruitshorts/core/data/models/watch_progress.dart';
|
||||
import 'package:fruitshorts/core/data/repositories/favorite_repository.dart';
|
||||
import 'package:fruitshorts/core/data/repositories/playback_repository.dart';
|
||||
import 'package:fruitshorts/core/data/repositories/watch_history_repository.dart';
|
||||
import 'package:fruitshorts/l10n/app_localizations.dart';
|
||||
import 'package:shared_preferences_platform_interface/in_memory_shared_preferences_async.dart';
|
||||
import 'package:shared_preferences_platform_interface/shared_preferences_async_platform_interface.dart';
|
||||
|
||||
import '../support/fake_catalog.dart';
|
||||
import '../support/fake_category.dart';
|
||||
import '../support/fake_interaction_repositories.dart';
|
||||
import '../support/widget_player.dart';
|
||||
|
||||
const _viewport = Size(390, 844);
|
||||
const _tolerance = 0.1;
|
||||
const _dramaId = 'the-signal-demo';
|
||||
const _banner = Key('following-continue-banner');
|
||||
const _page = Key('following-page');
|
||||
const _favoritesGrid = Key('following-favorites-grid');
|
||||
const _historyRecord = WatchProgress(
|
||||
dramaId: _dramaId,
|
||||
episodeId: 'the-signal-demo-3',
|
||||
dramaTitle: 'Recent story',
|
||||
episodeNumber: 3,
|
||||
totalEpisodes: 8,
|
||||
position: Duration(seconds: 25),
|
||||
duration: Duration(seconds: 100),
|
||||
);
|
||||
|
||||
Future<void> _pumpFollowing(
|
||||
WidgetTester tester, {
|
||||
bool hasFavorites = true,
|
||||
}) async {
|
||||
tester.view.physicalSize = _viewport;
|
||||
tester.view.devicePixelRatio = 1;
|
||||
addTearDown(tester.view.resetPhysicalSize);
|
||||
addTearDown(tester.view.resetDevicePixelRatio);
|
||||
final catalog = FakeCatalogRepository();
|
||||
final cards = hasFavorites
|
||||
? catalog.dramas.take(1).toList()
|
||||
: catalog.dramas.take(0).toList();
|
||||
await tester.pumpWidget(
|
||||
LumaDramaApp(
|
||||
playerFactory: createWidgetPlayer,
|
||||
catalogRepository: catalog,
|
||||
categoryRepository: FakeCategoryRepository(catalog: catalog),
|
||||
watchHistoryRepository: InMemoryWatchHistoryRepository(
|
||||
seed: [_historyRecord],
|
||||
),
|
||||
favoriteRepository: InMemoryFavoriteRepository(
|
||||
cards: cards,
|
||||
saved: cards.map((drama) => drama.id).toSet(),
|
||||
),
|
||||
playbackRepository: FakePlaybackRepository(),
|
||||
commentRepository: FakeCommentRepository(),
|
||||
interactionRepository: FakeInteractionRepository(),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.byKey(const Key('tab-follow')));
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
|
||||
void main() {
|
||||
setUp(() {
|
||||
SharedPreferencesAsyncPlatform.instance =
|
||||
InMemorySharedPreferencesAsync.empty();
|
||||
});
|
||||
|
||||
testWidgets('should_hide_continue_banner_when_recent_history_exists', (
|
||||
tester,
|
||||
) async {
|
||||
await _pumpFollowing(tester);
|
||||
expect(find.byKey(_banner), findsNothing);
|
||||
expect(find.byKey(const Key('following-card-$_dramaId')), findsOneWidget);
|
||||
expect(find.byKey(const Key('following-add-card')), findsOneWidget);
|
||||
expect(tester.takeException(), isNull);
|
||||
});
|
||||
|
||||
for (final hasFavorites in [true, false]) {
|
||||
testWidgets(
|
||||
'should_omit_section_heading_and_count_when_favorites_are_$hasFavorites',
|
||||
(tester) async {
|
||||
await _pumpFollowing(tester, hasFavorites: hasFavorites);
|
||||
final l10n = AppLocalizations.of(tester.element(find.byKey(_page)))!;
|
||||
final grid = find.byKey(_favoritesGrid);
|
||||
expect(
|
||||
find.descendant(of: grid, matching: find.text(l10n.favoriteDramas)),
|
||||
findsNothing,
|
||||
);
|
||||
expect(
|
||||
find.descendant(
|
||||
of: grid,
|
||||
matching: find.text(l10n.totalDramasCount(hasFavorites ? 1 : 0)),
|
||||
),
|
||||
findsNothing,
|
||||
);
|
||||
if (hasFavorites) {
|
||||
final header = tester.getRect(
|
||||
find.byKey(const Key('following-header')),
|
||||
);
|
||||
final card = tester.getRect(
|
||||
find.byKey(const Key('following-card-$_dramaId')),
|
||||
);
|
||||
expect(card.top, closeTo(header.bottom, _tolerance));
|
||||
} else {
|
||||
expect(
|
||||
find.byKey(const Key('following-favorites-empty')),
|
||||
findsOneWidget,
|
||||
);
|
||||
}
|
||||
expect(tester.takeException(), isNull);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('should_keep_history_continue_entry_when_history_tab_opens', (
|
||||
tester,
|
||||
) async {
|
||||
await _pumpFollowing(tester);
|
||||
await tester.tap(find.byKey(const Key('following-tab-history')));
|
||||
await tester.pumpAndSettle();
|
||||
expect(
|
||||
find.byKey(const Key('following-history-card-$_dramaId')),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(
|
||||
find.byKey(const Key('following-history-continue-$_dramaId')),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(
|
||||
find.byKey(const Key('following-progress-$_dramaId')),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(find.byKey(_banner), findsNothing);
|
||||
expect(tester.takeException(), isNull);
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user