···
+
import 'package:cached_network_image/cached_network_image.dart';
+
import 'package:flutter/foundation.dart';
+
import 'package:flutter/material.dart';
+
import '../constants/app_colors.dart';
+
import '../models/post.dart';
+
import '../utils/date_time_utils.dart';
+
/// Post card widget for displaying feed posts
+
/// Displays a post with:
+
/// - Community and author information
+
/// - Post title and text content
+
/// - External embed (link preview with image)
+
/// - Action buttons (share, comment, like)
+
/// The [currentTime] parameter allows passing the current time for
+
/// time-ago calculations, enabling:
+
/// - Periodic updates of time strings
+
/// - Deterministic testing without DateTime.now()
+
class PostCard extends StatelessWidget {
+
const PostCard({required this.post, this.currentTime, super.key});
+
final FeedViewPost post;
+
final DateTime? currentTime;
+
Widget build(BuildContext context) {
+
margin: const EdgeInsets.only(bottom: 8),
+
decoration: const BoxDecoration(
+
color: AppColors.background,
+
border: Border(bottom: BorderSide(color: AppColors.border)),
+
padding: const EdgeInsets.fromLTRB(16, 4, 16, 1),
+
crossAxisAlignment: CrossAxisAlignment.start,
+
// Community and author info
+
// Community avatar placeholder
+
decoration: BoxDecoration(
+
color: AppColors.primary,
+
borderRadius: BorderRadius.circular(4),
+
post.post.community.name[0].toUpperCase(),
+
style: const TextStyle(
+
color: AppColors.textPrimary,
+
fontWeight: FontWeight.bold,
+
const SizedBox(width: 8),
+
crossAxisAlignment: CrossAxisAlignment.start,
+
'c/${post.post.community.name}',
+
style: const TextStyle(
+
color: AppColors.textPrimary,
+
fontWeight: FontWeight.bold,
+
'@${post.post.author.handle}',
+
style: const TextStyle(
+
color: AppColors.textSecondary,
+
DateTimeUtils.formatTimeAgo(
+
currentTime: currentTime,
+
color: AppColors.textPrimary.withValues(alpha: 0.5),
+
const SizedBox(height: 8),
+
if (post.post.title != null) ...[
+
style: const TextStyle(
+
color: AppColors.textPrimary,
+
fontWeight: FontWeight.w400,
+
// Spacing after title (only if we have content below)
+
if (post.post.title != null &&
+
(post.post.embed?.external != null ||
+
post.post.text.isNotEmpty))
+
const SizedBox(height: 8),
+
// Embed (link preview)
+
if (post.post.embed?.external != null) ...[
+
_EmbedCard(embed: post.post.embed!.external!),
+
const SizedBox(height: 8),
+
// Post text body preview
+
if (post.post.text.isNotEmpty) ...[
+
padding: const EdgeInsets.all(10),
+
decoration: BoxDecoration(
+
color: AppColors.backgroundSecondary,
+
borderRadius: BorderRadius.circular(8),
+
color: AppColors.textPrimary.withValues(alpha: 0.7),
+
overflow: TextOverflow.ellipsis,
+
// Reduced spacing before action buttons
+
const SizedBox(height: 4),
+
mainAxisAlignment: MainAxisAlignment.end,
+
// TODO: Handle share interaction with backend
+
debugPrint('Share button tapped for post');
+
// Increased padding for better touch targets
+
padding: const EdgeInsets.symmetric(
+
color: AppColors.textPrimary.withValues(alpha: 0.6),
+
const SizedBox(width: 8),
+
// TODO: Navigate to post detail/comments screen
+
debugPrint('Comment button tapped for post');
+
// Increased padding for better touch targets
+
padding: const EdgeInsets.symmetric(
+
mainAxisSize: MainAxisSize.min,
+
Icons.chat_bubble_outline,
+
color: AppColors.textPrimary.withValues(alpha: 0.6),
+
const SizedBox(width: 5),
+
DateTimeUtils.formatCount(
+
post.post.stats.commentCount,
+
color: AppColors.textPrimary.withValues(alpha: 0.6),
+
const SizedBox(width: 8),
+
// TODO: Handle upvote/like interaction with backend
+
debugPrint('Heart button tapped for post');
+
// Increased padding for better touch targets
+
padding: const EdgeInsets.symmetric(
+
mainAxisSize: MainAxisSize.min,
+
color: AppColors.textPrimary.withValues(alpha: 0.6),
+
const SizedBox(width: 5),
+
DateTimeUtils.formatCount(post.post.stats.score),
+
color: AppColors.textPrimary.withValues(alpha: 0.6),
+
/// Embed card widget for displaying link previews
+
/// Shows a thumbnail image for external embeds with loading and error states.
+
class _EmbedCard extends StatelessWidget {
+
const _EmbedCard({required this.embed});
+
final ExternalEmbed embed;
+
Widget build(BuildContext context) {
+
// Only show image if thumbnail exists
+
if (embed.thumb == null) {
+
return const SizedBox.shrink();
+
decoration: BoxDecoration(
+
borderRadius: BorderRadius.circular(8),
+
border: Border.all(color: AppColors.border),
+
clipBehavior: Clip.antiAlias,
+
child: CachedNetworkImage(
+
imageUrl: embed.thumb!,
+
width: double.infinity,
+
(context, url) => Container(
+
width: double.infinity,
+
color: AppColors.background,
+
child: CircularProgressIndicator(
+
color: AppColors.loadingIndicator,
+
errorWidget: (context, url, error) {
+
debugPrint('❌ Image load error: $error');
+
debugPrint('URL: $url');
+
width: double.infinity,
+
color: AppColors.background,
+
color: AppColors.loadingIndicator,