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

140 lines
4 KiB
Dart

import 'package:flutter/material.dart';
import '../theme/app_theme.dart';
/// Custom card widget enforcing sojorn's visual system
class sojornCard extends StatelessWidget {
final Widget child;
final EdgeInsets? padding;
final EdgeInsets? margin;
final VoidCallback? onTap;
final Color? backgroundColor;
final bool showBorder;
// Removed final bool showShadow; to align with AppTheme.cardTheme
const sojornCard({
super.key,
required this.child,
this.padding,
this.margin,
this.onTap,
this.backgroundColor,
this.showBorder = true,
// Removed this.showShadow = false;
});
@override
Widget build(BuildContext context) {
final card = Container(
margin: margin ??
const EdgeInsets.symmetric(
horizontal: AppTheme.spacingMd,
vertical: AppTheme.spacingSm,
),
decoration: BoxDecoration(
color: backgroundColor ??
AppTheme.white, // Replaced AppTheme.surfaceElevated
borderRadius: BorderRadius.circular(12.0), // Replaced AppTheme.radiusLg
border: showBorder
? Border.all(
color: AppTheme.egyptianBlue,
width: 0.5) // Replaced AppTheme.border
: null,
// Removed boxShadow: showShadow ? AppTheme.shadowMd : null,
),
child: Padding(
padding: padding ?? const EdgeInsets.all(AppTheme.spacingLg),
child: child,
),
);
if (onTap != null) {
return Material(
color: Colors.transparent,
child: InkWell(
onTap: onTap,
borderRadius:
BorderRadius.circular(12.0), // Replaced AppTheme.radiusLg
child: card,
),
);
}
return card;
}
}
/// Section card with optional header
class sojornSectionCard extends StatelessWidget {
final String? title;
final String? subtitle;
final Widget? trailing;
final Widget child;
final EdgeInsets? padding;
final EdgeInsets? margin;
const sojornSectionCard({
super.key,
this.title,
this.subtitle,
this.trailing,
required this.child,
this.padding,
this.margin,
});
@override
Widget build(BuildContext context) {
return sojornCard(
padding: EdgeInsets.zero,
margin: margin,
// Removed showShadow property as it's no longer supported by sojornCard
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (title != null || subtitle != null || trailing != null) ...[
Padding(
padding: const EdgeInsets.all(AppTheme.spacingLg),
child: Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (title != null)
Text(
title!,
style: AppTheme.headlineSmall,
),
if (subtitle != null) ...[
const SizedBox(height: AppTheme.spacingXs),
Text(
subtitle!,
style: AppTheme.textTheme.bodyMedium?.copyWith(
// Replaced bodySmall
color: AppTheme.navyText
.withOpacity(0.8), // Replaced textSecondary
),
),
],
],
),
),
if (trailing != null) ...[
const SizedBox(width: AppTheme.spacingMd),
trailing!,
],
],
),
),
const Divider(height: 1),
],
Padding(
padding: padding ?? const EdgeInsets.all(AppTheme.spacingLg),
child: child,
),
],
),
);
}
}