mirror of
https://gitlab.com/mysocialportal/relatica
synced 2024-10-18 12:23:31 +00:00
Convert DirectMessage System to Riverpod
This commit is contained in:
parent
bb0a6bd36b
commit
f0ab80d312
12 changed files with 729 additions and 371 deletions
|
@ -18,7 +18,6 @@ import 'models/instance_info.dart';
|
||||||
import 'services/auth_service.dart';
|
import 'services/auth_service.dart';
|
||||||
import 'services/blocks_manager.dart';
|
import 'services/blocks_manager.dart';
|
||||||
import 'services/connections_manager.dart';
|
import 'services/connections_manager.dart';
|
||||||
import 'services/direct_message_service.dart';
|
|
||||||
import 'services/entry_manager_service.dart';
|
import 'services/entry_manager_service.dart';
|
||||||
import 'services/feature_version_checker.dart';
|
import 'services/feature_version_checker.dart';
|
||||||
import 'services/fediverse_server_validator.dart';
|
import 'services/fediverse_server_validator.dart';
|
||||||
|
@ -128,9 +127,6 @@ Future<void> dependencyInjectionInitialization() async {
|
||||||
getIt.registerSingleton<ActiveProfileSelector<FollowRequestsManager>>(
|
getIt.registerSingleton<ActiveProfileSelector<FollowRequestsManager>>(
|
||||||
ActiveProfileSelector((p) => FollowRequestsManager(p))
|
ActiveProfileSelector((p) => FollowRequestsManager(p))
|
||||||
..subscribeToProfileSwaps());
|
..subscribeToProfileSwaps());
|
||||||
getIt.registerSingleton<ActiveProfileSelector<DirectMessageService>>(
|
|
||||||
ActiveProfileSelector((p) => DirectMessageService(p))
|
|
||||||
..subscribeToProfileSwaps());
|
|
||||||
getIt.registerSingleton<ActiveProfileSelector<InteractionsManager>>(
|
getIt.registerSingleton<ActiveProfileSelector<InteractionsManager>>(
|
||||||
ActiveProfileSelector((p) => InteractionsManager(p))
|
ActiveProfileSelector((p) => InteractionsManager(p))
|
||||||
..subscribeToProfileSwaps());
|
..subscribeToProfileSwaps());
|
||||||
|
@ -167,12 +163,6 @@ void clearCaches() {
|
||||||
_logger.severe('Error clearing IConnections Repo: $error'),
|
_logger.severe('Error clearing IConnections Repo: $error'),
|
||||||
);
|
);
|
||||||
|
|
||||||
getIt<ActiveProfileSelector<DirectMessageService>>().activeEntry.match(
|
|
||||||
onSuccess: (service) => service.clear(),
|
|
||||||
onError: (error) =>
|
|
||||||
_logger.severe('Error clearing DirectMessageService Repo: $error'),
|
|
||||||
);
|
|
||||||
|
|
||||||
getIt<ActiveProfileSelector<EntryManagerService>>().activeEntry.match(
|
getIt<ActiveProfileSelector<EntryManagerService>>().activeEntry.match(
|
||||||
onSuccess: (service) => service.clear(),
|
onSuccess: (service) => service.clear(),
|
||||||
onError: (error) =>
|
onError: (error) =>
|
||||||
|
|
|
@ -16,7 +16,6 @@ import 'routes.dart';
|
||||||
import 'services/auth_service.dart';
|
import 'services/auth_service.dart';
|
||||||
import 'services/blocks_manager.dart';
|
import 'services/blocks_manager.dart';
|
||||||
import 'services/connections_manager.dart';
|
import 'services/connections_manager.dart';
|
||||||
import 'services/direct_message_service.dart';
|
|
||||||
import 'services/entry_manager_service.dart';
|
import 'services/entry_manager_service.dart';
|
||||||
import 'services/follow_requests_manager.dart';
|
import 'services/follow_requests_manager.dart';
|
||||||
import 'services/gallery_service.dart';
|
import 'services/gallery_service.dart';
|
||||||
|
@ -147,11 +146,6 @@ class _AppState extends fr.ConsumerState<App> {
|
||||||
create: (_) =>
|
create: (_) =>
|
||||||
getIt<ActiveProfileSelector<FollowRequestsManager>>(),
|
getIt<ActiveProfileSelector<FollowRequestsManager>>(),
|
||||||
),
|
),
|
||||||
ChangeNotifierProvider<
|
|
||||||
ActiveProfileSelector<DirectMessageService>>(
|
|
||||||
create: (_) =>
|
|
||||||
getIt<ActiveProfileSelector<DirectMessageService>>(),
|
|
||||||
),
|
|
||||||
ChangeNotifierProvider<
|
ChangeNotifierProvider<
|
||||||
ActiveProfileSelector<InteractionsManager>>(
|
ActiveProfileSelector<InteractionsManager>>(
|
||||||
create: (_) =>
|
create: (_) =>
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'basic_credentials.dart';
|
||||||
import 'credentials_intf.dart';
|
import 'credentials_intf.dart';
|
||||||
|
|
||||||
class Profile {
|
class Profile {
|
||||||
|
@ -30,6 +31,15 @@ class Profile {
|
||||||
loggedIn: false,
|
loggedIn: false,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
factory Profile.empty() => Profile(
|
||||||
|
credentials: BasicCredentials.empty(),
|
||||||
|
username: '',
|
||||||
|
userId: '',
|
||||||
|
avatar: '',
|
||||||
|
serverName: '',
|
||||||
|
loggedIn: false,
|
||||||
|
);
|
||||||
|
|
||||||
factory Profile.fromJson(
|
factory Profile.fromJson(
|
||||||
Map<String, dynamic> json,
|
Map<String, dynamic> json,
|
||||||
ICredentials Function(Map<String, dynamic> json) credentialsFromJson,
|
ICredentials Function(Map<String, dynamic> json) credentialsFromJson,
|
||||||
|
|
|
@ -62,8 +62,27 @@ class DirectMessage {
|
||||||
identical(this, other) ||
|
identical(this, other) ||
|
||||||
other is DirectMessage &&
|
other is DirectMessage &&
|
||||||
runtimeType == other.runtimeType &&
|
runtimeType == other.runtimeType &&
|
||||||
id == other.id;
|
id == other.id &&
|
||||||
|
senderId == other.senderId &&
|
||||||
|
senderScreenName == other.senderScreenName &&
|
||||||
|
recipientId == other.recipientId &&
|
||||||
|
recipientScreenName == other.recipientScreenName &&
|
||||||
|
title == other.title &&
|
||||||
|
text == other.text &&
|
||||||
|
createdAt == other.createdAt &&
|
||||||
|
seen == other.seen &&
|
||||||
|
parentUri == other.parentUri;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode => id.hashCode;
|
int get hashCode =>
|
||||||
|
id.hashCode ^
|
||||||
|
senderId.hashCode ^
|
||||||
|
senderScreenName.hashCode ^
|
||||||
|
recipientId.hashCode ^
|
||||||
|
recipientScreenName.hashCode ^
|
||||||
|
title.hashCode ^
|
||||||
|
text.hashCode ^
|
||||||
|
createdAt.hashCode ^
|
||||||
|
seen.hashCode ^
|
||||||
|
parentUri.hashCode;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
|
|
||||||
import '../globals.dart';
|
import '../globals.dart';
|
||||||
|
@ -23,6 +24,13 @@ class DirectMessageThread {
|
||||||
required this.parentUri,
|
required this.parentUri,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
DirectMessageThread deepCopy() => DirectMessageThread(
|
||||||
|
messages: List.from(messages),
|
||||||
|
participants: List.from(participants),
|
||||||
|
title: title,
|
||||||
|
parentUri: parentUri,
|
||||||
|
);
|
||||||
|
|
||||||
get allSeen => messages.isEmpty
|
get allSeen => messages.isEmpty
|
||||||
? false
|
? false
|
||||||
: messages
|
: messages
|
||||||
|
@ -83,8 +91,15 @@ class DirectMessageThread {
|
||||||
identical(this, other) ||
|
identical(this, other) ||
|
||||||
other is DirectMessageThread &&
|
other is DirectMessageThread &&
|
||||||
runtimeType == other.runtimeType &&
|
runtimeType == other.runtimeType &&
|
||||||
parentUri == other.parentUri;
|
title == other.title &&
|
||||||
|
parentUri == other.parentUri &&
|
||||||
|
listEquals(messages, other.messages) &&
|
||||||
|
listEquals(participants, other.participants);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode => parentUri.hashCode;
|
int get hashCode =>
|
||||||
|
title.hashCode ^
|
||||||
|
parentUri.hashCode ^
|
||||||
|
Object.hashAll(messages) ^
|
||||||
|
Object.hashAll(participants);
|
||||||
}
|
}
|
||||||
|
|
163
lib/riverpod_controllers/direct_message_services.dart
Normal file
163
lib/riverpod_controllers/direct_message_services.dart
Normal file
|
@ -0,0 +1,163 @@
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
|
import 'package:relatica/models/connection.dart';
|
||||||
|
import 'package:relatica/models/direct_message_thread.dart';
|
||||||
|
import 'package:relatica/models/exec_error.dart';
|
||||||
|
import 'package:result_monad/result_monad.dart';
|
||||||
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
|
||||||
|
import '../friendica_client/friendica_client.dart';
|
||||||
|
import '../friendica_client/paging_data.dart';
|
||||||
|
import '../globals.dart';
|
||||||
|
import '../models/auth/oauth_credentials.dart';
|
||||||
|
import '../models/auth/profile.dart';
|
||||||
|
import '../models/direct_message.dart';
|
||||||
|
import '../services/feature_version_checker.dart';
|
||||||
|
|
||||||
|
part 'direct_message_services.g.dart';
|
||||||
|
|
||||||
|
@Riverpod(keepAlive: true)
|
||||||
|
class DirectMessageThreadIds extends _$DirectMessageThreadIds {
|
||||||
|
static final _logger = Logger('DirectMessageThreadIdsProvider');
|
||||||
|
late Profile userProfile;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<String> build(Profile profile) {
|
||||||
|
userProfile = profile;
|
||||||
|
update();
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> update() async {
|
||||||
|
final threads = <String>[];
|
||||||
|
await DirectMessagingClient(userProfile)
|
||||||
|
.getDirectMessages(PagingData())
|
||||||
|
.match(
|
||||||
|
onSuccess: (update) {
|
||||||
|
final newThreads = DirectMessageThread.createThreads(update);
|
||||||
|
for (final t in newThreads) {
|
||||||
|
threads.add(t.parentUri);
|
||||||
|
ref
|
||||||
|
.read(directMessageThreadServiceProvider(userProfile, t.parentUri)
|
||||||
|
.notifier)
|
||||||
|
.update(t);
|
||||||
|
}
|
||||||
|
_logger.fine(
|
||||||
|
'Updated ${update.length} direct messages, across ${newThreads.length} threads');
|
||||||
|
},
|
||||||
|
onError: (error) {
|
||||||
|
_logger.severe('Error getting direct messages: $error');
|
||||||
|
},
|
||||||
|
);
|
||||||
|
state = threads;
|
||||||
|
}
|
||||||
|
|
||||||
|
FutureResult<DirectMessage, ExecError> newThread(
|
||||||
|
Connection receiver, String text) async {
|
||||||
|
if (userProfile.credentials is OAuthCredentials) {
|
||||||
|
final result = getIt<FriendicaVersionChecker>()
|
||||||
|
.canUseFeatureResult(RelaticaFeatures.directMessageCreation);
|
||||||
|
if (result.isFailure) {
|
||||||
|
return result.errorCast();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final result = await DirectMessagingClient(userProfile).postDirectMessage(
|
||||||
|
null,
|
||||||
|
receiver.id,
|
||||||
|
text,
|
||||||
|
);
|
||||||
|
result.match(onSuccess: (newMessage) {
|
||||||
|
DirectMessageThread.createThreads([newMessage]).forEach((thread) {
|
||||||
|
state = [...state, thread.parentUri];
|
||||||
|
ref
|
||||||
|
.read(directMessageThreadServiceProvider(
|
||||||
|
userProfile, thread.parentUri)
|
||||||
|
.notifier)
|
||||||
|
.update(thread);
|
||||||
|
});
|
||||||
|
}, onError: (error) {
|
||||||
|
_logger.severe('Error getting direct messages: $error');
|
||||||
|
});
|
||||||
|
|
||||||
|
ref.invalidateSelf();
|
||||||
|
return result.execErrorCast();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Riverpod(keepAlive: true)
|
||||||
|
class DirectMessageThreadService extends _$DirectMessageThreadService {
|
||||||
|
static final _logger = Logger('DirectMessageThreadServiceProvider');
|
||||||
|
String threadId = '';
|
||||||
|
|
||||||
|
@override
|
||||||
|
DirectMessageThread build(Profile profile, String id) {
|
||||||
|
_logger.severe('build id = $id');
|
||||||
|
threadId = id;
|
||||||
|
state = DirectMessageThread(
|
||||||
|
messages: [],
|
||||||
|
participants: [],
|
||||||
|
title: 'Uninitialized',
|
||||||
|
parentUri: '',
|
||||||
|
);
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
void update(DirectMessageThread thread) {
|
||||||
|
print('OldThread == NewThread? ${state == thread}');
|
||||||
|
state = thread;
|
||||||
|
}
|
||||||
|
|
||||||
|
FutureResult<DirectMessage, ExecError> newReplyMessage(
|
||||||
|
DirectMessage original, String text) async {
|
||||||
|
if (!state.messages.contains(original)) {
|
||||||
|
final error =
|
||||||
|
'Message is not for this thread: ${state.parentUri}, $original';
|
||||||
|
_logger.severe(error);
|
||||||
|
return buildErrorResult(
|
||||||
|
type: ErrorType.notFound,
|
||||||
|
message: error,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (profile.credentials is OAuthCredentials) {
|
||||||
|
final result = getIt<FriendicaVersionChecker>()
|
||||||
|
.canUseFeatureResult(RelaticaFeatures.directMessageCreation);
|
||||||
|
if (result.isFailure) {
|
||||||
|
return result.errorCast();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final result = await DirectMessagingClient(profile).postDirectMessage(
|
||||||
|
original.id,
|
||||||
|
original.senderId,
|
||||||
|
text,
|
||||||
|
);
|
||||||
|
result.match(onSuccess: (newMessage) {
|
||||||
|
state.messages.add(newMessage);
|
||||||
|
}, onError: (error) {
|
||||||
|
_logger.severe('Error getting direct messages: $error');
|
||||||
|
});
|
||||||
|
|
||||||
|
update(state);
|
||||||
|
return result.execErrorCast();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> markMessageRead(DirectMessage m) async {
|
||||||
|
final oldIndex = state.messages.indexOf(m);
|
||||||
|
if (oldIndex < 0) {
|
||||||
|
_logger.severe('Message is not for this thread: ${state.parentUri}, $m');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await DirectMessagingClient(profile).markDirectMessageRead(m).match(
|
||||||
|
onSuccess: (updatedItem) {
|
||||||
|
final newState = state.deepCopy();
|
||||||
|
newState.messages[oldIndex] = updatedItem;
|
||||||
|
update(newState);
|
||||||
|
},
|
||||||
|
onError: (error) {
|
||||||
|
_logger.severe('Error getting direct messages: $error');
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
344
lib/riverpod_controllers/direct_message_services.g.dart
Normal file
344
lib/riverpod_controllers/direct_message_services.g.dart
Normal file
|
@ -0,0 +1,344 @@
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'direct_message_services.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// RiverpodGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
String _$directMessageThreadIdsHash() =>
|
||||||
|
r'30b269250935a6966d4fa47c479d29a4bb562729';
|
||||||
|
|
||||||
|
/// Copied from Dart SDK
|
||||||
|
class _SystemHash {
|
||||||
|
_SystemHash._();
|
||||||
|
|
||||||
|
static int combine(int hash, int value) {
|
||||||
|
// ignore: parameter_assignments
|
||||||
|
hash = 0x1fffffff & (hash + value);
|
||||||
|
// ignore: parameter_assignments
|
||||||
|
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
|
||||||
|
return hash ^ (hash >> 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int finish(int hash) {
|
||||||
|
// ignore: parameter_assignments
|
||||||
|
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
|
||||||
|
// ignore: parameter_assignments
|
||||||
|
hash = hash ^ (hash >> 11);
|
||||||
|
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class _$DirectMessageThreadIds
|
||||||
|
extends BuildlessNotifier<List<String>> {
|
||||||
|
late final Profile profile;
|
||||||
|
|
||||||
|
List<String> build(
|
||||||
|
Profile profile,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See also [DirectMessageThreadIds].
|
||||||
|
@ProviderFor(DirectMessageThreadIds)
|
||||||
|
const directMessageThreadIdsProvider = DirectMessageThreadIdsFamily();
|
||||||
|
|
||||||
|
/// See also [DirectMessageThreadIds].
|
||||||
|
class DirectMessageThreadIdsFamily extends Family<List<String>> {
|
||||||
|
/// See also [DirectMessageThreadIds].
|
||||||
|
const DirectMessageThreadIdsFamily();
|
||||||
|
|
||||||
|
/// See also [DirectMessageThreadIds].
|
||||||
|
DirectMessageThreadIdsProvider call(
|
||||||
|
Profile profile,
|
||||||
|
) {
|
||||||
|
return DirectMessageThreadIdsProvider(
|
||||||
|
profile,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
DirectMessageThreadIdsProvider getProviderOverride(
|
||||||
|
covariant DirectMessageThreadIdsProvider provider,
|
||||||
|
) {
|
||||||
|
return call(
|
||||||
|
provider.profile,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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'directMessageThreadIdsProvider';
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See also [DirectMessageThreadIds].
|
||||||
|
class DirectMessageThreadIdsProvider
|
||||||
|
extends NotifierProviderImpl<DirectMessageThreadIds, List<String>> {
|
||||||
|
/// See also [DirectMessageThreadIds].
|
||||||
|
DirectMessageThreadIdsProvider(
|
||||||
|
Profile profile,
|
||||||
|
) : this._internal(
|
||||||
|
() => DirectMessageThreadIds()..profile = profile,
|
||||||
|
from: directMessageThreadIdsProvider,
|
||||||
|
name: r'directMessageThreadIdsProvider',
|
||||||
|
debugGetCreateSourceHash:
|
||||||
|
const bool.fromEnvironment('dart.vm.product')
|
||||||
|
? null
|
||||||
|
: _$directMessageThreadIdsHash,
|
||||||
|
dependencies: DirectMessageThreadIdsFamily._dependencies,
|
||||||
|
allTransitiveDependencies:
|
||||||
|
DirectMessageThreadIdsFamily._allTransitiveDependencies,
|
||||||
|
profile: profile,
|
||||||
|
);
|
||||||
|
|
||||||
|
DirectMessageThreadIdsProvider._internal(
|
||||||
|
super._createNotifier, {
|
||||||
|
required super.name,
|
||||||
|
required super.dependencies,
|
||||||
|
required super.allTransitiveDependencies,
|
||||||
|
required super.debugGetCreateSourceHash,
|
||||||
|
required super.from,
|
||||||
|
required this.profile,
|
||||||
|
}) : super.internal();
|
||||||
|
|
||||||
|
final Profile profile;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<String> runNotifierBuild(
|
||||||
|
covariant DirectMessageThreadIds notifier,
|
||||||
|
) {
|
||||||
|
return notifier.build(
|
||||||
|
profile,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Override overrideWith(DirectMessageThreadIds Function() create) {
|
||||||
|
return ProviderOverride(
|
||||||
|
origin: this,
|
||||||
|
override: DirectMessageThreadIdsProvider._internal(
|
||||||
|
() => create()..profile = profile,
|
||||||
|
from: from,
|
||||||
|
name: null,
|
||||||
|
dependencies: null,
|
||||||
|
allTransitiveDependencies: null,
|
||||||
|
debugGetCreateSourceHash: null,
|
||||||
|
profile: profile,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
NotifierProviderElement<DirectMessageThreadIds, List<String>>
|
||||||
|
createElement() {
|
||||||
|
return _DirectMessageThreadIdsProviderElement(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return other is DirectMessageThreadIdsProvider && other.profile == profile;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode {
|
||||||
|
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||||
|
hash = _SystemHash.combine(hash, profile.hashCode);
|
||||||
|
|
||||||
|
return _SystemHash.finish(hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mixin DirectMessageThreadIdsRef on NotifierProviderRef<List<String>> {
|
||||||
|
/// The parameter `profile` of this provider.
|
||||||
|
Profile get profile;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DirectMessageThreadIdsProviderElement
|
||||||
|
extends NotifierProviderElement<DirectMessageThreadIds, List<String>>
|
||||||
|
with DirectMessageThreadIdsRef {
|
||||||
|
_DirectMessageThreadIdsProviderElement(super.provider);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Profile get profile => (origin as DirectMessageThreadIdsProvider).profile;
|
||||||
|
}
|
||||||
|
|
||||||
|
String _$directMessageThreadServiceHash() =>
|
||||||
|
r'f6a518e07e5e017ef0e2b9f1821408bbca77aef5';
|
||||||
|
|
||||||
|
abstract class _$DirectMessageThreadService
|
||||||
|
extends BuildlessNotifier<DirectMessageThread> {
|
||||||
|
late final Profile profile;
|
||||||
|
late final String id;
|
||||||
|
|
||||||
|
DirectMessageThread build(
|
||||||
|
Profile profile,
|
||||||
|
String id,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See also [DirectMessageThreadService].
|
||||||
|
@ProviderFor(DirectMessageThreadService)
|
||||||
|
const directMessageThreadServiceProvider = DirectMessageThreadServiceFamily();
|
||||||
|
|
||||||
|
/// See also [DirectMessageThreadService].
|
||||||
|
class DirectMessageThreadServiceFamily extends Family<DirectMessageThread> {
|
||||||
|
/// See also [DirectMessageThreadService].
|
||||||
|
const DirectMessageThreadServiceFamily();
|
||||||
|
|
||||||
|
/// See also [DirectMessageThreadService].
|
||||||
|
DirectMessageThreadServiceProvider call(
|
||||||
|
Profile profile,
|
||||||
|
String id,
|
||||||
|
) {
|
||||||
|
return DirectMessageThreadServiceProvider(
|
||||||
|
profile,
|
||||||
|
id,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
DirectMessageThreadServiceProvider getProviderOverride(
|
||||||
|
covariant DirectMessageThreadServiceProvider provider,
|
||||||
|
) {
|
||||||
|
return call(
|
||||||
|
provider.profile,
|
||||||
|
provider.id,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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'directMessageThreadServiceProvider';
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See also [DirectMessageThreadService].
|
||||||
|
class DirectMessageThreadServiceProvider extends NotifierProviderImpl<
|
||||||
|
DirectMessageThreadService, DirectMessageThread> {
|
||||||
|
/// See also [DirectMessageThreadService].
|
||||||
|
DirectMessageThreadServiceProvider(
|
||||||
|
Profile profile,
|
||||||
|
String id,
|
||||||
|
) : this._internal(
|
||||||
|
() => DirectMessageThreadService()
|
||||||
|
..profile = profile
|
||||||
|
..id = id,
|
||||||
|
from: directMessageThreadServiceProvider,
|
||||||
|
name: r'directMessageThreadServiceProvider',
|
||||||
|
debugGetCreateSourceHash:
|
||||||
|
const bool.fromEnvironment('dart.vm.product')
|
||||||
|
? null
|
||||||
|
: _$directMessageThreadServiceHash,
|
||||||
|
dependencies: DirectMessageThreadServiceFamily._dependencies,
|
||||||
|
allTransitiveDependencies:
|
||||||
|
DirectMessageThreadServiceFamily._allTransitiveDependencies,
|
||||||
|
profile: profile,
|
||||||
|
id: id,
|
||||||
|
);
|
||||||
|
|
||||||
|
DirectMessageThreadServiceProvider._internal(
|
||||||
|
super._createNotifier, {
|
||||||
|
required super.name,
|
||||||
|
required super.dependencies,
|
||||||
|
required super.allTransitiveDependencies,
|
||||||
|
required super.debugGetCreateSourceHash,
|
||||||
|
required super.from,
|
||||||
|
required this.profile,
|
||||||
|
required this.id,
|
||||||
|
}) : super.internal();
|
||||||
|
|
||||||
|
final Profile profile;
|
||||||
|
final String id;
|
||||||
|
|
||||||
|
@override
|
||||||
|
DirectMessageThread runNotifierBuild(
|
||||||
|
covariant DirectMessageThreadService notifier,
|
||||||
|
) {
|
||||||
|
return notifier.build(
|
||||||
|
profile,
|
||||||
|
id,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Override overrideWith(DirectMessageThreadService Function() create) {
|
||||||
|
return ProviderOverride(
|
||||||
|
origin: this,
|
||||||
|
override: DirectMessageThreadServiceProvider._internal(
|
||||||
|
() => create()
|
||||||
|
..profile = profile
|
||||||
|
..id = id,
|
||||||
|
from: from,
|
||||||
|
name: null,
|
||||||
|
dependencies: null,
|
||||||
|
allTransitiveDependencies: null,
|
||||||
|
debugGetCreateSourceHash: null,
|
||||||
|
profile: profile,
|
||||||
|
id: id,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
NotifierProviderElement<DirectMessageThreadService, DirectMessageThread>
|
||||||
|
createElement() {
|
||||||
|
return _DirectMessageThreadServiceProviderElement(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return other is DirectMessageThreadServiceProvider &&
|
||||||
|
other.profile == profile &&
|
||||||
|
other.id == id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode {
|
||||||
|
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||||
|
hash = _SystemHash.combine(hash, profile.hashCode);
|
||||||
|
hash = _SystemHash.combine(hash, id.hashCode);
|
||||||
|
|
||||||
|
return _SystemHash.finish(hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mixin DirectMessageThreadServiceRef
|
||||||
|
on NotifierProviderRef<DirectMessageThread> {
|
||||||
|
/// The parameter `profile` of this provider.
|
||||||
|
Profile get profile;
|
||||||
|
|
||||||
|
/// The parameter `id` of this provider.
|
||||||
|
String get id;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DirectMessageThreadServiceProviderElement
|
||||||
|
extends NotifierProviderElement<DirectMessageThreadService,
|
||||||
|
DirectMessageThread> with DirectMessageThreadServiceRef {
|
||||||
|
_DirectMessageThreadServiceProviderElement(super.provider);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Profile get profile => (origin as DirectMessageThreadServiceProvider).profile;
|
||||||
|
@override
|
||||||
|
String get id => (origin as DirectMessageThreadServiceProvider).id;
|
||||||
|
}
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:result_monad/result_monad.dart';
|
import 'package:result_monad/result_monad.dart';
|
||||||
|
|
||||||
|
@ -7,15 +8,14 @@ import '../controls/padding.dart';
|
||||||
import '../controls/responsive_max_width.dart';
|
import '../controls/responsive_max_width.dart';
|
||||||
import '../controls/standard_appbar.dart';
|
import '../controls/standard_appbar.dart';
|
||||||
import '../globals.dart';
|
import '../globals.dart';
|
||||||
|
import '../models/auth/profile.dart';
|
||||||
import '../models/direct_message_thread.dart';
|
import '../models/direct_message_thread.dart';
|
||||||
import '../models/exec_error.dart';
|
import '../riverpod_controllers/direct_message_services.dart';
|
||||||
import '../services/auth_service.dart';
|
import '../services/auth_service.dart';
|
||||||
import '../services/direct_message_service.dart';
|
|
||||||
import '../utils/active_profile_selector.dart';
|
|
||||||
import '../utils/clipboard_utils.dart';
|
import '../utils/clipboard_utils.dart';
|
||||||
import '../utils/snackbar_builder.dart';
|
import '../utils/snackbar_builder.dart';
|
||||||
|
|
||||||
class MessageThreadScreen extends StatefulWidget {
|
class MessageThreadScreen extends ConsumerStatefulWidget {
|
||||||
final String parentThreadId;
|
final String parentThreadId;
|
||||||
|
|
||||||
const MessageThreadScreen({
|
const MessageThreadScreen({
|
||||||
|
@ -24,141 +24,128 @@ class MessageThreadScreen extends StatefulWidget {
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<MessageThreadScreen> createState() => _MessageThreadScreenState();
|
ConsumerState<MessageThreadScreen> createState() =>
|
||||||
|
_MessageThreadScreenState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _MessageThreadScreenState extends State<MessageThreadScreen> {
|
class _MessageThreadScreenState extends ConsumerState<MessageThreadScreen> {
|
||||||
final textController = TextEditingController();
|
final textController = TextEditingController();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final service = context
|
final profile = context.watch<AccountsService>().currentProfile;
|
||||||
.watch<ActiveProfileSelector<DirectMessageService>>()
|
final t = ref.watch(
|
||||||
.activeEntry
|
directMessageThreadServiceProvider(profile, widget.parentThreadId));
|
||||||
.value;
|
final title = t.title.isEmpty ? 'Thread' : t.title;
|
||||||
final result = service.getThreadByParentUri(widget.parentThreadId);
|
|
||||||
final title = result.fold(
|
|
||||||
onSuccess: (t) => t.title.isEmpty ? 'Thread' : t.title,
|
|
||||||
onError: (_) => 'Thread');
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: StandardAppBar.build(context, title),
|
appBar: StandardAppBar.build(context, title),
|
||||||
body: buildBody(result, service),
|
body: buildBody(profile, t),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget buildBody(
|
Widget buildBody(
|
||||||
Result<DirectMessageThread, ExecError> result,
|
Profile profile,
|
||||||
DirectMessageService service,
|
DirectMessageThread thread,
|
||||||
) {
|
) {
|
||||||
return result.fold(
|
final service = ref.read(
|
||||||
onSuccess: (thread) {
|
directMessageThreadServiceProvider(profile, thread.parentUri).notifier);
|
||||||
final yourId = getIt<AccountsService>().currentProfile.userId;
|
final yourId = getIt<AccountsService>().currentProfile.userId;
|
||||||
final yourAvatarUrl = getIt<AccountsService>().currentProfile.avatar;
|
final yourAvatarUrl = getIt<AccountsService>().currentProfile.avatar;
|
||||||
final participants =
|
final participants =
|
||||||
Map.fromEntries(thread.participants.map((p) => MapEntry(p.id, p)));
|
Map.fromEntries(thread.participants.map((p) => MapEntry(p.id, p)));
|
||||||
return Center(
|
return Center(
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: ResponsiveMaxWidth(
|
child: ResponsiveMaxWidth(
|
||||||
child: ListView.separated(
|
child: ListView.separated(
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final m = thread.messages[index];
|
final m = thread.messages[index];
|
||||||
final textPieces = m.text.split('...\n');
|
final textPieces = m.text.split('...\n');
|
||||||
final text = textPieces.length == 1
|
final text =
|
||||||
? textPieces[0]
|
textPieces.length == 1 ? textPieces[0] : textPieces[1];
|
||||||
: textPieces[1];
|
final imageUrl = m.senderId == yourId
|
||||||
final imageUrl = m.senderId == yourId
|
? yourAvatarUrl
|
||||||
? yourAvatarUrl
|
: participants[m.senderId]?.avatarUrl ?? '';
|
||||||
: participants[m.senderId]?.avatarUrl ?? '';
|
return ListTile(
|
||||||
return ListTile(
|
onTap: m.seen ? null : () => service.markMessageRead(m),
|
||||||
onTap: m.seen
|
onLongPress: () async {
|
||||||
? null
|
await copyToClipboard(context: context, text: m.text);
|
||||||
: () => service.markMessageRead(
|
|
||||||
widget.parentThreadId, 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(),
|
leading: ImageControl(
|
||||||
itemCount: thread.messages.length),
|
imageUrl: imageUrl,
|
||||||
),
|
iconOverride: const Icon(Icons.person),
|
||||||
),
|
width: 32.0,
|
||||||
const VerticalDivider(),
|
onTap: null,
|
||||||
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,
|
|
||||||
),
|
|
||||||
borderRadius: BorderRadius.circular(5.0),
|
|
||||||
),
|
),
|
||||||
|
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,
|
||||||
),
|
),
|
||||||
|
borderRadius: BorderRadius.circular(5.0),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
ElevatedButton(
|
),
|
||||||
onPressed: () async {
|
),
|
||||||
if (textController.text.isEmpty) {
|
ElevatedButton(
|
||||||
buildSnackbar(context, "Can't submit an empty reply");
|
onPressed: () async {
|
||||||
return;
|
if (textController.text.isEmpty) {
|
||||||
}
|
buildSnackbar(context, "Can't submit an empty reply");
|
||||||
final othersMessages =
|
return;
|
||||||
thread.messages.where((m) => m.senderId != yourId);
|
}
|
||||||
if (othersMessages.isEmpty) {
|
final othersMessages =
|
||||||
buildSnackbar(
|
thread.messages.where((m) => m.senderId != yourId);
|
||||||
context, "Have to wait for a response before sending");
|
if (othersMessages.isEmpty) {
|
||||||
return;
|
buildSnackbar(
|
||||||
}
|
context, "Have to wait for a response before sending");
|
||||||
await service
|
return;
|
||||||
.newReplyMessage(
|
}
|
||||||
thread.parentUri,
|
await service
|
||||||
othersMessages.last,
|
.newReplyMessage(
|
||||||
textController.text,
|
othersMessages.last,
|
||||||
)
|
textController.text,
|
||||||
.match(onSuccess: (_) {
|
)
|
||||||
setState(() {
|
.match(onSuccess: (_) {
|
||||||
textController.clear();
|
setState(() {
|
||||||
});
|
textController.clear();
|
||||||
}, onError: (error) {
|
});
|
||||||
if (mounted) {
|
}, onError: (error) {
|
||||||
buildSnackbar(context, error.message);
|
if (mounted) {
|
||||||
}
|
buildSnackbar(context, error.message);
|
||||||
});
|
}
|
||||||
},
|
});
|
||||||
child: const Text('Submit'),
|
},
|
||||||
),
|
child: const Text('Submit'),
|
||||||
const VerticalPadding(),
|
),
|
||||||
],
|
const VerticalPadding(),
|
||||||
));
|
],
|
||||||
},
|
));
|
||||||
onError: (error) => Center(
|
|
||||||
child: Text('Error getting thread: $error'),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
@ -7,27 +8,26 @@ import '../controls/responsive_max_width.dart';
|
||||||
import '../controls/standard_appbar.dart';
|
import '../controls/standard_appbar.dart';
|
||||||
import '../controls/status_and_refresh_button.dart';
|
import '../controls/status_and_refresh_button.dart';
|
||||||
import '../globals.dart';
|
import '../globals.dart';
|
||||||
|
import '../models/auth/profile.dart';
|
||||||
|
import '../riverpod_controllers/direct_message_services.dart';
|
||||||
import '../routes.dart';
|
import '../routes.dart';
|
||||||
import '../services/direct_message_service.dart';
|
import '../services/auth_service.dart';
|
||||||
import '../services/network_status_service.dart';
|
import '../services/network_status_service.dart';
|
||||||
import '../utils/active_profile_selector.dart';
|
|
||||||
import '../utils/dateutils.dart';
|
import '../utils/dateutils.dart';
|
||||||
|
|
||||||
class MessagesScreen extends StatelessWidget {
|
class MessagesScreen extends ConsumerWidget {
|
||||||
const MessagesScreen({super.key});
|
const MessagesScreen({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final service = context
|
final profile = context.watch<AccountsService>().currentProfile;
|
||||||
.watch<ActiveProfileSelector<DirectMessageService>>()
|
final service = ref.watch(directMessageThreadIdsProvider(profile).notifier);
|
||||||
.activeEntry
|
|
||||||
.value;
|
|
||||||
final nss = getIt<NetworkStatusService>();
|
final nss = getIt<NetworkStatusService>();
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: StandardAppBar.build(context, 'Direct Message Threads', actions: [
|
appBar: StandardAppBar.build(context, 'Direct Message Threads', actions: [
|
||||||
StatusAndRefreshButton(
|
StatusAndRefreshButton(
|
||||||
valueListenable: nss.directMessageUpdateStatus,
|
valueListenable: nss.directMessageUpdateStatus,
|
||||||
refreshFunction: () async => await service.updateThreads(),
|
refreshFunction: () async => await service.update(),
|
||||||
busyColor: Theme.of(context).colorScheme.surface,
|
busyColor: Theme.of(context).colorScheme.surface,
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
|
@ -39,24 +39,24 @@ class MessagesScreen extends StatelessWidget {
|
||||||
]),
|
]),
|
||||||
body: RefreshIndicator(
|
body: RefreshIndicator(
|
||||||
onRefresh: () async {
|
onRefresh: () async {
|
||||||
service.updateThreads();
|
await service.update();
|
||||||
},
|
},
|
||||||
child: Center(child: buildBody(context, service)),
|
child: Center(child: buildBody(profile, ref)),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget buildBody(BuildContext context, DirectMessageService service) {
|
Widget buildBody(Profile profile, WidgetRef ref) {
|
||||||
final threads = service.getThreads();
|
final threadIds = ref.watch(directMessageThreadIdsProvider(profile));
|
||||||
threads.sort((t1, t2) =>
|
return threadIds.isEmpty
|
||||||
t2.messages.last.createdAt.compareTo(t1.messages.last.createdAt));
|
|
||||||
return threads.isEmpty
|
|
||||||
? const Text('No Direct Message Threads')
|
? const Text('No Direct Message Threads')
|
||||||
: ResponsiveMaxWidth(
|
: ResponsiveMaxWidth(
|
||||||
child: ListView.separated(
|
child: ListView.separated(
|
||||||
itemCount: threads.length,
|
itemCount: threadIds.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final thread = threads[index];
|
final threadId = threadIds[index];
|
||||||
|
final thread = ref.watch(
|
||||||
|
directMessageThreadServiceProvider(profile, threadId));
|
||||||
final style = thread.allSeen
|
final style = thread.allSeen
|
||||||
? null
|
? null
|
||||||
: const TextStyle(fontWeight: FontWeight.bold);
|
: const TextStyle(fontWeight: FontWeight.bold);
|
||||||
|
|
|
@ -1,17 +1,20 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:multi_trigger_autocomplete/multi_trigger_autocomplete.dart';
|
import 'package:multi_trigger_autocomplete/multi_trigger_autocomplete.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
import '../controls/autocomplete/mention_autocomplete_options.dart';
|
import '../controls/autocomplete/mention_autocomplete_options.dart';
|
||||||
import '../controls/padding.dart';
|
import '../controls/padding.dart';
|
||||||
import '../controls/standard_appbar.dart';
|
import '../controls/standard_appbar.dart';
|
||||||
import '../globals.dart';
|
import '../globals.dart';
|
||||||
|
import '../riverpod_controllers/direct_message_services.dart';
|
||||||
|
import '../services/auth_service.dart';
|
||||||
import '../services/connections_manager.dart';
|
import '../services/connections_manager.dart';
|
||||||
import '../services/direct_message_service.dart';
|
|
||||||
import '../utils/active_profile_selector.dart';
|
import '../utils/active_profile_selector.dart';
|
||||||
import '../utils/snackbar_builder.dart';
|
import '../utils/snackbar_builder.dart';
|
||||||
|
|
||||||
class MessagesNewThread extends StatelessWidget {
|
class MessagesNewThread extends ConsumerWidget {
|
||||||
final receiverController = TextEditingController();
|
final receiverController = TextEditingController();
|
||||||
final replyController = TextEditingController();
|
final replyController = TextEditingController();
|
||||||
final focusNode = FocusNode();
|
final focusNode = FocusNode();
|
||||||
|
@ -19,14 +22,15 @@ class MessagesNewThread extends StatelessWidget {
|
||||||
MessagesNewThread({super.key});
|
MessagesNewThread({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: StandardAppBar.build(context, 'New Thread'),
|
appBar: StandardAppBar.build(context, 'New Thread'),
|
||||||
body: buildBody(context),
|
body: buildBody(context, ref),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget buildBody(BuildContext context) {
|
Widget buildBody(BuildContext context, WidgetRef ref) {
|
||||||
|
final profile = context.watch<AccountsService>().currentProfile;
|
||||||
final border = OutlineInputBorder(
|
final border = OutlineInputBorder(
|
||||||
borderSide: BorderSide(
|
borderSide: BorderSide(
|
||||||
color: Theme.of(context).colorScheme.surface,
|
color: Theme.of(context).colorScheme.surface,
|
||||||
|
@ -88,16 +92,14 @@ class MessagesNewThread extends StatelessWidget {
|
||||||
const VerticalPadding(),
|
const VerticalPadding(),
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
final result =
|
final result = await getIt<
|
||||||
await getIt<ActiveProfileSelector<ConnectionsManager>>()
|
ActiveProfileSelector<ConnectionsManager>>()
|
||||||
.activeEntry
|
.activeEntry
|
||||||
.andThen((manager) => manager.getByHandle(
|
.andThen((manager) => manager.getByHandle(
|
||||||
receiverController.text.trim().substring(1)))
|
receiverController.text.trim().substring(1)))
|
||||||
.andThenAsync((connection) async =>
|
.andThenAsync((connection) async => await ref
|
||||||
getIt<ActiveProfileSelector<DirectMessageService>>()
|
.read(directMessageThreadIdsProvider(profile).notifier)
|
||||||
.activeEntry
|
.newThread(connection, replyController.text));
|
||||||
.andThenAsync((dms) async => dms.newThread(
|
|
||||||
connection, replyController.text)));
|
|
||||||
result.match(onSuccess: (_) {
|
result.match(onSuccess: (_) {
|
||||||
if (context.canPop()) {
|
if (context.canPop()) {
|
||||||
context.pop();
|
context.pop();
|
||||||
|
|
|
@ -1,167 +0,0 @@
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:logging/logging.dart';
|
|
||||||
import 'package:result_monad/result_monad.dart';
|
|
||||||
|
|
||||||
import '../friendica_client/friendica_client.dart';
|
|
||||||
import '../friendica_client/paging_data.dart';
|
|
||||||
import '../globals.dart';
|
|
||||||
import '../models/auth/oauth_credentials.dart';
|
|
||||||
import '../models/auth/profile.dart';
|
|
||||||
import '../models/connection.dart';
|
|
||||||
import '../models/direct_message.dart';
|
|
||||||
import '../models/direct_message_thread.dart';
|
|
||||||
import '../models/exec_error.dart';
|
|
||||||
import 'feature_version_checker.dart';
|
|
||||||
|
|
||||||
class DirectMessageService extends ChangeNotifier {
|
|
||||||
static final _logger = Logger('$DirectMessageService');
|
|
||||||
final _threads = <String, DirectMessageThread>{};
|
|
||||||
final Profile profile;
|
|
||||||
var _firstLoading = true;
|
|
||||||
|
|
||||||
DirectMessageService(this.profile);
|
|
||||||
|
|
||||||
void clear() {
|
|
||||||
_threads.clear();
|
|
||||||
_firstLoading = true;
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
List<DirectMessageThread> getThreads({bool unreadyOnly = false}) {
|
|
||||||
if (_threads.isEmpty && _firstLoading) {
|
|
||||||
updateThreads();
|
|
||||||
_firstLoading = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (unreadyOnly) {
|
|
||||||
return _threads.values.where((t) => !t.allSeen).toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
return _threads.values.toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
Result<DirectMessageThread, ExecError> getThreadByParentUri(String uri) {
|
|
||||||
if (_threads.containsKey(uri)) {
|
|
||||||
return Result.ok(_threads[uri]!);
|
|
||||||
}
|
|
||||||
|
|
||||||
return buildErrorResult(
|
|
||||||
type: ErrorType.notFound, message: 'Thread ID not found: $uri');
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> updateThreads() async {
|
|
||||||
await DirectMessagingClient(profile).getDirectMessages(PagingData()).match(
|
|
||||||
onSuccess: (update) {
|
|
||||||
final newThreads = DirectMessageThread.createThreads(update);
|
|
||||||
_threads.clear();
|
|
||||||
for (final t in newThreads) {
|
|
||||||
//TODO do merge operation
|
|
||||||
_threads[t.parentUri] = t;
|
|
||||||
}
|
|
||||||
_logger.fine(
|
|
||||||
'Updated ${update.length} direct messages, across ${newThreads.length} threads');
|
|
||||||
notifyListeners();
|
|
||||||
},
|
|
||||||
onError: (error) {
|
|
||||||
_logger.severe('Error getting direct messages: $error');
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
FutureResult<DirectMessage, ExecError> newThread(
|
|
||||||
Connection receiver, String text) async {
|
|
||||||
if (profile.credentials is OAuthCredentials) {
|
|
||||||
final result = getIt<FriendicaVersionChecker>()
|
|
||||||
.canUseFeatureResult(RelaticaFeatures.directMessageCreation);
|
|
||||||
if (result.isFailure) {
|
|
||||||
return result.errorCast();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final result = await DirectMessagingClient(profile).postDirectMessage(
|
|
||||||
null,
|
|
||||||
receiver.id,
|
|
||||||
text,
|
|
||||||
);
|
|
||||||
result.match(onSuccess: (newMessage) {
|
|
||||||
DirectMessageThread.createThreads([newMessage]).forEach((thread) {
|
|
||||||
_threads[thread.parentUri] = thread;
|
|
||||||
});
|
|
||||||
notifyListeners();
|
|
||||||
}, onError: (error) {
|
|
||||||
_logger.severe('Error getting direct messages: $error');
|
|
||||||
});
|
|
||||||
|
|
||||||
return result.execErrorCast();
|
|
||||||
}
|
|
||||||
|
|
||||||
FutureResult<DirectMessage, ExecError> newReplyMessage(
|
|
||||||
String threadId, DirectMessage original, String text) async {
|
|
||||||
final thread = _threads[threadId];
|
|
||||||
if (thread == null) {
|
|
||||||
final error = 'Message is not for this thread: $threadId, $original';
|
|
||||||
_logger.severe(error);
|
|
||||||
return buildErrorResult(
|
|
||||||
type: ErrorType.notFound,
|
|
||||||
message: error,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!thread.messages.contains(original)) {
|
|
||||||
final error = 'Message is not for this thread: $threadId, $original';
|
|
||||||
_logger.severe(error);
|
|
||||||
return buildErrorResult(
|
|
||||||
type: ErrorType.notFound,
|
|
||||||
message: error,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (profile.credentials is OAuthCredentials) {
|
|
||||||
final result = getIt<FriendicaVersionChecker>()
|
|
||||||
.canUseFeatureResult(RelaticaFeatures.directMessageCreation);
|
|
||||||
if (result.isFailure) {
|
|
||||||
return result.errorCast();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final result = await DirectMessagingClient(profile).postDirectMessage(
|
|
||||||
original.id,
|
|
||||||
original.senderId,
|
|
||||||
text,
|
|
||||||
);
|
|
||||||
result.match(onSuccess: (newMessage) {
|
|
||||||
thread.messages.add(newMessage);
|
|
||||||
notifyListeners();
|
|
||||||
}, onError: (error) {
|
|
||||||
_logger.severe('Error getting direct messages: $error');
|
|
||||||
});
|
|
||||||
|
|
||||||
return result.execErrorCast();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> markMessageRead(String threadId, DirectMessage m) async {
|
|
||||||
final thread = _threads[threadId];
|
|
||||||
if (thread == null) {
|
|
||||||
_logger.severe('Message is not for this thread: $threadId, $m');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final oldIndex = thread.messages.indexOf(m);
|
|
||||||
if (oldIndex < 0) {
|
|
||||||
_logger.severe('Message is not for this thread: $threadId, $m');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await DirectMessagingClient(profile).markDirectMessageRead(m).match(
|
|
||||||
onSuccess: (update) {
|
|
||||||
thread.messages.removeAt(oldIndex);
|
|
||||||
thread.messages.insert(oldIndex, update);
|
|
||||||
notifyListeners();
|
|
||||||
},
|
|
||||||
onError: (error) {
|
|
||||||
_logger.severe('Error getting direct messages: $error');
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -14,7 +14,6 @@ import '../models/user_notification.dart';
|
||||||
import '../serializers/mastodon/follow_request_mastodon_extensions.dart';
|
import '../serializers/mastodon/follow_request_mastodon_extensions.dart';
|
||||||
import '../utils/active_profile_selector.dart';
|
import '../utils/active_profile_selector.dart';
|
||||||
import 'auth_service.dart';
|
import 'auth_service.dart';
|
||||||
import 'direct_message_service.dart';
|
|
||||||
import 'feature_version_checker.dart';
|
import 'feature_version_checker.dart';
|
||||||
import 'follow_requests_manager.dart';
|
import 'follow_requests_manager.dart';
|
||||||
import 'network_status_service.dart';
|
import 'network_status_service.dart';
|
||||||
|
@ -184,40 +183,42 @@ class NotificationsManager extends ChangeNotifier {
|
||||||
|
|
||||||
List<UserNotification> buildUnreadMessageNotifications(
|
List<UserNotification> buildUnreadMessageNotifications(
|
||||||
bool useActualRequests) {
|
bool useActualRequests) {
|
||||||
final myId = profile.userId;
|
// TODO Re-wire into DMS once this is converted to Riverpod Controller
|
||||||
final dmsResult = getIt<ActiveProfileSelector<DirectMessageService>>()
|
// final myId = profile.userId;
|
||||||
.getForProfile(profile)
|
// final dmsResult = getIt<ActiveProfileSelector<DirectMessageService>>()
|
||||||
.transform((d) => d.getThreads(unreadyOnly: true).map((t) {
|
// .getForProfile(profile)
|
||||||
final fromAccount =
|
// .transform((d) => d.getThreads(unreadyOnly: true).map((t) {
|
||||||
t.participants.firstWhere((p) => p.id != myId);
|
// final fromAccount =
|
||||||
final latestMessage = t.messages
|
// t.participants.firstWhere((p) => p.id != myId);
|
||||||
.reduce((s, m) => s.createdAt > m.createdAt ? s : m);
|
// final latestMessage = t.messages
|
||||||
return UserNotification(
|
// .reduce((s, m) => s.createdAt > m.createdAt ? s : m);
|
||||||
id: (fromAccount.hashCode ^
|
// return UserNotification(
|
||||||
t.parentUri.hashCode ^
|
// id: (fromAccount.hashCode ^
|
||||||
t.title.hashCode)
|
// t.parentUri.hashCode ^
|
||||||
.toString(),
|
// t.title.hashCode)
|
||||||
type: NotificationType.direct_message,
|
// .toString(),
|
||||||
fromId: fromAccount.id,
|
// type: NotificationType.direct_message,
|
||||||
fromName: fromAccount.name,
|
// fromId: fromAccount.id,
|
||||||
fromUrl: fromAccount.profileUrl,
|
// fromName: fromAccount.name,
|
||||||
timestamp: latestMessage.createdAt,
|
// fromUrl: fromAccount.profileUrl,
|
||||||
iid: t.parentUri,
|
// timestamp: latestMessage.createdAt,
|
||||||
dismissed: false,
|
// iid: t.parentUri,
|
||||||
content: '${fromAccount.name} sent you a direct message',
|
// dismissed: false,
|
||||||
link: '');
|
// content: '${fromAccount.name} sent you a direct message',
|
||||||
}).toList())
|
// link: '');
|
||||||
.getValueOrElse(() => []);
|
// }).toList())
|
||||||
|
// .getValueOrElse(() => []);
|
||||||
|
|
||||||
final followRequestResult = !useActualRequests
|
final followRequestResult = !useActualRequests
|
||||||
? []
|
? <UserNotification>[]
|
||||||
: getIt<ActiveProfileSelector<FollowRequestsManager>>()
|
: getIt<ActiveProfileSelector<FollowRequestsManager>>()
|
||||||
.getForProfile(profile)
|
.getForProfile(profile)
|
||||||
.transform(
|
.transform(
|
||||||
(fm) => fm.requests.map((r) => r.toUserNotification()).toList())
|
(fm) => fm.requests.map((r) => r.toUserNotification()).toList())
|
||||||
.getValueOrElse(() => []);
|
.getValueOrElse(() => []);
|
||||||
|
|
||||||
return [...dmsResult, ...followRequestResult];
|
// return [...dmsResult, ...followRequestResult];
|
||||||
|
return followRequestResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
void updateNotification(UserNotification notification) {}
|
void updateNotification(UserNotification notification) {}
|
||||||
|
@ -229,9 +230,9 @@ class NotificationsManager extends ChangeNotifier {
|
||||||
getIt<NetworkStatusService>().startNotificationUpdate();
|
getIt<NetworkStatusService>().startNotificationUpdate();
|
||||||
if (DateTime.now().difference(lastDmsUpdate) >
|
if (DateTime.now().difference(lastDmsUpdate) >
|
||||||
minimumDmsAndCrsUpdateDuration) {
|
minimumDmsAndCrsUpdateDuration) {
|
||||||
await getIt<ActiveProfileSelector<DirectMessageService>>()
|
// await getIt<ActiveProfileSelector<DirectMessageService>>()
|
||||||
.getForProfile(profile)
|
// .getForProfile(profile)
|
||||||
.transformAsync((dms) async => await dms.updateThreads());
|
// .transformAsync((dms) async => await dms.updateThreads());
|
||||||
lastDmsUpdate = DateTime.now();
|
lastDmsUpdate = DateTime.now();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue