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

Commit

Permalink
WIP: Re-write EditTransaction screen
Browse files Browse the repository at this point in the history
  • Loading branch information
ILIYANGERMANOV committed May 28, 2022
1 parent 16dc98c commit c8ab7e5
Show file tree
Hide file tree
Showing 7 changed files with 245 additions and 11 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.ivy.wallet.domain.action.viewmodel.transaction

import com.ivy.frp.action.FPAction
import com.ivy.frp.then
import com.ivy.wallet.domain.data.core.Transaction
import com.ivy.wallet.io.persistence.dao.TransactionDao
import javax.inject.Inject

class SaveTransactionLocallyAct @Inject constructor(
private val transactionDao: TransactionDao
) : FPAction<Transaction, Unit>() {
override suspend fun Transaction.compose(): suspend () -> Unit = {
this.copy(
isSynced = false
).toEntity()
} then transactionDao::save
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@ import androidx.compose.foundation.layout.BoxWithConstraintsScope
import androidx.compose.runtime.Composable
import com.ivy.frp.view.navigation.Screen
import com.ivy.wallet.domain.data.TransactionType
import com.ivy.wallet.domain.data.core.Account
import com.ivy.wallet.domain.data.core.Category
import com.ivy.wallet.domain.data.core.Transaction
import com.ivy.wallet.ui.architecture.FRP

sealed class TransactionScreen : Screen {
data class NewTransaction(
val type: TransactionType
val type: TransactionType,
val account: Account?,
val category: Category?
) : TransactionScreen()

data class EditTransaction(
Expand All @@ -22,7 +26,11 @@ fun BoxWithConstraintsScope.TransactionScreen(screen: TransactionScreen) {
FRP<TrnState, TrnEvent, TransactionViewModel>(
initialEvent = when (screen) {
is TransactionScreen.EditTransaction -> TrnEvent.LoadTransaction(screen.transaction)
is TransactionScreen.NewTransaction -> TrnEvent.NewTransaction(screen.type)
is TransactionScreen.NewTransaction -> TrnEvent.NewTransaction(
type = screen.type,
account = screen.account,
category = screen.category
)
}
) { state, onEvent ->
UI(state = state, onEvent = onEvent)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,136 @@
package com.ivy.wallet.ui.transaction

import arrow.core.NonEmptyList
import com.ivy.frp.monad.Res
import com.ivy.frp.monad.mapError
import com.ivy.frp.monad.mapSuccess
import com.ivy.frp.then
import com.ivy.frp.thenInvokeAfter
import com.ivy.frp.viewmodel.FRPViewModel
import com.ivy.wallet.domain.action.account.AccountsAct
import com.ivy.wallet.domain.action.category.CategoriesAct
import com.ivy.wallet.domain.action.viewmodel.transaction.SaveTransactionLocallyAct
import com.ivy.wallet.domain.data.TransactionType
import com.ivy.wallet.domain.data.core.Transaction
import com.ivy.wallet.ui.transaction.data.TrnDate
import com.ivy.wallet.utils.timeNowUTC
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import java.math.BigDecimal
import java.util.*
import javax.inject.Inject

@HiltViewModel
class TransactionViewModel @Inject constructor(

private val accountsAct: AccountsAct,
private val categoriesAct: CategoriesAct,
private val saveTransactionLocallyAct: SaveTransactionLocallyAct
) : FRPViewModel<TrnState, TrnEvent>() {
override val _state: MutableStateFlow<TrnState> = MutableStateFlow(TrnState.Initial)

override suspend fun handleEvent(event: TrnEvent): suspend () -> TrnState {
TODO("Not yet implemented")
override suspend fun handleEvent(event: TrnEvent): suspend () -> TrnState = when (event) {
is TrnEvent.NewTransaction -> newTransaction(event)
is TrnEvent.LoadTransaction -> loadTransaction(event)
is TrnEvent.AccountChanged -> TODO()
is TrnEvent.AmountChanged -> TODO()
is TrnEvent.CategoryChanged -> TODO()
is TrnEvent.DateChanged -> TODO()
is TrnEvent.DescriptionChanged -> TODO()
is TrnEvent.DueChanged -> TODO()
is TrnEvent.TitleChanged -> TODO()
is TrnEvent.ToAccountChanged -> TODO()
is TrnEvent.TypeChanged -> TODO()
is TrnEvent.SetExchangeRate -> TODO()
is TrnEvent.Save -> TODO()
TrnEvent.LoadTitleSuggestions -> TODO()
}

private suspend fun newTransaction(event: TrnEvent.NewTransaction) = ::loadRequiredData then {
if (it.first.isEmpty()) {
Res.Err("No accounts created")
} else {
Res.Ok(
Pair(NonEmptyList.fromListUnsafe(it.first), it.second)
)
}
} mapError { errMsg ->
TrnState.Invalid(message = errMsg)
} mapSuccess { (accounts, categories) ->
TrnState.NewTransaction(
type = event.type,
account = event.account ?: accounts.head,
amount = BigDecimal.ZERO,
date = TrnDate.ActualDate(timeNowUTC()),
category = event.category,
title = null,
description = null,

//TODO: Handle transfers properly
toAccount = null,
toAmount = null,
exchangeRate = null,
//TODO: Handle transfers properly

titleSuggestions = emptyList(),

accounts = accounts,
categories = categories
)
} then {
when (it) {
is Res.Ok -> it.data
is Res.Err -> it.error
}
}

private suspend fun loadTransaction(event: TrnEvent.LoadTransaction) =
::loadRequiredData then { (accounts, categories) ->
TrnState.EditTransaction(
transaction = event.transaction,

titleSuggestions = emptyList(),

accounts = accounts,
categories = categories
)
}

private suspend fun loadRequiredData() =
accountsAct thenInvokeAfter { accs ->
Pair(accs, categoriesAct(Unit))
}

private suspend fun createNewTransaction(state: TrnState.NewTransaction) = with(state) {
if (amount <= BigDecimal.ZERO) {
return@with Res.Err("Transaction's amount can NOT be zero. Must be >0!")
}

if (type == TransactionType.TRANSFER && toAccount == null) {
//TRANSFER w/o toAccount
return@with Res.Err("Transfers must have \"To\" account.")
}

Res.Ok(
Transaction(
id = UUID.randomUUID(),
amount = amount,
accountId = account.id,
type = type,
categoryId = category?.id,
title = title,
description = description,
toAccountId = toAccount?.id,
toAmount = toAmount ?: amount, //TODO: Handle properly transfers exchange rate
dateTime = (date as? TrnDate.ActualDate)?.dateTime,
dueDate = (date as? TrnDate.DueDate)?.dueDate?.atTime(12, 0),

attachmentUrl = null,

isDeleted = false,
isSynced = false,
)
)
}

private fun isEditMode(): Boolean = stateVal() is TrnState.EditTransaction
}
56 changes: 55 additions & 1 deletion app/src/main/java/com/ivy/wallet/ui/transaction/TrnEvent.kt
Original file line number Diff line number Diff line change
@@ -1,14 +1,68 @@
package com.ivy.wallet.ui.transaction

import com.ivy.wallet.domain.data.TransactionType
import com.ivy.wallet.domain.data.core.Account
import com.ivy.wallet.domain.data.core.Category
import com.ivy.wallet.domain.data.core.Transaction
import java.math.BigDecimal
import java.time.LocalDate
import java.time.LocalDateTime

sealed class TrnEvent {
data class NewTransaction(
val type: TransactionType
val type: TransactionType,
val account: Account?,
val category: Category?
) : TrnEvent()

data class LoadTransaction(
val transaction: Transaction
) : TrnEvent()

// ---------------------------------
data class AmountChanged(
val newAmount: BigDecimal
) : TrnEvent()

data class TitleChanged(
val newTitle: String
) : TrnEvent()

data class DescriptionChanged(
val newDesc: String
) : TrnEvent()

data class AccountChanged(
val newAccount: Account
) : TrnEvent()

data class CategoryChanged(
val newCategory: Category
) : TrnEvent()

data class DateChanged(
val dateTime: LocalDateTime
) : TrnEvent()

data class DueChanged(
val dueDateTime: LocalDate
) : TrnEvent()

data class TypeChanged(
val type: TransactionType
) : TrnEvent()

data class ToAccountChanged(
val account: Account
) : TrnEvent()

data class SetExchangeRate(
val exchangeRate: BigDecimal
) : TrnEvent()

//----------------------------

object Save : TrnEvent()

object LoadTitleSuggestions : TrnEvent()
}
28 changes: 23 additions & 5 deletions app/src/main/java/com/ivy/wallet/ui/transaction/TrnState.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,42 @@ import com.ivy.wallet.domain.data.TransactionType
import com.ivy.wallet.domain.data.core.Account
import com.ivy.wallet.domain.data.core.Category
import com.ivy.wallet.domain.data.core.Transaction
import com.ivy.wallet.ui.transaction.data.TrnDate
import com.ivy.wallet.ui.transaction.data.TrnExchangeRate
import java.math.BigDecimal
import java.time.LocalDateTime

sealed class TrnState {
object Initial : TrnState()

data class NewTransaction(
val type: TransactionType,
val account: Account,
val toAccount: Account?,
val amount: BigDecimal,
val category: Category?,
val dateTime: LocalDateTime,
val date: TrnDate,
val title: String?,
val description: String?
val description: String?,

//Transfers
val toAccount: Account?,
val toAmount: BigDecimal?,
val exchangeRate: TrnExchangeRate?,
//--------------------------

val titleSuggestions: List<String>,

val accounts: List<Account>,
val categories: List<Category>
) : TrnState()

data class EditTransaction(
val transaction: Transaction
val transaction: Transaction,

val titleSuggestions: List<String>,

val accounts: List<Account>,
val categories: List<Category>
) : TrnState()

data class Invalid(val message: String) : TrnState()
}
10 changes: 10 additions & 0 deletions app/src/main/java/com/ivy/wallet/ui/transaction/data/TrnDate.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.ivy.wallet.ui.transaction.data

import java.time.LocalDate
import java.time.LocalDateTime

sealed class TrnDate {
data class ActualDate(val dateTime: LocalDateTime) : TrnDate()

data class DueDate(val dueDate: LocalDate) : TrnDate()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.ivy.wallet.ui.transaction.data

import java.math.BigDecimal

data class TrnExchangeRate(
val fromCurrency: String,
val toCurrency: String,
val rate: BigDecimal
)

0 comments on commit c8ab7e5

Please sign in to comment.