Compare commits

..

2 Commits

Author SHA1 Message Date
Yaros d74a11f979 chore: rename restoreSystemUI to restoreEdgeToEdge 2026-06-24 13:06:28 +02:00
Yaros fb0d356ea7 fix(mobile): app doesn't exit full-screen mode 2026-06-24 12:01:50 +02:00
8 changed files with 26 additions and 120 deletions
@@ -446,7 +446,6 @@ class SyncStreamService {
await _syncStreamRepository.updateAssetsV1([asset], debugLabel: 'websocket-edit');
await _syncStreamRepository.replaceAssetEditsV1(asset.id, assetEdits, debugLabel: 'websocket-edit');
await _refreshAssetOcrAndFaces(asset.id);
_logger.info(
'Successfully processed AssetEditReadyV1 event for asset ${asset.id} with ${assetEdits.length} edits',
@@ -485,7 +484,6 @@ class SyncStreamService {
await _syncStreamRepository.updateAssetsV2([asset], debugLabel: 'websocket-edit');
await _syncStreamRepository.replaceAssetEditsV1(asset.id, assetEdits, debugLabel: 'websocket-edit');
await _refreshAssetOcrAndFaces(asset.id);
_logger.info(
'Successfully processed AssetEditReadyV2 event for asset ${asset.id} with ${assetEdits.length} edits',
@@ -495,22 +493,6 @@ class SyncStreamService {
}
}
Future<void> _refreshAssetOcrAndFaces(String assetId) async {
try {
final ocr = await _api.assetsApi.getAssetOcr(assetId);
await _syncStreamRepository.replaceAssetOcr(assetId, ocr ?? const []);
} catch (error, stackTrace) {
_logger.severe("Error refreshing OCR for asset $assetId", error, stackTrace);
}
try {
final faces = await _api.facesApi.getFaces(assetId);
await _syncStreamRepository.replaceAssetFaces(assetId, faces ?? const []);
} catch (error, stackTrace) {
_logger.severe("Error refreshing faces for asset $assetId", error, stackTrace);
}
}
Future<void> _handleRemoteDeleted(Iterable<String> remoteIds) async {
if (remoteIds.isEmpty) {
return Future.value();
@@ -896,71 +896,6 @@ class SyncStreamRepository extends DriftDatabaseRepository {
}
}
/// Replaces all OCR rows for [assetId] with [data] (e.g. after an asset edit re-runs OCR).
Future<void> replaceAssetOcr(String assetId, Iterable<AssetOcrResponseDto> data) async {
try {
await _db.batch((batch) {
batch.deleteWhere(_db.assetOcrEntity, (row) => row.assetId.equals(assetId));
for (final ocr in data) {
batch.insert(
_db.assetOcrEntity,
AssetOcrEntityCompanion(
id: Value(ocr.id),
assetId: Value(ocr.assetId),
recognizedText: Value(ocr.text),
x1: Value(ocr.x1),
y1: Value(ocr.y1),
x2: Value(ocr.x2),
y2: Value(ocr.y2),
x3: Value(ocr.x3),
y3: Value(ocr.y3),
x4: Value(ocr.x4),
y4: Value(ocr.y4),
boxScore: Value(ocr.boxScore),
textScore: Value(ocr.textScore),
isVisible: const Value(true),
),
);
}
});
} catch (error, stack) {
_logger.severe('Error: replaceAssetOcr', error, stack);
rethrow;
}
}
Future<void> replaceAssetFaces(String assetId, Iterable<AssetFaceResponseDto> data) async {
try {
await _db.batch((batch) {
batch.deleteWhere(_db.assetFaceEntity, (row) => row.assetId.equals(assetId));
for (final face in data) {
batch.insert(
_db.assetFaceEntity,
AssetFaceEntityCompanion(
id: Value(face.id),
assetId: Value(assetId),
personId: Value(face.person?.id),
imageWidth: Value(face.imageWidth),
imageHeight: Value(face.imageHeight),
boundingBoxX1: Value(face.boundingBoxX1),
boundingBoxY1: Value(face.boundingBoxY1),
boundingBoxX2: Value(face.boundingBoxX2),
boundingBoxY2: Value(face.boundingBoxY2),
sourceType: Value(face.sourceType.orElse(null)?.value ?? SourceType.machineLearning.value),
isVisible: const Value(true),
deletedAt: const Value(null),
),
);
}
});
} catch (error, stack) {
_logger.severe('Error: replaceAssetFaces', error, stack);
rethrow;
}
}
Future<void> pruneAssets() async {
try {
await _db.transaction(() async {
@@ -12,6 +12,7 @@ import 'package:immich_mobile/presentation/widgets/images/image_provider.dart';
import 'package:immich_mobile/presentation/widgets/memory/memory_bottom_info.widget.dart';
import 'package:immich_mobile/presentation/widgets/memory/memory_card.widget.dart';
import 'package:immich_mobile/providers/haptic_feedback.provider.dart';
import 'package:immich_mobile/utils/system_ui.utils.dart';
import 'package:immich_mobile/widgets/memories/memory_epilogue.dart';
import 'package:immich_mobile/widgets/memories/memory_progress_indicator.dart';
@@ -49,7 +50,7 @@ class DriftMemoryPage extends HookConsumerWidget {
SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersive);
return () {
// Clean up to normal edge to edge when we are done
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
restoreEdgeToEdge();
};
});
@@ -328,7 +329,7 @@ class DriftMemoryPage extends HookConsumerWidget {
// turn off full screen mode here
// https://github.com/Milad-Akarie/auto_route_library/issues/1799
context.maybePop();
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
restoreEdgeToEdge();
},
shape: const CircleBorder(),
color: Colors.white.withValues(alpha: 0.2),
@@ -19,6 +19,7 @@ import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart'
import 'package:immich_mobile/providers/asset_viewer/video_player_provider.dart';
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/utils/system_ui.utils.dart';
import 'package:immich_mobile/widgets/common/immich_loading_indicator.dart';
import 'package:immich_mobile/widgets/photo_view/photo_view.dart';
import 'package:wakelock_plus/wakelock_plus.dart';
@@ -76,7 +77,7 @@ class _DriftSlideshowPageState extends ConsumerState<DriftSlideshowPage> with Si
_pageController.dispose();
_crossfadeController.dispose();
unawaited(WakelockPlus.disable());
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
unawaited(restoreEdgeToEdge());
super.dispose();
}
@@ -255,7 +256,7 @@ class _DriftSlideshowPageState extends ConsumerState<DriftSlideshowPage> with Si
}
void _onTapUp() async {
await SystemChrome.setEnabledSystemUIMode(_showAppBar ? SystemUiMode.immersive : SystemUiMode.edgeToEdge);
await (_showAppBar ? SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersive) : restoreEdgeToEdge());
WidgetsBinding.instance.addPostFrameCallback((_) {
setState(() {
@@ -23,6 +23,7 @@ import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart'
import 'package:immich_mobile/providers/cast.provider.dart';
import 'package:immich_mobile/providers/infrastructure/current_album.provider.dart';
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
import 'package:immich_mobile/utils/system_ui.utils.dart';
import 'package:immich_mobile/widgets/photo_view/photo_view.dart';
@RoutePage()
@@ -128,7 +129,7 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
_reloadSubscription?.cancel();
_stackChildrenKeepAlive?.close();
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
unawaited(restoreEdgeToEdge());
super.dispose();
}
@@ -251,10 +252,8 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
}
void _setSystemUIMode(bool controls, bool details) {
final mode = !controls || (CurrentPlatform.isIOS && details)
? SystemUiMode.immersiveSticky
: SystemUiMode.edgeToEdge;
unawaited(SystemChrome.setEnabledSystemUIMode(mode));
final immersive = !controls || (CurrentPlatform.isIOS && details);
unawaited(immersive ? SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky) : restoreEdgeToEdge());
}
@override
+2 -26
View File
@@ -7,7 +7,6 @@ import 'package:immich_mobile/infrastructure/repositories/network.repository.dar
import 'package:immich_mobile/models/server_info/server_version.model.dart';
import 'package:immich_mobile/providers/auth.provider.dart';
import 'package:immich_mobile/providers/background_sync.provider.dart';
import 'package:immich_mobile/providers/infrastructure/ocr.provider.dart';
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
import 'package:immich_mobile/providers/server_info.provider.dart';
import 'package:immich_mobile/utils/debounce.dart';
@@ -182,34 +181,11 @@ class WebsocketNotifier extends StateNotifier<WebsocketState> {
}
void _handleSyncAssetEditReadyV1(dynamic data) {
final assetId = _assetIdFromEditReady(data);
unawaited(
_ref.read(backgroundSyncProvider).syncWebsocketEditV1(data).whenComplete(() => _onAssetEditApplied(assetId)),
);
unawaited(_ref.read(backgroundSyncProvider).syncWebsocketEditV1(data));
}
void _handleSyncAssetEditReadyV2(dynamic data) {
final assetId = _assetIdFromEditReady(data);
unawaited(
_ref.read(backgroundSyncProvider).syncWebsocketEditV2(data).whenComplete(() => _onAssetEditApplied(assetId)),
);
}
String? _assetIdFromEditReady(dynamic data) {
if (data is Map && data['asset'] is Map) {
final id = (data['asset'] as Map)['id'];
return id is String ? id : null;
}
return null;
}
/// The edit handler refreshes OCR/faces in the drift DB from a background isolate,
/// so the main-isolate UI providers must be invalidated here to re-read the new data.
void _onAssetEditApplied(String? assetId) {
if (assetId == null) {
return;
}
_ref.invalidate(ocrAssetProvider(assetId));
unawaited(_ref.read(backgroundSyncProvider).syncWebsocketEditV2(data));
}
void _processBatchedAssetUploadReadyV1() {
-2
View File
@@ -36,7 +36,6 @@ class ApiService {
late MemoriesApi memoriesApi;
late SessionsApi sessionsApi;
late TagsApi tagsApi;
late FacesApi facesApi;
ApiService() {
// The below line ensures that the api clients are initialized when the service is instantiated
@@ -78,7 +77,6 @@ class ApiService {
memoriesApi = MemoriesApi(_apiClient);
sessionsApi = SessionsApi(_apiClient);
tagsApi = TagsApi(_apiClient);
facesApi = FacesApi(_apiClient);
}
Future<String> resolveAndSetEndpoint(String serverUrl) async {
+14
View File
@@ -0,0 +1,14 @@
import 'dart:async';
import 'package:flutter/services.dart';
/// Restore the system bars and return to edge-to-edge layout.
///
/// On Android 15+/API 36 edge-to-edge is enforced, so calling
/// setEnabledSystemUIMode(edgeToEdge) does NOT re-show bars that an immersive
/// mode (immersive / immersiveSticky) previously hid. Explicitly request all
/// overlays first, then return to edge-to-edge layout.
Future<void> restoreEdgeToEdge() async {
await SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: SystemUiOverlay.values);
await SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
}