feat(flutter): 统一空列表占位组件
This commit is contained in:
1 parent
19babb8f4a
commit
6d08330388
24 files changed
+394
-63
No files matched your search
@@ -11,6 +11,7 @@ import '../../core/data/services/catalog_controller.dart';
|
||||
import '../../core/data/services/category_controller.dart';
|
||||
import '../../core/theme/app_colors.dart';
|
||||
import '../../core/theme/app_theme.dart';
|
||||
import '../../core/widgets/empty_state.dart';
|
||||
import '../../core/widgets/remote_image.dart';
|
||||
import '../../l10n/app_localizations.dart';
|
||||
import 'discover_binding.dart';
|
||||
|
||||
@@ -44,14 +44,19 @@ class _HomeDramaGrid extends StatelessWidget {
|
||||
|
||||
final dramas = categories.visibleDramas;
|
||||
if (dramas.isEmpty) {
|
||||
final missingCategory = categories.selectedSectionMissing;
|
||||
return SliverPadding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 6, 16, 24),
|
||||
sliver: SliverToBoxAdapter(
|
||||
child: Text(
|
||||
categories.selectedSectionMissing
|
||||
child: EmptyState(
|
||||
key: const Key('home-all-dramas-empty'),
|
||||
icon: missingCategory
|
||||
? Icons.grid_view_rounded
|
||||
: Icons.search_off_rounded,
|
||||
title: missingCategory
|
||||
? l10n.noCategoryDramas
|
||||
: l10n.noSearchResults,
|
||||
style: const TextStyle(color: AppColors.muted),
|
||||
subtitle: l10n.emptyHint,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -15,6 +15,7 @@ import '../../core/platform/haptics.dart';
|
||||
import '../../core/theme/app_colors.dart';
|
||||
import '../../core/theme/app_theme.dart';
|
||||
import '../../core/widgets/auth_gate.dart';
|
||||
import '../../core/widgets/empty_state.dart';
|
||||
import '../../l10n/app_localizations.dart';
|
||||
import 'downloads_binding.dart';
|
||||
|
||||
@@ -144,14 +145,11 @@ class _DownloadsBody extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
if (downloads.licenses.isEmpty)
|
||||
Padding(
|
||||
EmptyState(
|
||||
key: const Key('downloads-empty'),
|
||||
padding: const EdgeInsets.symmetric(vertical: 34),
|
||||
child: Text(
|
||||
l10n.noDownloads,
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(color: Colors.white70),
|
||||
),
|
||||
icon: Icons.download_outlined,
|
||||
title: l10n.noDownloads,
|
||||
subtitle: l10n.emptyHint,
|
||||
)
|
||||
else
|
||||
for (final license in downloads.licenses) ...[
|
||||
|
||||
@@ -17,6 +17,7 @@ import '../../core/theme/app_theme.dart';
|
||||
import '../../core/widgets/remote_image.dart';
|
||||
import '../../core/widgets/auth_gate.dart';
|
||||
import '../../core/widgets/download_action.dart';
|
||||
import '../../core/widgets/empty_state.dart';
|
||||
import '../../l10n/app_localizations.dart';
|
||||
import '../interaction/widgets/report_dialog.dart';
|
||||
import 'drama_detail_binding.dart';
|
||||
@@ -114,28 +115,13 @@ class _DramaDetailPageState extends State<DramaDetailPage> {
|
||||
foregroundColor: Colors.white,
|
||||
title: Text(localizedDramaTitle(l10n, drama)),
|
||||
),
|
||||
body: Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 32),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.movie_filter_outlined,
|
||||
size: 48,
|
||||
color: Colors.white38,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
l10n.noEpisodesYet,
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(color: Colors.white70),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
FilledButton(onPressed: _load, child: Text(l10n.retry)),
|
||||
],
|
||||
),
|
||||
),
|
||||
body: EmptyState(
|
||||
key: const Key('drama-detail-empty'),
|
||||
icon: Icons.video_library_outlined,
|
||||
title: l10n.noEpisodesYet,
|
||||
subtitle: l10n.emptyHint,
|
||||
actionLabel: l10n.retry,
|
||||
onAction: _load,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import '../../core/formatting/error_text.dart';
|
||||
import '../../core/platform/haptics.dart';
|
||||
import '../../core/theme/app_theme.dart';
|
||||
import '../../core/widgets/auth_gate.dart';
|
||||
import '../../core/widgets/empty_state.dart';
|
||||
import '../../core/widgets/library_drama_tile.dart';
|
||||
import '../../l10n/app_localizations.dart';
|
||||
import '../drama_detail/drama_detail_view.dart';
|
||||
@@ -101,12 +102,11 @@ class _FavoritesBody extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
if (favorites.cards.isEmpty) {
|
||||
return Center(
|
||||
return EmptyState(
|
||||
key: const Key('favorites-empty'),
|
||||
child: Text(
|
||||
l10n.noFavorites,
|
||||
style: const TextStyle(color: Colors.white70),
|
||||
),
|
||||
icon: Icons.favorite_border_rounded,
|
||||
title: l10n.noFavorites,
|
||||
subtitle: l10n.emptyHint,
|
||||
);
|
||||
}
|
||||
return RefreshIndicator(
|
||||
|
||||
@@ -14,6 +14,7 @@ import '../../core/platform/haptics.dart';
|
||||
import '../../core/theme/app_colors.dart';
|
||||
import '../../core/theme/app_theme.dart';
|
||||
import '../../core/widgets/auth_gate.dart';
|
||||
import '../../core/widgets/empty_state.dart';
|
||||
import '../../l10n/app_localizations.dart';
|
||||
import '../discover/discover_view.dart';
|
||||
import '../mine/mine_view.dart';
|
||||
@@ -46,14 +47,20 @@ class HomePage extends StatelessWidget {
|
||||
body: Center(child: CircularProgressIndicator()),
|
||||
);
|
||||
}
|
||||
if (catalog.error != null || catalog.dramas.isEmpty) {
|
||||
if (catalog.error != null) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.black,
|
||||
body: Center(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(catalog.error == null ? l10n.noStories : l10n.catalogError),
|
||||
const Icon(
|
||||
Icons.error_outline_rounded,
|
||||
color: Colors.white70,
|
||||
size: 40,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text(l10n.catalogError),
|
||||
const SizedBox(height: 12),
|
||||
FilledButton(onPressed: catalog.load, child: Text(l10n.retry)),
|
||||
if (kDebugMode) ...[
|
||||
@@ -68,6 +75,19 @@ class HomePage extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
}
|
||||
if (catalog.dramas.isEmpty) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.black,
|
||||
body: EmptyState(
|
||||
key: const Key('home-empty'),
|
||||
icon: Icons.movie_outlined,
|
||||
title: l10n.noStories,
|
||||
subtitle: l10n.emptyHint,
|
||||
actionLabel: l10n.retry,
|
||||
onAction: catalog.load,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return _HomeTabs(
|
||||
key: ValueKey(catalog.dramas.first.id),
|
||||
|
||||
@@ -12,6 +12,7 @@ import '../../core/data/services/auth_controller.dart';
|
||||
import '../../core/data/services/interaction_controller.dart';
|
||||
import '../../core/data/services/favorite_controller.dart';
|
||||
import '../../core/formatting/error_text.dart';
|
||||
import '../../core/widgets/empty_state.dart';
|
||||
import '../../core/widgets/remote_image.dart';
|
||||
import '../../core/theme/app_colors.dart';
|
||||
import '../../core/theme/app_theme.dart';
|
||||
@@ -670,12 +671,11 @@ class _MyPageState extends State<MyPage> {
|
||||
const SizedBox(height: 9),
|
||||
]
|
||||
else
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 22),
|
||||
child: Text(
|
||||
l10n.noSavedShows,
|
||||
style: const TextStyle(color: Color(0xFFE6E1E3)),
|
||||
),
|
||||
EmptyState(
|
||||
key: const Key('mine-saved-empty'),
|
||||
icon: Icons.bookmark_border_rounded,
|
||||
title: l10n.noSavedShows,
|
||||
subtitle: l10n.emptyHint,
|
||||
),
|
||||
// 最近观看(服务端 /app/watch/recent):只渲染能在已加载目录里匹配到的剧;
|
||||
// 匹配不到的(例如不在当前首页分区里)先不显示,K2 的记录页会做独立请求。
|
||||
@@ -732,9 +732,11 @@ class _MyPageState extends State<MyPage> {
|
||||
],
|
||||
)
|
||||
else if (_recentDramas.isEmpty)
|
||||
Text(
|
||||
l10n.noStories,
|
||||
style: const TextStyle(color: Color(0xFFB7B3B8)),
|
||||
EmptyState(
|
||||
key: const Key('mine-recent-empty'),
|
||||
icon: Icons.history_rounded,
|
||||
title: l10n.noStories,
|
||||
subtitle: l10n.emptyHint,
|
||||
)
|
||||
else
|
||||
for (final drama in _recentDramas) ...[
|
||||
|
||||
@@ -12,6 +12,7 @@ import '../../core/formatting/error_text.dart';
|
||||
import '../../core/formatting/localized_date_time.dart';
|
||||
import '../../core/theme/app_colors.dart';
|
||||
import '../../core/theme/app_theme.dart';
|
||||
import '../../core/widgets/empty_state.dart';
|
||||
import '../../l10n/app_localizations.dart';
|
||||
|
||||
/// Anonymous system notice list with pagination and detail dialog.
|
||||
@@ -109,12 +110,11 @@ class _NoticesPageState extends State<NoticesPage> {
|
||||
children: [
|
||||
SizedBox(
|
||||
height: MediaQuery.sizeOf(context).height * 0.7,
|
||||
child: Center(
|
||||
child: EmptyState(
|
||||
key: const Key('notices-empty'),
|
||||
child: Text(
|
||||
l10n.noNotices,
|
||||
style: const TextStyle(color: AppColors.muted),
|
||||
),
|
||||
icon: Icons.notifications_none_rounded,
|
||||
title: l10n.noNotices,
|
||||
subtitle: l10n.emptyHint,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -14,6 +14,7 @@ import '../../../core/formatting/error_text.dart';
|
||||
import '../../../core/platform/haptics.dart';
|
||||
import '../../../core/theme/app_colors.dart';
|
||||
import '../../../core/widgets/auth_gate.dart';
|
||||
import '../../../core/widgets/empty_state.dart';
|
||||
import '../../../l10n/app_localizations.dart';
|
||||
import '../../interaction/widgets/report_dialog.dart';
|
||||
import 'comment_tile.dart';
|
||||
@@ -219,12 +220,11 @@ class _DramaCommentsSheetState extends State<DramaCommentsSheet> {
|
||||
);
|
||||
}
|
||||
if (_controller.comments.isEmpty) {
|
||||
return Center(
|
||||
return EmptyState(
|
||||
key: const Key('comment-list-empty'),
|
||||
child: Text(
|
||||
l10n.noComments,
|
||||
style: const TextStyle(color: AppColors.muted),
|
||||
),
|
||||
icon: Icons.chat_bubble_outline_rounded,
|
||||
title: l10n.noComments,
|
||||
compact: true,
|
||||
);
|
||||
}
|
||||
return ListView.builder(
|
||||
|
||||
@@ -17,6 +17,7 @@ import '../../core/platform/haptics.dart';
|
||||
import '../../core/theme/app_colors.dart';
|
||||
import '../../core/theme/app_theme.dart';
|
||||
import '../../core/widgets/auth_gate.dart';
|
||||
import '../../core/widgets/empty_state.dart';
|
||||
import '../../core/widgets/remote_image.dart';
|
||||
import '../../l10n/app_localizations.dart';
|
||||
import '../drama_detail/drama_detail_view.dart';
|
||||
@@ -92,11 +93,11 @@ class _WatchRecordsBodyState extends State<_WatchRecordsBody> {
|
||||
);
|
||||
}
|
||||
if (controller.records.isEmpty) {
|
||||
return Center(
|
||||
child: Text(
|
||||
l10n.noWatchRecords,
|
||||
style: const TextStyle(color: Colors.white70),
|
||||
),
|
||||
return EmptyState(
|
||||
key: const Key('watch-records-empty'),
|
||||
icon: Icons.history_rounded,
|
||||
title: l10n.noWatchRecords,
|
||||
subtitle: l10n.emptyHint,
|
||||
);
|
||||
}
|
||||
return Column(
|
||||
|
||||
Reference in new issue
Block a user