diff --git a/lib/community/utils/post_card_action_helpers.dart b/lib/community/utils/post_card_action_helpers.dart index f0c88b390..26e1a9fc4 100644 --- a/lib/community/utils/post_card_action_helpers.dart +++ b/lib/community/utils/post_card_action_helpers.dart @@ -18,6 +18,8 @@ import 'package:thunder/shared/advanced_share_sheet.dart'; import 'package:thunder/shared/picker_item.dart'; import 'package:thunder/shared/snackbar.dart'; import 'package:thunder/thunder/bloc/thunder_bloc.dart'; +import 'package:thunder/utils/instance.dart'; +import 'package:thunder/utils/navigate_instance.dart'; import 'package:thunder/utils/navigate_user.dart'; import 'package:lemmy_api_client/v3.dart'; @@ -28,6 +30,7 @@ import 'package:thunder/utils/global_context.dart'; enum PostCardAction { visitProfile, visitCommunity, + visitInstance, sharePost, shareMedia, shareLink, @@ -78,6 +81,11 @@ final List postCardActionItems = [ icon: Icons.person_search_rounded, label: AppLocalizations.of(GlobalContext.context)!.visitUserProfile, ), + ExtendedPostCardActions( + postCardAction: PostCardAction.visitInstance, + icon: Icons.language, + label: AppLocalizations.of(GlobalContext.context)!.visitInstance, + ), ExtendedPostCardActions( postCardAction: PostCardAction.sharePost, icon: Icons.share_rounded, @@ -230,6 +238,9 @@ void onSelected(BuildContext context, PostCardAction postCardAction, PostViewMed case PostCardAction.visitProfile: navigateToUserPage(context, userId: postViewMedia.postView.post.creatorId); break; + case PostCardAction.visitInstance: + navigateToInstancePage(context, instanceHost: fetchInstanceNameFromUrl(postViewMedia.postView.community.actorId)!); + break; case PostCardAction.sharePost: Share.share(postViewMedia.postView.post.apId); break; diff --git a/lib/community/widgets/community_sidebar.dart b/lib/community/widgets/community_sidebar.dart index 67a8c7e9a..da4760dfe 100644 --- a/lib/community/widgets/community_sidebar.dart +++ b/lib/community/widgets/community_sidebar.dart @@ -21,6 +21,7 @@ import 'package:thunder/core/enums/local_settings.dart'; import 'package:thunder/core/singletons/lemmy_client.dart'; import 'package:thunder/core/singletons/preferences.dart'; import 'package:thunder/feed/bloc/feed_bloc.dart'; +import 'package:thunder/instance/instance_view.dart'; import 'package:thunder/shared/common_markdown_body.dart'; import 'package:thunder/shared/snackbar.dart'; import 'package:thunder/shared/user_avatar.dart'; @@ -133,47 +134,8 @@ class _CommunitySidebarState extends State { const SidebarSectionHeader(value: "Host Instance"), Padding( padding: const EdgeInsets.symmetric(horizontal: 8.0), - child: Column( - children: [ - Row( - children: [ - CircleAvatar( - backgroundColor: widget.fullCommunityView!.site?.icon != null ? Colors.transparent : theme.colorScheme.secondaryContainer, - foregroundImage: widget.fullCommunityView!.site?.icon != null ? CachedNetworkImageProvider(widget.fullCommunityView!.site!.icon!) : null, - maxRadius: 24, - child: widget.fullCommunityView!.site?.icon == null - ? Text( - widget.fullCommunityView!.moderators.first.moderator!.name[0].toUpperCase(), - semanticsLabel: '', - style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16), - ) - : null, - ), - const SizedBox(width: 16.0), - Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Text( - widget.fullCommunityView!.site?.name ?? '', - overflow: TextOverflow.ellipsis, - maxLines: 1, - style: theme.textTheme.headlineSmall?.copyWith(fontWeight: FontWeight.w600), - ), - Flexible( - child: Text( - widget.fullCommunityView!.site?.description ?? '', - style: theme.textTheme.bodyMedium, - ), - ), - ], - ), - ], - ), - const Divider(), - CommonMarkdownBody(body: widget.fullCommunityView!.site?.sidebar ?? ''), - ], + child: InstanceView( + site: widget.fullCommunityView!.site!, ), ), ], diff --git a/lib/community/widgets/post_card.dart b/lib/community/widgets/post_card.dart index 2567d2e55..129f200bf 100644 --- a/lib/community/widgets/post_card.dart +++ b/lib/community/widgets/post_card.dart @@ -239,6 +239,7 @@ class _PostCardState extends State { context, widget.postViewMedia, actionsToInclude: [ + PostCardAction.visitInstance, PostCardAction.visitProfile, PostCardAction.visitCommunity, PostCardAction.blockCommunity, diff --git a/lib/community/widgets/post_card_view_comfortable.dart b/lib/community/widgets/post_card_view_comfortable.dart index 3b12d1796..572bd93bd 100644 --- a/lib/community/widgets/post_card_view_comfortable.dart +++ b/lib/community/widgets/post_card_view_comfortable.dart @@ -271,6 +271,7 @@ class PostCardViewComfortable extends StatelessWidget { context, postViewMedia, actionsToInclude: [ + PostCardAction.visitInstance, PostCardAction.visitProfile, PostCardAction.visitCommunity, PostCardAction.blockCommunity, diff --git a/lib/instance/instance_page.dart b/lib/instance/instance_page.dart new file mode 100644 index 000000000..3d4f333a8 --- /dev/null +++ b/lib/instance/instance_page.dart @@ -0,0 +1,59 @@ +import 'package:flutter/material.dart'; +import 'package:lemmy_api_client/v3.dart'; +import 'package:thunder/instance/instance_view.dart'; +import 'package:thunder/utils/instance.dart'; +import 'package:thunder/utils/links.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +class InstancePage extends StatefulWidget { + final Site site; + + const InstancePage({ + super.key, + required this.site, + }); + + @override + State createState() => _InstancePageState(); +} + +class _InstancePageState extends State { + @override + Widget build(BuildContext context) { + final AppLocalizations l10n = AppLocalizations.of(context)!; + final ThemeData theme = Theme.of(context); + + return Container( + color: theme.colorScheme.background, + child: SafeArea( + top: false, + child: CustomScrollView( + slivers: [ + SliverAppBar( + pinned: true, + title: Text(fetchInstanceNameFromUrl(widget.site.actorId) ?? ''), + actions: [ + IconButton( + tooltip: l10n.openInBrowser, + onPressed: () => openLink(context, url: widget.site.actorId), + icon: Icon( + Icons.open_in_browser_rounded, + semanticLabel: l10n.openInBrowser, + ), + ), + ], + ), + SliverToBoxAdapter( + child: Padding( + padding: const EdgeInsets.all(20), + child: Material( + child: InstanceView(site: widget.site), + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/instance/instance_view.dart b/lib/instance/instance_view.dart new file mode 100644 index 000000000..da0a051ad --- /dev/null +++ b/lib/instance/instance_view.dart @@ -0,0 +1,64 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; +import 'package:lemmy_api_client/v3.dart'; +import 'package:thunder/shared/common_markdown_body.dart'; + +class InstanceView extends StatelessWidget { + final Site site; + + const InstanceView({super.key, required this.site}); + + @override + Widget build(BuildContext context) { + final ThemeData theme = Theme.of(context); + + return Column( + children: [ + Row( + children: [ + CircleAvatar( + backgroundColor: site.icon != null ? Colors.transparent : theme.colorScheme.secondaryContainer, + foregroundImage: site.icon != null ? CachedNetworkImageProvider(site.icon!) : null, + maxRadius: 24, + child: site.icon == null + ? Text( + site.name[0], + semanticsLabel: '', + style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16), + ) + : null, + ), + const SizedBox(width: 16.0), + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + site.name, + overflow: TextOverflow.ellipsis, + maxLines: 1, + style: theme.textTheme.headlineSmall?.copyWith(fontWeight: FontWeight.w600), + ), + Row( + children: [ + Flexible( + child: Text( + site.description ?? '', + style: theme.textTheme.bodyMedium, + ), + ), + ], + ), + ], + ), + ), + ], + ), + const Divider(), + CommonMarkdownBody(body: site.sidebar ?? ''), + ], + ); + } +} diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 674b8f485..2dfb30bee 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -240,6 +240,8 @@ "@comment": {}, "comments": "Comments", "@comments": {}, + "visitInstance": "Visit Instance", + "openInBrowser": "Open in Browser", "postLocked": "Post locked. No replies allowed.", "@postLocked": {}, "replyingTo": "Replying to {author}", diff --git a/lib/utils/navigate_instance.dart b/lib/utils/navigate_instance.dart new file mode 100644 index 000000000..83c519cff --- /dev/null +++ b/lib/utils/navigate_instance.dart @@ -0,0 +1,38 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:lemmy_api_client/v3.dart'; +import 'package:swipeable_page_route/swipeable_page_route.dart'; +import 'package:thunder/core/auth/bloc/auth_bloc.dart'; +import 'package:thunder/instance/instance_page.dart'; +import 'package:thunder/thunder/bloc/thunder_bloc.dart'; +import 'package:thunder/utils/swipe.dart'; + +Future navigateToInstancePage(BuildContext context, {required String instanceHost}) async { + ThunderBloc thunderBloc = context.read(); + AuthBloc authBloc = context.read(); + + final bool reduceAnimations = thunderBloc.state.reduceAnimations; + + FullSiteView? fullSiteView; + try { + fullSiteView = await LemmyApiV3(instanceHost).run(const GetSite()).timeout(const Duration(seconds: 5)); + } catch (e) {} + + if (fullSiteView?.siteView?.site != null && context.mounted) { + Navigator.of(context).push( + SwipeablePageRoute( + transitionDuration: reduceAnimations ? const Duration(milliseconds: 100) : null, + backGestureDetectionWidth: 45, + canOnlySwipeFromEdge: !thunderBloc.state.enableFullScreenSwipeNavigationGesture, + builder: (context) => MultiBlocProvider( + providers: [ + BlocProvider.value(value: thunderBloc), + ], + child: InstancePage( + site: fullSiteView!.siteView!.site, + ), + ), + ), + ); + } +}