Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 412633d0aa | |||
| 2bba33f834 |
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@immich/cli",
|
"name": "@immich/cli",
|
||||||
"version": "2.2.90",
|
"version": "2.2.89",
|
||||||
"description": "Command Line Interface (CLI) for Immich",
|
"description": "Command Line Interface (CLI) for Immich",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"exports": "./dist/index.js",
|
"exports": "./dist/index.js",
|
||||||
|
|||||||
Vendored
-4
@@ -1,8 +1,4 @@
|
|||||||
[
|
[
|
||||||
{
|
|
||||||
"label": "v1.142.1",
|
|
||||||
"url": "https://v1.142.1.archive.immich.app"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"label": "v1.142.0",
|
"label": "v1.142.0",
|
||||||
"url": "https://v1.142.0.archive.immich.app"
|
"url": "https://v1.142.0.archive.immich.app"
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "immich-e2e",
|
"name": "immich-e2e",
|
||||||
"version": "1.142.1",
|
"version": "1.142.0",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
+6
-4
@@ -54,6 +54,12 @@ class BackgroundWorker(context: Context, params: WorkerParameters) :
|
|||||||
|
|
||||||
private var foregroundFuture: ListenableFuture<Void>? = null
|
private var foregroundFuture: ListenableFuture<Void>? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
if (!loader.initialized()) {
|
||||||
|
loader.startInitialization(ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val NOTIFICATION_CHANNEL_ID = "immich::background_worker::notif"
|
private const val NOTIFICATION_CHANNEL_ID = "immich::background_worker::notif"
|
||||||
private const val NOTIFICATION_ID = 100
|
private const val NOTIFICATION_ID = 100
|
||||||
@@ -62,10 +68,6 @@ class BackgroundWorker(context: Context, params: WorkerParameters) :
|
|||||||
override fun startWork(): ListenableFuture<Result> {
|
override fun startWork(): ListenableFuture<Result> {
|
||||||
Log.i(TAG, "Starting background upload worker")
|
Log.i(TAG, "Starting background upload worker")
|
||||||
|
|
||||||
if (!loader.initialized()) {
|
|
||||||
loader.startInitialization(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
val notificationChannel = NotificationChannel(
|
val notificationChannel = NotificationChannel(
|
||||||
NOTIFICATION_CHANNEL_ID,
|
NOTIFICATION_CHANNEL_ID,
|
||||||
NOTIFICATION_CHANNEL_ID,
|
NOTIFICATION_CHANNEL_ID,
|
||||||
|
|||||||
@@ -35,8 +35,8 @@ platform :android do
|
|||||||
task: 'bundle',
|
task: 'bundle',
|
||||||
build_type: 'Release',
|
build_type: 'Release',
|
||||||
properties: {
|
properties: {
|
||||||
"android.injected.version.code" => 3015,
|
"android.injected.version.code" => 3014,
|
||||||
"android.injected.version.name" => "1.142.1",
|
"android.injected.version.name" => "1.142.0",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
upload_to_play_store(skip_upload_apk: true, skip_upload_images: true, skip_upload_screenshots: true, aab: '../build/app/outputs/bundle/release/app-release.aab')
|
upload_to_play_store(skip_upload_apk: true, skip_upload_images: true, skip_upload_screenshots: true, aab: '../build/app/outputs/bundle/release/app-release.aab')
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ platform :ios do
|
|||||||
path: "./Runner.xcodeproj",
|
path: "./Runner.xcodeproj",
|
||||||
)
|
)
|
||||||
increment_version_number(
|
increment_version_number(
|
||||||
version_number: "1.142.1"
|
version_number: "1.142.0"
|
||||||
)
|
)
|
||||||
increment_build_number(
|
increment_build_number(
|
||||||
build_number: latest_testflight_build_number + 1,
|
build_number: latest_testflight_build_number + 1,
|
||||||
|
|||||||
@@ -1,20 +1,22 @@
|
|||||||
import 'package:immich_mobile/domain/models/album/local_album.model.dart';
|
import 'package:immich_mobile/domain/models/album/local_album.model.dart';
|
||||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||||
import 'package:immich_mobile/domain/models/exif.model.dart';
|
import 'package:immich_mobile/domain/models/exif.model.dart';
|
||||||
import 'package:immich_mobile/extensions/platform_extensions.dart';
|
|
||||||
import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/remote_asset.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/remote_asset.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/utils/exif.converter.dart';
|
import 'package:immich_mobile/infrastructure/utils/exif.converter.dart';
|
||||||
|
import 'package:platform/platform.dart';
|
||||||
|
|
||||||
class AssetService {
|
class AssetService {
|
||||||
final RemoteAssetRepository _remoteAssetRepository;
|
final RemoteAssetRepository _remoteAssetRepository;
|
||||||
final DriftLocalAssetRepository _localAssetRepository;
|
final DriftLocalAssetRepository _localAssetRepository;
|
||||||
|
final Platform _platform;
|
||||||
|
|
||||||
const AssetService({
|
const AssetService({
|
||||||
required RemoteAssetRepository remoteAssetRepository,
|
required RemoteAssetRepository remoteAssetRepository,
|
||||||
required DriftLocalAssetRepository localAssetRepository,
|
required DriftLocalAssetRepository localAssetRepository,
|
||||||
}) : _remoteAssetRepository = remoteAssetRepository,
|
}) : _remoteAssetRepository = remoteAssetRepository,
|
||||||
_localAssetRepository = localAssetRepository;
|
_localAssetRepository = localAssetRepository,
|
||||||
|
_platform = const LocalPlatform();
|
||||||
|
|
||||||
Future<BaseAsset?> getAsset(BaseAsset asset) {
|
Future<BaseAsset?> getAsset(BaseAsset asset) {
|
||||||
final id = asset is LocalAsset ? asset.id : (asset as RemoteAsset).id;
|
final id = asset is LocalAsset ? asset.id : (asset as RemoteAsset).id;
|
||||||
@@ -69,7 +71,7 @@ class AssetService {
|
|||||||
width = exif?.width ?? asset.width?.toDouble();
|
width = exif?.width ?? asset.width?.toDouble();
|
||||||
height = exif?.height ?? asset.height?.toDouble();
|
height = exif?.height ?? asset.height?.toDouble();
|
||||||
} else if (asset is LocalAsset) {
|
} else if (asset is LocalAsset) {
|
||||||
isFlipped = CurrentPlatform.isAndroid && (asset.orientation == 90 || asset.orientation == 270);
|
isFlipped = _platform.isAndroid && (asset.orientation == 90 || asset.orientation == 270);
|
||||||
width = asset.width?.toDouble();
|
width = asset.width?.toDouble();
|
||||||
height = asset.height?.toDouble();
|
height = asset.height?.toDouble();
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,24 +1,29 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:immich_mobile/domain/models/album/local_album.model.dart';
|
import 'package:immich_mobile/domain/models/album/local_album.model.dart';
|
||||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||||
import 'package:immich_mobile/extensions/platform_extensions.dart';
|
|
||||||
import 'package:immich_mobile/infrastructure/repositories/local_album.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/local_album.repository.dart';
|
||||||
import 'package:immich_mobile/platform/native_sync_api.g.dart';
|
import 'package:immich_mobile/platform/native_sync_api.g.dart';
|
||||||
import 'package:immich_mobile/utils/datetime_helpers.dart';
|
import 'package:immich_mobile/utils/datetime_helpers.dart';
|
||||||
import 'package:immich_mobile/utils/diff.dart';
|
import 'package:immich_mobile/utils/diff.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
|
import 'package:platform/platform.dart';
|
||||||
|
|
||||||
class LocalSyncService {
|
class LocalSyncService {
|
||||||
final DriftLocalAlbumRepository _localAlbumRepository;
|
final DriftLocalAlbumRepository _localAlbumRepository;
|
||||||
final NativeSyncApi _nativeSyncApi;
|
final NativeSyncApi _nativeSyncApi;
|
||||||
|
final Platform _platform;
|
||||||
final Logger _log = Logger("DeviceSyncService");
|
final Logger _log = Logger("DeviceSyncService");
|
||||||
|
|
||||||
LocalSyncService({required DriftLocalAlbumRepository localAlbumRepository, required NativeSyncApi nativeSyncApi})
|
LocalSyncService({
|
||||||
: _localAlbumRepository = localAlbumRepository,
|
required DriftLocalAlbumRepository localAlbumRepository,
|
||||||
_nativeSyncApi = nativeSyncApi;
|
required NativeSyncApi nativeSyncApi,
|
||||||
|
Platform? platform,
|
||||||
|
}) : _localAlbumRepository = localAlbumRepository,
|
||||||
|
_nativeSyncApi = nativeSyncApi,
|
||||||
|
_platform = platform ?? const LocalPlatform();
|
||||||
|
|
||||||
Future<void> sync({bool full = false}) async {
|
Future<void> sync({bool full = false}) async {
|
||||||
final Stopwatch stopwatch = Stopwatch()..start();
|
final Stopwatch stopwatch = Stopwatch()..start();
|
||||||
@@ -48,14 +53,14 @@ class LocalSyncService {
|
|||||||
final dbAlbums = await _localAlbumRepository.getAll();
|
final dbAlbums = await _localAlbumRepository.getAll();
|
||||||
// On Android, we need to sync all albums since it is not possible to
|
// On Android, we need to sync all albums since it is not possible to
|
||||||
// detect album deletions from the native side
|
// detect album deletions from the native side
|
||||||
if (CurrentPlatform.isAndroid) {
|
if (_platform.isAndroid) {
|
||||||
for (final album in dbAlbums) {
|
for (final album in dbAlbums) {
|
||||||
final deviceIds = await _nativeSyncApi.getAssetIdsForAlbum(album.id);
|
final deviceIds = await _nativeSyncApi.getAssetIdsForAlbum(album.id);
|
||||||
await _localAlbumRepository.syncDeletes(album.id, deviceIds);
|
await _localAlbumRepository.syncDeletes(album.id, deviceIds);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (CurrentPlatform.isIOS) {
|
if (_platform.isIOS) {
|
||||||
// On iOS, we need to full sync albums that are marked as cloud as the delta sync
|
// On iOS, we need to full sync albums that are marked as cloud as the delta sync
|
||||||
// does not include changes for cloud albums. If ignoreIcloudAssets is enabled,
|
// does not include changes for cloud albums. If ignoreIcloudAssets is enabled,
|
||||||
// remove the albums from the local database from the previous sync
|
// remove the albums from the local database from the previous sync
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
import 'package:flutter/foundation.dart';
|
|
||||||
|
|
||||||
extension CurrentPlatform on TargetPlatform {
|
|
||||||
@pragma('vm:prefer-inline')
|
|
||||||
static bool get isIOS => defaultTargetPlatform == TargetPlatform.iOS;
|
|
||||||
|
|
||||||
@pragma('vm:prefer-inline')
|
|
||||||
static bool get isAndroid => defaultTargetPlatform == TargetPlatform.android;
|
|
||||||
}
|
|
||||||
@@ -1,20 +1,22 @@
|
|||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import 'package:immich_mobile/domain/models/album/local_album.model.dart';
|
import 'package:immich_mobile/domain/models/album/local_album.model.dart';
|
||||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||||
import 'package:immich_mobile/extensions/platform_extensions.dart';
|
|
||||||
import 'package:immich_mobile/infrastructure/entities/local_album.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/local_album.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/local_album.entity.drift.dart';
|
import 'package:immich_mobile/infrastructure/entities/local_album.entity.drift.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/local_album_asset.entity.drift.dart';
|
import 'package:immich_mobile/infrastructure/entities/local_album_asset.entity.drift.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/local_asset.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/local_asset.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/local_asset.entity.drift.dart';
|
import 'package:immich_mobile/infrastructure/entities/local_asset.entity.drift.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||||
|
import 'package:platform/platform.dart';
|
||||||
|
|
||||||
enum SortLocalAlbumsBy { id, backupSelection, isIosSharedAlbum, name, assetCount, newestAsset }
|
enum SortLocalAlbumsBy { id, backupSelection, isIosSharedAlbum, name, assetCount, newestAsset }
|
||||||
|
|
||||||
class DriftLocalAlbumRepository extends DriftDatabaseRepository {
|
class DriftLocalAlbumRepository extends DriftDatabaseRepository {
|
||||||
final Drift _db;
|
final Drift _db;
|
||||||
|
final Platform _platform;
|
||||||
const DriftLocalAlbumRepository(this._db) : super(_db);
|
const DriftLocalAlbumRepository(this._db, {Platform? platform})
|
||||||
|
: _platform = platform ?? const LocalPlatform(),
|
||||||
|
super(_db);
|
||||||
|
|
||||||
Future<List<LocalAlbum>> getAll({Set<SortLocalAlbumsBy> sortBy = const {}}) {
|
Future<List<LocalAlbum>> getAll({Set<SortLocalAlbumsBy> sortBy = const {}}) {
|
||||||
final assetCount = _db.localAlbumAssetEntity.assetId.count();
|
final assetCount = _db.localAlbumAssetEntity.assetId.count();
|
||||||
@@ -59,7 +61,7 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository {
|
|||||||
// Remove all assets that are only in this particular album
|
// Remove all assets that are only in this particular album
|
||||||
// We cannot remove all assets in the album because they might be in other albums in iOS
|
// We cannot remove all assets in the album because they might be in other albums in iOS
|
||||||
// That is not the case on Android since asset <-> album has one:one mapping
|
// That is not the case on Android since asset <-> album has one:one mapping
|
||||||
final assetsToDelete = CurrentPlatform.isIOS ? await _getUniqueAssetsInAlbum(albumId) : await getAssetIds(albumId);
|
final assetsToDelete = _platform.isIOS ? await _getUniqueAssetsInAlbum(albumId) : await getAssetIds(albumId);
|
||||||
await _deleteAssets(assetsToDelete);
|
await _deleteAssets(assetsToDelete);
|
||||||
|
|
||||||
await _db.managers.localAlbumEntity
|
await _db.managers.localAlbumEntity
|
||||||
@@ -142,7 +144,7 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (CurrentPlatform.isAndroid) {
|
if (_platform.isAndroid) {
|
||||||
// On Android, an asset can only be in one album
|
// On Android, an asset can only be in one album
|
||||||
// So, get the albums that are marked for deletion
|
// So, get the albums that are marked for deletion
|
||||||
// and delete all the assets that are in those albums
|
// and delete all the assets that are in those albums
|
||||||
@@ -263,7 +265,7 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository {
|
|||||||
return Future.value();
|
return Future.value();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (CurrentPlatform.isAndroid) {
|
if (_platform.isAndroid) {
|
||||||
return _deleteAssets(assetIds);
|
return _deleteAssets(assetIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,6 @@ import 'package:easy_localization/easy_localization.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/domain/models/album/local_album.model.dart';
|
import 'package:immich_mobile/domain/models/album/local_album.model.dart';
|
||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
|
||||||
import 'package:immich_mobile/entities/store.entity.dart';
|
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
import 'package:immich_mobile/extensions/theme_extensions.dart';
|
import 'package:immich_mobile/extensions/theme_extensions.dart';
|
||||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||||
@@ -36,6 +34,21 @@ class _DriftBackupPageState extends ConsumerState<DriftBackupPage> {
|
|||||||
ref.read(driftBackupProvider.notifier).getBackupStatus(currentUser.id);
|
ref.read(driftBackupProvider.notifier).getBackupStatus(currentUser.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> startBackup() async {
|
||||||
|
final currentUser = ref.read(currentUserProvider);
|
||||||
|
if (currentUser == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await ref.read(backgroundSyncProvider).syncRemote();
|
||||||
|
await ref.read(driftBackupProvider.notifier).getBackupStatus(currentUser.id);
|
||||||
|
await ref.read(driftBackupProvider.notifier).startBackup(currentUser.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> stopBackup() async {
|
||||||
|
await ref.read(driftBackupProvider.notifier).cancel();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final selectedAlbum = ref
|
final selectedAlbum = ref
|
||||||
@@ -43,24 +56,6 @@ class _DriftBackupPageState extends ConsumerState<DriftBackupPage> {
|
|||||||
.where((album) => album.backupSelection == BackupSelection.selected)
|
.where((album) => album.backupSelection == BackupSelection.selected)
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
final backupNotifier = ref.read(driftBackupProvider.notifier);
|
|
||||||
final backgroundManager = ref.read(backgroundSyncProvider);
|
|
||||||
|
|
||||||
Future<void> startBackup() async {
|
|
||||||
final currentUser = Store.tryGet(StoreKey.currentUser);
|
|
||||||
if (currentUser == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await backgroundManager.syncRemote();
|
|
||||||
await backupNotifier.getBackupStatus(currentUser.id);
|
|
||||||
await backupNotifier.startBackup(currentUser.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> stopBackup() async {
|
|
||||||
await backupNotifier.cancel();
|
|
||||||
}
|
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import 'package:immich_mobile/entities/store.entity.dart';
|
|||||||
import 'package:immich_mobile/providers/auth.provider.dart';
|
import 'package:immich_mobile/providers/auth.provider.dart';
|
||||||
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
||||||
import 'package:immich_mobile/providers/backup/backup.provider.dart';
|
import 'package:immich_mobile/providers/backup/backup.provider.dart';
|
||||||
import 'package:immich_mobile/providers/backup/drift_backup.provider.dart';
|
|
||||||
import 'package:immich_mobile/providers/gallery_permission.provider.dart';
|
import 'package:immich_mobile/providers/gallery_permission.provider.dart';
|
||||||
import 'package:immich_mobile/providers/server_info.provider.dart';
|
import 'package:immich_mobile/providers/server_info.provider.dart';
|
||||||
import 'package:immich_mobile/providers/websocket.provider.dart';
|
import 'package:immich_mobile/providers/websocket.provider.dart';
|
||||||
@@ -23,7 +22,6 @@ class SplashScreenPage extends StatefulHookConsumerWidget {
|
|||||||
|
|
||||||
class SplashScreenPageState extends ConsumerState<SplashScreenPage> {
|
class SplashScreenPageState extends ConsumerState<SplashScreenPage> {
|
||||||
final log = Logger("SplashScreenPage");
|
final log = Logger("SplashScreenPage");
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
@@ -51,7 +49,6 @@ class SplashScreenPageState extends ConsumerState<SplashScreenPage> {
|
|||||||
final infoProvider = ref.read(serverInfoProvider.notifier);
|
final infoProvider = ref.read(serverInfoProvider.notifier);
|
||||||
final wsProvider = ref.read(websocketProvider.notifier);
|
final wsProvider = ref.read(websocketProvider.notifier);
|
||||||
final backgroundManager = ref.read(backgroundSyncProvider);
|
final backgroundManager = ref.read(backgroundSyncProvider);
|
||||||
final backupProvider = ref.read(driftBackupProvider.notifier);
|
|
||||||
|
|
||||||
ref.read(authProvider.notifier).saveAuthInfo(accessToken: accessToken).then(
|
ref.read(authProvider.notifier).saveAuthInfo(accessToken: accessToken).then(
|
||||||
(_) async {
|
(_) async {
|
||||||
@@ -60,17 +57,13 @@ class SplashScreenPageState extends ConsumerState<SplashScreenPage> {
|
|||||||
infoProvider.getServerInfo();
|
infoProvider.getServerInfo();
|
||||||
|
|
||||||
if (Store.isBetaTimelineEnabled) {
|
if (Store.isBetaTimelineEnabled) {
|
||||||
await Future.wait([backgroundManager.syncLocal(), backgroundManager.syncRemote()]);
|
await backgroundManager.syncLocal();
|
||||||
await Future.wait([
|
await backgroundManager.syncRemote();
|
||||||
backgroundManager.hashAssets().then((_) {
|
await backgroundManager.hashAssets();
|
||||||
_resumeBackup(backupProvider);
|
}
|
||||||
}),
|
|
||||||
_resumeBackup(backupProvider),
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (Store.get(StoreKey.syncAlbums, false)) {
|
if (Store.get(StoreKey.syncAlbums, false)) {
|
||||||
await backgroundManager.syncLinkedAlbum();
|
await backgroundManager.syncLinkedAlbum();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log.severe('Failed establishing connection to the server: $e');
|
log.severe('Failed establishing connection to the server: $e');
|
||||||
@@ -113,17 +106,6 @@ class SplashScreenPageState extends ConsumerState<SplashScreenPage> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _resumeBackup(DriftBackupNotifier notifier) async {
|
|
||||||
final isEnableBackup = Store.get(StoreKey.enableBackup, false);
|
|
||||||
|
|
||||||
if (isEnableBackup) {
|
|
||||||
final currentUser = Store.tryGet(StoreKey.currentUser);
|
|
||||||
if (currentUser != null) {
|
|
||||||
notifier.handleBackupResume(currentUser.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return const Scaffold(
|
return const Scaffold(
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import 'package:immich_mobile/domain/models/timeline.model.dart';
|
|||||||
import 'package:immich_mobile/domain/services/timeline.service.dart';
|
import 'package:immich_mobile/domain/services/timeline.service.dart';
|
||||||
import 'package:immich_mobile/domain/utils/event_stream.dart';
|
import 'package:immich_mobile/domain/utils/event_stream.dart';
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
import 'package:immich_mobile/extensions/platform_extensions.dart';
|
|
||||||
import 'package:immich_mobile/extensions/scroll_extensions.dart';
|
import 'package:immich_mobile/extensions/scroll_extensions.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_stack.provider.dart';
|
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_stack.provider.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_stack.widget.dart';
|
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_stack.widget.dart';
|
||||||
@@ -31,6 +30,7 @@ import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
|
|||||||
import 'package:immich_mobile/widgets/common/immich_loading_indicator.dart';
|
import 'package:immich_mobile/widgets/common/immich_loading_indicator.dart';
|
||||||
import 'package:immich_mobile/widgets/photo_view/photo_view.dart';
|
import 'package:immich_mobile/widgets/photo_view/photo_view.dart';
|
||||||
import 'package:immich_mobile/widgets/photo_view/photo_view_gallery.dart';
|
import 'package:immich_mobile/widgets/photo_view/photo_view_gallery.dart';
|
||||||
|
import 'package:platform/platform.dart';
|
||||||
|
|
||||||
@RoutePage()
|
@RoutePage()
|
||||||
class AssetViewerPage extends StatelessWidget {
|
class AssetViewerPage extends StatelessWidget {
|
||||||
@@ -53,9 +53,10 @@ class AssetViewerPage extends StatelessWidget {
|
|||||||
|
|
||||||
class AssetViewer extends ConsumerStatefulWidget {
|
class AssetViewer extends ConsumerStatefulWidget {
|
||||||
final int initialIndex;
|
final int initialIndex;
|
||||||
|
final Platform? platform;
|
||||||
final int? heroOffset;
|
final int? heroOffset;
|
||||||
|
|
||||||
const AssetViewer({super.key, required this.initialIndex, this.heroOffset});
|
const AssetViewer({super.key, required this.initialIndex, this.platform, this.heroOffset});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
ConsumerState createState() => _AssetViewerState();
|
ConsumerState createState() => _AssetViewerState();
|
||||||
@@ -85,6 +86,7 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
|||||||
PhotoViewControllerBase? viewController;
|
PhotoViewControllerBase? viewController;
|
||||||
StreamSubscription? reloadSubscription;
|
StreamSubscription? reloadSubscription;
|
||||||
|
|
||||||
|
late Platform platform;
|
||||||
late final int heroOffset;
|
late final int heroOffset;
|
||||||
late PhotoViewControllerValue initialPhotoViewState;
|
late PhotoViewControllerValue initialPhotoViewState;
|
||||||
bool? hasDraggedDown;
|
bool? hasDraggedDown;
|
||||||
@@ -112,6 +114,7 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
|||||||
super.initState();
|
super.initState();
|
||||||
assert(ref.read(currentAssetNotifier) != null, "Current asset should not be null when opening the AssetViewer");
|
assert(ref.read(currentAssetNotifier) != null, "Current asset should not be null when opening the AssetViewer");
|
||||||
pageController = PageController(initialPage: widget.initialIndex);
|
pageController = PageController(initialPage: widget.initialIndex);
|
||||||
|
platform = widget.platform ?? const LocalPlatform();
|
||||||
totalAssets = ref.read(timelineServiceProvider).totalAssets;
|
totalAssets = ref.read(timelineServiceProvider).totalAssets;
|
||||||
bottomSheetController = DraggableScrollableController();
|
bottomSheetController = DraggableScrollableController();
|
||||||
WidgetsBinding.instance.addPostFrameCallback(_onAssetInit);
|
WidgetsBinding.instance.addPostFrameCallback(_onAssetInit);
|
||||||
@@ -635,7 +638,7 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
|||||||
gaplessPlayback: true,
|
gaplessPlayback: true,
|
||||||
loadingBuilder: _placeholderBuilder,
|
loadingBuilder: _placeholderBuilder,
|
||||||
pageController: pageController,
|
pageController: pageController,
|
||||||
scrollPhysics: CurrentPlatform.isIOS
|
scrollPhysics: platform.isIOS
|
||||||
? const FastScrollPhysics() // Use bouncing physics for iOS
|
? const FastScrollPhysics() // Use bouncing physics for iOS
|
||||||
: const FastClampingScrollPhysics(), // Use heavy physics for Android
|
: const FastClampingScrollPhysics(), // Use heavy physics for Android
|
||||||
itemCount: totalAssets,
|
itemCount: totalAssets,
|
||||||
|
|||||||
@@ -98,16 +98,16 @@ class _GeneralBottomSheetState extends ConsumerState<GeneralBottomSheet> {
|
|||||||
const ShareActionButton(source: ActionSource.timeline),
|
const ShareActionButton(source: ActionSource.timeline),
|
||||||
if (multiselect.hasRemote) ...[
|
if (multiselect.hasRemote) ...[
|
||||||
const ShareLinkActionButton(source: ActionSource.timeline),
|
const ShareLinkActionButton(source: ActionSource.timeline),
|
||||||
const DownloadActionButton(source: ActionSource.timeline),
|
|
||||||
isTrashEnable
|
|
||||||
? const TrashActionButton(source: ActionSource.timeline)
|
|
||||||
: const DeletePermanentActionButton(source: ActionSource.timeline),
|
|
||||||
const FavoriteActionButton(source: ActionSource.timeline),
|
|
||||||
const ArchiveActionButton(source: ActionSource.timeline),
|
const ArchiveActionButton(source: ActionSource.timeline),
|
||||||
|
const FavoriteActionButton(source: ActionSource.timeline),
|
||||||
|
const DownloadActionButton(source: ActionSource.timeline),
|
||||||
const EditDateTimeActionButton(source: ActionSource.timeline),
|
const EditDateTimeActionButton(source: ActionSource.timeline),
|
||||||
const EditLocationActionButton(source: ActionSource.timeline),
|
const EditLocationActionButton(source: ActionSource.timeline),
|
||||||
const MoveToLockFolderActionButton(source: ActionSource.timeline),
|
const MoveToLockFolderActionButton(source: ActionSource.timeline),
|
||||||
if (multiselect.selectedAssets.length > 1) const StackActionButton(source: ActionSource.timeline),
|
if (multiselect.selectedAssets.length > 1) const StackActionButton(source: ActionSource.timeline),
|
||||||
|
isTrashEnable
|
||||||
|
? const TrashActionButton(source: ActionSource.timeline)
|
||||||
|
: const DeletePermanentActionButton(source: ActionSource.timeline),
|
||||||
const DeleteActionButton(source: ActionSource.timeline),
|
const DeleteActionButton(source: ActionSource.timeline),
|
||||||
],
|
],
|
||||||
if (multiselect.hasLocal || multiselect.hasMerged) const DeleteLocalActionButton(source: ActionSource.timeline),
|
if (multiselect.hasLocal || multiselect.hasMerged) const DeleteLocalActionButton(source: ActionSource.timeline),
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ class Timeline extends StatelessWidget {
|
|||||||
this.showStorageIndicator,
|
this.showStorageIndicator,
|
||||||
this.withStack = false,
|
this.withStack = false,
|
||||||
this.appBar = const ImmichSliverAppBar(floating: true, pinned: false, snap: false),
|
this.appBar = const ImmichSliverAppBar(floating: true, pinned: false, snap: false),
|
||||||
this.bottomSheet = const GeneralBottomSheet(minChildSize: 0.18),
|
this.bottomSheet = const GeneralBottomSheet(),
|
||||||
this.groupBy,
|
this.groupBy,
|
||||||
this.withScrubber = true,
|
this.withScrubber = true,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
|
||||||
import 'package:immich_mobile/domain/services/log.service.dart';
|
import 'package:immich_mobile/domain/services/log.service.dart';
|
||||||
import 'package:immich_mobile/entities/store.entity.dart';
|
import 'package:immich_mobile/entities/store.entity.dart';
|
||||||
import 'package:immich_mobile/models/backup/backup_state.model.dart';
|
import 'package:immich_mobile/models/backup/backup_state.model.dart';
|
||||||
@@ -19,6 +18,7 @@ import 'package:immich_mobile/providers/memory.provider.dart';
|
|||||||
import 'package:immich_mobile/providers/notification_permission.provider.dart';
|
import 'package:immich_mobile/providers/notification_permission.provider.dart';
|
||||||
import 'package:immich_mobile/providers/server_info.provider.dart';
|
import 'package:immich_mobile/providers/server_info.provider.dart';
|
||||||
import 'package:immich_mobile/providers/tab.provider.dart';
|
import 'package:immich_mobile/providers/tab.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/user.provider.dart';
|
||||||
import 'package:immich_mobile/providers/websocket.provider.dart';
|
import 'package:immich_mobile/providers/websocket.provider.dart';
|
||||||
import 'package:immich_mobile/services/app_settings.service.dart';
|
import 'package:immich_mobile/services/app_settings.service.dart';
|
||||||
import 'package:immich_mobile/services/background.service.dart';
|
import 'package:immich_mobile/services/background.service.dart';
|
||||||
@@ -144,42 +144,32 @@ class AppLifeCycleNotifier extends StateNotifier<AppLifeCycleEnum> {
|
|||||||
|
|
||||||
final backgroundManager = _ref.read(backgroundSyncProvider);
|
final backgroundManager = _ref.read(backgroundSyncProvider);
|
||||||
final isAlbumLinkedSyncEnable = _ref.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.syncAlbums);
|
final isAlbumLinkedSyncEnable = _ref.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.syncAlbums);
|
||||||
|
final isEnableBackup = _ref.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.enableBackup);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await Future.wait([
|
// Run operations sequentially with state checks and error handling for each
|
||||||
_safeRun(backgroundManager.syncLocal(), "syncLocal"),
|
await _safeRun(backgroundManager.syncLocal(), "syncLocal");
|
||||||
_safeRun(backgroundManager.syncRemote(), "syncRemote"),
|
await _safeRun(backgroundManager.syncRemote(), "syncRemote");
|
||||||
]);
|
await _safeRun(backgroundManager.hashAssets(), "hashAssets");
|
||||||
|
|
||||||
await Future.wait([
|
|
||||||
_safeRun(backgroundManager.hashAssets(), "hashAssets").then((_) {
|
|
||||||
_resumeBackup();
|
|
||||||
}),
|
|
||||||
_resumeBackup(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (isAlbumLinkedSyncEnable) {
|
if (isAlbumLinkedSyncEnable) {
|
||||||
await _safeRun(backgroundManager.syncLinkedAlbum(), "syncLinkedAlbum");
|
await _safeRun(backgroundManager.syncLinkedAlbum(), "syncLinkedAlbum");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle backup resume only if still active
|
||||||
|
if (isEnableBackup) {
|
||||||
|
final currentUser = _ref.read(currentUserProvider);
|
||||||
|
if (currentUser != null) {
|
||||||
|
await _safeRun(
|
||||||
|
_ref.read(driftBackupProvider.notifier).handleBackupResume(currentUser.id),
|
||||||
|
"handleBackupResume",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (e, stackTrace) {
|
} catch (e, stackTrace) {
|
||||||
_log.severe("Error during background sync", e, stackTrace);
|
_log.severe("Error during background sync", e, stackTrace);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _resumeBackup() async {
|
|
||||||
final isEnableBackup = _ref.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.enableBackup);
|
|
||||||
|
|
||||||
if (isEnableBackup) {
|
|
||||||
final currentUser = Store.tryGet(StoreKey.currentUser);
|
|
||||||
if (currentUser != null) {
|
|
||||||
await _safeRun(
|
|
||||||
_ref.read(driftBackupProvider.notifier).handleBackupResume(currentUser.id),
|
|
||||||
"handleBackupResume",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper method to check if operations should continue
|
// Helper method to check if operations should continue
|
||||||
bool _shouldContinueOperation() {
|
bool _shouldContinueOperation() {
|
||||||
return [AppLifeCycleEnum.resumed, AppLifeCycleEnum.active].contains(state) &&
|
return [AppLifeCycleEnum.resumed, AppLifeCycleEnum.active].contains(state) &&
|
||||||
|
|||||||
Generated
+1
-1
@@ -3,7 +3,7 @@ Immich API
|
|||||||
|
|
||||||
This Dart package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
|
This Dart package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
|
||||||
|
|
||||||
- API version: 1.142.1
|
- API version: 1.142.0
|
||||||
- Generator version: 7.8.0
|
- Generator version: 7.8.0
|
||||||
- Build package: org.openapitools.codegen.languages.DartClientCodegen
|
- Build package: org.openapitools.codegen.languages.DartClientCodegen
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1429,7 +1429,7 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "5.0.1"
|
version: "5.0.1"
|
||||||
platform:
|
platform:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: platform
|
name: platform
|
||||||
sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
|
sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
|
||||||
|
|||||||
+2
-1
@@ -2,7 +2,7 @@ name: immich_mobile
|
|||||||
description: Immich - selfhosted backup media file on mobile phone
|
description: Immich - selfhosted backup media file on mobile phone
|
||||||
|
|
||||||
publish_to: 'none'
|
publish_to: 'none'
|
||||||
version: 1.142.1+3015
|
version: 1.142.0+3014
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=3.8.0 <4.0.0'
|
sdk: '>=3.8.0 <4.0.0'
|
||||||
@@ -57,6 +57,7 @@ dependencies:
|
|||||||
photo_manager: ^3.6.4
|
photo_manager: ^3.6.4
|
||||||
photo_manager_image_provider: ^2.2.0
|
photo_manager_image_provider: ^2.2.0
|
||||||
pinput: ^5.0.1
|
pinput: ^5.0.1
|
||||||
|
platform: ^3.1.6
|
||||||
punycode: ^1.0.0
|
punycode: ^1.0.0
|
||||||
riverpod_annotation: ^2.6.1
|
riverpod_annotation: ^2.6.1
|
||||||
scrollable_positioned_list: ^0.3.8
|
scrollable_positioned_list: ^0.3.8
|
||||||
|
|||||||
@@ -9858,7 +9858,7 @@
|
|||||||
"info": {
|
"info": {
|
||||||
"title": "Immich",
|
"title": "Immich",
|
||||||
"description": "Immich API",
|
"description": "Immich API",
|
||||||
"version": "1.142.1",
|
"version": "1.142.0",
|
||||||
"contact": {}
|
"contact": {}
|
||||||
},
|
},
|
||||||
"tags": [],
|
"tags": [],
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@immich/sdk",
|
"name": "@immich/sdk",
|
||||||
"version": "1.142.1",
|
"version": "1.142.0",
|
||||||
"description": "Auto-generated TypeScript SDK for the Immich API",
|
"description": "Auto-generated TypeScript SDK for the Immich API",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./build/index.js",
|
"main": "./build/index.js",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* Immich
|
* Immich
|
||||||
* 1.142.1
|
* 1.142.0
|
||||||
* DO NOT MODIFY - This file has been generated using oazapfts.
|
* DO NOT MODIFY - This file has been generated using oazapfts.
|
||||||
* See https://www.npmjs.com/package/oazapfts
|
* See https://www.npmjs.com/package/oazapfts
|
||||||
*/
|
*/
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "immich",
|
"name": "immich",
|
||||||
"version": "1.142.1",
|
"version": "1.142.0",
|
||||||
"description": "",
|
"description": "",
|
||||||
"author": "",
|
"author": "",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "immich-web",
|
"name": "immich-web",
|
||||||
"version": "1.142.1",
|
"version": "1.142.0",
|
||||||
"license": "GNU Affero General Public License version 3",
|
"license": "GNU Affero General Public License version 3",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -45,15 +45,4 @@ describe('Thumbnail component', () => {
|
|||||||
const tabbables = getTabbable(container!);
|
const tabbables = getTabbable(container!);
|
||||||
expect(tabbables.length).toBe(0);
|
expect(tabbables.length).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('shows thumbhash while image is loading', () => {
|
|
||||||
const asset = assetFactory.build({ originalPath: 'image.jpg', originalMimeType: 'image/jpeg' });
|
|
||||||
const sut = render(Thumbnail, {
|
|
||||||
asset,
|
|
||||||
selected: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const thumbhash = sut.getByTestId('thumbhash');
|
|
||||||
expect(thumbhash).not.toBeFalsy();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
import { authManager } from '$lib/managers/auth-manager.svelte';
|
import { authManager } from '$lib/managers/auth-manager.svelte';
|
||||||
import type { TimelineAsset } from '$lib/managers/timeline-manager/types';
|
import type { TimelineAsset } from '$lib/managers/timeline-manager/types';
|
||||||
import { mobileDevice } from '$lib/stores/mobile-device.svelte';
|
import { mobileDevice } from '$lib/stores/mobile-device.svelte';
|
||||||
|
import { isCached } from '$lib/utils/cache';
|
||||||
import { moveFocus } from '$lib/utils/focus-util';
|
import { moveFocus } from '$lib/utils/focus-util';
|
||||||
import { currentUrlReplaceAssetId } from '$lib/utils/navigation';
|
import { currentUrlReplaceAssetId } from '$lib/utils/navigation';
|
||||||
import { TUNABLES } from '$lib/utils/tunables';
|
import { TUNABLES } from '$lib/utils/tunables';
|
||||||
@@ -75,6 +76,12 @@
|
|||||||
IMAGE_THUMBNAIL: { THUMBHASH_FADE_DURATION },
|
IMAGE_THUMBNAIL: { THUMBHASH_FADE_DURATION },
|
||||||
} = TUNABLES;
|
} = TUNABLES;
|
||||||
|
|
||||||
|
const thumbnailURL = getAssetThumbnailUrl({
|
||||||
|
id: asset.id,
|
||||||
|
size: AssetMediaSize.Thumbnail,
|
||||||
|
cacheKey: asset.thumbhash,
|
||||||
|
});
|
||||||
|
|
||||||
let usingMobileDevice = $derived(mobileDevice.pointerCoarse);
|
let usingMobileDevice = $derived(mobileDevice.pointerCoarse);
|
||||||
let element: HTMLElement | undefined = $state();
|
let element: HTMLElement | undefined = $state();
|
||||||
let mouseOver = $state(false);
|
let mouseOver = $state(false);
|
||||||
@@ -313,7 +320,7 @@
|
|||||||
<ImageThumbnail
|
<ImageThumbnail
|
||||||
class={imageClass}
|
class={imageClass}
|
||||||
{brokenAssetClass}
|
{brokenAssetClass}
|
||||||
url={getAssetThumbnailUrl({ id: asset.id, size: AssetMediaSize.Thumbnail, cacheKey: asset.thumbhash })}
|
url={thumbnailURL}
|
||||||
altText={$getAltText(asset)}
|
altText={$getAltText(asset)}
|
||||||
widthStyle="{width}px"
|
widthStyle="{width}px"
|
||||||
heightStyle="{height}px"
|
heightStyle="{height}px"
|
||||||
@@ -344,17 +351,31 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if (!loaded || thumbError) && asset.thumbhash}
|
{#if asset.thumbhash}
|
||||||
<canvas
|
{#await isCached(new Request(thumbnailURL))}
|
||||||
use:thumbhash={{ base64ThumbHash: asset.thumbhash }}
|
<canvas
|
||||||
data-testid="thumbhash"
|
use:thumbhash={{ base64ThumbHash: asset.thumbhash }}
|
||||||
class="absolute top-0 object-cover"
|
data-testid="thumbhash"
|
||||||
style:width="{width}px"
|
class="absolute top-0 object-cover"
|
||||||
style:height="{height}px"
|
style:width="{width}px"
|
||||||
class:rounded-xl={selected}
|
style:height="{height}px"
|
||||||
draggable="false"
|
class:rounded-xl={selected}
|
||||||
out:fade={{ duration: THUMBHASH_FADE_DURATION }}
|
draggable="false"
|
||||||
></canvas>
|
></canvas>
|
||||||
|
{:then cached}
|
||||||
|
{#if !cached && !loaded && !thumbError}
|
||||||
|
<canvas
|
||||||
|
use:thumbhash={{ base64ThumbHash: asset.thumbhash }}
|
||||||
|
data-testid="thumbhash"
|
||||||
|
class="absolute top-0 object-cover"
|
||||||
|
style:width="{width}px"
|
||||||
|
style:height="{height}px"
|
||||||
|
class:rounded-xl={selected}
|
||||||
|
draggable="false"
|
||||||
|
out:fade={{ duration: THUMBHASH_FADE_DURATION }}
|
||||||
|
></canvas>
|
||||||
|
{/if}
|
||||||
|
{/await}
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
let cache: Cache | undefined;
|
||||||
|
|
||||||
|
const getCache = async () => {
|
||||||
|
cache ||= await openCache();
|
||||||
|
return cache;
|
||||||
|
};
|
||||||
|
|
||||||
|
const openCache = async () => {
|
||||||
|
const [key] = await caches.keys();
|
||||||
|
if (key) {
|
||||||
|
return caches.open(key);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isCached = async (req: Request) => {
|
||||||
|
const cache = await getCache();
|
||||||
|
return !!(await cache?.match(req));
|
||||||
|
};
|
||||||
@@ -29,6 +29,6 @@ export const TUNABLES = {
|
|||||||
NAVIGATE_ON_ASSET_IN_VIEW: getBoolean(storage.getItem('ASSET_GRID.NAVIGATE_ON_ASSET_IN_VIEW'), false),
|
NAVIGATE_ON_ASSET_IN_VIEW: getBoolean(storage.getItem('ASSET_GRID.NAVIGATE_ON_ASSET_IN_VIEW'), false),
|
||||||
},
|
},
|
||||||
IMAGE_THUMBNAIL: {
|
IMAGE_THUMBNAIL: {
|
||||||
THUMBHASH_FADE_DURATION: getNumber(storage.getItem('THUMBHASH_FADE_DURATION'), 100),
|
THUMBHASH_FADE_DURATION: getNumber(storage.getItem('THUMBHASH_FADE_DURATION'), 1000),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user