From e7081e52e5cb1c0d2eb2080d38fa58e5dcbeb68b Mon Sep 17 00:00:00 2001
From: Iliyan Germanov <iliyan.germanov971@gmail.com>
Date: Sat, 31 Aug 2024 23:46:35 +0300
Subject: [PATCH] Fix updating transactions's time (#3453)

* Implement a proper DateTimePicker

* Fix updating date & time on transaction

* Improve the time picker UI

* Fix Detekt errors
---
 .../main/java/com/ivy/wallet/RootActivity.kt  |   6 +
 .../ivy/transaction/EditTransactionScreen.kt  |  30 +---
 .../transaction/EditTransactionViewEvent.kt   |  11 +-
 .../transaction/EditTransactionViewModel.kt   |  77 +++++-----
 .../java/com/ivy/base/time/TimeProvider.kt    |   2 +
 .../ivy/base/time/impl/DeviceTimeProvider.kt  |   3 +
 .../main/java/com/ivy/ui/di/IvyUiBindings.kt  |  11 +-
 ...imePreferences.kt => DevicePreferences.kt} |   2 +-
 .../ivy/ui/time/impl/AndroidDateTimePicker.kt | 142 ++++++++++++++++++
 ...erences.kt => AndroidDevicePreferences.kt} |   6 +-
 .../com/ivy/ui/time/impl/DateTimePicker.kt    |  23 +++
 .../com/ivy/ui/time/impl/IvyTimeFormatter.kt  |  10 +-
 .../ivy/ui/time/impl/IvyTimeFormatterTest.kt  |  10 +-
 .../main/java/com/ivy/design/utils/Preview.kt |   4 +-
 14 files changed, 249 insertions(+), 88 deletions(-)
 rename shared/ui/core/src/main/java/com/ivy/ui/time/{DeviceTimePreferences.kt => DevicePreferences.kt} (76%)
 create mode 100644 shared/ui/core/src/main/java/com/ivy/ui/time/impl/AndroidDateTimePicker.kt
 rename shared/ui/core/src/main/java/com/ivy/ui/time/impl/{AndroidDeviceTimePreferences.kt => AndroidDevicePreferences.kt} (75%)
 create mode 100644 shared/ui/core/src/main/java/com/ivy/ui/time/impl/DateTimePicker.kt

diff --git a/app/src/main/java/com/ivy/wallet/RootActivity.kt b/app/src/main/java/com/ivy/wallet/RootActivity.kt
index 9809a14664..a83fbf993b 100644
--- a/app/src/main/java/com/ivy/wallet/RootActivity.kt
+++ b/app/src/main/java/com/ivy/wallet/RootActivity.kt
@@ -48,6 +48,7 @@ import com.ivy.navigation.Navigation
 import com.ivy.navigation.NavigationRoot
 import com.ivy.ui.R
 import com.ivy.ui.time.TimeFormatter
+import com.ivy.ui.time.impl.DateTimePicker
 import com.ivy.wallet.ui.applocked.AppLockedScreen
 import com.ivy.widget.balance.WalletBalanceWidgetReceiver
 import com.ivy.widget.transaction.AddTransactionWidget
@@ -78,6 +79,9 @@ class RootActivity : AppCompatActivity(), RootScreen {
     @Inject
     lateinit var timeFormatter: TimeFormatter
 
+    @Inject
+    lateinit var dateTimePicker: DateTimePicker
+
     private lateinit var createFileLauncher: ActivityResultLauncher<String>
     private lateinit var onFileCreated: (fileUri: Uri) -> Unit
 
@@ -154,6 +158,8 @@ class RootActivity : AppCompatActivity(), RootScreen {
                     }
                 }
             }
+
+            dateTimePicker.Content()
         }
     }
 
diff --git a/screen/edit-transaction/src/main/java/com/ivy/transaction/EditTransactionScreen.kt b/screen/edit-transaction/src/main/java/com/ivy/transaction/EditTransactionScreen.kt
index 7aa6e97723..dac4dabc1c 100644
--- a/screen/edit-transaction/src/main/java/com/ivy/transaction/EditTransactionScreen.kt
+++ b/screen/edit-transaction/src/main/java/com/ivy/transaction/EditTransactionScreen.kt
@@ -84,9 +84,7 @@ import kotlinx.collections.immutable.ImmutableSet
 import kotlinx.collections.immutable.persistentListOf
 import kotlinx.collections.immutable.persistentSetOf
 import java.time.Instant
-import java.time.LocalDate
 import java.time.LocalDateTime
-import java.time.LocalTime
 import java.time.ZoneOffset
 import java.util.UUID
 import kotlin.math.roundToInt
@@ -126,10 +124,10 @@ fun BoxWithConstraintsScope.EditTransactionScreen(screen: EditTransactionScreen)
         transactionAssociatedTags = uiState.transactionAssociatedTags,
         hasChanges = uiState.hasChanges,
         onSetDate = {
-            viewModel.onEvent(EditTransactionViewEvent.OnSetDate(it))
+            viewModel.onEvent(EditTransactionViewEvent.OnChangeDate)
         },
         onSetTime = {
-            viewModel.onEvent(EditTransactionViewEvent.OnSetTime(it))
+            viewModel.onEvent(EditTransactionViewEvent.OnChangeTime)
         },
         onTitleChange = {
             viewModel.onEvent(EditTransactionViewEvent.OnTitleChanged(it))
@@ -218,8 +216,8 @@ private fun BoxWithConstraintsScope.UI(
     onAccountChange: (Account) -> Unit,
     onToAccountChange: (Account) -> Unit,
     onDueDateChange: (LocalDateTime?) -> Unit,
-    onSetDate: (LocalDate) -> Unit,
-    onSetTime: (LocalTime) -> Unit,
+    onSetDate: () -> Unit,
+    onSetTime: () -> Unit,
     onSetTransactionType: (TransactionType) -> Unit,
 
     onCreateCategory: (CreateCategoryData) -> Unit,
@@ -382,24 +380,8 @@ private fun BoxWithConstraintsScope.UI(
         TransactionDateTime(
             dateTime = dateTime,
             dueDateTime = dueDate,
-            onEditDate = {
-                ivyContext.datePicker(
-                    initialDate = with(timeConverter) {
-                        dateTime?.toLocalDate()
-                    }
-                ) { date ->
-                    onSetDate((date))
-                }
-            },
-            onEditTime = {
-                ivyContext.timePicker(
-                    initialTime = with(timeConverter) {
-                        dateTime?.toLocalTime()
-                    }
-                ) { time ->
-                    onSetTime(time)
-                }
-            }
+            onEditDate = onSetDate,
+            onEditTime = onSetTime,
         )
 
         if (transactionType == TransactionType.TRANSFER && customExchangeRateState.showCard) {
diff --git a/screen/edit-transaction/src/main/java/com/ivy/transaction/EditTransactionViewEvent.kt b/screen/edit-transaction/src/main/java/com/ivy/transaction/EditTransactionViewEvent.kt
index 3a5357aa4f..4eba08d963 100644
--- a/screen/edit-transaction/src/main/java/com/ivy/transaction/EditTransactionViewEvent.kt
+++ b/screen/edit-transaction/src/main/java/com/ivy/transaction/EditTransactionViewEvent.kt
@@ -13,9 +13,7 @@ import com.ivy.wallet.domain.deprecated.logic.model.CreateCategoryData
 import kotlinx.collections.immutable.ImmutableList
 import kotlinx.collections.immutable.ImmutableSet
 import java.time.Instant
-import java.time.LocalDate
 import java.time.LocalDateTime
-import java.time.LocalTime
 
 @Immutable
 data class EditTransactionViewState(
@@ -48,10 +46,11 @@ sealed interface EditTransactionViewEvent {
     data class OnAccountChanged(val newAccount: Account) : EditTransactionViewEvent
     data class OnToAccountChanged(val newAccount: Account) : EditTransactionViewEvent
     data class OnDueDateChanged(val newDueDate: LocalDateTime?) : EditTransactionViewEvent
-    data class OnSetDateTime(val newDateTime: LocalDateTime) : EditTransactionViewEvent
-    data class OnSetDate(val newDate: LocalDate) : EditTransactionViewEvent
-    data class OnSetTime(val newTime: LocalTime) : EditTransactionViewEvent
-    data class OnSetTransactionType(val newTransactionType: TransactionType) : EditTransactionViewEvent
+    data object OnChangeDate : EditTransactionViewEvent
+    data object OnChangeTime : EditTransactionViewEvent
+    data class OnSetTransactionType(val newTransactionType: TransactionType) :
+        EditTransactionViewEvent
+
     data object OnPayPlannedPayment : EditTransactionViewEvent
     data object Delete : EditTransactionViewEvent
     data object Duplicate : EditTransactionViewEvent
diff --git a/screen/edit-transaction/src/main/java/com/ivy/transaction/EditTransactionViewModel.kt b/screen/edit-transaction/src/main/java/com/ivy/transaction/EditTransactionViewModel.kt
index eb3e0b2220..339684a8f8 100644
--- a/screen/edit-transaction/src/main/java/com/ivy/transaction/EditTransactionViewModel.kt
+++ b/screen/edit-transaction/src/main/java/com/ivy/transaction/EditTransactionViewModel.kt
@@ -34,7 +34,6 @@ import com.ivy.legacy.datamodel.Account
 import com.ivy.legacy.datamodel.temp.toDomain
 import com.ivy.legacy.domain.deprecated.logic.AccountCreator
 import com.ivy.legacy.utils.computationThread
-import com.ivy.legacy.utils.convertUTCToLocal
 import com.ivy.legacy.utils.ioThread
 import com.ivy.legacy.utils.toLowerCaseLocal
 import com.ivy.legacy.utils.uiThread
@@ -43,6 +42,7 @@ import com.ivy.navigation.MainScreen
 import com.ivy.navigation.Navigation
 import com.ivy.ui.ComposeViewModel
 import com.ivy.ui.R
+import com.ivy.ui.time.impl.DateTimePicker
 import com.ivy.wallet.domain.action.account.AccountByIdAct
 import com.ivy.wallet.domain.action.account.AccountsAct
 import com.ivy.wallet.domain.action.transaction.TrnByIdAct
@@ -71,9 +71,7 @@ import kotlinx.coroutines.delay
 import kotlinx.coroutines.launch
 import java.math.BigDecimal
 import java.time.Instant
-import java.time.LocalDate
 import java.time.LocalDateTime
-import java.time.LocalTime
 import java.util.UUID
 import javax.inject.Inject
 
@@ -106,6 +104,7 @@ class EditTransactionViewModel @Inject constructor(
     private val features: Features,
     private val timeConverter: TimeConverter,
     private val timeProvider: TimeProvider,
+    private val dateTimePicker: DateTimePicker,
 ) : ComposeViewModel<EditTransactionViewState, EditTransactionViewEvent>() {
 
     private val transactionType = mutableStateOf(TransactionType.EXPENSE)
@@ -322,9 +321,8 @@ class EditTransactionViewModel @Inject constructor(
 
             is EditTransactionViewEvent.OnDueDateChanged -> onDueDateChanged(event.newDueDate)
             EditTransactionViewEvent.OnPayPlannedPayment -> onPayPlannedPayment()
-            is EditTransactionViewEvent.OnSetDateTime -> onSetDateTime(event.newDateTime)
-            is EditTransactionViewEvent.OnSetDate -> onSetDate(event.newDate)
-            is EditTransactionViewEvent.OnSetTime -> onSetTime(event.newTime)
+            is EditTransactionViewEvent.OnChangeDate -> handleChangeDate()
+            is EditTransactionViewEvent.OnChangeTime -> handleChangeTime()
             is EditTransactionViewEvent.OnSetTransactionType ->
                 onSetTransactionType(event.newTransactionType)
 
@@ -536,45 +534,46 @@ class EditTransactionViewModel @Inject constructor(
         saveIfEditMode()
     }
 
-    private fun onSetDateTime(newDateTime: LocalDateTime) {
-        val newDateTimeUtc = with(timeConverter) { newDateTime.toUTC() }
-        loadedTransaction = loadedTransaction().copy(
-            dateTime = newDateTimeUtc
-        )
-        dateTime.value = newDateTimeUtc
-
-        saveIfEditMode()
+    private fun handleChangeDate() {
+        dateTimePicker.pickDate(
+            initialDate = loadedTransaction?.dateTime,
+        ) { localDate ->
+            val localTime = loadedTransaction().dateTime?.let {
+                with(timeConverter) { it.toLocalTime() }
+            } ?: timeProvider.localTimeNow()
+            loadedTransaction = loadedTransaction().copy(
+                date = localDate,
+            )
+            updateDateTime(localDate.atTime(localTime))
+        }
     }
 
-    private fun onSetDate(newDate: LocalDate) {
-        loadedTransaction = loadedTransaction().copy(
-            date = newDate
-        )
-        val localDateTime = with(timeConverter) {
-            (dateTime.value ?: timeProvider.utcNow()).toLocalDateTime()
-        }
-        onSetDateTime(
-            localDateTime
-                .withDayOfMonth(newDate.dayOfMonth)
-                .withMonth(newDate.monthValue)
-                .withYear(newDate.year)
-        )
+    private fun handleChangeTime() {
+        dateTimePicker.pickTime(
+            initialTime = loadedTransaction?.dateTime?.let {
+                with(timeConverter) {
+                    it.toLocalDateTime()
+                }
+            }?.toLocalTime()
+        ) { localTime ->
+            val localDate = loadedTransaction().dateTime?.let {
+                with(timeConverter) { it.toLocalDate() }
+            } ?: timeProvider.localDateNow()
+            loadedTransaction = loadedTransaction().copy(
+                time = localTime,
+            )
+            updateDateTime(localDate.atTime(localTime))
+        }
     }
 
-    private fun onSetTime(newTime: LocalTime) {
+    private fun updateDateTime(newDateTime: LocalDateTime) {
+        val newDateTimeUtc = with(timeConverter) { newDateTime.toUTC() }
         loadedTransaction = loadedTransaction().copy(
-            time = newTime.convertUTCToLocal()
-        )
-        val localDateTime = with(timeConverter) {
-            (dateTime.value ?: timeProvider.utcNow()).toLocalDateTime()
-        }
-        onSetDateTime(
-            localDateTime
-                .withHour(newTime.hour)
-                .withMinute(newTime.minute)
-                .withSecond(0)
-                .withNano(0)
+            dateTime = newDateTimeUtc,
         )
+        dateTime.value = newDateTimeUtc
+
+        saveIfEditMode()
     }
 
     private fun onSetTransactionType(newTransactionType: TransactionType) {
diff --git a/shared/base/src/main/java/com/ivy/base/time/TimeProvider.kt b/shared/base/src/main/java/com/ivy/base/time/TimeProvider.kt
index df2ce282de..0472218a3d 100644
--- a/shared/base/src/main/java/com/ivy/base/time/TimeProvider.kt
+++ b/shared/base/src/main/java/com/ivy/base/time/TimeProvider.kt
@@ -3,6 +3,7 @@ package com.ivy.base.time
 import java.time.Instant
 import java.time.LocalDate
 import java.time.LocalDateTime
+import java.time.LocalTime
 import java.time.ZoneId
 
 interface TimeProvider {
@@ -10,4 +11,5 @@ interface TimeProvider {
     fun utcNow(): Instant
     fun localNow(): LocalDateTime
     fun localDateNow(): LocalDate
+    fun localTimeNow(): LocalTime
 }
diff --git a/shared/base/src/main/java/com/ivy/base/time/impl/DeviceTimeProvider.kt b/shared/base/src/main/java/com/ivy/base/time/impl/DeviceTimeProvider.kt
index 907257b129..726d595653 100644
--- a/shared/base/src/main/java/com/ivy/base/time/impl/DeviceTimeProvider.kt
+++ b/shared/base/src/main/java/com/ivy/base/time/impl/DeviceTimeProvider.kt
@@ -4,6 +4,7 @@ import com.ivy.base.time.TimeProvider
 import java.time.Instant
 import java.time.LocalDate
 import java.time.LocalDateTime
+import java.time.LocalTime
 import java.time.ZoneId
 import javax.inject.Inject
 
@@ -17,4 +18,6 @@ class DeviceTimeProvider @Inject constructor() : TimeProvider {
     override fun localNow(): LocalDateTime = LocalDateTime.now()
 
     override fun localDateNow(): LocalDate = localNow().toLocalDate()
+
+    override fun localTimeNow(): LocalTime = localNow().toLocalTime()
 }
\ No newline at end of file
diff --git a/shared/ui/core/src/main/java/com/ivy/ui/di/IvyUiBindings.kt b/shared/ui/core/src/main/java/com/ivy/ui/di/IvyUiBindings.kt
index da313ad1d6..17e88820b6 100644
--- a/shared/ui/core/src/main/java/com/ivy/ui/di/IvyUiBindings.kt
+++ b/shared/ui/core/src/main/java/com/ivy/ui/di/IvyUiBindings.kt
@@ -1,8 +1,10 @@
 package com.ivy.ui.di
 
-import com.ivy.ui.time.DeviceTimePreferences
+import com.ivy.ui.time.DevicePreferences
 import com.ivy.ui.time.TimeFormatter
-import com.ivy.ui.time.impl.AndroidDeviceTimePreferences
+import com.ivy.ui.time.impl.AndroidDateTimePicker
+import com.ivy.ui.time.impl.AndroidDevicePreferences
+import com.ivy.ui.time.impl.DateTimePicker
 import com.ivy.ui.time.impl.IvyTimeFormatter
 import dagger.Binds
 import dagger.Module
@@ -16,5 +18,8 @@ interface IvyUiBindings {
     fun timeFormatter(impl: IvyTimeFormatter): TimeFormatter
 
     @Binds
-    fun deviceTimePreferences(impl: AndroidDeviceTimePreferences): DeviceTimePreferences
+    fun deviceTimePreferences(impl: AndroidDevicePreferences): DevicePreferences
+
+    @Binds
+    fun dateTimePicker(impl: AndroidDateTimePicker): DateTimePicker
 }
\ No newline at end of file
diff --git a/shared/ui/core/src/main/java/com/ivy/ui/time/DeviceTimePreferences.kt b/shared/ui/core/src/main/java/com/ivy/ui/time/DevicePreferences.kt
similarity index 76%
rename from shared/ui/core/src/main/java/com/ivy/ui/time/DeviceTimePreferences.kt
rename to shared/ui/core/src/main/java/com/ivy/ui/time/DevicePreferences.kt
index 4139a84a90..6b7a19e7b4 100644
--- a/shared/ui/core/src/main/java/com/ivy/ui/time/DeviceTimePreferences.kt
+++ b/shared/ui/core/src/main/java/com/ivy/ui/time/DevicePreferences.kt
@@ -2,7 +2,7 @@ package com.ivy.ui.time
 
 import java.util.Locale
 
-interface DeviceTimePreferences {
+interface DevicePreferences {
     fun is24HourFormat(): Boolean
     fun locale(): Locale
 }
\ No newline at end of file
diff --git a/shared/ui/core/src/main/java/com/ivy/ui/time/impl/AndroidDateTimePicker.kt b/shared/ui/core/src/main/java/com/ivy/ui/time/impl/AndroidDateTimePicker.kt
new file mode 100644
index 0000000000..126bf6bde4
--- /dev/null
+++ b/shared/ui/core/src/main/java/com/ivy/ui/time/impl/AndroidDateTimePicker.kt
@@ -0,0 +1,142 @@
+package com.ivy.ui.time.impl
+
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Button
+import androidx.compose.material3.DatePicker
+import androidx.compose.material3.DatePickerDialog
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Text
+import androidx.compose.material3.TimePicker
+import androidx.compose.material3.rememberDatePickerState
+import androidx.compose.material3.rememberTimePickerState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.Immutable
+import androidx.compose.runtime.Stable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import com.ivy.base.time.TimeConverter
+import com.ivy.base.time.TimeProvider
+import java.time.Instant
+import java.time.LocalDate
+import java.time.LocalTime
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Stable
+@Singleton
+class AndroidDateTimePicker @Inject constructor(
+    private val timeProvider: TimeProvider,
+    private val timeConverter: TimeConverter,
+) : DateTimePicker {
+    private var datePickerViewState by mutableStateOf<DatePickerViewState?>(null)
+    private var timePickerViewState by mutableStateOf<TimePickerViewState?>(null)
+
+    @Composable
+    override fun Content() {
+        datePickerViewState?.let { DatePicker(it) }
+        timePickerViewState?.let { TimePicker(it) }
+    }
+
+    @OptIn(ExperimentalMaterial3Api::class)
+    @Composable
+    fun DatePicker(
+        viewState: DatePickerViewState,
+        modifier: Modifier = Modifier
+    ) {
+        val pickerState = rememberDatePickerState(
+            initialSelectedDateMillis = viewState.initialDate?.toEpochMilli(),
+        )
+        DatePickerDialog(
+            modifier = modifier,
+            onDismissRequest = { datePickerViewState = null },
+            confirmButton = {
+                ConfirmButton(
+                    onClick = {
+                        datePickerViewState = null
+                        pickerState.selectedDateMillis
+                            ?.let(Instant::ofEpochMilli)
+                            ?.let {
+                                with(timeConverter) { it.toLocalDate() }
+                            }?.let(viewState.onDatePicked)
+                    }
+                )
+            }
+        ) {
+            DatePicker(state = pickerState)
+        }
+    }
+
+    @OptIn(ExperimentalMaterial3Api::class)
+    @Composable
+    fun TimePicker(
+        viewState: TimePickerViewState,
+        modifier: Modifier = Modifier
+    ) {
+        val time = viewState.initialTime ?: timeProvider.localNow().toLocalTime()
+        val pickerState = rememberTimePickerState(
+            initialHour = time.hour,
+            initialMinute = time.minute,
+        )
+        DatePickerDialog(
+            modifier = modifier,
+            onDismissRequest = { timePickerViewState = null },
+            confirmButton = {
+                ConfirmButton(
+                    onClick = {
+                        timePickerViewState = null
+                        viewState.onTimePicked(
+                            LocalTime.of(pickerState.hour, pickerState.minute)
+                        )
+                    }
+                )
+            }
+        ) {
+            TimePicker(
+                modifier = Modifier.padding(16.dp),
+                state = pickerState
+            )
+        }
+    }
+
+    @Composable
+    fun ConfirmButton(
+        modifier: Modifier = Modifier,
+        onClick: () -> Unit,
+    ) {
+        Button(
+            modifier = modifier,
+            onClick = onClick
+        ) {
+            Text(text = "Select")
+        }
+    }
+
+    override fun pickDate(initialDate: Instant?, onDatePick: (LocalDate) -> Unit) {
+        datePickerViewState = DatePickerViewState(
+            initialDate = initialDate,
+            onDatePicked = onDatePick
+        )
+    }
+
+    override fun pickTime(initialTime: LocalTime?, onTimePick: (LocalTime) -> Unit) {
+        timePickerViewState = TimePickerViewState(
+            initialTime = initialTime,
+            onTimePicked = onTimePick
+        )
+    }
+
+    @Immutable
+    data class DatePickerViewState(
+        val initialDate: Instant?,
+        val onDatePicked: (LocalDate) -> Unit
+    )
+
+    @Immutable
+    data class TimePickerViewState(
+        val initialTime: LocalTime?,
+        val onTimePicked: (LocalTime) -> Unit
+    )
+}
\ No newline at end of file
diff --git a/shared/ui/core/src/main/java/com/ivy/ui/time/impl/AndroidDeviceTimePreferences.kt b/shared/ui/core/src/main/java/com/ivy/ui/time/impl/AndroidDevicePreferences.kt
similarity index 75%
rename from shared/ui/core/src/main/java/com/ivy/ui/time/impl/AndroidDeviceTimePreferences.kt
rename to shared/ui/core/src/main/java/com/ivy/ui/time/impl/AndroidDevicePreferences.kt
index 0c5b2be115..facc185ee1 100644
--- a/shared/ui/core/src/main/java/com/ivy/ui/time/impl/AndroidDeviceTimePreferences.kt
+++ b/shared/ui/core/src/main/java/com/ivy/ui/time/impl/AndroidDevicePreferences.kt
@@ -2,15 +2,15 @@ package com.ivy.ui.time.impl
 
 import android.content.Context
 import android.text.format.DateFormat
-import com.ivy.ui.time.DeviceTimePreferences
+import com.ivy.ui.time.DevicePreferences
 import dagger.hilt.android.qualifiers.ApplicationContext
 import java.util.Locale
 import javax.inject.Inject
 
-class AndroidDeviceTimePreferences @Inject constructor(
+class AndroidDevicePreferences @Inject constructor(
     @ApplicationContext
     private val context: Context,
-) : DeviceTimePreferences {
+) : DevicePreferences {
     override fun is24HourFormat(): Boolean = DateFormat.is24HourFormat(context)
     override fun locale(): Locale = Locale.getDefault()
 }
\ No newline at end of file
diff --git a/shared/ui/core/src/main/java/com/ivy/ui/time/impl/DateTimePicker.kt b/shared/ui/core/src/main/java/com/ivy/ui/time/impl/DateTimePicker.kt
new file mode 100644
index 0000000000..d3fd114bb3
--- /dev/null
+++ b/shared/ui/core/src/main/java/com/ivy/ui/time/impl/DateTimePicker.kt
@@ -0,0 +1,23 @@
+package com.ivy.ui.time.impl
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.Stable
+import java.time.Instant
+import java.time.LocalDate
+import java.time.LocalTime
+
+@Stable
+interface DateTimePicker {
+    @Composable
+    fun Content()
+
+    fun pickDate(
+        initialDate: Instant?,
+        onDatePick: (LocalDate) -> Unit
+    )
+
+    fun pickTime(
+        initialTime: LocalTime?,
+        onTimePick: (LocalTime) -> Unit
+    )
+}
\ No newline at end of file
diff --git a/shared/ui/core/src/main/java/com/ivy/ui/time/impl/IvyTimeFormatter.kt b/shared/ui/core/src/main/java/com/ivy/ui/time/impl/IvyTimeFormatter.kt
index cbf20687d5..e73a6cf3f2 100644
--- a/shared/ui/core/src/main/java/com/ivy/ui/time/impl/IvyTimeFormatter.kt
+++ b/shared/ui/core/src/main/java/com/ivy/ui/time/impl/IvyTimeFormatter.kt
@@ -4,7 +4,7 @@ import com.ivy.base.resource.ResourceProvider
 import com.ivy.base.time.TimeConverter
 import com.ivy.base.time.TimeProvider
 import com.ivy.ui.R
-import com.ivy.ui.time.DeviceTimePreferences
+import com.ivy.ui.time.DevicePreferences
 import com.ivy.ui.time.TimeFormatter
 import java.time.Instant
 import java.time.LocalDate
@@ -18,7 +18,7 @@ class IvyTimeFormatter @Inject constructor(
     private val resourceProvider: ResourceProvider,
     private val timeProvider: TimeProvider,
     private val converter: TimeConverter,
-    private val deviceTimePreferences: DeviceTimePreferences,
+    private val devicePreferences: DevicePreferences,
 ) : TimeFormatter {
 
     override fun LocalDateTime.format(style: TimeFormatter.Style): String {
@@ -43,7 +43,7 @@ class IvyTimeFormatter @Inject constructor(
             }
         }
         val formatted = dateTime.format(
-            DateTimeFormatter.ofPattern(pattern, deviceTimePreferences.locale())
+            DateTimeFormatter.ofPattern(pattern, devicePreferences.locale())
         )
         val prefix = when (relativeDay) {
             RelativeDay.Yesterday -> resourceProvider.getString(R.string.yesterday)
@@ -60,10 +60,10 @@ class IvyTimeFormatter @Inject constructor(
     }
 
     override fun LocalTime.format(): String = this.format(
-        DateTimeFormatter.ofPattern(localeTimeFormat(), deviceTimePreferences.locale())
+        DateTimeFormatter.ofPattern(localeTimeFormat(), devicePreferences.locale())
     )
 
-    private fun localeTimeFormat(): String = if (deviceTimePreferences.is24HourFormat()) {
+    private fun localeTimeFormat(): String = if (devicePreferences.is24HourFormat()) {
         "HH:mm"
     } else {
         "h:mm a"
diff --git a/shared/ui/core/src/test/java/com/ivy/ui/time/impl/IvyTimeFormatterTest.kt b/shared/ui/core/src/test/java/com/ivy/ui/time/impl/IvyTimeFormatterTest.kt
index 0681acefd7..86b2970c46 100644
--- a/shared/ui/core/src/test/java/com/ivy/ui/time/impl/IvyTimeFormatterTest.kt
+++ b/shared/ui/core/src/test/java/com/ivy/ui/time/impl/IvyTimeFormatterTest.kt
@@ -6,7 +6,7 @@ import com.ivy.base.resource.TestResourceProvider
 import com.ivy.base.time.TimeConverter
 import com.ivy.base.time.TimeProvider
 import com.ivy.ui.R
-import com.ivy.ui.time.DeviceTimePreferences
+import com.ivy.ui.time.DevicePreferences
 import com.ivy.ui.time.TimeFormatter.Style
 import io.kotest.matchers.shouldBe
 import io.mockk.every
@@ -26,7 +26,7 @@ class IvyTimeFormatterTest {
 
     private val timeProvider = mockk<TimeProvider>()
     private val converter = mockk<TimeConverter>()
-    private val deviceTimePreferences = mockk<DeviceTimePreferences> {
+    private val devicePreferences = mockk<DevicePreferences> {
         every { locale() } returns Locale.ENGLISH
     }
 
@@ -42,7 +42,7 @@ class IvyTimeFormatterTest {
             },
             timeProvider = timeProvider,
             converter = converter,
-            deviceTimePreferences = deviceTimePreferences
+            devicePreferences = devicePreferences
         )
     }
 
@@ -179,7 +179,7 @@ class IvyTimeFormatterTest {
     ) {
         // Given
         every { timeProvider.localDateNow() } returns testCase.today
-        every { deviceTimePreferences.is24HourFormat() } returns testCase.is24HourFormat
+        every { devicePreferences.is24HourFormat() } returns testCase.is24HourFormat
         val date = testCase.date
 
         // When
@@ -221,7 +221,7 @@ class IvyTimeFormatterTest {
         @TestParameter testCase: TimeFormattingTestCase
     ) {
         // Given
-        every { deviceTimePreferences.is24HourFormat() } returns testCase.is24HourFormat
+        every { devicePreferences.is24HourFormat() } returns testCase.is24HourFormat
         val time = testCase.time
 
         // When
diff --git a/temp/old-design/src/main/java/com/ivy/design/utils/Preview.kt b/temp/old-design/src/main/java/com/ivy/design/utils/Preview.kt
index 8711a170ee..6669ac31a4 100644
--- a/temp/old-design/src/main/java/com/ivy/design/utils/Preview.kt
+++ b/temp/old-design/src/main/java/com/ivy/design/utils/Preview.kt
@@ -18,7 +18,7 @@ import com.ivy.design.api.IvyDesign
 import com.ivy.design.api.IvyUI
 import com.ivy.design.api.systems.IvyWalletDesign
 import com.ivy.design.l0_system.UI
-import com.ivy.ui.time.impl.AndroidDeviceTimePreferences
+import com.ivy.ui.time.impl.AndroidDevicePreferences
 import com.ivy.ui.time.impl.IvyTimeFormatter
 
 @Deprecated("Old design system. Use `:ivy-design` and Material3")
@@ -63,7 +63,7 @@ fun IvyPreview(
             resourceProvider = AndroidResourceProvider(LocalContext.current),
             timeProvider = timeProvider,
             converter = timeConverter,
-            deviceTimePreferences = AndroidDeviceTimePreferences(LocalContext.current)
+            devicePreferences = AndroidDevicePreferences(LocalContext.current)
         )
     )
 }