relatica/lib/screens/message_thread_screen.dart

163 wiersze
5.8 KiB
Dart

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<MessageThreadScreen> createState() => _MessageThreadScreenState();
}
class _MessageThreadScreenState extends State<MessageThreadScreen> {
final textController = TextEditingController();
@override
Widget build(BuildContext context) {
final service = context
.watch<ActiveProfileSelector<DirectMessageService>>()
.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<DirectMessageThread, ExecError> result,
DirectMessageService service,
) {
return result.fold(
onSuccess: (thread) {
final yourId = getIt<AccountsService>().currentProfile.userId;
final yourAvatarUrl = getIt<AccountsService>().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'),
),
);
}
}