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

Commit

Permalink
Exchange rates
Browse files Browse the repository at this point in the history
  • Loading branch information
ILIYANGERMANOV committed Dec 27, 2022
1 parent 7417c97 commit c9ef7b3
Show file tree
Hide file tree
Showing 16 changed files with 143 additions and 76 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package com.ivy.wallet.domain.action.exchange

import com.ivy.frp.action.Action
import com.ivy.wallet.io.network.RestClient
import com.ivy.wallet.io.persistence.dao.ExchangeRateDao
import com.ivy.wallet.io.persistence.data.ExchangeRateEntity
import timber.log.Timber
import javax.inject.Inject

class SyncExchangeRatesAct @Inject constructor(
private val exchangeRateDao: ExchangeRateDao,
restClient: RestClient
) : Action<SyncExchangeRatesAct.Input, Unit>() {
data class Input(
val baseCurrency: String,
)

private val service = restClient.exchangeRatesService

companion object {
private val URLS = listOf(
"https://cdn.jsdelivr.net/gh/fawazahmed0/currency-api@1/latest/currencies/eur.json",
"https://cdn.jsdelivr.net/gh/fawazahmed0/currency-api@1/latest/currencies/eur.min.json",
"https://raw.githubusercontent.com/fawazahmed0/currency-api/1/latest/currencies/eur.min.json",
"https://raw.githubusercontent.com/fawazahmed0/currency-api/1/latest/currencies/eur.json",
)
}

override suspend fun Input.willDo() = io {
sync(baseCurrency)
}

private suspend fun sync(baseCurrency: String) {
if (baseCurrency.isBlank()) return
val baseCurrencyLower = baseCurrency.lowercase()

var eurRates: Map<String, Double> = emptyMap()
for (url in URLS) {
eurRates = fetchEurRates(url)
if (eurRates.isNotEmpty()) break
}
if (eurRates.isEmpty()) return

// At this point we must have non-empty EUR rates
// Now we must convert them to base currency
/*
"eur": {
"bgn": 1.955902,
"usd": 1.062366,
}
*/
val eurBaseCurr = eurRates[baseCurrencyLower] ?: return

val rateEntities = eurRates.mapNotNull { (target, rate) ->
try {
val baseTargetRate = rate / eurBaseCurr
ExchangeRateEntity(
baseCurrency = baseCurrency.uppercase(),
currency = target.uppercase(),
rate = baseTargetRate
)
} catch (e: Exception) {
e.printStackTrace()
null
}
}.toList()
Timber.d("Updating exchange rates: $rateEntities")
exchangeRateDao.saveAll(rateEntities)
}

private suspend fun fetchEurRates(url: String): Map<String, Double> {
return try {
service.getExchangeRates(url).eur
} catch (e: Exception) {
e.printStackTrace()
emptyMap()
}
}
}
Original file line number Diff line number Diff line change
@@ -1,51 +1,19 @@
package com.ivy.wallet.domain.deprecated.logic.currency

import com.ivy.wallet.domain.data.core.Account
import com.ivy.wallet.domain.data.core.ExchangeRate
import com.ivy.wallet.domain.data.core.PlannedPaymentRule
import com.ivy.wallet.domain.data.core.Transaction
import com.ivy.wallet.io.network.RestClient
import com.ivy.wallet.io.network.service.CoinbaseService
import com.ivy.wallet.io.persistence.dao.AccountDao
import com.ivy.wallet.io.persistence.dao.ExchangeRateDao
import com.ivy.wallet.io.persistence.dao.SettingsDao
import com.ivy.wallet.utils.sendToCrashlytics
import java.util.*

@Deprecated("Use FP style, look into `domain.fp` package")
class ExchangeRatesLogic(
restClient: RestClient,
private val exchangeRateDao: ExchangeRateDao
) {
private val coinbaseService = restClient.coinbaseService

suspend fun sync(
baseCurrency: String
) {
try {
if (baseCurrency.isBlank()) return

val response = coinbaseService.getExchangeRates(
url = CoinbaseService.exchangeRatesUrl(
baseCurrencyCode = baseCurrency
)
)

response.data.rates.forEach { (currency, rate) ->
exchangeRateDao.save(
ExchangeRate(
baseCurrency = baseCurrency,
currency = currency,
rate = rate
).toEntity()
)
}
} catch (e: Exception) {
e.printStackTrace()
e.sendToCrashlytics("Failed to sync exchange rates")
}
}

suspend fun amountBaseCurrency(
plannedPayment: PlannedPaymentRule,
baseCurrency: String,
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/java/com/ivy/wallet/io/network/RestClient.kt
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ class RestClient private constructor(
)
}
val analyticsService: AnalyticsService by lazy { retrofit.create(AnalyticsService::class.java) }
val coinbaseService: CoinbaseService by lazy { retrofit.create(CoinbaseService::class.java) }
val exchangeRatesService: ExchangeRatesService by lazy { retrofit.create(ExchangeRatesService::class.java) }
val githubService: GithubService by lazy { retrofit.create(GithubService::class.java) }
val nukeService: NukeService by lazy { retrofit.create(NukeService::class.java) }
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.ivy.wallet.io.network.request.currency

data class ExchangeRatesResponse(
val date: String,
val eur: Map<String, Double>,
)

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.ivy.wallet.io.network.service

import com.ivy.wallet.io.network.request.currency.ExchangeRatesResponse
import retrofit2.http.GET
import retrofit2.http.Url

interface ExchangeRatesService {
@GET
suspend fun getExchangeRates(
@Url url: String,
): ExchangeRatesResponse
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import com.ivy.wallet.io.persistence.migration.*
spec = IvyRoomDatabase.DeleteSEMigration::class
)
],
version = 122,
version = 123,
exportSchema = true
)
@TypeConverters(RoomTypeConverters::class)
Expand Down Expand Up @@ -72,7 +72,8 @@ abstract class IvyRoomDatabase : RoomDatabase() {
Migration117to118_Budgets(),
Migration118to119_Loans(),
Migration119to120_LoanTransactions(),
Migration120to121_DropWishlistItem()
Migration120to121_DropWishlistItem(),
Migration122to123_ExchangeRates()
)
.build()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ interface ExchangeRateDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun save(value: ExchangeRateEntity)

@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun saveAll(value: List<ExchangeRateEntity>)

@Query("SELECT * FROM exchange_rates WHERE baseCurrency = :baseCurrency AND currency = :currency")
suspend fun findByBaseCurrencyAndCurrency(
baseCurrency: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ data class ExchangeRateEntity(
val baseCurrency: String,
val currency: String,
val rate: Double,
val manualOverride: Boolean = false,
) {
fun toDomain(): ExchangeRate = ExchangeRate(
baseCurrency = baseCurrency,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.ivy.wallet.io.persistence.migration

import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase

class Migration122to123_ExchangeRates : Migration(122, 123) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE exchange_rates ADD COLUMN manualOverride INTEGER NOT NULL DEFAULT 0")
}
}
6 changes: 4 additions & 2 deletions app/src/main/java/com/ivy/wallet/ui/home/HomeViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import com.ivy.frp.view.navigation.Navigation
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.exchange.SyncExchangeRatesAct
import com.ivy.wallet.domain.action.global.StartDayOfMonthAct
import com.ivy.wallet.domain.action.settings.CalcBufferDiffAct
import com.ivy.wallet.domain.action.settings.SettingsAct
Expand Down Expand Up @@ -62,7 +63,8 @@ class HomeViewModel @Inject constructor(
private val shouldHideBalanceAct: ShouldHideBalanceAct,
private val updateSettingsAct: UpdateSettingsAct,
private val updateAccCacheAct: UpdateAccCacheAct,
private val updateCategoriesCacheAct: UpdateCategoriesCacheAct
private val updateCategoriesCacheAct: UpdateCategoriesCacheAct,
private val syncExchangeRatesAct: SyncExchangeRatesAct,
) : FRPViewModel<HomeState, HomeEvent>() {
override val _state: MutableStateFlow<HomeState> = MutableStateFlow(
HomeState.initial(ivyWalletCtx = ivyContext)
Expand Down Expand Up @@ -306,7 +308,7 @@ class HomeViewModel @Inject constructor(
)
} then updateSettingsAct then {
//update exchange rates from POV of the new base currency
exchangeRatesLogic.sync(baseCurrency = newCurrency)
syncExchangeRatesAct(SyncExchangeRatesAct.Input(baseCurrency = newCurrency))
} then {
reload()
}
Expand Down
6 changes: 4 additions & 2 deletions app/src/main/java/com/ivy/wallet/ui/main/MainViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.ivy.frp.test.TestIdlingResource
import com.ivy.frp.view.navigation.Navigation
import com.ivy.wallet.domain.action.exchange.SyncExchangeRatesAct
import com.ivy.wallet.domain.deprecated.logic.AccountCreator
import com.ivy.wallet.domain.deprecated.logic.currency.ExchangeRatesLogic
import com.ivy.wallet.domain.deprecated.logic.model.CreateAccountData
Expand All @@ -28,6 +29,7 @@ class MainViewModel @Inject constructor(
private val nav: Navigation,
private val ivySync: IvySync,
private val exchangeRatesLogic: ExchangeRatesLogic,
private val syncExchangeRatesAct: SyncExchangeRatesAct,
private val accountCreator: AccountCreator,
private val sharedPrefs: SharedPrefs,
) : ViewModel() {
Expand Down Expand Up @@ -64,8 +66,8 @@ class MainViewModel @Inject constructor(
ivySync.sync() //sync app data

//Sync exchange rates
exchangeRatesLogic.sync(
baseCurrency = baseCurrency
syncExchangeRatesAct(
SyncExchangeRatesAct.Input(baseCurrency = baseCurrency)
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.ivy.wallet.ui.onboarding.viewmodel

import androidx.lifecycle.MutableLiveData
import com.ivy.frp.view.navigation.Navigation
import com.ivy.wallet.domain.action.exchange.SyncExchangeRatesAct
import com.ivy.wallet.domain.data.IvyCurrency
import com.ivy.wallet.domain.data.analytics.AnalyticsEvent
import com.ivy.wallet.domain.data.core.Category
Expand Down Expand Up @@ -46,7 +47,8 @@ class OnboardingRouter(
private val ivySync: IvySync,
private val preloadDataLogic: PreloadDataLogic,
private val categoryDao: CategoryDao,
private val logoutLogic: LogoutLogic
private val logoutLogic: LogoutLogic,
private val syncExchangeRatesAct: SyncExchangeRatesAct,
) {

var isLoginCache = false
Expand Down Expand Up @@ -266,8 +268,10 @@ class OnboardingRouter(
ioThread {
transactionReminderLogic.scheduleReminder()

exchangeRatesLogic.sync(
baseCurrency = baseCurrency?.code ?: IvyCurrency.getDefault().code
syncExchangeRatesAct(
SyncExchangeRatesAct.Input(
baseCurrency = baseCurrency?.code ?: IvyCurrency.getDefault().code
)
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import com.ivy.frp.test.TestIdlingResource
import com.ivy.frp.view.navigation.Navigation
import com.ivy.wallet.domain.action.account.AccountsAct
import com.ivy.wallet.domain.action.category.CategoriesAct
import com.ivy.wallet.domain.action.exchange.SyncExchangeRatesAct
import com.ivy.wallet.domain.data.IvyCurrency
import com.ivy.wallet.domain.data.core.Account
import com.ivy.wallet.domain.data.core.Category
Expand Down Expand Up @@ -56,6 +57,7 @@ class OnboardingViewModel @Inject constructor(

private val accountsAct: AccountsAct,
private val categoriesAct: CategoriesAct,
private val syncExchangeRatesAct: SyncExchangeRatesAct,

//Only OnboardingRouter stuff
sharedPrefs: SharedPrefs,
Expand All @@ -64,7 +66,7 @@ class OnboardingViewModel @Inject constructor(
transactionReminderLogic: TransactionReminderLogic,
preloadDataLogic: PreloadDataLogic,
exchangeRatesLogic: ExchangeRatesLogic,
logoutLogic: LogoutLogic
logoutLogic: LogoutLogic,
) : ViewModel() {

private val _state = MutableLiveData(OnboardingState.SPLASH)
Expand Down Expand Up @@ -106,7 +108,8 @@ class OnboardingViewModel @Inject constructor(
ivySync = ivySync,
preloadDataLogic = preloadDataLogic,
transactionReminderLogic = transactionReminderLogic,
logoutLogic = logoutLogic
logoutLogic = logoutLogic,
syncExchangeRatesAct = syncExchangeRatesAct,
)

fun start(screen: Onboarding, isSystemDarkMode: Boolean) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import androidx.lifecycle.viewModelScope
import com.ivy.frp.monad.Res
import com.ivy.frp.test.TestIdlingResource
import com.ivy.frp.view.navigation.Navigation
import com.ivy.wallet.domain.action.exchange.SyncExchangeRatesAct
import com.ivy.wallet.domain.action.global.StartDayOfMonthAct
import com.ivy.wallet.domain.action.global.UpdateStartDayOfMonthAct
import com.ivy.wallet.domain.action.transaction.FetchAllTrnsFromServerAct
Expand Down Expand Up @@ -58,7 +59,8 @@ class SettingsViewModel @Inject constructor(
private val startDayOfMonthAct: StartDayOfMonthAct,
private val updateStartDayOfMonthAct: UpdateStartDayOfMonthAct,
private val fetchAllTrnsFromServerAct: FetchAllTrnsFromServerAct,
private val nav: Navigation
private val nav: Navigation,
private val syncExchangeRatesAct: SyncExchangeRatesAct,
) : ViewModel() {

private val _user = MutableLiveData<User?>()
Expand Down Expand Up @@ -170,7 +172,11 @@ class SettingsViewModel @Inject constructor(
)
)

exchangeRatesLogic.sync(baseCurrency = newCurrency)
syncExchangeRatesAct(
SyncExchangeRatesAct.Input(
baseCurrency = newCurrency
)
)
}
start()

Expand Down

0 comments on commit c9ef7b3

Please sign in to comment.