feat: locked view mobile (#18316)

* feat: locked/private view

* feat: locked/private view

* feat: mobile lock/private view

* feat: mobile lock/private view

* merge main

* pr feedback

* pr feedback

* bottom sheet sizing

* always lock when navigating away
This commit is contained in:
Alex
2025-05-20 08:35:22 -05:00
committed by GitHub
parent 397808dd1a
commit fe71894308
57 changed files with 1893 additions and 289 deletions
@@ -1,4 +1,5 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/enums.dart';
import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/interfaces/asset_api.interface.dart';
import 'package:immich_mobile/providers/api.provider.dart';
@@ -48,4 +49,27 @@ class AssetApiRepository extends ApiRepository implements IAssetApiRepository {
}
return result;
}
@override
Future<void> updateVisibility(
List<String> ids,
AssetVisibilityEnum visibility,
) async {
return _api.updateAssets(
AssetBulkUpdateDto(ids: ids, visibility: _mapVisibility(visibility)),
);
}
_mapVisibility(AssetVisibilityEnum visibility) {
switch (visibility) {
case AssetVisibilityEnum.timeline:
return AssetVisibility.timeline;
case AssetVisibilityEnum.hidden:
return AssetVisibility.hidden;
case AssetVisibilityEnum.locked:
return AssetVisibility.locked;
case AssetVisibilityEnum.archive:
return AssetVisibility.archive;
}
}
}
@@ -55,4 +55,26 @@ class AuthApiRepository extends ApiRepository implements IAuthApiRepository {
userId: dto.userId,
);
}
@override
Future<bool> unlockPinCode(String pinCode) async {
try {
await _apiService.authenticationApi
.unlockAuthSession(SessionUnlockDto(pinCode: pinCode));
return true;
} catch (_) {
return false;
}
}
@override
Future<void> setupPinCode(String pinCode) {
return _apiService.authenticationApi
.setupPinCode(PinCodeSetupDto(pinCode: pinCode));
}
@override
Future<void> lockPinCode() {
return _apiService.authenticationApi.lockAuthSession();
}
}
@@ -0,0 +1,35 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/interfaces/biometric.interface.dart';
import 'package:immich_mobile/models/auth/biometric_status.model.dart';
import 'package:local_auth/local_auth.dart';
final biometricRepositoryProvider =
Provider((ref) => BiometricRepository(LocalAuthentication()));
class BiometricRepository implements IBiometricRepository {
final LocalAuthentication _localAuth;
BiometricRepository(this._localAuth);
@override
Future<BiometricStatus> getStatus() async {
final bool canAuthenticateWithBiometrics =
await _localAuth.canCheckBiometrics;
final bool canAuthenticate =
canAuthenticateWithBiometrics || await _localAuth.isDeviceSupported();
final availableBiometric = await _localAuth.getAvailableBiometrics();
return BiometricStatus(
canAuthenticate: canAuthenticate,
availableBiometrics: availableBiometric,
);
}
@override
Future<bool> authenticate(String? message) async {
return _localAuth.authenticate(
localizedReason: message ?? 'please_auth_to_access'.tr(),
);
}
}
@@ -0,0 +1,27 @@
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/interfaces/secure_storage.interface.dart';
final secureStorageRepositoryProvider =
Provider((ref) => SecureStorageRepository(const FlutterSecureStorage()));
class SecureStorageRepository implements ISecureStorageRepository {
final FlutterSecureStorage _secureStorage;
SecureStorageRepository(this._secureStorage);
@override
Future<String?> read(String key) {
return _secureStorage.read(key: key);
}
@override
Future<void> write(String key, String value) {
return _secureStorage.write(key: key, value: value);
}
@override
Future<void> delete(String key) {
return _secureStorage.delete(key: key);
}
}
@@ -45,8 +45,8 @@ class TimelineRepository extends DatabaseRepository
.where()
.ownerIdEqualToAnyChecksum(fastHash(userId))
.filter()
.isArchivedEqualTo(true)
.isTrashedEqualTo(false)
.visibilityEqualTo(AssetVisibilityEnum.archive)
.sortByFileCreatedAtDesc();
return _watchRenderList(query, GroupAssetsBy.none);
@@ -59,6 +59,8 @@ class TimelineRepository extends DatabaseRepository
.ownerIdEqualToAnyChecksum(fastHash(userId))
.filter()
.isFavoriteEqualTo(true)
.not()
.visibilityEqualTo(AssetVisibilityEnum.locked)
.isTrashedEqualTo(false)
.sortByFileCreatedAtDesc();
@@ -94,8 +96,8 @@ class TimelineRepository extends DatabaseRepository
Stream<RenderList> watchAllVideosTimeline() {
final query = db.assets
.filter()
.isArchivedEqualTo(false)
.isTrashedEqualTo(false)
.visibilityEqualTo(AssetVisibilityEnum.timeline)
.typeEqualTo(AssetType.video)
.sortByFileCreatedAtDesc();
@@ -111,9 +113,9 @@ class TimelineRepository extends DatabaseRepository
.where()
.ownerIdEqualToAnyChecksum(fastHash(userId))
.filter()
.isArchivedEqualTo(false)
.isTrashedEqualTo(false)
.stackPrimaryAssetIdIsNull()
.visibilityEqualTo(AssetVisibilityEnum.timeline)
.sortByFileCreatedAtDesc();
return _watchRenderList(query, groupAssetByOption);
@@ -129,8 +131,8 @@ class TimelineRepository extends DatabaseRepository
.where()
.anyOf(isarUserIds, (qb, id) => qb.ownerIdEqualToAnyChecksum(id))
.filter()
.isArchivedEqualTo(false)
.isTrashedEqualTo(false)
.visibilityEqualTo(AssetVisibilityEnum.timeline)
.stackPrimaryAssetIdIsNull()
.sortByFileCreatedAtDesc();
return _watchRenderList(query, groupAssetByOption);
@@ -151,6 +153,7 @@ class TimelineRepository extends DatabaseRepository
.remoteIdIsNotNull()
.filter()
.ownerIdEqualTo(fastHash(userId))
.visibilityEqualTo(AssetVisibilityEnum.timeline)
.isTrashedEqualTo(false)
.stackPrimaryAssetIdIsNull()
.sortByFileCreatedAtDesc();
@@ -158,6 +161,22 @@ class TimelineRepository extends DatabaseRepository
return _watchRenderList(query, GroupAssetsBy.none);
}
@override
Stream<RenderList> watchLockedTimeline(
String userId,
GroupAssetsBy getGroupByOption,
) {
final query = db.assets
.where()
.ownerIdEqualToAnyChecksum(fastHash(userId))
.filter()
.visibilityEqualTo(AssetVisibilityEnum.locked)
.isTrashedEqualTo(false)
.sortByFileCreatedAtDesc();
return _watchRenderList(query, getGroupByOption);
}
Stream<RenderList> _watchRenderList(
QueryBuilder<Asset, Asset, QAfterSortBy> query,
GroupAssetsBy groupAssetsBy,