From 761db7a69dffcb83e78a368af33749bb6ccbd507 Mon Sep 17 00:00:00 2001 From: OneWay Date: Thu, 15 Oct 2026 13:43:13 +0800 Subject: [PATCH] Keep the camera FAB stable across folder navigation - Skip rendering the FAB until saved preferences load, so it is never drawn at the default corner and never jumps to its dragged position - Share user preferences as one hot process-wide StateFlow instead of a cold disk read per screen, so a newly created gallery screen receives the FAB position synchronously in the ViewModel constructor --- .../preferences/UserPreferencesRepository.kt | 36 +++++++++++++------ .../onecamera/ui/gallery/GalleryScreen.kt | 14 ++++---- .../onecamera/ui/gallery/GalleryViewModel.kt | 3 ++ 3 files changed, 36 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/com/example/onecamera/data/preferences/UserPreferencesRepository.kt b/app/src/main/java/com/example/onecamera/data/preferences/UserPreferencesRepository.kt index cb70865..44bf90d 100644 --- a/app/src/main/java/com/example/onecamera/data/preferences/UserPreferencesRepository.kt +++ b/app/src/main/java/com/example/onecamera/data/preferences/UserPreferencesRepository.kt @@ -9,8 +9,13 @@ import androidx.datastore.preferences.core.floatPreferencesKey import androidx.datastore.preferences.core.stringPreferencesKey import androidx.datastore.preferences.preferencesDataStore import dagger.hilt.android.qualifiers.ApplicationContext -import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn import javax.inject.Inject import javax.inject.Singleton @@ -41,17 +46,26 @@ class UserPreferencesRepository @Inject constructor( val SCREEN_TRANSITIONS_ENABLED = booleanPreferencesKey("screen_transitions_enabled") } - val userPreferencesFlow: Flow = context.dataStore.data.map { prefs -> - UserPreferences( - rootFolderUri = prefs[Keys.ROOT_FOLDER_URI], - cameraFabXFraction = prefs[Keys.CAMERA_FAB_X_FRACTION]?.coerceIn(0f, 1f) ?: 1f, - cameraFabYFraction = prefs[Keys.CAMERA_FAB_Y_FRACTION]?.coerceIn(0f, 1f) ?: 1f, - gallerySortAscending = prefs[Keys.GALLERY_SORT_ASCENDING] ?: true, - galleryGridMode = prefs[Keys.GALLERY_GRID_MODE] ?: true, - zipJpegCompression = prefs[Keys.ZIP_JPEG_COMPRESSION] ?: true, - screenTransitionsEnabled = prefs[Keys.SCREEN_TRANSITIONS_ENABLED] ?: true, + // Hot, process-wide cache: the DataStore file is read once, and every screen + // created later receives the current value synchronously on subscription, + // instead of re-reading disk per screen and flashing default positions. + val userPreferencesFlow: StateFlow = context.dataStore.data + .map { prefs -> + UserPreferences( + rootFolderUri = prefs[Keys.ROOT_FOLDER_URI], + cameraFabXFraction = prefs[Keys.CAMERA_FAB_X_FRACTION]?.coerceIn(0f, 1f) ?: 1f, + cameraFabYFraction = prefs[Keys.CAMERA_FAB_Y_FRACTION]?.coerceIn(0f, 1f) ?: 1f, + gallerySortAscending = prefs[Keys.GALLERY_SORT_ASCENDING] ?: true, + galleryGridMode = prefs[Keys.GALLERY_GRID_MODE] ?: true, + zipJpegCompression = prefs[Keys.ZIP_JPEG_COMPRESSION] ?: true, + screenTransitionsEnabled = prefs[Keys.SCREEN_TRANSITIONS_ENABLED] ?: true, + ) + } + .stateIn( + scope = CoroutineScope(SupervisorJob() + Dispatchers.IO), + started = SharingStarted.Eagerly, + initialValue = UserPreferences(), ) - } suspend fun setRootFolderUri(uri: String) { context.dataStore.edit { prefs -> diff --git a/app/src/main/java/com/example/onecamera/ui/gallery/GalleryScreen.kt b/app/src/main/java/com/example/onecamera/ui/gallery/GalleryScreen.kt index 07482ac..c561d90 100644 --- a/app/src/main/java/com/example/onecamera/ui/gallery/GalleryScreen.kt +++ b/app/src/main/java/com/example/onecamera/ui/gallery/GalleryScreen.kt @@ -474,12 +474,14 @@ fun GalleryScreen( enter = fadeIn(tween(250)), exit = fadeOut(tween(150)), ) { - DraggableCameraFab( - xFraction = uiState.cameraFabXFraction, - yFraction = uiState.cameraFabYFraction, - onPositionChange = viewModel::saveCameraFabPosition, - onClick = { onNavigateToCamera(uiState.currentFolderUri ?: folderUri) }, - ) + if (uiState.viewPreferencesLoaded) { + DraggableCameraFab( + xFraction = uiState.cameraFabXFraction, + yFraction = uiState.cameraFabYFraction, + onPositionChange = viewModel::saveCameraFabPosition, + onClick = { onNavigateToCamera(uiState.currentFolderUri ?: folderUri) }, + ) + } } SelectionActionsBar( diff --git a/app/src/main/java/com/example/onecamera/ui/gallery/GalleryViewModel.kt b/app/src/main/java/com/example/onecamera/ui/gallery/GalleryViewModel.kt index e52d029..5e57c17 100644 --- a/app/src/main/java/com/example/onecamera/ui/gallery/GalleryViewModel.kt +++ b/app/src/main/java/com/example/onecamera/ui/gallery/GalleryViewModel.kt @@ -205,6 +205,9 @@ class GalleryViewModel @Inject constructor( loadJob = viewModelScope.launch { uiState.value = uiState.value.copy( currentFolderUri = folderUriString, + folderName = state.folderName.ifEmpty { + folderUriString.substringAfterLast(':').substringAfterLast('/') + }, isLoading = true, error = null, )