Redesign threaded conversation layout with dashboard-style reply chains
- Add top padding to anchor zone for better spacing - Move reactions into stage actions to match main post width - Left-align interaction buttons (reply, like, save) - Create dashboard-style reply chains with: - Header showing reply count and 'Dashboard View' badge - Individual reply cards with menu buttons - 'View Thread' action buttons - Consistent width matching main post - Subtle shadows and borders for depth - Add staggered animations for reply items - Improve visual hierarchy and spacing throughout
This commit is contained in:
parent
904996a14e
commit
c281a224d3
|
|
@ -315,7 +315,7 @@ class _ThreadedConversationScreenState extends ConsumerState<ThreadedConversatio
|
|||
return SlideTransition(
|
||||
position: _slideAnimation,
|
||||
child: Container(
|
||||
margin: const EdgeInsets.fromLTRB(16, 0, 16, 0),
|
||||
margin: const EdgeInsets.fromLTRB(16, 12, 16, 0),
|
||||
child: GestureDetector(
|
||||
onTap: () => _navigateToPost(parentPost.id),
|
||||
child: Container(
|
||||
|
|
@ -546,7 +546,20 @@ class _ThreadedConversationScreenState extends ConsumerState<ThreadedConversatio
|
|||
Widget _buildStageActions(Post post) {
|
||||
final isLiked = _likedByPost[post.id] ?? (post.isLiked ?? false);
|
||||
final isSaved = _savedByPost[post.id] ?? (post.isSaved ?? false);
|
||||
return Row(
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Reactions section - full width like main post
|
||||
ReactionStrip(
|
||||
reactions: _reactionCountsFor(post),
|
||||
myReactions: _myReactionsFor(post),
|
||||
reactionUsers: _reactionUsersFor(post),
|
||||
onToggle: (emoji) => _toggleReaction(post.id, emoji),
|
||||
onAdd: () => _openReactionPicker(post.id),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
// Actions row - left aligned
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ElevatedButton.icon(
|
||||
|
|
@ -596,6 +609,8 @@ class _ThreadedConversationScreenState extends ConsumerState<ThreadedConversatio
|
|||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -657,54 +672,91 @@ class _ThreadedConversationScreenState extends ConsumerState<ThreadedConversatio
|
|||
child: FadeTransition(
|
||||
opacity: _fadeAnimation,
|
||||
child: Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
ReactionStrip(
|
||||
reactions: _reactionCountsFor(_focusContext!.targetPost),
|
||||
myReactions: _myReactionsFor(_focusContext!.targetPost),
|
||||
reactionUsers: _reactionUsersFor(_focusContext!.targetPost),
|
||||
onToggle: (emoji) =>
|
||||
_toggleReaction(_focusContext!.targetPost.id, emoji),
|
||||
onAdd: () =>
|
||||
_openReactionPicker(_focusContext!.targetPost.id),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(32),
|
||||
margin: const EdgeInsets.fromLTRB(16, 16, 16, 0),
|
||||
padding: const EdgeInsets.all(24),
|
||||
decoration: BoxDecoration(
|
||||
color: AppTheme.cardSurface,
|
||||
borderRadius: BorderRadius.circular(22),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(
|
||||
color: AppTheme.navyBlue.withValues(alpha: 0.12),
|
||||
width: 2,
|
||||
color: AppTheme.navyBlue.withValues(alpha: 0.1),
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
'No replies yet',
|
||||
style: GoogleFonts.inter(
|
||||
color: AppTheme.textSecondary,
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return SlideTransition(
|
||||
position: _slideAnimation,
|
||||
child: FadeTransition(
|
||||
opacity: _fadeAnimation,
|
||||
child: Container(
|
||||
margin: const EdgeInsets.fromLTRB(16, 16, 16, 0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Dashboard header
|
||||
Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: AppTheme.navyBlue.withValues(alpha: 0.05),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(
|
||||
color: AppTheme.navyBlue.withValues(alpha: 0.1),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.chat_bubble_outline,
|
||||
size: 48,
|
||||
color: AppTheme.textSecondary,
|
||||
color: AppTheme.brightNavy,
|
||||
size: 20,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
_focusContext!.targetPost.allowChain
|
||||
? 'Be the first to reply'
|
||||
: 'Replies are disabled',
|
||||
'${children.length} ${children.length == 1 ? 'Reply' : 'Replies'}',
|
||||
style: GoogleFonts.inter(
|
||||
color: AppTheme.textSecondary,
|
||||
color: AppTheme.navyBlue,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||||
decoration: BoxDecoration(
|
||||
color: AppTheme.brightNavy.withValues(alpha: 0.1),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: Text(
|
||||
'Dashboard View',
|
||||
style: GoogleFonts.inter(
|
||||
color: AppTheme.brightNavy,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
// Reply chains as dashboard items
|
||||
...children.asMap().entries.map((entry) {
|
||||
final index = entry.key;
|
||||
final post = entry.value;
|
||||
return _buildDashboardReplyItem(post, index);
|
||||
}).toList(),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
@ -712,44 +764,125 @@ class _ThreadedConversationScreenState extends ConsumerState<ThreadedConversatio
|
|||
);
|
||||
}
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
ReactionStrip(
|
||||
reactions: _reactionCountsFor(_focusContext!.targetPost),
|
||||
myReactions: _myReactionsFor(_focusContext!.targetPost),
|
||||
reactionUsers: _reactionUsersFor(_focusContext!.targetPost),
|
||||
onToggle: (emoji) =>
|
||||
_toggleReaction(_focusContext!.targetPost.id, emoji),
|
||||
onAdd: () =>
|
||||
_openReactionPicker(_focusContext!.targetPost.id),
|
||||
Widget _buildDashboardReplyItem(Post post, int index) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(bottom: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: AppTheme.cardSurface,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(
|
||||
color: AppTheme.navyBlue.withValues(alpha: 0.08),
|
||||
width: 1,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Wrap(
|
||||
spacing: 10,
|
||||
runSpacing: 10,
|
||||
children: children.asMap().entries.map((entry) {
|
||||
final index = entry.key;
|
||||
final post = entry.value;
|
||||
return InteractiveReplyBlock(
|
||||
key: ValueKey('reply_${post.id}'),
|
||||
post: post,
|
||||
compactPreview: true,
|
||||
isLikedOverride:
|
||||
_likedByPost[post.id] ?? (post.isLiked ?? false),
|
||||
onToggleLike: () => _toggleLike(post),
|
||||
onTap: () => _navigateToPost(post.id),
|
||||
)
|
||||
.animate(delay: (index * 90).ms)
|
||||
.fadeIn(duration: 300.ms, curve: Curves.easeOutCubic)
|
||||
.slideY(begin: 0.06, end: 0, curve: Curves.easeOutBack);
|
||||
}).toList(),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: AppTheme.navyBlue.withValues(alpha: 0.04),
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: () => _navigateToPost(post.id),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Reply header with menu button
|
||||
Row(
|
||||
children: [
|
||||
_buildCompactAvatar(post),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
post.author?.displayName ?? 'Anonymous',
|
||||
style: GoogleFonts.inter(
|
||||
color: AppTheme.navyBlue,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
timeago.format(post.createdAt),
|
||||
style: GoogleFonts.inter(
|
||||
color: AppTheme.textSecondary,
|
||||
fontSize: 11,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// Menu button
|
||||
Container(
|
||||
padding: const EdgeInsets.all(6),
|
||||
decoration: BoxDecoration(
|
||||
color: AppTheme.navyBlue.withValues(alpha: 0.05),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Icon(
|
||||
Icons.more_horiz,
|
||||
size: 16,
|
||||
color: AppTheme.textSecondary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
// Reply content
|
||||
Text(
|
||||
post.body,
|
||||
style: GoogleFonts.inter(
|
||||
color: AppTheme.navyText,
|
||||
fontSize: 14,
|
||||
height: 1.4,
|
||||
),
|
||||
maxLines: 3,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
// Reply actions
|
||||
Row(
|
||||
children: [
|
||||
ReactionStrip(
|
||||
reactions: _reactionCountsFor(post),
|
||||
myReactions: _myReactionsFor(post),
|
||||
reactionUsers: _reactionUsersFor(post),
|
||||
onToggle: (emoji) => _toggleReaction(post.id, emoji),
|
||||
onAdd: () => _openReactionPicker(post.id),
|
||||
),
|
||||
const Spacer(),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: AppTheme.brightNavy.withValues(alpha: 0.1),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Text(
|
||||
'View Thread',
|
||||
style: GoogleFonts.inter(
|
||||
color: AppTheme.brightNavy,
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
).animate(delay: (index * 100).ms)
|
||||
.fadeIn(duration: 300.ms, curve: Curves.easeOutCubic)
|
||||
.slideY(begin: 0.04, end: 0, curve: Curves.easeOutBack);
|
||||
}
|
||||
|
||||
void _toggleReaction(String postId, String emoji) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue