From 8ce764a4119d140f55a682efc2b1a8db3ef88ac6 Mon Sep 17 00:00:00 2001 From: Ankit Suda Date: Sat, 12 Nov 2022 23:18:48 +0530 Subject: [PATCH] Fix first week of day in calendar screen --- app/build.gradle | 3 + .../ankitsuda/rebound/ui/main/MainScreen.kt | 63 +++++---- .../rebound/ui/main/MainScreenViewModel.kt | 25 +++- modules/common-ui-compose/build.gradle | 4 + .../ankitsuda/common/compose/AppSettings.kt | 33 +++++ .../rebound/ui/calendar/CalendarScreen.kt | 133 +++++++++--------- .../ui/calendar/CalendarScreenViewModel.kt | 32 +++-- .../components/CalendarViewComponents.kt | 38 +++-- 8 files changed, 213 insertions(+), 118 deletions(-) create mode 100644 modules/common-ui-compose/src/main/java/com/ankitsuda/common/compose/AppSettings.kt diff --git a/app/build.gradle b/app/build.gradle index 60c6e0ee..bf740c82 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -57,6 +57,8 @@ android { } } compileOptions { + coreLibraryDesugaringEnabled true + sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } @@ -168,4 +170,5 @@ dependencies { api Deps.Dagger.hilt kapt Deps.Dagger.hiltCompiler + coreLibraryDesugaring Deps.Android.desugaring } \ No newline at end of file diff --git a/app/src/main/java/com/ankitsuda/rebound/ui/main/MainScreen.kt b/app/src/main/java/com/ankitsuda/rebound/ui/main/MainScreen.kt index e2a6d45c..e2fc353b 100644 --- a/app/src/main/java/com/ankitsuda/rebound/ui/main/MainScreen.kt +++ b/app/src/main/java/com/ankitsuda/rebound/ui/main/MainScreen.kt @@ -66,6 +66,7 @@ import com.google.accompanist.navigation.material.rememberBottomSheetNavigator import kotlinx.coroutines.launch import timber.log.Timber import com.ankitsuda.common.compose.R +import java.time.DayOfWeek /** * Root screen of the app @@ -103,6 +104,9 @@ private fun MainLayout( val currentWorkoutId by viewModel.currentWorkoutId.collectAsState(initial = NONE_WORKOUT_ID) + val appSettings by viewModel.appSettings.collectAsState( + initial = AppSettings.defValues() + ) val panelHidden = currentWorkoutId == NONE_WORKOUT_ID @@ -136,6 +140,7 @@ private fun MainLayout( CompositionLocalProvider( LocalDialog provides dialog, LocalPanel provides mainPanel, + LocalAppSettings provides appSettings ) { Box() { /** @@ -243,35 +248,35 @@ private fun BottomBar( navController: NavHostController, ) { val bottomNavigationItems = listOf( - BottomNavigationScreens( - TabRootScreen.HomeTab.route, - stringResource(id = R.string.home), - Icons.Outlined.Home, - Icons.Filled.Home - ), - BottomNavigationScreens( - TabRootScreen.HistoryTab.route, - stringResource(id = R.string.history), - Icons.Outlined.WatchLater, - Icons.Filled.WatchLater - ), - BottomNavigationScreens( - TabRootScreen.WorkoutTab.route, - stringResource(id = R.string.workout), - Icons.Outlined.PlayArrow, - Icons.Filled.PlayArrow - ), - BottomNavigationScreens( - TabRootScreen.ExercisesTab.route, - stringResource(id = R.string.exercises), - Icons.Outlined.FitnessCenter, Icons.Filled.FitnessCenter - ), - BottomNavigationScreens( - TabRootScreen.MoreTab.route, - stringResource(id = R.string.more), - Icons.Outlined.Menu, - Icons.Filled.Menu - ) + BottomNavigationScreens( + TabRootScreen.HomeTab.route, + stringResource(id = R.string.home), + Icons.Outlined.Home, + Icons.Filled.Home + ), + BottomNavigationScreens( + TabRootScreen.HistoryTab.route, + stringResource(id = R.string.history), + Icons.Outlined.WatchLater, + Icons.Filled.WatchLater + ), + BottomNavigationScreens( + TabRootScreen.WorkoutTab.route, + stringResource(id = R.string.workout), + Icons.Outlined.PlayArrow, + Icons.Filled.PlayArrow + ), + BottomNavigationScreens( + TabRootScreen.ExercisesTab.route, + stringResource(id = R.string.exercises), + Icons.Outlined.FitnessCenter, Icons.Filled.FitnessCenter + ), + BottomNavigationScreens( + TabRootScreen.MoreTab.route, + stringResource(id = R.string.more), + Icons.Outlined.Menu, + Icons.Filled.Menu + ) ) val theme = LocalThemeState.current diff --git a/app/src/main/java/com/ankitsuda/rebound/ui/main/MainScreenViewModel.kt b/app/src/main/java/com/ankitsuda/rebound/ui/main/MainScreenViewModel.kt index d697d556..c0723ccd 100644 --- a/app/src/main/java/com/ankitsuda/rebound/ui/main/MainScreenViewModel.kt +++ b/app/src/main/java/com/ankitsuda/rebound/ui/main/MainScreenViewModel.kt @@ -19,6 +19,8 @@ import androidx.core.os.HandlerCompat.postDelayed import androidx.lifecycle.LiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.map +import androidx.lifecycle.viewModelScope +import com.ankitsuda.common.compose.AppSettings import com.ankitsuda.rebound.R import com.ankitsuda.rebound.data.datastore.PrefStorage import com.ankitsuda.rebound.data.stopwatch.* @@ -28,7 +30,9 @@ import com.ankitsuda.rebound.resttimer.TimerState import com.ankitsuda.rebound.resttimer.getFormattedStopWatchTime import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.* +import kotlinx.coroutines.launch import timber.log.Timber +import java.time.DayOfWeek import java.util.* import java.util.logging.Handler import javax.inject.Inject @@ -37,8 +41,8 @@ import kotlin.math.max @HiltViewModel class MainScreenViewModel @Inject constructor( - prefStorage: PrefStorage, - private val restTimerRepository: RestTimerRepository + restTimerRepository: RestTimerRepository, + private val prefStorage: PrefStorage ) : ViewModel() { val currentWorkoutId = prefStorage.currentWorkoutId @@ -62,4 +66,21 @@ class MainScreenViewModel @Inject constructor( null } + private var _appSettings = MutableStateFlow(AppSettings.defValues()) + val appSettings = _appSettings.asStateFlow() + + init { + observeSettings() + } + + private fun observeSettings() { + viewModelScope.launch { + prefStorage.firstDayOfWeek.collect { + _appSettings.value = AppSettings( + firstDayOfWeek = DayOfWeek.of(it) + ) + } + } + } + } \ No newline at end of file diff --git a/modules/common-ui-compose/build.gradle b/modules/common-ui-compose/build.gradle index 973287a1..edd0c169 100644 --- a/modules/common-ui-compose/build.gradle +++ b/modules/common-ui-compose/build.gradle @@ -32,6 +32,8 @@ android { compileOptions { + coreLibraryDesugaringEnabled true + sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } @@ -85,6 +87,8 @@ dependencies { api Deps.Android.Accompanist.navigationFlowlayout api Deps.Android.Accompanist.navigationAnimation + coreLibraryDesugaring Deps.Android.desugaring + // 3rd party // api Deps.Android.Compose.coil } diff --git a/modules/common-ui-compose/src/main/java/com/ankitsuda/common/compose/AppSettings.kt b/modules/common-ui-compose/src/main/java/com/ankitsuda/common/compose/AppSettings.kt new file mode 100644 index 00000000..1e8e7367 --- /dev/null +++ b/modules/common-ui-compose/src/main/java/com/ankitsuda/common/compose/AppSettings.kt @@ -0,0 +1,33 @@ +/* + * 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.common.compose + +import androidx.compose.runtime.staticCompositionLocalOf +import java.time.DayOfWeek + +data class AppSettings( + val firstDayOfWeek: DayOfWeek +) { + companion object { + fun defValues(): AppSettings = AppSettings( + firstDayOfWeek = DayOfWeek.SUNDAY + ) + + } +} + +val LocalAppSettings = staticCompositionLocalOf { + error("No AppSettings given") +} \ No newline at end of file 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 85d68e0d..83a81553 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 @@ -15,7 +15,7 @@ package com.ankitsuda.rebound.ui.calendar import androidx.compose.foundation.background -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState @@ -28,24 +28,18 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavController -import com.ankitsuda.rebound.ui.components.TopBar -import com.ankitsuda.rebound.ui.components.TopBarIconButton -import me.onebone.toolbar.CollapsingToolbarScaffold -import me.onebone.toolbar.rememberCollapsingToolbarScaffoldState import com.ankitsuda.base.utils.toEpochMillis import com.ankitsuda.base.utils.toLocalDate import com.ankitsuda.navigation.DATE_KEY import com.ankitsuda.navigation.SELECTED_DATE_KEY import com.ankitsuda.rebound.ui.calendar.components.CalendarMonthItem +import com.ankitsuda.rebound.ui.components.TopBar +import com.ankitsuda.rebound.ui.components.TopBarIconButton import kotlinx.coroutines.launch +import me.onebone.toolbar.CollapsingToolbarScaffold import me.onebone.toolbar.ScrollStrategy -import timber.log.Timber -import java.lang.Exception -import java.text.SimpleDateFormat -import java.time.Instant +import me.onebone.toolbar.rememberCollapsingToolbarScaffoldState import java.time.LocalDate -import java.time.ZoneId -import java.util.* @Composable fun CalendarScreen( @@ -60,74 +54,81 @@ fun CalendarScreen( val collapsingState = rememberCollapsingToolbarScaffoldState() val scrollState = rememberLazyListState() - val calendar = viewModel.calendar + val mCalendar by viewModel.calendar.collectAsState() val countsWithDate by viewModel.workoutsCountOnDates.collectAsState() val today = LocalDate.now() val coroutine = rememberCoroutineScope() - LaunchedEffect(key1 = Unit) { - if (calendar.isEmpty()) { - viewModel.getCalendar() - try { - scrollState.scrollToItem(calendar.indexOf((calendar.filter { - try { - it.month == selectedDate.month.value && it.year == selectedDate.year - } catch (e1: Exception) { - it.month == today.month.value && today.year == today.year - } - }[0]))) - } catch (e: Exception) { - e.printStackTrace() + mCalendar?.let { calendar -> + LaunchedEffect(key1 = Unit) { + if (calendar.isNotEmpty()) { + try { + scrollState.scrollToItem(calendar.indexOf((calendar.filter { + try { + it.month == selectedDate.month.value && it.year == selectedDate.year + } catch (e1: Exception) { + it.month == today.month.value && today.year == today.year + } + }[0]))) + } catch (e: Exception) { + e.printStackTrace() + } } } - } - CollapsingToolbarScaffold( - scrollStrategy = ScrollStrategy.EnterAlwaysCollapsed, - state = collapsingState, - toolbar = { - TopBar(title = stringResource(id = R.string.calendar), strictLeftIconAlignToStart = true, leftIconBtn = { - TopBarIconButton(icon = Icons.Outlined.Close, title = stringResource(id = R.string.close_calendar)) { - navController.popBackStack() - } - }, rightIconBtn = { - TopBarIconButton( - icon = Icons.Outlined.Today, - title = stringResource(id = R.string.jump_to_today), - onClick = { - coroutine.launch { - scrollState.animateScrollToItem(calendar.indexOf((calendar.filter { - it.month == today.month.value && it.year == today.year - }[0]))) + CollapsingToolbarScaffold( + scrollStrategy = ScrollStrategy.EnterAlwaysCollapsed, + state = collapsingState, + toolbar = { + TopBar( + title = stringResource(id = R.string.calendar), + strictLeftIconAlignToStart = true, + leftIconBtn = { + TopBarIconButton( + icon = Icons.Outlined.Close, + title = stringResource(id = R.string.close_calendar) + ) { + navController.popBackStack() } + }, + rightIconBtn = { + TopBarIconButton( + icon = Icons.Outlined.Today, + title = stringResource(id = R.string.jump_to_today), + onClick = { + coroutine.launch { + scrollState.animateScrollToItem(calendar.indexOf((calendar.filter { + it.month == today.month.value && it.year == today.year + }[0]))) + } + }) }) - }) - }, - modifier = Modifier.background(MaterialTheme.colors.background) - ) { - LazyColumn( - state = scrollState, modifier = Modifier - .fillMaxSize() - .background(MaterialTheme.colors.background) + }, + modifier = Modifier.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.date.time - dateItem.date.toEpochMillis() + LazyColumn( + state = scrollState, modifier = Modifier + .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() - ) - navController.popBackStack() - }) + ) + navController.popBackStack() + }) + } } - } + } } } 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 39248c46..ff421bff 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 @@ -18,6 +18,7 @@ import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.snapshots.SnapshotStateList import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.ankitsuda.rebound.data.datastore.PrefStorage import com.ankitsuda.rebound.data.repositories.WorkoutsRepository import com.ankitsuda.rebound.domain.entities.CountWithDate import com.ankitsuda.rebound.ui.calendar.models.CalendarMonth @@ -26,9 +27,7 @@ import com.ankitsuda.rebound.ui.calendar.models.MonthConfig import com.ankitsuda.rebound.ui.calendar.models.OutDateStyle import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Job -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import timber.log.Timber import java.time.DayOfWeek @@ -39,10 +38,14 @@ import javax.inject.Inject @HiltViewModel class CalendarScreenViewModel @Inject constructor( - private val workoutsRepository: WorkoutsRepository + private val workoutsRepository: WorkoutsRepository, + private val prefs: PrefStorage ) : ViewModel() { - private var _calendar: SnapshotStateList = mutableStateListOf() - val calendar = _calendar +// private var _calendar: SnapshotStateList = mutableStateListOf() +// val calendar = _calendar + + private var _calendar = MutableStateFlow?>(null) + val calendar = _calendar.asStateFlow() private var _workoutsCountOnDates = MutableStateFlow?>(null) val workoutsCountOnDates = _workoutsCountOnDates.asStateFlow() @@ -50,6 +53,17 @@ class CalendarScreenViewModel @Inject constructor( private var calendarJob: Job? = null private var countJob: Job? = null + private var firstDayOfWeek = DayOfWeek.MONDAY + + init { + viewModelScope.launch { + prefs.firstDayOfWeek.collect { + firstDayOfWeek = DayOfWeek.of(it) + getCalendar() + } + } + } + fun getCalendar( ) { calendarJob?.cancel() @@ -61,12 +75,12 @@ class CalendarScreenViewModel @Inject constructor( endMonth = YearMonth.of(Year.now().value, Month.DECEMBER), hasBoundaries = true, maxRowCount = Int.MAX_VALUE, - firstDayOfWeek = DayOfWeek.MONDAY, + firstDayOfWeek = firstDayOfWeek, job = Job() ) refreshCounts() - _calendar.addAll(monthConfig.months) + _calendar.value = monthConfig.months } } @@ -81,8 +95,6 @@ class CalendarScreenViewModel @Inject constructor( dateEnd = YearMonth.of(Year.now().value, Month.DECEMBER).atEndOfMonth() ).firstOrNull() - Timber.d("Newcounts $counts") - _workoutsCountOnDates.value = counts } } 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 6ad05a72..6e65be1b 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 @@ -14,6 +14,7 @@ package com.ankitsuda.rebound.ui.calendar.components +import android.icu.util.Calendar.WeekData import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.clickable @@ -24,6 +25,7 @@ import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.key +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -32,7 +34,9 @@ import androidx.compose.ui.text.style.TextAlign 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.toLocalDate +import com.ankitsuda.common.compose.LocalAppSettings import com.ankitsuda.rebound.domain.entities.CountWithDate import com.ankitsuda.rebound.ui.calendar.models.CalendarDay import com.ankitsuda.rebound.ui.calendar.models.CalendarMonth @@ -43,6 +47,10 @@ import java.time.LocalDate import java.time.format.DateTimeFormatter import java.util.* import com.ankitsuda.common.compose.R +import com.ankitsuda.rebound.ui.theme.ReboundTheme +import java.time.DayOfWeek +import java.time.format.TextStyle +import java.time.temporal.WeekFields const val WEIGHT_7DAY_WEEK = 1f / 7f @@ -53,15 +61,24 @@ fun CalendarMonthItem( selectedDate: LocalDate, onClickOnDay: (CalendarDay) -> Unit ) { - val dayNames = listOf( - stringResource(id = R.string.mon), - stringResource(id = R.string.tue), - stringResource(id = R.string.wed), - stringResource(id = R.string.thu), - stringResource(id = R.string.fri), - stringResource(id = R.string.sat), - stringResource(id = R.string.sun) - ) + val firstDayOfWeek = LocalAppSettings.current.firstDayOfWeek + + val dayNames = remember(firstDayOfWeek) { + val days = (7 - firstDayOfWeek.ordinal) + var allDays = DayOfWeek.values().toList() + allDays = allDays.takeLast(days) + allDays.dropLast(days) + allDays.map { it.getDisplayName(TextStyle.SHORT, Locale.getDefault()) } + } + +// val dayNames = listOf( +// stringResource(id = R.string.mon), +// stringResource(id = R.string.tue), +// stringResource(id = R.string.wed), +// stringResource(id = R.string.thu), +// stringResource(id = R.string.fri), +// stringResource(id = R.string.sat), +// stringResource(id = R.string.sun) +// ) val monthFormatter = DateTimeFormatter.ofPattern("LLLL yyyy") val dayFormatter = DateTimeFormatter.ofPattern("d") @@ -71,7 +88,6 @@ fun CalendarMonthItem( .fillMaxWidth() .padding(start = 8.dp, end = 8.dp) ) { - CalendarMonthHeader(month.yearMonth.format(monthFormatter)) Row(modifier = Modifier.fillMaxWidth()) { @@ -141,7 +157,7 @@ fun ColumnScope.CalendarMonthHeader(text: String) { fun RowScope.CalendarDayNameItem(text: String) { Text( text = text, - style = MaterialTheme.typography.body2, + style = ReboundTheme.typography.body2.copy(color = ReboundTheme.colors.onBackground), textAlign = TextAlign.Center, modifier = Modifier .padding(start = 8.dp, end = 8.dp, top = 16.dp)