💄 New media post layout
This commit is contained in:
@@ -9,8 +9,10 @@ import 'package:island/models/post.dart';
|
||||
import 'package:island/pods/network.dart';
|
||||
import 'package:island/pods/userinfo.dart';
|
||||
import 'package:island/screens/posts/compose.dart';
|
||||
import 'package:island/services/responsive.dart';
|
||||
import 'package:island/widgets/alert.dart';
|
||||
import 'package:island/widgets/app_scaffold.dart';
|
||||
import 'package:island/widgets/content/cloud_file_collection.dart';
|
||||
import 'package:island/widgets/extended_refresh_indicator.dart';
|
||||
import 'package:island/widgets/post/post_award_sheet.dart';
|
||||
import 'package:island/widgets/post/post_item.dart';
|
||||
@@ -19,6 +21,7 @@ import 'package:island/widgets/post/post_pin_sheet.dart';
|
||||
import 'package:island/widgets/post/post_quick_reply.dart';
|
||||
import 'package:island/widgets/post/compose_sheet.dart';
|
||||
import 'package:island/widgets/post/post_replies.dart';
|
||||
import 'package:island/widgets/post/post_shared.dart';
|
||||
import 'package:island/widgets/response.dart';
|
||||
import 'package:island/utils/share_utils.dart';
|
||||
import 'package:island/widgets/safety/abuse_report_helper.dart';
|
||||
@@ -62,6 +65,10 @@ class PostState extends Notifier<AsyncValue<SnPost?>> {
|
||||
}
|
||||
}
|
||||
|
||||
bool _isMediaPost(SnPost? post) {
|
||||
return post != null && post.type == 0 && post.attachments.isNotEmpty;
|
||||
}
|
||||
|
||||
class PostActionButtons extends HookConsumerWidget {
|
||||
final SnPost post;
|
||||
final EdgeInsets renderingPadding;
|
||||
@@ -435,6 +442,88 @@ class PostActionButtons extends HookConsumerWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class _PostDetailLargeScreenLayout extends StatelessWidget {
|
||||
final SnPost post;
|
||||
final WidgetRef ref;
|
||||
final String postId;
|
||||
final Function(SnPost) onUpdate;
|
||||
final VoidCallback onRefresh;
|
||||
|
||||
const _PostDetailLargeScreenLayout({
|
||||
required this.post,
|
||||
required this.ref,
|
||||
required this.postId,
|
||||
required this.onUpdate,
|
||||
required this.onRefresh,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 3,
|
||||
child: Material(
|
||||
color: Theme.of(context).cardTheme.color,
|
||||
elevation: 8,
|
||||
child: Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: CloudFileList(
|
||||
files: post.attachments,
|
||||
disableConstraint: true,
|
||||
padding: EdgeInsets.zero,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: CustomScrollView(
|
||||
slivers: [
|
||||
SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
PostHeader(
|
||||
item: post,
|
||||
isFullPost: true,
|
||||
isCompact: false,
|
||||
renderingPadding: EdgeInsets.zero,
|
||||
),
|
||||
const Gap(8),
|
||||
PostBody(
|
||||
item: post,
|
||||
isFullPost: true,
|
||||
isTextSelectable: true,
|
||||
renderingPadding: EdgeInsets.zero,
|
||||
hideAttachments: true,
|
||||
textScale: post.type == 1 ? 1.2 : 1.1,
|
||||
),
|
||||
const Gap(12),
|
||||
PostActionButtons(
|
||||
post: post,
|
||||
renderingPadding: EdgeInsets.zero,
|
||||
onRefresh: onRefresh,
|
||||
onUpdate: onUpdate,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
PostRepliesList(postId: postId, maxWidth: 800),
|
||||
SliverGap(MediaQuery.of(context).padding.bottom + 80),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class PostDetailScreen extends HookConsumerWidget {
|
||||
final String id;
|
||||
const PostDetailScreen({super.key, required this.id});
|
||||
@@ -452,6 +541,8 @@ class PostDetailScreen extends HookConsumerWidget {
|
||||
),
|
||||
body: postState.when(
|
||||
data: (post) {
|
||||
final isMediaPostLayout = isWideScreen(context) && _isMediaPost(post);
|
||||
|
||||
return Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
@@ -460,64 +551,78 @@ class PostDetailScreen extends HookConsumerWidget {
|
||||
ref.invalidate(postProvider(id));
|
||||
ref.read(postRepliesProvider(id).notifier).refresh();
|
||||
},
|
||||
child: CustomScrollView(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
slivers: [
|
||||
SliverToBoxAdapter(
|
||||
child: Center(
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(maxWidth: 800),
|
||||
child: PostItem(
|
||||
item: post!,
|
||||
isFullPost: true,
|
||||
isEmbedReply: false,
|
||||
textScale: post.type == 1 ? 1.2 : 1.1,
|
||||
onUpdate: (newItem) {
|
||||
// Update the local state with the new post data
|
||||
ref
|
||||
.read(postStateProvider(id).notifier)
|
||||
.updatePost(newItem);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: Center(
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(maxWidth: 800),
|
||||
child: PostActionButtons(
|
||||
post: post,
|
||||
renderingPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 8,
|
||||
child: isMediaPostLayout
|
||||
? _PostDetailLargeScreenLayout(
|
||||
post: post!,
|
||||
ref: ref,
|
||||
postId: id,
|
||||
onUpdate: (newItem) {
|
||||
ref
|
||||
.read(postStateProvider(id).notifier)
|
||||
.updatePost(newItem);
|
||||
},
|
||||
onRefresh: () {
|
||||
ref.invalidate(postProvider(id));
|
||||
ref.read(postRepliesProvider(id).notifier).refresh();
|
||||
},
|
||||
)
|
||||
: CustomScrollView(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
slivers: [
|
||||
SliverToBoxAdapter(
|
||||
child: Center(
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(maxWidth: 800),
|
||||
child: PostItem(
|
||||
item: post!,
|
||||
isFullPost: true,
|
||||
isEmbedReply: false,
|
||||
textScale: post.type == 1 ? 1.2 : 1.1,
|
||||
onUpdate: (newItem) {
|
||||
ref
|
||||
.read(postStateProvider(id).notifier)
|
||||
.updatePost(newItem);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
onRefresh: () {
|
||||
ref.invalidate(postProvider(id));
|
||||
ref
|
||||
.read(postRepliesProvider(id).notifier)
|
||||
.refresh();
|
||||
},
|
||||
onUpdate: (newItem) {
|
||||
ref
|
||||
.read(postStateProvider(id).notifier)
|
||||
.updatePost(newItem);
|
||||
},
|
||||
),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: Center(
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(maxWidth: 800),
|
||||
child: PostActionButtons(
|
||||
post: post,
|
||||
renderingPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 8,
|
||||
),
|
||||
onRefresh: () {
|
||||
ref.invalidate(postProvider(id));
|
||||
ref
|
||||
.read(postRepliesProvider(id).notifier)
|
||||
.refresh();
|
||||
},
|
||||
onUpdate: (newItem) {
|
||||
ref
|
||||
.read(postStateProvider(id).notifier)
|
||||
.updatePost(newItem);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
PostRepliesList(postId: id, maxWidth: 800),
|
||||
SliverGap(MediaQuery.of(context).padding.bottom + 80),
|
||||
],
|
||||
),
|
||||
),
|
||||
PostRepliesList(postId: id, maxWidth: 800),
|
||||
SliverGap(MediaQuery.of(context).padding.bottom + 80),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (user.value != null)
|
||||
if (user.value != null && !isMediaPostLayout)
|
||||
Positioned(
|
||||
bottom: 16 + MediaQuery.of(context).padding.bottom,
|
||||
left: 16,
|
||||
right: 16,
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(maxWidth: 660),
|
||||
constraints: BoxConstraints(maxWidth: 800),
|
||||
child: postState.when(
|
||||
data: (post) => PostQuickReply(
|
||||
parent: post!,
|
||||
|
||||
@@ -167,7 +167,9 @@ class _PostsSearchTab extends HookConsumerWidget {
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8,
|
||||
),
|
||||
child: const PostItemSkeleton(),
|
||||
child: const PostItemSkeleton(
|
||||
maxWidth: double.infinity,
|
||||
),
|
||||
),
|
||||
itemBuilder: (context, index, post) {
|
||||
return Card(
|
||||
@@ -320,7 +322,7 @@ class _PostsSearchTab extends HookConsumerWidget {
|
||||
isRefreshable: false,
|
||||
footerSkeletonChild: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
child: const PostItemSkeleton(),
|
||||
child: const PostItemSkeleton(maxWidth: double.infinity),
|
||||
),
|
||||
itemBuilder: (context, index, post) {
|
||||
return Center(
|
||||
|
||||
@@ -67,6 +67,7 @@ class PostActionableItem extends HookConsumerWidget {
|
||||
final bool isEmbedReply;
|
||||
final bool isEmbedOpenable;
|
||||
final bool isCompact;
|
||||
final bool hideAttachments;
|
||||
final double? borderRadius;
|
||||
final VoidCallback? onRefresh;
|
||||
final Function(SnPost)? onUpdate;
|
||||
@@ -80,6 +81,7 @@ class PostActionableItem extends HookConsumerWidget {
|
||||
this.isEmbedReply = true,
|
||||
this.isEmbedOpenable = false,
|
||||
this.isCompact = false,
|
||||
this.hideAttachments = false,
|
||||
this.borderRadius,
|
||||
this.onRefresh,
|
||||
this.onUpdate,
|
||||
@@ -110,6 +112,7 @@ class PostActionableItem extends HookConsumerWidget {
|
||||
isEmbedOpenable: isEmbedOpenable,
|
||||
isTextSelectable: false,
|
||||
isCompact: isCompact,
|
||||
hideAttachments: hideAttachments,
|
||||
onRefresh: onRefresh,
|
||||
onUpdate: onUpdate,
|
||||
onOpen: onOpen,
|
||||
@@ -308,6 +311,7 @@ class PostItem extends HookConsumerWidget {
|
||||
final bool isTextSelectable;
|
||||
final bool isTranslatable;
|
||||
final bool isCompact;
|
||||
final bool hideAttachments;
|
||||
final double? textScale;
|
||||
final VoidCallback? onRefresh;
|
||||
final Function(SnPost)? onUpdate;
|
||||
@@ -323,6 +327,7 @@ class PostItem extends HookConsumerWidget {
|
||||
this.isTextSelectable = true,
|
||||
this.isTranslatable = true,
|
||||
this.isCompact = false,
|
||||
this.hideAttachments = false,
|
||||
this.textScale,
|
||||
this.onRefresh,
|
||||
this.onUpdate,
|
||||
@@ -569,6 +574,7 @@ class PostItem extends HookConsumerWidget {
|
||||
isTextSelectable: isTextSelectable,
|
||||
translationSection: translationSection,
|
||||
renderingPadding: renderingPadding,
|
||||
hideAttachments: hideAttachments,
|
||||
),
|
||||
if (item.embedView != null)
|
||||
EmbedViewRenderer(
|
||||
|
||||
@@ -69,7 +69,7 @@ class SliverPostList extends HookConsumerWidget {
|
||||
isSliver: true,
|
||||
footerSkeletonChild: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
child: const PostItemSkeleton(),
|
||||
child: PostItemSkeleton(maxWidth: maxWidth ?? double.infinity),
|
||||
),
|
||||
itemBuilder: (context, index, post) {
|
||||
if (maxWidth != null) {
|
||||
|
||||
@@ -50,7 +50,7 @@ class PostRepliesList extends HookConsumerWidget {
|
||||
|
||||
final skeletonItem = Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
child: const PostItemSkeleton(),
|
||||
child: PostItemSkeleton(maxWidth: maxWidth ?? double.infinity),
|
||||
);
|
||||
|
||||
return PaginationList(
|
||||
|
||||
@@ -870,6 +870,7 @@ class PostBody extends ConsumerWidget {
|
||||
final EdgeInsets renderingPadding;
|
||||
final bool isRelativeTime;
|
||||
final bool hideOverlay;
|
||||
final bool hideAttachments;
|
||||
final double? textScale;
|
||||
|
||||
const PostBody({
|
||||
@@ -882,6 +883,7 @@ class PostBody extends ConsumerWidget {
|
||||
this.renderingPadding = EdgeInsets.zero,
|
||||
this.isRelativeTime = true,
|
||||
this.hideOverlay = false,
|
||||
this.hideAttachments = false,
|
||||
this.textScale,
|
||||
});
|
||||
|
||||
@@ -1140,7 +1142,7 @@ class PostBody extends ConsumerWidget {
|
||||
right: renderingPadding.horizontal,
|
||||
),
|
||||
),
|
||||
if (item.attachments.isNotEmpty && item.type != 1)
|
||||
if (item.attachments.isNotEmpty && item.type != 1 && !hideAttachments)
|
||||
CloudFileList(
|
||||
files: item.attachments,
|
||||
isColumn: !isInteractive,
|
||||
|
||||
Reference in New Issue
Block a user