diff --git a/modules/core-data/src/main/java/com/ankitsuda/rebound/data/db/daos/WorkoutsDao.kt b/modules/core-data/src/main/java/com/ankitsuda/rebound/data/db/daos/WorkoutsDao.kt index 3b00c0a3..af74d2d9 100644 --- a/modules/core-data/src/main/java/com/ankitsuda/rebound/data/db/daos/WorkoutsDao.kt +++ b/modules/core-data/src/main/java/com/ankitsuda/rebound/data/db/daos/WorkoutsDao.kt @@ -21,6 +21,7 @@ import com.ankitsuda.base.utils.generateId import com.ankitsuda.rebound.domain.LogSetType import com.ankitsuda.rebound.domain.entities.* import kotlinx.coroutines.flow.Flow +import java.time.LocalDate import java.time.LocalDateTime @Dao @@ -150,6 +151,14 @@ interface WorkoutsDao { @Query("SELECT COUNT(*) as count, start_at as date FROM workouts WHERE date(start_at / 1000,'unixepoch') >= date(:dateStart / 1000,'unixepoch') AND date(start_at / 1000,'unixepoch') <= date(:dateEnd / 1000,'unixepoch') AND is_hidden = 0 AND in_progress = 0 GROUP BY start_at") fun getWorkoutsCountOnDateRange(dateStart: Long, dateEnd: Long): Flow> + @Query(""" + SELECT SUM(count) FROM (SELECT COUNT(*) as count FROM workouts WHERE +date(start_at / 1000,'unixepoch') >= date(:dateStart / 1000,'unixepoch') AND + date(start_at / 1000,'unixepoch') <= date(:dateEnd / 1000,'unixepoch') +AND is_hidden = 0 AND in_progress = 0 GROUP BY start_at) + """) + fun getWorkoutsCountOnMonthOnDateRangeAlt(dateStart: Long, dateEnd: Long): Flow + @Query(""" SELECT SUM(count) FROM (SELECT COUNT(*) as count FROM workouts WHERE date(start_at / 10000,'unixepoch') >= date(:date / 10000,'unixepoch') AND @@ -198,6 +207,17 @@ AND is_hidden = 0 AND in_progress = 0 GROUP BY start_at) @Query("UPDATE exercise_workout_junctions SET superset_id = :supersetId WHERE id = :junctionId") suspend fun updateExerciseWorkoutJunctionSupersetId(junctionId: String, supersetId: Int?) + @Query( + """ + SELECT * FROM workouts w + WHERE + date(start_at / 1000,'unixepoch') >= date(:dateStart / 1000,'unixepoch') AND date(start_at / 1000,'unixepoch') <= date(:dateEnd / 1000,'unixepoch') + AND w.is_hidden = 0 AND w.in_progress = 0 + ORDER BY w.completed_at DESC + """ + ) + fun getWorkoutsWithExtraInfoAltPaged(dateStart: Long, dateEnd: Long): PagingSource + @Query( """ SELECT * FROM workouts w diff --git a/modules/core-data/src/main/java/com/ankitsuda/rebound/data/repositories/WorkoutsRepository.kt b/modules/core-data/src/main/java/com/ankitsuda/rebound/data/repositories/WorkoutsRepository.kt index fcb9f6d4..55627af8 100644 --- a/modules/core-data/src/main/java/com/ankitsuda/rebound/data/repositories/WorkoutsRepository.kt +++ b/modules/core-data/src/main/java/com/ankitsuda/rebound/data/repositories/WorkoutsRepository.kt @@ -276,9 +276,19 @@ class WorkoutsRepository @Inject constructor( // fun getWorkoutsWithExtraInfoAlt() = workoutsDao.getWorkoutsWithExtraInfoAlt() - fun getWorkoutsWithExtraInfoPaged() = + fun getWorkoutsWithExtraInfoPaged( + dateStart: LocalDate? = null, + dateEnd: LocalDate? = null + ) = Pager(PagingConfig(pageSize = 15)) { - workoutsDao.getWorkoutsWithExtraInfoAltPaged() + if (dateStart != null && dateEnd != null) { + workoutsDao.getWorkoutsWithExtraInfoAltPaged( + dateStart.toEpochMillis(), + dateEnd.toEpochMillis() + ) + } else { + workoutsDao.getWorkoutsWithExtraInfoAltPaged() + } } .flow.map { it.map { item -> @@ -293,7 +303,7 @@ class WorkoutsRepository @Inject constructor( } fun getWorkoutsWithExtraInfo() = - workoutsDao.getWorkoutsWithExtraInfoAlt() + workoutsDao.getWorkoutsWithExtraInfoAlt() .map { it.map { item -> val logEntries = item.junctions?.flatMap { j -> j.logEntries } @@ -312,6 +322,12 @@ class WorkoutsRepository @Inject constructor( dateEnd = dateEnd.toEpochMillis() ) + fun getWorkoutsCountOnMonthOnDateRangeAlt(dateStart: LocalDate, dateEnd: LocalDate) = + workoutsDao.getWorkoutsCountOnMonthOnDateRangeAlt( + dateStart = dateStart.toEpochMillis(), + dateEnd = dateEnd.toEpochMillis() + ) + fun getWorkoutsCountOnMonth(date: Long) = workoutsDao.getWorkoutsCountOnMonth( date = date diff --git a/modules/navigation/src/main/java/com/ankitsuda/navigation/Screens.kt b/modules/navigation/src/main/java/com/ankitsuda/navigation/Screens.kt index 44d8ff3d..c46bb3f0 100644 --- a/modules/navigation/src/main/java/com/ankitsuda/navigation/Screens.kt +++ b/modules/navigation/src/main/java/com/ankitsuda/navigation/Screens.kt @@ -26,9 +26,13 @@ import com.ankitsuda.base.utils.toEpochMillis import com.google.accompanist.navigation.material.ExperimentalMaterialNavigationApi import com.google.accompanist.navigation.material.bottomSheet import java.time.LocalDate +import java.time.Year import java.util.* const val DATE_KEY = "date" +const val DAY_KEY = "day" +const val MONTH_KEY = "month" +const val YEAR_KEY = "year" const val SELECTED_DATE_KEY = "selected_date" const val WORKOUT_ID_KEY = "workout_id" const val EXERCISE_ID_KEY = "exercise_id" @@ -107,20 +111,44 @@ sealed class LeafScreen( ) : LeafScreen(route = route, root = root) data class History( - override val route: String = "history?$DATE_KEY={$DATE_KEY}", + override val route: String = "history?$DAY_KEY={$DAY_KEY}&$MONTH_KEY={$MONTH_KEY}&$YEAR_KEY={${YEAR_KEY}}", override val root: TabRootScreen = TabRootScreen.HistoryTab ) : LeafScreen( route = route, root = root, arguments = listOf( - navArgument(DATE_KEY) { - nullable = true - type = NavType.StringType + navArgument(DAY_KEY) { + type = NavType.IntType + nullable = false + defaultValue = -1 + }, + navArgument(MONTH_KEY) { + type = NavType.IntType + nullable = false + defaultValue = -1 + }, + navArgument(YEAR_KEY) { + type = NavType.IntType + nullable = false + defaultValue = -1 } )) { - fun createRoute(date: Date, root: TabRootScreen = TabRootScreen.HistoryTab) = - "${root.route}/history?$DATE_KEY=${date.time}" + companion object { + fun createRoute( + day: Int, + month: Int, + year: Int, + root: TabRootScreen = TabRootScreen.HistoryTab + ) = + "${root.route}/history?$DAY_KEY=$day&$MONTH_KEY=$month&$YEAR_KEY=$year" + + fun createRoute(month: Int, year: Int, root: TabRootScreen = TabRootScreen.HistoryTab) = + "${root.route}/history?$DAY_KEY=-1&$MONTH_KEY=$month&$YEAR_KEY=$year" + + fun createRoute(year: Int, root: TabRootScreen = TabRootScreen.HistoryTab) = + "${root.route}/history?$DAY_KEY=-1&$MONTH_KEY=-1&$YEAR_KEY=$year" + } } data class Workout( diff --git a/modules/ui-calendar/src/main/java/com/ankitsuda/rebound/ui/calendar/CalendarScreen.kt b/modules/ui-calendar/src/main/java/com/ankitsuda/rebound/ui/calendar/CalendarScreen.kt index 83a81553..d247b599 100644 --- a/modules/ui-calendar/src/main/java/com/ankitsuda/rebound/ui/calendar/CalendarScreen.kt +++ b/modules/ui-calendar/src/main/java/com/ankitsuda/rebound/ui/calendar/CalendarScreen.kt @@ -17,22 +17,22 @@ package com.ankitsuda.rebound.ui.calendar import androidx.compose.foundation.background import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.Close +import androidx.compose.material.icons.outlined.ArrowBack import androidx.compose.material.icons.outlined.Today import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavController -import com.ankitsuda.base.utils.toEpochMillis +import com.ankitsuda.base.utils.generateId import com.ankitsuda.base.utils.toLocalDate -import com.ankitsuda.navigation.DATE_KEY -import com.ankitsuda.navigation.SELECTED_DATE_KEY +import com.ankitsuda.navigation.* import com.ankitsuda.rebound.ui.calendar.components.CalendarMonthItem +import com.ankitsuda.rebound.ui.calendar.components.CalendarYearHeader import com.ankitsuda.rebound.ui.components.TopBar import com.ankitsuda.rebound.ui.components.TopBarIconButton import kotlinx.coroutines.launch @@ -40,10 +40,13 @@ import me.onebone.toolbar.CollapsingToolbarScaffold import me.onebone.toolbar.ScrollStrategy import me.onebone.toolbar.rememberCollapsingToolbarScaffoldState import java.time.LocalDate +import java.time.Month +import java.time.Year @Composable fun CalendarScreen( navController: NavController, + navigator: Navigator = LocalNavigator.current, viewModel: CalendarScreenViewModel = hiltViewModel() ) { val selectedDate = @@ -86,8 +89,8 @@ fun CalendarScreen( strictLeftIconAlignToStart = true, leftIconBtn = { TopBarIconButton( - icon = Icons.Outlined.Close, - title = stringResource(id = R.string.close_calendar) + icon = Icons.Outlined.ArrowBack, + title = stringResource(id = R.string.back) ) { navController.popBackStack() } @@ -112,20 +115,45 @@ fun CalendarScreen( .fillMaxSize() .background(MaterialTheme.colors.background) ) { - items(calendar, key = { "${it.month}_${it.year}" }) { - val month = it - CalendarMonthItem( - month = month, - selectedDate = selectedDate, - countsWithDate = countsWithDate ?: emptyList(), - onClickOnDay = { dateItem -> - navController.previousBackStackEntry?.savedStateHandle?.set( - DATE_KEY, - dateItem.date.toEpochMillis() - + for (item in calendar) { + if (item.month == Month.JANUARY.value) { + item(key = "year_header_${item.year}") { + CalendarYearHeader( + year = Year.of(item.year), + onClick = { + navigator.navigate( + LeafScreen.History.createRoute( + year = item.year, + ) + ) + } ) - navController.popBackStack() - }) + } + } + item(key = "month_block_${item.month}_${item.year}") { + CalendarMonthItem( + month = item, + selectedDate = selectedDate, + countsWithDate = countsWithDate ?: emptyList(), + onClickOnMonth = { monthItem -> + navigator.navigate( + LeafScreen.History.createRoute( + month = monthItem.month, + year = monthItem.year, + ) + ) + }, + onClickOnDay = { dateItem -> + navigator.navigate( + LeafScreen.History.createRoute( + day = dateItem.day, + month = dateItem.date.monthValue, + year = dateItem.date.year, + ) + ) + } + ) + } } } diff --git a/modules/ui-calendar/src/main/java/com/ankitsuda/rebound/ui/calendar/CalendarScreenViewModel.kt b/modules/ui-calendar/src/main/java/com/ankitsuda/rebound/ui/calendar/CalendarScreenViewModel.kt index ff421bff..5599f9c8 100644 --- a/modules/ui-calendar/src/main/java/com/ankitsuda/rebound/ui/calendar/CalendarScreenViewModel.kt +++ b/modules/ui-calendar/src/main/java/com/ankitsuda/rebound/ui/calendar/CalendarScreenViewModel.kt @@ -41,9 +41,6 @@ class CalendarScreenViewModel @Inject constructor( private val workoutsRepository: WorkoutsRepository, private val prefs: PrefStorage ) : ViewModel() { -// private var _calendar: SnapshotStateList = mutableStateListOf() -// val calendar = _calendar - private var _calendar = MutableStateFlow?>(null) val calendar = _calendar.asStateFlow() diff --git a/modules/ui-calendar/src/main/java/com/ankitsuda/rebound/ui/calendar/components/CalendarViewComponents.kt b/modules/ui-calendar/src/main/java/com/ankitsuda/rebound/ui/calendar/components/CalendarViewComponents.kt index 6e65be1b..574b826d 100644 --- a/modules/ui-calendar/src/main/java/com/ankitsuda/rebound/ui/calendar/components/CalendarViewComponents.kt +++ b/modules/ui-calendar/src/main/java/com/ankitsuda/rebound/ui/calendar/components/CalendarViewComponents.kt @@ -35,6 +35,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.ankitsuda.base.util.* import com.ankitsuda.base.utils.extensions.toArrayList +import com.ankitsuda.base.utils.generateId import com.ankitsuda.base.utils.toLocalDate import com.ankitsuda.common.compose.LocalAppSettings import com.ankitsuda.rebound.domain.entities.CountWithDate @@ -49,6 +50,7 @@ import java.util.* import com.ankitsuda.common.compose.R import com.ankitsuda.rebound.ui.theme.ReboundTheme import java.time.DayOfWeek +import java.time.Year import java.time.format.TextStyle import java.time.temporal.WeekFields @@ -59,7 +61,8 @@ fun CalendarMonthItem( month: CalendarMonth, countsWithDate: List, selectedDate: LocalDate, - onClickOnDay: (CalendarDay) -> Unit + onClickOnDay: (CalendarDay) -> Unit, + onClickOnMonth: (CalendarMonth) -> Unit ) { val firstDayOfWeek = LocalAppSettings.current.firstDayOfWeek @@ -88,7 +91,12 @@ fun CalendarMonthItem( .fillMaxWidth() .padding(start = 8.dp, end = 8.dp) ) { - CalendarMonthHeader(month.yearMonth.format(monthFormatter)) + CalendarMonthHeader( + text = month.yearMonth.format(monthFormatter), + onClick = { + onClickOnMonth(month) + } + ) Row(modifier = Modifier.fillMaxWidth()) { for (name in dayNames) { @@ -97,7 +105,7 @@ fun CalendarMonthItem( } for (week in month.weekDays) { - key(week) { + key("${month.yearMonth}_week_${month.weekDays.indexOf(week)}") { Row( modifier = Modifier .defaultMinSize(256.dp) @@ -112,7 +120,7 @@ fun CalendarMonthItem( val formattedDay = day.date.format(dayFormatter) - key("${day.date.dayOfMonth}_${day.date.month}_${day.date.year}") { + key("${day.date.dayOfMonth}_${day.date.month.value}_${day.date.year}") { CalendarDayItem( text = formattedDay, modifier = Modifier.weight( @@ -139,7 +147,7 @@ fun CalendarMonthItem( } @Composable -fun ColumnScope.CalendarMonthHeader(text: String) { +fun ColumnScope.CalendarMonthHeader(text: String, onClick: () -> Unit) { Text( text = text, style = MaterialTheme.typography.body1, @@ -148,11 +156,27 @@ fun ColumnScope.CalendarMonthHeader(text: String) { color = LocalThemeState.current.onBackgroundColor, modifier = Modifier .fillMaxWidth() + .clickable(onClick = onClick) .padding(20.dp) .align(Alignment.CenterHorizontally) ) } +@Composable +fun CalendarYearHeader(year: Year, onClick: () -> Unit) { + Text( + text = year.toString(), + style = MaterialTheme.typography.body1, + textAlign = TextAlign.Center, + fontSize = 20.sp, + color = LocalThemeState.current.onBackgroundColor, + modifier = Modifier + .fillMaxWidth() + .clickable(onClick = onClick) + .padding(20.dp) + ) +} + @Composable fun RowScope.CalendarDayNameItem(text: String) { Text( diff --git a/modules/ui-history/src/main/java/com/ankitsuda/rebound/ui/history/HistoryScreen.kt b/modules/ui-history/src/main/java/com/ankitsuda/rebound/ui/history/HistoryScreen.kt index 60ba3f37..55b33202 100644 --- a/modules/ui-history/src/main/java/com/ankitsuda/rebound/ui/history/HistoryScreen.kt +++ b/modules/ui-history/src/main/java/com/ankitsuda/rebound/ui/history/HistoryScreen.kt @@ -26,6 +26,7 @@ import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.MaterialTheme import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.ArrowBack import androidx.compose.material.icons.outlined.DateRange import androidx.compose.material.icons.outlined.MoreVert import androidx.compose.runtime.Composable @@ -50,6 +51,7 @@ import com.ankitsuda.rebound.ui.components.TopBar2 import com.ankitsuda.rebound.ui.components.TopBarIconButton import com.ankitsuda.rebound.ui.history.components.HistoryHeader import com.ankitsuda.rebound.ui.history.components.HistorySessionItemCard +import com.ankitsuda.rebound.ui.history.enums.WorkoutsDateRangeType import com.ankitsuda.rebound.ui.history.models.CountWithLocalDate import com.ankitsuda.rebound.ui.theme.ReboundTheme import kotlinx.coroutines.delay @@ -61,6 +63,7 @@ import me.onebone.toolbar.ToolbarWithFabScaffold import me.onebone.toolbar.rememberCollapsingToolbarScaffoldState import timber.log.Timber import java.time.LocalDate +import java.time.YearMonth @Composable fun HistoryScreen( @@ -70,88 +73,107 @@ fun HistoryScreen( ) { val scrollState = rememberLazyListState() - val argumentsDate = navController.currentBackStackEntry - ?.savedStateHandle - ?.getLiveData(DATE_KEY)?.observeAsState() - val collapsingState = rememberCollapsingToolbarScaffoldState() val workoutsPage = viewModel.workouts.collectAsLazyPagingItems() - LaunchedEffect(key1 = argumentsDate?.value) { - if (argumentsDate?.value == null) return@LaunchedEffect; - - try { - val mDate = argumentsDate.value?.toLocalDate() - var index = -1 - -// var loopIndex = 0; -// for (map in workoutsMap) { -// if (map.key?.month == mDate?.month && map.key?.year == mDate?.year) { -// index = loopIndex; -// break; -// } else { -// loopIndex += 1 + map.value.size + val dateRangeType = viewModel.dateRangeType + val argDay = viewModel.argDay + val argMonth = viewModel.argMonth + val argYear = viewModel.argYear + +// LaunchedEffect(key1 = argumentsDate?.value) { +// if (argumentsDate?.value == null) return@LaunchedEffect; +// +// try { +// val mDate = argumentsDate.value?.toLocalDate() +// var index = -1 +// +//// var loopIndex = 0; +//// for (map in workoutsMap) { +//// if (map.key?.month == mDate?.month && map.key?.year == mDate?.year) { +//// index = loopIndex; +//// break; +//// } else { +//// loopIndex += 1 + map.value.size +//// } +//// } +//// workoutsPage?.forEachIndexed { i, item -> +//// if (item is WorkoutWithExtraInfo && item.workout?.completedAt.toString() == mDate.toString()) { +//// index = i +//// } +//// } +// +// val allWorkouts = viewModel.workouts2.firstOrNull() +// +// if (allWorkouts != null) { +// for (i in allWorkouts.indices) { +// val item = allWorkouts[i] +// if (item is WorkoutWithExtraInfo) { +// val a = item.workout?.completedAt?.toLocalDate().toString() +// val b = mDate.toString() +// if (a == b) { +// index = i +// break +// } +// } // } // } -// workoutsPage?.forEachIndexed { i, item -> -// if (item is WorkoutWithExtraInfo && item.workout?.completedAt.toString() == mDate.toString()) { -// index = i +// +// if (index > -1) { +// try { +// workoutsPage[index] +// } catch (e: Exception) { +// e.printStackTrace() // } +// delay(100) +// scrollState.animateScrollToItem( +// index +// ) // } - - val allWorkouts = viewModel.workouts2.firstOrNull() - - if (allWorkouts != null) { - for (i in allWorkouts.indices) { - val item = allWorkouts[i] - if (item is WorkoutWithExtraInfo) { - val a = item.workout?.completedAt?.toLocalDate().toString() - val b = mDate.toString() - if (a == b) { - index = i - break - } - } - } - } - - if (index > -1) { - try { - workoutsPage[index] - } catch (e: Exception) { - e.printStackTrace() - } - delay(100) - scrollState.animateScrollToItem( - index - ) - } - - Timber.d("items count ${workoutsPage.itemCount}") - - navController.currentBackStackEntry - ?.savedStateHandle?.remove(DATE_KEY) - } catch (e: Exception) { - e.printStackTrace() - } - } +// +// Timber.d("items count ${workoutsPage.itemCount}") +// +// navController.currentBackStackEntry +// ?.savedStateHandle?.remove(DATE_KEY) +// } catch (e: Exception) { +// e.printStackTrace() +// } +// } CollapsingToolbarScaffold( scrollStrategy = ScrollStrategy.EnterAlwaysCollapsed, state = collapsingState, toolbar = { TopBar2( - title = stringResource(id = R.string.history), + title = when (dateRangeType) { + WorkoutsDateRangeType.ALL -> stringResource( + id = R.string.history + ) + WorkoutsDateRangeType.MONTH -> YearMonth.of(argYear!!, argMonth!!).toString() + WorkoutsDateRangeType.YEAR -> "Year $argYear" + WorkoutsDateRangeType.DAY -> LocalDate.of(argYear!!, argMonth!!, argDay!!) + .toString() + }, toolbarState = collapsingState.toolbarState, navigationIcon = { - TopBarIconButton( - icon = Icons.Outlined.DateRange, - title = stringResource(id = R.string.show_calendar), - onClick = { - navigator.navigate(LeafScreen.Calendar.createRoute(selectedDate = LocalDate.now())) - } - ) + if (dateRangeType == WorkoutsDateRangeType.ALL) { + TopBarIconButton( + icon = Icons.Outlined.DateRange, + title = stringResource(id = R.string.show_calendar), + onClick = { + navigator.navigate(LeafScreen.Calendar.createRoute(selectedDate = LocalDate.now())) + } + ) + } else { + TopBarIconButton( + icon = Icons.Outlined.ArrowBack, + title = stringResource(id = R.string.back), + onClick = { + navigator.goBack() + } + ) + } }, actions = { TopBarIconButton( @@ -176,46 +198,51 @@ fun HistoryScreen( verticalArrangement = Arrangement.spacedBy(20.dp), contentPadding = PaddingValues(24.dp) ) { - if (workoutsPage != null) { - items(workoutsPage!!, key = { - when (it) { - is WorkoutWithExtraInfo -> { - it.workout!!.id - } - is CountWithDate -> { - it.date - } - else -> { - generateId() - } + items(workoutsPage, key = { + when (it) { + is WorkoutWithExtraInfo -> { + it.workout!!.id } - }) { - when (it) { - is WorkoutWithExtraInfo -> - HistorySessionItemCard( - modifier = Modifier - .fillMaxWidth(), - onClick = { - navigator.navigate( - LeafScreen.Session.createRoute( - workoutId = it.workout?.id!! - ) - ) - }, - title = it.workout?.name.toString(), - totalExercises = it.totalExercises ?: 0, - duration = it.workout?.getDuration(), - volume = it.totalVolume, - prs = it.totalPRs ?: 0, - date = it.workout?.startAt ?: it.workout?.completedAt - ?: it.workout?.createdAt, - ) - is CountWithDate -> - HistoryHeader( - date = it.date.toLocalDate() ?: LocalDate.now(), - totalWorkouts = it.count.toInt() - ) + is CountWithDate -> { + it.date.toString() + } + is Long -> { + it.toString() } + else -> { + generateId() + } + } + }) { + when (it) { + is WorkoutWithExtraInfo -> + HistorySessionItemCard( + modifier = Modifier + .fillMaxWidth(), + onClick = { + navigator.navigate( + LeafScreen.Session.createRoute( + workoutId = it.workout?.id!! + ) + ) + }, + title = it.workout?.name.toString(), + totalExercises = it.totalExercises ?: 0, + duration = it.workout?.getDuration(), + volume = it.totalVolume, + prs = it.totalPRs ?: 0, + date = it.workout?.startAt ?: it.workout?.completedAt + ?: it.workout?.createdAt, + ) + is Long -> HistoryHeader( + title = null, + totalWorkouts = it.toInt() + ) + is CountWithDate -> + HistoryHeader( + date = it.date.toLocalDate() ?: LocalDate.now(), + totalWorkouts = it.count.toInt() + ) } } } diff --git a/modules/ui-history/src/main/java/com/ankitsuda/rebound/ui/history/HistoryScreenViewModel.kt b/modules/ui-history/src/main/java/com/ankitsuda/rebound/ui/history/HistoryScreenViewModel.kt index 851b97bc..26bd225e 100644 --- a/modules/ui-history/src/main/java/com/ankitsuda/rebound/ui/history/HistoryScreenViewModel.kt +++ b/modules/ui-history/src/main/java/com/ankitsuda/rebound/ui/history/HistoryScreenViewModel.kt @@ -14,6 +14,7 @@ package com.ankitsuda.rebound.ui.history +import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import androidx.paging.PagingData @@ -21,55 +22,62 @@ import androidx.paging.cachedIn import androidx.paging.insertSeparators import com.ankitsuda.base.utils.extensions.shareWhileObserved import com.ankitsuda.base.utils.toEpochMillis +import com.ankitsuda.navigation.* import com.ankitsuda.rebound.data.repositories.WorkoutsRepository import com.ankitsuda.rebound.domain.entities.CountWithDate import com.ankitsuda.rebound.domain.entities.WorkoutWithExtraInfo +import com.ankitsuda.rebound.ui.history.enums.WorkoutsDateRangeType import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import timber.log.Timber +import java.time.LocalDate +import java.time.Month +import java.time.Year +import java.time.YearMonth import java.time.temporal.TemporalAdjusters import javax.inject.Inject @HiltViewModel class HistoryScreenViewModel @Inject constructor( + handle: SavedStateHandle, private val workoutsRepository: WorkoutsRepository, ) : ViewModel() { - val workouts = workoutsRepository.getWorkoutsWithExtraInfoPaged() - .map { - mapData(it) - } - .cachedIn(viewModelScope) - .shareWhileObserved(viewModelScope) + val argDay = handle.get(DAY_KEY).takeIf { it != -1 } + val argMonth = handle.get(MONTH_KEY).takeIf { it != -1 } + val argYear = handle.get(YEAR_KEY).takeIf { it != -1 } - val workouts2 = workoutsRepository.getWorkoutsWithExtraInfo() - .map { - val newList = arrayListOf() - it.forEachIndexed { index, after -> - val before = it.getOrNull(index - 1) - val afterDate = after.workout?.completedAt?.toLocalDate() - ?.with(TemporalAdjusters.firstDayOfMonth()) - - val beforeDate = before?.workout?.completedAt?.toLocalDate() - ?.with(TemporalAdjusters.firstDayOfMonth()) + val dateRangeType = if (argDay != null && argMonth != null && argYear != null) { + WorkoutsDateRangeType.DAY + } else if (argDay == null && argMonth != null && argYear != null) { + WorkoutsDateRangeType.MONTH + } else if (argDay == null && argMonth == null && argYear != null) { + WorkoutsDateRangeType.YEAR + } else { + WorkoutsDateRangeType.ALL + } - if (after.workout?.completedAt != null && afterDate != null && beforeDate != afterDate) { - val mWorkoutsCounts = workoutsRepository.getWorkoutsCountOnMonth( - date = after.workout!!.completedAt!!.toEpochMillis() - ).firstOrNull() + private val dateStart = when (dateRangeType) { + WorkoutsDateRangeType.MONTH -> LocalDate.of(argYear!!, argMonth!!, 1) + WorkoutsDateRangeType.YEAR -> LocalDate.of(argYear!!, 1, 1) + WorkoutsDateRangeType.DAY -> LocalDate.of(argYear!!, argMonth!!, argDay!!) + else -> null + } - newList.add( - CountWithDate( - date = afterDate.toEpochMillis(), - count = mWorkoutsCounts ?: 0 - ) - ) - } - newList.add(after) - } + private val dateEnd = when (dateRangeType) { + WorkoutsDateRangeType.MONTH -> YearMonth.of(argYear!!, argMonth!!).atEndOfMonth() + WorkoutsDateRangeType.YEAR -> Year.of(argYear!!).atMonth(Month.DECEMBER).atEndOfMonth() + WorkoutsDateRangeType.DAY -> LocalDate.of(argYear!!, argMonth!!, argDay!!) + else -> null + } - newList.toList() + val workouts = workoutsRepository.getWorkoutsWithExtraInfoPaged( + dateStart, dateEnd + ) + .map { + mapData(it) } + .cachedIn(viewModelScope) .shareWhileObserved(viewModelScope) private fun mapData(pagingData: PagingData) = @@ -82,14 +90,23 @@ class HistoryScreenViewModel @Inject constructor( ?.with(TemporalAdjusters.firstDayOfMonth()) if (after.workout?.completedAt != null && afterDate != null && beforeDate != afterDate) { - val mWorkoutsCounts = workoutsRepository.getWorkoutsCountOnMonth( - date = after.workout!!.completedAt!!.toEpochMillis() - ).firstOrNull() - CountWithDate( - date = afterDate.toEpochMillis(), - count = mWorkoutsCounts ?: 0 - ) + if ( + dateRangeType != WorkoutsDateRangeType.YEAR && + dateRangeType != WorkoutsDateRangeType.ALL && + dateStart != null && dateEnd != null) { + workoutsRepository.getWorkoutsCountOnMonthOnDateRangeAlt( + dateStart, + dateEnd + ).firstOrNull() ?: 0L + } else { + CountWithDate( + date = afterDate.toEpochMillis(), + count = workoutsRepository.getWorkoutsCountOnMonth( + date = after.workout!!.completedAt!!.toEpochMillis() + ).firstOrNull() ?: 0 + ) + } } else { null } @@ -97,10 +114,4 @@ class HistoryScreenViewModel @Inject constructor( null } } - - init { - viewModelScope.launch { - workouts2.collect() - } - } } \ No newline at end of file diff --git a/modules/ui-history/src/main/java/com/ankitsuda/rebound/ui/history/components/HistoryHeader.kt b/modules/ui-history/src/main/java/com/ankitsuda/rebound/ui/history/components/HistoryHeader.kt index 7495e668..d1bb54cc 100644 --- a/modules/ui-history/src/main/java/com/ankitsuda/rebound/ui/history/components/HistoryHeader.kt +++ b/modules/ui-history/src/main/java/com/ankitsuda/rebound/ui/history/components/HistoryHeader.kt @@ -34,15 +34,23 @@ fun HistoryHeader(date: LocalDate, totalWorkouts: Int) { val isSameYear = LocalDate.now().year == date.year val dateFormatter = DateTimeFormatter.ofPattern(if (isSameYear) "MMMM" else "MMMM yyyy") + HistoryHeader(title = dateFormatter.format(date), totalWorkouts = totalWorkouts) +} + +@OptIn(ExperimentalComposeUiApi::class) +@Composable +fun HistoryHeader(title: String?, totalWorkouts: Int) { Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically, ) { - Text( - text = dateFormatter.format(date), - style = ReboundTheme.typography.subtitle1.copy(color = ReboundTheme.colors.onBackground) - ) + title?.let { + Text( + text = it, + style = ReboundTheme.typography.subtitle1.copy(color = ReboundTheme.colors.onBackground) + ) + } Text( text = pluralStringResource( id = R.plurals.number_of_workouts, diff --git a/modules/ui-history/src/main/java/com/ankitsuda/rebound/ui/history/enums/WorkoutsDateRangeType.kt b/modules/ui-history/src/main/java/com/ankitsuda/rebound/ui/history/enums/WorkoutsDateRangeType.kt new file mode 100644 index 00000000..a7979283 --- /dev/null +++ b/modules/ui-history/src/main/java/com/ankitsuda/rebound/ui/history/enums/WorkoutsDateRangeType.kt @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2022 Ankit Suda. + * + * Licensed under the GNU General Public License v3 + * + * This is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + */ + +package com.ankitsuda.rebound.ui.history.enums + +import com.ankitsuda.rebound.domain.entities.BodyPartUnitType + +enum class WorkoutsDateRangeType(val value: String) { + ALL("all"), + MONTH("month"), + YEAR("year"), + DAY("day"); + + companion object { + fun fromString(value: String?): WorkoutsDateRangeType { + return values().find { it.value == value } ?: ALL + } + } +} \ No newline at end of file