From 278668b8c5c9698a38eccc17b37e7d0a0f7e2fc9 Mon Sep 17 00:00:00 2001 From: Zack Pollard Date: Mon, 4 Aug 2025 22:41:44 +0100 Subject: [PATCH 1/5] fix: improvements to sync and upload when resuming app (#20524) - App will now kick off hashing after local sync if the lifecycle is in resumed or active state - We now wait for hashing to complete before we kick off the upload process --- mobile/lib/providers/app_life_cycle.provider.dart | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/mobile/lib/providers/app_life_cycle.provider.dart b/mobile/lib/providers/app_life_cycle.provider.dart index 31342bf23e..0696a8d7f1 100644 --- a/mobile/lib/providers/app_life_cycle.provider.dart +++ b/mobile/lib/providers/app_life_cycle.provider.dart @@ -86,11 +86,12 @@ class AppLifeCycleNotifier extends StateNotifier { // Ensure proper cleanup before starting new background tasks try { await Future.wait([ - backgroundManager.syncLocal().then((_) { + Future(() async { + await backgroundManager.syncLocal(); Logger("AppLifeCycleNotifier").fine("Hashing assets after syncLocal"); // Check if app is still active before hashing - if (state == AppLifeCycleEnum.resumed) { - backgroundManager.hashAssets(); + if ([AppLifeCycleEnum.resumed, AppLifeCycleEnum.active].contains(state)) { + await backgroundManager.hashAssets(); } }), backgroundManager.syncRemote(), From 094e3a27573d6d68793f568019130d90f57a1b8a Mon Sep 17 00:00:00 2001 From: Brandon Wees Date: Mon, 4 Aug 2025 16:41:58 -0500 Subject: [PATCH 2/5] fix(mobile): cleanly handle logout when no host is set (#20521) * fix: cleanly handle logging out when no host is set on API * move conditional to auth_api repo --- mobile/lib/repositories/auth_api.repository.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mobile/lib/repositories/auth_api.repository.dart b/mobile/lib/repositories/auth_api.repository.dart index e488f69578..4b0880ddcf 100644 --- a/mobile/lib/repositories/auth_api.repository.dart +++ b/mobile/lib/repositories/auth_api.repository.dart @@ -25,6 +25,8 @@ class AuthApiRepository extends ApiRepository { } Future logout() async { + if (_apiService.apiClient.basePath.isEmpty) return; + await _apiService.authenticationApi.logout().timeout(const Duration(seconds: 7)); } From 4d0c9172e57fd62519c4293ebce55be0a968becd Mon Sep 17 00:00:00 2001 From: Brandon Wees Date: Mon, 4 Aug 2025 17:14:26 -0500 Subject: [PATCH 3/5] fix: not clearing local data when logging out while sync is running (#20646) --- mobile/lib/domain/utils/background_sync.dart | 8 ++++++-- mobile/lib/main.dart | 1 - 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/mobile/lib/domain/utils/background_sync.dart b/mobile/lib/domain/utils/background_sync.dart index 1944591c93..cbf4030788 100644 --- a/mobile/lib/domain/utils/background_sync.dart +++ b/mobile/lib/domain/utils/background_sync.dart @@ -37,7 +37,7 @@ class BackgroundSyncManager { this.onHashingError, }); - Future cancel() { + Future cancel() async { final futures = []; if (_syncTask != null) { @@ -52,7 +52,11 @@ class BackgroundSyncManager { _syncWebsocketTask?.cancel(); _syncWebsocketTask = null; - return Future.wait(futures); + try { + await Future.wait(futures); + } on CanceledError { + // Ignore cancellation errors + } } // No need to cancel the task, as it can also be run when the user logs out diff --git a/mobile/lib/main.dart b/mobile/lib/main.dart index d1f415a304..0bac282694 100644 --- a/mobile/lib/main.dart +++ b/mobile/lib/main.dart @@ -83,7 +83,6 @@ Future initApp() async { }; PlatformDispatcher.instance.onError = (error, stack) { - debugPrint("FlutterError - Catch all: $error \n $stack"); log.severe('PlatformDispatcher - Catch all', error, stack); return true; }; From 990d9ba9a8a83a1df51d2ef5e5b03fa0d5ba5918 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Wojtaszko?= Date: Tue, 5 Aug 2025 00:24:19 +0200 Subject: [PATCH 4/5] fix: adjust margin and gap for trailing elements in control app bar (#20645) --- web/src/lib/components/shared-components/control-app-bar.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/lib/components/shared-components/control-app-bar.svelte b/web/src/lib/components/shared-components/control-app-bar.svelte index a28dfd45cf..ef4e26e849 100644 --- a/web/src/lib/components/shared-components/control-app-bar.svelte +++ b/web/src/lib/components/shared-components/control-app-bar.svelte @@ -97,7 +97,7 @@ {@render children?.()} -
+
{@render trailing?.()}
From 750d21aebabc58eaf050bd22fe19d5b9a4f094f2 Mon Sep 17 00:00:00 2001 From: Brandon Wees Date: Mon, 4 Aug 2025 17:25:58 -0500 Subject: [PATCH 5/5] fix(mobile): use storageIndicator setting for beta timeline (#20639) * fix: use storageIndicator setting for beta timeline * fix: reactively update the storage indicator icons when setting is changed * Update drift_trash.page.dart * override to bool for storageIndicator --- .../presentation/pages/dev/main_timeline.page.dart | 5 ++--- mobile/lib/presentation/pages/drift_trash.page.dart | 1 - .../lib/presentation/pages/local_timeline.page.dart | 1 - .../widgets/images/thumbnail_tile.widget.dart | 11 ++++++++--- .../presentation/widgets/timeline/timeline.state.dart | 4 ++-- .../widgets/timeline/timeline.widget.dart | 4 ++-- .../asset_list_settings/asset_list_settings.dart | 9 +++++++-- 7 files changed, 21 insertions(+), 14 deletions(-) diff --git a/mobile/lib/presentation/pages/dev/main_timeline.page.dart b/mobile/lib/presentation/pages/dev/main_timeline.page.dart index 0582399eaf..dd227a7635 100644 --- a/mobile/lib/presentation/pages/dev/main_timeline.page.dart +++ b/mobile/lib/presentation/pages/dev/main_timeline.page.dart @@ -16,17 +16,16 @@ class MainTimelinePage extends ConsumerWidget { return memoryLaneProvider.maybeWhen( data: (memories) { return memories.isEmpty - ? const Timeline(showStorageIndicator: true) + ? const Timeline() : Timeline( topSliverWidget: SliverToBoxAdapter( key: Key('memory-lane-${memories.first.assets.first.id}'), child: DriftMemoryLane(memories: memories), ), topSliverWidgetHeight: 200, - showStorageIndicator: true, ); }, - orElse: () => const Timeline(showStorageIndicator: true), + orElse: () => const Timeline(), ); } } diff --git a/mobile/lib/presentation/pages/drift_trash.page.dart b/mobile/lib/presentation/pages/drift_trash.page.dart index 4d18d12d01..43e9217cdf 100644 --- a/mobile/lib/presentation/pages/drift_trash.page.dart +++ b/mobile/lib/presentation/pages/drift_trash.page.dart @@ -28,7 +28,6 @@ class DriftTrashPage extends StatelessWidget { }), ], child: Timeline( - showStorageIndicator: true, appBar: SliverAppBar( title: Text('trash'.t(context: context)), floating: true, diff --git a/mobile/lib/presentation/pages/local_timeline.page.dart b/mobile/lib/presentation/pages/local_timeline.page.dart index 67bc17cb37..c53b18d9e7 100644 --- a/mobile/lib/presentation/pages/local_timeline.page.dart +++ b/mobile/lib/presentation/pages/local_timeline.page.dart @@ -26,7 +26,6 @@ class LocalTimelinePage extends StatelessWidget { child: Timeline( appBar: MesmerizingSliverAppBar(title: album.name), bottomSheet: const LocalAlbumBottomSheet(), - showStorageIndicator: true, ), ); } diff --git a/mobile/lib/presentation/widgets/images/thumbnail_tile.widget.dart b/mobile/lib/presentation/widgets/images/thumbnail_tile.widget.dart index b9ef1ca45a..37743c5e86 100644 --- a/mobile/lib/presentation/widgets/images/thumbnail_tile.widget.dart +++ b/mobile/lib/presentation/widgets/images/thumbnail_tile.widget.dart @@ -2,10 +2,12 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; +import 'package:immich_mobile/domain/models/setting.model.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/duration_extensions.dart'; import 'package:immich_mobile/extensions/theme_extensions.dart'; import 'package:immich_mobile/presentation/widgets/images/thumbnail.widget.dart'; +import 'package:immich_mobile/providers/infrastructure/setting.provider.dart'; import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; class ThumbnailTile extends ConsumerWidget { @@ -13,7 +15,7 @@ class ThumbnailTile extends ConsumerWidget { this.asset, { this.size = const Size.square(256), this.fit = BoxFit.cover, - this.showStorageIndicator = true, + this.showStorageIndicator, this.lockSelection = false, this.heroOffset, super.key, @@ -22,7 +24,7 @@ class ThumbnailTile extends ConsumerWidget { final BaseAsset asset; final Size size; final BoxFit fit; - final bool showStorageIndicator; + final bool? showStorageIndicator; final bool lockSelection; final int? heroOffset; @@ -52,6 +54,9 @@ class ThumbnailTile extends ConsumerWidget { final hasStack = asset is RemoteAsset && (asset as RemoteAsset).stackId != null; + final bool storageIndicator = + showStorageIndicator ?? ref.watch(settingsProvider.select((s) => s.get(Setting.showStorageIndicator))); + return Stack( children: [ AnimatedContainer( @@ -86,7 +91,7 @@ class ThumbnailTile extends ConsumerWidget { child: _VideoIndicator(asset.duration), ), ), - if (showStorageIndicator) + if (storageIndicator) switch (asset.storage) { AssetState.local => const Align( alignment: Alignment.bottomRight, diff --git a/mobile/lib/presentation/widgets/timeline/timeline.state.dart b/mobile/lib/presentation/widgets/timeline/timeline.state.dart index ad3ae3ccf6..da1f7fcc9d 100644 --- a/mobile/lib/presentation/widgets/timeline/timeline.state.dart +++ b/mobile/lib/presentation/widgets/timeline/timeline.state.dart @@ -14,7 +14,7 @@ class TimelineArgs { final double maxHeight; final double spacing; final int columnCount; - final bool showStorageIndicator; + final bool? showStorageIndicator; final bool withStack; final GroupAssetsBy? groupBy; @@ -23,7 +23,7 @@ class TimelineArgs { required this.maxHeight, this.spacing = kTimelineSpacing, this.columnCount = kTimelineColumnCount, - this.showStorageIndicator = false, + this.showStorageIndicator, this.withStack = false, this.groupBy, }); diff --git a/mobile/lib/presentation/widgets/timeline/timeline.widget.dart b/mobile/lib/presentation/widgets/timeline/timeline.widget.dart index b0886f020e..94e13c4e9f 100644 --- a/mobile/lib/presentation/widgets/timeline/timeline.widget.dart +++ b/mobile/lib/presentation/widgets/timeline/timeline.widget.dart @@ -31,7 +31,7 @@ class Timeline extends StatelessWidget { super.key, this.topSliverWidget, this.topSliverWidgetHeight, - this.showStorageIndicator = false, + this.showStorageIndicator, this.withStack = false, this.appBar = const ImmichSliverAppBar(floating: true, pinned: false, snap: false), this.bottomSheet = const GeneralBottomSheet(), @@ -40,7 +40,7 @@ class Timeline extends StatelessWidget { final Widget? topSliverWidget; final double? topSliverWidgetHeight; - final bool showStorageIndicator; + final bool? showStorageIndicator; final Widget? appBar; final Widget? bottomSheet; final bool withStack; diff --git a/mobile/lib/widgets/settings/asset_list_settings/asset_list_settings.dart b/mobile/lib/widgets/settings/asset_list_settings/asset_list_settings.dart index 9f0ed0aa87..907cd19843 100644 --- a/mobile/lib/widgets/settings/asset_list_settings/asset_list_settings.dart +++ b/mobile/lib/widgets/settings/asset_list_settings/asset_list_settings.dart @@ -2,11 +2,13 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/providers/app_settings.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/setting.provider.dart'; import 'package:immich_mobile/services/app_settings.service.dart'; +import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart'; import 'package:immich_mobile/widgets/settings/asset_list_settings/asset_list_group_settings.dart'; import 'package:immich_mobile/widgets/settings/settings_sub_page_scaffold.dart'; import 'package:immich_mobile/widgets/settings/settings_switch_list_tile.dart'; -import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart'; + import 'asset_list_layout_settings.dart'; class AssetListSettings extends HookConsumerWidget { @@ -20,7 +22,10 @@ class AssetListSettings extends HookConsumerWidget { SettingsSwitchListTile( valueNotifier: showStorageIndicator, title: 'theme_setting_asset_list_storage_indicator_title'.tr(), - onChanged: (_) => ref.invalidate(appSettingsServiceProvider), + onChanged: (_) { + ref.invalidate(appSettingsServiceProvider); + ref.invalidate(settingsProvider); + }, ), const LayoutSettings(), const GroupSettings(),