import 'package:flutter/material.dart'; import 'package:provider/provider.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 '../globals.dart'; import '../models/direct_message_thread.dart'; import '../models/exec_error.dart'; import '../services/auth_service.dart'; import '../services/direct_message_service.dart'; import '../utils/active_profile_selector.dart'; import '../utils/clipboard_utils.dart'; import '../utils/snackbar_builder.dart'; class MessageThreadScreen extends StatefulWidget { final String parentThreadId; const MessageThreadScreen({ super.key, required this.parentThreadId, }); @override State createState() => _MessageThreadScreenState(); } class _MessageThreadScreenState extends State { final textController = TextEditingController(); @override Widget build(BuildContext context) { final service = context .watch>() .activeEntry .value; final result = service.getThreadByParentUri(widget.parentThreadId); final title = result.fold( onSuccess: (t) => t.title.isEmpty ? 'Thread' : t.title, onError: (_) => 'Thread'); return Scaffold( appBar: StandardAppBar.build(context, title), body: buildBody(result, service), ); } Widget buildBody( Result result, DirectMessageService service, ) { return result.fold( onSuccess: (thread) { final yourId = getIt().currentProfile.userId; final yourAvatarUrl = getIt().currentProfile.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( widget.parentThreadId, 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, maxLines: 4, decoration: InputDecoration( labelText: 'Reply Text', border: OutlineInputBorder( borderSide: BorderSide( color: Theme.of(context).colorScheme.background, ), 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( thread.parentUri, othersMessages.last, textController.text, ) .match(onSuccess: (_) { setState(() { textController.clear(); }); }, onError: (error) { if (mounted) { buildSnackbar(context, error.message); } }); }, child: const Text('Submit'), ), const VerticalPadding(), ], )); }, onError: (error) => Center( child: Text('Error getting thread: $error'), ), ); } }