Date calculator tool

Fuck LocalDateTime, now ZonedDateTime is my new homie

squashed commit
This commit is contained in:
sadellie 2023-08-01 22:55:20 +03:00
parent fbcda09c32
commit 79269dcea5
19 changed files with 898 additions and 294 deletions

View File

@ -11,6 +11,7 @@
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/Theme.Unitto"> android:theme="@style/Theme.Unitto">
<activity <activity
android:windowSoftInputMode="adjustResize"
android:name=".MainActivity" android:name=".MainActivity"
android:allowTaskReparenting="true" android:allowTaskReparenting="true"
android:exported="true"> android:exported="true">

View File

@ -1370,4 +1370,8 @@ Used in this dialog window. Should be short -->
<string name="add_time_zone_title">Add time zone</string> <string name="add_time_zone_title">Add time zone</string>
<string name="middle_zero_option">Zero in the middle</string> <string name="middle_zero_option">Zero in the middle</string>
<string name="middle_zero_option_support">Swap zero and decimal buttons</string> <string name="middle_zero_option_support">Swap zero and decimal buttons</string>
<string name="difference">Difference</string>
<string name="add">Add</string>
<string name="subtract">Subtract</string>
<string name="date_calculator">Date calculator</string>
</resources> </resources>

View File

@ -55,7 +55,7 @@ import com.sadellie.unitto.core.base.R
import java.time.Instant import java.time.Instant
import java.time.LocalDateTime import java.time.LocalDateTime
import java.time.ZoneId import java.time.ZoneId
import java.time.ZoneOffset import java.time.ZonedDateTime
import kotlin.math.max import kotlin.math.max
@Composable @Composable
@ -127,13 +127,13 @@ fun TimePickerDialog(
@Composable @Composable
fun DatePickerDialog( fun DatePickerDialog(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
localDateTime: LocalDateTime, localDateTime: ZonedDateTime,
confirmLabel: String = stringResource(R.string.ok_label), confirmLabel: String = stringResource(R.string.ok_label),
dismissLabel: String = stringResource(R.string.cancel_label), dismissLabel: String = stringResource(R.string.cancel_label),
onDismiss: () -> Unit = {}, onDismiss: () -> Unit = {},
onConfirm: (LocalDateTime) -> Unit, onConfirm: (ZonedDateTime) -> Unit,
) { ) {
val pickerState = rememberDatePickerState(localDateTime.toEpochSecond(ZoneOffset.UTC) * 1000) val pickerState = rememberDatePickerState(localDateTime.toEpochSecond() * 1000)
AlertDialog( AlertDialog(
onDismissRequest = onDismiss, onDismissRequest = onDismiss,

View File

@ -29,4 +29,6 @@ android {
dependencies { dependencies {
testImplementation(libs.junit) testImplementation(libs.junit)
implementation(project(mapOf("path" to ":data:userprefs")))
} }

View File

@ -1,234 +0,0 @@
/*
* Unitto is a unit converter for Android
* Copyright (c) 2023 Elshan Agaev
*
* This program 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 program 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.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.sadellie.unitto.feature.datedifference
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.expandVertically
import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.sadellie.unitto.core.base.R
import com.sadellie.unitto.core.ui.common.DatePickerDialog
import com.sadellie.unitto.core.ui.common.MenuButton
import com.sadellie.unitto.core.ui.common.SettingsButton
import com.sadellie.unitto.core.ui.common.TimePickerDialog
import com.sadellie.unitto.core.ui.common.UnittoScreenWithTopBar
import com.sadellie.unitto.feature.datedifference.components.DateTimeResultBlock
import com.sadellie.unitto.feature.datedifference.components.DateTimeSelectorBlock
import java.time.LocalDateTime
@Composable
internal fun DateDifferenceRoute(
viewModel: DateDifferenceViewModel = hiltViewModel(),
navigateToMenu: () -> Unit,
navigateToSettings: () -> Unit,
) {
val uiState = viewModel.uiState.collectAsStateWithLifecycle()
DateDifferenceScreen(
navigateToMenu = navigateToMenu,
navigateToSettings = navigateToSettings,
uiState = uiState.value,
setStartTime = viewModel::setStartTime,
setEndTime = viewModel::setEndTime,
setStartDate = viewModel::setStartDate,
setEndDate = viewModel::setEndDate,
)
}
@OptIn(ExperimentalLayoutApi::class)
@Composable
internal fun DateDifferenceScreen(
navigateToMenu: () -> Unit,
navigateToSettings: () -> Unit,
setStartTime: (Int, Int) -> Unit,
setEndTime: (Int, Int) -> Unit,
setStartDate: (LocalDateTime) -> Unit,
setEndDate: (LocalDateTime) -> Unit,
uiState: UIState,
) {
var dialogState by remember { mutableStateOf(DialogState.NONE) }
UnittoScreenWithTopBar(
title = { Text(stringResource(R.string.date_difference)) },
navigationIcon = { MenuButton(navigateToMenu) },
actions = {
SettingsButton(navigateToSettings)
}
) { paddingValues ->
FlowRow(
modifier = Modifier
.padding(paddingValues)
.padding(horizontal = 16.dp),
maxItemsInEachRow = 2,
verticalArrangement = Arrangement.spacedBy(16.dp),
horizontalArrangement = Arrangement.spacedBy(16.dp)
) {
DateTimeSelectorBlock(
modifier = Modifier
.weight(1f)
.fillMaxWidth(),
title = stringResource(R.string.date_difference_start),
onClick = { dialogState = DialogState.FROM },
onTimeClick = { dialogState = DialogState.FROM_TIME },
onDateClick = { dialogState = DialogState.FROM_DATE },
dateTime = uiState.start
)
DateTimeSelectorBlock(
modifier = Modifier
.weight(1f)
.fillMaxWidth(),
title = stringResource(R.string.date_difference_end),
onClick = { dialogState = DialogState.TO },
onTimeClick = { dialogState = DialogState.TO_TIME },
onDateClick = { dialogState = DialogState.TO_DATE },
dateTime = uiState.end
)
AnimatedVisibility(
visible = uiState.result is DateDifference.Default,
enter = expandVertically(),
exit = shrinkVertically()
) {
DateTimeResultBlock(
modifier = Modifier
.weight(2f)
.fillMaxWidth(),
dateDifference = uiState.result
)
}
}
}
fun resetDialog() {
dialogState = DialogState.NONE
}
when (dialogState) {
DialogState.FROM -> {
TimePickerDialog(
hour = uiState.start.hour,
minute = uiState.start.minute,
onDismiss = ::resetDialog,
onConfirm = { hour, minute ->
setStartTime(hour, minute)
dialogState = DialogState.FROM_DATE
},
confirmLabel = stringResource(R.string.next_label),
)
}
DialogState.FROM_TIME -> {
TimePickerDialog(
hour = uiState.start.hour,
minute = uiState.start.minute,
onDismiss = ::resetDialog,
onConfirm = { hour, minute ->
setStartTime(hour, minute)
resetDialog()
},
)
}
DialogState.FROM_DATE -> {
DatePickerDialog(
localDateTime = uiState.start,
onDismiss = ::resetDialog,
onConfirm = {
setStartDate(it)
resetDialog()
}
)
}
DialogState.TO -> {
TimePickerDialog(
hour = uiState.end.hour,
minute = uiState.end.minute,
onDismiss = ::resetDialog,
onConfirm = { hour, minute ->
setEndTime(hour, minute)
dialogState = DialogState.TO_DATE
},
confirmLabel = stringResource(R.string.next_label),
)
}
DialogState.TO_TIME -> {
TimePickerDialog(
hour = uiState.end.hour,
minute = uiState.end.minute,
onDismiss = ::resetDialog,
onConfirm = { hour, minute ->
setEndTime(hour, minute)
resetDialog()
},
)
}
DialogState.TO_DATE -> {
DatePickerDialog(
localDateTime = uiState.end,
onDismiss = ::resetDialog,
onConfirm = {
setEndDate(it)
resetDialog()
}
)
}
else -> {}
}
}
private enum class DialogState {
NONE, FROM, FROM_TIME, FROM_DATE, TO, TO_TIME, TO_DATE
}
@Preview
@Composable
private fun DateDifferenceScreenPreview() {
DateDifferenceScreen(
navigateToMenu = {},
navigateToSettings = {},
setStartTime = { _, _ -> },
setEndTime = { _, _ -> },
uiState = UIState(
result = DateDifference.Default(4, 1, 2, 3, 4)
),
setStartDate = {},
setEndDate = {},
)
}

View File

@ -0,0 +1,117 @@
/*
* Unitto is a unit converter for Android
* Copyright (c) 2023 Elshan Agaev
*
* This program 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 program 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.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.sadellie.unitto.feature.datedifference
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.material3.Tab
import androidx.compose.material3.TabRow
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import com.sadellie.unitto.core.base.R
import com.sadellie.unitto.core.ui.common.MenuButton
import com.sadellie.unitto.core.ui.common.SettingsButton
import com.sadellie.unitto.core.ui.common.UnittoScreenWithTopBar
import com.sadellie.unitto.feature.datedifference.addsubtract.AddSubtractPage
import com.sadellie.unitto.feature.datedifference.difference.DateDifferencePage
import kotlinx.coroutines.launch
@Composable
internal fun DateToolsRoute(
navigateToMenu: () -> Unit,
navigateToSettings: () -> Unit,
) {
DateToolsScreen(
navigateToMenu = navigateToMenu,
navigateToSettings = navigateToSettings,
)
}
@Composable
internal fun DateToolsScreen(
navigateToMenu: () -> Unit,
navigateToSettings: () -> Unit,
) {
val addSubtractLabel = "${stringResource(R.string.add)}/${stringResource(R.string.subtract)}"
val differenceLabel = stringResource(R.string.difference)
val allTabs = remember { mutableListOf(addSubtractLabel, differenceLabel) }
val pagerState = rememberPagerState { allTabs.size }
val coroutineScope = rememberCoroutineScope()
UnittoScreenWithTopBar(
modifier = Modifier
.navigationBarsPadding()
.imePadding(),
title = { Text(stringResource(R.string.date_calculator)) },
navigationIcon = { MenuButton(navigateToMenu) },
actions = {
SettingsButton(navigateToSettings)
},
) { paddingValues ->
Column(
modifier = Modifier.padding(paddingValues),
) {
TabRow(
selectedTabIndex = pagerState.currentPage,
) {
allTabs.forEachIndexed { index, tab ->
Tab(
selected = index == pagerState.currentPage,
onClick = {
coroutineScope.launch {
pagerState.animateScrollToPage(index)
}
},
text = { Text(tab) }
)
}
}
HorizontalPager(
state = pagerState,
verticalAlignment = Alignment.Top
) { page ->
when (page) {
0 -> AddSubtractPage()
1 -> DateDifferencePage()
}
}
}
}
}
@Preview
@Composable
private fun DateDifferenceScreenPreview() {
DateToolsScreen(
navigateToMenu = {},
navigateToSettings = {},
)
}

View File

@ -18,10 +18,10 @@
package com.sadellie.unitto.feature.datedifference package com.sadellie.unitto.feature.datedifference
import java.time.LocalDateTime import java.time.ZonedDateTime
import java.time.temporal.ChronoUnit import java.time.temporal.ChronoUnit
internal sealed class DateDifference( internal sealed class ZonedDateTimeDifference(
open val years: Long = 0, open val years: Long = 0,
open val months: Long = 0, open val months: Long = 0,
open val days: Long = 0, open val days: Long = 0,
@ -34,7 +34,7 @@ internal sealed class DateDifference(
override val days: Long = 0, override val days: Long = 0,
override val hours: Long = 0, override val hours: Long = 0,
override val minutes: Long = 0, override val minutes: Long = 0,
) : DateDifference( ) : ZonedDateTimeDifference(
years = years, years = years,
months = months, months = months,
days = days, days = days,
@ -42,15 +42,15 @@ internal sealed class DateDifference(
minutes = minutes, minutes = minutes,
) )
object Zero : DateDifference() object Zero : ZonedDateTimeDifference()
} }
// https://stackoverflow.com/a/25760725 // https://stackoverflow.com/a/25760725
internal infix operator fun LocalDateTime.minus(localDateTime: LocalDateTime): DateDifference { internal infix operator fun ZonedDateTime.minus(localDateTime: ZonedDateTime): ZonedDateTimeDifference {
if (this == localDateTime) return DateDifference.Zero if (this == localDateTime) return ZonedDateTimeDifference.Zero
var fromDateTime: LocalDateTime = this var fromDateTime: ZonedDateTime = this
var toDateTime: LocalDateTime = localDateTime var toDateTime: ZonedDateTime = localDateTime
// Swap to avoid negative // Swap to avoid negative
if (this > localDateTime) { if (this > localDateTime) {
@ -58,7 +58,7 @@ internal infix operator fun LocalDateTime.minus(localDateTime: LocalDateTime): D
toDateTime = this toDateTime = this
} }
var tempDateTime = LocalDateTime.from(fromDateTime) var tempDateTime = ZonedDateTime.from(fromDateTime)
val years = tempDateTime.until(toDateTime, ChronoUnit.YEARS) val years = tempDateTime.until(toDateTime, ChronoUnit.YEARS)
@ -74,9 +74,9 @@ internal infix operator fun LocalDateTime.minus(localDateTime: LocalDateTime): D
tempDateTime = tempDateTime.plusHours(hours) tempDateTime = tempDateTime.plusHours(hours)
val minutes = tempDateTime.until(toDateTime, ChronoUnit.MINUTES) val minutes = tempDateTime.until(toDateTime, ChronoUnit.MINUTES)
if (listOf(years, months, days, hours, minutes).sum() == 0L) return DateDifference.Zero if (listOf(years, months, days, hours, minutes).sum() == 0L) return ZonedDateTimeDifference.Zero
return DateDifference.Default( return ZonedDateTimeDifference.Default(
years = years, months = months, days = days, hours = hours, minutes = minutes years = years, months = months, days = days, hours = hours, minutes = minutes
) )
} }

View File

@ -0,0 +1,241 @@
/*
* Unitto is a unit converter for Android
* Copyright (c) 2023 Elshan Agaev
*
* This program 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 program 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.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.sadellie.unitto.feature.datedifference.addsubtract
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.provider.CalendarContract
import androidx.compose.foundation.layout.Arrangement
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.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Event
import androidx.compose.material.icons.outlined.Add
import androidx.compose.material.icons.outlined.Remove
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SegmentedButton
import androidx.compose.material3.SegmentedButtonDefaults
import androidx.compose.material3.SingleChoiceSegmentedButtonRow
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.sadellie.unitto.core.base.R
import com.sadellie.unitto.feature.datedifference.components.DateTimeDialogs
import com.sadellie.unitto.feature.datedifference.components.DateTimeSelectorBlock
import com.sadellie.unitto.feature.datedifference.components.DialogState
import com.sadellie.unitto.feature.datedifference.components.TimeUnitTextField
import java.time.ZonedDateTime
@Composable
internal fun AddSubtractPage(
viewModel: AddSubtractViewModel = hiltViewModel(),
) {
val uiState = viewModel.uiState.collectAsStateWithLifecycle().value
AddSubtractView(
uiState = uiState,
updateStart = viewModel::updateStart,
updateYears = viewModel::updateYears,
updateMonths = viewModel::updateMonths,
updateDays = viewModel::updateDays,
updateHours = viewModel::updateHours,
updateMinutes = viewModel::updateMinutes,
updateAddition = viewModel::updateAddition
)
}
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
@OptIn(ExperimentalLayoutApi::class)
@Composable
private fun AddSubtractView(
uiState: AddSubtractState,
updateStart: (ZonedDateTime) -> Unit,
updateYears: (String) -> Unit,
updateMonths: (String) -> Unit,
updateDays: (String) -> Unit,
updateHours: (String) -> Unit,
updateMinutes: (String) -> Unit,
updateAddition: (Boolean) -> Unit,
) {
var dialogState by remember { mutableStateOf(DialogState.NONE) }
val mContext = LocalContext.current
Scaffold(
floatingActionButton = {
FloatingActionButton(onClick = { mContext.addEvent(uiState.start, uiState.result) }) {
Icon(
imageVector = Icons.Default.Event,
contentDescription = null,
)
}
}
) {
LazyColumn(
modifier = Modifier.padding(start = 16.dp, end = 16.dp, top = 16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp),
contentPadding = PaddingValues(bottom = 88.dp)
) {
item("dates") {
FlowRow(
modifier = Modifier,
maxItemsInEachRow = 2,
verticalArrangement = Arrangement.spacedBy(16.dp),
horizontalArrangement = Arrangement.spacedBy(16.dp)
) {
DateTimeSelectorBlock(
modifier = Modifier
.weight(1f)
.fillMaxWidth(),
title = stringResource(R.string.date_difference_start),
dateTime = uiState.start,
onLongClick = { updateStart(ZonedDateTime.now()) },
onClick = { dialogState = DialogState.FROM },
onTimeClick = { dialogState = DialogState.FROM_TIME },
onDateClick = { dialogState = DialogState.FROM_DATE },
)
DateTimeSelectorBlock(
modifier = Modifier
.weight(1f)
.fillMaxWidth(),
title = stringResource(R.string.date_difference_end),
dateTime = uiState.result,
)
}
}
item("modes") {
SingleChoiceSegmentedButtonRow(
modifier = Modifier
.fillMaxWidth(),
) {
SegmentedButton(
selected = uiState.addition,
onClick = { updateAddition(true) },
shape = SegmentedButtonDefaults.shape(position = 0, count = 2),
icon = {}
) {
Icon(Icons.Outlined.Add, null)
}
SegmentedButton(
selected = !uiState.addition,
onClick = { updateAddition(false) },
shape = SegmentedButtonDefaults.shape(position = 1, count = 2),
icon = {}
) {
Icon(Icons.Outlined.Remove, null)
}
}
}
item("textFields") {
Column {
TimeUnitTextField(
value = uiState.years,
onValueChange = updateYears,
label = stringResource(R.string.date_difference_years),
formatterSymbols = uiState.formatterSymbols
)
TimeUnitTextField(
value = uiState.months,
onValueChange = updateMonths,
label = stringResource(R.string.date_difference_months),
formatterSymbols = uiState.formatterSymbols
)
TimeUnitTextField(
value = uiState.days,
onValueChange = updateDays,
label = stringResource(R.string.date_difference_days),
formatterSymbols = uiState.formatterSymbols
)
TimeUnitTextField(
value = uiState.hours,
onValueChange = updateHours,
label = stringResource(R.string.date_difference_hours),
formatterSymbols = uiState.formatterSymbols
)
TimeUnitTextField(
value = uiState.minutes,
onValueChange = updateMinutes,
label = stringResource(R.string.date_difference_minutes),
imeAction = ImeAction.Done,
formatterSymbols = uiState.formatterSymbols
)
}
}
}
}
DateTimeDialogs(
dialogState = dialogState,
updateDialogState = { dialogState = it },
date = uiState.start,
updateDate = updateStart,
bothState = DialogState.FROM,
timeState = DialogState.FROM_TIME,
dateState = DialogState.FROM_DATE,
)
}
private fun Context.addEvent(start: ZonedDateTime, end: ZonedDateTime) {
val startMillis: Long = start.toEpochSecond() * 1000
val endMillis: Long = end.toEpochSecond() * 1000
val intent = Intent(Intent.ACTION_INSERT)
.setData(CalendarContract.Events.CONTENT_URI)
.putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, startMillis)
.putExtra(CalendarContract.EXTRA_EVENT_END_TIME, endMillis)
.putExtra(CalendarContract.Events.AVAILABILITY, CalendarContract.Events.AVAILABILITY_BUSY)
startActivity(intent)
}
@Preview
@Composable
fun AddSubtractViewPreview() {
AddSubtractView(
uiState = AddSubtractState(
years = "12"
),
updateStart = {},
updateYears = {},
updateMonths = {},
updateDays = {},
updateHours = {},
updateMinutes = {},
updateAddition = {}
)
}

View File

@ -0,0 +1,34 @@
/*
* Unitto is a unit converter for Android
* Copyright (c) 2023 Elshan Agaev
*
* This program 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 program 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.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.sadellie.unitto.feature.datedifference.addsubtract
import com.sadellie.unitto.core.ui.common.textfield.FormatterSymbols
import java.time.ZonedDateTime
internal data class AddSubtractState(
val start: ZonedDateTime = ZonedDateTime.now(),
val result: ZonedDateTime = ZonedDateTime.now(),
val years: String = "",
val months: String = "",
val days: String = "",
val hours: String = "",
val minutes: String = "",
val addition: Boolean = true,
val formatterSymbols: FormatterSymbols = FormatterSymbols.Spaces,
)

View File

@ -0,0 +1,129 @@
/*
* Unitto is a unit converter for Android
* Copyright (c) 2023 Elshan Agaev
*
* This program 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 program 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.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.sadellie.unitto.feature.datedifference.addsubtract
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.sadellie.unitto.core.ui.common.textfield.AllFormatterSymbols
import com.sadellie.unitto.data.userprefs.UserPreferencesRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import java.time.ZonedDateTime
import javax.inject.Inject
@HiltViewModel
internal class AddSubtractViewModel @Inject constructor(
userPreferencesRepository: UserPreferencesRepository
) : ViewModel() {
private val _uiState = MutableStateFlow(AddSubtractState())
val uiState: StateFlow<AddSubtractState> = _uiState
.combine(userPreferencesRepository.allPreferencesFlow) { uiState, userPrefs ->
return@combine uiState.copy(
formatterSymbols = AllFormatterSymbols.getById(userPrefs.separator)
)
}
.onEach { updateResult() }
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000L), AddSubtractState())
private fun updateResult() = viewModelScope.launch(Dispatchers.Default) {
// Gets canceled, works with latest _uiState only
_uiState.update { ui ->
val newResult = if (ui.addition) {
ui.start
.plusYears(ui.years.ifEmpty { "0" }.toLong())
.plusMonths(ui.months.ifEmpty { "0" }.toLong())
.plusDays(ui.days.ifEmpty { "0" }.toLong())
.plusHours(ui.hours.ifEmpty { "0" }.toLong())
.plusMinutes(ui.minutes.ifEmpty { "0" }.toLong())
} else {
ui.start
.minusYears(ui.years.ifEmpty { "0" }.toLong())
.minusMonths(ui.months.ifEmpty { "0" }.toLong())
.minusDays(ui.days.ifEmpty { "0" }.toLong())
.minusHours(ui.hours.ifEmpty { "0" }.toLong())
.minusMinutes(ui.minutes.ifEmpty { "0" }.toLong())
}
ui.copy(result = newResult)
}
}
fun updateStart(newValue: ZonedDateTime) = _uiState.update { it.copy(start = newValue) }
fun updateYears(newValue: String) = _uiState.update {
val years = when {
newValue.isEmpty() -> newValue
newValue.toLong() > 9_999L -> "9999"
else -> newValue
}
it.copy(years = years)
}
fun updateMonths(newValue: String) = _uiState.update {
val months = when {
newValue.isEmpty() -> newValue
newValue.toLong() > 9_999L -> "9999"
else -> newValue
}
it.copy(months = months)
}
fun updateDays(newValue: String) = _uiState.update {
val days = when {
newValue.isEmpty() -> newValue
newValue.toLong() > 99_999L -> "99999"
else -> newValue
}
it.copy(days = days)
}
fun updateHours(newValue: String) = _uiState.update {
val hours = when {
newValue.isEmpty() -> newValue
newValue.toLong() > 9_999_999L -> "9999999"
else -> newValue
}
it.copy(hours = hours)
}
fun updateMinutes(newValue: String) = _uiState.update {
val minutes = when {
newValue.isEmpty() -> newValue
newValue.toLong() > 99_999_999L -> "99999999"
else -> newValue
}
it.copy(minutes = minutes)
}
// BCE is not handled properly because who gives a shit...
fun updateAddition(newValue: Boolean) = _uiState.update { it.copy(addition = newValue) }
}

View File

@ -0,0 +1,79 @@
/*
* Unitto is a unit converter for Android
* Copyright (c) 2023 Elshan Agaev
*
* This program 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 program 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.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.sadellie.unitto.feature.datedifference.components
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import com.sadellie.unitto.core.base.R
import com.sadellie.unitto.core.ui.common.DatePickerDialog
import com.sadellie.unitto.core.ui.common.TimePickerDialog
import java.time.ZonedDateTime
@Composable
internal fun DateTimeDialogs(
dialogState: DialogState?,
updateDialogState: (DialogState) -> Unit,
date: ZonedDateTime,
updateDate: (ZonedDateTime) -> Unit,
bothState: DialogState,
timeState: DialogState,
dateState: DialogState,
) {
when (dialogState) {
bothState -> {
TimePickerDialog(
hour = date.hour,
minute = date.minute,
onDismiss = { updateDialogState(DialogState.NONE) },
onConfirm = { hour, minute ->
updateDate(date.withHour(hour).withMinute(minute))
updateDialogState(dateState)
},
confirmLabel = stringResource(R.string.next_label),
)
}
timeState -> {
TimePickerDialog(
hour = date.hour,
minute = date.minute,
onDismiss = { updateDialogState(DialogState.NONE) },
onConfirm = { hour, minute ->
updateDate(date.withHour(hour).withMinute(minute))
updateDialogState(DialogState.NONE)
},
)
}
dateState -> {
DatePickerDialog(
localDateTime = date,
onDismiss = { updateDialogState(DialogState.NONE) },
onConfirm = {
updateDate(it)
updateDialogState(DialogState.NONE)
}
)
}
else -> {}
}
}
internal enum class DialogState { NONE, FROM, FROM_TIME, FROM_DATE, TO, TO_TIME, TO_DATE }

View File

@ -47,20 +47,20 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.sadellie.unitto.core.base.R import com.sadellie.unitto.core.base.R
import com.sadellie.unitto.core.ui.common.squashable import com.sadellie.unitto.core.ui.common.squashable
import com.sadellie.unitto.feature.datedifference.DateDifference import com.sadellie.unitto.feature.datedifference.ZonedDateTimeDifference
@Composable @Composable
internal fun DateTimeResultBlock( internal fun DateTimeResultBlock(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
dateDifference: DateDifference zonedDateTimeDifference: ZonedDateTimeDifference
) { ) {
val clipboardManager = LocalClipboardManager.current val clipboardManager = LocalClipboardManager.current
val years = dateDifference.years.formatDateTimeValue(R.string.date_difference_years) val years = zonedDateTimeDifference.years.formatDateTimeValue(R.string.date_difference_years)
val months = dateDifference.months.formatDateTimeValue(R.string.date_difference_months) val months = zonedDateTimeDifference.months.formatDateTimeValue(R.string.date_difference_months)
val days = dateDifference.days.formatDateTimeValue(R.string.date_difference_days) val days = zonedDateTimeDifference.days.formatDateTimeValue(R.string.date_difference_days)
val hours = dateDifference.hours.formatDateTimeValue(R.string.date_difference_hours) val hours = zonedDateTimeDifference.hours.formatDateTimeValue(R.string.date_difference_hours)
val minutes = dateDifference.minutes.formatDateTimeValue(R.string.date_difference_minutes) val minutes = zonedDateTimeDifference.minutes.formatDateTimeValue(R.string.date_difference_minutes)
val texts = listOf(years, months, days, hours, minutes) val texts = listOf(years, months, days, hours, minutes)
@ -117,10 +117,10 @@ private fun Long.formatDateTimeValue(@StringRes id: Int): String {
@Preview @Preview
@Composable @Composable
private fun PreviewCard() { private fun DateTimeResultBlockPreview() {
DateTimeResultBlock( DateTimeResultBlock(
modifier = Modifier, modifier = Modifier,
dateDifference = DateDifference.Default( zonedDateTimeDifference = ZonedDateTimeDifference.Default(
months = 1, months = 1,
days = 2, days = 2,
hours = 3, hours = 3,

View File

@ -24,6 +24,7 @@ import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
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
@ -32,24 +33,27 @@ 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.platform.LocalContext import androidx.compose.ui.platform.LocalContext
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.common.squashable import com.sadellie.unitto.core.ui.common.squashable
import java.time.LocalDateTime import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter
@Composable @Composable
internal fun DateTimeSelectorBlock( internal fun DateTimeSelectorBlock(
modifier: Modifier, modifier: Modifier = Modifier,
title: String, title: String,
dateTime: LocalDateTime, dateTime: ZonedDateTime,
onClick: () -> Unit, onClick: () -> Unit = {},
onTimeClick: () -> Unit, onTimeClick: () -> Unit = {},
onDateClick: () -> Unit, onDateClick: () -> Unit = {},
onLongClick: () -> Unit = {},
) { ) {
Column( Column(
modifier = modifier modifier = modifier
.squashable( .squashable(
onClick = onClick, onClick = onClick,
onLongClick = onLongClick,
interactionSource = remember { MutableInteractionSource() }, interactionSource = remember { MutableInteractionSource() },
cornerRadiusRange = 8.dp..32.dp cornerRadiusRange = 8.dp..32.dp
) )
@ -107,3 +111,13 @@ private val time24Formatter by lazy { DateTimeFormatter.ofPattern("HH:mm") }
private val time12Formatter by lazy { DateTimeFormatter.ofPattern("hh:mm") } private val time12Formatter by lazy { DateTimeFormatter.ofPattern("hh:mm") }
private val dateFormatter by lazy { DateTimeFormatter.ofPattern("EEE, MMM d, y") } private val dateFormatter by lazy { DateTimeFormatter.ofPattern("EEE, MMM d, y") }
private val mTimeFormatter by lazy { DateTimeFormatter.ofPattern("a") } private val mTimeFormatter by lazy { DateTimeFormatter.ofPattern("a") }
@Preview
@Composable
fun DateTimeSelectorBlockPreview() {
DateTimeSelectorBlock(
title = "End",
dateTime = ZonedDateTime.now(),
modifier = Modifier.width(224.dp)
)
}

View File

@ -0,0 +1,73 @@
/*
* Unitto is a unit converter for Android
* Copyright (c) 2023 Elshan Agaev
*
* This program 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 program 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.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.sadellie.unitto.feature.datedifference.components
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.scaleIn
import androidx.compose.animation.scaleOut
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Clear
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import com.sadellie.unitto.core.ui.common.textfield.ExpressionTransformer
import com.sadellie.unitto.core.ui.common.textfield.FormatterSymbols
@Composable
internal fun TimeUnitTextField(
value: String,
onValueChange: (String) -> Unit,
label: String,
imeAction: ImeAction = ImeAction.Next,
formatterSymbols: FormatterSymbols
) {
OutlinedTextField(
modifier = Modifier.fillMaxWidth(),
value = value,
onValueChange = { newValue ->
onValueChange(newValue.filter { it.isDigit() })
},
label = { Text(label, color = MaterialTheme.colorScheme.onSurfaceVariant) },
trailingIcon = {
AnimatedVisibility(
visible = value.isNotBlank(),
enter = scaleIn(),
exit = scaleOut()
) {
IconButton(onClick = { onValueChange("") }) {
Icon(Icons.Outlined.Clear, null)
}
}
},
keyboardOptions = KeyboardOptions(
autoCorrect = false,
keyboardType = KeyboardType.Decimal,
imeAction = imeAction
),
visualTransformation = ExpressionTransformer(formatterSymbols)
)
}

View File

@ -0,0 +1,145 @@
/*
* Unitto is a unit converter for Android
* Copyright (c) 2023 Elshan Agaev
*
* This program 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 program 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.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.sadellie.unitto.feature.datedifference.difference
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.expandVertically
import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.sadellie.unitto.core.base.R
import com.sadellie.unitto.feature.datedifference.ZonedDateTimeDifference
import com.sadellie.unitto.feature.datedifference.components.DateTimeDialogs
import com.sadellie.unitto.feature.datedifference.components.DateTimeResultBlock
import com.sadellie.unitto.feature.datedifference.components.DateTimeSelectorBlock
import com.sadellie.unitto.feature.datedifference.components.DialogState
import java.time.ZonedDateTime
@Composable
internal fun DateDifferencePage(
viewModel: DateDifferenceViewModel = hiltViewModel(),
) {
val uiState = viewModel.uiState.collectAsStateWithLifecycle()
DateDifferenceView(
uiState = uiState.value,
setStartDate = viewModel::setStartDate,
setEndDate = viewModel::setEndDate
)
}
@Composable
@OptIn(ExperimentalLayoutApi::class)
private fun DateDifferenceView(
uiState: DifferenceUIState,
setStartDate: (ZonedDateTime) -> Unit,
setEndDate: (ZonedDateTime) -> Unit,
) {
var dialogState by remember { mutableStateOf(DialogState.NONE) }
FlowRow(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
maxItemsInEachRow = 2,
verticalArrangement = Arrangement.spacedBy(16.dp),
horizontalArrangement = Arrangement.spacedBy(16.dp)
) {
DateTimeSelectorBlock(
modifier = Modifier
.weight(1f)
.fillMaxWidth(),
title = stringResource(R.string.date_difference_start),
dateTime = uiState.start,
onClick = { dialogState = DialogState.FROM },
onTimeClick = { dialogState = DialogState.FROM_TIME },
onDateClick = { dialogState = DialogState.FROM_DATE },
)
DateTimeSelectorBlock(
modifier = Modifier
.weight(1f)
.fillMaxWidth(),
title = stringResource(R.string.date_difference_end),
dateTime = uiState.end,
onClick = { dialogState = DialogState.TO },
onTimeClick = { dialogState = DialogState.TO_TIME },
onDateClick = { dialogState = DialogState.TO_DATE },
)
AnimatedVisibility(
visible = uiState.result is ZonedDateTimeDifference.Default,
enter = expandVertically(),
exit = shrinkVertically()
) {
DateTimeResultBlock(
modifier = Modifier
.weight(2f)
.fillMaxWidth(),
zonedDateTimeDifference = uiState.result,
)
}
}
DateTimeDialogs(
dialogState = dialogState,
updateDialogState = { dialogState = it },
date = uiState.start,
updateDate = setStartDate,
bothState = DialogState.FROM,
timeState = DialogState.FROM_TIME,
dateState = DialogState.FROM_DATE,
)
DateTimeDialogs(
dialogState = dialogState,
updateDialogState = { dialogState = it },
date = uiState.end,
updateDate = setEndDate,
bothState = DialogState.TO,
timeState = DialogState.TO_TIME,
dateState = DialogState.TO_DATE,
)
}
@Preview
@Composable
fun DateDifferenceViewPreview() {
DateDifferenceView(
uiState = DifferenceUIState(),
setStartDate = {},
setEndDate = {},
)
}

View File

@ -16,10 +16,12 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.sadellie.unitto.feature.datedifference package com.sadellie.unitto.feature.datedifference.difference
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.sadellie.unitto.feature.datedifference.ZonedDateTimeDifference
import com.sadellie.unitto.feature.datedifference.minus
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
@ -30,29 +32,25 @@ import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.time.LocalDateTime import java.time.ZonedDateTime
import javax.inject.Inject import javax.inject.Inject
@HiltViewModel @HiltViewModel
internal class DateDifferenceViewModel @Inject constructor() : ViewModel() { internal class DateDifferenceViewModel @Inject constructor() : ViewModel() {
private val _start = MutableStateFlow(LocalDateTime.now()) private val _start = MutableStateFlow(ZonedDateTime.now())
private val _end = MutableStateFlow(LocalDateTime.now()) private val _end = MutableStateFlow(ZonedDateTime.now())
private val _result = MutableStateFlow<DateDifference>(DateDifference.Zero) private val _result = MutableStateFlow<ZonedDateTimeDifference>(ZonedDateTimeDifference.Zero)
val uiState = combine(_start, _end, _result) { start, end, result -> val uiState = combine(_start, _end, _result) { start, end, result ->
return@combine UIState(start, end, result) return@combine DifferenceUIState(start, end, result)
} }
.stateIn( .stateIn(
viewModelScope, SharingStarted.WhileSubscribed(5000L), UIState() viewModelScope, SharingStarted.WhileSubscribed(5000L), DifferenceUIState()
) )
fun setStartTime(hour: Int, minute: Int) = _start.update { it.withHour(hour).withMinute(minute) } fun setStartDate(dateTime: ZonedDateTime) = _start.update { dateTime }
fun setEndTime(hour: Int, minute: Int) = _end.update { it.withHour(hour).withMinute(minute) } fun setEndDate(dateTime: ZonedDateTime) = _end.update { dateTime }
fun setStartDate(dateTime: LocalDateTime) = _start.update { dateTime }
fun setEndDate(dateTime: LocalDateTime) = _end.update { dateTime }
init { init {
viewModelScope.launch(Dispatchers.Default) { viewModelScope.launch(Dispatchers.Default) {

View File

@ -16,12 +16,13 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.sadellie.unitto.feature.datedifference package com.sadellie.unitto.feature.datedifference.difference
import java.time.LocalDateTime import com.sadellie.unitto.feature.datedifference.ZonedDateTimeDifference
import java.time.ZonedDateTime
internal data class UIState( internal data class DifferenceUIState(
val start: LocalDateTime = LocalDateTime.now(), val start: ZonedDateTime = ZonedDateTime.now(),
val end: LocalDateTime = LocalDateTime.now(), val end: ZonedDateTime = ZonedDateTime.now(),
val result: DateDifference = DateDifference.Zero val result: ZonedDateTimeDifference = ZonedDateTimeDifference.Zero
) )

View File

@ -22,7 +22,7 @@ import androidx.navigation.NavGraphBuilder
import androidx.navigation.compose.composable import androidx.navigation.compose.composable
import androidx.navigation.navDeepLink import androidx.navigation.navDeepLink
import com.sadellie.unitto.core.base.TopLevelDestinations import com.sadellie.unitto.core.base.TopLevelDestinations
import com.sadellie.unitto.feature.datedifference.DateDifferenceRoute import com.sadellie.unitto.feature.datedifference.DateToolsRoute
private val dateDifferenceRoute: String by lazy { TopLevelDestinations.DateDifference.route } private val dateDifferenceRoute: String by lazy { TopLevelDestinations.DateDifference.route }
@ -36,7 +36,7 @@ fun NavGraphBuilder.dateDifferenceScreen(
navDeepLink { uriPattern = "app://com.sadellie.unitto/$dateDifferenceRoute" } navDeepLink { uriPattern = "app://com.sadellie.unitto/$dateDifferenceRoute" }
) )
) { ) {
DateDifferenceRoute( DateToolsRoute(
navigateToMenu = navigateToMenu, navigateToMenu = navigateToMenu,
navigateToSettings = navigateToSettings navigateToSettings = navigateToSettings
) )

View File

@ -20,32 +20,32 @@ package com.sadellie.unitto.feature.datedifference
import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import java.time.LocalDateTime import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter
class DateDifferenceKtTest { class ZonedDateTimeDifferenceKtTest {
private val fromatt: DateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm") private val fromatt: DateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")
private val `may 1 2023`: LocalDateTime = LocalDateTime.parse("2023-05-01 12:00", fromatt) private val `may 1 2023`: ZonedDateTime = ZonedDateTime.parse("2023-05-01 12:00", fromatt)
private val `may 2 2023`: LocalDateTime = LocalDateTime.parse("2023-05-02 12:00", fromatt) private val `may 2 2023`: ZonedDateTime = ZonedDateTime.parse("2023-05-02 12:00", fromatt)
private val `june 1 2023`: LocalDateTime = LocalDateTime.parse("2023-06-01 12:00", fromatt) private val `june 1 2023`: ZonedDateTime = ZonedDateTime.parse("2023-06-01 12:00", fromatt)
@Test @Test
fun `same dates`() { fun `same dates`() {
assertEquals(DateDifference.Zero, `may 1 2023` - `may 1 2023`) assertEquals(ZonedDateTimeDifference.Zero, `may 1 2023` - `may 1 2023`)
} }
@Test @Test
fun `positive difference dates one day`() { fun `positive difference dates one day`() {
assertEquals(DateDifference.Default(days = 1), `may 1 2023` - `may 2 2023`) assertEquals(ZonedDateTimeDifference.Default(days = 1), `may 1 2023` - `may 2 2023`)
} }
@Test @Test
fun `positive difference dates one minth`() { fun `positive difference dates one minth`() {
assertEquals(DateDifference.Default(months = 1), `may 1 2023` - `june 1 2023`) assertEquals(ZonedDateTimeDifference.Default(months = 1), `may 1 2023` - `june 1 2023`)
} }
@Test @Test
fun `negative difference dates one day`() { fun `negative difference dates one day`() {
assertEquals(DateDifference.Default(days = 1), `may 2 2023` - `may 1 2023`) assertEquals(ZonedDateTimeDifference.Default(days = 1), `may 2 2023` - `may 1 2023`)
} }
} }