diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml index 69e8615..fdf8d99 100644 --- a/.idea/kotlinc.xml +++ b/.idea/kotlinc.xml @@ -1,6 +1,6 @@ - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 773fe0f..8978d23 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,6 +1,6 @@ - + diff --git a/app/build.gradle b/app/build.gradle index ba814af..16c0807 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -19,14 +19,14 @@ spotless { android { namespace 'seamuslowry.daytracker' - compileSdk 33 + compileSdk 34 defaultConfig { applicationId "seamuslowry.daytracker" minSdk 33 - targetSdk 33 - versionCode 17 - versionName "0.23.1" + targetSdk 34 + versionCode 18 + versionName "0.23.2" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { @@ -100,7 +100,7 @@ dependencies { debugImplementation "androidx.compose.ui:ui-test-manifest" } -task printVersionName { +tasks.register('printVersionName') { doLast { println android.defaultConfig.versionName } diff --git a/app/src/main/java/seamuslowry/daytracker/data/repos/SettingsRepo.kt b/app/src/main/java/seamuslowry/daytracker/data/repos/SettingsRepo.kt index 4078b7d..220878e 100644 --- a/app/src/main/java/seamuslowry/daytracker/data/repos/SettingsRepo.kt +++ b/app/src/main/java/seamuslowry/daytracker/data/repos/SettingsRepo.kt @@ -5,6 +5,7 @@ import androidx.datastore.core.DataStore import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.booleanPreferencesKey import androidx.datastore.preferences.core.edit +import androidx.datastore.preferences.core.floatPreferencesKey import androidx.datastore.preferences.core.stringPreferencesKey import androidx.datastore.preferences.preferencesDataStore import dagger.hilt.android.qualifiers.ApplicationContext @@ -22,6 +23,7 @@ class SettingsRepo @Inject constructor(@ApplicationContext private val context: val REMINDER_ENABLED = booleanPreferencesKey("REMINDER_ENABLED_KEY") val REMINDER_TIME = stringPreferencesKey("REMINDER_TIME") val SHOW_RECORDED_VALUES = booleanPreferencesKey("SHOW_RECORDED_VALUES") + val MIN_CALENDAR_SIZE = floatPreferencesKey("MIN_CALENDAR_SIZE") } suspend fun setReminderEnabled(enabled: Boolean) { @@ -42,12 +44,19 @@ class SettingsRepo @Inject constructor(@ApplicationContext private val context: } } + suspend fun setMinCalendarSize(minSize: Float) { + context.dataStore.edit { + it[MIN_CALENDAR_SIZE] = minSize + } + } + val settings: Flow = context.dataStore.data .map { Settings( reminderEnabled = it[REMINDER_ENABLED] ?: false, reminderTime = it[REMINDER_TIME]?.let { time -> LocalTime.parse(time) } ?: LocalTime.of(18, 0), showRecordedValues = it[SHOW_RECORDED_VALUES] ?: false, + minCalendarSize = it[MIN_CALENDAR_SIZE], ) } } @@ -56,4 +65,5 @@ data class Settings( val reminderEnabled: Boolean = false, val reminderTime: LocalTime = LocalTime.of(18, 0), val showRecordedValues: Boolean = false, + val minCalendarSize: Float? = null, ) diff --git a/app/src/main/java/seamuslowry/daytracker/ui/screens/report/ReportScreen.kt b/app/src/main/java/seamuslowry/daytracker/ui/screens/report/ReportScreen.kt index af99fe5..4591fdc 100644 --- a/app/src/main/java/seamuslowry/daytracker/ui/screens/report/ReportScreen.kt +++ b/app/src/main/java/seamuslowry/daytracker/ui/screens/report/ReportScreen.kt @@ -1,51 +1,27 @@ package seamuslowry.daytracker.ui.screens.report -import android.animation.ArgbEvaluator -import androidx.compose.animation.animateContentSize -import androidx.compose.animation.core.tween -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.FlowRow -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.aspectRatio -import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.lazy.grid.GridCells -import androidx.compose.foundation.lazy.grid.LazyVerticalGrid -import androidx.compose.foundation.lazy.grid.items -import androidx.compose.material3.Card -import androidx.compose.material3.Divider import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.FilterChip -import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.alpha -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp -import androidx.core.graphics.ColorUtils import androidx.hilt.navigation.compose.hiltViewModel import seamuslowry.daytracker.R -import seamuslowry.daytracker.models.ItemConfiguration import seamuslowry.daytracker.models.localeFormat import seamuslowry.daytracker.ui.shared.ArrowPicker +import seamuslowry.daytracker.ui.shared.CalendarGrid import java.time.LocalDate -import java.time.format.TextStyle -import java.util.Locale @Composable fun ReportScreen( @@ -81,16 +57,12 @@ fun ReportScreen( onSelect = viewModel::select, modifier = Modifier.fillMaxWidth(), ) - LazyVerticalGrid( - columns = GridCells.Adaptive(minSize = 192.dp), - horizontalArrangement = Arrangement.spacedBy(16.dp), - verticalArrangement = Arrangement.spacedBy(16.dp), - contentPadding = PaddingValues(16.dp), - ) { - items(items = groupedItems.entries.toList(), key = { it.key.id }) { - DisplayDates(entry = it, onSelectDate = onSelectDate) - } - } + CalendarGrid( + groupedItems = groupedItems, + onSelectDate = onSelectDate, + minCalendarSize = 288.dp, + spacing = 16.dp, + ) } } @@ -107,130 +79,3 @@ fun DisplaySelection( } } } - -@Composable -fun DisplayDates( - entry: Map.Entry>>, - modifier: Modifier = Modifier, - onSelectDate: (d: LocalDate) -> Unit = {}, -) { - Card(modifier = modifier) { - Column( - modifier = Modifier - .animateContentSize(animationSpec = tween(300)) - .fillMaxWidth() - .padding(8.dp), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center, - ) { - Text(text = entry.key.name, style = MaterialTheme.typography.titleLarge) - Divider(modifier = Modifier.padding(4.dp), color = MaterialTheme.colorScheme.onSurfaceVariant) - Row( - modifier = Modifier - .fillMaxWidth() - .padding(vertical = 8.dp), - horizontalArrangement = Arrangement.Center, - ) { - entry.value.first().forEach { - Text( - text = it.date.dayOfWeek.getDisplayName( - TextStyle.SHORT, - Locale.getDefault(), - ), - textAlign = TextAlign.Center, - modifier = Modifier.weight(1f), - style = MaterialTheme.typography.bodySmall, - ) - } - } - - entry.value.forEach { - Row(modifier = Modifier.fillMaxWidth().aspectRatio(7f), horizontalArrangement = Arrangement.Center) { - it.forEach { - DisplayDate( - date = it, - modifier = Modifier.weight(1f), - onSelectDate = { onSelectDate(it.date) }, - ) - } - } - } - } - } -} - -@Composable -fun DisplayDate( - date: DateDisplay, - modifier: Modifier = Modifier, - onSelectDate: () -> Unit = {}, -) { - val color = when { - !date.inRange -> Color.Transparent - date.value == null || date.maxValue == null -> Color.Transparent - else -> Color(ArgbEvaluator().evaluate(date.value.toFloat().div(date.maxValue), MaterialTheme.colorScheme.error.toArgb(), MaterialTheme.colorScheme.primary.toArgb()) as Int) - } - - val textAlpha = when { - !date.inRange -> 0.5f - else -> 1f - } - - val textColor = when { - color == Color.Transparent -> MaterialTheme.colorScheme.onBackground - ColorUtils.calculateContrast(MaterialTheme.colorScheme.onPrimary.toArgb(), color.toArgb()) > 2.5f -> MaterialTheme.colorScheme.onPrimary - ColorUtils.calculateContrast(MaterialTheme.colorScheme.onError.toArgb(), color.toArgb()) > 2.5f -> MaterialTheme.colorScheme.onError - else -> MaterialTheme.colorScheme.onBackground - } - - val value = when { - date.text != null -> stringResource(date.text) - date.value != null -> date.value.toString() - else -> null - } - - val smallText = when { - date.showValue -> date.date.dayOfMonth.toString() - else -> null - } - - val largeText = when { - date.showValue -> value - else -> date.date.dayOfMonth.toString() - } - - Box( - contentAlignment = Alignment.Center, - modifier = modifier - .background(color) - .fillMaxHeight() - .clickable( - enabled = date.date <= LocalDate.now(), - onClick = onSelectDate, - ), - ) { - if (smallText != null) { - Text( - text = smallText, - color = textColor, - modifier = Modifier - .align(Alignment.TopStart) - .alpha(textAlpha) - .padding(horizontal = 4.dp), - textAlign = TextAlign.Center, - style = MaterialTheme.typography.bodySmall, - fontWeight = FontWeight.ExtraLight, - ) - } - if (largeText != null) { - Text( - text = largeText, - color = textColor, - modifier = Modifier.alpha(textAlpha), - textAlign = TextAlign.Center, - style = MaterialTheme.typography.bodyMedium, - fontWeight = FontWeight.Light, - ) - } - } -} diff --git a/app/src/main/java/seamuslowry/daytracker/ui/screens/report/ReportViewModel.kt b/app/src/main/java/seamuslowry/daytracker/ui/screens/report/ReportViewModel.kt index 10aed7e..0b10694 100644 --- a/app/src/main/java/seamuslowry/daytracker/ui/screens/report/ReportViewModel.kt +++ b/app/src/main/java/seamuslowry/daytracker/ui/screens/report/ReportViewModel.kt @@ -18,6 +18,8 @@ import seamuslowry.daytracker.data.repos.ItemRepo import seamuslowry.daytracker.data.repos.SettingsRepo import seamuslowry.daytracker.models.Item import seamuslowry.daytracker.models.ItemConfiguration +import seamuslowry.daytracker.ui.shared.DateDisplay +import seamuslowry.daytracker.ui.shared.mapToCalendarStructure import java.time.LocalDate import java.time.temporal.ChronoField import java.time.temporal.ChronoUnit @@ -63,27 +65,7 @@ class ReportViewModel @Inject constructor( val displayItems: StateFlow>>> = combine(state, items, showRecordedValues) { s, i, srv -> - val dayOfWeekField = WeekFields.of(Locale.getDefault()).dayOfWeek() - val range = s.dateRange.start.range(dayOfWeekField) - val blanksFrom = s.dateRange.start.with(dayOfWeekField, range.minimum) - val blanksTo = s.dateRange.endInclusive.with(dayOfWeekField, range.maximum) - val baseDate = DateDisplay(showValue = srv, date = s.anchorDate) - - val startingBlanks = List(ChronoUnit.DAYS.between(blanksFrom, s.dateRange.start).toInt()) { baseDate.copy(date = s.dateRange.start.minusDays(it.toLong() + 1), inRange = false) }.reversed() - val endingBlanks = List(ChronoUnit.DAYS.between(s.dateRange.endInclusive, blanksTo).toInt()) { baseDate.copy(date = s.dateRange.endInclusive.plusDays(it.toLong() + 1), inRange = false) } - - val sequence = generateSequence(s.dateRange.start) { it.plusDays(1) }.takeWhile { it <= s.dateRange.endInclusive } - - i.mapValues { entry -> - val sequenceDisplays = sequence.map { date -> - entry.value.firstOrNull { item -> item.date == date }?.let { item -> - val selection = entry.key.trackingType.options.firstOrNull { it.value == item.value } - baseDate.copy(value = item.value, text = selection?.shortText, maxValue = entry.key.trackingType.options.size, date = date) - } ?: baseDate.copy(date = date) - }.toList() - - (startingBlanks + sequenceDisplays + endingBlanks).chunked(range.maximum.toInt()) - } + mapToCalendarStructure(s.dateRange, i, srv) }.stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(TIMEOUT_MILLIS), @@ -112,15 +94,6 @@ enum class DisplayOption(@StringRes val label: Int, val field: (locale: Locale) WEEK(R.string.display_week, { WeekFields.of(it).dayOfWeek() }, ChronoUnit.WEEKS), } -data class DateDisplay( - val value: Int? = null, - @StringRes val text: Int? = null, - val maxValue: Int? = null, - val date: LocalDate, - val inRange: Boolean = true, - val showValue: Boolean = false, -) - data class ReportState( val selectedOption: DisplayOption = DisplayOption.MONTH, val anchorDate: LocalDate = LocalDate.now(), diff --git a/app/src/main/java/seamuslowry/daytracker/ui/screens/settings/SettingsScreen.kt b/app/src/main/java/seamuslowry/daytracker/ui/screens/settings/SettingsScreen.kt index 332bda4..f3fce33 100644 --- a/app/src/main/java/seamuslowry/daytracker/ui/screens/settings/SettingsScreen.kt +++ b/app/src/main/java/seamuslowry/daytracker/ui/screens/settings/SettingsScreen.kt @@ -12,6 +12,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.material3.AlertDialog import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Slider import androidx.compose.material3.Switch import androidx.compose.material3.Text import androidx.compose.material3.TextButton @@ -27,6 +28,7 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp @@ -34,8 +36,13 @@ import androidx.core.content.ContextCompat import androidx.hilt.navigation.compose.hiltViewModel import kotlinx.coroutines.launch import seamuslowry.daytracker.R +import seamuslowry.daytracker.models.ItemConfiguration import seamuslowry.daytracker.models.localeFormat +import seamuslowry.daytracker.ui.shared.CalendarGrid +import seamuslowry.daytracker.ui.shared.mapToCalendarStructure +import java.time.LocalDate import java.time.LocalTime +import java.time.temporal.ChronoField @Composable fun SettingsScreen( @@ -52,7 +59,12 @@ fun SettingsScreen( onSetReminderEnabled = { scope.launch { viewModel.setReminderEnabled(it) } }, onSetReminderTime = { scope.launch { viewModel.setReminderTime(it) } }, ) - CalendarSection(showValues = state.showRecordedValues, onSetShowValues = { scope.launch { viewModel.setShowRecordedValues(it) } }) + CalendarSection( + showValues = state.showRecordedValues, + onSetShowValues = { scope.launch { viewModel.setShowRecordedValues(it) } }, + minCalendarSize = state.minCalendarSize, + onSetMinCalendarSize = { scope.launch { viewModel.setMinCalendarSize(it) } }, + ) } } @@ -60,14 +72,45 @@ fun SettingsScreen( fun CalendarSection( showValues: Boolean, onSetShowValues: (value: Boolean) -> Unit, + minCalendarSize: Float?, + onSetMinCalendarSize: (value: Float) -> Unit, modifier: Modifier = Modifier, ) { + val calendarSizeRange = 200f..500f + + val screenWidth = LocalConfiguration.current.screenWidthDp + val columns = minCalendarSize?.let { (screenWidth / it).toInt() + 1 } ?: 1 + + val now = LocalDate.now() + val range = now.range(ChronoField.DAY_OF_MONTH) + val dateRange = now.withDayOfMonth(range.minimum.toInt())..now.withDayOfMonth(range.maximum.toInt()) + + val calendars = mapToCalendarStructure( + dateRange, + (0..columns).associate { calenderIndex -> + ItemConfiguration(id = calenderIndex.toLong(), name = calenderIndex.toString()) to listOf() // TODO transform dateRange to list of Items + }, + false, + ) + + val calendarSize = (minCalendarSize ?: 0f).coerceAtLeast(calendarSizeRange.start) + Column(modifier = modifier) { Text(text = stringResource(R.string.calendar_section_title), modifier = Modifier.padding(vertical = 8.dp), style = MaterialTheme.typography.headlineSmall) Row(modifier = Modifier.fillMaxWidth().padding(top = 4.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceBetween) { Text(text = stringResource(R.string.show_recorded_values)) Switch(checked = showValues, onCheckedChange = onSetShowValues) } + Column(modifier = Modifier.fillMaxWidth()) { + Text(text = "Minimum Calendar Size") + Slider(value = calendarSize, onValueChange = onSetMinCalendarSize, valueRange = calendarSizeRange) + CalendarGrid( + groupedItems = calendars, + onSelectDate = {}, + minCalendarSize = calendarSize.dp, + spacing = 16.dp, + ) + } } } diff --git a/app/src/main/java/seamuslowry/daytracker/ui/screens/settings/SettingsViewModel.kt b/app/src/main/java/seamuslowry/daytracker/ui/screens/settings/SettingsViewModel.kt index d3e7bd1..c564541 100644 --- a/app/src/main/java/seamuslowry/daytracker/ui/screens/settings/SettingsViewModel.kt +++ b/app/src/main/java/seamuslowry/daytracker/ui/screens/settings/SettingsViewModel.kt @@ -39,6 +39,10 @@ class SettingsViewModel @Inject constructor( settingsRepo.setShowRecordedValues(value) } + suspend fun setMinCalendarSize(value: Float) { + settingsRepo.setMinCalendarSize(value) + } + private fun scheduleReminder(time: LocalTime) { workManager.scheduleReminderWorker(time) } diff --git a/app/src/main/java/seamuslowry/daytracker/ui/shared/CalendarGrid.kt b/app/src/main/java/seamuslowry/daytracker/ui/shared/CalendarGrid.kt new file mode 100644 index 0000000..d4440f9 --- /dev/null +++ b/app/src/main/java/seamuslowry/daytracker/ui/shared/CalendarGrid.kt @@ -0,0 +1,256 @@ +package seamuslowry.daytracker.ui.shared + +import android.animation.ArgbEvaluator +import androidx.annotation.StringRes +import androidx.compose.animation.animateContentSize +import androidx.compose.animation.core.tween +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.items +import androidx.compose.material3.Card +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.core.graphics.ColorUtils +import seamuslowry.daytracker.models.Item +import seamuslowry.daytracker.models.ItemConfiguration +import java.time.LocalDate +import java.time.format.TextStyle +import java.time.temporal.ChronoUnit +import java.time.temporal.WeekFields +import java.util.Locale + +data class DateDisplay( + val value: Int? = null, + @StringRes val text: Int? = null, + val maxValue: Int? = null, + val date: LocalDate, + val inRange: Boolean = true, + val showValue: Boolean = false, +) + +@Composable +fun CalendarGrid( + groupedItems: Map>>, + onSelectDate: (d: LocalDate) -> Unit = {}, + minCalendarSize: Dp, + spacing: Dp = minCalendarSize.div(20).coerceAtLeast(5.dp), +) { + LazyVerticalGrid( + columns = GridCells.Adaptive(minSize = minCalendarSize), + horizontalArrangement = Arrangement.spacedBy(spacing), + verticalArrangement = Arrangement.spacedBy(spacing), + contentPadding = PaddingValues(spacing), + ) { + items(items = groupedItems.entries.toList(), key = { it.key.id }) { + DisplayDates(entry = it, onSelectDate = onSelectDate) + } + } +} + +@Composable +fun DisplayDates( + entry: Map.Entry>>, + modifier: Modifier = Modifier, + onSelectDate: (d: LocalDate) -> Unit = {}, +) { + Card(modifier = modifier) { + Column( + modifier = Modifier + .animateContentSize(animationSpec = tween(300)) + .fillMaxWidth() + .padding(8.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, + ) { + Text(text = entry.key.name, style = MaterialTheme.typography.titleLarge) + HorizontalDivider( + modifier = Modifier.padding(4.dp), + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) + Row( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 8.dp), + horizontalArrangement = Arrangement.Center, + ) { + entry.value.first().forEach { + Text( + text = it.date.dayOfWeek.getDisplayName( + TextStyle.SHORT, + Locale.getDefault(), + ), + textAlign = TextAlign.Center, + modifier = Modifier.weight(1f), + style = MaterialTheme.typography.bodySmall, + ) + } + } + + entry.value.forEach { + Row(modifier = Modifier.fillMaxWidth().aspectRatio(7f), horizontalArrangement = Arrangement.Center) { + it.forEach { + DisplayDate( + date = it, + modifier = Modifier.weight(1f), + onSelectDate = { onSelectDate(it.date) }, + ) + } + } + } + } + } +} + +@Composable +fun DisplayDate( + date: DateDisplay, + modifier: Modifier = Modifier, + onSelectDate: () -> Unit = {}, +) { + val color = when { + !date.inRange -> Color.Transparent + date.value == null || date.maxValue == null -> Color.Transparent + else -> Color(ArgbEvaluator().evaluate(date.value.toFloat().div(date.maxValue), MaterialTheme.colorScheme.error.toArgb(), MaterialTheme.colorScheme.primary.toArgb()) as Int) + } + + val textAlpha = when { + !date.inRange -> 0.5f + else -> 1f + } + + val textColor = when { + color == Color.Transparent -> MaterialTheme.colorScheme.onBackground + ColorUtils.calculateContrast(MaterialTheme.colorScheme.onPrimary.toArgb(), color.toArgb()) > 2.5f -> MaterialTheme.colorScheme.onPrimary + ColorUtils.calculateContrast(MaterialTheme.colorScheme.onError.toArgb(), color.toArgb()) > 2.5f -> MaterialTheme.colorScheme.onError + else -> MaterialTheme.colorScheme.onBackground + } + + val value = when { + date.text != null -> stringResource(date.text) + date.value != null -> date.value.toString() + else -> null + } + + val smallText = when { + date.showValue -> date.date.dayOfMonth.toString() + else -> null + } + + val largeText = when { + date.showValue -> value + else -> date.date.dayOfMonth.toString() + } + + Box( + modifier = modifier + .background(color) + .fillMaxHeight() + .clickable( + enabled = date.date <= LocalDate.now(), + onClick = onSelectDate, + ), + contentAlignment = Alignment.Center, + ) { + if (smallText != null) { + Text( + text = smallText, + color = textColor, + modifier = Modifier + .alpha(textAlpha) + .align(Alignment.TopStart) + .padding(3.dp), + textAlign = TextAlign.Center, + style = MaterialTheme.typography.bodySmall, + fontWeight = FontWeight.Light, + ) + } + if (largeText != null) { + Text( + text = largeText, + color = textColor, + modifier = Modifier + .alpha(textAlpha), + textAlign = TextAlign.Center, + style = MaterialTheme.typography.bodyMedium, + ) + } + } +} + +fun mapToCalendarStructure(dateRange: ClosedRange, itemsMap: Map>, showRecordedValues: Boolean): Map>> { + val dayOfWeekField = WeekFields.of(Locale.getDefault()).dayOfWeek() + val range = dateRange.start.range(dayOfWeekField) + val blanksFrom = dateRange.start.with(dayOfWeekField, range.minimum) + val blanksTo = dateRange.endInclusive.with(dayOfWeekField, range.maximum) + val baseDate = DateDisplay(showValue = showRecordedValues, date = dateRange.start) + + val startingBlanks = List(ChronoUnit.DAYS.between(blanksFrom, dateRange.start).toInt()) { baseDate.copy(date = dateRange.start.minusDays(it.toLong() + 1), inRange = false) }.reversed() + val endingBlanks = List(ChronoUnit.DAYS.between(dateRange.endInclusive, blanksTo).toInt()) { baseDate.copy(date = dateRange.endInclusive.plusDays(it.toLong() + 1), inRange = false) } + + val sequence = generateSequence(dateRange.start) { it.plusDays(1) }.takeWhile { it <= dateRange.endInclusive } + + return itemsMap.mapValues { entry -> + val sequenceDisplays = sequence.map { date -> + entry.value.firstOrNull { item -> item.date == date }?.let { item -> + val selection = entry.key.trackingType.options.firstOrNull { it.value == item.value } + baseDate.copy(value = item.value, text = selection?.shortText, maxValue = entry.key.trackingType.options.size, date = date) + } ?: baseDate.copy(date = date) + }.toList() + + (startingBlanks + sequenceDisplays + endingBlanks).chunked(range.maximum.toInt()) + } +} + +@Composable +@Preview(widthDp = 41, heightDp = 41) +fun TestDisplayDate() { + DisplayDate(date = DateDisplay(value = 10, maxValue = 10, date = LocalDate.now(), inRange = true, showValue = true)) +} + +@Composable +@Preview(widthDp = 25, heightDp = 25) +fun SmallDisplayDate() { + DisplayDate(date = DateDisplay(value = 10, maxValue = 10, date = LocalDate.now(), inRange = true, showValue = true)) +} + +@Composable +@Preview(widthDp = 29, heightDp = 29) +fun CarolineDisplayDate() { + DisplayDate(date = DateDisplay(value = 10, maxValue = 10, date = LocalDate.now().withDayOfMonth(22), inRange = true, showValue = true)) +} + +@Composable +@Preview(widthDp = 50, heightDp = 50) +fun NormalDisplayDate() { + DisplayDate(date = DateDisplay(value = 10, maxValue = 10, date = LocalDate.now(), inRange = true, showValue = true)) +} + +@Composable +@Preview(widthDp = 50, heightDp = 50) +fun NormalNoValueDisplayDate() { + DisplayDate(date = DateDisplay(value = 10, maxValue = 10, date = LocalDate.now(), inRange = true, showValue = false)) +} diff --git a/build.gradle b/build.gradle index 79170c3..4b443ae 100644 --- a/build.gradle +++ b/build.gradle @@ -1,18 +1,18 @@ buildscript { ext { - compose_bom_version = '2023.05.01' - compose_material_version = '1.2.0-alpha02' - compose_compiler_version = '1.4.5' - hilt_version = '2.44' + compose_bom_version = '2023.08.00' + compose_material_version = '1.2.0-alpha05' + compose_compiler_version = '1.5.1' + hilt_version = '2.47' hilt_ext_version = '1.0.0' - room_version = '2.5.1' + room_version = '2.5.2' } }// Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id 'com.android.application' version '8.0.2' apply false - id 'com.android.library' version '8.0.2' apply false - id 'org.jetbrains.kotlin.android' version '1.8.20' apply false + id 'com.android.application' version '8.1.1' apply false + id 'com.android.library' version '8.1.1' apply false + id 'org.jetbrains.kotlin.android' version '1.9.0' apply false id "com.diffplug.spotless" version "6.18.0" apply false - id 'com.google.dagger.hilt.android' version '2.44' apply false - id 'com.google.devtools.ksp' version "1.8.20-1.0.11" apply false + id 'com.google.dagger.hilt.android' version '2.47' apply false + id 'com.google.devtools.ksp' version "1.9.0-1.0.13" apply false } \ No newline at end of file