309 lines
		
	
	
		
			9.4 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
			
		
		
	
	
			309 lines
		
	
	
		
			9.4 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
import 'package:easy_localization/easy_localization.dart';
 | 
						|
import 'package:flutter/material.dart';
 | 
						|
import 'package:flutter/services.dart';
 | 
						|
import 'package:go_router/go_router.dart';
 | 
						|
import 'package:gap/gap.dart';
 | 
						|
import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
						|
import 'package:island/models/post.dart';
 | 
						|
import 'package:island/pods/network.dart';
 | 
						|
import 'package:island/services/time.dart';
 | 
						|
import 'package:island/widgets/alert.dart';
 | 
						|
import 'package:island/widgets/post/post_item.dart';
 | 
						|
import 'package:island/widgets/post/post_shared.dart';
 | 
						|
import 'package:material_symbols_icons/symbols.dart';
 | 
						|
import 'package:styled_widget/styled_widget.dart';
 | 
						|
import 'package:super_context_menu/super_context_menu.dart';
 | 
						|
 | 
						|
class PostItemCreator extends HookConsumerWidget {
 | 
						|
  final Color? backgroundColor;
 | 
						|
  final SnPost item;
 | 
						|
  final EdgeInsets? padding;
 | 
						|
  final bool isOpenable;
 | 
						|
  final Function? onRefresh;
 | 
						|
  final Function(SnPost)? onUpdate;
 | 
						|
 | 
						|
  const PostItemCreator({
 | 
						|
    super.key,
 | 
						|
    required this.item,
 | 
						|
    this.backgroundColor,
 | 
						|
    this.padding,
 | 
						|
    this.isOpenable = true,
 | 
						|
    this.onRefresh,
 | 
						|
    this.onUpdate,
 | 
						|
  });
 | 
						|
 | 
						|
  @override
 | 
						|
  Widget build(BuildContext context, WidgetRef ref) {
 | 
						|
    final renderingPadding =
 | 
						|
        padding ?? const EdgeInsets.symmetric(horizontal: 8, vertical: 8);
 | 
						|
 | 
						|
    return ContextMenuWidget(
 | 
						|
      menuProvider: (_) {
 | 
						|
        return Menu(
 | 
						|
          children: [
 | 
						|
            MenuAction(
 | 
						|
              title: 'edit'.tr(),
 | 
						|
              image: MenuImage.icon(Symbols.edit),
 | 
						|
              callback: () {
 | 
						|
                context
 | 
						|
                    .pushNamed('postEdit', pathParameters: {'id': item.id})
 | 
						|
                    .then((value) {
 | 
						|
                      if (value != null) {
 | 
						|
                        onRefresh?.call();
 | 
						|
                      }
 | 
						|
                    });
 | 
						|
              },
 | 
						|
            ),
 | 
						|
            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();
 | 
						|
                          });
 | 
						|
                    }
 | 
						|
                  },
 | 
						|
                );
 | 
						|
              },
 | 
						|
            ),
 | 
						|
            MenuSeparator(),
 | 
						|
            MenuAction(
 | 
						|
              title: 'copyLink'.tr(),
 | 
						|
              image: MenuImage.icon(Symbols.link),
 | 
						|
              callback: () {
 | 
						|
                Clipboard.setData(
 | 
						|
                  ClipboardData(text: 'https://solian.app/posts/${item.id}'),
 | 
						|
                );
 | 
						|
              },
 | 
						|
            ),
 | 
						|
          ],
 | 
						|
        );
 | 
						|
      },
 | 
						|
      child: Material(
 | 
						|
        color: backgroundColor ?? Theme.of(context).colorScheme.surface,
 | 
						|
        child: InkWell(
 | 
						|
          borderRadius: BorderRadius.circular(12),
 | 
						|
          onTap: () {
 | 
						|
            if (isOpenable) {
 | 
						|
              context.pushNamed('postDetail', pathParameters: {'id': item.id});
 | 
						|
            }
 | 
						|
          },
 | 
						|
          child: Column(
 | 
						|
            crossAxisAlignment: CrossAxisAlignment.start,
 | 
						|
            children: [
 | 
						|
              Gap(renderingPadding.vertical),
 | 
						|
              PostHeader(item: item, renderingPadding: renderingPadding),
 | 
						|
              PostBody(item: item, renderingPadding: renderingPadding),
 | 
						|
              ReferencedPostWidget(
 | 
						|
                item: item,
 | 
						|
                renderingPadding: renderingPadding,
 | 
						|
              ),
 | 
						|
              const Gap(16),
 | 
						|
              _buildAnalyticsSection(
 | 
						|
                context,
 | 
						|
              ).padding(horizontal: renderingPadding.horizontal),
 | 
						|
              Gap(renderingPadding.vertical),
 | 
						|
            ],
 | 
						|
          ),
 | 
						|
        ),
 | 
						|
      ),
 | 
						|
    );
 | 
						|
  }
 | 
						|
 | 
						|
  Widget _buildAnalyticsSection(BuildContext context) {
 | 
						|
    return Column(
 | 
						|
      crossAxisAlignment: CrossAxisAlignment.start,
 | 
						|
      children: [
 | 
						|
        Text('Analytics', style: Theme.of(context).textTheme.titleSmall),
 | 
						|
        const Gap(8),
 | 
						|
        Card(
 | 
						|
          elevation: 1,
 | 
						|
          margin: EdgeInsets.zero,
 | 
						|
          shape: RoundedRectangleBorder(
 | 
						|
            borderRadius: BorderRadius.circular(12),
 | 
						|
            side: BorderSide(
 | 
						|
              color: Theme.of(context).colorScheme.outline.withOpacity(0.3),
 | 
						|
            ),
 | 
						|
          ),
 | 
						|
          child: Padding(
 | 
						|
            padding: const EdgeInsets.all(16),
 | 
						|
            child: Row(
 | 
						|
              mainAxisAlignment: MainAxisAlignment.spaceAround,
 | 
						|
              children: [
 | 
						|
                _buildMetricItem(
 | 
						|
                  context,
 | 
						|
                  Symbols.visibility,
 | 
						|
                  'Views',
 | 
						|
                  '${item.viewsUnique} / ${item.viewsTotal}',
 | 
						|
                  'Unique / Total',
 | 
						|
                ),
 | 
						|
                _buildMetricItem(
 | 
						|
                  context,
 | 
						|
                  Symbols.thumb_up,
 | 
						|
                  'Upvotes',
 | 
						|
                  '${item.upvotes}',
 | 
						|
                  null,
 | 
						|
                ),
 | 
						|
                _buildMetricItem(
 | 
						|
                  context,
 | 
						|
                  Symbols.thumb_down,
 | 
						|
                  'Downvotes',
 | 
						|
                  '${item.downvotes}',
 | 
						|
                  null,
 | 
						|
                ),
 | 
						|
              ],
 | 
						|
            ),
 | 
						|
          ),
 | 
						|
        ),
 | 
						|
        const Gap(16),
 | 
						|
        if (item.reactionsCount.isNotEmpty) _buildReactionsSection(context),
 | 
						|
        if (item.meta != null && item.meta!.isNotEmpty)
 | 
						|
          _buildMetadataSection(context),
 | 
						|
        const Gap(16),
 | 
						|
        Row(
 | 
						|
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
 | 
						|
          children: [
 | 
						|
            Text(
 | 
						|
              'Created: ${item.createdAt?.formatSystem() ?? ''}',
 | 
						|
              style: TextStyle(
 | 
						|
                fontSize: 12,
 | 
						|
                color: Theme.of(context).colorScheme.secondary,
 | 
						|
              ),
 | 
						|
            ),
 | 
						|
            if (item.editedAt != null)
 | 
						|
              Text(
 | 
						|
                'Edited: ${item.editedAt!.formatSystem()}',
 | 
						|
                style: TextStyle(
 | 
						|
                  fontSize: 12,
 | 
						|
                  color: Theme.of(context).colorScheme.secondary,
 | 
						|
                ),
 | 
						|
              ),
 | 
						|
          ],
 | 
						|
        ),
 | 
						|
      ],
 | 
						|
    );
 | 
						|
  }
 | 
						|
 | 
						|
  Widget _buildMetricItem(
 | 
						|
    BuildContext context,
 | 
						|
    IconData icon,
 | 
						|
    String label,
 | 
						|
    String value,
 | 
						|
    String? subtitle,
 | 
						|
  ) {
 | 
						|
    return Column(
 | 
						|
      children: [
 | 
						|
        Icon(icon, size: 20, color: Theme.of(context).colorScheme.primary),
 | 
						|
        const Gap(4),
 | 
						|
        Text(
 | 
						|
          label,
 | 
						|
          style: TextStyle(
 | 
						|
            fontSize: 12,
 | 
						|
            color: Theme.of(context).colorScheme.secondary,
 | 
						|
          ),
 | 
						|
        ),
 | 
						|
        Text(
 | 
						|
          value,
 | 
						|
          style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
 | 
						|
        ),
 | 
						|
        if (subtitle != null)
 | 
						|
          Text(
 | 
						|
            subtitle,
 | 
						|
            style: TextStyle(
 | 
						|
              fontSize: 10,
 | 
						|
              color: Theme.of(context).colorScheme.secondary,
 | 
						|
            ),
 | 
						|
          ),
 | 
						|
      ],
 | 
						|
    );
 | 
						|
  }
 | 
						|
 | 
						|
  Widget _buildReactionsSection(BuildContext context) {
 | 
						|
    return Column(
 | 
						|
      crossAxisAlignment: CrossAxisAlignment.start,
 | 
						|
      children: [
 | 
						|
        Text(
 | 
						|
          'reactions'.plural(
 | 
						|
            item.reactionsCount.isNotEmpty
 | 
						|
                ? item.reactionsCount.values.reduce((a, b) => a + b)
 | 
						|
                : 0,
 | 
						|
          ),
 | 
						|
          style: TextStyle(
 | 
						|
            fontSize: 14,
 | 
						|
            fontWeight: FontWeight.w500,
 | 
						|
            color: Theme.of(context).colorScheme.secondary,
 | 
						|
          ),
 | 
						|
        ),
 | 
						|
        const Gap(8),
 | 
						|
        PostReactionList(
 | 
						|
          parentId: item.id,
 | 
						|
          reactions: item.reactionsCount,
 | 
						|
          reactionsMade: item.reactionsMade,
 | 
						|
          padding: EdgeInsets.zero,
 | 
						|
        ),
 | 
						|
        const Gap(16),
 | 
						|
      ],
 | 
						|
    );
 | 
						|
  }
 | 
						|
 | 
						|
  Widget _buildMetadataSection(BuildContext context) {
 | 
						|
    return Column(
 | 
						|
      crossAxisAlignment: CrossAxisAlignment.start,
 | 
						|
      children: [
 | 
						|
        const Gap(16),
 | 
						|
        Text('Metadata', style: Theme.of(context).textTheme.titleSmall),
 | 
						|
        const Gap(8),
 | 
						|
        Container(
 | 
						|
          padding: const EdgeInsets.all(12),
 | 
						|
          decoration: BoxDecoration(
 | 
						|
            color: Theme.of(
 | 
						|
              context,
 | 
						|
            ).colorScheme.surfaceVariant.withOpacity(0.5),
 | 
						|
            borderRadius: BorderRadius.circular(8),
 | 
						|
            border: Border.all(
 | 
						|
              color: Theme.of(context).colorScheme.outline.withOpacity(0.3),
 | 
						|
            ),
 | 
						|
          ),
 | 
						|
          child: Column(
 | 
						|
            crossAxisAlignment: CrossAxisAlignment.start,
 | 
						|
            children: [
 | 
						|
              for (final entry in item.meta!.entries)
 | 
						|
                Padding(
 | 
						|
                  padding: const EdgeInsets.only(bottom: 8),
 | 
						|
                  child: Row(
 | 
						|
                    crossAxisAlignment: CrossAxisAlignment.start,
 | 
						|
                    children: [
 | 
						|
                      Text(
 | 
						|
                        '${entry.key}: ',
 | 
						|
                        style: const TextStyle(
 | 
						|
                          fontWeight: FontWeight.bold,
 | 
						|
                          fontSize: 12,
 | 
						|
                        ),
 | 
						|
                      ),
 | 
						|
                      Expanded(
 | 
						|
                        child: Text(
 | 
						|
                          '${entry.value}',
 | 
						|
                          style: const TextStyle(fontSize: 12),
 | 
						|
                        ),
 | 
						|
                      ),
 | 
						|
                    ],
 | 
						|
                  ),
 | 
						|
                ),
 | 
						|
            ],
 | 
						|
          ),
 | 
						|
        ),
 | 
						|
      ],
 | 
						|
    );
 | 
						|
  }
 | 
						|
}
 |