✨ User cache
This commit is contained in:
@ -42,7 +42,8 @@ class AttachmentZoomView extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _AttachmentZoomViewState extends State<AttachmentZoomView> {
|
||||
late final PageController _pageController = PageController(initialPage: widget.initialIndex ?? 0);
|
||||
late final PageController _pageController =
|
||||
PageController(initialPage: widget.initialIndex ?? 0);
|
||||
|
||||
bool _showOverlay = true;
|
||||
bool _dismissable = true;
|
||||
@ -107,7 +108,9 @@ class _AttachmentZoomViewState extends State<AttachmentZoomView> {
|
||||
|
||||
if (!mounted) return;
|
||||
context.showSnackbar(
|
||||
(!kIsWeb && (Platform.isIOS || Platform.isAndroid)) ? 'attachmentSaved'.tr() : 'attachmentSavedDesktop'.tr(),
|
||||
(!kIsWeb && (Platform.isIOS || Platform.isAndroid))
|
||||
? 'attachmentSaved'.tr()
|
||||
: 'attachmentSavedDesktop'.tr(),
|
||||
action: (!kIsWeb && (Platform.isIOS || Platform.isAndroid))
|
||||
? SnackBarAction(
|
||||
label: 'openInAlbum'.tr(),
|
||||
@ -131,7 +134,8 @@ class _AttachmentZoomViewState extends State<AttachmentZoomView> {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Color get _unFocusColor => Theme.of(context).colorScheme.onSurface.withOpacity(0.75);
|
||||
Color get _unFocusColor =>
|
||||
Theme.of(context).colorScheme.onSurface.withOpacity(0.75);
|
||||
|
||||
bool _showDetail = false;
|
||||
|
||||
@ -150,7 +154,9 @@ class _AttachmentZoomViewState extends State<AttachmentZoomView> {
|
||||
onDismissed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
direction: _dismissable ? DismissiblePageDismissDirection.multi : DismissiblePageDismissDirection.none,
|
||||
direction: _dismissable
|
||||
? DismissiblePageDismissDirection.multi
|
||||
: DismissiblePageDismissDirection.none,
|
||||
backgroundColor: Colors.transparent,
|
||||
isFullScreen: true,
|
||||
child: GestureDetector(
|
||||
@ -165,10 +171,13 @@ class _AttachmentZoomViewState extends State<AttachmentZoomView> {
|
||||
return Hero(
|
||||
tag: 'attachment-${widget.data.first.rid}-$heroTag',
|
||||
child: PhotoView(
|
||||
key: Key('attachment-detail-${widget.data.first.rid}-$heroTag'),
|
||||
backgroundDecoration: BoxDecoration(color: Colors.transparent),
|
||||
key: Key(
|
||||
'attachment-detail-${widget.data.first.rid}-$heroTag'),
|
||||
backgroundDecoration:
|
||||
BoxDecoration(color: Colors.transparent),
|
||||
scaleStateChangedCallback: (scaleState) {
|
||||
setState(() => _dismissable = scaleState == PhotoViewScaleState.initial);
|
||||
setState(() => _dismissable =
|
||||
scaleState == PhotoViewScaleState.initial);
|
||||
},
|
||||
imageProvider: UniversalImage.provider(
|
||||
sn.getAttachmentUrl(widget.data.first.rid),
|
||||
@ -181,10 +190,12 @@ class _AttachmentZoomViewState extends State<AttachmentZoomView> {
|
||||
pageController: _pageController,
|
||||
enableRotation: true,
|
||||
scaleStateChangedCallback: (scaleState) {
|
||||
setState(() => _dismissable = scaleState == PhotoViewScaleState.initial);
|
||||
setState(() => _dismissable =
|
||||
scaleState == PhotoViewScaleState.initial);
|
||||
},
|
||||
builder: (context, idx) {
|
||||
final heroTag = widget.heroTags?.elementAt(idx) ?? uuid.v4();
|
||||
final heroTag =
|
||||
widget.heroTags?.elementAt(idx) ?? uuid.v4();
|
||||
return PhotoViewGalleryPageOptions(
|
||||
imageProvider: UniversalImage.provider(
|
||||
sn.getAttachmentUrl(widget.data.elementAt(idx).rid),
|
||||
@ -200,11 +211,15 @@ class _AttachmentZoomViewState extends State<AttachmentZoomView> {
|
||||
width: 20.0,
|
||||
height: 20.0,
|
||||
child: CircularProgressIndicator(
|
||||
value: event == null ? 0 : event.cumulativeBytesLoaded / (event.expectedTotalBytes ?? 1),
|
||||
value: event == null
|
||||
? 0
|
||||
: event.cumulativeBytesLoaded /
|
||||
(event.expectedTotalBytes ?? 1),
|
||||
),
|
||||
),
|
||||
),
|
||||
backgroundDecoration: BoxDecoration(color: Colors.transparent),
|
||||
backgroundDecoration:
|
||||
BoxDecoration(color: Colors.transparent),
|
||||
);
|
||||
}),
|
||||
Positioned(
|
||||
@ -223,9 +238,8 @@ class _AttachmentZoomViewState extends State<AttachmentZoomView> {
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
)
|
||||
.opacity(_showOverlay ? 1 : 0, animate: true)
|
||||
.animate(const Duration(milliseconds: 300), Curves.easeInOut),
|
||||
).opacity(_showOverlay ? 1 : 0, animate: true).animate(
|
||||
const Duration(milliseconds: 300), Curves.easeInOut),
|
||||
),
|
||||
),
|
||||
Align(
|
||||
@ -257,9 +271,11 @@ class _AttachmentZoomViewState extends State<AttachmentZoomView> {
|
||||
child: Builder(builder: (context) {
|
||||
final ud = context.read<UserDirectoryProvider>();
|
||||
final item = widget.data.elementAt(
|
||||
widget.data.length > 1 ? _pageController.page?.round() ?? 0 : 0,
|
||||
widget.data.length > 1
|
||||
? _pageController.page?.round() ?? 0
|
||||
: 0,
|
||||
);
|
||||
final account = ud.getAccountFromCache(item.accountId);
|
||||
final account = ud.getFromCache(item.accountId);
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@ -277,15 +293,20 @@ class _AttachmentZoomViewState extends State<AttachmentZoomView> {
|
||||
Expanded(
|
||||
child: IgnorePointer(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'attachmentUploadBy'.tr(),
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall,
|
||||
),
|
||||
Text(
|
||||
account?.nick ?? 'unknown'.tr(),
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium,
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -299,11 +320,13 @@ class _AttachmentZoomViewState extends State<AttachmentZoomView> {
|
||||
).padding(right: 8),
|
||||
),
|
||||
InkWell(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(16)),
|
||||
borderRadius:
|
||||
const BorderRadius.all(Radius.circular(16)),
|
||||
onTap: _isDownloading
|
||||
? null
|
||||
: () =>
|
||||
_saveToAlbum(widget.data.length > 1 ? _pageController.page?.round() ?? 0 : 0),
|
||||
: () => _saveToAlbum(widget.data.length > 1
|
||||
? _pageController.page?.round() ?? 0
|
||||
: 0),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(6),
|
||||
child: !_isDownloading
|
||||
@ -351,7 +374,8 @@ class _AttachmentZoomViewState extends State<AttachmentZoomView> {
|
||||
]),
|
||||
style: metaTextStyle,
|
||||
).padding(right: 2),
|
||||
if (item.metadata['exif']?['Megapixels'] != null &&
|
||||
if (item.metadata['exif']?['Megapixels'] !=
|
||||
null &&
|
||||
item.metadata['exif']?['Model'] != null)
|
||||
Text(
|
||||
'${item.metadata['exif']?['Megapixels']}MP',
|
||||
@ -362,7 +386,8 @@ class _AttachmentZoomViewState extends State<AttachmentZoomView> {
|
||||
item.size.formatBytes(),
|
||||
style: metaTextStyle,
|
||||
),
|
||||
if (item.metadata['width'] != null && item.metadata['height'] != null)
|
||||
if (item.metadata['width'] != null &&
|
||||
item.metadata['height'] != null)
|
||||
Text(
|
||||
'${item.metadata['width']}x${item.metadata['height']}',
|
||||
style: metaTextStyle,
|
||||
@ -377,8 +402,10 @@ class _AttachmentZoomViewState extends State<AttachmentZoomView> {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
builder: (context) => _AttachmentZoomDetailPopup(
|
||||
data: widget.data
|
||||
.elementAt(widget.data.length > 1 ? _pageController.page?.round() ?? 0 : 0),
|
||||
data: widget.data.elementAt(
|
||||
widget.data.length > 1
|
||||
? _pageController.page?.round() ?? 0
|
||||
: 0),
|
||||
),
|
||||
).then((_) {
|
||||
_showDetail = false;
|
||||
@ -386,15 +413,15 @@ class _AttachmentZoomViewState extends State<AttachmentZoomView> {
|
||||
},
|
||||
child: Text(
|
||||
'viewDetailedAttachment'.tr(),
|
||||
style: metaTextStyle.copyWith(decoration: TextDecoration.underline),
|
||||
style: metaTextStyle.copyWith(
|
||||
decoration: TextDecoration.underline),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
)
|
||||
.opacity(_showOverlay ? 1 : 0, animate: true)
|
||||
.animate(const Duration(milliseconds: 300), Curves.easeInOut),
|
||||
).opacity(_showOverlay ? 1 : 0, animate: true).animate(
|
||||
const Duration(milliseconds: 300), Curves.easeInOut),
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -409,7 +436,9 @@ class _AttachmentZoomViewState extends State<AttachmentZoomView> {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
builder: (context) => _AttachmentZoomDetailPopup(
|
||||
data: widget.data.elementAt(widget.data.length > 1 ? _pageController.page?.round() ?? 0 : 0),
|
||||
data: widget.data.elementAt(widget.data.length > 1
|
||||
? _pageController.page?.round() ?? 0
|
||||
: 0),
|
||||
),
|
||||
).then((_) {
|
||||
_showDetail = false;
|
||||
@ -429,7 +458,7 @@ class _AttachmentZoomDetailPopup extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final ud = context.read<UserDirectoryProvider>();
|
||||
final account = ud.getAccountFromCache(data.accountId);
|
||||
final account = ud.getFromCache(data.accountId);
|
||||
|
||||
const tableGap = TableRow(
|
||||
children: [
|
||||
@ -447,7 +476,9 @@ class _AttachmentZoomDetailPopup extends StatelessWidget {
|
||||
children: [
|
||||
const Icon(Symbols.info, size: 24),
|
||||
const Gap(16),
|
||||
Text('attachmentDetailInfo').tr().textStyle(Theme.of(context).textTheme.titleLarge!),
|
||||
Text('attachmentDetailInfo')
|
||||
.tr()
|
||||
.textStyle(Theme.of(context).textTheme.titleLarge!),
|
||||
],
|
||||
).padding(horizontal: 20, top: 16, bottom: 12),
|
||||
Expanded(
|
||||
@ -461,7 +492,8 @@ class _AttachmentZoomDetailPopup extends StatelessWidget {
|
||||
TableRow(
|
||||
children: [
|
||||
TableCell(
|
||||
child: Text('attachmentUploadBy').tr().padding(right: 16),
|
||||
child:
|
||||
Text('attachmentUploadBy').tr().padding(right: 16),
|
||||
),
|
||||
TableCell(
|
||||
child: Row(
|
||||
@ -472,9 +504,13 @@ class _AttachmentZoomDetailPopup extends StatelessWidget {
|
||||
radius: 8,
|
||||
),
|
||||
const Gap(8),
|
||||
Text(data.accountId > 0 ? account?.nick ?? 'unknown'.tr() : 'unknown'.tr()),
|
||||
Text(data.accountId > 0
|
||||
? account?.nick ?? 'unknown'.tr()
|
||||
: 'unknown'.tr()),
|
||||
const Gap(8),
|
||||
Text('#${data.accountId}', style: GoogleFonts.robotoMono()).opacity(0.75),
|
||||
Text('#${data.accountId}',
|
||||
style: GoogleFonts.robotoMono())
|
||||
.opacity(0.75),
|
||||
],
|
||||
),
|
||||
),
|
||||
@ -495,7 +531,9 @@ class _AttachmentZoomDetailPopup extends StatelessWidget {
|
||||
children: [
|
||||
Text(data.size.formatBytes()),
|
||||
const Gap(12),
|
||||
Text('${data.size} Bytes', style: GoogleFonts.robotoMono()).opacity(0.75),
|
||||
Text('${data.size} Bytes',
|
||||
style: GoogleFonts.robotoMono())
|
||||
.opacity(0.75),
|
||||
],
|
||||
)),
|
||||
],
|
||||
@ -510,19 +548,27 @@ class _AttachmentZoomDetailPopup extends StatelessWidget {
|
||||
TableRow(
|
||||
children: [
|
||||
TableCell(child: Text('Hash').padding(right: 16)),
|
||||
TableCell(child: Text(data.hash, style: GoogleFonts.robotoMono(fontSize: 11)).opacity(0.9)),
|
||||
TableCell(
|
||||
child: Text(data.hash,
|
||||
style: GoogleFonts.robotoMono(fontSize: 11))
|
||||
.opacity(0.9)),
|
||||
],
|
||||
),
|
||||
tableGap,
|
||||
...(data.metadata['exif']?.keys.map((k) => TableRow(
|
||||
children: [
|
||||
TableCell(child: Text(k).padding(right: 16)),
|
||||
TableCell(child: Text(data.metadata['exif'][k].toString())),
|
||||
TableCell(
|
||||
child: Text(
|
||||
data.metadata['exif'][k].toString())),
|
||||
],
|
||||
)) ??
|
||||
[]),
|
||||
],
|
||||
).padding(horizontal: 20, vertical: 8, bottom: MediaQuery.of(context).padding.bottom),
|
||||
).padding(
|
||||
horizontal: 20,
|
||||
vertical: 8,
|
||||
bottom: MediaQuery.of(context).padding.bottom),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
@ -51,7 +51,7 @@ class ChatMessage extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
final ua = context.read<UserProvider>();
|
||||
final ud = context.read<UserDirectoryProvider>();
|
||||
final user = ud.getAccountFromCache(data.sender.accountId);
|
||||
final user = ud.getFromCache(data.sender.accountId);
|
||||
|
||||
final isOwner = ua.isAuthorized && data.sender.accountId == ua.user?.id;
|
||||
|
||||
|
@ -380,7 +380,7 @@ class ChatMessageInputState extends State<ChatMessageInput> {
|
||||
_isEncrypted ? Icon(Symbols.lock, size: 18) : null,
|
||||
hintText: widget.otherMember != null
|
||||
? 'fieldChatMessageDirect'.tr(args: [
|
||||
'@${ud.getAccountFromCache(widget.otherMember?.accountId)?.name}',
|
||||
'@${ud.getFromCache(widget.otherMember?.accountId)?.name}',
|
||||
])
|
||||
: 'fieldChatMessage'.tr(args: [
|
||||
widget.controller.channel?.name ?? 'loading'.tr()
|
||||
|
@ -33,11 +33,13 @@ class ChatTypingIndicator extends StatelessWidget {
|
||||
const Icon(Symbols.more_horiz, weight: 600, size: 20),
|
||||
const Gap(8),
|
||||
Text(
|
||||
'messageTyping'.plural(controller.typingMembers.length, args: [
|
||||
'messageTyping'
|
||||
.plural(controller.typingMembers.length, args: [
|
||||
controller.typingMembers
|
||||
.map((ele) => (ele.nick?.isNotEmpty ?? false)
|
||||
? ele.nick!
|
||||
: ud.getAccountFromCache(ele.accountId)?.name ?? 'unknown')
|
||||
: ud.getFromCache(ele.accountId)?.name ??
|
||||
'unknown')
|
||||
.join(', '),
|
||||
]),
|
||||
),
|
||||
|
@ -95,9 +95,10 @@ class OpenablePostItem extends StatelessWidget {
|
||||
openColor: Colors.transparent,
|
||||
openElevation: 0,
|
||||
transitionType: ContainerTransitionType.fade,
|
||||
closedColor: Theme.of(context).colorScheme.surfaceContainerLow.withOpacity(
|
||||
cfg.prefs.getBool(kAppBackgroundStoreKey) == true ? 0.75 : 1,
|
||||
),
|
||||
closedColor:
|
||||
Theme.of(context).colorScheme.surfaceContainerLow.withOpacity(
|
||||
cfg.prefs.getBool(kAppBackgroundStoreKey) == true ? 0.75 : 1,
|
||||
),
|
||||
closedShape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(16)),
|
||||
),
|
||||
@ -138,9 +139,11 @@ class PostItem extends StatelessWidget {
|
||||
final box = context.findRenderObject() as RenderBox?;
|
||||
final url = 'https://solsynth.dev/posts/${data.id}';
|
||||
if (!kIsWeb && (Platform.isAndroid || Platform.isIOS)) {
|
||||
Share.shareUri(Uri.parse(url), sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size);
|
||||
Share.shareUri(Uri.parse(url),
|
||||
sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size);
|
||||
} else {
|
||||
Share.share(url, sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size);
|
||||
Share.share(url,
|
||||
sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size);
|
||||
}
|
||||
}
|
||||
|
||||
@ -158,7 +161,8 @@ class PostItem extends StatelessWidget {
|
||||
child: MultiProvider(
|
||||
providers: [
|
||||
Provider<SnNetworkProvider>(create: (_) => context.read()),
|
||||
ChangeNotifierProvider<ConfigProvider>(create: (_) => context.read()),
|
||||
ChangeNotifierProvider<ConfigProvider>(
|
||||
create: (_) => context.read()),
|
||||
],
|
||||
child: ResponsiveBreakpoints.builder(
|
||||
breakpoints: ResponsiveBreakpoints.of(context).breakpoints,
|
||||
@ -186,7 +190,8 @@ class PostItem extends StatelessWidget {
|
||||
sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size,
|
||||
);
|
||||
} else {
|
||||
await FileSaver.instance.saveFile(name: 'Solar Network Post #${data.id}.png', file: imageFile);
|
||||
await FileSaver.instance.saveFile(
|
||||
name: 'Solar Network Post #${data.id}.png', file: imageFile);
|
||||
}
|
||||
|
||||
await imageFile.delete();
|
||||
@ -200,7 +205,9 @@ class PostItem extends StatelessWidget {
|
||||
final isAuthor = ua.isAuthorized && data.publisher.accountId == ua.user?.id;
|
||||
|
||||
// Video full view
|
||||
if (showFullPost && data.type == 'video' && ResponsiveBreakpoints.of(context).largerThan(TABLET)) {
|
||||
if (showFullPost &&
|
||||
data.type == 'video' &&
|
||||
ResponsiveBreakpoints.of(context).largerThan(TABLET)) {
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
@ -220,7 +227,8 @@ class PostItem extends StatelessWidget {
|
||||
if (onDeleted != null) {}
|
||||
},
|
||||
).padding(bottom: 8),
|
||||
if (data.preload?.video != null) _PostVideoPlayer(data: data).padding(bottom: 8),
|
||||
if (data.preload?.video != null)
|
||||
_PostVideoPlayer(data: data).padding(bottom: 8),
|
||||
_PostHeadline(data: data).padding(horizontal: 4, bottom: 8),
|
||||
_PostFeaturedComment(data: data),
|
||||
_PostBottomAction(
|
||||
@ -268,7 +276,8 @@ class PostItem extends StatelessWidget {
|
||||
if (onDeleted != null) {}
|
||||
},
|
||||
).padding(horizontal: 12, top: 8, bottom: 8),
|
||||
if (data.preload?.video != null) _PostVideoPlayer(data: data).padding(horizontal: 12, bottom: 8),
|
||||
if (data.preload?.video != null)
|
||||
_PostVideoPlayer(data: data).padding(horizontal: 12, bottom: 8),
|
||||
Container(
|
||||
width: double.infinity,
|
||||
margin: const EdgeInsets.only(bottom: 4, left: 12, right: 12),
|
||||
@ -311,8 +320,13 @@ class PostItem extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
),
|
||||
Text('postArticle').tr().fontSize(13).opacity(0.75).padding(horizontal: 24, bottom: 8),
|
||||
_PostFeaturedComment(data: data, maxWidth: maxWidth).padding(horizontal: 12),
|
||||
Text('postArticle')
|
||||
.tr()
|
||||
.fontSize(13)
|
||||
.opacity(0.75)
|
||||
.padding(horizontal: 24, bottom: 8),
|
||||
_PostFeaturedComment(data: data, maxWidth: maxWidth)
|
||||
.padding(horizontal: 12),
|
||||
_PostBottomAction(
|
||||
data: data,
|
||||
showComments: showComments,
|
||||
@ -327,7 +341,8 @@ class PostItem extends StatelessWidget {
|
||||
}
|
||||
|
||||
final displayableAttachments = data.preload?.attachments
|
||||
?.where((ele) => ele?.mediaType != SnMediaType.image || data.type != 'article')
|
||||
?.where((ele) =>
|
||||
ele?.mediaType != SnMediaType.image || data.type != 'article')
|
||||
.toList();
|
||||
|
||||
final cfg = context.read<ConfigProvider>();
|
||||
@ -352,9 +367,13 @@ class PostItem extends StatelessWidget {
|
||||
if (onDeleted != null) onDeleted!();
|
||||
},
|
||||
).padding(horizontal: 12, vertical: 8),
|
||||
if (data.preload?.video != null) _PostVideoPlayer(data: data).padding(horizontal: 12, bottom: 8),
|
||||
if (data.type == 'question') _PostQuestionHint(data: data).padding(horizontal: 16, bottom: 8),
|
||||
if (data.body['title'] != null || data.body['description'] != null)
|
||||
if (data.preload?.video != null)
|
||||
_PostVideoPlayer(data: data).padding(horizontal: 12, bottom: 8),
|
||||
if (data.type == 'question')
|
||||
_PostQuestionHint(data: data)
|
||||
.padding(horizontal: 16, bottom: 8),
|
||||
if (data.body['title'] != null ||
|
||||
data.body['description'] != null)
|
||||
_PostHeadline(
|
||||
data: data,
|
||||
isEnlarge: data.type == 'article' && showFullPost,
|
||||
@ -368,7 +387,8 @@ class PostItem extends StatelessWidget {
|
||||
if (data.repostTo != null)
|
||||
_PostQuoteContent(child: data.repostTo!).padding(
|
||||
horizontal: 12,
|
||||
bottom: data.preload?.attachments?.isNotEmpty ?? false ? 12 : 0,
|
||||
bottom:
|
||||
data.preload?.attachments?.isNotEmpty ?? false ? 12 : 0,
|
||||
),
|
||||
if (data.visibility > 0)
|
||||
_PostVisibilityHint(data: data).padding(
|
||||
@ -380,7 +400,9 @@ class PostItem extends StatelessWidget {
|
||||
horizontal: 16,
|
||||
vertical: 4,
|
||||
),
|
||||
if (data.tags.isNotEmpty) _PostTagsList(data: data).padding(horizontal: 16, top: 4, bottom: 6),
|
||||
if (data.tags.isNotEmpty)
|
||||
_PostTagsList(data: data)
|
||||
.padding(horizontal: 16, top: 4, bottom: 6),
|
||||
],
|
||||
),
|
||||
),
|
||||
@ -393,12 +415,16 @@ class PostItem extends StatelessWidget {
|
||||
fit: showFullPost ? BoxFit.cover : BoxFit.contain,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
),
|
||||
if (data.preload?.poll != null) PostPoll(poll: data.preload!.poll!).padding(horizontal: 12, vertical: 4),
|
||||
if (data.body['content'] != null && (cfg.prefs.getBool(kAppExpandPostLink) ?? true))
|
||||
if (data.preload?.poll != null)
|
||||
PostPoll(poll: data.preload!.poll!)
|
||||
.padding(horizontal: 12, vertical: 4),
|
||||
if (data.body['content'] != null &&
|
||||
(cfg.prefs.getBool(kAppExpandPostLink) ?? true))
|
||||
LinkPreviewWidget(
|
||||
text: data.body['content'],
|
||||
).padding(horizontal: 4),
|
||||
_PostFeaturedComment(data: data, maxWidth: maxWidth).padding(horizontal: 12),
|
||||
_PostFeaturedComment(data: data, maxWidth: maxWidth)
|
||||
.padding(horizontal: 12),
|
||||
Container(
|
||||
constraints: BoxConstraints(maxWidth: maxWidth ?? double.infinity),
|
||||
child: Column(
|
||||
@ -460,7 +486,8 @@ class PostShareImageWidget extends StatelessWidget {
|
||||
showMenu: false,
|
||||
isRelativeDate: false,
|
||||
).padding(horizontal: 16, bottom: 8),
|
||||
if (data.type == 'question') _PostQuestionHint(data: data).padding(horizontal: 16, bottom: 8),
|
||||
if (data.type == 'question')
|
||||
_PostQuestionHint(data: data).padding(horizontal: 16, bottom: 8),
|
||||
_PostHeadline(
|
||||
data: data,
|
||||
isEnlarge: data.type == 'article',
|
||||
@ -475,7 +502,8 @@ class PostShareImageWidget extends StatelessWidget {
|
||||
child: data.repostTo!,
|
||||
isRelativeDate: false,
|
||||
).padding(horizontal: 16, bottom: 8),
|
||||
if (data.type != 'article' && (data.preload?.attachments?.isNotEmpty ?? false))
|
||||
if (data.type != 'article' &&
|
||||
(data.preload?.attachments?.isNotEmpty ?? false))
|
||||
StyledWidget(AttachmentList(
|
||||
data: data.preload!.attachments!,
|
||||
columned: true,
|
||||
@ -484,7 +512,8 @@ class PostShareImageWidget extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (data.visibility > 0) _PostVisibilityHint(data: data),
|
||||
if (data.body['content_truncated'] == true) _PostTruncatedHint(data: data),
|
||||
if (data.body['content_truncated'] == true)
|
||||
_PostTruncatedHint(data: data),
|
||||
],
|
||||
).padding(horizontal: 16),
|
||||
_PostBottomAction(
|
||||
@ -544,7 +573,8 @@ class PostShareImageWidget extends StatelessWidget {
|
||||
version: QrVersions.auto,
|
||||
size: 100,
|
||||
gapless: true,
|
||||
embeddedImage: AssetImage('assets/icon/icon-light-radius.png'),
|
||||
embeddedImage:
|
||||
AssetImage('assets/icon/icon-light-radius.png'),
|
||||
embeddedImageStyle: QrEmbeddedImageStyle(
|
||||
size: Size(28, 28),
|
||||
),
|
||||
@ -575,9 +605,11 @@ class _PostQuestionHint extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
children: [
|
||||
Icon(data.body['answer'] == null ? Symbols.help : Symbols.check_circle, size: 20),
|
||||
Icon(data.body['answer'] == null ? Symbols.help : Symbols.check_circle,
|
||||
size: 20),
|
||||
const Gap(4),
|
||||
if (data.body['answer'] == null && data.body['reward']?.toDouble() != null)
|
||||
if (data.body['answer'] == null &&
|
||||
data.body['reward']?.toDouble() != null)
|
||||
Text('postQuestionUnansweredWithReward'.tr(args: [
|
||||
'${data.body['reward']}',
|
||||
])).opacity(0.75)
|
||||
@ -613,7 +645,9 @@ class _PostBottomAction extends StatelessWidget {
|
||||
);
|
||||
|
||||
final String? mostTypicalReaction = data.metric.reactionList.isNotEmpty
|
||||
? data.metric.reactionList.entries.reduce((a, b) => a.value > b.value ? a : b).key
|
||||
? data.metric.reactionList.entries
|
||||
.reduce((a, b) => a.value > b.value ? a : b)
|
||||
.key
|
||||
: null;
|
||||
|
||||
return Row(
|
||||
@ -627,7 +661,8 @@ class _PostBottomAction extends StatelessWidget {
|
||||
InkWell(
|
||||
child: Row(
|
||||
children: [
|
||||
if (mostTypicalReaction == null || kTemplateReactions[mostTypicalReaction] == null)
|
||||
if (mostTypicalReaction == null ||
|
||||
kTemplateReactions[mostTypicalReaction] == null)
|
||||
Icon(Symbols.add_reaction, size: 20, color: iconColor)
|
||||
else
|
||||
Text(
|
||||
@ -639,7 +674,8 @@ class _PostBottomAction extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
const Gap(8),
|
||||
if (data.totalUpvote > 0 && data.totalUpvote >= data.totalDownvote)
|
||||
if (data.totalUpvote > 0 &&
|
||||
data.totalUpvote >= data.totalDownvote)
|
||||
Text('postReactionUpvote').plural(
|
||||
data.totalUpvote,
|
||||
)
|
||||
@ -658,8 +694,12 @@ class _PostBottomAction extends StatelessWidget {
|
||||
data: data,
|
||||
onChanged: (value, attr, delta) {
|
||||
onChanged(data.copyWith(
|
||||
totalUpvote: attr == 1 ? data.totalUpvote + delta : data.totalUpvote,
|
||||
totalDownvote: attr == 2 ? data.totalDownvote + delta : data.totalDownvote,
|
||||
totalUpvote: attr == 1
|
||||
? data.totalUpvote + delta
|
||||
: data.totalUpvote,
|
||||
totalDownvote: attr == 2
|
||||
? data.totalDownvote + delta
|
||||
: data.totalDownvote,
|
||||
metric: data.metric.copyWith(reactionList: value),
|
||||
));
|
||||
},
|
||||
@ -766,7 +806,9 @@ class _PostHeadline extends StatelessWidget {
|
||||
children: [
|
||||
Text(
|
||||
'articleWrittenAt'.tr(
|
||||
args: [DateFormat('y/M/d HH:mm').format(data.createdAt.toLocal())],
|
||||
args: [
|
||||
DateFormat('y/M/d HH:mm').format(data.createdAt.toLocal())
|
||||
],
|
||||
),
|
||||
style: TextStyle(fontSize: 13),
|
||||
),
|
||||
@ -774,7 +816,9 @@ class _PostHeadline extends StatelessWidget {
|
||||
if (data.editedAt != null)
|
||||
Text(
|
||||
'articleEditedAt'.tr(
|
||||
args: [DateFormat('y/M/d HH:mm').format(data.editedAt!.toLocal())],
|
||||
args: [
|
||||
DateFormat('y/M/d HH:mm').format(data.editedAt!.toLocal())
|
||||
],
|
||||
),
|
||||
style: TextStyle(fontSize: 13),
|
||||
),
|
||||
@ -871,7 +915,9 @@ class _PostContentHeader extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final ud = context.read<UserDirectoryProvider>();
|
||||
final user = data.publisher.type == 0 ? ud.getAccountFromCache(data.publisher.accountId) : null;
|
||||
final user = data.publisher.type == 0
|
||||
? ud.getFromCache(data.publisher.accountId)
|
||||
: null;
|
||||
|
||||
return Row(
|
||||
children: [
|
||||
@ -882,7 +928,8 @@ class _PostContentHeader extends StatelessWidget {
|
||||
borderRadius: data.publisher.type == 1 ? (isCompact ? 4 : 8) : 20,
|
||||
badge: (user?.badges.isNotEmpty ?? false)
|
||||
? Icon(
|
||||
kBadgesMeta[user!.badges.first.type]?.$2 ?? Symbols.question_mark,
|
||||
kBadgesMeta[user!.badges.first.type]?.$2 ??
|
||||
Symbols.question_mark,
|
||||
color: kBadgesMeta[user.badges.first.type]?.$3,
|
||||
fill: 1,
|
||||
size: 18,
|
||||
@ -926,8 +973,10 @@ class _PostContentHeader extends StatelessWidget {
|
||||
const Gap(4),
|
||||
Text(
|
||||
isRelativeDate
|
||||
? RelativeTime(context).format((data.publishedAt ?? data.createdAt).toLocal())
|
||||
: DateFormat('y/M/d HH:mm').format((data.publishedAt ?? data.createdAt).toLocal()),
|
||||
? RelativeTime(context).format(
|
||||
(data.publishedAt ?? data.createdAt).toLocal())
|
||||
: DateFormat('y/M/d HH:mm').format(
|
||||
(data.publishedAt ?? data.createdAt).toLocal()),
|
||||
).fontSize(13),
|
||||
],
|
||||
).opacity(0.8),
|
||||
@ -945,8 +994,10 @@ class _PostContentHeader extends StatelessWidget {
|
||||
const Gap(4),
|
||||
Text(
|
||||
isRelativeDate
|
||||
? RelativeTime(context).format((data.publishedAt ?? data.createdAt).toLocal())
|
||||
: DateFormat('y/M/d HH:mm').format((data.publishedAt ?? data.createdAt).toLocal()),
|
||||
? RelativeTime(context).format(
|
||||
(data.publishedAt ?? data.createdAt).toLocal())
|
||||
: DateFormat('y/M/d HH:mm').format(
|
||||
(data.publishedAt ?? data.createdAt).toLocal()),
|
||||
).fontSize(13),
|
||||
],
|
||||
).opacity(0.8),
|
||||
@ -1129,7 +1180,8 @@ class _PostContentBody extends StatelessWidget {
|
||||
if (data.body['content'] == null) return const SizedBox.shrink();
|
||||
final content = MarkdownTextContent(
|
||||
isAutoWarp: data.type == 'story',
|
||||
isEnlargeSticker: RegExp(r"^:([-\w]+):$").hasMatch(data.body['content'] ?? ''),
|
||||
isEnlargeSticker:
|
||||
RegExp(r"^:([-\w]+):$").hasMatch(data.body['content'] ?? ''),
|
||||
textScaler: isEnlarge ? TextScaler.linear(1.1) : null,
|
||||
content: data.body['content'],
|
||||
attachments: data.preload?.attachments,
|
||||
@ -1178,10 +1230,12 @@ class _PostQuoteContent extends StatelessWidget {
|
||||
onDeleted: () {},
|
||||
).padding(bottom: 4),
|
||||
_PostContentBody(data: child),
|
||||
if (child.visibility > 0) _PostVisibilityHint(data: child).padding(top: 4),
|
||||
if (child.visibility > 0)
|
||||
_PostVisibilityHint(data: child).padding(top: 4),
|
||||
],
|
||||
).padding(horizontal: 16),
|
||||
if (child.type != 'article' && (child.preload?.attachments?.isNotEmpty ?? false))
|
||||
if (child.type != 'article' &&
|
||||
(child.preload?.attachments?.isNotEmpty ?? false))
|
||||
ClipRRect(
|
||||
borderRadius: const BorderRadius.only(
|
||||
bottomLeft: Radius.circular(8),
|
||||
@ -1332,7 +1386,9 @@ class _PostTruncatedHint extends StatelessWidget {
|
||||
const Gap(4),
|
||||
Text('postReadEstimate').tr(args: [
|
||||
'${Duration(
|
||||
seconds: (data.body['content_length'] as num).toDouble() * 60 ~/ kHumanReadSpeed,
|
||||
seconds: (data.body['content_length'] as num).toDouble() *
|
||||
60 ~/
|
||||
kHumanReadSpeed,
|
||||
).inSeconds}s',
|
||||
]),
|
||||
],
|
||||
@ -1371,7 +1427,8 @@ class _PostFeaturedCommentState extends State<_PostFeaturedComment> {
|
||||
// If this is a answered question, fetch the answer instead
|
||||
if (widget.data.type == 'question' && widget.data.body['answer'] != null) {
|
||||
final sn = context.read<SnNetworkProvider>();
|
||||
final resp = await sn.client.get('/cgi/co/posts/${widget.data.body['answer']}');
|
||||
final resp =
|
||||
await sn.client.get('/cgi/co/posts/${widget.data.body['answer']}');
|
||||
_isAnswer = true;
|
||||
setState(() => _featuredComment = SnPost.fromJson(resp.data));
|
||||
return;
|
||||
@ -1379,9 +1436,11 @@ class _PostFeaturedCommentState extends State<_PostFeaturedComment> {
|
||||
|
||||
try {
|
||||
final sn = context.read<SnNetworkProvider>();
|
||||
final resp = await sn.client.get('/cgi/co/posts/${widget.data.id}/replies/featured', queryParameters: {
|
||||
'take': 1,
|
||||
});
|
||||
final resp = await sn.client.get(
|
||||
'/cgi/co/posts/${widget.data.id}/replies/featured',
|
||||
queryParameters: {
|
||||
'take': 1,
|
||||
});
|
||||
setState(() => _featuredComment = SnPost.fromJson(resp.data[0]));
|
||||
} catch (err) {
|
||||
if (!mounted) return;
|
||||
@ -1410,7 +1469,9 @@ class _PostFeaturedCommentState extends State<_PostFeaturedComment> {
|
||||
width: double.infinity,
|
||||
child: Material(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||
color: _isAnswer ? Colors.green.withOpacity(0.5) : Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||
color: _isAnswer
|
||||
? Colors.green.withOpacity(0.5)
|
||||
: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||
child: InkWell(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||
onTap: () {
|
||||
@ -1430,11 +1491,17 @@ class _PostFeaturedCommentState extends State<_PostFeaturedComment> {
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
const Gap(2),
|
||||
Icon(_isAnswer ? Symbols.task_alt : Symbols.prompt_suggestion, size: 20),
|
||||
Icon(_isAnswer ? Symbols.task_alt : Symbols.prompt_suggestion,
|
||||
size: 20),
|
||||
const Gap(10),
|
||||
Text(
|
||||
_isAnswer ? 'postQuestionAnswerTitle' : 'postFeaturedComment',
|
||||
style: Theme.of(context).textTheme.titleMedium!.copyWith(fontSize: 15),
|
||||
_isAnswer
|
||||
? 'postQuestionAnswerTitle'
|
||||
: 'postFeaturedComment',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleMedium!
|
||||
.copyWith(fontSize: 15),
|
||||
).tr(),
|
||||
],
|
||||
),
|
||||
@ -1572,7 +1639,8 @@ class _PostGetInsightPopupState extends State<_PostGetInsightPopup> {
|
||||
}
|
||||
|
||||
RegExp cleanThinkingRegExp = RegExp(r'<think>[\s\S]*?</think>');
|
||||
setState(() => _response = out.replaceAll(cleanThinkingRegExp, '').trim());
|
||||
setState(
|
||||
() => _response = out.replaceAll(cleanThinkingRegExp, '').trim());
|
||||
} catch (err) {
|
||||
if (!mounted) return;
|
||||
context.showErrorDialog(err);
|
||||
@ -1595,11 +1663,16 @@ class _PostGetInsightPopupState extends State<_PostGetInsightPopup> {
|
||||
children: [
|
||||
const Icon(Symbols.book_4_spark, size: 24),
|
||||
const Gap(16),
|
||||
Text('postGetInsightTitle', style: Theme.of(context).textTheme.titleLarge).tr(),
|
||||
Text('postGetInsightTitle',
|
||||
style: Theme.of(context).textTheme.titleLarge)
|
||||
.tr(),
|
||||
],
|
||||
).padding(horizontal: 20, top: 16, bottom: 12),
|
||||
const Gap(4),
|
||||
Text('postGetInsightDescription', style: Theme.of(context).textTheme.bodySmall).tr().padding(horizontal: 20),
|
||||
Text('postGetInsightDescription',
|
||||
style: Theme.of(context).textTheme.bodySmall)
|
||||
.tr()
|
||||
.padding(horizontal: 20),
|
||||
const Gap(4),
|
||||
if (_response == null)
|
||||
Expanded(
|
||||
@ -1617,12 +1690,16 @@ class _PostGetInsightPopupState extends State<_PostGetInsightPopup> {
|
||||
leading: const Icon(Symbols.info),
|
||||
title: Text('aiThinkingProcess'.tr()),
|
||||
tilePadding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
collapsedBackgroundColor: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||
collapsedBackgroundColor:
|
||||
Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||
minTileHeight: 32,
|
||||
children: [
|
||||
SelectableText(
|
||||
_thinkingProcess!,
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(fontStyle: FontStyle.italic),
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium!
|
||||
.copyWith(fontStyle: FontStyle.italic),
|
||||
).padding(horizontal: 20, vertical: 8),
|
||||
],
|
||||
).padding(vertical: 8),
|
||||
@ -1659,7 +1736,8 @@ class _PostVideoPlayer extends StatelessWidget {
|
||||
aspectRatio: 16 / 9,
|
||||
child: ClipRRect(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||
child: AttachmentItem(data: data.preload!.video!, heroTag: 'post-video-${data.id}'),
|
||||
child: AttachmentItem(
|
||||
data: data.preload!.video!, heroTag: 'post-video-${data.id}'),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
@ -23,7 +23,7 @@ class PublisherPopoverCard extends StatelessWidget {
|
||||
final sn = context.read<SnNetworkProvider>();
|
||||
final ud = context.read<UserDirectoryProvider>();
|
||||
|
||||
final user = data.type == 0 ? ud.getAccountFromCache(data.accountId) : null;
|
||||
final user = data.type == 0 ? ud.getFromCache(data.accountId) : null;
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@ -85,7 +85,9 @@ class PublisherPopoverCard extends StatelessWidget {
|
||||
(ele) => Tooltip(
|
||||
richMessage: TextSpan(
|
||||
children: [
|
||||
TextSpan(text: kBadgesMeta[ele.type]?.$1.tr() ?? 'unknown'.tr()),
|
||||
TextSpan(
|
||||
text: kBadgesMeta[ele.type]?.$1.tr() ??
|
||||
'unknown'.tr()),
|
||||
if (ele.metadata['title'] != null)
|
||||
TextSpan(
|
||||
text: '\n${ele.metadata['title']}',
|
||||
@ -146,7 +148,10 @@ class PublisherPopoverCard extends StatelessWidget {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Text('publisherTotalDownvote').tr().fontSize(13).opacity(0.75),
|
||||
Text('publisherTotalDownvote')
|
||||
.tr()
|
||||
.fontSize(13)
|
||||
.opacity(0.75),
|
||||
Text(data.totalDownvote.toString()),
|
||||
],
|
||||
),
|
||||
|
Reference in New Issue
Block a user