Redesign Date calculator pages

This commit is contained in:
Sad Ellie 2023-11-14 17:46:59 +03:00
parent b83401e05a
commit dde5033784
4 changed files with 206 additions and 136 deletions

View File

@ -25,20 +25,23 @@ import android.content.Intent
import android.provider.CalendarContract import android.provider.CalendarContract
import androidx.activity.compose.BackHandler import androidx.activity.compose.BackHandler
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInVertically import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Event import androidx.compose.material.icons.filled.Event
@ -47,6 +50,7 @@ import androidx.compose.material.icons.outlined.Remove
import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.SegmentedButton import androidx.compose.material3.SegmentedButton
import androidx.compose.material3.SegmentedButtonDefaults import androidx.compose.material3.SegmentedButtonDefaults
@ -142,6 +146,16 @@ private fun AddSubtractView(
focusedTextFieldValue = null focusedTextFieldValue = null
} }
val showResult = remember(uiState.start, uiState.result) { uiState.start != uiState.result }
val inputWidth = animateFloatAsState(
targetValue = if (showResult) 0.5f else 1f,
label = "inputWidth"
)
val resultWidth = animateFloatAsState(
targetValue = if (showResult) 1f else 0f,
label = "resultWidth"
)
Column(Modifier.fillMaxSize()) { Column(Modifier.fillMaxSize()) {
Scaffold( Scaffold(
modifier = Modifier modifier = Modifier
@ -159,24 +173,23 @@ private fun AddSubtractView(
) )
} }
}, },
contentWindowInsets = WindowInsets(0,0,0,0) contentWindowInsets = WindowInsets(0, 0, 0, 0)
) { ) {
Column( Column(
modifier = Modifier modifier = Modifier
.verticalScroll(rememberScrollState()) .verticalScroll(rememberScrollState())
.padding(horizontal = 16.dp), .padding(horizontal = 16.dp, vertical = 16.dp),
verticalArrangement = Arrangement.spacedBy(2.dp), verticalArrangement = Arrangement.spacedBy(12.dp),
) { ) {
FlowRow( Row(
modifier = Modifier.padding(vertical = 16.dp), modifier = Modifier
maxItemsInEachRow = 2, .fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(16.dp), horizontalArrangement = Arrangement.spacedBy(8.dp)
horizontalArrangement = Arrangement.spacedBy(16.dp)
) { ) {
DateTimeSelectorBlock( DateTimeSelectorBlock(
modifier = Modifier modifier = Modifier
.weight(1f) .fillMaxWidth(inputWidth.value),
.fillMaxWidth(), containerColor = MaterialTheme.colorScheme.secondaryContainer,
title = stringResource(R.string.date_calculator_start), title = stringResource(R.string.date_calculator_start),
dateTime = uiState.start, dateTime = uiState.start,
onLongClick = { updateStart(ZonedDateTime.now()) }, onLongClick = { updateStart(ZonedDateTime.now()) },
@ -187,8 +200,8 @@ private fun AddSubtractView(
DateTimeSelectorBlock( DateTimeSelectorBlock(
modifier = Modifier modifier = Modifier
.weight(1f) .fillMaxWidth(resultWidth.value),
.fillMaxWidth(), containerColor = MaterialTheme.colorScheme.tertiaryContainer,
title = stringResource(R.string.date_calculator_end), title = stringResource(R.string.date_calculator_end),
dateTime = uiState.result, dateTime = uiState.result,
) )
@ -212,74 +225,110 @@ private fun AddSubtractView(
shape = SegmentedButtonDefaults.itemShape(index = 1, count = 2), shape = SegmentedButtonDefaults.itemShape(index = 1, count = 2),
icon = {} icon = {}
) { ) {
Icon(Icons.Outlined.Remove, stringResource(R.string.date_calculator_subtract)) Icon(
Icons.Outlined.Remove,
stringResource(R.string.date_calculator_subtract)
)
} }
} }
TimeUnitTextField( Column(
modifier = Modifier.onFocusEvent { modifier = Modifier
if (it.hasFocus) { .fillMaxWidth()
addSymbol = updateYears .background(
focusedTextFieldValue = uiState.years MaterialTheme.colorScheme.secondaryContainer,
} RoundedCornerShape(32.dp)
}, )
value = uiState.years, .padding(16.dp, 24.dp),
onValueChange = updateYears, verticalArrangement = Arrangement.spacedBy(4.dp)
label = stringResource(R.string.date_calculator_years), ) {
formatterSymbols = uiState.formatterSymbols TimeUnitTextField(
) modifier = Modifier
.onFocusEvent {
if (it.hasFocus) {
addSymbol = updateYears
focusedTextFieldValue = uiState.years
}
}
.fillMaxWidth(),
value = uiState.years,
onValueChange = updateYears,
label = stringResource(R.string.date_calculator_years),
formatterSymbols = uiState.formatterSymbols
)
TimeUnitTextField( Row(
modifier = Modifier.onFocusEvent { modifier = Modifier
if (it.hasFocus) { .fillMaxWidth(),
addSymbol = updateMonths horizontalArrangement = Arrangement.spacedBy(8.dp)
focusedTextFieldValue = uiState.months ) {
} TimeUnitTextField(
}, modifier = Modifier
value = uiState.months, .onFocusEvent {
onValueChange = updateMonths, if (it.hasFocus) {
label = stringResource(R.string.date_calculator_months), addSymbol = updateMonths
formatterSymbols = uiState.formatterSymbols focusedTextFieldValue = uiState.months
) }
}
.weight(1f),
value = uiState.months,
onValueChange = updateMonths,
label = stringResource(R.string.date_calculator_months),
formatterSymbols = uiState.formatterSymbols
)
TimeUnitTextField( TimeUnitTextField(
modifier = Modifier.onFocusEvent { modifier = Modifier
if (it.hasFocus) { .onFocusEvent {
addSymbol = updateDays if (it.hasFocus) {
focusedTextFieldValue = uiState.days addSymbol = updateDays
} focusedTextFieldValue = uiState.days
}, }
value = uiState.days, }
onValueChange = updateDays, .weight(1f),
label = stringResource(R.string.date_calculator_days), value = uiState.days,
formatterSymbols = uiState.formatterSymbols onValueChange = updateDays,
) label = stringResource(R.string.date_calculator_days),
formatterSymbols = uiState.formatterSymbols
)
}
TimeUnitTextField( Row(
modifier = Modifier.onFocusEvent { modifier = Modifier
if (it.hasFocus) { .fillMaxWidth(),
addSymbol = updateHours horizontalArrangement = Arrangement.spacedBy(8.dp)
focusedTextFieldValue = uiState.hours ) {
} TimeUnitTextField(
}, modifier = Modifier
value = uiState.hours, .onFocusEvent {
onValueChange = updateHours, if (it.hasFocus) {
label = stringResource(R.string.date_calculator_hours), addSymbol = updateHours
formatterSymbols = uiState.formatterSymbols focusedTextFieldValue = uiState.hours
) }
}
.weight(1f),
value = uiState.hours,
onValueChange = updateHours,
label = stringResource(R.string.date_calculator_hours),
formatterSymbols = uiState.formatterSymbols
)
TimeUnitTextField( TimeUnitTextField(
modifier = Modifier.onFocusEvent { modifier = Modifier
if (it.hasFocus) { .onFocusEvent {
addSymbol = updateMinutes if (it.hasFocus) {
focusedTextFieldValue = uiState.minutes addSymbol = updateMinutes
} focusedTextFieldValue = uiState.minutes
}, }
value = uiState.minutes, }
onValueChange = updateMinutes, .weight(1f),
label = stringResource(R.string.date_calculator_minutes), value = uiState.minutes,
formatterSymbols = uiState.formatterSymbols onValueChange = updateMinutes,
) label = stringResource(R.string.date_calculator_minutes),
formatterSymbols = uiState.formatterSymbols
)
}
}
} }
} }
AnimatedVisibility( AnimatedVisibility(

View File

@ -35,14 +35,17 @@ import androidx.compose.foundation.layout.width
import androidx.compose.material.ripple.rememberRipple import androidx.compose.material.ripple.rememberRipple
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.contentColorFor
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.sadellie.unitto.core.ui.LocalLocale import com.sadellie.unitto.core.ui.LocalLocale
import com.sadellie.unitto.core.ui.common.ProvideColor
import com.sadellie.unitto.core.ui.common.squashable import com.sadellie.unitto.core.ui.common.squashable
import com.sadellie.unitto.core.ui.datetime.formatDateWeekDayMonthYear import com.sadellie.unitto.core.ui.datetime.formatDateWeekDayMonthYear
import com.sadellie.unitto.core.ui.datetime.formatTimeAmPm import com.sadellie.unitto.core.ui.datetime.formatTimeAmPm
@ -52,6 +55,7 @@ import java.time.ZonedDateTime
@Composable @Composable
internal fun DateTimeSelectorBlock( internal fun DateTimeSelectorBlock(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
containerColor: Color,
title: String, title: String,
dateTime: ZonedDateTime, dateTime: ZonedDateTime,
onClick: () -> Unit = {}, onClick: () -> Unit = {},
@ -62,44 +66,35 @@ internal fun DateTimeSelectorBlock(
val locale = LocalLocale.current val locale = LocalLocale.current
val is24Hour = DateFormat.is24HourFormat(LocalContext.current) val is24Hour = DateFormat.is24HourFormat(LocalContext.current)
Column( ProvideColor(
modifier = modifier MaterialTheme.colorScheme.contentColorFor(containerColor)
.squashable(
onClick = onClick,
onLongClick = onLongClick,
interactionSource = remember { MutableInteractionSource() },
cornerRadiusRange = 8.dp..32.dp
)
.background(MaterialTheme.colorScheme.secondaryContainer)
.padding(16.dp),
horizontalAlignment = Alignment.Start
) { ) {
Text(title, style = MaterialTheme.typography.labelMedium)
Column( Column(
modifier = Modifier.clickable( modifier = Modifier
indication = rememberRipple(), .squashable(
interactionSource = remember { MutableInteractionSource() }, onClick = onClick,
onClick = onTimeClick onLongClick = onLongClick,
) interactionSource = remember { MutableInteractionSource() },
) { cornerRadiusRange = 8.dp..32.dp
AnimatedContent(
targetState = dateTime,
transitionSpec = {
slideInVertically { height -> height } + fadeIn() togetherWith
slideOutVertically { height -> -height } + fadeOut() using
SizeTransform()
},
label = "Animated time",
) { time ->
Text(
text = time.formatTimeShort(locale, is24Hour),
style = MaterialTheme.typography.displaySmall,
maxLines = 1
) )
} .background(containerColor)
.then(modifier)
.padding(16.dp),
horizontalAlignment = Alignment.Start
) {
Text(
text = title,
style = MaterialTheme.typography.labelMedium,
maxLines = 1,
)
if (!is24Hour) { Column(
modifier = Modifier.clickable(
indication = rememberRipple(),
interactionSource = remember { MutableInteractionSource() },
onClick = onTimeClick
)
) {
AnimatedContent( AnimatedContent(
targetState = dateTime, targetState = dateTime,
transitionSpec = { transitionSpec = {
@ -107,35 +102,54 @@ internal fun DateTimeSelectorBlock(
slideOutVertically { height -> -height } + fadeOut() using slideOutVertically { height -> -height } + fadeOut() using
SizeTransform() SizeTransform()
}, },
label = "Animated am/pm", label = "Animated time",
) { time -> ) { time ->
Text( Text(
text = time.formatTimeAmPm(locale), text = time.formatTimeShort(locale, is24Hour),
style = MaterialTheme.typography.bodyLarge, style = MaterialTheme.typography.displaySmall,
maxLines = 1 maxLines = 1
) )
} }
}
}
AnimatedContent( if (!is24Hour) {
targetState = dateTime, AnimatedContent(
transitionSpec = { targetState = dateTime,
slideInVertically { height -> height } + fadeIn() togetherWith transitionSpec = {
slideOutVertically { height -> -height } + fadeOut() using slideInVertically { height -> height } + fadeIn() togetherWith
SizeTransform() slideOutVertically { height -> -height } + fadeOut() using
}, SizeTransform()
label = "Animated date", },
) { date -> label = "Animated am/pm",
Text( ) { time ->
modifier = Modifier.clickable( Text(
indication = rememberRipple(), text = time.formatTimeAmPm(locale),
interactionSource = remember { MutableInteractionSource() }, style = MaterialTheme.typography.bodyLarge,
onClick = onDateClick maxLines = 1
), )
text = date.formatDateWeekDayMonthYear(locale), }
style = MaterialTheme.typography.bodySmall }
) }
AnimatedContent(
targetState = dateTime,
transitionSpec = {
slideInVertically { height -> height } + fadeIn() togetherWith
slideOutVertically { height -> -height } + fadeOut() using
SizeTransform()
},
label = "Animated date",
) { date ->
Text(
modifier = Modifier.clickable(
indication = rememberRipple(),
interactionSource = remember { MutableInteractionSource() },
onClick = onDateClick
),
text = date.formatDateWeekDayMonthYear(locale),
style = MaterialTheme.typography.bodySmall,
maxLines = 1
)
}
} }
} }
} }
@ -144,8 +158,10 @@ internal fun DateTimeSelectorBlock(
@Composable @Composable
fun DateTimeSelectorBlockPreview() { fun DateTimeSelectorBlockPreview() {
DateTimeSelectorBlock( DateTimeSelectorBlock(
modifier = Modifier
.width(224.dp),
containerColor = MaterialTheme.colorScheme.secondaryContainer,
title = "End", title = "End",
dateTime = ZonedDateTime.now(), dateTime = ZonedDateTime.now(),
modifier = Modifier.width(224.dp)
) )
} }

View File

@ -21,7 +21,6 @@ package com.sadellie.unitto.feature.datecalculator.components
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.scaleIn import androidx.compose.animation.scaleIn
import androidx.compose.animation.scaleOut import androidx.compose.animation.scaleOut
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Clear import androidx.compose.material.icons.outlined.Clear
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
@ -46,7 +45,7 @@ internal fun TimeUnitTextField(
formatterSymbols: FormatterSymbols formatterSymbols: FormatterSymbols
) = CompositionLocalProvider(LocalTextInputService provides null) { ) = CompositionLocalProvider(LocalTextInputService provides null) {
OutlinedTextField( OutlinedTextField(
modifier = modifier.fillMaxWidth(), modifier = modifier,
value = value, value = value,
onValueChange = { newValue -> onValueChange = { newValue ->
onValueChange(newValue.copy(newValue.text.filter { it.isDigit() })) onValueChange(newValue.copy(newValue.text.filter { it.isDigit() }))

View File

@ -21,12 +21,14 @@ package com.sadellie.unitto.feature.datecalculator.difference
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.expandVertically import androidx.compose.animation.expandVertically
import androidx.compose.animation.shrinkVertically import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
@ -72,11 +74,12 @@ private fun DateDifferenceView(
.fillMaxSize() .fillMaxSize()
.padding(16.dp), .padding(16.dp),
maxItemsInEachRow = 2, maxItemsInEachRow = 2,
verticalArrangement = Arrangement.spacedBy(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp),
horizontalArrangement = Arrangement.spacedBy(16.dp) horizontalArrangement = Arrangement.spacedBy(8.dp)
) { ) {
DateTimeSelectorBlock( DateTimeSelectorBlock(
modifier = Modifier modifier = Modifier
.background(MaterialTheme.colorScheme.secondaryContainer)
.weight(1f) .weight(1f)
.fillMaxWidth(), .fillMaxWidth(),
title = stringResource(R.string.date_calculator_start), title = stringResource(R.string.date_calculator_start),
@ -85,10 +88,12 @@ private fun DateDifferenceView(
onLongClick = { setStartDate(ZonedDateTime.now()) }, onLongClick = { setStartDate(ZonedDateTime.now()) },
onTimeClick = { dialogState = DialogState.FROM_TIME }, onTimeClick = { dialogState = DialogState.FROM_TIME },
onDateClick = { dialogState = DialogState.FROM_DATE }, onDateClick = { dialogState = DialogState.FROM_DATE },
containerColor = MaterialTheme.colorScheme.secondaryContainer
) )
DateTimeSelectorBlock( DateTimeSelectorBlock(
modifier = Modifier modifier = Modifier
.background(MaterialTheme.colorScheme.secondaryContainer)
.weight(1f) .weight(1f)
.fillMaxWidth(), .fillMaxWidth(),
title = stringResource(R.string.date_calculator_end), title = stringResource(R.string.date_calculator_end),
@ -97,6 +102,7 @@ private fun DateDifferenceView(
onLongClick = { setStartDate(ZonedDateTime.now()) }, onLongClick = { setStartDate(ZonedDateTime.now()) },
onTimeClick = { dialogState = DialogState.TO_TIME }, onTimeClick = { dialogState = DialogState.TO_TIME },
onDateClick = { dialogState = DialogState.TO_DATE }, onDateClick = { dialogState = DialogState.TO_DATE },
containerColor = MaterialTheme.colorScheme.secondaryContainer
) )
AnimatedVisibility( AnimatedVisibility(