import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:result_monad/result_monad.dart'; import '../controls/image_control.dart'; import '../controls/padding.dart'; import '../controls/responsive_max_width.dart'; import '../controls/standard_appbar.dart'; import '../models/auth/profile.dart'; import '../models/direct_message_thread.dart'; import '../riverpod_controllers/account_services.dart'; import '../riverpod_controllers/direct_message_services.dart'; import '../utils/clipboard_utils.dart'; import '../utils/snackbar_builder.dart'; class MessageThreadScreen extends ConsumerStatefulWidget { final String parentThreadId; const MessageThreadScreen({ super.key, required this.parentThreadId, }); @override ConsumerState createState() => _MessageThreadScreenState(); } class _MessageThreadScreenState extends ConsumerState { final textController = TextEditingController(); @override Widget build(BuildContext context) { final profile = ref.watch(activeProfileProvider); final t = ref.watch( directMessageThreadServiceProvider(profile, widget.parentThreadId)); final title = t.title.isEmpty ? 'Thread' : t.title; return Scaffold( appBar: StandardAppBar.build(context, title), body: buildBody(profile, t), ); } Widget buildBody( Profile profile, DirectMessageThread thread, ) { final service = ref.read( directMessageThreadServiceProvider(profile, thread.parentUri).notifier); final yourId = profile.userId; final yourAvatarUrl = profile.avatar; final participants = Map.fromEntries(thread.participants.map((p) => MapEntry(p.id, p))); return Center( child: Column( children: [ Expanded( child: ResponsiveMaxWidth( child: ListView.separated( itemBuilder: (context, index) { final m = thread.messages[index]; final textPieces = m.text.split('...\n'); final text = textPieces.length == 1 ? textPieces[0] : textPieces[1]; final imageUrl = m.senderId == yourId ? yourAvatarUrl : participants[m.senderId]?.avatarUrl ?? ''; return ListTile( onTap: m.seen ? null : () => service.markMessageRead(m), onLongPress: () async { await copyToClipboard(context: context, text: m.text); }, leading: ImageControl( imageUrl: imageUrl, iconOverride: const Icon(Icons.person), width: 32.0, onTap: null, ), title: Text( text, style: m.seen ? null : const TextStyle(fontWeight: FontWeight.bold), ), subtitle: Text( DateTime.fromMillisecondsSinceEpoch(m.createdAt * 1000) .toString()), ); }, separatorBuilder: (_, __) => const Divider(), itemCount: thread.messages.length), ), ), const VerticalDivider(), Padding( padding: const EdgeInsets.all(8.0), child: ResponsiveMaxWidth( child: TextFormField( controller: textController, textCapitalization: TextCapitalization.sentences, spellCheckConfiguration: const SpellCheckConfiguration(), maxLines: 4, decoration: InputDecoration( labelText: 'Reply Text', border: OutlineInputBorder( borderSide: BorderSide( color: Theme.of(context).colorScheme.surface, ), borderRadius: BorderRadius.circular(5.0), ), ), ), ), ), ElevatedButton( onPressed: () async { if (textController.text.isEmpty) { buildSnackbar(context, "Can't submit an empty reply"); return; } final othersMessages = thread.messages.where((m) => m.senderId != yourId); if (othersMessages.isEmpty) { buildSnackbar( context, "Have to wait for a response before sending"); return; } await service .newReplyMessage( othersMessages.last, textController.text, ) .match(onSuccess: (_) { setState(() { textController.clear(); }); }, onError: (error) { if (mounted) { buildSnackbar(context, error.message); } }); }, child: const Text('Submit'), ), const VerticalPadding(), ], )); } }