From ba3ad9feede00cfc7455f290f4f9e2fd9212de2d Mon Sep 17 00:00:00 2001 From: Hamlet Jiang Su Date: Thu, 18 Jan 2024 10:15:25 -0800 Subject: [PATCH] initial refactor of post card --- lib/community/widgets/post_card.dart | 6 - .../widgets/post_card_type_badge.dart | 141 +++++++--------- .../widgets/post_card_view_compact.dart | 157 ++++++++---------- lib/post/widgets/post_view.dart | 5 +- .../pages/post_appearance_settings_page.dart | 6 - 5 files changed, 137 insertions(+), 178 deletions(-) diff --git a/lib/community/widgets/post_card.dart b/lib/community/widgets/post_card.dart index 6959ac8e7..28b23cce2 100644 --- a/lib/community/widgets/post_card.dart +++ b/lib/community/widgets/post_card.dart @@ -192,16 +192,10 @@ class _PostCardState extends State { child: state.useCompactView ? PostCardViewCompact( postViewMedia: widget.postViewMedia, - showThumbnailPreviewOnRight: state.showThumbnailPreviewOnRight, - showTextPostIndicator: state.showTextPostIndicator, - showPostAuthor: state.showPostAuthor, - hideNsfwPreviews: state.hideNsfwPreviews, - markPostReadOnMediaView: state.markPostReadOnMediaView, communityMode: widget.communityMode, isUserLoggedIn: isUserLoggedIn, listingType: widget.listingType, navigateToPost: ({PostViewMedia? postViewMedia}) async => await navigateToPost(context, postViewMedia: widget.postViewMedia), - indicateRead: widget.indicateRead, ) : PostCardViewComfortable( postViewMedia: widget.postViewMedia, diff --git a/lib/community/widgets/post_card_type_badge.dart b/lib/community/widgets/post_card_type_badge.dart index 445832a20..c47cfae0f 100644 --- a/lib/community/widgets/post_card_type_badge.dart +++ b/lib/community/widgets/post_card_type_badge.dart @@ -1,103 +1,88 @@ import 'package:flutter/material.dart'; + import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:thunder/core/enums/media_type.dart'; import 'package:thunder/core/theme/bloc/theme_bloc.dart'; -import '../../core/enums/media_type.dart'; -import '../../core/models/post_view_media.dart'; +/// Base representation of a media type badge. Holds the icon and color. +class MediaTypeBadgeItem { + /// The icon associated with the media type + final Icon icon; + + /// The color associated with the media type + final Color baseColor; + + const MediaTypeBadgeItem({required this.baseColor, required this.icon}); +} class TypeBadge extends StatelessWidget { - const TypeBadge({ - super.key, - required this.postViewMedia, - required this.read, - }); + /// Determines whether the badge should be dimmed or not. This is usually to indicate when a post has been read. + final bool dim; + + /// The media type of the badge. This is used to determine the badge color and icon. + final MediaType mediaType; - final PostViewMedia postViewMedia; - final bool read; + const TypeBadge({super.key, required this.dim, required this.mediaType}); @override Widget build(BuildContext context) { final theme = Theme.of(context); - - Color getMaterialColor(Color blendColor) { - return Color.alphaBlend(theme.colorScheme.primaryContainer.withOpacity(0.6), blendColor).withOpacity(read ? 0.55 : 1); - } - - Color getIconColor(Color blendColor) { - return Color.alphaBlend(theme.colorScheme.onPrimaryContainer.withOpacity(0.9), blendColor).withOpacity(read ? 0.55 : 1); - } - final bool darkTheme = context.read().state.useDarkTheme; + const borderRadius = BorderRadius.only(topLeft: Radius.circular(15), bottomLeft: Radius.circular(4), bottomRight: Radius.circular(12), topRight: Radius.circular(4)); + + Map mediaTypeItems = { + MediaType.text: MediaTypeBadgeItem( + baseColor: Colors.green, + icon: Icon(size: 17, Icons.wysiwyg_rounded, color: getIconColor(theme, Colors.green)), + ), + MediaType.link: MediaTypeBadgeItem( + baseColor: Colors.blue, + icon: Icon(size: 19, Icons.link_rounded, color: getIconColor(theme, Colors.blue)), + ), + MediaType.image: MediaTypeBadgeItem( + baseColor: Colors.red, + icon: Icon(size: 17, Icons.image_rounded, color: getIconColor(theme, Colors.red)), + ) + }; return SizedBox( height: 28, width: 28, child: Material( - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(15), - bottomLeft: Radius.circular(4), - bottomRight: Radius.circular(12), - topRight: Radius.circular(4), - ), + borderRadius: borderRadius, // This is the thin sliver between the badge and the preview. // It should be made to match the read background color in the compact file. - color: read - ? Color.alphaBlend( - theme.colorScheme.onBackground.withOpacity(darkTheme ? 0.05 : 0.075), - theme.colorScheme.background, - ) - : theme.colorScheme.background, + color: dim ? Color.alphaBlend(theme.colorScheme.onBackground.withOpacity(darkTheme ? 0.05 : 0.075), theme.colorScheme.background) : theme.colorScheme.background, child: Padding( - padding: const EdgeInsets.only( - left: 2.5, - top: 2.5, - ), - child: postViewMedia.media.isEmpty - ? Material( - borderRadius: const BorderRadius.only( - bottomLeft: Radius.circular(4), - bottomRight: Radius.circular(12), - topLeft: Radius.circular(12), - topRight: Radius.circular(4), - ), - color: getMaterialColor(Colors.green), - child: Icon( - size: 17, - Icons.wysiwyg_rounded, - color: getIconColor(Colors.green), - ), - ) - : postViewMedia.media.firstOrNull?.mediaType == MediaType.link - ? Material( - borderRadius: const BorderRadius.only( - bottomLeft: Radius.circular(4), - bottomRight: Radius.circular(12), - topLeft: Radius.circular(12), - topRight: Radius.circular(4), - ), - color: getMaterialColor(Colors.blue), - child: Icon( - size: 19, - Icons.link_rounded, - color: getIconColor(Colors.blue), - ), - ) - : Material( - borderRadius: const BorderRadius.only( - bottomLeft: Radius.circular(4), - bottomRight: Radius.circular(12), - topLeft: Radius.circular(12), - topRight: Radius.circular(4), - ), - color: getMaterialColor(Colors.red), - child: Icon( - size: 17, - Icons.image_outlined, - color: getIconColor(Colors.red), - ), - ), + padding: const EdgeInsets.only(left: 2.5, top: 2.5), + child: switch (mediaType) { + MediaType.text => typeBadgeItem(context, mediaTypeItems[MediaType.text]!), + MediaType.link => typeBadgeItem(context, mediaTypeItems[MediaType.link]!), + MediaType.image => typeBadgeItem(context, mediaTypeItems[MediaType.image]!), + _ => typeBadgeItem(context, mediaTypeItems[MediaType.text]!), + }, ), ), ); } + + Widget typeBadgeItem(context, MediaTypeBadgeItem mediaTypeBadgeItem) { + final theme = Theme.of(context); + const innerBorderRadius = BorderRadius.only(topLeft: Radius.circular(12), bottomLeft: Radius.circular(4), bottomRight: Radius.circular(12), topRight: Radius.circular(4)); + + return Material( + borderRadius: innerBorderRadius, + color: getMaterialColor(theme, mediaTypeBadgeItem.baseColor), + child: mediaTypeBadgeItem.icon, + ); + } + + Color getMaterialColor(ThemeData theme, Color blendColor) { + return Color.alphaBlend(theme.colorScheme.primaryContainer.withOpacity(0.6), blendColor).withOpacity(dim ? 0.55 : 1); + } + + Color getIconColor(ThemeData theme, Color blendColor) { + return Color.alphaBlend(theme.colorScheme.onPrimaryContainer.withOpacity(0.9), blendColor).withOpacity(dim ? 0.55 : 1); + } } diff --git a/lib/community/widgets/post_card_view_compact.dart b/lib/community/widgets/post_card_view_compact.dart index fb4072392..199c3e72a 100644 --- a/lib/community/widgets/post_card_view_compact.dart +++ b/lib/community/widgets/post_card_view_compact.dart @@ -1,13 +1,15 @@ import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:lemmy_api_client/v3.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:html_unescape/html_unescape_small.dart'; import 'package:thunder/account/bloc/account_bloc.dart'; import 'package:thunder/community/widgets/post_card_metadata.dart'; import 'package:thunder/community/widgets/post_card_type_badge.dart'; +import 'package:thunder/core/auth/bloc/auth_bloc.dart'; import 'package:thunder/core/enums/font_scale.dart'; +import 'package:thunder/core/enums/media_type.dart'; import 'package:thunder/core/enums/view_mode.dart'; import 'package:thunder/core/models/post_view_media.dart'; import 'package:thunder/core/theme/bloc/theme_bloc.dart'; @@ -16,29 +18,17 @@ import 'package:thunder/thunder/bloc/thunder_bloc.dart'; class PostCardViewCompact extends StatelessWidget { final PostViewMedia postViewMedia; - final bool showThumbnailPreviewOnRight; - final bool showTextPostIndicator; - final bool showPostAuthor; - final bool hideNsfwPreviews; final bool communityMode; - final bool markPostReadOnMediaView; final bool isUserLoggedIn; final ListingType? listingType; final void Function({PostViewMedia? postViewMedia})? navigateToPost; - final bool indicateRead; const PostCardViewCompact({ super.key, required this.postViewMedia, - required this.showThumbnailPreviewOnRight, - required this.showTextPostIndicator, - required this.showPostAuthor, - required this.hideNsfwPreviews, required this.communityMode, - required this.markPostReadOnMediaView, required this.isUserLoggedIn, required this.listingType, - required this.indicateRead, this.navigateToPost, }); @@ -47,6 +37,10 @@ class PostCardViewCompact extends StatelessWidget { final theme = Theme.of(context); final ThunderState state = context.watch().state; + bool showThumbnailPreviewOnRight = state.showThumbnailPreviewOnRight; + bool showTextPostIndicator = state.showTextPostIndicator; + bool indicateRead = state.dimReadPosts; + final showCommunitySubscription = (listingType == ListingType.all || listingType == ListingType.local) && isUserLoggedIn && context.read().state.subsciptions.map((subscription) => subscription.community.actorId).contains(postViewMedia.postView.community.actorId); @@ -62,42 +56,12 @@ class PostCardViewCompact extends StatelessWidget { return Container( color: indicateRead && postViewMedia.postView.read ? theme.colorScheme.onBackground.withOpacity(darkTheme ? 0.05 : 0.075) : null, - padding: const EdgeInsets.only( - bottom: 8.0, - top: 6, - ), + padding: const EdgeInsets.only(bottom: 8.0, top: 6), child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ !showThumbnailPreviewOnRight && (postViewMedia.media.isNotEmpty || showTextPostIndicator) - ? ExcludeSemantics( - child: Stack( - alignment: AlignmentDirectional.bottomEnd, - children: [ - Padding( - padding: const EdgeInsets.symmetric( - horizontal: 10.0, - vertical: 4, - ), - child: MediaView( - scrapeMissingPreviews: state.scrapeMissingPreviews, - postView: postViewMedia, - showFullHeightImages: false, - hideNsfwPreviews: hideNsfwPreviews, - markPostReadOnMediaView: markPostReadOnMediaView, - viewMode: ViewMode.compact, - isUserLoggedIn: isUserLoggedIn, - navigateToPost: navigateToPost, - read: indicateRead && postViewMedia.postView.read, - ), - ), - Padding( - padding: const EdgeInsets.only(right: 6, bottom: 0), - child: TypeBadge(postViewMedia: postViewMedia, read: indicateRead && postViewMedia.postView.read), - ), - ], - ), - ) + ? ThumbnailPreview(postViewMedia: postViewMedia, navigateToPost: navigateToPost) : const SizedBox(width: 8.0), Expanded( child: Column( @@ -108,11 +72,12 @@ class PostCardViewCompact extends StatelessWidget { children: [ if (postViewMedia.postView.post.locked) ...[ WidgetSpan( - child: Icon( - Icons.lock, - color: indicateRead && postViewMedia.postView.read ? Colors.red.withOpacity(0.55) : Colors.red, - size: 15 * textScaleFactor, - )), + child: Icon( + Icons.lock, + color: indicateRead && postViewMedia.postView.read ? Colors.red.withOpacity(0.55) : Colors.red, + size: 15 * textScaleFactor, + ), + ), ], if (postViewMedia.postView.saved) WidgetSpan( @@ -132,11 +97,7 @@ class PostCardViewCompact extends StatelessWidget { ), ), if (postViewMedia.postView.post.featuredCommunity || postViewMedia.postView.post.featuredLocal || postViewMedia.postView.saved || postViewMedia.postView.post.locked) - const WidgetSpan( - child: SizedBox( - width: 3.5, - ), - ), + const WidgetSpan(child: SizedBox(width: 3.5)), TextSpan( text: HtmlUnescape().convert(postViewMedia.postView.post.name), style: theme.textTheme.bodyMedium?.copyWith( @@ -176,40 +137,64 @@ class PostCardViewCompact extends StatelessWidget { ), ), showThumbnailPreviewOnRight && (postViewMedia.media.isNotEmpty || showTextPostIndicator) - ? ExcludeSemantics( - child: Stack( - alignment: AlignmentDirectional.bottomEnd, - children: [ - Padding( - padding: const EdgeInsets.symmetric( - horizontal: 10.0, - vertical: 4, - ), - child: MediaView( - scrapeMissingPreviews: state.scrapeMissingPreviews, - postView: postViewMedia, - showFullHeightImages: false, - hideNsfwPreviews: hideNsfwPreviews, - markPostReadOnMediaView: markPostReadOnMediaView, - viewMode: ViewMode.compact, - isUserLoggedIn: isUserLoggedIn, - navigateToPost: navigateToPost, - read: indicateRead && postViewMedia.postView.read, - ), - ), - Padding( - padding: const EdgeInsets.only(right: 6, bottom: 0), - child: TypeBadge( - postViewMedia: postViewMedia, - read: indicateRead && postViewMedia.postView.read, - ), - ), - ], - ), - ) + ? ThumbnailPreview(postViewMedia: postViewMedia, navigateToPost: navigateToPost) : const SizedBox(width: 8.0), ], ), ); } } + +/// Displays the thumbnail preview for the post. This can be text, media, or links. +class ThumbnailPreview extends StatelessWidget { + /// The [PostViewMedia] to display the thumbnail preview for + final PostViewMedia postViewMedia; + + /// The callback function to navigate to the post + final void Function({PostViewMedia? postViewMedia})? navigateToPost; + + const ThumbnailPreview({ + super.key, + required this.postViewMedia, + required this.navigateToPost, + }); + + @override + Widget build(BuildContext context) { + final state = context.read().state; + final isUserLoggedIn = context.read().state.isLoggedIn; + + final indicateRead = state.dimReadPosts; + final hideNsfwPreviews = state.hideNsfwPreviews; + final markPostReadOnMediaView = state.markPostReadOnMediaView; + + return ExcludeSemantics( + child: Stack( + alignment: AlignmentDirectional.bottomEnd, + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 10.0, vertical: 4), + child: MediaView( + scrapeMissingPreviews: state.scrapeMissingPreviews, + postView: postViewMedia, + showFullHeightImages: false, + hideNsfwPreviews: hideNsfwPreviews, + markPostReadOnMediaView: markPostReadOnMediaView, + viewMode: ViewMode.compact, + isUserLoggedIn: isUserLoggedIn, + navigateToPost: navigateToPost, + read: indicateRead && postViewMedia.postView.read, + ), + ), + Padding( + padding: const EdgeInsets.only(right: 6, bottom: 0), + child: TypeBadge( + mediaType: postViewMedia.media.firstOrNull?.mediaType ?? MediaType.text, + dim: indicateRead && postViewMedia.postView.read, + ), + ), + ], + ), + ); + } +} diff --git a/lib/post/widgets/post_view.dart b/lib/post/widgets/post_view.dart index 28ec507d6..bb6e7070c 100644 --- a/lib/post/widgets/post_view.dart +++ b/lib/post/widgets/post_view.dart @@ -24,6 +24,7 @@ import 'package:thunder/core/auth/helpers/fetch_account.dart'; import 'package:thunder/core/enums/font_scale.dart'; import 'package:thunder/core/enums/full_name_separator.dart'; import 'package:thunder/core/enums/local_settings.dart'; +import 'package:thunder/core/enums/media_type.dart'; import 'package:thunder/core/enums/post_body_view_type.dart'; import 'package:thunder/core/enums/view_mode.dart'; import 'package:thunder/core/singletons/lemmy_client.dart'; @@ -558,8 +559,8 @@ class _PostSubviewState extends State with SingleTickerProviderStat Padding( padding: const EdgeInsets.only(right: 6, bottom: 0), child: TypeBadge( - postViewMedia: postViewMedia, - read: false, + mediaType: postViewMedia.media.firstOrNull?.mediaType ?? MediaType.text, + dim: false, ), ), ], diff --git a/lib/settings/pages/post_appearance_settings_page.dart b/lib/settings/pages/post_appearance_settings_page.dart index 458f78a48..03f5fc443 100644 --- a/lib/settings/pages/post_appearance_settings_page.dart +++ b/lib/settings/pages/post_appearance_settings_page.dart @@ -354,15 +354,9 @@ class _PostAppearanceSettingsPageState extends State ? IgnorePointer( child: PostCardViewCompact( postViewMedia: snapshot.data![index]!, - showThumbnailPreviewOnRight: showThumbnailPreviewOnRight, - showTextPostIndicator: showTextPostIndicator, - showPostAuthor: showPostAuthor, - hideNsfwPreviews: hideNsfwPreviews, communityMode: false, - markPostReadOnMediaView: false, isUserLoggedIn: true, listingType: ListingType.all, - indicateRead: dimReadPosts, ), ) : IgnorePointer(