0
Fork 0
mirror of https://github.com/immich-app/immich.git synced 2025-02-04 01:09:14 -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
*.drift.dart
*.gen.dart
*.g.swift
*.g.kt
openapi/*
# Miscellaneous

View file

@ -1,8 +1,23 @@
<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
android:label="immich"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
android:label="Immich"
android:name=".ImmichApp"
android:icon="@mipmap/ic_launcher"
android:usesCleartextTraffic="true"
android:requestLegacyExternalStorage="true"
android:largeHeap="true"
android:enableOnBackInvokedCallback="true"
>
<activity
android:name=".MainActivity"
android:exported="true"

View file

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

View file

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

View file

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

View file

@ -4,6 +4,9 @@ import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter_web_auth_2/flutter_web_auth_2.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/utils/immich_api_client.dart';
import 'package:immich_mobile/utils/mixins/log_context.mixin.dart';
@ -103,4 +106,32 @@ class LoginService with LogContext {
}
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/models/asset.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';
class SyncService with LogContext {
final DriftDatabaseRepository _db;
SyncService(this._db);
SyncService();
Future<bool> doFullSyncForUserDrift(
User user, {
DateTime? updatedUtil,
int? limit,
}) async {
final helper = IsolateHelper()..preIsolateHandling();
try {
await _db.computeWithDatabase(
connect: (connection) => DriftDatabaseRepository(connection),
computation: (database) async {
helper.postIsolateHandling(database: database);
final logger = Logger("SyncService <Isolate>");
final syncClient = di<ImmichApiClient>().getSyncApi();
return await IsolateHelper.run(() async {
try {
final logger = Logger("SyncService <Isolate>");
final syncClient = di<ImmichApiClient>().getSyncApi();
final chunkSize = limit ?? kFullSyncChunkSize;
final updatedTill = updatedUtil ?? DateTime.now().toUtc();
updatedUtil ??= DateTime.now().toUtc();
String? lastAssetId;
final chunkSize = limit ?? kFullSyncChunkSize;
final updatedTill = updatedUtil ?? DateTime.now().toUtc();
updatedUtil ??= DateTime.now().toUtc();
String? lastAssetId;
while (true) {
logger.info(
"Requesting more chunks from lastId - ${lastAssetId ?? "<initial_fetch>"}",
);
while (true) {
logger.info(
"Requesting more chunks from lastId - ${lastAssetId ?? "<initial_fetch>"}",
);
final assets = await syncClient.getFullSyncForUser(AssetFullSyncDto(
limit: chunkSize,
updatedUntil: updatedTill,
lastId: lastAssetId,
userId: user.id,
));
if (assets == null) {
break;
}
await di<IAssetRepository>().addAll(assets.map(Asset.remote));
lastAssetId = assets.lastOrNull?.id;
if (assets.length != chunkSize) break;
final assets = await syncClient.getFullSyncForUser(AssetFullSyncDto(
limit: chunkSize,
updatedUntil: updatedTill,
lastId: lastAssetId,
userId: user.id,
));
if (assets == null) {
break;
}
return true;
},
);
} catch (e, s) {
log.severe("Error performing full sync for user - ${user.name}", e, s);
}
return false;
await di<IAssetRepository>().addAll(assets.map(Asset.remote));
lastAssetId = assets.lastOrNull?.id;
if (assets.length != chunkSize) break;
}
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 {
try {
final userDto = await _userApi.getMyUser();
final [
userDto as UserAdminResponseDto?,
preferencesDto as UserPreferencesResponseDto?
] = await Future.wait([
_userApi.getMyUser(),
_userApi.getMyPreferences(),
]);
if (userDto == null) {
log.severe("Cannot fetch my user.");
return null;
}
final preferencesDto = await _userApi.getMyPreferences();
return User.fromAdminDto(userDto, preferencesDto);
} catch (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:flutter/material.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/modules/login/states/login_page.state.dart';
import 'package:immich_mobile/presentation/router/router.dart';
@ -45,6 +48,14 @@ class _SplashScreenState extends State<SplashScreenPage>
super.dispose();
}
Future<void> _tryLogin() async {
if (await di<LoginService>().tryLoginFromSplash() && mounted) {
unawaited(context.replaceRoute(const TabControllerRoute()));
} else {
unawaited(context.replaceRoute(const LoginRoute()));
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
@ -52,7 +63,7 @@ class _SplashScreenState extends State<SplashScreenPage>
future: di.allReady(),
builder: (_, snap) {
if (snap.hasData) {
context.replaceRoute(const LoginRoute());
_tryLogin();
} else if (snap.hasError) {
log.severe(
"Error while initializing the app",

View file

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

View file

@ -1,4 +1,8 @@
import 'dart:async';
import 'dart:isolate';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:immich_mobile/domain/repositories/database.repository.dart';
import 'package:immich_mobile/service_locator.dart';
import 'package:immich_mobile/utils/immich_api_client.dart';
@ -12,10 +16,13 @@ class _ImApiClientData {
const _ImApiClientData({required this.endpoint, required this.headersMap});
}
// !! Should be used only from the root isolate
class IsolateHelper {
// Cache the ApiClient to reconstruct it later after inside the isolate
late final _ImApiClientData? _clientData;
static RootIsolateToken get _rootToken => RootIsolateToken.instance!;
IsolateHelper();
void preIsolateHandling() {
@ -26,7 +33,7 @@ class IsolateHelper {
);
}
void postIsolateHandling({required DriftDatabaseRepository database}) {
void postIsolateHandling() {
assert(_clientData != null);
// Reconstruct client from cached data
final client = ImmichApiClient(endpoint: _clientData!.endpoint);
@ -36,11 +43,21 @@ class IsolateHelper {
// Register all services in the isolates memory
ServiceLocator.configureServicesForIsolate(
database: database,
database: DriftDatabaseRepository(),
apiClient: client,
);
// Init log manager to continue listening to log events
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"
description:
name: flutter_adaptive_scaffold
sha256: "6b587d439c7da037432bbfc78d9676e1d08f2d7490f08e8d689a20f08e049802"
sha256: "8c515a2cb8abb3a567f8e77f10b33f47bb6fcadfe31f62364e0aca36280cdf93"
url: "https://pub.dev"
source: hosted
version: "0.2.6"
version: "0.3.1"
flutter_bloc:
dependency: "direct main"
description:
@ -769,6 +769,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.1"
pigeon:
dependency: "direct dev"
description:
name: pigeon
sha256: "95481446c02fa79fcf0e8014882f8a3b87fd06c257e9e1c3d4cc6d102a925ad8"
url: "https://pub.dev"
source: hosted
version: "22.4.0"
platform:
dependency: transitive
description:

View file

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