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
This commit is contained in:
1 parent
428749d8a8
commit
761db7a69d
3 files changed
+36
-17
No files matched your search
+25
-11
@@ -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<UserPreferences> = 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<UserPreferences> = 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 ->
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
Reference in new issue
Block a user