Add profile changing to connections

This commit is contained in:
Hank Grabowski 2023-03-11 20:50:31 -05:00
parent b01ea9b95e
commit 4a3c4bd4fd
17 changed files with 192 additions and 83 deletions

View file

@ -1,11 +1,15 @@
import 'package:flutter/material.dart';
import 'package:logging/logging.dart';
import '../../globals.dart';
import '../../models/connection.dart';
import '../../services/connections_manager.dart';
import '../../utils/active_profile_selector.dart';
import '../image_control.dart';
class MentionAutocompleteOptions extends StatelessWidget {
static final _logger = Logger('$MentionAutocompleteOptions');
const MentionAutocompleteOptions({
Key? key,
required this.query,
@ -17,7 +21,15 @@ class MentionAutocompleteOptions extends StatelessWidget {
@override
Widget build(BuildContext context) {
final users = getIt<ConnectionsManager>().getKnownUsersByName(query);
final users = getIt<ActiveProfileSelector<ConnectionsManager>>()
.activeEntry
.andThenSuccess((manager) => manager.getKnownUsersByName(query))
.fold(
onSuccess: (users) => users,
onError: (error) {
_logger.severe('Error getting users list: $error');
return [];
});
if (users.isEmpty) return const SizedBox.shrink();

View file

@ -55,8 +55,10 @@ class NotificationControl extends StatelessWidget {
},
);
final fromIcon =
getIt<ConnectionsManager>().getById(notification.fromId).fold(
final fromIcon = getIt<ActiveProfileSelector<ConnectionsManager>>()
.activeEntry
.andThen((manager) => manager.getById(notification.fromId))
.fold(
onSuccess: (connection) => ImageControl(
imageUrl: connection.avatarUrl.toString(),
iconOverride: const Icon(Icons.person),

View file

@ -1,5 +1,7 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:logging/logging.dart';
import 'package:relatica/utils/active_profile_selector.dart';
import '../../globals.dart';
import '../../models/connection.dart';
@ -11,6 +13,7 @@ import '../image_control.dart';
import '../padding.dart';
class StatusHeaderControl extends StatelessWidget {
static final _logger = Logger('$StatusHeaderControl');
final TimelineEntry entry;
const StatusHeaderControl({
@ -24,13 +27,24 @@ class StatusHeaderControl extends StatelessWidget {
@override
Widget build(BuildContext context) {
final author = getIt<ConnectionsManager>()
.getById(entry.authorId)
.getValueOrElse(() => Connection());
late final Connection author;
late final Connection reshareAuthor;
final reshareAuthor = getIt<ConnectionsManager>()
getIt<ActiveProfileSelector<ConnectionsManager>>().activeEntry.match(
onSuccess: (manager) {
author =
manager.getById(entry.authorId).getValueOrElse(() => Connection());
reshareAuthor = manager
.getById(entry.reshareAuthorId)
.getValueOrElse(() => Connection());
},
onError: (error) {
_logger.severe('Error getting connections manageR: $error');
author = Connection();
reshareAuthor = Connection();
},
);
return Wrap(
children: [
ImageControl(

View file

@ -1,5 +1,6 @@
import 'dart:io';
import 'package:logging/logging.dart';
import 'package:objectbox/objectbox.dart';
import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart';
@ -11,13 +12,15 @@ class ObjectBoxCache {
ObjectBoxCache._create(this.store);
static Future<ObjectBoxCache> create() async {
static Future<ObjectBoxCache> create(
{String baseDir = 'objectboxcache', String? subDir}) async {
final docsDir = await getApplicationSupportDirectory();
final path = p.join(docsDir.path, 'objectboxcache');
final path = p.join(docsDir.path, baseDir, subDir);
Directory(path).createSync(recursive: true);
_logger.info('ObjectBoxCache path: $path');
final store =
await openStore(directory: path, macosApplicationGroup: 'T69YZGT58U.relatica');
final store = await openStore(
directory: path, macosApplicationGroup: 'T69YZGT58U.relatica');
return ObjectBoxCache._create(store);
}
}

View file

@ -1,6 +1,5 @@
import 'package:result_monad/result_monad.dart';
import '../../globals.dart';
import '../../models/connection.dart';
import '../../models/exec_error.dart';
import '../../objectbox.g.dart';
@ -12,8 +11,8 @@ class ObjectBoxConnectionsRepo implements IConnectionsRepo {
late final Box<Connection> box;
final memCache = MemoryConnectionsRepo();
ObjectBoxConnectionsRepo() {
box = getIt<ObjectBoxCache>().store.box<Connection>();
ObjectBoxConnectionsRepo(ObjectBoxCache cache) {
box = cache.store.box<Connection>();
}
@override

View file

@ -9,6 +9,7 @@ import 'data/objectbox/objectbox_cache.dart';
import 'data/objectbox/objectbox_connections_repo.dart';
import 'data/objectbox/objectbox_hashtag_repo.dart';
import 'globals.dart';
import 'models/auth/profile.dart';
import 'services/auth_service.dart';
import 'services/connections_manager.dart';
import 'services/direct_message_service.dart';
@ -36,6 +37,9 @@ Future<void> dependencyInjectionInitialization() async {
getIt.registerLazySingleton<NetworkStatusService>(
() => NetworkStatusService());
getIt.registerSingleton<ActiveProfileSelector<IConnectionsRepo>>(
ActiveProfileSelector(null));
final secretsService = SecretsService();
final serviceInit = await secretsService.initialize();
final authService = AccountsService(secretsService);
@ -47,20 +51,47 @@ Future<void> dependencyInjectionInitialization() async {
final objectBoxCache = await ObjectBoxCache.create();
getIt.registerSingleton<ObjectBoxCache>(objectBoxCache);
getIt.registerSingleton<IConnectionsRepo>(ObjectBoxConnectionsRepo());
getIt.registerSingleton<IHashtagRepo>(ObjectBoxHashtagRepo());
getIt.registerSingleton<IGroupsRepo>(MemoryGroupsRepo());
getIt.registerLazySingleton<ConnectionsManager>(() => ConnectionsManager());
getIt.registerSingleton<ActiveProfileSelector<IGroupsRepo>>(
ActiveProfileSelector((p) => MemoryGroupsRepo()));
getIt.registerSingleton<ActiveProfileSelector<ConnectionsManager>>(
ActiveProfileSelector(
(p) => ConnectionsManager(
getIt<ActiveProfileSelector<IConnectionsRepo>>().getForProfile(p).value,
getIt<ActiveProfileSelector<IGroupsRepo>>().getForProfile(p).value,
),
));
getIt.registerLazySingleton<HashtagService>(() => HashtagService());
//TODO wrap it ActiveProfileSelector
getIt.registerSingleton(galleryService);
//TODO wrap it ActiveProfileSelector
getIt.registerSingleton<EntryManagerService>(entryManagerService);
getIt.registerSingleton<AccountsService>(authService);
//TODO wrap it ActiveProfileSelector
getIt.registerSingleton<TimelineManager>(timelineManager);
getIt.registerLazySingleton<MediaUploadAttachmentHelper>(
() => MediaUploadAttachmentHelper());
getIt.registerLazySingleton<ActiveProfileSelector<NotificationsManager>>(
() => ActiveProfileSelector((_) => NotificationsManager()));
//TODO wrap it ActiveProfileSelector
getIt.registerLazySingleton<DirectMessageService>(
() => DirectMessageService());
//TODO wrap it ActiveProfileSelector
getIt.registerLazySingleton<InteractionsManager>(() => InteractionsManager());
}
Future<void> updateProfileDependencyInjectors(Profile profile) async {
final objectBox = await ObjectBoxCache.create(
baseDir: 'profileboxcaches',
subDir: '${profile.username}_${profile.serverName}',
);
final connectionReposSelector =
getIt<ActiveProfileSelector<IConnectionsRepo>>();
connectionReposSelector.injectEntry(
profile, ObjectBoxConnectionsRepo(objectBox));
}

View file

@ -57,8 +57,9 @@ class App extends StatelessWidget {
create: (_) => getIt<AccountsService>(),
lazy: true,
),
ChangeNotifierProvider<ConnectionsManager>(
create: (_) => getIt<ConnectionsManager>(),
ChangeNotifierProvider<ActiveProfileSelector<ConnectionsManager>>(
create: (_) =>
getIt<ActiveProfileSelector<ConnectionsManager>>(),
lazy: true,
),
ChangeNotifierProvider<EntryManagerService>(

View file

@ -1,9 +1,13 @@
import 'package:logging/logging.dart';
import 'package:relatica/utils/active_profile_selector.dart';
import '../globals.dart';
import '../services/connections_manager.dart';
import 'connection.dart';
import 'direct_message.dart';
class DirectMessageThread {
static final _logger = Logger('$DirectMessageThread');
final List<DirectMessage> messages;
final List<Connection> participants;
@ -27,7 +31,13 @@ class DirectMessageThread {
static List<DirectMessageThread> createThreads(
Iterable<DirectMessage> messages) {
final cm = getIt<ConnectionsManager>();
final cm =
getIt<ActiveProfileSelector<ConnectionsManager>>().activeEntry.fold(
onSuccess: (m) => m,
onError: (error) {
_logger.severe('Error getting connection manager: $error');
return null;
});
final threads = <String, List<DirectMessage>>{};
for (final m in messages) {
final thread = threads.putIfAbsent(m.parentUri, () => []);
@ -49,9 +59,9 @@ class DirectMessageThread {
}
}
final participants = participantIds
.map((pid) => cm.getById(pid))
.where((pr) => pr.isSuccess)
.map((pr) => pr.value)
.map((pid) => cm?.getById(pid))
.where((pr) => pr != null && pr.isSuccess)
.map((pr) => pr!.value)
.toList();
final thread = DirectMessageThread(
messages: threadMessages,

View file

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:logging/logging.dart';
import 'package:provider/provider.dart';
import 'package:relatica/utils/active_profile_selector.dart';
import '../controls/app_bottom_nav_bar.dart';
import '../controls/padding.dart';
@ -24,7 +25,10 @@ class _ContactsScreenState extends State<ContactsScreen> {
@override
Widget build(BuildContext context) {
final nss = getIt<NetworkStatusService>();
final manager = context.watch<ConnectionsManager>();
final manager = context
.watch<ActiveProfileSelector<ConnectionsManager>>()
.activeEntry
.value;
final allContacts = manager.getMyContacts();
final filterTextLC = filterText.toLowerCase();
final contacts = allContacts

View file

@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:multi_trigger_autocomplete/multi_trigger_autocomplete.dart';
import 'package:relatica/utils/active_profile_selector.dart';
import '../controls/autocomplete/mention_autocomplete_options.dart';
import '../controls/padding.dart';
@ -82,8 +83,11 @@ class MessagesNewThread extends StatelessWidget {
const VerticalPadding(),
ElevatedButton(
onPressed: () async {
final result = await getIt<ConnectionsManager>()
.getByHandle(receiverController.text.trim().substring(1))
final result =
await getIt<ActiveProfileSelector<ConnectionsManager>>()
.activeEntry
.andThen((manager) => manager.getByHandle(
receiverController.text.trim().substring(1)))
.andThenAsync((connection) async =>
getIt<DirectMessageService>()
.newThread(connection, replyController.text));

View file

@ -369,10 +369,7 @@ class _SignInScreenState extends State<SignInScreen> {
}
final result = await getIt<AccountsService>().signIn(creds);
if (!mounted) {
return;
}
if (result.isFailure) {
if (mounted && result.isFailure) {
buildSnackbar(context, 'Error signing in: ${result.error}');
return;
}

View file

@ -1,4 +1,5 @@
import 'package:logging/logging.dart';
import 'package:relatica/utils/active_profile_selector.dart';
import '../../globals.dart';
import '../../models/direct_message.dart';
@ -40,7 +41,9 @@ extension DirectMessageFriendicaExtension on DirectMessage {
final String parentUri = json['friendica_parent_uri'];
final cm = getIt<ConnectionsManager>();
getIt<ActiveProfileSelector<ConnectionsManager>>()
.activeEntry
.andThenSuccess((cm) {
if (getIt<AccountsService>().currentProfile.userId != senderId) {
final s = ConnectionFriendicaExtensions.fromJson(json['sender']);
if (cm.getById(s.id).isFailure) {
@ -54,6 +57,7 @@ extension DirectMessageFriendicaExtension on DirectMessage {
cm.addConnection(r);
}
}
});
return DirectMessage(
id: id,

View file

@ -1,4 +1,5 @@
import 'package:logging/logging.dart';
import 'package:relatica/utils/active_profile_selector.dart';
import '../../globals.dart';
import '../../models/user_notification.dart';
@ -23,7 +24,9 @@ extension NotificationMastodonExtension on UserNotification {
final type = NotificationType.parse(json['type']);
final from = ConnectionMastodonExtensions.fromJson(json['account']);
getIt<ConnectionsManager>().addConnection(from);
getIt<ActiveProfileSelector<ConnectionsManager>>()
.activeEntry
.andThenSuccess((manager) => manager.addConnection(from));
var statusId = '';
var statusLink = '';
var content = '';
@ -58,6 +61,9 @@ extension NotificationMastodonExtension on UserNotification {
: '';
content = "$baseContent $shareInfo $referenceType: ${status.body}";
break;
case NotificationType.direct_message:
// this is a Relatica internal type so nothing to do here
break;
}
return UserNotification(

View file

@ -1,4 +1,5 @@
import 'package:logging/logging.dart';
import 'package:relatica/utils/active_profile_selector.dart';
import '../../globals.dart';
import '../../models/engagement_summary.dart';
@ -56,16 +57,22 @@ extension TimelineEntryMastodonExtensions on TimelineEntry {
repliesCount: repliesCount,
);
final connectionManager = getIt<ConnectionsManager>();
final connectionManager =
getIt<ActiveProfileSelector<ConnectionsManager>>().activeEntry.fold(
onSuccess: (m) => m,
onError: (error) {
_logger.severe('Error getting connection manager: $error');
return null;
});
final connection = ConnectionMastodonExtensions.fromJson(json['account']);
connectionManager.addConnection(connection);
connectionManager?.addConnection(connection);
late final String reshareAuthor;
late final String reshareAuthorId;
if (json['reblog'] != null) {
final rebloggedUser =
ConnectionMastodonExtensions.fromJson(json['reblog']['account']);
connectionManager.addConnection(rebloggedUser);
connectionManager?.addConnection(rebloggedUser);
reshareAuthor = rebloggedUser.name;
reshareAuthorId = rebloggedUser.id;
} else {

View file

@ -2,6 +2,7 @@ import 'dart:collection';
import 'package:flutter/foundation.dart';
import 'package:logging/logging.dart';
import 'package:relatica/di_initialization.dart';
import 'package:relatica/services/secrets_service.dart';
import 'package:result_monad/result_monad.dart';
import 'package:shared_preferences/shared_preferences.dart';
@ -86,6 +87,7 @@ class AccountsService extends ChangeNotifier {
_loggedInProfiles.add(loginProfile);
_loggedOutProfiles.remove(loginProfile);
await secretsService.addOrUpdateProfile(loginProfile);
await updateProfileDependencyInjectors(loginProfile);
if (withNotification) {
notifyListeners();
}

View file

@ -20,10 +20,7 @@ class ConnectionsManager extends ChangeNotifier {
late final IConnectionsRepo conRepo;
late final IGroupsRepo groupsRepo;
ConnectionsManager() {
conRepo = getIt<IConnectionsRepo>();
groupsRepo = getIt<IGroupsRepo>();
}
ConnectionsManager(this.conRepo, this.groupsRepo);
bool addConnection(Connection connection) {
return conRepo.addConnection(connection);

View file

@ -6,14 +6,16 @@ import '../globals.dart';
import '../models/auth/profile.dart';
import '../models/exec_error.dart';
class ActiveProfileSelector<T extends ChangeNotifier> extends ChangeNotifier {
class ActiveProfileSelector<T> extends ChangeNotifier {
final _entries = <Profile, T>{};
final T Function(Profile p) _entryBuilder;
final T Function(Profile p)? _entryBuilder;
ActiveProfileSelector(T Function(Profile p) entryBuilder)
ActiveProfileSelector(T Function(Profile p)? entryBuilder)
: _entryBuilder = entryBuilder;
bool get canCreateOnDemand => _entryBuilder != null;
Result<T, ExecError> get activeEntry {
final service = getIt<AccountsService>();
if (!service.loggedIn) {
@ -23,16 +25,30 @@ class ActiveProfileSelector<T extends ChangeNotifier> extends ChangeNotifier {
);
}
final p = service.currentProfile;
return getForProfile(service.currentProfile);
}
Result<T, ExecError> getForProfile(Profile p) {
return runCatching(() {
final entry = _entries.putIfAbsent(p, () => _buildNewEntry(p));
return Result.ok(entry).execErrorCast();
}).execErrorCast();
}
bool injectEntry(Profile p, T entry) {
if (_entries.containsKey(p)) {
return false;
}
_entries[p] = entry;
return true;
}
T _buildNewEntry(Profile p) {
final newEntry = _entryBuilder(p);
final newEntry = _entryBuilder!(p);
if (newEntry is ChangeNotifier) {
newEntry.addListener(() => notifyListeners());
}
return newEntry;
}
}