relatica/lib/services/connections_manager.dart

416 wiersze
13 KiB
Dart

import 'dart:collection';
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:logging/logging.dart';
import 'package:result_monad/result_monad.dart';
import '../data/interfaces/connections_repo_intf.dart';
import '../data/interfaces/groups_repo.intf.dart';
import '../friendica_client/friendica_client.dart';
import '../friendica_client/paging_data.dart';
import '../globals.dart';
import '../models/connection.dart';
import '../models/exec_error.dart';
import '../models/group_data.dart';
import 'auth_service.dart';
class ConnectionsManager extends ChangeNotifier {
static final _logger = Logger('$ConnectionsManager');
late final IConnectionsRepo conRepo;
late final IGroupsRepo groupsRepo;
ConnectionsManager(this.conRepo, this.groupsRepo);
List<Connection> getKnownUsersByName(String name) {
return conRepo.getKnownUsersByName(name);
}
bool upsertConnection(Connection connection) {
if (connection.status != ConnectionStatus.unknown) {
return conRepo.upsertConnection(connection);
}
return conRepo.getById(connection.id).fold(
onSuccess: (original) => conRepo.upsertConnection(
connection.copy(status: original.status),
),
onError: (_) => conRepo.upsertConnection(connection),
);
}
bool upsertAllConnections(Iterable<Connection> newConnections) {
newConnections.forEach(upsertConnection);
return true;
}
Future<void> acceptFollowRequest(Connection connection) async {
_logger.finest(
'Attempting to accept follow request ${connection.name}: ${connection.status}');
await RelationshipsClient(getIt<AccountsService>().currentProfile)
.acceptFollow(connection)
.match(
onSuccess: (update) {
_logger
.finest('Successfully followed ${update.name}: ${update.status}');
upsertConnection(update);
notifyListeners();
},
onError: (error) {
_logger.severe('Error following ${connection.name}: $error');
},
);
}
Future<void> rejectFollowRequest(Connection connection) async {
_logger.finest(
'Attempting to accept follow request ${connection.name}: ${connection.status}');
await RelationshipsClient(getIt<AccountsService>().currentProfile)
.rejectFollow(connection)
.match(
onSuccess: (update) {
_logger
.finest('Successfully followed ${update.name}: ${update.status}');
upsertConnection(update);
notifyListeners();
},
onError: (error) {
_logger.severe('Error following ${connection.name}: $error');
},
);
}
Future<void> ignoreFollowRequest(Connection connection) async {
_logger.finest(
'Attempting to accept follow request ${connection.name}: ${connection.status}');
await RelationshipsClient(getIt<AccountsService>().currentProfile)
.ignoreFollow(connection)
.match(
onSuccess: (update) {
_logger
.finest('Successfully followed ${update.name}: ${update.status}');
upsertConnection(update);
notifyListeners();
},
onError: (error) {
_logger.severe('Error following ${connection.name}');
},
);
}
Future<void> follow(Connection connection) async {
_logger.finest(
'Attempting to follow ${connection.name}: ${connection.status}');
await RelationshipsClient(getIt<AccountsService>().currentProfile)
.followConnection(connection)
.match(
onSuccess: (update) {
_logger
.finest('Successfully followed ${update.name}: ${update.status}');
upsertConnection(update);
notifyListeners();
},
onError: (error) {
_logger.severe('Error following ${connection.name}');
},
);
}
Future<void> unfollow(Connection connection) async {
_logger.finest(
'Attempting to unfollow ${connection.name}: ${connection.status}');
await RelationshipsClient(getIt<AccountsService>().currentProfile)
.unFollowConnection(connection)
.match(
onSuccess: (update) {
_logger
.finest('Successfully unfollowed ${update.name}: ${update.status}');
upsertConnection(update);
notifyListeners();
},
onError: (error) {
_logger.severe('Error following ${connection.name}');
},
);
}
List<Connection> getMyContacts() {
return conRepo.getMyContacts();
}
Future<void> updateAllContacts() async {
_logger.fine('Updating all contacts');
final client = RelationshipsClient(getIt<AccountsService>().currentProfile);
final results = <String, Connection>{};
var moreResults = true;
var maxId = -1;
const limit = 50;
var currentPage = PagingData(limit: limit);
final originalContacts = conRepo.getMyContacts().toSet();
while (moreResults) {
await client.getMyFollowers(currentPage).match(onSuccess: (followers) {
for (final f in followers.data) {
originalContacts.remove(f);
results[f.id] = f.copy(status: ConnectionStatus.theyFollowYou);
int id = int.parse(f.id);
maxId = max(maxId, id);
}
if (followers.next != null) {
currentPage = followers.next!;
}
moreResults = followers.next != null;
}, onError: (error) {
_logger.severe('Error getting followers data: $error');
});
}
moreResults = true;
currentPage = PagingData(limit: limit);
while (moreResults) {
await client.getMyFollowing(currentPage).match(onSuccess: (following) {
for (final f in following.data) {
originalContacts.remove(f);
if (results.containsKey(f.id)) {
results[f.id] = f.copy(status: ConnectionStatus.mutual);
} else {
results[f.id] = f.copy(status: ConnectionStatus.youFollowThem);
}
int id = int.parse(f.id);
maxId = max(maxId, id);
}
if (following.next != null) {
currentPage = following.next!;
}
moreResults = following.next != null;
}, onError: (error) {
_logger.severe('Error getting followers data: $error');
});
}
for (final noLongerFollowed in originalContacts) {
results[noLongerFollowed.id] =
noLongerFollowed.copy(status: ConnectionStatus.none);
}
upsertAllConnections(results.values);
final myContacts = conRepo.getMyContacts().toList();
myContacts.sort((c1, c2) => c1.name.compareTo(c2.name));
_logger.fine('# Contacts:${myContacts.length}');
notifyListeners();
}
List<GroupData> getMyGroups() {
final myGroups = groupsRepo.getMyGroups();
if (myGroups.isEmpty) {
_updateMyGroups(true);
}
return myGroups;
}
Result<List<Connection>, ExecError> getGroupMembers(GroupData group) {
return groupsRepo
.getGroupMembers(group)
.transform(
(members) => members
..sort((c1, c2) =>
c1.name.toLowerCase().compareTo(c2.name.toLowerCase())),
)
.execErrorCast();
}
FutureResult<GroupData, ExecError> createGroup(String newName) async {
final result = await GroupsClient(getIt<AccountsService>().currentProfile)
.createGroup(newName)
.withResultAsync((newGroup) async {
groupsRepo.upsertGroup(newGroup);
notifyListeners();
});
return result.execErrorCast();
}
FutureResult<GroupData, ExecError> renameGroup(
String id, String newName) async {
final result = await GroupsClient(getIt<AccountsService>().currentProfile)
.renameGroup(id, newName)
.withResultAsync((renamedGroup) async {
groupsRepo.upsertGroup(renamedGroup);
notifyListeners();
});
return result.execErrorCast();
}
FutureResult<bool, ExecError> deleteGroup(GroupData groupData) async {
final result = await GroupsClient(getIt<AccountsService>().currentProfile)
.deleteGroup(groupData)
.withResultAsync((_) async {
groupsRepo.deleteGroup(groupData);
notifyListeners();
});
return result.execErrorCast();
}
void refreshGroups() {
_updateMyGroups(true);
}
Future<void> refreshGroupMemberships(GroupData group) async {
var page = PagingData(limit: 50);
final client = GroupsClient(getIt<AccountsService>().currentProfile);
final allResults = <Connection>{};
var moreResults = true;
while (moreResults) {
await client.getGroupMembers(group, page).match(onSuccess: (results) {
moreResults = results.data.isNotEmpty && results.next != null;
page = results.next ?? page;
allResults.addAll(results.data);
}, onError: (error) {
_logger.severe('Error getting group listing data: $error');
moreResults = false;
});
}
groupsRepo.deleteGroup(group);
groupsRepo.upsertGroup(group);
for (final c in allResults) {
upsertConnection(c);
groupsRepo.addConnectionToGroup(group, c);
}
notifyListeners();
}
Result<List<GroupData>, ExecError> getGroupsForUser(String id) {
final result = groupsRepo.getGroupsForUser(id);
if (result.isSuccess) {
return result;
}
if (result.isFailure && result.error.type != ErrorType.notFound) {
return result;
}
_refreshGroupListData(id, true);
return Result.ok(UnmodifiableListView([]));
}
FutureResult<bool, ExecError> addUserToGroup(
GroupData group, Connection connection) async {
_logger.finest('Adding ${connection.name} to group: ${group.name}');
final result = await GroupsClient(getIt<AccountsService>().currentProfile)
.addConnectionToGroup(group, connection);
result.match(
onSuccess: (_) => _refreshGroupListData(connection.id, true),
onError: (error) {
_logger
.severe('Error adding ${connection.name} to group: ${group.name}');
},
);
return result.execErrorCast();
}
FutureResult<bool, ExecError> removeUserFromGroup(
GroupData group, Connection connection) async {
_logger.finest('Removing ${connection.name} from group: ${group.name}');
return GroupsClient(getIt<AccountsService>().currentProfile)
.removeConnectionFromGroup(group, connection)
.withResultAsync((_) async => refreshGroupMemberships(group))
.withResultAsync((_) async => notifyListeners())
.mapError(
(error) {
_logger.severe(
'Error removing ${connection.name} from group: ${group.name}');
return error;
},
);
}
Result<Connection, ExecError> getById(String id) {
return conRepo.getById(id).andThenSuccess((c) {
if (c.status == ConnectionStatus.unknown) {
_refreshConnection(c, true);
}
return c;
}).execErrorCast();
}
Result<Connection, ExecError> getByName(String name) {
return conRepo.getByName(name).andThenSuccess((c) {
if (c.status == ConnectionStatus.unknown) {
_refreshConnection(c, true);
}
return c;
}).execErrorCast();
}
Result<Connection, ExecError> getByHandle(String handle) {
return conRepo.getByHandle(handle).andThenSuccess((c) {
if (c.status == ConnectionStatus.unknown) {
_refreshConnection(c, true);
}
return c;
}).execErrorCast();
}
Future<void> fullRefresh(Connection connection) async {
await _updateMyGroups(false);
await _refreshGroupListData(connection.id, false);
await _refreshConnection(connection, false);
notifyListeners();
}
Future<void> _refreshGroupListData(String id, bool withNotification) async {
_logger.finest('Refreshing member list data for Connection $id');
await GroupsClient(getIt<AccountsService>().currentProfile)
.getMemberGroupsForConnection(id)
.match(
onSuccess: (groups) {
groupsRepo.updateConnectionGroupData(id, groups);
if (withNotification) {
notifyListeners();
}
},
onError: (error) {
_logger.severe('Error getting list data for $id: $error');
},
);
}
Future<void> _refreshConnection(
Connection connection, bool withNotification) async {
_logger.finest('Refreshing connection data for ${connection.name}');
await RelationshipsClient(getIt<AccountsService>().currentProfile)
.getConnectionWithStatus(connection)
.match(
onSuccess: (update) {
upsertConnection(update);
if (withNotification) {
notifyListeners();
}
},
onError: (error) {
_logger.severe('Error getting updates for ${connection.name}: $error');
},
);
}
Future<void> _updateMyGroups(bool withNotification) async {
_logger.finest('Refreshing my groups list');
await GroupsClient(getIt<AccountsService>().currentProfile)
.getGroups()
.match(
onSuccess: (groups) {
_logger.finest('Got updated groups:${groups.map((e) => e.name)}');
groupsRepo.clearMyGroups();
groupsRepo.addAllGroups(groups);
if (withNotification) {
notifyListeners();
}
},
onError: (error) {
_logger.severe('Error getting my groups: $error');
},
);
}
}