0
Fork 0
mirror of https://github.com/immich-app/immich.git synced 2025-02-11 01:18:24 -05:00

preliminary auto-login

This commit is contained in:
shenlong-tanwen 2024-09-14 23:05:58 +05:30
parent e9b023a09e
commit 1786438123
13 changed files with 193 additions and 63 deletions

View file

@ -3,6 +3,8 @@
*.gr.dart *.gr.dart
*.drift.dart *.drift.dart
*.gen.dart *.gen.dart
*.g.swift
*.g.kt
openapi/* openapi/*
# Miscellaneous # Miscellaneous

View file

@ -1,8 +1,23 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<!-- photo_manager -->
<uses-permission android:name="android.permission.READ_MEDIA_VISUAL_USER_SELECTED" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
<uses-permission android:name="android.permission.MANAGE_MEDIA" />
<!-- /photo_manager -->
<application <application
android:label="immich" android:label="Immich"
android:name="${applicationName}" android:name=".ImmichApp"
android:icon="@mipmap/ic_launcher"> android:icon="@mipmap/ic_launcher"
android:usesCleartextTraffic="true"
android:requestLegacyExternalStorage="true"
android:largeHeap="true"
android:enableOnBackInvokedCallback="true"
>
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:exported="true" android:exported="true"

View file

@ -1,53 +1,87 @@
PODS: PODS:
- device_info_plus (0.0.1):
- Flutter
- Flutter (1.0.0) - Flutter (1.0.0)
- flutter_web_auth_2 (3.0.0):
- Flutter
- package_info_plus (0.4.5):
- Flutter
- path_provider_foundation (0.0.1): - path_provider_foundation (0.0.1):
- Flutter - Flutter
- FlutterMacOS - FlutterMacOS
- photo_manager (2.0.0): - photo_manager (2.0.0):
- Flutter - Flutter
- FlutterMacOS - FlutterMacOS
- "sqlite3 (3.45.3+1)": - sqflite (0.0.3):
- "sqlite3/common (= 3.45.3+1)" - Flutter
- "sqlite3/common (3.45.3+1)" - FlutterMacOS
- "sqlite3/fts5 (3.45.3+1)": - "sqlite3 (3.46.1+1)":
- "sqlite3/common (= 3.46.1+1)"
- "sqlite3/common (3.46.1+1)"
- "sqlite3/dbstatvtab (3.46.1+1)":
- sqlite3/common - sqlite3/common
- "sqlite3/perf-threadsafe (3.45.3+1)": - "sqlite3/fts5 (3.46.1+1)":
- sqlite3/common - sqlite3/common
- "sqlite3/rtree (3.45.3+1)": - "sqlite3/perf-threadsafe (3.46.1+1)":
- sqlite3/common
- "sqlite3/rtree (3.46.1+1)":
- sqlite3/common - sqlite3/common
- sqlite3_flutter_libs (0.0.1): - sqlite3_flutter_libs (0.0.1):
- Flutter - Flutter
- "sqlite3 (~> 3.45.3+1)" - "sqlite3 (~> 3.46.0+1)"
- sqlite3/dbstatvtab
- sqlite3/fts5 - sqlite3/fts5
- sqlite3/perf-threadsafe - sqlite3/perf-threadsafe
- sqlite3/rtree - sqlite3/rtree
- url_launcher_ios (0.0.1):
- Flutter
DEPENDENCIES: DEPENDENCIES:
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
- Flutter (from `Flutter`) - Flutter (from `Flutter`)
- flutter_web_auth_2 (from `.symlinks/plugins/flutter_web_auth_2/ios`)
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
- photo_manager (from `.symlinks/plugins/photo_manager/ios`) - photo_manager (from `.symlinks/plugins/photo_manager/ios`)
- sqflite (from `.symlinks/plugins/sqflite/darwin`)
- sqlite3_flutter_libs (from `.symlinks/plugins/sqlite3_flutter_libs/ios`) - sqlite3_flutter_libs (from `.symlinks/plugins/sqlite3_flutter_libs/ios`)
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
SPEC REPOS: SPEC REPOS:
trunk: trunk:
- sqlite3 - sqlite3
EXTERNAL SOURCES: EXTERNAL SOURCES:
device_info_plus:
:path: ".symlinks/plugins/device_info_plus/ios"
Flutter: Flutter:
:path: Flutter :path: Flutter
flutter_web_auth_2:
:path: ".symlinks/plugins/flutter_web_auth_2/ios"
package_info_plus:
:path: ".symlinks/plugins/package_info_plus/ios"
path_provider_foundation: path_provider_foundation:
:path: ".symlinks/plugins/path_provider_foundation/darwin" :path: ".symlinks/plugins/path_provider_foundation/darwin"
photo_manager: photo_manager:
:path: ".symlinks/plugins/photo_manager/ios" :path: ".symlinks/plugins/photo_manager/ios"
sqflite:
:path: ".symlinks/plugins/sqflite/darwin"
sqlite3_flutter_libs: sqlite3_flutter_libs:
:path: ".symlinks/plugins/sqlite3_flutter_libs/ios" :path: ".symlinks/plugins/sqlite3_flutter_libs/ios"
url_launcher_ios:
:path: ".symlinks/plugins/url_launcher_ios/ios"
SPEC CHECKSUMS: SPEC CHECKSUMS:
device_info_plus: 97af1d7e84681a90d0693e63169a5d50e0839a0d
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
flutter_web_auth_2: 051cf9f5dc366f31b5dcc4e2952c2b954767be8a
package_info_plus: 58f0028419748fad15bf008b270aaa8e54380b1c
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
photo_manager: ff695c7a1dd5bc379974953a2b5c0a293f7c4c8a photo_manager: ff695c7a1dd5bc379974953a2b5c0a293f7c4c8a
sqlite3: 02d1f07eaaa01f80a1c16b4b31dfcbb3345ee01a sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec
sqlite3_flutter_libs: 9bfe005308998aeca155330bbc2ea6dddf834a3b sqlite3: 0bb0e6389d824e40296f531b858a2a0b71c0d2fb
sqlite3_flutter_libs: c00457ebd31e59fa6bb830380ddba24d44fbcd3b
url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe
PODFILE CHECKSUM: 64c9b5291666c0ca3caabdfe9865c141ac40321d PODFILE CHECKSUM: 64c9b5291666c0ca3caabdfe9865c141ac40321d

View file

@ -57,5 +57,9 @@
<string>https</string> <string>https</string>
</array> </array>
<!-- /URL Launcher --> <!-- /URL Launcher -->
<!-- photo_manager -->
<key>NSPhotoLibraryUsageDescription</key>
<string>In order to access iOS photo library for local assets and albums</string>
<!-- /photo_manager -->
</dict> </dict>
</plist> </plist>

View file

@ -14,7 +14,11 @@ import 'database.repository.drift.dart';
@DriftDatabase(tables: [Logs, Store, LocalAlbum, Asset, User]) @DriftDatabase(tables: [Logs, Store, LocalAlbum, Asset, User])
class DriftDatabaseRepository extends $DriftDatabaseRepository { class DriftDatabaseRepository extends $DriftDatabaseRepository {
DriftDatabaseRepository([QueryExecutor? executor]) DriftDatabaseRepository([QueryExecutor? executor])
: super(executor ?? driftDatabase(name: 'db')); : super(executor ??
driftDatabase(
name: 'db',
native: const DriftNativeOptions(shareAcrossIsolates: true),
));
@override @override
int get schemaVersion => 1; int get schemaVersion => 1;

View file

@ -4,6 +4,9 @@ import 'dart:io';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter_web_auth_2/flutter_web_auth_2.dart'; import 'package:flutter_web_auth_2/flutter_web_auth_2.dart';
import 'package:http/http.dart'; import 'package:http/http.dart';
import 'package:immich_mobile/domain/interfaces/store.interface.dart';
import 'package:immich_mobile/domain/models/store.model.dart';
import 'package:immich_mobile/domain/services/user.service.dart';
import 'package:immich_mobile/service_locator.dart'; import 'package:immich_mobile/service_locator.dart';
import 'package:immich_mobile/utils/immich_api_client.dart'; import 'package:immich_mobile/utils/immich_api_client.dart';
import 'package:immich_mobile/utils/mixins/log_context.mixin.dart'; import 'package:immich_mobile/utils/mixins/log_context.mixin.dart';
@ -103,4 +106,32 @@ class LoginService with LogContext {
} }
return null; return null;
} }
Future<bool> tryLoginFromSplash() async {
final serverEndpoint =
await di<IStoreRepository>().tryGet(StoreKey.serverEndpoint);
if (serverEndpoint == null) {
return false;
}
ServiceLocator.registerApiClient(serverEndpoint);
ServiceLocator.registerPostValidationServices();
ServiceLocator.registerPostGlobalStates();
final accessToken =
await di<IStoreRepository>().tryGet(StoreKey.accessToken);
if (accessToken == null) {
return false;
}
/// Set token to interceptor
await di<ImmichApiClient>().init(accessToken: accessToken);
final user = await di<UserService>().getMyUser();
if (user == null) {
return false;
}
return true;
}
} }

View file

@ -1,4 +1,5 @@
import 'package:drift/isolate.dart'; import 'dart:async';
import 'package:immich_mobile/domain/interfaces/asset.interface.dart'; import 'package:immich_mobile/domain/interfaces/asset.interface.dart';
import 'package:immich_mobile/domain/models/asset.model.dart'; import 'package:immich_mobile/domain/models/asset.model.dart';
import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/domain/models/user.model.dart';
@ -12,56 +13,51 @@ import 'package:logging/logging.dart';
import 'package:openapi/api.dart'; import 'package:openapi/api.dart';
class SyncService with LogContext { class SyncService with LogContext {
final DriftDatabaseRepository _db; SyncService();
SyncService(this._db);
Future<bool> doFullSyncForUserDrift( Future<bool> doFullSyncForUserDrift(
User user, { User user, {
DateTime? updatedUtil, DateTime? updatedUtil,
int? limit, int? limit,
}) async { }) async {
final helper = IsolateHelper()..preIsolateHandling(); return await IsolateHelper.run(() async {
try { try {
await _db.computeWithDatabase( final logger = Logger("SyncService <Isolate>");
connect: (connection) => DriftDatabaseRepository(connection), final syncClient = di<ImmichApiClient>().getSyncApi();
computation: (database) async {
helper.postIsolateHandling(database: database);
final logger = Logger("SyncService <Isolate>");
final syncClient = di<ImmichApiClient>().getSyncApi();
final chunkSize = limit ?? kFullSyncChunkSize; final chunkSize = limit ?? kFullSyncChunkSize;
final updatedTill = updatedUtil ?? DateTime.now().toUtc(); final updatedTill = updatedUtil ?? DateTime.now().toUtc();
updatedUtil ??= DateTime.now().toUtc(); updatedUtil ??= DateTime.now().toUtc();
String? lastAssetId; String? lastAssetId;
while (true) { while (true) {
logger.info( logger.info(
"Requesting more chunks from lastId - ${lastAssetId ?? "<initial_fetch>"}", "Requesting more chunks from lastId - ${lastAssetId ?? "<initial_fetch>"}",
); );
final assets = await syncClient.getFullSyncForUser(AssetFullSyncDto( final assets = await syncClient.getFullSyncForUser(AssetFullSyncDto(
limit: chunkSize, limit: chunkSize,
updatedUntil: updatedTill, updatedUntil: updatedTill,
lastId: lastAssetId, lastId: lastAssetId,
userId: user.id, userId: user.id,
)); ));
if (assets == null) { if (assets == null) {
break; break;
}
await di<IAssetRepository>().addAll(assets.map(Asset.remote));
lastAssetId = assets.lastOrNull?.id;
if (assets.length != chunkSize) break;
} }
return true; await di<IAssetRepository>().addAll(assets.map(Asset.remote));
},
); lastAssetId = assets.lastOrNull?.id;
} catch (e, s) { if (assets.length != chunkSize) break;
log.severe("Error performing full sync for user - ${user.name}", e, s); }
}
return false; return true;
} catch (e, s) {
log.severe("Error performing full sync for user - ${user.name}", e, s);
} finally {
await di<DriftDatabaseRepository>().close();
}
return false;
});
} }
} }

View file

@ -9,13 +9,19 @@ class UserService with LogContext {
Future<User?> getMyUser() async { Future<User?> getMyUser() async {
try { try {
final userDto = await _userApi.getMyUser(); final [
userDto as UserAdminResponseDto?,
preferencesDto as UserPreferencesResponseDto?
] = await Future.wait([
_userApi.getMyUser(),
_userApi.getMyPreferences(),
]);
if (userDto == null) { if (userDto == null) {
log.severe("Cannot fetch my user."); log.severe("Cannot fetch my user.");
return null; return null;
} }
final preferencesDto = await _userApi.getMyPreferences();
return User.fromAdminDto(userDto, preferencesDto); return User.fromAdminDto(userDto, preferencesDto);
} catch (e, s) { } catch (e, s) {
log.severe("Error while fetching my user", e, s); log.severe("Error while fetching my user", e, s);

View file

@ -1,6 +1,9 @@
import 'dart:async';
import 'package:auto_route/auto_route.dart'; import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:immich_mobile/domain/services/login.service.dart';
import 'package:immich_mobile/presentation/components/image/immich_logo.widget.dart'; import 'package:immich_mobile/presentation/components/image/immich_logo.widget.dart';
import 'package:immich_mobile/presentation/modules/login/states/login_page.state.dart'; import 'package:immich_mobile/presentation/modules/login/states/login_page.state.dart';
import 'package:immich_mobile/presentation/router/router.dart'; import 'package:immich_mobile/presentation/router/router.dart';
@ -45,6 +48,14 @@ class _SplashScreenState extends State<SplashScreenPage>
super.dispose(); super.dispose();
} }
Future<void> _tryLogin() async {
if (await di<LoginService>().tryLoginFromSplash() && mounted) {
unawaited(context.replaceRoute(const TabControllerRoute()));
} else {
unawaited(context.replaceRoute(const LoginRoute()));
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
@ -52,7 +63,7 @@ class _SplashScreenState extends State<SplashScreenPage>
future: di.allReady(), future: di.allReady(),
builder: (_, snap) { builder: (_, snap) {
if (snap.hasData) { if (snap.hasData) {
context.replaceRoute(const LoginRoute()); _tryLogin();
} else if (snap.hasError) { } else if (snap.hasError) {
log.severe( log.severe(
"Error while initializing the app", "Error while initializing the app",

View file

@ -92,7 +92,7 @@ class ServiceLocator {
_registerFactory<ServerInfoService>(() => ServerInfoService( _registerFactory<ServerInfoService>(() => ServerInfoService(
di<ImmichApiClient>().getServerApi(), di<ImmichApiClient>().getServerApi(),
)); ));
_registerFactory<SyncService>(() => SyncService(di())); _registerFactory<SyncService>(() => SyncService());
} }
static void registerPostGlobalStates() { static void registerPostGlobalStates() {

View file

@ -1,4 +1,8 @@
import 'dart:async';
import 'dart:isolate';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:immich_mobile/domain/repositories/database.repository.dart'; import 'package:immich_mobile/domain/repositories/database.repository.dart';
import 'package:immich_mobile/service_locator.dart'; import 'package:immich_mobile/service_locator.dart';
import 'package:immich_mobile/utils/immich_api_client.dart'; import 'package:immich_mobile/utils/immich_api_client.dart';
@ -12,10 +16,13 @@ class _ImApiClientData {
const _ImApiClientData({required this.endpoint, required this.headersMap}); const _ImApiClientData({required this.endpoint, required this.headersMap});
} }
// !! Should be used only from the root isolate
class IsolateHelper { class IsolateHelper {
// Cache the ApiClient to reconstruct it later after inside the isolate // Cache the ApiClient to reconstruct it later after inside the isolate
late final _ImApiClientData? _clientData; late final _ImApiClientData? _clientData;
static RootIsolateToken get _rootToken => RootIsolateToken.instance!;
IsolateHelper(); IsolateHelper();
void preIsolateHandling() { void preIsolateHandling() {
@ -26,7 +33,7 @@ class IsolateHelper {
); );
} }
void postIsolateHandling({required DriftDatabaseRepository database}) { void postIsolateHandling() {
assert(_clientData != null); assert(_clientData != null);
// Reconstruct client from cached data // Reconstruct client from cached data
final client = ImmichApiClient(endpoint: _clientData!.endpoint); final client = ImmichApiClient(endpoint: _clientData!.endpoint);
@ -36,11 +43,21 @@ class IsolateHelper {
// Register all services in the isolates memory // Register all services in the isolates memory
ServiceLocator.configureServicesForIsolate( ServiceLocator.configureServicesForIsolate(
database: database, database: DriftDatabaseRepository(),
apiClient: client, apiClient: client,
); );
// Init log manager to continue listening to log events // Init log manager to continue listening to log events
LogManager.I.init(); LogManager.I.init();
} }
static Future<T> run<T>(FutureOr<T> Function() computation) async {
final helper = IsolateHelper()..preIsolateHandling();
final token = _rootToken;
return await Isolate.run(() async {
BackgroundIsolateBinaryMessenger.ensureInitialized(token);
helper.postIsolateHandling();
return await computation();
});
}
} }

View file

@ -359,10 +359,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: flutter_adaptive_scaffold name: flutter_adaptive_scaffold
sha256: "6b587d439c7da037432bbfc78d9676e1d08f2d7490f08e8d689a20f08e049802" sha256: "8c515a2cb8abb3a567f8e77f10b33f47bb6fcadfe31f62364e0aca36280cdf93"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.2.6" version: "0.3.1"
flutter_bloc: flutter_bloc:
dependency: "direct main" dependency: "direct main"
description: description:
@ -769,6 +769,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.1" version: "2.1.1"
pigeon:
dependency: "direct dev"
description:
name: pigeon
sha256: "95481446c02fa79fcf0e8014882f8a3b87fd06c257e9e1c3d4cc6d102a925ad8"
url: "https://pub.dev"
source: hosted
version: "22.4.0"
platform: platform:
dependency: transitive dependency: transitive
description: description:

View file

@ -48,7 +48,7 @@ dependencies:
flutter_web_auth_2: ^3.1.2 flutter_web_auth_2: ^3.1.2
# components # components
material_symbols_icons: ^4.2785.1 material_symbols_icons: ^4.2785.1
flutter_adaptive_scaffold: ^0.2.6 flutter_adaptive_scaffold: ^0.3.1
scrollable_positioned_list: ^0.3.8 scrollable_positioned_list: ^0.3.8
cached_network_image: ^3.4.1 cached_network_image: ^3.4.1
flutter_cache_manager: ^3.4.1 flutter_cache_manager: ^3.4.1
@ -77,6 +77,8 @@ dev_dependencies:
slang_build_runner: ^3.31.0 slang_build_runner: ^3.31.0
# Assets constant generator # Assets constant generator
flutter_gen_runner: ^5.7.0 flutter_gen_runner: ^5.7.0
# Type safe platform channels
pigeon: ^22.4.0
flutter: flutter:
uses-material-design: true uses-material-design: true