import 'dart:async'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:immich_mobile/domain/interfaces/store.interface.dart'; import 'package:immich_mobile/domain/interfaces/user.interface.dart'; import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/domain/services/login.service.dart'; import 'package:immich_mobile/i18n/strings.g.dart'; import 'package:immich_mobile/presentation/modules/login/models/login_page.model.dart'; import 'package:immich_mobile/service_locator.dart'; import 'package:immich_mobile/utils/immich_api_client.dart'; import 'package:immich_mobile/utils/mixins/log.mixin.dart'; import 'package:immich_mobile/utils/snackbar_manager.dart'; class LoginPageCubit extends Cubit with LogMixin { LoginPageCubit() : super(LoginPageState.initial()); String _appendSchema(String url) { // Add schema if none is set url = url.trimLeft().startsWith(RegExp(r"https?://")) ? url : "https://$url"; // Remove trailing slash(es) url = url.trimRight().replaceFirst(RegExp(r"/+$"), ""); return url; } String? validateServerUrl(String? url) { if (url == null || url.isEmpty) { return t.login.error.empty_server_url; } url = _appendSchema(url); final uri = Uri.tryParse(url); if (uri == null || !uri.isAbsolute || uri.host.isEmpty || !uri.scheme.startsWith("http")) { return t.login.error.invalid_server_url; } return null; } Future validateServer(String url) async { url = _appendSchema(url); final LoginService loginService = di(); try { // parse instead of tryParse since the method expects a valid well formed URI final uri = Uri.parse(url); emit(state.copyWith(isValidationInProgress: true)); // Check if the endpoint is available if (!await loginService.isEndpointAvailable(uri)) { SnackbarManager.showError(t.login.error.server_not_reachable); return; } // Check for /.well-known/immich url = await loginService.resolveEndpoint(uri); di().upsert(StoreKey.serverEndpoint, url); await di().handlePostUrlResolution(url); emit(state.copyWith(isServerValidated: true)); } finally { emit(state.copyWith(isValidationInProgress: false)); } } Future passwordLogin({ required String email, required String password, }) async { try { emit(state.copyWith(isValidationInProgress: true)); final accessToken = await di().passwordLogin(email, password); if (accessToken == null) { SnackbarManager.showError(t.login.error.error_login); return; } await _postLogin(accessToken); } catch (e, s) { SnackbarManager.showError(t.login.error.error_login); log.e("Cannot perform password login", e, s); } finally { emit(state.copyWith(isValidationInProgress: false)); } } Future oAuthLogin() async { try { emit(state.copyWith(isValidationInProgress: true)); final accessToken = await di().oAuthLogin(); if (accessToken == null) { SnackbarManager.showError(t.login.error.error_login_oauth); return; } await _postLogin(accessToken); } catch (e, s) { SnackbarManager.showError(t.login.error.error_login_oauth); log.e("Cannot perform oauth login", e, s); } finally { emit(state.copyWith(isValidationInProgress: false)); } } Future _postLogin(String accessToken) async { await di().upsert(StoreKey.accessToken, accessToken); /// Set token to interceptor await di().init(accessToken: accessToken); final user = await di().handlePostLogin(); if (user == null) { SnackbarManager.showError(t.login.error.error_login); return; } await di().upsert(user); emit(state.copyWith( isValidationInProgress: false, isLoginSuccessful: true, )); } void resetServerValidation() { emit(LoginPageState.initial()); } }