mirror of
https://github.com/Myzel394/NumberHub.git
synced 2025-06-19 08:45:27 +02:00
Date calculator tool
Fuck LocalDateTime, now ZonedDateTime is my new homie squashed commit
This commit is contained in:
parent
fbcda09c32
commit
79269dcea5
@ -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">
|
||||||
|
@ -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>
|
@ -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,
|
||||||
|
@ -29,4 +29,6 @@ android {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
testImplementation(libs.junit)
|
testImplementation(libs.junit)
|
||||||
|
|
||||||
|
implementation(project(mapOf("path" to ":data:userprefs")))
|
||||||
}
|
}
|
||||||
|
@ -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 = {},
|
|
||||||
)
|
|
||||||
}
|
|
@ -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 = {},
|
||||||
|
)
|
||||||
|
}
|
@ -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
|
||||||
)
|
)
|
||||||
}
|
}
|
@ -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 = {}
|
||||||
|
)
|
||||||
|
}
|
@ -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,
|
||||||
|
)
|
@ -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) }
|
||||||
|
}
|
@ -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 }
|
@ -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,
|
||||||
|
@ -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)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
@ -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)
|
||||||
|
)
|
||||||
|
}
|
@ -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 = {},
|
||||||
|
)
|
||||||
|
}
|
@ -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) {
|
@ -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
|
||||||
)
|
)
|
@ -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
|
||||||
)
|
)
|
||||||
|
@ -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`)
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user