sojorn/sojorn_app/lib/widgets/post_item.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

137 lines
3.4 KiB
Dart

import 'package:flutter/material.dart';
import '../models/post.dart';
import '../theme/app_theme.dart';
import 'sojorn_post_card.dart';
import 'post/post_view_mode.dart';
/// UnifiedPostTile - Backward-compatible wrapper around sojornPostCard.
///
/// This class is now DEPRECATED in favor of sojornPostCard.
/// It exists only for backward compatibility with existing code.
///
/// ## New Usage
/// ```dart
/// sojornPostCard(
/// post: post,
/// mode: PostViewMode.feed,
/// onTap: () {},
/// )
/// ```
///
/// ## Migration Guide
/// Replace `UnifiedPostTile(...)` with `sojornPostCard(...)`
class UnifiedPostTile extends StatelessWidget {
final Post post;
final VoidCallback? onTap;
final VoidCallback? onChain;
final bool showDivider;
final bool isDetailView;
final bool showThreadSpine;
final double? avatarSize;
const UnifiedPostTile({
super.key,
required this.post,
this.onTap,
this.onChain,
this.showDivider = true,
this.isDetailView = false,
this.showThreadSpine = false,
this.avatarSize,
});
/// Convert legacy parameters to PostViewMode
PostViewMode get _viewMode {
if (isDetailView) return PostViewMode.detail;
if (avatarSize != null && avatarSize! < 32) return PostViewMode.compact;
return PostViewMode.feed;
}
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Thread spine line (if enabled in threaded view)
if (showThreadSpine) _buildThreadSpine(context),
// Backward compatibility: wrap in divider if needed
if (showDivider)
Column(
children: [
_buildPostContent(context),
_buildStrictDivider(),
],
)
else
_buildPostContent(context),
],
);
}
Widget _buildPostContent(BuildContext context) {
return sojornPostCard(
post: post,
mode: _viewMode,
onTap: onTap,
onChain: onChain,
);
}
/// STRICT SEPARATION: Full-width Divider at bottom of every post
Widget _buildStrictDivider() {
return Container(
height: 1.0,
width: double.infinity,
color: AppTheme.egyptianBlue.withOpacity(0.15),
margin: EdgeInsets.zero,
);
}
/// Thread spine: vertical line connecting parent to child in threaded view
Widget _buildThreadSpine(BuildContext context) {
return CustomPaint(
size: const Size(2, AppTheme.spacingMd),
painter: ThreadSpinePainter(
color: AppTheme.egyptianBlue.withOpacity(0.3),
width: 2,
),
);
}
}
/// Thread Spine Painter - draws vertical line for threaded views
class ThreadSpinePainter extends CustomPainter {
final Color color;
final double width;
ThreadSpinePainter({required this.color, required this.width});
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = color
..strokeWidth = width
..style = PaintingStyle.fill;
// Draw vertical line from center-top to center-bottom
canvas.drawRect(
Rect.fromLTWH(
size.width / 2 - width / 2,
0,
width,
size.height,
),
paint,
);
}
@override
bool shouldRepaint(ThreadSpinePainter oldDelegate) {
return oldDelegate.color != color || oldDelegate.width != width;
}
}
/// Backward-compatible alias for UnifiedPostTile
typedef PostItem = UnifiedPostTile;