Fladder/lib/providers/auth_provider.dart
PartyDonut c299492d6d
feat: Android TV support (#503)
Co-authored-by: PartyDonut <PartyDonut@users.noreply.github.com>
2025-09-28 21:07:49 +02:00

243 lines
8.2 KiB
Dart

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<AuthNotifier, LoginScreenModel>((ref) {
return AuthNotifier(ref);
});
class AuthNotifier extends StateNotifier<LoginScreenModel> {
AuthNotifier(this.ref) : super(LoginScreenModel());
final Ref ref;
late final JellyService api = ref.read(jellyApiProvider);
BuildContext? context;
Future<void> 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<void> _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<Response<List<AccountModel>>?> 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<Response<AccountModel>?> authenticateUsingSecret(String secret) async {
clearAllProviders();
var response = await api.quickConnectAuthenticate(secret);
return _createAccountModel(response);
}
Future<Response<AccountModel>?> authenticateByName(String userName, String password) async {
clearAllProviders();
var response = await api.usersAuthenticateByNamePost(userName: userName, password: password);
return _createAccountModel(response);
}
Future<Response<AccountModel>?> _createAccountModel(Response<AuthenticationResult> 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<Response?> logOutUser() async {
final currentUser = ref.read(userProvider);
state = state.copyWith(serverLoginModel: null);
await ref.read(sharedUtilityProvider).removeAccount(currentUser);
clearAllProviders();
return null;
}
Future<void> 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<void> 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<AccountModel> 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,
);
}
}
}