Skip to content

Commit a24d8b2

Browse files
authored
feat: opt in toggle to show images (#130)
* feat: add user preference for showing/hiding images * feat: expose new user preference in settings - update testing * feat: enforce user preference for images to all relevant components - add new strings and icons - update testing * docs: update changelog * fix: missing bracket after merge
1 parent 287eff4 commit a24d8b2

23 files changed

Lines changed: 440 additions & 157 deletions

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2020

2121
### Changed
2222

23-
-
23+
- A new toggle to show images was introduced (off by default). [#29](https://github.com/LibreFitOrg/LibreFit/issues/29)
2424

2525
### Deprecated
2626

app/src/main/java/org/librefit/db/repository/UserPreferencesRepository.kt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ private val SHOW_KEEP_ANDROID_OPEN_KEY = booleanPreferencesKey("showKeepAndroidO
5353
private val USE_SCROLL_WHEEL_FOR_INPUT_KEY = booleanPreferencesKey("use_number_picker")
5454
private val DISMISS_SCROLL_WHELL_INPUT_AUTOMATICALLY =
5555
booleanPreferencesKey("dismiss_input_modal_bottom_sheet_automatically_key")
56+
private val SHOW_EXERCISES_IMAGES_KEY = booleanPreferencesKey("show_exercises_images_key")
5657
private val UNIT_SYSTEM_KEY = stringPreferencesKey("unit_system")
5758
/**
5859
* Central repository managing application-level preferences, including theme, unit systems, and language.
@@ -169,6 +170,14 @@ class UserPreferencesRepository @Inject constructor(
169170
initialValue = true
170171
)
171172

173+
val showExercisesImages: StateFlow<Boolean?> = dataStore.data
174+
.map { preferences -> preferences[SHOW_EXERCISES_IMAGES_KEY] }
175+
.stateIn(
176+
scope = applicationScope,
177+
started = SharingStarted.Eagerly,
178+
initialValue = null
179+
)
180+
172181
val dismissScrollWheelInputAutomatically: StateFlow<Boolean> = dataStore.data
173182
.map { preferences -> preferences[DISMISS_SCROLL_WHELL_INPUT_AUTOMATICALLY] == true }
174183
.stateIn(
@@ -314,6 +323,12 @@ class UserPreferencesRepository @Inject constructor(
314323
dataStore.edit { preferences -> preferences[USE_SCROLL_WHEEL_FOR_INPUT_KEY] = useScroll }
315324
}
316325

326+
suspend fun saveShowExercisesImages(show: Boolean) {
327+
dataStore.edit { preferences ->
328+
preferences[SHOW_EXERCISES_IMAGES_KEY] = show
329+
}
330+
}
331+
317332
suspend fun saveDismissScrollWheelInputAutomatically(dismissAutomatically: Boolean) {
318333
dataStore.edit { preferences ->
319334
preferences[DISMISS_SCROLL_WHELL_INPUT_AUTOMATICALLY] = dismissAutomatically

app/src/main/java/org/librefit/ui/components/ExerciseCard.kt

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ import kotlin.time.Duration.Companion.seconds
141141
* @param isDragging when `true`, it applies a shadow to further emphasize with a shadow that the card is dragged.
142142
* @param useScrollWheelForInput If `true`, [InputModalBottomSheet] appears instead of keyboard
143143
* @param dismissScrollWheelInputAutomatically If both this and [useScrollWheelForInput] are `true`, the [InputModalBottomSheet] will be dismissed automatically after first edit.
144+
* @param showExercisesImages If `true`, it shows image of exercise
144145
* @param updateExerciseNotes A function to update notes based on [UiExercise.id]. For more details, refer to
145146
* [org.librefit.ui.screens.workout.WorkoutScreenViewModel.updateExerciseNotes] and
146147
* [org.librefit.ui.screens.editWorkout.EditWorkoutScreenViewModel.updateExerciseNotes].
@@ -195,6 +196,7 @@ fun SharedTransitionScope.ExerciseCard(
195196
isDragging: Boolean,
196197
useScrollWheelForInput: Boolean,
197198
dismissScrollWheelInputAutomatically: Boolean,
199+
showExercisesImages: Boolean?,
198200
onReorderRequest: () -> Unit,
199201
deleteSet: (Long) -> Unit,
200202
updateExerciseNotes: (String, Long) -> Unit,
@@ -244,23 +246,25 @@ fun SharedTransitionScope.ExerciseCard(
244246
) {
245247
val model =
246248
remember(exerciseWithSets.exerciseDC.images) { exerciseWithSets.exerciseDC.images.firstOrNull() }
247-
AsyncImage(
248-
model = model?.let { "file:///android_asset/${it}" },
249-
fallback = painterResource(R.drawable.no_image),
250-
contentDescription = exerciseWithSets.exerciseDC.name,
251-
contentScale = ContentScale.Crop,
252-
colorFilter = if (model == null) ColorFilter.tint(MaterialTheme.colorScheme.onSurfaceVariant) else null,
253-
modifier = Modifier
254-
.padding(end = 10.dp)
255-
.sharedElement(
256-
sharedContentState = rememberSharedContentState(
257-
key = exerciseWithSets.exercise.id.toString() + exerciseWithSets.exerciseDC.id
258-
),
259-
animatedVisibilityScope = animatedVisibilityScope
260-
)
261-
.size(50.dp)
262-
.clip(MaterialTheme.shapes.medium)
263-
)
249+
if (showExercisesImages == true) {
250+
AsyncImage(
251+
model = model?.let { "file:///android_asset/${it}" },
252+
fallback = painterResource(R.drawable.no_image),
253+
contentDescription = exerciseWithSets.exerciseDC.name,
254+
contentScale = ContentScale.Crop,
255+
colorFilter = if (model == null) ColorFilter.tint(MaterialTheme.colorScheme.onSurfaceVariant) else null,
256+
modifier = Modifier
257+
.padding(end = 10.dp)
258+
.sharedElement(
259+
sharedContentState = rememberSharedContentState(
260+
key = exerciseWithSets.exercise.id.toString() + exerciseWithSets.exerciseDC.id
261+
),
262+
animatedVisibilityScope = animatedVisibilityScope
263+
)
264+
.size(50.dp)
265+
.clip(MaterialTheme.shapes.medium)
266+
)
267+
}
264268
Text(
265269
text = exerciseWithSets.exerciseDC.name,
266270
style = MaterialTheme.typography.headlineSmall,
@@ -1027,6 +1031,7 @@ private fun ExerciseCardPreview() {
10271031
isDragging = false,
10281032
useScrollWheelForInput = false,
10291033
dismissScrollWheelInputAutomatically = false,
1034+
showExercisesImages = false,
10301035
updateExerciseNotes = { notes, _ ->
10311036
e.value = e.value.copy(exercise = e.value.exercise.copy(notes = notes))
10321037
},

app/src/main/java/org/librefit/ui/components/ExerciseCardSmall.kt

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ import org.librefit.util.Formatter.formatTime
7979
fun SharedTransitionScope.ExerciseCardSmall(
8080
exerciseWithSets: UiExerciseWithSets,
8181
isRoutine: Boolean = false,
82+
showExercisesImages: Boolean?,
8283
animatedVisibilityScope: AnimatedVisibilityScope,
8384
onDetail: () -> Unit
8485
) {
@@ -103,23 +104,25 @@ fun SharedTransitionScope.ExerciseCardSmall(
103104
) {
104105
val model =
105106
remember(exerciseWithSets.exerciseDC.images) { exerciseWithSets.exerciseDC.images.firstOrNull() }
106-
AsyncImage(
107-
model = model?.let { "file:///android_asset/${it}" },
108-
fallback = painterResource(R.drawable.no_image),
109-
contentDescription = exerciseWithSets.exerciseDC.name,
110-
contentScale = ContentScale.Crop,
111-
colorFilter = if (model == null) ColorFilter.tint(MaterialTheme.colorScheme.onSurfaceVariant) else null,
112-
modifier = Modifier
113-
.padding(end = 10.dp)
114-
.sharedElement(
115-
sharedContentState = rememberSharedContentState(
116-
key = exerciseWithSets.exercise.id.toString() + exerciseWithSets.exerciseDC.id
117-
),
118-
animatedVisibilityScope = animatedVisibilityScope
119-
)
120-
.size(50.dp)
121-
.clip(MaterialTheme.shapes.medium)
122-
)
107+
if (showExercisesImages == true) {
108+
AsyncImage(
109+
model = model?.let { "file:///android_asset/${it}" },
110+
fallback = painterResource(R.drawable.no_image),
111+
contentDescription = exerciseWithSets.exerciseDC.name,
112+
contentScale = ContentScale.Crop,
113+
colorFilter = if (model == null) ColorFilter.tint(MaterialTheme.colorScheme.onSurfaceVariant) else null,
114+
modifier = Modifier
115+
.padding(end = 10.dp)
116+
.sharedElement(
117+
sharedContentState = rememberSharedContentState(
118+
key = exerciseWithSets.exercise.id.toString() + exerciseWithSets.exerciseDC.id
119+
),
120+
animatedVisibilityScope = animatedVisibilityScope
121+
)
122+
.size(50.dp)
123+
.clip(MaterialTheme.shapes.medium)
124+
)
125+
}
123126
Text(
124127
modifier = Modifier.weight(1f),
125128
text = exerciseWithSets.exerciseDC.name,
@@ -289,6 +292,7 @@ private fun ExerciseCardSmallPreview() {
289292
),
290293
sets = persistentListOf(UiSet(completed = true), UiSet(reps = 10), UiSet())
291294
),
295+
showExercisesImages = null,
292296
animatedVisibilityScope = this
293297
) { }
294298
}

app/src/main/java/org/librefit/ui/screens/editExercise/EditExerciseScreen.kt

Lines changed: 27 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ fun SharedTransitionScope.EditExerciseScreen(
8787

8888
val exerciseDC by viewModel.exerciseDC.collectAsStateWithLifecycle()
8989

90+
val showExercisesImages by viewModel.showExercisesImages.collectAsStateWithLifecycle()
91+
9092
val isCreateMode = exerciseDC.id.isBlank()
9193

9294
EditExerciseScreenContent(
@@ -103,6 +105,7 @@ fun SharedTransitionScope.EditExerciseScreen(
103105
instructions = exerciseDC.instructions,
104106
category = exerciseDC.category,
105107
images = exerciseDC.images,
108+
showExercisesImages = showExercisesImages,
106109
navigateBack = navController::navigateUp,
107110
animatedVisibilityScope = animatedVisibilityScope,
108111
updateValue = viewModel::updateValue,
@@ -141,6 +144,7 @@ private fun SharedTransitionScope.EditExerciseScreenContent(
141144
instructions: List<String>,
142145
images: List<String>,
143146
category: Category,
147+
showExercisesImages: Boolean?,
144148
animatedVisibilityScope: AnimatedVisibilityScope,
145149
navigateBack: () -> Unit,
146150
updateValue: (ExerciseProperty) -> Unit,
@@ -191,26 +195,28 @@ private fun SharedTransitionScope.EditExerciseScreenContent(
191195
item {
192196
// TODO: implement display all images (like in a horizontal pager)
193197
val model = remember(images) { images.firstOrNull() }
194-
AsyncImage(
195-
model = model?.let { "file:///android_asset/${it}" },
196-
fallback = painterResource(R.drawable.no_image),
197-
contentDescription = name,
198-
contentScale = ContentScale.Crop,
199-
filterQuality = FilterQuality.High,
200-
colorFilter = if (model == null) ColorFilter.tint(MaterialTheme.colorScheme.onSurfaceVariant) else null,
201-
modifier = Modifier
202-
.sharedElement(
203-
sharedContentState = rememberSharedContentState(stringId + exerciseDcId),
204-
animatedVisibilityScope = animatedVisibilityScope
205-
)
206-
.fillMaxWidth()
207-
.clip(MaterialTheme.shapes.extraLarge)
208-
.border(
209-
0.5.dp,
210-
color = MaterialTheme.colorScheme.outlineVariant,
211-
shape = MaterialTheme.shapes.extraLarge
212-
),
213-
)
198+
if (showExercisesImages == true) {
199+
AsyncImage(
200+
model = model?.let { "file:///android_asset/${it}" },
201+
fallback = painterResource(R.drawable.no_image),
202+
contentDescription = name,
203+
contentScale = ContentScale.Crop,
204+
filterQuality = FilterQuality.High,
205+
colorFilter = if (model == null) ColorFilter.tint(MaterialTheme.colorScheme.onSurfaceVariant) else null,
206+
modifier = Modifier
207+
.sharedElement(
208+
sharedContentState = rememberSharedContentState(stringId + exerciseDcId),
209+
animatedVisibilityScope = animatedVisibilityScope
210+
)
211+
.fillMaxWidth()
212+
.clip(MaterialTheme.shapes.extraLarge)
213+
.border(
214+
0.5.dp,
215+
color = MaterialTheme.colorScheme.outlineVariant,
216+
shape = MaterialTheme.shapes.extraLarge
217+
),
218+
)
219+
}
214220
}
215221
item {
216222
}
@@ -420,6 +426,7 @@ private fun EditExerciseScreenContentPreview() {
420426
instructions = e.instructions,
421427
category = e.category,
422428
images = e.images,
429+
showExercisesImages = null,
423430
animatedVisibilityScope = this,
424431
saveExercise = {},
425432
updatePrimaryMuscles = {},

app/src/main/java/org/librefit/ui/screens/editExercise/EditExerciseScreenViewModel.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import kotlinx.coroutines.flow.asStateFlow
2121
import kotlinx.coroutines.flow.update
2222
import kotlinx.coroutines.launch
2323
import org.librefit.db.repository.DatasetRepository
24+
import org.librefit.db.repository.UserPreferencesRepository
2425
import org.librefit.enums.exercise.Category
2526
import org.librefit.enums.exercise.Equipment
2627
import org.librefit.enums.exercise.ExerciseProperty
@@ -38,9 +39,12 @@ import kotlin.uuid.Uuid
3839
@HiltViewModel
3940
class EditExerciseScreenViewModel @Inject constructor(
4041
savedStateHandle: SavedStateHandle,
41-
private val datasetRepository: DatasetRepository
42+
private val datasetRepository: DatasetRepository,
43+
userPreferencesRepository: UserPreferencesRepository
4244
) : ViewModel() {
4345

46+
val showExercisesImages = userPreferencesRepository.showExercisesImages
47+
4448
val exerciseDCid = savedStateHandle.toRoute<Route.EditExerciseScreen>().exerciseDCid
4549

4650
@OptIn(ExperimentalUuidApi::class)

app/src/main/java/org/librefit/ui/screens/editWorkout/EditWorkoutScreen.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ fun SharedTransitionScope.EditWorkoutScreen(
8484

8585
val useScrollWheelForInput by viewModel.useScrollWheelForInput.collectAsStateWithLifecycle()
8686

87+
val showExercisesImages by viewModel.showExercisesImages.collectAsStateWithLifecycle()
88+
8789
val dismissInputAutomatically by viewModel.dismissScrollWheelInputAutomatically.collectAsStateWithLifecycle()
8890

8991
LaunchedEffect(Unit) {
@@ -117,6 +119,7 @@ fun SharedTransitionScope.EditWorkoutScreen(
117119
isTitleEmpty = viewModel.isTitleEmpty(),
118120
dismissInputAutomatically = dismissInputAutomatically,
119121
useScrollWheelForInput = useScrollWheelForInput,
122+
showExercisesImages = showExercisesImages,
120123
updateTitle = viewModel::updateTitle,
121124
updateNotes = viewModel::updateNotes,
122125
updateSetTime = viewModel::updateSetTime,
@@ -149,6 +152,7 @@ private fun SharedTransitionScope.EditWorkoutScreenContent(
149152
isTitleEmpty: Boolean,
150153
dismissInputAutomatically: Boolean,
151154
useScrollWheelForInput: Boolean,
155+
showExercisesImages: Boolean?,
152156
updateTitle: (String) -> Unit,
153157
updateNotes: (String) -> Unit,
154158
deleteSet: (Long) -> Unit,
@@ -339,6 +343,7 @@ private fun SharedTransitionScope.EditWorkoutScreenContent(
339343
addSet = addSetToExercise,
340344
isDragging = isDragging,
341345
useScrollWheelForInput = useScrollWheelForInput,
346+
showExercisesImages = showExercisesImages,
342347
dismissScrollWheelInputAutomatically = dismissInputAutomatically,
343348
onDetail = { id, idExerciseDC ->
344349
navController.navigate(
@@ -425,6 +430,7 @@ private fun EditWorkoutScreenPreview() {
425430
isTitleEmpty = false,
426431
useScrollWheelForInput = false,
427432
dismissInputAutomatically = false,
433+
showExercisesImages = null,
428434
updateTitle = { _ -> },
429435
updateNotes = { _ -> },
430436
addSetToExercise = { _ -> },

app/src/main/java/org/librefit/ui/screens/editWorkout/EditWorkoutScreenViewModel.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ class EditWorkoutScreenViewModel @Inject constructor(
4949
@param:IoDispatcher private val ioDispatcher: CoroutineDispatcher,
5050
userPreferencesRepository: UserPreferencesRepository
5151
) : ViewModel() {
52-
52+
val showExercisesImages = userPreferencesRepository.showExercisesImages
5353
val useScrollWheelForInput = userPreferencesRepository.useScrollWheelForInput
5454

5555
val dismissScrollWheelInputAutomatically =

0 commit comments

Comments
 (0)