✨ Post share card
This commit is contained in:
@@ -793,5 +793,10 @@
|
|||||||
"joinedAt": "Joined at {}",
|
"joinedAt": "Joined at {}",
|
||||||
"searchAccounts": "Search accounts...",
|
"searchAccounts": "Search accounts...",
|
||||||
"webFeeds": "Web Feeds",
|
"webFeeds": "Web Feeds",
|
||||||
"polls": "Polls"
|
"polls": "Polls",
|
||||||
|
"sharePostSlogan": "Explore more on the Solar Network",
|
||||||
|
"filesListAdditional": {
|
||||||
|
"one": "+{} file remaining",
|
||||||
|
"other": "+{} files remaining"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -791,5 +791,10 @@
|
|||||||
"joinedAt": "加入于 {}",
|
"joinedAt": "加入于 {}",
|
||||||
"searchAccounts": "搜索帐号……",
|
"searchAccounts": "搜索帐号……",
|
||||||
"webFeeds": "订阅源",
|
"webFeeds": "订阅源",
|
||||||
"polls": "投票"
|
"polls": "投票",
|
||||||
|
"sharePostSlogan": "加入 Solar Network 以便探索更多",
|
||||||
|
"filesListAdditional": {
|
||||||
|
"one": "+{} 个文件被折叠",
|
||||||
|
"other": "+{} 个文件被折叠"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -31,6 +31,7 @@ class CloudFileList extends HookConsumerWidget {
|
|||||||
final bool disableZoomIn;
|
final bool disableZoomIn;
|
||||||
final bool disableConstraint;
|
final bool disableConstraint;
|
||||||
final EdgeInsets? padding;
|
final EdgeInsets? padding;
|
||||||
|
final bool isColumn;
|
||||||
const CloudFileList({
|
const CloudFileList({
|
||||||
super.key,
|
super.key,
|
||||||
required this.files,
|
required this.files,
|
||||||
@@ -40,6 +41,7 @@ class CloudFileList extends HookConsumerWidget {
|
|||||||
this.disableZoomIn = false,
|
this.disableZoomIn = false,
|
||||||
this.disableConstraint = false,
|
this.disableConstraint = false,
|
||||||
this.padding,
|
this.padding,
|
||||||
|
this.isColumn = false,
|
||||||
});
|
});
|
||||||
|
|
||||||
double calculateAspectRatio() {
|
double calculateAspectRatio() {
|
||||||
@@ -63,6 +65,74 @@ class CloudFileList extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (files.isEmpty) return const SizedBox.shrink();
|
if (files.isEmpty) return const SizedBox.shrink();
|
||||||
|
|
||||||
|
if (isColumn) {
|
||||||
|
final children = <Widget>[];
|
||||||
|
const maxFiles = 2;
|
||||||
|
final filesToShow = files.take(maxFiles).toList();
|
||||||
|
|
||||||
|
for (var i = 0; i < filesToShow.length; i++) {
|
||||||
|
final file = filesToShow[i];
|
||||||
|
final isImage = file.mimeType?.startsWith('image') ?? false;
|
||||||
|
final isAudio = file.mimeType?.startsWith('audio') ?? false;
|
||||||
|
final widgetItem = ClipRRect(
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
|
child: _CloudFileListEntry(
|
||||||
|
file: file,
|
||||||
|
heroTag: heroTags[i],
|
||||||
|
isImage: isImage,
|
||||||
|
disableZoomIn: disableZoomIn,
|
||||||
|
onTap: () {
|
||||||
|
if (!isImage) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!disableZoomIn) {
|
||||||
|
context.pushTransparentRoute(
|
||||||
|
CloudFileZoomIn(item: file, heroTag: heroTags[i]),
|
||||||
|
rootNavigator: true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget item;
|
||||||
|
if (isAudio) {
|
||||||
|
item = SizedBox(height: 120, child: widgetItem);
|
||||||
|
} else {
|
||||||
|
item = AspectRatio(
|
||||||
|
aspectRatio: file.fileMeta?['ratio'] as double? ?? 1.0,
|
||||||
|
child: widgetItem,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
children.add(item);
|
||||||
|
if (i < filesToShow.length - 1) {
|
||||||
|
children.add(const Gap(8));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (files.length > maxFiles) {
|
||||||
|
children.add(const Gap(8));
|
||||||
|
children.add(
|
||||||
|
Text(
|
||||||
|
'filesListAdditional'.plural(files.length - filesToShow.length),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||||
|
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.7),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Padding(
|
||||||
|
padding: padding ?? EdgeInsets.zero,
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: children,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
if (files.length == 1) {
|
if (files.length == 1) {
|
||||||
final isImage = files.first.mimeType?.startsWith('image') ?? false;
|
final isImage = files.first.mimeType?.startsWith('image') ?? false;
|
||||||
final isAudio = files.first.mimeType?.startsWith('audio') ?? false;
|
final isAudio = files.first.mimeType?.startsWith('audio') ?? false;
|
||||||
|
@@ -23,7 +23,6 @@ import 'package:island/widgets/safety/abuse_report_helper.dart';
|
|||||||
import 'package:island/widgets/share/share_sheet.dart';
|
import 'package:island/widgets/share/share_sheet.dart';
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
import 'package:path_provider/path_provider.dart' show getTemporaryDirectory;
|
import 'package:path_provider/path_provider.dart' show getTemporaryDirectory;
|
||||||
import 'package:relative_time/relative_time.dart';
|
|
||||||
import 'package:screenshot/screenshot.dart';
|
import 'package:screenshot/screenshot.dart';
|
||||||
import 'package:share_plus/share_plus.dart';
|
import 'package:share_plus/share_plus.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
@@ -103,13 +102,13 @@ class PostActionableItem extends HookConsumerWidget {
|
|||||||
textDirection: TextDirection.ltr,
|
textDirection: TextDirection.ltr,
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
width: 520,
|
width: 520,
|
||||||
height: 640,
|
|
||||||
child: PostItemScreenshot(item: item, isFullPost: isFullPost),
|
child: PostItemScreenshot(item: item, isFullPost: isFullPost),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
context: context,
|
context: context,
|
||||||
pixelRatio: MediaQuery.of(context).devicePixelRatio,
|
pixelRatio: MediaQuery.of(context).devicePixelRatio,
|
||||||
|
delay: const Duration(seconds: 1),
|
||||||
)
|
)
|
||||||
.then((Uint8List? image) async {
|
.then((Uint8List? image) async {
|
||||||
if (image == null) return;
|
if (image == null) return;
|
||||||
@@ -468,7 +467,8 @@ class PostItem extends HookConsumerWidget {
|
|||||||
translationSection: translationSection,
|
translationSection: translationSection,
|
||||||
renderingPadding: renderingPadding,
|
renderingPadding: renderingPadding,
|
||||||
),
|
),
|
||||||
if (isShowReference) ReferencedPostWidget(item: item),
|
if (isShowReference)
|
||||||
|
ReferencedPostWidget(item: item, renderingPadding: renderingPadding),
|
||||||
if (item.repliesCount > 0 && isEmbedReply)
|
if (item.repliesCount > 0 && isEmbedReply)
|
||||||
PostReplyPreview(
|
PostReplyPreview(
|
||||||
parent: item,
|
parent: item,
|
||||||
|
@@ -1,9 +1,12 @@
|
|||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:island/models/post.dart';
|
import 'package:island/models/post.dart';
|
||||||
import 'package:island/widgets/post/post_shared.dart';
|
import 'package:island/widgets/post/post_shared.dart';
|
||||||
|
import 'package:qr_flutter/qr_flutter.dart';
|
||||||
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
|
||||||
class PostItemScreenshot extends ConsumerWidget {
|
class PostItemScreenshot extends ConsumerWidget {
|
||||||
final SnPost item;
|
final SnPost item;
|
||||||
@@ -31,15 +34,22 @@ class PostItemScreenshot extends ConsumerWidget {
|
|||||||
.map((e) => e.key)
|
.map((e) => e.key)
|
||||||
.last;
|
.last;
|
||||||
|
|
||||||
return Column(
|
final isDark = MediaQuery.of(context).platformBrightness == Brightness.dark;
|
||||||
|
|
||||||
|
return Material(
|
||||||
|
elevation: 0,
|
||||||
|
color: Theme.of(context).colorScheme.surface,
|
||||||
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
|
Gap(renderingPadding.vertical),
|
||||||
PostHeader(
|
PostHeader(
|
||||||
item: item,
|
item: item,
|
||||||
isFullPost: isFullPost,
|
isFullPost: isFullPost,
|
||||||
isInteractive: false,
|
isInteractive: false,
|
||||||
renderingPadding: renderingPadding,
|
renderingPadding: renderingPadding,
|
||||||
|
isRelativeTime: false,
|
||||||
trailing:
|
trailing:
|
||||||
mostReaction != null
|
mostReaction != null
|
||||||
? Row(
|
? Row(
|
||||||
@@ -65,8 +75,61 @@ class PostItemScreenshot extends ConsumerWidget {
|
|||||||
isInteractive: false,
|
isInteractive: false,
|
||||||
),
|
),
|
||||||
if (isShowReference)
|
if (isShowReference)
|
||||||
ReferencedPostWidget(item: item, isInteractive: false),
|
ReferencedPostWidget(
|
||||||
|
item: item,
|
||||||
|
isInteractive: false,
|
||||||
|
renderingPadding: renderingPadding,
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
color: Theme.of(context).colorScheme.surfaceContainerLow,
|
||||||
|
margin: const EdgeInsets.only(top: 8),
|
||||||
|
padding: EdgeInsets.symmetric(
|
||||||
|
horizontal: renderingPadding.horizontal,
|
||||||
|
vertical: 4,
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: 44,
|
||||||
|
height: 44,
|
||||||
|
child: Image.asset(
|
||||||
|
'assets/icons/icon${isDark ? '-dark' : ''}.png',
|
||||||
|
width: 40,
|
||||||
|
height: 40,
|
||||||
|
),
|
||||||
|
).padding(vertical: 8, right: 12),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const Text(
|
||||||
|
'Solar Network',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Text(
|
||||||
|
'sharePostSlogan',
|
||||||
|
style: TextStyle(fontSize: 12),
|
||||||
|
).tr().opacity(0.9),
|
||||||
],
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
QrImageView(
|
||||||
|
data: 'https://solian.app/posts/${item.id}',
|
||||||
|
version: QrVersions.auto,
|
||||||
|
size: 60,
|
||||||
|
errorCorrectionLevel: QrErrorCorrectLevel.M,
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
foregroundColor: Theme.of(context).colorScheme.onSurface,
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -348,11 +348,13 @@ class PostTruncateHint extends StatelessWidget {
|
|||||||
class ReferencedPostWidget extends StatelessWidget {
|
class ReferencedPostWidget extends StatelessWidget {
|
||||||
final SnPost item;
|
final SnPost item;
|
||||||
final bool isInteractive;
|
final bool isInteractive;
|
||||||
|
final EdgeInsets renderingPadding;
|
||||||
|
|
||||||
const ReferencedPostWidget({
|
const ReferencedPostWidget({
|
||||||
super.key,
|
super.key,
|
||||||
required this.item,
|
required this.item,
|
||||||
this.isInteractive = true,
|
this.isInteractive = true,
|
||||||
|
this.renderingPadding = EdgeInsets.zero,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -363,8 +365,15 @@ class ReferencedPostWidget extends StatelessWidget {
|
|||||||
final isReply = item.repliedPost != null;
|
final isReply = item.repliedPost != null;
|
||||||
|
|
||||||
final content = Container(
|
final content = Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
padding: EdgeInsets.symmetric(
|
||||||
margin: const EdgeInsets.only(top: 8),
|
horizontal: renderingPadding.horizontal,
|
||||||
|
vertical: 8,
|
||||||
|
),
|
||||||
|
margin: EdgeInsets.only(
|
||||||
|
top: 8,
|
||||||
|
left: renderingPadding.vertical,
|
||||||
|
right: renderingPadding.vertical,
|
||||||
|
),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Theme.of(context).colorScheme.surfaceVariant.withOpacity(0.5),
|
color: Theme.of(context).colorScheme.surfaceVariant.withOpacity(0.5),
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
@@ -522,6 +531,7 @@ class PostHeader extends StatelessWidget {
|
|||||||
final Widget? trailing;
|
final Widget? trailing;
|
||||||
final bool isInteractive;
|
final bool isInteractive;
|
||||||
final EdgeInsets renderingPadding;
|
final EdgeInsets renderingPadding;
|
||||||
|
final bool isRelativeTime;
|
||||||
|
|
||||||
const PostHeader({
|
const PostHeader({
|
||||||
super.key,
|
super.key,
|
||||||
@@ -530,6 +540,7 @@ class PostHeader extends StatelessWidget {
|
|||||||
this.trailing,
|
this.trailing,
|
||||||
this.isInteractive = true,
|
this.isInteractive = true,
|
||||||
this.renderingPadding = EdgeInsets.zero,
|
this.renderingPadding = EdgeInsets.zero,
|
||||||
|
this.isRelativeTime = true,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -569,15 +580,21 @@ class PostHeader extends StatelessWidget {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
isFullPost
|
!isFullPost && isRelativeTime
|
||||||
? (item.publishedAt ?? item.createdAt)!.formatSystem()
|
? (item.publishedAt ?? item.createdAt)!.formatRelative(
|
||||||
: (item.publishedAt ?? item.createdAt)!.formatRelative(
|
|
||||||
context,
|
context,
|
||||||
),
|
)
|
||||||
|
: (item.publishedAt ?? item.createdAt)!.formatSystem(),
|
||||||
).fontSize(10),
|
).fontSize(10),
|
||||||
if (item.editedAt != null)
|
if (item.editedAt != null)
|
||||||
Text(
|
Text(
|
||||||
'editedAt'.tr(args: [item.editedAt!.formatSystem()]),
|
'editedAt'.tr(
|
||||||
|
args: [
|
||||||
|
!isFullPost && isRelativeTime
|
||||||
|
? item.editedAt!.formatRelative(context)
|
||||||
|
: item.editedAt!.formatSystem(),
|
||||||
|
],
|
||||||
|
),
|
||||||
).fontSize(10),
|
).fontSize(10),
|
||||||
if (item.visibility != 0)
|
if (item.visibility != 0)
|
||||||
Text(
|
Text(
|
||||||
@@ -711,6 +728,7 @@ class PostBody extends ConsumerWidget {
|
|||||||
if (item.attachments.isNotEmpty && item.type != 1)
|
if (item.attachments.isNotEmpty && item.type != 1)
|
||||||
CloudFileList(
|
CloudFileList(
|
||||||
files: item.attachments,
|
files: item.attachments,
|
||||||
|
isColumn: !isInteractive,
|
||||||
padding: EdgeInsets.symmetric(
|
padding: EdgeInsets.symmetric(
|
||||||
horizontal: renderingPadding.horizontal,
|
horizontal: renderingPadding.horizontal,
|
||||||
vertical: 4,
|
vertical: 4,
|
||||||
|
Reference in New Issue
Block a user