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.core.stringPreferencesKey
|
||||||
import androidx.datastore.preferences.preferencesDataStore
|
import androidx.datastore.preferences.preferencesDataStore
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
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.map
|
||||||
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@@ -41,17 +46,26 @@ class UserPreferencesRepository @Inject constructor(
|
|||||||
val SCREEN_TRANSITIONS_ENABLED = booleanPreferencesKey("screen_transitions_enabled")
|
val SCREEN_TRANSITIONS_ENABLED = booleanPreferencesKey("screen_transitions_enabled")
|
||||||
}
|
}
|
||||||
|
|
||||||
val userPreferencesFlow: Flow<UserPreferences> = context.dataStore.data.map { prefs ->
|
// Hot, process-wide cache: the DataStore file is read once, and every screen
|
||||||
UserPreferences(
|
// created later receives the current value synchronously on subscription,
|
||||||
rootFolderUri = prefs[Keys.ROOT_FOLDER_URI],
|
// instead of re-reading disk per screen and flashing default positions.
|
||||||
cameraFabXFraction = prefs[Keys.CAMERA_FAB_X_FRACTION]?.coerceIn(0f, 1f) ?: 1f,
|
val userPreferencesFlow: StateFlow<UserPreferences> = context.dataStore.data
|
||||||
cameraFabYFraction = prefs[Keys.CAMERA_FAB_Y_FRACTION]?.coerceIn(0f, 1f) ?: 1f,
|
.map { prefs ->
|
||||||
gallerySortAscending = prefs[Keys.GALLERY_SORT_ASCENDING] ?: true,
|
UserPreferences(
|
||||||
galleryGridMode = prefs[Keys.GALLERY_GRID_MODE] ?: true,
|
rootFolderUri = prefs[Keys.ROOT_FOLDER_URI],
|
||||||
zipJpegCompression = prefs[Keys.ZIP_JPEG_COMPRESSION] ?: true,
|
cameraFabXFraction = prefs[Keys.CAMERA_FAB_X_FRACTION]?.coerceIn(0f, 1f) ?: 1f,
|
||||||
screenTransitionsEnabled = prefs[Keys.SCREEN_TRANSITIONS_ENABLED] ?: true,
|
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) {
|
suspend fun setRootFolderUri(uri: String) {
|
||||||
context.dataStore.edit { prefs ->
|
context.dataStore.edit { prefs ->
|
||||||
|
|||||||
@@ -474,12 +474,14 @@ fun GalleryScreen(
|
|||||||
enter = fadeIn(tween(250)),
|
enter = fadeIn(tween(250)),
|
||||||
exit = fadeOut(tween(150)),
|
exit = fadeOut(tween(150)),
|
||||||
) {
|
) {
|
||||||
DraggableCameraFab(
|
if (uiState.viewPreferencesLoaded) {
|
||||||
xFraction = uiState.cameraFabXFraction,
|
DraggableCameraFab(
|
||||||
yFraction = uiState.cameraFabYFraction,
|
xFraction = uiState.cameraFabXFraction,
|
||||||
onPositionChange = viewModel::saveCameraFabPosition,
|
yFraction = uiState.cameraFabYFraction,
|
||||||
onClick = { onNavigateToCamera(uiState.currentFolderUri ?: folderUri) },
|
onPositionChange = viewModel::saveCameraFabPosition,
|
||||||
)
|
onClick = { onNavigateToCamera(uiState.currentFolderUri ?: folderUri) },
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SelectionActionsBar(
|
SelectionActionsBar(
|
||||||
|
|||||||
@@ -205,6 +205,9 @@ class GalleryViewModel @Inject constructor(
|
|||||||
loadJob = viewModelScope.launch {
|
loadJob = viewModelScope.launch {
|
||||||
uiState.value = uiState.value.copy(
|
uiState.value = uiState.value.copy(
|
||||||
currentFolderUri = folderUriString,
|
currentFolderUri = folderUriString,
|
||||||
|
folderName = state.folderName.ifEmpty {
|
||||||
|
folderUriString.substringAfterLast(':').substringAfterLast('/')
|
||||||
|
},
|
||||||
isLoading = true,
|
isLoading = true,
|
||||||
error = null,
|
error = null,
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in new issue
Block a user