sojorn/sojorn_app/lib/widgets/post_card.dart
Patrick Britton 3c4680bdd7 Initial commit: Complete threaded conversation system with inline replies
**Major Features Added:**
- **Inline Reply System**: Replace compose screen with inline reply boxes
- **Thread Navigation**: Parent/child navigation with jump functionality
- **Chain Flow UI**: Reply counts, expand/collapse animations, visual hierarchy
- **Enhanced Animations**: Smooth transitions, hover effects, micro-interactions

 **Frontend Changes:**
- **ThreadedCommentWidget**: Complete rewrite with animations and navigation
- **ThreadNode Model**: Added parent references and descendant counting
- **ThreadedConversationScreen**: Integrated navigation handlers
- **PostDetailScreen**: Replaced with threaded conversation view
- **ComposeScreen**: Added reply indicators and context
- **PostActions**: Fixed visibility checks for chain buttons

 **Backend Changes:**
- **API Route**: Added /posts/:id/thread endpoint
- **Post Repository**: Include allow_chain and visibility fields in feed
- **Thread Handler**: Support for fetching post chains

 **UI/UX Improvements:**
- **Reply Context**: Clear indication when replying to specific posts
- **Character Counting**: 500 character limit with live counter
- **Visual Hierarchy**: Depth-based indentation and styling
- **Smooth Animations**: SizeTransition, FadeTransition, hover states
- **Chain Navigation**: Parent/child buttons with visual feedback

 **Technical Enhancements:**
- **Animation Controllers**: Proper lifecycle management
- **State Management**: Clean separation of concerns
- **Navigation Callbacks**: Reusable navigation system
- **Error Handling**: Graceful fallbacks and user feedback

This creates a Reddit-style threaded conversation experience with smooth
animations, inline replies, and intuitive navigation between posts in a chain.
2026-01-30 07:40:19 -06:00

108 lines
2.8 KiB
Dart

import 'package:flutter/material.dart';
import '../models/post.dart';
import '../theme/app_theme.dart';
import 'chain_quote_widget.dart';
import 'post/post_actions.dart';
import 'post/post_body.dart';
import 'post/post_header.dart';
import 'post/post_media.dart';
enum PostCardVariant {
feed,
detail,
profile,
}
/// @deprecated
/// Use [UnifiedPostTile] from post_item.dart instead.
/// PostCard is deprecated in favor of the "Strict Flat" design system.
@Deprecated('Use UnifiedPostTile from post_item.dart instead. PostCard is deprecated.')
class PostCard extends StatelessWidget {
final Post post;
final PostCardVariant variant;
final VoidCallback? onTap;
final VoidCallback? onChain;
final VoidCallback? onChainParentTap;
final bool isAlternate;
final bool showChainContext;
const PostCard({
super.key,
required this.post,
this.variant = PostCardVariant.feed,
this.onTap,
this.onChain,
this.onChainParentTap,
this.isAlternate = false,
this.showChainContext = true,
});
@override
Widget build(BuildContext context) {
final padding = switch (variant) {
PostCardVariant.detail =>
const EdgeInsets.symmetric(vertical: AppTheme.spacingMd),
_ => EdgeInsets.zero,
};
const contentPadding = EdgeInsets.symmetric(
horizontal: AppTheme.spacingLg,
vertical: AppTheme.spacingSm,
);
Widget content = Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Chain Context (The Quote Box)
if (showChainContext && post.chainParent != null) ...[
ChainQuoteWidget(
parent: post.chainParent!,
onTap: onChainParentTap,
),
const SizedBox(height: AppTheme.spacingSm),
],
// Main Post Content
PostHeader(post: post),
const SizedBox(height: AppTheme.spacingMd),
PostBody(text: post.body, bodyFormat: post.bodyFormat),
PostMedia(post: post),
const SizedBox(height: AppTheme.spacingMd),
PostActions(
post: post,
onChain: onChain,
),
],
);
if (isAlternate) {
content = Container(
padding: contentPadding,
decoration: BoxDecoration(
color: AppTheme.white, // Replaced AppTheme.surface
borderRadius: BorderRadius.circular(8.0), // Replaced AppTheme.radiusMd
),
child: content,
);
} else {
content = Padding(
padding: contentPadding,
child: content,
);
}
return Material(
color: Colors.transparent,
child: InkWell(
onTap: onTap,
highlightColor: Colors.transparent,
splashColor: Colors.transparent,
child: Padding(
padding: padding,
child: content,
),
),
);
}
}