282 lines
10 KiB
Dart
282 lines
10 KiB
Dart
import 'package:collection/collection.dart';
|
|
import 'package:easy_localization/easy_localization.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:gap/gap.dart';
|
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|
import 'package:island/models/post.dart';
|
|
import 'package:island/pods/config.dart';
|
|
import 'package:island/widgets/post/post_shared.dart';
|
|
import 'package:island/widgets/content/image.dart';
|
|
import 'package:island/widgets/content/markdown.dart';
|
|
import 'package:island/widgets/content/cloud_files.dart';
|
|
import 'package:qr_flutter/qr_flutter.dart';
|
|
import 'package:styled_widget/styled_widget.dart';
|
|
|
|
const kAvailableStickers = {
|
|
'angry',
|
|
'clap',
|
|
'confuse',
|
|
'pray',
|
|
'thumb_up',
|
|
'party',
|
|
};
|
|
|
|
bool _getReactionImageAvailable(String symbol) {
|
|
return kAvailableStickers.contains(symbol);
|
|
}
|
|
|
|
Widget _buildReactionIcon(String symbol, double size, {double iconSize = 24}) {
|
|
if (_getReactionImageAvailable(symbol)) {
|
|
return Image.asset(
|
|
'assets/images/stickers/$symbol.png',
|
|
width: size,
|
|
height: size,
|
|
fit: BoxFit.contain,
|
|
alignment: Alignment.bottomCenter,
|
|
);
|
|
} else {
|
|
return Text(
|
|
kReactionTemplates[symbol]?.icon ?? '',
|
|
style: TextStyle(fontSize: iconSize),
|
|
);
|
|
}
|
|
}
|
|
|
|
class PostItemScreenshot extends ConsumerWidget {
|
|
final SnPost item;
|
|
final EdgeInsets? padding;
|
|
final bool isFullPost;
|
|
final bool isShowReference;
|
|
const PostItemScreenshot({
|
|
super.key,
|
|
required this.item,
|
|
this.padding,
|
|
this.isFullPost = false,
|
|
this.isShowReference = true,
|
|
});
|
|
|
|
@override
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
|
final renderingPadding =
|
|
padding ?? const EdgeInsets.symmetric(horizontal: 8, vertical: 8);
|
|
|
|
final mostReaction = item.reactionsCount.isEmpty
|
|
? null
|
|
: item.reactionsCount.entries
|
|
.sortedBy((e) => e.value)
|
|
.map((e) => e.key)
|
|
.last;
|
|
|
|
final isDark = MediaQuery.of(context).platformBrightness == Brightness.dark;
|
|
|
|
return Material(
|
|
elevation: 0,
|
|
color: Theme.of(context).colorScheme.surface,
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Gap(renderingPadding.vertical),
|
|
PostHeader(
|
|
hideOverlay: true,
|
|
item: item,
|
|
isFullPost: isFullPost,
|
|
isInteractive: false,
|
|
renderingPadding: renderingPadding,
|
|
isRelativeTime: false,
|
|
trailing: mostReaction != null
|
|
? 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: mostReaction.contains('+')
|
|
? Consumer(
|
|
builder: (context, ref, child) {
|
|
final baseUrl = ref.watch(serverUrlProvider);
|
|
final stickerUri =
|
|
'$baseUrl/sphere/stickers/lookup/$mostReaction/open';
|
|
return SizedBox(
|
|
width: 28,
|
|
height: 28,
|
|
child: UniversalImage(
|
|
uri: stickerUri,
|
|
width: 28,
|
|
height: 28,
|
|
fit: BoxFit.contain,
|
|
).center(),
|
|
);
|
|
},
|
|
)
|
|
: _buildReactionIcon(mostReaction, 32).padding(
|
|
bottom: _getReactionImageAvailable(mostReaction)
|
|
? 2
|
|
: 0,
|
|
),
|
|
)
|
|
: null,
|
|
),
|
|
PostBody(
|
|
item: item,
|
|
renderingPadding: renderingPadding,
|
|
isFullPost: isFullPost,
|
|
isRelativeTime: false,
|
|
isTextSelectable: false,
|
|
isInteractive: false,
|
|
hideOverlay: true,
|
|
),
|
|
if (isShowReference)
|
|
ReferencedPostWidget(
|
|
item: item,
|
|
isInteractive: false,
|
|
renderingPadding: renderingPadding,
|
|
),
|
|
if (item.repliesCount > 0)
|
|
Consumer(
|
|
builder: (context, ref, child) {
|
|
final repliesState = ref.watch(repliesProvider(item.id));
|
|
final posts = repliesState.posts;
|
|
|
|
return Container(
|
|
margin: EdgeInsets.only(
|
|
left: renderingPadding.horizontal,
|
|
right: renderingPadding.horizontal,
|
|
top: 8,
|
|
),
|
|
padding: const 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: const BorderRadius.all(Radius.circular(8)),
|
|
),
|
|
width: double.infinity,
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
spacing: 4,
|
|
children: [
|
|
Text(
|
|
'repliesCount',
|
|
style: const TextStyle(
|
|
fontSize: 15,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
).plural(item.repliesCount).padding(horizontal: 5),
|
|
if (posts.isEmpty && repliesState.loading)
|
|
Row(
|
|
children: [
|
|
const SizedBox(
|
|
width: 24,
|
|
height: 24,
|
|
child: CircularProgressIndicator(strokeWidth: 2),
|
|
),
|
|
const Gap(8),
|
|
const Text('loading').tr(),
|
|
],
|
|
).padding(horizontal: 5),
|
|
if (posts.isNotEmpty)
|
|
...posts.map(
|
|
(post) => ConstrainedBox(
|
|
constraints: const BoxConstraints(maxWidth: 400),
|
|
child: Row(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
spacing: 8,
|
|
children: [
|
|
ProfilePictureWidget(
|
|
file:
|
|
post.publisher.picture ??
|
|
post.publisher.account?.profile.picture,
|
|
radius: 12,
|
|
).padding(top: 4),
|
|
if (post.content?.isNotEmpty ?? false)
|
|
Expanded(
|
|
child: MarkdownTextContent(
|
|
content: post.content!,
|
|
attachments: post.attachments,
|
|
).padding(top: 2),
|
|
)
|
|
else
|
|
Expanded(
|
|
child:
|
|
Text(
|
|
'postHasAttachments',
|
|
style: const TextStyle(height: 2),
|
|
)
|
|
.plural(post.attachments.length)
|
|
.padding(top: 2),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
},
|
|
),
|
|
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),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|