relatica/lib/screens/message_thread_screen.dart

195 wiersze
6.2 KiB
Dart

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 '../controls/status_and_refresh_button.dart';
import '../models/auth/profile.dart';
import '../models/direct_message.dart';
import '../models/direct_message_thread.dart';
import '../riverpod_controllers/account_services.dart';
import '../riverpod_controllers/connection_manager_services.dart';
import '../riverpod_controllers/direct_message_services.dart';
import '../riverpod_controllers/networking/network_status_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<MessageThreadScreen> createState() =>
_MessageThreadScreenState();
}
class _MessageThreadScreenState extends ConsumerState<MessageThreadScreen> {
final textController = TextEditingController();
@override
void initState() {
super.initState();
ref
.read(directMessageThreadServiceProvider(
ref.read(activeProfileProvider),
widget.parentThreadId,
).notifier)
.refresh();
}
@override
Widget build(BuildContext context) {
final profile = ref.watch(activeProfileProvider);
final loading = ref.watch(directMessageLoadingProvider(profile));
final thread = ref.watch(
directMessageThreadServiceProvider(profile, widget.parentThreadId));
final title = thread.title.isEmpty ? 'Thread' : thread.title;
return Scaffold(
appBar: StandardAppBar.build(context, title, actions: [
StatusAndRefreshButton(
executing: loading,
refreshFunction: () async => await ref
.read(directMessageThreadServiceProvider(
profile,
widget.parentThreadId,
).notifier)
.refresh(),
busyColor: Theme.of(context).colorScheme.surface,
),
]),
body: buildBody(profile, thread, loading),
);
}
Widget buildBody(
Profile profile,
DirectMessageThread thread,
bool loading,
) {
return Center(
child: Column(
children: [
if (loading) const LinearProgressIndicator(),
Expanded(
child: ResponsiveMaxWidth(
child: ListView.separated(
itemBuilder: (context, index) {
final m = thread.messages[index];
return _DirectMessageListItem(
m, profile, widget.parentThreadId);
},
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',
alignLabelWithHint: true,
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 != profile.userId);
if (othersMessages.isEmpty) {
buildSnackbar(
context, "Have to wait for a response before sending");
return;
}
await ref
.read(directMessageThreadServiceProvider(
profile, thread.parentUri)
.notifier)
.newReplyMessage(
othersMessages.last,
textController.text,
)
.match(onSuccess: (_) {
setState(() {
textController.clear();
});
}, onError: (error) {
if (mounted) {
buildSnackbar(context, error.message);
}
});
},
child: const Text('Submit'),
),
const VerticalPadding(),
],
));
}
}
class _DirectMessageListItem extends ConsumerWidget {
final DirectMessage message;
final Profile profile;
final String threadId;
const _DirectMessageListItem(this.message, this.profile, this.threadId);
@override
Widget build(BuildContext context, WidgetRef ref) {
final senderAvatar = ref
.read(connectionByIdProvider(profile, message.senderId))
.fold(onSuccess: (c) => c.avatarUrl, onError: (_) => '');
final m = message;
final textPieces = m.text.split('...\n');
final text = textPieces.length == 1 ? textPieces[0] : textPieces[1];
final imageUrl =
m.senderId == profile.userId ? profile.avatar : senderAvatar;
return ListTile(
onTap: m.seen
? null
: () => ref
.read(DirectMessageThreadServiceProvider(profile, threadId)
.notifier)
.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()),
);
}
}