Fix DM status update reflecting in notifications, etc.

merge-requests/67/merge
Hank Grabowski 2024-12-19 20:23:23 -05:00
rodzic 571f690320
commit 648d6e29dd
23 zmienionych plików z 406 dodań i 125 usunięć

Wyświetl plik

@ -101,14 +101,6 @@ class StandardAppDrawer extends ConsumerWidget {
'Settings',
() => context.pushNamed(ScreenPaths.settings),
),
// TODO Add back in clearing ability but has to do disk caches too
// buildMenuButton(context, 'Clear Caches', () async {
// final confirm = await showYesNoDialog(
// context, 'You want to clear all memory and disk cache data?');
// if (confirm == true) {
// clearCaches();
// }
// }),
],
),
),

Wyświetl plik

@ -1,15 +1,12 @@
import 'package:flutter/foundation.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../riverpod_controllers/connection_manager_services.dart';
import 'auth/profile.dart';
import 'connection.dart';
import 'direct_message.dart';
class DirectMessageThread {
final List<DirectMessage> messages;
final List<Connection> participants;
final List<String> participantIds;
final String title;
@ -17,14 +14,22 @@ class DirectMessageThread {
DirectMessageThread({
required this.messages,
required this.participants,
required this.participantIds,
required this.title,
required this.parentUri,
});
DirectMessageThread deepCopy() => DirectMessageThread(
messages: List.from(messages),
participants: List.from(participants),
participantIds: List.from(participantIds),
title: title,
parentUri: parentUri,
);
DirectMessageThread copy({List<DirectMessage>? messages}) =>
DirectMessageThread(
messages: messages ?? this.messages,
participantIds: participantIds,
title: title,
parentUri: parentUri,
);
@ -36,7 +41,6 @@ class DirectMessageThread {
.reduce((allUnseen, thisUnseen) => allUnseen && thisUnseen);
static List<DirectMessageThread> createThreads(
Ref ref,
Profile profile,
Iterable<DirectMessage> messages,
) {
@ -60,14 +64,9 @@ class DirectMessageThread {
title = m.title;
}
}
final participants = participantIds
.map((pid) => ref.read(connectionByIdProvider(profile, pid)))
.where((pr) => pr.isSuccess)
.map((pr) => pr.value)
.toList();
final thread = DirectMessageThread(
messages: threadMessages,
participants: participants,
participantIds: participantIds.toList(),
title: title,
parentUri: parentUri,
);
@ -88,12 +87,12 @@ class DirectMessageThread {
title == other.title &&
parentUri == other.parentUri &&
listEquals(messages, other.messages) &&
listEquals(participants, other.participants);
listEquals(participantIds, other.participantIds);
@override
int get hashCode =>
title.hashCode ^
parentUri.hashCode ^
Object.hashAll(messages) ^
Object.hashAll(participants);
Object.hashAll(participantIds);
}

Wyświetl plik

@ -69,7 +69,7 @@ final activeProfileProvider = NotifierProvider<ActiveProfile, Profile>.internal(
);
typedef _$ActiveProfile = Notifier<Profile>;
String _$credentialSigninHash() => r'9c6515a30bbdd3d8272ad4bba57c98a9391866c9';
String _$credentialSigninHash() => r'e9731ad5b916838122a2a86961af4cfe4bef57b5';
/// Copied from Dart SDK
class _SystemHash {
@ -236,7 +236,7 @@ class _CredentialSigninProviderElement
(origin as CredentialSigninProvider).credentials;
}
String _$profileManagerHash() => r'2bd46dca2f9307fa0197d21ba55627b89b297884';
String _$profileManagerHash() => r'5f37162a9b7b19f3cd9e804d83dbb37cda96568b';
abstract class _$ProfileManager extends BuildlessNotifier<bool> {
late final Profile profile;
@ -381,7 +381,7 @@ class _ProfileManagerProviderElement
}
String _$accountServicesInitializerHash() =>
r'1872c6987b80737764687a679eb8b987f42c4ff3';
r'386b364d2fe41581ad17d5823a7f47dc12bc4760';
/// See also [AccountServicesInitializer].
@ProviderFor(AccountServicesInitializer)

Wyświetl plik

@ -6,7 +6,7 @@ part of 'blocks_services.dart';
// RiverpodGenerator
// **************************************************************************
String _$blocksManagerHash() => r'd04635c7bf3072984d3575a2fdb6c84056729e5e';
String _$blocksManagerHash() => r'e8285887e48ab751515c9a2e9fdc0a90ce12e437';
/// Copied from Dart SDK
class _SystemHash {

Wyświetl plik

@ -332,7 +332,7 @@ class _TimelineGroupingListProviderElement
GroupingType get type => (origin as TimelineGroupingListProvider).type;
}
String _$circlesRepoHash() => r'f02b6233914071968c47213c79bb64c81f562dd3';
String _$circlesRepoHash() => r'82b95a626e70888ea4a4f0a2c1a736cd0bdcf9c9';
abstract class _$CirclesRepo extends BuildlessNotifier<ICirclesRepo> {
late final Profile profile;
@ -476,7 +476,7 @@ class _CirclesRepoProviderElement
Profile get profile => (origin as _CirclesRepoProvider).profile;
}
String _$circlesHash() => r'fbd0b71fa7027a8c1cf16413df288b666734b180';
String _$circlesHash() => r'84741e2bec4232522eadd6f83046b4d147645985';
abstract class _$Circles
extends BuildlessNotifier<List<TimelineGroupingListData>> {

Wyświetl plik

@ -1312,7 +1312,7 @@ class _ConnectionsRepoClearerProviderElement
}
String _$connectionModifierHash() =>
r'35d92e5d4c0162ca7c44ab9b9f296856a58704c0';
r'75d5a90c167916273292f8e03d462f95a0fe04a3';
abstract class _$ConnectionModifier
extends BuildlessAutoDisposeNotifier<Connection> {
@ -1628,7 +1628,7 @@ class _UpdateStatusInternalProviderElement
}
String _$allContactsUpdaterHash() =>
r'cfab7072e13b11396b4e3aaaed75148fe97f0b55';
r'84f97838de84166a1ed907d75eedaa1decf56daf';
abstract class _$AllContactsUpdater extends BuildlessNotifier<bool> {
late final Profile profile;

Wyświetl plik

@ -12,6 +12,7 @@ import '../models/exec_error.dart';
import '../models/networking/paging_data.dart';
import 'feature_checker_services.dart';
import 'networking/friendica_dms_client_services.dart';
import 'notification_services.dart';
part 'direct_message_services.g.dart';
@ -30,8 +31,7 @@ class DirectMessageThreadIds extends _$DirectMessageThreadIds {
await ref.read(directMessagesProvider(profile, PagingData()).future).match(
onSuccess: (update) {
final newThreads =
DirectMessageThread.createThreads(ref, profile, update);
final newThreads = DirectMessageThread.createThreads(profile, update);
for (final t in newThreads) {
threads.add(t.parentUri);
ref
@ -69,7 +69,7 @@ class DirectMessageThreadIds extends _$DirectMessageThreadIds {
text,
).future);
result.match(onSuccess: (newMessage) {
DirectMessageThread.createThreads(ref, profile, [newMessage])
DirectMessageThread.createThreads(profile, [newMessage])
.forEach((thread) {
state = [...state, thread.parentUri];
ref
@ -97,7 +97,7 @@ class DirectMessageThreadService extends _$DirectMessageThreadService {
threadId = id;
state = DirectMessageThread(
messages: [],
participants: [],
participantIds: [],
title: 'Uninitialized',
parentUri: '',
);
@ -108,6 +108,16 @@ class DirectMessageThreadService extends _$DirectMessageThreadService {
state = thread;
}
Future<void> refresh() async {
await ref
.read(
updateThreadProvider(profile, state.parentUri, PagingData()).future)
.withResult((messages) {
messages.sort((m1, m2) => m1.createdAt.compareTo(m2.createdAt));
state = state.copy(messages: messages);
});
}
FutureResult<DirectMessage, ExecError> newReplyMessage(
DirectMessage original, String text) async {
if (!state.messages.contains(original)) {
@ -159,6 +169,7 @@ class DirectMessageThreadService extends _$DirectMessageThreadService {
final newState = state.deepCopy();
newState.messages[oldIndex] = updatedItem;
update(newState);
ref.read(notificationsManagerProvider(profile).notifier).refreshDms();
},
onError: (error) {
_logger.severe(

Wyświetl plik

@ -7,7 +7,7 @@ part of 'direct_message_services.dart';
// **************************************************************************
String _$directMessageThreadIdsHash() =>
r'76d081ab346af0247a62dc58f270a064b355f26f';
r'ce42c0a00ab785c7685f8c135bada67bea2fcb14';
/// Copied from Dart SDK
class _SystemHash {
@ -175,7 +175,7 @@ class _DirectMessageThreadIdsProviderElement
}
String _$directMessageThreadServiceHash() =>
r'83940611b62a1bb4616f5ebbb93e1398f34db607';
r'5ef67edc0bb13c3834ed998bd107af36762fde79';
abstract class _$DirectMessageThreadService
extends BuildlessNotifier<DirectMessageThread> {

Wyświetl plik

@ -739,7 +739,7 @@ class _EntryTreeManagerProviderElement extends NotifierProviderElement<
String get id => (origin as EntryTreeManagerProvider).id;
}
String _$timelineUpdaterHash() => r'8359f110e4718fb2048d8ce3c0a885bd3d064d28';
String _$timelineUpdaterHash() => r'38d091cafcfa347f52bd3260d7ab72178bd2dc7b';
abstract class _$TimelineUpdater extends BuildlessAutoDisposeNotifier<bool> {
late final Profile profile;

Wyświetl plik

@ -294,7 +294,7 @@ class _VersionErrorStringProviderElement
(origin as VersionErrorStringProvider).feature;
}
String _$featureCheckHash() => r'efedd8bae092a28579b6e750088c07e1b35357d6';
String _$featureCheckHash() => r'8e60d2e4278f205aac2e0e434b595cb7aa5b192b';
/// See also [featureCheck].
@ProviderFor(featureCheck)

Wyświetl plik

@ -7,7 +7,7 @@ part of 'instance_info_services.dart';
// **************************************************************************
String _$instanceInfoManagerHash() =>
r'8d591a07234dadde0560ded347cb3499779c4070';
r'88f181cbbee4b4043bf0be55e8379fcc8ad2b8b3';
/// Copied from Dart SDK
class _SystemHash {

Wyświetl plik

@ -46,6 +46,36 @@ Future<Result<List<DirectMessage>, ExecError>> directMessages(
return result;
}
@riverpod
Future<Result<List<DirectMessage>, ExecError>> updateThread(
Ref ref,
Profile profile,
String threadUri,
PagingData page,
) async {
ref.read(directMessageLoadingProvider(profile));
Future.microtask(
() async =>
ref.read(directMessageLoadingProvider(profile).notifier).begin(),
);
final baseUrl =
'https://${profile.serverName}/api/direct_messages/conversation';
final pagingQP = page.toQueryParameters(limitKeyword: 'count');
final url = '$baseUrl?$pagingQP&uri=$threadUri';
final request = Uri.parse(url);
_logger.finest(() => 'Getting direct messages with paging data $page');
final result = (await ref
.read(getApiListRequestProvider(profile, request).future)
.andThenSuccessAsync((response) async => response.data
.map((json) => DirectMessageFriendicaExtension.fromJson(json))
.toList()))
.execErrorCast();
ref.read(directMessageLoadingProvider(profile).notifier).end();
return result;
}
@riverpod
Future<Result<DirectMessage, ExecError>> markDirectMessageRead(
Ref ref,

Wyświetl plik

@ -180,6 +180,175 @@ class _DirectMessagesProviderElement extends AutoDisposeFutureProviderElement<
PagingData get page => (origin as DirectMessagesProvider).page;
}
String _$updateThreadHash() => r'425416c431e9f0c0f592c07eef2c3f3618c6fcfb';
/// See also [updateThread].
@ProviderFor(updateThread)
const updateThreadProvider = UpdateThreadFamily();
/// See also [updateThread].
class UpdateThreadFamily
extends Family<AsyncValue<Result<List<DirectMessage>, ExecError>>> {
/// See also [updateThread].
const UpdateThreadFamily();
/// See also [updateThread].
UpdateThreadProvider call(
Profile profile,
String threadUri,
PagingData page,
) {
return UpdateThreadProvider(
profile,
threadUri,
page,
);
}
@override
UpdateThreadProvider getProviderOverride(
covariant UpdateThreadProvider provider,
) {
return call(
provider.profile,
provider.threadUri,
provider.page,
);
}
static const Iterable<ProviderOrFamily>? _dependencies = null;
@override
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
@override
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
_allTransitiveDependencies;
@override
String? get name => r'updateThreadProvider';
}
/// See also [updateThread].
class UpdateThreadProvider
extends AutoDisposeFutureProvider<Result<List<DirectMessage>, ExecError>> {
/// See also [updateThread].
UpdateThreadProvider(
Profile profile,
String threadUri,
PagingData page,
) : this._internal(
(ref) => updateThread(
ref as UpdateThreadRef,
profile,
threadUri,
page,
),
from: updateThreadProvider,
name: r'updateThreadProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$updateThreadHash,
dependencies: UpdateThreadFamily._dependencies,
allTransitiveDependencies:
UpdateThreadFamily._allTransitiveDependencies,
profile: profile,
threadUri: threadUri,
page: page,
);
UpdateThreadProvider._internal(
super._createNotifier, {
required super.name,
required super.dependencies,
required super.allTransitiveDependencies,
required super.debugGetCreateSourceHash,
required super.from,
required this.profile,
required this.threadUri,
required this.page,
}) : super.internal();
final Profile profile;
final String threadUri;
final PagingData page;
@override
Override overrideWith(
FutureOr<Result<List<DirectMessage>, ExecError>> Function(
UpdateThreadRef provider)
create,
) {
return ProviderOverride(
origin: this,
override: UpdateThreadProvider._internal(
(ref) => create(ref as UpdateThreadRef),
from: from,
name: null,
dependencies: null,
allTransitiveDependencies: null,
debugGetCreateSourceHash: null,
profile: profile,
threadUri: threadUri,
page: page,
),
);
}
@override
AutoDisposeFutureProviderElement<Result<List<DirectMessage>, ExecError>>
createElement() {
return _UpdateThreadProviderElement(this);
}
@override
bool operator ==(Object other) {
return other is UpdateThreadProvider &&
other.profile == profile &&
other.threadUri == threadUri &&
other.page == page;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, profile.hashCode);
hash = _SystemHash.combine(hash, threadUri.hashCode);
hash = _SystemHash.combine(hash, page.hashCode);
return _SystemHash.finish(hash);
}
}
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
mixin UpdateThreadRef
on AutoDisposeFutureProviderRef<Result<List<DirectMessage>, ExecError>> {
/// The parameter `profile` of this provider.
Profile get profile;
/// The parameter `threadUri` of this provider.
String get threadUri;
/// The parameter `page` of this provider.
PagingData get page;
}
class _UpdateThreadProviderElement extends AutoDisposeFutureProviderElement<
Result<List<DirectMessage>, ExecError>> with UpdateThreadRef {
_UpdateThreadProviderElement(super.provider);
@override
Profile get profile => (origin as UpdateThreadProvider).profile;
@override
String get threadUri => (origin as UpdateThreadProvider).threadUri;
@override
PagingData get page => (origin as UpdateThreadProvider).page;
}
String _$markDirectMessageReadHash() =>
r'62d62dd9e1cf638b0364cdeab742df00da685ccc';

Wyświetl plik

@ -645,7 +645,7 @@ class _GetMyFollowersProviderElement extends AutoDisposeFutureProviderElement<
}
String _$connectionWithStatusClientHash() =>
r'65dde1fad60635c3de95b440ae3046ce47676a9d';
r'687d060bab00296f5aa802713af4ea47989536b4';
/// See also [connectionWithStatusClient].
@ProviderFor(connectionWithStatusClient)

Wyświetl plik

@ -350,7 +350,7 @@ class _DeleteStatusEntryByIdProviderElement
String get id => (origin as DeleteStatusEntryByIdProvider).id;
}
String _$createNewStatusHash() => r'6c2ddd3e4acd4f01d30c2c8d85dc15e36db83501';
String _$createNewStatusHash() => r'c140a6e3a2776edb93a0e389ef5115d3b2e85fb0';
/// See also [createNewStatus].
@ProviderFor(createNewStatus)
@ -568,7 +568,7 @@ class _CreateNewStatusProviderElement
Visibility get visibility => (origin as CreateNewStatusProvider).visibility;
}
String _$editStatusHash() => r'94d0d20e834b16f2d8a09feba003ec89b628bf22';
String _$editStatusHash() => r'cad5eeef14a26a7886c129230fa5e7f1c78da7e7';
/// See also [editStatus].
@ProviderFor(editStatus)
@ -922,7 +922,7 @@ class _ResharePostProviderElement
String get id => (origin as ResharePostProvider).id;
}
String _$unResharePostHash() => r'4b7ca0426ec3c016624c825af43495ba2a21f17d';
String _$unResharePostHash() => r'6f8ea49b584ca01a2a6d87064fa682d3dc78311d';
/// See also [unResharePost].
@ProviderFor(unResharePost)

Wyświetl plik

@ -6,12 +6,14 @@ import 'package:stack_trace/stack_trace.dart';
import '../globals.dart';
import '../models/auth/profile.dart';
import '../models/connection.dart';
import '../models/exec_error.dart';
import '../models/networking/paged_response.dart';
import '../models/networking/pages_manager.dart';
import '../models/networking/paging_data.dart';
import '../models/user_notification.dart';
import '../serializers/mastodon/follow_request_mastodon_extensions.dart';
import 'connection_manager_services.dart';
import 'direct_message_services.dart';
import 'feature_checker_services.dart';
import 'follow_requests_services.dart';
@ -62,7 +64,23 @@ class NotificationsManager extends _$NotificationsManager {
Future<void> refreshConnectionRequestNotifications() async {
_logger.info('refreshConnectionRequestNotifications');
connectionRequests.clear();
await _postFetchOperations([], true, updateDms: false);
await _postFetchOperations(
[],
true,
updateDms: false,
updateFollowRequests: true,
);
}
Future<void> refreshDms() async {
_logger.info('refreshDms');
dms.clear();
await _postFetchOperations(
[],
true,
updateDms: true,
updateFollowRequests: false,
);
}
void clear({bool withListenerNotification = true}) {
@ -254,7 +272,10 @@ class NotificationsManager extends _$NotificationsManager {
.map((id) => ref.watch(directMessageThreadServiceProvider(profile, id)))
.where((t) => !t.allSeen)
.map((t) {
final fromAccount = t.participants.firstWhere((p) => p.id != myId);
final fromAccountId = t.participantIds.firstWhere((pid) => pid != myId);
final fromAccount = ref
.watch(connectionByIdProvider(profile, fromAccountId))
.getValueOrElse(() => Connection());
final latestMessage =
t.messages.reduce((s, m) => s.createdAt > m.createdAt ? s : m);
return UserNotification(

Wyświetl plik

@ -7,7 +7,7 @@ part of 'notification_services.dart';
// **************************************************************************
String _$notificationsManagerHash() =>
r'c7a691dce74c2a2db04fd78a459a13c9d8256e18';
r'dc6a88e34b4d6bc3cea3541aba7453e223374842';
/// Copied from Dart SDK
class _SystemHash {

Wyświetl plik

@ -6,7 +6,7 @@ part of 'persistent_info_services.dart';
// RiverpodGenerator
// **************************************************************************
String _$persistentInfoHash() => r'8881204fcc60dcf435340cc5c28e1e293c97dbe3';
String _$persistentInfoHash() => r'62bf2f76f88d746cf75f4b663d76302d4c3213fd';
/// Copied from Dart SDK
class _SystemHash {

Wyświetl plik

@ -329,7 +329,7 @@ class _CheckTimelineEntryProviderElement
}
String _$timelineEntryFiltersHash() =>
r'dd386f6ecd047a1dcd8602451c7a445510b03f7d';
r'cd4344f10166b5cabb7869c350e6d83f571cb20d';
abstract class _$TimelineEntryFilters
extends BuildlessNotifier<Map<String, TimelineEntryFilter>> {

Wyświetl plik

@ -173,7 +173,7 @@ class _TimelineMaintainerProviderElement extends NotifierProviderElement<
Profile get profile => (origin as TimelineMaintainerProvider).profile;
}
String _$timelineManagerHash() => r'fa6c46745b69e61ce3dd8cb07822d428ea8b66f5';
String _$timelineManagerHash() => r'9f806c36aeb207547fde5a352df1c1fb051cc9af';
abstract class _$TimelineManager extends BuildlessNotifier<Timeline> {
late final Profile profile;

Wyświetl plik

@ -6,10 +6,14 @@ 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';
@ -29,63 +33,57 @@ class MessageThreadScreen extends ConsumerStatefulWidget {
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 t = ref.watch(
final loading = ref.watch(directMessageLoadingProvider(profile));
final thread = ref.watch(
directMessageThreadServiceProvider(profile, widget.parentThreadId));
final title = t.title.isEmpty ? 'Thread' : t.title;
final title = thread.title.isEmpty ? 'Thread' : thread.title;
return Scaffold(
appBar: StandardAppBar.build(context, title),
body: buildBody(profile, t),
appBar: StandardAppBar.build(context, title, actions: [
StatusAndRefreshButton3(
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,
) {
final service = ref.read(
directMessageThreadServiceProvider(profile, thread.parentUri).notifier);
final yourId = profile.userId;
final yourAvatarUrl = profile.avatar;
final participants =
Map.fromEntries(thread.participants.map((p) => MapEntry(p.id, p)));
return Center(
child: Column(
children: [
if (loading) const LinearProgressIndicator(),
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()),
);
return _DirectMessageListItem(
m, profile, widget.parentThreadId);
},
separatorBuilder: (_, __) => const Divider(),
itemCount: thread.messages.length),
@ -119,17 +117,20 @@ class _MessageThreadScreenState extends ConsumerState<MessageThreadScreen> {
return;
}
final othersMessages =
thread.messages.where((m) => m.senderId != yourId);
thread.messages.where((m) => m.senderId != profile.userId);
if (othersMessages.isEmpty) {
buildSnackbar(
context, "Have to wait for a response before sending");
return;
}
await service
await ref
.read(directMessageThreadServiceProvider(
profile, thread.parentUri)
.notifier)
.newReplyMessage(
othersMessages.last,
textController.text,
)
othersMessages.last,
textController.text,
)
.match(onSuccess: (_) {
setState(() {
textController.clear();
@ -147,3 +148,46 @@ class _MessageThreadScreenState extends ConsumerState<MessageThreadScreen> {
));
}
}
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()),
);
}
}

Wyświetl plik

@ -8,6 +8,7 @@ import '../controls/standard_appbar.dart';
import '../controls/status_and_refresh_button.dart';
import '../models/auth/profile.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 '../routes.dart';
@ -19,13 +20,16 @@ class MessagesScreen extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final profile = ref.watch(activeProfileProvider);
final service = ref.watch(directMessageThreadIdsProvider(profile).notifier);
ref.watch(directMessageThreadIdsProvider(profile));
final loading = ref.watch(directMessageLoadingProvider(profile));
return Scaffold(
appBar: StandardAppBar.build(context, 'Direct Message Threads', actions: [
StatusAndRefreshButton2(
watcher: (ref2) => ref2.watch(directMessageLoadingProvider(profile)),
refreshFunction: () async => await service.update(),
StatusAndRefreshButton3(
executing: loading,
refreshFunction: () async => await ref
.read(directMessageThreadIdsProvider(profile).notifier)
.update(),
busyColor: Theme.of(context).colorScheme.surface,
),
IconButton(
@ -37,44 +41,68 @@ class MessagesScreen extends ConsumerWidget {
]),
body: RefreshIndicator(
onRefresh: () async {
await service.update();
await ref
.read(directMessageThreadIdsProvider(profile).notifier)
.update();
},
child: Center(child: buildBody(profile, ref)),
child: Center(
child: Column(
children: [
if (loading) const LinearProgressIndicator(),
Expanded(child: buildBody(profile, ref))
],
)),
),
);
}
Widget buildBody(Profile profile, WidgetRef ref) {
final threadIds = ref.watch(directMessageThreadIdsProvider(profile));
return threadIds.isEmpty
? const Text('No Direct Message Threads')
: ResponsiveMaxWidth(
child: ListView.separated(
itemCount: threadIds.length,
itemBuilder: (context, index) {
itemBuilder: (context, i) {
final index = i;
final threadId = threadIds[index];
final thread = ref.watch(
directMessageThreadServiceProvider(profile, threadId));
final style = thread.allSeen
? null
: const TextStyle(fontWeight: FontWeight.bold);
final participantsAvatars = <String>[];
final participantNames = <String>[];
for (final pid in thread.participantIds) {
if (pid == profile.userId) {
continue;
}
ref.watch(connectionByIdProvider(profile, pid)).match(
onSuccess: (c) {
participantsAvatars.add(c.avatarUrl);
participantNames.add('${c.name}(${c.handle})');
},
onError: (_) => participantNames.add('Person 1'));
}
return ListTile(
onTap: () => context.pushNamed(
ScreenPaths.thread,
queryParameters: {'uri': thread.parentUri},
),
leading: ImageControl(
imageUrl: thread.participants.first.avatarUrl.toString(),
iconOverride: const Icon(Icons.person),
width: 32.0,
onTap: null,
),
leading: thread.participantIds.isEmpty
? null
: ImageControl(
imageUrl: participantsAvatars.first,
iconOverride: const Icon(Icons.person),
width: 32.0,
onTap: null,
),
title: Text(
[
'You',
...thread.participants
.map((p) => '${p.name}(${p.handle})')
].join(thread.participants.length == 1 ? ' & ' : ', '),
...participantNames,
].join(participantNames.length == 1 ? ' & ' : ', '),
softWrap: true,
style: style,
),
@ -83,8 +111,10 @@ class MessagesScreen extends ConsumerWidget {
style: style,
),
trailing: Text(
ElapsedDateUtils.elapsedTimeStringFromEpochSeconds(
thread.messages.last.createdAt),
thread.messages.isEmpty
? ''
: ElapsedDateUtils.elapsedTimeStringFromEpochSeconds(
thread.messages.last.createdAt),
style: style,
),
);

Wyświetl plik

@ -40,21 +40,6 @@ extension DirectMessageFriendicaExtension on DirectMessage {
final String parentUri = json['friendica_parent_uri'];
// TODO Add back when RP conversion is complete
// getIt<ActiveProfileSelector<ConnectionsManager>>()
// .activeEntry
// .andThenSuccess((cm) {
// if (getIt<AccountsService>().currentProfile.userId != senderId) {
// final s = ConnectionFriendicaExtensions.fromJson(json['sender']);
// cm.upsertConnection(s);
// }
//
// if (getIt<AccountsService>().currentProfile.userId != recipientId) {
// final r = ConnectionFriendicaExtensions.fromJson(json['recipient']);
// cm.upsertConnection(r);
// }
// });
return DirectMessage(
id: id,
senderId: senderId,