双列网格原来是两个 Column 一次性建出所有卡片,翻几页后上百张卡连同封面都留在树上; 现在改成按行的 SliverList,只构建可见行,剧场页/新片页/搜索结果的滚动容器一并换成 sliver。 搜索结果、加载失败等状态同步改成 sliver,滚动位置与下拉刷新行为保持不变。
This commit is contained in:
1 parent
4aaa5bbd60
commit
fe49527dd8
6 files changed
+193
-145
No files matched your search
@@ -153,14 +153,19 @@ class _DiscoverPageState extends State<DiscoverPage> {
|
|||||||
edgeOffset: _stickyHeaderHeight + 8,
|
edgeOffset: _stickyHeaderHeight + 8,
|
||||||
onRefresh: () =>
|
onRefresh: () =>
|
||||||
Future.wait([categories.load(), theater.refreshCurrent()]),
|
Future.wait([categories.load(), theater.refreshCurrent()]),
|
||||||
child: ListView(
|
child: CustomScrollView(
|
||||||
controller: logic.scroll,
|
controller: logic.scroll,
|
||||||
physics: AppPullToRefresh.physics,
|
physics: AppPullToRefresh.physics,
|
||||||
padding: EdgeInsets.only(
|
slivers: [
|
||||||
top: showHeader ? _stickyHeaderHeight : 0,
|
if (showHeader)
|
||||||
bottom: AppDimens.spaceXl,
|
const SliverToBoxAdapter(
|
||||||
),
|
child: SizedBox(height: _stickyHeaderHeight),
|
||||||
children: _content(l10n, categories),
|
),
|
||||||
|
..._content(l10n, categories),
|
||||||
|
const SliverToBoxAdapter(
|
||||||
|
child: SizedBox(height: AppDimens.spaceXl),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -228,12 +233,12 @@ class _DiscoverPageState extends State<DiscoverPage> {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
if (categories.loading && categories.categories.isEmpty) {
|
if (categories.loading && categories.categories.isEmpty) {
|
||||||
return [_block(const CircularProgressIndicator(strokeWidth: 2))];
|
return [_sliverBlock(const CircularProgressIndicator(strokeWidth: 2))];
|
||||||
}
|
}
|
||||||
final error = categories.error;
|
final error = categories.error;
|
||||||
if (error != null) {
|
if (error != null) {
|
||||||
return [
|
return [
|
||||||
_block(
|
_sliverBlock(
|
||||||
EmptyState(
|
EmptyState(
|
||||||
key: const Key('home-categories-error'),
|
key: const Key('home-categories-error'),
|
||||||
icon: Icons.cloud_off_rounded,
|
icon: Icons.cloud_off_rounded,
|
||||||
@@ -253,12 +258,12 @@ class _DiscoverPageState extends State<DiscoverPage> {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
if (theater.loadingFirstPage) {
|
if (theater.loadingFirstPage) {
|
||||||
return [_block(const CircularProgressIndicator(strokeWidth: 2))];
|
return [_sliverBlock(const CircularProgressIndicator(strokeWidth: 2))];
|
||||||
}
|
}
|
||||||
final theaterError = theater.lastError;
|
final theaterError = theater.lastError;
|
||||||
if (theaterError != null && theater.dramas.isEmpty) {
|
if (theaterError != null && theater.dramas.isEmpty) {
|
||||||
return [
|
return [
|
||||||
_block(
|
_sliverBlock(
|
||||||
EmptyState(
|
EmptyState(
|
||||||
key: const Key('home-categories-error'),
|
key: const Key('home-categories-error'),
|
||||||
icon: Icons.cloud_off_rounded,
|
icon: Icons.cloud_off_rounded,
|
||||||
@@ -282,38 +287,49 @@ class _DiscoverPageState extends State<DiscoverPage> {
|
|||||||
List<Widget> _theaterBlocks(AppLocalizations l10n) {
|
List<Widget> _theaterBlocks(AppLocalizations l10n) {
|
||||||
final dramas = theater.dramas;
|
final dramas = theater.dramas;
|
||||||
return [
|
return [
|
||||||
_toolbar(),
|
SliverToBoxAdapter(child: _toolbar()),
|
||||||
if (dramas.isEmpty)
|
if (dramas.isEmpty)
|
||||||
Padding(
|
SliverToBoxAdapter(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 40),
|
child: Padding(
|
||||||
child: EmptyState(
|
padding: const EdgeInsets.symmetric(vertical: 40),
|
||||||
key: const Key('discover-empty'),
|
child: EmptyState(
|
||||||
icon: Icons.movie_filter_outlined,
|
key: const Key('discover-empty'),
|
||||||
title: l10n.noStories,
|
icon: Icons.movie_filter_outlined,
|
||||||
subtitle: l10n.emptyHint,
|
title: l10n.noStories,
|
||||||
|
subtitle: l10n.emptyHint,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
DramaGrid(l10n: l10n, dramas: dramas, onOpen: _openDrama),
|
SliverDramaGrid(
|
||||||
|
key: const Key('home-all-dramas-grid'),
|
||||||
|
l10n: l10n,
|
||||||
|
dramas: dramas,
|
||||||
|
onOpen: _openDrama,
|
||||||
|
),
|
||||||
if (theater.loadingMore)
|
if (theater.loadingMore)
|
||||||
const Padding(
|
const SliverToBoxAdapter(
|
||||||
padding: EdgeInsets.symmetric(vertical: 20),
|
child: Padding(
|
||||||
child: Center(
|
padding: EdgeInsets.symmetric(vertical: 20),
|
||||||
child: SizedBox.square(
|
child: Center(
|
||||||
key: Key('theater-load-more'),
|
child: SizedBox.square(
|
||||||
dimension: 22,
|
key: Key('theater-load-more'),
|
||||||
child: CircularProgressIndicator(strokeWidth: 2),
|
dimension: 22,
|
||||||
|
child: CircularProgressIndicator(strokeWidth: 2),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
else if (dramas.isNotEmpty && !theater.hasMore)
|
else if (dramas.isNotEmpty && !theater.hasMore)
|
||||||
Padding(
|
SliverToBoxAdapter(
|
||||||
key: const Key('paged-list-no-more'),
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
key: const Key('paged-list-no-more'),
|
||||||
child: Text(
|
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||||
l10n.noMoreItems,
|
child: Text(
|
||||||
textAlign: TextAlign.center,
|
l10n.noMoreItems,
|
||||||
style: const TextStyle(color: AppColors.textHint, fontSize: 12),
|
textAlign: TextAlign.center,
|
||||||
|
style: const TextStyle(color: AppColors.textHint, fontSize: 12),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
@@ -401,6 +417,8 @@ class _DiscoverPageState extends State<DiscoverPage> {
|
|||||||
Navigator.of(context).push<void>(MaterialPageRoute(builder: (_) => page));
|
Navigator.of(context).push<void>(MaterialPageRoute(builder: (_) => page));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _sliverBlock(Widget child) => SliverToBoxAdapter(child: _block(child));
|
||||||
|
|
||||||
Widget _block(Widget child) => ConstrainedBox(
|
Widget _block(Widget child) => ConstrainedBox(
|
||||||
constraints: const BoxConstraints(minHeight: 280),
|
constraints: const BoxConstraints(minHeight: 280),
|
||||||
child: Center(child: child),
|
child: Center(child: child),
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import '../../../l10n/app_localizations.dart';
|
|||||||
import 'drama_grid.dart';
|
import 'drama_grid.dart';
|
||||||
|
|
||||||
/// 剧场搜索结果:加载、失败与空结果都留在同一页内。
|
/// 剧场搜索结果:加载、失败与空结果都留在同一页内。
|
||||||
|
///
|
||||||
|
/// 自身是 sliver,好让结果网格跟着剧场页一起懒加载。
|
||||||
class DiscoverSearchResults extends StatelessWidget {
|
class DiscoverSearchResults extends StatelessWidget {
|
||||||
const DiscoverSearchResults({
|
const DiscoverSearchResults({
|
||||||
super.key,
|
super.key,
|
||||||
@@ -26,16 +28,24 @@ class DiscoverSearchResults extends StatelessWidget {
|
|||||||
final VoidCallback onRetry;
|
final VoidCallback onRetry;
|
||||||
final ValueChanged<Drama> onOpen;
|
final ValueChanged<Drama> onOpen;
|
||||||
|
|
||||||
|
static const EdgeInsets _topPadding = EdgeInsets.only(top: 12);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final l10n = AppLocalizations.of(context)!;
|
final l10n = AppLocalizations.of(context)!;
|
||||||
return Padding(
|
if (!loading && error == null && results.isNotEmpty) {
|
||||||
padding: const EdgeInsets.only(top: 12),
|
return SliverPadding(
|
||||||
child: _content(l10n),
|
padding: _topPadding,
|
||||||
|
sliver: SliverDramaGrid(l10n: l10n, dramas: results, onOpen: onOpen),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return SliverPadding(
|
||||||
|
padding: _topPadding,
|
||||||
|
sliver: SliverToBoxAdapter(child: _state(l10n)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _content(AppLocalizations l10n) {
|
Widget _state(AppLocalizations l10n) {
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return const Padding(
|
return const Padding(
|
||||||
padding: EdgeInsets.symmetric(vertical: 80),
|
padding: EdgeInsets.symmetric(vertical: 80),
|
||||||
@@ -61,14 +71,11 @@ class DiscoverSearchResults extends StatelessWidget {
|
|||||||
onAction: onRetry,
|
onAction: onRetry,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (results.isEmpty) {
|
return EmptyState(
|
||||||
return EmptyState(
|
key: const Key('discover-search-empty'),
|
||||||
key: const Key('discover-search-empty'),
|
icon: Icons.search_off_rounded,
|
||||||
icon: Icons.search_off_rounded,
|
title: l10n.noSearchResults,
|
||||||
title: l10n.noSearchResults,
|
subtitle: l10n.emptyHint,
|
||||||
subtitle: l10n.emptyHint,
|
);
|
||||||
);
|
|
||||||
}
|
|
||||||
return DramaGrid(l10n: l10n, dramas: results, onOpen: onOpen);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -10,7 +10,6 @@ import '../../../core/theme/app_dimens.dart';
|
|||||||
import '../../../core/widgets/remote_image.dart';
|
import '../../../core/widgets/remote_image.dart';
|
||||||
import '../../../l10n/app_localizations.dart';
|
import '../../../l10n/app_localizations.dart';
|
||||||
|
|
||||||
const int _columns = 2;
|
|
||||||
const double _columnGap = 8;
|
const double _columnGap = 8;
|
||||||
const double _cardGap = 14;
|
const double _cardGap = 14;
|
||||||
const double _gridHorizontalPadding = 12;
|
const double _gridHorizontalPadding = 12;
|
||||||
@@ -29,8 +28,10 @@ const Color _tagChipBackground = AppColors.raised;
|
|||||||
final RegExp _tagSplitter = RegExp(r'[,/、·]');
|
final RegExp _tagSplitter = RegExp(r'[,/、·]');
|
||||||
|
|
||||||
/// 剧场/分类页共用的双列剧目网格。
|
/// 剧场/分类页共用的双列剧目网格。
|
||||||
class DramaGrid extends StatelessWidget {
|
///
|
||||||
const DramaGrid({
|
/// 按行懒加载:翻到第 N 页时只构建当前可见的几张卡,长列表不会把整本目录都留在树上。
|
||||||
|
class SliverDramaGrid extends StatelessWidget {
|
||||||
|
const SliverDramaGrid({
|
||||||
super.key,
|
super.key,
|
||||||
required this.l10n,
|
required this.l10n,
|
||||||
required this.dramas,
|
required this.dramas,
|
||||||
@@ -43,33 +44,41 @@ class DramaGrid extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Padding(
|
final rowCount = (dramas.length + 1) ~/ 2;
|
||||||
key: const Key('home-all-dramas-section'),
|
return SliverPadding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: _gridHorizontalPadding),
|
padding: const EdgeInsets.symmetric(horizontal: _gridHorizontalPadding),
|
||||||
child: Row(
|
sliver: SliverList(
|
||||||
key: const Key('home-all-dramas-grid'),
|
delegate: SliverChildBuilderDelegate((context, row) {
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
final left = dramas[row * 2];
|
||||||
children: [
|
final rightIndex = row * 2 + 1;
|
||||||
Expanded(child: _column(0)),
|
final right = rightIndex < dramas.length ? dramas[rightIndex] : null;
|
||||||
const SizedBox(width: _columnGap),
|
return Padding(
|
||||||
Expanded(child: _column(1)),
|
// 行间距沿用卡片间距,首行不需要。
|
||||||
],
|
padding: EdgeInsets.only(top: row == 0 ? 0 : _cardGap),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Expanded(child: _card(left)),
|
||||||
|
const SizedBox(width: _columnGap),
|
||||||
|
Expanded(
|
||||||
|
child: right == null
|
||||||
|
? const SizedBox.shrink()
|
||||||
|
: _card(right),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}, childCount: rowCount),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _column(int start) {
|
Widget _card(Drama drama) {
|
||||||
final children = <Widget>[];
|
return DramaGridCard(
|
||||||
for (var index = start; index < dramas.length; index += _columns) {
|
l10n: l10n,
|
||||||
final drama = dramas[index];
|
drama: drama,
|
||||||
children.add(
|
onTap: () => onOpen(drama),
|
||||||
DramaGridCard(l10n: l10n, drama: drama, onTap: () => onOpen(drama)),
|
);
|
||||||
);
|
|
||||||
if (index + _columns < dramas.length) {
|
|
||||||
children.add(const SizedBox(height: _cardGap));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Column(children: children);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -135,64 +135,81 @@ class _NewReleasesPageState extends State<NewReleasesPage> {
|
|||||||
child: AppPullToRefresh(
|
child: AppPullToRefresh(
|
||||||
key: const Key('new-releases-refresh'),
|
key: const Key('new-releases-refresh'),
|
||||||
onRefresh: _refresh,
|
onRefresh: _refresh,
|
||||||
child: ListView(
|
child: CustomScrollView(
|
||||||
key: const Key('new-releases-list'),
|
key: const Key('new-releases-list'),
|
||||||
controller: _scroll,
|
controller: _scroll,
|
||||||
physics: AppPullToRefresh.physics,
|
physics: AppPullToRefresh.physics,
|
||||||
padding: const EdgeInsets.only(bottom: 24),
|
slivers: hero == null
|
||||||
children: hero == null
|
|
||||||
? [
|
? [
|
||||||
SizedBox(
|
SliverToBoxAdapter(
|
||||||
height: 420,
|
child: SizedBox(
|
||||||
child: Center(
|
height: 420,
|
||||||
child: Text(
|
child: Center(
|
||||||
l10n.noStories,
|
child: Text(
|
||||||
key: const Key('new-releases-empty'),
|
l10n.noStories,
|
||||||
style: const TextStyle(
|
key: const Key('new-releases-empty'),
|
||||||
color: AppColors.muted,
|
style: const TextStyle(
|
||||||
|
color: AppColors.muted,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
: [
|
: [
|
||||||
Padding(
|
SliverToBoxAdapter(
|
||||||
padding: const EdgeInsets.symmetric(
|
child: Padding(
|
||||||
horizontal: 16,
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 16,
|
||||||
|
),
|
||||||
|
child: _heroCard(context, l10n, hero),
|
||||||
),
|
),
|
||||||
child: _heroCard(context, l10n, hero),
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 14),
|
const SliverToBoxAdapter(
|
||||||
const _FilmStrip(),
|
child: SizedBox(height: 14),
|
||||||
const SizedBox(height: 14),
|
),
|
||||||
|
const SliverToBoxAdapter(child: _FilmStrip()),
|
||||||
|
const SliverToBoxAdapter(
|
||||||
|
child: SizedBox(height: 14),
|
||||||
|
),
|
||||||
if (more.isNotEmpty) ...[
|
if (more.isNotEmpty) ...[
|
||||||
_moreHeader(l10n),
|
SliverToBoxAdapter(
|
||||||
const SizedBox(height: 10),
|
child: _moreHeader(l10n),
|
||||||
SizedBox(
|
),
|
||||||
height: _rowHeight,
|
const SliverToBoxAdapter(
|
||||||
child: ListView.separated(
|
child: SizedBox(height: 10),
|
||||||
key: const Key('new-releases-row'),
|
),
|
||||||
scrollDirection: Axis.horizontal,
|
SliverToBoxAdapter(
|
||||||
padding: const EdgeInsets.symmetric(
|
child: SizedBox(
|
||||||
horizontal: 16,
|
height: _rowHeight,
|
||||||
|
child: ListView.separated(
|
||||||
|
key: const Key('new-releases-row'),
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 16,
|
||||||
|
),
|
||||||
|
itemCount: more.length,
|
||||||
|
separatorBuilder: (_, _) =>
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
itemBuilder: (context, index) =>
|
||||||
|
_moreCard(
|
||||||
|
context,
|
||||||
|
l10n,
|
||||||
|
more[index],
|
||||||
|
index,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
itemCount: more.length,
|
|
||||||
separatorBuilder: (_, _) =>
|
|
||||||
const SizedBox(width: 10),
|
|
||||||
itemBuilder: (context, index) =>
|
|
||||||
_moreCard(
|
|
||||||
context,
|
|
||||||
l10n,
|
|
||||||
more[index],
|
|
||||||
index,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
const SizedBox(height: 20),
|
const SliverToBoxAdapter(
|
||||||
_allHeader(l10n),
|
child: SizedBox(height: 20),
|
||||||
const SizedBox(height: 10),
|
),
|
||||||
DramaGrid(
|
SliverToBoxAdapter(child: _allHeader(l10n)),
|
||||||
|
const SliverToBoxAdapter(
|
||||||
|
child: SizedBox(height: 10),
|
||||||
|
),
|
||||||
|
SliverDramaGrid(
|
||||||
key: const Key('new-releases-all-grid'),
|
key: const Key('new-releases-all-grid'),
|
||||||
l10n: l10n,
|
l10n: l10n,
|
||||||
dramas: visible,
|
dramas: visible,
|
||||||
@@ -200,12 +217,17 @@ class _NewReleasesPageState extends State<NewReleasesPage> {
|
|||||||
playDramaFromCatalog(context, drama),
|
playDramaFromCatalog(context, drama),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
PagedLoadFooter(
|
SliverToBoxAdapter(
|
||||||
loading: pager.loading,
|
child: Padding(
|
||||||
hasMore: pager.hasMore,
|
padding: const EdgeInsets.only(bottom: 24),
|
||||||
hasItems: visible.isNotEmpty,
|
child: PagedLoadFooter(
|
||||||
loadMoreKey: const Key(
|
loading: pager.loading,
|
||||||
'new-releases-load-more',
|
hasMore: pager.hasMore,
|
||||||
|
hasItems: visible.isNotEmpty,
|
||||||
|
loadMoreKey: const Key(
|
||||||
|
'new-releases-load-more',
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -384,7 +384,7 @@ void main() {
|
|||||||
|
|
||||||
expect(refresh.edgeOffset, 52);
|
expect(refresh.edgeOffset, 52);
|
||||||
expect(
|
expect(
|
||||||
tester.widget<ListView>(_verticalFeed).physics,
|
tester.widget<CustomScrollView>(_verticalFeed).physics,
|
||||||
AppPullToRefresh.physics,
|
AppPullToRefresh.physics,
|
||||||
);
|
);
|
||||||
await tester.drag(_verticalFeed, const Offset(0, 240));
|
await tester.drag(_verticalFeed, const Offset(0, 240));
|
||||||
@@ -396,7 +396,8 @@ void main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final Finder _verticalFeed = find.byWidgetPredicate(
|
final Finder _verticalFeed = find.byWidgetPredicate(
|
||||||
(widget) => widget is ListView && widget.scrollDirection == Axis.vertical,
|
(widget) =>
|
||||||
|
widget is CustomScrollView && widget.scrollDirection == Axis.vertical,
|
||||||
);
|
);
|
||||||
|
|
||||||
Future<void> _pumpTheater(
|
Future<void> _pumpTheater(
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import 'package:fruitshorts/core/data/models/home_feed.dart';
|
|||||||
import 'package:fruitshorts/core/theme/app_colors.dart';
|
import 'package:fruitshorts/core/theme/app_colors.dart';
|
||||||
import 'package:fruitshorts/core/widgets/app_pull_to_refresh.dart';
|
import 'package:fruitshorts/core/widgets/app_pull_to_refresh.dart';
|
||||||
import 'package:fruitshorts/pages/discover/discover_view.dart';
|
import 'package:fruitshorts/pages/discover/discover_view.dart';
|
||||||
|
import 'package:fruitshorts/pages/discover/widgets/drama_grid.dart';
|
||||||
import 'package:fruitshorts/core/data/repositories/favorite_repository.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/playback_repository.dart';
|
||||||
import 'package:fruitshorts/core/data/repositories/watch_history_repository.dart';
|
import 'package:fruitshorts/core/data/repositories/watch_history_repository.dart';
|
||||||
@@ -234,25 +235,26 @@ void main() {
|
|||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
final list = find.byKey(const Key('new-releases-list'));
|
final list = find.byKey(const Key('new-releases-list'));
|
||||||
expect(tester.widget<ListView>(list).physics, AppPullToRefresh.physics);
|
expect(
|
||||||
await tester.drag(list, const Offset(0, -400));
|
tester.widget<CustomScrollView>(list).physics,
|
||||||
await tester.pump();
|
AppPullToRefresh.physics,
|
||||||
expect(_countKeys('home-drama-card-'), 20);
|
);
|
||||||
|
|
||||||
|
// 网格懒加载后只保留可见行,页数直接看传给网格的剧目数。
|
||||||
|
List<Drama> gridDramas() => tester
|
||||||
|
.widget<SliverDramaGrid>(find.byKey(const Key('new-releases-all-grid')))
|
||||||
|
.dramas;
|
||||||
|
expect(gridDramas(), hasLength(20));
|
||||||
|
|
||||||
|
// jumpTo 不发滚动通知,滑到底后再拖一下才会触发加载。
|
||||||
final position = _scrollPosition(tester, const Key('new-releases-list'));
|
final position = _scrollPosition(tester, const Key('new-releases-list'));
|
||||||
position.jumpTo(position.maxScrollExtent);
|
position.jumpTo(position.maxScrollExtent);
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
expect(find.byKey(const Key('new-releases-load-more')), findsOneWidget);
|
await tester.drag(list, const Offset(0, -60));
|
||||||
await tester.pump(const Duration(milliseconds: 299));
|
|
||||||
expect(_countKeys('home-drama-card-'), 20);
|
|
||||||
await tester.pump(const Duration(milliseconds: 1));
|
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
expect(_countKeys('home-drama-card-'), 40);
|
expect(find.byKey(const Key('new-releases-load-more')), findsOneWidget);
|
||||||
|
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
position.jumpTo(position.maxScrollExtent);
|
expect(gridDramas(), hasLength(40));
|
||||||
await tester.pumpAndSettle();
|
|
||||||
expect(find.byKey(const Key('new-releases-no-more')), findsNothing);
|
|
||||||
|
|
||||||
position.jumpTo(0);
|
position.jumpTo(0);
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
@@ -262,10 +264,7 @@ void main() {
|
|||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
expect(repository.fetchCalls, beforeRefresh + 1);
|
expect(repository.fetchCalls, beforeRefresh + 1);
|
||||||
expect(position.pixels, 0);
|
expect(position.pixels, 0);
|
||||||
|
expect(gridDramas(), hasLength(20));
|
||||||
await tester.drag(list, const Offset(0, -400));
|
|
||||||
await tester.pump();
|
|
||||||
expect(_countKeys('home-drama-card-'), 20);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('should_refresh_reserve_without_load_more', (tester) async {
|
testWidgets('should_refresh_reserve_without_load_more', (tester) async {
|
||||||
@@ -327,14 +326,6 @@ ScrollPosition _scrollPosition(WidgetTester tester, Key listKey) {
|
|||||||
return tester.state<ScrollableState>(scrollable.first).position;
|
return tester.state<ScrollableState>(scrollable.first).position;
|
||||||
}
|
}
|
||||||
|
|
||||||
int _countKeys(String prefix) => find
|
|
||||||
.byWidgetPredicate((widget) {
|
|
||||||
final key = widget.key;
|
|
||||||
return key is ValueKey<String> && key.value.startsWith(prefix);
|
|
||||||
})
|
|
||||||
.evaluate()
|
|
||||||
.length;
|
|
||||||
|
|
||||||
class _CountingCatalogRepository extends FakeCatalogRepository {
|
class _CountingCatalogRepository extends FakeCatalogRepository {
|
||||||
_CountingCatalogRepository({super.dramas});
|
_CountingCatalogRepository({super.dramas});
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user