import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:intl/intl.dart'; import 'package:flutter_animate/flutter_animate.dart'; import '../../../providers/ideas_provider.dart'; import '../../../providers/auth_provider.dart'; import '../../../models/idea.dart'; import '../../../models/collaboration.dart'; import '../../../widgets/vote_buttons.dart'; import '../../../widgets/comment_thread.dart'; import '../../../widgets/funnel_status_badge.dart'; import '../../../widgets/loading_indicator.dart'; import '../../../widgets/error_display.dart'; import '../../../core/theme/app_theme.dart'; import '../../../core/constants/app_constants.dart'; import '../../../core/api/endpoints.dart'; import '../../../core/api/client.dart'; import '../../../providers/disk_provider.dart'; class IdeaViewScreen extends ConsumerStatefulWidget { final String id; const IdeaViewScreen({super.key, required this.id}); @override ConsumerState createState() => _IdeaViewScreenState(); } class _IdeaViewScreenState extends ConsumerState { final _commentController = TextEditingController(); @override void initState() { super.initState(); Future.microtask(() { ref.read(ideasProvider.notifier).loadIdea(widget.id); ref.read(ideaCommentsProvider(widget.id).notifier).loadComments(); ref.read(ideaActivityProvider(widget.id).notifier).loadActivities(); }); } @override void dispose() { _commentController.dispose(); super.dispose(); } void _showPresentationOptions(BuildContext context) { final diskState = ref.read(diskProvider); final hasDrive = diskState.connectedProviders.isNotEmpty; showModalBottomSheet( context: context, builder: (ctx) => SafeArea( child: Padding( padding: const EdgeInsets.all(16), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Text('Создать презентацию', style: Theme.of(context).textTheme.titleMedium?.copyWith( fontWeight: FontWeight.w600, )), const SizedBox(height: 16), ListTile( leading: const Icon(Icons.download, color: VoIdeaColors.primary), title: const Text('Скачать HTML'), subtitle: const Text('Откроется в браузере'), onTap: () { Navigator.pop(ctx); _generateAndDownload(); }, ), if (hasDrive) ...diskState.connectedProviders.map((p) { final provider = p['provider'] as String? ?? ''; return ListTile( leading: Icon(_providerIcon(provider), color: VoIdeaColors.success), title: Text('Отправить на ${_providerLabel(provider)}'), subtitle: Text(p['email'] ?? ''), onTap: () { Navigator.pop(ctx); _generateAndUpload(provider); }, ); }), if (!hasDrive) const Padding( padding: EdgeInsets.all(12), child: Text( 'Подключите облачный диск в настройках, чтобы сохранять презентации в облако', style: TextStyle(fontSize: 12, color: VoIdeaColors.muted), textAlign: TextAlign.center, ), ), ], ), ), ), ); } IconData _providerIcon(String provider) { switch (provider) { case 'google': return Icons.g_mobiledata; case 'yandex': return Icons.cloud; case 'apple': return Icons.apple; default: return Icons.cloud_outlined; } } String _providerLabel(String provider) { switch (provider) { case 'google': return 'Google Диск'; case 'yandex': return 'Яндекс Диск'; case 'apple': return 'iCloud'; default: return provider; } } Future _generateAndDownload() async { try { final api = ref.read(apiClientProvider); final resp = await api.post( '${Endpoints.ideaPresentation(widget.id)}?destination=download', ); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Презентация скачана')), ); } } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Ошибка: $e')), ); } } } Future _generateAndUpload(String provider) async { try { final api = ref.read(apiClientProvider); final resp = await api.post( '${Endpoints.ideaPresentation(widget.id)}?destination=drive&drive_provider=$provider', ); if (mounted) { final data = resp.data as Map; ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Презентация отправлена на ${_providerLabel(provider)}'), ), ); } } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Ошибка загрузки: $e')), ); } } } @override Widget build(BuildContext context) { final ideaState = ref.watch(ideasProvider); final commentsState = ref.watch(ideaCommentsProvider(widget.id)); final activityState = ref.watch(ideaActivityProvider(widget.id)); final authState = ref.watch(authProvider); final ideasNotifier = ref.read(ideasProvider.notifier); final commentsNotifier = ref.read(ideaCommentsProvider(widget.id).notifier); final currentUserId = authState.user?.id ?? ''; final idea = ideaState.selectedIdea; if (ideaState.isLoading && idea == null) { return Scaffold( appBar: AppBar(), body: const LoadingIndicator(), ); } if (idea == null) { return Scaffold( appBar: AppBar(), body: ErrorDisplay( message: ideaState.error ?? 'Идея не найдена', onRetry: () => ref.read(ideasProvider.notifier).loadIdea(widget.id), ), ); } final funnelIndex = AppConstants.funnelStates.indexOf(idea.funnelStatus ?? ''); final prevStatus = funnelIndex > 0 ? AppConstants.funnelStates[funnelIndex - 1] : null; final nextStatus = funnelIndex < AppConstants.funnelStates.length - 1 ? AppConstants.funnelStates[funnelIndex + 1] : null; return Scaffold( appBar: AppBar( title: const Text('Идея'), actions: [ if (idea.userId == currentUserId) IconButton( icon: const Icon(Icons.edit_outlined), onPressed: () => context.push('/ideas/${widget.id}/edit'), ), ], ), body: SingleChildScrollView( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Expanded( child: Text( idea.title, style: Theme.of(context).textTheme.headlineSmall?.copyWith( fontWeight: FontWeight.bold, ), ), ), if (idea.funnelStatus != null) FunnelStatusBadge(status: idea.funnelStatus), ], ).animate().fadeIn(), const SizedBox(height: 8), Row( children: [ Text( idea.userName, style: Theme.of(context).textTheme.bodySmall?.copyWith( color: VoIdeaColors.muted, ), ), const SizedBox(width: 16), Text( DateFormat('d MMM yyyy').format(idea.createdAt), style: Theme.of(context).textTheme.bodySmall?.copyWith( color: VoIdeaColors.muted, ), ), ], ).animate().fadeIn(delay: 100.ms), const SizedBox(height: 16), if (idea.description.isNotEmpty) Text( idea.description, style: Theme.of(context).textTheme.bodyLarge, ).animate().fadeIn(delay: 200.ms), const SizedBox(height: 24), Row( children: [ VoteButtons( upvotes: idea.upvotes, downvotes: idea.downvotes, onUpvote: () => commentsNotifier.toggleVote(widget.id), onDownvote: () => commentsNotifier.toggleVote(widget.id), ), const Spacer(), if (prevStatus != null) IconButton( icon: const Icon(Icons.arrow_back), onPressed: () => ideasNotifier.updateFunnel(widget.id, prevStatus), tooltip: 'Переместить в ${AppConstants.funnelLabels[prevStatus]}', ), if (nextStatus != null) IconButton( icon: const Icon(Icons.arrow_forward), onPressed: () => ideasNotifier.updateFunnel(widget.id, nextStatus), tooltip: 'Переместить в ${AppConstants.funnelLabels[nextStatus]}', ), ], ).animate().fadeIn(delay: 300.ms), const SizedBox(height: 16), Row( children: [ Expanded( child: OutlinedButton.icon( icon: const Icon(Icons.slideshow, size: 18), label: const Text('Презентация'), onPressed: () => _showPresentationOptions(context), ), ), ], ), const Divider(height: 32), Text( 'Комментарии', style: Theme.of(context).textTheme.titleMedium?.copyWith( fontWeight: FontWeight.w600, ), ).animate().fadeIn(delay: 400.ms), const SizedBox(height: 12), Row( children: [ Expanded( child: TextField( controller: _commentController, decoration: const InputDecoration( hintText: 'Напишите комментарий...', isDense: true, ), textCapitalization: TextCapitalization.sentences, ), ), const SizedBox(width: 8), IconButton( icon: const Icon(Icons.send), color: VoIdeaColors.primary, onPressed: () { if (_commentController.text.isNotEmpty) { commentsNotifier .addComment(_commentController.text.trim()) .then((_) => _commentController.clear()); } }, ), ], ), const SizedBox(height: 16), if (commentsState.isLoading) const LoadingIndicator() else CommentThread( comments: commentsState.comments, currentUserId: currentUserId, onAddComment: (body, {parentId}) => commentsNotifier.addComment(body, parentId: parentId), ).animate().fadeIn(delay: 500.ms), const Divider(height: 32), Text( 'Активность', style: Theme.of(context).textTheme.titleMedium?.copyWith( fontWeight: FontWeight.w600, ), ).animate().fadeIn(delay: 600.ms), const SizedBox(height: 12), if (activityState.isLoading) const LoadingIndicator() else ...activityState.activities.map( (a) => Padding( padding: const EdgeInsets.only(bottom: 8), child: Row( children: [ CircleAvatar( radius: 12, backgroundColor: VoIdeaColors.primary.withOpacity(0.1), child: Text( a.userName.isNotEmpty ? a.userName[0].toUpperCase() : '?', style: const TextStyle( fontSize: 10, color: VoIdeaColors.primary), ), ), const SizedBox(width: 8), Expanded( child: Text.rich( TextSpan( children: [ TextSpan( text: a.userName, style: const TextStyle( fontWeight: FontWeight.w500), ), TextSpan( text: ' ${a.action}', ), if (a.description != null) TextSpan( text: ': ${a.description}', style: TextStyle( color: VoIdeaColors.muted), ), ], ), style: const TextStyle(fontSize: 13), ), ), Text( DateFormat('HH:mm').format(a.createdAt), style: const TextStyle( fontSize: 11, color: VoIdeaColors.muted), ), ], ), ), ), ], ), ), ); } }