Skip to content

Commit 3a2e40c

Browse files
authored
Merge pull request #405 from code-payments/fix/balance-currency-mismatch
fix(balance): prevent mismatches when changing currencies
2 parents a6f5b64 + b3b859d commit 3a2e40c

File tree

2 files changed

+35
-19
lines changed

2 files changed

+35
-19
lines changed

api/src/main/java/com/getcode/network/BalanceController.kt

Lines changed: 33 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import kotlinx.coroutines.flow.onEach
3737
import kotlinx.coroutines.flow.stateIn
3838
import kotlinx.coroutines.launch
3939
import timber.log.Timber
40+
import java.util.Locale
4041
import java.util.concurrent.TimeUnit
4142
import javax.inject.Inject
4243

@@ -45,7 +46,7 @@ data class BalanceDisplay(
4546
val formattedValue: String = "",
4647
val currency: Currency? = null,
4748

48-
)
49+
)
4950

5051
open class BalanceController @Inject constructor(
5152
exchange: Exchange,
@@ -73,10 +74,11 @@ open class BalanceController @Inject constructor(
7374
prefs.observeOrDefault(
7475
PrefsString.KEY_BALANCE_CURRENCY_SELECTED,
7576
getDefaultCurrency()?.name.orEmpty()
76-
)
77-
.mapNotNull { CurrencyCode.tryValueOf(it) }
78-
.map { getCurrencyFromCode(it) }
79-
.stateIn(scope, SharingStarted.Eagerly, getCurrencyFromCode(getDefaultCurrency()))
77+
).mapNotNull { CurrencyCode.tryValueOf(it) }
78+
.map { getCurrencyFromCode(it) }
79+
.distinctUntilChanged()
80+
.onEach { Timber.d("currency for balance is now ${it?.code}") }
81+
.stateIn(scope, SharingStarted.Eagerly, getCurrencyFromCode(getDefaultCurrency()))
8082

8183
private val _balanceDisplay = MutableStateFlow<BalanceDisplay?>(null)
8284

@@ -115,7 +117,8 @@ open class BalanceController @Inject constructor(
115117
refreshBalance(balance, rate.fx)
116118
}.distinctUntilChanged().onEach { (marketValue, amountText) ->
117119
val display = _balanceDisplay.value ?: BalanceDisplay()
118-
_balanceDisplay.value = display.copy(marketValue = marketValue, formattedValue = amountText)
120+
_balanceDisplay.value =
121+
display.copy(marketValue = marketValue, formattedValue = amountText)
119122
}.launchIn(scope)
120123
}
121124

@@ -130,13 +133,14 @@ open class BalanceController @Inject constructor(
130133
Timber.d("FetchBalance - Not authenticated")
131134
return Completable.complete()
132135
}
133-
val owner = SessionManager.getKeyPair() ?: return Completable.error(IllegalStateException("Missing Owner"))
136+
val owner = SessionManager.getKeyPair()
137+
?: return Completable.error(IllegalStateException("Missing Owner"))
134138

135139
fun getTokenAccountInfos(): Completable {
136140
return accountRepository.getTokenAccountInfos(owner)
137141
.flatMapCompletable { infos ->
138-
val organizer = SessionManager.getOrganizer() ?:
139-
return@flatMapCompletable Completable.error(IllegalStateException("Missing Organizer"))
142+
val organizer = SessionManager.getOrganizer()
143+
?: return@flatMapCompletable Completable.error(IllegalStateException("Missing Organizer"))
140144

141145
scope.launch { organizer.setAccountInfo(infos) }
142146
balanceRepository.setBalance(organizer.availableBalance.toKinValueDouble())
@@ -151,7 +155,10 @@ open class BalanceController @Inject constructor(
151155
}
152156
.onErrorResumeNext {
153157
Timber.i("Error: ${it.javaClass.simpleName} ${it.cause}")
154-
val organizer = SessionManager.getOrganizer() ?: return@onErrorResumeNext Completable.error(IllegalStateException("Missing Organizer"))
158+
val organizer =
159+
SessionManager.getOrganizer() ?: return@onErrorResumeNext Completable.error(
160+
IllegalStateException("Missing Organizer")
161+
)
155162

156163
when (it) {
157164
is AccountRepository.FetchAccountInfosException.MigrationRequiredException -> {
@@ -163,13 +170,15 @@ open class BalanceController @Inject constructor(
163170
.ignoreElement()
164171
.concatWith(getTokenAccountInfos())
165172
}
173+
166174
is AccountRepository.FetchAccountInfosException.NotFoundException -> {
167175
transactionRepository.createAccounts(
168176
organizer = organizer
169177
)
170178
.ignoreElement()
171179
.concatWith(getTokenAccountInfos())
172180
}
181+
173182
else -> {
174183
Completable.error(it)
175184
}
@@ -178,7 +187,6 @@ open class BalanceController @Inject constructor(
178187
}
179188

180189

181-
182190
suspend fun fetchBalanceSuspend() {
183191
if (SessionManager.isAuthenticated() != true) {
184192
Timber.d("FetchBalance - Not authenticated")
@@ -188,15 +196,17 @@ open class BalanceController @Inject constructor(
188196

189197
try {
190198
val accountInfo = accountRepository.getTokenAccountInfos(owner).blockingGet()
191-
val organizer = SessionManager.getOrganizer() ?: throw IllegalStateException("Missing Organizer")
199+
val organizer =
200+
SessionManager.getOrganizer() ?: throw IllegalStateException("Missing Organizer")
192201

193202
organizer.setAccountInfo(accountInfo)
194203
balanceRepository.setBalance(organizer.availableBalance.toKinValueDouble())
195204
transactionReceiver.receiveFromIncoming(organizer)
196205
transactionRepository.swapIfNeeded(organizer)
197206
} catch (ex: Exception) {
198207
Timber.i("Error: ${ex.javaClass.simpleName} ${ex.cause}")
199-
val organizer = SessionManager.getOrganizer() ?: throw IllegalStateException("Missing Organizer")
208+
val organizer =
209+
SessionManager.getOrganizer() ?: throw IllegalStateException("Missing Organizer")
200210

201211
when (ex) {
202212
is AccountRepository.FetchAccountInfosException.MigrationRequiredException -> {
@@ -206,6 +216,7 @@ open class BalanceController @Inject constructor(
206216
organizer = organizer
207217
)
208218
}
219+
209220
is AccountRepository.FetchAccountInfosException.NotFoundException -> {
210221
transactionRepository.createAccounts(
211222
organizer = organizer
@@ -219,7 +230,9 @@ open class BalanceController @Inject constructor(
219230
val preferredCurrency = preferredCurrency.value
220231
val fiatValue = FormatUtils.getFiatValue(balance, rate)
221232

222-
val prefix = formatPrefix(preferredCurrency).takeIf { it != preferredCurrency?.code }.orEmpty()
233+
val prefix =
234+
formatPrefix(preferredCurrency).takeIf { it != preferredCurrency?.code }.orEmpty()
235+
223236
val amountText = StringBuilder().apply {
224237
append(prefix)
225238
append(formatAmount(fiatValue, preferredCurrency))
@@ -230,6 +243,8 @@ open class BalanceController @Inject constructor(
230243
}
231244
}.toString()
232245

246+
Timber.d("formatted balance is now $prefix $amountText in ${preferredCurrency?.code}")
247+
233248
return fiatValue to amountText
234249
}
235250

@@ -238,13 +253,14 @@ open class BalanceController @Inject constructor(
238253
return if (!isKin(selectedCurrency)) selectedCurrency.symbol else ""
239254
}
240255

241-
private fun isKin(selectedCurrency: Currency): Boolean = selectedCurrency.code == CurrencyCode.KIN.name
256+
private fun isKin(selectedCurrency: Currency): Boolean =
257+
selectedCurrency.code == CurrencyCode.KIN.name
242258

243259
private fun formatAmount(amount: Double, currency: Currency?): String {
244260
return if (amount % 1 == 0.0 || currency?.code == CurrencyCode.KIN.name) {
245-
String.format("%,.0f", amount)
261+
String.format(Locale.getDefault(), "%,.0f", amount)
246262
} else {
247-
String.format("%,.2f", amount)
263+
String.format(Locale.getDefault(), "%,.2f", amount)
248264
}
249265
}
250266
}

app/src/main/java/com/getcode/view/main/balance/BalanceSheet.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ import com.getcode.ui.components.chat.ChatNode
5757
import com.getcode.util.Kin
5858
import com.getcode.util.NumberInputHelper
5959
import com.getcode.utils.network.NetworkState
60+
import com.getcode.utils.trace
6061
import com.getcode.view.main.account.BucketDebugger
6162
import com.getcode.view.main.giveKin.AmountAnimatedInputUiModel
6263
import com.getcode.view.main.giveKin.AmountArea
@@ -227,9 +228,8 @@ fun BalanceTop(
227228
isClickable: Boolean,
228229
onClick: () -> Unit = {}
229230
) {
230-
val text = rememberSaveable(state.amountText) { state.amountText }
231231
AmountArea(
232-
amountText = text,
232+
amountText = state.amountText,
233233
isAltCaption = false,
234234
isAltCaptionKinIcon = false,
235235
isLoading = state.chatsLoading,

0 commit comments

Comments
 (0)