2023-01-25 01:53:55 +00:00
|
|
|
import 'package:flutter/material.dart';
|
2024-10-02 17:17:48 +00:00
|
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
2023-01-25 01:53:55 +00:00
|
|
|
import 'package:provider/provider.dart';
|
|
|
|
import 'package:result_monad/result_monad.dart';
|
|
|
|
|
|
|
|
import '../controls/image_control.dart';
|
2023-01-25 18:06:46 +00:00
|
|
|
import '../controls/padding.dart';
|
2023-04-13 14:30:09 +00:00
|
|
|
import '../controls/responsive_max_width.dart';
|
2023-01-25 01:53:55 +00:00
|
|
|
import '../controls/standard_appbar.dart';
|
|
|
|
import '../globals.dart';
|
2024-10-02 17:17:48 +00:00
|
|
|
import '../models/auth/profile.dart';
|
2023-01-25 01:53:55 +00:00
|
|
|
import '../models/direct_message_thread.dart';
|
2024-10-02 17:17:48 +00:00
|
|
|
import '../riverpod_controllers/direct_message_services.dart';
|
2023-01-25 01:53:55 +00:00
|
|
|
import '../services/auth_service.dart';
|
2023-03-14 03:47:40 +00:00
|
|
|
import '../utils/clipboard_utils.dart';
|
2023-01-25 18:06:46 +00:00
|
|
|
import '../utils/snackbar_builder.dart';
|
2023-01-25 01:53:55 +00:00
|
|
|
|
2024-10-02 17:17:48 +00:00
|
|
|
class MessageThreadScreen extends ConsumerStatefulWidget {
|
2023-01-25 01:53:55 +00:00
|
|
|
final String parentThreadId;
|
|
|
|
|
|
|
|
const MessageThreadScreen({
|
|
|
|
super.key,
|
|
|
|
required this.parentThreadId,
|
|
|
|
});
|
|
|
|
|
2023-01-25 18:06:46 +00:00
|
|
|
@override
|
2024-10-02 17:17:48 +00:00
|
|
|
ConsumerState<MessageThreadScreen> createState() =>
|
|
|
|
_MessageThreadScreenState();
|
2023-01-25 18:06:46 +00:00
|
|
|
}
|
|
|
|
|
2024-10-02 17:17:48 +00:00
|
|
|
class _MessageThreadScreenState extends ConsumerState<MessageThreadScreen> {
|
2023-01-25 18:06:46 +00:00
|
|
|
final textController = TextEditingController();
|
|
|
|
|
2023-01-25 01:53:55 +00:00
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
2024-10-02 17:17:48 +00:00
|
|
|
final profile = context.watch<AccountsService>().currentProfile;
|
|
|
|
final t = ref.watch(
|
|
|
|
directMessageThreadServiceProvider(profile, widget.parentThreadId));
|
|
|
|
final title = t.title.isEmpty ? 'Thread' : t.title;
|
2023-01-25 01:53:55 +00:00
|
|
|
return Scaffold(
|
|
|
|
appBar: StandardAppBar.build(context, title),
|
2024-10-02 17:17:48 +00:00
|
|
|
body: buildBody(profile, t),
|
2023-01-25 01:53:55 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2023-01-25 17:26:29 +00:00
|
|
|
Widget buildBody(
|
2024-10-02 17:17:48 +00:00
|
|
|
Profile profile,
|
|
|
|
DirectMessageThread thread,
|
2023-01-25 17:26:29 +00:00
|
|
|
) {
|
2024-10-02 17:17:48 +00:00
|
|
|
final service = ref.read(
|
|
|
|
directMessageThreadServiceProvider(profile, thread.parentUri).notifier);
|
|
|
|
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(m),
|
|
|
|
onLongPress: () async {
|
|
|
|
await copyToClipboard(context: context, text: m.text);
|
2023-04-13 14:30:09 +00:00
|
|
|
},
|
2024-10-02 17:17:48 +00:00
|
|
|
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),
|
2023-01-25 17:26:29 +00:00
|
|
|
),
|
2024-10-02 17:17:48 +00:00
|
|
|
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,
|
2023-01-25 18:06:46 +00:00
|
|
|
),
|
2024-10-02 17:17:48 +00:00
|
|
|
borderRadius: BorderRadius.circular(5.0),
|
2023-01-25 18:06:46 +00:00
|
|
|
),
|
|
|
|
),
|
|
|
|
),
|
2024-10-02 17:17:48 +00:00
|
|
|
),
|
|
|
|
),
|
|
|
|
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(),
|
|
|
|
],
|
|
|
|
));
|
2023-01-25 01:53:55 +00:00
|
|
|
}
|
|
|
|
}
|