mirror of
https://gitlab.com/mysocialportal/relatica
synced 2024-10-18 13:33:32 +00:00
Refactored auth system to support multiple logins (hypothetically)
This commit is contained in:
parent
d2f5c347bc
commit
a6bf00aea4
23 changed files with 490 additions and 305 deletions
|
@ -1,5 +1,4 @@
|
|||
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';
|
||||
|
@ -9,7 +8,6 @@ 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/TimelineIdentifiers.dart';
|
||||
import 'services/auth_service.dart';
|
||||
import 'services/connections_manager.dart';
|
||||
import 'services/direct_message_service.dart';
|
||||
|
@ -27,15 +25,24 @@ import 'services/timeline_manager.dart';
|
|||
final _logger = Logger('DI_Init');
|
||||
|
||||
Future<void> dependencyInjectionInitialization() async {
|
||||
final authService = AccountsService();
|
||||
final secretsService = SecretsService();
|
||||
final entryManagerService = EntryManagerService();
|
||||
final timelineManager = TimelineManager();
|
||||
final galleryService = GalleryService();
|
||||
|
||||
final service = SettingsService();
|
||||
await service.initialize();
|
||||
getIt.registerSingleton<SettingsService>(service);
|
||||
final settingsService = SettingsService();
|
||||
await settingsService.initialize();
|
||||
getIt.registerSingleton<SettingsService>(settingsService);
|
||||
getIt.registerLazySingleton<NetworkStatusService>(
|
||||
() => NetworkStatusService());
|
||||
|
||||
final secretsService = SecretsService();
|
||||
final serviceInit = await secretsService.initialize();
|
||||
final authService = AccountsService(secretsService);
|
||||
if (serviceInit.isFailure) {
|
||||
_logger.severe('Error initializing credentials');
|
||||
} else {
|
||||
await authService.initialize();
|
||||
}
|
||||
|
||||
final objectBoxCache = await ObjectBoxCache.create();
|
||||
getIt.registerSingleton<ObjectBoxCache>(objectBoxCache);
|
||||
|
@ -46,7 +53,6 @@ Future<void> dependencyInjectionInitialization() async {
|
|||
getIt.registerLazySingleton<HashtagService>(() => HashtagService());
|
||||
getIt.registerSingleton(galleryService);
|
||||
getIt.registerSingleton<EntryManagerService>(entryManagerService);
|
||||
getIt.registerSingleton<SecretsService>(secretsService);
|
||||
getIt.registerSingleton<AccountsService>(authService);
|
||||
getIt.registerSingleton<TimelineManager>(timelineManager);
|
||||
getIt.registerLazySingleton<MediaUploadAttachmentHelper>(
|
||||
|
@ -55,24 +61,5 @@ Future<void> dependencyInjectionInitialization() async {
|
|||
() => NotificationsManager());
|
||||
getIt.registerLazySingleton<DirectMessageService>(
|
||||
() => DirectMessageService());
|
||||
getIt.registerLazySingleton<NetworkStatusService>(
|
||||
() => NetworkStatusService());
|
||||
getIt.registerLazySingleton<InteractionsManager>(() => InteractionsManager());
|
||||
|
||||
await secretsService.initialize().andThenSuccessAsync((credentials) async {
|
||||
if (credentials.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
final wasLoggedIn = await authService.getStoredLoginState();
|
||||
if (wasLoggedIn) {
|
||||
final result = await authService.signIn(credentials);
|
||||
if (result.isSuccess) {
|
||||
timelineManager.updateTimeline(
|
||||
TimelineIdentifiers.home(), TimelineRefreshType.loadOlder);
|
||||
}
|
||||
} else {
|
||||
_logger.severe('Was not logged in');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ import 'package:result_monad/result_monad.dart';
|
|||
import '../friendica_client/paged_response.dart';
|
||||
import '../globals.dart';
|
||||
import '../models/TimelineIdentifiers.dart';
|
||||
import '../models/auth/credentials.dart';
|
||||
import '../models/auth/profile.dart';
|
||||
import '../models/connection.dart';
|
||||
import '../models/direct_message.dart';
|
||||
import '../models/exec_error.dart';
|
||||
|
@ -309,7 +309,7 @@ class RelationshipsClient extends FriendicaClient {
|
|||
PagingData page) async {
|
||||
_logger.finest(() => 'Getting following with paging data $page');
|
||||
_networkStatusService.startConnectionUpdateStatus();
|
||||
final myId = credentials.userId;
|
||||
final myId = profile.userId;
|
||||
final baseUrl = 'https://$serverName/api/v1/accounts/$myId';
|
||||
final result = await _getApiListRequest(
|
||||
Uri.parse('$baseUrl/following?${page.toQueryParameters()}'),
|
||||
|
@ -326,7 +326,7 @@ class RelationshipsClient extends FriendicaClient {
|
|||
PagingData page) async {
|
||||
_logger.finest(() => 'Getting followers data with page data $page');
|
||||
_networkStatusService.startConnectionUpdateStatus();
|
||||
final myId = credentials.userId;
|
||||
final myId = profile.userId;
|
||||
final baseUrl = 'https://$serverName/api/v1/accounts/$myId';
|
||||
final result1 = await _getApiListRequest(
|
||||
Uri.parse('$baseUrl/followers&${page.toQueryParameters()}'),
|
||||
|
@ -344,7 +344,7 @@ class RelationshipsClient extends FriendicaClient {
|
|||
Connection connection) async {
|
||||
_logger.finest(() => 'Getting group (Mastodon List) data');
|
||||
_networkStatusService.startConnectionUpdateStatus();
|
||||
final myId = credentials.userId;
|
||||
final myId = profile.userId;
|
||||
final id = int.parse(connection.id);
|
||||
final paging = '?min_id=${id - 1}&max_id=${id + 1}';
|
||||
final baseUrl = 'https://$serverName/api/v1/accounts/$myId';
|
||||
|
@ -491,7 +491,7 @@ class RemoteFileClient extends FriendicaClient {
|
|||
final response = await http.get(
|
||||
url,
|
||||
headers: {
|
||||
'Authorization': _credentials.authHeaderValue,
|
||||
'Authorization': _profile.credentials.authHeaderValue,
|
||||
},
|
||||
);
|
||||
|
||||
|
@ -515,7 +515,7 @@ class RemoteFileClient extends FriendicaClient {
|
|||
}) async {
|
||||
final postUri = Uri.parse('https://$serverName/api/friendica/photo/create');
|
||||
final request = http.MultipartRequest('POST', postUri);
|
||||
request.headers['Authorization'] = _credentials.authHeaderValue;
|
||||
request.headers['Authorization'] = _profile.credentials.authHeaderValue;
|
||||
request.fields['desc'] = description;
|
||||
request.fields['album'] = album;
|
||||
request.files.add(await http.MultipartFile.fromBytes(
|
||||
|
@ -721,7 +721,7 @@ class TimelineClient extends FriendicaClient {
|
|||
case TimelineType.profile:
|
||||
return '/accounts/${type.auxData}/statuses';
|
||||
case TimelineType.self:
|
||||
final myId = credentials.userId;
|
||||
final myId = profile.userId;
|
||||
return '/accounts/$myId/statuses';
|
||||
}
|
||||
}
|
||||
|
@ -743,15 +743,15 @@ class TimelineClient extends FriendicaClient {
|
|||
abstract class FriendicaClient {
|
||||
static final _logger = Logger('$FriendicaClient');
|
||||
|
||||
final Credentials _credentials;
|
||||
final Profile _profile;
|
||||
|
||||
late final NetworkStatusService _networkStatusService;
|
||||
|
||||
String get serverName => _credentials.serverName;
|
||||
String get serverName => _profile.serverName;
|
||||
|
||||
Credentials get credentials => _credentials;
|
||||
Profile get profile => _profile;
|
||||
|
||||
FriendicaClient(Credentials credentials) : _credentials = credentials {
|
||||
FriendicaClient(Profile credentials) : _profile = credentials {
|
||||
_networkStatusService = getIt<NetworkStatusService>();
|
||||
}
|
||||
|
||||
|
@ -761,7 +761,7 @@ abstract class FriendicaClient {
|
|||
final response = await http.get(
|
||||
url,
|
||||
headers: {
|
||||
'Authorization': _credentials.authHeaderValue,
|
||||
'Authorization': _profile.credentials.authHeaderValue,
|
||||
'Content-Type': 'application/json; charset=UTF-8'
|
||||
},
|
||||
);
|
||||
|
@ -788,7 +788,7 @@ abstract class FriendicaClient {
|
|||
final response = await http.post(
|
||||
url,
|
||||
headers: {
|
||||
'Authorization': _credentials.authHeaderValue,
|
||||
'Authorization': _profile.credentials.authHeaderValue,
|
||||
'Content-Type': 'application/json; charset=UTF-8'
|
||||
},
|
||||
body: jsonEncode(body),
|
||||
|
@ -813,7 +813,7 @@ abstract class FriendicaClient {
|
|||
final response = await http.delete(
|
||||
url,
|
||||
headers: {
|
||||
'Authorization': _credentials.authHeaderValue,
|
||||
'Authorization': _profile.credentials.authHeaderValue,
|
||||
'Content-Type': 'application/json; charset=UTF-8'
|
||||
},
|
||||
body: jsonEncode(body),
|
||||
|
|
75
lib/models/auth/basic_credentials.dart
Normal file
75
lib/models/auth/basic_credentials.dart
Normal file
|
@ -0,0 +1,75 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:relatica/models/auth/credentials_intf.dart';
|
||||
import 'package:relatica/models/exec_error.dart';
|
||||
import 'package:result_monad/src/result_monad_base.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
class BasicCredentials extends ICredentials {
|
||||
late final String id;
|
||||
final String username;
|
||||
final String password;
|
||||
final String serverName;
|
||||
late final String _authHeaderValue;
|
||||
|
||||
@override
|
||||
String get authHeaderValue => _authHeaderValue;
|
||||
|
||||
BasicCredentials({
|
||||
String? id,
|
||||
required this.username,
|
||||
required this.password,
|
||||
required this.serverName,
|
||||
}) {
|
||||
this.id = id ?? const Uuid().v4();
|
||||
final authenticationString = "$username:$password";
|
||||
final encodedAuthString = base64Encode(utf8.encode(authenticationString));
|
||||
_authHeaderValue = "Basic $encodedAuthString";
|
||||
}
|
||||
|
||||
factory BasicCredentials.fromJson(Map<String, dynamic> json) =>
|
||||
BasicCredentials(
|
||||
username: json['username'],
|
||||
password: json['password'],
|
||||
serverName: json['serverName'],
|
||||
);
|
||||
|
||||
factory BasicCredentials.empty() => BasicCredentials(
|
||||
username: '',
|
||||
password: '',
|
||||
serverName: '',
|
||||
);
|
||||
|
||||
bool get isEmpty =>
|
||||
username.isEmpty && password.isEmpty && serverName.isEmpty;
|
||||
|
||||
BasicCredentials copy({
|
||||
String? username,
|
||||
String? password,
|
||||
String? serverName,
|
||||
bool? loggedIn,
|
||||
}) {
|
||||
return BasicCredentials(
|
||||
username: username ?? this.username,
|
||||
password: password ?? this.password,
|
||||
serverName: serverName ?? this.serverName,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'Credentials{username: $username, password?: ${password.isNotEmpty}, serverName: $serverName}';
|
||||
}
|
||||
|
||||
@override
|
||||
FutureResult<ICredentials, ExecError> signIn() async {
|
||||
return Result.ok(this);
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() => {
|
||||
'username': username,
|
||||
'password': password,
|
||||
'serverName': serverName,
|
||||
};
|
||||
}
|
|
@ -1,62 +0,0 @@
|
|||
import 'dart:convert';
|
||||
|
||||
class Credentials {
|
||||
final String username;
|
||||
final String password;
|
||||
final String serverName;
|
||||
final String userId;
|
||||
final String avatar;
|
||||
late final String _authHeaderValue;
|
||||
|
||||
String get authHeaderValue => _authHeaderValue;
|
||||
|
||||
Credentials({
|
||||
required this.username,
|
||||
required this.password,
|
||||
required this.serverName,
|
||||
required this.userId,
|
||||
required this.avatar,
|
||||
}) {
|
||||
final authenticationString = "$username:$password";
|
||||
final encodedAuthString = base64Encode(utf8.encode(authenticationString));
|
||||
_authHeaderValue = "Basic $encodedAuthString";
|
||||
}
|
||||
|
||||
factory Credentials.empty() => Credentials(
|
||||
username: '',
|
||||
password: '',
|
||||
serverName: '',
|
||||
userId: '',
|
||||
avatar: '',
|
||||
);
|
||||
|
||||
bool get isEmpty =>
|
||||
username.isEmpty &&
|
||||
password.isEmpty &&
|
||||
serverName.isEmpty &&
|
||||
userId.isEmpty &&
|
||||
avatar.isEmpty;
|
||||
|
||||
String get handle => '$username@$serverName';
|
||||
|
||||
Credentials copy({
|
||||
String? username,
|
||||
String? password,
|
||||
String? serverName,
|
||||
String? userId,
|
||||
String? avatar,
|
||||
}) {
|
||||
return Credentials(
|
||||
username: username ?? this.username,
|
||||
password: password ?? this.password,
|
||||
serverName: serverName ?? this.serverName,
|
||||
userId: userId ?? this.userId,
|
||||
avatar: avatar ?? this.avatar,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'Credentials{username: $username, password?: ${password.isNotEmpty}, serverName: $serverName, userId: $userId}';
|
||||
}
|
||||
}
|
15
lib/models/auth/credentials_intf.dart
Normal file
15
lib/models/auth/credentials_intf.dart
Normal file
|
@ -0,0 +1,15 @@
|
|||
import 'package:result_monad/result_monad.dart';
|
||||
|
||||
import '../exec_error.dart';
|
||||
|
||||
abstract class ICredentials {
|
||||
String get authHeaderValue;
|
||||
|
||||
String get serverName;
|
||||
|
||||
String get id;
|
||||
|
||||
FutureResult<ICredentials, ExecError> signIn();
|
||||
|
||||
Map<String, dynamic> toJson();
|
||||
}
|
78
lib/models/auth/profile.dart
Normal file
78
lib/models/auth/profile.dart
Normal file
|
@ -0,0 +1,78 @@
|
|||
import 'package:uuid/uuid.dart';
|
||||
|
||||
import 'credentials_intf.dart';
|
||||
|
||||
class Profile {
|
||||
final ICredentials credentials;
|
||||
final String username;
|
||||
final String userId;
|
||||
final String avatar;
|
||||
final String serverName;
|
||||
final String id;
|
||||
final bool loggedIn;
|
||||
|
||||
String get handle => '$username@$serverName';
|
||||
|
||||
Profile({
|
||||
String? id,
|
||||
required this.credentials,
|
||||
required this.username,
|
||||
required this.userId,
|
||||
required this.avatar,
|
||||
required this.serverName,
|
||||
required this.loggedIn,
|
||||
}) : id = id ?? const Uuid().v4();
|
||||
|
||||
factory Profile.credentialsOnly(ICredentials credentials) => Profile(
|
||||
credentials: credentials,
|
||||
username: '',
|
||||
userId: '',
|
||||
avatar: '',
|
||||
serverName: credentials.serverName,
|
||||
loggedIn: false,
|
||||
);
|
||||
|
||||
factory Profile.fromJson(
|
||||
Map<String, dynamic> json,
|
||||
ICredentials Function(Map<String, dynamic> json) credentialsFromJson,
|
||||
) {
|
||||
final credentials = credentialsFromJson(json['credentials']);
|
||||
return Profile(
|
||||
credentials: credentials,
|
||||
username: json['username'],
|
||||
userId: json['userId'],
|
||||
avatar: json['avatar'],
|
||||
serverName: json['serverName'],
|
||||
loggedIn: json['loggedIn'],
|
||||
id: json['id'],
|
||||
);
|
||||
}
|
||||
|
||||
Profile copyWithLoginUpdate(bool status) => Profile(
|
||||
credentials: credentials,
|
||||
username: username,
|
||||
userId: userId,
|
||||
avatar: avatar,
|
||||
serverName: serverName,
|
||||
id: id,
|
||||
loggedIn: status,
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'credentials': credentials.toJson(),
|
||||
'username': username,
|
||||
'userId': userId,
|
||||
'avatar': avatar,
|
||||
'serverName': serverName,
|
||||
'loggedIn': loggedIn,
|
||||
'id': id,
|
||||
};
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is Profile && runtimeType == other.runtimeType && id == other.id;
|
||||
|
||||
@override
|
||||
int get hashCode => id.hashCode;
|
||||
}
|
|
@ -46,7 +46,7 @@ class _ImageViewerScreenState extends State<ImageViewerScreen> {
|
|||
final appsDir = await getApplicationDocumentsDirectory();
|
||||
final filename = p.basename(attachment.fullFileUri.path);
|
||||
final bytesResult =
|
||||
await RemoteFileClient(getIt<AccountsService>().currentCredentials)
|
||||
await RemoteFileClient(getIt<AccountsService>().currentProfile)
|
||||
.getFileBytes(attachment.uri);
|
||||
if (bytesResult.isFailure && mounted) {
|
||||
buildSnackbar(context,
|
||||
|
|
|
@ -5,7 +5,6 @@ import '../controls/app_bottom_nav_bar.dart';
|
|||
import '../data/interfaces/connections_repo_intf.dart';
|
||||
import '../globals.dart';
|
||||
import '../routes.dart';
|
||||
import '../services/auth_service.dart';
|
||||
import '../services/notifications_manager.dart';
|
||||
import '../services/timeline_manager.dart';
|
||||
|
||||
|
@ -31,12 +30,6 @@ class MenusScreen extends StatelessWidget {
|
|||
getIt<NotificationsManager>().clear();
|
||||
}
|
||||
}),
|
||||
buildMenuButton('Logout', () async {
|
||||
final confirm = await showYesNoDialog(context, 'Log out account?');
|
||||
if (confirm == true) {
|
||||
await getIt<AccountsService>().signOut();
|
||||
}
|
||||
}),
|
||||
];
|
||||
return Scaffold(
|
||||
body: Center(
|
||||
|
|
|
@ -47,9 +47,8 @@ class _MessageThreadScreenState extends State<MessageThreadScreen> {
|
|||
) {
|
||||
return result.fold(
|
||||
onSuccess: (thread) {
|
||||
final yourId = getIt<AccountsService>().currentCredentials.userId;
|
||||
final yourAvatarUrl =
|
||||
getIt<AccountsService>().currentCredentials.avatar;
|
||||
final yourId = getIt<AccountsService>().currentProfile.userId;
|
||||
final yourAvatarUrl = getIt<AccountsService>().currentProfile.avatar;
|
||||
final participants =
|
||||
Map.fromEntries(thread.participants.map((p) => MapEntry(p.id, p)));
|
||||
return Center(
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:relatica/controls/padding.dart';
|
||||
|
||||
import '../controls/standard_appbar.dart';
|
||||
import '../globals.dart';
|
||||
import '../services/auth_service.dart';
|
||||
|
||||
class ProfileScreen extends StatefulWidget {
|
||||
|
@ -15,13 +17,24 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final authService = context.watch<AccountsService>();
|
||||
final profile = authService.currentProfile;
|
||||
return Scaffold(
|
||||
appBar: StandardAppBar.build(context, 'Profile'),
|
||||
body: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text('Profile: ${authService.currentCredentials.handle}'),
|
||||
Text('Profile: ${profile.handle}'),
|
||||
const VerticalPadding(),
|
||||
ElevatedButton(
|
||||
onPressed: () async {
|
||||
final confirm =
|
||||
await showYesNoDialog(context, 'Log out account?');
|
||||
if (confirm == true) {
|
||||
await getIt<AccountsService>().signOut(profile);
|
||||
}
|
||||
},
|
||||
child: const Text('Logout'))
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:relatica/models/auth/basic_credentials.dart';
|
||||
import 'package:string_validator/string_validator.dart';
|
||||
|
||||
import '../controls/padding.dart';
|
||||
import '../globals.dart';
|
||||
import '../models/auth/credentials.dart';
|
||||
import '../services/auth_service.dart';
|
||||
import '../services/secrets_service.dart';
|
||||
import '../utils/snackbar_builder.dart';
|
||||
|
||||
class SignInScreen extends StatefulWidget {
|
||||
|
@ -23,15 +22,14 @@ class _SignInScreenState extends State<SignInScreen> {
|
|||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
getIt<SecretsService>().credentials.andThenSuccess((credentials) {
|
||||
if (credentials.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
usernameController.text = credentials.username;
|
||||
passwordController.text = credentials.password;
|
||||
serverNameController.text = credentials.serverName;
|
||||
});
|
||||
final profiles = getIt<AccountsService>().loggedOutProfiles;
|
||||
if (profiles.isEmpty) {
|
||||
return;
|
||||
}
|
||||
final credentials = profiles.first.credentials as BasicCredentials;
|
||||
usernameController.text = credentials.username;
|
||||
passwordController.text = credentials.password;
|
||||
serverNameController.text = credentials.serverName;
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -56,14 +54,14 @@ class _SignInScreenState extends State<SignInScreen> {
|
|||
isFQDN(value ?? '') ? null : 'Not a valid server name',
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.alternate_email),
|
||||
hintText: 'Username (user@example.com)',
|
||||
hintText: 'Server Name (friendica.example.com)',
|
||||
border: OutlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: Theme.of(context).backgroundColor,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(5.0),
|
||||
),
|
||||
labelText: 'Username',
|
||||
labelText: 'Server Name',
|
||||
),
|
||||
),
|
||||
const VerticalPadding(),
|
||||
|
@ -139,12 +137,10 @@ class _SignInScreenState extends State<SignInScreen> {
|
|||
|
||||
void _signIn(BuildContext context) async {
|
||||
if (formKey.currentState?.validate() ?? false) {
|
||||
final creds = Credentials(
|
||||
final creds = BasicCredentials(
|
||||
username: usernameController.text,
|
||||
password: passwordController.text,
|
||||
serverName: serverNameController.text,
|
||||
userId: '',
|
||||
avatar: '');
|
||||
serverName: serverNameController.text);
|
||||
|
||||
final result = await getIt<AccountsService>().signIn(creds);
|
||||
if (result.isFailure) {
|
||||
|
|
|
@ -43,7 +43,7 @@ class _UserProfileScreenState extends State<UserProfileScreen> {
|
|||
final manager = context.watch<ConnectionsManager>();
|
||||
final body = manager.getById(widget.userId).fold(onSuccess: (profile) {
|
||||
final notMyProfile =
|
||||
getIt<AccountsService>().currentCredentials.userId != profile.id;
|
||||
getIt<AccountsService>().currentProfile.userId != profile.id;
|
||||
|
||||
return RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
|
|
|
@ -41,14 +41,14 @@ extension DirectMessageFriendicaExtension on DirectMessage {
|
|||
final String parentUri = json['friendica_parent_uri'];
|
||||
|
||||
final cm = getIt<ConnectionsManager>();
|
||||
if (getIt<AccountsService>().currentCredentials.userId != senderId) {
|
||||
if (getIt<AccountsService>().currentProfile.userId != senderId) {
|
||||
final s = ConnectionFriendicaExtensions.fromJson(json['sender']);
|
||||
if (cm.getById(s.id).isFailure) {
|
||||
cm.addConnection(s);
|
||||
}
|
||||
}
|
||||
|
||||
if (getIt<AccountsService>().currentCredentials.userId != recipientId) {
|
||||
if (getIt<AccountsService>().currentProfile.userId != recipientId) {
|
||||
final r = ConnectionFriendicaExtensions.fromJson(json['recipient']);
|
||||
if (cm.getById(r.id).isFailure) {
|
||||
cm.addConnection(r);
|
||||
|
|
|
@ -20,7 +20,7 @@ extension ConnectionMastodonExtensions on Connection {
|
|||
if (handleFromJson.contains('@')) {
|
||||
handle = handleFromJson;
|
||||
} else {
|
||||
final server = getIt<AccountsService>().currentCredentials.serverName;
|
||||
final server = getIt<AccountsService>().currentProfile.serverName;
|
||||
handle = '$handleFromJson@$server';
|
||||
}
|
||||
|
||||
|
|
|
@ -1,64 +1,145 @@
|
|||
import 'dart:collection';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:relatica/services/secrets_service.dart';
|
||||
import 'package:result_monad/result_monad.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import '../friendica_client/friendica_client.dart';
|
||||
import '../globals.dart';
|
||||
import '../models/auth/credentials.dart';
|
||||
import '../models/auth/credentials_intf.dart';
|
||||
import '../models/auth/profile.dart';
|
||||
import '../models/exec_error.dart';
|
||||
import 'secrets_service.dart';
|
||||
import 'timeline_manager.dart';
|
||||
|
||||
class AccountsService extends ChangeNotifier {
|
||||
Credentials? _currentCredentials;
|
||||
bool _loggedIn = false;
|
||||
static final _logger = Logger('$AccountsService');
|
||||
Profile? _currentProfile;
|
||||
final _loggedInProfiles = <Profile>{};
|
||||
final _loggedOutProfiles = <Profile>{};
|
||||
|
||||
bool get loggedIn => _loggedIn && _currentCredentials != null;
|
||||
final SecretsService secretsService;
|
||||
|
||||
Credentials get currentCredentials => _currentCredentials!;
|
||||
AccountsService(this.secretsService);
|
||||
|
||||
Future<bool> getStoredLoginState() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
return prefs.getBool('logged-in') ?? false;
|
||||
bool get loggedIn => _currentProfile != null;
|
||||
|
||||
List<Profile> get loggedInProfiles => UnmodifiableListView(_loggedInProfiles);
|
||||
|
||||
List<Profile> get loggedOutProfiles =>
|
||||
UnmodifiableListView(_loggedOutProfiles);
|
||||
|
||||
Profile get currentProfile => _currentProfile!;
|
||||
|
||||
FutureResult<bool, ExecError> initialize() async {
|
||||
final lastActiveProfile = await _getStoredLoginState();
|
||||
|
||||
final result = await runCatchingAsync(() async {
|
||||
for (final p in secretsService.profiles) {
|
||||
if (!p.loggedIn) {
|
||||
_loggedOutProfiles.add(p);
|
||||
}
|
||||
|
||||
final pr = await signIn(p.credentials, withNotification: false);
|
||||
if (pr.isSuccess) {
|
||||
final profile = pr.value;
|
||||
if (profile.id.isNotEmpty && profile.id == lastActiveProfile) {
|
||||
_currentProfile = profile;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_currentProfile == null && _loggedInProfiles.isNotEmpty) {
|
||||
setActiveProfile(_loggedInProfiles.first);
|
||||
}
|
||||
|
||||
return Result.ok(loggedIn);
|
||||
});
|
||||
|
||||
return result.execErrorCast();
|
||||
}
|
||||
|
||||
FutureResult<FriendicaClient, ExecError> signIn(
|
||||
Credentials credentials) async {
|
||||
final client = ProfileClient(credentials);
|
||||
final result = await client.getMyProfile();
|
||||
FutureResult<Profile, ExecError> signIn(ICredentials credentials,
|
||||
{bool withNotification = true}) async {
|
||||
ICredentials? credentialsCache;
|
||||
final result =
|
||||
await credentials.signIn().andThenAsync((signedInCredentials) async {
|
||||
final client =
|
||||
ProfileClient(Profile.credentialsOnly(signedInCredentials));
|
||||
credentialsCache = signedInCredentials;
|
||||
return await client.getMyProfile();
|
||||
}).andThenAsync((profileData) async {
|
||||
final loginProfile = Profile(
|
||||
credentials: credentialsCache!,
|
||||
username: profileData.name,
|
||||
serverName: credentialsCache!.serverName,
|
||||
avatar: profileData.avatarUrl,
|
||||
userId: profileData.id,
|
||||
id: credentials.id,
|
||||
loggedIn: true,
|
||||
);
|
||||
|
||||
if (_loggedInProfiles.isEmpty) {
|
||||
setActiveProfile(loginProfile, withNotification: withNotification);
|
||||
}
|
||||
_loggedInProfiles.add(loginProfile);
|
||||
_loggedOutProfiles.remove(loginProfile);
|
||||
secretsService.addOrUpdateProfile(loginProfile);
|
||||
if (withNotification) {
|
||||
notifyListeners();
|
||||
}
|
||||
return Result.ok(loginProfile);
|
||||
});
|
||||
|
||||
if (result.isFailure) {
|
||||
await clearCredentials();
|
||||
return result.errorCast();
|
||||
_logger.severe('Error signing in: ${result.error}');
|
||||
}
|
||||
|
||||
getIt<SecretsService>().storeCredentials(
|
||||
client.credentials.copy(
|
||||
userId: result.value.id,
|
||||
avatar: result.value.avatarUrl,
|
||||
),
|
||||
);
|
||||
await _setLoginState(true);
|
||||
_currentCredentials = credentials;
|
||||
notifyListeners();
|
||||
return Result.ok(client);
|
||||
return result.execErrorCast();
|
||||
}
|
||||
|
||||
Future signOut() async {
|
||||
await _setLoginState(false);
|
||||
getIt<TimelineManager>().clear();
|
||||
_currentCredentials = null;
|
||||
notifyListeners();
|
||||
Future signOut(Profile profile, {bool withNotification = true}) async {
|
||||
if (_currentProfile == profile) {
|
||||
clearActiveProfile(withNotification: withNotification);
|
||||
}
|
||||
_loggedInProfiles.remove(profile);
|
||||
secretsService.addOrUpdateProfile(profile);
|
||||
|
||||
if (_loggedInProfiles.isNotEmpty) {
|
||||
setActiveProfile(
|
||||
_loggedInProfiles.first,
|
||||
withNotification: withNotification,
|
||||
);
|
||||
}
|
||||
|
||||
if (withNotification) {
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
Future clearCredentials() async {
|
||||
_currentCredentials = null;
|
||||
await _setLoginState(false);
|
||||
notifyListeners();
|
||||
Future clearActiveProfile({bool withNotification = true}) async {
|
||||
_currentProfile = null;
|
||||
if (withNotification) {
|
||||
notifyListeners();
|
||||
}
|
||||
await _saveStoredLoginState();
|
||||
}
|
||||
|
||||
Future<void> _setLoginState(bool state) async {
|
||||
Future<void> setActiveProfile(Profile profile,
|
||||
{bool withNotification = true}) async {
|
||||
_currentProfile = profile;
|
||||
if (withNotification) {
|
||||
notifyListeners();
|
||||
}
|
||||
await _saveStoredLoginState();
|
||||
}
|
||||
|
||||
Future<void> _saveStoredLoginState() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setBool('logged-in', state);
|
||||
_loggedIn = state;
|
||||
await prefs.setString('active_profile_id', _currentProfile?.id ?? '');
|
||||
}
|
||||
|
||||
Future<String> _getStoredLoginState() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
return prefs.getString('active_profile_id') ?? '';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,7 +44,7 @@ class ConnectionsManager extends ChangeNotifier {
|
|||
Future<void> acceptFollowRequest(Connection connection) async {
|
||||
_logger.finest(
|
||||
'Attempting to accept follow request ${connection.name}: ${connection.status}');
|
||||
await RelationshipsClient(getIt<AccountsService>().currentCredentials)
|
||||
await RelationshipsClient(getIt<AccountsService>().currentProfile)
|
||||
.acceptFollow(connection)
|
||||
.match(
|
||||
onSuccess: (update) {
|
||||
|
@ -62,7 +62,7 @@ class ConnectionsManager extends ChangeNotifier {
|
|||
Future<void> rejectFollowRequest(Connection connection) async {
|
||||
_logger.finest(
|
||||
'Attempting to accept follow request ${connection.name}: ${connection.status}');
|
||||
await RelationshipsClient(getIt<AccountsService>().currentCredentials)
|
||||
await RelationshipsClient(getIt<AccountsService>().currentProfile)
|
||||
.rejectFollow(connection)
|
||||
.match(
|
||||
onSuccess: (update) {
|
||||
|
@ -80,7 +80,7 @@ class ConnectionsManager extends ChangeNotifier {
|
|||
Future<void> ignoreFollowRequest(Connection connection) async {
|
||||
_logger.finest(
|
||||
'Attempting to accept follow request ${connection.name}: ${connection.status}');
|
||||
await RelationshipsClient(getIt<AccountsService>().currentCredentials)
|
||||
await RelationshipsClient(getIt<AccountsService>().currentProfile)
|
||||
.ignoreFollow(connection)
|
||||
.match(
|
||||
onSuccess: (update) {
|
||||
|
@ -98,7 +98,7 @@ class ConnectionsManager extends ChangeNotifier {
|
|||
Future<void> follow(Connection connection) async {
|
||||
_logger.finest(
|
||||
'Attempting to follow ${connection.name}: ${connection.status}');
|
||||
await RelationshipsClient(getIt<AccountsService>().currentCredentials)
|
||||
await RelationshipsClient(getIt<AccountsService>().currentProfile)
|
||||
.followConnection(connection)
|
||||
.match(
|
||||
onSuccess: (update) {
|
||||
|
@ -116,7 +116,7 @@ class ConnectionsManager extends ChangeNotifier {
|
|||
Future<void> unfollow(Connection connection) async {
|
||||
_logger.finest(
|
||||
'Attempting to unfollow ${connection.name}: ${connection.status}');
|
||||
await RelationshipsClient(getIt<AccountsService>().currentCredentials)
|
||||
await RelationshipsClient(getIt<AccountsService>().currentProfile)
|
||||
.unFollowConnection(connection)
|
||||
.match(
|
||||
onSuccess: (update) {
|
||||
|
@ -137,8 +137,7 @@ class ConnectionsManager extends ChangeNotifier {
|
|||
|
||||
Future<void> updateAllContacts() async {
|
||||
_logger.fine('Updating all contacts');
|
||||
final client =
|
||||
RelationshipsClient(getIt<AccountsService>().currentCredentials);
|
||||
final client = RelationshipsClient(getIt<AccountsService>().currentProfile);
|
||||
final results = <String, Connection>{};
|
||||
var moreResults = true;
|
||||
var maxId = -1;
|
||||
|
@ -215,9 +214,8 @@ class ConnectionsManager extends ChangeNotifier {
|
|||
FutureResult<bool, ExecError> addUserToGroup(
|
||||
GroupData group, Connection connection) async {
|
||||
_logger.finest('Adding ${connection.name} to group: ${group.name}');
|
||||
final result =
|
||||
await GroupsClient(getIt<AccountsService>().currentCredentials)
|
||||
.addConnectionToGroup(group, connection);
|
||||
final result = await GroupsClient(getIt<AccountsService>().currentProfile)
|
||||
.addConnectionToGroup(group, connection);
|
||||
result.match(
|
||||
onSuccess: (_) => _refreshGroupListData(connection.id, true),
|
||||
onError: (error) {
|
||||
|
@ -232,9 +230,8 @@ class ConnectionsManager extends ChangeNotifier {
|
|||
FutureResult<bool, ExecError> removeUserFromGroup(
|
||||
GroupData group, Connection connection) async {
|
||||
_logger.finest('Removing ${connection.name} from group: ${group.name}');
|
||||
final result =
|
||||
await GroupsClient(getIt<AccountsService>().currentCredentials)
|
||||
.removeConnectionFromGroup(group, connection);
|
||||
final result = await GroupsClient(getIt<AccountsService>().currentProfile)
|
||||
.removeConnectionFromGroup(group, connection);
|
||||
result.match(
|
||||
onSuccess: (_) => _refreshGroupListData(connection.id, true),
|
||||
onError: (error) {
|
||||
|
@ -282,7 +279,7 @@ class ConnectionsManager extends ChangeNotifier {
|
|||
|
||||
Future<void> _refreshGroupListData(String id, bool withNotification) async {
|
||||
_logger.finest('Refreshing member list data for Connection $id');
|
||||
await GroupsClient(getIt<AccountsService>().currentCredentials)
|
||||
await GroupsClient(getIt<AccountsService>().currentProfile)
|
||||
.getMemberGroupsForConnection(id)
|
||||
.match(
|
||||
onSuccess: (groups) {
|
||||
|
@ -300,7 +297,7 @@ class ConnectionsManager extends ChangeNotifier {
|
|||
Future<void> _refreshConnection(
|
||||
Connection connection, bool withNotification) async {
|
||||
_logger.finest('Refreshing connection data for ${connection.name}');
|
||||
await RelationshipsClient(getIt<AccountsService>().currentCredentials)
|
||||
await RelationshipsClient(getIt<AccountsService>().currentProfile)
|
||||
.getConnectionWithStatus(connection)
|
||||
.match(
|
||||
onSuccess: (update) {
|
||||
|
@ -317,7 +314,7 @@ class ConnectionsManager extends ChangeNotifier {
|
|||
|
||||
Future<void> _updateMyGroups(bool withNotification) async {
|
||||
_logger.finest('Refreshing my groups list');
|
||||
await GroupsClient(getIt<AccountsService>().currentCredentials)
|
||||
await GroupsClient(getIt<AccountsService>().currentProfile)
|
||||
.getGroups()
|
||||
.match(
|
||||
onSuccess: (groups) {
|
||||
|
|
|
@ -37,7 +37,7 @@ class DirectMessageService extends ChangeNotifier {
|
|||
}
|
||||
|
||||
Future<void> updateThreads() async {
|
||||
await DirectMessagingClient(getIt<AccountsService>().currentCredentials)
|
||||
await DirectMessagingClient(getIt<AccountsService>().currentProfile)
|
||||
.getDirectMessages(PagingData())
|
||||
.match(
|
||||
onSuccess: (update) {
|
||||
|
@ -62,7 +62,7 @@ class DirectMessageService extends ChangeNotifier {
|
|||
FutureResult<DirectMessage, ExecError> newThread(
|
||||
Connection receiver, String text) async {
|
||||
final result =
|
||||
await DirectMessagingClient(getIt<AccountsService>().currentCredentials)
|
||||
await DirectMessagingClient(getIt<AccountsService>().currentProfile)
|
||||
.postDirectMessage(
|
||||
null,
|
||||
receiver.id,
|
||||
|
@ -102,7 +102,7 @@ class DirectMessageService extends ChangeNotifier {
|
|||
}
|
||||
|
||||
final result =
|
||||
await DirectMessagingClient(getIt<AccountsService>().currentCredentials)
|
||||
await DirectMessagingClient(getIt<AccountsService>().currentProfile)
|
||||
.postDirectMessage(
|
||||
original.id,
|
||||
original.senderId,
|
||||
|
@ -130,7 +130,7 @@ class DirectMessageService extends ChangeNotifier {
|
|||
return;
|
||||
}
|
||||
|
||||
await DirectMessagingClient(getIt<AccountsService>().currentCredentials)
|
||||
await DirectMessagingClient(getIt<AccountsService>().currentProfile)
|
||||
.markDirectMessageRead(m)
|
||||
.match(
|
||||
onSuccess: (update) {
|
||||
|
|
|
@ -43,7 +43,7 @@ class EntryManagerService extends ChangeNotifier {
|
|||
|
||||
Result<EntryTreeItem, ExecError> getPostTreeEntryBy(String id) {
|
||||
_logger.finest('Getting post: $id');
|
||||
final currentId = getIt<AccountsService>().currentCredentials.userId;
|
||||
final currentId = getIt<AccountsService>().currentProfile.userId;
|
||||
final postNode = _getPostRootNode(id);
|
||||
if (postNode == null) {
|
||||
return Result.error(ExecError(
|
||||
|
@ -69,8 +69,8 @@ class EntryManagerService extends ChangeNotifier {
|
|||
FutureResult<bool, ExecError> deleteEntryById(String id) async {
|
||||
_logger.finest('Delete entry: $id');
|
||||
final result =
|
||||
await StatusesClient(getIt<AccountsService>().currentCredentials)
|
||||
.deleteEntryById(id);
|
||||
await StatusesClient(getIt<AccountsService>().currentProfile)
|
||||
.deleteEntryById(id);
|
||||
if (result.isFailure) {
|
||||
return result.errorCast();
|
||||
}
|
||||
|
@ -80,8 +80,7 @@ class EntryManagerService extends ChangeNotifier {
|
|||
return Result.ok(true);
|
||||
}
|
||||
|
||||
FutureResult<bool, ExecError> createNewStatus(
|
||||
String text, {
|
||||
FutureResult<bool, ExecError> createNewStatus(String text, {
|
||||
String spoilerText = '',
|
||||
String inReplyToId = '',
|
||||
required NewEntryMediaItems mediaItems,
|
||||
|
@ -109,12 +108,12 @@ class EntryManagerService extends ChangeNotifier {
|
|||
}
|
||||
|
||||
final uploadResult =
|
||||
await MediaUploadAttachmentHelper.getUploadableImageBytes(
|
||||
await MediaUploadAttachmentHelper.getUploadableImageBytes(
|
||||
item.localFilePath,
|
||||
).andThenAsync(
|
||||
(imageBytes) async =>
|
||||
await RemoteFileClient(getIt<AccountsService>().currentCredentials)
|
||||
.uploadFileAsAttachment(
|
||||
(imageBytes) async =>
|
||||
await RemoteFileClient(getIt<AccountsService>().currentProfile)
|
||||
.uploadFileAsAttachment(
|
||||
bytes: imageBytes,
|
||||
album: mediaItems.albumName,
|
||||
description: item.description,
|
||||
|
@ -131,15 +130,15 @@ class EntryManagerService extends ChangeNotifier {
|
|||
}
|
||||
|
||||
final result =
|
||||
await StatusesClient(getIt<AccountsService>().currentCredentials)
|
||||
.createNewStatus(
|
||||
text: text,
|
||||
spoilerText: spoilerText,
|
||||
inReplyToId: inReplyToId,
|
||||
mediaIds: mediaIds)
|
||||
.andThenSuccessAsync((item) async {
|
||||
await StatusesClient(getIt<AccountsService>().currentProfile)
|
||||
.createNewStatus(
|
||||
text: text,
|
||||
spoilerText: spoilerText,
|
||||
inReplyToId: inReplyToId,
|
||||
mediaIds: mediaIds)
|
||||
.andThenSuccessAsync((item) async {
|
||||
await processNewItems(
|
||||
[item], getIt<AccountsService>().currentCredentials.username, null);
|
||||
[item], getIt<AccountsService>().currentProfile.username, null);
|
||||
return item;
|
||||
}).andThenSuccessAsync((item) async {
|
||||
if (inReplyToId.isNotEmpty) {
|
||||
|
@ -158,7 +157,7 @@ class EntryManagerService extends ChangeNotifier {
|
|||
_logger.finest('${status.id} status created');
|
||||
return true;
|
||||
}).mapError(
|
||||
(error) {
|
||||
(error) {
|
||||
_logger.finest('Error creating post: $error');
|
||||
return ExecError(
|
||||
type: ErrorType.localError,
|
||||
|
@ -171,7 +170,7 @@ class EntryManagerService extends ChangeNotifier {
|
|||
FutureResult<List<EntryTreeItem>, ExecError> updateTimeline(
|
||||
TimelineIdentifiers type, int maxId, int sinceId) async {
|
||||
_logger.fine(() => 'Updating timeline');
|
||||
final client = TimelineClient(getIt<AccountsService>().currentCredentials);
|
||||
final client = TimelineClient(getIt<AccountsService>().currentProfile);
|
||||
final itemsResult = await client.getTimeline(
|
||||
type: type,
|
||||
page: PagingData(
|
||||
|
@ -186,9 +185,11 @@ class EntryManagerService extends ChangeNotifier {
|
|||
|
||||
itemsResult.value.sort((t1, t2) => t1.id.compareTo(t2.id));
|
||||
final updatedPosts = await processNewItems(
|
||||
itemsResult.value, client.credentials.userId, client);
|
||||
itemsResult.value, client.profile.userId, client);
|
||||
_logger.finest(() {
|
||||
final postCount = _entries.values.where((e) => e.parentId.isEmpty).length;
|
||||
final postCount = _entries.values
|
||||
.where((e) => e.parentId.isEmpty)
|
||||
.length;
|
||||
final commentCount = _entries.length - postCount;
|
||||
final orphanCount = _entries.values
|
||||
.where(
|
||||
|
@ -199,11 +200,9 @@ class EntryManagerService extends ChangeNotifier {
|
|||
return Result.ok(updatedPosts);
|
||||
}
|
||||
|
||||
Future<List<EntryTreeItem>> processNewItems(
|
||||
List<TimelineEntry> items,
|
||||
String currentId,
|
||||
FriendicaClient? client,
|
||||
) async {
|
||||
Future<List<EntryTreeItem>> processNewItems(List<TimelineEntry> items,
|
||||
String currentId,
|
||||
FriendicaClient? client,) async {
|
||||
items.sort((i1, i2) => int.parse(i1.id).compareTo(int.parse(i2.id)));
|
||||
final allSeenItems = [...items];
|
||||
for (final item in items) {
|
||||
|
@ -227,10 +226,12 @@ class EntryManagerService extends ChangeNotifier {
|
|||
}
|
||||
|
||||
for (final o in orphans) {
|
||||
await StatusesClient(getIt<AccountsService>().currentCredentials)
|
||||
await StatusesClient(getIt<AccountsService>().currentProfile)
|
||||
.getPostOrComment(o.id, fullContext: true)
|
||||
.andThenSuccessAsync((items) async {
|
||||
final parentPostId = items.firstWhere((e) => e.parentId.isEmpty).id;
|
||||
final parentPostId = items
|
||||
.firstWhere((e) => e.parentId.isEmpty)
|
||||
.id;
|
||||
_parentPostIds[o.id] = parentPostId;
|
||||
allSeenItems.addAll(items);
|
||||
for (final item in items) {
|
||||
|
@ -258,7 +259,7 @@ class EntryManagerService extends ChangeNotifier {
|
|||
for (final item in seenItemsCopy) {
|
||||
if (item.parentId.isEmpty) {
|
||||
final postNode =
|
||||
_postNodes.putIfAbsent(item.id, () => _Node(item.id));
|
||||
_postNodes.putIfAbsent(item.id, () => _Node(item.id));
|
||||
postNodesToReturn.add(postNode);
|
||||
allSeenItems.remove(item);
|
||||
} else {
|
||||
|
@ -300,28 +301,31 @@ class EntryManagerService extends ChangeNotifier {
|
|||
.toList();
|
||||
|
||||
_logger.finest(
|
||||
'Completed processing new items ${client == null ? 'sub level' : 'top level'}');
|
||||
'Completed processing new items ${client == null
|
||||
? 'sub level'
|
||||
: 'top level'}');
|
||||
return updatedPosts;
|
||||
}
|
||||
|
||||
FutureResult<EntryTreeItem, ExecError> refreshStatusChain(String id) async {
|
||||
_logger.finest('Refreshing post: $id');
|
||||
final client = StatusesClient(getIt<AccountsService>().currentCredentials);
|
||||
final client = StatusesClient(getIt<AccountsService>().currentProfile);
|
||||
final result = await client
|
||||
.getPostOrComment(id, fullContext: false)
|
||||
.andThenAsync((rootItems) async => await client
|
||||
.getPostOrComment(id, fullContext: true)
|
||||
.andThenSuccessAsync(
|
||||
(contextItems) async => [...rootItems, ...contextItems]))
|
||||
.andThenAsync((rootItems) async =>
|
||||
await client
|
||||
.getPostOrComment(id, fullContext: true)
|
||||
.andThenSuccessAsync(
|
||||
(contextItems) async => [...rootItems, ...contextItems]))
|
||||
.andThenSuccessAsync((items) async {
|
||||
await processNewItems(items, client.credentials.username, null);
|
||||
await processNewItems(items, client.profile.username, null);
|
||||
});
|
||||
|
||||
return result.mapValue((_) {
|
||||
_logger.finest('$id post updated');
|
||||
return _nodeToTreeItem(_getPostRootNode(id)!, client.credentials.userId);
|
||||
return _nodeToTreeItem(_getPostRootNode(id)!, client.profile.userId);
|
||||
}).mapError(
|
||||
(error) {
|
||||
(error) {
|
||||
_logger.finest('$id error updating: $error');
|
||||
return ExecError(
|
||||
type: ErrorType.localError,
|
||||
|
@ -333,17 +337,17 @@ class EntryManagerService extends ChangeNotifier {
|
|||
|
||||
FutureResult<EntryTreeItem, ExecError> resharePost(String id) async {
|
||||
_logger.finest('Resharing post: $id');
|
||||
final client = StatusesClient(getIt<AccountsService>().currentCredentials);
|
||||
final client = StatusesClient(getIt<AccountsService>().currentProfile);
|
||||
final result =
|
||||
await client.resharePost(id).andThenSuccessAsync((item) async {
|
||||
await processNewItems([item], client.credentials.username, null);
|
||||
await client.resharePost(id).andThenSuccessAsync((item) async {
|
||||
await processNewItems([item], client.profile.username, null);
|
||||
});
|
||||
|
||||
return result.mapValue((_) {
|
||||
_logger.finest('$id post updated after reshare');
|
||||
return _nodeToTreeItem(_postNodes[id]!, client.credentials.userId);
|
||||
return _nodeToTreeItem(_postNodes[id]!, client.profile.userId);
|
||||
}).mapError(
|
||||
(error) {
|
||||
(error) {
|
||||
_logger.finest('$id error updating: $error');
|
||||
return ExecError(
|
||||
type: ErrorType.localError,
|
||||
|
@ -355,10 +359,10 @@ class EntryManagerService extends ChangeNotifier {
|
|||
|
||||
FutureResult<bool, ExecError> unResharePost(String id) async {
|
||||
_logger.finest('Unresharing post: $id');
|
||||
final client = StatusesClient(getIt<AccountsService>().currentCredentials);
|
||||
final client = StatusesClient(getIt<AccountsService>().currentProfile);
|
||||
final result =
|
||||
await client.unResharePost(id).andThenSuccessAsync((item) async {
|
||||
await processNewItems([item], client.credentials.username, null);
|
||||
await client.unResharePost(id).andThenSuccessAsync((item) async {
|
||||
await processNewItems([item], client.profile.username, null);
|
||||
});
|
||||
|
||||
if (result.isFailure) {
|
||||
|
@ -371,10 +375,10 @@ class EntryManagerService extends ChangeNotifier {
|
|||
return Result.ok(true);
|
||||
}
|
||||
|
||||
FutureResult<EntryTreeItem, ExecError> toggleFavorited(
|
||||
String id, bool newStatus) async {
|
||||
FutureResult<EntryTreeItem, ExecError> toggleFavorited(String id,
|
||||
bool newStatus) async {
|
||||
final client =
|
||||
InteractionsClient(getIt<AccountsService>().currentCredentials);
|
||||
InteractionsClient(getIt<AccountsService>().currentProfile);
|
||||
final result = await client.changeFavoriteStatus(id, newStatus);
|
||||
if (result.isFailure) {
|
||||
return result.errorCast();
|
||||
|
@ -387,7 +391,7 @@ class EntryManagerService extends ChangeNotifier {
|
|||
: _postNodes[update.parentId]!.getChildById(update.id)!;
|
||||
|
||||
notifyListeners();
|
||||
return Result.ok(_nodeToTreeItem(node, client.credentials.userId));
|
||||
return Result.ok(_nodeToTreeItem(node, client.profile.userId));
|
||||
}
|
||||
|
||||
EntryTreeItem _nodeToTreeItem(_Node node, String currentId) {
|
||||
|
@ -464,7 +468,7 @@ class _Node {
|
|||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is _Node && runtimeType == other.runtimeType && id == other.id;
|
||||
other is _Node && runtimeType == other.runtimeType && id == other.id;
|
||||
|
||||
@override
|
||||
int get hashCode => id.hashCode;
|
||||
|
|
|
@ -40,9 +40,8 @@ class GalleryService extends ChangeNotifier {
|
|||
}
|
||||
|
||||
FutureResult<List<GalleryData>, ExecError> updateGalleries() async {
|
||||
final result =
|
||||
await GalleryClient(getIt<AccountsService>().currentCredentials)
|
||||
.getGalleryData();
|
||||
final result = await GalleryClient(getIt<AccountsService>().currentProfile)
|
||||
.getGalleryData();
|
||||
if (result.isFailure) {
|
||||
return result.errorCast();
|
||||
}
|
||||
|
@ -92,7 +91,7 @@ class GalleryService extends ChangeNotifier {
|
|||
final pagesToUse = nextPageOnly ? [pages.last] : pages;
|
||||
for (final page in pagesToUse) {
|
||||
final result =
|
||||
await GalleryClient(getIt<AccountsService>().currentCredentials)
|
||||
await GalleryClient(getIt<AccountsService>().currentProfile)
|
||||
.getGalleryImages(galleryName, page);
|
||||
if (result.isFailure) {
|
||||
return result.errorCast();
|
||||
|
|
|
@ -32,7 +32,7 @@ class InteractionsManager extends ChangeNotifier {
|
|||
FutureResult<List<Connection>, ExecError> updateLikesForStatus(
|
||||
String statusId) async {
|
||||
final likesResult =
|
||||
await InteractionsClient(getIt<AccountsService>().currentCredentials)
|
||||
await InteractionsClient(getIt<AccountsService>().currentProfile)
|
||||
.getLikes(statusId);
|
||||
if (likesResult.isSuccess) {
|
||||
_likesByStatusId[statusId] = likesResult.value;
|
||||
|
@ -44,7 +44,7 @@ class InteractionsManager extends ChangeNotifier {
|
|||
FutureResult<List<Connection>, ExecError> updateResharesForStatus(
|
||||
String statusId) async {
|
||||
final resharesResult =
|
||||
await InteractionsClient(getIt<AccountsService>().currentCredentials)
|
||||
await InteractionsClient(getIt<AccountsService>().currentProfile)
|
||||
.getReshares(statusId);
|
||||
if (resharesResult.isSuccess) {
|
||||
_resharesByStatusId[statusId] = resharesResult.value;
|
||||
|
|
|
@ -129,7 +129,7 @@ class NotificationsManager extends ChangeNotifier {
|
|||
|
||||
FutureResult<bool, ExecError> markSeen(UserNotification notification) async {
|
||||
final result =
|
||||
await NotificationsClient(getIt<AccountsService>().currentCredentials)
|
||||
await NotificationsClient(getIt<AccountsService>().currentProfile)
|
||||
.clearNotification(notification);
|
||||
if (result.isSuccess) {
|
||||
notifyListeners();
|
||||
|
@ -141,7 +141,7 @@ class NotificationsManager extends ChangeNotifier {
|
|||
|
||||
FutureResult<List<UserNotification>, ExecError> markAllAsRead() async {
|
||||
final result =
|
||||
await NotificationsClient(getIt<AccountsService>().currentCredentials)
|
||||
await NotificationsClient(getIt<AccountsService>().currentProfile)
|
||||
.clearNotifications();
|
||||
if (result.isFailure) {
|
||||
return result.errorCast();
|
||||
|
@ -153,7 +153,7 @@ class NotificationsManager extends ChangeNotifier {
|
|||
}
|
||||
|
||||
List<UserNotification> buildUnreadMessageNotifications() {
|
||||
final myId = getIt<AccountsService>().currentCredentials.userId;
|
||||
final myId = getIt<AccountsService>().currentProfile.userId;
|
||||
final result =
|
||||
getIt<DirectMessageService>().getThreads(unreadyOnly: true).map((t) {
|
||||
final fromAccount = t.participants.firstWhere((p) => p.id != myId);
|
||||
|
@ -178,7 +178,7 @@ class NotificationsManager extends ChangeNotifier {
|
|||
static FutureResult<PagedResponse<List<UserNotification>>, ExecError>
|
||||
_clientGetNotificationsRequest(PagingData page) async {
|
||||
final result =
|
||||
await NotificationsClient(getIt<AccountsService>().currentCredentials)
|
||||
await NotificationsClient(getIt<AccountsService>().currentProfile)
|
||||
.getNotifications(page);
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -1,27 +1,21 @@
|
|||
import 'dart:collection';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:result_monad/result_monad.dart';
|
||||
|
||||
import '../models/auth/credentials.dart';
|
||||
import '../models/auth/basic_credentials.dart';
|
||||
import '../models/auth/profile.dart';
|
||||
import '../models/exec_error.dart';
|
||||
|
||||
class SecretsService {
|
||||
static const _usernameKey = 'username';
|
||||
static const _passwordKey = 'password';
|
||||
static const _serverNameKey = 'server-name';
|
||||
static const _userIdKey = 'user-id';
|
||||
static const _avatarKey = 'avatar';
|
||||
static const _basicProfilesKey = 'basic_profiles';
|
||||
static const _oauthProfilesKey = 'oauth_profiles';
|
||||
|
||||
Credentials? _cachedCredentials;
|
||||
final _cachedProfiles = <Profile>{};
|
||||
|
||||
Result<Credentials, ExecError> get credentials => _cachedCredentials != null
|
||||
? Result.ok(_cachedCredentials!)
|
||||
: Result.error(
|
||||
ExecError(
|
||||
type: ErrorType.localError,
|
||||
message: 'Credentials not initialized',
|
||||
),
|
||||
);
|
||||
List<Profile> get profiles => UnmodifiableListView(_cachedProfiles);
|
||||
|
||||
final _secureStorage = const FlutterSecureStorage(
|
||||
iOptions: IOSOptions(
|
||||
|
@ -29,18 +23,15 @@ class SecretsService {
|
|||
),
|
||||
);
|
||||
|
||||
FutureResult<Credentials, ExecError> initialize() async {
|
||||
return await getCredentials();
|
||||
FutureResult<List<Profile>, ExecError> initialize() async {
|
||||
return await loadProfiles();
|
||||
}
|
||||
|
||||
FutureResult<Credentials, ExecError> clearCredentials() async {
|
||||
FutureResult<List<Profile>, ExecError> clearCredentials() async {
|
||||
try {
|
||||
await _secureStorage.delete(key: _usernameKey);
|
||||
await _secureStorage.delete(key: _passwordKey);
|
||||
await _secureStorage.delete(key: _serverNameKey);
|
||||
await _secureStorage.delete(key: _userIdKey);
|
||||
await _secureStorage.delete(key: _avatarKey);
|
||||
return Result.ok(Credentials.empty());
|
||||
await _secureStorage.delete(key: _basicProfilesKey);
|
||||
await _secureStorage.delete(key: _oauthProfilesKey);
|
||||
return Result.ok(profiles);
|
||||
} on PlatformException catch (e) {
|
||||
return Result.error(ExecError(
|
||||
type: ErrorType.localError,
|
||||
|
@ -49,15 +40,11 @@ class SecretsService {
|
|||
}
|
||||
}
|
||||
|
||||
Result<Credentials, ExecError> storeCredentials(Credentials credentials) {
|
||||
FutureResult<List<Profile>, ExecError> addOrUpdateProfile(
|
||||
Profile profile) async {
|
||||
try {
|
||||
_secureStorage.write(key: _usernameKey, value: credentials.username);
|
||||
_secureStorage.write(key: _passwordKey, value: credentials.password);
|
||||
_secureStorage.write(key: _serverNameKey, value: credentials.serverName);
|
||||
_secureStorage.write(key: _userIdKey, value: credentials.userId);
|
||||
_secureStorage.write(key: _avatarKey, value: credentials.avatar);
|
||||
_cachedCredentials = credentials;
|
||||
return Result.ok(credentials);
|
||||
_cachedProfiles.add(profile);
|
||||
return await saveCredentials();
|
||||
} on PlatformException catch (e) {
|
||||
return Result.error(ExecError(
|
||||
type: ErrorType.localError,
|
||||
|
@ -66,24 +53,47 @@ class SecretsService {
|
|||
}
|
||||
}
|
||||
|
||||
FutureResult<Credentials, ExecError> getCredentials() async {
|
||||
FutureResult<List<Profile>, ExecError> removeProfile(Profile profile) async {
|
||||
try {
|
||||
final username = await _secureStorage.read(key: _usernameKey);
|
||||
final password = await _secureStorage.read(key: _passwordKey);
|
||||
final serverName = await _secureStorage.read(key: _serverNameKey);
|
||||
final userId = await _secureStorage.read(key: _userIdKey);
|
||||
final avatar = await _secureStorage.read(key: _avatarKey);
|
||||
if (username == null || password == null || serverName == null) {
|
||||
return Result.ok(Credentials.empty());
|
||||
_cachedProfiles.remove(profile);
|
||||
return await saveCredentials();
|
||||
} on PlatformException catch (e) {
|
||||
return Result.error(ExecError(
|
||||
type: ErrorType.localError,
|
||||
message: e.message ?? '',
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
FutureResult<List<Profile>, ExecError> loadProfiles() async {
|
||||
try {
|
||||
final basicJson = await _secureStorage.read(key: _basicProfilesKey);
|
||||
if (basicJson == null) {
|
||||
return Result.ok(profiles);
|
||||
}
|
||||
_cachedCredentials = Credentials(
|
||||
username: username,
|
||||
password: password,
|
||||
serverName: serverName,
|
||||
userId: userId ?? '',
|
||||
avatar: avatar ?? '',
|
||||
);
|
||||
return Result.ok(_cachedCredentials!);
|
||||
final basicCreds = (jsonDecode(basicJson) as List<dynamic>)
|
||||
.map((json) => Profile.fromJson(json, BasicCredentials.fromJson))
|
||||
.toList();
|
||||
_cachedProfiles.addAll(basicCreds);
|
||||
return Result.ok(profiles);
|
||||
} on PlatformException catch (e) {
|
||||
return Result.error(ExecError(
|
||||
type: ErrorType.localError,
|
||||
message: e.message ?? '',
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
FutureResult<List<Profile>, ExecError> saveCredentials() async {
|
||||
try {
|
||||
final basicCredsJson = _cachedProfiles
|
||||
.where((p) => p.credentials is BasicCredentials)
|
||||
.map((p) => p.toJson())
|
||||
.toList();
|
||||
final basicCredsString = jsonEncode(basicCredsJson);
|
||||
await _secureStorage.write(
|
||||
key: _basicProfilesKey, value: basicCredsString);
|
||||
return Result.ok(profiles);
|
||||
} on PlatformException catch (e) {
|
||||
return Result.error(ExecError(
|
||||
type: ErrorType.localError,
|
||||
|
|
|
@ -46,7 +46,7 @@ class TimelineManager extends ChangeNotifier {
|
|||
|
||||
Future<void> _refreshGroupData() async {
|
||||
_logger.finest('Refreshing member group data ');
|
||||
await GroupsClient(getIt<AccountsService>().currentCredentials)
|
||||
await GroupsClient(getIt<AccountsService>().currentProfile)
|
||||
.getGroups()
|
||||
.match(
|
||||
onSuccess: (groups) {
|
||||
|
|
Loading…
Reference in a new issue