relatica/lib/screens/message_thread_screen.dart

152 wiersze
5.2 KiB
Dart
Czysty Zwykły widok Historia

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
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';
import '../controls/responsive_max_width.dart';
import '../controls/standard_appbar.dart';
import '../globals.dart';
import '../models/auth/profile.dart';
import '../models/direct_message_thread.dart';
import '../riverpod_controllers/direct_message_services.dart';
import '../services/auth_service.dart';
import '../utils/clipboard_utils.dart';
2023-01-25 18:06:46 +00:00
import '../utils/snackbar_builder.dart';
class MessageThreadScreen extends ConsumerStatefulWidget {
final String parentThreadId;
const MessageThreadScreen({
super.key,
required this.parentThreadId,
});
2023-01-25 18:06:46 +00:00
@override
ConsumerState<MessageThreadScreen> createState() =>
_MessageThreadScreenState();
2023-01-25 18:06:46 +00:00
}
class _MessageThreadScreenState extends ConsumerState<MessageThreadScreen> {
2023-01-25 18:06:46 +00:00
final textController = TextEditingController();
@override
Widget build(BuildContext context) {
final profile = context.watch<AccountsService>().currentProfile;
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 = 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);
},
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,
2023-01-25 18:06:46 +00:00
),
borderRadius: BorderRadius.circular(5.0),
2023-01-25 18:06:46 +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(),
],
));
}
}