Add new useRefreshable hook
This commit is contained in:
parent
0bef4fe1af
commit
0d434fafcc
|
@ -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();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
|
@ -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) {
|
||||||
|
|
|
@ -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}')
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in New Issue