Add new useRefreshable hook

This commit is contained in:
shilangyu 2021-01-09 16:34:24 +00:00
parent 0bef4fe1af
commit 0d434fafcc
3 changed files with 71 additions and 40 deletions

View File

@ -0,0 +1,37 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'memo_future.dart';
class Refreshable<T> {
const Refreshable({@required this.snapshot, @required this.refresh})
: assert(snapshot != null),
assert(refresh != null);
final AsyncSnapshot<T> snapshot;
final AsyncCallback refresh;
}
Refreshable<T> useRefreshable<T>(AsyncValueGetter<T> fetcher,
[List<Object> keys = const <dynamic>[]]) {
final newData = useState<T>(null);
final snapshot = useMemoFuture(() async {
newData.value = null;
return fetcher();
}, keys);
final outSnapshot = () {
if (newData.value != null) {
return AsyncSnapshot.withData(ConnectionState.done, newData.value);
}
return snapshot;
}();
return Refreshable(
snapshot: outSnapshot,
refresh: () async {
newData.value = await fetcher();
},
);
}

View File

@ -8,7 +8,7 @@ import 'package:fuzzy/fuzzy.dart';
import 'package:lemmy_api_client/lemmy_api_client.dart'; import 'package:lemmy_api_client/lemmy_api_client.dart';
import '../hooks/delayed_loading.dart'; import '../hooks/delayed_loading.dart';
import '../hooks/memo_future.dart'; import '../hooks/refreshable.dart';
import '../hooks/stores.dart'; import '../hooks/stores.dart';
import '../util/extensions/api.dart'; import '../util/extensions/api.dart';
import '../util/extensions/iterators.dart'; import '../util/extensions/iterators.dart';
@ -60,14 +60,11 @@ class CommunitiesTab extends HookWidget {
} }
// TODO: rebuild when instances/accounts change // TODO: rebuild when instances/accounts change
final instancesSnap = useMemoFuture(getInstances); final instancesRefreshable = useRefreshable(getInstances);
final communitiesSnap = useMemoFuture(getCommunities); final communitiesRefreshable = useRefreshable(getCommunities);
final updatedCommunities = if (communitiesRefreshable.snapshot.hasError ||
useState<List<List<CommunityFollowerView>>>(null); instancesRefreshable.snapshot.hasError) {
final updatedInstances = useState<List<SiteView>>(null);
if (communitiesSnap.hasError || instancesSnap.hasError) {
return Scaffold( return Scaffold(
appBar: AppBar(), appBar: AppBar(),
body: Center( body: Center(
@ -77,15 +74,16 @@ class CommunitiesTab extends HookWidget {
Padding( Padding(
padding: const EdgeInsets.all(8), padding: const EdgeInsets.all(8),
child: Text( child: Text(
communitiesSnap.error?.toString() ?? communitiesRefreshable.snapshot.error?.toString() ??
instancesSnap.error?.toString(), instancesRefreshable.snapshot.error?.toString(),
), ),
) )
], ],
), ),
), ),
); );
} else if (!communitiesSnap.hasData || !instancesSnap.hasData) { } else if (!communitiesRefreshable.snapshot.hasData ||
!instancesRefreshable.snapshot.hasData) {
return Scaffold( return Scaffold(
appBar: AppBar(), appBar: AppBar(),
body: const Center( body: const Center(
@ -97,11 +95,10 @@ class CommunitiesTab extends HookWidget {
refresh() async { refresh() async {
await HapticFeedback.mediumImpact(); await HapticFeedback.mediumImpact();
try { try {
final i = getInstances(); await Future.wait([
final c = getCommunities(); instancesRefreshable.refresh(),
await Future.wait([i, c]); communitiesRefreshable.refresh(),
updatedInstances.value = await i; ]);
updatedCommunities.value = await c;
// ignore: avoid_catches_without_on_clauses // ignore: avoid_catches_without_on_clauses
} catch (e) { } catch (e) {
Scaffold.of(context) Scaffold.of(context)
@ -109,8 +106,8 @@ class CommunitiesTab extends HookWidget {
} }
} }
final instances = updatedInstances.value ?? instancesSnap.data; final instances = instancesRefreshable.snapshot.data;
final communities = updatedCommunities.value ?? communitiesSnap.data final communities = communitiesRefreshable.snapshot.data
..forEach( ..forEach(
(e) => e.sort((a, b) => a.communityName.compareTo(b.communityName))); (e) => e.sort((a, b) => a.communityName.compareTo(b.communityName)));
@ -239,6 +236,7 @@ class CommunitiesTab extends HookWidget {
], ],
), ),
trailing: _CommunitySubscribeToggle( trailing: _CommunitySubscribeToggle(
key: ValueKey(comm.communityId),
instanceHost: comm.instanceHost, instanceHost: comm.instanceHost,
communityId: comm.communityId, communityId: comm.communityId,
), ),
@ -258,9 +256,10 @@ class _CommunitySubscribeToggle extends HookWidget {
final String instanceHost; final String instanceHost;
const _CommunitySubscribeToggle( const _CommunitySubscribeToggle(
{@required this.instanceHost, @required this.communityId}) {@required this.instanceHost, @required this.communityId, Key key})
: assert(instanceHost != null), : assert(instanceHost != null),
assert(communityId != null); assert(communityId != null),
super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {

View File

@ -6,7 +6,7 @@ import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:lemmy_api_client/lemmy_api_client.dart'; import 'package:lemmy_api_client/lemmy_api_client.dart';
import '../hooks/logged_in_action.dart'; import '../hooks/logged_in_action.dart';
import '../hooks/memo_future.dart'; import '../hooks/refreshable.dart';
import '../hooks/stores.dart'; import '../hooks/stores.dart';
import '../util/more_icon.dart'; import '../util/more_icon.dart';
import '../widgets/comment_section.dart'; import '../widgets/comment_section.dart';
@ -19,10 +19,8 @@ class FullPostPage extends HookWidget {
final int id; final int id;
final String instanceHost; final String instanceHost;
final PostView post; final PostView post;
final GlobalKey<RefreshIndicatorState> _refreshIndicatorKey =
GlobalKey<RefreshIndicatorState>();
FullPostPage({@required this.id, @required this.instanceHost}) const FullPostPage({@required this.id, @required this.instanceHost})
: assert(id != null), : assert(id != null),
assert(instanceHost != null), assert(instanceHost != null),
post = null; post = null;
@ -33,23 +31,22 @@ class FullPostPage extends HookWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final accStore = useAccountsStore(); final accStore = useAccountsStore();
final fullPostSnap = useMemoFuture(() => LemmyApi(instanceHost) final fullPostRefreshable = useRefreshable(() => LemmyApi(instanceHost)
.v1 .v1
.getPost(id: id, auth: accStore.defaultTokenFor(instanceHost)?.raw)); .getPost(id: id, auth: accStore.defaultTokenFor(instanceHost)?.raw));
final loggedInAction = useLoggedInAction(instanceHost); final loggedInAction = useLoggedInAction(instanceHost);
final newComments = useState(const <CommentView>[]); final newComments = useState(const <CommentView>[]);
final updatedPost = useState<FullPostView>(null);
// FALLBACK VIEW
if (!fullPostSnap.hasData && this.post == null) { // FALLBACK VIEW
if (!fullPostRefreshable.snapshot.hasData && this.post == null) {
return Scaffold( return Scaffold(
appBar: AppBar(), appBar: AppBar(),
body: Center( body: Center(
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
if (fullPostSnap.hasError) if (fullPostRefreshable.snapshot.hasError)
Text(fullPostSnap.error.toString()) Text(fullPostRefreshable.snapshot.error.toString())
else else
const CircularProgressIndicator(), const CircularProgressIndicator(),
], ],
@ -60,10 +57,11 @@ class FullPostPage extends HookWidget {
// VARIABLES // VARIABLES
final post = updatedPost.value?.post ?? final post = fullPostRefreshable.snapshot.hasData
(fullPostSnap.hasData ? fullPostSnap.data.post : this.post); ? fullPostRefreshable.snapshot.data.post
: this.post;
final fullPost = updatedPost.value ?? fullPostSnap.data; final fullPost = fullPostRefreshable.snapshot.data;
// FUNCTIONS // FUNCTIONS
@ -71,9 +69,7 @@ class FullPostPage extends HookWidget {
await HapticFeedback.mediumImpact(); await HapticFeedback.mediumImpact();
try { try {
return await LemmyApi(instanceHost) await fullPostRefreshable.refresh();
.v1
.getPost(id: id, auth: accStore.defaultTokenFor(instanceHost)?.raw);
// ignore: avoid_catches_without_on_clauses // ignore: avoid_catches_without_on_clauses
} catch (e) { } catch (e) {
Scaffold.of(context).showSnackBar(SnackBar( Scaffold.of(context).showSnackBar(SnackBar(
@ -110,23 +106,22 @@ class FullPostPage extends HookWidget {
child: const Icon(Icons.comment)), child: const Icon(Icons.comment)),
body: RefreshIndicator( body: RefreshIndicator(
onRefresh: refresh, onRefresh: refresh,
key: _refreshIndicatorKey,
child: ListView( child: ListView(
physics: const AlwaysScrollableScrollPhysics(), physics: const AlwaysScrollableScrollPhysics(),
children: [ children: [
Post(post, fullPost: true), Post(post, fullPost: true),
if (fullPostSnap.hasData) if (fullPostRefreshable.snapshot.hasData)
CommentSection( CommentSection(
newComments.value.followedBy(fullPost.comments).toList(), newComments.value.followedBy(fullPost.comments).toList(),
postCreatorId: fullPost.post.creatorId) postCreatorId: fullPost.post.creatorId)
else if (fullPostSnap.hasError) else if (fullPostRefreshable.snapshot.hasError)
Padding( Padding(
padding: padding:
const EdgeInsets.symmetric(horizontal: 10, vertical: 30), const EdgeInsets.symmetric(horizontal: 10, vertical: 30),
child: Column( child: Column(
children: [ children: [
const Icon(Icons.error), const Icon(Icons.error),
Text('Error: ${fullPostSnap.error}') Text('Error: ${fullPostRefreshable.snapshot.error}')
], ],
), ),
) )