Compare commits

...

4 Commits

5 changed files with 86 additions and 104 deletions

View File

@@ -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
}, },

View File

@@ -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?;

View File

@@ -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();
} }
} }

View File

@@ -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;
} }
} }

View File

@@ -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;
} }