From 356d3d4d3ece405b5318c04c0132b567fc96bb7f Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Tue, 26 Nov 2024 00:00:09 +0800 Subject: [PATCH] :refactor: Central post fetching logic --- lib/controllers/post_write_controller.dart | 39 ++----- lib/main.dart | 2 + lib/providers/post.dart | 119 +++++++++++++++++++++ lib/screens/explore.dart | 34 +----- lib/screens/post/post_detail.dart | 18 +--- lib/screens/post/post_search.dart | 39 ++----- lib/types/post.dart | 1 + lib/types/post.freezed.dart | 53 +++++++-- lib/types/post.g.dart | 4 + lib/widgets/post/post_comment_list.dart | 37 +------ 10 files changed, 206 insertions(+), 140 deletions(-) create mode 100644 lib/providers/post.dart diff --git a/lib/controllers/post_write_controller.dart b/lib/controllers/post_write_controller.dart index 88fc89a..0c6b27c 100644 --- a/lib/controllers/post_write_controller.dart +++ b/lib/controllers/post_write_controller.dart @@ -6,6 +6,7 @@ import 'package:flutter/material.dart'; import 'package:image_picker/image_picker.dart'; import 'package:mime/mime.dart'; import 'package:provider/provider.dart'; +import 'package:surface/providers/post.dart'; import 'package:surface/providers/sn_attachment.dart'; import 'package:surface/providers/sn_network.dart'; import 'package:surface/types/attachment.dart'; @@ -180,53 +181,35 @@ class PostWriteController extends ChangeNotifier { int? reposting, int? replying, }) async { - final sn = context.read(); - final attach = context.read(); + final pt = context.read(); isLoading = true; notifyListeners(); try { if (editing != null) { - final resp = await sn.client.get('/cgi/co/posts/$editing'); - final post = SnPost.fromJson(resp.data); - final alts = await attach - .getMultiple(post.body['attachments']?.cast() ?? []); + final post = await pt.getPost(editing); publisher = post.publisher; titleController.text = post.body['title'] ?? ''; descriptionController.text = post.body['description'] ?? ''; contentController.text = post.body['content'] ?? ''; publishedAt = post.publishedAt; publishedUntil = post.publishedUntil; - attachments.addAll(alts.map((ele) => PostWriteMedia(ele))); - - editingPost = post.copyWith( - preload: SnPostPreload( - attachments: alts, - ), + attachments.addAll( + post.preload?.attachments?.map((ele) => PostWriteMedia(ele)) ?? [], ); + + editingPost = post; } if (replying != null) { - final resp = await sn.client.get('/cgi/co/posts/$replying'); - final post = SnPost.fromJson(resp.data); - replyingPost = post.copyWith( - preload: SnPostPreload( - attachments: await attach - .getMultiple(post.body['attachments']?.cast() ?? []), - ), - ); + final post = await pt.getPost(replying); + replyingPost = post; } if (reposting != null) { - final resp = await sn.client.get('/cgi/co/posts/$reposting'); - final post = SnPost.fromJson(resp.data); - repostingPost = post.copyWith( - preload: SnPostPreload( - attachments: await attach - .getMultiple(post.body['attachments']?.cast() ?? []), - ), - ); + final post = await pt.getPost(reposting); + replyingPost = post; } } catch (err) { if (!context.mounted) return; diff --git a/lib/main.dart b/lib/main.dart index 70238f5..bf62bd8 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -15,6 +15,7 @@ import 'package:surface/providers/channel.dart'; import 'package:surface/providers/chat_call.dart'; import 'package:surface/providers/navigation.dart'; import 'package:surface/providers/notification.dart'; +import 'package:surface/providers/post.dart'; import 'package:surface/providers/sn_attachment.dart'; import 'package:surface/providers/sn_network.dart'; import 'package:surface/providers/theme.dart'; @@ -82,6 +83,7 @@ class SolianApp extends StatelessWidget { // Data layer Provider(create: (_) => SnNetworkProvider()), Provider(create: (ctx) => SnAttachmentProvider(ctx)), + Provider(create: (ctx) => SnPostContentProvider(ctx)), Provider(create: (ctx) => UserDirectoryProvider(ctx)), ChangeNotifierProvider(create: (ctx) => UserProvider(ctx)), ChangeNotifierProvider(create: (ctx) => WebSocketProvider(ctx)), diff --git a/lib/providers/post.dart b/lib/providers/post.dart new file mode 100644 index 0000000..9c33499 --- /dev/null +++ b/lib/providers/post.dart @@ -0,0 +1,119 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:surface/providers/sn_attachment.dart'; +import 'package:surface/providers/sn_network.dart'; +import 'package:surface/types/post.dart'; + +class SnPostContentProvider { + late final SnNetworkProvider _sn; + late final SnAttachmentProvider _attach; + + SnPostContentProvider(BuildContext context) { + _sn = context.read(); + _attach = context.read(); + } + + Future> _preloadRelatedDataInBatch(List out) async { + Set rids = {}; + for (var i = 0; i < out.length; i++) { + rids.addAll(out[i].body['attachments']?.cast() ?? []); + if (out[i].body['thumbnail'] != null) { + rids.add(out[i].body['thumbnail']); + } + } + + final attachments = await _attach.getMultiple(rids.toList()); + for (var i = 0; i < out.length; i++) { + out[i] = out[i].copyWith( + preload: SnPostPreload( + thumbnail: attachments + .where((ele) => ele?.rid == out[i].body['thumbnail']) + .firstOrNull, + attachments: attachments + .where((ele) => + out[i].body['attachments']?.contains(ele?.rid) ?? false) + .toList(), + ), + ); + } + + return out; + } + + Future _preloadRelatedDataSingle(SnPost out) async { + Set rids = {}; + rids.addAll(out.body['attachments']?.cast() ?? []); + if (out.body['thumbnail'] != null) { + rids.add(out.body['thumbnail']); + } + + final attachments = await _attach.getMultiple(rids.toList()); + out = out.copyWith( + preload: SnPostPreload( + thumbnail: attachments + .where((ele) => ele?.rid == out.body['thumbnail']) + .firstOrNull, + attachments: attachments + .where( + (ele) => out.body['attachments']?.contains(ele?.rid) ?? false) + .toList(), + ), + ); + + return out; + } + + Future<(List, int)> listPosts({int take = 10, int offset = 0}) async { + final resp = await _sn.client.get('/cgi/co/posts', queryParameters: { + 'take': take, + 'offset': offset, + }); + final List out = await _preloadRelatedDataInBatch( + List.from(resp.data['data']?.map((e) => SnPost.fromJson(e)) ?? []), + ); + + return (out, resp.data['count'] as int); + } + + Future<(List, int)> listPostReplies( + dynamic parentId, { + int take = 10, + int offset = 0, + }) async { + final resp = await _sn.client + .get('/cgi/co/posts/$parentId/replies', queryParameters: { + 'take': take, + 'offset': offset, + }); + final List out = await _preloadRelatedDataInBatch( + List.from(resp.data['data']?.map((e) => SnPost.fromJson(e)) ?? []), + ); + + return (out, resp.data['count'] as int); + } + + Future<(List, int)> searchPosts( + String searchTerm, { + int take = 10, + int offset = 0, + }) async { + final resp = await _sn.client.get('/cgi/co/posts/search', queryParameters: { + 'take': take, + 'offset': offset, + 'probe': searchTerm, + }); + final List out = await _preloadRelatedDataInBatch( + List.from(resp.data['data']?.map((e) => SnPost.fromJson(e)) ?? []), + ); + + return (out, resp.data['count'] as int); + } + + Future getPost(dynamic id) async { + final resp = await _sn.client.get('/cgi/co/posts/$id'); + final out = _preloadRelatedDataSingle( + SnPost.fromJson(resp.data['data']), + ); + return out; + } +} diff --git a/lib/screens/explore.dart b/lib/screens/explore.dart index e278b20..95b41b5 100644 --- a/lib/screens/explore.dart +++ b/lib/screens/explore.dart @@ -5,8 +5,7 @@ import 'package:gap/gap.dart'; import 'package:go_router/go_router.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:provider/provider.dart'; -import 'package:surface/providers/sn_attachment.dart'; -import 'package:surface/providers/sn_network.dart'; +import 'package:surface/providers/post.dart'; import 'package:surface/types/post.dart'; import 'package:surface/widgets/post/post_item.dart'; import 'package:very_good_infinite_list/very_good_infinite_list.dart'; @@ -31,36 +30,13 @@ class _ExploreScreenState extends State { setState(() => _isBusy = true); - final sn = context.read(); - final resp = await sn.client.get('/cgi/co/posts', queryParameters: { - 'take': 10, - 'offset': _posts.length, - }); - final List out = - List.from(resp.data['data']?.map((e) => SnPost.fromJson(e)) ?? []); - - Set rids = {}; - for (var i = 0; i < out.length; i++) { - rids.addAll(out[i].body['attachments']?.cast() ?? []); - } + final pt = context.read(); + final result = await pt.listPosts(take: 10, offset: _posts.length); + final out = result.$1; if (!mounted) return; - final attach = context.read(); - final attachments = await attach.getMultiple(rids.toList()); - for (var i = 0; i < out.length; i++) { - out[i] = out[i].copyWith( - preload: SnPostPreload( - attachments: attachments - .where( - (ele) => - out[i].body['attachments']?.contains(ele?.rid) ?? false, - ) - .toList(), - ), - ); - } - _postCount = resp.data['count']; + _postCount = result.$2; _posts.addAll(out); if (mounted) setState(() => _isBusy = false); diff --git a/lib/screens/post/post_detail.dart b/lib/screens/post/post_detail.dart index 23c1d5c..5cf2729 100644 --- a/lib/screens/post/post_detail.dart +++ b/lib/screens/post/post_detail.dart @@ -7,8 +7,7 @@ import 'package:go_router/go_router.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:provider/provider.dart'; import 'package:styled_widget/styled_widget.dart'; -import 'package:surface/providers/sn_attachment.dart'; -import 'package:surface/providers/sn_network.dart'; +import 'package:surface/providers/post.dart'; import 'package:surface/providers/userinfo.dart'; import 'package:surface/types/post.dart'; import 'package:surface/widgets/dialog.dart'; @@ -39,19 +38,10 @@ class _PostDetailScreenState extends State { setState(() => _isBusy = true); try { - final sn = context.read(); - final attach = context.read(); - final resp = await sn.client.get('/cgi/co/posts/${widget.slug}'); + final pt = context.read(); + final post = await pt.getPost(widget.slug); if (!mounted) return; - final attachments = await attach.getMultiple( - resp.data['body']['attachments']?.cast() ?? [], - ); - if (!mounted) return; - _data = SnPost.fromJson(resp.data).copyWith( - preload: SnPostPreload( - attachments: attachments, - ), - ); + _data = post; } catch (err) { context.showErrorDialog(err); } finally { diff --git a/lib/screens/post/post_search.dart b/lib/screens/post/post_search.dart index 088c906..32686d7 100644 --- a/lib/screens/post/post_search.dart +++ b/lib/screens/post/post_search.dart @@ -5,8 +5,7 @@ import 'package:go_router/go_router.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:provider/provider.dart'; import 'package:styled_widget/styled_widget.dart'; -import 'package:surface/providers/sn_attachment.dart'; -import 'package:surface/providers/sn_network.dart'; +import 'package:surface/providers/post.dart'; import 'package:surface/types/post.dart'; import 'package:surface/widgets/post/post_item.dart'; import 'package:very_good_infinite_list/very_good_infinite_list.dart'; @@ -35,40 +34,20 @@ class _PostSearchScreenState extends State { final stopwatch = Stopwatch()..start(); - final sn = context.read(); - final resp = await sn.client.get('/cgi/co/posts/search', queryParameters: { - 'take': 10, - 'offset': _posts.length, - 'probe': _searchTerm, - }); - final List out = - List.from(resp.data['data']?.map((e) => SnPost.fromJson(e)) ?? []); - - Set rids = {}; - for (var i = 0; i < out.length; i++) { - rids.addAll(out[i].body['attachments']?.cast() ?? []); - } + final pt = context.read(); + final result = await pt.searchPosts( + _searchTerm, + take: 10, + offset: _posts.length, + ); + final List out = result.$1; if (!mounted) return; - final attach = context.read(); - final attachments = await attach.getMultiple(rids.toList()); - for (var i = 0; i < out.length; i++) { - out[i] = out[i].copyWith( - preload: SnPostPreload( - attachments: attachments - .where( - (ele) => - out[i].body['attachments']?.contains(ele?.rid) ?? false, - ) - .toList(), - ), - ); - } stopwatch.stop(); _lastTook = stopwatch.elapsed; - _postCount = resp.data['count']; + _postCount = result.$2; _posts.addAll(out); if (mounted) setState(() => _isBusy = false); diff --git a/lib/types/post.dart b/lib/types/post.dart index 4f9301c..c04604b 100644 --- a/lib/types/post.dart +++ b/lib/types/post.dart @@ -53,6 +53,7 @@ class SnPost with _$SnPost { @freezed class SnPostPreload with _$SnPostPreload { const factory SnPostPreload({ + required SnAttachment? thumbnail, required List? attachments, }) = _SnPostPreload; diff --git a/lib/types/post.freezed.dart b/lib/types/post.freezed.dart index 8b00e4e..cd21182 100644 --- a/lib/types/post.freezed.dart +++ b/lib/types/post.freezed.dart @@ -953,6 +953,7 @@ SnPostPreload _$SnPostPreloadFromJson(Map json) { /// @nodoc mixin _$SnPostPreload { + SnAttachment? get thumbnail => throw _privateConstructorUsedError; List? get attachments => throw _privateConstructorUsedError; /// Serializes this SnPostPreload to a JSON map. @@ -971,7 +972,9 @@ abstract class $SnPostPreloadCopyWith<$Res> { SnPostPreload value, $Res Function(SnPostPreload) then) = _$SnPostPreloadCopyWithImpl<$Res, SnPostPreload>; @useResult - $Res call({List? attachments}); + $Res call({SnAttachment? thumbnail, List? attachments}); + + $SnAttachmentCopyWith<$Res>? get thumbnail; } /// @nodoc @@ -989,15 +992,34 @@ class _$SnPostPreloadCopyWithImpl<$Res, $Val extends SnPostPreload> @pragma('vm:prefer-inline') @override $Res call({ + Object? thumbnail = freezed, Object? attachments = freezed, }) { return _then(_value.copyWith( + thumbnail: freezed == thumbnail + ? _value.thumbnail + : thumbnail // ignore: cast_nullable_to_non_nullable + as SnAttachment?, attachments: freezed == attachments ? _value.attachments : attachments // ignore: cast_nullable_to_non_nullable as List?, ) as $Val); } + + /// Create a copy of SnPostPreload + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $SnAttachmentCopyWith<$Res>? get thumbnail { + if (_value.thumbnail == null) { + return null; + } + + return $SnAttachmentCopyWith<$Res>(_value.thumbnail!, (value) { + return _then(_value.copyWith(thumbnail: value) as $Val); + }); + } } /// @nodoc @@ -1008,7 +1030,10 @@ abstract class _$$SnPostPreloadImplCopyWith<$Res> __$$SnPostPreloadImplCopyWithImpl<$Res>; @override @useResult - $Res call({List? attachments}); + $Res call({SnAttachment? thumbnail, List? attachments}); + + @override + $SnAttachmentCopyWith<$Res>? get thumbnail; } /// @nodoc @@ -1024,9 +1049,14 @@ class __$$SnPostPreloadImplCopyWithImpl<$Res> @pragma('vm:prefer-inline') @override $Res call({ + Object? thumbnail = freezed, Object? attachments = freezed, }) { return _then(_$SnPostPreloadImpl( + thumbnail: freezed == thumbnail + ? _value.thumbnail + : thumbnail // ignore: cast_nullable_to_non_nullable + as SnAttachment?, attachments: freezed == attachments ? _value._attachments : attachments // ignore: cast_nullable_to_non_nullable @@ -1038,12 +1068,16 @@ class __$$SnPostPreloadImplCopyWithImpl<$Res> /// @nodoc @JsonSerializable() class _$SnPostPreloadImpl implements _SnPostPreload { - const _$SnPostPreloadImpl({required final List? attachments}) + const _$SnPostPreloadImpl( + {required this.thumbnail, + required final List? attachments}) : _attachments = attachments; factory _$SnPostPreloadImpl.fromJson(Map json) => _$$SnPostPreloadImplFromJson(json); + @override + final SnAttachment? thumbnail; final List? _attachments; @override List? get attachments { @@ -1056,7 +1090,7 @@ class _$SnPostPreloadImpl implements _SnPostPreload { @override String toString() { - return 'SnPostPreload(attachments: $attachments)'; + return 'SnPostPreload(thumbnail: $thumbnail, attachments: $attachments)'; } @override @@ -1064,14 +1098,16 @@ class _$SnPostPreloadImpl implements _SnPostPreload { return identical(this, other) || (other.runtimeType == runtimeType && other is _$SnPostPreloadImpl && + (identical(other.thumbnail, thumbnail) || + other.thumbnail == thumbnail) && const DeepCollectionEquality() .equals(other._attachments, _attachments)); } @JsonKey(includeFromJson: false, includeToJson: false) @override - int get hashCode => Object.hash( - runtimeType, const DeepCollectionEquality().hash(_attachments)); + int get hashCode => Object.hash(runtimeType, thumbnail, + const DeepCollectionEquality().hash(_attachments)); /// Create a copy of SnPostPreload /// with the given fields replaced by the non-null parameter values. @@ -1091,11 +1127,14 @@ class _$SnPostPreloadImpl implements _SnPostPreload { abstract class _SnPostPreload implements SnPostPreload { const factory _SnPostPreload( - {required final List? attachments}) = _$SnPostPreloadImpl; + {required final SnAttachment? thumbnail, + required final List? attachments}) = _$SnPostPreloadImpl; factory _SnPostPreload.fromJson(Map json) = _$SnPostPreloadImpl.fromJson; + @override + SnAttachment? get thumbnail; @override List? get attachments; diff --git a/lib/types/post.g.dart b/lib/types/post.g.dart index 83ba11f..addcb8e 100644 --- a/lib/types/post.g.dart +++ b/lib/types/post.g.dart @@ -102,6 +102,9 @@ Map _$$SnPostImplToJson(_$SnPostImpl instance) => _$SnPostPreloadImpl _$$SnPostPreloadImplFromJson(Map json) => _$SnPostPreloadImpl( + thumbnail: json['thumbnail'] == null + ? null + : SnAttachment.fromJson(json['thumbnail'] as Map), attachments: (json['attachments'] as List?) ?.map((e) => e == null ? null @@ -111,6 +114,7 @@ _$SnPostPreloadImpl _$$SnPostPreloadImplFromJson(Map json) => Map _$$SnPostPreloadImplToJson(_$SnPostPreloadImpl instance) => { + 'thumbnail': instance.thumbnail?.toJson(), 'attachments': instance.attachments?.map((e) => e?.toJson()).toList(), }; diff --git a/lib/widgets/post/post_comment_list.dart b/lib/widgets/post/post_comment_list.dart index f74c451..da4a148 100644 --- a/lib/widgets/post/post_comment_list.dart +++ b/lib/widgets/post/post_comment_list.dart @@ -5,8 +5,7 @@ import 'package:go_router/go_router.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:provider/provider.dart'; import 'package:styled_widget/styled_widget.dart'; -import 'package:surface/providers/sn_attachment.dart'; -import 'package:surface/providers/sn_network.dart'; +import 'package:surface/providers/post.dart'; import 'package:surface/providers/userinfo.dart'; import 'package:surface/types/post.dart'; import 'package:surface/widgets/post/post_item.dart'; @@ -37,39 +36,13 @@ class PostCommentSliverListState extends State { setState(() => _isBusy = true); - final sn = context.read(); - final resp = await sn.client.get( - '/cgi/co/posts/${widget.parentPostId}/replies', - queryParameters: { - 'take': 10, - 'offset': _posts.length, - }, - ); - final List out = - List.from(resp.data['data']?.map((e) => SnPost.fromJson(e)) ?? []); - - Set rids = {}; - for (var i = 0; i < out.length; i++) { - rids.addAll(out[i].body['attachments']?.cast() ?? []); - } + final pt = context.read(); + final result = await pt.listPostReplies(widget.parentPostId); + final List out = result.$1; if (!mounted) return; - final attach = context.read(); - final attachments = await attach.getMultiple(rids.toList()); - for (var i = 0; i < out.length; i++) { - out[i] = out[i].copyWith( - preload: SnPostPreload( - attachments: attachments - .where( - (ele) => - out[i].body['attachments']?.contains(ele?.rid) ?? false, - ) - .toList(), - ), - ); - } - _postCount = resp.data['count']; + _postCount = result.$2; _posts.addAll(out); if (mounted) setState(() => _isBusy = false);