💄 Redesign the post tags and categories page
This commit is contained in:
@@ -618,6 +618,7 @@
|
|||||||
"tagsHint": "Enter tags, separated by commas",
|
"tagsHint": "Enter tags, separated by commas",
|
||||||
"categories": "Categories",
|
"categories": "Categories",
|
||||||
"categoriesHint": "Enter categories, separated by commas",
|
"categoriesHint": "Enter categories, separated by commas",
|
||||||
|
"categoriesAndTags": "Categories & Tags",
|
||||||
"chatNotJoined": "You have not joined this chat yet.",
|
"chatNotJoined": "You have not joined this chat yet.",
|
||||||
"chatUnableJoin": "You can't join this chat due to it's access control settings.",
|
"chatUnableJoin": "You can't join this chat due to it's access control settings.",
|
||||||
"chatJoin": "Join the Chat",
|
"chatJoin": "Join the Chat",
|
||||||
|
|||||||
52
lib/pods/post/post_categories.dart
Normal file
52
lib/pods/post/post_categories.dart
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
// Post Categories Notifier
|
||||||
|
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';
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -105,8 +105,7 @@ final routerProvider = Provider<GoRouter>((ref) {
|
|||||||
GoRoute(
|
GoRoute(
|
||||||
name: 'articleCompose',
|
name: 'articleCompose',
|
||||||
path: '/articles/compose',
|
path: '/articles/compose',
|
||||||
builder:
|
builder: (context, state) => ArticleComposeScreen(
|
||||||
(context, state) => ArticleComposeScreen(
|
|
||||||
initialState: state.extra as PostComposeInitialState?,
|
initialState: state.extra as PostComposeInitialState?,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -190,8 +189,7 @@ final routerProvider = Provider<GoRouter>((ref) {
|
|||||||
GoRoute(
|
GoRoute(
|
||||||
name: 'explore',
|
name: 'explore',
|
||||||
path: '/',
|
path: '/',
|
||||||
pageBuilder:
|
pageBuilder: (context, state) => CustomTransitionPage(
|
||||||
(context, state) => CustomTransitionPage(
|
|
||||||
key: const ValueKey('explore'),
|
key: const ValueKey('explore'),
|
||||||
child: const ExploreScreen(),
|
child: const ExploreScreen(),
|
||||||
transitionsBuilder: _tabPagesTransitionBuilder,
|
transitionsBuilder: _tabPagesTransitionBuilder,
|
||||||
@@ -220,11 +218,6 @@ final routerProvider = Provider<GoRouter>((ref) {
|
|||||||
return PostCategoryDetailScreen(slug: slug, isCategory: true);
|
return PostCategoryDetailScreen(slug: slug, isCategory: true);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
GoRoute(
|
|
||||||
name: 'postTags',
|
|
||||||
path: '/posts/tags',
|
|
||||||
builder: (context, state) => const PostTagsListScreen(),
|
|
||||||
),
|
|
||||||
GoRoute(
|
GoRoute(
|
||||||
name: 'postTagDetail',
|
name: 'postTagDetail',
|
||||||
path: '/posts/tags/:slug',
|
path: '/posts/tags/:slug',
|
||||||
@@ -260,8 +253,7 @@ final routerProvider = Provider<GoRouter>((ref) {
|
|||||||
|
|
||||||
// Chat tab
|
// Chat tab
|
||||||
ShellRoute(
|
ShellRoute(
|
||||||
pageBuilder:
|
pageBuilder: (context, state, child) => CustomTransitionPage(
|
||||||
(context, state, child) => CustomTransitionPage(
|
|
||||||
key: const ValueKey('chat'),
|
key: const ValueKey('chat'),
|
||||||
child: ChatShellScreen(child: child),
|
child: ChatShellScreen(child: child),
|
||||||
transitionsBuilder: _tabPagesTransitionBuilder,
|
transitionsBuilder: _tabPagesTransitionBuilder,
|
||||||
@@ -303,8 +295,7 @@ final routerProvider = Provider<GoRouter>((ref) {
|
|||||||
GoRoute(
|
GoRoute(
|
||||||
name: 'realmList',
|
name: 'realmList',
|
||||||
path: '/realms',
|
path: '/realms',
|
||||||
pageBuilder:
|
pageBuilder: (context, state) => CustomTransitionPage(
|
||||||
(context, state) => CustomTransitionPage(
|
|
||||||
key: const ValueKey('realms'),
|
key: const ValueKey('realms'),
|
||||||
child: const RealmListScreen(),
|
child: const RealmListScreen(),
|
||||||
transitionsBuilder: _tabPagesTransitionBuilder,
|
transitionsBuilder: _tabPagesTransitionBuilder,
|
||||||
@@ -336,8 +327,7 @@ final routerProvider = Provider<GoRouter>((ref) {
|
|||||||
|
|
||||||
// Account tab
|
// Account tab
|
||||||
ShellRoute(
|
ShellRoute(
|
||||||
pageBuilder:
|
pageBuilder: (context, state, child) => CustomTransitionPage(
|
||||||
(context, state, child) => CustomTransitionPage(
|
|
||||||
key: const ValueKey('account'),
|
key: const ValueKey('account'),
|
||||||
child: AccountShellScreen(child: child),
|
child: AccountShellScreen(child: child),
|
||||||
transitionsBuilder: _tabPagesTransitionBuilder,
|
transitionsBuilder: _tabPagesTransitionBuilder,
|
||||||
@@ -352,8 +342,8 @@ final routerProvider = Provider<GoRouter>((ref) {
|
|||||||
GoRoute(
|
GoRoute(
|
||||||
name: 'stickerMarketplace',
|
name: 'stickerMarketplace',
|
||||||
path: '/stickers',
|
path: '/stickers',
|
||||||
builder:
|
builder: (context, state) =>
|
||||||
(context, state) => const MarketplaceStickersScreen(),
|
const MarketplaceStickersScreen(),
|
||||||
routes: [
|
routes: [
|
||||||
GoRoute(
|
GoRoute(
|
||||||
name: 'stickerPackDetail',
|
name: 'stickerPackDetail',
|
||||||
@@ -368,8 +358,8 @@ final routerProvider = Provider<GoRouter>((ref) {
|
|||||||
GoRoute(
|
GoRoute(
|
||||||
name: 'webFeedMarketplace',
|
name: 'webFeedMarketplace',
|
||||||
path: '/feeds',
|
path: '/feeds',
|
||||||
builder:
|
builder: (context, state) =>
|
||||||
(context, state) => const MarketplaceWebFeedsScreen(),
|
const MarketplaceWebFeedsScreen(),
|
||||||
routes: [
|
routes: [
|
||||||
GoRoute(
|
GoRoute(
|
||||||
name: 'webFeedDetail',
|
name: 'webFeedDetail',
|
||||||
@@ -516,26 +506,22 @@ final routerProvider = Provider<GoRouter>((ref) {
|
|||||||
GoRoute(
|
GoRoute(
|
||||||
name: 'developerHub',
|
name: 'developerHub',
|
||||||
path: '/developers',
|
path: '/developers',
|
||||||
builder:
|
builder: (context, state) => DeveloperHubScreen(
|
||||||
(context, state) => DeveloperHubScreen(
|
initialPublisherName: state.uri.queryParameters['publisher'],
|
||||||
initialPublisherName:
|
|
||||||
state.uri.queryParameters['publisher'],
|
|
||||||
initialProjectId: state.uri.queryParameters['project'],
|
initialProjectId: state.uri.queryParameters['project'],
|
||||||
),
|
),
|
||||||
routes: [
|
routes: [
|
||||||
GoRoute(
|
GoRoute(
|
||||||
name: 'developerProjectNew',
|
name: 'developerProjectNew',
|
||||||
path: ':name/projects/new',
|
path: ':name/projects/new',
|
||||||
builder:
|
builder: (context, state) => NewProjectScreen(
|
||||||
(context, state) => NewProjectScreen(
|
|
||||||
publisherName: state.pathParameters['name']!,
|
publisherName: state.pathParameters['name']!,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
name: 'developerProjectEdit',
|
name: 'developerProjectEdit',
|
||||||
path: ':name/projects/:id/edit',
|
path: ':name/projects/:id/edit',
|
||||||
builder:
|
builder: (context, state) => EditProjectScreen(
|
||||||
(context, state) => EditProjectScreen(
|
|
||||||
publisherName: state.pathParameters['name']!,
|
publisherName: state.pathParameters['name']!,
|
||||||
id: state.pathParameters['id']!,
|
id: state.pathParameters['id']!,
|
||||||
),
|
),
|
||||||
@@ -558,8 +544,7 @@ final routerProvider = Provider<GoRouter>((ref) {
|
|||||||
GoRoute(
|
GoRoute(
|
||||||
name: 'developerAppDetail',
|
name: 'developerAppDetail',
|
||||||
path: 'apps/:appId',
|
path: 'apps/:appId',
|
||||||
builder:
|
builder: (context, state) => AppDetailScreen(
|
||||||
(context, state) => AppDetailScreen(
|
|
||||||
publisherName: state.pathParameters['name']!,
|
publisherName: state.pathParameters['name']!,
|
||||||
projectId: state.pathParameters['projectId']!,
|
projectId: state.pathParameters['projectId']!,
|
||||||
appId: state.pathParameters['appId']!,
|
appId: state.pathParameters['appId']!,
|
||||||
@@ -568,8 +553,7 @@ final routerProvider = Provider<GoRouter>((ref) {
|
|||||||
GoRoute(
|
GoRoute(
|
||||||
name: 'developerBotDetail',
|
name: 'developerBotDetail',
|
||||||
path: 'bots/:botId',
|
path: 'bots/:botId',
|
||||||
builder:
|
builder: (context, state) => BotDetailScreen(
|
||||||
(context, state) => BotDetailScreen(
|
|
||||||
publisherName: state.pathParameters['name']!,
|
publisherName: state.pathParameters['name']!,
|
||||||
projectId: state.pathParameters['projectId']!,
|
projectId: state.pathParameters['projectId']!,
|
||||||
botId: state.pathParameters['botId']!,
|
botId: state.pathParameters['botId']!,
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ class ExploreScreen extends HookConsumerWidget {
|
|||||||
// Listen for post creation events to refresh activities
|
// Listen for post creation events to refresh activities
|
||||||
useEffect(() {
|
useEffect(() {
|
||||||
final subscription = eventBus.on<PostCreatedEvent>().listen((event) {
|
final subscription = eventBus.on<PostCreatedEvent>().listen((event) {
|
||||||
ref.invalidate(activityListProvider);
|
ref.read(activityListProvider.notifier).refresh();
|
||||||
});
|
});
|
||||||
return subscription.cancel;
|
return subscription.cancel;
|
||||||
}, []);
|
}, []);
|
||||||
@@ -183,25 +183,13 @@ class ExploreScreen extends HookConsumerWidget {
|
|||||||
children: [
|
children: [
|
||||||
const Icon(Symbols.category),
|
const Icon(Symbols.category),
|
||||||
const Gap(12),
|
const Gap(12),
|
||||||
Text('categories').tr(),
|
Text('categoriesAndTags').tr(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
context.pushNamed('postCategories');
|
context.pushNamed('postCategories');
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
PopupMenuItem(
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
const Icon(Symbols.label),
|
|
||||||
const Gap(12),
|
|
||||||
Text('tags').tr(),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
onTap: () {
|
|
||||||
context.pushNamed('postTags');
|
|
||||||
},
|
|
||||||
),
|
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
@@ -490,25 +478,13 @@ class ExploreScreen extends HookConsumerWidget {
|
|||||||
children: [
|
children: [
|
||||||
const Icon(Symbols.category),
|
const Icon(Symbols.category),
|
||||||
const Gap(12),
|
const Gap(12),
|
||||||
Text('categories').tr(),
|
Text('categoriesAndTags').tr(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
context.pushNamed('postCategories');
|
context.pushNamed('postCategories');
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
PopupMenuItem(
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
const Icon(Symbols.label),
|
|
||||||
const Gap(12),
|
|
||||||
Text('tags').tr(),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
onTap: () {
|
|
||||||
context.pushNamed('postTags');
|
|
||||||
},
|
|
||||||
),
|
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
|
|||||||
@@ -2,73 +2,64 @@ import 'package:easy_localization/easy_localization.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:island/models/post_category.dart';
|
import 'package:island/pods/post/post_categories.dart';
|
||||||
import 'package:island/models/post_tag.dart';
|
|
||||||
import 'package:island/pods/network.dart';
|
|
||||||
import 'package:island/pods/paging.dart';
|
|
||||||
import 'package:island/widgets/app_scaffold.dart';
|
import 'package:island/widgets/app_scaffold.dart';
|
||||||
import 'package:island/widgets/paging/pagination_list.dart';
|
import 'package:island/widgets/paging/pagination_list.dart';
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
|
|
||||||
// Post Categories Notifier
|
class PostCategoriesListScreen extends HookConsumerWidget {
|
||||||
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 {
|
|
||||||
const PostCategoriesListScreen({super.key});
|
const PostCategoriesListScreen({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
return AppScaffold(
|
return DefaultTabController(
|
||||||
appBar: AppBar(title: const Text('categories').tr()),
|
length: 2,
|
||||||
body: PaginationList(
|
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 _CategoriesTab extends ConsumerWidget {
|
||||||
|
const _CategoriesTab();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
return PaginationList(
|
||||||
provider: postCategoriesProvider,
|
provider: postCategoriesProvider,
|
||||||
notifier: postCategoriesProvider.notifier,
|
notifier: postCategoriesProvider.notifier,
|
||||||
|
footerSkeletonMaxWidth: 640,
|
||||||
padding: EdgeInsets.zero,
|
padding: EdgeInsets.zero,
|
||||||
itemBuilder: (context, index, category) {
|
itemBuilder: (context, index, category) {
|
||||||
return ListTile(
|
return Center(
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(maxWidth: 640),
|
||||||
|
child: ListTile(
|
||||||
leading: const Icon(Symbols.category),
|
leading: const Icon(Symbols.category),
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
trailing: const Icon(Symbols.chevron_right),
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
@@ -80,26 +71,29 @@ class PostCategoriesListScreen extends ConsumerWidget {
|
|||||||
pathParameters: {'slug': category.slug},
|
pathParameters: {'slug': category.slug},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class PostTagsListScreen extends ConsumerWidget {
|
class _TagsTab extends ConsumerWidget {
|
||||||
const PostTagsListScreen({super.key});
|
const _TagsTab();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
return AppScaffold(
|
return PaginationList(
|
||||||
appBar: AppBar(title: const Text('tags').tr()),
|
|
||||||
body: PaginationList(
|
|
||||||
provider: postTagsProvider,
|
provider: postTagsProvider,
|
||||||
notifier: postTagsProvider.notifier,
|
notifier: postTagsProvider.notifier,
|
||||||
|
footerSkeletonMaxWidth: 640,
|
||||||
padding: EdgeInsets.zero,
|
padding: EdgeInsets.zero,
|
||||||
itemBuilder: (context, index, tag) {
|
itemBuilder: (context, index, tag) {
|
||||||
return ListTile(
|
return Center(
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(maxWidth: 640),
|
||||||
|
child: ListTile(
|
||||||
title: Text(tag.name ?? '#${tag.slug}'),
|
title: Text(tag.name ?? '#${tag.slug}'),
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
leading: const Icon(Symbols.label),
|
leading: const Icon(Symbols.label),
|
||||||
@@ -111,9 +105,10 @@ class PostTagsListScreen extends ConsumerWidget {
|
|||||||
pathParameters: {'slug': tag.slug},
|
pathParameters: {'slug': tag.slug},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
),
|
||||||
},
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ class PaginationList<T> extends HookConsumerWidget {
|
|||||||
final bool showDefaultWidgets;
|
final bool showDefaultWidgets;
|
||||||
final EdgeInsets? padding;
|
final EdgeInsets? padding;
|
||||||
final Widget? footerSkeletonChild;
|
final Widget? footerSkeletonChild;
|
||||||
|
final double? footerSkeletonMaxWidth;
|
||||||
const PaginationList({
|
const PaginationList({
|
||||||
super.key,
|
super.key,
|
||||||
required this.provider,
|
required this.provider,
|
||||||
@@ -32,6 +33,7 @@ class PaginationList<T> extends HookConsumerWidget {
|
|||||||
this.showDefaultWidgets = true,
|
this.showDefaultWidgets = true,
|
||||||
this.padding,
|
this.padding,
|
||||||
this.footerSkeletonChild,
|
this.footerSkeletonChild,
|
||||||
|
this.footerSkeletonMaxWidth,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -53,7 +55,9 @@ class PaginationList<T> extends HookConsumerWidget {
|
|||||||
).colorScheme.surfaceContainerHighest,
|
).colorScheme.surfaceContainerHighest,
|
||||||
),
|
),
|
||||||
containersColor: Theme.of(context).colorScheme.surfaceContainerLow,
|
containersColor: Theme.of(context).colorScheme.surfaceContainerLow,
|
||||||
child: footerSkeletonChild ?? const _DefaultSkeletonChild(),
|
child:
|
||||||
|
footerSkeletonChild ??
|
||||||
|
_DefaultSkeletonChild(maxWidth: footerSkeletonMaxWidth),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return SliverList.list(children: content);
|
return SliverList.list(children: content);
|
||||||
@@ -75,6 +79,7 @@ class PaginationList<T> extends HookConsumerWidget {
|
|||||||
noti: noti,
|
noti: noti,
|
||||||
data: data,
|
data: data,
|
||||||
skeletonChild: footerSkeletonChild,
|
skeletonChild: footerSkeletonChild,
|
||||||
|
skeletonMaxWidth: footerSkeletonMaxWidth,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
final entry = data.value?[idx];
|
final entry = data.value?[idx];
|
||||||
@@ -102,7 +107,9 @@ class PaginationList<T> extends HookConsumerWidget {
|
|||||||
).colorScheme.surfaceContainerHighest,
|
).colorScheme.surfaceContainerHighest,
|
||||||
),
|
),
|
||||||
containersColor: Theme.of(context).colorScheme.surfaceContainerLow,
|
containersColor: Theme.of(context).colorScheme.surfaceContainerLow,
|
||||||
child: footerSkeletonChild ?? const _DefaultSkeletonChild(),
|
child:
|
||||||
|
footerSkeletonChild ??
|
||||||
|
_DefaultSkeletonChild(maxWidth: footerSkeletonMaxWidth),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
@@ -128,6 +135,7 @@ class PaginationList<T> extends HookConsumerWidget {
|
|||||||
noti: noti,
|
noti: noti,
|
||||||
data: data,
|
data: data,
|
||||||
skeletonChild: footerSkeletonChild,
|
skeletonChild: footerSkeletonChild,
|
||||||
|
skeletonMaxWidth: footerSkeletonMaxWidth,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
final entry = data.value?[idx];
|
final entry = data.value?[idx];
|
||||||
@@ -159,6 +167,7 @@ class PaginationWidget<T> extends HookConsumerWidget {
|
|||||||
final bool isSliver;
|
final bool isSliver;
|
||||||
final bool showDefaultWidgets;
|
final bool showDefaultWidgets;
|
||||||
final Widget? footerSkeletonChild;
|
final Widget? footerSkeletonChild;
|
||||||
|
final double? footerSkeletonMaxWidth;
|
||||||
const PaginationWidget({
|
const PaginationWidget({
|
||||||
super.key,
|
super.key,
|
||||||
required this.provider,
|
required this.provider,
|
||||||
@@ -168,6 +177,7 @@ class PaginationWidget<T> extends HookConsumerWidget {
|
|||||||
this.isSliver = false,
|
this.isSliver = false,
|
||||||
this.showDefaultWidgets = true,
|
this.showDefaultWidgets = true,
|
||||||
this.footerSkeletonChild,
|
this.footerSkeletonChild,
|
||||||
|
this.footerSkeletonMaxWidth,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -189,7 +199,9 @@ class PaginationWidget<T> extends HookConsumerWidget {
|
|||||||
).colorScheme.surfaceContainerHighest,
|
).colorScheme.surfaceContainerHighest,
|
||||||
),
|
),
|
||||||
containersColor: Theme.of(context).colorScheme.surfaceContainerLow,
|
containersColor: Theme.of(context).colorScheme.surfaceContainerLow,
|
||||||
child: footerSkeletonChild ?? const _DefaultSkeletonChild(),
|
child:
|
||||||
|
footerSkeletonChild ??
|
||||||
|
_DefaultSkeletonChild(maxWidth: footerSkeletonMaxWidth),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return SliverList.list(children: content);
|
return SliverList.list(children: content);
|
||||||
@@ -207,6 +219,7 @@ class PaginationWidget<T> extends HookConsumerWidget {
|
|||||||
noti: noti,
|
noti: noti,
|
||||||
data: data,
|
data: data,
|
||||||
skeletonChild: footerSkeletonChild,
|
skeletonChild: footerSkeletonChild,
|
||||||
|
skeletonMaxWidth: footerSkeletonMaxWidth,
|
||||||
);
|
);
|
||||||
final content = contentBuilder(data.value ?? [], footer);
|
final content = contentBuilder(data.value ?? [], footer);
|
||||||
|
|
||||||
@@ -229,7 +242,9 @@ class PaginationWidget<T> extends HookConsumerWidget {
|
|||||||
).colorScheme.surfaceContainerHighest,
|
).colorScheme.surfaceContainerHighest,
|
||||||
),
|
),
|
||||||
containersColor: Theme.of(context).colorScheme.surfaceContainerLow,
|
containersColor: Theme.of(context).colorScheme.surfaceContainerLow,
|
||||||
child: footerSkeletonChild ?? const _DefaultSkeletonChild(),
|
child:
|
||||||
|
footerSkeletonChild ??
|
||||||
|
_DefaultSkeletonChild(maxWidth: footerSkeletonMaxWidth),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
@@ -250,6 +265,7 @@ class PaginationWidget<T> extends HookConsumerWidget {
|
|||||||
noti: noti,
|
noti: noti,
|
||||||
data: data,
|
data: data,
|
||||||
skeletonChild: footerSkeletonChild,
|
skeletonChild: footerSkeletonChild,
|
||||||
|
skeletonMaxWidth: footerSkeletonMaxWidth,
|
||||||
);
|
);
|
||||||
final content = contentBuilder(data.value ?? [], footer);
|
final content = contentBuilder(data.value ?? [], footer);
|
||||||
|
|
||||||
@@ -272,6 +288,7 @@ class PaginationListFooter<T> extends HookConsumerWidget {
|
|||||||
final PaginationController<T> noti;
|
final PaginationController<T> noti;
|
||||||
final AsyncValue<List<T>> data;
|
final AsyncValue<List<T>> data;
|
||||||
final Widget? skeletonChild;
|
final Widget? skeletonChild;
|
||||||
|
final double? skeletonMaxWidth;
|
||||||
final bool isSliver;
|
final bool isSliver;
|
||||||
|
|
||||||
const PaginationListFooter({
|
const PaginationListFooter({
|
||||||
@@ -279,6 +296,7 @@ class PaginationListFooter<T> extends HookConsumerWidget {
|
|||||||
required this.noti,
|
required this.noti,
|
||||||
required this.data,
|
required this.data,
|
||||||
this.skeletonChild,
|
this.skeletonChild,
|
||||||
|
this.skeletonMaxWidth,
|
||||||
this.isSliver = false,
|
this.isSliver = false,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -293,7 +311,7 @@ class PaginationListFooter<T> extends HookConsumerWidget {
|
|||||||
highlightColor: Theme.of(context).colorScheme.surfaceContainerHighest,
|
highlightColor: Theme.of(context).colorScheme.surfaceContainerHighest,
|
||||||
),
|
),
|
||||||
containersColor: Theme.of(context).colorScheme.surfaceContainerLow,
|
containersColor: Theme.of(context).colorScheme.surfaceContainerLow,
|
||||||
child: skeletonChild ?? _DefaultSkeletonChild(),
|
child: skeletonChild ?? _DefaultSkeletonChild(maxWidth: skeletonMaxWidth),
|
||||||
);
|
);
|
||||||
final child = hasBeenVisible.value
|
final child = hasBeenVisible.value
|
||||||
? data.isLoading
|
? data.isLoading
|
||||||
@@ -322,14 +340,24 @@ class PaginationListFooter<T> extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _DefaultSkeletonChild extends StatelessWidget {
|
class _DefaultSkeletonChild extends StatelessWidget {
|
||||||
const _DefaultSkeletonChild();
|
final double? maxWidth;
|
||||||
|
const _DefaultSkeletonChild({this.maxWidth});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ListTile(
|
final content = ListTile(
|
||||||
title: Text('Some data'),
|
title: Text('Some data'),
|
||||||
subtitle: const Text('Subtitle here'),
|
subtitle: const Text('Subtitle here'),
|
||||||
trailing: const Icon(Icons.ac_unit),
|
trailing: const Icon(Icons.ac_unit),
|
||||||
);
|
);
|
||||||
|
if (maxWidth != null) {
|
||||||
|
return Center(
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: BoxConstraints(maxWidth: maxWidth!),
|
||||||
|
child: content,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return content;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,33 +9,14 @@ import 'package:island/models/post_category.dart';
|
|||||||
import 'package:island/models/post_tag.dart';
|
import 'package:island/models/post_tag.dart';
|
||||||
import 'package:island/models/realm.dart';
|
import 'package:island/models/realm.dart';
|
||||||
import 'package:island/pods/network.dart';
|
import 'package:island/pods/network.dart';
|
||||||
|
import 'package:island/pods/post/post_categories.dart';
|
||||||
import 'package:island/screens/realm/realms.dart';
|
import 'package:island/screens/realm/realms.dart';
|
||||||
import 'package:island/widgets/content/cloud_files.dart';
|
import 'package:island/widgets/content/cloud_files.dart';
|
||||||
import 'package:island/widgets/content/sheet.dart';
|
import 'package:island/widgets/content/sheet.dart';
|
||||||
import 'package:island/widgets/post/compose_shared.dart';
|
import 'package:island/widgets/post/compose_shared.dart';
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
|
||||||
part 'compose_settings_sheet.g.dart';
|
|
||||||
|
|
||||||
@riverpod
|
|
||||||
Future<List<SnPostCategory>> postCategories(Ref ref) async {
|
|
||||||
final apiClient = ref.watch(apiClientProvider);
|
|
||||||
final resp = await apiClient.get('/sphere/posts/categories');
|
|
||||||
final categories =
|
|
||||||
resp.data
|
|
||||||
.map((e) => SnPostCategory.fromJson(e))
|
|
||||||
.cast<SnPostCategory>()
|
|
||||||
.toList();
|
|
||||||
// Remove duplicates based on id
|
|
||||||
final uniqueCategories = <String, SnPostCategory>{};
|
|
||||||
for (final category in categories) {
|
|
||||||
uniqueCategories[category.id] = category;
|
|
||||||
}
|
|
||||||
return uniqueCategories.values.toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
class ComposeSettingsSheet extends HookConsumerWidget {
|
class ComposeSettingsSheet extends HookConsumerWidget {
|
||||||
final ComposeState state;
|
final ComposeState state;
|
||||||
|
|
||||||
@@ -121,8 +102,7 @@ class ComposeSettingsSheet extends HookConsumerWidget {
|
|||||||
void showVisibilitySheet() {
|
void showVisibilitySheet() {
|
||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
context: context,
|
context: context,
|
||||||
builder:
|
builder: (context) => SheetScaffold(
|
||||||
(context) => SheetScaffold(
|
|
||||||
titleText: 'postVisibility'.tr(),
|
titleText: 'postVisibility'.tr(),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
@@ -182,8 +162,8 @@ class ComposeSettingsSheet extends HookConsumerWidget {
|
|||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
onTapOutside:
|
onTapOutside: (_) =>
|
||||||
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
),
|
),
|
||||||
|
|
||||||
// Tags field
|
// Tags field
|
||||||
@@ -209,8 +189,7 @@ class ComposeSettingsSheet extends HookConsumerWidget {
|
|||||||
Wrap(
|
Wrap(
|
||||||
spacing: 8,
|
spacing: 8,
|
||||||
runSpacing: 8,
|
runSpacing: 8,
|
||||||
children:
|
children: currentTags.map((tag) {
|
||||||
currentTags.map((tag) {
|
|
||||||
return Container(
|
return Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Theme.of(context).colorScheme.primary,
|
color: Theme.of(context).colorScheme.primary,
|
||||||
@@ -226,8 +205,7 @@ class ComposeSettingsSheet extends HookConsumerWidget {
|
|||||||
Text(
|
Text(
|
||||||
'#$tag',
|
'#$tag',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color:
|
color: Theme.of(
|
||||||
Theme.of(
|
|
||||||
context,
|
context,
|
||||||
).colorScheme.onPrimary,
|
).colorScheme.onPrimary,
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
@@ -244,8 +222,7 @@ class ComposeSettingsSheet extends HookConsumerWidget {
|
|||||||
child: Icon(
|
child: Icon(
|
||||||
Icons.close,
|
Icons.close,
|
||||||
size: 16,
|
size: 16,
|
||||||
color:
|
color: Theme.of(
|
||||||
Theme.of(
|
|
||||||
context,
|
context,
|
||||||
).colorScheme.onPrimary,
|
).colorScheme.onPrimary,
|
||||||
),
|
),
|
||||||
@@ -274,8 +251,8 @@ class ComposeSettingsSheet extends HookConsumerWidget {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
suggestionsCallback:
|
suggestionsCallback: (pattern) =>
|
||||||
(pattern) => _fetchTagSuggestions(pattern, ref),
|
_fetchTagSuggestions(pattern, ref),
|
||||||
itemBuilder: (context, suggestion) {
|
itemBuilder: (context, suggestion) {
|
||||||
return ListTile(
|
return ListTile(
|
||||||
shape: const RoundedRectangleBorder(
|
shape: const RoundedRectangleBorder(
|
||||||
@@ -314,21 +291,17 @@ class ComposeSettingsSheet extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
hint: Text('categories'.tr(), style: TextStyle(fontSize: 15)),
|
hint: Text('categories'.tr(), style: TextStyle(fontSize: 15)),
|
||||||
items:
|
items: (postCategories.value ?? <SnPostCategory>[]).map((item) {
|
||||||
(postCategories.value ?? <SnPostCategory>[]).map((item) {
|
|
||||||
return DropdownMenuItem(
|
return DropdownMenuItem(
|
||||||
value: item,
|
value: item,
|
||||||
enabled: false,
|
enabled: false,
|
||||||
child: StatefulBuilder(
|
child: StatefulBuilder(
|
||||||
builder: (context, menuSetState) {
|
builder: (context, menuSetState) {
|
||||||
final isSelected = state.categories.value.contains(
|
final isSelected = state.categories.value.contains(item);
|
||||||
item,
|
|
||||||
);
|
|
||||||
return InkWell(
|
return InkWell(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
isSelected
|
isSelected
|
||||||
? state.categories.value =
|
? state.categories.value = state.categories.value
|
||||||
state.categories.value
|
|
||||||
.where((e) => e != item)
|
.where((e) => e != item)
|
||||||
.toList()
|
.toList()
|
||||||
: state.categories.value = [
|
: state.categories.value = [
|
||||||
@@ -339,9 +312,7 @@ class ComposeSettingsSheet extends HookConsumerWidget {
|
|||||||
},
|
},
|
||||||
child: Container(
|
child: Container(
|
||||||
height: double.infinity,
|
height: double.infinity,
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||||
horizontal: 16.0,
|
|
||||||
),
|
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
if (isSelected)
|
if (isSelected)
|
||||||
|
|||||||
@@ -1,51 +0,0 @@
|
|||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
|
|
||||||
part of 'compose_settings_sheet.dart';
|
|
||||||
|
|
||||||
// **************************************************************************
|
|
||||||
// RiverpodGenerator
|
|
||||||
// **************************************************************************
|
|
||||||
|
|
||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
// ignore_for_file: type=lint, type=warning
|
|
||||||
|
|
||||||
@ProviderFor(postCategories)
|
|
||||||
const postCategoriesProvider = PostCategoriesProvider._();
|
|
||||||
|
|
||||||
final class PostCategoriesProvider
|
|
||||||
extends
|
|
||||||
$FunctionalProvider<
|
|
||||||
AsyncValue<List<SnPostCategory>>,
|
|
||||||
List<SnPostCategory>,
|
|
||||||
FutureOr<List<SnPostCategory>>
|
|
||||||
>
|
|
||||||
with
|
|
||||||
$FutureModifier<List<SnPostCategory>>,
|
|
||||||
$FutureProvider<List<SnPostCategory>> {
|
|
||||||
const PostCategoriesProvider._()
|
|
||||||
: super(
|
|
||||||
from: null,
|
|
||||||
argument: null,
|
|
||||||
retry: null,
|
|
||||||
name: r'postCategoriesProvider',
|
|
||||||
isAutoDispose: true,
|
|
||||||
dependencies: null,
|
|
||||||
$allTransitiveDependencies: null,
|
|
||||||
);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String debugGetCreateSourceHash() => _$postCategoriesHash();
|
|
||||||
|
|
||||||
@$internal
|
|
||||||
@override
|
|
||||||
$FutureProviderElement<List<SnPostCategory>> $createElement(
|
|
||||||
$ProviderPointer pointer,
|
|
||||||
) => $FutureProviderElement(pointer);
|
|
||||||
|
|
||||||
@override
|
|
||||||
FutureOr<List<SnPostCategory>> create(Ref ref) {
|
|
||||||
return postCategories(ref);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String _$postCategoriesHash() => r'8799c10eb91cf8c8c7ea72eff3475e1eaa7b9a2b';
|
|
||||||
Reference in New Issue
Block a user