♻️ Replaced all list with own pagination list

This commit is contained in:
2025-12-06 02:29:11 +08:00
parent c4ac256896
commit fd79c11d18
25 changed files with 1028 additions and 3073 deletions

View File

@@ -61,6 +61,47 @@ mixin AsyncPaginationController<T> on AsyncNotifier<List<T>>
} }
} }
mixin AutoDisposeAsyncPaginationController<T>
on AutoDisposeAsyncNotifier<List<T>>
implements PaginationController<T> {
@override
int? totalCount;
@override
int get fetchedCount => state.value?.length ?? 0;
@override
bool get fetchedAll => totalCount != null && fetchedCount >= totalCount!;
@override
FutureOr<List<T>> build() async => fetch();
@override
Future<void> refresh() async {
totalCount = null;
state = AsyncData<List<T>>([]);
final newState = await AsyncValue.guard<List<T>>(() async {
return await fetch();
});
state = newState;
}
@override
Future<void> fetchFurther() async {
if (fetchedAll) return;
state = AsyncLoading<List<T>>();
final newState = await AsyncValue.guard<List<T>>(() async {
final elements = await fetch();
return [...?state.valueOrNull, ...elements];
});
state = newState;
}
}
mixin FamilyAsyncPaginationController<T, Arg> mixin FamilyAsyncPaginationController<T, Arg>
on AutoDisposeFamilyAsyncNotifier<List<T>, Arg> on AutoDisposeFamilyAsyncNotifier<List<T>, Arg>
implements PaginationController<T> { implements PaginationController<T> {

View File

@@ -1,4 +1,3 @@
import 'dart:async';
import 'package:easy_localization/easy_localization.dart'; 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';
@@ -6,134 +5,55 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/models/post_category.dart'; import 'package:island/models/post_category.dart';
import 'package:island/models/post_tag.dart'; import 'package:island/models/post_tag.dart';
import 'package:island/pods/network.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/response.dart'; import 'package:island/widgets/paging/pagination_list.dart';
import 'package:material_symbols_icons/symbols.dart'; import 'package:material_symbols_icons/symbols.dart';
import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
// Post Categories Notifier // Post Categories Notifier
final postCategoriesNotifierProvider = StateNotifierProvider.autoDispose< final postCategoriesNotifierProvider = AsyncNotifierProvider.autoDispose<
PostCategoriesNotifier, PostCategoriesNotifier,
AsyncValue<CursorPagingData<SnPostCategory>> List<SnPostCategory>
>((ref) { >(PostCategoriesNotifier.new);
return PostCategoriesNotifier(ref);
});
class PostCategoriesNotifier class PostCategoriesNotifier
extends StateNotifier<AsyncValue<CursorPagingData<SnPostCategory>>> { extends AutoDisposeAsyncNotifier<List<SnPostCategory>>
final AutoDisposeRef ref; with AutoDisposeAsyncPaginationController<SnPostCategory> {
static const int _pageSize = 20; @override
bool _isLoading = false; Future<List<SnPostCategory>> fetch() async {
PostCategoriesNotifier(this.ref) : super(const AsyncValue.loading()) {
state = const AsyncValue.data(
CursorPagingData(items: [], hasMore: false, nextCursor: null),
);
fetch(cursor: null);
}
Future<void> fetch({String? cursor}) async {
if (_isLoading) return;
_isLoading = true;
if (cursor == null) {
state = const AsyncValue.loading();
}
try {
final client = ref.read(apiClientProvider); final client = ref.read(apiClientProvider);
final offset = cursor == null ? 0 : int.parse(cursor);
final response = await client.get( final response = await client.get(
'/sphere/posts/categories', '/sphere/posts/categories',
queryParameters: { queryParameters: {'offset': fetchedCount, 'take': 20, 'order': 'usage'},
'offset': offset,
'take': _pageSize,
'order': 'usage',
},
); );
totalCount = int.parse(response.headers.value('X-Total') ?? '0');
final data = response.data as List; final data = response.data as List;
final categories = return data.map((json) => SnPostCategory.fromJson(json)).toList();
data.map((json) => SnPostCategory.fromJson(json)).toList();
final hasMore = categories.length == _pageSize;
final nextCursor =
hasMore ? (offset + categories.length).toString() : null;
state = AsyncValue.data(
CursorPagingData(
items: [...(state.value?.items ?? []), ...categories],
hasMore: hasMore,
nextCursor: nextCursor,
),
);
} catch (e, stack) {
state = AsyncValue.error(e, stack);
} finally {
_isLoading = false;
}
} }
} }
// Post Tags Notifier // Post Tags Notifier
final postTagsNotifierProvider = StateNotifierProvider.autoDispose< final postTagsNotifierProvider =
PostTagsNotifier, AsyncNotifierProvider.autoDispose<PostTagsNotifier, List<SnPostTag>>(
AsyncValue<CursorPagingData<SnPostTag>> PostTagsNotifier.new,
>((ref) {
return PostTagsNotifier(ref);
});
class PostTagsNotifier
extends StateNotifier<AsyncValue<CursorPagingData<SnPostTag>>> {
final AutoDisposeRef ref;
static const int _pageSize = 20;
bool _isLoading = false;
PostTagsNotifier(this.ref) : super(const AsyncValue.loading()) {
state = const AsyncValue.data(
CursorPagingData(items: [], hasMore: false, nextCursor: null),
); );
fetch(cursor: null);
}
Future<void> fetch({String? cursor}) async { class PostTagsNotifier extends AutoDisposeAsyncNotifier<List<SnPostTag>>
if (_isLoading) return; with AutoDisposeAsyncPaginationController<SnPostTag> {
@override
_isLoading = true; Future<List<SnPostTag>> fetch() async {
if (cursor == null) {
state = const AsyncValue.loading();
}
try {
final client = ref.read(apiClientProvider); final client = ref.read(apiClientProvider);
final offset = cursor == null ? 0 : int.parse(cursor);
final response = await client.get( final response = await client.get(
'/sphere/posts/tags', '/sphere/posts/tags',
queryParameters: { queryParameters: {'offset': fetchedCount, 'take': 20, 'order': 'usage'},
'offset': offset,
'take': _pageSize,
'order': 'usage',
},
); );
totalCount = int.parse(response.headers.value('X-Total') ?? '0');
final data = response.data as List; final data = response.data as List;
final tags = data.map((json) => SnPostTag.fromJson(json)).toList(); return data.map((json) => SnPostTag.fromJson(json)).toList();
final hasMore = tags.length == _pageSize;
final nextCursor = hasMore ? (offset + tags.length).toString() : null;
state = AsyncValue.data(
CursorPagingData(
items: [...(state.value?.items ?? []), ...tags],
hasMore: hasMore,
nextCursor: nextCursor,
),
);
} catch (e, stack) {
state = AsyncValue.error(e, stack);
} finally {
_isLoading = false;
}
} }
} }
@@ -142,29 +62,16 @@ class PostCategoriesListScreen extends ConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final categoriesState = ref.watch(postCategoriesNotifierProvider);
return AppScaffold( return AppScaffold(
appBar: AppBar(title: const Text('categories').tr()), appBar: AppBar(title: const Text('categories').tr()),
body: categoriesState.when( body: PaginationList(
data: (data) { provider: postCategoriesNotifierProvider,
if (data.items.isEmpty) { notifier: postCategoriesNotifierProvider.notifier,
return const Center(child: Text('No categories found'));
}
return ListView.builder(
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
itemCount: data.items.length + (data.hasMore ? 1 : 0), itemBuilder: (context, index, category) {
itemBuilder: (context, index) {
if (index >= data.items.length) {
ref
.read(postCategoriesNotifierProvider.notifier)
.fetch(cursor: data.nextCursor);
return const Center(child: CircularProgressIndicator());
}
final category = data.items[index];
return ListTile( return ListTile(
leading: const Icon(Symbols.category), leading: const Icon(Symbols.category),
contentPadding: EdgeInsets.symmetric(horizontal: 24), contentPadding: const EdgeInsets.symmetric(horizontal: 24),
trailing: const Icon(Symbols.chevron_right), trailing: const Icon(Symbols.chevron_right),
title: Text(category.categoryDisplayTitle), title: Text(category.categoryDisplayTitle),
subtitle: Text('postCount'.plural(category.usage)), subtitle: Text('postCount'.plural(category.usage)),
@@ -176,14 +83,6 @@ class PostCategoriesListScreen extends ConsumerWidget {
}, },
); );
}, },
);
},
loading: () => const Center(child: CircularProgressIndicator()),
error:
(error, stack) => ResponseErrorWidget(
error: error,
onRetry: () => ref.invalidate(postCategoriesNotifierProvider),
),
), ),
); );
} }
@@ -194,29 +93,16 @@ class PostTagsListScreen extends ConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final tagsState = ref.watch(postTagsNotifierProvider);
return AppScaffold( return AppScaffold(
appBar: AppBar(title: const Text('tags').tr()), appBar: AppBar(title: const Text('tags').tr()),
body: tagsState.when( body: PaginationList(
data: (data) { provider: postTagsNotifierProvider,
if (data.items.isEmpty) { notifier: postTagsNotifierProvider.notifier,
return const Center(child: Text('No tags found'));
}
return ListView.builder(
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
itemCount: data.items.length + (data.hasMore ? 1 : 0), itemBuilder: (context, index, tag) {
itemBuilder: (context, index) {
if (index >= data.items.length) {
ref
.read(postTagsNotifierProvider.notifier)
.fetch(cursor: data.nextCursor);
return const Center(child: CircularProgressIndicator());
}
final tag = data.items[index];
return ListTile( return ListTile(
title: Text(tag.name ?? '#${tag.slug}'), title: Text(tag.name ?? '#${tag.slug}'),
contentPadding: EdgeInsets.symmetric(horizontal: 24), contentPadding: const EdgeInsets.symmetric(horizontal: 24),
leading: const Icon(Symbols.label), leading: const Icon(Symbols.label),
trailing: const Icon(Symbols.chevron_right), trailing: const Icon(Symbols.chevron_right),
subtitle: Text('postCount'.plural(tag.usage)), subtitle: Text('postCount'.plural(tag.usage)),
@@ -228,14 +114,6 @@ class PostTagsListScreen extends ConsumerWidget {
}, },
); );
}, },
);
},
loading: () => const Center(child: CircularProgressIndicator()),
error:
(error, stack) => ResponseErrorWidget(
error: error,
onRetry: () => ref.invalidate(postTagsNotifierProvider),
),
), ),
); );
} }

View File

@@ -7,18 +7,18 @@ import 'package:island/models/post.dart';
import 'package:island/pods/network.dart'; import 'package:island/pods/network.dart';
import 'package:island/widgets/app_scaffold.dart'; import 'package:island/widgets/app_scaffold.dart';
import 'package:island/widgets/post/post_item.dart'; import 'package:island/widgets/post/post_item.dart';
import 'package:island/widgets/response.dart';
import 'package:riverpod_paging_utils/riverpod_paging_utils.dart'; import 'package:island/pods/paging.dart';
import 'package:island/widgets/paging/pagination_list.dart';
import 'package:styled_widget/styled_widget.dart'; import 'package:styled_widget/styled_widget.dart';
final postSearchNotifierProvider = StateNotifierProvider.autoDispose< final postSearchNotifierProvider =
PostSearchNotifier, AsyncNotifierProvider.autoDispose<PostSearchNotifier, List<SnPost>>(
AsyncValue<CursorPagingData<SnPost>> PostSearchNotifier.new,
>((ref) => PostSearchNotifier(ref)); );
class PostSearchNotifier class PostSearchNotifier extends AutoDisposeAsyncNotifier<List<SnPost>>
extends StateNotifier<AsyncValue<CursorPagingData<SnPost>>> { with AutoDisposeAsyncPaginationController<SnPost> {
final AutoDisposeRef ref;
static const int _pageSize = 20; static const int _pageSize = 20;
String _currentQuery = ''; String _currentQuery = '';
String? _pubName; String? _pubName;
@@ -28,12 +28,13 @@ class PostSearchNotifier
List<String>? _tags; List<String>? _tags;
bool _shuffle = false; bool _shuffle = false;
bool? _pinned; bool? _pinned;
bool _isLoading = false;
PostSearchNotifier(this.ref) : super(const AsyncValue.loading()) { @override
state = const AsyncValue.data( FutureOr<List<SnPost>> build() async {
CursorPagingData(items: [], hasMore: false, nextCursor: null), // Initial state is empty if no query/filters, or fetch if needed
); // But original logic allowed initial empty state.
// Let's replicate original logic: return empty list initially if no query.
return [];
} }
Future<void> search( Future<void> search(
@@ -46,8 +47,6 @@ class PostSearchNotifier
bool shuffle = false, bool shuffle = false,
bool? pinned, bool? pinned,
}) async { }) async {
if (_isLoading) return;
_currentQuery = query.trim(); _currentQuery = query.trim();
_pubName = pubName; _pubName = pubName;
_realm = realm; _realm = realm;
@@ -57,7 +56,6 @@ class PostSearchNotifier
_shuffle = shuffle; _shuffle = shuffle;
_pinned = pinned; _pinned = pinned;
// Allow search even with empty query if any filters are applied
final hasFilters = final hasFilters =
pubName != null || pubName != null ||
realm != null || realm != null ||
@@ -68,30 +66,23 @@ class PostSearchNotifier
pinned != null; pinned != null;
if (_currentQuery.isEmpty && !hasFilters) { if (_currentQuery.isEmpty && !hasFilters) {
state = AsyncValue.data( state = const AsyncData([]);
CursorPagingData(items: [], hasMore: false, nextCursor: null), totalCount = null;
);
return; return;
} }
await fetch(cursor: null); await refresh();
} }
Future<void> fetch({String? cursor}) async { @override
if (_isLoading) return; Future<List<SnPost>> fetch() async {
_isLoading = true;
state = const AsyncValue.loading();
try {
final client = ref.read(apiClientProvider); final client = ref.read(apiClientProvider);
final offset = cursor == null ? 0 : int.parse(cursor);
final response = await client.get( final response = await client.get(
'/sphere/posts', '/sphere/posts',
queryParameters: { queryParameters: {
'query': _currentQuery, 'query': _currentQuery,
'offset': offset, 'offset': fetchedCount,
'take': _pageSize, 'take': _pageSize,
'vector': false, 'vector': false,
if (_pubName != null) 'pub': _pubName, if (_pubName != null) 'pub': _pubName,
@@ -104,23 +95,9 @@ class PostSearchNotifier
}, },
); );
totalCount = int.parse(response.headers.value('X-Total') ?? '0');
final data = response.data as List; final data = response.data as List;
final posts = data.map((json) => SnPost.fromJson(json)).toList(); return data.map((json) => SnPost.fromJson(json)).toList();
final hasMore = posts.length == _pageSize;
final nextCursor = hasMore ? (offset + posts.length).toString() : null;
state = AsyncValue.data(
CursorPagingData(
items: posts,
hasMore: hasMore,
nextCursor: nextCursor,
),
);
} catch (e, stack) {
state = AsyncValue.error(e, stack);
} finally {
_isLoading = false;
}
} }
} }
@@ -339,24 +316,14 @@ class PostSearchScreen extends HookConsumerWidget {
), ),
), ),
), ),
searchState.when( // Use PaginationList with isSliver=true
data: (data) { PaginationList(
if (data.items.isEmpty && searchController.text.isNotEmpty) { provider: postSearchNotifierProvider,
return SliverFillRemaining( notifier: postSearchNotifierProvider.notifier,
child: Center(child: Text('noResultsFound'.tr())), isSliver: true,
); isRefreshable:
} false, // CustomScrollView handles refreshing usually, but here we don't have PullToRefresh
itemBuilder: (context, index, post) {
return SliverList(
delegate: SliverChildBuilderDelegate((context, index) {
if (index >= data.items.length) {
ref
.read(postSearchNotifierProvider.notifier)
.fetch(cursor: data.nextCursor);
return Center(child: CircularProgressIndicator());
}
final post = data.items[index];
return Center( return Center(
child: ConstrainedBox( child: ConstrainedBox(
constraints: BoxConstraints(maxWidth: 600), constraints: BoxConstraints(maxWidth: 600),
@@ -365,28 +332,17 @@ class PostSearchScreen extends HookConsumerWidget {
horizontal: 8, horizontal: 8,
vertical: 4, vertical: 4,
), ),
child: PostActionableItem( child: PostActionableItem(item: post, borderRadius: 8),
item: post,
borderRadius: 8,
), ),
), ),
),
);
}, childCount: data.items.length + (data.hasMore ? 1 : 0)),
); );
}, },
loading:
() => SliverFillRemaining(
child: Center(child: CircularProgressIndicator()),
),
error:
(error, stack) => SliverFillRemaining(
child: ResponseErrorWidget(
error: error,
onRetry:
() => ref.invalidate(postSearchNotifierProvider),
),
), ),
if (searchState.valueOrNull?.isEmpty == true &&
searchController.text.isNotEmpty &&
!searchState.isLoading)
SliverFillRemaining(
child: Center(child: Text('noResultsFound'.tr())),
), ),
], ],
); );

View File

@@ -22,14 +22,12 @@ import 'package:island/widgets/alert.dart';
import 'package:island/widgets/app_scaffold.dart'; import 'package:island/widgets/app_scaffold.dart';
import 'package:island/widgets/content/cloud_files.dart'; import 'package:island/widgets/content/cloud_files.dart';
import 'package:material_symbols_icons/symbols.dart'; import 'package:material_symbols_icons/symbols.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:island/pods/paging.dart';
import 'package:riverpod_paging_utils/riverpod_paging_utils.dart'; import 'package:island/widgets/paging/pagination_list.dart';
import 'package:styled_widget/styled_widget.dart'; import 'package:styled_widget/styled_widget.dart';
part 'realm_detail.g.dart'; final realmAppbarForegroundColorProvider = FutureProvider.autoDispose
.family<Color?, String>((ref, realmSlug) async {
@riverpod
Future<Color?> realmAppbarForegroundColor(Ref ref, String realmSlug) async {
final realm = await ref.watch(realmProvider(realmSlug).future); final realm = await ref.watch(realmProvider(realmSlug).future);
if (realm?.background == null) return null; if (realm?.background == null) return null;
final colors = await ColorExtractionService.getColorsFromImage( final colors = await ColorExtractionService.getColorsFromImage(
@@ -40,14 +38,18 @@ Future<Color?> realmAppbarForegroundColor(Ref ref, String realmSlug) async {
); );
if (colors.isEmpty) return null; if (colors.isEmpty) return null;
final dominantColor = colors.first; final dominantColor = colors.first;
return dominantColor.computeLuminance() > 0.5 ? Colors.black : Colors.white; return dominantColor.computeLuminance() > 0.5
} ? Colors.black
: Colors.white;
});
@riverpod final realmIdentityProvider = FutureProvider.autoDispose
Future<SnRealmMember?> realmIdentity(Ref ref, String realmSlug) async { .family<SnRealmMember?, String>((ref, realmSlug) async {
try { try {
final apiClient = ref.watch(apiClientProvider); final apiClient = ref.watch(apiClientProvider);
final response = await apiClient.get('/pass/realms/$realmSlug/members/me'); final response = await apiClient.get(
'/pass/realms/$realmSlug/members/me',
);
return SnRealmMember.fromJson(response.data); return SnRealmMember.fromJson(response.data);
} catch (err) { } catch (err) {
if (err is DioException && err.response?.statusCode == 404) { if (err is DioException && err.response?.statusCode == 404) {
@@ -55,14 +57,16 @@ Future<SnRealmMember?> realmIdentity(Ref ref, String realmSlug) async {
} }
rethrow; rethrow;
} }
} });
@riverpod final realmChatRoomsProvider = FutureProvider.autoDispose
Future<List<SnChatRoom>> realmChatRooms(Ref ref, String realmSlug) async { .family<List<SnChatRoom>, String>((ref, realmSlug) async {
final apiClient = ref.watch(apiClientProvider); final apiClient = ref.watch(apiClientProvider);
final response = await apiClient.get('/sphere/realms/$realmSlug/chat'); final response = await apiClient.get('/sphere/realms/$realmSlug/chat');
return (response.data as List).map((e) => SnChatRoom.fromJson(e)).toList(); return (response.data as List)
} .map((e) => SnChatRoom.fromJson(e))
.toList();
});
class RealmDetailScreen extends HookConsumerWidget { class RealmDetailScreen extends HookConsumerWidget {
final String slug; final String slug;
@@ -520,49 +524,32 @@ class _RealmActionMenu extends HookConsumerWidget {
} }
} }
@riverpod final realmMemberListNotifierProvider = AsyncNotifierProvider.autoDispose
class RealmMemberListNotifier extends _$RealmMemberListNotifier .family<RealmMemberListNotifier, List<SnRealmMember>, String>(
with CursorPagingNotifierMixin<SnRealmMember> { RealmMemberListNotifier.new,
);
class RealmMemberListNotifier
extends AutoDisposeFamilyAsyncNotifier<List<SnRealmMember>, String>
with FamilyAsyncPaginationController<SnRealmMember, String> {
static const int _pageSize = 20; static const int _pageSize = 20;
ValueNotifier<int> totalCount = ValueNotifier(0);
@override @override
Future<CursorPagingData<SnRealmMember>> build(String realmSlug) async { Future<List<SnRealmMember>> fetch() async {
totalCount.value = 0;
return fetch();
}
@override
Future<CursorPagingData<SnRealmMember>> fetch({String? cursor}) async {
final apiClient = ref.read(apiClientProvider); final apiClient = ref.read(apiClientProvider);
final offset = cursor != null ? int.parse(cursor) : 0;
final response = await apiClient.get( final response = await apiClient.get(
'/pass/realms/$realmSlug/members', '/pass/realms/$arg/members',
queryParameters: { queryParameters: {
'offset': offset, 'offset': fetchedCount,
'take': _pageSize, 'take': _pageSize,
'withStatus': true, 'withStatus': true,
}, },
); );
final total = int.parse(response.headers.value('X-Total') ?? '0'); totalCount = int.parse(response.headers.value('X-Total') ?? '0');
totalCount.value = total;
final List<dynamic> data = response.data; final List<dynamic> data = response.data;
final members = data.map((e) => SnRealmMember.fromJson(e)).toList(); return data.map((e) => SnRealmMember.fromJson(e)).toList();
final hasMore = offset + members.length < total;
final nextCursor = hasMore ? (offset + members.length).toString() : null;
return CursorPagingData(
items: members,
hasMore: hasMore,
nextCursor: nextCursor,
);
}
void dispose() {
totalCount.dispose();
} }
} }
@@ -574,11 +561,10 @@ class _RealmMemberListSheet extends HookConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final realmIdentity = ref.watch(realmIdentityProvider(realmSlug)); final realmIdentity = ref.watch(realmIdentityProvider(realmSlug));
final memberListProvider = realmMemberListNotifierProvider(realmSlug); final memberListProvider = realmMemberListNotifierProvider(realmSlug);
final memberListNotifier = ref.watch(memberListProvider.notifier); // memberListNotifier is not watched here to prevent unnecessary rebuilds of this widget
// when we only need it for passing to PaginationList as a Refreshable
useEffect(() { // However, we used useEffect to dispose it, but AutoDispose handles it.
return memberListNotifier.dispose; // So we remove the useEffect and the watch.
}, []);
Future<void> invitePerson() async { Future<void> invitePerson() async {
final result = await showModalBottomSheet( final result = await showModalBottomSheet(
@@ -606,17 +592,19 @@ class _RealmMemberListSheet extends HookConsumerWidget {
padding: EdgeInsets.only(top: 16, left: 20, right: 16, bottom: 12), padding: EdgeInsets.only(top: 16, left: 20, right: 16, bottom: 12),
child: Row( child: Row(
children: [ children: [
ListenableBuilder( Consumer(
listenable: memberListNotifier.totalCount, builder: (context, ref, _) {
builder: // effective watch to rebuild when data changes (and totalCount updates)
(context, _) => Text( ref.watch(memberListProvider);
'members'.plural(memberListNotifier.totalCount.value), final notifier = ref.read(memberListProvider.notifier);
key: ValueKey(memberListNotifier), return Text(
'members'.plural(notifier.totalCount ?? 0),
style: Theme.of(context).textTheme.headlineSmall?.copyWith( style: Theme.of(context).textTheme.headlineSmall?.copyWith(
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
letterSpacing: -0.5, letterSpacing: -0.5,
), ),
), );
},
), ),
const Spacer(), const Spacer(),
IconButton( IconButton(
@@ -643,19 +631,10 @@ class _RealmMemberListSheet extends HookConsumerWidget {
Widget buildMemberListContent() { Widget buildMemberListContent() {
return Expanded( return Expanded(
child: PagingHelperView( child: PaginationList(
provider: memberListProvider, provider: memberListProvider,
futureRefreshable: memberListProvider.future, notifier: memberListProvider.notifier,
notifierRefreshable: memberListProvider.notifier, itemBuilder: (context, index, member) {
contentBuilder: (data, widgetCount, endItemView) {
return ListView.builder(
itemCount: widgetCount,
itemBuilder: (context, index) {
if (index == data.items.length) {
return endItemView;
}
final member = data.items[index];
return ListTile( return ListTile(
contentPadding: EdgeInsets.only(left: 16, right: 12), contentPadding: EdgeInsets.only(left: 16, right: 12),
leading: AccountPfcGestureDetector( leading: AccountPfcGestureDetector(
@@ -742,8 +721,6 @@ class _RealmMemberListSheet extends HookConsumerWidget {
), ),
); );
}, },
);
},
), ),
); );
} }

View File

@@ -1,552 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'realm_detail.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$realmAppbarForegroundColorHash() =>
r'8131c047a984318a4cc3fbb5daa5ef0ce44dfae5';
/// Copied from Dart SDK
class _SystemHash {
_SystemHash._();
static int combine(int hash, int value) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + value);
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
return hash ^ (hash >> 6);
}
static int finish(int hash) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
// ignore: parameter_assignments
hash = hash ^ (hash >> 11);
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
}
}
/// See also [realmAppbarForegroundColor].
@ProviderFor(realmAppbarForegroundColor)
const realmAppbarForegroundColorProvider = RealmAppbarForegroundColorFamily();
/// See also [realmAppbarForegroundColor].
class RealmAppbarForegroundColorFamily extends Family<AsyncValue<Color?>> {
/// See also [realmAppbarForegroundColor].
const RealmAppbarForegroundColorFamily();
/// See also [realmAppbarForegroundColor].
RealmAppbarForegroundColorProvider call(String realmSlug) {
return RealmAppbarForegroundColorProvider(realmSlug);
}
@override
RealmAppbarForegroundColorProvider getProviderOverride(
covariant RealmAppbarForegroundColorProvider provider,
) {
return call(provider.realmSlug);
}
static const Iterable<ProviderOrFamily>? _dependencies = null;
@override
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
@override
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
_allTransitiveDependencies;
@override
String? get name => r'realmAppbarForegroundColorProvider';
}
/// See also [realmAppbarForegroundColor].
class RealmAppbarForegroundColorProvider
extends AutoDisposeFutureProvider<Color?> {
/// See also [realmAppbarForegroundColor].
RealmAppbarForegroundColorProvider(String realmSlug)
: this._internal(
(ref) => realmAppbarForegroundColor(
ref as RealmAppbarForegroundColorRef,
realmSlug,
),
from: realmAppbarForegroundColorProvider,
name: r'realmAppbarForegroundColorProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$realmAppbarForegroundColorHash,
dependencies: RealmAppbarForegroundColorFamily._dependencies,
allTransitiveDependencies:
RealmAppbarForegroundColorFamily._allTransitiveDependencies,
realmSlug: realmSlug,
);
RealmAppbarForegroundColorProvider._internal(
super._createNotifier, {
required super.name,
required super.dependencies,
required super.allTransitiveDependencies,
required super.debugGetCreateSourceHash,
required super.from,
required this.realmSlug,
}) : super.internal();
final String realmSlug;
@override
Override overrideWith(
FutureOr<Color?> Function(RealmAppbarForegroundColorRef provider) create,
) {
return ProviderOverride(
origin: this,
override: RealmAppbarForegroundColorProvider._internal(
(ref) => create(ref as RealmAppbarForegroundColorRef),
from: from,
name: null,
dependencies: null,
allTransitiveDependencies: null,
debugGetCreateSourceHash: null,
realmSlug: realmSlug,
),
);
}
@override
AutoDisposeFutureProviderElement<Color?> createElement() {
return _RealmAppbarForegroundColorProviderElement(this);
}
@override
bool operator ==(Object other) {
return other is RealmAppbarForegroundColorProvider &&
other.realmSlug == realmSlug;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, realmSlug.hashCode);
return _SystemHash.finish(hash);
}
}
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
mixin RealmAppbarForegroundColorRef on AutoDisposeFutureProviderRef<Color?> {
/// The parameter `realmSlug` of this provider.
String get realmSlug;
}
class _RealmAppbarForegroundColorProviderElement
extends AutoDisposeFutureProviderElement<Color?>
with RealmAppbarForegroundColorRef {
_RealmAppbarForegroundColorProviderElement(super.provider);
@override
String get realmSlug =>
(origin as RealmAppbarForegroundColorProvider).realmSlug;
}
String _$realmIdentityHash() => r'd5a3ecc6eeec291cebbfc9a45d8aac7195366381';
/// See also [realmIdentity].
@ProviderFor(realmIdentity)
const realmIdentityProvider = RealmIdentityFamily();
/// See also [realmIdentity].
class RealmIdentityFamily extends Family<AsyncValue<SnRealmMember?>> {
/// See also [realmIdentity].
const RealmIdentityFamily();
/// See also [realmIdentity].
RealmIdentityProvider call(String realmSlug) {
return RealmIdentityProvider(realmSlug);
}
@override
RealmIdentityProvider getProviderOverride(
covariant RealmIdentityProvider provider,
) {
return call(provider.realmSlug);
}
static const Iterable<ProviderOrFamily>? _dependencies = null;
@override
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
@override
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
_allTransitiveDependencies;
@override
String? get name => r'realmIdentityProvider';
}
/// See also [realmIdentity].
class RealmIdentityProvider extends AutoDisposeFutureProvider<SnRealmMember?> {
/// See also [realmIdentity].
RealmIdentityProvider(String realmSlug)
: this._internal(
(ref) => realmIdentity(ref as RealmIdentityRef, realmSlug),
from: realmIdentityProvider,
name: r'realmIdentityProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$realmIdentityHash,
dependencies: RealmIdentityFamily._dependencies,
allTransitiveDependencies:
RealmIdentityFamily._allTransitiveDependencies,
realmSlug: realmSlug,
);
RealmIdentityProvider._internal(
super._createNotifier, {
required super.name,
required super.dependencies,
required super.allTransitiveDependencies,
required super.debugGetCreateSourceHash,
required super.from,
required this.realmSlug,
}) : super.internal();
final String realmSlug;
@override
Override overrideWith(
FutureOr<SnRealmMember?> Function(RealmIdentityRef provider) create,
) {
return ProviderOverride(
origin: this,
override: RealmIdentityProvider._internal(
(ref) => create(ref as RealmIdentityRef),
from: from,
name: null,
dependencies: null,
allTransitiveDependencies: null,
debugGetCreateSourceHash: null,
realmSlug: realmSlug,
),
);
}
@override
AutoDisposeFutureProviderElement<SnRealmMember?> createElement() {
return _RealmIdentityProviderElement(this);
}
@override
bool operator ==(Object other) {
return other is RealmIdentityProvider && other.realmSlug == realmSlug;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, realmSlug.hashCode);
return _SystemHash.finish(hash);
}
}
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
mixin RealmIdentityRef on AutoDisposeFutureProviderRef<SnRealmMember?> {
/// The parameter `realmSlug` of this provider.
String get realmSlug;
}
class _RealmIdentityProviderElement
extends AutoDisposeFutureProviderElement<SnRealmMember?>
with RealmIdentityRef {
_RealmIdentityProviderElement(super.provider);
@override
String get realmSlug => (origin as RealmIdentityProvider).realmSlug;
}
String _$realmChatRoomsHash() => r'5f199906fb287b109e2a2d2a81dcb6675bdcb816';
/// See also [realmChatRooms].
@ProviderFor(realmChatRooms)
const realmChatRoomsProvider = RealmChatRoomsFamily();
/// See also [realmChatRooms].
class RealmChatRoomsFamily extends Family<AsyncValue<List<SnChatRoom>>> {
/// See also [realmChatRooms].
const RealmChatRoomsFamily();
/// See also [realmChatRooms].
RealmChatRoomsProvider call(String realmSlug) {
return RealmChatRoomsProvider(realmSlug);
}
@override
RealmChatRoomsProvider getProviderOverride(
covariant RealmChatRoomsProvider provider,
) {
return call(provider.realmSlug);
}
static const Iterable<ProviderOrFamily>? _dependencies = null;
@override
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
@override
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
_allTransitiveDependencies;
@override
String? get name => r'realmChatRoomsProvider';
}
/// See also [realmChatRooms].
class RealmChatRoomsProvider
extends AutoDisposeFutureProvider<List<SnChatRoom>> {
/// See also [realmChatRooms].
RealmChatRoomsProvider(String realmSlug)
: this._internal(
(ref) => realmChatRooms(ref as RealmChatRoomsRef, realmSlug),
from: realmChatRoomsProvider,
name: r'realmChatRoomsProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$realmChatRoomsHash,
dependencies: RealmChatRoomsFamily._dependencies,
allTransitiveDependencies:
RealmChatRoomsFamily._allTransitiveDependencies,
realmSlug: realmSlug,
);
RealmChatRoomsProvider._internal(
super._createNotifier, {
required super.name,
required super.dependencies,
required super.allTransitiveDependencies,
required super.debugGetCreateSourceHash,
required super.from,
required this.realmSlug,
}) : super.internal();
final String realmSlug;
@override
Override overrideWith(
FutureOr<List<SnChatRoom>> Function(RealmChatRoomsRef provider) create,
) {
return ProviderOverride(
origin: this,
override: RealmChatRoomsProvider._internal(
(ref) => create(ref as RealmChatRoomsRef),
from: from,
name: null,
dependencies: null,
allTransitiveDependencies: null,
debugGetCreateSourceHash: null,
realmSlug: realmSlug,
),
);
}
@override
AutoDisposeFutureProviderElement<List<SnChatRoom>> createElement() {
return _RealmChatRoomsProviderElement(this);
}
@override
bool operator ==(Object other) {
return other is RealmChatRoomsProvider && other.realmSlug == realmSlug;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, realmSlug.hashCode);
return _SystemHash.finish(hash);
}
}
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
mixin RealmChatRoomsRef on AutoDisposeFutureProviderRef<List<SnChatRoom>> {
/// The parameter `realmSlug` of this provider.
String get realmSlug;
}
class _RealmChatRoomsProviderElement
extends AutoDisposeFutureProviderElement<List<SnChatRoom>>
with RealmChatRoomsRef {
_RealmChatRoomsProviderElement(super.provider);
@override
String get realmSlug => (origin as RealmChatRoomsProvider).realmSlug;
}
String _$realmMemberListNotifierHash() =>
r'ab38c550c43cbf93d4c3e92e6658d76f40252c1f';
abstract class _$RealmMemberListNotifier
extends BuildlessAutoDisposeAsyncNotifier<CursorPagingData<SnRealmMember>> {
late final String realmSlug;
FutureOr<CursorPagingData<SnRealmMember>> build(String realmSlug);
}
/// See also [RealmMemberListNotifier].
@ProviderFor(RealmMemberListNotifier)
const realmMemberListNotifierProvider = RealmMemberListNotifierFamily();
/// See also [RealmMemberListNotifier].
class RealmMemberListNotifierFamily
extends Family<AsyncValue<CursorPagingData<SnRealmMember>>> {
/// See also [RealmMemberListNotifier].
const RealmMemberListNotifierFamily();
/// See also [RealmMemberListNotifier].
RealmMemberListNotifierProvider call(String realmSlug) {
return RealmMemberListNotifierProvider(realmSlug);
}
@override
RealmMemberListNotifierProvider getProviderOverride(
covariant RealmMemberListNotifierProvider provider,
) {
return call(provider.realmSlug);
}
static const Iterable<ProviderOrFamily>? _dependencies = null;
@override
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
@override
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
_allTransitiveDependencies;
@override
String? get name => r'realmMemberListNotifierProvider';
}
/// See also [RealmMemberListNotifier].
class RealmMemberListNotifierProvider
extends
AutoDisposeAsyncNotifierProviderImpl<
RealmMemberListNotifier,
CursorPagingData<SnRealmMember>
> {
/// See also [RealmMemberListNotifier].
RealmMemberListNotifierProvider(String realmSlug)
: this._internal(
() => RealmMemberListNotifier()..realmSlug = realmSlug,
from: realmMemberListNotifierProvider,
name: r'realmMemberListNotifierProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$realmMemberListNotifierHash,
dependencies: RealmMemberListNotifierFamily._dependencies,
allTransitiveDependencies:
RealmMemberListNotifierFamily._allTransitiveDependencies,
realmSlug: realmSlug,
);
RealmMemberListNotifierProvider._internal(
super._createNotifier, {
required super.name,
required super.dependencies,
required super.allTransitiveDependencies,
required super.debugGetCreateSourceHash,
required super.from,
required this.realmSlug,
}) : super.internal();
final String realmSlug;
@override
FutureOr<CursorPagingData<SnRealmMember>> runNotifierBuild(
covariant RealmMemberListNotifier notifier,
) {
return notifier.build(realmSlug);
}
@override
Override overrideWith(RealmMemberListNotifier Function() create) {
return ProviderOverride(
origin: this,
override: RealmMemberListNotifierProvider._internal(
() => create()..realmSlug = realmSlug,
from: from,
name: null,
dependencies: null,
allTransitiveDependencies: null,
debugGetCreateSourceHash: null,
realmSlug: realmSlug,
),
);
}
@override
AutoDisposeAsyncNotifierProviderElement<
RealmMemberListNotifier,
CursorPagingData<SnRealmMember>
>
createElement() {
return _RealmMemberListNotifierProviderElement(this);
}
@override
bool operator ==(Object other) {
return other is RealmMemberListNotifierProvider &&
other.realmSlug == realmSlug;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, realmSlug.hashCode);
return _SystemHash.finish(hash);
}
}
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
mixin RealmMemberListNotifierRef
on AutoDisposeAsyncNotifierProviderRef<CursorPagingData<SnRealmMember>> {
/// The parameter `realmSlug` of this provider.
String get realmSlug;
}
class _RealmMemberListNotifierProviderElement
extends
AutoDisposeAsyncNotifierProviderElement<
RealmMemberListNotifier,
CursorPagingData<SnRealmMember>
>
with RealmMemberListNotifierRef {
_RealmMemberListNotifierProviderElement(super.provider);
@override
String get realmSlug => (origin as RealmMemberListNotifierProvider).realmSlug;
}
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package

View File

@@ -5,29 +5,28 @@ import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/models/file.dart'; import 'package:island/models/file.dart';
import 'package:island/pods/network.dart'; import 'package:island/pods/network.dart';
import 'package:island/pods/paging.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/paging/pagination_list.dart';
import 'package:material_symbols_icons/symbols.dart'; import 'package:material_symbols_icons/symbols.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
import 'package:styled_widget/styled_widget.dart'; import 'package:styled_widget/styled_widget.dart';
import 'package:url_launcher/url_launcher_string.dart'; import 'package:url_launcher/url_launcher_string.dart';
part 'chat_link_attachments.g.dart'; final chatCloudFileListNotifierProvider = AsyncNotifierProvider.autoDispose<
ChatCloudFileListNotifier,
List<SnCloudFile>
>(ChatCloudFileListNotifier.new);
@riverpod class ChatCloudFileListNotifier
class ChatCloudFileListNotifier extends _$ChatCloudFileListNotifier extends AutoDisposeAsyncNotifier<List<SnCloudFile>>
with CursorPagingNotifierMixin<SnCloudFile> { with AutoDisposeAsyncPaginationController<SnCloudFile> {
@override @override
Future<CursorPagingData<SnCloudFile>> build() => fetch(cursor: null); Future<List<SnCloudFile>> fetch() async {
@override
Future<CursorPagingData<SnCloudFile>> fetch({required String? cursor}) async {
final client = ref.read(apiClientProvider); final client = ref.read(apiClientProvider);
final offset = cursor == null ? 0 : int.parse(cursor);
final take = 20; final take = 20;
final queryParameters = {'offset': offset, 'take': take}; final queryParameters = {'offset': fetchedCount, 'take': take};
final response = await client.get( final response = await client.get(
'/drive/files/me', '/drive/files/me',
@@ -38,16 +37,9 @@ class ChatCloudFileListNotifier extends _$ChatCloudFileListNotifier
(response.data as List) (response.data as List)
.map((e) => SnCloudFile.fromJson(e as Map<String, dynamic>)) .map((e) => SnCloudFile.fromJson(e as Map<String, dynamic>))
.toList(); .toList();
final total = int.parse(response.headers.value('X-Total') ?? '0'); totalCount = int.parse(response.headers.value('X-Total') ?? '0');
final hasMore = offset + items.length < total; return items;
final nextCursor = hasMore ? (offset + items.length).toString() : null;
return CursorPagingData(
items: items,
hasMore: hasMore,
nextCursor: nextCursor,
);
} }
} }
@@ -77,23 +69,12 @@ class ChatLinkAttachment extends HookConsumerWidget {
Expanded( Expanded(
child: TabBarView( child: TabBarView(
children: [ children: [
PagingHelperView( PaginationList(
provider: chatCloudFileListNotifierProvider, provider: chatCloudFileListNotifierProvider,
futureRefreshable: chatCloudFileListNotifierProvider.future, notifier: chatCloudFileListNotifierProvider.notifier,
notifierRefreshable:
chatCloudFileListNotifierProvider.notifier,
contentBuilder:
(data, widgetCount, endItemView) => ListView.builder(
padding: EdgeInsets.only(top: 8), padding: EdgeInsets.only(top: 8),
itemCount: widgetCount, itemBuilder: (context, index, item) {
itemBuilder: (context, index) { final itemType = item.mimeType?.split('/').firstOrNull;
if (index == widgetCount - 1) {
return endItemView;
}
final item = data.items[index];
final itemType =
item.mimeType?.split('/').firstOrNull;
return ListTile( return ListTile(
leading: ClipRRect( leading: ClipRRect(
borderRadius: const BorderRadius.all( borderRadius: const BorderRadius.all(
@@ -132,7 +113,6 @@ class ChatLinkAttachment extends HookConsumerWidget {
); );
}, },
), ),
),
SingleChildScrollView( SingleChildScrollView(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,

View File

@@ -1,31 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'chat_link_attachments.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$chatCloudFileListNotifierHash() =>
r'5da3929229fe00212530f63bd19ae4cd829176f5';
/// See also [ChatCloudFileListNotifier].
@ProviderFor(ChatCloudFileListNotifier)
final chatCloudFileListNotifierProvider = AutoDisposeAsyncNotifierProvider<
ChatCloudFileListNotifier,
CursorPagingData<SnCloudFile>
>.internal(
ChatCloudFileListNotifier.new,
name: r'chatCloudFileListNotifierProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$chatCloudFileListNotifierHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$ChatCloudFileListNotifier =
AutoDisposeAsyncNotifier<CursorPagingData<SnCloudFile>>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package

View File

@@ -4,55 +4,40 @@ import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/models/poll.dart'; import 'package:island/models/poll.dart';
import 'package:island/pods/network.dart'; import 'package:island/pods/network.dart';
import 'package:island/pods/paging.dart';
import 'package:island/screens/creators/poll/poll_list.dart'; import 'package:island/screens/creators/poll/poll_list.dart';
import 'package:island/services/time.dart'; import 'package:island/services/time.dart';
import 'package:island/widgets/account/account_pfc.dart'; import 'package:island/widgets/account/account_pfc.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/paging/pagination_list.dart';
import 'package:island/widgets/poll/poll_stats_widget.dart'; import 'package:island/widgets/poll/poll_stats_widget.dart';
import 'package:island/widgets/response.dart'; import 'package:island/widgets/response.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
import 'package:styled_widget/styled_widget.dart'; import 'package:styled_widget/styled_widget.dart';
part 'poll_feedback.g.dart'; final pollFeedbackNotifierProvider = AsyncNotifierProvider.autoDispose
.family<PollFeedbackNotifier, List<SnPollAnswer>, String>(
PollFeedbackNotifier.new,
);
@riverpod class PollFeedbackNotifier
class PollFeedbackNotifier extends _$PollFeedbackNotifier extends AutoDisposeFamilyAsyncNotifier<List<SnPollAnswer>, String>
with CursorPagingNotifierMixin<SnPollAnswer> { with FamilyAsyncPaginationController<SnPollAnswer, String> {
static const int _pageSize = 20; static const int _pageSize = 20;
@override @override
Future<CursorPagingData<SnPollAnswer>> build(String id) { Future<List<SnPollAnswer>> fetch() async {
// immediately load first page
return fetch(cursor: null);
}
@override
Future<CursorPagingData<SnPollAnswer>> fetch({
required String? cursor,
}) async {
final client = ref.read(apiClientProvider); final client = ref.read(apiClientProvider);
final offset = cursor == null ? 0 : int.parse(cursor);
final queryParams = {'offset': offset, 'take': _pageSize}; final queryParams = {'offset': fetchedCount, 'take': _pageSize};
final response = await client.get( final response = await client.get(
'/sphere/polls/$id/feedback', '/sphere/polls/$arg/feedback',
queryParameters: queryParams, queryParameters: queryParams,
); );
final total = int.parse(response.headers.value('X-Total') ?? '0'); totalCount = int.parse(response.headers.value('X-Total') ?? '0');
final List<dynamic> data = response.data; final List<dynamic> data = response.data;
final items = data.map((json) => SnPollAnswer.fromJson(json)).toList(); return data.map((json) => SnPollAnswer.fromJson(json)).toList();
final hasMore = offset + items.length < total;
final nextCursor = hasMore ? (offset + items.length).toString() : null;
return CursorPagingData(
items: items,
hasMore: hasMore,
nextCursor: nextCursor,
);
} }
} }
@@ -64,6 +49,7 @@ class PollFeedbackSheet extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final poll = ref.watch(pollWithStatsProvider(pollId)); final poll = ref.watch(pollWithStatsProvider(pollId));
final provider = pollFeedbackNotifierProvider(pollId);
return SheetScaffold( return SheetScaffold(
titleText: title ?? 'Poll feedback', titleText: title ?? 'Poll feedback',
@@ -74,27 +60,20 @@ class PollFeedbackSheet extends HookConsumerWidget {
SliverToBoxAdapter(child: _PollHeader(poll: data)), SliverToBoxAdapter(child: _PollHeader(poll: data)),
SliverToBoxAdapter(child: const Divider(height: 1)), SliverToBoxAdapter(child: const Divider(height: 1)),
SliverGap(4), SliverGap(4),
PagingHelperSliverView( PaginationList(
provider: pollFeedbackNotifierProvider(pollId), provider: provider,
futureRefreshable: notifier: provider.notifier,
pollFeedbackNotifierProvider(pollId).future, isSliver: true,
notifierRefreshable: itemBuilder: (context, index, answer) {
pollFeedbackNotifierProvider(pollId).notifier, return Column(
contentBuilder: children: [
(val, widgetCount, endItemView) => SliverList.separated( _PollAnswerTile(answer: answer, poll: data),
itemCount: widgetCount, if (index <
itemBuilder: (context, index) { (ref.read(provider).valueOrNull?.length ?? 0) - 1)
if (index == widgetCount - 1) {
// Provided by PagingHelperView to indicate end/loading
return endItemView;
}
final answer = val.items[index];
return _PollAnswerTile(answer: answer, poll: data);
},
separatorBuilder:
(context, index) =>
const Divider(height: 1).padding(vertical: 4), const Divider(height: 1).padding(vertical: 4),
), ],
);
},
), ),
SliverGap(4 + MediaQuery.of(context).padding.bottom), SliverGap(4 + MediaQuery.of(context).padding.bottom),
], ],

View File

@@ -1,180 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'poll_feedback.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$pollFeedbackNotifierHash() =>
r'1bf3925b5b751cfd1a9abafb75274f1e95e7f27e';
/// Copied from Dart SDK
class _SystemHash {
_SystemHash._();
static int combine(int hash, int value) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + value);
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
return hash ^ (hash >> 6);
}
static int finish(int hash) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
// ignore: parameter_assignments
hash = hash ^ (hash >> 11);
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
}
}
abstract class _$PollFeedbackNotifier
extends BuildlessAutoDisposeAsyncNotifier<CursorPagingData<SnPollAnswer>> {
late final String id;
FutureOr<CursorPagingData<SnPollAnswer>> build(String id);
}
/// See also [PollFeedbackNotifier].
@ProviderFor(PollFeedbackNotifier)
const pollFeedbackNotifierProvider = PollFeedbackNotifierFamily();
/// See also [PollFeedbackNotifier].
class PollFeedbackNotifierFamily
extends Family<AsyncValue<CursorPagingData<SnPollAnswer>>> {
/// See also [PollFeedbackNotifier].
const PollFeedbackNotifierFamily();
/// See also [PollFeedbackNotifier].
PollFeedbackNotifierProvider call(String id) {
return PollFeedbackNotifierProvider(id);
}
@override
PollFeedbackNotifierProvider getProviderOverride(
covariant PollFeedbackNotifierProvider provider,
) {
return call(provider.id);
}
static const Iterable<ProviderOrFamily>? _dependencies = null;
@override
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
@override
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
_allTransitiveDependencies;
@override
String? get name => r'pollFeedbackNotifierProvider';
}
/// See also [PollFeedbackNotifier].
class PollFeedbackNotifierProvider
extends
AutoDisposeAsyncNotifierProviderImpl<
PollFeedbackNotifier,
CursorPagingData<SnPollAnswer>
> {
/// See also [PollFeedbackNotifier].
PollFeedbackNotifierProvider(String id)
: this._internal(
() => PollFeedbackNotifier()..id = id,
from: pollFeedbackNotifierProvider,
name: r'pollFeedbackNotifierProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$pollFeedbackNotifierHash,
dependencies: PollFeedbackNotifierFamily._dependencies,
allTransitiveDependencies:
PollFeedbackNotifierFamily._allTransitiveDependencies,
id: id,
);
PollFeedbackNotifierProvider._internal(
super._createNotifier, {
required super.name,
required super.dependencies,
required super.allTransitiveDependencies,
required super.debugGetCreateSourceHash,
required super.from,
required this.id,
}) : super.internal();
final String id;
@override
FutureOr<CursorPagingData<SnPollAnswer>> runNotifierBuild(
covariant PollFeedbackNotifier notifier,
) {
return notifier.build(id);
}
@override
Override overrideWith(PollFeedbackNotifier Function() create) {
return ProviderOverride(
origin: this,
override: PollFeedbackNotifierProvider._internal(
() => create()..id = id,
from: from,
name: null,
dependencies: null,
allTransitiveDependencies: null,
debugGetCreateSourceHash: null,
id: id,
),
);
}
@override
AutoDisposeAsyncNotifierProviderElement<
PollFeedbackNotifier,
CursorPagingData<SnPollAnswer>
>
createElement() {
return _PollFeedbackNotifierProviderElement(this);
}
@override
bool operator ==(Object other) {
return other is PollFeedbackNotifierProvider && other.id == id;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, id.hashCode);
return _SystemHash.finish(hash);
}
}
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
mixin PollFeedbackNotifierRef
on AutoDisposeAsyncNotifierProviderRef<CursorPagingData<SnPollAnswer>> {
/// The parameter `id` of this provider.
String get id;
}
class _PollFeedbackNotifierProviderElement
extends
AutoDisposeAsyncNotifierProviderElement<
PollFeedbackNotifier,
CursorPagingData<SnPollAnswer>
>
with PollFeedbackNotifierRef {
_PollFeedbackNotifierProviderElement(super.provider);
@override
String get id => (origin as PollFeedbackNotifierProvider).id;
}
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package

View File

@@ -5,49 +5,38 @@ import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/models/file.dart'; import 'package:island/models/file.dart';
import 'package:island/pods/network.dart'; import 'package:island/pods/network.dart';
import 'package:island/pods/paging.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/paging/pagination_list.dart';
import 'package:material_symbols_icons/symbols.dart'; import 'package:material_symbols_icons/symbols.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
import 'package:styled_widget/styled_widget.dart'; import 'package:styled_widget/styled_widget.dart';
import 'package:url_launcher/url_launcher_string.dart'; import 'package:url_launcher/url_launcher_string.dart';
part 'compose_link_attachments.g.dart'; final cloudFileListNotifierProvider =
AsyncNotifierProvider.autoDispose<CloudFileListNotifier, List<SnCloudFile>>(
CloudFileListNotifier.new,
);
@riverpod class CloudFileListNotifier extends AutoDisposeAsyncNotifier<List<SnCloudFile>>
class CloudFileListNotifier extends _$CloudFileListNotifier with AutoDisposeAsyncPaginationController<SnCloudFile> {
with CursorPagingNotifierMixin<SnCloudFile> {
@override @override
Future<CursorPagingData<SnCloudFile>> build() => fetch(cursor: null); Future<List<SnCloudFile>> fetch() async {
@override
Future<CursorPagingData<SnCloudFile>> fetch({required String? cursor}) async {
final client = ref.read(apiClientProvider); final client = ref.read(apiClientProvider);
final offset = cursor == null ? 0 : int.parse(cursor);
final take = 20; final take = 20;
final queryParameters = {'offset': offset, 'take': take}; final queryParameters = {'offset': fetchedCount, 'take': take};
final response = await client.get( final response = await client.get(
'/drive/files/me', '/drive/files/me',
queryParameters: queryParameters, queryParameters: queryParameters,
); );
final List<SnCloudFile> items = totalCount = int.parse(response.headers.value('X-Total') ?? '0');
(response.data as List) final List<dynamic> data = response.data;
return data
.map((e) => SnCloudFile.fromJson(e as Map<String, dynamic>)) .map((e) => SnCloudFile.fromJson(e as Map<String, dynamic>))
.toList(); .toList();
final total = int.parse(response.headers.value('X-Total') ?? '0');
final hasMore = offset + items.length < total;
final nextCursor = hasMore ? (offset + items.length).toString() : null;
return CursorPagingData(
items: items,
hasMore: hasMore,
nextCursor: nextCursor,
);
} }
} }
@@ -58,6 +47,7 @@ class ComposeLinkAttachment extends HookConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final idController = useTextEditingController(); final idController = useTextEditingController();
final errorMessage = useState<String?>(null); final errorMessage = useState<String?>(null);
final provider = cloudFileListNotifierProvider;
return SheetScaffold( return SheetScaffold(
heightFactor: 0.6, heightFactor: 0.6,
@@ -77,22 +67,12 @@ class ComposeLinkAttachment extends HookConsumerWidget {
Expanded( Expanded(
child: TabBarView( child: TabBarView(
children: [ children: [
PagingHelperView( PaginationList(
provider: cloudFileListNotifierProvider,
futureRefreshable: cloudFileListNotifierProvider.future,
notifierRefreshable: cloudFileListNotifierProvider.notifier,
contentBuilder:
(data, widgetCount, endItemView) => ListView.builder(
padding: EdgeInsets.only(top: 8), padding: EdgeInsets.only(top: 8),
itemCount: widgetCount, provider: provider,
itemBuilder: (context, index) { notifier: provider.notifier,
if (index == widgetCount - 1) { itemBuilder: (context, index, item) {
return endItemView; final itemType = item.mimeType?.split('/').firstOrNull;
}
final item = data.items[index];
final itemType =
item.mimeType?.split('/').firstOrNull;
return ListTile( return ListTile(
leading: ClipRRect( leading: ClipRRect(
borderRadius: const BorderRadius.all( borderRadius: const BorderRadius.all(
@@ -131,7 +111,6 @@ class ComposeLinkAttachment extends HookConsumerWidget {
); );
}, },
), ),
),
SingleChildScrollView( SingleChildScrollView(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,

View File

@@ -1,31 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'compose_link_attachments.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$cloudFileListNotifierHash() =>
r'e2c8a076a9e635c7b43a87d00f78775427ba6334';
/// See also [CloudFileListNotifier].
@ProviderFor(CloudFileListNotifier)
final cloudFileListNotifierProvider = AutoDisposeAsyncNotifierProvider<
CloudFileListNotifier,
CursorPagingData<SnCloudFile>
>.internal(
CloudFileListNotifier.new,
name: r'cloudFileListNotifierProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$cloudFileListNotifierHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$CloudFileListNotifier =
AutoDisposeAsyncNotifier<CursorPagingData<SnCloudFile>>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package

View File

@@ -2,45 +2,33 @@ import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/models/post.dart'; import 'package:island/models/post.dart';
import 'package:island/pods/network.dart'; import 'package:island/pods/network.dart';
import 'package:island/pods/paging.dart';
import 'package:island/widgets/content/sheet.dart'; import 'package:island/widgets/content/sheet.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:island/widgets/paging/pagination_list.dart';
import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
part 'post_award_history_sheet.g.dart'; final postAwardListNotifierProvider = AsyncNotifierProvider.autoDispose
.family<PostAwardListNotifier, List<SnPostAward>, String>(
PostAwardListNotifier.new,
);
@riverpod class PostAwardListNotifier
class PostAwardListNotifier extends _$PostAwardListNotifier extends AutoDisposeFamilyAsyncNotifier<List<SnPostAward>, String>
with CursorPagingNotifierMixin<SnPostAward> { with FamilyAsyncPaginationController<SnPostAward, String> {
static const int _pageSize = 20; static const int _pageSize = 20;
@override @override
Future<CursorPagingData<SnPostAward>> build({required String postId}) { Future<List<SnPostAward>> fetch() async {
return fetch(cursor: null);
}
@override
Future<CursorPagingData<SnPostAward>> fetch({required String? cursor}) async {
final client = ref.read(apiClientProvider); final client = ref.read(apiClientProvider);
final offset = cursor == null ? 0 : int.parse(cursor);
final queryParams = {'offset': offset, 'take': _pageSize}; final queryParams = {'offset': fetchedCount, 'take': _pageSize};
final response = await client.get( final response = await client.get(
'/sphere/posts/$postId/awards', '/sphere/posts/$arg/awards',
queryParameters: queryParams, queryParameters: queryParams,
); );
final total = int.parse(response.headers.value('X-Total') ?? '0'); totalCount = int.parse(response.headers.value('X-Total') ?? '0');
final List<dynamic> data = response.data; final List<dynamic> data = response.data;
final awards = data.map((json) => SnPostAward.fromJson(json)).toList(); return data.map((json) => SnPostAward.fromJson(json)).toList();
final hasMore = offset + awards.length < total;
final nextCursor = hasMore ? (offset + awards.length).toString() : null;
return CursorPagingData(
items: awards,
hasMore: hasMore,
nextCursor: nextCursor,
);
} }
} }
@@ -51,32 +39,23 @@ class PostAwardHistorySheet extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final provider = postAwardListNotifierProvider(postId: postId); final provider = postAwardListNotifierProvider(postId);
return SheetScaffold( return SheetScaffold(
titleText: 'Award History', titleText: 'Award History',
child: PagingHelperView( child: PaginationList(
provider: provider, provider: provider,
futureRefreshable: provider.future, notifier: provider.notifier,
notifierRefreshable: provider.notifier, itemBuilder: (context, index, award) {
contentBuilder:
(data, widgetCount, endItemView) => ListView.builder(
itemCount: widgetCount,
itemBuilder: (context, index) {
if (index == widgetCount - 1) {
return endItemView;
}
final award = data.items[index];
return Column( return Column(
children: [ children: [
PostAwardItem(award: award), PostAwardItem(award: award),
if (index < (ref.read(provider).valueOrNull?.length ?? 0) - 1)
const Divider(height: 1), const Divider(height: 1),
], ],
); );
}, },
), ),
),
); );
} }
} }

View File

@@ -1,180 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'post_award_history_sheet.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$postAwardListNotifierHash() =>
r'834d08f90ef352a2dfb0192455c75b1620e859c2';
/// Copied from Dart SDK
class _SystemHash {
_SystemHash._();
static int combine(int hash, int value) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + value);
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
return hash ^ (hash >> 6);
}
static int finish(int hash) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
// ignore: parameter_assignments
hash = hash ^ (hash >> 11);
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
}
}
abstract class _$PostAwardListNotifier
extends BuildlessAutoDisposeAsyncNotifier<CursorPagingData<SnPostAward>> {
late final String postId;
FutureOr<CursorPagingData<SnPostAward>> build({required String postId});
}
/// See also [PostAwardListNotifier].
@ProviderFor(PostAwardListNotifier)
const postAwardListNotifierProvider = PostAwardListNotifierFamily();
/// See also [PostAwardListNotifier].
class PostAwardListNotifierFamily
extends Family<AsyncValue<CursorPagingData<SnPostAward>>> {
/// See also [PostAwardListNotifier].
const PostAwardListNotifierFamily();
/// See also [PostAwardListNotifier].
PostAwardListNotifierProvider call({required String postId}) {
return PostAwardListNotifierProvider(postId: postId);
}
@override
PostAwardListNotifierProvider getProviderOverride(
covariant PostAwardListNotifierProvider provider,
) {
return call(postId: provider.postId);
}
static const Iterable<ProviderOrFamily>? _dependencies = null;
@override
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
@override
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
_allTransitiveDependencies;
@override
String? get name => r'postAwardListNotifierProvider';
}
/// See also [PostAwardListNotifier].
class PostAwardListNotifierProvider
extends
AutoDisposeAsyncNotifierProviderImpl<
PostAwardListNotifier,
CursorPagingData<SnPostAward>
> {
/// See also [PostAwardListNotifier].
PostAwardListNotifierProvider({required String postId})
: this._internal(
() => PostAwardListNotifier()..postId = postId,
from: postAwardListNotifierProvider,
name: r'postAwardListNotifierProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$postAwardListNotifierHash,
dependencies: PostAwardListNotifierFamily._dependencies,
allTransitiveDependencies:
PostAwardListNotifierFamily._allTransitiveDependencies,
postId: postId,
);
PostAwardListNotifierProvider._internal(
super._createNotifier, {
required super.name,
required super.dependencies,
required super.allTransitiveDependencies,
required super.debugGetCreateSourceHash,
required super.from,
required this.postId,
}) : super.internal();
final String postId;
@override
FutureOr<CursorPagingData<SnPostAward>> runNotifierBuild(
covariant PostAwardListNotifier notifier,
) {
return notifier.build(postId: postId);
}
@override
Override overrideWith(PostAwardListNotifier Function() create) {
return ProviderOverride(
origin: this,
override: PostAwardListNotifierProvider._internal(
() => create()..postId = postId,
from: from,
name: null,
dependencies: null,
allTransitiveDependencies: null,
debugGetCreateSourceHash: null,
postId: postId,
),
);
}
@override
AutoDisposeAsyncNotifierProviderElement<
PostAwardListNotifier,
CursorPagingData<SnPostAward>
>
createElement() {
return _PostAwardListNotifierProviderElement(this);
}
@override
bool operator ==(Object other) {
return other is PostAwardListNotifierProvider && other.postId == postId;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, postId.hashCode);
return _SystemHash.finish(hash);
}
}
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
mixin PostAwardListNotifierRef
on AutoDisposeAsyncNotifierProviderRef<CursorPagingData<SnPostAward>> {
/// The parameter `postId` of this provider.
String get postId;
}
class _PostAwardListNotifierProviderElement
extends
AutoDisposeAsyncNotifierProviderElement<
PostAwardListNotifier,
CursorPagingData<SnPostAward>
>
with PostAwardListNotifierRef {
_PostAwardListNotifierProviderElement(super.provider);
@override
String get postId => (origin as PostAwardListNotifierProvider).postId;
}
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package

View File

@@ -1,79 +1,75 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/models/post.dart'; import 'package:island/models/post.dart';
import 'package:island/pods/network.dart'; import 'package:island/pods/network.dart';
import 'package:island/pods/paging.dart';
import 'package:island/widgets/paging/pagination_list.dart';
import 'package:island/widgets/post/post_item.dart'; import 'package:island/widgets/post/post_item.dart';
import 'package:island/widgets/post/post_item_creator.dart'; import 'package:island/widgets/post/post_item_creator.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
part 'post_list.g.dart'; part 'post_list.freezed.dart';
@riverpod @freezed
class PostListNotifier extends _$PostListNotifier sealed class PostListQuery with _$PostListQuery {
with CursorPagingNotifierMixin<SnPost> { const factory PostListQuery({
static const int _pageSize = 20;
@override
Future<CursorPagingData<SnPost>> build({
String? pubName, String? pubName,
String? realm, String? realm,
int? type, int? type,
List<String>? categories, List<String>? categories,
List<String>? tags, List<String>? tags,
bool? pinned, bool? pinned,
bool shuffle = false, @Default(false) bool shuffle,
bool? includeReplies, bool? includeReplies,
bool? mediaOnly, bool? mediaOnly,
String? queryTerm, String? queryTerm,
String? order, String? order,
int? periodStart, int? periodStart,
int? periodEnd, int? periodEnd,
bool orderDesc = true, @Default(true) bool orderDesc,
}) { }) = _PostListQuery;
return fetch(cursor: null); }
}
final postListNotifierProvider = AsyncNotifierProvider.autoDispose
.family<PostListNotifier, List<SnPost>, PostListQuery>(
PostListNotifier.new,
);
class PostListNotifier
extends AutoDisposeFamilyAsyncNotifier<List<SnPost>, PostListQuery>
with FamilyAsyncPaginationController<SnPost, PostListQuery> {
static const int _pageSize = 20;
@override @override
Future<CursorPagingData<SnPost>> fetch({required String? cursor}) async { Future<List<SnPost>> fetch() async {
final client = ref.read(apiClientProvider); final client = ref.read(apiClientProvider);
final offset = cursor == null ? 0 : int.parse(cursor);
final queryParams = { final queryParams = {
'offset': offset, 'offset': fetchedCount,
'take': _pageSize, 'take': _pageSize,
'replies': includeReplies, 'replies': arg.includeReplies,
'orderDesc': orderDesc, 'orderDesc': arg.orderDesc,
if (shuffle) 'shuffle': shuffle, if (arg.shuffle) 'shuffle': arg.shuffle,
if (pubName != null) 'pub': pubName, if (arg.pubName != null) 'pub': arg.pubName,
if (realm != null) 'realm': realm, if (arg.realm != null) 'realm': arg.realm,
if (type != null) 'type': type, if (arg.type != null) 'type': arg.type,
if (tags != null) 'tags': tags, if (arg.tags != null) 'tags': arg.tags,
if (categories != null) 'categories': categories, if (arg.categories != null) 'categories': arg.categories,
if (pinned != null) 'pinned': pinned, if (arg.pinned != null) 'pinned': arg.pinned,
if (order != null) 'order': order, if (arg.order != null) 'order': arg.order,
if (periodStart != null) 'periodStart': periodStart, if (arg.periodStart != null) 'periodStart': arg.periodStart,
if (periodEnd != null) 'periodEnd': periodEnd, if (arg.periodEnd != null) 'periodEnd': arg.periodEnd,
if (queryTerm != null) 'query': queryTerm, if (arg.queryTerm != null) 'query': arg.queryTerm,
if (mediaOnly != null) 'media': mediaOnly, if (arg.mediaOnly != null) 'media': arg.mediaOnly,
}; };
final response = await client.get( final response = await client.get(
'/sphere/posts', '/sphere/posts',
queryParameters: queryParams, queryParameters: queryParams,
); );
final total = int.parse(response.headers.value('X-Total') ?? '0'); totalCount = int.parse(response.headers.value('X-Total') ?? '0');
final List<dynamic> data = response.data; final List<dynamic> data = response.data;
final posts = data.map((json) => SnPost.fromJson(json)).toList(); return data.map((json) => SnPost.fromJson(json)).toList();
final hasMore = offset + posts.length < total;
final nextCursor = hasMore ? (offset + posts.length).toString() : null;
return CursorPagingData(
items: posts,
hasMore: hasMore,
nextCursor: nextCursor,
);
} }
} }
@@ -137,7 +133,7 @@ class SliverPostList extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final provider = postListNotifierProvider( final params = PostListQuery(
pubName: pubName, pubName: pubName,
realm: realm, realm: realm,
type: type, type: type,
@@ -153,20 +149,15 @@ class SliverPostList extends HookConsumerWidget {
periodEnd: periodEnd, periodEnd: periodEnd,
orderDesc: orderDesc ?? true, orderDesc: orderDesc ?? true,
); );
return PagingHelperSliverView( final provider = postListNotifierProvider(params);
final notifier = provider.notifier;
return PaginationList(
provider: provider, provider: provider,
futureRefreshable: provider.future, notifier: notifier,
notifierRefreshable: provider.notifier, isRefreshable: false,
contentBuilder: isSliver: true,
(data, widgetCount, endItemView) => SliverList.builder( itemBuilder: (context, index, post) {
itemCount: widgetCount,
itemBuilder: (context, index) {
if (index == widgetCount - 1) {
return endItemView;
}
final post = data.items[index];
if (maxWidth != null) { if (maxWidth != null) {
return Center( return Center(
child: ConstrainedBox( child: ConstrainedBox(
@@ -178,7 +169,6 @@ class SliverPostList extends HookConsumerWidget {
return _buildPostItem(post); return _buildPostItem(post);
}, },
),
); );
} }

View File

@@ -0,0 +1,320 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// coverage:ignore-file
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'post_list.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$PostListQuery {
String? get pubName; String? get realm; int? get type; List<String>? get categories; List<String>? get tags; bool? get pinned; bool get shuffle; bool? get includeReplies; bool? get mediaOnly; String? get queryTerm; String? get order; int? get periodStart; int? get periodEnd; bool get orderDesc;
/// Create a copy of PostListQuery
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$PostListQueryCopyWith<PostListQuery> get copyWith => _$PostListQueryCopyWithImpl<PostListQuery>(this as PostListQuery, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is PostListQuery&&(identical(other.pubName, pubName) || other.pubName == pubName)&&(identical(other.realm, realm) || other.realm == realm)&&(identical(other.type, type) || other.type == type)&&const DeepCollectionEquality().equals(other.categories, categories)&&const DeepCollectionEquality().equals(other.tags, tags)&&(identical(other.pinned, pinned) || other.pinned == pinned)&&(identical(other.shuffle, shuffle) || other.shuffle == shuffle)&&(identical(other.includeReplies, includeReplies) || other.includeReplies == includeReplies)&&(identical(other.mediaOnly, mediaOnly) || other.mediaOnly == mediaOnly)&&(identical(other.queryTerm, queryTerm) || other.queryTerm == queryTerm)&&(identical(other.order, order) || other.order == order)&&(identical(other.periodStart, periodStart) || other.periodStart == periodStart)&&(identical(other.periodEnd, periodEnd) || other.periodEnd == periodEnd)&&(identical(other.orderDesc, orderDesc) || other.orderDesc == orderDesc));
}
@override
int get hashCode => Object.hash(runtimeType,pubName,realm,type,const DeepCollectionEquality().hash(categories),const DeepCollectionEquality().hash(tags),pinned,shuffle,includeReplies,mediaOnly,queryTerm,order,periodStart,periodEnd,orderDesc);
@override
String toString() {
return 'PostListQuery(pubName: $pubName, realm: $realm, type: $type, categories: $categories, tags: $tags, pinned: $pinned, shuffle: $shuffle, includeReplies: $includeReplies, mediaOnly: $mediaOnly, queryTerm: $queryTerm, order: $order, periodStart: $periodStart, periodEnd: $periodEnd, orderDesc: $orderDesc)';
}
}
/// @nodoc
abstract mixin class $PostListQueryCopyWith<$Res> {
factory $PostListQueryCopyWith(PostListQuery value, $Res Function(PostListQuery) _then) = _$PostListQueryCopyWithImpl;
@useResult
$Res call({
String? pubName, String? realm, int? type, List<String>? categories, List<String>? tags, bool? pinned, bool shuffle, bool? includeReplies, bool? mediaOnly, String? queryTerm, String? order, int? periodStart, int? periodEnd, bool orderDesc
});
}
/// @nodoc
class _$PostListQueryCopyWithImpl<$Res>
implements $PostListQueryCopyWith<$Res> {
_$PostListQueryCopyWithImpl(this._self, this._then);
final PostListQuery _self;
final $Res Function(PostListQuery) _then;
/// Create a copy of PostListQuery
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? pubName = freezed,Object? realm = freezed,Object? type = freezed,Object? categories = freezed,Object? tags = freezed,Object? pinned = freezed,Object? shuffle = null,Object? includeReplies = freezed,Object? mediaOnly = freezed,Object? queryTerm = freezed,Object? order = freezed,Object? periodStart = freezed,Object? periodEnd = freezed,Object? orderDesc = null,}) {
return _then(_self.copyWith(
pubName: freezed == pubName ? _self.pubName : pubName // ignore: cast_nullable_to_non_nullable
as String?,realm: freezed == realm ? _self.realm : realm // ignore: cast_nullable_to_non_nullable
as String?,type: freezed == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
as int?,categories: freezed == categories ? _self.categories : categories // ignore: cast_nullable_to_non_nullable
as List<String>?,tags: freezed == tags ? _self.tags : tags // ignore: cast_nullable_to_non_nullable
as List<String>?,pinned: freezed == pinned ? _self.pinned : pinned // ignore: cast_nullable_to_non_nullable
as bool?,shuffle: null == shuffle ? _self.shuffle : shuffle // ignore: cast_nullable_to_non_nullable
as bool,includeReplies: freezed == includeReplies ? _self.includeReplies : includeReplies // ignore: cast_nullable_to_non_nullable
as bool?,mediaOnly: freezed == mediaOnly ? _self.mediaOnly : mediaOnly // ignore: cast_nullable_to_non_nullable
as bool?,queryTerm: freezed == queryTerm ? _self.queryTerm : queryTerm // ignore: cast_nullable_to_non_nullable
as String?,order: freezed == order ? _self.order : order // ignore: cast_nullable_to_non_nullable
as String?,periodStart: freezed == periodStart ? _self.periodStart : periodStart // ignore: cast_nullable_to_non_nullable
as int?,periodEnd: freezed == periodEnd ? _self.periodEnd : periodEnd // ignore: cast_nullable_to_non_nullable
as int?,orderDesc: null == orderDesc ? _self.orderDesc : orderDesc // ignore: cast_nullable_to_non_nullable
as bool,
));
}
}
/// Adds pattern-matching-related methods to [PostListQuery].
extension PostListQueryPatterns on PostListQuery {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _PostListQuery value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _PostListQuery() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _PostListQuery value) $default,){
final _that = this;
switch (_that) {
case _PostListQuery():
return $default(_that);}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _PostListQuery value)? $default,){
final _that = this;
switch (_that) {
case _PostListQuery() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String? pubName, String? realm, int? type, List<String>? categories, List<String>? tags, bool? pinned, bool shuffle, bool? includeReplies, bool? mediaOnly, String? queryTerm, String? order, int? periodStart, int? periodEnd, bool orderDesc)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _PostListQuery() when $default != null:
return $default(_that.pubName,_that.realm,_that.type,_that.categories,_that.tags,_that.pinned,_that.shuffle,_that.includeReplies,_that.mediaOnly,_that.queryTerm,_that.order,_that.periodStart,_that.periodEnd,_that.orderDesc);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String? pubName, String? realm, int? type, List<String>? categories, List<String>? tags, bool? pinned, bool shuffle, bool? includeReplies, bool? mediaOnly, String? queryTerm, String? order, int? periodStart, int? periodEnd, bool orderDesc) $default,) {final _that = this;
switch (_that) {
case _PostListQuery():
return $default(_that.pubName,_that.realm,_that.type,_that.categories,_that.tags,_that.pinned,_that.shuffle,_that.includeReplies,_that.mediaOnly,_that.queryTerm,_that.order,_that.periodStart,_that.periodEnd,_that.orderDesc);}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String? pubName, String? realm, int? type, List<String>? categories, List<String>? tags, bool? pinned, bool shuffle, bool? includeReplies, bool? mediaOnly, String? queryTerm, String? order, int? periodStart, int? periodEnd, bool orderDesc)? $default,) {final _that = this;
switch (_that) {
case _PostListQuery() when $default != null:
return $default(_that.pubName,_that.realm,_that.type,_that.categories,_that.tags,_that.pinned,_that.shuffle,_that.includeReplies,_that.mediaOnly,_that.queryTerm,_that.order,_that.periodStart,_that.periodEnd,_that.orderDesc);case _:
return null;
}
}
}
/// @nodoc
class _PostListQuery implements PostListQuery {
const _PostListQuery({this.pubName, this.realm, this.type, final List<String>? categories, final List<String>? tags, this.pinned, this.shuffle = false, this.includeReplies, this.mediaOnly, this.queryTerm, this.order, this.periodStart, this.periodEnd, this.orderDesc = true}): _categories = categories,_tags = tags;
@override final String? pubName;
@override final String? realm;
@override final int? type;
final List<String>? _categories;
@override List<String>? get categories {
final value = _categories;
if (value == null) return null;
if (_categories is EqualUnmodifiableListView) return _categories;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(value);
}
final List<String>? _tags;
@override List<String>? get tags {
final value = _tags;
if (value == null) return null;
if (_tags is EqualUnmodifiableListView) return _tags;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(value);
}
@override final bool? pinned;
@override@JsonKey() final bool shuffle;
@override final bool? includeReplies;
@override final bool? mediaOnly;
@override final String? queryTerm;
@override final String? order;
@override final int? periodStart;
@override final int? periodEnd;
@override@JsonKey() final bool orderDesc;
/// Create a copy of PostListQuery
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$PostListQueryCopyWith<_PostListQuery> get copyWith => __$PostListQueryCopyWithImpl<_PostListQuery>(this, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _PostListQuery&&(identical(other.pubName, pubName) || other.pubName == pubName)&&(identical(other.realm, realm) || other.realm == realm)&&(identical(other.type, type) || other.type == type)&&const DeepCollectionEquality().equals(other._categories, _categories)&&const DeepCollectionEquality().equals(other._tags, _tags)&&(identical(other.pinned, pinned) || other.pinned == pinned)&&(identical(other.shuffle, shuffle) || other.shuffle == shuffle)&&(identical(other.includeReplies, includeReplies) || other.includeReplies == includeReplies)&&(identical(other.mediaOnly, mediaOnly) || other.mediaOnly == mediaOnly)&&(identical(other.queryTerm, queryTerm) || other.queryTerm == queryTerm)&&(identical(other.order, order) || other.order == order)&&(identical(other.periodStart, periodStart) || other.periodStart == periodStart)&&(identical(other.periodEnd, periodEnd) || other.periodEnd == periodEnd)&&(identical(other.orderDesc, orderDesc) || other.orderDesc == orderDesc));
}
@override
int get hashCode => Object.hash(runtimeType,pubName,realm,type,const DeepCollectionEquality().hash(_categories),const DeepCollectionEquality().hash(_tags),pinned,shuffle,includeReplies,mediaOnly,queryTerm,order,periodStart,periodEnd,orderDesc);
@override
String toString() {
return 'PostListQuery(pubName: $pubName, realm: $realm, type: $type, categories: $categories, tags: $tags, pinned: $pinned, shuffle: $shuffle, includeReplies: $includeReplies, mediaOnly: $mediaOnly, queryTerm: $queryTerm, order: $order, periodStart: $periodStart, periodEnd: $periodEnd, orderDesc: $orderDesc)';
}
}
/// @nodoc
abstract mixin class _$PostListQueryCopyWith<$Res> implements $PostListQueryCopyWith<$Res> {
factory _$PostListQueryCopyWith(_PostListQuery value, $Res Function(_PostListQuery) _then) = __$PostListQueryCopyWithImpl;
@override @useResult
$Res call({
String? pubName, String? realm, int? type, List<String>? categories, List<String>? tags, bool? pinned, bool shuffle, bool? includeReplies, bool? mediaOnly, String? queryTerm, String? order, int? periodStart, int? periodEnd, bool orderDesc
});
}
/// @nodoc
class __$PostListQueryCopyWithImpl<$Res>
implements _$PostListQueryCopyWith<$Res> {
__$PostListQueryCopyWithImpl(this._self, this._then);
final _PostListQuery _self;
final $Res Function(_PostListQuery) _then;
/// Create a copy of PostListQuery
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? pubName = freezed,Object? realm = freezed,Object? type = freezed,Object? categories = freezed,Object? tags = freezed,Object? pinned = freezed,Object? shuffle = null,Object? includeReplies = freezed,Object? mediaOnly = freezed,Object? queryTerm = freezed,Object? order = freezed,Object? periodStart = freezed,Object? periodEnd = freezed,Object? orderDesc = null,}) {
return _then(_PostListQuery(
pubName: freezed == pubName ? _self.pubName : pubName // ignore: cast_nullable_to_non_nullable
as String?,realm: freezed == realm ? _self.realm : realm // ignore: cast_nullable_to_non_nullable
as String?,type: freezed == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
as int?,categories: freezed == categories ? _self._categories : categories // ignore: cast_nullable_to_non_nullable
as List<String>?,tags: freezed == tags ? _self._tags : tags // ignore: cast_nullable_to_non_nullable
as List<String>?,pinned: freezed == pinned ? _self.pinned : pinned // ignore: cast_nullable_to_non_nullable
as bool?,shuffle: null == shuffle ? _self.shuffle : shuffle // ignore: cast_nullable_to_non_nullable
as bool,includeReplies: freezed == includeReplies ? _self.includeReplies : includeReplies // ignore: cast_nullable_to_non_nullable
as bool?,mediaOnly: freezed == mediaOnly ? _self.mediaOnly : mediaOnly // ignore: cast_nullable_to_non_nullable
as bool?,queryTerm: freezed == queryTerm ? _self.queryTerm : queryTerm // ignore: cast_nullable_to_non_nullable
as String?,order: freezed == order ? _self.order : order // ignore: cast_nullable_to_non_nullable
as String?,periodStart: freezed == periodStart ? _self.periodStart : periodStart // ignore: cast_nullable_to_non_nullable
as int?,periodEnd: freezed == periodEnd ? _self.periodEnd : periodEnd // ignore: cast_nullable_to_non_nullable
as int?,orderDesc: null == orderDesc ? _self.orderDesc : orderDesc // ignore: cast_nullable_to_non_nullable
as bool,
));
}
}
// dart format on

View File

@@ -1,457 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'post_list.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$postListNotifierHash() => r'bfc3d652dffc5ff3a94a6c3d04aac65354fe63b5';
/// Copied from Dart SDK
class _SystemHash {
_SystemHash._();
static int combine(int hash, int value) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + value);
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
return hash ^ (hash >> 6);
}
static int finish(int hash) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
// ignore: parameter_assignments
hash = hash ^ (hash >> 11);
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
}
}
abstract class _$PostListNotifier
extends BuildlessAutoDisposeAsyncNotifier<CursorPagingData<SnPost>> {
late final String? pubName;
late final String? realm;
late final int? type;
late final List<String>? categories;
late final List<String>? tags;
late final bool? pinned;
late final bool shuffle;
late final bool? includeReplies;
late final bool? mediaOnly;
late final String? queryTerm;
late final String? order;
late final int? periodStart;
late final int? periodEnd;
late final bool orderDesc;
FutureOr<CursorPagingData<SnPost>> build({
String? pubName,
String? realm,
int? type,
List<String>? categories,
List<String>? tags,
bool? pinned,
bool shuffle = false,
bool? includeReplies,
bool? mediaOnly,
String? queryTerm,
String? order,
int? periodStart,
int? periodEnd,
bool orderDesc = true,
});
}
/// See also [PostListNotifier].
@ProviderFor(PostListNotifier)
const postListNotifierProvider = PostListNotifierFamily();
/// See also [PostListNotifier].
class PostListNotifierFamily
extends Family<AsyncValue<CursorPagingData<SnPost>>> {
/// See also [PostListNotifier].
const PostListNotifierFamily();
/// See also [PostListNotifier].
PostListNotifierProvider call({
String? pubName,
String? realm,
int? type,
List<String>? categories,
List<String>? tags,
bool? pinned,
bool shuffle = false,
bool? includeReplies,
bool? mediaOnly,
String? queryTerm,
String? order,
int? periodStart,
int? periodEnd,
bool orderDesc = true,
}) {
return PostListNotifierProvider(
pubName: pubName,
realm: realm,
type: type,
categories: categories,
tags: tags,
pinned: pinned,
shuffle: shuffle,
includeReplies: includeReplies,
mediaOnly: mediaOnly,
queryTerm: queryTerm,
order: order,
periodStart: periodStart,
periodEnd: periodEnd,
orderDesc: orderDesc,
);
}
@override
PostListNotifierProvider getProviderOverride(
covariant PostListNotifierProvider provider,
) {
return call(
pubName: provider.pubName,
realm: provider.realm,
type: provider.type,
categories: provider.categories,
tags: provider.tags,
pinned: provider.pinned,
shuffle: provider.shuffle,
includeReplies: provider.includeReplies,
mediaOnly: provider.mediaOnly,
queryTerm: provider.queryTerm,
order: provider.order,
periodStart: provider.periodStart,
periodEnd: provider.periodEnd,
orderDesc: provider.orderDesc,
);
}
static const Iterable<ProviderOrFamily>? _dependencies = null;
@override
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
@override
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
_allTransitiveDependencies;
@override
String? get name => r'postListNotifierProvider';
}
/// See also [PostListNotifier].
class PostListNotifierProvider
extends
AutoDisposeAsyncNotifierProviderImpl<
PostListNotifier,
CursorPagingData<SnPost>
> {
/// See also [PostListNotifier].
PostListNotifierProvider({
String? pubName,
String? realm,
int? type,
List<String>? categories,
List<String>? tags,
bool? pinned,
bool shuffle = false,
bool? includeReplies,
bool? mediaOnly,
String? queryTerm,
String? order,
int? periodStart,
int? periodEnd,
bool orderDesc = true,
}) : this._internal(
() =>
PostListNotifier()
..pubName = pubName
..realm = realm
..type = type
..categories = categories
..tags = tags
..pinned = pinned
..shuffle = shuffle
..includeReplies = includeReplies
..mediaOnly = mediaOnly
..queryTerm = queryTerm
..order = order
..periodStart = periodStart
..periodEnd = periodEnd
..orderDesc = orderDesc,
from: postListNotifierProvider,
name: r'postListNotifierProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$postListNotifierHash,
dependencies: PostListNotifierFamily._dependencies,
allTransitiveDependencies:
PostListNotifierFamily._allTransitiveDependencies,
pubName: pubName,
realm: realm,
type: type,
categories: categories,
tags: tags,
pinned: pinned,
shuffle: shuffle,
includeReplies: includeReplies,
mediaOnly: mediaOnly,
queryTerm: queryTerm,
order: order,
periodStart: periodStart,
periodEnd: periodEnd,
orderDesc: orderDesc,
);
PostListNotifierProvider._internal(
super._createNotifier, {
required super.name,
required super.dependencies,
required super.allTransitiveDependencies,
required super.debugGetCreateSourceHash,
required super.from,
required this.pubName,
required this.realm,
required this.type,
required this.categories,
required this.tags,
required this.pinned,
required this.shuffle,
required this.includeReplies,
required this.mediaOnly,
required this.queryTerm,
required this.order,
required this.periodStart,
required this.periodEnd,
required this.orderDesc,
}) : super.internal();
final String? pubName;
final String? realm;
final int? type;
final List<String>? categories;
final List<String>? tags;
final bool? pinned;
final bool shuffle;
final bool? includeReplies;
final bool? mediaOnly;
final String? queryTerm;
final String? order;
final int? periodStart;
final int? periodEnd;
final bool orderDesc;
@override
FutureOr<CursorPagingData<SnPost>> runNotifierBuild(
covariant PostListNotifier notifier,
) {
return notifier.build(
pubName: pubName,
realm: realm,
type: type,
categories: categories,
tags: tags,
pinned: pinned,
shuffle: shuffle,
includeReplies: includeReplies,
mediaOnly: mediaOnly,
queryTerm: queryTerm,
order: order,
periodStart: periodStart,
periodEnd: periodEnd,
orderDesc: orderDesc,
);
}
@override
Override overrideWith(PostListNotifier Function() create) {
return ProviderOverride(
origin: this,
override: PostListNotifierProvider._internal(
() =>
create()
..pubName = pubName
..realm = realm
..type = type
..categories = categories
..tags = tags
..pinned = pinned
..shuffle = shuffle
..includeReplies = includeReplies
..mediaOnly = mediaOnly
..queryTerm = queryTerm
..order = order
..periodStart = periodStart
..periodEnd = periodEnd
..orderDesc = orderDesc,
from: from,
name: null,
dependencies: null,
allTransitiveDependencies: null,
debugGetCreateSourceHash: null,
pubName: pubName,
realm: realm,
type: type,
categories: categories,
tags: tags,
pinned: pinned,
shuffle: shuffle,
includeReplies: includeReplies,
mediaOnly: mediaOnly,
queryTerm: queryTerm,
order: order,
periodStart: periodStart,
periodEnd: periodEnd,
orderDesc: orderDesc,
),
);
}
@override
AutoDisposeAsyncNotifierProviderElement<
PostListNotifier,
CursorPagingData<SnPost>
>
createElement() {
return _PostListNotifierProviderElement(this);
}
@override
bool operator ==(Object other) {
return other is PostListNotifierProvider &&
other.pubName == pubName &&
other.realm == realm &&
other.type == type &&
other.categories == categories &&
other.tags == tags &&
other.pinned == pinned &&
other.shuffle == shuffle &&
other.includeReplies == includeReplies &&
other.mediaOnly == mediaOnly &&
other.queryTerm == queryTerm &&
other.order == order &&
other.periodStart == periodStart &&
other.periodEnd == periodEnd &&
other.orderDesc == orderDesc;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, pubName.hashCode);
hash = _SystemHash.combine(hash, realm.hashCode);
hash = _SystemHash.combine(hash, type.hashCode);
hash = _SystemHash.combine(hash, categories.hashCode);
hash = _SystemHash.combine(hash, tags.hashCode);
hash = _SystemHash.combine(hash, pinned.hashCode);
hash = _SystemHash.combine(hash, shuffle.hashCode);
hash = _SystemHash.combine(hash, includeReplies.hashCode);
hash = _SystemHash.combine(hash, mediaOnly.hashCode);
hash = _SystemHash.combine(hash, queryTerm.hashCode);
hash = _SystemHash.combine(hash, order.hashCode);
hash = _SystemHash.combine(hash, periodStart.hashCode);
hash = _SystemHash.combine(hash, periodEnd.hashCode);
hash = _SystemHash.combine(hash, orderDesc.hashCode);
return _SystemHash.finish(hash);
}
}
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
mixin PostListNotifierRef
on AutoDisposeAsyncNotifierProviderRef<CursorPagingData<SnPost>> {
/// The parameter `pubName` of this provider.
String? get pubName;
/// The parameter `realm` of this provider.
String? get realm;
/// The parameter `type` of this provider.
int? get type;
/// The parameter `categories` of this provider.
List<String>? get categories;
/// The parameter `tags` of this provider.
List<String>? get tags;
/// The parameter `pinned` of this provider.
bool? get pinned;
/// The parameter `shuffle` of this provider.
bool get shuffle;
/// The parameter `includeReplies` of this provider.
bool? get includeReplies;
/// The parameter `mediaOnly` of this provider.
bool? get mediaOnly;
/// The parameter `queryTerm` of this provider.
String? get queryTerm;
/// The parameter `order` of this provider.
String? get order;
/// The parameter `periodStart` of this provider.
int? get periodStart;
/// The parameter `periodEnd` of this provider.
int? get periodEnd;
/// The parameter `orderDesc` of this provider.
bool get orderDesc;
}
class _PostListNotifierProviderElement
extends
AutoDisposeAsyncNotifierProviderElement<
PostListNotifier,
CursorPagingData<SnPost>
>
with PostListNotifierRef {
_PostListNotifierProviderElement(super.provider);
@override
String? get pubName => (origin as PostListNotifierProvider).pubName;
@override
String? get realm => (origin as PostListNotifierProvider).realm;
@override
int? get type => (origin as PostListNotifierProvider).type;
@override
List<String>? get categories =>
(origin as PostListNotifierProvider).categories;
@override
List<String>? get tags => (origin as PostListNotifierProvider).tags;
@override
bool? get pinned => (origin as PostListNotifierProvider).pinned;
@override
bool get shuffle => (origin as PostListNotifierProvider).shuffle;
@override
bool? get includeReplies =>
(origin as PostListNotifierProvider).includeReplies;
@override
bool? get mediaOnly => (origin as PostListNotifierProvider).mediaOnly;
@override
String? get queryTerm => (origin as PostListNotifierProvider).queryTerm;
@override
String? get order => (origin as PostListNotifierProvider).order;
@override
int? get periodStart => (origin as PostListNotifierProvider).periodStart;
@override
int? get periodEnd => (origin as PostListNotifierProvider).periodEnd;
@override
bool get orderDesc => (origin as PostListNotifierProvider).orderDesc;
}
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package

View File

@@ -8,60 +8,63 @@ import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/models/post.dart'; import 'package:island/models/post.dart';
import 'package:island/pods/network.dart'; import 'package:island/pods/network.dart';
import 'package:island/pods/paging.dart';
import 'package:island/services/time.dart'; import 'package:island/services/time.dart';
import 'package:island/widgets/account/account_pfc.dart'; import 'package:island/widgets/account/account_pfc.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/paging/pagination_list.dart';
import 'package:material_symbols_icons/symbols.dart'; import 'package:material_symbols_icons/symbols.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
import 'package:styled_widget/styled_widget.dart'; import 'package:styled_widget/styled_widget.dart';
import 'package:island/widgets/stickers/sticker_picker.dart'; import 'package:island/widgets/stickers/sticker_picker.dart';
import 'package:island/pods/config.dart'; import 'package:island/pods/config.dart';
part 'post_reaction_sheet.g.dart'; class ReactionListParams {
final String symbol;
final String postId;
@riverpod const ReactionListParams({required this.symbol, required this.postId});
class ReactionListNotifier extends _$ReactionListNotifier
with CursorPagingNotifierMixin<SnPostReaction> { @override
bool operator ==(Object other) =>
identical(this, other) ||
other is ReactionListParams &&
runtimeType == other.runtimeType &&
symbol == other.symbol &&
postId == other.postId;
@override
int get hashCode => symbol.hashCode ^ postId.hashCode;
}
final reactionListNotifierProvider = AsyncNotifierProvider.autoDispose
.family<ReactionListNotifier, List<SnPostReaction>, ReactionListParams>(
ReactionListNotifier.new,
);
class ReactionListNotifier
extends
AutoDisposeFamilyAsyncNotifier<List<SnPostReaction>, ReactionListParams>
with FamilyAsyncPaginationController<SnPostReaction, ReactionListParams> {
static const int _pageSize = 20; static const int _pageSize = 20;
int? totalCount;
@override @override
Future<CursorPagingData<SnPostReaction>> build({ Future<List<SnPostReaction>> fetch() async {
required String symbol,
required String postId,
}) {
return fetch(cursor: null);
}
@override
Future<CursorPagingData<SnPostReaction>> fetch({
required String? cursor,
}) async {
final client = ref.read(apiClientProvider); final client = ref.read(apiClientProvider);
final offset = cursor == null ? 0 : int.parse(cursor);
final response = await client.get( final response = await client.get(
'/sphere/posts/$postId/reactions', '/sphere/posts/${arg.postId}/reactions',
queryParameters: {'symbol': symbol, 'offset': offset, 'take': _pageSize}, queryParameters: {
'symbol': arg.symbol,
'offset': fetchedCount,
'take': _pageSize,
},
); );
totalCount = int.tryParse(response.headers.value('x-total') ?? '0') ?? 0; totalCount = int.tryParse(response.headers.value('x-total') ?? '0') ?? 0;
final List<dynamic> data = response.data; final List<dynamic> data = response.data;
final reactions = return data.map((json) => SnPostReaction.fromJson(json)).toList();
data.map((json) => SnPostReaction.fromJson(json)).toList();
final hasMore = reactions.length == _pageSize;
final nextCursor = hasMore ? (offset + reactions.length).toString() : null;
return CursorPagingData(
items: reactions,
hasMore: hasMore,
nextCursor: nextCursor,
);
} }
} }
@@ -485,10 +488,8 @@ class ReactionDetailsPopup extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final provider = reactionListNotifierProvider( final params = ReactionListParams(symbol: symbol, postId: postId);
symbol: symbol, final provider = reactionListNotifierProvider(params);
postId: postId,
);
final width = math.min(MediaQuery.of(context).size.width * 0.8, 480.0); final width = math.min(MediaQuery.of(context).size.width * 0.8, 480.0);
return PopupCard( return PopupCard(
@@ -516,20 +517,11 @@ class ReactionDetailsPopup extends HookConsumerWidget {
), ),
const Divider(height: 1), const Divider(height: 1),
Expanded( Expanded(
child: PagingHelperView( child: PaginationList(
provider: provider, provider: provider,
futureRefreshable: provider.future, notifier: provider.notifier,
notifierRefreshable: provider.notifier,
contentBuilder:
(data, widgetCount, endItemView) => ListView.builder(
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
itemCount: widgetCount, itemBuilder: (context, index, reaction) {
itemBuilder: (context, index) {
if (index == widgetCount - 1) {
return endItemView;
}
final reaction = data.items[index];
return ListTile( return ListTile(
leading: AccountPfcGestureDetector( leading: AccountPfcGestureDetector(
uname: reaction.account?.name ?? 'unknown', uname: reaction.account?.name ?? 'unknown',
@@ -545,7 +537,6 @@ class ReactionDetailsPopup extends HookConsumerWidget {
}, },
), ),
), ),
),
], ],
), ),
), ),

View File

@@ -1,206 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'post_reaction_sheet.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$reactionListNotifierHash() =>
r'92cf80d2461e46ca62cf6e6a37f8b16c239e7449';
/// Copied from Dart SDK
class _SystemHash {
_SystemHash._();
static int combine(int hash, int value) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + value);
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
return hash ^ (hash >> 6);
}
static int finish(int hash) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
// ignore: parameter_assignments
hash = hash ^ (hash >> 11);
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
}
}
abstract class _$ReactionListNotifier
extends
BuildlessAutoDisposeAsyncNotifier<CursorPagingData<SnPostReaction>> {
late final String symbol;
late final String postId;
FutureOr<CursorPagingData<SnPostReaction>> build({
required String symbol,
required String postId,
});
}
/// See also [ReactionListNotifier].
@ProviderFor(ReactionListNotifier)
const reactionListNotifierProvider = ReactionListNotifierFamily();
/// See also [ReactionListNotifier].
class ReactionListNotifierFamily
extends Family<AsyncValue<CursorPagingData<SnPostReaction>>> {
/// See also [ReactionListNotifier].
const ReactionListNotifierFamily();
/// See also [ReactionListNotifier].
ReactionListNotifierProvider call({
required String symbol,
required String postId,
}) {
return ReactionListNotifierProvider(symbol: symbol, postId: postId);
}
@override
ReactionListNotifierProvider getProviderOverride(
covariant ReactionListNotifierProvider provider,
) {
return call(symbol: provider.symbol, postId: provider.postId);
}
static const Iterable<ProviderOrFamily>? _dependencies = null;
@override
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
@override
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
_allTransitiveDependencies;
@override
String? get name => r'reactionListNotifierProvider';
}
/// See also [ReactionListNotifier].
class ReactionListNotifierProvider
extends
AutoDisposeAsyncNotifierProviderImpl<
ReactionListNotifier,
CursorPagingData<SnPostReaction>
> {
/// See also [ReactionListNotifier].
ReactionListNotifierProvider({required String symbol, required String postId})
: this._internal(
() =>
ReactionListNotifier()
..symbol = symbol
..postId = postId,
from: reactionListNotifierProvider,
name: r'reactionListNotifierProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$reactionListNotifierHash,
dependencies: ReactionListNotifierFamily._dependencies,
allTransitiveDependencies:
ReactionListNotifierFamily._allTransitiveDependencies,
symbol: symbol,
postId: postId,
);
ReactionListNotifierProvider._internal(
super._createNotifier, {
required super.name,
required super.dependencies,
required super.allTransitiveDependencies,
required super.debugGetCreateSourceHash,
required super.from,
required this.symbol,
required this.postId,
}) : super.internal();
final String symbol;
final String postId;
@override
FutureOr<CursorPagingData<SnPostReaction>> runNotifierBuild(
covariant ReactionListNotifier notifier,
) {
return notifier.build(symbol: symbol, postId: postId);
}
@override
Override overrideWith(ReactionListNotifier Function() create) {
return ProviderOverride(
origin: this,
override: ReactionListNotifierProvider._internal(
() =>
create()
..symbol = symbol
..postId = postId,
from: from,
name: null,
dependencies: null,
allTransitiveDependencies: null,
debugGetCreateSourceHash: null,
symbol: symbol,
postId: postId,
),
);
}
@override
AutoDisposeAsyncNotifierProviderElement<
ReactionListNotifier,
CursorPagingData<SnPostReaction>
>
createElement() {
return _ReactionListNotifierProviderElement(this);
}
@override
bool operator ==(Object other) {
return other is ReactionListNotifierProvider &&
other.symbol == symbol &&
other.postId == postId;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, symbol.hashCode);
hash = _SystemHash.combine(hash, postId.hashCode);
return _SystemHash.finish(hash);
}
}
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
mixin ReactionListNotifierRef
on AutoDisposeAsyncNotifierProviderRef<CursorPagingData<SnPostReaction>> {
/// The parameter `symbol` of this provider.
String get symbol;
/// The parameter `postId` of this provider.
String get postId;
}
class _ReactionListNotifierProviderElement
extends
AutoDisposeAsyncNotifierProviderElement<
ReactionListNotifier,
CursorPagingData<SnPostReaction>
>
with ReactionListNotifierRef {
_ReactionListNotifierProviderElement(super.provider);
@override
String get symbol => (origin as ReactionListNotifierProvider).symbol;
@override
String get postId => (origin as ReactionListNotifierProvider).postId;
}
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package

View File

@@ -2,54 +2,30 @@ import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/models/post.dart'; import 'package:island/models/post.dart';
import 'package:island/pods/network.dart'; import 'package:island/pods/network.dart';
import 'package:island/pods/paging.dart';
import 'package:island/widgets/paging/pagination_list.dart';
import 'package:island/widgets/post/post_item.dart'; import 'package:island/widgets/post/post_item.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
import 'package:styled_widget/styled_widget.dart';
part 'post_replies.g.dart'; final postRepliesNotifierProvider = AsyncNotifierProvider.autoDispose
.family<PostRepliesNotifier, List<SnPost>, String>(PostRepliesNotifier.new);
@riverpod class PostRepliesNotifier
class PostRepliesNotifier extends _$PostRepliesNotifier extends AutoDisposeFamilyAsyncNotifier<List<SnPost>, String>
with CursorPagingNotifierMixin<SnPost> { with FamilyAsyncPaginationController<SnPost, String> {
static const int _pageSize = 20; static const int _pageSize = 20;
PostRepliesNotifier();
String? _postId;
@override @override
Future<CursorPagingData<SnPost>> build(String postId) { Future<List<SnPost>> fetch() async {
_postId = postId;
return fetch(cursor: null);
}
@override
Future<CursorPagingData<SnPost>> fetch({required String? cursor}) async {
if (_postId == null) {
throw StateError('PostRepliesNotifier must be initialized with postId');
}
final client = ref.read(apiClientProvider); final client = ref.read(apiClientProvider);
final offset = cursor == null ? 0 : int.parse(cursor);
final response = await client.get( final response = await client.get(
'/sphere/posts/$_postId/replies', '/sphere/posts/$arg/replies',
queryParameters: {'offset': offset, 'take': _pageSize}, queryParameters: {'offset': fetchedCount, 'take': _pageSize},
); );
final total = int.parse(response.headers.value('X-Total') ?? '0'); totalCount = int.parse(response.headers.value('X-Total') ?? '0');
final List<dynamic> data = response.data; final List<dynamic> data = response.data;
final posts = data.map((json) => SnPost.fromJson(json)).toList(); return data.map((json) => SnPost.fromJson(json)).toList();
final hasMore = offset + posts.length < total;
final nextCursor = hasMore ? (offset + posts.length).toString() : null;
return CursorPagingData(
items: posts,
hasMore: hasMore,
nextCursor: nextCursor,
);
} }
} }
@@ -66,37 +42,19 @@ class PostRepliesList extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
return PagingHelperSliverView( final provider = postRepliesNotifierProvider(postId);
provider: postRepliesNotifierProvider(postId),
futureRefreshable: postRepliesNotifierProvider(postId).future,
notifierRefreshable: postRepliesNotifierProvider(postId).notifier,
contentBuilder: (data, widgetCount, endItemView) {
if (data.items.isEmpty) {
return SliverToBoxAdapter(
child: Column(
children: [
Text(
'No replies',
textAlign: TextAlign.center,
).fontSize(18).bold(),
const Text('Why not start a discussion?'),
],
).padding(vertical: 16),
);
}
return SliverList.builder(
itemCount: widgetCount,
itemBuilder: (context, index) {
if (index == widgetCount - 1) {
return endItemView;
}
return PaginationList(
provider: provider,
notifier: provider.notifier,
isRefreshable: false,
isSliver: true,
itemBuilder: (context, index, item) {
final contentWidget = Card( final contentWidget = Card(
margin: EdgeInsets.symmetric(horizontal: 8, vertical: 4), margin: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
child: PostActionableItem( child: PostActionableItem(
borderRadius: 8, borderRadius: 8,
item: data.items[index], item: item,
isShowReference: false, isShowReference: false,
isEmbedOpenable: true, isEmbedOpenable: true,
onOpen: onOpen, onOpen: onOpen,
@@ -113,7 +71,5 @@ class PostRepliesList extends HookConsumerWidget {
); );
}, },
); );
},
);
} }
} }

View File

@@ -1,180 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'post_replies.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$postRepliesNotifierHash() =>
r'1cdda919249e3bf34459369e033ad5de8dbcf3f8';
/// Copied from Dart SDK
class _SystemHash {
_SystemHash._();
static int combine(int hash, int value) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + value);
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
return hash ^ (hash >> 6);
}
static int finish(int hash) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
// ignore: parameter_assignments
hash = hash ^ (hash >> 11);
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
}
}
abstract class _$PostRepliesNotifier
extends BuildlessAutoDisposeAsyncNotifier<CursorPagingData<SnPost>> {
late final String postId;
FutureOr<CursorPagingData<SnPost>> build(String postId);
}
/// See also [PostRepliesNotifier].
@ProviderFor(PostRepliesNotifier)
const postRepliesNotifierProvider = PostRepliesNotifierFamily();
/// See also [PostRepliesNotifier].
class PostRepliesNotifierFamily
extends Family<AsyncValue<CursorPagingData<SnPost>>> {
/// See also [PostRepliesNotifier].
const PostRepliesNotifierFamily();
/// See also [PostRepliesNotifier].
PostRepliesNotifierProvider call(String postId) {
return PostRepliesNotifierProvider(postId);
}
@override
PostRepliesNotifierProvider getProviderOverride(
covariant PostRepliesNotifierProvider provider,
) {
return call(provider.postId);
}
static const Iterable<ProviderOrFamily>? _dependencies = null;
@override
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
@override
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
_allTransitiveDependencies;
@override
String? get name => r'postRepliesNotifierProvider';
}
/// See also [PostRepliesNotifier].
class PostRepliesNotifierProvider
extends
AutoDisposeAsyncNotifierProviderImpl<
PostRepliesNotifier,
CursorPagingData<SnPost>
> {
/// See also [PostRepliesNotifier].
PostRepliesNotifierProvider(String postId)
: this._internal(
() => PostRepliesNotifier()..postId = postId,
from: postRepliesNotifierProvider,
name: r'postRepliesNotifierProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$postRepliesNotifierHash,
dependencies: PostRepliesNotifierFamily._dependencies,
allTransitiveDependencies:
PostRepliesNotifierFamily._allTransitiveDependencies,
postId: postId,
);
PostRepliesNotifierProvider._internal(
super._createNotifier, {
required super.name,
required super.dependencies,
required super.allTransitiveDependencies,
required super.debugGetCreateSourceHash,
required super.from,
required this.postId,
}) : super.internal();
final String postId;
@override
FutureOr<CursorPagingData<SnPost>> runNotifierBuild(
covariant PostRepliesNotifier notifier,
) {
return notifier.build(postId);
}
@override
Override overrideWith(PostRepliesNotifier Function() create) {
return ProviderOverride(
origin: this,
override: PostRepliesNotifierProvider._internal(
() => create()..postId = postId,
from: from,
name: null,
dependencies: null,
allTransitiveDependencies: null,
debugGetCreateSourceHash: null,
postId: postId,
),
);
}
@override
AutoDisposeAsyncNotifierProviderElement<
PostRepliesNotifier,
CursorPagingData<SnPost>
>
createElement() {
return _PostRepliesNotifierProviderElement(this);
}
@override
bool operator ==(Object other) {
return other is PostRepliesNotifierProvider && other.postId == postId;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, postId.hashCode);
return _SystemHash.finish(hash);
}
}
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
mixin PostRepliesNotifierRef
on AutoDisposeAsyncNotifierProviderRef<CursorPagingData<SnPost>> {
/// The parameter `postId` of this provider.
String get postId;
}
class _PostRepliesNotifierProviderElement
extends
AutoDisposeAsyncNotifierProviderElement<
PostRepliesNotifier,
CursorPagingData<SnPost>
>
with PostRepliesNotifierRef {
_PostRepliesNotifierProviderElement(super.provider);
@override
String get postId => (origin as PostRepliesNotifierProvider).postId;
}
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package

View File

@@ -14,9 +14,10 @@ class PostShuffleScreen extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final postListState = ref.watch(postListNotifierProvider(shuffle: true)); const params = PostListQuery(shuffle: true);
final postListState = ref.watch(postListNotifierProvider(params));
final postListNotifier = ref.watch( final postListNotifier = ref.watch(
postListNotifierProvider(shuffle: true).notifier, postListNotifierProvider(params).notifier,
); );
final cardSwiperController = useMemoized(() => CardSwiperController(), []); final cardSwiperController = useMemoized(() => CardSwiperController(), []);
@@ -37,12 +38,13 @@ class PostShuffleScreen extends HookConsumerWidget {
kBottomControlHeight + MediaQuery.of(context).padding.bottom, kBottomControlHeight + MediaQuery.of(context).padding.bottom,
), ),
child: Builder( child: Builder(
key: ValueKey(postListState.value?.items.length ?? 0), key: ValueKey(postListState.valueOrNull?.length ?? 0),
builder: (context) { builder: (context) {
if ((postListState.value?.items.length ?? 0) > 0) { final items = postListState.valueOrNull ?? [];
if (items.isNotEmpty) {
return CardSwiper( return CardSwiper(
controller: cardSwiperController, controller: cardSwiperController,
cardsCount: postListState.value!.items.length, cardsCount: items.length,
isLoop: false, isLoop: false,
cardBuilder: ( cardBuilder: (
context, context,
@@ -60,9 +62,7 @@ class PostShuffleScreen extends HookConsumerWidget {
borderRadius: const BorderRadius.all( borderRadius: const BorderRadius.all(
Radius.circular(8), Radius.circular(8),
), ),
child: PostActionableItem( child: PostActionableItem(item: items[index]),
item: postListState.value!.items[index],
),
), ),
), ),
), ),
@@ -70,8 +70,8 @@ class PostShuffleScreen extends HookConsumerWidget {
); );
}, },
onEnd: () async { onEnd: () async {
if (postListState.value?.hasMore ?? true) { if (!postListNotifier.fetchedAll) {
postListNotifier.forceRefresh(); postListNotifier.fetchFurther();
} }
}, },
); );

View File

@@ -3,53 +3,36 @@ import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.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/paging.dart';
import 'package:island/widgets/paging/pagination_list.dart';
import 'package:island/widgets/realm/realm_card.dart'; import 'package:island/widgets/realm/realm_card.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
import 'package:styled_widget/styled_widget.dart'; import 'package:styled_widget/styled_widget.dart';
part 'realm_list.g.dart'; final realmListNotifierProvider = AsyncNotifierProvider.autoDispose
.family<RealmListNotifier, List<SnRealm>, String?>(RealmListNotifier.new);
@riverpod class RealmListNotifier
class RealmListNotifier extends _$RealmListNotifier extends AutoDisposeFamilyAsyncNotifier<List<SnRealm>, String?>
with CursorPagingNotifierMixin<SnRealm> { with FamilyAsyncPaginationController<SnRealm, String?> {
static const int _pageSize = 20; static const int _pageSize = 20;
@override @override
Future<CursorPagingData<SnRealm>> build(String? query) { Future<List<SnRealm>> fetch() async {
return fetch(cursor: null, query: query);
}
@override
Future<CursorPagingData<SnRealm>> fetch({
required String? cursor,
String? query,
}) async {
final client = ref.read(apiClientProvider); final client = ref.read(apiClientProvider);
final offset = cursor == null ? 0 : int.parse(cursor);
final queryParams = { final queryParams = {
'offset': offset, 'offset': fetchedCount,
'take': _pageSize, 'take': _pageSize,
if (query != null && query.isNotEmpty) 'query': query, if (arg != null && arg!.isNotEmpty) 'query': arg,
}; };
final response = await client.get( final response = await client.get(
'/sphere/discovery/realms', '/sphere/discovery/realms',
queryParameters: queryParams, queryParameters: queryParams,
); );
final total = int.parse(response.headers.value('X-Total') ?? '0'); totalCount = int.parse(response.headers.value('X-Total') ?? '0');
final List<dynamic> data = response.data; final List<dynamic> data = response.data;
final realms = data.map((json) => SnRealm.fromJson(json)).toList(); return data.map((json) => SnRealm.fromJson(json)).toList();
final hasMore = offset + realms.length < total;
final nextCursor = hasMore ? (offset + realms.length).toString() : null;
return CursorPagingData(
items: realms,
hasMore: hasMore,
nextCursor: nextCursor,
);
} }
} }
@@ -60,34 +43,32 @@ class SliverRealmList extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
return PagingHelperSliverView( final provider = realmListNotifierProvider(query);
provider: realmListNotifierProvider(query), return PaginationList(
futureRefreshable: realmListNotifierProvider(query).future, provider: provider,
notifierRefreshable: realmListNotifierProvider(query).notifier, notifier: provider.notifier,
contentBuilder: isSliver: true,
(data, widgetCount, endItemView) => SliverList.separated( isRefreshable: false,
itemCount: widgetCount, itemBuilder: (context, index, realm) {
itemBuilder: (context, index) { return Column(
if (index == widgetCount - 1) { children: [
return endItemView; Padding(
} padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
final realm = data.items[index];
return Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 8,
),
child: child:
ConstrainedBox( ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 540), constraints: const BoxConstraints(maxWidth: 540),
child: RealmCard(realm: realm), child: RealmCard(realm: realm),
).center(), ).center(),
),
if (index <
(ref.read(provider).valueOrNull?.length ?? 0) -
1) // Add gap except for last item? Actually PaginationList handles loading indicator which might look like last item.
// Wait, ref.read(provider).valueOrNull?.length might change.
// Simpler to just add bottom padding to all, or Gap.
const Gap(8),
],
); );
}, },
separatorBuilder: (_, _) => const Gap(8),
),
); );
} }
} }

View File

@@ -1,179 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'realm_list.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$realmListNotifierHash() => r'8ae5c3ae2837acae4c7bf5e44578518afc9ea1a6';
/// Copied from Dart SDK
class _SystemHash {
_SystemHash._();
static int combine(int hash, int value) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + value);
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
return hash ^ (hash >> 6);
}
static int finish(int hash) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
// ignore: parameter_assignments
hash = hash ^ (hash >> 11);
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
}
}
abstract class _$RealmListNotifier
extends BuildlessAutoDisposeAsyncNotifier<CursorPagingData<SnRealm>> {
late final String? query;
FutureOr<CursorPagingData<SnRealm>> build(String? query);
}
/// See also [RealmListNotifier].
@ProviderFor(RealmListNotifier)
const realmListNotifierProvider = RealmListNotifierFamily();
/// See also [RealmListNotifier].
class RealmListNotifierFamily
extends Family<AsyncValue<CursorPagingData<SnRealm>>> {
/// See also [RealmListNotifier].
const RealmListNotifierFamily();
/// See also [RealmListNotifier].
RealmListNotifierProvider call(String? query) {
return RealmListNotifierProvider(query);
}
@override
RealmListNotifierProvider getProviderOverride(
covariant RealmListNotifierProvider provider,
) {
return call(provider.query);
}
static const Iterable<ProviderOrFamily>? _dependencies = null;
@override
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
@override
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
_allTransitiveDependencies;
@override
String? get name => r'realmListNotifierProvider';
}
/// See also [RealmListNotifier].
class RealmListNotifierProvider
extends
AutoDisposeAsyncNotifierProviderImpl<
RealmListNotifier,
CursorPagingData<SnRealm>
> {
/// See also [RealmListNotifier].
RealmListNotifierProvider(String? query)
: this._internal(
() => RealmListNotifier()..query = query,
from: realmListNotifierProvider,
name: r'realmListNotifierProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$realmListNotifierHash,
dependencies: RealmListNotifierFamily._dependencies,
allTransitiveDependencies:
RealmListNotifierFamily._allTransitiveDependencies,
query: query,
);
RealmListNotifierProvider._internal(
super._createNotifier, {
required super.name,
required super.dependencies,
required super.allTransitiveDependencies,
required super.debugGetCreateSourceHash,
required super.from,
required this.query,
}) : super.internal();
final String? query;
@override
FutureOr<CursorPagingData<SnRealm>> runNotifierBuild(
covariant RealmListNotifier notifier,
) {
return notifier.build(query);
}
@override
Override overrideWith(RealmListNotifier Function() create) {
return ProviderOverride(
origin: this,
override: RealmListNotifierProvider._internal(
() => create()..query = query,
from: from,
name: null,
dependencies: null,
allTransitiveDependencies: null,
debugGetCreateSourceHash: null,
query: query,
),
);
}
@override
AutoDisposeAsyncNotifierProviderElement<
RealmListNotifier,
CursorPagingData<SnRealm>
>
createElement() {
return _RealmListNotifierProviderElement(this);
}
@override
bool operator ==(Object other) {
return other is RealmListNotifierProvider && other.query == query;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, query.hashCode);
return _SystemHash.finish(hash);
}
}
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
mixin RealmListNotifierRef
on AutoDisposeAsyncNotifierProviderRef<CursorPagingData<SnRealm>> {
/// The parameter `query` of this provider.
String? get query;
}
class _RealmListNotifierProviderElement
extends
AutoDisposeAsyncNotifierProviderElement<
RealmListNotifier,
CursorPagingData<SnRealm>
>
with RealmListNotifierRef {
_RealmListNotifierProviderElement(super.provider);
@override
String? get query => (origin as RealmListNotifierProvider).query;
}
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package

View File

@@ -2,49 +2,34 @@ import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/models/thought.dart'; import 'package:island/models/thought.dart';
import 'package:island/pods/network.dart'; import 'package:island/pods/network.dart';
import 'package:island/pods/paging.dart';
import 'package:island/services/time.dart'; import 'package:island/services/time.dart';
import 'package:island/widgets/content/sheet.dart'; import 'package:island/widgets/content/sheet.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:island/widgets/paging/pagination_list.dart';
import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
part 'thought_sequence_list.g.dart'; final thoughtSequenceListNotifierProvider = AsyncNotifierProvider.autoDispose<
ThoughtSequenceListNotifier,
List<SnThinkingSequence>
>(ThoughtSequenceListNotifier.new);
@riverpod class ThoughtSequenceListNotifier
class ThoughtSequenceListNotifier extends _$ThoughtSequenceListNotifier extends AutoDisposeAsyncNotifier<List<SnThinkingSequence>>
with CursorPagingNotifierMixin<SnThinkingSequence> { with AutoDisposeAsyncPaginationController<SnThinkingSequence> {
static const int _pageSize = 20; static const int _pageSize = 20;
@override @override
Future<CursorPagingData<SnThinkingSequence>> build() { Future<List<SnThinkingSequence>> fetch() async {
return fetch(cursor: null);
}
@override
Future<CursorPagingData<SnThinkingSequence>> fetch({
required String? cursor,
}) async {
final client = ref.read(apiClientProvider); final client = ref.read(apiClientProvider);
final offset = cursor == null ? 0 : int.parse(cursor);
final queryParams = {'offset': offset, 'take': _pageSize}; final queryParams = {'offset': fetchedCount, 'take': _pageSize};
final response = await client.get( final response = await client.get(
'/insight/thought/sequences', '/insight/thought/sequences',
queryParameters: queryParams, queryParameters: queryParams,
); );
final total = int.parse(response.headers.value('X-Total') ?? '0'); totalCount = int.parse(response.headers.value('X-Total') ?? '0');
final List<dynamic> data = response.data; final List<dynamic> data = response.data;
final sequences = return data.map((json) => SnThinkingSequence.fromJson(json)).toList();
data.map((json) => SnThinkingSequence.fromJson(json)).toList();
final hasMore = offset + sequences.length < total;
final nextCursor = hasMore ? (offset + sequences.length).toString() : null;
return CursorPagingData(
items: sequences,
hasMore: hasMore,
nextCursor: nextCursor,
);
} }
} }
@@ -58,19 +43,10 @@ class ThoughtSequenceSelector extends HookConsumerWidget {
final provider = thoughtSequenceListNotifierProvider; final provider = thoughtSequenceListNotifierProvider;
return SheetScaffold( return SheetScaffold(
titleText: 'Select Conversation', titleText: 'Select Conversation',
child: PagingHelperView( child: PaginationList(
provider: provider, provider: provider,
futureRefreshable: provider.future, notifier: provider.notifier,
notifierRefreshable: provider.notifier, itemBuilder: (context, index, sequence) {
contentBuilder:
(data, widgetCount, endItemView) => ListView.builder(
itemCount: widgetCount,
itemBuilder: (context, index) {
if (index == widgetCount - 1) {
return endItemView;
}
final sequence = data.items[index];
return ListTile( return ListTile(
title: Text(sequence.topic ?? 'Untitled Conversation'), title: Text(sequence.topic ?? 'Untitled Conversation'),
subtitle: Text(sequence.createdAt.formatSystem()), subtitle: Text(sequence.createdAt.formatSystem()),
@@ -81,7 +57,6 @@ class ThoughtSequenceSelector extends HookConsumerWidget {
); );
}, },
), ),
),
); );
} }
} }

View File

@@ -1,31 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'thought_sequence_list.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$thoughtSequenceListNotifierHash() =>
r'fc619402d8691d523d19d3c7633adfada4956db4';
/// See also [ThoughtSequenceListNotifier].
@ProviderFor(ThoughtSequenceListNotifier)
final thoughtSequenceListNotifierProvider = AutoDisposeAsyncNotifierProvider<
ThoughtSequenceListNotifier,
CursorPagingData<SnThinkingSequence>
>.internal(
ThoughtSequenceListNotifier.new,
name: r'thoughtSequenceListNotifierProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$thoughtSequenceListNotifierHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$ThoughtSequenceListNotifier =
AutoDisposeAsyncNotifier<CursorPagingData<SnThinkingSequence>>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package