Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat/small dates better #44

Closed
wants to merge 17 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .idea/kotlinc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 5 additions & 5 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -100,7 +100,7 @@ dependencies {
debugImplementation "androidx.compose.ui:ui-test-manifest"
}

task printVersionName {
tasks.register('printVersionName') {
doLast {
println android.defaultConfig.versionName
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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) {
Expand All @@ -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<Settings> = 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],
)
}
}
Expand All @@ -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,
)
Original file line number Diff line number Diff line change
@@ -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(
Expand Down Expand Up @@ -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,
)
}
}

Expand All @@ -107,130 +79,3 @@ fun DisplaySelection(
}
}
}

@Composable
fun DisplayDates(
entry: Map.Entry<ItemConfiguration, List<List<DateDisplay>>>,
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,
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -63,27 +65,7 @@ class ReportViewModel @Inject constructor(

val displayItems: StateFlow<Map<ItemConfiguration, List<List<DateDisplay>>>> = 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),
Expand Down Expand Up @@ -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(),
Expand Down
Loading