From f6fe39620493148a2673c1242fee6d791eae2f09 Mon Sep 17 00:00:00 2001 From: Ankit Suda Date: Tue, 22 Nov 2022 17:36:47 +0530 Subject: [PATCH] Improve exercises screen --- .../rebound/data/db/daos/ExercisesDao.kt | 8 ++ .../data/repositories/ExercisesRepository.kt | 7 ++ .../domain/entities/ExerciseWithExtraInfo.kt | 6 ++ .../rebound/ui/exercises/ExercisesScreen.kt | 63 +++++++------- .../ui/exercises/ExercisesScreenViewModel.kt | 87 ++++++++++--------- 5 files changed, 98 insertions(+), 73 deletions(-) diff --git a/modules/core-data/src/main/java/com/ankitsuda/rebound/data/db/daos/ExercisesDao.kt b/modules/core-data/src/main/java/com/ankitsuda/rebound/data/db/daos/ExercisesDao.kt index 43e042ad..4977f899 100644 --- a/modules/core-data/src/main/java/com/ankitsuda/rebound/data/db/daos/ExercisesDao.kt +++ b/modules/core-data/src/main/java/com/ankitsuda/rebound/data/db/daos/ExercisesDao.kt @@ -14,11 +14,13 @@ package com.ankitsuda.rebound.data.db.daos +import androidx.paging.PagingSource import androidx.room.Dao import androidx.room.Insert import androidx.room.Query import androidx.room.Transaction import com.ankitsuda.rebound.domain.entities.Exercise +import com.ankitsuda.rebound.domain.entities.ExerciseWithExtraInfo import com.ankitsuda.rebound.domain.entities.ExerciseWithMuscle import com.ankitsuda.rebound.domain.entities.LogEntriesWithWorkout import kotlinx.coroutines.flow.Flow @@ -32,6 +34,12 @@ interface ExercisesDao { @Query("SELECT * FROM exercises ORDER BY name") fun getAllExercises(): Flow> + @Query( + """ + """ + ) + fun getAllExercisesWithExtraInfoPaged(searchQuery: String?): PagingSource + @Query("SELECT * FROM exercises ORDER BY name") fun getAllExercisesWithMuscles(): Flow> diff --git a/modules/core-data/src/main/java/com/ankitsuda/rebound/data/repositories/ExercisesRepository.kt b/modules/core-data/src/main/java/com/ankitsuda/rebound/data/repositories/ExercisesRepository.kt index 8ceee5e6..10677f7d 100644 --- a/modules/core-data/src/main/java/com/ankitsuda/rebound/data/repositories/ExercisesRepository.kt +++ b/modules/core-data/src/main/java/com/ankitsuda/rebound/data/repositories/ExercisesRepository.kt @@ -14,6 +14,8 @@ package com.ankitsuda.rebound.data.repositories +import androidx.paging.Pager +import androidx.paging.PagingConfig import com.ankitsuda.base.utils.generateId import com.ankitsuda.rebound.data.db.daos.ExercisesDao import com.ankitsuda.rebound.data.db.daos.MusclesDao @@ -62,6 +64,11 @@ class ExercisesRepository @Inject constructor( list } + fun getExercisesWithExtraInfoPaged(searchQuery: String? = null) = Pager(PagingConfig(pageSize = 15)) { + exercisesDao.getAllExercisesWithExtraInfoPaged(searchQuery = searchQuery) + }.flow + + suspend fun createExercise( name: String? = null, notes: String? = null, diff --git a/modules/domain/src/main/java/com/ankitsuda/rebound/domain/entities/ExerciseWithExtraInfo.kt b/modules/domain/src/main/java/com/ankitsuda/rebound/domain/entities/ExerciseWithExtraInfo.kt index 9b58bceb..445e257e 100644 --- a/modules/domain/src/main/java/com/ankitsuda/rebound/domain/entities/ExerciseWithExtraInfo.kt +++ b/modules/domain/src/main/java/com/ankitsuda/rebound/domain/entities/ExerciseWithExtraInfo.kt @@ -20,7 +20,13 @@ import java.time.LocalDateTime import java.util.* data class ExerciseWithExtraInfo( + @Embedded var exercise: Exercise, + @Relation( + parentColumn = "primary_muscle_tag", + entityColumn = "tag" + ) var primaryMuscle: Muscle?, + @ColumnInfo(name = "logs_count") var logsCount: Long, ) diff --git a/modules/ui-exercises/src/main/java/com/ankitsuda/rebound/ui/exercises/ExercisesScreen.kt b/modules/ui-exercises/src/main/java/com/ankitsuda/rebound/ui/exercises/ExercisesScreen.kt index b99f6146..50198c83 100644 --- a/modules/ui-exercises/src/main/java/com/ankitsuda/rebound/ui/exercises/ExercisesScreen.kt +++ b/modules/ui-exercises/src/main/java/com/ankitsuda/rebound/ui/exercises/ExercisesScreen.kt @@ -31,9 +31,14 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavController +import androidx.paging.compose.LazyPagingItems +import androidx.paging.compose.collectAsLazyPagingItems +import androidx.paging.compose.items +import com.ankitsuda.base.utils.generateId import com.ankitsuda.navigation.LeafScreen import com.ankitsuda.navigation.LocalNavigator import com.ankitsuda.navigation.Navigator +import com.ankitsuda.navigation.RESULT_EXERCISES_SCREEN_EXERCISE_ID import com.ankitsuda.rebound.domain.entities.ExerciseWithExtraInfo import com.ankitsuda.rebound.ui.components.* import com.ankitsuda.rebound.ui.theme.LocalThemeState @@ -43,11 +48,6 @@ import com.google.accompanist.pager.ExperimentalPagerApi import me.onebone.toolbar.ScrollStrategy import me.onebone.toolbar.rememberCollapsingToolbarScaffoldState -@OptIn( - ExperimentalPagerApi::class, - ExperimentalFoundationApi::class, - ExperimentalAnimationApi::class -) @Composable fun ExercisesScreen( navController: NavController, @@ -58,7 +58,7 @@ fun ExercisesScreen( val isSearchMode by viewModel.isSearchMode.observeAsState(false) val searchTerm by viewModel.searchTerm.observeAsState("") - val groupedExercises by viewModel.groupedExercises.collectAsState(initial = emptyMap()) + val exercisesPaged = viewModel.exercisesPaged.collectAsLazyPagingItems() val layout: @Composable () -> Unit = { ExercisesScreenContent( @@ -67,13 +67,14 @@ fun ExercisesScreen( isBottomSheet = isBottomSheet, isSearchMode = isSearchMode, searchTerm = searchTerm, - groupedExercises = groupedExercises, + exercisesPaged = exercisesPaged, onToggleSearchMode = { viewModel.toggleSearchMode() + }, + onChangeSearchTerm = { + viewModel.setSearchTerm(it) } - ) { - viewModel.setSearchTerm(it) - } + ) } if (isBottomSheet) { @@ -85,7 +86,6 @@ fun ExercisesScreen( } } -@OptIn(ExperimentalFoundationApi::class) @Composable private fun ExercisesScreenContent( navController: NavController, @@ -93,7 +93,7 @@ private fun ExercisesScreenContent( isBottomSheet: Boolean, isSearchMode: Boolean, searchTerm: String, - groupedExercises: Map>, + exercisesPaged: LazyPagingItems, onToggleSearchMode: () -> Unit, onChangeSearchTerm: (String) -> Unit ) { @@ -149,28 +149,32 @@ private fun ExercisesScreenContent( modifier = Modifier .fillMaxSize(), ) { - for (group in groupedExercises) { - stickyHeader(key = group.key) { - Text( - modifier = Modifier - .fillMaxWidth() - .background(LocalThemeState.current.backgroundColor) - .padding(horizontal = 24.dp, vertical = 8.dp), - text = group.key, - style = ReboundTheme.typography.caption, - color = LocalThemeState.current.onBackgroundColor.copy(alpha = 0.75f) - ) + items(exercisesPaged, key = { + when (it) { + is ExerciseWithExtraInfo -> it.exercise.exerciseId + is String -> "header_$it" + else -> generateId() } - - items(items = group.value, key = { it.exercise.exerciseId }) { item -> - ExerciseItem( + }) { item -> + when (item) { + is String -> + Text( + modifier = Modifier + .fillMaxWidth() + .background(LocalThemeState.current.backgroundColor) + .padding(horizontal = 24.dp, vertical = 8.dp), + text = item, + style = ReboundTheme.typography.caption, + color = LocalThemeState.current.onBackgroundColor.copy(alpha = 0.75f) + ) + is ExerciseWithExtraInfo -> ExerciseItem( name = item.exercise.name.toString(), muscle = item.primaryMuscle?.name.toString(), totalLogs = item.logsCount, onClick = { if (isBottomSheet) { navController.previousBackStackEntry?.savedStateHandle?.set( - "result_exercises_screen_exercise_id", + RESULT_EXERCISES_SCREEN_EXERCISE_ID, item.exercise.exerciseId ) navController.popBackStack() @@ -181,12 +185,11 @@ private fun ExercisesScreenContent( ) ) } - }) + } + ) } } } - - } } } \ No newline at end of file diff --git a/modules/ui-exercises/src/main/java/com/ankitsuda/rebound/ui/exercises/ExercisesScreenViewModel.kt b/modules/ui-exercises/src/main/java/com/ankitsuda/rebound/ui/exercises/ExercisesScreenViewModel.kt index 6fd7abc6..d3e7044d 100644 --- a/modules/ui-exercises/src/main/java/com/ankitsuda/rebound/ui/exercises/ExercisesScreenViewModel.kt +++ b/modules/ui-exercises/src/main/java/com/ankitsuda/rebound/ui/exercises/ExercisesScreenViewModel.kt @@ -18,6 +18,9 @@ import androidx.compose.runtime.snapshots.SnapshotStateList import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import androidx.paging.PagingData +import androidx.paging.cachedIn +import androidx.paging.insertSeparators import com.ankitsuda.base.utils.extensions.shareWhileObserved import com.ankitsuda.rebound.data.repositories.ExercisesRepository import com.ankitsuda.rebound.data.repositories.MusclesRepository @@ -33,66 +36,64 @@ import javax.inject.Inject class ExercisesScreenViewModel @Inject constructor( private val exercisesRepository: ExercisesRepository ) : ViewModel() { - private var _isSearchMode = MutableLiveData(false) + private var _isSearchMode = MutableStateFlow(false) val isSearchMode = _isSearchMode - private var _searchTerm = MutableLiveData("") + private var _searchTerm = MutableStateFlow("") val searchTerm = _searchTerm - private val allExercises = arrayListOf() - - private var _filteredExercises: SnapshotStateList = - SnapshotStateList() + val exercisesPaged = _searchTerm.flatMapLatest { + exercisesRepository.getExercisesWithExtraInfoPaged( + searchQuery = it.trim().takeIf { q -> q.isNotBlank() } + ) + .map { pagingData -> + mapPage(pagingData) + } + .cachedIn(viewModelScope) + .shareWhileObserved(viewModelScope) + } - private var _groupedExercises: MutableStateFlow>> = - MutableStateFlow(emptyMap()) - val groupedExercises = _groupedExercises - .shareWhileObserved(viewModelScope) - init { - viewModelScope.launch { - exercisesRepository.getExercisesWithExtraInfo().collect { - allExercises.clear() - allExercises.addAll(it) - filterExercises() + private fun mapPage(pagingData: PagingData): PagingData = + pagingData.insertSeparators { before, after -> + val afterFirstChar = after?.exercise?.name?.firstOrNull()?.uppercase() + if (before?.exercise?.name?.firstOrNull() + ?.uppercase() != afterFirstChar + ) { + afterFirstChar + } else { + null } } - } fun toggleSearchMode() { - _isSearchMode.value = !(_isSearchMode.value)!! - filterExercises() + _isSearchMode.value = !_isSearchMode.value + _searchTerm.value = "" } fun setSearchTerm(term: String) { _searchTerm.value = term - filterExercises() } - private fun filterExercises() { - viewModelScope.launch { -// if (_searchTerm.value?.isNotBlank() == true) { -// _filteredExercises.value = allExercises.last() -// .filter { it.exercise.name?.contains(_searchTerm.value!!, true) == true } +// private fun filterExercises() { +// viewModelScope.launch { +// _filteredExercises.clear() +// if (_isSearchMode.value == true) { +// _filteredExercises.addAll(allExercises.filter { +// it.exercise.name?.contains( +// _searchTerm.value!!, +// true +// ) == true +// }) // } else { - Timber.d("Filtering list $allExercises") - _filteredExercises.clear() - if (_isSearchMode.value == true) { - _filteredExercises.addAll(allExercises.filter { - it.exercise.name?.contains( - _searchTerm.value!!, - true - ) == true - }) - } else { - _filteredExercises.addAll(allExercises) - } - - _groupedExercises.emit(_filteredExercises.groupBy { - (it.exercise.name?.firstOrNull() ?: "#").toString().uppercase(Locale.getDefault()) - }) +// _filteredExercises.addAll(allExercises) // } - } - } +// +// _groupedExercises.emit(_filteredExercises.groupBy { +// (it.exercise.name?.firstOrNull() ?: "#").toString().uppercase(Locale.getDefault()) +// }) +//// } +// } +// } } \ No newline at end of file