@@ -6,12 +6,11 @@ import androidx.lifecycle.ProcessLifecycleOwner
66import com.getcode.opencode.controllers.CurrencyController
77import com.getcode.opencode.exchange.Exchange
88import com.getcode.opencode.internal.extensions.fromCode
9- import com.getcode.opencode.model.financial.LiveMintDataResponse
9+ import com.getcode.opencode.internal.manager.VerifiedProtoManager
1010import com.getcode.opencode.model.financial.Currency
1111import com.getcode.opencode.model.financial.CurrencyCode
1212import com.getcode.opencode.model.financial.Rate
1313import com.getcode.solana.keys.Mint
14- import com.getcode.util.format
1514import com.getcode.util.locale.LocaleHelper
1615import com.getcode.util.resources.ResourceHelper
1716import com.getcode.util.resources.ResourceType
@@ -23,21 +22,19 @@ import kotlinx.coroutines.Job
2322import kotlinx.coroutines.SupervisorJob
2423import kotlinx.coroutines.flow.Flow
2524import kotlinx.coroutines.flow.MutableStateFlow
26- import kotlinx.coroutines.flow.filterIsInstance
25+ import kotlinx.coroutines.flow.distinctUntilChanged
2726import kotlinx.coroutines.launch
2827import kotlinx.coroutines.withContext
29- import kotlinx.datetime.Instant
30- import java.util.Date
3128import javax.inject.Inject
32- import kotlin.time.Clock
33- import kotlin.time.Duration.Companion.minutes
3429
3530internal class OpenCodeExchange @Inject constructor(
3631 private val currencyController : CurrencyController ,
32+ private val verifiedStateManager : VerifiedProtoManager ,
3733 private val resources : ResourceHelper ,
3834 private val locale : LocaleHelper ,
3935) : Exchange, DefaultLifecycleObserver {
4036
37+ private var ratesCollectionJob: Job ? = null
4138 private var exchangeRatesStream: Job ? = null
4239
4340 private val scope = CoroutineScope (SupervisorJob () + Dispatchers .IO )
@@ -70,7 +67,7 @@ internal class OpenCodeExchange @Inject constructor(
7067
7168 override suspend fun setPreferredBalanceCurrency (currencyCode : CurrencyCode ) {
7269 balanceCurrency = currencyCode
73- rates .rateFor(currencyCode)?.let {
70+ verifiedStateManager .rateFor(currencyCode)?.let {
7471 _balanceRate .value = it
7572 } ? : run {
7673 _balanceRate .value = Rate .oneToOne.copy(currency = currencyCode)
@@ -89,7 +86,7 @@ internal class OpenCodeExchange @Inject constructor(
8986
9087 override suspend fun setPreferredEntryCurrency (currencyCode : CurrencyCode ) {
9188 entryCurrency = currencyCode
92- rates .rateFor(currencyCode)?.let {
89+ verifiedStateManager .rateFor(currencyCode)?.let {
9390 _entryRate .value = it
9491 } ? : run {
9592 _entryRate .value = Rate .oneToOne.copy(currency = currencyCode)
@@ -103,15 +100,8 @@ internal class OpenCodeExchange @Inject constructor(
103100 private var balanceCurrency: CurrencyCode ? = null
104101 private var entryCurrency: CurrencyCode ? = null
105102
106- private val _rates = MutableStateFlow (emptyMap<CurrencyCode , Rate >())
107- private var rates = RatesBox (0 , emptyMap())
108- set(value) {
109- field = value
110- _rates .value = value.rates
111- }
112-
113- override fun rates () = rates.rates
114- override fun observeRates (): Flow <Map <CurrencyCode , Rate >> = _rates
103+ override fun rates () = verifiedStateManager.rates()
104+ override fun observeRates (): Flow <Map <CurrencyCode , Rate >> = verifiedStateManager.observeRates()
115105
116106 override suspend fun getCurrenciesWithRates (rates : Map <CurrencyCode , Rate >): List <Currency > =
117107 withContext(Dispatchers .Default ) {
@@ -149,14 +139,15 @@ internal class OpenCodeExchange @Inject constructor(
149139
150140 private fun stopStreamingRates () {
151141 exchangeRatesStream?.cancel()
142+ ratesCollectionJob?.cancel()
152143 }
153144
154- override fun rateFor (currencyCode : CurrencyCode ): Rate ? = rates .rateFor(currencyCode)
145+ override fun rateFor (currencyCode : CurrencyCode ): Rate ? = verifiedStateManager .rateFor(currencyCode)
155146
156- override fun rateForUsd (): Rate = rates.rateForUsd()
147+ override fun rateForUsd (): Rate = verifiedStateManager.rateFor( CurrencyCode . USD ) ? : Rate .oneToOne
157148
158149 override fun rateToUsd (from : CurrencyCode ): Rate ? {
159- val fromRate = rates .rateFor(from) ? : return null
150+ val fromRate = verifiedStateManager .rateFor(from) ? : return null
160151
161152 return Rate (
162153 fx = 1 / fromRate.fx,
@@ -166,35 +157,31 @@ internal class OpenCodeExchange @Inject constructor(
166157
167158 private fun streamRates () {
168159 stopStreamingRates()
160+ // Start the stream so CurrencyService populates VerifiedProtoManager
169161 exchangeRatesStream = scope.launch {
170162 currencyController.streamLiveMintData(this , mints, tag = " exchange" )
171- .filterIsInstance<LiveMintDataResponse .ExchangeRates >()
172- .collect { exchangeData ->
163+ .collect { /* stream is consumed to keep it alive; rates are saved to VerifiedProtoManager by CurrencyService */ }
164+ }
165+ // Observe rates from VerifiedProtoManager (single source of truth)
166+ ratesCollectionJob = scope.launch {
167+ verifiedStateManager.observeRates()
168+ .distinctUntilChanged()
169+ .collect { rates ->
170+ if (rates.isEmpty()) return @collect
173171 trace(tag = " Exchange" , message = " Rates updated" )
174- val associatedRates = exchangeData.rates.associateBy { it.rate.currency }
175- rates = RatesBox (
176- dateMillis = Clock .System .now().toEpochMilliseconds(),
177- rates = associatedRates.mapValues { it.value.rate },
178- )
179- updateRates()
172+ updateRates(rates)
180173 }
181174 }
182175 }
183176
184- private fun updateRates () {
185- if (rates.isEmpty) {
186- return
187- }
188-
189- val balanceRate = balanceCurrency?.let { rates.rateFor(it) }
177+ private fun updateRates (rates : Map <CurrencyCode , Rate >) {
178+ val balanceRate = balanceCurrency?.let { rates[it] }
190179 val balanceChanged = _balanceRate .value != balanceRate
191180 if (balanceChanged) {
192181 _balanceRate .value = if (balanceRate != null ) {
193182 trace(
194183 tag = " Background" ,
195- message = " Updated the local currency: $balanceCurrency , " +
196- " Staleness ${(System .currentTimeMillis() - rates.dateMillis).minutes} mins, " +
197- " Date: ${Date (rates.dateMillis)} " ,
184+ message = " Updated the local currency: $balanceCurrency " ,
198185 type = TraceType .Process
199186 )
200187 balanceRate
@@ -204,20 +191,17 @@ internal class OpenCodeExchange @Inject constructor(
204191 message = " local:: Rate for $balanceCurrency not found. Defaulting to USD." ,
205192 type = TraceType .Process
206193 )
207- rates.rateForUsd()
194+ rates[ CurrencyCode . USD ] ? : Rate .oneToOne
208195 }
209196 }
210197
211-
212- val entryRate = entryCurrency?.let { rates.rateFor(it) }
198+ val entryRate = entryCurrency?.let { rates[it] }
213199 val entryChanged = _entryRate .value != entryRate
214200 if (entryChanged) {
215201 _entryRate .value = if (entryRate != null ) {
216202 trace(
217203 tag = " Background" ,
218- message = " Updated the entry currency: $entryCurrency , " +
219- " Staleness ${System .currentTimeMillis() - rates.dateMillis} ms, " +
220- " Date: ${Date (rates.dateMillis)} " ,
204+ message = " Updated the entry currency: $entryCurrency " ,
221205 type = TraceType .Process
222206 )
223207 entryRate
@@ -227,7 +211,7 @@ internal class OpenCodeExchange @Inject constructor(
227211 message = " entry:: Rate for $entryCurrency not found. Defaulting to USD." ,
228212 type = TraceType .Process
229213 )
230- rates.rateForUsd()
214+ rates[ CurrencyCode . USD ] ? : Rate .oneToOne
231215 }
232216 }
233217
@@ -236,36 +220,7 @@ internal class OpenCodeExchange @Inject constructor(
236220 tag = " Background" ,
237221 message = " Updated rates" ,
238222 type = TraceType .Process ,
239- metadata = {
240- " date" to Instant .fromEpochMilliseconds(rates.dateMillis)
241- .format(" yyyy-MM-dd HH:mm:ss" )
242- }
243223 )
244224 }
245225 }
246- }
247-
248- private data class RatesBox (
249- val dateMillis : Long ,
250- val rates : Map <CurrencyCode , Rate >,
251- ) {
252- constructor (dateMillis: Long , rates: List <Rate >) : this (
253- dateMillis,
254- rates.associateBy { it.currency },
255- )
256-
257-
258- val isEmpty: Boolean
259- get() = rates.isEmpty()
260-
261- fun rateFor (currencyCode : CurrencyCode ): Rate ? = rates[currencyCode]
262-
263- fun rateFor (currency : Currency ): Rate ? {
264- val currencyCode = CurrencyCode .tryValueOf(currency.code)
265- return currencyCode?.let { rates[it] }
266- }
267-
268- fun rateForUsd (): Rate {
269- return rates[CurrencyCode .USD ] ? : Rate .oneToOne
270- }
271226}
0 commit comments