♻️ Continued to migrate list pagination

This commit is contained in:
2025-12-06 01:32:46 +08:00
parent c1fc8ea3fe
commit c4ac256896
14 changed files with 561 additions and 1478 deletions

View File

@@ -4,60 +4,51 @@ 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/poll/poll_editor.dart'; import 'package:island/screens/poll/poll_editor.dart';
import 'package:island/widgets/alert.dart'; import 'package:island/widgets/alert.dart';
import 'package:island/widgets/app_scaffold.dart'; import 'package:island/widgets/app_scaffold.dart';
import 'package:island/widgets/paging/pagination_list.dart';
import 'package:island/widgets/poll/poll_feedback.dart'; import 'package:island/widgets/poll/poll_feedback.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_annotation/riverpod_annotation.dart';
import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
import 'package:island/widgets/extended_refresh_indicator.dart'; import 'package:island/widgets/extended_refresh_indicator.dart';
import 'package:styled_widget/styled_widget.dart'; import 'package:styled_widget/styled_widget.dart';
part 'poll_list.g.dart'; part 'poll_list.g.dart';
@riverpod final pollListNotifierProvider = AsyncNotifierProvider.family.autoDispose(
class PollListNotifier extends _$PollListNotifier PollListNotifier.new,
with CursorPagingNotifierMixin<SnPollWithStats> { );
static const int _pageSize = 20;
class PollListNotifier
extends AutoDisposeFamilyAsyncNotifier<List<SnPollWithStats>, String?>
with FamilyAsyncPaginationController<SnPollWithStats, String?> {
static const int pageSize = 20;
@override @override
Future<CursorPagingData<SnPollWithStats>> build(String? pubName) { Future<List<SnPollWithStats>> fetch() async {
// immediately load first page
return fetch(cursor: null);
}
@override
Future<CursorPagingData<SnPollWithStats>> fetch({
required String? cursor,
}) async {
final client = ref.read(apiClientProvider); final client = ref.read(apiClientProvider);
final offset = cursor == null ? 0 : int.parse(cursor);
// read the current family argument passed to provider // read the current family argument passed to provider
final currentPub = pubName;
final queryParams = { final queryParams = {
'offset': offset, 'offset': fetchedCount.toString(),
'take': _pageSize, 'take': pageSize,
if (currentPub != null) 'pub': currentPub, if (arg != null) 'pub': arg,
}; };
final response = await client.get( final response = await client.get(
'/sphere/polls/me', '/sphere/polls/me',
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 items =
final items = data.map((json) => SnPollWithStats.fromJson(json)).toList(); response.data
.map((json) => SnPollWithStats.fromJson(json))
.cast<SnPollWithStats>()
.toList();
final hasMore = offset + items.length < total; return items;
final nextCursor = hasMore ? (offset + items.length).toString() : null;
return CursorPagingData(
items: items,
hasMore: hasMore,
nextCursor: nextCursor,
);
} }
} }
@@ -97,20 +88,11 @@ class CreatorPollListScreen extends HookConsumerWidget {
), ),
body: ExtendedRefreshIndicator( body: ExtendedRefreshIndicator(
onRefresh: () => ref.refresh(pollListNotifierProvider(pubName).future), onRefresh: () => ref.refresh(pollListNotifierProvider(pubName).future),
child: CustomScrollView( child: PaginationList(
slivers: [
PagingHelperSliverView(
provider: pollListNotifierProvider(pubName), provider: pollListNotifierProvider(pubName),
futureRefreshable: pollListNotifierProvider(pubName).future, notifier: pollListNotifierProvider(pubName).notifier,
notifierRefreshable: pollListNotifierProvider(pubName).notifier, padding: const EdgeInsets.only(top: 12),
contentBuilder: itemBuilder: (context, index, pollWithStats) {
(data, widgetCount, endItemView) => SliverList.builder(
itemCount: widgetCount,
itemBuilder: (context, index) {
if (index == widgetCount - 1) {
return endItemView;
}
final pollWithStats = data.items[index];
return ConstrainedBox( return ConstrainedBox(
constraints: BoxConstraints(maxWidth: 640), constraints: BoxConstraints(maxWidth: 640),
child: _CreatorPollItem( child: _CreatorPollItem(
@@ -121,9 +103,6 @@ class CreatorPollListScreen extends HookConsumerWidget {
}, },
), ),
), ),
],
),
),
); );
} }
} }

View File

@@ -148,154 +148,5 @@ class _PollWithStatsProviderElement
String get id => (origin as PollWithStatsProvider).id; String get id => (origin as PollWithStatsProvider).id;
} }
String _$pollListNotifierHash() => r'd5b822e737788be8982f5cb3b501d460441930c1';
abstract class _$PollListNotifier
extends
BuildlessAutoDisposeAsyncNotifier<CursorPagingData<SnPollWithStats>> {
late final String? pubName;
FutureOr<CursorPagingData<SnPollWithStats>> build(String? pubName);
}
/// See also [PollListNotifier].
@ProviderFor(PollListNotifier)
const pollListNotifierProvider = PollListNotifierFamily();
/// See also [PollListNotifier].
class PollListNotifierFamily
extends Family<AsyncValue<CursorPagingData<SnPollWithStats>>> {
/// See also [PollListNotifier].
const PollListNotifierFamily();
/// See also [PollListNotifier].
PollListNotifierProvider call(String? pubName) {
return PollListNotifierProvider(pubName);
}
@override
PollListNotifierProvider getProviderOverride(
covariant PollListNotifierProvider provider,
) {
return call(provider.pubName);
}
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'pollListNotifierProvider';
}
/// See also [PollListNotifier].
class PollListNotifierProvider
extends
AutoDisposeAsyncNotifierProviderImpl<
PollListNotifier,
CursorPagingData<SnPollWithStats>
> {
/// See also [PollListNotifier].
PollListNotifierProvider(String? pubName)
: this._internal(
() => PollListNotifier()..pubName = pubName,
from: pollListNotifierProvider,
name: r'pollListNotifierProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$pollListNotifierHash,
dependencies: PollListNotifierFamily._dependencies,
allTransitiveDependencies:
PollListNotifierFamily._allTransitiveDependencies,
pubName: pubName,
);
PollListNotifierProvider._internal(
super._createNotifier, {
required super.name,
required super.dependencies,
required super.allTransitiveDependencies,
required super.debugGetCreateSourceHash,
required super.from,
required this.pubName,
}) : super.internal();
final String? pubName;
@override
FutureOr<CursorPagingData<SnPollWithStats>> runNotifierBuild(
covariant PollListNotifier notifier,
) {
return notifier.build(pubName);
}
@override
Override overrideWith(PollListNotifier Function() create) {
return ProviderOverride(
origin: this,
override: PollListNotifierProvider._internal(
() => create()..pubName = pubName,
from: from,
name: null,
dependencies: null,
allTransitiveDependencies: null,
debugGetCreateSourceHash: null,
pubName: pubName,
),
);
}
@override
AutoDisposeAsyncNotifierProviderElement<
PollListNotifier,
CursorPagingData<SnPollWithStats>
>
createElement() {
return _PollListNotifierProviderElement(this);
}
@override
bool operator ==(Object other) {
return other is PollListNotifierProvider && other.pubName == pubName;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, pubName.hashCode);
return _SystemHash.finish(hash);
}
}
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
mixin PollListNotifierRef
on AutoDisposeAsyncNotifierProviderRef<CursorPagingData<SnPollWithStats>> {
/// The parameter `pubName` of this provider.
String? get pubName;
}
class _PollListNotifierProviderElement
extends
AutoDisposeAsyncNotifierProviderElement<
PollListNotifier,
CursorPagingData<SnPollWithStats>
>
with PollListNotifierRef {
_PollListNotifierProviderElement(super.provider);
@override
String? get pubName => (origin as PollListNotifierProvider).pubName;
}
// ignore_for_file: type=lint // 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 // 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

@@ -6,54 +6,43 @@ import 'package:google_fonts/google_fonts.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/models/publication_site.dart'; import 'package:island/models/publication_site.dart';
import 'package:island/pods/network.dart'; import 'package:island/pods/network.dart';
import 'package:island/pods/paging.dart';
import 'package:island/screens/creators/sites/site_edit.dart'; import 'package:island/screens/creators/sites/site_edit.dart';
import 'package:island/widgets/alert.dart'; import 'package:island/widgets/alert.dart';
import 'package:island/widgets/app_scaffold.dart'; import 'package:island/widgets/app_scaffold.dart';
import 'package:island/widgets/paging/pagination_list.dart';
import 'package: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:island/widgets/extended_refresh_indicator.dart'; import 'package:island/widgets/extended_refresh_indicator.dart';
import 'package:styled_widget/styled_widget.dart'; import 'package:styled_widget/styled_widget.dart';
part 'site_list.g.dart'; final siteListNotifierProvider = AsyncNotifierProvider.family.autoDispose(
SiteListNotifier.new,
);
@riverpod class SiteListNotifier
class SiteListNotifier extends _$SiteListNotifier extends AutoDisposeFamilyAsyncNotifier<List<SnPublicationSite>, String>
with CursorPagingNotifierMixin<SnPublicationSite> { with FamilyAsyncPaginationController<SnPublicationSite, String> {
static const int _pageSize = 20; static const int pageSize = 20;
@override @override
Future<CursorPagingData<SnPublicationSite>> build(String? pubName) { Future<List<SnPublicationSite>> fetch() async {
// immediately load first page
return fetch(cursor: null);
}
@override
Future<CursorPagingData<SnPublicationSite>> fetch({
required String? cursor,
}) async {
final client = ref.read(apiClientProvider); final client = ref.read(apiClientProvider);
final offset = cursor == null ? 0 : int.parse(cursor);
// read the current family argument passed to provider // read the current family argument passed to provider
final queryParams = {'offset': offset, 'take': _pageSize}; final queryParams = {'offset': fetchedCount.toString(), 'take': pageSize};
final response = await client.get( final response = await client.get(
'/zone/sites/$pubName', '/zone/sites/$arg',
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 items =
final items = data.map((json) => SnPublicationSite.fromJson(json)).toList(); response.data
.map((json) => SnPublicationSite.fromJson(json))
.cast<SnPublicationSite>()
.toList();
final hasMore = offset + items.length < total; return items;
final nextCursor = hasMore ? (offset + items.length).toString() : null;
return CursorPagingData(
items: items,
hasMore: hasMore,
nextCursor: nextCursor,
);
} }
} }
@@ -84,25 +73,16 @@ class CreatorSiteListScreen extends HookConsumerWidget {
child: CustomScrollView( child: CustomScrollView(
slivers: [ slivers: [
const SliverGap(8), const SliverGap(8),
PagingHelperSliverView( PaginationList(
provider: siteListNotifierProvider(pubName), provider: siteListNotifierProvider(pubName),
futureRefreshable: siteListNotifierProvider(pubName).future, notifier: siteListNotifierProvider(pubName).notifier,
notifierRefreshable: siteListNotifierProvider(pubName).notifier, itemBuilder: (context, index, site) {
contentBuilder:
(data, widgetCount, endItemView) => SliverList.builder(
itemCount: widgetCount,
itemBuilder: (context, index) {
if (index == widgetCount - 1) {
return endItemView;
}
final site = data.items[index];
return ConstrainedBox( return ConstrainedBox(
constraints: BoxConstraints(maxWidth: 640), constraints: BoxConstraints(maxWidth: 640),
child: _CreatorSiteItem(site: site, pubName: pubName), child: _CreatorSiteItem(site: site, pubName: pubName),
).center(); ).center();
}, },
), ),
),
], ],
), ),
), ),

View File

@@ -1,183 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'site_list.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$siteListNotifierHash() => r'1670cadcc0c7ccbd98bc33bbf5b4af21e9cb166c';
/// 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 _$SiteListNotifier
extends
BuildlessAutoDisposeAsyncNotifier<CursorPagingData<SnPublicationSite>> {
late final String? pubName;
FutureOr<CursorPagingData<SnPublicationSite>> build(String? pubName);
}
/// See also [SiteListNotifier].
@ProviderFor(SiteListNotifier)
const siteListNotifierProvider = SiteListNotifierFamily();
/// See also [SiteListNotifier].
class SiteListNotifierFamily
extends Family<AsyncValue<CursorPagingData<SnPublicationSite>>> {
/// See also [SiteListNotifier].
const SiteListNotifierFamily();
/// See also [SiteListNotifier].
SiteListNotifierProvider call(String? pubName) {
return SiteListNotifierProvider(pubName);
}
@override
SiteListNotifierProvider getProviderOverride(
covariant SiteListNotifierProvider provider,
) {
return call(provider.pubName);
}
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'siteListNotifierProvider';
}
/// See also [SiteListNotifier].
class SiteListNotifierProvider
extends
AutoDisposeAsyncNotifierProviderImpl<
SiteListNotifier,
CursorPagingData<SnPublicationSite>
> {
/// See also [SiteListNotifier].
SiteListNotifierProvider(String? pubName)
: this._internal(
() => SiteListNotifier()..pubName = pubName,
from: siteListNotifierProvider,
name: r'siteListNotifierProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$siteListNotifierHash,
dependencies: SiteListNotifierFamily._dependencies,
allTransitiveDependencies:
SiteListNotifierFamily._allTransitiveDependencies,
pubName: pubName,
);
SiteListNotifierProvider._internal(
super._createNotifier, {
required super.name,
required super.dependencies,
required super.allTransitiveDependencies,
required super.debugGetCreateSourceHash,
required super.from,
required this.pubName,
}) : super.internal();
final String? pubName;
@override
FutureOr<CursorPagingData<SnPublicationSite>> runNotifierBuild(
covariant SiteListNotifier notifier,
) {
return notifier.build(pubName);
}
@override
Override overrideWith(SiteListNotifier Function() create) {
return ProviderOverride(
origin: this,
override: SiteListNotifierProvider._internal(
() => create()..pubName = pubName,
from: from,
name: null,
dependencies: null,
allTransitiveDependencies: null,
debugGetCreateSourceHash: null,
pubName: pubName,
),
);
}
@override
AutoDisposeAsyncNotifierProviderElement<
SiteListNotifier,
CursorPagingData<SnPublicationSite>
>
createElement() {
return _SiteListNotifierProviderElement(this);
}
@override
bool operator ==(Object other) {
return other is SiteListNotifierProvider && other.pubName == pubName;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, pubName.hashCode);
return _SystemHash.finish(hash);
}
}
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
mixin SiteListNotifierRef
on
AutoDisposeAsyncNotifierProviderRef<
CursorPagingData<SnPublicationSite>
> {
/// The parameter `pubName` of this provider.
String? get pubName;
}
class _SiteListNotifierProviderElement
extends
AutoDisposeAsyncNotifierProviderElement<
SiteListNotifier,
CursorPagingData<SnPublicationSite>
>
with SiteListNotifierRef {
_SiteListNotifierProviderElement(super.provider);
@override
String? get pubName => (origin as SiteListNotifierProvider).pubName;
}
// 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

@@ -7,6 +7,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/models/file.dart'; import 'package:island/models/file.dart';
import 'package:island/models/sticker.dart'; import 'package:island/models/sticker.dart';
import 'package:island/pods/network.dart'; import 'package:island/pods/network.dart';
import 'package:island/pods/paging.dart';
import 'package:island/services/responsive.dart'; import 'package:island/services/responsive.dart';
import 'package:island/widgets/alert.dart'; import 'package:island/widgets/alert.dart';
import 'package:island/widgets/app_scaffold.dart'; import 'package:island/widgets/app_scaffold.dart';
@@ -14,10 +15,10 @@ import 'package:island/widgets/content/cloud_file_picker.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/screens/creators/stickers/pack_detail.dart'; import 'package:island/screens/creators/stickers/pack_detail.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_annotation/riverpod_annotation.dart';
import 'package:styled_widget/styled_widget.dart'; import 'package:styled_widget/styled_widget.dart';
import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
part 'stickers.g.dart'; part 'stickers.g.dart';
@@ -81,20 +82,10 @@ class SliverStickerPacksList extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
return PagingHelperView( return PaginationList(
provider: stickerPacksNotifierProvider(pubName), provider: stickerPacksNotifierProvider(pubName),
futureRefreshable: stickerPacksNotifierProvider(pubName).future, notifier: stickerPacksNotifierProvider(pubName).notifier,
notifierRefreshable: stickerPacksNotifierProvider(pubName).notifier, itemBuilder: (context, index, sticker) {
contentBuilder:
(data, widgetCount, endItemView) => ListView.builder(
padding: EdgeInsets.zero,
itemCount: widgetCount,
itemBuilder: (context, index) {
if (index == widgetCount - 1) {
return endItemView;
}
final sticker = data.items[index];
return ListTile( return ListTile(
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: const BorderRadius.all(Radius.circular(8)), borderRadius: const BorderRadius.all(Radius.circular(8)),
@@ -124,9 +115,7 @@ class SliverStickerPacksList extends HookConsumerWidget {
), ),
).then((value) { ).then((value) {
if (value != null) { if (value != null) {
ref.invalidate( ref.invalidate(stickerPackContentProvider(id));
stickerPackContentProvider(id),
);
} }
}); });
}, },
@@ -146,46 +135,41 @@ class SliverStickerPacksList extends HookConsumerWidget {
}, },
); );
}, },
),
); );
} }
} }
@riverpod final stickerPacksNotifierProvider = AsyncNotifierProvider.family.autoDispose(
class StickerPacksNotifier extends _$StickerPacksNotifier StickerPacksNotifier.new,
with CursorPagingNotifierMixin<SnStickerPack> { );
static const int _pageSize = 20;
class StickerPacksNotifier
extends AutoDisposeFamilyAsyncNotifier<List<SnStickerPack>, String>
with FamilyAsyncPaginationController<SnStickerPack, String> {
static const int pageSize = 20;
@override @override
Future<CursorPagingData<SnStickerPack>> build(String pubName) { Future<List<SnStickerPack>> fetch() async {
return fetch(cursor: null);
}
@override
Future<CursorPagingData<SnStickerPack>> fetch({
required String? cursor,
}) async {
final client = ref.read(apiClientProvider); final client = ref.read(apiClientProvider);
final offset = cursor == null ? 0 : int.parse(cursor);
try { try {
final response = await client.get( final response = await client.get(
'/sphere/stickers', '/sphere/stickers',
queryParameters: {'offset': offset, 'take': _pageSize, 'pub': pubName}, queryParameters: {
'offset': fetchedCount.toString(),
'take': pageSize,
'pub': arg,
},
); );
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 stickers =
final stickers = data.map((e) => SnStickerPack.fromJson(e)).toList(); response.data
.map((e) => SnStickerPack.fromJson(e))
.cast<SnStickerPack>()
.toList();
final hasMore = offset + stickers.length < total; return stickers;
final nextCursor = hasMore ? (offset + stickers.length).toString() : null;
return CursorPagingData(
items: stickers,
hasMore: hasMore,
nextCursor: nextCursor,
);
} catch (err) { } catch (err) {
rethrow; rethrow;
} }

View File

@@ -147,154 +147,5 @@ class _StickerPackProviderElement
String? get packId => (origin as StickerPackProvider).packId; String? get packId => (origin as StickerPackProvider).packId;
} }
String _$stickerPacksNotifierHash() =>
r'30024b35235f3085a5b1ec2204d0a974ee907e22';
abstract class _$StickerPacksNotifier
extends BuildlessAutoDisposeAsyncNotifier<CursorPagingData<SnStickerPack>> {
late final String pubName;
FutureOr<CursorPagingData<SnStickerPack>> build(String pubName);
}
/// See also [StickerPacksNotifier].
@ProviderFor(StickerPacksNotifier)
const stickerPacksNotifierProvider = StickerPacksNotifierFamily();
/// See also [StickerPacksNotifier].
class StickerPacksNotifierFamily
extends Family<AsyncValue<CursorPagingData<SnStickerPack>>> {
/// See also [StickerPacksNotifier].
const StickerPacksNotifierFamily();
/// See also [StickerPacksNotifier].
StickerPacksNotifierProvider call(String pubName) {
return StickerPacksNotifierProvider(pubName);
}
@override
StickerPacksNotifierProvider getProviderOverride(
covariant StickerPacksNotifierProvider provider,
) {
return call(provider.pubName);
}
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'stickerPacksNotifierProvider';
}
/// See also [StickerPacksNotifier].
class StickerPacksNotifierProvider
extends
AutoDisposeAsyncNotifierProviderImpl<
StickerPacksNotifier,
CursorPagingData<SnStickerPack>
> {
/// See also [StickerPacksNotifier].
StickerPacksNotifierProvider(String pubName)
: this._internal(
() => StickerPacksNotifier()..pubName = pubName,
from: stickerPacksNotifierProvider,
name: r'stickerPacksNotifierProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$stickerPacksNotifierHash,
dependencies: StickerPacksNotifierFamily._dependencies,
allTransitiveDependencies:
StickerPacksNotifierFamily._allTransitiveDependencies,
pubName: pubName,
);
StickerPacksNotifierProvider._internal(
super._createNotifier, {
required super.name,
required super.dependencies,
required super.allTransitiveDependencies,
required super.debugGetCreateSourceHash,
required super.from,
required this.pubName,
}) : super.internal();
final String pubName;
@override
FutureOr<CursorPagingData<SnStickerPack>> runNotifierBuild(
covariant StickerPacksNotifier notifier,
) {
return notifier.build(pubName);
}
@override
Override overrideWith(StickerPacksNotifier Function() create) {
return ProviderOverride(
origin: this,
override: StickerPacksNotifierProvider._internal(
() => create()..pubName = pubName,
from: from,
name: null,
dependencies: null,
allTransitiveDependencies: null,
debugGetCreateSourceHash: null,
pubName: pubName,
),
);
}
@override
AutoDisposeAsyncNotifierProviderElement<
StickerPacksNotifier,
CursorPagingData<SnStickerPack>
>
createElement() {
return _StickerPacksNotifierProviderElement(this);
}
@override
bool operator ==(Object other) {
return other is StickerPacksNotifierProvider && other.pubName == pubName;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, pubName.hashCode);
return _SystemHash.finish(hash);
}
}
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
mixin StickerPacksNotifierRef
on AutoDisposeAsyncNotifierProviderRef<CursorPagingData<SnStickerPack>> {
/// The parameter `pubName` of this provider.
String get pubName;
}
class _StickerPacksNotifierProviderElement
extends
AutoDisposeAsyncNotifierProviderElement<
StickerPacksNotifier,
CursorPagingData<SnStickerPack>
>
with StickerPacksNotifierRef {
_StickerPacksNotifierProviderElement(super.provider);
@override
String get pubName => (origin as StickerPacksNotifierProvider).pubName;
}
// ignore_for_file: type=lint // 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 // 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,41 +1,37 @@
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/webfeed.dart'; import 'package:island/models/webfeed.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/paging/pagination_list.dart';
import 'package:island/widgets/web_article_card.dart'; import 'package:island/widgets/web_article_card.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
part 'articles.g.dart'; part 'articles.g.dart';
part 'articles.freezed.dart';
@riverpod @freezed
class ArticlesListNotifier extends _$ArticlesListNotifier sealed class ArticleListQuery with _$ArticleListQuery {
with CursorPagingNotifierMixin<SnWebArticle> { const factory ArticleListQuery({String? feedId, String? publisherId}) =
static const int _pageSize = 20; _ArticleListQuery;
}
Map<String, dynamic> _params = {}; final articlesListNotifierProvider = AsyncNotifierProvider.family.autoDispose(
ArticlesListNotifier.new,
);
class ArticlesListNotifier
extends AutoDisposeFamilyAsyncNotifier<List<SnWebArticle>, ArticleListQuery>
with FamilyAsyncPaginationController<SnWebArticle, ArticleListQuery> {
static const int pageSize = 20;
@override @override
Future<CursorPagingData<SnWebArticle>> build({ Future<List<SnWebArticle>> fetch() async {
String? feedId,
String? publisherId,
}) async {
_params = {
if (feedId != null) 'feedId': feedId,
if (publisherId != null) 'publisherId': publisherId,
};
return fetch(cursor: null);
}
@override
Future<CursorPagingData<SnWebArticle>> 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 = {'limit': _pageSize, 'offset': offset, ..._params}; final queryParams = {'limit': pageSize, 'offset': fetchedCount.toString()};
try { try {
final response = await client.get( final response = await client.get(
@@ -43,23 +39,17 @@ class ArticlesListNotifier extends _$ArticlesListNotifier
queryParameters: queryParams, queryParameters: queryParams,
); );
final List<dynamic> data = response.data;
final articles = final articles =
data response.data
.map( .map(
(json) => SnWebArticle.fromJson(json as Map<String, dynamic>), (json) => SnWebArticle.fromJson(json as Map<String, dynamic>),
) )
.cast<SnWebArticle>()
.toList(); .toList();
final total = int.tryParse(response.headers.value('X-Total') ?? '0') ?? 0; totalCount = int.tryParse(response.headers.value('X-Total') ?? '0') ?? 0;
final hasMore = offset + articles.length < total;
final nextCursor = hasMore ? (offset + articles.length).toString() : null;
return CursorPagingData( return articles;
items: articles,
hasMore: hasMore,
nextCursor: nextCursor,
);
} catch (e) { } catch (e) {
debugPrint('Error fetching articles: $e'); debugPrint('Error fetching articles: $e');
rethrow; rethrow;
@@ -85,34 +75,17 @@ class SliverArticlesList extends ConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
return PagingHelperSliverView( final provider = articlesListNotifierProvider(
provider: articlesListNotifierProvider( ArticleListQuery(feedId: feedId, publisherId: publisherId),
feedId: feedId, );
publisherId: publisherId, return PaginationList(
), provider: provider,
futureRefreshable: notifier: provider.notifier,
articlesListNotifierProvider( isRefreshable: false,
feedId: feedId, isSliver: true,
publisherId: publisherId, itemBuilder: (context, index, article) {
).future,
notifierRefreshable:
articlesListNotifierProvider(
feedId: feedId,
publisherId: publisherId,
).notifier,
contentBuilder:
(data, widgetCount, endItemView) => SliverList.separated(
itemCount: widgetCount,
itemBuilder: (context, index) {
if (index == widgetCount - 1) {
return endItemView;
}
final article = data.items[index];
return WebArticleCard(article: article, showDetails: true); return WebArticleCard(article: article, showDetails: true);
}, },
separatorBuilder: (context, index) => const SizedBox(height: 12),
),
); );
} }
} }

View File

@@ -0,0 +1,268 @@
// 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 'articles.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$ArticleListQuery {
String? get feedId; String? get publisherId;
/// Create a copy of ArticleListQuery
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$ArticleListQueryCopyWith<ArticleListQuery> get copyWith => _$ArticleListQueryCopyWithImpl<ArticleListQuery>(this as ArticleListQuery, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is ArticleListQuery&&(identical(other.feedId, feedId) || other.feedId == feedId)&&(identical(other.publisherId, publisherId) || other.publisherId == publisherId));
}
@override
int get hashCode => Object.hash(runtimeType,feedId,publisherId);
@override
String toString() {
return 'ArticleListQuery(feedId: $feedId, publisherId: $publisherId)';
}
}
/// @nodoc
abstract mixin class $ArticleListQueryCopyWith<$Res> {
factory $ArticleListQueryCopyWith(ArticleListQuery value, $Res Function(ArticleListQuery) _then) = _$ArticleListQueryCopyWithImpl;
@useResult
$Res call({
String? feedId, String? publisherId
});
}
/// @nodoc
class _$ArticleListQueryCopyWithImpl<$Res>
implements $ArticleListQueryCopyWith<$Res> {
_$ArticleListQueryCopyWithImpl(this._self, this._then);
final ArticleListQuery _self;
final $Res Function(ArticleListQuery) _then;
/// Create a copy of ArticleListQuery
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? feedId = freezed,Object? publisherId = freezed,}) {
return _then(_self.copyWith(
feedId: freezed == feedId ? _self.feedId : feedId // ignore: cast_nullable_to_non_nullable
as String?,publisherId: freezed == publisherId ? _self.publisherId : publisherId // ignore: cast_nullable_to_non_nullable
as String?,
));
}
}
/// Adds pattern-matching-related methods to [ArticleListQuery].
extension ArticleListQueryPatterns on ArticleListQuery {
/// 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( _ArticleListQuery value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _ArticleListQuery() 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( _ArticleListQuery value) $default,){
final _that = this;
switch (_that) {
case _ArticleListQuery():
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( _ArticleListQuery value)? $default,){
final _that = this;
switch (_that) {
case _ArticleListQuery() 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? feedId, String? publisherId)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _ArticleListQuery() when $default != null:
return $default(_that.feedId,_that.publisherId);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? feedId, String? publisherId) $default,) {final _that = this;
switch (_that) {
case _ArticleListQuery():
return $default(_that.feedId,_that.publisherId);}
}
/// 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? feedId, String? publisherId)? $default,) {final _that = this;
switch (_that) {
case _ArticleListQuery() when $default != null:
return $default(_that.feedId,_that.publisherId);case _:
return null;
}
}
}
/// @nodoc
class _ArticleListQuery implements ArticleListQuery {
const _ArticleListQuery({this.feedId, this.publisherId});
@override final String? feedId;
@override final String? publisherId;
/// Create a copy of ArticleListQuery
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$ArticleListQueryCopyWith<_ArticleListQuery> get copyWith => __$ArticleListQueryCopyWithImpl<_ArticleListQuery>(this, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _ArticleListQuery&&(identical(other.feedId, feedId) || other.feedId == feedId)&&(identical(other.publisherId, publisherId) || other.publisherId == publisherId));
}
@override
int get hashCode => Object.hash(runtimeType,feedId,publisherId);
@override
String toString() {
return 'ArticleListQuery(feedId: $feedId, publisherId: $publisherId)';
}
}
/// @nodoc
abstract mixin class _$ArticleListQueryCopyWith<$Res> implements $ArticleListQueryCopyWith<$Res> {
factory _$ArticleListQueryCopyWith(_ArticleListQuery value, $Res Function(_ArticleListQuery) _then) = __$ArticleListQueryCopyWithImpl;
@override @useResult
$Res call({
String? feedId, String? publisherId
});
}
/// @nodoc
class __$ArticleListQueryCopyWithImpl<$Res>
implements _$ArticleListQueryCopyWith<$Res> {
__$ArticleListQueryCopyWithImpl(this._self, this._then);
final _ArticleListQuery _self;
final $Res Function(_ArticleListQuery) _then;
/// Create a copy of ArticleListQuery
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? feedId = freezed,Object? publisherId = freezed,}) {
return _then(_ArticleListQuery(
feedId: freezed == feedId ? _self.feedId : feedId // ignore: cast_nullable_to_non_nullable
as String?,publisherId: freezed == publisherId ? _self.publisherId : publisherId // ignore: cast_nullable_to_non_nullable
as String?,
));
}
}
// dart format on

View File

@@ -25,201 +25,5 @@ final subscribedFeedsProvider =
@Deprecated('Will be removed in 3.0. Use Ref instead') @Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element // ignore: unused_element
typedef SubscribedFeedsRef = AutoDisposeFutureProviderRef<List<SnWebFeed>>; typedef SubscribedFeedsRef = AutoDisposeFutureProviderRef<List<SnWebFeed>>;
String _$articlesListNotifierHash() =>
r'579741af4d90c7c81f2e2697e57c4895b7a9dabc';
/// 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 _$ArticlesListNotifier
extends BuildlessAutoDisposeAsyncNotifier<CursorPagingData<SnWebArticle>> {
late final String? feedId;
late final String? publisherId;
FutureOr<CursorPagingData<SnWebArticle>> build({
String? feedId,
String? publisherId,
});
}
/// See also [ArticlesListNotifier].
@ProviderFor(ArticlesListNotifier)
const articlesListNotifierProvider = ArticlesListNotifierFamily();
/// See also [ArticlesListNotifier].
class ArticlesListNotifierFamily
extends Family<AsyncValue<CursorPagingData<SnWebArticle>>> {
/// See also [ArticlesListNotifier].
const ArticlesListNotifierFamily();
/// See also [ArticlesListNotifier].
ArticlesListNotifierProvider call({String? feedId, String? publisherId}) {
return ArticlesListNotifierProvider(
feedId: feedId,
publisherId: publisherId,
);
}
@override
ArticlesListNotifierProvider getProviderOverride(
covariant ArticlesListNotifierProvider provider,
) {
return call(feedId: provider.feedId, publisherId: provider.publisherId);
}
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'articlesListNotifierProvider';
}
/// See also [ArticlesListNotifier].
class ArticlesListNotifierProvider
extends
AutoDisposeAsyncNotifierProviderImpl<
ArticlesListNotifier,
CursorPagingData<SnWebArticle>
> {
/// See also [ArticlesListNotifier].
ArticlesListNotifierProvider({String? feedId, String? publisherId})
: this._internal(
() =>
ArticlesListNotifier()
..feedId = feedId
..publisherId = publisherId,
from: articlesListNotifierProvider,
name: r'articlesListNotifierProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$articlesListNotifierHash,
dependencies: ArticlesListNotifierFamily._dependencies,
allTransitiveDependencies:
ArticlesListNotifierFamily._allTransitiveDependencies,
feedId: feedId,
publisherId: publisherId,
);
ArticlesListNotifierProvider._internal(
super._createNotifier, {
required super.name,
required super.dependencies,
required super.allTransitiveDependencies,
required super.debugGetCreateSourceHash,
required super.from,
required this.feedId,
required this.publisherId,
}) : super.internal();
final String? feedId;
final String? publisherId;
@override
FutureOr<CursorPagingData<SnWebArticle>> runNotifierBuild(
covariant ArticlesListNotifier notifier,
) {
return notifier.build(feedId: feedId, publisherId: publisherId);
}
@override
Override overrideWith(ArticlesListNotifier Function() create) {
return ProviderOverride(
origin: this,
override: ArticlesListNotifierProvider._internal(
() =>
create()
..feedId = feedId
..publisherId = publisherId,
from: from,
name: null,
dependencies: null,
allTransitiveDependencies: null,
debugGetCreateSourceHash: null,
feedId: feedId,
publisherId: publisherId,
),
);
}
@override
AutoDisposeAsyncNotifierProviderElement<
ArticlesListNotifier,
CursorPagingData<SnWebArticle>
>
createElement() {
return _ArticlesListNotifierProviderElement(this);
}
@override
bool operator ==(Object other) {
return other is ArticlesListNotifierProvider &&
other.feedId == feedId &&
other.publisherId == publisherId;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, feedId.hashCode);
hash = _SystemHash.combine(hash, publisherId.hashCode);
return _SystemHash.finish(hash);
}
}
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
mixin ArticlesListNotifierRef
on AutoDisposeAsyncNotifierProviderRef<CursorPagingData<SnWebArticle>> {
/// The parameter `feedId` of this provider.
String? get feedId;
/// The parameter `publisherId` of this provider.
String? get publisherId;
}
class _ArticlesListNotifierProviderElement
extends
AutoDisposeAsyncNotifierProviderElement<
ArticlesListNotifier,
CursorPagingData<SnWebArticle>
>
with ArticlesListNotifierRef {
_ArticlesListNotifierProviderElement(super.provider);
@override
String? get feedId => (origin as ArticlesListNotifierProvider).feedId;
@override
String? get publisherId =>
(origin as ArticlesListNotifierProvider).publisherId;
}
// ignore_for_file: type=lint // 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 // 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,17 +1,16 @@
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:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/models/webfeed.dart'; import 'package:island/models/webfeed.dart';
import 'package:island/pods/network.dart'; import 'package:island/pods/network.dart';
import 'package:island/pods/paging.dart';
import 'package:island/widgets/alert.dart'; import 'package:island/widgets/alert.dart';
import 'package:island/widgets/app_scaffold.dart'; import 'package:island/widgets/app_scaffold.dart';
import 'package:island/widgets/paging/pagination_list.dart';
import 'package:island/widgets/web_article_card.dart'; import 'package:island/widgets/web_article_card.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_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 'feed_detail.g.dart'; part 'feed_detail.g.dart';
@@ -23,52 +22,32 @@ Future<SnWebFeed> marketplaceWebFeed(Ref ref, String feedId) async {
return SnWebFeed.fromJson(resp.data); return SnWebFeed.fromJson(resp.data);
} }
/// Provider for web feed articles content final marketplaceWebFeedContentNotifierProvider = AsyncNotifierProvider.family
@riverpod .autoDispose(MarketplaceWebFeedContentNotifier.new);
class MarketplaceWebFeedContentNotifier class MarketplaceWebFeedContentNotifier
extends _$MarketplaceWebFeedContentNotifier extends AutoDisposeFamilyAsyncNotifier<List<SnWebArticle>, String>
with CursorPagingNotifierMixin<SnWebArticle> { with FamilyAsyncPaginationController<SnWebArticle, String> {
static const int _pageSize = 20; static const int pageSize = 20;
@override @override
Future<CursorPagingData<SnWebArticle>> build(String feedId) async { Future<List<SnWebArticle>> fetch() async {
_feedId = feedId;
return fetch(cursor: null);
}
late final String _feedId;
ValueNotifier<int> totalCount = ValueNotifier(0);
@override
Future<CursorPagingData<SnWebArticle>> 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.toString(), 'take': pageSize};
final response = await client.get( final response = await client.get(
'/sphere/feeds/$_feedId/articles', '/sphere/feeds/$arg/articles',
queryParameters: queryParams, queryParameters: queryParams,
); );
final total = int.parse(response.headers.value('X-Total') ?? '0'); totalCount = int.parse(response.headers.value('X-Total') ?? '0');
totalCount.value = total; final articles =
final List<dynamic> data = response.data; response.data
final articles = data.map((json) => SnWebArticle.fromJson(json)).toList(); .map((json) => SnWebArticle.fromJson(json))
.cast<SnWebArticle>()
.toList();
final hasMore = offset + articles.length < total; return articles;
final nextCursor = hasMore ? (offset + articles.length).toString() : null;
return CursorPagingData(
items: articles,
hasMore: hasMore,
nextCursor: nextCursor,
);
}
void dispose() {
totalCount.dispose();
} }
} }
@@ -126,10 +105,6 @@ class MarketplaceWebFeedDetailScreen extends HookConsumerWidget {
marketplaceWebFeedContentNotifierProvider(id).notifier, marketplaceWebFeedContentNotifierProvider(id).notifier,
); );
useEffect(() {
return feedNotifier.dispose;
}, []);
return AppScaffold( return AppScaffold(
appBar: AppBar(title: Text(feed.value?.title ?? 'loading'.tr())), appBar: AppBar(title: Text(feed.value?.title ?? 'loading'.tr())),
body: Column( body: Column(
@@ -147,13 +122,9 @@ class MarketplaceWebFeedDetailScreen extends HookConsumerWidget {
spacing: 4, spacing: 4,
children: [ children: [
const Icon(Symbols.rss_feed, size: 16), const Icon(Symbols.rss_feed, size: 16),
ListenableBuilder( Text(
listenable: feedNotifier.totalCount,
builder:
(context, _) => Text(
'webFeedArticleCount'.plural( 'webFeedArticleCount'.plural(
feedNotifier.totalCount.value, feedNotifier.totalCount ?? 0,
),
), ),
), ),
], ],
@@ -174,29 +145,12 @@ class MarketplaceWebFeedDetailScreen extends HookConsumerWidget {
const Divider(height: 1), const Divider(height: 1),
// Articles list // Articles list
Expanded( Expanded(
child: PagingHelperView( child: PaginationList(
provider: marketplaceWebFeedContentNotifierProvider(id), provider: marketplaceWebFeedContentNotifierProvider(id),
futureRefreshable: notifier: marketplaceWebFeedContentNotifierProvider(id).notifier,
marketplaceWebFeedContentNotifierProvider(id).future, itemBuilder: (context, index, article) {
notifierRefreshable:
marketplaceWebFeedContentNotifierProvider(id).notifier,
contentBuilder:
(data, widgetCount, endItemView) => ListView.separated(
padding: const EdgeInsets.symmetric(
horizontal: 24,
vertical: 20,
),
itemCount: widgetCount,
itemBuilder: (context, index) {
if (index == widgetCount - 1) {
return endItemView;
}
final article = data.items[index];
return WebArticleCard(article: article); return WebArticleCard(article: article);
}, },
separatorBuilder: (context, index) => const Gap(12),
),
), ),
), ),
Container( Container(

View File

@@ -290,169 +290,5 @@ class _MarketplaceWebFeedSubscriptionProviderElement
(origin as MarketplaceWebFeedSubscriptionProvider).feedId; (origin as MarketplaceWebFeedSubscriptionProvider).feedId;
} }
String _$marketplaceWebFeedContentNotifierHash() =>
r'25688082884cb824eeff300888ba38c9748295dc';
abstract class _$MarketplaceWebFeedContentNotifier
extends BuildlessAutoDisposeAsyncNotifier<CursorPagingData<SnWebArticle>> {
late final String feedId;
FutureOr<CursorPagingData<SnWebArticle>> build(String feedId);
}
/// Provider for web feed articles content
///
/// Copied from [MarketplaceWebFeedContentNotifier].
@ProviderFor(MarketplaceWebFeedContentNotifier)
const marketplaceWebFeedContentNotifierProvider =
MarketplaceWebFeedContentNotifierFamily();
/// Provider for web feed articles content
///
/// Copied from [MarketplaceWebFeedContentNotifier].
class MarketplaceWebFeedContentNotifierFamily
extends Family<AsyncValue<CursorPagingData<SnWebArticle>>> {
/// Provider for web feed articles content
///
/// Copied from [MarketplaceWebFeedContentNotifier].
const MarketplaceWebFeedContentNotifierFamily();
/// Provider for web feed articles content
///
/// Copied from [MarketplaceWebFeedContentNotifier].
MarketplaceWebFeedContentNotifierProvider call(String feedId) {
return MarketplaceWebFeedContentNotifierProvider(feedId);
}
@override
MarketplaceWebFeedContentNotifierProvider getProviderOverride(
covariant MarketplaceWebFeedContentNotifierProvider provider,
) {
return call(provider.feedId);
}
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'marketplaceWebFeedContentNotifierProvider';
}
/// Provider for web feed articles content
///
/// Copied from [MarketplaceWebFeedContentNotifier].
class MarketplaceWebFeedContentNotifierProvider
extends
AutoDisposeAsyncNotifierProviderImpl<
MarketplaceWebFeedContentNotifier,
CursorPagingData<SnWebArticle>
> {
/// Provider for web feed articles content
///
/// Copied from [MarketplaceWebFeedContentNotifier].
MarketplaceWebFeedContentNotifierProvider(String feedId)
: this._internal(
() => MarketplaceWebFeedContentNotifier()..feedId = feedId,
from: marketplaceWebFeedContentNotifierProvider,
name: r'marketplaceWebFeedContentNotifierProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$marketplaceWebFeedContentNotifierHash,
dependencies: MarketplaceWebFeedContentNotifierFamily._dependencies,
allTransitiveDependencies:
MarketplaceWebFeedContentNotifierFamily._allTransitiveDependencies,
feedId: feedId,
);
MarketplaceWebFeedContentNotifierProvider._internal(
super._createNotifier, {
required super.name,
required super.dependencies,
required super.allTransitiveDependencies,
required super.debugGetCreateSourceHash,
required super.from,
required this.feedId,
}) : super.internal();
final String feedId;
@override
FutureOr<CursorPagingData<SnWebArticle>> runNotifierBuild(
covariant MarketplaceWebFeedContentNotifier notifier,
) {
return notifier.build(feedId);
}
@override
Override overrideWith(MarketplaceWebFeedContentNotifier Function() create) {
return ProviderOverride(
origin: this,
override: MarketplaceWebFeedContentNotifierProvider._internal(
() => create()..feedId = feedId,
from: from,
name: null,
dependencies: null,
allTransitiveDependencies: null,
debugGetCreateSourceHash: null,
feedId: feedId,
),
);
}
@override
AutoDisposeAsyncNotifierProviderElement<
MarketplaceWebFeedContentNotifier,
CursorPagingData<SnWebArticle>
>
createElement() {
return _MarketplaceWebFeedContentNotifierProviderElement(this);
}
@override
bool operator ==(Object other) {
return other is MarketplaceWebFeedContentNotifierProvider &&
other.feedId == feedId;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, feedId.hashCode);
return _SystemHash.finish(hash);
}
}
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
mixin MarketplaceWebFeedContentNotifierRef
on AutoDisposeAsyncNotifierProviderRef<CursorPagingData<SnWebArticle>> {
/// The parameter `feedId` of this provider.
String get feedId;
}
class _MarketplaceWebFeedContentNotifierProviderElement
extends
AutoDisposeAsyncNotifierProviderElement<
MarketplaceWebFeedContentNotifier,
CursorPagingData<SnWebArticle>
>
with MarketplaceWebFeedContentNotifierRef {
_MarketplaceWebFeedContentNotifierProviderElement(super.provider);
@override
String get feedId =>
(origin as MarketplaceWebFeedContentNotifierProvider).feedId;
}
// ignore_for_file: type=lint // 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 // 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,3 +1,4 @@
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:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
@@ -6,52 +7,44 @@ import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/models/webfeed.dart'; import 'package:island/models/webfeed.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/paging/pagination_list.dart';
import 'package:material_symbols_icons/symbols.dart'; import 'package:material_symbols_icons/symbols.dart';
import 'dart:async';
import 'package:riverpod_annotation/riverpod_annotation.dart'; final marketplaceWebFeedsNotifierProvider = AsyncNotifierProvider(
import 'package:riverpod_paging_utils/riverpod_paging_utils.dart'; MarketplaceWebFeedsNotifier.new,
);
part 'feed_marketplace.g.dart'; class MarketplaceWebFeedsNotifier extends AsyncNotifier<List<SnWebFeed>>
with
@riverpod AsyncPaginationController<SnWebFeed>,
class MarketplaceWebFeedsNotifier extends _$MarketplaceWebFeedsNotifier AsyncPaginationFilter<String?, SnWebFeed> {
with CursorPagingNotifierMixin<SnWebFeed> { @override
String? _query; String? currentFilter;
@override @override
Future<CursorPagingData<SnWebFeed>> build({required String? query}) { Future<List<SnWebFeed>> fetch() async {
_query = query;
return fetch(cursor: null);
}
@override
Future<CursorPagingData<SnWebFeed>> 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/feeds/explore', '/sphere/feeds/explore',
queryParameters: { queryParameters: {
'offset': offset, 'offset': fetchedCount.toString(),
'take': 20, 'take': 20,
if (_query != null && _query!.isNotEmpty) 'query': _query, if (currentFilter != null && currentFilter!.isNotEmpty)
'query': currentFilter,
}, },
); );
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 feeds =
final feeds = data.map((e) => SnWebFeed.fromJson(e)).toList(); response.data
.map((e) => SnWebFeed.fromJson(e))
.cast<SnWebFeed>()
.toList();
final hasMore = offset + feeds.length < total; return feeds;
final nextCursor = hasMore ? (offset + feeds.length).toString() : null;
return CursorPagingData(
items: feeds,
hasMore: hasMore,
nextCursor: nextCursor,
);
} }
} }
@@ -86,16 +79,8 @@ class MarketplaceWebFeedsScreen extends HookConsumerWidget {
title: const Text('webFeeds').tr(), title: const Text('webFeeds').tr(),
actions: const [Gap(8)], actions: const [Gap(8)],
), ),
body: PagingHelperView( body: Column(
provider: marketplaceWebFeedsNotifierProvider(query: query.value),
futureRefreshable:
marketplaceWebFeedsNotifierProvider(query: query.value).future,
notifierRefreshable:
marketplaceWebFeedsNotifierProvider(query: query.value).notifier,
contentBuilder:
(data, widgetCount, endItemView) => Column(
children: [ children: [
// Search bar above the list
Padding( Padding(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
child: SearchBar( child: SearchBar(
@@ -137,15 +122,11 @@ class MarketplaceWebFeedsScreen extends HookConsumerWidget {
), ),
), ),
Expanded( Expanded(
child: ListView.builder( child: PaginationList(
provider: marketplaceWebFeedsNotifierProvider,
notifier: marketplaceWebFeedsNotifierProvider.notifier,
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
itemCount: widgetCount, itemBuilder: (context, index, feed) {
itemBuilder: (context, index) {
if (index == widgetCount - 1) {
return endItemView;
}
final feed = data.items[index];
return ListTile( return ListTile(
title: Text(feed.title), title: Text(feed.title),
subtitle: Text(feed.description ?? ''), subtitle: Text(feed.description ?? ''),
@@ -163,7 +144,6 @@ class MarketplaceWebFeedsScreen extends HookConsumerWidget {
), ),
], ],
), ),
),
); );
} }
} }

View File

@@ -1,180 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'feed_marketplace.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$marketplaceWebFeedsNotifierHash() =>
r'774b2985f2f7d61fe958f534f84e39f814327c4e';
/// 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 _$MarketplaceWebFeedsNotifier
extends BuildlessAutoDisposeAsyncNotifier<CursorPagingData<SnWebFeed>> {
late final String? query;
FutureOr<CursorPagingData<SnWebFeed>> build({required String? query});
}
/// See also [MarketplaceWebFeedsNotifier].
@ProviderFor(MarketplaceWebFeedsNotifier)
const marketplaceWebFeedsNotifierProvider = MarketplaceWebFeedsNotifierFamily();
/// See also [MarketplaceWebFeedsNotifier].
class MarketplaceWebFeedsNotifierFamily
extends Family<AsyncValue<CursorPagingData<SnWebFeed>>> {
/// See also [MarketplaceWebFeedsNotifier].
const MarketplaceWebFeedsNotifierFamily();
/// See also [MarketplaceWebFeedsNotifier].
MarketplaceWebFeedsNotifierProvider call({required String? query}) {
return MarketplaceWebFeedsNotifierProvider(query: query);
}
@override
MarketplaceWebFeedsNotifierProvider getProviderOverride(
covariant MarketplaceWebFeedsNotifierProvider provider,
) {
return call(query: 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'marketplaceWebFeedsNotifierProvider';
}
/// See also [MarketplaceWebFeedsNotifier].
class MarketplaceWebFeedsNotifierProvider
extends
AutoDisposeAsyncNotifierProviderImpl<
MarketplaceWebFeedsNotifier,
CursorPagingData<SnWebFeed>
> {
/// See also [MarketplaceWebFeedsNotifier].
MarketplaceWebFeedsNotifierProvider({required String? query})
: this._internal(
() => MarketplaceWebFeedsNotifier()..query = query,
from: marketplaceWebFeedsNotifierProvider,
name: r'marketplaceWebFeedsNotifierProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$marketplaceWebFeedsNotifierHash,
dependencies: MarketplaceWebFeedsNotifierFamily._dependencies,
allTransitiveDependencies:
MarketplaceWebFeedsNotifierFamily._allTransitiveDependencies,
query: query,
);
MarketplaceWebFeedsNotifierProvider._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<SnWebFeed>> runNotifierBuild(
covariant MarketplaceWebFeedsNotifier notifier,
) {
return notifier.build(query: query);
}
@override
Override overrideWith(MarketplaceWebFeedsNotifier Function() create) {
return ProviderOverride(
origin: this,
override: MarketplaceWebFeedsNotifierProvider._internal(
() => create()..query = query,
from: from,
name: null,
dependencies: null,
allTransitiveDependencies: null,
debugGetCreateSourceHash: null,
query: query,
),
);
}
@override
AutoDisposeAsyncNotifierProviderElement<
MarketplaceWebFeedsNotifier,
CursorPagingData<SnWebFeed>
>
createElement() {
return _MarketplaceWebFeedsNotifierProviderElement(this);
}
@override
bool operator ==(Object other) {
return other is MarketplaceWebFeedsNotifierProvider && 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 MarketplaceWebFeedsNotifierRef
on AutoDisposeAsyncNotifierProviderRef<CursorPagingData<SnWebFeed>> {
/// The parameter `query` of this provider.
String? get query;
}
class _MarketplaceWebFeedsNotifierProviderElement
extends
AutoDisposeAsyncNotifierProviderElement<
MarketplaceWebFeedsNotifier,
CursorPagingData<SnWebFeed>
>
with MarketplaceWebFeedsNotifierRef {
_MarketplaceWebFeedsNotifierProviderElement(super.provider);
@override
String? get query => (origin as MarketplaceWebFeedsNotifierProvider).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

@@ -9,8 +9,8 @@ import 'package:island/screens/creators/poll/poll_list.dart';
import 'package:island/screens/poll/poll_editor.dart'; import 'package:island/screens/poll/poll_editor.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_paging_utils/riverpod_paging_utils.dart';
import 'package:styled_widget/styled_widget.dart'; import 'package:styled_widget/styled_widget.dart';
import 'package:island/widgets/post/publishers_modal.dart'; import 'package:island/widgets/post/publishers_modal.dart';
@@ -45,23 +45,10 @@ class ComposePollSheet extends HookConsumerWidget {
child: TabBarView( child: TabBarView(
children: [ children: [
// Link/Select existing poll list // Link/Select existing poll list
PagingHelperView( PaginationList(
provider: pollListNotifierProvider(pub?.name), provider: pollListNotifierProvider(pub?.name),
futureRefreshable: notifier: pollListNotifierProvider(pub?.name).notifier,
pollListNotifierProvider(pub?.name).future, itemBuilder: (context, index, poll) {
notifierRefreshable:
pollListNotifierProvider(pub?.name).notifier,
contentBuilder:
(data, widgetCount, endItemView) => ListView.builder(
padding: EdgeInsets.zero,
itemCount: widgetCount,
itemBuilder: (context, index) {
if (index == widgetCount - 1) {
return endItemView;
}
final poll = data.items[index];
return ListTile( return ListTile(
leading: const Icon(Symbols.how_to_vote, fill: 1), leading: const Icon(Symbols.how_to_vote, fill: 1),
title: Text(poll.title ?? 'untitled'.tr()), title: Text(poll.title ?? 'untitled'.tr()),
@@ -74,7 +61,6 @@ class ComposePollSheet extends HookConsumerWidget {
); );
}, },
), ),
),
// Create new poll and return it // Create new poll and return it
SingleChildScrollView( SingleChildScrollView(