diff --git a/app/src/main/java/one/mixin/android/extension/Dimesions.kt b/app/src/main/java/one/mixin/android/extension/Dimesions.kt index c45ca9b1dc..21833e431f 100644 --- a/app/src/main/java/one/mixin/android/extension/Dimesions.kt +++ b/app/src/main/java/one/mixin/android/extension/Dimesions.kt @@ -1,16 +1,17 @@ package one.mixin.android.extension +import androidx.compose.ui.unit.Dp import one.mixin.android.MixinApplication val Int.dp: Int get() = MixinApplication.appContext.dpToPx(this.toFloat()) -val Int.composeDp: androidx.compose.ui.unit.Dp - get() = androidx.compose.ui.unit.Dp(this.toFloat()) +val Int.composeDp: Dp + get() = Dp(this.toFloat()) val Int.sp: Int get() = MixinApplication.appContext.spToPx(this.toFloat()) val Float.dp: Int get() = MixinApplication.appContext.dpToPx(this) -val Float.composeDp: androidx.compose.ui.unit.Dp - get() = androidx.compose.ui.unit.Dp(this.toFloat()) +val Float.composeDp: Dp + get() = Dp(this.toFloat()) val Float.sp: Int get() = MixinApplication.appContext.spToPx(this) diff --git a/app/src/main/java/one/mixin/android/ui/home/web3/components/FloatingActions.kt b/app/src/main/java/one/mixin/android/ui/home/web3/components/FloatingActions.kt index 1c52f69609..1e7c698b63 100644 --- a/app/src/main/java/one/mixin/android/ui/home/web3/components/FloatingActions.kt +++ b/app/src/main/java/one/mixin/android/ui/home/web3/components/FloatingActions.kt @@ -1,18 +1,25 @@ package one.mixin.android.ui.home.web3.components +import one.mixin.android.Constants +import one.mixin.android.api.response.web3.SwapToken import androidx.compose.foundation.background +import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.widthIn +import androidx.compose.foundation.rememberScrollState import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -import one.mixin.android.Constants +import androidx.compose.ui.unit.sp import one.mixin.android.R -import one.mixin.android.api.response.web3.SwapToken import one.mixin.android.compose.theme.MixinAppTheme import one.mixin.android.ui.home.web3.trade.FocusedField import java.math.BigDecimal @@ -66,41 +73,66 @@ fun FloatingActions( } } FocusedField.PRICE -> { - Row( - modifier = Modifier - .fillMaxWidth() - .background(MixinAppTheme.colors.backgroundWindow) - .padding(horizontal = 12.dp, vertical = 8.dp), - horizontalArrangement = Arrangement.SpaceBetween, - ) { - InputAction(stringResource(R.string.market_price), showBorder = true) { - onSetPriceMultiplier(1.0f) - onMarketPriceClick?.invoke() + val priceActions = priceQuickActions( + fromToken = fromToken, + toToken = toToken, + isPriceInverted = isPriceInverted, + onSetPriceMultiplier = onSetPriceMultiplier, + onMarketPriceClick = onMarketPriceClick, + onDone = onDone, + doneLabel = stringResource(R.string.Done), + ) + if (priceActions.size == 4) { + Row( + modifier = Modifier + .fillMaxWidth() + .background(MixinAppTheme.colors.backgroundWindow) + .padding(horizontal = 12.dp, vertical = 8.dp), + horizontalArrangement = Arrangement.SpaceBetween, + ) { + priceActions.forEach { action -> + InputAction( + text = action.label, + showBorder = action.showBorder, + onAction = action.onClick, + ) + } } + } else { + BoxWithConstraints( + modifier = Modifier + .fillMaxWidth() + .background(MixinAppTheme.colors.backgroundWindow) + ) { + val buttonSpacing = 3.dp + val minButtonWidth = 72.dp + val availableWidth = maxWidth - 16.dp + val calculatedButtonWidth = (availableWidth - buttonSpacing * (priceActions.size - 1)) / priceActions.size + val buttonWidth = if (calculatedButtonWidth > minButtonWidth) calculatedButtonWidth else minButtonWidth - val isFromUsd = fromToken?.assetId?.let { id -> - Constants.AssetId.usdtAssets.containsKey(id) || Constants.AssetId.usdcAssets.containsKey(id) - } == true - val isToUsd = toToken?.assetId?.let { id -> - Constants.AssetId.usdtAssets.containsKey(id) || Constants.AssetId.usdcAssets.containsKey(id) - } == true - - if (isToUsd && !isFromUsd) { - InputAction("+10%", showBorder = true) { - onSetPriceMultiplier(displayPriceMultiplier(1.1f, isPriceInverted)) - } - InputAction("+20%", showBorder = true) { - onSetPriceMultiplier(displayPriceMultiplier(1.2f, isPriceInverted)) - } - } else { - InputAction("-10%", showBorder = true) { - onSetPriceMultiplier(displayPriceMultiplier(0.9f, isPriceInverted)) - } - InputAction("-20%", showBorder = true) { - onSetPriceMultiplier(displayPriceMultiplier(0.8f, isPriceInverted)) + Row( + modifier = Modifier + .fillMaxWidth() + .horizontalScroll(rememberScrollState()) + .padding(horizontal = 8.dp, vertical = 8.dp), + horizontalArrangement = Arrangement.Start, + ) { + priceActions.forEachIndexed { index, action -> + InputAction( + text = action.label, + modifier = Modifier.widthIn(min = buttonWidth), + showBorder = action.showBorder, + horizontalPadding = 14.dp, + verticalPadding = 6.dp, + fontSize = 13.sp, + onAction = action.onClick, + ) + if (index != priceActions.lastIndex) { + Spacer(modifier = Modifier.width(buttonSpacing)) + } + } } } - InputAction(stringResource(R.string.Done), showBorder = false) { onDone() } } } else -> {} @@ -114,3 +146,69 @@ private fun displayPriceMultiplier(displayMultiplier: Float, isPriceInverted: Bo .divide(BigDecimal(displayMultiplier.toString()), 8, RoundingMode.HALF_UP) .toFloat() } + +private data class PriceQuickAction( + val label: String, + val showBorder: Boolean = true, + val onClick: () -> Unit, +) + +private fun priceQuickActions( + fromToken: SwapToken?, + toToken: SwapToken?, + isPriceInverted: Boolean, + onSetPriceMultiplier: (Float?) -> Unit, + onMarketPriceClick: (() -> Unit)?, + onDone: () -> Unit, + doneLabel: String, +): List { + val isFromUsd = fromToken.isUsdToken() + val isToUsd = toToken.isUsdToken() + val marketAction = PriceQuickAction("market") { + onSetPriceMultiplier(1.0f) + onMarketPriceClick?.invoke() + } + val decreaseActions = listOf( + PriceQuickAction("-10%") { + onSetPriceMultiplier(displayPriceMultiplier(0.9f, isPriceInverted)) + }, + PriceQuickAction("-20%") { + onSetPriceMultiplier(displayPriceMultiplier(0.8f, isPriceInverted)) + }, + ) + val increaseActions = listOf( + PriceQuickAction("+10%") { + onSetPriceMultiplier(displayPriceMultiplier(1.1f, isPriceInverted)) + }, + PriceQuickAction("+20%") { + onSetPriceMultiplier(displayPriceMultiplier(1.2f, isPriceInverted)) + }, + ) + + return if (isFromUsd == isToUsd) { + listOf( + decreaseActions[1], + decreaseActions[0], + marketAction, + increaseActions[0], + increaseActions[1], + ) + } else if (isToUsd) { + buildList { + add(marketAction) + addAll(increaseActions) + add(PriceQuickAction(doneLabel, showBorder = false, onClick = onDone)) + } + } else { + buildList { + add(marketAction) + addAll(decreaseActions) + add(PriceQuickAction(doneLabel, showBorder = false, onClick = onDone)) + } + } +} + +private fun SwapToken?.isUsdToken(): Boolean { + val assetId = this?.assetId ?: return false + return assetId in Constants.AssetId.usdtAssets || assetId in Constants.AssetId.usdcAssets +} diff --git a/app/src/main/java/one/mixin/android/ui/home/web3/components/InputAction.kt b/app/src/main/java/one/mixin/android/ui/home/web3/components/InputAction.kt index cc00538974..cca601d60c 100644 --- a/app/src/main/java/one/mixin/android/ui/home/web3/components/InputAction.kt +++ b/app/src/main/java/one/mixin/android/ui/home/web3/components/InputAction.kt @@ -6,6 +6,7 @@ import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.collectIsPressedAsState import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.defaultMinSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.layout.wrapContentWidth @@ -18,9 +19,10 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.TextUnit import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import one.mixin.android.compose.theme.MixinAppTheme @@ -28,7 +30,11 @@ import one.mixin.android.compose.theme.MixinAppTheme @Composable fun InputAction( text: String, + modifier: Modifier = Modifier, showBorder: Boolean = true, + horizontalPadding: Dp = 20.dp, + verticalPadding: Dp = 6.dp, + fontSize: TextUnit = 14.sp, onAction: () -> Unit, ) { val interactionSource = remember { MutableInteractionSource() } @@ -36,7 +42,7 @@ fun InputAction( Box( modifier = if (showBorder) { - Modifier + modifier .wrapContentWidth() .wrapContentHeight() .clip(RoundedCornerShape(20.dp)) @@ -47,9 +53,10 @@ fun InputAction( ) { onAction.invoke() } - .padding(20.dp, 6.dp) + .defaultMinSize(minHeight = 32.dp) + .padding(horizontalPadding, verticalPadding) } else { - Modifier + modifier .wrapContentWidth() .wrapContentHeight() .clickable( @@ -58,7 +65,8 @@ fun InputAction( ) { onAction.invoke() } - .padding(6.dp, 6.dp) + .defaultMinSize(minHeight = 32.dp) + .padding(verticalPadding, verticalPadding) }, contentAlignment = Alignment.Center, ) { @@ -66,6 +74,7 @@ fun InputAction( text = text, textAlign = TextAlign.Center, style = TextStyle( + fontSize = fontSize, lineHeight = 16.sp, color = if (isPressed) MixinAppTheme.colors.textAssist else MixinAppTheme.colors.textPrimary, ), @@ -78,4 +87,3 @@ fun InputAction( fun PreviewInputActionMax() { InputAction("MAX") {} } - diff --git a/app/src/main/java/one/mixin/android/ui/home/web3/trade/CandleChart.kt b/app/src/main/java/one/mixin/android/ui/home/web3/trade/CandleChart.kt index ffdfe606cf..e31efb21bb 100644 --- a/app/src/main/java/one/mixin/android/ui/home/web3/trade/CandleChart.kt +++ b/app/src/main/java/one/mixin/android/ui/home/web3/trade/CandleChart.kt @@ -51,6 +51,7 @@ import androidx.compose.ui.text.drawText import androidx.compose.ui.text.rememberTextMeasurer import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel @@ -532,8 +533,8 @@ private fun PerpsCandleChartCanvas( items: List, timeFrame: String, context: android.content.Context, - candleWidth: androidx.compose.ui.unit.Dp, - spacing: androidx.compose.ui.unit.Dp, + candleWidth: Dp, + spacing: Dp, touchXOnChart: Float?, scrollOffset: Float, viewportWidth: Float, diff --git a/app/src/main/java/one/mixin/android/ui/landing/MnemonicPhraseFragment.kt b/app/src/main/java/one/mixin/android/ui/landing/MnemonicPhraseFragment.kt index e05dcda362..610aaea527 100644 --- a/app/src/main/java/one/mixin/android/ui/landing/MnemonicPhraseFragment.kt +++ b/app/src/main/java/one/mixin/android/ui/landing/MnemonicPhraseFragment.kt @@ -123,6 +123,7 @@ class MnemonicPhraseFragment : BaseFragment(R.layout.fragment_compose) { private fun anonymousRequest(words: List? = null) { lifecycleScope.launch { + errorInfo = null landingViewModel.updateMnemonicPhraseState(MnemonicPhraseState.Creating) val sessionKey = generateEd25519KeyPair() val edKey = if (!words.isNullOrEmpty()) { @@ -183,12 +184,14 @@ class MnemonicPhraseFragment : BaseFragment(R.layout.fragment_compose) { }, failureBlock = { r -> - if (r.errorCode == NEED_CAPTCHA) { - if (words.isNullOrEmpty()) { - AnalyticsTracker.trackSignUpCaptcha() - } else { - AnalyticsTracker.trackLoginCaptcha("mnemonic_phrase") - } + if (r.errorCode == NEED_CAPTCHA) { + errorInfo = null + landingViewModel.updateMnemonicPhraseState(MnemonicPhraseState.Creating) + if (words.isNullOrEmpty()) { + AnalyticsTracker.trackSignUpCaptcha() + } else { + AnalyticsTracker.trackLoginCaptcha("mnemonic_phrase") + } initAndLoadCaptcha(sessionKey, edKey, r.errorDescription) } else { errorInfo = requireContext().getMixinErrorStringByCode(r.errorCode, r.errorDescription) @@ -216,6 +219,8 @@ class MnemonicPhraseFragment : BaseFragment(R.layout.fragment_compose) { private var captchaView: CaptchaView? = null private fun initAndLoadCaptcha(sessionKey: EdKeyPair, edKey: EdKeyPair, errorDescription: String) = lifecycleScope.launch { + errorInfo = null + landingViewModel.updateMnemonicPhraseState(MnemonicPhraseState.Creating) if (captchaView == null) { captchaView = CaptchaView( @@ -258,6 +263,8 @@ class MnemonicPhraseFragment : BaseFragment(R.layout.fragment_compose) { private fun reSend(sessionKey: EdKeyPair, edKey: EdKeyPair, hCaptchaResponse: String? = null, gRecaptchaResponse: String? = null, gtRecaptchaResponse: String? = null) { lifecycleScope.launch { + errorInfo = null + landingViewModel.updateMnemonicPhraseState(MnemonicPhraseState.Creating) val (messageHex, signatureHex) = buildAnonymousRequestPayload(edKey) val r = handleMixinResponse( invokeNetwork = { @@ -277,6 +284,7 @@ class MnemonicPhraseFragment : BaseFragment(R.layout.fragment_compose) { failureBlock = { r -> if (r.errorCode == NEED_CAPTCHA) { + errorInfo = null landingViewModel.updateMnemonicPhraseState(MnemonicPhraseState.Creating) initAndLoadCaptcha(sessionKey, edKey, r.errorDescription) } else { diff --git a/app/src/main/java/one/mixin/android/ui/wallet/LimitTransferBottomSheetDialogFragment.kt b/app/src/main/java/one/mixin/android/ui/wallet/LimitTransferBottomSheetDialogFragment.kt index 9665a98e32..1ef34d477a 100644 --- a/app/src/main/java/one/mixin/android/ui/wallet/LimitTransferBottomSheetDialogFragment.kt +++ b/app/src/main/java/one/mixin/android/ui/wallet/LimitTransferBottomSheetDialogFragment.kt @@ -434,6 +434,7 @@ class LimitTransferBottomSheetDialogFragment : MixinComposeBottomSheetDialogFrag override fun onDismiss(dialog: DialogInterface) { super.onDismiss(dialog); onDestroyAction?.invoke() } private fun showPin() { + errorInfo = null PinInputBottomSheetDialogFragment.newInstance(biometricInfo = getBiometricInfo(), from = 1).setOnPinComplete { pin -> lifecycleScope.launch(CoroutineExceptionHandler { _, error -> handleException(error) }) { doAfterPinComplete(pin) diff --git a/app/src/main/java/one/mixin/android/ui/wallet/SwapTransferBottomSheetDialogFragment.kt b/app/src/main/java/one/mixin/android/ui/wallet/SwapTransferBottomSheetDialogFragment.kt index efb5625621..11e2077c12 100644 --- a/app/src/main/java/one/mixin/android/ui/wallet/SwapTransferBottomSheetDialogFragment.kt +++ b/app/src/main/java/one/mixin/android/ui/wallet/SwapTransferBottomSheetDialogFragment.kt @@ -623,6 +623,7 @@ class SwapTransferBottomSheetDialogFragment : MixinComposeBottomSheetDialogFragm } private fun showPin() { + errorInfo = null PinInputBottomSheetDialogFragment.newInstance(biometricInfo = getBiometricInfo(), from = 1).setOnPinComplete { pin -> lifecycleScope.launch( CoroutineExceptionHandler { _, error -> diff --git a/app/src/main/java/one/mixin/android/ui/wallet/transfer/TransferBottomSheetDialogFragment.kt b/app/src/main/java/one/mixin/android/ui/wallet/transfer/TransferBottomSheetDialogFragment.kt index e068782303..62c5bbef60 100644 --- a/app/src/main/java/one/mixin/android/ui/wallet/transfer/TransferBottomSheetDialogFragment.kt +++ b/app/src/main/java/one/mixin/android/ui/wallet/transfer/TransferBottomSheetDialogFragment.kt @@ -644,6 +644,7 @@ class TransferBottomSheetDialogFragment : MixinBottomSheetDialogFragment() { } private fun showPin() { + transferViewModel.errorMessage = null PinInputBottomSheetDialogFragment.newInstance(biometricInfo = getBiometricInfo(), from = 1).setOnPinComplete { pin -> lifecycleScope.launch( CoroutineExceptionHandler { _, error -> diff --git a/app/src/main/java/one/mixin/android/ui/wallet/transfer/TransferInvoiceBottomSheetDialogFragment.kt b/app/src/main/java/one/mixin/android/ui/wallet/transfer/TransferInvoiceBottomSheetDialogFragment.kt index 01e569551e..5dd8fcbf0e 100644 --- a/app/src/main/java/one/mixin/android/ui/wallet/transfer/TransferInvoiceBottomSheetDialogFragment.kt +++ b/app/src/main/java/one/mixin/android/ui/wallet/transfer/TransferInvoiceBottomSheetDialogFragment.kt @@ -359,6 +359,7 @@ class TransferInvoiceBottomSheetDialogFragment : MixinBottomSheetDialogFragment( } private fun showPin() { + transferViewModel.errorMessage = null PinInputBottomSheetDialogFragment.newInstance(biometricInfo = getBiometricInfo(), from = 1).setOnPinComplete { pin -> lifecycleScope.launch( CoroutineExceptionHandler { _, error ->