♻️ Better categorized fetching in publisher page

This commit is contained in:
LittleSheep 2024-12-01 23:20:05 +08:00
parent 177ff513ee
commit 9588fc0475
2 changed files with 286 additions and 307 deletions

View File

@ -66,11 +66,13 @@ class SnPostContentProvider {
Future<(List<SnPost>, int)> listPosts({ Future<(List<SnPost>, int)> listPosts({
int take = 10, int take = 10,
int offset = 0, int offset = 0,
String? type,
String? author, String? author,
}) async { }) async {
final resp = await _sn.client.get('/cgi/co/posts', queryParameters: { final resp = await _sn.client.get('/cgi/co/posts', queryParameters: {
'take': take, 'take': take,
'offset': offset, 'offset': offset,
if (type != null) 'type': type,
if (author != null) 'author': author, if (author != null) 'author': author,
}); });
final List<SnPost> out = await _preloadRelatedDataInBatch( final List<SnPost> out = await _preloadRelatedDataInBatch(

View File

@ -1,5 +1,3 @@
import 'dart:convert';
import 'dart:developer';
import 'dart:ui'; import 'dart:ui';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
@ -29,8 +27,11 @@ class PostPublisherScreen extends StatefulWidget {
State<PostPublisherScreen> createState() => _PostPublisherScreenState(); State<PostPublisherScreen> createState() => _PostPublisherScreenState();
} }
class _PostPublisherScreenState extends State<PostPublisherScreen> { class _PostPublisherScreenState extends State<PostPublisherScreen>
with SingleTickerProviderStateMixin {
late final ScrollController _scrollController = ScrollController(); late final ScrollController _scrollController = ScrollController();
late final TabController _tabController =
TabController(length: 3, vsync: this);
SnPublisher? _publisher; SnPublisher? _publisher;
SnAccount? _account; SnAccount? _account;
@ -66,9 +67,8 @@ class _PostPublisherScreenState extends State<PostPublisherScreen> {
); );
if (!mounted) return; if (!mounted) return;
_subscription = SnSubscription.fromJson(resp.data); _subscription = SnSubscription.fromJson(resp.data);
} catch (err) { } catch (_) {
if (!mounted) return; // ignore due to maybe 404
context.showErrorDialog(err);
} finally { } finally {
setState(() => _isSubscribing = false); setState(() => _isSubscribing = false);
} }
@ -131,12 +131,19 @@ class _PostPublisherScreenState extends State<PostPublisherScreen> {
Future<void> _fetchPosts() async { Future<void> _fetchPosts() async {
if (_isBusy) return; if (_isBusy) return;
_isBusy = true;
setState(() => _isBusy = true);
try { try {
final pt = context.read<SnPostContentProvider>(); final pt = context.read<SnPostContentProvider>();
final result = await pt.listPosts( final result = await pt.listPosts(
offset: _posts.length, offset: _posts.length,
author: widget.name, author: widget.name,
type: switch (_tabController.index) {
1 => 'story',
2 => 'article',
_ => null,
},
); );
_postCount = result.$2; _postCount = result.$2;
_posts.addAll(result.$1); _posts.addAll(result.$1);
@ -144,11 +151,15 @@ class _PostPublisherScreenState extends State<PostPublisherScreen> {
if (!mounted) return; if (!mounted) return;
context.showErrorDialog(err); context.showErrorDialog(err);
} finally { } finally {
_isBusy = false; setState(() => _isBusy = false);
setState(() {});
} }
} }
void _updateFetchType() {
_posts.clear();
_fetchPosts();
}
@override @override
void initState() { void initState() {
super.initState(); super.initState();
@ -157,12 +168,15 @@ class _PostPublisherScreenState extends State<PostPublisherScreen> {
_fetchSubscription(); _fetchSubscription();
}); });
_scrollController.addListener(_updateAppBarBlur); _scrollController.addListener(_updateAppBarBlur);
_tabController.addListener(_updateFetchType);
} }
@override @override
void dispose() { void dispose() {
_scrollController.removeListener(_updateAppBarBlur); _scrollController.removeListener(_updateAppBarBlur);
_scrollController.dispose(); _scrollController.dispose();
_tabController.removeListener(_updateFetchType);
_tabController.dispose();
super.dispose(); super.dispose();
} }
@ -182,17 +196,13 @@ class _PostPublisherScreenState extends State<PostPublisherScreen> {
final sn = context.read<SnNetworkProvider>(); final sn = context.read<SnNetworkProvider>();
// TODO fix loading on different type return Scaffold(
return DefaultTabController(
length: 3,
child: Scaffold(
body: NestedScrollView( body: NestedScrollView(
controller: _scrollController, controller: _scrollController,
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) { headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[ return <Widget>[
SliverOverlapAbsorber( SliverOverlapAbsorber(
handle: handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
NestedScrollView.sliverOverlapAbsorberHandleFor(context),
sliver: MultiSliver( sliver: MultiSliver(
children: [ children: [
SliverAppBar( SliverAppBar(
@ -242,8 +252,7 @@ class _PostPublisherScreenState extends State<PostPublisherScreen> {
top: 0, top: 0,
left: 0, left: 0,
right: 0, right: 0,
height: height: 56 + MediaQuery.of(context).padding.top,
56 + MediaQuery.of(context).padding.top,
child: ClipRect( child: ClipRect(
child: BackdropFilter( child: BackdropFilter(
filter: ImageFilter.blur( filter: ImageFilter.blur(
@ -252,8 +261,7 @@ class _PostPublisherScreenState extends State<PostPublisherScreen> {
), ),
child: Container( child: Container(
color: Colors.black.withOpacity( color: Colors.black.withOpacity(
clampDouble( clampDouble(_appBarBlur * 0.1, 0, 0.5),
_appBarBlur * 0.1, 0, 0.5),
), ),
), ),
), ),
@ -288,8 +296,7 @@ class _PostPublisherScreenState extends State<PostPublisherScreen> {
.textTheme .textTheme
.titleMedium, .titleMedium,
).bold(), ).bold(),
Text('@${_publisher!.name}') Text('@${_publisher!.name}').fontSize(13),
.fontSize(13),
], ],
), ),
), ),
@ -366,6 +373,7 @@ class _PostPublisherScreenState extends State<PostPublisherScreen> {
), ),
SliverToBoxAdapter(child: const Divider(height: 1)), SliverToBoxAdapter(child: const Divider(height: 1)),
TabBar( TabBar(
controller: _tabController,
tabs: [ tabs: [
Tab( Tab(
icon: Icon( icon: Icon(
@ -395,19 +403,15 @@ class _PostPublisherScreenState extends State<PostPublisherScreen> {
]; ];
}, },
body: TabBarView( body: TabBarView(
children: [ controller: _tabController,
InfiniteList( children: List.filled(
itemCount: _posts.length, 3,
isLoading: _isBusy, _PublisherPostList(
hasReachedMax: isBusy: _isBusy,
_postCount != null && _posts.length >= _postCount!, postCount: _postCount,
onFetchData: _fetchPosts, posts: _posts,
itemBuilder: (context, idx) { fetchPosts: _fetchPosts,
return GestureDetector( onChanged: (idx, data) {
child: PostItem(
data: _posts[idx],
maxWidth: 640,
onChanged: (data) {
setState(() => _posts[idx] = data); setState(() => _posts[idx] = data);
}, },
onDeleted: () { onDeleted: () {
@ -415,84 +419,57 @@ class _PostPublisherScreenState extends State<PostPublisherScreen> {
_fetchPosts(); _fetchPosts();
}, },
), ),
onTap: () {
GoRouter.of(context).pushNamed(
'postDetail',
pathParameters: {'slug': _posts[idx].id.toString()},
extra: _posts[idx],
);
},
);
},
separatorBuilder: (context, index) => const Divider(height: 1),
),
InfiniteList(
itemCount: _posts.where((e) => e.type == 'story').length,
isLoading: _isBusy,
hasReachedMax:
_postCount != null && _posts.length >= _postCount!,
onFetchData: _fetchPosts,
itemBuilder: (context, idx) {
return GestureDetector(
child: PostItem(
data:
_posts.where((e) => e.type == 'story').elementAt(idx),
maxWidth: 640,
onChanged: (data) {
setState(() => _posts[idx] = data);
},
onDeleted: () {
_posts.clear();
_fetchPosts();
},
),
onTap: () {
GoRouter.of(context).pushNamed(
'postDetail',
pathParameters: {'slug': _posts[idx].id.toString()},
extra: _posts[idx],
);
},
);
},
separatorBuilder: (context, index) => const Divider(height: 1),
),
InfiniteList(
itemCount: _posts.where((e) => e.type == 'article').length,
isLoading: _isBusy,
hasReachedMax:
_postCount != null && _posts.length >= _postCount!,
onFetchData: _fetchPosts,
itemBuilder: (context, idx) {
return GestureDetector(
child: PostItem(
data: _posts
.where((e) => e.type == 'article')
.elementAt(idx),
maxWidth: 640,
onChanged: (data) {
setState(() => _posts[idx] = data);
},
onDeleted: () {
_posts.clear();
_fetchPosts();
},
),
onTap: () {
GoRouter.of(context).pushNamed(
'postDetail',
pathParameters: {'slug': _posts[idx].id.toString()},
extra: _posts[idx],
);
},
);
},
separatorBuilder: (context, index) => const Divider(height: 1),
),
],
), ),
), ),
), ),
); );
} }
} }
class _PublisherPostList extends StatelessWidget {
final bool isBusy;
final int? postCount;
final List<SnPost> posts;
final void Function() fetchPosts;
final void Function(int index, SnPost data) onChanged;
final void Function() onDeleted;
const _PublisherPostList({
super.key,
required this.isBusy,
required this.postCount,
required this.posts,
required this.fetchPosts,
required this.onChanged,
required this.onDeleted,
});
@override
Widget build(BuildContext context) {
return InfiniteList(
itemCount: posts.length,
isLoading: isBusy,
hasReachedMax: postCount != null && posts.length >= postCount!,
onFetchData: fetchPosts,
itemBuilder: (context, idx) {
return GestureDetector(
child: PostItem(
data: posts[idx],
maxWidth: 640,
onChanged: (data) {
onChanged(idx, data);
},
onDeleted: onDeleted,
),
onTap: () {
GoRouter.of(context).pushNamed(
'postDetail',
pathParameters: {'slug': posts[idx].id.toString()},
extra: posts[idx],
);
},
);
},
separatorBuilder: (context, index) => const Divider(height: 1),
);
}
}