fix: 优化追剧页切换与编辑按钮
This commit is contained in:
1 parent
f67b8bd514
commit
3f3dc9263c
5 files changed
+267
-187
No files matched your search
@@ -1243,3 +1243,9 @@ $ flutter test
|
|||||||
- 追剧加号卡片移到全部追剧卡片之后,空列表时仍在左上角。
|
- 追剧加号卡片移到全部追剧卡片之后,空列表时仍在左上角。
|
||||||
- 加号改为 `#8E8E93`、28 尺寸、1.5 描边,卡片底色与圆角不变。
|
- 加号改为 `#8E8E93`、28 尺寸、1.5 描边,卡片底色与圆角不变。
|
||||||
- 刷新、分页与各页独立滚动位置保持;`flutter analyze`:No issues found。
|
- 刷新、分页与各页独立滚动位置保持;`flutter analyze`:No issues found。
|
||||||
|
|
||||||
|
## 2026-10-22 追剧页切换与编辑按钮修正
|
||||||
|
|
||||||
|
- 追剧与历史页增加 keep-alive,tab 与编辑态改用局部 ValueNotifier 更新,切换不再重建整个页面。
|
||||||
|
- 编辑图标调整为 20、`#B0B0B0`,热区 40×40,保持右侧 14 边距和垂直居中。
|
||||||
|
- 列表刷新期间保留已有数据与各页滚动位置;`flutter analyze`:No issues found。
|
||||||
@@ -150,7 +150,8 @@ abstract final class AppDimens {
|
|||||||
static const double followingHeaderHeight = 52;
|
static const double followingHeaderHeight = 52;
|
||||||
static const double followingTabFont = 18;
|
static const double followingTabFont = 18;
|
||||||
static const double followingTabGap = 24;
|
static const double followingTabGap = 24;
|
||||||
static const double followingEditIconSize = 24;
|
static const double followingEditIconSize = 20;
|
||||||
|
static const double followingEditHitSize = 40;
|
||||||
static const double followingAddIconSize = 28;
|
static const double followingAddIconSize = 28;
|
||||||
static const double followingAddIconStroke = 1.5;
|
static const double followingAddIconStroke = 1.5;
|
||||||
static const double followingGridGap = 6;
|
static const double followingGridGap = 6;
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
// Author: fruit.com.cn
|
||||||
|
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
|
class FollowingKeepAliveTab extends StatefulWidget {
|
||||||
|
const FollowingKeepAliveTab({super.key, required this.child});
|
||||||
|
|
||||||
|
final Widget child;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<FollowingKeepAliveTab> createState() => _FollowingKeepAliveTabState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FollowingKeepAliveTabState extends State<FollowingKeepAliveTab>
|
||||||
|
with AutomaticKeepAliveClientMixin {
|
||||||
|
@override
|
||||||
|
bool get wantKeepAlive => true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
super.build(context);
|
||||||
|
return widget.child;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
@@ -20,6 +21,7 @@ import '../../l10n/app_localizations.dart';
|
|||||||
import '../player/open_player.dart';
|
import '../player/open_player.dart';
|
||||||
import 'following_grid_card.dart';
|
import 'following_grid_card.dart';
|
||||||
import 'following_history_card.dart';
|
import 'following_history_card.dart';
|
||||||
|
import 'following_keep_alive_tab.dart';
|
||||||
import 'following_paged_grid.dart';
|
import 'following_paged_grid.dart';
|
||||||
|
|
||||||
enum FollowingTab { favorites, history }
|
enum FollowingTab { favorites, history }
|
||||||
@@ -42,9 +44,11 @@ class _FollowingPageState extends State<FollowingPage> {
|
|||||||
final PageController _pageController = PageController();
|
final PageController _pageController = PageController();
|
||||||
final ScrollController _favoriteScroll = ScrollController();
|
final ScrollController _favoriteScroll = ScrollController();
|
||||||
final ScrollController _historyScroll = ScrollController();
|
final ScrollController _historyScroll = ScrollController();
|
||||||
|
final ValueNotifier<FollowingTab> _tab = ValueNotifier<FollowingTab>(
|
||||||
|
FollowingTab.favorites,
|
||||||
|
);
|
||||||
|
final ValueNotifier<bool> _editing = ValueNotifier(false);
|
||||||
late final WatchRecordsController _history;
|
late final WatchRecordsController _history;
|
||||||
FollowingTab _tab = FollowingTab.favorites;
|
|
||||||
bool _editing = false;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@@ -64,6 +68,8 @@ class _FollowingPageState extends State<FollowingPage> {
|
|||||||
_pageController.dispose();
|
_pageController.dispose();
|
||||||
_favoriteScroll.dispose();
|
_favoriteScroll.dispose();
|
||||||
_historyScroll.dispose();
|
_historyScroll.dispose();
|
||||||
|
_tab.dispose();
|
||||||
|
_editing.dispose();
|
||||||
_history.dispose();
|
_history.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
@@ -88,18 +94,22 @@ class _FollowingPageState extends State<FollowingPage> {
|
|||||||
controller: _pageController,
|
controller: _pageController,
|
||||||
onPageChanged: _onPageChanged,
|
onPageChanged: _onPageChanged,
|
||||||
children: [
|
children: [
|
||||||
_FavoritesGrid(
|
FollowingKeepAliveTab(
|
||||||
controller: widget.favoriteController,
|
child: _FavoritesGrid(
|
||||||
editing: _editing,
|
controller: widget.favoriteController,
|
||||||
scrollController: _favoriteScroll,
|
editing: _editing,
|
||||||
onGoHome: widget.onGoHome,
|
scrollController: _favoriteScroll,
|
||||||
onListEmptied: _exitEditingIfEmpty,
|
onGoHome: widget.onGoHome,
|
||||||
|
onListEmptied: _exitEditingIfEmpty,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
_HistoryList(
|
FollowingKeepAliveTab(
|
||||||
controller: _history,
|
child: _HistoryList(
|
||||||
editing: _editing,
|
controller: _history,
|
||||||
scrollController: _historyScroll,
|
editing: _editing,
|
||||||
onListEmptied: _exitEditingIfEmpty,
|
scrollController: _historyScroll,
|
||||||
|
onListEmptied: _exitEditingIfEmpty,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -118,63 +128,85 @@ class _FollowingPageState extends State<FollowingPage> {
|
|||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
horizontal: AppDimens.feedPageHorizontal,
|
horizontal: AppDimens.feedPageHorizontal,
|
||||||
),
|
),
|
||||||
child: Row(
|
child: ValueListenableBuilder<FollowingTab>(
|
||||||
children: [
|
valueListenable: _tab,
|
||||||
_tabButton(FollowingTab.favorites, l10n.followingTab, 'favorites'),
|
builder: (context, selectedTab, _) {
|
||||||
const SizedBox(width: AppDimens.followingTabGap),
|
return ValueListenableBuilder<bool>(
|
||||||
_tabButton(FollowingTab.history, l10n.watchHistory, 'history'),
|
valueListenable: _editing,
|
||||||
const Spacer(),
|
builder: (context, editing, _) {
|
||||||
if (_editing)
|
return Row(
|
||||||
SizedBox(
|
children: [
|
||||||
width: 44,
|
_tabButton(
|
||||||
height: 44,
|
FollowingTab.favorites,
|
||||||
child: InkWell(
|
l10n.followingTab,
|
||||||
key: const Key('following-edit'),
|
'favorites',
|
||||||
onTap: _toggleEditing,
|
selectedTab == FollowingTab.favorites,
|
||||||
child: Align(
|
|
||||||
alignment: Alignment.centerRight,
|
|
||||||
child: Text(
|
|
||||||
l10n.done,
|
|
||||||
style: const TextStyle(
|
|
||||||
color: AppColors.textPrimary,
|
|
||||||
fontSize: AppDimens.followingTabFont,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(width: AppDimens.followingTabGap),
|
||||||
),
|
_tabButton(
|
||||||
)
|
FollowingTab.history,
|
||||||
else
|
l10n.watchHistory,
|
||||||
Semantics(
|
'history',
|
||||||
button: true,
|
selectedTab == FollowingTab.history,
|
||||||
label: l10n.edit,
|
),
|
||||||
child: SizedBox(
|
const Spacer(),
|
||||||
width: 44,
|
if (editing)
|
||||||
height: 44,
|
SizedBox(
|
||||||
child: InkWell(
|
width: AppDimens.followingEditHitSize,
|
||||||
key: const Key('following-edit'),
|
height: AppDimens.followingEditHitSize,
|
||||||
onTap: _toggleEditing,
|
child: InkWell(
|
||||||
child: const Align(
|
key: const Key('following-edit'),
|
||||||
alignment: Alignment.centerRight,
|
onTap: _toggleEditing,
|
||||||
child: SizedBox.square(
|
child: Align(
|
||||||
dimension: AppDimens.followingEditIconSize,
|
alignment: Alignment.centerRight,
|
||||||
child: Icon(
|
child: Text(
|
||||||
Icons.edit_outlined,
|
l10n.done,
|
||||||
color: AppColors.textPrimary,
|
style: const TextStyle(
|
||||||
|
color: AppColors.textPrimary,
|
||||||
|
fontSize: AppDimens.followingTabFont,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
else
|
||||||
|
Semantics(
|
||||||
|
button: true,
|
||||||
|
label: l10n.edit,
|
||||||
|
child: SizedBox(
|
||||||
|
width: AppDimens.followingEditHitSize,
|
||||||
|
height: AppDimens.followingEditHitSize,
|
||||||
|
child: InkWell(
|
||||||
|
key: const Key('following-edit'),
|
||||||
|
onTap: _toggleEditing,
|
||||||
|
child: const Align(
|
||||||
|
alignment: Alignment.centerRight,
|
||||||
|
child: Icon(
|
||||||
|
Icons.edit_outlined,
|
||||||
|
size: AppDimens.followingEditIconSize,
|
||||||
|
color: AppColors.textTabInactive,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
),
|
);
|
||||||
),
|
},
|
||||||
),
|
);
|
||||||
],
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _tabButton(FollowingTab tab, String label, String keyName) {
|
Widget _tabButton(
|
||||||
final selected = _tab == tab;
|
FollowingTab tab,
|
||||||
|
String label,
|
||||||
|
String keyName,
|
||||||
|
bool selected,
|
||||||
|
) {
|
||||||
return InkWell(
|
return InkWell(
|
||||||
key: Key('following-tab-$keyName'),
|
key: Key('following-tab-$keyName'),
|
||||||
onTap: () => _selectTab(tab),
|
onTap: () => _selectTab(tab),
|
||||||
@@ -198,35 +230,30 @@ class _FollowingPageState extends State<FollowingPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _selectTab(FollowingTab tab) {
|
void _selectTab(FollowingTab tab) {
|
||||||
if (_tab != tab || _editing) {
|
_editing.value = false;
|
||||||
setState(() {
|
_tab.value = tab;
|
||||||
_tab = tab;
|
if (_pageController.hasClients) {
|
||||||
_editing = false;
|
unawaited(
|
||||||
});
|
_pageController.animateToPage(
|
||||||
|
tab.index,
|
||||||
|
duration: AppDimens.followingPageAnimationDuration,
|
||||||
|
curve: Curves.easeOutCubic,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
_pageController.animateToPage(
|
|
||||||
tab.index,
|
|
||||||
duration: AppDimens.followingPageAnimationDuration,
|
|
||||||
curve: Curves.easeOutCubic,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onPageChanged(int index) {
|
void _onPageChanged(int index) {
|
||||||
final tab = FollowingTab.values[index];
|
final tab = FollowingTab.values[index];
|
||||||
if (_tab == tab && !_editing) return;
|
_editing.value = false;
|
||||||
setState(() {
|
_tab.value = tab;
|
||||||
_tab = tab;
|
|
||||||
_editing = false;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _toggleEditing() {
|
void _toggleEditing() {
|
||||||
setState(() => _editing = !_editing);
|
_editing.value = !_editing.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _exitEditingIfEmpty() {
|
void _exitEditingIfEmpty() => _editing.value = false;
|
||||||
if (_editing && mounted) setState(() => _editing = false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class _FavoritesGrid extends StatelessWidget {
|
class _FavoritesGrid extends StatelessWidget {
|
||||||
@@ -239,60 +266,69 @@ class _FavoritesGrid extends StatelessWidget {
|
|||||||
});
|
});
|
||||||
|
|
||||||
final FavoriteController controller;
|
final FavoriteController controller;
|
||||||
final bool editing;
|
final ValueListenable<bool> editing;
|
||||||
final ScrollController scrollController;
|
final ScrollController scrollController;
|
||||||
final VoidCallback onGoHome;
|
final VoidCallback onGoHome;
|
||||||
final VoidCallback onListEmptied;
|
final VoidCallback onListEmptied;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ListenableBuilder(
|
return ValueListenableBuilder<bool>(
|
||||||
listenable: controller,
|
valueListenable: editing,
|
||||||
builder: (context, _) {
|
builder: (context, isEditing, _) {
|
||||||
final l10n = AppLocalizations.of(context)!;
|
return ListenableBuilder(
|
||||||
return FollowingPagedGrid(
|
listenable: controller,
|
||||||
key: const Key('following-favorites-grid'),
|
builder: (context, _) {
|
||||||
scrollController: scrollController,
|
final l10n = AppLocalizations.of(context)!;
|
||||||
loading: controller.loading,
|
return FollowingPagedGrid(
|
||||||
loadingMore: controller.loadingMore,
|
key: const Key('following-favorites-grid'),
|
||||||
hasMore: controller.hasMore,
|
scrollController: scrollController,
|
||||||
hasItems: controller.cards.isNotEmpty,
|
loading: controller.loading,
|
||||||
showInitialLoading: false,
|
loadingMore: controller.loadingMore,
|
||||||
errorMessage: controller.error == null
|
hasMore: controller.hasMore,
|
||||||
? null
|
hasItems: controller.cards.isNotEmpty,
|
||||||
: errorText(l10n, controller.error, l10n.favoritesError),
|
showInitialLoading: false,
|
||||||
loadMoreError: controller.loadMoreError == null
|
errorMessage: controller.error == null
|
||||||
? null
|
? null
|
||||||
: errorText(l10n, controller.loadMoreError, l10n.favoritesError),
|
: errorText(l10n, controller.error, l10n.favoritesError),
|
||||||
onRefresh: controller.load,
|
loadMoreError: controller.loadMoreError == null
|
||||||
onLoadMore: controller.loadMore,
|
? null
|
||||||
itemCount: controller.cards.length + 1,
|
: errorText(
|
||||||
empty: const SizedBox.shrink(),
|
l10n,
|
||||||
itemBuilder: (context, index) {
|
controller.loadMoreError,
|
||||||
if (index == controller.cards.length) {
|
l10n.favoritesError,
|
||||||
return FollowingAddCard(onTap: onGoHome);
|
),
|
||||||
}
|
onRefresh: controller.load,
|
||||||
final drama = controller.cards[index];
|
onLoadMore: controller.loadMore,
|
||||||
final episode = drama.episodes.isEmpty
|
itemCount: controller.cards.length + 1,
|
||||||
? 1
|
empty: const SizedBox.shrink(),
|
||||||
: drama.episodes.first.number;
|
itemBuilder: (context, index) {
|
||||||
return FollowingGridCard(
|
if (index == controller.cards.length) {
|
||||||
dramaId: drama.id,
|
return FollowingAddCard(onTap: onGoHome);
|
||||||
title: drama.title,
|
|
||||||
posterUrl: drama.posterUrl,
|
|
||||||
viewCount: drama.viewCount,
|
|
||||||
editing: editing,
|
|
||||||
subtitle: l10n.favoriteEpisodeProgress(
|
|
||||||
episode,
|
|
||||||
drama.episodeCount,
|
|
||||||
),
|
|
||||||
onTap: () => unawaited(playDramaFromCatalog(context, drama)),
|
|
||||||
onRemove: () async {
|
|
||||||
if (await _confirmRemove(context) &&
|
|
||||||
await controller.remove(drama.id) &&
|
|
||||||
controller.cards.isEmpty) {
|
|
||||||
onListEmptied();
|
|
||||||
}
|
}
|
||||||
|
final drama = controller.cards[index];
|
||||||
|
final episode = drama.episodes.isEmpty
|
||||||
|
? 1
|
||||||
|
: drama.episodes.first.number;
|
||||||
|
return FollowingGridCard(
|
||||||
|
dramaId: drama.id,
|
||||||
|
title: drama.title,
|
||||||
|
posterUrl: drama.posterUrl,
|
||||||
|
viewCount: drama.viewCount,
|
||||||
|
editing: isEditing,
|
||||||
|
subtitle: l10n.favoriteEpisodeProgress(
|
||||||
|
episode,
|
||||||
|
drama.episodeCount,
|
||||||
|
),
|
||||||
|
onTap: () => unawaited(playDramaFromCatalog(context, drama)),
|
||||||
|
onRemove: () async {
|
||||||
|
if (await _confirmRemove(context) &&
|
||||||
|
await controller.remove(drama.id) &&
|
||||||
|
controller.cards.isEmpty) {
|
||||||
|
onListEmptied();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -311,70 +347,80 @@ class _HistoryList extends StatelessWidget {
|
|||||||
});
|
});
|
||||||
|
|
||||||
final WatchRecordsController controller;
|
final WatchRecordsController controller;
|
||||||
final bool editing;
|
final ValueListenable<bool> editing;
|
||||||
final ScrollController scrollController;
|
final ScrollController scrollController;
|
||||||
final VoidCallback onListEmptied;
|
final VoidCallback onListEmptied;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ListenableBuilder(
|
return ValueListenableBuilder<bool>(
|
||||||
listenable: controller,
|
valueListenable: editing,
|
||||||
builder: (context, _) {
|
builder: (context, isEditing, _) {
|
||||||
final l10n = AppLocalizations.of(context)!;
|
return ListenableBuilder(
|
||||||
return FollowingPagedGrid(
|
listenable: controller,
|
||||||
key: const Key('following-history-grid'),
|
builder: (context, _) {
|
||||||
scrollController: scrollController,
|
final l10n = AppLocalizations.of(context)!;
|
||||||
loading: controller.loading,
|
return FollowingPagedGrid(
|
||||||
loadingMore: controller.loadingMore,
|
key: const Key('following-history-grid'),
|
||||||
hasMore: controller.hasMore,
|
scrollController: scrollController,
|
||||||
hasItems: controller.records.isNotEmpty,
|
loading: controller.loading,
|
||||||
singleColumn: true,
|
loadingMore: controller.loadingMore,
|
||||||
errorMessage: controller.error == null
|
hasMore: controller.hasMore,
|
||||||
? null
|
hasItems: controller.records.isNotEmpty,
|
||||||
: errorText(l10n, controller.error, l10n.progressError),
|
singleColumn: true,
|
||||||
loadMoreError: controller.loadMoreError == null
|
errorMessage: controller.error == null
|
||||||
? null
|
? null
|
||||||
: errorText(l10n, controller.loadMoreError, l10n.progressError),
|
: errorText(l10n, controller.error, l10n.progressError),
|
||||||
onRefresh: controller.load,
|
loadMoreError: controller.loadMoreError == null
|
||||||
onLoadMore: controller.loadMore,
|
? null
|
||||||
itemCount: controller.records.length,
|
: errorText(
|
||||||
empty: EmptyState(
|
l10n,
|
||||||
key: const Key('following-history-empty'),
|
controller.loadMoreError,
|
||||||
icon: Icons.history_rounded,
|
l10n.progressError,
|
||||||
title: l10n.noWatchRecords,
|
),
|
||||||
subtitle: l10n.emptyHint,
|
onRefresh: controller.load,
|
||||||
),
|
onLoadMore: controller.loadMore,
|
||||||
itemBuilder: (context, index) {
|
itemCount: controller.records.length,
|
||||||
final record = controller.records[index];
|
empty: EmptyState(
|
||||||
final progress = record.progress.fraction;
|
key: const Key('following-history-empty'),
|
||||||
final hasProgress = record.progress.duration > Duration.zero;
|
icon: Icons.history_rounded,
|
||||||
final watchedPercent = (progress * 100)
|
title: l10n.noWatchRecords,
|
||||||
.round()
|
subtitle: l10n.emptyHint,
|
||||||
.clamp(0, 100)
|
),
|
||||||
.toInt();
|
itemBuilder: (context, index) {
|
||||||
void openRecord() {
|
final record = controller.records[index];
|
||||||
unawaited(_openHistory(context, controller, record));
|
final progress = record.progress.fraction;
|
||||||
}
|
final hasProgress = record.progress.duration > Duration.zero;
|
||||||
|
final watchedPercent = (progress * 100)
|
||||||
return FollowingHistoryCard(
|
.round()
|
||||||
dramaId: record.dramaId,
|
.clamp(0, 100)
|
||||||
title: _historyTitle(l10n, record),
|
.toInt();
|
||||||
posterUrl: record.displayCover,
|
void openRecord() {
|
||||||
viewCount: record.drama?.viewCount ?? record.progress.playCount,
|
unawaited(_openHistory(context, controller, record));
|
||||||
progress: hasProgress ? progress : null,
|
|
||||||
progressLabel: hasProgress
|
|
||||||
? l10n.watchedPercent(watchedPercent)
|
|
||||||
: null,
|
|
||||||
editing: editing,
|
|
||||||
subtitle: _historySubtitle(l10n, record),
|
|
||||||
onTap: openRecord,
|
|
||||||
onContinue: openRecord,
|
|
||||||
onRemove: () async {
|
|
||||||
if (await _confirmRemove(context) &&
|
|
||||||
await controller.remove(record.dramaId) &&
|
|
||||||
controller.records.isEmpty) {
|
|
||||||
onListEmptied();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return FollowingHistoryCard(
|
||||||
|
dramaId: record.dramaId,
|
||||||
|
title: _historyTitle(l10n, record),
|
||||||
|
posterUrl: record.displayCover,
|
||||||
|
viewCount:
|
||||||
|
record.drama?.viewCount ?? record.progress.playCount,
|
||||||
|
progress: hasProgress ? progress : null,
|
||||||
|
progressLabel: hasProgress
|
||||||
|
? l10n.watchedPercent(watchedPercent)
|
||||||
|
: null,
|
||||||
|
editing: isEditing,
|
||||||
|
subtitle: _historySubtitle(l10n, record),
|
||||||
|
onTap: openRecord,
|
||||||
|
onContinue: openRecord,
|
||||||
|
onRemove: () async {
|
||||||
|
if (await _confirmRemove(context) &&
|
||||||
|
await controller.remove(record.dramaId) &&
|
||||||
|
controller.records.isEmpty) {
|
||||||
|
onListEmptied();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -53,6 +53,9 @@ void main() {
|
|||||||
expect(tab.style.fontSize, AppDimens.followingTabFont);
|
expect(tab.style.fontSize, AppDimens.followingTabFont);
|
||||||
expect(tab.style.fontWeight, FontWeight.w600);
|
expect(tab.style.fontWeight, FontWeight.w600);
|
||||||
expect(tab.style.color, AppColors.textPrimary);
|
expect(tab.style.color, AppColors.textPrimary);
|
||||||
|
final editIcon = tester.widget<Icon>(find.byIcon(Icons.edit_outlined));
|
||||||
|
expect(editIcon.size, AppDimens.followingEditIconSize);
|
||||||
|
expect(editIcon.color, AppColors.textTabInactive);
|
||||||
final grid = tester.widget<SliverGrid>(
|
final grid = tester.widget<SliverGrid>(
|
||||||
find.descendant(
|
find.descendant(
|
||||||
of: find.byKey(const Key('following-favorites-grid')),
|
of: find.byKey(const Key('following-favorites-grid')),
|
||||||
|
|||||||
Reference in new issue
Block a user