relatica/lib/riverpod_controllers/account_services.dart

271 wiersze
7.8 KiB
Dart

import 'dart:async';
import 'dart:collection';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:logging/logging.dart';
import 'package:result_monad/result_monad.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:stack_trace/stack_trace.dart';
import '../models/auth/credentials_intf.dart';
import '../models/auth/profile.dart';
import '../models/exec_error.dart';
import 'background_updater_services.dart';
import 'connection_manager_services.dart';
import 'globals_services.dart';
import 'instance_info_services.dart';
import 'networking/friendica_profile_client_services.dart';
import 'status_service.dart';
part 'account_services.g.dart';
@Riverpod(keepAlive: true)
class LoggedInProfiles extends _$LoggedInProfiles {
@override
List<Profile> build() {
return UnmodifiableListView([]);
}
void add(Profile profile) {
state = [...state, profile];
}
void remove(Profile profile) {
final profilesWithUpdate = List<Profile>.from(state);
profilesWithUpdate.removeWhere((p) => p == profile);
state = UnmodifiableListView(profilesWithUpdate);
}
void clear() {
state = UnmodifiableListView([]);
}
}
@Riverpod(keepAlive: true)
class LoggedOutProfiles extends _$LoggedOutProfiles {
@override
List<Profile> build() {
return [];
}
void add(Profile profile) {
state = [...state, profile];
}
void remove(Profile profile) {
final profilesWithUpdate = List<Profile>.from(state);
profilesWithUpdate.removeWhere((p) => p == profile);
state = UnmodifiableListView(profilesWithUpdate);
}
void clear() {
state = UnmodifiableListView([]);
}
}
const _activeProfileIdKey = 'active_profile_id';
@Riverpod(keepAlive: true)
class ActiveProfile extends _$ActiveProfile {
bool get hasActiveProfile => state != Profile.empty();
@override
Profile build() {
return Profile.empty();
}
void setActiveProfile(Profile profile) {
ref.read(backgroundUpdatersProvider(state).notifier).stop();
state = profile;
_saveStoredLoginState();
ref.read(backgroundUpdatersProvider(state).notifier).reset();
}
void clear() {
state = Profile.empty();
_saveStoredLoginState();
}
void _saveStoredLoginState() {
final prefs = ref.read(sharedPreferencesProvider);
prefs.setString(_activeProfileIdKey, state.id);
}
}
@riverpod
bool loggedIn(Ref ref) => ref.watch(activeProfileProvider) != Profile.empty();
final _pmLogger = Logger('ProfileManagerProvider');
@riverpod
class CredentialSignin extends _$CredentialSignin {
@override
bool build(ICredentials credentials) {
return false;
}
FutureResult<Profile, ExecError> signIn(bool activateProfileOnSuccess) async {
final result =
await credentials.signIn(ref).andThenAsync((signedInCredentials) async {
ref.read(statusServiceProvider.notifier).setStatus(
'Getting user profile from ${signedInCredentials.serverName}');
return await ref.read(
myProfileProvider(Profile.credentialsOnly(signedInCredentials))
.future);
}).andThenAsync((profileResult) async {
final profileData = profileResult.$1;
final profile = profileResult.$2;
final loginProfile = Profile(
credentials: profile.credentials,
username: profileData.name,
serverName: profile.credentials.serverName,
avatar: profileData.avatarUrl,
userId: profileData.id,
loggedIn: true,
);
ref
.read(statusServiceProvider.notifier)
.setStatus('Loaded user profile ${profileData.handle}');
if (activateProfileOnSuccess) {
ref.read(activeProfileProvider.notifier).setActiveProfile(loginProfile);
}
ref.read(loggedInProfilesProvider.notifier).add(loginProfile);
ref.read(loggedOutProfilesProvider.notifier).remove(loginProfile);
await ref.read(secretsServiceProvider).addOrUpdateProfile(loginProfile);
await ref.read(connectionRepoInitProvider(loginProfile).future);
ref.read(instanceRulesManagerProvider(loginProfile));
await ref
.read(instanceInfoManagerProvider(loginProfile).notifier)
.update();
state = true;
return Result.ok(loginProfile);
});
if (result.isFailure) {
ref
.read(statusServiceProvider.notifier)
.setStatus('Error signing in: ${result.error}');
_pmLogger.severe(
'Error signing in $credentials: ${result.error}', Trace.current());
}
return result.execErrorCast();
}
}
@Riverpod(keepAlive: true)
class ProfileManager extends _$ProfileManager {
@override
bool build(Profile profile) {
return false;
}
FutureResult<Profile, ExecError> signIn(bool activateProfileOnSuccess) async {
return await ref
.read(credentialSigninProvider(profile.credentials).notifier)
.signIn(activateProfileOnSuccess);
}
Future signOut({bool withNotification = true}) async {
if (ref.read(activeProfileProvider) == profile) {
ref.read(activeProfileProvider.notifier).clear();
}
final updatedProfile = profile.copyWithLoginUpdate(false);
ref.read(loggedInProfilesProvider.notifier).remove(profile);
ref.read(loggedOutProfilesProvider.notifier).add(updatedProfile);
ref.read(secretsServiceProvider).addOrUpdateProfile(updatedProfile);
if (ref.read(loggedInProfilesProvider).isNotEmpty) {
ref.read(activeProfileProvider.notifier).setActiveProfile(
ref.read(loggedInProfilesProvider).first,
);
}
if (withNotification) {
ref.notifyListeners();
}
}
}
@riverpod
class AccountServicesInitializer extends _$AccountServicesInitializer {
var initialized = false;
@override
bool build() {
initialized = false;
return false;
}
FutureResult<bool, ExecError> initialize() async {
var initializing = state;
if (initialized) {
return Result.ok(true);
}
if (initializing) {
return buildErrorResult(
type: ErrorType.localError, message: 'Already initializing');
}
final lastActiveProfile = _getStoredLoginState();
final secretsService = ref.read(secretsServiceProvider);
final result = await runCatchingAsync(() async {
final initialProfiles = secretsService.profiles;
for (final p in initialProfiles) {
if (!p.loggedIn) {
ref.read(loggedOutProfilesProvider.notifier).add(p);
continue;
}
final pr =
await ref.read(profileManagerProvider(p).notifier).signIn(false);
if (pr.isSuccess) {
final profile = pr.value;
if (profile.id.isNotEmpty && profile.id == lastActiveProfile) {
ref.read(activeProfileProvider.notifier).setActiveProfile(profile);
}
} else {
await ref.read(profileManagerProvider(p).notifier).signOut();
}
}
if (!ref.read(activeProfileProvider.notifier).hasActiveProfile &&
ref.read(loggedInProfilesProvider).isNotEmpty) {
final firstProfile = ref.read(loggedOutProfilesProvider).first;
ref.read(activeProfileProvider.notifier).setActiveProfile(firstProfile);
}
return Result.ok(
ref.read(activeProfileProvider.notifier).hasActiveProfile);
});
initialized = true;
state = false;
ref.notifyListeners();
return result.execErrorCast();
}
String _getStoredLoginState() {
final prefs = ref.read(sharedPreferencesProvider);
return prefs.getString('active_profile_id') ?? '';
}
}
@riverpod
class AccountClearer extends _$AccountClearer {
@override
bool build() {
return true;
}
void clearAllProfiles() {
ref.read(loggedInProfilesProvider.notifier).clear();
ref.read(loggedOutProfilesProvider.notifier).clear();
ref.read(activeProfileProvider.notifier).clear();
ref.read(secretsServiceProvider).clearCredentials();
}
}