💄 Better screenshot of post
This commit is contained in:
@@ -7,6 +7,7 @@ import 'package:island/models/post.dart';
|
|||||||
import 'package:island/pods/config.dart';
|
import 'package:island/pods/config.dart';
|
||||||
import 'package:island/widgets/alert.dart';
|
import 'package:island/widgets/alert.dart';
|
||||||
import 'package:island/widgets/post/post_item_screenshot.dart';
|
import 'package:island/widgets/post/post_item_screenshot.dart';
|
||||||
|
import 'package:island/widgets/post/post_shared.dart';
|
||||||
import 'package:path_provider/path_provider.dart' show getTemporaryDirectory;
|
import 'package:path_provider/path_provider.dart' show getTemporaryDirectory;
|
||||||
import 'package:screenshot/screenshot.dart';
|
import 'package:screenshot/screenshot.dart';
|
||||||
import 'package:share_plus/share_plus.dart';
|
import 'package:share_plus/share_plus.dart';
|
||||||
@@ -29,6 +30,9 @@ Future<void> sharePostAsScreenshot(
|
|||||||
sharedPreferencesProvider.overrideWithValue(
|
sharedPreferencesProvider.overrideWithValue(
|
||||||
ref.watch(sharedPreferencesProvider),
|
ref.watch(sharedPreferencesProvider),
|
||||||
),
|
),
|
||||||
|
repliesProvider(
|
||||||
|
post.id,
|
||||||
|
).overrideWithValue(ref.watch(repliesProvider(post.id))),
|
||||||
],
|
],
|
||||||
child: Directionality(
|
child: Directionality(
|
||||||
textDirection: TextDirection.ltr,
|
textDirection: TextDirection.ltr,
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart' hide TextDirection;
|
import 'package:easy_localization/easy_localization.dart' hide TextDirection;
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
@@ -15,10 +13,10 @@ import 'package:island/pods/network.dart';
|
|||||||
import 'package:island/pods/translate.dart';
|
import 'package:island/pods/translate.dart';
|
||||||
import 'package:island/pods/userinfo.dart';
|
import 'package:island/pods/userinfo.dart';
|
||||||
import 'package:island/screens/posts/compose.dart';
|
import 'package:island/screens/posts/compose.dart';
|
||||||
|
import 'package:island/utils/share_utils.dart';
|
||||||
import 'package:island/widgets/alert.dart';
|
import 'package:island/widgets/alert.dart';
|
||||||
import 'package:island/widgets/content/markdown.dart';
|
import 'package:island/widgets/content/markdown.dart';
|
||||||
import 'package:island/widgets/content/image.dart';
|
import 'package:island/widgets/content/image.dart';
|
||||||
import 'package:island/widgets/post/post_item_screenshot.dart';
|
|
||||||
import 'package:island/widgets/post/post_award_sheet.dart';
|
import 'package:island/widgets/post/post_award_sheet.dart';
|
||||||
import 'package:island/widgets/post/post_pin_sheet.dart';
|
import 'package:island/widgets/post/post_pin_sheet.dart';
|
||||||
import 'package:island/widgets/post/post_shared.dart';
|
import 'package:island/widgets/post/post_shared.dart';
|
||||||
@@ -28,9 +26,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:island/widgets/post/compose_sheet.dart';
|
import 'package:island/widgets/post/compose_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:screenshot/screenshot.dart';
|
|
||||||
import 'package:share_plus/share_plus.dart';
|
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
import 'package:super_context_menu/super_context_menu.dart';
|
import 'package:super_context_menu/super_context_menu.dart';
|
||||||
|
|
||||||
@@ -102,10 +97,9 @@ class PostActionableItem extends HookConsumerWidget {
|
|||||||
final config = ref.watch(appSettingsProvider);
|
final config = ref.watch(appSettingsProvider);
|
||||||
|
|
||||||
final widgetItem = InkWell(
|
final widgetItem = InkWell(
|
||||||
borderRadius:
|
borderRadius: borderRadius != null
|
||||||
borderRadius != null
|
? BorderRadius.all(Radius.circular(borderRadius!))
|
||||||
? BorderRadius.all(Radius.circular(borderRadius!))
|
: null,
|
||||||
: null,
|
|
||||||
child: PostItem(
|
child: PostItem(
|
||||||
key: key,
|
key: key,
|
||||||
item: item,
|
item: item,
|
||||||
@@ -126,51 +120,6 @@ class PostActionableItem extends HookConsumerWidget {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
final screenshotController = useMemoized(() => ScreenshotController(), []);
|
|
||||||
|
|
||||||
void shareAsScreenshot() async {
|
|
||||||
if (kIsWeb) return;
|
|
||||||
showLoadingModal(context);
|
|
||||||
await screenshotController
|
|
||||||
.captureFromWidget(
|
|
||||||
ProviderScope(
|
|
||||||
overrides: [
|
|
||||||
sharedPreferencesProvider.overrideWithValue(
|
|
||||||
ref.watch(sharedPreferencesProvider),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
child: Directionality(
|
|
||||||
textDirection: TextDirection.ltr,
|
|
||||||
child: SizedBox(
|
|
||||||
width: 520,
|
|
||||||
child: PostItemScreenshot(item: item, isFullPost: isFullPost),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
context: context,
|
|
||||||
pixelRatio: MediaQuery.of(context).devicePixelRatio,
|
|
||||||
delay: const Duration(seconds: 1),
|
|
||||||
)
|
|
||||||
.then((Uint8List? image) async {
|
|
||||||
if (image == null) return;
|
|
||||||
final directory = await getTemporaryDirectory();
|
|
||||||
final imagePath =
|
|
||||||
await File('${directory.path}/image.png').create();
|
|
||||||
await imagePath.writeAsBytes(image);
|
|
||||||
|
|
||||||
if (!context.mounted) return;
|
|
||||||
hideLoadingModal(context);
|
|
||||||
final box = context.findRenderObject() as RenderBox?;
|
|
||||||
await Share.shareXFiles([
|
|
||||||
XFile(imagePath.path),
|
|
||||||
], sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size);
|
|
||||||
})
|
|
||||||
.catchError((err) {
|
|
||||||
if (context.mounted) hideLoadingModal(context);
|
|
||||||
showErrorAlert(err);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return ContextMenuWidget(
|
return ContextMenuWidget(
|
||||||
menuProvider: (_) {
|
menuProvider: (_) {
|
||||||
return Menu(
|
return Menu(
|
||||||
@@ -319,7 +268,7 @@ class PostActionableItem extends HookConsumerWidget {
|
|||||||
title: 'sharePostPhoto'.tr(),
|
title: 'sharePostPhoto'.tr(),
|
||||||
image: MenuImage.icon(Symbols.share_reviews),
|
image: MenuImage.icon(Symbols.share_reviews),
|
||||||
callback: () {
|
callback: () {
|
||||||
shareAsScreenshot();
|
sharePostAsScreenshot(context, ref, item);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
MenuSeparator(),
|
MenuSeparator(),
|
||||||
@@ -329,7 +278,7 @@ class PostActionableItem extends HookConsumerWidget {
|
|||||||
callback: () {
|
callback: () {
|
||||||
showAbuseReportSheet(
|
showAbuseReportSheet(
|
||||||
context,
|
context,
|
||||||
resourceIdentifier: 'post/${item.id}',
|
resourceIdentifier: 'post:${item.id}',
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -337,14 +286,12 @@ class PostActionableItem extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
child: Material(
|
child: Material(
|
||||||
color:
|
color: config.cardTransparency < 1
|
||||||
config.cardTransparency < 1
|
? Colors.transparent
|
||||||
? Colors.transparent
|
: Theme.of(context).cardTheme.color,
|
||||||
: Theme.of(context).cardTheme.color,
|
borderRadius: borderRadius != null
|
||||||
borderRadius:
|
? BorderRadius.all(Radius.circular(borderRadius!))
|
||||||
borderRadius != null
|
: null,
|
||||||
? BorderRadius.all(Radius.circular(borderRadius!))
|
|
||||||
: null,
|
|
||||||
child: widgetItem,
|
child: widgetItem,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -417,24 +364,21 @@ class PostItem extends HookConsumerWidget {
|
|||||||
reacting.value = false;
|
reacting.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
final mostReaction =
|
final mostReaction = item.reactionsCount.isEmpty
|
||||||
item.reactionsCount.isEmpty
|
? null
|
||||||
? null
|
: item.reactionsCount.entries
|
||||||
: item.reactionsCount.entries
|
.sortedBy((e) => e.value)
|
||||||
.sortedBy((e) => e.value)
|
.map((e) => e.key)
|
||||||
.map((e) => e.key)
|
.last;
|
||||||
.last;
|
|
||||||
|
|
||||||
final postLanguage =
|
final postLanguage = item.content != null && isTranslatable
|
||||||
item.content != null && isTranslatable
|
? ref.watch(detectStringLanguageProvider(item.content!))
|
||||||
? ref.watch(detectStringLanguageProvider(item.content!))
|
: null;
|
||||||
: null;
|
|
||||||
|
|
||||||
final currentLanguage = isTranslatable ? context.locale.toString() : null;
|
final currentLanguage = isTranslatable ? context.locale.toString() : null;
|
||||||
final translatableLanguage =
|
final translatableLanguage = postLanguage != null && isTranslatable
|
||||||
postLanguage != null && isTranslatable
|
? postLanguage.substring(0, 2) != currentLanguage!.substring(0, 2)
|
||||||
? postLanguage.substring(0, 2) != currentLanguage!.substring(0, 2)
|
: false;
|
||||||
: false;
|
|
||||||
|
|
||||||
final translating = useState(false);
|
final translating = useState(false);
|
||||||
final translatedText = useState<String?>(null);
|
final translatedText = useState<String?>(null);
|
||||||
@@ -466,55 +410,49 @@ class PostItem extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final translatedWidget =
|
final translatedWidget = (translatedText.value?.isNotEmpty ?? false)
|
||||||
(translatedText.value?.isNotEmpty ?? false)
|
? Column(
|
||||||
? Column(
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
children: [
|
||||||
children: [
|
Row(
|
||||||
Row(
|
children: [
|
||||||
children: [
|
const Expanded(child: Divider()),
|
||||||
const Expanded(child: Divider()),
|
const Gap(8),
|
||||||
const Gap(8),
|
const Text('translated').tr().fontSize(11).opacity(0.75),
|
||||||
const Text('translated').tr().fontSize(11).opacity(0.75),
|
],
|
||||||
],
|
|
||||||
),
|
|
||||||
MarkdownTextContent(
|
|
||||||
content: translatedText.value!,
|
|
||||||
isSelectable: isTextSelectable,
|
|
||||||
attachments: item.attachments,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
: null;
|
|
||||||
|
|
||||||
final translatableWidget =
|
|
||||||
(isTranslatable && translatableLanguage)
|
|
||||||
? Align(
|
|
||||||
alignment: Alignment.centerLeft,
|
|
||||||
child: TextButton.icon(
|
|
||||||
onPressed: translating.value ? null : translate,
|
|
||||||
style: ButtonStyle(
|
|
||||||
padding: const WidgetStatePropertyAll(
|
|
||||||
EdgeInsets.symmetric(horizontal: 2),
|
|
||||||
),
|
|
||||||
visualDensity: const VisualDensity(
|
|
||||||
horizontal: 0,
|
|
||||||
vertical: -4,
|
|
||||||
),
|
|
||||||
foregroundColor: WidgetStatePropertyAll(
|
|
||||||
translatedText.value == null ? null : Colors.grey,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
icon: const Icon(Symbols.translate),
|
|
||||||
label:
|
|
||||||
translatedText.value != null
|
|
||||||
? const Text('translated').tr()
|
|
||||||
: translating.value
|
|
||||||
? const Text('translating').tr()
|
|
||||||
: const Text('translate').tr(),
|
|
||||||
),
|
),
|
||||||
)
|
MarkdownTextContent(
|
||||||
: null;
|
content: translatedText.value!,
|
||||||
|
isSelectable: isTextSelectable,
|
||||||
|
attachments: item.attachments,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
final translatableWidget = (isTranslatable && translatableLanguage)
|
||||||
|
? Align(
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
child: TextButton.icon(
|
||||||
|
onPressed: translating.value ? null : translate,
|
||||||
|
style: ButtonStyle(
|
||||||
|
padding: const WidgetStatePropertyAll(
|
||||||
|
EdgeInsets.symmetric(horizontal: 2),
|
||||||
|
),
|
||||||
|
visualDensity: const VisualDensity(horizontal: 0, vertical: -4),
|
||||||
|
foregroundColor: WidgetStatePropertyAll(
|
||||||
|
translatedText.value == null ? null : Colors.grey,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
icon: const Icon(Symbols.translate),
|
||||||
|
label: translatedText.value != null
|
||||||
|
? const Text('translated').tr()
|
||||||
|
: translating.value
|
||||||
|
? const Text('translating').tr()
|
||||||
|
: const Text('translate').tr(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: null;
|
||||||
|
|
||||||
final translationSection = Column(
|
final translationSection = Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
@@ -534,97 +472,87 @@ class PostItem extends HookConsumerWidget {
|
|||||||
isFullPost: isFullPost,
|
isFullPost: isFullPost,
|
||||||
isCompact: isCompact,
|
isCompact: isCompact,
|
||||||
renderingPadding: renderingPadding,
|
renderingPadding: renderingPadding,
|
||||||
trailing:
|
trailing: isCompact
|
||||||
isCompact
|
? null
|
||||||
? null
|
: SizedBox(
|
||||||
: SizedBox(
|
width: 36,
|
||||||
width: 36,
|
height: 36,
|
||||||
height: 36,
|
child: IconButton(
|
||||||
child: IconButton(
|
icon: mostReaction == null
|
||||||
icon:
|
? const Icon(Symbols.add_reaction)
|
||||||
mostReaction == null
|
: Badge(
|
||||||
? const Icon(Symbols.add_reaction)
|
label: Center(
|
||||||
: Badge(
|
child: Text(
|
||||||
label: Center(
|
'x${item.reactionsCount[mostReaction]}',
|
||||||
child: Text(
|
style: const TextStyle(fontSize: 11),
|
||||||
'x${item.reactionsCount[mostReaction]}',
|
textAlign: TextAlign.center,
|
||||||
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('+')
|
|
||||||
? HookConsumer(
|
|
||||||
builder: (context, ref, child) {
|
|
||||||
final baseUrl = ref.watch(
|
|
||||||
serverUrlProvider,
|
|
||||||
);
|
|
||||||
final stickerUri =
|
|
||||||
'$baseUrl/sphere/stickers/lookup/$mostReaction/open';
|
|
||||||
return SizedBox(
|
|
||||||
width: 32,
|
|
||||||
height: 32,
|
|
||||||
child:
|
|
||||||
UniversalImage(
|
|
||||||
uri: stickerUri,
|
|
||||||
width: 28,
|
|
||||||
height: 28,
|
|
||||||
fit: BoxFit.contain,
|
|
||||||
).center(),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
)
|
|
||||||
: _buildReactionIcon(
|
|
||||||
mostReaction,
|
|
||||||
32,
|
|
||||||
).padding(
|
|
||||||
bottom:
|
|
||||||
_getReactionImageAvailable(
|
|
||||||
mostReaction,
|
|
||||||
)
|
|
||||||
? 2
|
|
||||||
: 0,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
style: ButtonStyle(
|
),
|
||||||
backgroundColor: WidgetStatePropertyAll(
|
offset: const Offset(4, 20),
|
||||||
(item.reactionsMade[mostReaction] ?? false)
|
backgroundColor: Theme.of(
|
||||||
? Theme.of(
|
context,
|
||||||
|
).colorScheme.primary.withOpacity(0.75),
|
||||||
|
textColor: Theme.of(context).colorScheme.onPrimary,
|
||||||
|
child: mostReaction.contains('+')
|
||||||
|
? HookConsumer(
|
||||||
|
builder: (context, ref, child) {
|
||||||
|
final baseUrl = ref.watch(
|
||||||
|
serverUrlProvider,
|
||||||
|
);
|
||||||
|
final stickerUri =
|
||||||
|
'$baseUrl/sphere/stickers/lookup/$mostReaction/open';
|
||||||
|
return SizedBox(
|
||||||
|
width: 32,
|
||||||
|
height: 32,
|
||||||
|
child: UniversalImage(
|
||||||
|
uri: stickerUri,
|
||||||
|
width: 28,
|
||||||
|
height: 28,
|
||||||
|
fit: BoxFit.contain,
|
||||||
|
).center(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
: _buildReactionIcon(mostReaction, 32).padding(
|
||||||
|
bottom:
|
||||||
|
_getReactionImageAvailable(mostReaction)
|
||||||
|
? 2
|
||||||
|
: 0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
style: ButtonStyle(
|
||||||
|
backgroundColor: WidgetStatePropertyAll(
|
||||||
|
(item.reactionsMade[mostReaction] ?? false)
|
||||||
|
? Theme.of(
|
||||||
context,
|
context,
|
||||||
).colorScheme.primary.withOpacity(0.5)
|
).colorScheme.primary.withOpacity(0.5)
|
||||||
: null,
|
: null,
|
||||||
),
|
|
||||||
),
|
|
||||||
onPressed: () {
|
|
||||||
showModalBottomSheet(
|
|
||||||
context: context,
|
|
||||||
useRootNavigator: true,
|
|
||||||
isScrollControlled: true,
|
|
||||||
builder: (BuildContext context) {
|
|
||||||
return PostReactionSheet(
|
|
||||||
reactionsCount: item.reactionsCount,
|
|
||||||
reactionsMade: item.reactionsMade,
|
|
||||||
onReact: (symbol, attitude) {
|
|
||||||
reactPost(symbol, attitude);
|
|
||||||
},
|
|
||||||
postId: item.id,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
padding: EdgeInsets.zero,
|
|
||||||
visualDensity: const VisualDensity(
|
|
||||||
horizontal: -3,
|
|
||||||
vertical: -3,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
onPressed: () {
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
useRootNavigator: true,
|
||||||
|
isScrollControlled: true,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return PostReactionSheet(
|
||||||
|
reactionsCount: item.reactionsCount,
|
||||||
|
reactionsMade: item.reactionsMade,
|
||||||
|
onReact: (symbol, attitude) {
|
||||||
|
reactPost(symbol, attitude);
|
||||||
|
},
|
||||||
|
postId: item.id,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
visualDensity: const VisualDensity(
|
||||||
|
horizontal: -3,
|
||||||
|
vertical: -3,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
PostBody(
|
PostBody(
|
||||||
item: item,
|
item: item,
|
||||||
@@ -708,25 +636,24 @@ class PostReactionList extends HookConsumerWidget {
|
|||||||
horizontal: VisualDensity.minimumDensity,
|
horizontal: VisualDensity.minimumDensity,
|
||||||
vertical: VisualDensity.minimumDensity,
|
vertical: VisualDensity.minimumDensity,
|
||||||
),
|
),
|
||||||
onPressed:
|
onPressed: submitting.value
|
||||||
submitting.value
|
? null
|
||||||
? null
|
: () {
|
||||||
: () {
|
showModalBottomSheet(
|
||||||
showModalBottomSheet(
|
context: context,
|
||||||
context: context,
|
isScrollControlled: true,
|
||||||
isScrollControlled: true,
|
builder: (BuildContext context) {
|
||||||
builder: (BuildContext context) {
|
return PostReactionSheet(
|
||||||
return PostReactionSheet(
|
reactionsCount: reactions,
|
||||||
reactionsCount: reactions,
|
reactionsMade: reactionsMade,
|
||||||
reactionsMade: reactionsMade,
|
onReact: (symbol, attitude) {
|
||||||
onReact: (symbol, attitude) {
|
reactPost(symbol, attitude);
|
||||||
reactPost(symbol, attitude);
|
},
|
||||||
},
|
postId: parentId,
|
||||||
postId: parentId,
|
);
|
||||||
);
|
},
|
||||||
},
|
);
|
||||||
);
|
},
|
||||||
},
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
for (final symbol in reactions.keys)
|
for (final symbol in reactions.keys)
|
||||||
@@ -741,15 +668,14 @@ class PostReactionList extends HookConsumerWidget {
|
|||||||
Text('x${reactions[symbol]}').bold(),
|
Text('x${reactions[symbol]}').bold(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
onPressed:
|
onPressed: submitting.value
|
||||||
submitting.value
|
? null
|
||||||
? null
|
: () {
|
||||||
: () {
|
reactPost(
|
||||||
reactPost(
|
symbol,
|
||||||
symbol,
|
kReactionTemplates[symbol]?.attitude ?? 0,
|
||||||
kReactionTemplates[symbol]?.attitude ?? 0,
|
);
|
||||||
);
|
},
|
||||||
},
|
|
||||||
visualDensity: const VisualDensity(
|
visualDensity: const VisualDensity(
|
||||||
horizontal: VisualDensity.minimumDensity,
|
horizontal: VisualDensity.minimumDensity,
|
||||||
vertical: VisualDensity.minimumDensity,
|
vertical: VisualDensity.minimumDensity,
|
||||||
|
|||||||
@@ -4,10 +4,44 @@ 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/pods/config.dart';
|
||||||
import 'package:island/widgets/post/post_shared.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:qr_flutter/qr_flutter.dart';
|
||||||
import 'package:styled_widget/styled_widget.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 {
|
class PostItemScreenshot extends ConsumerWidget {
|
||||||
final SnPost item;
|
final SnPost item;
|
||||||
final EdgeInsets? padding;
|
final EdgeInsets? padding;
|
||||||
@@ -51,18 +85,42 @@ class PostItemScreenshot extends ConsumerWidget {
|
|||||||
renderingPadding: renderingPadding,
|
renderingPadding: renderingPadding,
|
||||||
isRelativeTime: false,
|
isRelativeTime: false,
|
||||||
trailing: mostReaction != null
|
trailing: mostReaction != null
|
||||||
? Row(
|
? Badge(
|
||||||
children: [
|
label: Center(
|
||||||
Text(
|
child: Text(
|
||||||
kReactionTemplates[mostReaction]?.icon ?? '',
|
|
||||||
style: const TextStyle(fontSize: 20),
|
|
||||||
),
|
|
||||||
const Gap(4),
|
|
||||||
Text(
|
|
||||||
'x${item.reactionsCount[mostReaction]}',
|
'x${item.reactionsCount[mostReaction]}',
|
||||||
style: const TextStyle(fontSize: 11),
|
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,
|
: null,
|
||||||
),
|
),
|
||||||
@@ -81,6 +139,93 @@ class PostItemScreenshot extends ConsumerWidget {
|
|||||||
isInteractive: false,
|
isInteractive: false,
|
||||||
renderingPadding: renderingPadding,
|
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(
|
Container(
|
||||||
color: Theme.of(context).colorScheme.surfaceContainerLow,
|
color: Theme.of(context).colorScheme.surfaceContainerLow,
|
||||||
margin: const EdgeInsets.only(top: 8),
|
margin: const EdgeInsets.only(top: 8),
|
||||||
|
|||||||
Reference in New Issue
Block a user