import 'package:flutter/material.dart'; import 'package:chopper/chopper.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart'; import 'package:fladder/models/account_model.dart'; import 'package:fladder/models/credentials_model.dart'; import 'package:fladder/models/login_screen_model.dart'; import 'package:fladder/providers/api_provider.dart'; import 'package:fladder/providers/dashboard_provider.dart'; import 'package:fladder/providers/favourites_provider.dart'; import 'package:fladder/providers/image_provider.dart'; import 'package:fladder/providers/library_screen_provider.dart'; import 'package:fladder/providers/service_provider.dart'; import 'package:fladder/providers/shared_provider.dart'; import 'package:fladder/providers/user_provider.dart'; import 'package:fladder/providers/views_provider.dart'; import 'package:fladder/screens/login/lock_screen.dart'; import 'package:fladder/screens/shared/fladder_snackbar.dart'; import 'package:fladder/util/fladder_config.dart'; import 'package:fladder/util/localization_helper.dart'; import 'package:fladder/util/string_extensions.dart'; final authProvider = StateNotifierProvider((ref) { return AuthNotifier(ref); }); class AuthNotifier extends StateNotifier { AuthNotifier(this.ref) : super(LoginScreenModel()); final Ref ref; late final JellyService api = ref.read(jellyApiProvider); BuildContext? context; Future initModel(BuildContext newContext) async { context ??= newContext; ref.read(userProvider.notifier).clear(); final currentAccounts = ref.read(authProvider.notifier).getSavedAccounts(); ref.read(lockScreenActiveProvider.notifier).update((state) => true); if (FladderConfig.baseUrl != null) { final url = FladderConfig.baseUrl; state = state.copyWith( hasBaseUrl: true, ); if (url != null) { await setServer(url); } } state = state.copyWith( accounts: currentAccounts, screen: currentAccounts.isEmpty ? LoginScreenType.login : LoginScreenType.users, ); } Future _fetchServerInfo(String url) async { try { final newCredentials = CredentialsModel.createNewCredentials().copyWith(server: url.rtrim('/')); final newLoginModel = ServerLoginModel(tempCredentials: newCredentials); state = state.copyWith( serverLoginModel: newLoginModel, loading: true, ); final publicUsers = (await getPublicUsers())?.body ?? []; final quickConnectStatus = (await api.quickConnectEnabled()).body ?? false; final branding = await api.getBranding(); final serverResponse = await api.systemInfoPublicGet(); state = state.copyWith( screen: quickConnectStatus ? LoginScreenType.code : LoginScreenType.login, serverLoginModel: newLoginModel.copyWith( tempCredentials: newCredentials.copyWith( serverName: serverResponse.body?.serverName, serverId: serverResponse.body?.id, ), accounts: publicUsers, hasQuickConnect: quickConnectStatus, serverMessage: branding.body?.loginDisclaimer, ), loading: false, ); } catch (e) { state = state.copyWith( errorMessage: context?.localized.invalidUrl, loading: false, ); if (context != null) { fladderSnackbar(context!, title: context!.localized.unableToConnectHost); } } } String? _parseUrl(String url) { if (url.isEmpty) { return null; } if (!Uri.parse(url).isAbsolute) { return context?.localized.invalidUrl; } if (!url.startsWith('https://') && !url.startsWith('http://')) { return context?.localized.invalidUrlDesc; } return null; } Future>?> getPublicUsers() async { try { state = state.copyWith(loading: true); final credentials = state.serverLoginModel?.tempCredentials; if (credentials == null) return null; var response = await api.usersPublicGet(credentials); if (response.isSuccessful && response.body != null) { var models = response.body ?? []; return response.copyWith(body: models.toList()); } state = state.copyWith( serverLoginModel: state.serverLoginModel?.copyWith( accounts: response.body ?? [], ), ); return response.copyWith(body: []); } catch (e) { return null; } finally { state = state.copyWith(loading: false); } } Future?> authenticateUsingSecret(String secret) async { clearAllProviders(); var response = await api.quickConnectAuthenticate(secret); return _createAccountModel(response); } Future?> authenticateByName(String userName, String password) async { clearAllProviders(); var response = await api.usersAuthenticateByNamePost(userName: userName, password: password); return _createAccountModel(response); } Future?> _createAccountModel(Response response) async { CredentialsModel? credentials = state.serverLoginModel?.tempCredentials; if (credentials == null) return null; if (response.isSuccessful && (response.body?.accessToken?.isNotEmpty ?? false)) { var serverResponse = await api.systemInfoPublicGet(); credentials = credentials.copyWith( token: response.body?.accessToken ?? "", serverId: response.body?.serverId, serverName: serverResponse.body?.serverName ?? "", ); var imageUrl = ref.read(imageUtilityProvider).getUserImageUrl(response.body?.user?.id ?? ""); AccountModel newUser = AccountModel( name: response.body?.user?.name ?? "", id: response.body?.user?.id ?? "", avatar: imageUrl, credentials: credentials, lastUsed: DateTime.now(), ); ref.read(sharedUtilityProvider).addAccount(newUser); ref.read(userProvider.notifier).userState = newUser; final currentAccounts = ref.read(authProvider.notifier).getSavedAccounts(); state = state.copyWith( serverLoginModel: null, accounts: currentAccounts, ); return Response(response.base, newUser); } return Response(response.base, null); } Future logOutUser() async { final currentUser = ref.read(userProvider); state = state.copyWith(serverLoginModel: null); await ref.read(sharedUtilityProvider).removeAccount(currentUser); clearAllProviders(); return null; } Future switchUser() async { clearAllProviders(); } void clearAllProviders() { ref.read(dashboardProvider.notifier).clear(); ref.read(viewsProvider.notifier).clear(); ref.read(favouritesProvider.notifier).clear(); ref.read(userProvider.notifier).clear(); ref.read(libraryScreenProvider.notifier).clear(); } Future setServer(String server) async { final url = (state.hasBaseUrl ? FladderConfig.baseUrl : server); if (url == null || server.isEmpty) return; final isUrlValid = _parseUrl(url); state = state.copyWith( errorMessage: isUrlValid, serverLoginModel: null, ); if (isUrlValid == null) { await _fetchServerInfo(url); } } List getSavedAccounts() { state = state.copyWith(accounts: ref.read(sharedUtilityProvider).getAccounts()); return state.accounts; } void reOrderUsers(int oldIndex, int newIndex) { final accounts = state.accounts; final original = accounts.elementAt(oldIndex); accounts.removeAt(oldIndex); accounts.insert(newIndex, original); ref.read(sharedUtilityProvider).saveAccounts(accounts); } void addNewUser() { state = state.copyWith( screen: LoginScreenType.login, ); } void goUserSelect() { state = state.copyWith( serverLoginModel: state.hasBaseUrl ? state.serverLoginModel : null, screen: LoginScreenType.users, ); } void tryParseUrl(String server) { if (server.isNotEmpty && state.errorMessage != null) { final url = server; final isUrlValid = _parseUrl(url); state = state.copyWith( errorMessage: isUrlValid, ); } } }