✨ Edited the post item styles
This commit is contained in:
@@ -399,19 +399,9 @@ class _ActivityListView extends HookConsumerWidget {
|
|||||||
switch (item.type) {
|
switch (item.type) {
|
||||||
case 'posts.new':
|
case 'posts.new':
|
||||||
case 'posts.new.replies':
|
case 'posts.new.replies':
|
||||||
final isReply = item.type == 'posts.new.replies';
|
itemWidget = PostActionableItem(
|
||||||
itemWidget = PostItem(
|
borderRadius: 8,
|
||||||
backgroundColor:
|
|
||||||
isWideScreen(context) ? Colors.transparent : null,
|
|
||||||
item: SnPost.fromJson(item.data!),
|
item: SnPost.fromJson(item.data!),
|
||||||
padding:
|
|
||||||
isReply
|
|
||||||
? const EdgeInsets.only(
|
|
||||||
left: 16,
|
|
||||||
right: 16,
|
|
||||||
bottom: 16,
|
|
||||||
)
|
|
||||||
: null,
|
|
||||||
onRefresh: () {
|
onRefresh: () {
|
||||||
activitiesNotifier.forceRefresh();
|
activitiesNotifier.forceRefresh();
|
||||||
},
|
},
|
||||||
@@ -422,21 +412,10 @@ class _ActivityListView extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
if (isReply) {
|
itemWidget = Card(
|
||||||
itemWidget = Column(
|
margin: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
child: itemWidget,
|
||||||
children: [
|
);
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
const Icon(Symbols.reply),
|
|
||||||
const Gap(8),
|
|
||||||
Text('Replying your post'),
|
|
||||||
],
|
|
||||||
).padding(horizontal: 20, vertical: 8),
|
|
||||||
itemWidget,
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case 'discovery':
|
case 'discovery':
|
||||||
itemWidget = _DiscoveryActivityItem(data: item.data!);
|
itemWidget = _DiscoveryActivityItem(data: item.data!);
|
||||||
@@ -445,7 +424,7 @@ class _ActivityListView extends HookConsumerWidget {
|
|||||||
itemWidget = const Placeholder();
|
itemWidget = const Placeholder();
|
||||||
}
|
}
|
||||||
|
|
||||||
return Column(children: [itemWidget, const Divider(height: 1)]);
|
return itemWidget;
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
SliverGap(getTabbedPadding(context).bottom),
|
SliverGap(getTabbedPadding(context).bottom),
|
||||||
|
@@ -470,7 +470,9 @@ class PostComposeScreen extends HookConsumerWidget {
|
|||||||
color: colorScheme.primary,
|
color: colorScheme.primary,
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () => ComposeLogic.addAttachmentById(ref, state, context),
|
onPressed:
|
||||||
|
() =>
|
||||||
|
ComposeLogic.addAttachmentById(ref, state, context),
|
||||||
icon: const Icon(Symbols.attach_file),
|
icon: const Icon(Symbols.attach_file),
|
||||||
color: colorScheme.primary,
|
color: colorScheme.primary,
|
||||||
),
|
),
|
||||||
@@ -655,7 +657,7 @@ class PostComposeScreen extends HookConsumerWidget {
|
|||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
controller: scrollController,
|
controller: scrollController,
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
child: PostItem(item: post, isOpenable: false),
|
child: PostItem(item: post),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@@ -71,7 +71,6 @@ class PostDetailScreen extends HookConsumerWidget {
|
|||||||
children: [
|
children: [
|
||||||
PostItem(
|
PostItem(
|
||||||
item: post!,
|
item: post!,
|
||||||
isOpenable: false,
|
|
||||||
isFullPost: true,
|
isFullPost: true,
|
||||||
backgroundColor: isWide ? Colors.transparent : null,
|
backgroundColor: isWide ? Colors.transparent : null,
|
||||||
onUpdate: (newItem) {
|
onUpdate: (newItem) {
|
||||||
|
@@ -86,6 +86,7 @@ class AccountStatusCreationWidget extends HookConsumerWidget {
|
|||||||
onTap: () {
|
onTap: () {
|
||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
context: context,
|
context: context,
|
||||||
|
useRootNavigator: true,
|
||||||
builder:
|
builder:
|
||||||
(context) => AccountStatusCreationSheet(
|
(context) => AccountStatusCreationSheet(
|
||||||
initialStatus:
|
initialStatus:
|
||||||
|
@@ -49,7 +49,7 @@ class AccountStatusCreationSheet extends HookConsumerWidget {
|
|||||||
final user = ref.watch(userInfoProvider);
|
final user = ref.watch(userInfoProvider);
|
||||||
final apiClient = ref.read(apiClientProvider);
|
final apiClient = ref.read(apiClientProvider);
|
||||||
await apiClient.request(
|
await apiClient.request(
|
||||||
'/accounts/me/statuses',
|
'/id/accounts/me/statuses',
|
||||||
data: {
|
data: {
|
||||||
'attitude': attitude.value,
|
'attitude': attitude.value,
|
||||||
'is_invisible': isInvisible.value,
|
'is_invisible': isInvisible.value,
|
||||||
|
@@ -228,7 +228,8 @@ class MessageItem extends HookConsumerWidget {
|
|||||||
return CloudFileList(
|
return CloudFileList(
|
||||||
files: remoteMessage.attachments,
|
files: remoteMessage.attachments,
|
||||||
maxWidth: constraints.maxWidth,
|
maxWidth: constraints.maxWidth,
|
||||||
).padding(vertical: 4);
|
padding: EdgeInsets.symmetric(vertical: 4),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
if (remoteMessage.meta['embeds'] != null)
|
if (remoteMessage.meta['embeds'] != null)
|
||||||
|
@@ -27,6 +27,7 @@ class CloudFileList extends HookConsumerWidget {
|
|||||||
final double? minWidth;
|
final double? minWidth;
|
||||||
final bool disableZoomIn;
|
final bool disableZoomIn;
|
||||||
final bool disableConstraint;
|
final bool disableConstraint;
|
||||||
|
final EdgeInsets? padding;
|
||||||
const CloudFileList({
|
const CloudFileList({
|
||||||
super.key,
|
super.key,
|
||||||
required this.files,
|
required this.files,
|
||||||
@@ -35,6 +36,7 @@ class CloudFileList extends HookConsumerWidget {
|
|||||||
this.minWidth,
|
this.minWidth,
|
||||||
this.disableZoomIn = false,
|
this.disableZoomIn = false,
|
||||||
this.disableConstraint = false,
|
this.disableConstraint = false,
|
||||||
|
this.padding,
|
||||||
});
|
});
|
||||||
|
|
||||||
double calculateAspectRatio() {
|
double calculateAspectRatio() {
|
||||||
@@ -60,7 +62,8 @@ class CloudFileList extends HookConsumerWidget {
|
|||||||
if (files.isEmpty) return const SizedBox.shrink();
|
if (files.isEmpty) return const SizedBox.shrink();
|
||||||
if (files.length == 1) {
|
if (files.length == 1) {
|
||||||
final isImage = files.first.mimeType?.startsWith('image') ?? false;
|
final isImage = files.first.mimeType?.startsWith('image') ?? false;
|
||||||
return ConstrainedBox(
|
return Container(
|
||||||
|
padding: padding,
|
||||||
constraints: BoxConstraints(
|
constraints: BoxConstraints(
|
||||||
maxHeight: disableConstraint ? double.infinity : maxHeight,
|
maxHeight: disableConstraint ? double.infinity : maxHeight,
|
||||||
minWidth: minWidth ?? 0,
|
minWidth: minWidth ?? 0,
|
||||||
@@ -75,7 +78,7 @@ class CloudFileList extends HookConsumerWidget {
|
|||||||
child: AspectRatio(
|
child: AspectRatio(
|
||||||
aspectRatio: calculateAspectRatio(),
|
aspectRatio: calculateAspectRatio(),
|
||||||
child: ClipRRect(
|
child: ClipRRect(
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(16)),
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
child: _CloudFileListEntry(
|
child: _CloudFileListEntry(
|
||||||
file: files.first,
|
file: files.first,
|
||||||
heroTag: heroTags.first,
|
heroTag: heroTags.first,
|
||||||
@@ -95,7 +98,7 @@ class CloudFileList extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
).padding(horizontal: 3);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ConstrainedBox(
|
return ConstrainedBox(
|
||||||
@@ -105,7 +108,7 @@ class CloudFileList extends HookConsumerWidget {
|
|||||||
child: ListView.separated(
|
child: ListView.separated(
|
||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
itemCount: files.length,
|
itemCount: files.length,
|
||||||
padding: EdgeInsets.symmetric(horizontal: 3),
|
padding: padding,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
return AspectRatio(
|
return AspectRatio(
|
||||||
aspectRatio:
|
aspectRatio:
|
||||||
@@ -133,6 +136,7 @@ class CloudFileList extends HookConsumerWidget {
|
|||||||
item: files[index],
|
item: files[index],
|
||||||
heroTag: heroTags[index],
|
heroTag: heroTags[index],
|
||||||
),
|
),
|
||||||
|
rootNavigator: true,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -184,7 +188,7 @@ class CloudFileZoomIn extends HookConsumerWidget {
|
|||||||
final filePath = '${tempDir.path}/${item.id}.${extension(item.name)}';
|
final filePath = '${tempDir.path}/${item.id}.${extension(item.name)}';
|
||||||
|
|
||||||
await client.download(
|
await client.download(
|
||||||
'/files/${item.id}',
|
'/drive/files/${item.id}',
|
||||||
filePath,
|
filePath,
|
||||||
queryParameters: {'original': true},
|
queryParameters: {'original': true},
|
||||||
);
|
);
|
||||||
@@ -334,7 +338,6 @@ class CloudFileZoomIn extends HookConsumerWidget {
|
|||||||
imageProvider: CloudImageWidget.provider(
|
imageProvider: CloudImageWidget.provider(
|
||||||
fileId: item.id,
|
fileId: item.id,
|
||||||
serverUrl: serverUrl,
|
serverUrl: serverUrl,
|
||||||
original: true,
|
|
||||||
),
|
),
|
||||||
// Apply rotation transformation
|
// Apply rotation transformation
|
||||||
customSize: MediaQuery.of(context).size,
|
customSize: MediaQuery.of(context).size,
|
||||||
@@ -475,7 +478,6 @@ class _CloudFileListEntry extends StatelessWidget {
|
|||||||
final bool isImage;
|
final bool isImage;
|
||||||
final bool disableZoomIn;
|
final bool disableZoomIn;
|
||||||
final VoidCallback? onTap;
|
final VoidCallback? onTap;
|
||||||
final BoxFit fit;
|
|
||||||
|
|
||||||
const _CloudFileListEntry({
|
const _CloudFileListEntry({
|
||||||
required this.file,
|
required this.file,
|
||||||
@@ -483,7 +485,6 @@ class _CloudFileListEntry extends StatelessWidget {
|
|||||||
required this.isImage,
|
required this.isImage,
|
||||||
required this.disableZoomIn,
|
required this.disableZoomIn,
|
||||||
this.onTap,
|
this.onTap,
|
||||||
this.fit = BoxFit.contain,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -506,10 +507,10 @@ class _CloudFileListEntry extends StatelessWidget {
|
|||||||
item: file,
|
item: file,
|
||||||
heroTag: heroTag,
|
heroTag: heroTag,
|
||||||
noBlurhash: true,
|
noBlurhash: true,
|
||||||
fit: fit,
|
fit: BoxFit.contain,
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
CloudFileWidget(item: file, heroTag: heroTag, fit: fit),
|
CloudFileWidget(item: file, heroTag: heroTag, fit: BoxFit.contain),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@@ -92,7 +92,10 @@ class CloudImageWidget extends ConsumerWidget {
|
|||||||
required String serverUrl,
|
required String serverUrl,
|
||||||
bool original = false,
|
bool original = false,
|
||||||
}) {
|
}) {
|
||||||
final uri = '$serverUrl/drive/files/$fileId?original=$original';
|
final uri =
|
||||||
|
original
|
||||||
|
? '$serverUrl/drive/files/$fileId?original=true'
|
||||||
|
: '$serverUrl/drive/files/$fileId';
|
||||||
return CachedNetworkImageProvider(uri);
|
return CachedNetworkImageProvider(uri);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:collection/collection.dart';
|
||||||
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:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
@@ -5,36 +6,79 @@ import 'package:flutter/services.dart';
|
|||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.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 'dart:math' as math;
|
|
||||||
import 'package:island/models/embed.dart';
|
|
||||||
import 'package:island/models/post.dart';
|
import 'package:island/models/post.dart';
|
||||||
import 'package:island/pods/config.dart';
|
|
||||||
import 'package:island/pods/network.dart';
|
import 'package:island/pods/network.dart';
|
||||||
import 'package:island/pods/userinfo.dart';
|
import 'package:island/pods/userinfo.dart';
|
||||||
import 'package:island/screens/posts/compose.dart';
|
|
||||||
import 'package:island/services/responsive.dart';
|
|
||||||
import 'package:island/services/time.dart';
|
import 'package:island/services/time.dart';
|
||||||
import 'package:island/widgets/account/account_name.dart';
|
import 'package:island/widgets/account/account_name.dart';
|
||||||
import 'package:island/widgets/alert.dart';
|
import 'package:island/widgets/alert.dart';
|
||||||
import 'package:island/widgets/app_scaffold.dart';
|
|
||||||
import 'package:island/widgets/content/cloud_file_collection.dart';
|
import 'package:island/widgets/content/cloud_file_collection.dart';
|
||||||
import 'package:island/widgets/content/cloud_files.dart';
|
import 'package:island/widgets/content/cloud_files.dart';
|
||||||
import 'package:island/widgets/content/embed/link.dart';
|
|
||||||
import 'package:island/widgets/content/markdown.dart';
|
import 'package:island/widgets/content/markdown.dart';
|
||||||
import 'package:island/widgets/safety/abuse_report_helper.dart';
|
|
||||||
import 'package:island/widgets/post/post_replies_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:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
import 'package:super_context_menu/super_context_menu.dart';
|
|
||||||
|
|
||||||
class PostItem extends HookConsumerWidget {
|
class PostActionableItem extends HookConsumerWidget {
|
||||||
final Color? backgroundColor;
|
final Color? backgroundColor;
|
||||||
final SnPost item;
|
final SnPost item;
|
||||||
final EdgeInsets? padding;
|
final EdgeInsets? padding;
|
||||||
final bool isOpenable;
|
|
||||||
final bool isFullPost;
|
final bool isFullPost;
|
||||||
final bool showReferencePost;
|
final bool isShowReference;
|
||||||
|
final double? borderRadius;
|
||||||
|
final Function? onRefresh;
|
||||||
|
final Function(SnPost)? onUpdate;
|
||||||
|
const PostActionableItem({
|
||||||
|
super.key,
|
||||||
|
required this.item,
|
||||||
|
this.backgroundColor,
|
||||||
|
this.padding,
|
||||||
|
this.isFullPost = false,
|
||||||
|
this.isShowReference = true,
|
||||||
|
this.borderRadius,
|
||||||
|
this.onRefresh,
|
||||||
|
this.onUpdate,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final user = ref.watch(userInfoProvider);
|
||||||
|
final isAuthor = useMemoized(
|
||||||
|
() => user.value != null && user.value?.id == item.publisher.accountId,
|
||||||
|
[user],
|
||||||
|
);
|
||||||
|
|
||||||
|
final widgetItem = InkWell(
|
||||||
|
borderRadius:
|
||||||
|
borderRadius != null
|
||||||
|
? BorderRadius.all(Radius.circular(borderRadius!))
|
||||||
|
: null,
|
||||||
|
child: PostItem(
|
||||||
|
key: key,
|
||||||
|
item: item,
|
||||||
|
backgroundColor: backgroundColor,
|
||||||
|
padding: padding,
|
||||||
|
isFullPost: isFullPost,
|
||||||
|
isShowReference: isShowReference,
|
||||||
|
isTextSelectable: false,
|
||||||
|
onRefresh: onRefresh,
|
||||||
|
onUpdate: onUpdate,
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
context.pushNamed('postDetail', pathParameters: {'id': item.id});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return widgetItem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PostItem extends HookConsumerWidget {
|
||||||
|
final SnPost item;
|
||||||
|
final Color? backgroundColor;
|
||||||
|
final EdgeInsets? padding;
|
||||||
|
final bool isFullPost;
|
||||||
|
final bool isShowReference;
|
||||||
|
final bool isTextSelectable;
|
||||||
final Function? onRefresh;
|
final Function? onRefresh;
|
||||||
final Function(SnPost)? onUpdate;
|
final Function(SnPost)? onUpdate;
|
||||||
const PostItem({
|
const PostItem({
|
||||||
@@ -42,9 +86,9 @@ class PostItem extends HookConsumerWidget {
|
|||||||
required this.item,
|
required this.item,
|
||||||
this.backgroundColor,
|
this.backgroundColor,
|
||||||
this.padding,
|
this.padding,
|
||||||
this.isOpenable = true,
|
|
||||||
this.isFullPost = false,
|
this.isFullPost = false,
|
||||||
this.showReferencePost = true,
|
this.isShowReference = true,
|
||||||
|
this.isTextSelectable = true,
|
||||||
this.onRefresh,
|
this.onRefresh,
|
||||||
this.onUpdate,
|
this.onUpdate,
|
||||||
});
|
});
|
||||||
@@ -52,559 +96,213 @@ class PostItem extends HookConsumerWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final renderingPadding =
|
final renderingPadding =
|
||||||
padding ?? EdgeInsets.symmetric(horizontal: 12, vertical: 16);
|
padding ?? EdgeInsets.symmetric(horizontal: 8, vertical: 8);
|
||||||
|
|
||||||
final user = ref.watch(userInfoProvider);
|
final reacting = useState(false);
|
||||||
final isAuthor = useMemoized(
|
|
||||||
() => user.value != null && user.value?.id == item.publisher.accountId,
|
|
||||||
[user],
|
|
||||||
);
|
|
||||||
|
|
||||||
final hasBackground =
|
Future<void> reactPost(String symbol, int attitude) async {
|
||||||
ref.watch(backgroundImageFileProvider).valueOrNull != null;
|
final client = ref.watch(apiClientProvider);
|
||||||
|
reacting.value = true;
|
||||||
|
await client
|
||||||
|
.post(
|
||||||
|
'/sphere/posts/${item.id}/reactions',
|
||||||
|
data: {'symbol': symbol, 'attitude': attitude},
|
||||||
|
)
|
||||||
|
.catchError((err) {
|
||||||
|
showErrorAlert(err);
|
||||||
|
return err;
|
||||||
|
})
|
||||||
|
.then((resp) {
|
||||||
|
final isRemoving = resp.statusCode == 204;
|
||||||
|
final delta = isRemoving ? -1 : 1;
|
||||||
|
final reactionsCount = Map<String, int>.from(item.reactionsCount);
|
||||||
|
reactionsCount[symbol] = (reactionsCount[symbol] ?? 0) + delta;
|
||||||
|
onUpdate?.call(item.copyWith(reactionsCount: reactionsCount));
|
||||||
|
HapticFeedback.heavyImpact();
|
||||||
|
});
|
||||||
|
reacting.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
Widget child;
|
final mostReaction =
|
||||||
if (item.type == 1 && isFullPost) {
|
item.reactionsCount.isEmpty
|
||||||
child = Padding(
|
? null
|
||||||
padding: renderingPadding,
|
: item.reactionsCount.entries
|
||||||
child: Column(
|
.sortedBy((e) => e.value)
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
.map((e) => e.key)
|
||||||
|
.first;
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Gap(renderingPadding.horizontal),
|
||||||
|
Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
spacing: 12,
|
||||||
children: [
|
children: [
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
|
child: ProfilePictureWidget(
|
||||||
|
file: item.publisher.picture,
|
||||||
|
radius: 16,
|
||||||
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
context.pushNamed(
|
context.pushNamed(
|
||||||
'publisherProfile',
|
'publisherProfile',
|
||||||
pathParameters: {'name': item.publisher.name},
|
pathParameters: {'name': item.publisher.name},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
child: Row(
|
),
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
ProfilePictureWidget(file: item.publisher.picture),
|
Row(
|
||||||
const Gap(12),
|
spacing: 4,
|
||||||
Expanded(
|
children: [
|
||||||
child: Column(
|
Text(item.publisher.nick).bold(),
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
if (item.publisher.verification != null)
|
||||||
children: [
|
VerificationMark(mark: item.publisher.verification!),
|
||||||
Text(item.publisher.nick).bold(),
|
Text('@${item.publisher.name}').fontSize(11),
|
||||||
if (item.publisher.verification != null)
|
],
|
||||||
VerificationMark(
|
|
||||||
mark: item.publisher.verification!,
|
|
||||||
).padding(left: 4),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
isFullPost
|
isFullPost
|
||||||
? item.publishedAt?.formatSystem() ?? ''
|
? (item.publishedAt ?? item.createdAt)!.formatSystem()
|
||||||
: item.publishedAt?.formatRelative(context) ?? '',
|
: (item.publishedAt ?? item.createdAt)!.formatRelative(
|
||||||
).fontSize(11),
|
context,
|
||||||
|
),
|
||||||
|
).fontSize(10),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (item.visibility != 0)
|
IconButton(
|
||||||
Row(
|
icon:
|
||||||
mainAxisSize: MainAxisSize.min,
|
mostReaction == null
|
||||||
children: [
|
? const Icon(Symbols.add_reaction)
|
||||||
Icon(
|
: Badge(
|
||||||
_getVisibilityIcon(item.visibility),
|
label: Text(
|
||||||
size: 14,
|
'x${item.reactionsCount[mostReaction]}',
|
||||||
color: Theme.of(context).colorScheme.secondary,
|
style: TextStyle(fontSize: 11),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 4),
|
offset: Offset(4, 20),
|
||||||
Text(
|
backgroundColor: Theme.of(
|
||||||
_getVisibilityText(item.visibility).tr(),
|
context,
|
||||||
style: TextStyle(
|
).colorScheme.primary.withOpacity(0.75),
|
||||||
fontSize: 12,
|
textColor: Theme.of(context).colorScheme.onPrimary,
|
||||||
color: Theme.of(context).colorScheme.secondary,
|
child: Text(
|
||||||
),
|
kReactionTemplates[mostReaction]!.icon,
|
||||||
),
|
style: TextStyle(fontSize: 20),
|
||||||
],
|
),
|
||||||
).padding(top: 10, bottom: 2),
|
|
||||||
const Gap(16),
|
|
||||||
_ArticlePostDisplay(item: item, isFullPost: isFullPost),
|
|
||||||
if (item.tags.isNotEmpty || item.categories.isNotEmpty)
|
|
||||||
Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
if (item.tags.isNotEmpty)
|
|
||||||
Wrap(
|
|
||||||
children: [
|
|
||||||
for (final tag in item.tags)
|
|
||||||
InkWell(
|
|
||||||
child: Row(
|
|
||||||
spacing: 4,
|
|
||||||
children: [
|
|
||||||
const Icon(Symbols.label, size: 13),
|
|
||||||
Text(tag.name ?? '#${tag.slug}').fontSize(13),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
onTap: () {},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
if (item.categories.isNotEmpty)
|
|
||||||
Wrap(
|
|
||||||
children: [
|
|
||||||
for (final category in item.categories)
|
|
||||||
InkWell(
|
|
||||||
child: Row(
|
|
||||||
spacing: 4,
|
|
||||||
children: [
|
|
||||||
const Icon(Symbols.category, size: 13),
|
|
||||||
Text(
|
|
||||||
category.name ?? '#${category.slug}',
|
|
||||||
).fontSize(13),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
onTap: () {},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
if ((item.repliedPost != null || item.forwardedPost != null) &&
|
|
||||||
showReferencePost)
|
|
||||||
_buildReferencePost(context, item),
|
|
||||||
if (item.attachments.isNotEmpty && item.type != 1)
|
|
||||||
CloudFileList(
|
|
||||||
disableConstraint: isFullPost,
|
|
||||||
files: item.attachments,
|
|
||||||
maxWidth: math.min(
|
|
||||||
MediaQuery.of(context).size.width,
|
|
||||||
kWideScreenWidth,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (item.meta?['embeds'] != null)
|
|
||||||
...((item.meta!['embeds'] as List<dynamic>)
|
|
||||||
.where((embed) => embed['Type'] == 'link')
|
|
||||||
.map(
|
|
||||||
(embedData) => EmbedLinkWidget(
|
|
||||||
link: SnEmbedLink.fromJson(
|
|
||||||
embedData as Map<String, dynamic>,
|
|
||||||
),
|
),
|
||||||
maxWidth: math.min(
|
onPressed: () {
|
||||||
MediaQuery.of(context).size.width,
|
showModalBottomSheet(
|
||||||
kWideScreenWidth,
|
context: context,
|
||||||
),
|
useRootNavigator: true,
|
||||||
margin: EdgeInsets.only(top: 8),
|
builder: (BuildContext context) {
|
||||||
),
|
return _PostReactionSheet(
|
||||||
)),
|
reactionsCount: item.reactionsCount,
|
||||||
const Gap(8),
|
onReact: (symbol, attitude) {
|
||||||
Row(
|
reactPost(symbol, attitude);
|
||||||
children: [
|
},
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.only(right: 12),
|
|
||||||
child: ActionChip(
|
|
||||||
avatar: Icon(Symbols.reply, size: 16),
|
|
||||||
label: Text(
|
|
||||||
(item.repliesCount > 0)
|
|
||||||
? 'repliesCount'.plural(item.repliesCount)
|
|
||||||
: 'reply'.tr(),
|
|
||||||
),
|
|
||||||
visualDensity: const VisualDensity(
|
|
||||||
horizontal: VisualDensity.minimumDensity,
|
|
||||||
vertical: VisualDensity.minimumDensity,
|
|
||||||
),
|
|
||||||
onPressed: () {
|
|
||||||
if (isOpenable) {
|
|
||||||
showModalBottomSheet(
|
|
||||||
context: context,
|
|
||||||
isScrollControlled: true,
|
|
||||||
useRootNavigator: true,
|
|
||||||
builder: (context) => PostRepliesSheet(post: item),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: PostReactionList(
|
|
||||||
parentId: item.id,
|
|
||||||
reactions: item.reactionsCount,
|
|
||||||
padding: EdgeInsets.zero,
|
|
||||||
onReact: (symbol, attitude, delta) {
|
|
||||||
final reactionsCount = Map<String, int>.from(
|
|
||||||
item.reactionsCount,
|
|
||||||
);
|
|
||||||
reactionsCount[symbol] =
|
|
||||||
(reactionsCount[symbol] ?? 0) + delta;
|
|
||||||
onUpdate?.call(
|
|
||||||
item.copyWith(reactionsCount: reactionsCount),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
child = Padding(
|
|
||||||
padding: renderingPadding,
|
|
||||||
child: Column(
|
|
||||||
spacing: 8,
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
spacing: 12,
|
|
||||||
children: [
|
|
||||||
GestureDetector(
|
|
||||||
child: ProfilePictureWidget(file: item.publisher.picture),
|
|
||||||
onTap: () {
|
|
||||||
context.pushNamed(
|
|
||||||
'publisherProfile',
|
|
||||||
pathParameters: {'name': item.publisher.name},
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
);
|
||||||
Expanded(
|
},
|
||||||
child: GestureDetector(
|
padding: EdgeInsets.zero,
|
||||||
child: Column(
|
visualDensity: VisualDensity(horizontal: -3, vertical: -3),
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Text(item.publisher.nick).bold(),
|
|
||||||
if (item.publisher.verification != null)
|
|
||||||
VerificationMark(
|
|
||||||
mark: item.publisher.verification!,
|
|
||||||
).padding(left: 4),
|
|
||||||
Spacer(),
|
|
||||||
Text(
|
|
||||||
isFullPost
|
|
||||||
? item.publishedAt?.formatSystem() ?? ''
|
|
||||||
: item.publishedAt?.formatRelative(context) ??
|
|
||||||
'',
|
|
||||||
).fontSize(11).alignment(Alignment.bottomRight),
|
|
||||||
const Gap(4),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
// Add visibility indicator if not public (visibility != 0)
|
|
||||||
if (item.visibility != 0)
|
|
||||||
Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Icon(
|
|
||||||
_getVisibilityIcon(item.visibility),
|
|
||||||
size: 14,
|
|
||||||
color: Theme.of(context).colorScheme.secondary,
|
|
||||||
),
|
|
||||||
const SizedBox(width: 4),
|
|
||||||
Text(
|
|
||||||
_getVisibilityText(item.visibility).tr(),
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 12,
|
|
||||||
color:
|
|
||||||
Theme.of(context).colorScheme.secondary,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
).padding(top: 2, bottom: 2),
|
|
||||||
if (item.type == 1)
|
|
||||||
_ArticlePostDisplay(
|
|
||||||
item: item,
|
|
||||||
isFullPost: isFullPost,
|
|
||||||
)
|
|
||||||
else ...[
|
|
||||||
if (item.title?.isNotEmpty ?? false)
|
|
||||||
Text(
|
|
||||||
item.title!,
|
|
||||||
style: Theme.of(context).textTheme.titleMedium
|
|
||||||
?.copyWith(fontWeight: FontWeight.bold),
|
|
||||||
),
|
|
||||||
if (item.description?.isNotEmpty ?? false)
|
|
||||||
Text(
|
|
||||||
item.description!,
|
|
||||||
style: Theme.of(
|
|
||||||
context,
|
|
||||||
).textTheme.bodyMedium?.copyWith(
|
|
||||||
color:
|
|
||||||
Theme.of(
|
|
||||||
context,
|
|
||||||
).colorScheme.onSurfaceVariant,
|
|
||||||
),
|
|
||||||
).padding(bottom: 8),
|
|
||||||
if (item.content?.isNotEmpty ?? false)
|
|
||||||
MarkdownTextContent(
|
|
||||||
content: item.content!,
|
|
||||||
linesMargin:
|
|
||||||
item.type == 0
|
|
||||||
? EdgeInsets.only(bottom: 8)
|
|
||||||
: null,
|
|
||||||
attachments: item.attachments,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
// Render tags and categories if they exist
|
|
||||||
if (item.tags.isNotEmpty || item.categories.isNotEmpty)
|
|
||||||
Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
if (item.tags.isNotEmpty)
|
|
||||||
Wrap(
|
|
||||||
children: [
|
|
||||||
for (final tag in item.tags)
|
|
||||||
InkWell(
|
|
||||||
child: Row(
|
|
||||||
spacing: 4,
|
|
||||||
children: [
|
|
||||||
const Icon(Symbols.label, size: 13),
|
|
||||||
Text(
|
|
||||||
tag.name ?? '#${tag.slug}',
|
|
||||||
).fontSize(13),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
onTap: () {},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
if (item.categories.isNotEmpty)
|
|
||||||
Wrap(
|
|
||||||
children: [
|
|
||||||
for (final category in item.categories)
|
|
||||||
InkWell(
|
|
||||||
child: Row(
|
|
||||||
spacing: 4,
|
|
||||||
children: [
|
|
||||||
const Icon(
|
|
||||||
Symbols.category,
|
|
||||||
size: 13,
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
category.name ??
|
|
||||||
'#${category.slug}',
|
|
||||||
).fontSize(13),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
onTap: () {},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
// Show truncation hint if post is truncated
|
|
||||||
if (item.isTruncated && !isFullPost && item.type != 1)
|
|
||||||
_PostTruncateHint().padding(
|
|
||||||
bottom:
|
|
||||||
(item.attachments.isNotEmpty ||
|
|
||||||
item.repliedPost != null ||
|
|
||||||
item.forwardedPost != null)
|
|
||||||
? 8
|
|
||||||
: null,
|
|
||||||
),
|
|
||||||
if ((item.repliedPost != null ||
|
|
||||||
item.forwardedPost != null) &&
|
|
||||||
showReferencePost)
|
|
||||||
_buildReferencePost(context, item),
|
|
||||||
if (item.attachments.isNotEmpty && item.type != 1)
|
|
||||||
CloudFileList(
|
|
||||||
files: item.attachments,
|
|
||||||
disableConstraint: isFullPost,
|
|
||||||
maxWidth: math.min(
|
|
||||||
MediaQuery.of(context).size.width * 0.85,
|
|
||||||
kWideScreenWidth - 160,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
// Render embed links
|
|
||||||
if (item.meta?['embeds'] != null)
|
|
||||||
...((item.meta!['embeds'] as List<dynamic>)
|
|
||||||
.where((embed) => embed['Type'] == 'link')
|
|
||||||
.map(
|
|
||||||
(embedData) => EmbedLinkWidget(
|
|
||||||
link: SnEmbedLink.fromJson(
|
|
||||||
embedData as Map<String, dynamic>,
|
|
||||||
),
|
|
||||||
maxWidth: math.min(
|
|
||||||
MediaQuery.of(context).size.width * 0.85,
|
|
||||||
kWideScreenWidth - 160,
|
|
||||||
),
|
|
||||||
margin: EdgeInsets.only(top: 8),
|
|
||||||
),
|
|
||||||
)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
onTap: () {
|
|
||||||
if (isOpenable) {
|
|
||||||
context.pushNamed(
|
|
||||||
'postDetail',
|
|
||||||
pathParameters: {'id': item.id},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
Row(
|
],
|
||||||
|
).padding(horizontal: renderingPadding.horizontal, bottom: 4),
|
||||||
|
if (!isFullPost && item.type == 1)
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(color: Theme.of(context).dividerColor),
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(16)),
|
||||||
|
),
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||||
|
margin: EdgeInsets.only(
|
||||||
|
left: renderingPadding.horizontal,
|
||||||
|
right: renderingPadding.horizontal,
|
||||||
|
top: 4,
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
// Replies count button
|
Align(
|
||||||
Padding(
|
alignment: Alignment.centerLeft,
|
||||||
padding: const EdgeInsets.only(left: 52, right: 12),
|
child: Badge(
|
||||||
child: ActionChip(
|
label: Text('postArticle').tr(),
|
||||||
avatar: Icon(Symbols.reply, size: 16),
|
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||||
label: Text(
|
textColor: Theme.of(context).colorScheme.onPrimary,
|
||||||
(item.repliesCount > 0)
|
|
||||||
? 'repliesCount'.plural(item.repliesCount)
|
|
||||||
: 'reply'.tr(),
|
|
||||||
),
|
|
||||||
visualDensity: const VisualDensity(
|
|
||||||
horizontal: VisualDensity.minimumDensity,
|
|
||||||
vertical: VisualDensity.minimumDensity,
|
|
||||||
),
|
|
||||||
onPressed: () {
|
|
||||||
if (isOpenable) {
|
|
||||||
showModalBottomSheet(
|
|
||||||
context: context,
|
|
||||||
isScrollControlled: true,
|
|
||||||
useRootNavigator: true,
|
|
||||||
builder: (context) => PostRepliesSheet(post: item),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
// Reactions list
|
const Gap(4),
|
||||||
Expanded(
|
if (item.title != null)
|
||||||
child: PostReactionList(
|
Text(
|
||||||
parentId: item.id,
|
item.title!,
|
||||||
reactions: item.reactionsCount,
|
style: Theme.of(context).textTheme.titleMedium!.copyWith(
|
||||||
padding: EdgeInsets.zero,
|
fontWeight: FontWeight.bold,
|
||||||
onReact: (symbol, attitude, delta) {
|
),
|
||||||
final reactionsCount = Map<String, int>.from(
|
|
||||||
item.reactionsCount,
|
|
||||||
);
|
|
||||||
reactionsCount[symbol] =
|
|
||||||
(reactionsCount[symbol] ?? 0) + delta;
|
|
||||||
onUpdate?.call(
|
|
||||||
item.copyWith(reactionsCount: reactionsCount),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
),
|
if (item.description != null)
|
||||||
|
Text(
|
||||||
|
item.description!,
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium,
|
||||||
|
)
|
||||||
|
else
|
||||||
|
MarkdownTextContent(content: '${item.content!}...'),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
)
|
||||||
),
|
else if (item.content?.isNotEmpty ?? false)
|
||||||
);
|
Padding(
|
||||||
}
|
padding: EdgeInsets.only(
|
||||||
|
left: renderingPadding.horizontal,
|
||||||
return ContextMenuWidget(
|
right: renderingPadding.horizontal,
|
||||||
menuProvider: (_) {
|
|
||||||
return Menu(
|
|
||||||
children: [
|
|
||||||
if (isAuthor)
|
|
||||||
MenuAction(
|
|
||||||
title: 'edit'.tr(),
|
|
||||||
image: MenuImage.icon(Symbols.edit),
|
|
||||||
callback: () {
|
|
||||||
context
|
|
||||||
.pushNamed('postEdit', pathParameters: {'id': item.id})
|
|
||||||
.then((value) {
|
|
||||||
if (value != null) {
|
|
||||||
onRefresh?.call();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
if (isAuthor)
|
|
||||||
MenuAction(
|
|
||||||
title: 'delete'.tr(),
|
|
||||||
image: MenuImage.icon(Symbols.delete),
|
|
||||||
callback: () {
|
|
||||||
showConfirmAlert(
|
|
||||||
'deletePostHint'.tr(),
|
|
||||||
'deletePost'.tr(),
|
|
||||||
).then((confirm) {
|
|
||||||
if (confirm) {
|
|
||||||
final client = ref.watch(apiClientProvider);
|
|
||||||
client
|
|
||||||
.delete('/sphere/posts/${item.id}')
|
|
||||||
.catchError((err) {
|
|
||||||
showErrorAlert(err);
|
|
||||||
return err;
|
|
||||||
})
|
|
||||||
.then((_) {
|
|
||||||
onRefresh?.call();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
if (isAuthor) MenuSeparator(),
|
|
||||||
MenuAction(
|
|
||||||
title: 'copyLink'.tr(),
|
|
||||||
image: MenuImage.icon(Symbols.link),
|
|
||||||
callback: () {
|
|
||||||
Clipboard.setData(
|
|
||||||
ClipboardData(text: 'https://solsynth.dev/posts/${item.id}'),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
MenuAction(
|
child: MarkdownTextContent(
|
||||||
title: 'reply'.tr(),
|
content: item.content!,
|
||||||
image: MenuImage.icon(Symbols.reply),
|
isSelectable: isTextSelectable,
|
||||||
callback: () {
|
|
||||||
context.pushNamed(
|
|
||||||
'postCompose',
|
|
||||||
extra: PostComposeInitialState(replyingTo: item),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
MenuAction(
|
),
|
||||||
title: 'forward'.tr(),
|
if (item.attachments.isNotEmpty)
|
||||||
image: MenuImage.icon(Symbols.forward),
|
CloudFileList(
|
||||||
callback: () {
|
files: item.attachments,
|
||||||
context.pushNamed(
|
padding: EdgeInsets.symmetric(
|
||||||
'postCompose',
|
horizontal: renderingPadding.horizontal,
|
||||||
extra: PostComposeInitialState(forwardingTo: item),
|
vertical: 4,
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
MenuSeparator(),
|
),
|
||||||
MenuAction(
|
if (isShowReference)
|
||||||
title: 'share'.tr(),
|
_buildReferencePost(context, item, renderingPadding),
|
||||||
image: MenuImage.icon(Symbols.share),
|
Gap(renderingPadding.vertical),
|
||||||
callback: () {
|
],
|
||||||
showShareSheetLink(
|
|
||||||
context: context,
|
|
||||||
link: '${ref.read(serverUrlProvider)}/posts/${item.id}',
|
|
||||||
title: 'sharePost'.tr(),
|
|
||||||
toSystem: true,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
MenuAction(
|
|
||||||
title: 'abuseReport'.tr(),
|
|
||||||
image: MenuImage.icon(Symbols.flag),
|
|
||||||
callback: () {
|
|
||||||
showAbuseReportSheet(
|
|
||||||
context,
|
|
||||||
resourceIdentifier: 'post/${item.id}',
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
child: Material(
|
|
||||||
color: hasBackground ? Colors.transparent : backgroundColor,
|
|
||||||
child: child,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildReferencePost(BuildContext context, SnPost item) {
|
Widget _buildReferencePost(
|
||||||
|
BuildContext context,
|
||||||
|
SnPost item,
|
||||||
|
EdgeInsets renderingPadding,
|
||||||
|
) {
|
||||||
final referencePost = item.repliedPost ?? item.forwardedPost;
|
final referencePost = item.repliedPost ?? item.forwardedPost;
|
||||||
if (referencePost == null) return const SizedBox.shrink();
|
if (referencePost == null) return const SizedBox.shrink();
|
||||||
|
|
||||||
final isReply = item.repliedPost != null;
|
final isReply = item.repliedPost != null;
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
margin: const EdgeInsets.only(bottom: 8),
|
padding: EdgeInsets.symmetric(
|
||||||
padding: const EdgeInsets.all(12),
|
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),
|
||||||
|
@@ -45,11 +45,13 @@ class PostItemCreator extends HookConsumerWidget {
|
|||||||
title: 'edit'.tr(),
|
title: 'edit'.tr(),
|
||||||
image: MenuImage.icon(Symbols.edit),
|
image: MenuImage.icon(Symbols.edit),
|
||||||
callback: () {
|
callback: () {
|
||||||
context.pushNamed('postEdit', pathParameters: {'id': item.id}).then((value) {
|
context
|
||||||
if (value != null) {
|
.pushNamed('postEdit', pathParameters: {'id': item.id})
|
||||||
onRefresh?.call();
|
.then((value) {
|
||||||
}
|
if (value != null) {
|
||||||
});
|
onRefresh?.call();
|
||||||
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
MenuAction(
|
MenuAction(
|
||||||
@@ -80,7 +82,10 @@ class PostItemCreator extends HookConsumerWidget {
|
|||||||
image: MenuImage.icon(Symbols.link),
|
image: MenuImage.icon(Symbols.link),
|
||||||
callback: () {
|
callback: () {
|
||||||
// Copy post link to clipboard
|
// Copy post link to clipboard
|
||||||
context.pushNamed('postDetail', pathParameters: {'id': item.id});
|
context.pushNamed(
|
||||||
|
'postDetail',
|
||||||
|
pathParameters: {'id': item.id},
|
||||||
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -198,7 +203,8 @@ class PostItemCreator extends HookConsumerWidget {
|
|||||||
files: item.attachments,
|
files: item.attachments,
|
||||||
maxWidth: MediaQuery.of(context).size.width * 0.85,
|
maxWidth: MediaQuery.of(context).size.width * 0.85,
|
||||||
minWidth: MediaQuery.of(context).size.width * 0.9,
|
minWidth: MediaQuery.of(context).size.width * 0.9,
|
||||||
).padding(top: 8),
|
padding: EdgeInsets.only(top: 8),
|
||||||
|
),
|
||||||
|
|
||||||
// Reference post indicator
|
// Reference post indicator
|
||||||
if (item.repliedPost != null || item.forwardedPost != null)
|
if (item.repliedPost != null || item.forwardedPost != null)
|
||||||
@@ -211,7 +217,7 @@ class PostItemCreator extends HookConsumerWidget {
|
|||||||
size: 16,
|
size: 16,
|
||||||
color: Theme.of(context).colorScheme.secondary,
|
color: Theme.of(context).colorScheme.secondary,
|
||||||
),
|
),
|
||||||
const SizedBox(width: 4),
|
const Gap(4),
|
||||||
Text(
|
Text(
|
||||||
item.repliedPost != null
|
item.repliedPost != null
|
||||||
? 'repliedTo'.tr()
|
? 'repliedTo'.tr()
|
||||||
|
@@ -99,7 +99,7 @@ class PostRepliesList extends HookConsumerWidget {
|
|||||||
item: data.items[index],
|
item: data.items[index],
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
backgroundColor ?? (isWide ? Colors.transparent : null),
|
backgroundColor ?? (isWide ? Colors.transparent : null),
|
||||||
showReferencePost: false,
|
isShowReference: false,
|
||||||
),
|
),
|
||||||
const Divider(height: 1),
|
const Divider(height: 1),
|
||||||
],
|
],
|
||||||
|
Reference in New Issue
Block a user