mirror of
https://github.com/immich-app/immich.git
synced 2025-12-09 06:11:00 -08:00
Compare commits
4 Commits
v1.131.1
...
fix-mobile
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
675c64a22f | ||
|
|
d3aacbe74b | ||
|
|
41aefffe09 | ||
|
|
090762f9dd |
2
mobile/.vscode/settings.json
vendored
2
mobile/.vscode/settings.json
vendored
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"dart.flutterSdkPath": ".fvm/versions/3.22.1",
|
"dart.flutterSdkPath": ".fvm/versions/3.22.3",
|
||||||
"search.exclude": {
|
"search.exclude": {
|
||||||
"**/.fvm": true
|
"**/.fvm": true
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ class Store {
|
|||||||
|
|
||||||
/// Initializes the store (call exactly once per app start)
|
/// Initializes the store (call exactly once per app start)
|
||||||
static void init(Isar db) {
|
static void init(Isar db) {
|
||||||
|
print("Initializing store");
|
||||||
_db = db;
|
_db = db;
|
||||||
_populateCache();
|
_populateCache();
|
||||||
_db.storeValues.where().build().watch().listen(_onChangeListener);
|
_db.storeValues.where().build().watch().listen(_onChangeListener);
|
||||||
@@ -59,6 +60,9 @@ class Store {
|
|||||||
/// Removes the value synchronously from the cache and asynchronously from the DB
|
/// Removes the value synchronously from the cache and asynchronously from the DB
|
||||||
static Future<void> delete<T>(StoreKey<T> key) {
|
static Future<void> delete<T>(StoreKey<T> key) {
|
||||||
if (_cache[key.id] == null) return Future.value();
|
if (_cache[key.id] == null) return Future.value();
|
||||||
|
if(key.id == StoreKey.serverEndpoint.id) {
|
||||||
|
_log.info("Server endpoint changed to null");
|
||||||
|
}
|
||||||
_cache[key.id] = null;
|
_cache[key.id] = null;
|
||||||
return _db.writeTxn(() => _db.storeValues.delete(key.id));
|
return _db.writeTxn(() => _db.storeValues.delete(key.id));
|
||||||
}
|
}
|
||||||
@@ -76,12 +80,12 @@ class Store {
|
|||||||
/// updates the state if a value is updated in any isolate
|
/// updates the state if a value is updated in any isolate
|
||||||
static void _onChangeListener(List<StoreValue>? data) {
|
static void _onChangeListener(List<StoreValue>? data) {
|
||||||
if (data != null) {
|
if (data != null) {
|
||||||
|
final dbValues = _db.txnSync(() => _db.storeValues.getAllSync(data.map((e) => e.id).toList()));
|
||||||
for (StoreValue value in data) {
|
for (StoreValue value in data) {
|
||||||
final key = StoreKey.values.firstWhereOrNull((e) => e.id == value.id);
|
final dbValue = dbValues.firstWhere((e) => e?.id == value.id, orElse: () => null)?._extract(StoreKey.values[value.id]);
|
||||||
if (key != null) {
|
_cache[value.id] = dbValue;
|
||||||
_cache[value.id] = value._extract(key);
|
if(value.id == StoreKey.serverEndpoint.id) {
|
||||||
} else {
|
_log.info("Server endpoint changed to ${value.strValue}");
|
||||||
_log.warning("No key available for value id - ${value.id}");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -96,7 +100,8 @@ class StoreValue {
|
|||||||
int? intValue;
|
int? intValue;
|
||||||
String? strValue;
|
String? strValue;
|
||||||
|
|
||||||
T? _extract<T>(StoreKey<T> key) {
|
T? _extract<T>(StoreKey<T>? key) {
|
||||||
|
if (key == null) return null;
|
||||||
switch (key.type) {
|
switch (key.type) {
|
||||||
case const (int):
|
case const (int):
|
||||||
return intValue as T?;
|
return intValue as T?;
|
||||||
|
|||||||
@@ -19,45 +19,22 @@ class SplashScreenPage extends HookConsumerWidget {
|
|||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final apiService = ref.watch(apiServiceProvider);
|
final apiService = ref.watch(apiServiceProvider);
|
||||||
final serverUrl = Store.tryGet(StoreKey.serverUrl);
|
final serverUrl = Store.tryGet(StoreKey.serverUrl);
|
||||||
|
final endpoint = Store.tryGet(StoreKey.serverEndpoint);
|
||||||
final accessToken = Store.tryGet(StoreKey.accessToken);
|
final accessToken = Store.tryGet(StoreKey.accessToken);
|
||||||
final log = Logger("SplashScreenPage");
|
final log = Logger("SplashScreenPage");
|
||||||
|
|
||||||
void performLoggingIn() async {
|
void performLoggingIn() async {
|
||||||
bool isSuccess = false;
|
bool isAuthSuccess = false;
|
||||||
bool deviceIsOffline = false;
|
|
||||||
|
|
||||||
if (accessToken != null && serverUrl != null) {
|
if (accessToken != null && serverUrl != null && endpoint != null) {
|
||||||
try {
|
apiService.setEndpoint(endpoint);
|
||||||
// Resolve API server endpoint from user provided serverUrl
|
|
||||||
await apiService.resolveAndSetEndpoint(serverUrl);
|
|
||||||
} on ApiException catch (error, stackTrace) {
|
|
||||||
log.severe(
|
|
||||||
"Failed to resolve endpoint [ApiException]",
|
|
||||||
error,
|
|
||||||
stackTrace,
|
|
||||||
);
|
|
||||||
// okay, try to continue anyway if offline
|
|
||||||
if (error.code == 503) {
|
|
||||||
deviceIsOffline = true;
|
|
||||||
log.warning("Device seems to be offline upon launch");
|
|
||||||
} else {
|
|
||||||
log.severe("Failed to resolve endpoint", error);
|
|
||||||
}
|
|
||||||
} catch (error, stackTrace) {
|
|
||||||
log.severe(
|
|
||||||
"Failed to resolve endpoint [Catch All]",
|
|
||||||
error,
|
|
||||||
stackTrace,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
isSuccess = await ref
|
isAuthSuccess = await ref
|
||||||
.read(authenticationProvider.notifier)
|
.read(authenticationProvider.notifier)
|
||||||
.setSuccessLoginInfo(
|
.setSuccessLoginInfo(
|
||||||
accessToken: accessToken,
|
accessToken: accessToken,
|
||||||
serverUrl: serverUrl,
|
serverUrl: serverUrl,
|
||||||
offlineLogin: deviceIsOffline,
|
|
||||||
);
|
);
|
||||||
} catch (error, stackTrace) {
|
} catch (error, stackTrace) {
|
||||||
log.severe(
|
log.severe(
|
||||||
@@ -66,29 +43,29 @@ class SplashScreenPage extends HookConsumerWidget {
|
|||||||
stackTrace,
|
stackTrace,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
isAuthSuccess = false;
|
||||||
|
log.severe(
|
||||||
|
'Missing authentication, server, or endpoint info from the local store',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the device is offline and there is a currentUser stored locallly
|
if (!isAuthSuccess) {
|
||||||
// Proceed into the app
|
|
||||||
if (deviceIsOffline && Store.tryGet(StoreKey.currentUser) != null) {
|
|
||||||
context.replaceRoute(const TabControllerRoute());
|
|
||||||
} else if (isSuccess) {
|
|
||||||
// If device was able to login through the internet successfully
|
|
||||||
final hasPermission =
|
|
||||||
await ref.read(galleryPermissionNotifier.notifier).hasPermission;
|
|
||||||
if (hasPermission) {
|
|
||||||
// Resume backup (if enable) then navigate
|
|
||||||
ref.watch(backupProvider.notifier).resumeBackup();
|
|
||||||
}
|
|
||||||
context.replaceRoute(const TabControllerRoute());
|
|
||||||
} else {
|
|
||||||
log.severe(
|
log.severe(
|
||||||
'Unable to login through offline or online methods - logging out completely',
|
'Unable to login using offline or online methods - Logging out completely',
|
||||||
);
|
);
|
||||||
|
|
||||||
ref.read(authenticationProvider.notifier).logout();
|
ref.read(authenticationProvider.notifier).logout();
|
||||||
// User was unable to login through either offline or online methods
|
|
||||||
context.replaceRoute(const LoginRoute());
|
context.replaceRoute(const LoginRoute());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
context.replaceRoute(const TabControllerRoute());
|
||||||
|
|
||||||
|
final hasPermission =
|
||||||
|
await ref.read(galleryPermissionNotifier.notifier).hasPermission;
|
||||||
|
if (hasPermission) {
|
||||||
|
// Resume backup (if enable) then navigate
|
||||||
|
ref.watch(backupProvider.notifier).resumeBackup();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -156,7 +156,6 @@ class AuthenticationNotifier extends StateNotifier<AuthenticationState> {
|
|||||||
Future<bool> setSuccessLoginInfo({
|
Future<bool> setSuccessLoginInfo({
|
||||||
required String accessToken,
|
required String accessToken,
|
||||||
required String serverUrl,
|
required String serverUrl,
|
||||||
bool offlineLogin = false,
|
|
||||||
}) async {
|
}) async {
|
||||||
_apiService.setAccessToken(accessToken);
|
_apiService.setAccessToken(accessToken);
|
||||||
|
|
||||||
@@ -165,57 +164,56 @@ class AuthenticationNotifier extends StateNotifier<AuthenticationState> {
|
|||||||
Store.tryGet(StoreKey.deviceId) ?? await FlutterUdid.consistentUdid;
|
Store.tryGet(StoreKey.deviceId) ?? await FlutterUdid.consistentUdid;
|
||||||
|
|
||||||
bool shouldChangePassword = false;
|
bool shouldChangePassword = false;
|
||||||
User? user;
|
User? user = Store.tryGet(StoreKey.currentUser);
|
||||||
|
|
||||||
bool retResult = false;
|
UserAdminResponseDto? userResponse;
|
||||||
User? offlineUser = Store.tryGet(StoreKey.currentUser);
|
UserPreferencesResponseDto? userPreferences;
|
||||||
|
try {
|
||||||
// If the user is offline and there is a user saved on the device,
|
var responses = await Future.wait([
|
||||||
// if not try an online login
|
_apiService.usersApi.getMyUser(),
|
||||||
if (offlineLogin && offlineUser != null) {
|
_apiService.usersApi.getMyPreferences(),
|
||||||
user = offlineUser;
|
]);
|
||||||
retResult = false;
|
userResponse = responses[0] as UserAdminResponseDto;
|
||||||
} else {
|
userPreferences = responses[1] as UserPreferencesResponseDto;
|
||||||
UserAdminResponseDto? userResponseDto;
|
} on ApiException catch (error, stackTrace) {
|
||||||
UserPreferencesResponseDto? userPreferences;
|
if (error.code == 401) {
|
||||||
try {
|
_log.severe("Unauthorized access, token likely expired. Logging out.");
|
||||||
userResponseDto = await _apiService.usersApi.getMyUser();
|
|
||||||
userPreferences = await _apiService.usersApi.getMyPreferences();
|
|
||||||
} on ApiException catch (error, stackTrace) {
|
|
||||||
_log.severe(
|
|
||||||
"Error getting user information from the server [API EXCEPTION]",
|
|
||||||
error,
|
|
||||||
stackTrace,
|
|
||||||
);
|
|
||||||
if (error.innerException is SocketException) {
|
|
||||||
state = state.copyWith(isAuthenticated: true);
|
|
||||||
}
|
|
||||||
} catch (error, stackTrace) {
|
|
||||||
_log.severe(
|
|
||||||
"Error getting user information from the server [CATCH ALL]",
|
|
||||||
error,
|
|
||||||
stackTrace,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (userResponseDto != null) {
|
|
||||||
Store.put(StoreKey.deviceId, deviceId);
|
|
||||||
Store.put(StoreKey.deviceIdHash, fastHash(deviceId));
|
|
||||||
Store.put(
|
|
||||||
StoreKey.currentUser,
|
|
||||||
User.fromUserDto(userResponseDto, userPreferences),
|
|
||||||
);
|
|
||||||
Store.put(StoreKey.serverUrl, serverUrl);
|
|
||||||
Store.put(StoreKey.accessToken, accessToken);
|
|
||||||
|
|
||||||
shouldChangePassword = userResponseDto.shouldChangePassword;
|
|
||||||
user = User.fromUserDto(userResponseDto, userPreferences);
|
|
||||||
|
|
||||||
retResult = true;
|
|
||||||
} else {
|
|
||||||
_log.severe("Unable to get user information from the server.");
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
_log.severe(
|
||||||
|
"Error getting user information from the server [API EXCEPTION]",
|
||||||
|
stackTrace,
|
||||||
|
);
|
||||||
|
} catch (error, stackTrace) {
|
||||||
|
_log.severe(
|
||||||
|
"Error getting user information from the server [CATCH ALL]",
|
||||||
|
error,
|
||||||
|
stackTrace,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the user information is successfully retrieved, update the store
|
||||||
|
// Due to the flow of the code, this will always happen on first login
|
||||||
|
if (userResponse != null) {
|
||||||
|
Store.put(StoreKey.deviceId, deviceId);
|
||||||
|
Store.put(StoreKey.deviceIdHash, fastHash(deviceId));
|
||||||
|
Store.put(
|
||||||
|
StoreKey.currentUser,
|
||||||
|
User.fromUserDto(userResponse, userPreferences),
|
||||||
|
);
|
||||||
|
Store.put(StoreKey.serverUrl, serverUrl);
|
||||||
|
Store.put(StoreKey.accessToken, accessToken);
|
||||||
|
|
||||||
|
shouldChangePassword = userResponse.shouldChangePassword;
|
||||||
|
user = User.fromUserDto(userResponse, userPreferences);
|
||||||
|
} else {
|
||||||
|
_log.severe("Unable to get user information from the server.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the user is null, the login was not successful
|
||||||
|
// and we don't have a local copy of the user from a prior successful login
|
||||||
|
if (user == null) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
state = state.copyWith(
|
state = state.copyWith(
|
||||||
@@ -229,7 +227,7 @@ class AuthenticationNotifier extends StateNotifier<AuthenticationState> {
|
|||||||
deviceId: deviceId,
|
deviceId: deviceId,
|
||||||
);
|
);
|
||||||
|
|
||||||
return retResult;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -64,11 +64,13 @@ class ApiService implements Authentication {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<String> resolveAndSetEndpoint(String serverUrl) async {
|
Future<String> resolveAndSetEndpoint(String serverUrl) async {
|
||||||
final endpoint = await _resolveEndpoint(serverUrl);
|
var endpoint = Store.tryGet(StoreKey.serverEndpoint);
|
||||||
|
|
||||||
|
endpoint ??= await _resolveEndpoint(serverUrl);
|
||||||
setEndpoint(endpoint);
|
setEndpoint(endpoint);
|
||||||
|
|
||||||
// Save in hivebox for next startup
|
// Save in hivebox for next startup
|
||||||
Store.put(StoreKey.serverEndpoint, endpoint);
|
await Store.put(StoreKey.serverEndpoint, endpoint);
|
||||||
return endpoint;
|
return endpoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user