Skip to content

Commit

Permalink
Menu item to view comment or post in a specific instance (#277)
Browse files Browse the repository at this point in the history
Adds a new menu item to view posts/comments in another instance so you
can interact with them.

[view-federated-post-demo-down.webm](https://github.com/liftoff-app/liftoff/assets/1340627/4c0d2b50-61b9-4c22-bbf5-8966f29f2e4a)

[view-federated-comment-demo-down.webm](https://github.com/liftoff-app/liftoff/assets/1340627/ac0f278c-b707-4a55-be63-d7bf89b5f0d9)
  • Loading branch information
jcgurango authored Jul 6, 2023
1 parent d24c334 commit b2791b2
Show file tree
Hide file tree
Showing 10 changed files with 134 additions and 50 deletions.
4 changes: 4 additions & 0 deletions assets/l10n/intl_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,10 @@
"@view_on": {},
"foreign_community_info": "Loading the community in the other instance. Note that if this is the first time the community is being viewed in the instance, this process may take a while.",
"@foreign_community_info": {},
"federated_post_info": "Loading the post in the other instance. Note that if this is the first time the post is being viewed in the instance, this process may take a while.",
"@federated_post_info": {},
"not_found": "Not found",
"@not_found": {},

"error": "Error: {error_text}",
"@error" :{
Expand Down
22 changes: 22 additions & 0 deletions lib/pages/community/community.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import '../../widgets/failed_to_load.dart';
import '../../widgets/reveal_after_scroll.dart';
import '../../widgets/sortable_infinite_list.dart';
import '../create_post/create_post_fab.dart';
import '../federation_resolver.dart';
import 'community_about_tab.dart';
import 'community_more_menu.dart';
import 'community_overview.dart';
Expand Down Expand Up @@ -186,4 +187,25 @@ class CommunityPage extends HookWidget {
CommunityStore.fromId(id: id, instanceHost: instanceHost),
);
}

static Route fromApIdRoute(UserData userData, String apId) {
return SwipeablePageRoute(
builder: (context) {
return FederationResolver(
userData: userData,
query: apId,
loadingMessage: L10n.of(context).foreign_community_info,
exists: (response) => response.community != null,
builder: (buildContext, object) {
return MobxProvider.value(
value: CommunityStore.fromId(
id: object.community!.community.id,
instanceHost: userData.instanceHost)
..refresh(userData),
child: const CommunityPage(),
);
});
},
);
}
}
2 changes: 1 addition & 1 deletion lib/pages/community/community_follow_button.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class CommunityFollowButton extends HookWidget {

final loggedInAction = useLoggedInAction(
context.read<CommunityStore>().instanceHost, fallback: () {
ViewOnMenu.openForCommunity(context, communityView);
ViewOnMenu.openForCommunity(context, communityView.community.actorId);
});

return ObserverBuilder<CommunityStore>(builder: (context, store) {
Expand Down
3 changes: 2 additions & 1 deletion lib/pages/community/community_more_menu.dart
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ class CommunityMoreMenu extends HookWidget {
ListTile(
leading: const Icon(Icons.travel_explore),
title: Text(L10n.of(context).view_on),
onTap: () => ViewOnMenu.openForCommunity(context, communityView),
onTap: () => ViewOnMenu.openForCommunity(
context, communityView.community.actorId),
),
ObserverBuilder<CommunityStore>(builder: (context, store) {
return ListTile(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,45 @@ import 'package:lemmy_api_client/v3.dart';

import '../../l10n/l10n.dart';
import '../../stores/accounts_store.dart';
import 'community.dart';

class FederatedCommunityPage extends HookWidget {
class FederationResolver extends HookWidget {
final UserData userData;
final String community;
final String query;
final String loadingMessage;
final bool Function(ResolveObjectResponse response) exists;
final Widget Function(BuildContext buildContext, ResolveObjectResponse object)
builder;

const FederatedCommunityPage(this.userData, this.community);
const FederationResolver({
required this.userData,
required this.query,
required this.loadingMessage,
required this.builder,
required this.exists,
});

@override
Widget build(BuildContext context) {
final lemmyApi = LemmyApiV3(userData.instanceHost);

return FutureBuilder(
future: lemmyApi
.run(ResolveObject(q: community, auth: userData.jwt.raw))
.then((data) {
Navigator.of(context).pop();
Navigator.of(context).push(CommunityPage.fromIdRoute(
userData.instanceHost, data.community!.community.id));
}),
future: lemmyApi.run(ResolveObject(q: query, auth: userData.jwt.raw)),
builder: (context, snapshot) {
var message = loadingMessage;

if (snapshot.hasData) {
if (exists(snapshot.data!)) {
return Builder(
builder: (context) => builder(context, snapshot.data!));
} else {
message = L10n.of(context).not_found;
}
}

if (snapshot.hasError) {
message = 'Error: ${snapshot.error}';
}

return Scaffold(
appBar: AppBar(),
body: Container(
Expand All @@ -40,9 +58,7 @@ class FederatedCommunityPage extends HookWidget {
),
Container(height: 24),
Text(
snapshot.hasError
? 'Error: ${snapshot.error}'
: L10n.of(context).foreign_community_info,
message,
textAlign: TextAlign.center,
),
],
Expand Down
40 changes: 34 additions & 6 deletions lib/pages/full_post/full_post.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import 'package:nested/nested.dart';
import 'package:swipeable_page_route/swipeable_page_route.dart';

import '../../hooks/logged_in_action.dart';
import '../../l10n/l10n.dart';
import '../../stores/accounts_store.dart';
import '../../util/async_store_listener.dart';
import '../../util/extensions/api.dart';
Expand All @@ -22,6 +23,8 @@ import '../../widgets/post/save_post_button.dart';
import '../../widgets/pull_to_refresh.dart';
import '../../widgets/reveal_after_scroll.dart';
import '../../widgets/write_comment.dart';
import '../federation_resolver.dart';
import '../view_on_menu.dart';
import 'comment_section.dart';
import 'full_post_store.dart';

Expand All @@ -33,18 +36,20 @@ class FullPostPage extends HookWidget {
Widget build(BuildContext context) {
final scrollController = useScrollController();
final shareButtonKey = GlobalKey();
final fullPostStore = context.read<FullPostStore>();
var scrollOffset = 0.0;

final loggedInAction =
useLoggedInAction(context.read<FullPostStore>().instanceHost);
final replyLoggedInAction = useLoggedInAction(fullPostStore.instanceHost,
fallback: () =>
ViewOnMenu.openForPost(context, fullPostStore.postView!.post.apId));

return Nested(
children: [
AsyncStoreListener(
asyncStore: context.read<FullPostStore>().fullPostState,
asyncStore: fullPostStore.fullPostState,
),
AsyncStoreListener<BlockedCommunity>(
asyncStore: context.read<FullPostStore>().communityBlockingState,
asyncStore: fullPostStore.communityBlockingState,
successMessageBuilder: (context, data) {
final name = data.communityView.community.originPreferredName;
return '${data.blocked ? 'Blocked' : 'Unblocked'} $name';
Expand Down Expand Up @@ -139,7 +144,7 @@ class FullPostPage extends HookWidget {
),
if (!Platform.isAndroid && !post.post.locked)
IconButton(
onPressed: loggedInAction((_) => comment()),
onPressed: replyLoggedInAction((_) => comment()),
icon: const Icon(Icons.reply),
),
IconButton(
Expand All @@ -155,7 +160,7 @@ class FullPostPage extends HookWidget {
floatingActionButton: !Platform.isAndroid || post.post.locked
? null
: FloatingActionButton(
onPressed: loggedInAction((_) => comment()),
onPressed: replyLoggedInAction((_) => comment()),
child: const Icon(Icons.comment),
),
body: PullToRefresh(
Expand Down Expand Up @@ -207,4 +212,27 @@ class FullPostPage extends HookWidget {
child: const FullPostPage._(),
),
);
static Route fromApIdRoute(UserData userData, String apId,
{bool isSingleComment = false}) =>
SwipeablePageRoute(
builder: (context) {
return FederationResolver(
userData: userData,
query: apId,
loadingMessage: L10n.of(context).federated_post_info,
exists: (response) => isSingleComment
? response.comment != null
: response.post != null,
builder: (buildContext, object) => MobxProvider(
create: (context) => FullPostStore(
instanceHost: userData.instanceHost,
postId: isSingleComment
? object.comment!.post.id
: object.post!.post.id,
commentId:
isSingleComment ? object.comment!.comment.id : null)
..refresh(userData),
child: const FullPostPage._()));
},
);
}
32 changes: 15 additions & 17 deletions lib/pages/view_on_menu.dart
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:lemmy_api_client/v3.dart';
import 'package:swipeable_page_route/swipeable_page_route.dart';

import '../hooks/memo_future.dart';
import '../hooks/stores.dart';
import '../l10n/l10n.dart';
import '../stores/accounts_store.dart';
import '../util/mobx_provider.dart';
import '../util/observer_consumers.dart';
import '../widgets/bottom_modal.dart';
import '../widgets/cached_network_image.dart';
import 'community/community_store.dart';
import 'community/federated_community.dart';
import 'community/community.dart';
import 'full_post/full_post.dart';

class ViewOnMenu extends HookWidget {
final void Function(UserData userData) onSelect;
Expand Down Expand Up @@ -81,25 +78,26 @@ class ViewOnMenu extends HookWidget {

static void open(
BuildContext context, void Function(UserData userData) onSelect) {
final store = context.read<CommunityStore>();

showBottomModal(
context: context,
builder: (context) => MobxProvider.value(
value: store,
child: ViewOnMenu(
onSelect: onSelect,
),
builder: (context) => ViewOnMenu(
onSelect: onSelect,
),
);
}

static void openForCommunity(
BuildContext context, CommunityView communityView) {
static void openForCommunity(BuildContext context, String actorId) {
ViewOnMenu.open(context, (UserData userData) {
Navigator.of(context)
.push(CommunityPage.fromApIdRoute(userData, actorId));
});
}

static void openForPost(BuildContext context, String actorId,
{bool isSingleComment = false}) {
ViewOnMenu.open(context, (UserData userData) {
Navigator.of(context).push(SwipeablePageRoute(
builder: (context) => FederatedCommunityPage(
userData, communityView.community.actorId)));
Navigator.of(context).push(FullPostPage.fromApIdRoute(userData, actorId,
isSingleComment: isSingleComment));
});
}
}
24 changes: 13 additions & 11 deletions lib/widgets/comment/comment_actions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import 'package:lemmy_api_client/v3.dart';

import '../../hooks/logged_in_action.dart';
import '../../l10n/l10n.dart';
import '../../pages/view_on_menu.dart';
import '../../util/goto.dart';
import '../../util/observer_consumers.dart';
import '../tile_action.dart';
Expand All @@ -17,10 +18,13 @@ class CommentActions extends HookWidget {

@override
Widget build(BuildContext context) {
final commentStore = context.read<CommentStore>();
final loggedInAction = useLoggedInAction(
context.select<CommentStore, String>(
(store) => store.comment.instanceHost,
),
commentStore.comment.instanceHost,
fallback: () {
ViewOnMenu.openForPost(context, commentStore.comment.comment.apId,
isSingleComment: true);
},
);

return ObserverBuilder<CommentStore>(
Expand Down Expand Up @@ -70,15 +74,13 @@ class CommentActions extends HookWidget {
? L10n.of(context).mark_as_unread
: L10n.of(context).mark_as_read,
),
if (store.detached)
TileAction(
icon: Icons.link,
onPressed: () => goToPost(
context, comment.instanceHost, post.id,
commentId: comment.id),
tooltip: 'go to post',
),
const CommentMoreMenuButton(),
TileAction(
icon: Icons.link,
onPressed: () => goToPost(context, comment.instanceHost, post.id,
commentId: comment.id),
tooltip: 'go to post',
),
TileAction(
loading: store.savingState.isLoading,
icon:
Expand Down
7 changes: 7 additions & 0 deletions lib/widgets/comment/comment_more_menu_button.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import 'package:flutter_hooks/flutter_hooks.dart';

import '../../hooks/logged_in_action.dart';
import '../../l10n/l10n.dart';
import '../../pages/view_on_menu.dart';
import '../../stores/accounts_store.dart';
import '../../url_launcher.dart';
import '../../util/extensions/api.dart';
Expand Down Expand Up @@ -103,6 +104,12 @@ class _CommentMoreMenuPopup extends HookWidget {
Navigator.of(context).pop();
},
),
ListTile(
leading: const Icon(Icons.travel_explore),
title: Text(L10n.of(context).view_on),
onTap: () => ViewOnMenu.openForPost(context, comment.apId,
isSingleComment: true),
),
ListTile(
leading: Icon(shareIcon),
title: Text(L10n.of(context).share_url),
Expand Down
6 changes: 6 additions & 0 deletions lib/widgets/post/post_more_menu.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import '../../l10n/l10n.dart';
import '../../pages/create_post/create_post.dart';
import '../../pages/full_post/full_post_store.dart';
import '../../pages/settings/settings.dart';
import '../../pages/view_on_menu.dart';
import '../../stores/accounts_store.dart';
import '../../url_launcher.dart';
import '../../util/goto.dart';
Expand Down Expand Up @@ -84,6 +85,11 @@ class PostMoreMenu extends HookWidget {
Navigator.of(context).pop();
},
),
ListTile(
leading: const Icon(Icons.travel_explore),
title: Text(L10n.of(context).view_on),
onTap: () => ViewOnMenu.openForPost(context, post.post.apId),
),
if (isMine) ...[
ListTile(
leading: const Icon(Icons.edit),
Expand Down

0 comments on commit b2791b2

Please sign in to comment.