💄 Redesign the post tags and categories page

This commit is contained in:
2025-12-06 20:40:28 +08:00
parent 36b0f55a47
commit 3ef2f13dd3
8 changed files with 348 additions and 392 deletions

View File

@@ -102,7 +102,7 @@ class ExploreScreen extends HookConsumerWidget {
// Listen for post creation events to refresh activities
useEffect(() {
final subscription = eventBus.on<PostCreatedEvent>().listen((event) {
ref.invalidate(activityListProvider);
ref.read(activityListProvider.notifier).refresh();
});
return subscription.cancel;
}, []);
@@ -183,25 +183,13 @@ class ExploreScreen extends HookConsumerWidget {
children: [
const Icon(Symbols.category),
const Gap(12),
Text('categories').tr(),
Text('categoriesAndTags').tr(),
],
),
onTap: () {
context.pushNamed('postCategories');
},
),
PopupMenuItem(
child: Row(
children: [
const Icon(Symbols.label),
const Gap(12),
Text('tags').tr(),
],
),
onTap: () {
context.pushNamed('postTags');
},
),
PopupMenuItem(
child: Row(
children: [
@@ -490,25 +478,13 @@ class ExploreScreen extends HookConsumerWidget {
children: [
const Icon(Symbols.category),
const Gap(12),
Text('categories').tr(),
Text('categoriesAndTags').tr(),
],
),
onTap: () {
context.pushNamed('postCategories');
},
),
PopupMenuItem(
child: Row(
children: [
const Icon(Symbols.label),
const Gap(12),
Text('tags').tr(),
],
),
onTap: () {
context.pushNamed('postTags');
},
),
PopupMenuItem(
child: Row(
children: [

View File

@@ -2,118 +2,113 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/models/post_category.dart';
import 'package:island/models/post_tag.dart';
import 'package:island/pods/network.dart';
import 'package:island/pods/paging.dart';
import 'package:island/pods/post/post_categories.dart';
import 'package:island/widgets/app_scaffold.dart';
import 'package:island/widgets/paging/pagination_list.dart';
import 'package:material_symbols_icons/symbols.dart';
// Post Categories Notifier
final postCategoriesProvider = AsyncNotifierProvider.autoDispose<
PostCategoriesNotifier,
List<SnPostCategory>
>(PostCategoriesNotifier.new);
class PostCategoriesNotifier extends AsyncNotifier<List<SnPostCategory>>
with AsyncPaginationController<SnPostCategory> {
@override
Future<List<SnPostCategory>> fetch() async {
final client = ref.read(apiClientProvider);
final response = await client.get(
'/sphere/posts/categories',
queryParameters: {'offset': fetchedCount, 'take': 20, 'order': 'usage'},
);
totalCount = int.parse(response.headers.value('X-Total') ?? '0');
final data = response.data as List;
return data.map((json) => SnPostCategory.fromJson(json)).toList();
}
}
// Post Tags Notifier
final postTagsProvider =
AsyncNotifierProvider.autoDispose<PostTagsNotifier, List<SnPostTag>>(
PostTagsNotifier.new,
);
class PostTagsNotifier extends AsyncNotifier<List<SnPostTag>>
with AsyncPaginationController<SnPostTag> {
@override
Future<List<SnPostTag>> fetch() async {
final client = ref.read(apiClientProvider);
final response = await client.get(
'/sphere/posts/tags',
queryParameters: {'offset': fetchedCount, 'take': 20, 'order': 'usage'},
);
totalCount = int.parse(response.headers.value('X-Total') ?? '0');
final data = response.data as List;
return data.map((json) => SnPostTag.fromJson(json)).toList();
}
}
class PostCategoriesListScreen extends ConsumerWidget {
class PostCategoriesListScreen extends HookConsumerWidget {
const PostCategoriesListScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
return AppScaffold(
appBar: AppBar(title: const Text('categories').tr()),
body: PaginationList(
provider: postCategoriesProvider,
notifier: postCategoriesProvider.notifier,
padding: EdgeInsets.zero,
itemBuilder: (context, index, category) {
return ListTile(
leading: const Icon(Symbols.category),
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
trailing: const Icon(Symbols.chevron_right),
title: Text(category.categoryDisplayTitle),
subtitle: Text('postCount'.plural(category.usage)),
onTap: () {
context.pushNamed(
'postCategoryDetail',
pathParameters: {'slug': category.slug},
);
},
);
},
return DefaultTabController(
length: 2,
child: AppScaffold(
isNoBackground: false,
appBar: AppBar(
title: const Text('categoriesAndTags').tr(),
bottom: TabBar(
tabs: [
Tab(
child: Text(
'categories'.tr(),
style: TextStyle(
color: Theme.of(context).appBarTheme.foregroundColor,
),
),
),
Tab(
child: Text(
'tags'.tr(),
style: TextStyle(
color: Theme.of(context).appBarTheme.foregroundColor,
),
),
),
],
),
),
body: const TabBarView(children: [_CategoriesTab(), _TagsTab()]),
),
);
}
}
class PostTagsListScreen extends ConsumerWidget {
const PostTagsListScreen({super.key});
class _CategoriesTab extends ConsumerWidget {
const _CategoriesTab();
@override
Widget build(BuildContext context, WidgetRef ref) {
return AppScaffold(
appBar: AppBar(title: const Text('tags').tr()),
body: PaginationList(
provider: postTagsProvider,
notifier: postTagsProvider.notifier,
padding: EdgeInsets.zero,
itemBuilder: (context, index, tag) {
return ListTile(
title: Text(tag.name ?? '#${tag.slug}'),
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
leading: const Icon(Symbols.label),
trailing: const Icon(Symbols.chevron_right),
subtitle: Text('postCount'.plural(tag.usage)),
onTap: () {
context.pushNamed(
'postTagDetail',
pathParameters: {'slug': tag.slug},
);
},
);
},
),
return PaginationList(
provider: postCategoriesProvider,
notifier: postCategoriesProvider.notifier,
footerSkeletonMaxWidth: 640,
padding: EdgeInsets.zero,
itemBuilder: (context, index, category) {
return Center(
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 640),
child: ListTile(
leading: const Icon(Symbols.category),
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
trailing: const Icon(Symbols.chevron_right),
title: Text(category.categoryDisplayTitle),
subtitle: Text('postCount'.plural(category.usage)),
onTap: () {
context.pushNamed(
'postCategoryDetail',
pathParameters: {'slug': category.slug},
);
},
),
),
);
},
);
}
}
class _TagsTab extends ConsumerWidget {
const _TagsTab();
@override
Widget build(BuildContext context, WidgetRef ref) {
return PaginationList(
provider: postTagsProvider,
notifier: postTagsProvider.notifier,
footerSkeletonMaxWidth: 640,
padding: EdgeInsets.zero,
itemBuilder: (context, index, tag) {
return Center(
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 640),
child: ListTile(
title: Text(tag.name ?? '#${tag.slug}'),
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
leading: const Icon(Symbols.label),
trailing: const Icon(Symbols.chevron_right),
subtitle: Text('postCount'.plural(tag.usage)),
onTap: () {
context.pushNamed(
'postTagDetail',
pathParameters: {'slug': tag.slug},
);
},
),
),
);
},
);
}
}