diff --git a/ci_resources/Dockerfile.android b/ci_resources/Dockerfile.android index 25edb6a0ec..f54bfd51ad 100644 --- a/ci_resources/Dockerfile.android +++ b/ci_resources/Dockerfile.android @@ -17,6 +17,7 @@ RUN apt-get update && apt-get install -y \ ruby \ ruby-dev \ build-essential \ + jq \ && locale-gen en_US.UTF-8 \ && update-locale LANG=en_US.UTF-8 \ && apt-get clean diff --git a/ci_resources/upload_aab_to_huawei_app_gallery.sh b/ci_resources/upload_aab_to_huawei_app_gallery.sh new file mode 100755 index 0000000000..653433461e --- /dev/null +++ b/ci_resources/upload_aab_to_huawei_app_gallery.sh @@ -0,0 +1,134 @@ +#!/bin/bash + +set -eo pipefail + +# ---------------------------------------- +# 1. Obtain OAuth access token +# ---------------------------------------- +# Sending a POST request to retrieve access token using client credentials +TOKEN=$(curl -s -v -X POST "https://connect-api.cloud.huawei.com/api/oauth2/v1/token" \ + -H "Content-Type: application/json" \ + -d "{ + \"grant_type\": \"client_credentials\", + \"client_id\": \"$CLIENT_ID\", + \"client_secret\": \"$CLIENT_SECRET\" + }" | jq -r .access_token) + +echo "------------------------------------------------" +echo "[INFO] Token = $TOKEN" +echo "------------------------------------------------" + +# ---------------------------------------- +# 2. Get upload information (temporary URL + required headers) +# ---------------------------------------- +# Extract file name and size for the upload request +echo "[INFO] File path = $FILE_PATH" +FILE_NAME=$(basename "$FILE_PATH") +echo "[INFO] File name = $FILE_NAME" +CONTENT_LENGTH=$(stat -c %s "$FILE_PATH") +echo "[INFO] Content length = $CONTENT_LENGTH" + +# Request upload URL and headers for OBS (Huawei Object Storage Service) +UPLOAD_INFO=$(curl -s -v -X GET \ + "https://connect-api.cloud.huawei.com/api/publish/v2/upload-url/for-obs?appId=$APP_ID&fileName=$FILE_NAME&contentLength=$CONTENT_LENGTH" \ + -H "Authorization: Bearer $TOKEN" \ + -H "client_id: $CLIENT_ID" \ + -H "Content-Type: application/json") + +echo "------------------------------------------------" +echo "[INFO] Upload: $UPLOAD_INFO" +echo "------------------------------------------------" + +# ---------------------------------------- +# 3. Upload the AAB file to Huawei OBS using signed URL and headers +# ---------------------------------------- +# Parse the response to get upload URL and required headers +UPLOAD_URL=$(echo "$UPLOAD_INFO" | jq -r '.urlInfo.url') +HEADERS=$(echo "$UPLOAD_INFO" | jq -r '.urlInfo.headers') + +echo "------------------------------------------------" +echo "[INFO] Parsed upload URL: $UPLOAD_URL" +echo "[INFO] Extracting headers..." +echo "------------------------------------------------" + +# Extract individual headers for the PUT request +AUTH_HEADER=$(echo "$HEADERS" | jq -r '."Authorization"') +SHA256_HEADER=$(echo "$HEADERS" | jq -r '."x-amz-content-sha256"') +DATE_HEADER=$(echo "$HEADERS" | jq -r '."x-amz-date"') +HOST_HEADER=$(echo "$HEADERS" | jq -r '."Host"') +UA_HEADER=$(echo "$HEADERS" | jq -r '."user-agent"') +CT_HEADER=$(echo "$HEADERS" | jq -r '."Content-Type"') + +# Log headers (for debug purposes) +echo "[INFO] Authorization: $AUTH_HEADER" +echo "[INFO] x-amz-content-sha256: $SHA256_HEADER" +echo "[INFO] x-amz-date: $DATE_HEADER" +echo "[INFO] Host: $HOST_HEADER" +echo "[INFO] user-agent: $UA_HEADER" +echo "[INFO] Content-Type: $CT_HEADER" +echo "------------------------------------------------" + +# Perform the actual file upload using the signed PUT URL +echo "[INFO] Uploading file '$FILE_PATH' to Huawei OBS..." +RESPONSE=$(curl -v -X PUT "$UPLOAD_URL" \ + -H "Authorization: $AUTH_HEADER" \ + -H "x-amz-content-sha256: $SHA256_HEADER" \ + -H "x-amz-date: $DATE_HEADER" \ + -H "Host: $HOST_HEADER" \ + -H "user-agent: $UA_HEADER" \ + -H "Content-Type: $CT_HEADER" \ + --data-binary @"$FILE_PATH" 2>&1) + +echo "$RESPONSE" +echo "------------------------------------------------" + +# Check if upload succeeded +if echo "$RESPONSE" | grep -q "HTTP/1.1 200"; then + echo "[SUCCESS] File upload completed successfully." +else + echo "[ERROR] File upload failed:" + echo "$RESPONSE" + exit 1 +fi + +# ---------------------------------------- +# 4. Commit the uploaded file to AppGallery (register the uploaded file) +# ---------------------------------------- +# Huawei requires an additional API call to register the uploaded file to the app +echo "[INFO] Committing uploaded AAB file to AppGallery..." + +# Extract path (excluding hostname) from the upload URL +FILE_DEST_URL=$(echo "$UPLOAD_URL" | sed -E 's|https://[^/]+/||') + +# Prepare commit payload +COMMIT_PAYLOAD=$(cat < /dev/null; then + echo "[SUCCESS] File committed successfully and is now visible in AppGallery Console." +else + echo "[ERROR] Failed to commit file:" + echo "$COMMIT_RESPONSE" + exit 1 +fi diff --git a/core/config-toggles/src/main/assets/configs/feature_toggles_config.json b/core/config-toggles/src/main/assets/configs/feature_toggles_config.json index 66d5de0e26..109fa60297 100644 --- a/core/config-toggles/src/main/assets/configs/feature_toggles_config.json +++ b/core/config-toggles/src/main/assets/configs/feature_toggles_config.json @@ -49,7 +49,7 @@ }, { "name": "NEW_ATTESTATION_ENABLED", - "version": "5.23.0" + "version": "undefined" }, { "name": "TWIN_REFACTORING_ENABLED", diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/common/response/ResponseExt.kt b/core/datasource/src/main/java/com/tangem/datasource/api/common/response/ResponseExt.kt index 1699dbc089..926d9213aa 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/common/response/ResponseExt.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/common/response/ResponseExt.kt @@ -23,9 +23,10 @@ internal fun Response.toSafeApiResponse(analyticsErrorHandler: Anal if (code == null) { ApiResponseError.UnknownException(IllegalArgumentException("Unknown error status code: ${code()}")) } else { - sendHttpError(code, analyticsErrorHandler) + val errorBody = errorBody()?.string().orEmpty() // !!!Beware!!! string() closes stream after invocation + sendHttpError(code, analyticsErrorHandler, errorBody) - ApiResponseError.HttpException(code, message(), errorBody()?.string()) + ApiResponseError.HttpException(code, message(), errorBody) } } catch (e: Exception) { Timber.e(e, "UnknownException occured") @@ -39,6 +40,7 @@ internal fun Response.toSafeApiResponse(analyticsErrorHandler: Anal private fun Response.sendHttpError( code: ApiResponseError.HttpException.Code, analyticsErrorHandler: AnalyticsErrorHandler, + errorBody: String, ) { val fullRequestUrl = raw().request.url.toUrl() val shortUrl = fullRequestUrl.authority + fullRequestUrl.path @@ -46,7 +48,7 @@ private fun Response.sendHttpError( ApiErrorEvent( endpoint = shortUrl, code = code.numericCode, - message = errorBody()?.string().orEmpty(), + message = errorBody, ), ) } diff --git a/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/utils/ManagedCryptoCurrencyFactory.kt b/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/utils/ManagedCryptoCurrencyFactory.kt index 770cecd7a1..74cd8c167a 100644 --- a/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/utils/ManagedCryptoCurrencyFactory.kt +++ b/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/utils/ManagedCryptoCurrencyFactory.kt @@ -22,6 +22,7 @@ import com.tangem.domain.managetokens.model.ManagedCryptoCurrency.SourceNetwork import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.Network +import timber.log.Timber internal class ManagedCryptoCurrencyFactory( private val excludedBlockchains: ExcludedBlockchains, @@ -187,11 +188,18 @@ internal class ManagedCryptoCurrencyFactory( decimals = blockchain.decimals(), isL2Network = l2BlockchainsList.contains(blockchain), ) - network.canHandleTokens -> SourceNetwork.Default( - network = network, - decimals = decimals ?: return null, - contractAddress = contractAddress, - ) + network.canHandleTokens -> { + val formattedContractAddress = blockchain.reformatContractAddress(contractAddress) + if (formattedContractAddress == null) { + Timber.w("Couldn't reformat $contractAddress") + return null + } + SourceNetwork.Default( + network = network, + decimals = decimals ?: return null, + contractAddress = formattedContractAddress, + ) + } else -> null } } diff --git a/data/markets/src/main/java/com/tangem/data/markets/converters/TokenMarketInfoConverter.kt b/data/markets/src/main/java/com/tangem/data/markets/converters/TokenMarketInfoConverter.kt index 2e0146f681..a00841547c 100644 --- a/data/markets/src/main/java/com/tangem/data/markets/converters/TokenMarketInfoConverter.kt +++ b/data/markets/src/main/java/com/tangem/data/markets/converters/TokenMarketInfoConverter.kt @@ -72,7 +72,7 @@ internal class TokenMarketInfoConverter( TokenMarketInfo.Network( networkId = network.networkId, exchangeable = network.exchangeable, - contractAddress = network.contractAddress, + contractAddress = blockchain.reformatContractAddress(network.contractAddress), decimalCount = network.decimalCount, ) } diff --git a/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampTransactionRepository.kt b/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampTransactionRepository.kt index d4fbe4d861..84c6a72576 100644 --- a/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampTransactionRepository.kt +++ b/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampTransactionRepository.kt @@ -63,11 +63,17 @@ internal class DefaultOnrampTransactionRepository( }.map(transactionConverter::convert) } - override suspend fun updateTransactionStatus(externalTxId: String, status: OnrampStatus.Status) = - withContext(dispatchers.io) { - val updatedTx = getTransactionById(externalTxId)?.copy(status = status) ?: return@withContext - storeTransaction(updatedTx) - } + override suspend fun updateTransactionStatus( + externalTxId: String, + externalTxUrl: String, + status: OnrampStatus.Status, + ) = withContext(dispatchers.io) { + val updatedTx = getTransactionById(externalTxId)?.copy( + externalTxUrl = externalTxUrl, + status = status, + ) ?: return@withContext + storeTransaction(updatedTx) + } override suspend fun removeTransaction(externalTxId: String) { withContext(dispatchers.io) { diff --git a/domain/onramp/src/main/java/com/tangem/domain/onramp/OnrampUpdateTransactionStatusUseCase.kt b/domain/onramp/src/main/java/com/tangem/domain/onramp/OnrampUpdateTransactionStatusUseCase.kt index 8e28d63d32..6a31dba336 100644 --- a/domain/onramp/src/main/java/com/tangem/domain/onramp/OnrampUpdateTransactionStatusUseCase.kt +++ b/domain/onramp/src/main/java/com/tangem/domain/onramp/OnrampUpdateTransactionStatusUseCase.kt @@ -10,7 +10,12 @@ class OnrampUpdateTransactionStatusUseCase( private val errorResolver: OnrampErrorResolver, ) { - suspend operator fun invoke(externalTxId: String, status: OnrampStatus.Status) = Either.catch { - onrampTransactionRepository.updateTransactionStatus(externalTxId = externalTxId, status = status) - }.mapLeft(errorResolver::resolve) + suspend operator fun invoke(externalTxId: String, externalTxUrl: String, status: OnrampStatus.Status) = + Either.catch { + onrampTransactionRepository.updateTransactionStatus( + externalTxId = externalTxId, + externalTxUrl = externalTxUrl, + status = status, + ) + }.mapLeft(errorResolver::resolve) } \ No newline at end of file diff --git a/domain/onramp/src/main/java/com/tangem/domain/onramp/repositories/OnrampTransactionRepository.kt b/domain/onramp/src/main/java/com/tangem/domain/onramp/repositories/OnrampTransactionRepository.kt index ae94b073a8..a5187b67b9 100644 --- a/domain/onramp/src/main/java/com/tangem/domain/onramp/repositories/OnrampTransactionRepository.kt +++ b/domain/onramp/src/main/java/com/tangem/domain/onramp/repositories/OnrampTransactionRepository.kt @@ -14,7 +14,7 @@ interface OnrampTransactionRepository { fun getTransactions(userWalletId: UserWalletId, cryptoCurrencyId: CryptoCurrency.ID): Flow> - suspend fun updateTransactionStatus(externalTxId: String, status: OnrampStatus.Status) + suspend fun updateTransactionStatus(externalTxId: String, externalTxUrl: String, status: OnrampStatus.Status) suspend fun removeTransaction(externalTxId: String) } \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/factory/OnrampStateFactory.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/factory/OnrampStateFactory.kt index 351353cfe7..cec5b2338a 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/factory/OnrampStateFactory.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/factory/OnrampStateFactory.kt @@ -87,7 +87,6 @@ internal class OnrampStateFactory( topBarConfig = state.topBarConfig.copy(endButtonUM = endButton), buyButtonConfig = state.buyButtonConfig.copy(enabled = false), amountBlockState = state.amountBlockState.copy( - amountFieldModel = state.amountBlockState.amountFieldModel.copy(isError = true), secondaryFieldModel = OnrampAmountSecondaryFieldUM.Content(TextReference.EMPTY), ), providerBlockState = OnrampProviderBlockUM.Empty, diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/model/OnrampMainComponentModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/model/OnrampMainComponentModel.kt index 3b86caa30d..a3afe0f26f 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/model/OnrampMainComponentModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/model/OnrampMainComponentModel.kt @@ -335,6 +335,7 @@ internal class OnrampMainComponentModel @Inject constructor( (it as? OnrampMainComponentUM.Content)?.copy( errorNotification = null, providerBlockState = OnrampProviderBlockUM.Loading, + amountBlockState = it.amountBlockState.copy(secondaryFieldModel = OnrampAmountSecondaryFieldUM.Loading), ) ?: it } startLoadingQuotes() diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/ui/OnrampAmountContent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/ui/OnrampAmountContent.kt index 608587c822..eec3a23193 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/ui/OnrampAmountContent.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/ui/OnrampAmountContent.kt @@ -42,13 +42,16 @@ internal fun OnrampAmountContent(state: OnrampAmountBlockUM, modifier: Modifier horizontalAlignment = Alignment.CenterHorizontally, ) { OnrampCurrencyIcon(currencyUM = state.currencyUM) - OnrampAmountField(amountField = state.amountFieldModel) + OnrampAmountField( + amountField = state.amountFieldModel, + isLoading = state.secondaryFieldModel is OnrampAmountSecondaryFieldUM.Loading, + ) OnrampAmountSecondary(state = state.secondaryFieldModel) } } @Composable -private fun OnrampAmountField(amountField: AmountFieldModel) { +private fun OnrampAmountField(amountField: AmountFieldModel, isLoading: Boolean) { val decimalFormat = rememberDecimalFormat() val requester = remember { FocusRequester() } AmountTextField( @@ -67,7 +70,7 @@ private fun OnrampAmountField(amountField: AmountFieldModel) { color = TangemTheme.colors.text.primary1, textAlign = TextAlign.Center, ), - isEnabled = !amountField.isError, + isEnabled = !amountField.isError && !isLoading, isAutoResize = true, isValuePasted = amountField.isValuePasted, onValuePastedTriggerDismiss = amountField.onValuePastedTriggerDismiss, diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/DefaultSendComponent.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/DefaultSendComponent.kt index 645441fe6e..82168f4518 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/DefaultSendComponent.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/DefaultSendComponent.kt @@ -232,7 +232,7 @@ internal class DefaultSendComponent @AssistedInject constructor( params = SendConfirmComponent.Params( state = model.uiState.value, userWallet = model.userWallet, - currentRoute = currentRoute.filterIsInstance(), + currentRoute = currentRoute, isBalanceHidingFlow = model.isBalanceHiddenFlow, analyticsCategoryName = SendAnalyticEvents.SEND_CATEGORY, cryptoCurrencyStatus = model.cryptoCurrencyStatus, diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/confirm/SendConfirmComponent.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/confirm/SendConfirmComponent.kt index 3725079e87..8b93d2cb71 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/confirm/SendConfirmComponent.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/confirm/SendConfirmComponent.kt @@ -141,7 +141,7 @@ internal class SendConfirmComponent( val feeCryptoCurrencyStatus: CryptoCurrencyStatus, val appCurrency: AppCurrency, val callback: ModelCallback, - val currentRoute: Flow, + val currentRoute: Flow, val isBalanceHidingFlow: StateFlow, val predefinedValues: PredefinedValues, ) diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/confirm/model/SendConfirmModel.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/confirm/model/SendConfirmModel.kt index 1cf654f799..8b0a2cad19 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/confirm/model/SendConfirmModel.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/confirm/model/SendConfirmModel.kt @@ -430,7 +430,7 @@ internal class SendConfirmModel @Inject constructor( flow = uiState, flow2 = params.currentRoute, transform = { state, route -> state to route }, - ).onEach { (state, _) -> + ).filter { it.second is CommonSendRoute.Confirm }.onEach { (state, _) -> val amountUM = state.amountUM as? AmountState.Data val confirmUM = state.confirmUM params.callback.onResult( diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/model/SendModel.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/model/SendModel.kt index 68e21e4fd1..06a72af707 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/model/SendModel.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/model/SendModel.kt @@ -37,6 +37,7 @@ import com.tangem.features.send.v2.send.confirm.SendConfirmComponent import com.tangem.features.send.v2.send.confirm.model.SendConfirmAlertFactory import com.tangem.features.send.v2.send.ui.state.SendUM import com.tangem.features.send.v2.subcomponents.amount.SendAmountComponent +import com.tangem.features.send.v2.subcomponents.amount.SendAmountUpdateQRTrigger import com.tangem.features.send.v2.subcomponents.destination.SendDestinationComponent import com.tangem.features.send.v2.subcomponents.destination.model.transformers.SendDestinationInitialStateTransformer import com.tangem.features.send.v2.subcomponents.destination.ui.state.DestinationUM @@ -75,6 +76,7 @@ internal class SendModel @Inject constructor( private val getCardInfoUseCase: GetCardInfoUseCase, private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase, private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase, + private val sendAmountUpdateQRTrigger: SendAmountUpdateQRTrigger, ) : Model(), SendComponentCallback { private val params: SendComponent.Params = paramsContainer.require() @@ -111,6 +113,7 @@ internal class SendModel @Inject constructor( } override fun onAmountResult(amountUM: AmountState) { + resetPredefinedAmount() _uiState.update { it.copy(amountUM = amountUM) } } @@ -119,9 +122,20 @@ internal class SendModel @Inject constructor( } override fun onResult(sendUM: SendUM) { + resetPredefinedAmount() _uiState.update { sendUM } } + private fun resetPredefinedAmount() { + // reset predefined amount + val internalPredefinedValues = predefinedValues + predefinedValues = when (internalPredefinedValues) { + is PredefinedValues.Content.Deeplink -> internalPredefinedValues.copy(amount = "") + is PredefinedValues.Content.QrCode -> internalPredefinedValues.copy(amount = "") + PredefinedValues.Empty -> internalPredefinedValues + } + } + private fun initAppCurrency() { modelScope.launch { appCurrency = getSelectedAppCurrencyUseCase.invokeSync().getOrElse { AppCurrency.Default } @@ -228,11 +242,18 @@ internal class SendModel @Inject constructor( private fun onQrCodeScanned(address: String) { val parsedQrCode = parseQrCodeUseCase(address, cryptoCurrency).getOrNull() + // Decompose component can be active or inactive depending on its state and navigation stack + // If it is in inactive state use parameter to pass value to amount component + val amount = parsedQrCode?.amount?.parseBigDecimal(cryptoCurrency.decimals) predefinedValues = PredefinedValues.Content.QrCode( - amount = parsedQrCode?.amount?.parseBigDecimal(cryptoCurrency.decimals).orEmpty(), + amount = amount.orEmpty(), address = parsedQrCode?.address.orEmpty(), memo = parsedQrCode?.memo, ) + // If it is in active state use flow to update value in amount component + modelScope.launch { + amount?.let { sendAmountUpdateQRTrigger.triggerUpdateAmount(it) } + } } private fun onFailedTxEmailClick(errorMessage: String? = null) { diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/amount/SendAmountReduceTrigger.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/amount/SendAmountReduceTrigger.kt index 901ee1f0df..459994ffcf 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/amount/SendAmountReduceTrigger.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/amount/SendAmountReduceTrigger.kt @@ -25,14 +25,33 @@ interface SendAmountReduceListener { val ignoreReduceTriggerFlow: Flow } +/** + * Trigger amount change from another component. + * Different from another triggers because it takes raw string instead of BigDecimal + */ +interface SendAmountUpdateQRTrigger { + suspend fun triggerUpdateAmount(amountValue: String) +} + +/** + * Trigger amount change from another component. + * Different from another triggers because it takes raw string instead of BigDecimal + */ +interface SendAmountUpdateQRListener { + val updateAmountTriggerFlow: Flow +} + @Singleton internal class DefaultSendAmountReduceTrigger @Inject constructor() : SendAmountReduceTrigger, - SendAmountReduceListener { + SendAmountReduceListener, + SendAmountUpdateQRTrigger, + SendAmountUpdateQRListener { override val reduceToTriggerFlow = MutableSharedFlow() override val reduceByTriggerFlow = MutableSharedFlow() override val ignoreReduceTriggerFlow = MutableSharedFlow() + override val updateAmountTriggerFlow = MutableSharedFlow() override suspend fun triggerReduceBy(reduceBy: ReduceByData) { reduceByTriggerFlow.emit(reduceBy) @@ -45,4 +64,8 @@ internal class DefaultSendAmountReduceTrigger @Inject constructor() : override suspend fun triggerIgnoreReduce() { ignoreReduceTriggerFlow.emit(Unit) } + + override suspend fun triggerUpdateAmount(amountValue: String) { + updateAmountTriggerFlow.emit(amountValue) + } } \ No newline at end of file diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/amount/di/SendAmountModule.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/amount/di/SendAmountModule.kt index b486aeaeae..c88ca068c2 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/amount/di/SendAmountModule.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/amount/di/SendAmountModule.kt @@ -1,8 +1,7 @@ package com.tangem.features.send.v2.subcomponents.amount.di +import com.tangem.features.send.v2.subcomponents.amount.* import com.tangem.features.send.v2.subcomponents.amount.DefaultSendAmountReduceTrigger -import com.tangem.features.send.v2.subcomponents.amount.SendAmountReduceListener -import com.tangem.features.send.v2.subcomponents.amount.SendAmountReduceTrigger import dagger.Binds import dagger.Module import dagger.hilt.InstallIn @@ -20,4 +19,12 @@ internal interface SendAmountModule { @Singleton @Binds fun provideSendAmountReduceListener(impl: DefaultSendAmountReduceTrigger): SendAmountReduceListener + + @Singleton + @Binds + fun provideSendAmountUpdateQRListener(impl: DefaultSendAmountReduceTrigger): SendAmountUpdateQRListener + + @Singleton + @Binds + fun provideSendAmountUpdateQRTrigger(impl: DefaultSendAmountReduceTrigger): SendAmountUpdateQRTrigger } \ No newline at end of file diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/amount/model/SendAmountModel.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/amount/model/SendAmountModel.kt index 7e82ea275e..11974ef160 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/amount/model/SendAmountModel.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/amount/model/SendAmountModel.kt @@ -23,6 +23,7 @@ import com.tangem.features.send.v2.impl.R import com.tangem.features.send.v2.send.ui.state.ButtonsUM import com.tangem.features.send.v2.subcomponents.amount.SendAmountComponentParams import com.tangem.features.send.v2.subcomponents.amount.SendAmountReduceListener +import com.tangem.features.send.v2.subcomponents.amount.SendAmountUpdateQRListener import com.tangem.features.send.v2.subcomponents.amount.analytics.SendAmountAnalyticEvents import com.tangem.features.send.v2.subcomponents.amount.analytics.SendAmountAnalyticEvents.SelectedCurrencyType import com.tangem.features.send.v2.subcomponents.fee.SendFeeData @@ -44,6 +45,7 @@ internal class SendAmountModel @Inject constructor( private val getMinimumTransactionAmountSyncUseCase: GetMinimumTransactionAmountSyncUseCase, private val sendAmountReduceListener: SendAmountReduceListener, private val feeReloadTrigger: SendFeeReloadTrigger, + private val sendAmountUpdateQRListener: SendAmountUpdateQRListener, private val analyticsEventHandler: AnalyticsEventHandler, ) : Model(), AmountScreenClickIntents { @@ -65,6 +67,7 @@ internal class SendAmountModel @Inject constructor( subscribeOnAmountReduceByTriggerUpdates() subscribeOnAmountReduceToTriggerUpdates() subscribeOnAmountIgnoreReduceTriggerUpdates() + subscribeOnAmountUpdateQRTriggerUpdates() } private fun initMinBoundary() { @@ -99,10 +102,10 @@ internal class SendAmountModel @Inject constructor( ), ) } - val predefinedValues = params.predefinedValues as? PredefinedValues.Content - if (predefinedValues?.amount != null) { - onAmountValueChange(predefinedValues.amount) - } + } + val predefinedValues = params.predefinedValues as? PredefinedValues.Content + if (predefinedValues?.amount != null) { + onAmountValueChange(predefinedValues.amount) } } @@ -207,6 +210,14 @@ internal class SendAmountModel @Inject constructor( .launchIn(modelScope) } + private fun subscribeOnAmountUpdateQRTriggerUpdates() { + sendAmountUpdateQRListener.updateAmountTriggerFlow + .onEach { amount -> + onAmountValueChange(amount) + saveResult() + }.launchIn(modelScope) + } + private fun saveResult() { val params = params as? SendAmountComponentParams.AmountParams ?: return params.callback.onAmountResult(uiState.value) diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/block/StakingFeeBlock.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/block/StakingFeeBlock.kt index 0b4973970b..bce18ef1ec 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/block/StakingFeeBlock.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/block/StakingFeeBlock.kt @@ -89,6 +89,7 @@ internal fun StakingFeeBlock(feeState: FeeState) { iconRes = R.drawable.ic_bird_24, isSelected = true, paddingValues = PaddingValues(), + showDivider = false, ) FeeError(feeState) } diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/express/OnrampStatusFactory.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/express/OnrampStatusFactory.kt index e25e788d52..bcf2f0b1b2 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/express/OnrampStatusFactory.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/express/OnrampStatusFactory.kt @@ -15,9 +15,9 @@ import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.analytics.TokenOnrampAnalyticsEvent import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.feature.tokendetails.presentation.tokendetails.model.TokenDetailsClickIntents import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.TokenDetailsOnrampTransactionStateConverter -import com.tangem.feature.tokendetails.presentation.tokendetails.model.TokenDetailsClickIntents import com.tangem.utils.Provider import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -116,7 +116,11 @@ internal class OnrampStatusFactory @AssistedInject constructor( fiatCurrency = onrampTx.fromCurrencyCode, ), ) - onrampUpdateTransactionStatusUseCase(externalTxId = externalTxId, statusModel.status) + onrampUpdateTransactionStatusUseCase( + externalTxId = externalTxId, + externalTxUrl = statusModel.externalTxUrl.orEmpty(), + status = statusModel.status, + ) } } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/OnrampStatusFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/OnrampStatusFactory.kt index 3c1961eee0..aeb4c7ad66 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/OnrampStatusFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/OnrampStatusFactory.kt @@ -72,7 +72,11 @@ internal class OnrampStatusFactory @Inject constructor( fiatCurrency = onrampTx.fromCurrencyCode, ), ) - onrampUpdateTransactionStatusUseCase(externalTxId = externalTxId, statusModel.status) + onrampUpdateTransactionStatusUseCase( + externalTxId = externalTxId, + externalTxUrl = statusModel.externalTxUrl.orEmpty(), + status = statusModel.status, + ) } }, ) diff --git a/gradle.properties b/gradle.properties index a312f8c1a2..0e6c69b832 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,7 +6,7 @@ # http://www.gradle.org/docs/current/userguide/build_environment.html # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. -org.gradle.jvmargs = -Xmx6144m -XX:MaxMetaspaceSize=768m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 +org.gradle.jvmargs = -Xmx6144m -XX:MaxMetaspaceSize=768m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 -XX:+UseParallelGC # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects diff --git a/gradle/tangem_dependencies.toml b/gradle/tangem_dependencies.toml index ec47aff56d..bcba22609f 100644 --- a/gradle/tangem_dependencies.toml +++ b/gradle/tangem_dependencies.toml @@ -5,7 +5,7 @@ # https://github.com/tangem/tangem-sdk-android/ # https://github.com/tangem/vico -tangemBlockchainSdk = "develop-1034" +tangemBlockchainSdk = "develop-1040" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "develop-455" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^