diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index fe649479..c106e7fb 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -11,6 +11,7 @@ android:supportsRtl="true" android:theme="@style/Theme.Unitto"> diff --git a/core/base/src/main/res/values/strings.xml b/core/base/src/main/res/values/strings.xml index 4b7955a9..c3c8fae1 100644 --- a/core/base/src/main/res/values/strings.xml +++ b/core/base/src/main/res/values/strings.xml @@ -1370,4 +1370,8 @@ Used in this dialog window. Should be short --> Add time zone Zero in the middle Swap zero and decimal buttons + Difference + Add + Subtract + Date calculator \ No newline at end of file diff --git a/core/ui/src/main/java/com/sadellie/unitto/core/ui/common/DateTimePickerDialog.kt b/core/ui/src/main/java/com/sadellie/unitto/core/ui/common/DateTimePickerDialog.kt index c87264aa..fe387f57 100644 --- a/core/ui/src/main/java/com/sadellie/unitto/core/ui/common/DateTimePickerDialog.kt +++ b/core/ui/src/main/java/com/sadellie/unitto/core/ui/common/DateTimePickerDialog.kt @@ -55,7 +55,7 @@ import com.sadellie.unitto.core.base.R import java.time.Instant import java.time.LocalDateTime import java.time.ZoneId -import java.time.ZoneOffset +import java.time.ZonedDateTime import kotlin.math.max @Composable @@ -127,13 +127,13 @@ fun TimePickerDialog( @Composable fun DatePickerDialog( modifier: Modifier = Modifier, - localDateTime: LocalDateTime, + localDateTime: ZonedDateTime, confirmLabel: String = stringResource(R.string.ok_label), dismissLabel: String = stringResource(R.string.cancel_label), onDismiss: () -> Unit = {}, - onConfirm: (LocalDateTime) -> Unit, + onConfirm: (ZonedDateTime) -> Unit, ) { - val pickerState = rememberDatePickerState(localDateTime.toEpochSecond(ZoneOffset.UTC) * 1000) + val pickerState = rememberDatePickerState(localDateTime.toEpochSecond() * 1000) AlertDialog( onDismissRequest = onDismiss, diff --git a/feature/datedifference/build.gradle.kts b/feature/datedifference/build.gradle.kts index 57b8de18..639591a4 100644 --- a/feature/datedifference/build.gradle.kts +++ b/feature/datedifference/build.gradle.kts @@ -29,4 +29,6 @@ android { dependencies { testImplementation(libs.junit) + + implementation(project(mapOf("path" to ":data:userprefs"))) } diff --git a/feature/datedifference/src/main/java/com/sadellie/unitto/feature/datedifference/DateDifferenceScreen.kt b/feature/datedifference/src/main/java/com/sadellie/unitto/feature/datedifference/DateDifferenceScreen.kt deleted file mode 100644 index be811fd9..00000000 --- a/feature/datedifference/src/main/java/com/sadellie/unitto/feature/datedifference/DateDifferenceScreen.kt +++ /dev/null @@ -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 . - */ - -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 = {}, - ) -} diff --git a/feature/datedifference/src/main/java/com/sadellie/unitto/feature/datedifference/DateToolsScreen.kt b/feature/datedifference/src/main/java/com/sadellie/unitto/feature/datedifference/DateToolsScreen.kt new file mode 100644 index 00000000..1c44e5fa --- /dev/null +++ b/feature/datedifference/src/main/java/com/sadellie/unitto/feature/datedifference/DateToolsScreen.kt @@ -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 . + */ + +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 = {}, + ) +} diff --git a/feature/datedifference/src/main/java/com/sadellie/unitto/feature/datedifference/DateDifference.kt b/feature/datedifference/src/main/java/com/sadellie/unitto/feature/datedifference/ZonedDateTimeDifference.kt similarity index 79% rename from feature/datedifference/src/main/java/com/sadellie/unitto/feature/datedifference/DateDifference.kt rename to feature/datedifference/src/main/java/com/sadellie/unitto/feature/datedifference/ZonedDateTimeDifference.kt index 87c748a4..c2bab0a1 100644 --- a/feature/datedifference/src/main/java/com/sadellie/unitto/feature/datedifference/DateDifference.kt +++ b/feature/datedifference/src/main/java/com/sadellie/unitto/feature/datedifference/ZonedDateTimeDifference.kt @@ -18,10 +18,10 @@ package com.sadellie.unitto.feature.datedifference -import java.time.LocalDateTime +import java.time.ZonedDateTime import java.time.temporal.ChronoUnit -internal sealed class DateDifference( +internal sealed class ZonedDateTimeDifference( open val years: Long = 0, open val months: Long = 0, open val days: Long = 0, @@ -34,7 +34,7 @@ internal sealed class DateDifference( override val days: Long = 0, override val hours: Long = 0, override val minutes: Long = 0, - ) : DateDifference( + ) : ZonedDateTimeDifference( years = years, months = months, days = days, @@ -42,15 +42,15 @@ internal sealed class DateDifference( minutes = minutes, ) - object Zero : DateDifference() + object Zero : ZonedDateTimeDifference() } // https://stackoverflow.com/a/25760725 -internal infix operator fun LocalDateTime.minus(localDateTime: LocalDateTime): DateDifference { - if (this == localDateTime) return DateDifference.Zero +internal infix operator fun ZonedDateTime.minus(localDateTime: ZonedDateTime): ZonedDateTimeDifference { + if (this == localDateTime) return ZonedDateTimeDifference.Zero - var fromDateTime: LocalDateTime = this - var toDateTime: LocalDateTime = localDateTime + var fromDateTime: ZonedDateTime = this + var toDateTime: ZonedDateTime = localDateTime // Swap to avoid negative if (this > localDateTime) { @@ -58,7 +58,7 @@ internal infix operator fun LocalDateTime.minus(localDateTime: LocalDateTime): D toDateTime = this } - var tempDateTime = LocalDateTime.from(fromDateTime) + var tempDateTime = ZonedDateTime.from(fromDateTime) val years = tempDateTime.until(toDateTime, ChronoUnit.YEARS) @@ -74,9 +74,9 @@ internal infix operator fun LocalDateTime.minus(localDateTime: LocalDateTime): D tempDateTime = tempDateTime.plusHours(hours) 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 ) } diff --git a/feature/datedifference/src/main/java/com/sadellie/unitto/feature/datedifference/addsubtract/AddSubtractPage.kt b/feature/datedifference/src/main/java/com/sadellie/unitto/feature/datedifference/addsubtract/AddSubtractPage.kt new file mode 100644 index 00000000..99c27eca --- /dev/null +++ b/feature/datedifference/src/main/java/com/sadellie/unitto/feature/datedifference/addsubtract/AddSubtractPage.kt @@ -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 . + */ + +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 = {} + ) +} diff --git a/feature/datedifference/src/main/java/com/sadellie/unitto/feature/datedifference/addsubtract/AddSubtractUIState.kt b/feature/datedifference/src/main/java/com/sadellie/unitto/feature/datedifference/addsubtract/AddSubtractUIState.kt new file mode 100644 index 00000000..8ba16124 --- /dev/null +++ b/feature/datedifference/src/main/java/com/sadellie/unitto/feature/datedifference/addsubtract/AddSubtractUIState.kt @@ -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 . + */ + +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, +) diff --git a/feature/datedifference/src/main/java/com/sadellie/unitto/feature/datedifference/addsubtract/AddSubtractViewModel.kt b/feature/datedifference/src/main/java/com/sadellie/unitto/feature/datedifference/addsubtract/AddSubtractViewModel.kt new file mode 100644 index 00000000..4bb990eb --- /dev/null +++ b/feature/datedifference/src/main/java/com/sadellie/unitto/feature/datedifference/addsubtract/AddSubtractViewModel.kt @@ -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 . + */ + +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 = _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) } +} diff --git a/feature/datedifference/src/main/java/com/sadellie/unitto/feature/datedifference/components/DateTimeDialogs.kt b/feature/datedifference/src/main/java/com/sadellie/unitto/feature/datedifference/components/DateTimeDialogs.kt new file mode 100644 index 00000000..917c9354 --- /dev/null +++ b/feature/datedifference/src/main/java/com/sadellie/unitto/feature/datedifference/components/DateTimeDialogs.kt @@ -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 . + */ + +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 } diff --git a/feature/datedifference/src/main/java/com/sadellie/unitto/feature/datedifference/components/DateTimeResultBlock.kt b/feature/datedifference/src/main/java/com/sadellie/unitto/feature/datedifference/components/DateTimeResultBlock.kt index 2b841809..99f6b306 100644 --- a/feature/datedifference/src/main/java/com/sadellie/unitto/feature/datedifference/components/DateTimeResultBlock.kt +++ b/feature/datedifference/src/main/java/com/sadellie/unitto/feature/datedifference/components/DateTimeResultBlock.kt @@ -47,20 +47,20 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.sadellie.unitto.core.base.R import com.sadellie.unitto.core.ui.common.squashable -import com.sadellie.unitto.feature.datedifference.DateDifference +import com.sadellie.unitto.feature.datedifference.ZonedDateTimeDifference @Composable internal fun DateTimeResultBlock( modifier: Modifier = Modifier, - dateDifference: DateDifference + zonedDateTimeDifference: ZonedDateTimeDifference ) { val clipboardManager = LocalClipboardManager.current - val years = dateDifference.years.formatDateTimeValue(R.string.date_difference_years) - val months = dateDifference.months.formatDateTimeValue(R.string.date_difference_months) - val days = dateDifference.days.formatDateTimeValue(R.string.date_difference_days) - val hours = dateDifference.hours.formatDateTimeValue(R.string.date_difference_hours) - val minutes = dateDifference.minutes.formatDateTimeValue(R.string.date_difference_minutes) + val years = zonedDateTimeDifference.years.formatDateTimeValue(R.string.date_difference_years) + val months = zonedDateTimeDifference.months.formatDateTimeValue(R.string.date_difference_months) + val days = zonedDateTimeDifference.days.formatDateTimeValue(R.string.date_difference_days) + val hours = zonedDateTimeDifference.hours.formatDateTimeValue(R.string.date_difference_hours) + val minutes = zonedDateTimeDifference.minutes.formatDateTimeValue(R.string.date_difference_minutes) val texts = listOf(years, months, days, hours, minutes) @@ -117,10 +117,10 @@ private fun Long.formatDateTimeValue(@StringRes id: Int): String { @Preview @Composable -private fun PreviewCard() { +private fun DateTimeResultBlockPreview() { DateTimeResultBlock( modifier = Modifier, - dateDifference = DateDifference.Default( + zonedDateTimeDifference = ZonedDateTimeDifference.Default( months = 1, days = 2, hours = 3, diff --git a/feature/datedifference/src/main/java/com/sadellie/unitto/feature/datedifference/components/DateTimeSelectorBlock.kt b/feature/datedifference/src/main/java/com/sadellie/unitto/feature/datedifference/components/DateTimeSelectorBlock.kt index 55c913c0..3d30851e 100644 --- a/feature/datedifference/src/main/java/com/sadellie/unitto/feature/datedifference/components/DateTimeSelectorBlock.kt +++ b/feature/datedifference/src/main/java/com/sadellie/unitto/feature/datedifference/components/DateTimeSelectorBlock.kt @@ -24,6 +24,7 @@ import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width import androidx.compose.material.ripple.rememberRipple import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text @@ -32,24 +33,27 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.sadellie.unitto.core.ui.common.squashable -import java.time.LocalDateTime +import java.time.ZonedDateTime import java.time.format.DateTimeFormatter @Composable internal fun DateTimeSelectorBlock( - modifier: Modifier, + modifier: Modifier = Modifier, title: String, - dateTime: LocalDateTime, - onClick: () -> Unit, - onTimeClick: () -> Unit, - onDateClick: () -> Unit, + dateTime: ZonedDateTime, + onClick: () -> Unit = {}, + onTimeClick: () -> Unit = {}, + onDateClick: () -> Unit = {}, + onLongClick: () -> Unit = {}, ) { Column( modifier = modifier .squashable( onClick = onClick, + onLongClick = onLongClick, interactionSource = remember { MutableInteractionSource() }, 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 dateFormatter by lazy { DateTimeFormatter.ofPattern("EEE, MMM d, y") } private val mTimeFormatter by lazy { DateTimeFormatter.ofPattern("a") } + +@Preview +@Composable +fun DateTimeSelectorBlockPreview() { + DateTimeSelectorBlock( + title = "End", + dateTime = ZonedDateTime.now(), + modifier = Modifier.width(224.dp) + ) +} diff --git a/feature/datedifference/src/main/java/com/sadellie/unitto/feature/datedifference/components/TimeUnitTextField.kt b/feature/datedifference/src/main/java/com/sadellie/unitto/feature/datedifference/components/TimeUnitTextField.kt new file mode 100644 index 00000000..ceab816c --- /dev/null +++ b/feature/datedifference/src/main/java/com/sadellie/unitto/feature/datedifference/components/TimeUnitTextField.kt @@ -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 . + */ + +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) + ) +} diff --git a/feature/datedifference/src/main/java/com/sadellie/unitto/feature/datedifference/difference/DateDifferencePage.kt b/feature/datedifference/src/main/java/com/sadellie/unitto/feature/datedifference/difference/DateDifferencePage.kt new file mode 100644 index 00000000..cb230ae9 --- /dev/null +++ b/feature/datedifference/src/main/java/com/sadellie/unitto/feature/datedifference/difference/DateDifferencePage.kt @@ -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 . + */ + +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 = {}, + ) +} diff --git a/feature/datedifference/src/main/java/com/sadellie/unitto/feature/datedifference/DateDifferenceViewModel.kt b/feature/datedifference/src/main/java/com/sadellie/unitto/feature/datedifference/difference/DateDifferenceViewModel.kt similarity index 72% rename from feature/datedifference/src/main/java/com/sadellie/unitto/feature/datedifference/DateDifferenceViewModel.kt rename to feature/datedifference/src/main/java/com/sadellie/unitto/feature/datedifference/difference/DateDifferenceViewModel.kt index f80a4e4b..ec80b76e 100644 --- a/feature/datedifference/src/main/java/com/sadellie/unitto/feature/datedifference/DateDifferenceViewModel.kt +++ b/feature/datedifference/src/main/java/com/sadellie/unitto/feature/datedifference/difference/DateDifferenceViewModel.kt @@ -16,10 +16,12 @@ * along with this program. If not, see . */ -package com.sadellie.unitto.feature.datedifference +package com.sadellie.unitto.feature.datedifference.difference import androidx.lifecycle.ViewModel 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 kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow @@ -30,29 +32,25 @@ import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch -import java.time.LocalDateTime +import java.time.ZonedDateTime import javax.inject.Inject @HiltViewModel internal class DateDifferenceViewModel @Inject constructor() : ViewModel() { - private val _start = MutableStateFlow(LocalDateTime.now()) - private val _end = MutableStateFlow(LocalDateTime.now()) - private val _result = MutableStateFlow(DateDifference.Zero) + private val _start = MutableStateFlow(ZonedDateTime.now()) + private val _end = MutableStateFlow(ZonedDateTime.now()) + private val _result = MutableStateFlow(ZonedDateTimeDifference.Zero) val uiState = combine(_start, _end, _result) { start, end, result -> - return@combine UIState(start, end, result) + return@combine DifferenceUIState(start, end, result) } .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 setStartDate(dateTime: LocalDateTime) = _start.update { dateTime } - - fun setEndDate(dateTime: LocalDateTime) = _end.update { dateTime } + fun setEndDate(dateTime: ZonedDateTime) = _end.update { dateTime } init { viewModelScope.launch(Dispatchers.Default) { diff --git a/feature/datedifference/src/main/java/com/sadellie/unitto/feature/datedifference/UIState.kt b/feature/datedifference/src/main/java/com/sadellie/unitto/feature/datedifference/difference/DifferenceUIState.kt similarity index 66% rename from feature/datedifference/src/main/java/com/sadellie/unitto/feature/datedifference/UIState.kt rename to feature/datedifference/src/main/java/com/sadellie/unitto/feature/datedifference/difference/DifferenceUIState.kt index 7dd57728..172deda4 100644 --- a/feature/datedifference/src/main/java/com/sadellie/unitto/feature/datedifference/UIState.kt +++ b/feature/datedifference/src/main/java/com/sadellie/unitto/feature/datedifference/difference/DifferenceUIState.kt @@ -16,12 +16,13 @@ * along with this program. If not, see . */ -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( - val start: LocalDateTime = LocalDateTime.now(), - val end: LocalDateTime = LocalDateTime.now(), - val result: DateDifference = DateDifference.Zero +internal data class DifferenceUIState( + val start: ZonedDateTime = ZonedDateTime.now(), + val end: ZonedDateTime = ZonedDateTime.now(), + val result: ZonedDateTimeDifference = ZonedDateTimeDifference.Zero ) diff --git a/feature/datedifference/src/main/java/com/sadellie/unitto/feature/datedifference/navigation/DateDifferenceNavigation.kt b/feature/datedifference/src/main/java/com/sadellie/unitto/feature/datedifference/navigation/DateDifferenceNavigation.kt index d779d938..6a5c2e48 100644 --- a/feature/datedifference/src/main/java/com/sadellie/unitto/feature/datedifference/navigation/DateDifferenceNavigation.kt +++ b/feature/datedifference/src/main/java/com/sadellie/unitto/feature/datedifference/navigation/DateDifferenceNavigation.kt @@ -22,7 +22,7 @@ import androidx.navigation.NavGraphBuilder import androidx.navigation.compose.composable import androidx.navigation.navDeepLink 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 } @@ -36,7 +36,7 @@ fun NavGraphBuilder.dateDifferenceScreen( navDeepLink { uriPattern = "app://com.sadellie.unitto/$dateDifferenceRoute" } ) ) { - DateDifferenceRoute( + DateToolsRoute( navigateToMenu = navigateToMenu, navigateToSettings = navigateToSettings ) diff --git a/feature/datedifference/src/test/java/com/sadellie/unitto/feature/datedifference/DateDifferenceKtTest.kt b/feature/datedifference/src/test/java/com/sadellie/unitto/feature/datedifference/ZonedDateTimeDifferenceKtTest.kt similarity index 68% rename from feature/datedifference/src/test/java/com/sadellie/unitto/feature/datedifference/DateDifferenceKtTest.kt rename to feature/datedifference/src/test/java/com/sadellie/unitto/feature/datedifference/ZonedDateTimeDifferenceKtTest.kt index 7591a9ae..f3301bba 100644 --- a/feature/datedifference/src/test/java/com/sadellie/unitto/feature/datedifference/DateDifferenceKtTest.kt +++ b/feature/datedifference/src/test/java/com/sadellie/unitto/feature/datedifference/ZonedDateTimeDifferenceKtTest.kt @@ -20,32 +20,32 @@ package com.sadellie.unitto.feature.datedifference import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test -import java.time.LocalDateTime +import java.time.ZonedDateTime import java.time.format.DateTimeFormatter -class DateDifferenceKtTest { +class ZonedDateTimeDifferenceKtTest { 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 2 2023`: LocalDateTime = LocalDateTime.parse("2023-05-02 12:00", fromatt) - private val `june 1 2023`: LocalDateTime = LocalDateTime.parse("2023-06-01 12:00", fromatt) + private val `may 1 2023`: ZonedDateTime = ZonedDateTime.parse("2023-05-01 12:00", fromatt) + private val `may 2 2023`: ZonedDateTime = ZonedDateTime.parse("2023-05-02 12:00", fromatt) + private val `june 1 2023`: ZonedDateTime = ZonedDateTime.parse("2023-06-01 12:00", fromatt) @Test fun `same dates`() { - assertEquals(DateDifference.Zero, `may 1 2023` - `may 1 2023`) + assertEquals(ZonedDateTimeDifference.Zero, `may 1 2023` - `may 1 2023`) } @Test 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 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 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`) } }