From 0f0e9cee97658650a2eb0bc2eae2ecc806332678 Mon Sep 17 00:00:00 2001 From: ismurzin Date: Thu, 18 Apr 2024 09:08:02 +0400 Subject: [PATCH 01/19] fix user repository injection + infinity loading --- .../main/java/com/mycelium/bequant/remote/repositories/Api.kt | 1 - .../java/com/mycelium/wallet/activity/modern/ModernMain.kt | 4 ++-- .../com/mycelium/wallet/external/changelly2/remote/Api.kt | 1 - .../wallet/external/changelly2/remote/Changelly2Repository.kt | 2 -- 4 files changed, 2 insertions(+), 6 deletions(-) diff --git a/mbw/src/main/java/com/mycelium/bequant/remote/repositories/Api.kt b/mbw/src/main/java/com/mycelium/bequant/remote/repositories/Api.kt index 31a7e5786..4669cfb92 100644 --- a/mbw/src/main/java/com/mycelium/bequant/remote/repositories/Api.kt +++ b/mbw/src/main/java/com/mycelium/bequant/remote/repositories/Api.kt @@ -1,6 +1,5 @@ package com.mycelium.bequant.remote.repositories -import com.mycelium.wallet.external.changelly2.remote.UserRepository object Api { val accountRepository by lazy { AccountApiRepository() } diff --git a/mbw/src/main/java/com/mycelium/wallet/activity/modern/ModernMain.kt b/mbw/src/main/java/com/mycelium/wallet/activity/modern/ModernMain.kt index 054c76d81..ee32371cb 100644 --- a/mbw/src/main/java/com/mycelium/wallet/activity/modern/ModernMain.kt +++ b/mbw/src/main/java/com/mycelium/wallet/activity/modern/ModernMain.kt @@ -51,7 +51,7 @@ import com.mycelium.wallet.event.* import com.mycelium.wallet.external.changelly.ChangellyConstants import com.mycelium.wallet.external.changelly2.ExchangeFragment import com.mycelium.wallet.external.changelly2.HistoryFragment -import com.mycelium.wallet.external.changelly2.remote.UserRepository +import com.mycelium.wallet.external.changelly2.remote.Api import com.mycelium.wallet.external.mediaflow.NewsConstants import com.mycelium.wallet.fio.FioRequestNotificator import com.mycelium.wallet.modularisation.ModularisationVersionHelper @@ -91,7 +91,7 @@ class ModernMain : AppCompatActivity(), BackHandler { lateinit var binding: ModernMainBinding - private val userRepository = UserRepository() + private val userRepository = Api.userRepository public override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) diff --git a/mbw/src/main/java/com/mycelium/wallet/external/changelly2/remote/Api.kt b/mbw/src/main/java/com/mycelium/wallet/external/changelly2/remote/Api.kt index 1dc5abb07..8030e43ae 100644 --- a/mbw/src/main/java/com/mycelium/wallet/external/changelly2/remote/Api.kt +++ b/mbw/src/main/java/com/mycelium/wallet/external/changelly2/remote/Api.kt @@ -1,6 +1,5 @@ package com.mycelium.wallet.external.changelly2.remote -import com.mycelium.wallet.external.changelly2.remote.UserRepository object Api { val userRepository by lazy { UserRepository() } diff --git a/mbw/src/main/java/com/mycelium/wallet/external/changelly2/remote/Changelly2Repository.kt b/mbw/src/main/java/com/mycelium/wallet/external/changelly2/remote/Changelly2Repository.kt index e5273546f..7d07040ac 100644 --- a/mbw/src/main/java/com/mycelium/wallet/external/changelly2/remote/Changelly2Repository.kt +++ b/mbw/src/main/java/com/mycelium/wallet/external/changelly2/remote/Changelly2Repository.kt @@ -9,8 +9,6 @@ import java.math.BigDecimal object Changelly2Repository { private val api = ChangellyRetrofitFactory.api - val userRepository by lazy { UserRepository() } - fun supportCurrenciesFull( scope: CoroutineScope, From 2e83082663b789262eed0b4d8422c4977baf5abf Mon Sep 17 00:00:00 2001 From: ismurzin Date: Fri, 19 Apr 2024 11:06:32 +0400 Subject: [PATCH 02/19] add vip caching --- .../com/mycelium/bequant/remote/model/User.kt | 8 ++++ .../wallet/activity/modern/ModernMain.kt | 3 +- .../activity/modern/vip/VipViewModel.kt | 37 ++++--------------- .../changelly2/remote/UserRepository.kt | 33 +++++++++++++---- 4 files changed, 43 insertions(+), 38 deletions(-) diff --git a/mbw/src/main/java/com/mycelium/bequant/remote/model/User.kt b/mbw/src/main/java/com/mycelium/bequant/remote/model/User.kt index 8ffa0b02a..0c1d0f9f0 100644 --- a/mbw/src/main/java/com/mycelium/bequant/remote/model/User.kt +++ b/mbw/src/main/java/com/mycelium/bequant/remote/model/User.kt @@ -8,5 +8,13 @@ data class User( REGULAR; fun isVIP() = this == VIP + + companion object { + fun fromName(name: String) = when (name) { + VIP.name -> VIP + REGULAR.name -> REGULAR + else -> REGULAR + } + } } } diff --git a/mbw/src/main/java/com/mycelium/wallet/activity/modern/ModernMain.kt b/mbw/src/main/java/com/mycelium/wallet/activity/modern/ModernMain.kt index ee32371cb..6ee1a4a77 100644 --- a/mbw/src/main/java/com/mycelium/wallet/activity/modern/ModernMain.kt +++ b/mbw/src/main/java/com/mycelium/wallet/activity/modern/ModernMain.kt @@ -64,6 +64,7 @@ import com.mycelium.wapi.wallet.manager.State import com.squareup.otto.Subscribe import info.guardianproject.netcipher.proxy.OrbotHelper import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.launch import java.util.* import java.util.concurrent.TimeUnit @@ -160,7 +161,7 @@ class ModernMain : AppCompatActivity(), BackHandler { lifecycleScope.launchWhenResumed { ChangeLog.showIfNewVersion(this@ModernMain, supportFragmentManager) } - lifecycleScope.launchWhenStarted { + lifecycleScope.launch { userRepository.identify() userRepository.userFlow.collect { user -> val icon = diff --git a/mbw/src/main/java/com/mycelium/wallet/activity/modern/vip/VipViewModel.kt b/mbw/src/main/java/com/mycelium/wallet/activity/modern/vip/VipViewModel.kt index 50f3c1c4c..1620d55fa 100644 --- a/mbw/src/main/java/com/mycelium/wallet/activity/modern/vip/VipViewModel.kt +++ b/mbw/src/main/java/com/mycelium/wallet/activity/modern/vip/VipViewModel.kt @@ -7,7 +7,7 @@ import com.mycelium.wallet.update import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch class VipViewModel : ViewModel() { @@ -26,47 +26,26 @@ class VipViewModel : ViewModel() { init { viewModelScope.launch { - val initialUser = userRepository.userFlow.first() - _stateFlow.update { state -> - state.copy( - progress = false, - success = initialUser.status.isVIP(), - ) + userRepository.userFlow.collect { user -> + val success = user.status.isVIP() + _stateFlow.update { state -> state.copy(progress = false, success = success) } } } } fun updateVipText(text: String) { - _stateFlow.update { state -> state.copy(text = text, success = false, error = false) } + _stateFlow.update { s -> s.copy(text = text, error = false) } } private val exceptionHandler = CoroutineExceptionHandler { _, _ -> - _stateFlow.update { state -> - state.copy( - progress = false, - success = false, - error = true, - ) - } + _stateFlow.update { s -> s.copy(progress = false, error = true, success = false) } } fun applyCode() { viewModelScope.launch(exceptionHandler) { - _stateFlow.update { state -> - state.copy( - progress = true, - success = false, - error = false, - ) - } + _stateFlow.update { s -> s.copy(progress = true, error = false, success = false) } val status = userRepository.applyVIPCode(_stateFlow.value.text) - _stateFlow.update { state -> - state.copy( - progress = false, - success = status.isVIP(), - error = !status.isVIP(), - ) - } + _stateFlow.update { s -> s.copy(progress = false, error = !status.isVIP()) } } } } \ No newline at end of file diff --git a/mbw/src/main/java/com/mycelium/wallet/external/changelly2/remote/UserRepository.kt b/mbw/src/main/java/com/mycelium/wallet/external/changelly2/remote/UserRepository.kt index be48deb00..a0a33bf9a 100644 --- a/mbw/src/main/java/com/mycelium/wallet/external/changelly2/remote/UserRepository.kt +++ b/mbw/src/main/java/com/mycelium/wallet/external/changelly2/remote/UserRepository.kt @@ -1,35 +1,52 @@ package com.mycelium.wallet.external.changelly2.remote +import android.app.Activity +import androidx.core.content.edit import com.mycelium.bequant.remote.model.User +import com.mycelium.wallet.WalletApplication import com.mycelium.wallet.external.vip.VipRetrofitFactory import com.mycelium.wallet.external.vip.model.ActivateVipRequest import com.mycelium.wallet.update -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.filterNotNull -import kotlinx.coroutines.withContext class UserRepository { private val _userFlow = MutableStateFlow(null) private val vipApi = VipRetrofitFactory().createApi() + private val preference by lazy { + WalletApplication + .getInstance() + .getSharedPreferences(PREFERENCES_VIP_FILE, Activity.MODE_PRIVATE) + } + val userFlow = _userFlow.filterNotNull() suspend fun identify() { - try { - val checkResult = withContext(Dispatchers.IO) { vipApi.check() } + val status = try { + val checkResult = vipApi.check() // if user is VIP than response contains his code else response contains empty string val isVIP = checkResult.vipCode.isNotEmpty() - val status = if (isVIP) User.Status.VIP else User.Status.REGULAR - _userFlow.update { user -> user?.copy(status = status) ?: User(status) } + if (isVIP) User.Status.VIP + else User.Status.REGULAR } catch (_: Exception) { - _userFlow.value = User() + val statusName = preference.getString(VIP_STATUS_KEY, null) + if (statusName != null) User.Status.fromName(statusName) + else User.Status.REGULAR } + _userFlow.value = User(status) + preference.edit { putString(VIP_STATUS_KEY, status.name) } } suspend fun applyVIPCode(code: String): User.Status { - val response = withContext(Dispatchers.IO) { vipApi.activate(ActivateVipRequest(code)) } + val response = vipApi.activate(ActivateVipRequest(code)) val status = if (response.done) User.Status.VIP else User.Status.REGULAR _userFlow.update { user -> user?.copy(status = status) ?: User(status) } + preference.edit { putString(VIP_STATUS_KEY, status.name) } return status } + + private companion object { + const val PREFERENCES_VIP_FILE = "VIP_PREFERENCES" + const val VIP_STATUS_KEY = "VIP_STATUS" + } } From c03b4c13dc2a32f00a2c0a045feeedcbd83191c2 Mon Sep 17 00:00:00 2001 From: ismurzin Date: Mon, 22 Apr 2024 17:51:03 +0400 Subject: [PATCH 03/19] fix broken exchange logic + add network fee to calculation --- .../external/changelly/ChangellyAPIService.kt | 31 +++++++++----- .../external/changelly/model/FixRate.kt | 5 ++- .../changelly/model/FixRateForAmount.kt | 22 ---------- .../external/changelly2/ExchangeFragment.kt | 42 +++++++++++++------ .../changelly2/remote/Changelly2Repository.kt | 29 ++++++++----- .../changelly2/viewmodel/ExchangeViewModel.kt | 7 ++-- 6 files changed, 73 insertions(+), 63 deletions(-) delete mode 100644 mbw/src/main/java/com/mycelium/wallet/external/changelly/model/FixRateForAmount.kt diff --git a/mbw/src/main/java/com/mycelium/wallet/external/changelly/ChangellyAPIService.kt b/mbw/src/main/java/com/mycelium/wallet/external/changelly/ChangellyAPIService.kt index a253ae148..1b6f5932d 100644 --- a/mbw/src/main/java/com/mycelium/wallet/external/changelly/ChangellyAPIService.kt +++ b/mbw/src/main/java/com/mycelium/wallet/external/changelly/ChangellyAPIService.kt @@ -1,7 +1,12 @@ package com.mycelium.wallet.external.changelly -import com.mycelium.wallet.external.changelly.model.* -import org.jetbrains.annotations.TestOnly +import com.mycelium.wallet.external.changelly.model.ChangellyCurrency +import com.mycelium.wallet.external.changelly.model.ChangellyGetExchangeAmountResponse +import com.mycelium.wallet.external.changelly.model.ChangellyListResponse +import com.mycelium.wallet.external.changelly.model.ChangellyResponse +import com.mycelium.wallet.external.changelly.model.ChangellyTransaction +import com.mycelium.wallet.external.changelly.model.ChangellyTransactionOffer +import com.mycelium.wallet.external.changelly.model.FixRate import retrofit2.Call import retrofit2.Response import retrofit2.http.POST @@ -56,7 +61,17 @@ interface ChangellyAPIService { @Query("from") from: String, @Query("to") to: String, @Query("amountFrom") amount: BigDecimal = BigDecimal.ONE, - ): Response> + ): Response> + + @Deprecated( + "To get the fixed rate, you need to use getFixRateForAmount, but the transaction amount must be within limits", + ReplaceWith("getFixRateForAmount") + ) + @POST("getFixRate") + suspend fun getFixRate( + @Query("from") from: String, + @Query("to") to: String, + ): Response> @POST("createFixTransaction") suspend fun createFixTransaction( @@ -70,7 +85,8 @@ interface ChangellyAPIService { @POST("getTransactions") suspend fun getTransaction( - @Query("id") id: String + @Query("id") id: String, + @Query("limit") limit: Int = 1, ): Response>> @POST("getTransactions") @@ -78,13 +94,6 @@ interface ChangellyAPIService { @Query("id") id: List, ): Response>> - - @TestOnly - @POST("getFixRate") - suspend fun getFixRate(@Query("from") from: String, - @Query("to") to: String): Response> - - companion object { const val BCH = "BCH" const val BTC = "BTC" diff --git a/mbw/src/main/java/com/mycelium/wallet/external/changelly/model/FixRate.kt b/mbw/src/main/java/com/mycelium/wallet/external/changelly/model/FixRate.kt index 792f2579d..dbace016d 100644 --- a/mbw/src/main/java/com/mycelium/wallet/external/changelly/model/FixRate.kt +++ b/mbw/src/main/java/com/mycelium/wallet/external/changelly/model/FixRate.kt @@ -11,6 +11,7 @@ data class FixRate( val maxTo: BigDecimal, val minFrom: BigDecimal, val minTo: BigDecimal, - val amountFrom: BigDecimal, - val amountTo: BigDecimal, + val amountFrom: BigDecimal?, + val amountTo: BigDecimal?, + val networkFee: BigDecimal?, ) diff --git a/mbw/src/main/java/com/mycelium/wallet/external/changelly/model/FixRateForAmount.kt b/mbw/src/main/java/com/mycelium/wallet/external/changelly/model/FixRateForAmount.kt deleted file mode 100644 index 2db4651db..000000000 --- a/mbw/src/main/java/com/mycelium/wallet/external/changelly/model/FixRateForAmount.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.mycelium.wallet.external.changelly.model - -import java.math.BigDecimal - - -data class FixRateForAmount(val id: String, - val result: BigDecimal, - val networkFee: BigDecimal, - val from: String, - val to: String, - val amountFrom: BigDecimal, - val amountTo: BigDecimal, - val max: BigDecimal, - val maxFrom:BigDecimal, - val maxTo:BigDecimal, - val min:BigDecimal, - val minFrom:BigDecimal, - val minTo:BigDecimal) { - - fun getExpectedValue() = result - networkFee -} - diff --git a/mbw/src/main/java/com/mycelium/wallet/external/changelly2/ExchangeFragment.kt b/mbw/src/main/java/com/mycelium/wallet/external/changelly2/ExchangeFragment.kt index 6e4aa1046..5f5c30b71 100644 --- a/mbw/src/main/java/com/mycelium/wallet/external/changelly2/ExchangeFragment.kt +++ b/mbw/src/main/java/com/mycelium/wallet/external/changelly2/ExchangeFragment.kt @@ -22,7 +22,10 @@ import com.bumptech.glide.load.resource.bitmap.CircleCrop import com.bumptech.glide.request.RequestOptions import com.mrd.bitlib.model.BitcoinAddress import com.mycelium.view.RingDrawable -import com.mycelium.wallet.* +import com.mycelium.wallet.BuildConfig +import com.mycelium.wallet.MbwManager +import com.mycelium.wallet.R +import com.mycelium.wallet.Utils import com.mycelium.wallet.activity.modern.ModernMain import com.mycelium.wallet.activity.modern.event.BackHandler import com.mycelium.wallet.activity.modern.event.BackListener @@ -36,14 +39,19 @@ import com.mycelium.wallet.activity.util.toStringWithUnit import com.mycelium.wallet.activity.view.ValueKeyboard import com.mycelium.wallet.activity.view.loader import com.mycelium.wallet.databinding.FragmentChangelly2ExchangeBinding -import com.mycelium.wallet.event.* +import com.mycelium.wallet.event.ExchangeRatesRefreshed +import com.mycelium.wallet.event.ExchangeSourceChanged +import com.mycelium.wallet.event.PageSelectedEvent +import com.mycelium.wallet.event.SelectedAccountChanged +import com.mycelium.wallet.event.SelectedCurrencyChanged +import com.mycelium.wallet.event.TransactionBroadcasted import com.mycelium.wallet.external.changelly.model.ChangellyResponse import com.mycelium.wallet.external.changelly.model.ChangellyTransactionOffer import com.mycelium.wallet.external.changelly.model.FixRate -import com.mycelium.wallet.external.changelly.model.FixRateForAmount import com.mycelium.wallet.external.changelly2.remote.Changelly2Repository import com.mycelium.wallet.external.changelly2.viewmodel.ExchangeViewModel import com.mycelium.wallet.external.partner.openLink +import com.mycelium.wallet.startCoroutineTimer import com.mycelium.wapi.wallet.AesKeyCipher import com.mycelium.wapi.wallet.BroadcastResultType import com.mycelium.wapi.wallet.Transaction @@ -183,7 +191,7 @@ class ExchangeFragment : Fragment(), BackListener { val exchangeInfoResult = viewModel.exchangeInfo.value?.result if (friendlyDigits == null || exchangeInfoResult == null) N_A else amount.toBigDecimal().setScale(friendlyDigits, RoundingMode.HALF_UP) - ?.div(viewModel.exchangeInfo.value!!.getExpectedValue()) + ?.div(exchangeInfoResult) ?.stripTrailingZeros() ?.toPlainString() ?: N_A } catch (e: NumberFormatException) { @@ -358,13 +366,17 @@ class ExchangeFragment : Fragment(), BackListener { private fun computeBuyValue() { val amount = viewModel.sellValue.value - viewModel.buyValue.value = if (amount?.isNotEmpty() == true - && viewModel.exchangeInfo.value?.result != null) { + val info = viewModel.exchangeInfo.value + val rate = info?.result + val networkFee = info?.networkFee ?: BigDecimal.ZERO + viewModel.buyValue.value = if (amount?.isNotEmpty() == true && rate != null) { try { - (amount.toBigDecimal() * viewModel.exchangeInfo.value?.getExpectedValue()!!) - .setScale(viewModel.toCurrency.value?.friendlyDigits!!, RoundingMode.HALF_UP) - .stripTrailingZeros() - .toPlainString() + val result = amount.toBigDecimal() * rate - networkFee + if (result <= BigDecimal.ZERO) null + else result + .setScale(viewModel.toCurrency.value?.friendlyDigits!!, RoundingMode.HALF_UP) + .stripTrailingZeros() + .toPlainString() } catch (e: NumberFormatException) { "N/A" } @@ -439,7 +451,12 @@ class ExchangeFragment : Fragment(), BackListener { { result -> val data = result?.result?.firstOrNull() if (data != null) { - viewModel.exchangeInfo.value = data + val info = viewModel.exchangeInfo.value + viewModel.exchangeInfo.value = if (info == null) data else data.copy( + amountFrom = info.amountFrom, + amountTo = info.amountTo, + networkFee = info.networkFee, + ) viewModel.errorRemote.value = "" } else { viewModel.errorRemote.value = result?.error?.message ?: "" @@ -485,8 +502,7 @@ class ExchangeFragment : Fragment(), BackListener { fromAmount, { result -> result?.result?.firstOrNull()?.let { - val info = viewModel.exchangeInfo.value - viewModel.exchangeInfo.postValue(it) + viewModel.exchangeInfo.value = it viewModel.errorRemote.value = "" } ?: run { viewModel.errorRemote.value = result?.error?.message ?: "" diff --git a/mbw/src/main/java/com/mycelium/wallet/external/changelly2/remote/Changelly2Repository.kt b/mbw/src/main/java/com/mycelium/wallet/external/changelly2/remote/Changelly2Repository.kt index 7d07040ac..1cedf7b66 100644 --- a/mbw/src/main/java/com/mycelium/wallet/external/changelly2/remote/Changelly2Repository.kt +++ b/mbw/src/main/java/com/mycelium/wallet/external/changelly2/remote/Changelly2Repository.kt @@ -3,7 +3,12 @@ package com.mycelium.wallet.external.changelly2.remote import androidx.lifecycle.LifecycleCoroutineScope import com.mycelium.bequant.remote.doRequest import com.mycelium.wallet.external.changelly.ChangellyRetrofitFactory -import com.mycelium.wallet.external.changelly.model.* +import com.mycelium.wallet.external.changelly.model.ChangellyCurrency +import com.mycelium.wallet.external.changelly.model.ChangellyListResponse +import com.mycelium.wallet.external.changelly.model.ChangellyResponse +import com.mycelium.wallet.external.changelly.model.ChangellyTransaction +import com.mycelium.wallet.external.changelly.model.ChangellyTransactionOffer +import com.mycelium.wallet.external.changelly.model.FixRate import kotlinx.coroutines.CoroutineScope import java.math.BigDecimal @@ -26,7 +31,7 @@ object Changelly2Repository { from: String, to: String, amount: BigDecimal, - success: (ChangellyListResponse?) -> Unit, + success: (ChangellyListResponse?) -> Unit, error: (Int, String) -> Unit, finally: (() -> Unit)? = null ) = @@ -34,15 +39,17 @@ object Changelly2Repository { api.getFixRateForAmount(exportSymbol(from), exportSymbol(to), amount) }, success, error, finally) - fun fixRate(scope: CoroutineScope, - from: String, - to: String, - success: (ChangellyListResponse?) -> Unit, - error: (Int, String) -> Unit, - finally: (() -> Unit)? = null) = - doRequest(scope, { - api.getFixRateForAmount(exportSymbol(from), exportSymbol(to)) - }, success, error, finally) + fun fixRate( + scope: CoroutineScope, + from: String, + to: String, + success: (ChangellyListResponse?) -> Unit, + error: (Int, String) -> Unit, + finally: (() -> Unit)? = null + ) = + doRequest(scope, { + api.getFixRate(exportSymbol(from), exportSymbol(to)) + }, success, error, finally) fun createFixTransaction( scope: CoroutineScope, diff --git a/mbw/src/main/java/com/mycelium/wallet/external/changelly2/viewmodel/ExchangeViewModel.kt b/mbw/src/main/java/com/mycelium/wallet/external/changelly2/viewmodel/ExchangeViewModel.kt index 2a0f17202..447317aad 100644 --- a/mbw/src/main/java/com/mycelium/wallet/external/changelly2/viewmodel/ExchangeViewModel.kt +++ b/mbw/src/main/java/com/mycelium/wallet/external/changelly2/viewmodel/ExchangeViewModel.kt @@ -14,7 +14,6 @@ import com.mycelium.wallet.WalletApplication import com.mycelium.wallet.activity.util.toStringFriendlyWithUnit import com.mycelium.wallet.activity.util.toStringWithUnit import com.mycelium.wallet.external.changelly.model.FixRate -import com.mycelium.wallet.external.changelly.model.FixRateForAmount import com.mycelium.wapi.wallet.Address import com.mycelium.wapi.wallet.Transaction import com.mycelium.wapi.wallet.Util @@ -35,7 +34,7 @@ class ExchangeViewModel(application: Application) : AndroidViewModel(application val mbwManager = MbwManager.getInstance(WalletApplication.getInstance()) var currencies = setOf("BTC", "ETH") val fromAccount = MutableLiveData>() - val exchangeInfo = MutableLiveData() + val exchangeInfo = MutableLiveData() val sellValue = object : MutableLiveData() { override fun setValue(value: String?) { if (this.value != value) { @@ -59,7 +58,7 @@ class ExchangeViewModel(application: Application) : AndroidViewModel(application } } } - val swapEnableDelay = MutableLiveData(false) + val swapEnableDelay = MutableLiveData(false) val swapEnabled = MediatorLiveData().apply { value = false fun update() { @@ -152,7 +151,7 @@ class ExchangeViewModel(application: Application) : AndroidViewModel(application } val exchangeRateToValue = Transformations.map(exchangeInfo) { - it.getExpectedValue().toPlainString() + it.result.toPlainString() } val exchangeRateToCurrency = Transformations.map(exchangeInfo) { From 6c94cb3e558431ef78fb784b93aeb29ad274d658 Mon Sep 17 00:00:00 2001 From: ismurzin Date: Tue, 14 May 2024 16:16:05 +0500 Subject: [PATCH 04/19] separate changelly and viper + add local status storing --- .../com/mycelium/bequant/remote/model/User.kt | 20 ------- .../bequant/remote/model/UserStatus.kt | 16 ++++++ .../wallet/activity/modern/ModernMain.kt | 7 ++- .../activity/modern/vip/VipViewModel.kt | 6 +-- .../changelly/ChangellyOfferActivity.java | 2 +- .../changelly/ChangellyRetrofitFactory.kt | 30 ++++++++--- .../wallet/external/changelly2/remote/Api.kt | 2 +- .../changelly2/remote/Changelly2Repository.kt | 14 +++-- .../changelly2/remote/StatusRepository.kt | 54 +++++++++++++++++++ .../changelly2/remote/UserRepository.kt | 52 ------------------ 10 files changed, 112 insertions(+), 91 deletions(-) delete mode 100644 mbw/src/main/java/com/mycelium/bequant/remote/model/User.kt create mode 100644 mbw/src/main/java/com/mycelium/bequant/remote/model/UserStatus.kt create mode 100644 mbw/src/main/java/com/mycelium/wallet/external/changelly2/remote/StatusRepository.kt delete mode 100644 mbw/src/main/java/com/mycelium/wallet/external/changelly2/remote/UserRepository.kt diff --git a/mbw/src/main/java/com/mycelium/bequant/remote/model/User.kt b/mbw/src/main/java/com/mycelium/bequant/remote/model/User.kt deleted file mode 100644 index 0c1d0f9f0..000000000 --- a/mbw/src/main/java/com/mycelium/bequant/remote/model/User.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.mycelium.bequant.remote.model - -data class User( - val status: Status = Status.REGULAR, -) { - enum class Status { - VIP, - REGULAR; - - fun isVIP() = this == VIP - - companion object { - fun fromName(name: String) = when (name) { - VIP.name -> VIP - REGULAR.name -> REGULAR - else -> REGULAR - } - } - } -} diff --git a/mbw/src/main/java/com/mycelium/bequant/remote/model/UserStatus.kt b/mbw/src/main/java/com/mycelium/bequant/remote/model/UserStatus.kt new file mode 100644 index 000000000..40efb7968 --- /dev/null +++ b/mbw/src/main/java/com/mycelium/bequant/remote/model/UserStatus.kt @@ -0,0 +1,16 @@ +package com.mycelium.bequant.remote.model + +enum class UserStatus { + VIP, + REGULAR; + + fun isVIP() = this == VIP + + companion object { + fun fromName(name: String?) = when (name) { + VIP.name -> VIP + REGULAR.name -> REGULAR + else -> null + } + } +} diff --git a/mbw/src/main/java/com/mycelium/wallet/activity/modern/ModernMain.kt b/mbw/src/main/java/com/mycelium/wallet/activity/modern/ModernMain.kt index 6ee1a4a77..ae33d1a3f 100644 --- a/mbw/src/main/java/com/mycelium/wallet/activity/modern/ModernMain.kt +++ b/mbw/src/main/java/com/mycelium/wallet/activity/modern/ModernMain.kt @@ -92,7 +92,7 @@ class ModernMain : AppCompatActivity(), BackHandler { lateinit var binding: ModernMainBinding - private val userRepository = Api.userRepository + private val userRepository = Api.statusRepository public override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -162,10 +162,9 @@ class ModernMain : AppCompatActivity(), BackHandler { ChangeLog.showIfNewVersion(this@ModernMain, supportFragmentManager) } lifecycleScope.launch { - userRepository.identify() - userRepository.userFlow.collect { user -> + userRepository.statusFlow.collect { status -> val icon = - if (user.status.isVIP()) R.drawable.action_bar_logo_vip + if (status.isVIP()) R.drawable.action_bar_logo_vip else R.drawable.action_bar_logo supportActionBar?.setIcon(icon) } diff --git a/mbw/src/main/java/com/mycelium/wallet/activity/modern/vip/VipViewModel.kt b/mbw/src/main/java/com/mycelium/wallet/activity/modern/vip/VipViewModel.kt index 1620d55fa..31b6136ef 100644 --- a/mbw/src/main/java/com/mycelium/wallet/activity/modern/vip/VipViewModel.kt +++ b/mbw/src/main/java/com/mycelium/wallet/activity/modern/vip/VipViewModel.kt @@ -12,7 +12,7 @@ import kotlinx.coroutines.launch class VipViewModel : ViewModel() { - private val userRepository = Api.userRepository + private val userRepository = Api.statusRepository data class State( val success: Boolean = false, @@ -26,8 +26,8 @@ class VipViewModel : ViewModel() { init { viewModelScope.launch { - userRepository.userFlow.collect { user -> - val success = user.status.isVIP() + userRepository.statusFlow.collect { status -> + val success = status.isVIP() _stateFlow.update { state -> state.copy(progress = false, success = success) } } } diff --git a/mbw/src/main/java/com/mycelium/wallet/external/changelly/ChangellyOfferActivity.java b/mbw/src/main/java/com/mycelium/wallet/external/changelly/ChangellyOfferActivity.java index 51c15810e..3fa726d1b 100644 --- a/mbw/src/main/java/com/mycelium/wallet/external/changelly/ChangellyOfferActivity.java +++ b/mbw/src/main/java/com/mycelium/wallet/external/changelly/ChangellyOfferActivity.java @@ -156,7 +156,7 @@ private void createOffer() { amount = getIntent().getDoubleExtra(ChangellyAPIService.AMOUNT, 0); currency = getIntent().getStringExtra(ChangellyAPIService.FROM); receivingAddress = getIntent().getStringExtra(ChangellyAPIService.DESTADDRESS); - ChangellyRetrofitFactory.INSTANCE.getApi() + ChangellyRetrofitFactory.INSTANCE.getChangellyApi() .createTransaction(currency, BTC, amount, receivingAddress) .enqueue(new GetOfferCallback(amount)); progressDialog = new ProgressDialog(this); diff --git a/mbw/src/main/java/com/mycelium/wallet/external/changelly/ChangellyRetrofitFactory.kt b/mbw/src/main/java/com/mycelium/wallet/external/changelly/ChangellyRetrofitFactory.kt index 4b62810a9..e3cb98dbf 100644 --- a/mbw/src/main/java/com/mycelium/wallet/external/changelly/ChangellyRetrofitFactory.kt +++ b/mbw/src/main/java/com/mycelium/wallet/external/changelly/ChangellyRetrofitFactory.kt @@ -10,11 +10,12 @@ import retrofit2.converter.gson.GsonConverterFactory import javax.net.ssl.SSLContext object ChangellyRetrofitFactory { - private const val BASE_URL = "https://changelly-viper.mycelium.com/v2/" + private const val VIPER_BASE_URL = "https://changelly-viper.mycelium.com/v2/" + private const val CHANGELLY_BASE_URL = "https://api.changelly.com/v2/" - private val userKeyPair = UserKeysManager.userSignKeys + private val userKeyPair by lazy { UserKeysManager.userSignKeys } - private fun getHttpClient(): OkHttpClient { + private fun getViperHttpClient(): OkHttpClient { val sslContext = SSLContext.getInstance("TLSv1.3") sslContext.init(null, null, null) return OkHttpClient.Builder().apply { @@ -33,13 +34,28 @@ object ChangellyRetrofitFactory { }.build() } + private fun getChangellyHttpClient(): OkHttpClient { + return OkHttpClient.Builder().apply { + addInterceptor(ChangellyInterceptor()) + if (!BuildConfig.DEBUG) return@apply + addInterceptor(HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)) + }.build() + } - val api: ChangellyAPIService = + val viperApi: ChangellyAPIService by lazy { Retrofit.Builder() - .baseUrl(BASE_URL) + .baseUrl(VIPER_BASE_URL) .addConverterFactory(GsonConverterFactory.create()) - .client(getHttpClient()) + .client(getViperHttpClient()) .build() .create(ChangellyAPIService::class.java) -} + } + val changellyApi: ChangellyAPIService = + Retrofit.Builder() + .baseUrl(CHANGELLY_BASE_URL) + .addConverterFactory(GsonConverterFactory.create()) + .client(getChangellyHttpClient()) + .build() + .create(ChangellyAPIService::class.java) +} diff --git a/mbw/src/main/java/com/mycelium/wallet/external/changelly2/remote/Api.kt b/mbw/src/main/java/com/mycelium/wallet/external/changelly2/remote/Api.kt index 8030e43ae..b3d3e75ba 100644 --- a/mbw/src/main/java/com/mycelium/wallet/external/changelly2/remote/Api.kt +++ b/mbw/src/main/java/com/mycelium/wallet/external/changelly2/remote/Api.kt @@ -2,5 +2,5 @@ package com.mycelium.wallet.external.changelly2.remote object Api { - val userRepository by lazy { UserRepository() } + val statusRepository by lazy { StatusRepository() } } \ No newline at end of file diff --git a/mbw/src/main/java/com/mycelium/wallet/external/changelly2/remote/Changelly2Repository.kt b/mbw/src/main/java/com/mycelium/wallet/external/changelly2/remote/Changelly2Repository.kt index 1cedf7b66..1b37f3df3 100644 --- a/mbw/src/main/java/com/mycelium/wallet/external/changelly2/remote/Changelly2Repository.kt +++ b/mbw/src/main/java/com/mycelium/wallet/external/changelly2/remote/Changelly2Repository.kt @@ -2,6 +2,7 @@ package com.mycelium.wallet.external.changelly2.remote import androidx.lifecycle.LifecycleCoroutineScope import com.mycelium.bequant.remote.doRequest +import com.mycelium.wallet.external.changelly.ChangellyAPIService import com.mycelium.wallet.external.changelly.ChangellyRetrofitFactory import com.mycelium.wallet.external.changelly.model.ChangellyCurrency import com.mycelium.wallet.external.changelly.model.ChangellyListResponse @@ -13,7 +14,14 @@ import kotlinx.coroutines.CoroutineScope import java.math.BigDecimal object Changelly2Repository { - private val api = ChangellyRetrofitFactory.api + private val userRepository by lazy { Api.statusRepository } + private val viperApi by lazy { ChangellyRetrofitFactory.viperApi } + private val changellyApi = ChangellyRetrofitFactory.changellyApi + private val api + get(): ChangellyAPIService { + val status = userRepository.statusFlow.value + return if (status.isVIP()) viperApi else changellyApi + } fun supportCurrenciesFull( scope: CoroutineScope, @@ -83,7 +91,7 @@ object Changelly2Repository { finally: (() -> Unit)? = null ) { doRequest(scope, { - api.getTransaction(id) + changellyApi.getTransaction(id) }, success, error, finally) } @@ -94,7 +102,7 @@ object Changelly2Repository { finally: (() -> Unit)? = null ) { doRequest(scope, { - api.getTransactions(ids) + changellyApi.getTransactions(ids) }, success, error, finally) } } diff --git a/mbw/src/main/java/com/mycelium/wallet/external/changelly2/remote/StatusRepository.kt b/mbw/src/main/java/com/mycelium/wallet/external/changelly2/remote/StatusRepository.kt new file mode 100644 index 000000000..fadaadb2e --- /dev/null +++ b/mbw/src/main/java/com/mycelium/wallet/external/changelly2/remote/StatusRepository.kt @@ -0,0 +1,54 @@ +package com.mycelium.wallet.external.changelly2.remote + +import android.app.Activity +import androidx.core.content.edit +import com.mycelium.bequant.remote.model.UserStatus +import com.mycelium.wallet.WalletApplication +import com.mycelium.wallet.external.vip.VipRetrofitFactory +import com.mycelium.wallet.external.vip.model.ActivateVipRequest +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch + +class StatusRepository { + private val vipApi by lazy { VipRetrofitFactory().createApi() } + private val preference = WalletApplication.getInstance() + .getSharedPreferences(PREFERENCES_VIP_FILE, Activity.MODE_PRIVATE) + + private fun getLocalStatus() = UserStatus.fromName(preference.getString(VIP_STATUS_KEY, null)) + + private val _statusFlow = MutableStateFlow(getLocalStatus() ?: UserStatus.REGULAR) + val statusFlow = _statusFlow.asStateFlow() + + init { + val localStatus = getLocalStatus() + if (localStatus == null) { + GlobalScope.launch(Dispatchers.IO) { + try { + val checkResult = vipApi.check() + // if user is VIP than response contains his code else response contains empty string + val isVIP = checkResult.vipCode.isNotEmpty() + val status = if (isVIP) UserStatus.VIP else UserStatus.REGULAR + preference.edit { putString(VIP_STATUS_KEY, status.name) } + _statusFlow.value = status + } catch (_: Exception) { + } + } + } + } + + suspend fun applyVIPCode(code: String): UserStatus { + val response = vipApi.activate(ActivateVipRequest(code)) + val status = if (response.done) UserStatus.VIP else UserStatus.REGULAR + _statusFlow.value = status + preference.edit { putString(VIP_STATUS_KEY, status.name) } + return status + } + + private companion object { + const val PREFERENCES_VIP_FILE = "VIP_PREFERENCES" + const val VIP_STATUS_KEY = "VIP_STATUS" + } +} diff --git a/mbw/src/main/java/com/mycelium/wallet/external/changelly2/remote/UserRepository.kt b/mbw/src/main/java/com/mycelium/wallet/external/changelly2/remote/UserRepository.kt deleted file mode 100644 index a0a33bf9a..000000000 --- a/mbw/src/main/java/com/mycelium/wallet/external/changelly2/remote/UserRepository.kt +++ /dev/null @@ -1,52 +0,0 @@ -package com.mycelium.wallet.external.changelly2.remote - -import android.app.Activity -import androidx.core.content.edit -import com.mycelium.bequant.remote.model.User -import com.mycelium.wallet.WalletApplication -import com.mycelium.wallet.external.vip.VipRetrofitFactory -import com.mycelium.wallet.external.vip.model.ActivateVipRequest -import com.mycelium.wallet.update -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.filterNotNull - -class UserRepository { - private val _userFlow = MutableStateFlow(null) - private val vipApi = VipRetrofitFactory().createApi() - private val preference by lazy { - WalletApplication - .getInstance() - .getSharedPreferences(PREFERENCES_VIP_FILE, Activity.MODE_PRIVATE) - } - - val userFlow = _userFlow.filterNotNull() - - suspend fun identify() { - val status = try { - val checkResult = vipApi.check() - // if user is VIP than response contains his code else response contains empty string - val isVIP = checkResult.vipCode.isNotEmpty() - if (isVIP) User.Status.VIP - else User.Status.REGULAR - } catch (_: Exception) { - val statusName = preference.getString(VIP_STATUS_KEY, null) - if (statusName != null) User.Status.fromName(statusName) - else User.Status.REGULAR - } - _userFlow.value = User(status) - preference.edit { putString(VIP_STATUS_KEY, status.name) } - } - - suspend fun applyVIPCode(code: String): User.Status { - val response = vipApi.activate(ActivateVipRequest(code)) - val status = if (response.done) User.Status.VIP else User.Status.REGULAR - _userFlow.update { user -> user?.copy(status = status) ?: User(status) } - preference.edit { putString(VIP_STATUS_KEY, status.name) } - return status - } - - private companion object { - const val PREFERENCES_VIP_FILE = "VIP_PREFERENCES" - const val VIP_STATUS_KEY = "VIP_STATUS" - } -} From c4dedcf3191045a02fb5f3b05ecafa0f248bad4b Mon Sep 17 00:00:00 2001 From: ismurzin Date: Fri, 17 May 2024 19:38:41 +0500 Subject: [PATCH 05/19] separate viper calls --- .../wallet/activity/modern/vip/VipFragment.kt | 61 ++++++--- .../activity/modern/vip/VipViewModel.kt | 35 +++-- .../external/changelly/ChangellyAPIService.kt | 2 +- .../changelly/ChangellyRetrofitFactory.kt | 2 + .../external/changelly2/ExchangeFragment.kt | 126 +++++++++++------- .../changelly2/remote/Changelly2Repository.kt | 53 +++++--- .../changelly2/remote/StatusRepository.kt | 11 +- .../wallet/external/vip/VipRetrofitFactory.kt | 2 + mbw/src/main/res/values/strings.xml | 7 + 9 files changed, 201 insertions(+), 98 deletions(-) diff --git a/mbw/src/main/java/com/mycelium/wallet/activity/modern/vip/VipFragment.kt b/mbw/src/main/java/com/mycelium/wallet/activity/modern/vip/VipFragment.kt index 34e710921..6d31991f9 100644 --- a/mbw/src/main/java/com/mycelium/wallet/activity/modern/vip/VipFragment.kt +++ b/mbw/src/main/java/com/mycelium/wallet/activity/modern/vip/VipFragment.kt @@ -6,6 +6,7 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.view.inputmethod.InputMethodManager +import androidx.appcompat.app.AlertDialog import androidx.core.view.isVisible import androidx.core.widget.doOnTextChanged import androidx.fragment.app.Fragment @@ -49,7 +50,7 @@ class VipFragment : Fragment() { binding.vipProgress.isVisible = state.progress updateButtons(state) handleError(state.error) - handleSuccess(state.success) + handleSuccess(state.isVip) } } @@ -70,39 +71,59 @@ class VipFragment : Fragment() { private fun updateButtons(state: VipViewModel.State) { binding.vipApplyButton.apply { - isEnabled = state.text.isNotEmpty() && !state.progress && !state.error + isEnabled = state.text.isNotEmpty() && !state.progress && state.error == null text = if (state.progress) "" else getString(R.string.apply_vip_code) setOnClickListener { viewModel.applyCode() } } } - private fun handleError(error: Boolean) = binding.apply { - if (error) { - errorText.isVisible = true - vipCodeInput.setBackgroundResource(R.drawable.bg_input_text_filled_error) - } else { - errorText.isVisible = false - vipCodeInput.setBackgroundResource(R.drawable.bg_input_text_filled) + private fun handleError(error: VipViewModel.ErrorType?) = binding.apply { + when (error) { + null -> { + errorText.isVisible = false + vipCodeInput.setBackgroundResource(R.drawable.bg_input_text_filled) + } + + VipViewModel.ErrorType.BAD_REQUEST -> { + errorText.isVisible = true + vipCodeInput.setBackgroundResource(R.drawable.bg_input_text_filled_error) + } + + else -> { + hideKeyBoard() + showViperUnexpectedErrorDialog() + } } } private fun handleSuccess(success: Boolean) { - if (!success) return binding.apply { - vipApplyButton.isVisible = false - vipInputGroup.isVisible = false - vipSuccessGroup.isVisible = true - vipTitle.setText(R.string.vip_title_success) - vipCodeInput.apply { - hint = null - text = null - isFocusable = false - clearFocus() + vipApplyButton.isVisible = !success + vipInputGroup.isVisible = !success + vipSuccessGroup.isVisible = success + vipTitle.setText(if (success) R.string.vip_title_success else R.string.vip_title) + if (success) { + vipCodeInput.apply { + hint = null + text = null + clearFocus() + } + hideKeyBoard() + } else { + vipCodeInput.hint = getString(R.string.vip_code_hint) } - hideKeyBoard() } } + private fun showViperUnexpectedErrorDialog() { + AlertDialog.Builder(requireContext()) + .setTitle(getString(R.string.vip_unexpected_alert_title)) + .setMessage(getString(R.string.vip_unexpected_alert_message)) + .setPositiveButton(R.string.button_ok, null) + .setOnDismissListener { viewModel.resetState() } + .show() + } + private fun hideKeyBoard() { val imm = context?.getSystemService(Activity.INPUT_METHOD_SERVICE) as? InputMethodManager imm?.hideSoftInputFromWindow(requireView().windowToken, 0) diff --git a/mbw/src/main/java/com/mycelium/wallet/activity/modern/vip/VipViewModel.kt b/mbw/src/main/java/com/mycelium/wallet/activity/modern/vip/VipViewModel.kt index 31b6136ef..3fe28483b 100644 --- a/mbw/src/main/java/com/mycelium/wallet/activity/modern/vip/VipViewModel.kt +++ b/mbw/src/main/java/com/mycelium/wallet/activity/modern/vip/VipViewModel.kt @@ -9,14 +9,15 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch +import retrofit2.HttpException class VipViewModel : ViewModel() { private val userRepository = Api.statusRepository data class State( - val success: Boolean = false, - val error: Boolean = false, + val isVip: Boolean = false, + val error: ErrorType? = null, val progress: Boolean = true, val text: String = "", ) @@ -27,25 +28,41 @@ class VipViewModel : ViewModel() { init { viewModelScope.launch { userRepository.statusFlow.collect { status -> - val success = status.isVIP() - _stateFlow.update { state -> state.copy(progress = false, success = success) } + val isVip = status.isVIP() + _stateFlow.update { state -> state.copy(progress = false, isVip = isVip) } } } } fun updateVipText(text: String) { - _stateFlow.update { s -> s.copy(text = text, error = false) } + _stateFlow.update { s -> s.copy(text = text, error = null) } } - private val exceptionHandler = CoroutineExceptionHandler { _, _ -> - _stateFlow.update { s -> s.copy(progress = false, error = true, success = false) } + private val exceptionHandler = CoroutineExceptionHandler { _, e -> + var errorType = ErrorType.UNEXPECTED + if (e is HttpException) { + if (e.code() == 404 || e.code() == 409 || e.code() == 401) { + errorType = ErrorType.BAD_REQUEST + } + } + _stateFlow.update { s -> s.copy(progress = false, error = errorType, isVip = false) } } fun applyCode() { viewModelScope.launch(exceptionHandler) { - _stateFlow.update { s -> s.copy(progress = true, error = false, success = false) } + _stateFlow.update { s -> s.copy(progress = true, error = null, isVip = false) } val status = userRepository.applyVIPCode(_stateFlow.value.text) - _stateFlow.update { s -> s.copy(progress = false, error = !status.isVIP()) } + val error = if (status.isVIP()) null else ErrorType.BAD_REQUEST + _stateFlow.update { s -> s.copy(progress = false, error = error) } } } + + fun resetState() { + _stateFlow.update { s -> s.copy(progress = false, error = null, isVip = false) } + } + + enum class ErrorType { + UNEXPECTED, + BAD_REQUEST, + } } \ No newline at end of file diff --git a/mbw/src/main/java/com/mycelium/wallet/external/changelly/ChangellyAPIService.kt b/mbw/src/main/java/com/mycelium/wallet/external/changelly/ChangellyAPIService.kt index 1b6f5932d..074ebe8b6 100644 --- a/mbw/src/main/java/com/mycelium/wallet/external/changelly/ChangellyAPIService.kt +++ b/mbw/src/main/java/com/mycelium/wallet/external/changelly/ChangellyAPIService.kt @@ -81,7 +81,7 @@ interface ChangellyAPIService { @Query("address") address: String, @Query("rateId") rateId: String, @Query("refundAddress") refundAddress: String, - ): Response> + ): ChangellyResponse @POST("getTransactions") suspend fun getTransaction( diff --git a/mbw/src/main/java/com/mycelium/wallet/external/changelly/ChangellyRetrofitFactory.kt b/mbw/src/main/java/com/mycelium/wallet/external/changelly/ChangellyRetrofitFactory.kt index e3cb98dbf..e15262eda 100644 --- a/mbw/src/main/java/com/mycelium/wallet/external/changelly/ChangellyRetrofitFactory.kt +++ b/mbw/src/main/java/com/mycelium/wallet/external/changelly/ChangellyRetrofitFactory.kt @@ -7,6 +7,7 @@ import okhttp3.OkHttpClient import okhttp3.logging.HttpLoggingInterceptor import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory +import java.util.concurrent.TimeUnit import javax.net.ssl.SSLContext object ChangellyRetrofitFactory { @@ -19,6 +20,7 @@ object ChangellyRetrofitFactory { val sslContext = SSLContext.getInstance("TLSv1.3") sslContext.init(null, null, null) return OkHttpClient.Builder().apply { + connectTimeout(3, TimeUnit.SECONDS) // sslSocketFactory uses system defaults X509TrustManager, so deprecation suppressed // referring to sslSocketFactory(SSLSocketFactory, X509TrustManager) docs: /** diff --git a/mbw/src/main/java/com/mycelium/wallet/external/changelly2/ExchangeFragment.kt b/mbw/src/main/java/com/mycelium/wallet/external/changelly2/ExchangeFragment.kt index 5f5c30b71..556b09615 100644 --- a/mbw/src/main/java/com/mycelium/wallet/external/changelly2/ExchangeFragment.kt +++ b/mbw/src/main/java/com/mycelium/wallet/external/changelly2/ExchangeFragment.kt @@ -47,8 +47,9 @@ import com.mycelium.wallet.event.SelectedCurrencyChanged import com.mycelium.wallet.event.TransactionBroadcasted import com.mycelium.wallet.external.changelly.model.ChangellyResponse import com.mycelium.wallet.external.changelly.model.ChangellyTransactionOffer -import com.mycelium.wallet.external.changelly.model.FixRate import com.mycelium.wallet.external.changelly2.remote.Changelly2Repository +import com.mycelium.wallet.external.changelly2.remote.ViperStatusException +import com.mycelium.wallet.external.changelly2.remote.ViperUnexpectedException import com.mycelium.wallet.external.changelly2.viewmodel.ExchangeViewModel import com.mycelium.wallet.external.partner.openLink import com.mycelium.wallet.startCoroutineTimer @@ -67,6 +68,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import retrofit2.HttpException import java.math.BigDecimal import java.math.RoundingMode import java.util.concurrent.TimeUnit @@ -268,51 +270,7 @@ class ExchangeFragment : Fragment(), BackListener { } } binding?.exchangeButton?.setOnClickListener { - loader(true) - Changelly2Repository.createFixTransaction(lifecycleScope, - viewModel.exchangeInfo.value?.id!!, - Util.trimTestnetSymbolDecoration(viewModel.fromCurrency.value?.symbol!!), - Util.trimTestnetSymbolDecoration(viewModel.toCurrency.value?.symbol!!), - viewModel.sellValue.value!!, - viewModel.toAddress.value!!, - viewModel.fromAddress.value!!, - { result -> - if (result?.result != null) { - viewLifecycleOwner.lifecycleScope.launch(Dispatchers.Default) { - val unsignedTx = prepareTx( - if (BuildConfig.FLAVOR == "btctestnet") - viewModel.fromAddress.value!! - else - result.result!!.payinAddress!!, - result.result!!.amountExpectedFrom.toPlainString()) - if(unsignedTx != null) { - launch(Dispatchers.Main) { - loader(false) - acceptDialog(unsignedTx, result) { - sendTx(result.result!!.id!!, unsignedTx) - } - } - } - } - } else { - loader(false) - AlertDialog.Builder(requireContext()) - .setMessage(if (result?.error?.message?.startsWith("rateId was expired") == true) - getString(R.string.changelly_error_rate_expired) - else result?.error?.message) - .setPositiveButton(R.string.button_ok, null) - .setOnDismissListener { updateAmount() } - .show() - } - }, - { _, msg -> - loader(false) - AlertDialog.Builder(requireContext()) - .setMessage(msg) - .setPositiveButton(R.string.button_ok, null) - .setOnDismissListener { updateAmount() } - .show() - }) + createFixTransaction() } viewModel.fromCurrency.observe(viewLifecycleOwner) { coin -> binding?.sellLayout?.coinIcon?.let { @@ -364,6 +322,82 @@ class ExchangeFragment : Fragment(), BackListener { } } + private fun createFixTransaction(changellyOnly: Boolean = false){ + loader(true) + lifecycleScope.launch { + try { + val response = Changelly2Repository.createFixTransaction( + viewModel.exchangeInfo.value?.id!!, + Util.trimTestnetSymbolDecoration(viewModel.fromCurrency.value?.symbol!!), + Util.trimTestnetSymbolDecoration(viewModel.toCurrency.value?.symbol!!), + viewModel.sellValue.value!!, + viewModel.toAddress.value!!, + viewModel.fromAddress.value!!, + changellyOnly, + ) + val result = response.result + if (result != null) { + withContext(Dispatchers.Default) { + val addressTo = + if (BuildConfig.FLAVOR == "btctestnet") viewModel.fromAddress.value!! + else result.payinAddress!! + val amount = result.amountExpectedFrom.toPlainString() + val unsignedTx = prepareTx(addressTo, amount) + withContext(Dispatchers.Main) { + loader(false) + if (unsignedTx != null) { + acceptDialog(unsignedTx, response) { + sendTx(result.id!!, unsignedTx) + } + } + } + } + } else { + loader(false) + showErrorNotificationDialog(response.error?.message) + } + } catch (e: Exception) { + loader(false) + when (e) { + is HttpException -> showErrorNotificationDialog(e.message()) + is ViperStatusException -> showViperErrorDialog( + getString(R.string.vip_exchange_unexpected_alert_title), + getString(R.string.vip_exchange_status_expired_alert_message), + ) + is ViperUnexpectedException -> showViperErrorDialog( + getString(R.string.vip_exchange_unexpected_alert_title), + getString(R.string.vip_exchange_unexpected_alert_message), + ) + else -> showErrorNotificationDialog(e.message) + } + } + } + } + private fun showErrorNotificationDialog(message: String?) { + val localizedMessage = if (message?.startsWith("rateId was expired") == true) { + getString(R.string.changelly_error_rate_expired) + } else { + message ?: "Something went wrong." + } + AlertDialog.Builder(requireContext()) + .setMessage(localizedMessage) + .setPositiveButton(R.string.button_ok, null) + .setOnDismissListener { updateAmount() } + .show() + } + + private fun showViperErrorDialog(title: String, message: String) { + AlertDialog.Builder(requireContext()) + .setTitle(title) + .setMessage(message) + .setPositiveButton(R.string.vip_alert_proceed) { _, _ -> + updateAmount() + createFixTransaction(true) + } + .setNegativeButton(R.string.vip_alert_cancel, null) + .show() + } + private fun computeBuyValue() { val amount = viewModel.sellValue.value val info = viewModel.exchangeInfo.value diff --git a/mbw/src/main/java/com/mycelium/wallet/external/changelly2/remote/Changelly2Repository.kt b/mbw/src/main/java/com/mycelium/wallet/external/changelly2/remote/Changelly2Repository.kt index 1b37f3df3..b565f0b16 100644 --- a/mbw/src/main/java/com/mycelium/wallet/external/changelly2/remote/Changelly2Repository.kt +++ b/mbw/src/main/java/com/mycelium/wallet/external/changelly2/remote/Changelly2Repository.kt @@ -2,7 +2,6 @@ package com.mycelium.wallet.external.changelly2.remote import androidx.lifecycle.LifecycleCoroutineScope import com.mycelium.bequant.remote.doRequest -import com.mycelium.wallet.external.changelly.ChangellyAPIService import com.mycelium.wallet.external.changelly.ChangellyRetrofitFactory import com.mycelium.wallet.external.changelly.model.ChangellyCurrency import com.mycelium.wallet.external.changelly.model.ChangellyListResponse @@ -11,17 +10,13 @@ import com.mycelium.wallet.external.changelly.model.ChangellyTransaction import com.mycelium.wallet.external.changelly.model.ChangellyTransactionOffer import com.mycelium.wallet.external.changelly.model.FixRate import kotlinx.coroutines.CoroutineScope +import retrofit2.HttpException import java.math.BigDecimal object Changelly2Repository { private val userRepository by lazy { Api.statusRepository } private val viperApi by lazy { ChangellyRetrofitFactory.viperApi } private val changellyApi = ChangellyRetrofitFactory.changellyApi - private val api - get(): ChangellyAPIService { - val status = userRepository.statusFlow.value - return if (status.isVIP()) viperApi else changellyApi - } fun supportCurrenciesFull( scope: CoroutineScope, @@ -30,7 +25,7 @@ object Changelly2Repository { finally: (() -> Unit)? = null ) { doRequest(scope, { - api.getCurrenciesFull() + changellyApi.getCurrenciesFull() }, success, error, finally) } @@ -44,7 +39,7 @@ object Changelly2Repository { finally: (() -> Unit)? = null ) = doRequest(scope, { - api.getFixRateForAmount(exportSymbol(from), exportSymbol(to), amount) + changellyApi.getFixRateForAmount(exportSymbol(from), exportSymbol(to), amount) }, success, error, finally) fun fixRate( @@ -56,31 +51,46 @@ object Changelly2Repository { finally: (() -> Unit)? = null ) = doRequest(scope, { - api.getFixRate(exportSymbol(from), exportSymbol(to)) + changellyApi.getFixRate(exportSymbol(from), exportSymbol(to)) }, success, error, finally) - fun createFixTransaction( - scope: CoroutineScope, + suspend fun createFixTransaction( rateId: String, from: String, to: String, amount: String, addressTo: String, refundAddress: String, - success: (ChangellyResponse?) -> Unit, - error: (Int, String) -> Unit, - finally: (() -> Unit)? = null - ) { - doRequest(scope, { - api.createFixTransaction( + changellyOnly: Boolean, + ): ChangellyResponse { + val isVip = userRepository.statusFlow.value.isVIP() + if (!isVip || changellyOnly) { + return changellyApi.createFixTransaction( exportSymbol(from), exportSymbol(to), amount, addressTo, rateId, - refundAddress + refundAddress, ) - }, success, error, finally) + } + try { + return viperApi.createFixTransaction( + exportSymbol(from), + exportSymbol(to), + amount, + addressTo, + rateId, + refundAddress, + ) + } catch (e: Exception) { + // Http exception with 401 unauthorized code means that user isn't vip anymore + if (e is HttpException && e.code() == 401) { + userRepository.dropStatus() + throw ViperStatusException(e) + } + throw ViperUnexpectedException(e) + } } fun getTransaction( @@ -119,4 +129,7 @@ private fun importSymbol(currency: String) = private fun exportSymbol(currency: String) = if (currency.equals("USDT", true)) "USDT20".toLowerCase() - else currency.toLowerCase() \ No newline at end of file + else currency.toLowerCase() + +class ViperUnexpectedException(e: Exception) : Exception(e) +class ViperStatusException(e: Exception) : Exception(e) \ No newline at end of file diff --git a/mbw/src/main/java/com/mycelium/wallet/external/changelly2/remote/StatusRepository.kt b/mbw/src/main/java/com/mycelium/wallet/external/changelly2/remote/StatusRepository.kt index fadaadb2e..3284132db 100644 --- a/mbw/src/main/java/com/mycelium/wallet/external/changelly2/remote/StatusRepository.kt +++ b/mbw/src/main/java/com/mycelium/wallet/external/changelly2/remote/StatusRepository.kt @@ -19,12 +19,14 @@ class StatusRepository { private fun getLocalStatus() = UserStatus.fromName(preference.getString(VIP_STATUS_KEY, null)) - private val _statusFlow = MutableStateFlow(getLocalStatus() ?: UserStatus.REGULAR) + private val _statusFlow = MutableStateFlow(UserStatus.REGULAR) val statusFlow = _statusFlow.asStateFlow() init { val localStatus = getLocalStatus() - if (localStatus == null) { + if (localStatus != null) { + _statusFlow.value = localStatus + } else { GlobalScope.launch(Dispatchers.IO) { try { val checkResult = vipApi.check() @@ -47,6 +49,11 @@ class StatusRepository { return status } + fun dropStatus() { + preference.edit { putString(VIP_STATUS_KEY, UserStatus.REGULAR.name) } + _statusFlow.value = UserStatus.REGULAR + } + private companion object { const val PREFERENCES_VIP_FILE = "VIP_PREFERENCES" const val VIP_STATUS_KEY = "VIP_STATUS" diff --git a/mbw/src/main/java/com/mycelium/wallet/external/vip/VipRetrofitFactory.kt b/mbw/src/main/java/com/mycelium/wallet/external/vip/VipRetrofitFactory.kt index 03c22d804..34577c131 100644 --- a/mbw/src/main/java/com/mycelium/wallet/external/vip/VipRetrofitFactory.kt +++ b/mbw/src/main/java/com/mycelium/wallet/external/vip/VipRetrofitFactory.kt @@ -7,6 +7,7 @@ import okhttp3.OkHttpClient import okhttp3.logging.HttpLoggingInterceptor import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory +import java.util.concurrent.TimeUnit import javax.net.ssl.SSLContext class VipRetrofitFactory { @@ -21,6 +22,7 @@ class VipRetrofitFactory { sslContext.init(null, null, null) return OkHttpClient.Builder() .apply { + connectTimeout(3, TimeUnit.SECONDS) // sslSocketFactory uses system defaults X509TrustManager, so deprecation suppressed // referring to sslSocketFactory(SSLSocketFactory, X509TrustManager) docs: /** diff --git a/mbw/src/main/res/values/strings.xml b/mbw/src/main/res/values/strings.xml index b9654a4bc..0df09d37c 100644 --- a/mbw/src/main/res/values/strings.xml +++ b/mbw/src/main/res/values/strings.xml @@ -1869,4 +1869,11 @@ All new bitcoins, mined by the pool, are proportionally distributed among RMC bi Get the VIP experience VIP You are all set! + Make an exchange at regular rate? + Your VIP status has expired, so regular (non-VIP) rates now apply. + VIP service is currently unavailable due to technical reasons, so regular (non-VIP) rates are now in effect. + VIP service is currently unavailable + Please try again later + Yes, proceed + Cancel From 5e6d2a4e3824c29f950aa942a808344e9259f727 Mon Sep 17 00:00:00 2001 From: ismurzin Date: Fri, 17 May 2024 21:10:14 +0500 Subject: [PATCH 06/19] fix viper rates issue --- .../changelly2/remote/Changelly2Repository.kt | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/mbw/src/main/java/com/mycelium/wallet/external/changelly2/remote/Changelly2Repository.kt b/mbw/src/main/java/com/mycelium/wallet/external/changelly2/remote/Changelly2Repository.kt index b565f0b16..89c02474d 100644 --- a/mbw/src/main/java/com/mycelium/wallet/external/changelly2/remote/Changelly2Repository.kt +++ b/mbw/src/main/java/com/mycelium/wallet/external/changelly2/remote/Changelly2Repository.kt @@ -64,10 +64,12 @@ object Changelly2Repository { changellyOnly: Boolean, ): ChangellyResponse { val isVip = userRepository.statusFlow.value.isVIP() + val fromSymbol = exportSymbol(from) + val toSymbol = exportSymbol(to) if (!isVip || changellyOnly) { return changellyApi.createFixTransaction( - exportSymbol(from), - exportSymbol(to), + fromSymbol, + toSymbol, amount, addressTo, rateId, @@ -75,12 +77,17 @@ object Changelly2Repository { ) } try { + // changelly can handle rates only from same api keys + // that's why new rateId should be refetched + val rate = viperApi.getFixRate(fromSymbol, toSymbol) + val viperRateId = rate.body()?.result?.firstOrNull()?.id + ?: throw RuntimeException("Unable to fetch rates") return viperApi.createFixTransaction( - exportSymbol(from), - exportSymbol(to), + fromSymbol, + toSymbol, amount, addressTo, - rateId, + viperRateId, refundAddress, ) } catch (e: Exception) { From 3cbbecca78e883b0ec19c6912b763e08e7339a23 Mon Sep 17 00:00:00 2001 From: elvis Date: Tue, 28 May 2024 17:20:15 +0200 Subject: [PATCH 07/19] enable vip tab --- .../java/com/mycelium/wallet/activity/modern/ModernMain.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mbw/src/main/java/com/mycelium/wallet/activity/modern/ModernMain.kt b/mbw/src/main/java/com/mycelium/wallet/activity/modern/ModernMain.kt index 202c45f0a..d9f3188b1 100644 --- a/mbw/src/main/java/com/mycelium/wallet/activity/modern/ModernMain.kt +++ b/mbw/src/main/java/com/mycelium/wallet/activity/modern/ModernMain.kt @@ -77,7 +77,7 @@ class ModernMain : AppCompatActivity(), BackHandler { private var mNewsTab: TabLayout.Tab? = null private var mAccountsTab: TabLayout.Tab? = null private var mTransactionsTab: TabLayout.Tab? = null -// private var mVipTab: TabLayout.Tab? = null + private var mVipTab: TabLayout.Tab? = null private var mRecommendationsTab: TabLayout.Tab? = null private var mFioRequestsTab: TabLayout.Tab? = null private var refreshItem: MenuItem? = null @@ -125,8 +125,8 @@ class ModernMain : AppCompatActivity(), BackHandler { mTabsAdapter!!.addTab(mBalanceTab!!, BalanceMasterFragment::class.java, null, TAB_BALANCE) mTransactionsTab = binding.pagerTabs.newTab().setText(getString(R.string.tab_transactions)) mTabsAdapter!!.addTab(mTransactionsTab!!, TransactionHistoryFragment::class.java, null, TAB_HISTORY) -// mVipTab = binding.pagerTabs.newTab().setText(getString(R.string.tab_vip)) -// mTabsAdapter!!.addTab(mVipTab!!, VipFragment::class.java, null, TAB_VIP) + mVipTab = binding.pagerTabs.newTab().setText(getString(R.string.tab_vip)) + mTabsAdapter!!.addTab(mVipTab!!, VipFragment::class.java, null, TAB_VIP) if (getPartnersLocalized()?.isActive() == true) { mRecommendationsTab = From 1d16817d561181fff314e3f23d9207d97aeda286 Mon Sep 17 00:00:00 2001 From: ismurzin Date: Fri, 31 May 2024 22:09:48 +0700 Subject: [PATCH 08/19] add vip dependency to getFixRateFrorAmount + remove double request from createFixTransaction --- .../changelly2/remote/Changelly2Repository.kt | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/mbw/src/main/java/com/mycelium/wallet/external/changelly2/remote/Changelly2Repository.kt b/mbw/src/main/java/com/mycelium/wallet/external/changelly2/remote/Changelly2Repository.kt index 89c02474d..0655d6af0 100644 --- a/mbw/src/main/java/com/mycelium/wallet/external/changelly2/remote/Changelly2Repository.kt +++ b/mbw/src/main/java/com/mycelium/wallet/external/changelly2/remote/Changelly2Repository.kt @@ -39,7 +39,9 @@ object Changelly2Repository { finally: (() -> Unit)? = null ) = doRequest(scope, { - changellyApi.getFixRateForAmount(exportSymbol(from), exportSymbol(to), amount) + val isVip = userRepository.statusFlow.value.isVIP() + val api = if (isVip) viperApi else changellyApi + api.getFixRateForAmount(exportSymbol(from), exportSymbol(to), amount) }, success, error, finally) fun fixRate( @@ -77,17 +79,12 @@ object Changelly2Repository { ) } try { - // changelly can handle rates only from same api keys - // that's why new rateId should be refetched - val rate = viperApi.getFixRate(fromSymbol, toSymbol) - val viperRateId = rate.body()?.result?.firstOrNull()?.id - ?: throw RuntimeException("Unable to fetch rates") return viperApi.createFixTransaction( fromSymbol, toSymbol, amount, addressTo, - viperRateId, + rateId, refundAddress, ) } catch (e: Exception) { From 0886979ebc4f874bb3819c2a7dfce1a39e8767cc Mon Sep 17 00:00:00 2001 From: elvis Date: Mon, 10 Jun 2024 14:59:22 +0200 Subject: [PATCH 09/19] fix vip dialog state texts --- .../wallet/external/changelly2/ExchangeFragment.kt | 12 ++++++++---- mbw/src/main/res/values/strings.xml | 5 +++-- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/mbw/src/main/java/com/mycelium/wallet/external/changelly2/ExchangeFragment.kt b/mbw/src/main/java/com/mycelium/wallet/external/changelly2/ExchangeFragment.kt index 556b09615..e6e6c1c52 100644 --- a/mbw/src/main/java/com/mycelium/wallet/external/changelly2/ExchangeFragment.kt +++ b/mbw/src/main/java/com/mycelium/wallet/external/changelly2/ExchangeFragment.kt @@ -361,12 +361,16 @@ class ExchangeFragment : Fragment(), BackListener { when (e) { is HttpException -> showErrorNotificationDialog(e.message()) is ViperStatusException -> showViperErrorDialog( - getString(R.string.vip_exchange_unexpected_alert_title), + getString(R.string.vip_exchange_expired_title), getString(R.string.vip_exchange_status_expired_alert_message), + getString(R.string.proceed), + getString(R.string.cancel_transaction) ) is ViperUnexpectedException -> showViperErrorDialog( getString(R.string.vip_exchange_unexpected_alert_title), getString(R.string.vip_exchange_unexpected_alert_message), + getString(R.string.proceed), + getString(R.string.vip_alert_cancel) ) else -> showErrorNotificationDialog(e.message) } @@ -386,15 +390,15 @@ class ExchangeFragment : Fragment(), BackListener { .show() } - private fun showViperErrorDialog(title: String, message: String) { + private fun showViperErrorDialog(title: String, message: String, positive:String, negative:String) { AlertDialog.Builder(requireContext()) .setTitle(title) .setMessage(message) - .setPositiveButton(R.string.vip_alert_proceed) { _, _ -> + .setPositiveButton(positive) { _, _ -> updateAmount() createFixTransaction(true) } - .setNegativeButton(R.string.vip_alert_cancel, null) + .setNegativeButton(negative, null) .show() } diff --git a/mbw/src/main/res/values/strings.xml b/mbw/src/main/res/values/strings.xml index 0df09d37c..787383515 100644 --- a/mbw/src/main/res/values/strings.xml +++ b/mbw/src/main/res/values/strings.xml @@ -1869,9 +1869,10 @@ All new bitcoins, mined by the pool, are proportionally distributed among RMC bi Get the VIP experience VIP You are all set! - Make an exchange at regular rate? + Ooops, something went wrong + Vip SWAP is not available Your VIP status has expired, so regular (non-VIP) rates now apply. - VIP service is currently unavailable due to technical reasons, so regular (non-VIP) rates are now in effect. + VIP service is currently not available due to technical reasons. Try again later or proceed without VIP benefits. VIP service is currently unavailable Please try again later Yes, proceed From acd4e2096b9d57b964bb3feec1704c29e601918c Mon Sep 17 00:00:00 2001 From: elvis Date: Tue, 11 Jun 2024 17:44:20 +0200 Subject: [PATCH 10/19] remove limit for get changelly method --- .../mycelium/wallet/external/changelly/ChangellyAPIService.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/mbw/src/main/java/com/mycelium/wallet/external/changelly/ChangellyAPIService.kt b/mbw/src/main/java/com/mycelium/wallet/external/changelly/ChangellyAPIService.kt index 074ebe8b6..1a6a0bff5 100644 --- a/mbw/src/main/java/com/mycelium/wallet/external/changelly/ChangellyAPIService.kt +++ b/mbw/src/main/java/com/mycelium/wallet/external/changelly/ChangellyAPIService.kt @@ -85,8 +85,7 @@ interface ChangellyAPIService { @POST("getTransactions") suspend fun getTransaction( - @Query("id") id: String, - @Query("limit") limit: Int = 1, + @Query("id") id: String ): Response>> @POST("getTransactions") From 7fd0cb25dff027c5c327eccab64da37958427548 Mon Sep 17 00:00:00 2001 From: elvis Date: Fri, 14 Jun 2024 14:40:12 +0200 Subject: [PATCH 11/19] changelly fix time formatting --- .../com/mycelium/wallet/external/changelly2/HistoryFragment.kt | 2 +- .../external/changelly2/viewmodel/ExchangeResultViewModel.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mbw/src/main/java/com/mycelium/wallet/external/changelly2/HistoryFragment.kt b/mbw/src/main/java/com/mycelium/wallet/external/changelly2/HistoryFragment.kt index 463c71de8..9919126fd 100644 --- a/mbw/src/main/java/com/mycelium/wallet/external/changelly2/HistoryFragment.kt +++ b/mbw/src/main/java/com/mycelium/wallet/external/changelly2/HistoryFragment.kt @@ -73,7 +73,7 @@ class HistoryFragment : DialogFragment() { TxItem(it.id, it.amountExpectedFrom.toString(), it.getExpectedAmount().toString(), it.fixedCurrencyFrom(), it.fixedCurrencyTo(), - DateFormat.getDateInstance(DateFormat.LONG).format(Date(it.createdAt * 1000L)), + DateFormat.getDateInstance(DateFormat.LONG).format(Date(it.createdAt / 1000L)), it.getReadableStatus()) }) } diff --git a/mbw/src/main/java/com/mycelium/wallet/external/changelly2/viewmodel/ExchangeResultViewModel.kt b/mbw/src/main/java/com/mycelium/wallet/external/changelly2/viewmodel/ExchangeResultViewModel.kt index ac11567e1..5d240f03f 100644 --- a/mbw/src/main/java/com/mycelium/wallet/external/changelly2/viewmodel/ExchangeResultViewModel.kt +++ b/mbw/src/main/java/com/mycelium/wallet/external/changelly2/viewmodel/ExchangeResultViewModel.kt @@ -42,7 +42,7 @@ class ExchangeResultViewModel : ViewModel() { txId.value = result.id spendValue.value = "${result.amountExpectedFrom} ${result.currencyFrom.toUpperCase()}" getValue.value = "${result.getExpectedAmount()} ${result.currencyTo.toUpperCase()}" - date.value = DateFormat.getDateInstance(DateFormat.LONG).format(Date(result.createdAt * 1000L)) + date.value = DateFormat.getDateInstance(DateFormat.LONG).format(Date(result.createdAt / 1000L)) spendValueFiat.value = getFiatValue(result.amountExpectedFrom, result.currencyFrom) getValueFiat.value = getFiatValue(result.getExpectedAmount(), result.currencyTo) isExchangeComplete.value = result.status == "finished" From 7f15585d544b6316e0601cd4846c56b4d330268a Mon Sep 17 00:00:00 2001 From: ismurzin Date: Wed, 19 Jun 2024 20:05:29 +0700 Subject: [PATCH 12/19] fix viper exchange history --- .../external/changelly/ChangellyAPIService.kt | 4 +- .../changelly2/ExchangeResultFragment.kt | 61 +++++++++++-------- .../external/changelly2/HistoryFragment.kt | 58 +++++++++++------- .../changelly2/remote/Changelly2Repository.kt | 58 ++++++++++++------ 4 files changed, 109 insertions(+), 72 deletions(-) diff --git a/mbw/src/main/java/com/mycelium/wallet/external/changelly/ChangellyAPIService.kt b/mbw/src/main/java/com/mycelium/wallet/external/changelly/ChangellyAPIService.kt index 074ebe8b6..f0d7d23c3 100644 --- a/mbw/src/main/java/com/mycelium/wallet/external/changelly/ChangellyAPIService.kt +++ b/mbw/src/main/java/com/mycelium/wallet/external/changelly/ChangellyAPIService.kt @@ -87,12 +87,12 @@ interface ChangellyAPIService { suspend fun getTransaction( @Query("id") id: String, @Query("limit") limit: Int = 1, - ): Response>> + ): ChangellyResponse> @POST("getTransactions") suspend fun getTransactions( @Query("id") id: List, - ): Response>> + ): ChangellyResponse> companion object { const val BCH = "BCH" diff --git a/mbw/src/main/java/com/mycelium/wallet/external/changelly2/ExchangeResultFragment.kt b/mbw/src/main/java/com/mycelium/wallet/external/changelly2/ExchangeResultFragment.kt index 7ca6b36b3..7583d32f1 100644 --- a/mbw/src/main/java/com/mycelium/wallet/external/changelly2/ExchangeResultFragment.kt +++ b/mbw/src/main/java/com/mycelium/wallet/external/changelly2/ExchangeResultFragment.kt @@ -3,7 +3,12 @@ package com.mycelium.wallet.external.changelly2 import android.app.AlertDialog import android.graphics.Color import android.os.Bundle -import android.view.* +import android.view.LayoutInflater +import android.view.Menu +import android.view.MenuInflater +import android.view.MenuItem +import android.view.View +import android.view.ViewGroup import androidx.core.view.forEach import androidx.fragment.app.DialogFragment import androidx.fragment.app.viewModels @@ -21,8 +26,10 @@ import com.mycelium.wallet.startCoroutineTimer import com.mycelium.wapi.wallet.AddressUtils import com.mycelium.wapi.wallet.TransactionSummary import kotlinx.coroutines.Job +import kotlinx.coroutines.launch import java.text.DateFormat -import java.util.* +import java.util.Date +import java.util.UUID import java.util.concurrent.TimeUnit @@ -82,31 +89,31 @@ class ExchangeResultFragment : DialogFragment() { private fun update(txId: String) { loader(true) - Changelly2Repository.getTransaction(lifecycleScope, txId, - { response -> - response?.result?.first()?.let { result -> - binding?.toolbar?.title = result.getReadableStatus("exchange") - viewModel.setTransaction(result) - } ?: let { - AlertDialog.Builder(requireContext()) - .setMessage(response?.error?.message) - .setPositiveButton(R.string.button_ok) { _, _ -> - dismissAllowingStateLoss() - } - .show() - } - }, - { _, msg -> - AlertDialog.Builder(requireContext()) - .setMessage(msg) - .setPositiveButton(R.string.button_ok) { _, _ -> - dismissAllowingStateLoss() - } - .show() - }, - { - loader(false) - }) + lifecycleScope.launch { + try { + val transaction = Changelly2Repository.getTransaction(txId) + val result = transaction.result?.firstOrNull { it.id == txId } + if (transaction.error != null || result == null) { + showErrorDialog(transaction.error?.message) + return@launch + } + binding?.toolbar?.title = result.getReadableStatus("exchange") + viewModel.setTransaction(result) + } catch (e: Exception) { + showErrorDialog(e.message) + } finally { + loader(false) + } + } + } + + private fun showErrorDialog(message: String?) { + AlertDialog.Builder(requireContext()) + .setMessage(message) + .setPositiveButton(R.string.button_ok) { _, _ -> + dismissAllowingStateLoss() + } + .show() } override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { diff --git a/mbw/src/main/java/com/mycelium/wallet/external/changelly2/HistoryFragment.kt b/mbw/src/main/java/com/mycelium/wallet/external/changelly2/HistoryFragment.kt index 463c71de8..1ebca0466 100644 --- a/mbw/src/main/java/com/mycelium/wallet/external/changelly2/HistoryFragment.kt +++ b/mbw/src/main/java/com/mycelium/wallet/external/changelly2/HistoryFragment.kt @@ -2,11 +2,18 @@ package com.mycelium.wallet.external.changelly2 import android.content.Context import android.os.Bundle -import android.view.* +import android.util.Log +import android.view.LayoutInflater +import android.view.Menu +import android.view.MenuInflater +import android.view.MenuItem +import android.view.View +import android.view.ViewGroup import android.widget.LinearLayout import androidx.core.view.forEach import androidx.fragment.app.DialogFragment import androidx.lifecycle.lifecycleScope +import com.mycelium.wallet.Constants.TAG import com.mycelium.wallet.R import com.mycelium.wallet.activity.view.DividerItemDecoration import com.mycelium.wallet.activity.view.loader @@ -16,12 +23,15 @@ import com.mycelium.wallet.external.adapter.TxItem import com.mycelium.wallet.external.changelly2.remote.Changelly2Repository import com.mycelium.wallet.external.changelly2.remote.fixedCurrencyFrom import com.mycelium.wallet.external.changelly2.remote.fixedCurrencyTo +import kotlinx.coroutines.launch import java.text.DateFormat -import java.util.* +import java.util.Date +import java.util.UUID class HistoryFragment : DialogFragment() { + private val historyDateFormat = DateFormat.getDateInstance(DateFormat.LONG) var binding: FragmentChangelly2HistoryBinding? = null val pref by lazy { requireContext().getSharedPreferences(ExchangeFragment.PREF_FILE, Context.MODE_PRIVATE) } val adapter = TxHistoryAdapter() @@ -29,7 +39,7 @@ class HistoryFragment : DialogFragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setStyle(STYLE_NORMAL, R.style.Dialog_Changelly) - setHasOptionsMenu(true) + setHasOptionsMenu(false) } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View = @@ -64,27 +74,29 @@ class HistoryFragment : DialogFragment() { val txIds = (pref.getStringSet(ExchangeFragment.KEY_HISTORY, null) ?: setOf()).toList() .filterNotNull() .filterNot { it.isEmpty() } - if (txIds.isNotEmpty()) { - loader(true) - Changelly2Repository.getTransactions(lifecycleScope, txIds, - { - it?.result?.let { - adapter.submitList(it.map { - TxItem(it.id, - it.amountExpectedFrom.toString(), it.getExpectedAmount().toString(), - it.fixedCurrencyFrom(), it.fixedCurrencyTo(), - DateFormat.getDateInstance(DateFormat.LONG).format(Date(it.createdAt * 1000L)), - it.getReadableStatus()) - }) - } - }, - { _, _ -> + if (txIds.isEmpty()) return + loader(true) + lifecycleScope.launch { + try { + val result = Changelly2Repository.getTransactions(txIds).result ?: return@launch + adapter.submitList(result.map { + TxItem( + it.id, + it.amountExpectedFrom.toString(), + it.getExpectedAmount().toString(), + it.fixedCurrencyFrom(), + it.fixedCurrencyTo(), + historyDateFormat.format(Date(it.createdAt * 1000L)), + it.getReadableStatus() + ) + }) - }, - { - updateEmpty() - loader(false) - }) + } catch (e: Exception) { + Log.e(TAG, "${e.message}"); + } finally { + updateEmpty() + loader(false) + } } } diff --git a/mbw/src/main/java/com/mycelium/wallet/external/changelly2/remote/Changelly2Repository.kt b/mbw/src/main/java/com/mycelium/wallet/external/changelly2/remote/Changelly2Repository.kt index 0655d6af0..a591fbda9 100644 --- a/mbw/src/main/java/com/mycelium/wallet/external/changelly2/remote/Changelly2Repository.kt +++ b/mbw/src/main/java/com/mycelium/wallet/external/changelly2/remote/Changelly2Repository.kt @@ -1,6 +1,5 @@ package com.mycelium.wallet.external.changelly2.remote -import androidx.lifecycle.LifecycleCoroutineScope import com.mycelium.bequant.remote.doRequest import com.mycelium.wallet.external.changelly.ChangellyRetrofitFactory import com.mycelium.wallet.external.changelly.model.ChangellyCurrency @@ -8,8 +7,12 @@ import com.mycelium.wallet.external.changelly.model.ChangellyListResponse import com.mycelium.wallet.external.changelly.model.ChangellyResponse import com.mycelium.wallet.external.changelly.model.ChangellyTransaction import com.mycelium.wallet.external.changelly.model.ChangellyTransactionOffer +import com.mycelium.wallet.external.changelly.model.Error import com.mycelium.wallet.external.changelly.model.FixRate import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.withContext import retrofit2.HttpException import java.math.BigDecimal @@ -97,27 +100,42 @@ object Changelly2Repository { } } - fun getTransaction( - scope: CoroutineScope, - id: String, - success: (ChangellyResponse>?) -> Unit, - error: (Int, String) -> Unit, - finally: (() -> Unit)? = null - ) { - doRequest(scope, { - changellyApi.getTransaction(id) - }, success, error, finally) + suspend fun getTransaction(id: String): ChangellyResponse> { + val isVip = userRepository.statusFlow.value.isVIP() + val changellyTransactions = changellyApi.getTransaction(id) + if (!isVip) return changellyTransactions + if (changellyTransactions.result?.any { it.id == id } == true) return changellyTransactions + return try { + viperApi.getTransaction(id) + } catch (e: HttpException) { + ChangellyResponse(null, Error(e.code(), e.message())) + } catch (e: Exception) { + ChangellyResponse(null, Error(500, e.message ?: "")) + } } - fun getTransactions( - scope: LifecycleCoroutineScope, ids: List, - success: (ChangellyResponse>?) -> Unit, - error: (Int, String) -> Unit, - finally: (() -> Unit)? = null - ) { - doRequest(scope, { - changellyApi.getTransactions(ids) - }, success, error, finally) + suspend fun getTransactions(ids: List): ChangellyResponse> { + val isVip = userRepository.statusFlow.value.isVIP() + if (!isVip) return changellyApi.getTransactions(ids) + val changellyTransactionsDeferred = withContext(Dispatchers.IO) { + async { changellyApi.getTransactions(ids) } + } + val viperTransactionsDeferred = withContext(Dispatchers.IO) { + async { + try { + viperApi.getTransactions(ids) + } catch (e: HttpException) { + ChangellyResponse(null, Error(e.code(), e.message())) + } catch (e: Exception) { + ChangellyResponse(null, Error(500, e.message ?: "")) + } + } + } + val changellyTransactions = changellyTransactionsDeferred.await() + val viperTransactions = viperTransactionsDeferred.await() + val changellyResult = changellyTransactions.result ?: emptyList() + val viperResult = viperTransactions.result ?: emptyList() + return ChangellyResponse(changellyResult + viperResult) } } From 43dc3406547ee92146d00490df2635659af12c66 Mon Sep 17 00:00:00 2001 From: elvis Date: Tue, 25 Jun 2024 13:39:07 +0200 Subject: [PATCH 13/19] fix bug linked with network fee --- .../changelly/model/ChangellyGetExchangeAmountResponse.kt | 4 ++-- .../wallet/external/changelly/model/ChangellyTransaction.kt | 2 +- .../mycelium/wallet/external/changelly2/ExchangeFragment.kt | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/mbw/src/main/java/com/mycelium/wallet/external/changelly/model/ChangellyGetExchangeAmountResponse.kt b/mbw/src/main/java/com/mycelium/wallet/external/changelly/model/ChangellyGetExchangeAmountResponse.kt index c5449b54d..d87086c8e 100644 --- a/mbw/src/main/java/com/mycelium/wallet/external/changelly/model/ChangellyGetExchangeAmountResponse.kt +++ b/mbw/src/main/java/com/mycelium/wallet/external/changelly/model/ChangellyGetExchangeAmountResponse.kt @@ -18,8 +18,8 @@ data class ChangellyGetExchangeAmountResponse( ) { val receiveAmount: Double get() { - val fee = networkFee.toDoubleOrNull() ?: return .0 +// val fee = networkFee.toDoubleOrNull() ?: return .0 val to = amountTo.toDoubleOrNull() ?: return .0 - return to - fee + return to// - fee } } \ No newline at end of file diff --git a/mbw/src/main/java/com/mycelium/wallet/external/changelly/model/ChangellyTransaction.kt b/mbw/src/main/java/com/mycelium/wallet/external/changelly/model/ChangellyTransaction.kt index 000c725ac..46af1ff84 100644 --- a/mbw/src/main/java/com/mycelium/wallet/external/changelly/model/ChangellyTransaction.kt +++ b/mbw/src/main/java/com/mycelium/wallet/external/changelly/model/ChangellyTransaction.kt @@ -17,5 +17,5 @@ class ChangellyTransaction(val id: String, val createdAt: Long ) : Serializable { - fun getExpectedAmount(): BigDecimal? = amountExpectedTo?.minus(networkFee ?: BigDecimal.ZERO) + fun getExpectedAmount(): BigDecimal? = amountExpectedTo //?.minus(networkFee ?: BigDecimal.ZERO) } \ No newline at end of file diff --git a/mbw/src/main/java/com/mycelium/wallet/external/changelly2/ExchangeFragment.kt b/mbw/src/main/java/com/mycelium/wallet/external/changelly2/ExchangeFragment.kt index e6e6c1c52..a0973e5b7 100644 --- a/mbw/src/main/java/com/mycelium/wallet/external/changelly2/ExchangeFragment.kt +++ b/mbw/src/main/java/com/mycelium/wallet/external/changelly2/ExchangeFragment.kt @@ -406,10 +406,10 @@ class ExchangeFragment : Fragment(), BackListener { val amount = viewModel.sellValue.value val info = viewModel.exchangeInfo.value val rate = info?.result - val networkFee = info?.networkFee ?: BigDecimal.ZERO +// val networkFee = info?.networkFee ?: BigDecimal.ZERO viewModel.buyValue.value = if (amount?.isNotEmpty() == true && rate != null) { try { - val result = amount.toBigDecimal() * rate - networkFee + val result = amount.toBigDecimal() * rate //- networkFee if (result <= BigDecimal.ZERO) null else result .setScale(viewModel.toCurrency.value?.friendlyDigits!!, RoundingMode.HALF_UP) From 4447b2bd0f004e7b4737d71c8d5800f3ad84d5c4 Mon Sep 17 00:00:00 2001 From: ismurzin Date: Tue, 25 Jun 2024 20:44:07 +0700 Subject: [PATCH 14/19] remove unnecessary networkFee subtraction --- .../changelly/model/ChangellyTransaction.kt | 31 +++++++++---------- .../external/changelly2/ExchangeFragment.kt | 3 +- .../external/changelly2/HistoryFragment.kt | 2 +- .../viewmodel/ExchangeResultViewModel.kt | 4 +-- 4 files changed, 18 insertions(+), 22 deletions(-) diff --git a/mbw/src/main/java/com/mycelium/wallet/external/changelly/model/ChangellyTransaction.kt b/mbw/src/main/java/com/mycelium/wallet/external/changelly/model/ChangellyTransaction.kt index a7f0f76cf..1385d3efc 100644 --- a/mbw/src/main/java/com/mycelium/wallet/external/changelly/model/ChangellyTransaction.kt +++ b/mbw/src/main/java/com/mycelium/wallet/external/changelly/model/ChangellyTransaction.kt @@ -3,20 +3,17 @@ package com.mycelium.wallet.external.changelly.model import java.io.Serializable import java.math.BigDecimal - -class ChangellyTransaction(val id: String, - val status: String, - val moneySent: String, - val amountExpectedFrom: BigDecimal? = null, - val amountExpectedTo: BigDecimal? = null, - val networkFee: BigDecimal? = null, - val currencyFrom: String, - val moneyReceived: String, - val currencyTo: String, - val trackUrl: String, - val payoutAddress:String, - val createdAt: Long -) : Serializable { - - fun getExpectedAmount(): BigDecimal? = amountExpectedTo?.minus(networkFee ?: BigDecimal.ZERO) -} \ No newline at end of file +class ChangellyTransaction( + val id: String, + val status: String, + val moneySent: String, + val amountExpectedFrom: BigDecimal? = null, + val amountExpectedTo: BigDecimal? = null, + val networkFee: BigDecimal? = null, + val currencyFrom: String, + val moneyReceived: String, + val currencyTo: String, + val trackUrl: String, + val payoutAddress: String, + val createdAt: Long, +) : Serializable diff --git a/mbw/src/main/java/com/mycelium/wallet/external/changelly2/ExchangeFragment.kt b/mbw/src/main/java/com/mycelium/wallet/external/changelly2/ExchangeFragment.kt index 556b09615..462aefd0a 100644 --- a/mbw/src/main/java/com/mycelium/wallet/external/changelly2/ExchangeFragment.kt +++ b/mbw/src/main/java/com/mycelium/wallet/external/changelly2/ExchangeFragment.kt @@ -402,10 +402,9 @@ class ExchangeFragment : Fragment(), BackListener { val amount = viewModel.sellValue.value val info = viewModel.exchangeInfo.value val rate = info?.result - val networkFee = info?.networkFee ?: BigDecimal.ZERO viewModel.buyValue.value = if (amount?.isNotEmpty() == true && rate != null) { try { - val result = amount.toBigDecimal() * rate - networkFee + val result = amount.toBigDecimal() * rate if (result <= BigDecimal.ZERO) null else result .setScale(viewModel.toCurrency.value?.friendlyDigits!!, RoundingMode.HALF_UP) diff --git a/mbw/src/main/java/com/mycelium/wallet/external/changelly2/HistoryFragment.kt b/mbw/src/main/java/com/mycelium/wallet/external/changelly2/HistoryFragment.kt index 1ebca0466..1ee53a4bb 100644 --- a/mbw/src/main/java/com/mycelium/wallet/external/changelly2/HistoryFragment.kt +++ b/mbw/src/main/java/com/mycelium/wallet/external/changelly2/HistoryFragment.kt @@ -83,7 +83,7 @@ class HistoryFragment : DialogFragment() { TxItem( it.id, it.amountExpectedFrom.toString(), - it.getExpectedAmount().toString(), + it.amountExpectedTo.toString(), it.fixedCurrencyFrom(), it.fixedCurrencyTo(), historyDateFormat.format(Date(it.createdAt * 1000L)), diff --git a/mbw/src/main/java/com/mycelium/wallet/external/changelly2/viewmodel/ExchangeResultViewModel.kt b/mbw/src/main/java/com/mycelium/wallet/external/changelly2/viewmodel/ExchangeResultViewModel.kt index d144a6458..92fb75e6f 100644 --- a/mbw/src/main/java/com/mycelium/wallet/external/changelly2/viewmodel/ExchangeResultViewModel.kt +++ b/mbw/src/main/java/com/mycelium/wallet/external/changelly2/viewmodel/ExchangeResultViewModel.kt @@ -41,10 +41,10 @@ class ExchangeResultViewModel : ViewModel() { fun setTransaction(result: ChangellyTransaction) { txId.value = result.id spendValue.value = "${result.amountExpectedFrom} ${result.currencyFrom.toUpperCase()}" - getValue.value = "${result.getExpectedAmount()} ${result.currencyTo.toUpperCase()}" + getValue.value = "${result.amountExpectedTo} ${result.currencyTo.toUpperCase()}" date.value = DateFormat.getDateInstance(DateFormat.LONG).format(Date(result.createdAt * 1000L)) spendValueFiat.value = getFiatValue(result.amountExpectedFrom, result.currencyFrom) - getValueFiat.value = getFiatValue(result.getExpectedAmount(), result.currencyTo) + getValueFiat.value = getFiatValue(result.amountExpectedTo, result.currencyTo) isExchangeComplete.value = result.status == "finished" trackLink.value = if (result.status in LINK_ACTIVE_LIST) result.trackUrl else "" trackInProgress.value = result.status !in LINK_TEXT_LIST From eb2d2ccd3f0e8cb566e57dd20385e90cefd89646 Mon Sep 17 00:00:00 2001 From: elvis Date: Fri, 28 Jun 2024 13:51:15 +0200 Subject: [PATCH 15/19] add text for vip changelly --- mbw/src/main/res/values-ru/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mbw/src/main/res/values-ru/strings.xml b/mbw/src/main/res/values-ru/strings.xml index 14c4f2ae4..09dc02bb6 100644 --- a/mbw/src/main/res/values-ru/strings.xml +++ b/mbw/src/main/res/values-ru/strings.xml @@ -991,4 +991,6 @@ Получите VIP-опыт VIP Всё готово! + Ваш SWAP обмен недоступен + Ваш VIP-статус истек, поэтому теперь применяются обычные (не VIP) тарифы. \ No newline at end of file From c962e6a836ed0c35ef547ff8cf4507b01f31a68c Mon Sep 17 00:00:00 2001 From: elvis Date: Mon, 1 Jul 2024 13:56:24 +0200 Subject: [PATCH 16/19] fix changelly create date --- .../com/mycelium/wallet/external/changelly2/HistoryFragment.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mbw/src/main/java/com/mycelium/wallet/external/changelly2/HistoryFragment.kt b/mbw/src/main/java/com/mycelium/wallet/external/changelly2/HistoryFragment.kt index 1ee53a4bb..fa9c2e7d5 100644 --- a/mbw/src/main/java/com/mycelium/wallet/external/changelly2/HistoryFragment.kt +++ b/mbw/src/main/java/com/mycelium/wallet/external/changelly2/HistoryFragment.kt @@ -86,7 +86,7 @@ class HistoryFragment : DialogFragment() { it.amountExpectedTo.toString(), it.fixedCurrencyFrom(), it.fixedCurrencyTo(), - historyDateFormat.format(Date(it.createdAt * 1000L)), + historyDateFormat.format(Date(it.createdAt / 1000L)), it.getReadableStatus() ) }) From fcf6341d111478ed4eb129a476710dc1f2c9968b Mon Sep 17 00:00:00 2001 From: elvis Date: Mon, 1 Jul 2024 19:00:47 +0200 Subject: [PATCH 17/19] icon for usdt --- mbw/src/main/assets/token-logos/usdt_logo.png | Bin 0 -> 11405 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 mbw/src/main/assets/token-logos/usdt_logo.png diff --git a/mbw/src/main/assets/token-logos/usdt_logo.png b/mbw/src/main/assets/token-logos/usdt_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..bc0fb0473f1d825b88c16e27c6629aa97ebbc522 GIT binary patch literal 11405 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 Date: Tue, 2 Jul 2024 21:57:22 +0200 Subject: [PATCH 18/19] fix changelly history sorting --- .../mycelium/wallet/external/changelly2/HistoryFragment.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mbw/src/main/java/com/mycelium/wallet/external/changelly2/HistoryFragment.kt b/mbw/src/main/java/com/mycelium/wallet/external/changelly2/HistoryFragment.kt index fa9c2e7d5..db1557abf 100644 --- a/mbw/src/main/java/com/mycelium/wallet/external/changelly2/HistoryFragment.kt +++ b/mbw/src/main/java/com/mycelium/wallet/external/changelly2/HistoryFragment.kt @@ -79,7 +79,9 @@ class HistoryFragment : DialogFragment() { lifecycleScope.launch { try { val result = Changelly2Repository.getTransactions(txIds).result ?: return@launch - adapter.submitList(result.map { + adapter.submitList(result.sortedByDescending { + it.createdAt + }.map { TxItem( it.id, it.amountExpectedFrom.toString(), From a8b2882c55b8d5ee19073087e9936fae292a0684 Mon Sep 17 00:00:00 2001 From: elvis Date: Wed, 3 Jul 2024 15:56:41 +0200 Subject: [PATCH 19/19] #rm5842 localization --- .../mycelium/wallet/external/changelly2/ExchangeFragment.kt | 4 ++-- mbw/src/main/res/values-ru/strings.xml | 1 + mbw/src/main/res/values/strings.xml | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/mbw/src/main/java/com/mycelium/wallet/external/changelly2/ExchangeFragment.kt b/mbw/src/main/java/com/mycelium/wallet/external/changelly2/ExchangeFragment.kt index 780994409..8c021ea83 100644 --- a/mbw/src/main/java/com/mycelium/wallet/external/changelly2/ExchangeFragment.kt +++ b/mbw/src/main/java/com/mycelium/wallet/external/changelly2/ExchangeFragment.kt @@ -363,13 +363,13 @@ class ExchangeFragment : Fragment(), BackListener { is ViperStatusException -> showViperErrorDialog( getString(R.string.vip_exchange_expired_title), getString(R.string.vip_exchange_status_expired_alert_message), - getString(R.string.proceed), + getString(R.string.changelly2_proceed), getString(R.string.cancel_transaction) ) is ViperUnexpectedException -> showViperErrorDialog( getString(R.string.vip_exchange_unexpected_alert_title), getString(R.string.vip_exchange_unexpected_alert_message), - getString(R.string.proceed), + getString(R.string.changelly2_proceed), getString(R.string.vip_alert_cancel) ) else -> showErrorNotificationDialog(e.message) diff --git a/mbw/src/main/res/values-ru/strings.xml b/mbw/src/main/res/values-ru/strings.xml index 09dc02bb6..83a72b695 100644 --- a/mbw/src/main/res/values-ru/strings.xml +++ b/mbw/src/main/res/values-ru/strings.xml @@ -970,6 +970,7 @@ Транзакция отправлена Проблемы с соединением. Убедитесь, что вы онлайн. Сервис предоставляется сторонним поставщиком API. Нажимая на кнопку Exchange, вы принимаете Условия использования. + Продолжить курс устарел Восстановить Обменный курс устарел, пожалуйста, повторите попытку. diff --git a/mbw/src/main/res/values/strings.xml b/mbw/src/main/res/values/strings.xml index 787383515..59692cc65 100644 --- a/mbw/src/main/res/values/strings.xml +++ b/mbw/src/main/res/values/strings.xml @@ -1765,6 +1765,7 @@ All new bitcoins, mined by the pool, are proportionally distributed among RMC bi Service is available through third-party API provider. By clicking Exchange you accept the Terms of Use. Select account: Select account to receive funds: + Proceed Exchange History EXCHANGE No operations history