💄 Optimize post reply preview
This commit is contained in:
@@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:math' as math;
|
||||||
|
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
@@ -66,6 +68,7 @@ class PostReplyPreview extends HookConsumerWidget {
|
|||||||
final bool isOpenable;
|
final bool isOpenable;
|
||||||
final bool isCompact;
|
final bool isCompact;
|
||||||
final bool isAutoload;
|
final bool isAutoload;
|
||||||
|
final double? itemMaxWidth;
|
||||||
final VoidCallback? onOpen;
|
final VoidCallback? onOpen;
|
||||||
const PostReplyPreview({
|
const PostReplyPreview({
|
||||||
super.key,
|
super.key,
|
||||||
@@ -73,6 +76,7 @@ class PostReplyPreview extends HookConsumerWidget {
|
|||||||
this.isOpenable = false,
|
this.isOpenable = false,
|
||||||
this.isCompact = false,
|
this.isCompact = false,
|
||||||
this.isAutoload = true,
|
this.isAutoload = true,
|
||||||
|
this.itemMaxWidth,
|
||||||
this.onOpen,
|
this.onOpen,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -114,39 +118,49 @@ class PostReplyPreview extends HookConsumerWidget {
|
|||||||
return null;
|
return null;
|
||||||
}, [parent]);
|
}, [parent]);
|
||||||
|
|
||||||
final featuredReply =
|
final featuredReply = isOpenable
|
||||||
isOpenable ? null : ref.watch(postFeaturedReplyProvider(parent.id));
|
? null
|
||||||
|
: ref.watch(postFeaturedReplyProvider(parent.id));
|
||||||
|
|
||||||
final itemWidget =
|
Widget itemBuilder(double maxWidth) {
|
||||||
isOpenable
|
return isOpenable
|
||||||
? Column(
|
? Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
for (final post in posts.value)
|
for (final post in posts.value)
|
||||||
Column(
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
InkWell(
|
InkWell(
|
||||||
child: Row(
|
child: ConstrainedBox(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
constraints: BoxConstraints(maxWidth: maxWidth),
|
||||||
spacing: 8,
|
child: Row(
|
||||||
children: [
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
ProfilePictureWidget(
|
spacing: 8,
|
||||||
file: post.publisher.picture,
|
children: [
|
||||||
radius: 12,
|
ProfilePictureWidget(
|
||||||
).padding(top: 4),
|
file: post.publisher.picture,
|
||||||
if (post.content?.isNotEmpty ?? false)
|
radius: 12,
|
||||||
Expanded(
|
).padding(top: 4),
|
||||||
child: MarkdownTextContent(
|
if (post.content?.isNotEmpty ?? false)
|
||||||
content: post.content!,
|
Expanded(
|
||||||
attachments: post.attachments,
|
child: MarkdownTextContent(
|
||||||
).padding(top: 2),
|
content: post.content!,
|
||||||
)
|
attachments: post.attachments,
|
||||||
else
|
).padding(top: 2),
|
||||||
Expanded(
|
)
|
||||||
child: Text(
|
else
|
||||||
'postHasAttachments',
|
Expanded(
|
||||||
).plural(post.attachments.length),
|
child:
|
||||||
),
|
Text(
|
||||||
],
|
'postHasAttachments',
|
||||||
|
style: TextStyle(height: 2),
|
||||||
|
)
|
||||||
|
.plural(post.attachments.length)
|
||||||
|
.padding(top: 2),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
onOpen?.call();
|
onOpen?.call();
|
||||||
@@ -162,12 +176,14 @@ class PostReplyPreview extends HookConsumerWidget {
|
|||||||
isOpenable: true,
|
isOpenable: true,
|
||||||
isCompact: true,
|
isCompact: true,
|
||||||
isAutoload: false,
|
isAutoload: false,
|
||||||
|
itemMaxWidth: math.max(maxWidth - 24, 200),
|
||||||
onOpen: onOpen,
|
onOpen: onOpen,
|
||||||
).padding(left: 24),
|
).padding(left: 24),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (loading.value)
|
if (loading.value)
|
||||||
Row(
|
Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
spacing: 8,
|
spacing: 8,
|
||||||
children: [
|
children: [
|
||||||
SizedBox(
|
SizedBox(
|
||||||
@@ -179,8 +195,9 @@ class PostReplyPreview extends HookConsumerWidget {
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
else if (posts.value.length < parent.repliesCount)
|
else if (posts.value.length < parent.repliesCount)
|
||||||
InkWell(
|
GestureDetector(
|
||||||
child: Row(
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
spacing: 8,
|
spacing: 8,
|
||||||
children: [
|
children: [
|
||||||
const Icon(Symbols.keyboard_arrow_down, size: 20),
|
const Icon(Symbols.keyboard_arrow_down, size: 20),
|
||||||
@@ -193,81 +210,87 @@ class PostReplyPreview extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
: (featuredReply!).map(
|
: (featuredReply!).map(
|
||||||
data:
|
data: (data) => ConstrainedBox(
|
||||||
(data) => Row(
|
constraints: BoxConstraints(maxWidth: maxWidth),
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
child: Row(
|
||||||
spacing: 8,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
spacing: 8,
|
||||||
ProfilePictureWidget(
|
children: [
|
||||||
file: data.value?.publisher.picture,
|
ProfilePictureWidget(
|
||||||
radius: 12,
|
file: data.value?.publisher.picture,
|
||||||
).padding(top: 4),
|
radius: 12,
|
||||||
if (data.value?.content?.isNotEmpty ?? false)
|
).padding(top: 4),
|
||||||
Expanded(
|
if (data.value?.content?.isNotEmpty ?? false)
|
||||||
child: MarkdownTextContent(
|
Expanded(
|
||||||
content: data.value!.content!,
|
child: MarkdownTextContent(
|
||||||
attachments: data.value!.attachments,
|
content: data.value!.content!,
|
||||||
),
|
attachments: data.value!.attachments,
|
||||||
)
|
|
||||||
else
|
|
||||||
Expanded(
|
|
||||||
child: Text(
|
|
||||||
'postHasAttachments',
|
|
||||||
).plural(data.value?.attachments.length ?? 0),
|
|
||||||
),
|
),
|
||||||
],
|
)
|
||||||
),
|
else
|
||||||
error:
|
Expanded(
|
||||||
(e) => Row(
|
child: Text(
|
||||||
spacing: 8,
|
'postHasAttachments',
|
||||||
children: [
|
).plural(data.value?.attachments.length ?? 0),
|
||||||
const Icon(Symbols.close, size: 18),
|
|
||||||
Text(e.error.toString()),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
loading:
|
|
||||||
(_) => Row(
|
|
||||||
spacing: 8,
|
|
||||||
children: [
|
|
||||||
SizedBox(
|
|
||||||
width: 16,
|
|
||||||
height: 16,
|
|
||||||
child: CircularProgressIndicator(),
|
|
||||||
),
|
),
|
||||||
Text('loading').tr(),
|
],
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
final contentWidget =
|
|
||||||
isCompact
|
|
||||||
? itemWidget
|
|
||||||
: Container(
|
|
||||||
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Theme.of(context).colorScheme.surfaceContainerLow,
|
|
||||||
border: Border.all(
|
|
||||||
color: Theme.of(context).dividerColor.withOpacity(0.5),
|
|
||||||
),
|
),
|
||||||
borderRadius: BorderRadius.all(Radius.circular(8)),
|
|
||||||
),
|
),
|
||||||
child: Column(
|
error: (e) => Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
spacing: 8,
|
||||||
spacing: 4,
|
|
||||||
children: [
|
children: [
|
||||||
Text('repliesCount')
|
const Icon(Symbols.close, size: 18),
|
||||||
.plural(parent.repliesCount)
|
Text(e.error.toString()),
|
||||||
.fontSize(15)
|
],
|
||||||
.bold()
|
),
|
||||||
.padding(horizontal: 5),
|
loading: (_) => Row(
|
||||||
itemWidget,
|
spacing: 8,
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: 16,
|
||||||
|
height: 16,
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
),
|
||||||
|
Text('loading').tr(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return InkWell(
|
final contentWidget = isCompact
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
? itemBuilder(itemMaxWidth ?? MediaQuery.of(context).size.width)
|
||||||
|
: Container(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context).colorScheme.surfaceContainerLow,
|
||||||
|
border: Border.all(
|
||||||
|
color: Theme.of(context).dividerColor.withOpacity(0.5),
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(8)),
|
||||||
|
),
|
||||||
|
child: LayoutBuilder(
|
||||||
|
builder: (context, constraints) {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
spacing: 4,
|
||||||
|
children: [
|
||||||
|
Text('repliesCount')
|
||||||
|
.plural(parent.repliesCount)
|
||||||
|
.fontSize(15)
|
||||||
|
.bold()
|
||||||
|
.padding(horizontal: 5),
|
||||||
|
SingleChildScrollView(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
child: itemBuilder(constraints.maxWidth),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
context: context,
|
context: context,
|
||||||
@@ -479,8 +502,9 @@ class ReferencedPostWidget extends StatelessWidget {
|
|||||||
referencePost.description!,
|
referencePost.description!,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
color:
|
color: Theme.of(
|
||||||
Theme.of(context).colorScheme.onSurfaceVariant,
|
context,
|
||||||
|
).colorScheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
maxLines: 2,
|
maxLines: 2,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
@@ -490,10 +514,9 @@ class ReferencedPostWidget extends StatelessWidget {
|
|||||||
content: referencePost.content!,
|
content: referencePost.content!,
|
||||||
textStyle: const TextStyle(fontSize: 14),
|
textStyle: const TextStyle(fontSize: 14),
|
||||||
isSelectable: false,
|
isSelectable: false,
|
||||||
linesMargin:
|
linesMargin: referencePost.type == 0
|
||||||
referencePost.type == 0
|
? const EdgeInsets.only(bottom: 4)
|
||||||
? const EdgeInsets.only(bottom: 4)
|
: null,
|
||||||
: null,
|
|
||||||
attachments: item.attachments,
|
attachments: item.attachments,
|
||||||
).padding(bottom: 4),
|
).padding(bottom: 4),
|
||||||
if (referencePost.isTruncated)
|
if (referencePost.isTruncated)
|
||||||
@@ -537,11 +560,10 @@ class ReferencedPostWidget extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return content.gestures(
|
return content.gestures(
|
||||||
onTap:
|
onTap: () => context.pushNamed(
|
||||||
() => context.pushNamed(
|
'postDetail',
|
||||||
'postDetail',
|
pathParameters: {'id': referencePost!.id},
|
||||||
pathParameters: {'id': referencePost!.id},
|
),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -577,15 +599,14 @@ class PostHeader extends StatelessWidget {
|
|||||||
spacing: 12,
|
spacing: 12,
|
||||||
children: [
|
children: [
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap:
|
onTap: isInteractive
|
||||||
isInteractive
|
? () {
|
||||||
? () {
|
context.pushNamed(
|
||||||
context.pushNamed(
|
'publisherProfile',
|
||||||
'publisherProfile',
|
pathParameters: {'name': item.publisher.name},
|
||||||
pathParameters: {'name': item.publisher.name},
|
);
|
||||||
);
|
}
|
||||||
}
|
: null,
|
||||||
: null,
|
|
||||||
child: ProfilePictureWidget(
|
child: ProfilePictureWidget(
|
||||||
file:
|
file:
|
||||||
item.publisher.picture ??
|
item.publisher.picture ??
|
||||||
@@ -606,19 +627,19 @@ class PostHeader extends StatelessWidget {
|
|||||||
Flexible(
|
Flexible(
|
||||||
child:
|
child:
|
||||||
(item.publisher.account != null &&
|
(item.publisher.account != null &&
|
||||||
item.publisher.type == 0)
|
item.publisher.type == 0)
|
||||||
? AccountName(
|
? AccountName(
|
||||||
hideOverlay: hideOverlay,
|
hideOverlay: hideOverlay,
|
||||||
account: item.publisher.account!,
|
account: item.publisher.account!,
|
||||||
textOverride: item.publisher.nick,
|
textOverride: item.publisher.nick,
|
||||||
style: TextStyle(fontWeight: FontWeight.bold),
|
style: TextStyle(fontWeight: FontWeight.bold),
|
||||||
hideVerificationMark: true,
|
hideVerificationMark: true,
|
||||||
)
|
)
|
||||||
: Text(
|
: Text(
|
||||||
item.publisher.nick,
|
item.publisher.nick,
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
).bold(),
|
).bold(),
|
||||||
),
|
),
|
||||||
if (item.publisher.verification != null)
|
if (item.publisher.verification != null)
|
||||||
VerificationMark(
|
VerificationMark(
|
||||||
@@ -627,14 +648,13 @@ class PostHeader extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
if (item.realm == null)
|
if (item.realm == null)
|
||||||
Flexible(
|
Flexible(
|
||||||
child:
|
child: isCompact
|
||||||
isCompact
|
? const SizedBox.shrink()
|
||||||
? const SizedBox.shrink()
|
: Text(
|
||||||
: Text(
|
'@${item.publisher.name}',
|
||||||
'@${item.publisher.name}',
|
maxLines: 1,
|
||||||
maxLines: 1,
|
overflow: TextOverflow.ellipsis,
|
||||||
overflow: TextOverflow.ellipsis,
|
).fontSize(11),
|
||||||
).fontSize(11),
|
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
...([
|
...([
|
||||||
@@ -673,8 +693,8 @@ class PostHeader extends StatelessWidget {
|
|||||||
Text(
|
Text(
|
||||||
!isFullPost && isRelativeTime
|
!isFullPost && isRelativeTime
|
||||||
? (item.publishedAt ?? item.createdAt)!.formatRelative(
|
? (item.publishedAt ?? item.createdAt)!.formatRelative(
|
||||||
context,
|
context,
|
||||||
)
|
)
|
||||||
: (item.publishedAt ?? item.createdAt)!.formatSystem(),
|
: (item.publishedAt ?? item.createdAt)!.formatSystem(),
|
||||||
).fontSize(10),
|
).fontSize(10),
|
||||||
],
|
],
|
||||||
@@ -734,15 +754,14 @@ class PostBody extends ConsumerWidget {
|
|||||||
const Icon(Symbols.label, size: 16).padding(top: 2),
|
const Icon(Symbols.label, size: 16).padding(top: 2),
|
||||||
for (final tag in isFullPost ? item.tags : item.tags.take(3))
|
for (final tag in isFullPost ? item.tags : item.tags.take(3))
|
||||||
InkWell(
|
InkWell(
|
||||||
onTap:
|
onTap: isInteractive
|
||||||
isInteractive
|
? () {
|
||||||
? () {
|
GoRouter.of(context).pushNamed(
|
||||||
GoRouter.of(context).pushNamed(
|
'postTagDetail',
|
||||||
'postTagDetail',
|
pathParameters: {'slug': tag.slug},
|
||||||
pathParameters: {'slug': tag.slug},
|
);
|
||||||
);
|
}
|
||||||
}
|
: null,
|
||||||
: null,
|
|
||||||
child: Text('#${tag.name ?? tag.slug}'),
|
child: Text('#${tag.name ?? tag.slug}'),
|
||||||
),
|
),
|
||||||
if (!isFullPost && item.tags.length > 3)
|
if (!isFullPost && item.tags.length > 3)
|
||||||
@@ -761,15 +780,14 @@ class PostBody extends ConsumerWidget {
|
|||||||
for (final category
|
for (final category
|
||||||
in isFullPost ? item.categories : item.categories.take(2))
|
in isFullPost ? item.categories : item.categories.take(2))
|
||||||
InkWell(
|
InkWell(
|
||||||
onTap:
|
onTap: isInteractive
|
||||||
isInteractive
|
? () {
|
||||||
? () {
|
GoRouter.of(context).pushNamed(
|
||||||
GoRouter.of(context).pushNamed(
|
'postCategoryDetail',
|
||||||
'postCategoryDetail',
|
pathParameters: {'slug': category.slug},
|
||||||
pathParameters: {'slug': category.slug},
|
);
|
||||||
);
|
}
|
||||||
}
|
: null,
|
||||||
: null,
|
|
||||||
child: Text(category.categoryDisplayTitle),
|
child: Text(category.categoryDisplayTitle),
|
||||||
),
|
),
|
||||||
if (!isFullPost && item.categories.length > 2)
|
if (!isFullPost && item.categories.length > 2)
|
||||||
@@ -798,12 +816,11 @@ class PostBody extends ConsumerWidget {
|
|||||||
hideOverlay
|
hideOverlay
|
||||||
? text
|
? text
|
||||||
: Tooltip(
|
: Tooltip(
|
||||||
message:
|
message: !isFullPost && isRelativeTime
|
||||||
!isFullPost && isRelativeTime
|
? item.editedAt!.formatSystem()
|
||||||
? item.editedAt!.formatSystem()
|
: item.editedAt!.formatRelative(context),
|
||||||
: item.editedAt!.formatRelative(context),
|
child: text,
|
||||||
child: text,
|
),
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -936,10 +953,9 @@ class PostBody extends ConsumerWidget {
|
|||||||
],
|
],
|
||||||
).padding(bottom: 4),
|
).padding(bottom: 4),
|
||||||
MarkdownTextContent(
|
MarkdownTextContent(
|
||||||
content:
|
content: item.isTruncated
|
||||||
item.isTruncated
|
? '${item.content!}...'
|
||||||
? '${item.content!}...'
|
: item.content ?? '',
|
||||||
: item.content ?? '',
|
|
||||||
isSelectable: isTextSelectable,
|
isSelectable: isTextSelectable,
|
||||||
attachments: item.attachments,
|
attachments: item.attachments,
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in New Issue
Block a user