Skip to content
This repository has been archived by the owner on Nov 5, 2024. It is now read-only.

Commit

Permalink
Edit transaction time fixes (part 1) (#3446)
Browse files Browse the repository at this point in the history
* Minor refactor

* Minor refactor

* WIP: Rework time in the VM

* WIP: Rework

* WIP: Fix time problems

* Fix Detekt
ILIYANGERMANOV authored Aug 30, 2024
1 parent 8ef3841 commit f461c4f
Showing 13 changed files with 251 additions and 212 deletions.
Original file line number Diff line number Diff line change
@@ -24,6 +24,7 @@ import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.layout.positionInParent
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.TextFieldValue
@@ -34,6 +35,7 @@ import com.ivy.base.model.TransactionType
import com.ivy.data.model.Category
import com.ivy.data.model.Tag
import com.ivy.data.model.TagId
import com.ivy.design.api.LocalTimeConverter
import com.ivy.design.l0_system.Orange
import com.ivy.design.l0_system.UI
import com.ivy.design.l0_system.style
@@ -42,11 +44,10 @@ import com.ivy.legacy.IvyWalletPreview
import com.ivy.legacy.data.EditTransactionDisplayLoan
import com.ivy.legacy.datamodel.Account
import com.ivy.legacy.ivyWalletCtx
import com.ivy.legacy.rootView
import com.ivy.legacy.ui.component.edit.TransactionDateTime
import com.ivy.legacy.ui.component.edit.core.Description
import com.ivy.legacy.ui.component.tags.AddTagButton
import com.ivy.legacy.ui.component.tags.ShowTagModal
import com.ivy.legacy.utils.convertUTCtoLocal
import com.ivy.legacy.utils.onScreenStart
import com.ivy.navigation.EditPlannedScreen
import com.ivy.navigation.EditTransactionScreen
@@ -55,11 +56,10 @@ import com.ivy.navigation.navigation
import com.ivy.navigation.screenScopedViewModel
import com.ivy.ui.R
import com.ivy.wallet.domain.data.CustomExchangeRateState
import com.ivy.wallet.domain.data.IvyCurrency
import com.ivy.wallet.domain.deprecated.logic.model.CreateAccountData
import com.ivy.wallet.domain.deprecated.logic.model.CreateCategoryData
import com.ivy.wallet.ui.edit.core.Category
import com.ivy.legacy.ui.component.edit.core.Description
import com.ivy.wallet.domain.data.IvyCurrency
import com.ivy.wallet.ui.edit.core.DueDate
import com.ivy.wallet.ui.edit.core.EditBottomSheet
import com.ivy.wallet.ui.edit.core.Title
@@ -83,9 +83,11 @@ import kotlinx.collections.immutable.ImmutableList
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

@@ -99,7 +101,7 @@ fun BoxWithConstraintsScope.EditTransactionScreen(screen: EditTransactionScreen)
viewModel.start(screen)
}

val view = rootView()
val view = LocalView.current

UI(
screen = screen,
@@ -124,62 +126,62 @@ fun BoxWithConstraintsScope.EditTransactionScreen(screen: EditTransactionScreen)
transactionAssociatedTags = uiState.transactionAssociatedTags,
hasChanges = uiState.hasChanges,
onSetDate = {
viewModel.onEvent(EditTransactionEvent.OnSetDate(it))
viewModel.onEvent(EditTransactionViewEvent.OnSetDate(it))
},
onSetTime = {
viewModel.onEvent(EditTransactionEvent.OnSetTime(it))
viewModel.onEvent(EditTransactionViewEvent.OnSetTime(it))
},
onTitleChange = {
viewModel.onEvent(EditTransactionEvent.OnTitleChanged(it))
viewModel.onEvent(EditTransactionViewEvent.OnTitleChanged(it))
},
onDescriptionChange = {
viewModel.onEvent(EditTransactionEvent.OnDescriptionChanged(it))
viewModel.onEvent(EditTransactionViewEvent.OnDescriptionChanged(it))
},
onAmountChange = {
viewModel.onEvent(EditTransactionEvent.OnAmountChanged(it))
viewModel.onEvent(EditTransactionViewEvent.OnAmountChanged(it))
},
onCategoryChange = {
viewModel.onEvent(EditTransactionEvent.OnCategoryChanged(it))
viewModel.onEvent(EditTransactionViewEvent.OnCategoryChanged(it))
},
onAccountChange = {
viewModel.onEvent(EditTransactionEvent.OnAccountChanged(it))
viewModel.onEvent(EditTransactionViewEvent.OnAccountChanged(it))
},
onToAccountChange = {
viewModel.onEvent(EditTransactionEvent.OnToAccountChanged(it))
viewModel.onEvent(EditTransactionViewEvent.OnToAccountChanged(it))
},
onDueDateChange = {
viewModel.onEvent(EditTransactionEvent.OnDueDateChanged(it))
viewModel.onEvent(EditTransactionViewEvent.OnDueDateChanged(it))
},
onSetTransactionType = {
viewModel.onEvent(EditTransactionEvent.OnSetTransactionType(it))
viewModel.onEvent(EditTransactionViewEvent.OnSetTransactionType(it))
},
onCreateCategory = {
viewModel.onEvent(EditTransactionEvent.CreateCategory(it))
viewModel.onEvent(EditTransactionViewEvent.CreateCategory(it))
},
onEditCategory = {
viewModel.onEvent(EditTransactionEvent.EditCategory(it))
viewModel.onEvent(EditTransactionViewEvent.EditCategory(it))
},
onPayPlannedPayment = {
viewModel.onEvent(EditTransactionEvent.OnPayPlannedPayment)
viewModel.onEvent(EditTransactionViewEvent.OnPayPlannedPayment)
},
onSave = {
view.hideKeyboard()
viewModel.onEvent(EditTransactionEvent.Save(it))
viewModel.onEvent(EditTransactionViewEvent.Save(it))
},
onSetHasChanges = {
viewModel.onEvent(EditTransactionEvent.SetHasChanges(it))
viewModel.onEvent(EditTransactionViewEvent.SetHasChanges(it))
},
onDelete = {
viewModel.onEvent(EditTransactionEvent.Delete)
viewModel.onEvent(EditTransactionViewEvent.Delete)
},
onDuplicate = {
viewModel.onEvent(EditTransactionEvent.Duplicate)
viewModel.onEvent(EditTransactionViewEvent.Duplicate)
},
onCreateAccount = {
viewModel.onEvent(EditTransactionEvent.CreateAccount(it))
viewModel.onEvent(EditTransactionViewEvent.CreateAccount(it))
},
onExchangeRateChange = {
viewModel.onEvent(EditTransactionEvent.UpdateExchangeRate(it))
viewModel.onEvent(EditTransactionViewEvent.UpdateExchangeRate(it))
},
onTagOperation = {
viewModel.onEvent(it)
@@ -198,10 +200,10 @@ private fun BoxWithConstraintsScope.UI(
titleSuggestions: ImmutableSet<String>,
description: String?,
category: Category?,
dateTime: LocalDateTime?,
dateTime: Instant?,
account: Account?,
toAccount: Account?,
dueDate: LocalDateTime?,
dueDate: Instant?,
amount: Double,

customExchangeRateState: CustomExchangeRateState,
@@ -229,7 +231,7 @@ private fun BoxWithConstraintsScope.UI(
onDuplicate: () -> Unit,
onCreateAccount: (CreateAccountData) -> Unit,
onExchangeRateChange: (Double?) -> Unit = { },
onTagOperation: (EditTransactionEvent.TagEvent) -> Unit = {},
onTagOperation: (EditTransactionViewEvent.TagEvent) -> Unit = {},
loanData: EditTransactionDisplayLoan = EditTransactionDisplayLoan(),
backgroundProcessing: Boolean = false,
hasChanges: Boolean = false,
@@ -356,10 +358,13 @@ private fun BoxWithConstraintsScope.UI(

val ivyContext = ivyWalletCtx()

val timeConverter = LocalTimeConverter.current
if (dueDate != null) {
DueDate(dueDate = dueDate) {
ivyContext.datePicker(
initialDate = dueDate.toLocalDate()
initialDate = with(timeConverter) {
dueDate.toLocalDate()
}
) {
onDueDateChange(it.atTime(12, 0))
}
@@ -379,14 +384,18 @@ private fun BoxWithConstraintsScope.UI(
dueDateTime = dueDate,
onEditDate = {
ivyContext.datePicker(
initialDate = dateTime?.convertUTCtoLocal()?.toLocalDate()
initialDate = with(timeConverter) {
dateTime?.toLocalDate()
}
) { date ->
onSetDate((date))
}
},
onEditTime = {
ivyContext.timePicker(
initialTime = dateTime?.toLocalTime()
initialTime = with(timeConverter) {
dateTime?.toLocalTime()
}
) { time ->
onSetTime(time)
}
@@ -626,27 +635,27 @@ private fun BoxWithConstraintsScope.UI(
onDismiss = {
tagModelVisible = false
// Reset TagList, avoids showing incorrect tag list when user has searched for a tag
onTagOperation(EditTransactionEvent.TagEvent.OnTagSearch(""))
onTagOperation(EditTransactionViewEvent.TagEvent.OnTagSearch(""))
},
allTagList = tags,
selectedTagList = transactionAssociatedTags,
onTagAdd = {
onTagOperation(EditTransactionEvent.TagEvent.SaveTag(name = it))
onTagOperation(EditTransactionViewEvent.TagEvent.SaveTag(name = it))
},
onTagEdit = { oldTag, newTag ->
onTagOperation(EditTransactionEvent.TagEvent.OnTagEdit(oldTag, newTag))
onTagOperation(EditTransactionViewEvent.TagEvent.OnTagEdit(oldTag, newTag))
},
onTagDelete = {
onTagOperation(EditTransactionEvent.TagEvent.OnTagDelete(it))
onTagOperation(EditTransactionViewEvent.TagEvent.OnTagDelete(it))
},
onTagSelected = {
onTagOperation(EditTransactionEvent.TagEvent.OnTagSelect(it))
onTagOperation(EditTransactionViewEvent.TagEvent.OnTagSelect(it))
},
onTagDeSelected = {
onTagOperation(EditTransactionEvent.TagEvent.OnTagDeSelect(it))
onTagOperation(EditTransactionViewEvent.TagEvent.OnTagDeSelect(it))
},
onTagSearch = {
onTagOperation(EditTransactionEvent.TagEvent.OnTagSearch(it))
onTagOperation(EditTransactionViewEvent.TagEvent.OnTagSearch(it))
}
)
}
@@ -664,6 +673,7 @@ private fun shouldFocusAmount(amount: Double) = amount == 0.0

/** For Preview purpose **/
private val testDateTime = LocalDateTime.of(2023, 4, 27, 0, 35)
.toInstant(ZoneOffset.UTC)

@ExperimentalFoundationApi
@Preview

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,38 +1,68 @@
package com.ivy.transaction

import androidx.compose.runtime.Immutable
import com.ivy.base.model.TransactionType
import com.ivy.data.model.Category
import com.ivy.data.model.Tag
import com.ivy.data.model.TagId
import com.ivy.legacy.data.EditTransactionDisplayLoan
import com.ivy.legacy.datamodel.Account
import com.ivy.wallet.domain.data.CustomExchangeRateState
import com.ivy.wallet.domain.deprecated.logic.model.CreateAccountData
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

sealed interface EditTransactionEvent {
data class OnAmountChanged(val newAmount: Double) : EditTransactionEvent
data class OnTitleChanged(val newTitle: String?) : EditTransactionEvent
data class OnDescriptionChanged(val newDescription: String?) : EditTransactionEvent
data class OnCategoryChanged(val newCategory: Category?) : EditTransactionEvent
data class OnAccountChanged(val newAccount: Account) : EditTransactionEvent
data class OnToAccountChanged(val newAccount: Account) : EditTransactionEvent
data class OnDueDateChanged(val newDueDate: LocalDateTime?) : EditTransactionEvent
data class OnSetDateTime(val newDateTime: LocalDateTime) : EditTransactionEvent
data class OnSetDate(val newDate: LocalDate) : EditTransactionEvent
data class OnSetTime(val newTime: LocalTime) : EditTransactionEvent
data class OnSetTransactionType(val newTransactionType: TransactionType) : EditTransactionEvent
data object OnPayPlannedPayment : EditTransactionEvent
data object Delete : EditTransactionEvent
data object Duplicate : EditTransactionEvent
data class CreateCategory(val data: CreateCategoryData) : EditTransactionEvent
data class EditCategory(val updatedCategory: Category) : EditTransactionEvent
data class CreateAccount(val data: CreateAccountData) : EditTransactionEvent
data class Save(val closeScreen: Boolean) : EditTransactionEvent
data class SetHasChanges(val hasChangesValue: Boolean) : EditTransactionEvent
data class UpdateExchangeRate(val exRate: Double?) : EditTransactionEvent
@Immutable
data class EditTransactionViewState(
val transactionType: TransactionType,
val initialTitle: String?,
val titleSuggestions: ImmutableSet<String>,
val currency: String,
val description: String?,
val dateTime: Instant?,
val dueDate: Instant?,
val accounts: ImmutableList<Account>,
val categories: ImmutableList<Category>,
val account: Account?,
val toAccount: Account?,
val category: Category?,
val amount: Double,
val hasChanges: Boolean,
val displayLoanHelper: EditTransactionDisplayLoan,
val backgroundProcessingStarted: Boolean,
val customExchangeRateState: CustomExchangeRateState,
val tags: ImmutableList<Tag>,
val transactionAssociatedTags: ImmutableList<TagId>
)

sealed interface TagEvent : EditTransactionEvent {
sealed interface EditTransactionViewEvent {
data class OnAmountChanged(val newAmount: Double) : EditTransactionViewEvent
data class OnTitleChanged(val newTitle: String?) : EditTransactionViewEvent
data class OnDescriptionChanged(val newDescription: String?) : EditTransactionViewEvent
data class OnCategoryChanged(val newCategory: Category?) : 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 OnPayPlannedPayment : EditTransactionViewEvent
data object Delete : EditTransactionViewEvent
data object Duplicate : EditTransactionViewEvent
data class CreateCategory(val data: CreateCategoryData) : EditTransactionViewEvent
data class EditCategory(val updatedCategory: Category) : EditTransactionViewEvent
data class CreateAccount(val data: CreateAccountData) : EditTransactionViewEvent
data class Save(val closeScreen: Boolean) : EditTransactionViewEvent
data class SetHasChanges(val hasChangesValue: Boolean) : EditTransactionViewEvent
data class UpdateExchangeRate(val exRate: Double?) : EditTransactionViewEvent

sealed interface TagEvent : EditTransactionViewEvent {
data class SaveTag(val name: String) : TagEvent
data class OnTagSelect(val selectedTag: Tag) : TagEvent
data class OnTagDeSelect(val selectedTag: Tag) : TagEvent
Original file line number Diff line number Diff line change
@@ -35,10 +35,7 @@ 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.dateNowLocal
import com.ivy.legacy.utils.getTrueDate
import com.ivy.legacy.utils.ioThread
import com.ivy.legacy.utils.timeUTC
import com.ivy.legacy.utils.toLowerCaseLocal
import com.ivy.legacy.utils.uiThread
import com.ivy.navigation.EditTransactionScreen
@@ -71,10 +68,9 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.async
import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.launch
import java.math.BigDecimal
import java.time.Instant
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.LocalTime
@@ -110,18 +106,15 @@ class EditTransactionViewModel @Inject constructor(
private val features: Features,
private val timeConverter: TimeConverter,
private val timeProvider: TimeProvider,
) : ComposeViewModel<EditTransactionState, EditTransactionEvent>() {
) : ComposeViewModel<EditTransactionViewState, EditTransactionViewEvent>() {

private val transactionType = mutableStateOf(TransactionType.EXPENSE)
private val initialTitle = mutableStateOf<String?>(null)
private val titleSuggestions = mutableStateOf(persistentSetOf<String>())
private val currency = mutableStateOf("")
private val description = mutableStateOf<String?>(null)
private val dateTime = mutableStateOf<LocalDateTime?>(null)
private val dueDate = mutableStateOf<LocalDateTime?>(null)
private val paidHistory = mutableStateOf<LocalDateTime?>(null)
private val date = MutableStateFlow<LocalDate?>(null)
private val time = MutableStateFlow<LocalTime?>(null)
private val dateTime = mutableStateOf<Instant?>(null)
private val dueDate = mutableStateOf<Instant?>(null)
private val accounts = mutableStateOf<ImmutableList<Account>>(persistentListOf())
private val categories = mutableStateOf<ImmutableList<Category>>(persistentListOf())
private val tags = mutableStateOf<ImmutableList<Tag>>(persistentListOf())
@@ -133,6 +126,8 @@ class EditTransactionViewModel @Inject constructor(
private val hasChanges = mutableStateOf(false)
private val displayLoanHelper = mutableStateOf(EditTransactionDisplayLoan())

private var paidHistory: Instant? = null

// This is used to when the transaction is associated with a loan/loan record,
// used to indicate the background updating of loan/loanRecord data
private val backgroundProcessingStarted = mutableStateOf(false)
@@ -192,8 +187,8 @@ class EditTransactionViewModel @Inject constructor(
}

@Composable
override fun uiState(): EditTransactionState {
return EditTransactionState(
override fun uiState(): EditTransactionViewState {
return EditTransactionViewState(
transactionType = getTransactionType(),
initialTitle = getInitialTitle(),
titleSuggestions = getTitleSuggestions(),
@@ -242,12 +237,12 @@ class EditTransactionViewModel @Inject constructor(
}

@Composable
private fun getDateTime(): LocalDateTime? {
private fun getDateTime(): Instant? {
return dateTime.value
}

@Composable
private fun getDueDate(): LocalDateTime? {
private fun getDueDate(): Instant? {
return dueDate.value
}

@@ -311,40 +306,45 @@ class EditTransactionViewModel @Inject constructor(
return transactionAssociatedTags.value
}

override fun onEvent(event: EditTransactionEvent) {
@Suppress("CyclomaticComplexMethod")
override fun onEvent(event: EditTransactionViewEvent) {
when (event) {
is EditTransactionEvent.CreateAccount -> createAccount(event.data)
is EditTransactionEvent.CreateCategory -> createCategory(event.data)
EditTransactionEvent.Delete -> delete()
EditTransactionEvent.Duplicate -> duplicate()
is EditTransactionEvent.EditCategory -> editCategory(event.updatedCategory)
is EditTransactionEvent.OnAccountChanged -> onAccountChanged(event.newAccount)
is EditTransactionEvent.OnAmountChanged -> onAmountChanged(event.newAmount)
is EditTransactionEvent.OnCategoryChanged -> onCategoryChanged(event.newCategory)
is EditTransactionEvent.OnDescriptionChanged ->
is EditTransactionViewEvent.CreateAccount -> createAccount(event.data)
is EditTransactionViewEvent.CreateCategory -> createCategory(event.data)
EditTransactionViewEvent.Delete -> delete()
EditTransactionViewEvent.Duplicate -> duplicate()
is EditTransactionViewEvent.EditCategory -> editCategory(event.updatedCategory)
is EditTransactionViewEvent.OnAccountChanged -> onAccountChanged(event.newAccount)
is EditTransactionViewEvent.OnAmountChanged -> onAmountChanged(event.newAmount)
is EditTransactionViewEvent.OnCategoryChanged -> onCategoryChanged(event.newCategory)
is EditTransactionViewEvent.OnDescriptionChanged ->
onDescriptionChanged(event.newDescription)

is EditTransactionEvent.OnDueDateChanged -> onDueDateChanged(event.newDueDate)
EditTransactionEvent.OnPayPlannedPayment -> onPayPlannedPayment()
is EditTransactionEvent.OnSetDateTime -> onSetDateTime(event.newDateTime)
is EditTransactionEvent.OnSetDate -> onSetDate(event.newDate)
is EditTransactionEvent.OnSetTime -> onSetTime(event.newTime)
is EditTransactionEvent.OnSetTransactionType ->
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.OnSetTransactionType ->
onSetTransactionType(event.newTransactionType)

is EditTransactionEvent.OnTitleChanged -> onTitleChanged(event.newTitle)
is EditTransactionEvent.OnToAccountChanged -> onToAccountChanged(event.newAccount)
is EditTransactionEvent.Save -> save(event.closeScreen)
is EditTransactionEvent.SetHasChanges -> setHasChanges(event.hasChangesValue)
is EditTransactionEvent.UpdateExchangeRate -> updateExchangeRate(event.exRate)
is EditTransactionEvent.TagEvent -> when (event) {
is EditTransactionEvent.TagEvent.SaveTag -> onTagSaved(event.name)
is EditTransactionEvent.TagEvent.OnTagSelect -> associateTagToTransaction(event.selectedTag)
is EditTransactionEvent.TagEvent.OnTagDeSelect -> removeTagAssociation(event.selectedTag)
is EditTransactionEvent.TagEvent.OnTagSearch -> searchTag(event.query)
is EditTransactionEvent.TagEvent.OnTagDelete -> deleteTag(event.selectedTag)
is EditTransactionEvent.TagEvent.OnTagEdit -> updateTagInformation(event.newTag)
}
is EditTransactionViewEvent.OnTitleChanged -> onTitleChanged(event.newTitle)
is EditTransactionViewEvent.OnToAccountChanged -> onToAccountChanged(event.newAccount)
is EditTransactionViewEvent.Save -> save(event.closeScreen)
is EditTransactionViewEvent.SetHasChanges -> setHasChanges(event.hasChangesValue)
is EditTransactionViewEvent.UpdateExchangeRate -> updateExchangeRate(event.exRate)
is EditTransactionViewEvent.TagEvent -> handleTagEvent(event)
}
}

private fun handleTagEvent(event: EditTransactionViewEvent.TagEvent) {
when (event) {
is EditTransactionViewEvent.TagEvent.SaveTag -> onTagSaved(event.name)
is EditTransactionViewEvent.TagEvent.OnTagSelect -> associateTagToTransaction(event.selectedTag)
is EditTransactionViewEvent.TagEvent.OnTagDeSelect -> removeTagAssociation(event.selectedTag)
is EditTransactionViewEvent.TagEvent.OnTagSearch -> searchTag(event.query)
is EditTransactionViewEvent.TagEvent.OnTagDelete -> deleteTag(event.selectedTag)
is EditTransactionViewEvent.TagEvent.OnTagEdit -> updateTagInformation(event.newTag)
}
}

@@ -378,10 +378,10 @@ class EditTransactionViewModel @Inject constructor(

transactionType.value = transaction.type
initialTitle.value = transaction.title
dateTime.value = with(timeConverter) { transaction.dateTime?.toLocalDateTime() }
dateTime.value = transaction.dateTime
description.value = transaction.description
dueDate.value = with(timeConverter) { transaction.dueDate?.toLocalDateTime() }
paidHistory.value = with(timeConverter) { transaction.paidFor?.toLocalDateTime() }
dueDate.value = transaction.dueDate
paidHistory = transaction.paidFor
val selectedAccount = accountByIdAct(transaction.accountId)!!
account.value = selectedAccount
toAccount.value = transaction.toAccountId?.let {
@@ -527,19 +527,21 @@ class EditTransactionViewModel @Inject constructor(
}

private fun onDueDateChanged(newDueDate: LocalDateTime?) {
val newDueDateUtc = with(timeConverter) { newDueDate?.toUTC() }
loadedTransaction = loadedTransaction().copy(
dueDate = with(timeConverter) { newDueDate?.toUTC() }
dueDate = newDueDateUtc
)
dueDate.value = newDueDate
dueDate.value = newDueDateUtc

saveIfEditMode()
}

private fun onSetDateTime(newDateTime: LocalDateTime) {
val newDateTimeUtc = with(timeConverter) { newDateTime.toUTC() }
loadedTransaction = loadedTransaction().copy(
dateTime = with(timeConverter) { newDateTime.toUTC() }
dateTime = newDateTimeUtc
)
dateTime.value = newDateTime
dateTime.value = newDateTimeUtc

saveIfEditMode()
}
@@ -548,27 +550,30 @@ class EditTransactionViewModel @Inject constructor(
loadedTransaction = loadedTransaction().copy(
date = newDate
)
date.value = newDate
val localDateTime = with(timeConverter) {
(dateTime.value ?: timeProvider.utcNow()).toLocalDateTime()
}
onSetDateTime(
getTrueDate(
loadedTransaction?.date ?: dateNowLocal(),
(dateTime.value?.toLocalTime() ?: timeUTC()),
true
)
localDateTime
.withDayOfMonth(newDate.dayOfMonth)
.withMonth(newDate.monthValue)
.withYear(newDate.year)
)
}

private fun onSetTime(newTime: LocalTime) {
loadedTransaction = loadedTransaction().copy(
time = newTime.convertUTCToLocal()
)
time.value = newTime
val localDateTime = with(timeConverter) {
(dateTime.value ?: timeProvider.utcNow()).toLocalDateTime()
}
onSetDateTime(
getTrueDate(
dateTime.value?.toLocalDate() ?: dateNowLocal(),
loadedTransaction?.time ?: timeUTC(),
true
)
localDateTime
.withHour(newTime.hour)
.withMinute(newTime.minute)
.withSecond(0)
.withNano(0)
)
}

@@ -587,10 +592,9 @@ class EditTransactionViewModel @Inject constructor(
syncTransaction = false
) { paidTransaction ->
loadedTransaction = paidTransaction
paidHistory.value =
with(timeConverter) { paidTransaction.paidFor?.toLocalDateTime() }
dueDate.value = with(timeConverter) { paidTransaction.dueDate?.toLocalDateTime() }
dateTime.value = with(timeConverter) { paidTransaction.dateTime?.toLocalDateTime() }
paidHistory = paidTransaction.paidFor
dueDate.value = paidTransaction.dueDate
dateTime.value = paidTransaction.dateTime

saveIfEditMode(
closeScreen = true
@@ -702,8 +706,8 @@ class EditTransactionViewModel @Inject constructor(
description = description.value?.trim(),
amount = amount,
type = transactionType.value,
dueDate = with(timeConverter) { dueDate.value?.toUTC() },
paidFor = with(timeConverter) { paidHistory.value?.toUTC() },
dueDate = dueDate.value,
paidFor = paidHistory,
dateTime = when {
loadedTransaction().dateTime == null &&
dueDate.value == null -> {
@@ -961,6 +965,6 @@ class EditTransactionViewModel @Inject constructor(
}

private suspend fun shouldSortCategoriesAlphabetically(): Boolean {
return features.sortCategoriesAlphabetically.enabled(context).firstOrNull() ?: false
return features.sortCategoriesAlphabetically.isEnabled(context)
}
}
Original file line number Diff line number Diff line change
@@ -51,7 +51,7 @@ class FeaturesViewModel @Inject constructor(
private fun toggleFeature(event: FeaturesUiEvent.ToggleFeature) {
viewModelScope.launch {
val feature = features.allFeatures[event.index]
val enabled = feature.enabled(context).first() ?: false
val enabled = feature.enabledFlow(context).first() ?: false
feature.set(context, !enabled)
}
}
2 changes: 2 additions & 0 deletions shared/base/src/main/java/com/ivy/base/time/TimeConverter.kt
Original file line number Diff line number Diff line change
@@ -3,10 +3,12 @@ package com.ivy.base.time
import java.time.Instant
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.LocalTime

interface TimeConverter {
fun Instant.toLocalDateTime(): LocalDateTime
fun Instant.toLocalDate(): LocalDate
fun Instant.toLocalTime(): LocalTime

fun LocalDateTime.toUTC(): Instant
}
Original file line number Diff line number Diff line change
@@ -8,6 +8,7 @@ import java.time.DateTimeException
import java.time.Instant
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.LocalTime
import java.time.ZoneOffset
import javax.inject.Inject

@@ -43,4 +44,6 @@ class StandardTimeConverter @Inject constructor(
val zoneId = timeZoneProvider.getZoneId()
return this.atZone(zoneId).toInstant()
}

override fun Instant.toLocalTime(): LocalTime = toLocalDateTime().toLocalTime()
}
Original file line number Diff line number Diff line change
@@ -11,6 +11,7 @@ import androidx.datastore.preferences.core.edit
import com.ivy.data.datastore.DatastoreKeys
import com.ivy.data.datastore.dataStore
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map

@Immutable
@@ -22,16 +23,18 @@ class BoolFeature(
@Composable
fun asEnabledState(): Boolean {
val context = LocalContext.current
val featureFlag = remember { enabled(context) }
val featureFlag = remember { enabledFlow(context) }
.collectAsState(false).value
return featureFlag ?: false
}

fun enabled(appContext: Context): Flow<Boolean?> {
return appContext.dataStore.data.map {
suspend fun isEnabled(appContext: Context): Boolean =
enabledFlow(appContext).first() ?: false

fun enabledFlow(appContext: Context): Flow<Boolean?> = appContext.dataStore
.data.map {
it[featureKey]
}
}

suspend fun set(appContext: Context, enabled: Boolean) {
appContext.dataStore.edit {
Original file line number Diff line number Diff line change
@@ -9,13 +9,17 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.ivy.legacy.ivyWalletCtx
import com.ivy.legacy.utils.convertLocalToUTC
import com.ivy.legacy.utils.convertUTCToLocal
import com.ivy.legacy.utils.convertUTCtoLocal
import com.ivy.legacy.utils.formatLocalTime
import com.ivy.legacy.utils.formatNicely
import com.ivy.legacy.utils.getTrueDate
import com.ivy.legacy.utils.timeNowUTC
import com.ivy.ui.R
import com.ivy.wallet.ui.theme.components.IvyOutlinedButton
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.LocalTime

@Composable
fun DateTimeRow(
@@ -57,4 +61,25 @@ fun DateTimeRow(

Spacer(Modifier.width(24.dp))
}
}

// The timepicker returns time in UTC, but the date picker returns date in LocalTimeZone
// hence use this method to get both date & time in UTC
@Deprecated("Rework this to use the TimeConverter API")
fun getTrueDate(
date: LocalDate,
time: LocalTime,
convert: Boolean = true
): LocalDateTime {
val timeLocal = if (convert) time.convertUTCToLocal() else time

return timeNowUTC()
.withYear(date.year)
.withMonth(date.monthValue)
.withDayOfMonth(date.dayOfMonth)
.withHour(timeLocal.hour)
.withMinute(timeLocal.minute)
.withSecond(0)
.withNano(0)
.convertLocalToUTC()
}
Original file line number Diff line number Diff line change
@@ -17,23 +17,23 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.ivy.design.api.LocalTimeConverter
import com.ivy.design.api.LocalTimeFormatter
import com.ivy.design.api.LocalTimeProvider
import com.ivy.design.l0_system.UI
import com.ivy.design.l0_system.style
import com.ivy.legacy.IvyWalletComponentPreview
import com.ivy.legacy.utils.formatNicely
import com.ivy.legacy.utils.formatTimeOnly
import com.ivy.legacy.utils.timeNowLocal
import com.ivy.legacy.utils.timeNowUTC
import com.ivy.ui.R
import com.ivy.ui.time.TimeFormatter
import com.ivy.wallet.ui.theme.components.IvyIcon
import java.time.LocalDateTime
import java.time.Instant

@Suppress("MultipleEmitters")
@Deprecated("Old design system. Use `:ivy-design` and Material3")
@Composable
fun TransactionDateTime(
dateTime: LocalDateTime?,
dueDateTime: LocalDateTime?,
dateTime: Instant?,
dueDateTime: Instant?,
onEditDate: () -> Unit,
onEditTime: () -> Unit,
modifier: Modifier = Modifier
@@ -67,10 +67,14 @@ fun TransactionDateTime(
Spacer(Modifier.width(24.dp))
Spacer(Modifier.weight(1f))

val localDateTime = with(LocalTimeConverter.current) {
(dateTime ?: LocalTimeProvider.current.utcNow()).toLocalDateTime()
}
val timeFormatter = LocalTimeFormatter.current
Text(
text = (dateTime ?: timeNowUTC()).formatNicely(
noWeekDay = true
),
text = with(timeFormatter) {
localDateTime.format(TimeFormatter.Style.DateOnly(includeWeekDay = false))
},
style = UI.typo.nB2.style(
color = UI.colors.pureInverse,
fontWeight = FontWeight.ExtraBold
@@ -79,8 +83,11 @@ fun TransactionDateTime(
onEditDate()
}
)

Text(
text = " " + (dateTime?.formatTimeOnly() ?: timeNowLocal().formatTimeOnly()),
text = " " + with(timeFormatter) {
localDateTime.toLocalTime().format()
},
style = UI.typo.nB2.style(
color = UI.colors.pureInverse,
fontWeight = FontWeight.ExtraBold
@@ -99,7 +106,7 @@ fun TransactionDateTime(
private fun Preview() {
IvyWalletComponentPreview {
TransactionDateTime(
dateTime = timeNowUTC(),
dateTime = LocalTimeProvider.current.utcNow(),
dueDateTime = null,
onEditDate = {
},
Original file line number Diff line number Diff line change
@@ -16,19 +16,21 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.ivy.design.api.LocalTimeFormatter
import com.ivy.design.api.LocalTimeProvider
import com.ivy.design.l0_system.UI
import com.ivy.design.l0_system.style
import com.ivy.legacy.IvyWalletComponentPreview
import com.ivy.legacy.utils.formatDateOnly
import com.ivy.legacy.utils.timeNowUTC
import com.ivy.ui.R
import com.ivy.ui.time.TimeFormatter
import com.ivy.wallet.ui.theme.components.IvyIcon
import java.time.LocalDateTime
import java.time.Instant
import java.util.concurrent.TimeUnit

@Deprecated("Old design system. Use `:ivy-design` and Material3")
@Composable
fun DueDate(
dueDate: LocalDateTime,
dueDate: Instant,
onPickDueDate: () -> Unit,
) {
DueDateCard(
@@ -41,7 +43,7 @@ fun DueDate(

@Composable
private fun DueDateCard(
dueDate: LocalDateTime,
dueDate: Instant,
onClick: () -> Unit,
) {
Row(
@@ -71,7 +73,9 @@ private fun DueDateCard(
Spacer(Modifier.weight(1f))

Text(
text = dueDate.toLocalDate().formatDateOnly(),
text = with(LocalTimeFormatter.current) {
dueDate.formatLocal(TimeFormatter.Style.DateOnly(includeWeekDay = false))
},
style = UI.typo.nB2.style(
fontWeight = FontWeight.ExtraBold
)
@@ -81,12 +85,14 @@ private fun DueDateCard(
}
}

@Suppress("MagicNumber")
@Preview
@Composable
private fun Preview_OneTime() {
IvyWalletComponentPreview {
DueDate(
dueDate = timeNowUTC().plusDays(5),
dueDate = LocalTimeProvider.current.utcNow()
.plusSeconds(TimeUnit.DAYS.toSeconds(6)),
) {
}
}
28 changes: 7 additions & 21 deletions temp/legacy-code/src/main/java/com/ivy/legacy/utils/DateExt.kt
Original file line number Diff line number Diff line change
@@ -42,6 +42,7 @@ fun LocalDateTime.toEpochSeconds() = this.toEpochSecond(ZoneOffset.UTC)

fun LocalDateTime.millis() = this.toInstant(ZoneOffset.UTC).toEpochMilli()

@Deprecated("Use the TimeConverter interface via DI")
fun LocalDateTime.formatNicely(
noWeekDay: Boolean = false,
zone: ZoneId = ZoneOffset.systemDefault()
@@ -84,6 +85,7 @@ fun LocalDateTime.formatNicely(

fun LocalDateTime.getISOFormattedDateTime(): String = this.formatLocal("yyyyMMdd-HHmm")

@Deprecated("Use the TimeConverter interface via DI")
fun LocalDateTime.formatNicelyWithTime(
noWeekDay: Boolean = true,
zone: ZoneId = ZoneOffset.systemDefault()
@@ -124,25 +126,25 @@ fun LocalDateTime.formatNicelyWithTime(
}
}

@Deprecated("Use the TimeConverter interface via DI")
@Composable
fun LocalDateTime.formatLocalTime(): String {
val timeFormat = android.text.format.DateFormat.getTimeFormat(LocalContext.current)
return timeFormat.format(this.millis())
}

@Deprecated("Use the TimeConverter interface via DI")
fun LocalDate.formatDateOnly(): String = this.formatLocal("MMM. dd", ZoneOffset.systemDefault())

fun LocalDateTime.formatTimeOnly(): String = this.format(DateTimeFormatter.ofPattern("HH:mm"))

@Deprecated("Use the TimeConverter interface via DI")
fun LocalDate.formatDateOnlyWithYear(): String =
this.formatLocal("dd MMM, yyyy", ZoneOffset.systemDefault())

fun LocalDate.formatDateWeekDay(): String =
this.formatLocal("EEE, dd MMM", ZoneOffset.systemDefault())

@Deprecated("Use the TimeConverter interface via DI")
fun LocalDate.formatDateWeekDayLong(): String =
this.formatLocal("EEEE, dd MMM", ZoneOffset.systemDefault())

@Deprecated("Use the TimeConverter interface via DI")
fun LocalDate.formatNicely(
pattern: String = "EEE, dd MMM",
patternNoWeekDay: String = "dd MMM",
@@ -229,22 +231,6 @@ fun LocalDateTime.convertLocalToUTC(): LocalDateTime {
return this.minusSeconds(offset)
}

// The timepicker returns time in UTC, but the date picker returns date in LocalTimeZone
// hence use this method to get both date & time in UTC
fun getTrueDate(date: LocalDate, time: LocalTime, convert: Boolean = true): LocalDateTime {
val timeLocal = if (convert) time.convertUTCToLocal() else time

return timeNowUTC()
.withYear(date.year)
.withMonth(date.monthValue)
.withDayOfMonth(date.dayOfMonth)
.withHour(timeLocal.hour)
.withMinute(timeLocal.minute)
.withSecond(0)
.withNano(0)
.convertLocalToUTC()
}

fun LocalDate.formatLocal(
pattern: String = "dd MMM yyyy",
zone: ZoneId = ZoneOffset.systemDefault()
Original file line number Diff line number Diff line change
@@ -23,7 +23,6 @@ fun hideKeyboard() {
LocalView.current.hideKeyboard()
}

@Deprecated("Old design system. Use `:ivy-design` and Material3")
fun View.hideKeyboard() {
val imm: InputMethodManager =
context.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager

0 comments on commit f461c4f

Please sign in to comment.