1
1
package com.getcode.network
2
2
3
3
import android.content.Context
4
+ import android.icu.text.NumberFormat
5
+ import android.icu.text.NumberFormat.INTEGERSTYLE
4
6
import com.getcode.manager.SessionManager
5
7
import com.getcode.model.Currency
6
8
import com.getcode.model.CurrencyCode
9
+ import com.getcode.model.KinAmount
10
+ import com.getcode.model.PrefsString
7
11
import com.getcode.model.Rate
8
12
import com.getcode.network.client.TransactionReceiver
9
13
import com.getcode.network.exchange.Exchange
10
14
import com.getcode.network.repository.AccountRepository
11
15
import com.getcode.network.repository.BalanceRepository
16
+ import com.getcode.network.repository.PrefRepository
12
17
import com.getcode.network.repository.TransactionRepository
13
18
import com.getcode.solana.organizer.Organizer
14
19
import com.getcode.solana.organizer.Tray
15
20
import com.getcode.utils.FormatUtils
16
21
import com.getcode.utils.network.NetworkConnectivityListener
22
+ import com.getcode.utils.startupLog
17
23
import dagger.hilt.android.qualifiers.ApplicationContext
18
24
import io.reactivex.rxjava3.core.Completable
19
25
import kotlinx.coroutines.CoroutineScope
@@ -22,59 +28,85 @@ import kotlinx.coroutines.flow.Flow
22
28
import kotlinx.coroutines.flow.MutableStateFlow
23
29
import kotlinx.coroutines.flow.SharedFlow
24
30
import kotlinx.coroutines.flow.SharingStarted
31
+ import kotlinx.coroutines.flow.StateFlow
25
32
import kotlinx.coroutines.flow.combine
26
33
import kotlinx.coroutines.flow.distinctUntilChanged
34
+ import kotlinx.coroutines.flow.filterNotNull
35
+ import kotlinx.coroutines.flow.flatMapLatest
36
+ import kotlinx.coroutines.flow.flowOf
27
37
import kotlinx.coroutines.flow.flowOn
28
38
import kotlinx.coroutines.flow.launchIn
29
39
import kotlinx.coroutines.flow.map
30
40
import kotlinx.coroutines.flow.mapNotNull
31
41
import kotlinx.coroutines.flow.onEach
32
42
import kotlinx.coroutines.flow.stateIn
43
+ import kotlinx.coroutines.launch
33
44
import timber.log.Timber
34
45
import java.util.Locale
35
46
import java.util.concurrent.TimeUnit
36
47
import javax.inject.Inject
48
+ import kotlin.math.roundToInt
37
49
38
50
data class BalanceDisplay (
39
- val flag : Int? = null ,
40
51
val marketValue : Double = 0.0 ,
41
52
val formattedValue : String = " " ,
53
+ val currency : Currency ? = null ,
42
54
43
55
)
44
- class BalanceController @Inject constructor(
56
+ open class BalanceController @Inject constructor(
45
57
exchange : Exchange ,
46
58
networkObserver : NetworkConnectivityListener ,
47
- getCurrency : suspend (rates: Map <CurrencyCode , Rate >) -> Currency ,
59
+ getCurrency : suspend (rates: Map <CurrencyCode , Rate >, selected: String? ) -> Currency ,
48
60
@ApplicationContext
49
61
private val context : Context ,
50
62
private val balanceRepository : BalanceRepository ,
51
63
private val transactionRepository : TransactionRepository ,
52
64
private val accountRepository : AccountRepository ,
53
65
private val privacyMigration : PrivacyMigration ,
54
66
private val transactionReceiver : TransactionReceiver ,
55
- val getDefaultCountry : () -> String ,
56
- val suffix : () -> String ,
57
- ): CoroutineScope by CoroutineScope(Dispatchers .IO ) {
58
-
67
+ prefs : PrefRepository ,
68
+ getDefaultCurrency : () -> CurrencyCode ? ,
69
+ getCurrencyFromCode : (CurrencyCode ? ) -> Currency ? ,
70
+ val suffix : (Currency ? ) -> String ,
71
+ ) {
72
+ private val scope = CoroutineScope (Dispatchers .IO )
59
73
fun observeRawBalance (): Flow <Double > = balanceRepository.balanceFlow
60
74
61
75
val rawBalance: Double
62
76
get() = balanceRepository.balanceFlow.value
63
77
78
+ private val preferredCurrency: StateFlow <Currency ?> =
79
+ prefs.observeOrDefault(
80
+ PrefsString .KEY_BALANCE_CURRENCY_SELECTED ,
81
+ getDefaultCurrency()?.name.orEmpty()
82
+ )
83
+ .mapNotNull { CurrencyCode .tryValueOf(it) }
84
+ .map { getCurrencyFromCode(it) }
85
+ .stateIn(scope, SharingStarted .Eagerly , getCurrencyFromCode(getDefaultCurrency()))
86
+
64
87
private val _balanceDisplay = MutableStateFlow <BalanceDisplay ?>(null )
65
- val formattedBalance: SharedFlow <BalanceDisplay ?>
88
+
89
+ val formattedBalance: StateFlow <BalanceDisplay ?>
66
90
get() = _balanceDisplay
67
- .stateIn(this , SharingStarted .Eagerly , BalanceDisplay ())
91
+ .stateIn(scope , SharingStarted .Eagerly , BalanceDisplay ())
68
92
69
93
init {
70
94
combine(
71
95
exchange.observeRates()
72
96
.distinctUntilChanged()
73
97
.flowOn(Dispatchers .IO )
74
- .map { getCurrency(it) }
98
+ .flatMapLatest {
99
+ combine(
100
+ flowOf(it),
101
+ preferredCurrency
102
+ ) { a, b -> a to b }
103
+ }
104
+ .map { (rates, preferred) ->
105
+ getCurrency(rates, preferred?.code)
106
+ }
75
107
.onEach {
76
108
val display = _balanceDisplay .value ? : BalanceDisplay ()
77
- _balanceDisplay .value = display.copy(flag = it.resId )
109
+ _balanceDisplay .value = display.copy(currency = it)
78
110
}
79
111
.mapNotNull { currency -> CurrencyCode .tryValueOf(currency.code) }
80
112
.mapNotNull {
@@ -90,7 +122,7 @@ class BalanceController @Inject constructor(
90
122
}.distinctUntilChanged().onEach { (marketValue, amountText) ->
91
123
val display = _balanceDisplay .value ? : BalanceDisplay ()
92
124
_balanceDisplay .value = display.copy(marketValue = marketValue, formattedValue = amountText)
93
- }.launchIn(this )
125
+ }.launchIn(scope )
94
126
}
95
127
96
128
fun setTray (organizer : Organizer , tray : Tray ) {
@@ -99,6 +131,7 @@ class BalanceController @Inject constructor(
99
131
}
100
132
101
133
fun fetchBalance (): Completable {
134
+ startupLog(" fetchBalance" )
102
135
if (SessionManager .isAuthenticated() != true ) {
103
136
Timber .d(" FetchBalance - Not authenticated" )
104
137
return Completable .complete()
@@ -111,7 +144,7 @@ class BalanceController @Inject constructor(
111
144
val organizer = SessionManager .getOrganizer() ? :
112
145
return @flatMapCompletable Completable .error(IllegalStateException (" Missing Organizer" ))
113
146
114
- organizer.setAccountInfo(infos)
147
+ scope.launch { organizer.setAccountInfo(infos) }
115
148
balanceRepository.setBalance(organizer.availableBalance.toKinValueDouble())
116
149
transactionReceiver.receiveFromIncomingCompletable(organizer)
117
150
}
@@ -154,7 +187,6 @@ class BalanceController @Inject constructor(
154
187
155
188
156
189
suspend fun fetchBalanceSuspend () {
157
- Timber .d(" fetchBalance" )
158
190
if (SessionManager .isAuthenticated() != true ) {
159
191
Timber .d(" FetchBalance - Not authenticated" )
160
192
return
@@ -165,7 +197,6 @@ class BalanceController @Inject constructor(
165
197
val accountInfo = accountRepository.getTokenAccountInfos(owner).blockingGet()
166
198
val organizer = SessionManager .getOrganizer() ? : throw IllegalStateException (" Missing Organizer" )
167
199
168
- Timber .d(" updating balance and organizer" )
169
200
organizer.setAccountInfo(accountInfo)
170
201
balanceRepository.setBalance(organizer.availableBalance.toKinValueDouble())
171
202
transactionReceiver.receiveFromIncoming(organizer)
@@ -193,18 +224,35 @@ class BalanceController @Inject constructor(
193
224
}
194
225
195
226
private fun refreshBalance (balance : Double , rate : Double ): Pair <Double , String > {
227
+ val preferredCurrency = preferredCurrency.value
196
228
val fiatValue = FormatUtils .getFiatValue(balance, rate)
197
- val locale = Locale (
198
- Locale .getDefault().language,
199
- getDefaultCountry()
200
- )
201
- val fiatValueFormatted = FormatUtils .formatCurrency(fiatValue, locale)
229
+
230
+ val prefix = formatPrefix(preferredCurrency).takeIf { it != preferredCurrency?.code }.orEmpty()
202
231
val amountText = StringBuilder ().apply {
203
- append(fiatValueFormatted)
204
- append(" " )
205
- append(suffix())
232
+ append(prefix)
233
+ append(formatAmount(fiatValue, preferredCurrency))
234
+ val suffix = suffix(preferredCurrency)
235
+ if (suffix.isNotEmpty()) {
236
+ append(" " )
237
+ append(suffix)
238
+ }
206
239
}.toString()
207
240
208
241
return fiatValue to amountText
209
242
}
243
+
244
+ private fun formatPrefix (selectedCurrency : Currency ? ): String {
245
+ if (selectedCurrency == null ) return " "
246
+ return if (! isKin(selectedCurrency)) selectedCurrency.symbol else " "
247
+ }
248
+
249
+ private fun isKin (selectedCurrency : Currency ): Boolean = selectedCurrency.code == CurrencyCode .KIN .name
250
+
251
+ private fun formatAmount (amount : Double , currency : Currency ? ): String {
252
+ return if (amount % 1 == 0.0 || currency?.code == CurrencyCode .KIN .name) {
253
+ String .format(" %,.0f" , amount)
254
+ } else {
255
+ String .format(" %,.2f" , amount)
256
+ }
257
+ }
210
258
}
0 commit comments