kopia lustrzana https://gitlab.com/mysocialportal/relatica
195 wiersze
6.2 KiB
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()),
|
|
);
|
|
}
|
|
}
|