import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:timeago/timeago.dart' as timeago; import '../models/post.dart'; import '../models/thread_node.dart'; import '../services/api_service.dart'; import '../theme/app_theme.dart'; import '../widgets/threaded_comment_widget.dart'; /// Draggable bottom sheet for video comments (TikTok-style) class VideoCommentsSheet extends StatefulWidget { final String postId; final int initialCommentCount; final VoidCallback? onCommentPosted; const VideoCommentsSheet({ super.key, required this.postId, this.initialCommentCount = 0, this.onCommentPosted, }); @override State createState() => _VideoCommentsSheetState(); } class _VideoCommentsSheetState extends State with SingleTickerProviderStateMixin { late AnimationController _animationController; late Animation _animation; List _comments = []; ThreadNode? _threadTree; bool _isLoading = true; String? _error; final TextEditingController _commentController = TextEditingController(); bool _isPostingComment = false; @override void initState() { super.initState(); _animationController = AnimationController( vsync: this, duration: const Duration(milliseconds: 300), ); _animation = CurvedAnimation( parent: _animationController, curve: Curves.easeOutCubic, ); _loadComments(); } @override void dispose() { _animationController.dispose(); _commentController.dispose(); super.dispose(); } Future _loadComments() async { setState(() { _isLoading = true; _error = null; }); try { final comments = await ApiService.instance.getPostChain(widget.postId); if (mounted) { setState(() { _comments = comments; if (comments.isNotEmpty) { _threadTree = ThreadNode.buildTree(comments); } _isLoading = false; }); _animationController.forward(); } } catch (e) { if (mounted) { setState(() { _error = e.toString(); _isLoading = false; }); } } } @override Widget build(BuildContext context) { return DraggableScrollableSheet( initialChildSize: 0.5, // Start at 50% screen height minChildSize: 0.3, // Minimum 30% maxChildSize: 0.95, // Maximum 95% snap: true, snapSizes: const [0.5, 0.8, 0.95], builder: (context, scrollController) { return Container( decoration: BoxDecoration( color: AppTheme.scaffoldBg, borderRadius: const BorderRadius.vertical(top: Radius.circular(20)), boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.1), blurRadius: 10, offset: const Offset(0, -5), ), ], ), child: Column( children: [ // Drag handle _buildDragHandle(), // Header with comment count _buildHeader(), // Comments list or loading/error state Expanded( child: _buildCommentsList(scrollController), ), // Comment input _buildCommentInput(), ], ), ); }, ); } Widget _buildDragHandle() { return Container( margin: const EdgeInsets.symmetric(vertical: 12), width: 40, height: 4, decoration: BoxDecoration( color: AppTheme.textDisabled, borderRadius: BorderRadius.circular(2), ), ); } Widget _buildHeader() { return Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: Row( children: [ Text( 'Comments', style: GoogleFonts.literata( fontWeight: FontWeight.w600, color: AppTheme.navyBlue, fontSize: 18, ), ), const SizedBox(width: 8), AnimatedBuilder( animation: _animation, builder: (context, child) { return AnimatedContainer( duration: const Duration(milliseconds: 300), padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( color: AppTheme.brightNavy.withValues(alpha: 0.1 * _animation.value), borderRadius: BorderRadius.circular(12), ), child: Text( '${(_threadTree?.totalCount ?? widget.initialCommentCount)}', style: GoogleFonts.inter( color: AppTheme.brightNavy, fontSize: 12, fontWeight: FontWeight.w600, ), ), ); }, ), const Spacer(), IconButton( onPressed: _loadComments, icon: Icon(Icons.refresh, color: AppTheme.navyBlue), tooltip: 'Refresh comments', ), ], ), ); } Widget _buildCommentsList(ScrollController scrollController) { if (_isLoading) { return Center( child: CircularProgressIndicator( valueColor: AlwaysStoppedAnimation(AppTheme.brightNavy), ), ); } if (_error != null) { return Center( child: Padding( padding: const EdgeInsets.all(24), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.error_outline, size: 48, color: Colors.red[400], ), const SizedBox(height: 16), Text( 'Failed to load comments', style: GoogleFonts.literata( fontSize: 16, fontWeight: FontWeight.w600, color: AppTheme.navyBlue, ), ), const SizedBox(height: 8), Text( _error!, textAlign: TextAlign.center, style: GoogleFonts.inter( fontSize: 14, color: AppTheme.textSecondary, ), ), const SizedBox(height: 16), ElevatedButton( onPressed: _loadComments, style: ElevatedButton.styleFrom( backgroundColor: AppTheme.brightNavy, foregroundColor: Colors.white, ), child: Text('Try Again'), ), ], ), ), ); } if (_threadTree == null) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.chat_bubble_outline, size: 48, color: AppTheme.textDisabled, ), const SizedBox(height: 16), Text( 'No comments yet', style: GoogleFonts.literata( fontSize: 16, fontWeight: FontWeight.w600, color: AppTheme.navyBlue, ), ), const SizedBox(height: 8), Text( 'Be the first to comment!', style: GoogleFonts.inter( fontSize: 14, color: AppTheme.textSecondary, ), ), ], ), ); } return FadeTransition( opacity: _animation, child: RefreshIndicator( onRefresh: _loadComments, color: AppTheme.brightNavy, child: ListView.builder( controller: scrollController, padding: const EdgeInsets.symmetric(horizontal: 16), itemCount: 1, // We render the entire tree in one widget itemBuilder: (context, index) { return ThreadedCommentWidget( node: _threadTree!, onReply: _handleReply, onLike: _handleLike, isRootPost: true, ); }, ), ), ); } Widget _buildCommentInput() { return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: AppTheme.cardSurface, border: Border( top: BorderSide( color: AppTheme.navyBlue.withValues(alpha: 0.1), width: 1, ), ), ), child: SafeArea( child: Row( children: [ Expanded( child: TextField( controller: _commentController, maxLines: 1, decoration: InputDecoration( hintText: 'Add a comment...', border: OutlineInputBorder( borderRadius: BorderRadius.circular(25), borderSide: BorderSide.none, ), filled: true, fillColor: AppTheme.navyBlue.withValues(alpha: 0.05), contentPadding: const EdgeInsets.symmetric( horizontal: 16, vertical: 8, ), ), ), ), const SizedBox(width: 8), AnimatedContainer( duration: const Duration(milliseconds: 200), child: FloatingActionButton( onPressed: _isPostingComment ? null : _postComment, backgroundColor: _commentController.text.isNotEmpty ? AppTheme.brightNavy : AppTheme.textDisabled, mini: true, child: _isPostingComment ? SizedBox( width: 16, height: 16, child: CircularProgressIndicator( strokeWidth: 2, valueColor: AlwaysStoppedAnimation(Colors.white), ), ) : Icon( Icons.send, size: 16, color: Colors.white, ), ), ), ], ), ), ); } Future _postComment() async { if (_commentController.text.trim().isEmpty) return; setState(() => _isPostingComment = true); try { // Post comment using Go API await ApiService.instance.publishComment( postId: widget.postId, body: _commentController.text.trim(), ); _commentController.clear(); // Refresh comments await _loadComments(); // Notify parent if callback provided widget.onCommentPosted?.call(); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Comment posted!'), backgroundColor: Colors.green, duration: const Duration(seconds: 2), ), ); } } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Failed to post comment: $e'), backgroundColor: Colors.red, ), ); } } finally { if (mounted) { setState(() => _isPostingComment = false); } } } void _handleReply() { // TODO: Implement reply functionality ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Reply functionality coming soon!'), duration: Duration(seconds: 2), ), ); } void _handleLike() { // TODO: Implement like functionality ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Like functionality coming soon!'), duration: Duration(seconds: 2), ), ); } }