diff --git a/assets/i18n/en-US.json b/assets/i18n/en-US.json index f8c73559..c7525a26 100644 --- a/assets/i18n/en-US.json +++ b/assets/i18n/en-US.json @@ -637,6 +637,7 @@ "realmJoinSuccess": "Successfully joined the realm.", "discoverRealms": "Realms", "discoverPublishers": "Publishers", + "discoverShuffledPost": "Random Posts", "search": "Search", "publisherMembers": "Collaborators", "developerHub": "Developer Hub", diff --git a/lib/screens/chat/room_detail.g.dart b/lib/screens/chat/room_detail.g.dart index 60685d83..3187515b 100644 --- a/lib/screens/chat/room_detail.g.dart +++ b/lib/screens/chat/room_detail.g.dart @@ -7,7 +7,7 @@ part of 'room_detail.dart'; // ************************************************************************** String _$totalMessagesCountHash() => - r'a15c03461f25c2d4d39c0926509bf626ae2550a6'; + r'd55f1507aba2acdce5e468c1c2e15dba7640c571'; /// Copied from Dart SDK class _SystemHash { diff --git a/lib/screens/explore.dart b/lib/screens/explore.dart index ca3b1662..c9fdd626 100644 --- a/lib/screens/explore.dart +++ b/lib/screens/explore.dart @@ -399,6 +399,69 @@ class _DiscoveryActivityItem extends StatelessWidget { final items = data['items'] as List; final type = items.firstOrNull?['type'] ?? 'unknown'; + var flexWeights = isWideScreen(context) ? [3, 2, 1] : [4, 1]; + if (type == 'post') flexWeights = [3, 2]; + + final height = type == 'post' ? 280.0 : 180.0; + + final contentWidget = switch (type) { + 'post' => ListView.separated( + scrollDirection: Axis.horizontal, + itemCount: items.length, + separatorBuilder: (context, index) => const Gap(12), + padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4), + itemBuilder: (context, index) { + final item = items[index]; + return Container( + width: 320, + decoration: BoxDecoration( + border: Border.all( + width: 1 / MediaQuery.of(context).devicePixelRatio, + color: Theme.of(context).dividerColor.withOpacity(0.5), + ), + borderRadius: const BorderRadius.all(Radius.circular(8)), + ), + child: ClipRRect( + borderRadius: const BorderRadius.all(Radius.circular(8)), + child: SingleChildScrollView( + child: PostActionableItem( + item: SnPost.fromJson(item['data']), + isCompact: true, + ), + ), + ), + ); + }, + ), + _ => CarouselView.weighted( + flexWeights: flexWeights, + consumeMaxWeight: false, + enableSplash: false, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(8)), + ), + itemSnapping: false, + children: [ + for (final item in items) + switch (type) { + 'realm' => RealmCard( + realm: SnRealm.fromJson(item['data']), + maxWidth: 280, + ), + 'publisher' => PublisherCard( + publisher: SnPublisher.fromJson(item['data']), + maxWidth: 280, + ), + 'article' => WebArticleCard( + article: SnWebArticle.fromJson(item['data']), + maxWidth: 280, + ), + _ => const Placeholder(), + }, + ], + ), + }; + return Card( margin: EdgeInsets.symmetric(horizontal: 8, vertical: 4), child: Column( @@ -407,13 +470,20 @@ class _DiscoveryActivityItem extends StatelessWidget { Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ - const Icon(Symbols.explore, size: 19), + Icon(switch (type) { + 'realm' => Symbols.public, + 'publisher' => Symbols.account_circle, + 'article' => Symbols.auto_stories, + 'post' => Symbols.shuffle, + _ => Symbols.explore, + }, size: 19), const Gap(8), Text( (switch (type) { 'realm' => 'discoverRealms', 'publisher' => 'discoverPublishers', 'article' => 'discoverWebArticles', + 'post' => 'discoverShuffledPost', _ => 'unknown', }).tr(), style: Theme.of(context).textTheme.titleMedium, @@ -421,37 +491,8 @@ class _DiscoveryActivityItem extends StatelessWidget { ], ).padding(horizontal: 20, top: 8, bottom: 4), SizedBox( - height: 180, - child: ConstrainedBox( - constraints: const BoxConstraints(maxHeight: 200), - child: CarouselView.weighted( - flexWeights: - isWideScreen(context) ? [3, 2, 1] : [4, 1], - consumeMaxWeight: false, - enableSplash: false, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(8)), - ), - children: [ - for (final item in items) - switch (type) { - 'realm' => RealmCard( - realm: SnRealm.fromJson(item['data']), - maxWidth: 280, - ), - 'publisher' => PublisherCard( - publisher: SnPublisher.fromJson(item['data']), - maxWidth: 280, - ), - 'article' => WebArticleCard( - article: SnWebArticle.fromJson(item['data']), - maxWidth: 280, - ), - _ => Placeholder(), - }, - ], - ), - ), + height: height, + child: contentWidget, ).padding(bottom: 8, horizontal: 8), ], ), @@ -569,7 +610,8 @@ class ActivityListNotifier extends _$ActivityListNotifier if (cursor != null) 'cursor': cursor, 'take': take, if (filter != null) 'filter': filter, - if (kDebugMode) 'debugInclude': 'realms,publishers,articles', + if (kDebugMode) + 'debugInclude': 'realms,publishers,articles,shuffledPosts', }; final response = await client.get( @@ -584,12 +626,13 @@ class ActivityListNotifier extends _$ActivityListNotifier final hasMore = (items.firstOrNull?.type ?? 'empty') != 'empty'; final nextCursor = - items - .map((x) => x.createdAt) - .lastOrNull - ?.toUtc() - .toIso8601String() - .toString(); + items.isNotEmpty + ? items + .map((x) => x.createdAt) + .reduce((a, b) => a.isAfter(b) ? a : b) + .toUtc() + .toIso8601String() + : null; return CursorPagingData( items: items, diff --git a/lib/screens/explore.g.dart b/lib/screens/explore.g.dart index f5d59814..04da4d7a 100644 --- a/lib/screens/explore.g.dart +++ b/lib/screens/explore.g.dart @@ -7,7 +7,7 @@ part of 'explore.dart'; // ************************************************************************** String _$activityListNotifierHash() => - r'b75fd5c08d5f84ca433e16b7387d317ea72b91c9'; + r'a4968856ac34b59d47cfd4a7cbb39289aef2a1b1'; /// Copied from Dart SDK class _SystemHash { diff --git a/lib/widgets/post/post_item.dart b/lib/widgets/post/post_item.dart index 7dfadb38..19434daf 100644 --- a/lib/widgets/post/post_item.dart +++ b/lib/widgets/post/post_item.dart @@ -36,6 +36,7 @@ class PostActionableItem extends HookConsumerWidget { final bool isShowReference; final bool isEmbedReply; final bool isEmbedOpenable; + final bool isCompact; final double? borderRadius; final VoidCallback? onRefresh; final Function(SnPost)? onUpdate; @@ -48,6 +49,7 @@ class PostActionableItem extends HookConsumerWidget { this.isShowReference = true, this.isEmbedReply = true, this.isEmbedOpenable = false, + this.isCompact = false, this.borderRadius, this.onRefresh, this.onUpdate, @@ -76,6 +78,7 @@ class PostActionableItem extends HookConsumerWidget { isEmbedReply: isEmbedReply, isEmbedOpenable: isEmbedOpenable, isTextSelectable: false, + isCompact: isCompact, onRefresh: onRefresh, onUpdate: onUpdate, onOpen: onOpen, @@ -298,6 +301,7 @@ class PostItem extends HookConsumerWidget { final bool isEmbedOpenable; final bool isTextSelectable; final bool isTranslatable; + final bool isCompact; final VoidCallback? onRefresh; final Function(SnPost)? onUpdate; final VoidCallback? onOpen; @@ -311,6 +315,7 @@ class PostItem extends HookConsumerWidget { this.isEmbedOpenable = false, this.isTextSelectable = true, this.isTranslatable = true, + this.isCompact = false, this.onRefresh, this.onUpdate, this.onOpen, @@ -465,54 +470,64 @@ class PostItem extends HookConsumerWidget { PostHeader( item: item, isFullPost: isFullPost, + isCompact: isCompact, renderingPadding: renderingPadding, - trailing: IconButton( - icon: - mostReaction == null - ? const Icon(Symbols.add_reaction) - : Badge( - label: Center( - child: Text( - 'x${item.reactionsCount[mostReaction]}', - style: const TextStyle(fontSize: 11), - textAlign: TextAlign.center, - ), - ), - offset: const Offset(4, 20), - backgroundColor: Theme.of( - context, - ).colorScheme.primary.withOpacity(0.75), - textColor: Theme.of(context).colorScheme.onPrimary, - child: Text( - kReactionTemplates[mostReaction]?.icon ?? '', - style: const TextStyle(fontSize: 20), + trailing: + isCompact + ? null + : IconButton( + icon: + mostReaction == null + ? const Icon(Symbols.add_reaction) + : Badge( + label: Center( + child: Text( + 'x${item.reactionsCount[mostReaction]}', + style: const TextStyle(fontSize: 11), + textAlign: TextAlign.center, + ), + ), + offset: const Offset(4, 20), + backgroundColor: Theme.of( + context, + ).colorScheme.primary.withOpacity(0.75), + textColor: + Theme.of(context).colorScheme.onPrimary, + child: Text( + kReactionTemplates[mostReaction]?.icon ?? '', + style: const TextStyle(fontSize: 20), + ), + ), + style: ButtonStyle( + backgroundColor: WidgetStatePropertyAll( + (item.reactionsMade[mostReaction] ?? false) + ? Theme.of( + context, + ).colorScheme.primary.withOpacity(0.5) + : null, ), ), - style: ButtonStyle( - backgroundColor: WidgetStatePropertyAll( - (item.reactionsMade[mostReaction] ?? false) - ? Theme.of(context).colorScheme.primary.withOpacity(0.5) - : null, - ), - ), - onPressed: () { - showModalBottomSheet( - context: context, - useRootNavigator: true, - builder: (BuildContext context) { - return _PostReactionSheet( - reactionsCount: item.reactionsCount, - reactionsMade: item.reactionsMade, - onReact: (symbol, attitude) { - reactPost(symbol, attitude); + onPressed: () { + showModalBottomSheet( + context: context, + useRootNavigator: true, + builder: (BuildContext context) { + return _PostReactionSheet( + reactionsCount: item.reactionsCount, + reactionsMade: item.reactionsMade, + onReact: (symbol, attitude) { + reactPost(symbol, attitude); + }, + ); + }, + ); }, - ); - }, - ); - }, - padding: EdgeInsets.zero, - visualDensity: const VisualDensity(horizontal: -3, vertical: -3), - ), + padding: EdgeInsets.zero, + visualDensity: const VisualDensity( + horizontal: -3, + vertical: -3, + ), + ), ), PostBody( item: item, diff --git a/lib/widgets/post/post_shared.dart b/lib/widgets/post/post_shared.dart index c088da75..8132aaf3 100644 --- a/lib/widgets/post/post_shared.dart +++ b/lib/widgets/post/post_shared.dart @@ -532,6 +532,7 @@ class PostHeader extends StatelessWidget { final bool isInteractive; final EdgeInsets renderingPadding; final bool isRelativeTime; + final bool isCompact; const PostHeader({ super.key, @@ -541,6 +542,7 @@ class PostHeader extends StatelessWidget { this.isInteractive = true, this.renderingPadding = EdgeInsets.zero, this.isRelativeTime = true, + this.isCompact = false, }); @override @@ -584,11 +586,27 @@ class PostHeader extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.center, spacing: 4, children: [ - Text(item.publisher.nick).bold(), + Flexible( + child: + Text( + item.publisher.nick, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ).bold(), + ), if (item.publisher.verification != null) VerificationMark(mark: item.publisher.verification!), if (item.realm == null) - Text('@${item.publisher.name}').fontSize(11) + Flexible( + child: + isCompact + ? const SizedBox.shrink() + : Text( + '@${item.publisher.name}', + maxLines: 1, + overflow: TextOverflow.ellipsis, + ).fontSize(11), + ) else ...([ const Icon(Symbols.arrow_right, size: 14),