From 8dd1d36e6ba6d2a5fce74d5af392714dae85cc3d Mon Sep 17 00:00:00 2001 From: SeniorZhai Date: Mon, 16 Mar 2026 16:32:08 +0800 Subject: [PATCH 01/35] Trade spot guide, refine wallet onboarding guides and spot trade limit pricing Add pinch zoom to candle chart --- ...SpotTradeGuideBottomSheetDialogFragment.kt | 80 ++ .../ui/home/web3/trade/SpotTradeGuidePage.kt | 882 ++++++++++++++++++ .../ui/home/web3/trade/TradeFragment.kt | 26 +- .../android/ui/home/web3/trade/TradePage.kt | 9 +- .../AddWalletBottomSheetDialogFragment.kt | 36 +- .../wallet/components/AssetDashboardScreen.kt | 211 ++++- .../wallet/components/WalletCategoryFilter.kt | 6 +- .../mixin_import_safety_preview_0.png | Bin 0 -> 2151 bytes .../mixin_import_safety_preview_1.png | Bin 0 -> 1670 bytes .../mixin_import_safety_preview_2.png | Bin 0 -> 1407 bytes .../mixin_import_safety_preview_3.png | Bin 0 -> 1587 bytes .../mixin_import_safety_preview_4.png | Bin 0 -> 1518 bytes .../mixin_import_safety_preview_5.png | Bin 0 -> 1156 bytes .../mixin_import_safety_preview_6.png | Bin 0 -> 1527 bytes .../mixin_import_safety_preview_7.png | Bin 0 -> 3287 bytes .../main/res/drawable/ic_add_wallet_freee.xml | 25 + .../main/res/drawable/ic_add_watch_wallet.xml | 34 + app/src/main/res/drawable/ic_dot_assist.xml | 5 + .../main/res/drawable/ic_import_wallet.xml | 31 + .../fragment_add_wallet_bottom_sheet.xml | 574 ++++++++---- app/src/main/res/values-zh-rCN/strings.xml | 53 +- app/src/main/res/values/strings.xml | 51 +- 22 files changed, 1826 insertions(+), 197 deletions(-) create mode 100644 app/src/main/java/one/mixin/android/ui/home/web3/trade/SpotTradeGuideBottomSheetDialogFragment.kt create mode 100644 app/src/main/java/one/mixin/android/ui/home/web3/trade/SpotTradeGuidePage.kt create mode 100644 app/src/main/res/drawable-xxhdpi/mixin_import_safety_preview_0.png create mode 100644 app/src/main/res/drawable-xxhdpi/mixin_import_safety_preview_1.png create mode 100644 app/src/main/res/drawable-xxhdpi/mixin_import_safety_preview_2.png create mode 100644 app/src/main/res/drawable-xxhdpi/mixin_import_safety_preview_3.png create mode 100644 app/src/main/res/drawable-xxhdpi/mixin_import_safety_preview_4.png create mode 100644 app/src/main/res/drawable-xxhdpi/mixin_import_safety_preview_5.png create mode 100644 app/src/main/res/drawable-xxhdpi/mixin_import_safety_preview_6.png create mode 100644 app/src/main/res/drawable-xxhdpi/mixin_import_safety_preview_7.png create mode 100644 app/src/main/res/drawable/ic_add_wallet_freee.xml create mode 100644 app/src/main/res/drawable/ic_add_watch_wallet.xml create mode 100644 app/src/main/res/drawable/ic_dot_assist.xml create mode 100644 app/src/main/res/drawable/ic_import_wallet.xml diff --git a/app/src/main/java/one/mixin/android/ui/home/web3/trade/SpotTradeGuideBottomSheetDialogFragment.kt b/app/src/main/java/one/mixin/android/ui/home/web3/trade/SpotTradeGuideBottomSheetDialogFragment.kt new file mode 100644 index 0000000000..604ef1c179 --- /dev/null +++ b/app/src/main/java/one/mixin/android/ui/home/web3/trade/SpotTradeGuideBottomSheetDialogFragment.kt @@ -0,0 +1,80 @@ +package one.mixin.android.ui.home.web3.trade + +import android.annotation.SuppressLint +import android.app.Dialog +import android.os.Bundle +import android.view.Gravity +import android.view.View +import android.view.ViewGroup +import androidx.compose.runtime.Composable +import dagger.hilt.android.AndroidEntryPoint +import one.mixin.android.R +import one.mixin.android.compose.theme.MixinAppTheme +import one.mixin.android.extension.booleanFromAttribute +import one.mixin.android.extension.getSafeAreaInsetsTop +import one.mixin.android.extension.isNightMode +import one.mixin.android.extension.screenHeight +import one.mixin.android.ui.common.MixinComposeBottomSheetDialogFragment +import one.mixin.android.util.SystemUIManager + +@AndroidEntryPoint +class SpotTradeGuideBottomSheetDialogFragment : MixinComposeBottomSheetDialogFragment() { + + companion object { + const val TAG = "SpotTradeGuideBottomSheetDialogFragment" + private const val ARGS_INITIAL_TAB = "args_initial_tab" + + const val TAB_OVERVIEW = 0 + const val TAB_SWAP = 1 + const val TAB_LIMIT = 2 + + fun newInstance(initialTab: Int = TAB_OVERVIEW) = SpotTradeGuideBottomSheetDialogFragment().apply { + arguments = Bundle().apply { + putInt(ARGS_INITIAL_TAB, initialTab) + } + } + } + + override fun getTheme() = R.style.AppTheme_Dialog + + @SuppressLint("RestrictedApi") + override fun setupDialog(dialog: Dialog, style: Int) { + super.setupDialog(dialog, R.style.MixinBottomSheet) + dialog.window?.let { window -> + SystemUIManager.lightUI(window, requireContext().isNightMode()) + } + dialog.window?.setGravity(Gravity.BOTTOM) + dialog.window?.setLayout( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT, + ) + } + + override fun onStart() { + super.onStart() + dialog?.window?.let { window -> + SystemUIManager.lightUI( + window, + !requireContext().booleanFromAttribute(R.attr.flag_night), + ) + } + } + + @Composable + override fun ComposeContent() { + val initialTab = arguments?.getInt(ARGS_INITIAL_TAB, TAB_OVERVIEW) ?: TAB_OVERVIEW + MixinAppTheme { + SpotTradeGuidePage( + initialTab = initialTab, + pop = { dismiss() } + ) + } + } + + override fun getBottomSheetHeight(view: View): Int { + return requireContext().screenHeight() - view.getSafeAreaInsetsTop() + } + + override fun showError(error: String) { + } +} diff --git a/app/src/main/java/one/mixin/android/ui/home/web3/trade/SpotTradeGuidePage.kt b/app/src/main/java/one/mixin/android/ui/home/web3/trade/SpotTradeGuidePage.kt new file mode 100644 index 0000000000..73e6eac9ea --- /dev/null +++ b/app/src/main/java/one/mixin/android/ui/home/web3/trade/SpotTradeGuidePage.kt @@ -0,0 +1,882 @@ +package one.mixin.android.ui.home.web3.trade + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.horizontalScroll +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.Button +import androidx.compose.material.ButtonDefaults +import androidx.compose.material.CircularProgressIndicator +import androidx.compose.material.Icon +import androidx.compose.material.Surface +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.draw.clip +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import kotlinx.coroutines.delay +import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch +import one.mixin.android.Constants +import one.mixin.android.R +import one.mixin.android.compose.CoilImage +import one.mixin.android.compose.theme.MixinAppTheme +import one.mixin.android.extension.numberFormat8 +import one.mixin.android.extension.priceFormat +import one.mixin.android.ui.home.web3.components.OutlinedTab +import one.mixin.android.ui.wallet.alert.components.cardBackground +import one.mixin.android.vo.safe.TokenItem +import one.mixin.android.widget.components.DotText +import java.math.BigDecimal +import java.math.RoundingMode + +private val LIMIT_PRICE_STEP = BigDecimal("1000") +private val LIMIT_PRICE_TEN_THOUSAND = BigDecimal("10000") + +private enum class LimitStrategy( + val titleRes: Int, +) { + BuyLow( + titleRes = R.string.Spot_Trade_Guide_Limit_Strategy_Buy_Low, + ), + SellHigh( + titleRes = R.string.Spot_Trade_Guide_Limit_Strategy_Sell_High, + ), +} + +@Composable +fun SpotTradeGuidePage( + initialTab: Int = SpotTradeGuideBottomSheetDialogFragment.TAB_OVERVIEW, + pop: () -> Unit, +) { + val coroutineScope = rememberCoroutineScope() + val tabs = listOf( + stringResource(R.string.Overview), + stringResource(R.string.Trade_Simple), + stringResource(R.string.Trade_Advanced), + ) + val safeInitialTab = initialTab.coerceIn(0, tabs.lastIndex) + var selectedTab by remember(safeInitialTab) { mutableIntStateOf(safeInitialTab) } + + Column( + modifier = Modifier + .fillMaxSize() + .clip(RoundedCornerShape(topStart = 8.dp, topEnd = 8.dp)) + .background(MixinAppTheme.colors.background) + ) { + Box( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 20.dp, vertical = 20.dp), + ) { + Text( + text = stringResource(R.string.Trading_Guide), + fontSize = 18.sp, + fontWeight = FontWeight.W500, + color = MixinAppTheme.colors.textPrimary, + modifier = Modifier.align(Alignment.CenterStart), + ) + Icon( + painter = painterResource(id = R.drawable.ic_circle_close), + contentDescription = stringResource(id = R.string.close), + tint = Color.Unspecified, + modifier = Modifier + .align(Alignment.CenterEnd) + .clickable(onClick = pop), + ) + } + Column( + modifier = Modifier + .fillMaxSize() + .padding(horizontal = 16.dp) + ) { + Spacer(modifier = Modifier.height(16.dp)) + Row( + modifier = Modifier + .fillMaxWidth() + .horizontalScroll(rememberScrollState()) + ) { + tabs.forEachIndexed { index, tab -> + OutlinedTab( + text = tab, + selected = selectedTab == index, + showBadge = false, + onClick = { coroutineScope.launch { selectedTab = index } } + ) + if (index < tabs.lastIndex) { + Spacer(modifier = Modifier.width(10.dp)) + } + } + } + + Spacer(modifier = Modifier.height(16.dp)) + + Column( + modifier = Modifier + .fillMaxWidth() + .weight(1f) + .verticalScroll(rememberScrollState()) + ) { + when (selectedTab) { + SpotTradeGuideBottomSheetDialogFragment.TAB_OVERVIEW -> OverviewContent() + SpotTradeGuideBottomSheetDialogFragment.TAB_SWAP -> SimpleSwapContent() + SpotTradeGuideBottomSheetDialogFragment.TAB_LIMIT -> LimitTradeContent() + } + Spacer(modifier = Modifier.height(24.dp)) + } + + Spacer(modifier = Modifier.height(20.dp)) + SpotTradeGuideBottomNavigation( + selectedTab = selectedTab, + tabs = tabs, + onSelect = { targetTab -> + coroutineScope.launch { selectedTab = targetTab } + }, + onClose = pop, + ) + Spacer(modifier = Modifier.height(24.dp)) + } + } +} + +@Composable +private fun OverviewContent() { + TradeGuideInfoCard( + title = stringResource(R.string.Perpetual_Guide_Overview_Title), + description = stringResource(R.string.Spot_Trade_Guide_Overview_Desc), + sections = listOf( + stringResource(R.string.Perpetual_Features) to listOf( + stringResource(R.string.Spot_Trade_Guide_Feature_1), + stringResource(R.string.Spot_Trade_Guide_Feature_2), + ), + stringResource(R.string.Spot_Trade_Guide_Additional_Notes) to listOf( + stringResource(R.string.Spot_Trade_Guide_Note_1), + stringResource(R.string.Spot_Trade_Guide_Note_2), + ), + ) + ) +} + +@Composable +private fun SimpleSwapContent() { + SpotTradeExampleCard(limitStrategy = null) + Spacer(modifier = Modifier.height(16.dp)) + TradeGuideInfoCard( + title = stringResource(R.string.Brief_Introduction), + description = stringResource(R.string.Spot_Trade_Guide_Swap_Desc), + sections = listOf( + stringResource(R.string.Spot_Trade_Guide_Suitable_Scenarios) to listOf( + stringResource(R.string.Spot_Trade_Guide_Swap_Scenario_1), + stringResource(R.string.Spot_Trade_Guide_Swap_Scenario_2), + stringResource(R.string.Spot_Trade_Guide_Swap_Scenario_3), + ), + stringResource(R.string.Spot_Trade_Guide_Quote_Explanation) to listOf( + stringResource(R.string.Spot_Trade_Guide_Swap_Quote_1), + stringResource(R.string.Spot_Trade_Guide_Swap_Quote_2), + ), + stringResource(R.string.Perpetual_Risk_Warning) to listOf( + stringResource(R.string.Spot_Trade_Guide_Swap_Risk), + ), + ) + ) +} + +@Composable +private fun LimitTradeContent() { + SpotTradeExampleCard(limitStrategy = LimitStrategy.BuyLow) + Spacer(modifier = Modifier.height(16.dp)) + TradeGuideInfoCard( + title = stringResource(R.string.Brief_Introduction), + description = stringResource(R.string.Spot_Trade_Guide_Limit_Desc), + sections = listOf( + stringResource(R.string.Spot_Trade_Guide_Suitable_Scenarios) to listOf( + stringResource(R.string.Spot_Trade_Guide_Limit_Scenario_1), + stringResource(R.string.Spot_Trade_Guide_Limit_Scenario_2), + stringResource(R.string.Spot_Trade_Guide_Limit_Scenario_3), + stringResource(R.string.Spot_Trade_Guide_Limit_Scenario_4), + ), + stringResource(R.string.Perpetual_Risk_Warning) to listOf( + stringResource(R.string.Spot_Trade_Guide_Limit_Risk), + ), + ) + ) +} + +@Composable +private fun SpotTradeExampleCard( + limitStrategy: LimitStrategy?, +) { + val viewModel = hiltViewModel() + val usdtToken by viewModel.assetItemFlow(Constants.AssetId.USDT_ASSET_ETH_ID).collectAsStateWithLifecycle(initialValue = null) + val btcToken by viewModel.assetItemFlow(Constants.ChainId.BITCOIN_CHAIN_ID).collectAsStateWithLifecycle(initialValue = null) + var priceRefreshFlag by remember { mutableStateOf(false) } + val marketPrice = remember(usdtToken?.priceUsd, btcToken?.priceUsd, priceRefreshFlag) { + calculateMarketPrice(usdtToken, btcToken) + } + var isPairReversed by remember(limitStrategy) { mutableStateOf(false) } + var isPriceDisplayReversed by remember(limitStrategy) { mutableStateOf(false) } + var payAmount by remember(limitStrategy) { mutableStateOf(BigDecimal("1000")) } + var strategy by remember(limitStrategy) { mutableStateOf(limitStrategy ?: LimitStrategy.BuyLow) } + var limitPriceOffset by remember(limitStrategy, strategy) { mutableStateOf(BigDecimal.ZERO) } + + val fromToken = if (isPairReversed) btcToken else usdtToken + val toToken = if (isPairReversed) usdtToken else btcToken + val amountStep = remember(isPairReversed) { + if (isPairReversed) BigDecimal("0.001") else BigDecimal("100") + } + val limitBasePrice = remember(marketPrice, strategy) { + calculateLimitBasePrice( + marketPrice = marketPrice, + strategy = strategy, + ) + } + val effectivePrice = if (limitStrategy == null) { + marketPrice + } else { + limitBasePrice.add(limitPriceOffset).max(LIMIT_PRICE_STEP) + } + val estimatedReceive = remember(payAmount, effectivePrice, isPairReversed) { + calculateReceiveAmount( + amount = payAmount, + price = effectivePrice, + reversed = isPairReversed, + ) + } + + Column( + modifier = Modifier + .fillMaxWidth() + .cardBackground(MixinAppTheme.colors.background, MixinAppTheme.colors.borderColor) + .padding(16.dp) + ) { + Text( + text = stringResource(R.string.Perpetual_Example), + fontSize = 16.sp, + fontWeight = FontWeight.W500, + color = MixinAppTheme.colors.textPrimary, + ) + Spacer(modifier = Modifier.height(12.dp)) + if (limitStrategy != null) { + StrategyRow( + strategy = strategy, + onStrategySelected = { strategy = it }, + ) + Spacer(modifier = Modifier.height(12.dp)) + } + ExampleValueRow( + title = stringResource(R.string.Trade_Guide_Trading_Pair), + value = { + PairSwitcher( + fromToken = fromToken, + toToken = toToken, + fromFallbackSymbol = if (isPairReversed) "BTC" else "USDT", + toFallbackSymbol = if (isPairReversed) "USDT" else "BTC", + onSwitch = { + val currentFromPrice = if (isPairReversed) btcToken.safePrice() else usdtToken.safePrice() + val newFromPrice = if (isPairReversed) usdtToken.safePrice() else btcToken.safePrice() + payAmount = convertPayAmount(payAmount, currentFromPrice, newFromPrice) + isPairReversed = !isPairReversed + }, + ) + }, + ) + Spacer(modifier = Modifier.height(12.dp)) + ExampleValueRow( + title = stringResource(R.string.Trade_Guide_Pay_Amount), + value = { + AmountStepper( + amount = payAmount, + symbol = fromToken?.symbol ?: if (isPairReversed) "BTC" else "USDT", + step = amountStep, + onDecrease = { + payAmount = (payAmount - amountStep).max(amountStep) + }, + onIncrease = { + payAmount += amountStep + }, + ) + }, + ) + Spacer(modifier = Modifier.height(12.dp)) + ExampleValueRow( + title = stringResource( + if (limitStrategy == null) R.string.Trade_Guide_Exchange_Price else R.string.Trade_Guide_Order_Price + ), + value = { + if (limitStrategy == null) { + PriceSubtitle( + marketPrice = effectivePrice, + isReversed = isPriceDisplayReversed, + onSwitchDirection = { isPriceDisplayReversed = !isPriceDisplayReversed }, + onPriceExpired = { priceRefreshFlag = !priceRefreshFlag }, + ) + } else { + OrderPriceStepper( + price = effectivePrice, + symbol = usdtToken?.symbol ?: "USDT", + onDecrease = { + limitPriceOffset = (limitPriceOffset - LIMIT_PRICE_STEP) + .max(LIMIT_PRICE_STEP.subtract(limitBasePrice)) + }, + onIncrease = { + limitPriceOffset += LIMIT_PRICE_STEP + }, + ) + } + }, + ) + if (limitStrategy != null) { + Spacer(modifier = Modifier.height(12.dp)) + ExampleValueRow( + title = stringResource(R.string.Trade_Guide_Market_Price), + value = { + PriceSubtitle( + marketPrice = marketPrice, + isReversed = isPriceDisplayReversed, + onSwitchDirection = { isPriceDisplayReversed = !isPriceDisplayReversed }, + onPriceExpired = { priceRefreshFlag = !priceRefreshFlag }, + ) + }, + ) + } + Spacer(modifier = Modifier.height(12.dp)) + ExampleValueRow( + title = stringResource(R.string.Estimated_Receive), + value = { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(6.dp), + ) { + CoilImage( + model = toToken?.iconUrl, + placeholder = R.drawable.ic_avatar_place_holder, + modifier = Modifier + .size(18.dp) + .clip(CircleShape), + ) + Text( + text = "${estimatedReceive.numberFormat8()} ${toToken?.symbol ?: if (isPairReversed) "USDT" else "BTC"}", + fontSize = 15.sp, + lineHeight = 22.sp, + color = MixinAppTheme.colors.textPrimary, + fontWeight = FontWeight.W500, + textAlign = TextAlign.End, + ) + } + }, + ) + } +} + +@Composable +private fun StrategyRow( + strategy: LimitStrategy, + onStrategySelected: (LimitStrategy) -> Unit, +) { + ExampleValueRow( + title = stringResource(R.string.Trade_Guide_Trading_Strategy), + value = { + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + LimitStrategy.entries.forEach { item -> + val selected = item == strategy + Box( + modifier = Modifier + .clip(RoundedCornerShape(20.dp)) + .background( + if (selected) MixinAppTheme.colors.accent + else MixinAppTheme.colors.backgroundWindow + ) + .clickable { onStrategySelected(item) } + .padding(horizontal = 12.dp, vertical = 8.dp), + contentAlignment = Alignment.Center, + ) { + Text( + text = stringResource(item.titleRes), + color = if (selected) Color.White else MixinAppTheme.colors.textPrimary, + fontSize = 13.sp, + lineHeight = 18.sp, + ) + } + } + } + }, + ) +} + +@Composable +private fun ExampleValueRow( + title: String, + subtitle: (@Composable () -> Unit)? = null, + value: @Composable () -> Unit, +) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(12.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Column(modifier = Modifier.weight(1f)) { + Text( + text = title, + fontSize = 13.sp, + lineHeight = 18.sp, + color = MixinAppTheme.colors.textAssist, + ) + subtitle?.let { + Spacer(modifier = Modifier.height(4.dp)) + it() + } + } + Box(contentAlignment = Alignment.CenterEnd) { + value() + } + } +} + +@Composable +private fun PairSwitcher( + fromToken: TokenItem?, + toToken: TokenItem?, + fromFallbackSymbol: String, + toFallbackSymbol: String, + onSwitch: () -> Unit, +) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(6.dp), + modifier = Modifier.clickable(onClick = onSwitch), + ) { + GuideTokenBadge(token = fromToken, fallbackSymbol = fromFallbackSymbol) + Text( + text = "->", + fontSize = 14.sp, + color = MixinAppTheme.colors.textAssist, + ) + GuideTokenBadge(token = toToken, fallbackSymbol = toFallbackSymbol) + Icon( + painter = painterResource(id = R.drawable.ic_price_switch), + contentDescription = null, + tint = Color.Unspecified, + modifier = Modifier.size(18.dp), + ) + } +} + +@Composable +private fun GuideTokenBadge( + token: TokenItem?, + fallbackSymbol: String, +) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(6.dp), + ) { + CoilImage( + model = token?.iconUrl, + placeholder = R.drawable.ic_avatar_place_holder, + modifier = Modifier + .size(20.dp) + .clip(CircleShape), + ) + Text( + text = token?.symbol ?: fallbackSymbol, + fontSize = 14.sp, + lineHeight = 20.sp, + color = MixinAppTheme.colors.textPrimary, + fontWeight = FontWeight.W500, + ) + } +} + +@Composable +private fun AmountStepper( + amount: BigDecimal, + symbol: String, + step: BigDecimal, + onDecrease: () -> Unit, + onIncrease: () -> Unit, +) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp), + ) { + StepperButton( + text = "-", + enabled = amount > step, + onClick = onDecrease, + ) + Text( + text = "${amount.numberFormat8()} $symbol", + fontSize = 15.sp, + lineHeight = 22.sp, + color = MixinAppTheme.colors.textPrimary, + fontWeight = FontWeight.W500, + ) + StepperButton( + text = "+", + enabled = true, + onClick = onIncrease, + ) + } +} + +@Composable +private fun OrderPriceStepper( + price: BigDecimal, + symbol: String, + onDecrease: () -> Unit, + onIncrease: () -> Unit, +) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp), + ) { + StepperButton( + text = "-", + enabled = price > LIMIT_PRICE_STEP, + onClick = onDecrease, + ) + Text( + text = "${price.setScale(0, RoundingMode.DOWN).numberFormat8()} $symbol", + fontSize = 15.sp, + lineHeight = 22.sp, + color = MixinAppTheme.colors.textPrimary, + fontWeight = FontWeight.W500, + ) + StepperButton( + text = "+", + enabled = true, + onClick = onIncrease, + ) + } +} + +@Composable +private fun StepperButton( + text: String, + enabled: Boolean, + onClick: () -> Unit, +) { + Surface( + color = if (enabled) Color.Transparent else MixinAppTheme.colors.backgroundWindow, + shape = CircleShape, + border = BorderStroke(1.dp, MixinAppTheme.colors.borderColor), + modifier = Modifier + .size(24.dp) + .clickable(enabled = enabled, onClick = onClick), + ) { + Box(contentAlignment = Alignment.Center) { + Text( + text = text, + fontSize = 14.sp, + color = if (enabled) MixinAppTheme.colors.textPrimary else MixinAppTheme.colors.textAssist, + ) + } + } +} + +@Composable +private fun PriceSubtitle( + marketPrice: BigDecimal, + isReversed: Boolean, + onSwitchDirection: () -> Unit, + onPriceExpired: () -> Unit = {}, +) { + var quoteCountDown by remember(marketPrice) { mutableFloatStateOf(0f) } + + LaunchedEffect(marketPrice) { + while (isActive) { + quoteCountDown = 0f + while (isActive && quoteCountDown < 1f) { + delay(100) + quoteCountDown += 0.01f + } + onPriceExpired() + } + } + + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(6.dp), + ) { + Text( + text = if (isReversed) { + val inverted = safeDivide(BigDecimal.ONE, marketPrice) + "1 USDT ≈ ${inverted.numberFormat8()} BTC" + } else { + "1 BTC ≈ ${marketPrice.priceFormat()} USDT" + }, + fontSize = 14.sp, + color = MixinAppTheme.colors.textAssist, + ) + CircularProgressIndicator( + progress = quoteCountDown, + modifier = Modifier.size(12.dp), + strokeWidth = 2.dp, + color = MixinAppTheme.colors.textPrimary, + backgroundColor = MixinAppTheme.colors.textAssist, + ) + Icon( + painter = painterResource(id = R.drawable.ic_price_switch), + contentDescription = null, + tint = Color.Unspecified, + modifier = Modifier + .size(16.dp) + .clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = null, + onClick = onSwitchDirection, + ), + ) + } +} + +@Composable +private fun TradeGuideInfoCard( + title: String, + description: String, + sections: List>>, +) { + Column( + modifier = Modifier + .fillMaxWidth() + .cardBackground(MixinAppTheme.colors.background, MixinAppTheme.colors.borderColor) + .padding(16.dp) + ) { + Text( + text = title, + fontSize = 16.sp, + fontWeight = FontWeight.W500, + color = MixinAppTheme.colors.textPrimary, + ) + Spacer(modifier = Modifier.height(12.dp)) + Text( + text = description, + fontSize = 14.sp, + lineHeight = 20.sp, + color = MixinAppTheme.colors.textPrimary, + ) + sections.forEach { (sectionTitle, items) -> + Spacer(modifier = Modifier.height(16.dp)) + Text( + text = sectionTitle, + fontSize = 16.sp, + fontWeight = FontWeight.W500, + color = MixinAppTheme.colors.textPrimary, + ) + Spacer(modifier = Modifier.height(8.dp)) + items.forEach { item -> + DotText( + text = item, + modifier = Modifier.padding(vertical = 4.dp), + color = MixinAppTheme.colors.textPrimary, + ) + } + } + } +} + +@Composable +private fun SpotTradeGuideBottomNavigation( + selectedTab: Int, + tabs: List, + onSelect: (Int) -> Unit, + onClose: () -> Unit, +) { + val previousTab = (selectedTab - 1).takeIf { it >= 0 } + val nextTab = (selectedTab + 1).takeIf { it < tabs.size } + if (previousTab == null && nextTab == null) { + return + } + if (previousTab != null && nextTab == null) { + Row( + modifier = Modifier + .padding(horizontal = 20.dp) + .fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(12.dp), + ) { + SpotTradeGuideNavigationButton( + text = stringResource(R.string.Perpetual_Guide_Previous_Tab, tabs[previousTab]), + modifier = Modifier.weight(1f), + onClick = { onSelect(previousTab) }, + ) + SpotTradeGuideNavigationButton( + text = stringResource( + R.string.Perpetual_Guide_Next_Tab, + stringResource(R.string.Start) + ), + modifier = Modifier.weight(1f), + onClick = onClose, + ) + } + return + } + if (previousTab != null && nextTab != null) { + Row( + modifier = Modifier + .padding(horizontal = 20.dp) + .fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(12.dp), + ) { + SpotTradeGuideNavigationButton( + text = stringResource(R.string.Perpetual_Guide_Previous_Tab, tabs[previousTab]), + modifier = Modifier.weight(1f), + onClick = { onSelect(previousTab) }, + ) + SpotTradeGuideNavigationButton( + text = stringResource(R.string.Perpetual_Guide_Next_Tab, tabs[nextTab]), + modifier = Modifier.weight(1f), + onClick = { onSelect(nextTab) }, + ) + } + return + } + val targetIndex = previousTab ?: nextTab ?: return + val buttonText = if (previousTab != null) { + stringResource(R.string.Perpetual_Guide_Previous_Tab, tabs[targetIndex]) + } else { + stringResource(R.string.Perpetual_Guide_Next_Tab, tabs[targetIndex]) + } + Box( + modifier = Modifier.fillMaxWidth(), + contentAlignment = Alignment.Center, + ) { + SpotTradeGuideNavigationButton( + text = buttonText, + modifier = Modifier.fillMaxWidth(0.5f), + onClick = { onSelect(targetIndex) }, + ) + } +} + +@Composable +private fun SpotTradeGuideNavigationButton( + text: String, + modifier: Modifier = Modifier, + onClick: () -> Unit, +) { + Button( + modifier = modifier.height(48.dp), + onClick = onClick, + colors = ButtonDefaults.outlinedButtonColors( + backgroundColor = MixinAppTheme.colors.accent, + contentColor = Color.White, + ), + shape = RoundedCornerShape(32.dp), + elevation = ButtonDefaults.elevation( + pressedElevation = 0.dp, + defaultElevation = 0.dp, + hoveredElevation = 0.dp, + focusedElevation = 0.dp, + ), + ) { + Text( + text = text, + fontSize = 16.sp, + fontWeight = FontWeight.Bold, + color = Color.White, + ) + } +} + +private fun calculateMarketPrice( + usdtToken: TokenItem?, + btcToken: TokenItem?, +): BigDecimal { + val usdtPrice = usdtToken?.priceUsd?.toBigDecimalOrNull()?.takeIf { it > BigDecimal.ZERO } ?: BigDecimal.ONE + val btcPrice = btcToken?.priceUsd?.toBigDecimalOrNull()?.takeIf { it > BigDecimal.ZERO } ?: BigDecimal("95594.89") + return safeDivide(btcPrice, usdtPrice) +} + +private fun calculateLimitBasePrice( + marketPrice: BigDecimal, + strategy: LimitStrategy, +): BigDecimal { + val integerPrice = marketPrice + .max(BigDecimal.ZERO) + .setScale(0, RoundingMode.DOWN) + val tenThousandUnits = integerPrice.divideToIntegralValue(LIMIT_PRICE_TEN_THOUSAND) + val basePrice = when (strategy) { + LimitStrategy.BuyLow -> tenThousandUnits.multiply(LIMIT_PRICE_TEN_THOUSAND) + LimitStrategy.SellHigh -> { + if (integerPrice.remainder(LIMIT_PRICE_TEN_THOUSAND).compareTo(BigDecimal.ZERO) == 0) { + integerPrice + } else { + tenThousandUnits.add(BigDecimal.ONE).multiply(LIMIT_PRICE_TEN_THOUSAND) + } + } + } + return basePrice.max(LIMIT_PRICE_STEP) +} + +private fun calculateReceiveAmount( + amount: BigDecimal, + price: BigDecimal, + reversed: Boolean, +): BigDecimal { + return if (reversed) { + amount.multiply(price).setScale(8, RoundingMode.HALF_UP).stripTrailingZeros() + } else { + safeDivide(amount, price).setScale(8, RoundingMode.HALF_UP).stripTrailingZeros() + } +} + +private fun convertPayAmount( + amount: BigDecimal, + currentFromPrice: BigDecimal, + newFromPrice: BigDecimal, +): BigDecimal { + if (currentFromPrice <= BigDecimal.ZERO || newFromPrice <= BigDecimal.ZERO) { + return amount + } + return amount + .multiply(currentFromPrice) + .divide(newFromPrice, 8, RoundingMode.HALF_UP) + .stripTrailingZeros() +} + +private fun TokenItem?.safePrice(): BigDecimal { + return this?.priceUsd?.toBigDecimalOrNull()?.takeIf { it > BigDecimal.ZERO } ?: BigDecimal.ONE +} + +private fun safeDivide( + dividend: BigDecimal, + divisor: BigDecimal, +): BigDecimal { + if (divisor.compareTo(BigDecimal.ZERO) == 0) { + return BigDecimal.ZERO + } + return dividend.divide(divisor, 8, RoundingMode.HALF_UP).stripTrailingZeros() +} diff --git a/app/src/main/java/one/mixin/android/ui/home/web3/trade/TradeFragment.kt b/app/src/main/java/one/mixin/android/ui/home/web3/trade/TradeFragment.kt index 17091c54ee..0b21fafacf 100644 --- a/app/src/main/java/one/mixin/android/ui/home/web3/trade/TradeFragment.kt +++ b/app/src/main/java/one/mixin/android/ui/home/web3/trade/TradeFragment.kt @@ -369,10 +369,30 @@ class TradeFragment : BaseFragment() { this@apply.hideKeyboard() navTo(OrderDetailFragment.newInstance(orderId), OrderDetailFragment.TAG) }, - onShowTradingGuide = { + onShowTradingGuide = { tabIndex -> this@apply.hideKeyboard() - PerpetualGuideBottomSheetDialogFragment.newInstance() - .show(parentFragmentManager, PerpetualGuideBottomSheetDialogFragment.TAG) + when { + walletId == null && tabIndex >= SpotTradeGuideBottomSheetDialogFragment.TAB_LIMIT -> { + PerpetualGuideBottomSheetDialogFragment.newInstance() + .show(parentFragmentManager, PerpetualGuideBottomSheetDialogFragment.TAG) + } + tabIndex == 1 -> { + SpotTradeGuideBottomSheetDialogFragment.newInstance( + SpotTradeGuideBottomSheetDialogFragment.TAB_LIMIT + ).show(parentFragmentManager, SpotTradeGuideBottomSheetDialogFragment.TAG) + } + tabIndex == 0 -> { + SpotTradeGuideBottomSheetDialogFragment.newInstance( + SpotTradeGuideBottomSheetDialogFragment.TAB_SWAP + ).show(parentFragmentManager, SpotTradeGuideBottomSheetDialogFragment.TAG) + } + else -> { + SpotTradeGuideBottomSheetDialogFragment.newInstance( + SpotTradeGuideBottomSheetDialogFragment.TAB_OVERVIEW + ) + .show(parentFragmentManager, SpotTradeGuideBottomSheetDialogFragment.TAG) + } + } }, pop = { navigateUp(navController) diff --git a/app/src/main/java/one/mixin/android/ui/home/web3/trade/TradePage.kt b/app/src/main/java/one/mixin/android/ui/home/web3/trade/TradePage.kt index 7bc49ba2c6..4e0ce666b4 100644 --- a/app/src/main/java/one/mixin/android/ui/home/web3/trade/TradePage.kt +++ b/app/src/main/java/one/mixin/android/ui/home/web3/trade/TradePage.kt @@ -98,7 +98,7 @@ fun TradePage( onSwitchToLimitOrder: (String, SwapToken, SwapToken) -> Unit, pop: () -> Unit, onLimitOrderClick: (String) -> Unit, - onShowTradingGuide: () -> Unit, + onShowTradingGuide: (Int) -> Unit, onShowMarketList: (Boolean) -> Unit, onShowAllMarkets: () -> Unit, onShowAllOpenPositions: () -> Unit, @@ -195,7 +195,7 @@ fun TradePage( perpetualTabIndex = tabs.size tabs += TabItem(title = stringResource(R.string.Perpetual)) { PerpetualContent( - onShowTradingGuide = onShowTradingGuide, + onShowTradingGuide = { onShowTradingGuide(perpetualTabIndex ?: 0) }, onShowMarketList = onShowMarketList, onShowAllMarkets = onShowAllMarkets, onShowAllOpenPositions = onShowAllOpenPositions, @@ -232,7 +232,6 @@ fun TradePage( sheetBackgroundColor = MixinAppTheme.colors.background, sheetContent = { HelpBottomSheetContent( - hideGuide = perpetualTabIndex == null || pagerState.currentPage != perpetualTabIndex, onContactSupport = { coroutineScope.launch { bottomSheetState.hide() @@ -242,7 +241,7 @@ fun TradePage( onTradingGuide = { coroutineScope.launch { bottomSheetState.hide() - onShowTradingGuide() + onShowTradingGuide(pagerState.currentPage) } }, onDismiss = { @@ -396,7 +395,7 @@ fun TradePage( } if (isPerpetualTab && !isPerpetualTabBadgeDismissed) { onDismissPerpetualTabBadge() - onShowTradingGuide() + onShowTradingGuide(index) } onTabChanged(index) }, diff --git a/app/src/main/java/one/mixin/android/ui/wallet/AddWalletBottomSheetDialogFragment.kt b/app/src/main/java/one/mixin/android/ui/wallet/AddWalletBottomSheetDialogFragment.kt index 0a7657a5c5..2d246e81e8 100644 --- a/app/src/main/java/one/mixin/android/ui/wallet/AddWalletBottomSheetDialogFragment.kt +++ b/app/src/main/java/one/mixin/android/ui/wallet/AddWalletBottomSheetDialogFragment.kt @@ -3,7 +3,12 @@ package one.mixin.android.ui.wallet import android.annotation.SuppressLint import android.app.Dialog import android.view.LayoutInflater +import android.view.View +import one.mixin.android.R import one.mixin.android.databinding.FragmentAddWalletBottomSheetBinding +import one.mixin.android.extension.defaultSharedPreferences +import one.mixin.android.extension.openUrl +import one.mixin.android.extension.putBoolean import one.mixin.android.ui.common.MixinBottomSheetDialogFragment import one.mixin.android.widget.BottomSheet @@ -17,6 +22,7 @@ class AddWalletBottomSheetDialogFragment : MixinBottomSheetDialogFragment() { companion object { const val TAG = "AddWalletBottomSheetDialogFragment" + private const val PREF_FREE_TRANSFER_CARD_DISMISSED = "pref_free_transfer_card_dismissed" fun newInstance() = AddWalletBottomSheetDialogFragment() } @@ -34,6 +40,11 @@ class AddWalletBottomSheetDialogFragment : MixinBottomSheetDialogFragment() { setCustomView(contentView) } binding.apply { + val openFreeTransferDoc = { + requireContext().openUrl(getString(R.string.url_cross_wallet_transaction_free)) + } + val preferences = requireContext().defaultSharedPreferences + rightIv.setOnClickListener { dismiss() } addWatchAddress.setOnClickListener { callback?.invoke(Action.ADD_WATCH_ADDRESS) @@ -51,7 +62,30 @@ class AddWalletBottomSheetDialogFragment : MixinBottomSheetDialogFragment() { callback?.invoke(Action.CREATE_WALLET) dismiss() } + freeTransferCard.visibility = if (preferences.getBoolean(PREF_FREE_TRANSFER_CARD_DISMISSED, false)) { + View.GONE + } else { + View.VISIBLE + } + freeTransferCloseIv.setOnClickListener { + preferences.putBoolean(PREF_FREE_TRANSFER_CARD_DISMISSED, true) + freeTransferCard.visibility = View.GONE + } + bindOptionalView("free_transfer_card") { + openFreeTransferDoc() + } + bindOptionalView("free_transfer_learn_more") { + openFreeTransferDoc() + } } } -} + private fun bindOptionalView( + idName: String, + onClick: () -> Unit, + ) { + val id = binding.root.resources.getIdentifier(idName, "id", requireContext().packageName) + if (id == 0) return + binding.root.findViewById(id)?.setOnClickListener { onClick() } + } +} diff --git a/app/src/main/java/one/mixin/android/ui/wallet/components/AssetDashboardScreen.kt b/app/src/main/java/one/mixin/android/ui/wallet/components/AssetDashboardScreen.kt index 876ad33672..2280d32336 100644 --- a/app/src/main/java/one/mixin/android/ui/wallet/components/AssetDashboardScreen.kt +++ b/app/src/main/java/one/mixin/android/ui/wallet/components/AssetDashboardScreen.kt @@ -82,6 +82,13 @@ fun AssetDashboardScreen( val context = LocalContext.current val safeCreateGuidelineUrl: String = stringResource(R.string.safe_create_guideline_url) val safeLearnMoreUrl: String = stringResource(R.string.safe_learn_more_url) + val importWalletTitle = context.stringByName("import_wallet_title") + val importWalletDescription = context.stringByName("import_wallet_empty_description") + val importWalletGuideUrl = context.stringByName("import_wallet_guide_url") + val watchWalletDescription = context.stringByName("watch_wallet_empty_description") + val watchWalletGuideUrl = context.stringByName("watch_wallet_guide_url") + val importWalletIconRes = context.drawableByName("ic_import_wallet") + val watchWalletIconRes = context.drawableByName("ic_add_watch_wallet") val prefs = remember { context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE) } val hidePrivacyWalletInfo = remember { mutableStateOf(prefs.getBoolean(KEY_HIDE_PRIVACY_WALLET_INFO, false)) } val hideCommonWalletInfo = remember { mutableStateOf(prefs.getBoolean(KEY_HIDE_COMMON_WALLET_INFO, false)) } @@ -164,17 +171,27 @@ fun AssetDashboardScreen( .align(Alignment.TopEnd) .size(8.dp) .background(color = Color.Red, shape = CircleShape) - ) + ) } } } - val hasImported = wallets.any { it.isImported() } - val hasWatch = wallets.any { it.isWatch() } + val hasImportedWallets = wallets.any { it.isImported() } + val hasWatchWallets = wallets.any { it.isWatch() } + val hasSafeWallets = wallets.any { it.category == WalletCategory.MIXIN_SAFE.value } + val filteredWallets = remember(wallets, selectedCategory) { + wallets.filter { wallet -> + shouldShowWallet( + wallet = wallet, + selectedCategory = selectedCategory, + ) + } + } + val showTotalAssetsCard = selectedCategory == null || filteredWallets.isNotEmpty() WalletCategoryFilter( selectedCategory = selectedCategory, - hasImported = hasImported, - hasWatch = hasWatch, + hasImported = true, + hasWatch = true, hasSafe = true, showSafeBadge = !hasSeenSafeCategoryBadge.value, onCategorySelected = { @@ -199,8 +216,38 @@ fun AssetDashboardScreen( .fillMaxSize() .verticalScroll(rememberScrollState()) ) { - TotalAssetsCard(selectedCategory = selectedCategory) - Spacer(modifier = Modifier.height(20.dp)) + if (showTotalAssetsCard) { + TotalAssetsCard(selectedCategory = selectedCategory) + Spacer(modifier = Modifier.height(20.dp)) + } + + if (selectedCategory == "import" && !hasImportedWallets) { + WalletEmptyGuideCard( + title = importWalletTitle, + description = importWalletDescription, + iconRes = importWalletIconRes, + primaryActionText = stringResource(R.string.Import), + onPrimaryActionClick = onAddWalletClick, + onLearnMoreClick = { + context.openUrl(importWalletGuideUrl) + }, + ) + Spacer(modifier = Modifier.height(10.dp)) + } + + if (selectedCategory == "watch" && !hasWatchWallets) { + WalletEmptyGuideCard( + title = stringResource(R.string.add_watch_address), + description = watchWalletDescription, + iconRes = watchWalletIconRes, + primaryActionText = stringResource(R.string.Add), + onPrimaryActionClick = onAddWalletClick, + onLearnMoreClick = { + context.openUrl(watchWalletGuideUrl) + }, + ) + Spacer(modifier = Modifier.height(10.dp)) + } // Privacy wallet - always show if no filter or "all" selected if (selectedCategory == null) { @@ -211,7 +258,7 @@ fun AssetDashboardScreen( Spacer(modifier = Modifier.height(10.dp)) } - if (selectedCategory == WalletCategory.MIXIN_SAFE.value && wallets.any { it.category == WalletCategory.MIXIN_SAFE.value }.not()) { + if (selectedCategory == WalletCategory.MIXIN_SAFE.value && !hasSafeWallets) { if (Session.getAccount()?.membership?.isMembership() == true) { CreateSafeCard( onCreateClick = { @@ -231,18 +278,7 @@ fun AssetDashboardScreen( Spacer(modifier = Modifier.height(10.dp)) } - wallets.forEach { wallet -> - val shouldShow = when (selectedCategory) { - null -> wallet.category != WalletCategory.MIXIN_SAFE.value && wallet.category != WalletCategory.WATCH_ADDRESS.value // Exclude safe, watching wallets when no category filter is selected - WalletCategory.MIXIN_SAFE.value -> wallet.category == WalletCategory.MIXIN_SAFE.value - WalletCategory.CLASSIC.value -> wallet.category == WalletCategory.CLASSIC.value - "import" -> wallet.isImported() - "watch" -> wallet.isWatch() - else -> true - } - - if (!shouldShow) return@forEach - + filteredWallets.forEach { wallet -> if (wallet.category == WalletCategory.MIXIN_SAFE.value) { WalletCard( name = wallet.name, @@ -298,6 +334,30 @@ fun AssetDashboardScreen( } } +private fun shouldShowWallet( + wallet: one.mixin.android.db.web3.vo.WalletItem, + selectedCategory: String?, +): Boolean { + return when (selectedCategory) { + null -> wallet.category != WalletCategory.MIXIN_SAFE.value && wallet.category != WalletCategory.WATCH_ADDRESS.value + WalletCategory.MIXIN_SAFE.value -> wallet.category == WalletCategory.MIXIN_SAFE.value + WalletCategory.CLASSIC.value -> wallet.category == WalletCategory.CLASSIC.value + "import" -> wallet.isImported() + "watch" -> wallet.isWatch() + else -> true + } +} + +private fun Context.stringByName(name: String): String { + val resourceId = resources.getIdentifier(name, "string", packageName) + return if (resourceId != 0) getString(resourceId) else "" +} + +private fun Context.drawableByName(name: String): Int? { + return resources.getIdentifier(name, "drawable", packageName) + .takeIf { it != 0 } +} + @OptIn(ExperimentalFoundationApi::class) @Composable fun WalletInfoCard( @@ -384,6 +444,115 @@ fun WalletInfoCard( } } +@Composable +fun WalletEmptyGuideCard( + title: String, + description: String, + iconRes: Int?, + primaryActionText: String, + onPrimaryActionClick: () -> Unit, + onLearnMoreClick: () -> Unit, +) { + Column( + modifier = Modifier + .fillMaxWidth() + .cardBackground(MixinAppTheme.colors.background, MixinAppTheme.colors.borderColor) + .padding(16.dp) + ) { + Row(verticalAlignment = Alignment.CenterVertically) { + Column(modifier = Modifier.weight(1f)) { + Text( + text = title, + fontSize = 16.sp, + fontWeight = FontWeight.Bold, + color = MixinAppTheme.colors.textPrimary, + ) + + Spacer(modifier = Modifier.height(12.dp)) + + Text( + text = description, + fontSize = 14.sp, + lineHeight = 17.5.sp, + color = MixinAppTheme.colors.textMinor, + ) + } + + Spacer(modifier = Modifier.width(8.dp)) + + iconRes?.let { icon -> + Image( + painter = painterResource(id = icon), + contentDescription = null, + modifier = Modifier.size(48.dp) + ) + } + } + Spacer(modifier = Modifier.height(20.dp)) + + Row( + modifier = Modifier + .fillMaxWidth() + .background( + color = MixinAppTheme.colors.backgroundWindow, + shape = RoundedCornerShape(16.dp) + ), + ) { + Box( + modifier = Modifier + .weight(1f) + .clickable { onPrimaryActionClick() } + .padding(6.dp), + contentAlignment = Alignment.Center + ) { + Text( + text = primaryActionText, + fontSize = 14.sp, + fontWeight = FontWeight.SemiBold, + color = MixinAppTheme.colors.accent, + modifier = Modifier + .padding(6.dp) + .clip(RoundedCornerShape(bottomStart = 16.dp, topStart = 16.dp)) + ) + } + Spacer( + modifier = Modifier + .width(2.dp) + .height(24.dp) + .background(Color.Black.copy(alpha = 0.05f)) + .align(Alignment.CenterVertically) + ) + + Box( + modifier = Modifier + .weight(1f) + .clickable { onLearnMoreClick() } + .padding(6.dp), + contentAlignment = Alignment.Center + ) { + Text( + text = stringResource(R.string.Learn_More), + fontSize = 14.sp, + fontWeight = FontWeight.SemiBold, + color = MixinAppTheme.colors.textPrimary, + modifier = Modifier + .padding(6.dp) + .clip(RoundedCornerShape(topEnd = 16.dp, bottomEnd = 16.dp)) + ) + + Icon( + painter = painterResource(id = R.drawable.ic_arrow_top_right_small), + contentDescription = null, + tint = MixinAppTheme.colors.backgroundDark, + modifier = Modifier + .size(8.dp) + .align(Alignment.TopEnd) + ) + } + } + } +} + @Composable fun PrivacyWalletInfo( onLearnMoreClick: () -> Unit, @@ -773,4 +942,4 @@ fun CardPreview() { Spacer(modifier = Modifier.height(8.dp)) UpgradeSafeCard({}, {}) } -} \ No newline at end of file +} diff --git a/app/src/main/java/one/mixin/android/ui/wallet/components/WalletCategoryFilter.kt b/app/src/main/java/one/mixin/android/ui/wallet/components/WalletCategoryFilter.kt index 116241e003..4ada5b9019 100644 --- a/app/src/main/java/one/mixin/android/ui/wallet/components/WalletCategoryFilter.kt +++ b/app/src/main/java/one/mixin/android/ui/wallet/components/WalletCategoryFilter.kt @@ -28,9 +28,6 @@ fun WalletCategoryFilter( if (hasAll) { tabs.add(WalletCategoryTab(category = null, textResId = R.string.All, showBadge = false)) } - if (hasSafe) { - tabs.add(WalletCategoryTab(category = WalletCategory.MIXIN_SAFE.value, textResId = R.string.Wallet_Safe, showBadge = showSafeBadge)) - } if (hasCreated) { tabs.add(WalletCategoryTab(category = WalletCategory.CLASSIC.value, textResId = R.string.Wallet_Created, showBadge = false)) } @@ -40,6 +37,9 @@ fun WalletCategoryFilter( if (hasWatch) { tabs.add(WalletCategoryTab(category = "watch", textResId = R.string.Wallet_Watching, showBadge = false)) } + if (hasSafe) { + tabs.add(WalletCategoryTab(category = WalletCategory.MIXIN_SAFE.value, textResId = R.string.Wallet_Safe, showBadge = showSafeBadge)) + } val selectedIndex: Int = tabs.indexOfFirst { tab: WalletCategoryTab -> tab.category == selectedCategory }.let { index: Int -> if (index >= 0) index else 0 } diff --git a/app/src/main/res/drawable-xxhdpi/mixin_import_safety_preview_0.png b/app/src/main/res/drawable-xxhdpi/mixin_import_safety_preview_0.png new file mode 100644 index 0000000000000000000000000000000000000000..7ea990e2431ec8a88e6396f11983b314d0b443f9 GIT binary patch literal 2151 zcmcJQ=_3;i9LJ|zIkGUfNRx_Iqn_nh06^ql|5yJjf&cV>H?oY#h8hqrDL$Yi-XSN(Q9g{9 z6f#IfyDPu*#jTp=L6u=KA`u9Ar@(E58xNt5|9$z`LQw# zJ1NmI!&dv2r6k+^C>8#`#%_1)lb_S&bMf94k)fsq)Tgc%m>9bit)9B(W9-bNogp{) z+@+ge0RW*RR}hySd?vr=vM{4YK$Ae7SYrIWsanz6G{*D@Jt)5&iR;o^U90W^CrRa$bZ@pK81Uf}2CSPQ=i7S%~d(_2Oi0 z#@EzFPGZN^?i|Dfxyp@-#V*XoNsX#ES8WtmOruB#%4AWVUFd*a;bjfv~k3pPEf!Ksw^lm;~J5{e|8=C?Ujk|YhmBY zPu1q_jM5~|+G;Akka@^iOoin31z&}nn{vcxrM~*XJ?SY|C1$PcpM84KZ|({@WBUTG zOimYEqyaL_yz%-oHI#MD%@yAek3~R=G^h+z!zmk)wgd1quISDo(&$6QN|*A~KZRlA zFA);`mQA$Kz6Bq$K+EgG;Jc?pojcebpH|Pu3>9%AN7Dv8o!bS|lPNStZ>WKtla&(J zs?EOtTR}XPgOacFd~S5=tiwmN1fX#!iF zR5xkUjc$yq8$?=V?^~HSWn^WO0 zhD6{Wi@tZz!}}cMIV{L=WqgUd5tcW*bOX)tY0C1VOZoq9>8k0?V0N(^uxXxyCt1DS z?u*f|czc1ErQd>IB?PEh>+LCTXD%%~N=lc40uv1*Et;DB>U~L*ib79?fnrO04kNTu zf`XB;`M7M&!blH(*C^-|w?)ncyylq@rtj`db?)EpQrnIt$+r=18Q z1MR%1VCv)5X!kZ|%G*HlPul%2#(GamH(hT${`B6S;)W_En2?d3)f*cV!8`9{l5;k8 z2EJjxwnDN^3=I1nKbt)no2efTNPqKtu5P{HA^qU_)h7m#NzClw%TpK6@SruhTIzPs z15wc#PUVyz(-RFu-2~luc-K}YPQ#8kdkvIW!jrC1th)$X?aBVKdUpqM!eeRjnMzq^ zQ**O*V4}id$!prvFuN^Yc11_4n!l7ZgjqFGvZ;CUXxLN?LX*oKmrxhJz}wohmv z!Zmune)mi0@*l_!%@)!bwmQM^p~~T+wnu)&$62xg3oY<_3CsSY+FjG!$8sbLt>|e^ z%-C~-rg~IaXkQAobjpA`Z;<@pR@ha&`m-7KC{lRCx@tH*z{=o9UQc+V%OKE3mM!UG zZrdX{90KaNm>||KIq8H$>aO2PT0v@)Jbh&Y;B_%e8t0_nE{iLX@u*Q)ckUe9Qg01Vj$9PLeffdc}Uc<#BD9 z#qmvXXk?B-PS&lX{8^Q@*DczHW{trq7_BWe8O1umQE;>+J^ZPSo@(^z z(qfrzQ#*6#6RLb^sd$wU^B0y|+U}}4g0gjq1Ky<|3$O+;xt|WU0_905bylr7JDkeW zKL!uVr+|Z>8GDY(5|04QlPntFBhXrCzA<1ZwlGBe#;yjZ@nw3OLY6XpCf!@H?d5yg!D zkZ|%V2bZ^*3R*atywb8@w(k9E0ciLENX}oYEGASH^p;9(GXUvbh1CpQN~JJ9@LhZ`Z}78zcv?Ek z?czoV>tiY5;PUgHkvIOJvfzYGrtp3O}@S1<+2%tJ5x=(~5)<52E$5rdWA%KCiA zJN=$6FN>nT>Wx+(p?DWvk6HJKkqDB2#aZfz-L4IR`n{yGH!F0wh3jFrr$gRqC|;waCf0ua|0^ce J2#T>=>_3P^;JE+* literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/mixin_import_safety_preview_1.png b/app/src/main/res/drawable-xxhdpi/mixin_import_safety_preview_1.png new file mode 100644 index 0000000000000000000000000000000000000000..0209924fb00fa7d17d28885ad8ba585a41869221 GIT binary patch literal 1670 zcmchY`#%#30L8aP&F(eA3Tq=WHMNRzKZS0~mRFm3C2c069yega3mmDv(i(b?1l04P`6;jpd&Z|Ba8*0J=}m6caL-%yT#N^y?x@uWIn612p{xRKMvs))nm-%t*68veERce2 zl-dvG7)ptl(sxan%5_Uf!e zvs_)3(fJjlS!o04{Di3Dj0m8PprtS{FwSKsQPM zXb~OVXL+FN3j< zp7nQ?jb)!u3DF6q45hC>%f}5DCsA6#fo=%NlPSIAcqNa}gD`9T(KwR>R8DDOs_k*( zqb3u1jYH^J?=dx;V3={yT;KqWn;fW|NdwyyCS>KP;F*bGa4**s#1U#|K^*$Y;2UZZCApCLCH`bSDeJ8a>TGba9qDVaKVFt zJw&^}wc1TuS?4Nwxof6aO_fnN0`DjEhD+*6OK<`*0necPIG@!qrV=n^)#YWiSBBw* z>)s>5*>jdxcl=JsL9D$n3FI0fFV^GH+ZW0lVpSQdpX+#qeQ5=(m2F9K`2GRd%;Mc@ z>hNoBu_OK(nx)=l)ar0rR$jXhhOZ>ABnQ{)sf`Qv@ONK58Kj8zTgKdQ$Y)^sGA$> z-tbK?x6BvpMn{MxjyfTZ$?SEx#z+nNeUH#MI-U5uI&JOxL{;K?K=A8$|F1s@(>4Y@ zc3YomiB{l*k?c`>{@a*uG~aK8_DM0-Pp7U{;*PntzRUNq+dScsvw78ijnml0VaPJ^ zVe5nht@O-4aJJdE5RL+U;>yxyt08G_^%AqTvLjAfk&^%RR=`vM{sgw26+Px#gQtGI Ni5;GZt3Giq?q3jp`IGj8=QrkV8?s@KCxbO3Pzn>q!zkPE2Pk3qV0_{>#QquDAh6ipj z{(IH7Z!Mc$ySar#e^fAHi~naXec&Ru{>-lio(NLfv7u1Yud@$5{sj+S7Q^;V59As> zDlgKVg9d0P{KV%X(xZm0Qou?|JMQ|xJ%VE<7wE4G$Lur_Rp^Fw+fdQh&BW*XTLYbJ z`ipAIQQ5dxaX&&BN@q|YDv~)A)CF%K`B~v5WMf zJ+CGYXBMVs8EAv{g{o!`&kIq8YfBw7mCL+>wC^vb6yfI1$kEnxua<;@*&Es7G$@UCVNG`Nco0PXjn4U!bPCa)IP% z$ukOT6XtS9)gdJ^UEkNNw1TiQO4S}pdalur+8pIMa~N=Kkj9%>=xTYK_Z4D52>QA) z$rU$n)-mJB$?RxRbhX;)d=Tqqyamm{FS-|tnM$})@mB7^$y1-r=jGHB_s%Hx9t)k_ z?j%xULyY&p=H%4c*k=RewBPx9vl5G%qYS)FE>>P{WrYP@UQ+xgJHRbM<`n=L_$ z-4qa{IQiXl&|sU%A;lX#m!uH4vLf>d-Nh{ZF(~fhdmb+(D$eAIZ1n2OREpn4*rKne zz^PMW4v#{Z#zL)96)Q}b6m|LML%5Q|_p{$fYeYquXF-88jNx{jvl;nAra=S zZ$3EMg$30bbX)%Lz|7Gc-}#!L9A)hZ4G9lof_f*Gza%tGjZ+0B@l6=+I2D|>pr1%? zH|lKWo%d>Aqc3!PW~{zidJ#6Yq<*jJV4cb>k7{M2c4L4mQYN{d`Y{ZASW_m=Dn%ab zC(s4!kTK2BYp-sot*bn?{g7c!0!B{OCDE=8eU|Uy_4+S;yvyL=P1Xmc>BLXMheE|< zVRn9;fLG$a(#{AUZ5F0bi3KJsFuRl>*J%6Pv$H#JRv?KEr{e*6D7t@e@%&3K;2=_g z2W0&AX=4T*rx%WJanElUqr7Ba5k%Bq_JKQdtM`YooHdk_Od}ofibh+a2O!0;ESVEu z&0*D64(Zl2=pBGdo+B;m6?<+Q_4AQs+@?}C`CCdbEnX@v)zXD+vxLHIaR4owyxVT| z#fGbm82q%8r-4&X$)+@m zK0cf@PPv6U$(dZ<$P~>2)a|h?y=QhnHr8_IZRv$wq?Trn`cr%ZZ*ey%Cs}+PHL`8i zz?Gt9=x{Svmde?1k@pBm5THeu=C2NtZTvoZ#ToRzTp0Qs-0=@Lg_i1!G~<%Z8XG{4 zOpAhNNxB=l=j4B7A1OP|>07?>biPZ74(Oupx5{LMd*q4zyRoV6`iljLn9ga$kxFZN z!FZtxqi>wGyCxQ~*eGCDK?0IH+<5!Y{-@kBBA`$CHtA*@?IU(Oo!<@iO_h1S{~8n_ z?P|MKoE`I6&$j$9&P0Oca8cWPQV`=yO8k@smXgW literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/mixin_import_safety_preview_3.png b/app/src/main/res/drawable-xxhdpi/mixin_import_safety_preview_3.png new file mode 100644 index 0000000000000000000000000000000000000000..b45f81652841a00cb3c22e3a51e2a939f3231fae GIT binary patch literal 1587 zcmZXUc{CJy6vux`Su)|_k%?w3V`MBtNY+PY%-;g2H;1pjq^Ru)hd1zVUv_2?2faZT|NkBFP$SeSLK8yp>Y@kX7@D89j1rQXVkOE}*JRHdKyB5LjuR#3; zP`U>c2LZeh?BxHX3PAalG@xP)L~Vc=|Ir4ZPzj*?F8(MUK&^n?9DrB^$S*+6oNnd9 zPdE{r?Onje`j#~g+elJu3C$JWn9E>TrCNtOQy=JW+(998d_ClcyXsCWEsdSqwN5Kh zTOMsa9_S#>G~L;jb2-l5Salow*`wm*c;AP0H^TK$(_IgC%&9E_Aee#2>~{@j%;pEV z&@Cj=MlJKNtDt1t4JXij(okF(!XiE@7sBI}vL~hTA)AZXR>yU(CitgTbkLW{!Q#$H zX;GqOnd1-nzN@|PkCpE%*7a&^V#e&M4t)~V{g++ho*-^SX$V%?bl^C8RcTCKo2Q^_ zDQucVZN1$d9#0CP3CZ@)Q~bT-GY%B=%~}?p&y=&pN{x+Ab?=$&{c~BC9HOOF;5E4I zE{>LD!m(dNpq5$ixkQ1;rGZGi}{?fzM_bP8IMoJAE|q92EMw5 zo`7yGi-c(j3Y~tM9FywtI8>;S`T2R6RRiaUn#rMIVqksdWOv?ex9oalS_f|cpVW@} zfU>!zNw~58-keCzA*H8F&f=PVA!|51LwXAKrKPj%gVdBSO?R(Ci=A@mIknP(J8rND{-i7Qw}5O?obmcu6Hdn$NT` z6?%N7@?hOs$I3ht$6C4svePu2#j?Z$d`RvTlcpLwu9kzX5bcd$y3dvX*2R*D&=SFx z@*{e9N|86|WmXRpU)UX}(5I()C8p+3?dzku#?wc%bwtr2kAN<mur5BWR4hDb;5ajApWV2*hL?C7q^u8y=dB{ zNG`|q>gZjMAfJJ8?B}ezZEV6xrc^=Kd5gf-kE@Nn29!wZD>v#C_I*@UV@$RU-K&n7 z{>*PDTu4%K(OQS6kN&S9co~WNhtfh2hXD*;BETX@7R%gsZ{q1`)LwrvIeJ zo4qdt;@JwXg&y@pS(nL;<(Lv_!VT)N!u~hDsBMt$!kQiJ3;;7Ih*+&Wx~Aw92L$_U__I)q=_qolsP9&j z6EKFQG&N+!n?do=U<3+&c`?4$s>xmrm?kqtU)A};HSz@! L)*jPf>7V>JZwa5A literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/mixin_import_safety_preview_4.png b/app/src/main/res/drawable-xxhdpi/mixin_import_safety_preview_4.png new file mode 100644 index 0000000000000000000000000000000000000000..d7a92a3b55b632c848d0813100ca8750d311d042 GIT binary patch literal 1518 zcmZvce>~H99LK-=!Ini^&6;1EP=v5{;nI)M7SnFZ=u{gsqVlVgIXPl-!l=U;xztM5 zj}>DyB9j(HR70CQ9!Nqx67r)+>voUs`s40ikH_cpetn*g*X#Y~Cu=v|Q(w=`QX6=7!0O0{}l)X(n2s8{MGy`pyl64e}Dfs z4~N75H?IwRR~LmseQjwugTa76AW$e&OR}@Gc|4xhBqk<$dU^^30!wX(9RtE-ERjm2OvTeohlsj1Ocq*AF;scdU&tFErDt*vco zY3bi_si}#@V%68zYd2_jmX(#cySp2O9CFf@$#K4P2B6e|t@`MS zTqIg(5ZkNvIZoE|aD8pL8ezDN!RSzPL1r>{=e464jYjjd#7M*OrD-%X8qMdWvF=s{ zCz!F+Au8}cnBM^a&^=VLyWgRK33Xp$2??9}LJmnYx3PWq$+)PvociI;9E^Es-47+7 z0Wb7nIqNj;FD@ZxZ?x&7aIWX8KkCVAuTT|CmZZI(C!oU}AAmwZ^3k)RvD z(i(1x_+*1JYE*3pqX|SX(cmy8VIiG%qey2zPvW@T>hz#@gkp^X}et& zY#vuq#Juv#(Z3PzSWz>rZh`mOsX?F+LWD8@K1X(^A#Qf=m^>x+pf4t@trsrIXNCc( zSr(jl>rtV-4xt5aFd^8{C(Uk1w7NuEanz6sU8!4@-!!g?LMz)bj z1BQIX-jfXt{eSScXKa%@uW`|x^SJorFmRQPNir^|Q$E(pJ8-|7^_ZbliKKXwsd;UTeaC&yGGoI}lBT z4ri{H2Z+KGg1f#8qpZGlBKaJBb#4aofc`S584%y=AA?79d^e^$Kn$fDMfq#i{o>Q^eHy`AV=|VZj+J=s9Zo%qGF&3>M;Zgfa}|{92nD zfBr_8ZLi+s>WIpm*V1C~=CB(i?OtE6 zD9>=55D~M#p|HNwXN!{{u+JJti1v(3pIqLVThGCX_GFlK+?>4z3sc>6G0siXYuNjf z&xm;IK&sy~r=ni+*u{?RhdkH>9q``G69WkiY>8b9M}2 z0WbERdXE~C6kVvR^45JcFDxC|G_g!TTdCd!MK?bF@=L!UH;f&G+FkbZVx$M5zv8aO zqS^1q$+@g7_f_4JvEjJ0OB$~iYufz=k3}&9Q@}=IrDg;8Eixs}Ngrm*^A}u)U0)vj cj~;(m-Ni095ViE})_zt1l|m<1c(Bv{1gcGoRsaA1 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/mixin_import_safety_preview_5.png b/app/src/main/res/drawable-xxhdpi/mixin_import_safety_preview_5.png new file mode 100644 index 0000000000000000000000000000000000000000..bd449738e3213febf3a0e06de04e471b03f7b993 GIT binary patch literal 1156 zcmeAS@N?(olHy`uVBq!ia0vp^IUvlz3?z5#SpFYKr3LtexB_ViU<3mo8^QpRa5fVY z6NC#90dgTC5E7yu$Og(nNVqbf6huwlL!+HQQN@xVzhDLyjypfqzPvvtbKSM;k(KbC z@c3hMUu|68!ad1Y$G@g1gS&Bq2Ll818c!F;kc@k8uHEiiY#_jL!N_7;W5IDnl_x>} z|4)`~G@W>YZ%xX359LBR$D&}~vgW&a+i$-u+a15y=Y#X4cEz)g*Tb?vgohk4B z$KLr`cF;bjofTJV%@iyeH)X1>TvgQ&d`;c;)VG-m8-zV?i(Y&r*`=qR$>Job*l=$i z|Buy&ZLbNkxUpQ=z{s-gO3s7zi;vlFc=O==1YWbNDctotmmFF)=gdR@*~aoW3X=0p z_Ha&hKE?4miEpEjV^?g&u~kjFpA?Sjw5`q1T)%;_>tI%n>+VYow+_YYD7yslt=|2( z%j4OH>zi2KWk;8#a7{7!aJ`tN<jM9_l}4U42n2jY03s zgY$cPj+f_pElcp2slc#WY0r!NFl*o3z9p_4w%wKMR+{{qQnV`QLyJJ(o`k+-PuP!Y zO6cuTY;nvwbJ5DhyK}bAH0Gui5j$9gbtKb@#kE^L#JxQB>hrY5g8B6dvzAI9fBfme zob@VOIG&!o_x63+&*y=@2d(THH(YGt{*+cZqgk`%@`lzKizF@H2fHpv?QJMZi&y^X zyK&BLY2_u%Q)leoClcRh^T>4qJBvy|Nw3M72R!w+jHP2;6BW45Gn|?qz{0{3c>eeP zC)4<1roJ(|?(*%K+Fz#({-ZTBC%Q;8%$uh4_R0BMH6_1%4lhnM&AaTx9v>rrgxf&K zeyW)AoET-Me!FvgQHM(+xikY_d2+pZ@z{WO^4Y~)i+32yZpjw-zC715pl+-FtU2Wh z;@8!-UcF>*>)Vm_U{kAi&PTa@LBW}vWv7nYiCuRKIdDPo?8WB}uCsTYx&B1?GJC=D z-ozk5rWeXVRyqE&7!$qzv#Ra7cj94zMV&zZ9#*3dbI#cuR~7cIisbk6y*_)yd`5#m4U znwp}+BL!hCY)p>)Ka)MRo_92^Bo^SVwzYCJ^pV^TU3wfOpq9H~IMl-3jc)PgfJmgv5u!cbVjN%;cMF*u?wRJu!7nQIY+5 zNN~8gh81aQmvVd8?5(^PC0n_szk=mD*1j;QXklur@wpg6(KS_RWsg6nskF(v&(SMI zUSbP{tN~UKNofjfqRuLZ9s~&yNz7jp&zTlAFx1H&hFsb_RojJ79*e&2;RO()DFX6| zA3a^wsB=MaX-WE>)F*R9hv|U+GAK}G2h>Ayq0gQb^;S(^%a{+iqONdBU2{E2S2N`G zu7sbx#^&s!?(R)-^U5fxbw_ksL$wtMFX4|9E-8>uUWTp~U{0ZEM47Dx^ix^e6*a=c zZu>;3E3;*XB0|uZvCIHL8JmGIEdsBlqqob{3j%av+IE%CASfbs&7myaped1)nx-TJ zgL=J#FhV9_jw3R<8u8l0>k_dE2R=1|)2WQfH4usnJG3J}ZydbxPcH`MPbReaZD;r}k zIFG}eF1i#ZCYVdq^t6~ZNw1_QGI+T|98(t_wJ?R4u~fY~!13BD55*_xa*R$JY;F2! zBS$k?eJR|%Ll6B}WM2D<-I<-P@@B#3KJ>9Ab5XK4(}E}I%*+DG(WA);6^eZ-JYj-ymc?4t>#}_sNx^AR$(AU%q4`ZXLiTJU!2(PIqoog0 z#I|bG!Q5?18S1J;pQL2lk{-CS-JxiW-N*;gSiaX=Duw-TgOH&!&AIN87=Jdp0IUX>ag&1UmwRdt=V|iaQ)7=t0%T22Q#FC z<*JD$sve^Lqv$HPh7P<@_Bnq_`WH1Sh8z2#g@eW8dsGAFhPxT+{S@&ID$7c*eC7*H zb@DP8=&Strw>tF}jKL?bh7A@HIc%c`h`#1#Z5A=WhyN}UM*5HP>62A7@{mvV?6DvB NjWMxAl^Wrq{sjV(sGa}- literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/mixin_import_safety_preview_7.png b/app/src/main/res/drawable-xxhdpi/mixin_import_safety_preview_7.png new file mode 100644 index 0000000000000000000000000000000000000000..cabfe9440588668f8da1a5d4c6b01de7352a3fdf GIT binary patch literal 3287 zcmb7{_dgVlAIDG0*=Mgaa#mJW(ji;+$hwR!3TG#0-i5fRoRPhCNb(_vQ|3|Gdxfl1 zM##!KOF?zUBLINW%1BS!CX{M3hr$28K5y5>A*nfM)R^z;japA!VM(F7 zEQsogK6Mz~rQH-ROMfkF66+J1Z$+m0W$|{M3zw_iFaioA~I zVcdr6PlH2UOE%6$Z%haR`H8>)q3P!y0jwj;DJ_M}4P-Z-qdtLdja^3gTzu$o98ZCd zF|W#>E6pl@!bfOF&r2-}UqLdl72IWPQ=+?4*;13_aYn{qUZs{^DyEm8Zc3 z_%ZJ-o4#~pSX#JkgmagR_-a@!Z)G0fg=XNIE2vtKrfNuT8U@;i49lJ^f8W#&4`b*$41( z58kEfDu_c&Ny%7HKyEYV8DFwjd`dNWc6sSXNwto_C~-f7;Z+$uQ*j{0$F<5ry``Ha3gaKT1T4ZGIFmrohgo-|I| zHWAYw?D}VUy?Tby8r{e}J(5|LwlWddF(>qKKJAp96@7n8mAyK%TN-haSRlw>1-1(8 z7ryDs?yPzONWSl!m+l5pi94Oi5W4bcSJj0q2?PH0ihw`gY_xWbHN-gAhVlL;P;9WF-i{(kG;i5 z^w!H&UBuSr5qk{7jZDp%YzX_Rh7OqVA2A5jZvc<=G)XMl>O zm$mWXx+hTIFy@;gb&*U`Ub~-66Vxfx^z(Jco`Mg0?8A5-_v6!d_r;4-MA*iA=NR$) zoVgIiaS{>$!^&uHRI6I-Z)i=%hijVkV<9WwV`I?y;6PR7q0fOw?sD_?+>!WzV>~eE zNstrw+kyT49lQ`zFHve2$?7K}AYa~_{ZvRr8w&N{{KwZJJIoYZwQ!e5`2$Sck899w zd2;r}xBw5yTROW5eYZ&`FVP$poa)2LUDTrN@m;^5Xo(9=R2bfrNfUCO+lyq$W+Z@) z;j1`UrN97i;H{GmQS)jYIha{2}oh={5 z5OSoqmT?wj!qCSnYh6Ipi2b@wLw2bFs=5^Gz1{PL@P@9}QXJ@J1`e%|>=czOX0(GVpDB<6 zpWMK~f%83k9z1LQ^_BNlKSt&8u68Kpsm1-tPkQB}_4KKK>VabNJoPrttzl*?b5hu# zpq8r7@JCt2-2j=&PDhBf9bDZka=yvm*^Tax6G{x?BhA*RWZh^1f|i-AKmhV1+4U*NQ69iO*V9OO(43xK)N^D8^VqSc~Y0s zN{kk~pqHv=2E$)c0N@AP#6&NRmSx+Wbl>8-Ob!yNX@tEJz7^ADe+G6I2j#Po2aMon zv+QeH0%2QIx zFFeq<$Km&zW7OD$uX)3rCL9?ZoiH<8c6u&^I?BXCO#i632y-J;Eo0A z+*p#@v%H)7C$lRy?$=P$s8x2MQ~~PE1nBXP> z8RqLE2W*it*>QI}eGbAB*7d`1DhZ_+X6Teno!F-pq4*2ofYk1JB7%mP7`UCg@-b67 znVD(s9OG+HsdMtILA$;kO`6;$_%R6CRAV5e7}Gl@n$X{TyPRxCWm zep#&W!11&ng0Xb)^gH;2WGLqGUixh(tl3s;@>%6;#0X98AUW+^u@^C=d#=Xj&vgED zSor!xD1EOQ@nxZOuZ3o3ImIgX=IWZRz55;v68<^QZ@X>ynf&swR^$rnVArrV_7X8X zyoCli_((16hKp%BYwGCgiQHS5(gfPGOn3WBJHf4E|G@BDi;^Ht_AABQ-sS9}V#(|; zeGgRy4`;-m0wgB3WaRa*^=Oou7)O+?P7?w?MJ<( z@;fF%mcK2hW?p>lH5gI42=-CmWNRk#FSH56WDuY>h0~{>$AiENTE2Wf*A8Xx;2Y@X zy@QH@ox~R;9GnCU5=xhkU~q084MY=mcEFLDlK?@dA_1U+C8^{q`zhb#P(!?mrAwpVW| zJg4x9K%8#p(W*ZSLA!->x3cZH?&zAFZ-6vE#iHs}3GfEU=!NGpdaMey+i!%3PULLm zs6X-^oG=l{b$*z3*rw-4QgloR{6R$2hx^yJa{FNS>#VOUeQmC~?|24E{7>op|Ib~K aR7<_O6+_RTc>L`h03&^Ky-FSD=>GvJ+Ecjz literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/ic_add_wallet_freee.xml b/app/src/main/res/drawable/ic_add_wallet_freee.xml new file mode 100644 index 0000000000..55ee7f67e7 --- /dev/null +++ b/app/src/main/res/drawable/ic_add_wallet_freee.xml @@ -0,0 +1,25 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/ic_add_watch_wallet.xml b/app/src/main/res/drawable/ic_add_watch_wallet.xml new file mode 100644 index 0000000000..133faa646e --- /dev/null +++ b/app/src/main/res/drawable/ic_add_watch_wallet.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_dot_assist.xml b/app/src/main/res/drawable/ic_dot_assist.xml new file mode 100644 index 0000000000..aad828fb18 --- /dev/null +++ b/app/src/main/res/drawable/ic_dot_assist.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_import_wallet.xml b/app/src/main/res/drawable/ic_import_wallet.xml new file mode 100644 index 0000000000..a84ebf7212 --- /dev/null +++ b/app/src/main/res/drawable/ic_import_wallet.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_add_wallet_bottom_sheet.xml b/app/src/main/res/layout/fragment_add_wallet_bottom_sheet.xml index c2e6c8cc87..b08bef4856 100644 --- a/app/src/main/res/layout/fragment_add_wallet_bottom_sheet.xml +++ b/app/src/main/res/layout/fragment_add_wallet_bottom_sheet.xml @@ -1,12 +1,11 @@ + android:background="@drawable/bg_upper_round"> - + android:layout_height="wrap_content" + android:fillViewport="true" + android:overScrollMode="ifContentScrolls"> - + - + - + - + - + + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - - - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 8116d4ee06..88d3610840 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -358,7 +358,7 @@ 普通钱包转账到其他普通钱包、隐私钱包、Mixin Safe 金库或联系人。 手续费按月、按实际消耗的手续费币种和金额汇总返还到您的隐私钱包,阅读文档了解更多。 了解更多 - https://support.mixin.one/zh/article/cross-wallet-transaction-fee-event-1k9b47q + https://support.mixin.one/zh/article/mixin-10yozl6/ 桌面版已登入。 请升级 Mixin 桌面端至最新版! 检测到一个 Mixin 二维码,点击识别 @@ -634,6 +634,17 @@ 上次备份 稍后 了解更多 + 导入钱包 + 使用私钥或 12/24 个助记词导入。支持 Bitcoin、Solana、Ethereum 以及其他 EVM 网络。 + https://support.mixin.one/zh/article/5aac5l2v5a85ywl5yqp6k6w6kn6zkx5yyf77yf-eri489/ + 无需私钥即可观察地址。可快速查看余额、交易记录和收款活动通知,享受原生流畅体验。 + https://support.mixin.one/zh/article/5aac5l2v5re75yqg6kec5af5zyw5z2a77yf-r3pitk/ + 钱包之间免费提现 + 限时享受钱包之间免费提现。 + 导入到 Mixin 安全吗? + Mixin 是开源的、可复现构建的,并经过独立审计。 + 您的数据会被安全加密,并仅存储在您的本地设备上。 + Mixin 绝不会访问您的隐私信息,也不会与第三方共享。 忙线未接听 对方忙 链接桌面端 @@ -2249,8 +2260,20 @@ 举例说明 开仓交易 投入资金 - 场景一:价格上涨 - 场景二:价格下跌 + 具体说明 + Mixin 交易支持简单闪兑和专业模式,简单易用,支持多链、多币种,并聚合多个 DEX 与 CEX 的深度。 + 产品特点 + 最优聚合:支持 Uniswap、1inch、Jupiter、MixPay、BigONE 等 DEX 和 CEX。 + 跨链交易:支持 Bitcoin、Ethereum、Solana 等多链资产交易。 + 补充说明 + 交易手续费主要由交易所和网络提现手续费构成,Mixin 不额外收取交易手续费。 + 交易返佣主要来自交易所的手续费分润。 + 风险提示 + 举例说明 + 价格上涨 %1$s%% → 盈利 %2$s%% (+%3$s) + 价格下跌 %1$s%% → 盈利 %2$s%% (+%3$s) + 价格下跌 %1$s%% → 亏损 -%2$s %3$s + 价格上涨 %1$s%% → 亏损 -%2$s %3$s 场景%1$d:%2$s 价格上涨 价格下跌 @@ -2273,6 +2296,30 @@ 影响盈亏变化的放大倍数 当亏损接近已投入资金时,可能被系统强制平仓。 价格剧烈波动可能会快速消耗投入资金。 + 交易币种 + 支付金额 + 兑换价格 + 交易策略 + 成交价格 + 市场价格 + 适合场景 + 报价说明 + 闪兑是一种操作简单、按当前最优市场价格立即成交的兑换方式。 + 希望立即成交,追求操作效率和确定性。 + 兑换主流币或流动性充足的交易对,市价成交通常不会产生明显价差。 + 新手友好,操作简单、直观。 + 交易报价为 DEX 和 CEX 聚合最优价。 + 交易报价已包含交易所手续费和网络提现手续费。 + 市场剧烈波动时,实际成交价格可能与预期有差异。 + 低价买入\n高价卖出 + 低价买入 + 高价卖出 + 专业模式支持限价挂单,这是一种由用户自行设定成交价格,在市场价格达到预期时才会成交的交易方式。 + 有明确交易策略,例如区间交易、低买高卖等。 + 对成交价格敏感,希望严格按照指定价格或更优价格成交。 + 进行大额交易,希望通过挂单方式逐步成交,避免对市场造成冲击或产生明显滑点。 + 不急于立即成交,希望在价格到达预期区间后,由系统自动撮合完成交易。 + 市场剧烈波动时,可能会出现快速跳过挂单价格,导致无法成交。 永续合约 开仓 杠杆 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f653c4662f..2ff225e3ed 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -372,7 +372,7 @@ Regular wallet transfer to other regular wallets, private wallets, Mixin Safe, or contacts. The handling fee will be refunded to your private wallet on a monthly basis, based on the actual currency and amount of the handling fee consumed. Read the documentation for more information. more information - https://support.mixin.one/en/article/cross-wallet-transaction-fee-event-1k9b47q + https://support.mixin.one/en/article/campaign-free-transactions-between-mixin-wallets-kozded/ You are signed in on desktop Please upgrade Mixin Desktop to the latest version. Detected a Mixin QR code, tap to recognize @@ -657,6 +657,17 @@ Last Backup Later Learn More + Import Wallet + Import with a private key or 12/24-word recovery phrase. Supports Bitcoin, Solana, Ethereum, and other EVM networks. + https://support.mixin.one/en/article/how-to-import-mnemonic-phrases-tjujvv/ + Watch addresses without private keys. View balances, transactions, and receive activity notifications with a fast native experience. + https://support.mixin.one/en/article/how-to-add-a-watch-only-address-17ifo8w/ + Free transfers between wallets + Free transfers between wallets, for a limited time. + Is it safe to import into Mixin? + Mixin is open source, reproducible, and independently audited. + Your data is securely encrypted and stored locally on your device. + Mixin never accesses your private information or shares it with third parties. Line busy Line busy Link Desktop @@ -2315,6 +2326,16 @@ Please manage leverage and position size carefully and trade responsibly Example Perpetual + Instructions + Mixin Trade supports both simple swaps and a professional limit-order mode. It is easy to use, supports multiple chains and assets, and aggregates liquidity across DEXs and CEXs. + Product Features + Best aggregation: Supports Uniswap, 1inch, Jupiter, MixPay, BigONE, and more across DEXs and CEXs. + Cross-chain trading: Supports assets across Bitcoin, Ethereum, Solana, and other networks. + Additional Notes + Trading costs mainly come from exchange fees and network withdrawal fees. Mixin does not charge additional trading fees. + Trading rebates mainly come from fee-sharing arrangements with exchanges. + Risk Warning + Example Price up %1$s%% → Profit %2$s%% (+%3$s) Price down %1$s%% → Profit %2$s%% (+%3$s) Price down %1$s%% → Loss -%2$s %3$s @@ -2337,12 +2358,38 @@ Leverage amplifies both profits and losses The higher the leverage, the greater the PnL impact from price movements Please choose your leverage carefully. With high leverage, even small price fluctuations may result in significant losses. - Position size is calculated as “Margin × Leverage”, representing the total value of the position you control in a trade. + Position size is calculated as "Margin × Leverage", representing the total value of the position you control in a trade. Purpose: Defines your market exposure for the trade Determines how much your PnL is amplified + Support current position. + Offset floating losses. If losses approach your margin, your position may be liquidated by the system. Rapid price movements can quickly deplete your margin. + Trading Pair + Pay Amount + Exchange Price + Trading Strategy + Order Price + Market Price + Suitable Scenarios + Quote Details + Simple Swap is an easy-to-use trading mode that executes immediately at the current best available market price. + Ideal when you want immediate execution with a straightforward flow and predictable completion. + Well suited for mainstream assets or trading pairs with strong liquidity, where market execution usually keeps price differences limited. + Beginner-friendly, with a simple and intuitive experience. + Quotes are aggregated from the best prices available across DEXs and CEXs. + Quotes already include exchange fees and network withdrawal fees. + During sharp market movements, the final execution price may differ from the expected quote. + Buy low\nSell high + Buy low + Sell high + Professional mode supports limit orders, letting you set your own execution price so the order is matched only when the market reaches your target price or better. + Useful when you have a clear trading strategy, such as range trading or buying low and selling high. + Suitable when you are sensitive to execution price and want the order filled strictly at your target or better. + Helpful for larger orders when you want to place orders gradually and avoid large market impact or obvious slippage. + Suitable when you do not need immediate execution and prefer the system to match automatically once the price enters your target range. + During sharp market movements, the market may jump over your limit price quickly and the order may remain unfilled. Perpetual Open Position Leverage From 26c5b84f334aed532a500142ec69a8fc4e0a2432 Mon Sep 17 00:00:00 2001 From: SeniorZhai Date: Thu, 19 Mar 2026 16:27:51 +0800 Subject: [PATCH 02/35] Add pinch zoom to candle chart --- .../android/ui/home/web3/trade/CandleChart.kt | 66 +++++++++++++++++-- 1 file changed, 62 insertions(+), 4 deletions(-) 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 c146630f3e..012220acd7 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 @@ -3,6 +3,7 @@ package one.mixin.android.ui.home.web3.trade import androidx.compose.foundation.Canvas import androidx.compose.foundation.background import androidx.compose.foundation.border +import androidx.compose.foundation.gestures.detectTransformGestures import androidx.compose.foundation.gestures.detectDragGesturesAfterLongPress import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.layout.Arrangement @@ -27,6 +28,7 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -64,10 +66,16 @@ import org.threeten.bp.ZoneId import org.threeten.bp.ZonedDateTime import org.threeten.bp.format.DateTimeFormatter import java.math.BigDecimal +import kotlinx.coroutines.launch +import kotlin.math.abs import kotlin.math.max import kotlin.math.min +import kotlin.math.roundToInt private const val CANDLE_REFRESH_INTERVAL_MS = 10_000L +private const val DEFAULT_CANDLE_SCALE = 1f +private const val MIN_CANDLE_SCALE = 0.5f +private const val MAX_CANDLE_SCALE = 3f @Composable fun CandleChart( @@ -156,16 +164,24 @@ private fun ScrollableCandleChart( val items = candleView.items if (items.isEmpty()) return - val candleWidth = 6.dp - val spacing = 2.dp + val baseCandleWidth = 6.dp + val baseSpacing = 2.dp val density = LocalDensity.current val scrollState = rememberScrollState() + val coroutineScope = rememberCoroutineScope() var touchXOnChart by remember { mutableStateOf(null) } var isTouching by remember { mutableStateOf(false) } + var isPinching by remember { mutableStateOf(false) } + var candleScale by remember(items.size) { mutableStateOf(DEFAULT_CANDLE_SCALE) } + + val candleWidth = baseCandleWidth * candleScale + val spacing = baseSpacing * candleScale val candleStepPx = with(density) { (candleWidth + spacing).toPx() } val candleWidthPx = with(density) { candleWidth.toPx() } + val baseCandleWidthPx = with(density) { baseCandleWidth.toPx() } + val baseSpacingPx = with(density) { baseSpacing.toPx() } val chartStartPaddingPx = with(density) { 8.dp.toPx() } val totalChartWidthPx = with(density) { (8.dp + (candleWidth * items.size) + (spacing * (items.size - 1).coerceAtLeast(0))).toPx() @@ -231,7 +247,49 @@ private fun ScrollableCandleChart( modifier = Modifier .fillMaxSize() .padding(end = axisPanelWidth) - .pointerInput(items.size, scrollState.value) { + .pointerInput( + items.size, + viewportWidthPx, + chartStartPaddingPx, + baseCandleWidthPx, + baseSpacingPx, + ) { + detectTransformGestures( + panZoomLock = true, + onGesture = { centroid, pan, zoom, _ -> + if (abs(zoom - 1f) < 0.0001f && abs(pan.x) < 0.0001f) { + return@detectTransformGestures + } + + isPinching = true + isTouching = false + touchXOnChart = null + + val oldScale = candleScale + val newScale = (oldScale * zoom).coerceIn(MIN_CANDLE_SCALE, MAX_CANDLE_SCALE) + val oldStepPx = (baseCandleWidthPx + baseSpacingPx) * oldScale + val newStepPx = (baseCandleWidthPx + baseSpacingPx) * newScale + val contentX = scrollState.value + centroid.x - chartStartPaddingPx + val stepIndex = if (oldStepPx > 0f) contentX / oldStepPx else 0f + + candleScale = newScale + + val newTotalWidthPx = chartStartPaddingPx + + (baseCandleWidthPx * newScale * items.size) + + (baseSpacingPx * newScale * (items.size - 1).coerceAtLeast(0)) + val maxScroll = (newTotalWidthPx - viewportWidthPx).coerceAtLeast(0f) + val anchoredScroll = (stepIndex * newStepPx) - (centroid.x - chartStartPaddingPx) + val targetScroll = (anchoredScroll - pan.x).roundToInt() + .coerceIn(0, maxScroll.roundToInt()) + + coroutineScope.launch { + scrollState.scrollTo(targetScroll) + } + } + ) + } + .pointerInput(items.size, totalChartWidthPx, isPinching) { + if (isPinching) return@pointerInput detectDragGesturesAfterLongPress( onDragStart = { offset -> isTouching = true @@ -253,7 +311,7 @@ private fun ScrollableCandleChart( } ) } - .horizontalScroll(scrollState, enabled = !isTouching) + .horizontalScroll(scrollState, enabled = !isTouching && !isPinching) .clipToBounds() ) { PerpsCandleChartCanvas( From adb780b4d9e13f6a16d603b1448d5f2d158960f0 Mon Sep 17 00:00:00 2001 From: SeniorZhai Date: Tue, 24 Mar 2026 15:01:07 +0800 Subject: [PATCH 03/35] Update guide style --- .../ui/home/web3/trade/SpotTradeGuidePage.kt | 152 +++++++++++------- app/src/main/res/drawable/ic_token_usdt.xml | 12 ++ app/src/main/res/values-zh-rCN/strings.xml | 1 + app/src/main/res/values/strings.xml | 1 + 4 files changed, 105 insertions(+), 61 deletions(-) create mode 100644 app/src/main/res/drawable/ic_token_usdt.xml diff --git a/app/src/main/java/one/mixin/android/ui/home/web3/trade/SpotTradeGuidePage.kt b/app/src/main/java/one/mixin/android/ui/home/web3/trade/SpotTradeGuidePage.kt index 73e6eac9ea..0c9f7892e4 100644 --- a/app/src/main/java/one/mixin/android/ui/home/web3/trade/SpotTradeGuidePage.kt +++ b/app/src/main/java/one/mixin/android/ui/home/web3/trade/SpotTradeGuidePage.kt @@ -52,7 +52,6 @@ import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import one.mixin.android.Constants import one.mixin.android.R -import one.mixin.android.compose.CoilImage import one.mixin.android.compose.theme.MixinAppTheme import one.mixin.android.extension.numberFormat8 import one.mixin.android.extension.priceFormat @@ -123,7 +122,6 @@ fun SpotTradeGuidePage( .fillMaxSize() .padding(horizontal = 16.dp) ) { - Spacer(modifier = Modifier.height(16.dp)) Row( modifier = Modifier .fillMaxWidth() @@ -288,13 +286,13 @@ private fun SpotTradeExampleCard( fontWeight = FontWeight.W500, color = MixinAppTheme.colors.textPrimary, ) - Spacer(modifier = Modifier.height(12.dp)) + Spacer(modifier = Modifier.height(16.dp)) if (limitStrategy != null) { StrategyRow( strategy = strategy, onStrategySelected = { strategy = it }, ) - Spacer(modifier = Modifier.height(12.dp)) + Spacer(modifier = Modifier.height(16.dp)) } ExampleValueRow( title = stringResource(R.string.Trade_Guide_Trading_Pair), @@ -313,7 +311,7 @@ private fun SpotTradeExampleCard( ) }, ) - Spacer(modifier = Modifier.height(12.dp)) + Spacer(modifier = Modifier.height(16.dp)) ExampleValueRow( title = stringResource(R.string.Trade_Guide_Pay_Amount), value = { @@ -330,20 +328,11 @@ private fun SpotTradeExampleCard( ) }, ) - Spacer(modifier = Modifier.height(12.dp)) - ExampleValueRow( - title = stringResource( - if (limitStrategy == null) R.string.Trade_Guide_Exchange_Price else R.string.Trade_Guide_Order_Price - ), - value = { - if (limitStrategy == null) { - PriceSubtitle( - marketPrice = effectivePrice, - isReversed = isPriceDisplayReversed, - onSwitchDirection = { isPriceDisplayReversed = !isPriceDisplayReversed }, - onPriceExpired = { priceRefreshFlag = !priceRefreshFlag }, - ) - } else { + if (limitStrategy != null) { + Spacer(modifier = Modifier.height(16.dp)) + ExampleValueRow( + title = stringResource(R.string.Trade_Guide_Order_Price), + value = { OrderPriceStepper( price = effectivePrice, symbol = usdtToken?.symbol ?: "USDT", @@ -356,23 +345,33 @@ private fun SpotTradeExampleCard( }, ) } - }, + ) + } + Spacer(modifier = Modifier.height(16.dp)) + Text( + text = if (limitStrategy == null) stringResource(R.string.Trade_Guide_Exchange_Price) else stringResource(R.string.Trade_Guide_Current_Price), + fontSize = 14.sp, + lineHeight = 20.sp, + color = MixinAppTheme.colors.textPrimary, ) - if (limitStrategy != null) { - Spacer(modifier = Modifier.height(12.dp)) - ExampleValueRow( - title = stringResource(R.string.Trade_Guide_Market_Price), - value = { - PriceSubtitle( - marketPrice = marketPrice, - isReversed = isPriceDisplayReversed, - onSwitchDirection = { isPriceDisplayReversed = !isPriceDisplayReversed }, - onPriceExpired = { priceRefreshFlag = !priceRefreshFlag }, - ) - }, + + Spacer(modifier = Modifier.height(16.dp)) + if (limitStrategy == null) { + PriceSubtitle( + marketPrice = effectivePrice, + isReversed = isPriceDisplayReversed, + onSwitchDirection = { isPriceDisplayReversed = !isPriceDisplayReversed }, + onPriceExpired = { priceRefreshFlag = !priceRefreshFlag }, + ) + } else { + PriceSubtitle( + marketPrice = marketPrice, + isReversed = isPriceDisplayReversed, + onSwitchDirection = { isPriceDisplayReversed = !isPriceDisplayReversed }, + onPriceExpired = { priceRefreshFlag = !priceRefreshFlag }, ) } - Spacer(modifier = Modifier.height(12.dp)) + Spacer(modifier = Modifier.height(16.dp)) ExampleValueRow( title = stringResource(R.string.Estimated_Receive), value = { @@ -380,9 +379,10 @@ private fun SpotTradeExampleCard( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(6.dp), ) { - CoilImage( - model = toToken?.iconUrl, - placeholder = R.drawable.ic_avatar_place_holder, + Icon( + painter = painterResource(id = guideTokenIconRes(toToken, if (isPairReversed) "USDT" else "BTC")), + contentDescription = null, + tint = Color.Unspecified, modifier = Modifier .size(18.dp) .clip(CircleShape), @@ -409,23 +409,27 @@ private fun StrategyRow( ExampleValueRow( title = stringResource(R.string.Trade_Guide_Trading_Strategy), value = { - Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + Row(modifier = Modifier + .clip(RoundedCornerShape(6.dp)) + .background(MixinAppTheme.colors.backgroundWindow) + .padding(2.dp), + horizontalArrangement = Arrangement.spacedBy(8.dp)) { LimitStrategy.entries.forEach { item -> val selected = item == strategy Box( modifier = Modifier - .clip(RoundedCornerShape(20.dp)) + .clip(RoundedCornerShape(6.dp)) .background( if (selected) MixinAppTheme.colors.accent - else MixinAppTheme.colors.backgroundWindow + else Color.Transparent ) .clickable { onStrategySelected(item) } - .padding(horizontal = 12.dp, vertical = 8.dp), + .padding(horizontal = 8.dp, vertical = 2.dp), contentAlignment = Alignment.Center, ) { Text( text = stringResource(item.titleRes), - color = if (selected) Color.White else MixinAppTheme.colors.textPrimary, + color = if (selected) Color.White else MixinAppTheme.colors.textAssist, fontSize = 13.sp, lineHeight = 18.sp, ) @@ -503,9 +507,10 @@ private fun GuideTokenBadge( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(6.dp), ) { - CoilImage( - model = token?.iconUrl, - placeholder = R.drawable.ic_avatar_place_holder, + Icon( + painter = painterResource(id = guideTokenIconRes(token, fallbackSymbol)), + contentDescription = null, + tint = Color.Unspecified, modifier = Modifier .size(20.dp) .clip(CircleShape), @@ -532,10 +537,13 @@ private fun AmountStepper( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp), ) { - StepperButton( - text = "-", - enabled = amount > step, - onClick = onDecrease, + Icon( + painter = painterResource(id = R.drawable.ic_perps_minus), + contentDescription = null, + tint = Color.Unspecified, + modifier = Modifier + .size(16.dp) + .clickable(enabled = amount > step, onClick = onDecrease), ) Text( text = "${amount.numberFormat8()} $symbol", @@ -544,10 +552,13 @@ private fun AmountStepper( color = MixinAppTheme.colors.textPrimary, fontWeight = FontWeight.W500, ) - StepperButton( - text = "+", - enabled = true, - onClick = onIncrease, + Icon( + painter = painterResource(id = R.drawable.ic_perps_add), + contentDescription = null, + tint = Color.Unspecified, + modifier = Modifier + .size(16.dp) + .clickable(onClick = onIncrease), ) } } @@ -563,10 +574,13 @@ private fun OrderPriceStepper( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp), ) { - StepperButton( - text = "-", - enabled = price > LIMIT_PRICE_STEP, - onClick = onDecrease, + Icon( + painter = painterResource(id = R.drawable.ic_perps_minus), + contentDescription = null, + tint = Color.Unspecified, + modifier = Modifier + .size(16.dp) + .clickable(enabled = price > LIMIT_PRICE_STEP, onClick = onDecrease), ) Text( text = "${price.setScale(0, RoundingMode.DOWN).numberFormat8()} $symbol", @@ -575,10 +589,13 @@ private fun OrderPriceStepper( color = MixinAppTheme.colors.textPrimary, fontWeight = FontWeight.W500, ) - StepperButton( - text = "+", - enabled = true, - onClick = onIncrease, + Icon( + painter = painterResource(id = R.drawable.ic_perps_add), + contentDescription = null, + tint = Color.Unspecified, + modifier = Modifier + .size(16.dp) + .clickable(onClick = onIncrease), ) } } @@ -628,6 +645,7 @@ private fun PriceSubtitle( } Row( + modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(6.dp), ) { @@ -648,6 +666,7 @@ private fun PriceSubtitle( color = MixinAppTheme.colors.textPrimary, backgroundColor = MixinAppTheme.colors.textAssist, ) + Spacer(modifier = Modifier.weight(1f)) Icon( painter = painterResource(id = R.drawable.ic_price_switch), contentDescription = null, @@ -681,7 +700,7 @@ private fun TradeGuideInfoCard( fontWeight = FontWeight.W500, color = MixinAppTheme.colors.textPrimary, ) - Spacer(modifier = Modifier.height(12.dp)) + Spacer(modifier = Modifier.height(16.dp)) Text( text = description, fontSize = 14.sp, @@ -871,6 +890,17 @@ private fun TokenItem?.safePrice(): BigDecimal { return this?.priceUsd?.toBigDecimalOrNull()?.takeIf { it > BigDecimal.ZERO } ?: BigDecimal.ONE } +private fun guideTokenIconRes( + token: TokenItem?, + fallbackSymbol: String, +): Int { + return when ((token?.symbol ?: fallbackSymbol).uppercase()) { + "USDT" -> R.drawable.ic_token_usdt + "BTC" -> R.drawable.ic_chain_btc + else -> R.drawable.ic_avatar_place_holder + } +} + private fun safeDivide( dividend: BigDecimal, divisor: BigDecimal, diff --git a/app/src/main/res/drawable/ic_token_usdt.xml b/app/src/main/res/drawable/ic_token_usdt.xml new file mode 100644 index 0000000000..9b81d381b6 --- /dev/null +++ b/app/src/main/res/drawable/ic_token_usdt.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 88d3610840..490281d665 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -2299,6 +2299,7 @@ 交易币种 支付金额 兑换价格 + 当前价格 交易策略 成交价格 市场价格 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2ff225e3ed..085b05c175 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -2369,6 +2369,7 @@ Trading Pair Pay Amount Exchange Price + Current Price Trading Strategy Order Price Market Price From a68ed60838655df752e954beee4fd66025d06aa3 Mon Sep 17 00:00:00 2001 From: SeniorZhai Date: Wed, 25 Mar 2026 10:07:55 +0800 Subject: [PATCH 04/35] Add elevation to Button and create MixinButton component - Add explicit elevation=0.dp to all Button that didn't have it - Create MixinButton reusable component in MaterialButton.kt Co-Authored-By: Claude Opus 4.6 --- .../mixin/android/ui/home/web3/BrowserPage.kt | 6 ++++ .../PerpsCloseBottomSheetDialogFragment.kt | 6 ++++ .../PerpsConfirmBottomSheetDialogFragment.kt | 6 ++++ ...AccountConfirmBottomSheetDialogFragment.kt | 6 ++++ .../mixin/android/ui/logs/LogViewerScreen.kt | 18 ++++++++++ .../wc/sessionproposal/SessionProposalPage.kt | 6 ++++ .../ui/tip/wc/sessionproposal/WCPinBoard.kt | 12 +++++++ .../wc/sessionrequest/SessionRequestPage.kt | 6 ++++ ...sWalletFeeFreeBottomSheetDialogFragment.kt | 6 ++++ ...ghtningAddressBottomSheetDialogFragment.kt | 6 ++++ .../LimitTransferBottomSheetDialogFragment.kt | 6 ++++ .../SwapTransferBottomSheetDialogFragment.kt | 6 ++++ .../widget/components/MaterialButton.kt | 36 +++++++++++++++++++ 13 files changed, 126 insertions(+) diff --git a/app/src/main/java/one/mixin/android/ui/home/web3/BrowserPage.kt b/app/src/main/java/one/mixin/android/ui/home/web3/BrowserPage.kt index 5894c4ec54..9d9a043e08 100644 --- a/app/src/main/java/one/mixin/android/ui/home/web3/BrowserPage.kt +++ b/app/src/main/java/one/mixin/android/ui/home/web3/BrowserPage.kt @@ -447,6 +447,12 @@ fun BrowserPage( ), shape = RoundedCornerShape(20.dp), contentPadding = PaddingValues(horizontal = 36.dp, vertical = 11.dp), + elevation = ButtonDefaults.elevation( + defaultElevation = 0.dp, + pressedElevation = 0.dp, + hoveredElevation = 0.dp, + focusedElevation = 0.dp, + ), ) { Text(text = stringResource(id = if (step == WalletConnectBottomSheetDialogFragment.Step.Done) R.string.Done else R.string.Got_it), color = Color.White) } diff --git a/app/src/main/java/one/mixin/android/ui/home/web3/trade/perps/PerpsCloseBottomSheetDialogFragment.kt b/app/src/main/java/one/mixin/android/ui/home/web3/trade/perps/PerpsCloseBottomSheetDialogFragment.kt index cd4c0ca368..6d0358fc7a 100644 --- a/app/src/main/java/one/mixin/android/ui/home/web3/trade/perps/PerpsCloseBottomSheetDialogFragment.kt +++ b/app/src/main/java/one/mixin/android/ui/home/web3/trade/perps/PerpsCloseBottomSheetDialogFragment.kt @@ -485,6 +485,12 @@ class PerpsCloseBottomSheetDialogFragment : MixinComposeBottomSheetDialogFragmen ), shape = RoundedCornerShape(20.dp), contentPadding = PaddingValues(horizontal = 36.dp, vertical = 11.dp), + elevation = ButtonDefaults.elevation( + defaultElevation = 0.dp, + pressedElevation = 0.dp, + hoveredElevation = 0.dp, + focusedElevation = 0.dp, + ), ) { Text(text = stringResource(id = R.string.Done), color = Color.White) } diff --git a/app/src/main/java/one/mixin/android/ui/home/web3/trade/perps/PerpsConfirmBottomSheetDialogFragment.kt b/app/src/main/java/one/mixin/android/ui/home/web3/trade/perps/PerpsConfirmBottomSheetDialogFragment.kt index cfca81749c..faf11ddfbb 100644 --- a/app/src/main/java/one/mixin/android/ui/home/web3/trade/perps/PerpsConfirmBottomSheetDialogFragment.kt +++ b/app/src/main/java/one/mixin/android/ui/home/web3/trade/perps/PerpsConfirmBottomSheetDialogFragment.kt @@ -432,6 +432,12 @@ class PerpsConfirmBottomSheetDialogFragment : MixinComposeBottomSheetDialogFragm backgroundColor = MixinAppTheme.colors.accent, ), shape = RoundedCornerShape(20.dp), + elevation = ButtonDefaults.elevation( + pressedElevation = 0.dp, + defaultElevation = 0.dp, + hoveredElevation = 0.dp, + focusedElevation = 0.dp + ), contentPadding = PaddingValues(horizontal = 36.dp, vertical = 11.dp), ) { Text(text = stringResource(id = R.string.Done), color = Color.White) diff --git a/app/src/main/java/one/mixin/android/ui/landing/CreateAccountConfirmBottomSheetDialogFragment.kt b/app/src/main/java/one/mixin/android/ui/landing/CreateAccountConfirmBottomSheetDialogFragment.kt index 1abe98374d..e7486db140 100644 --- a/app/src/main/java/one/mixin/android/ui/landing/CreateAccountConfirmBottomSheetDialogFragment.kt +++ b/app/src/main/java/one/mixin/android/ui/landing/CreateAccountConfirmBottomSheetDialogFragment.kt @@ -144,6 +144,12 @@ class CreateAccountConfirmBottomSheetDialogFragment : MixinComposeBottomSheetDia colors = ButtonDefaults.outlinedButtonColors(backgroundColor = MixinAppTheme.colors.accent), shape = androidx.compose.foundation.shape.RoundedCornerShape(20.dp), contentPadding = androidx.compose.foundation.layout.PaddingValues(horizontal = 36.dp, vertical = 11.dp), + elevation = ButtonDefaults.elevation( + defaultElevation = 0.dp, + pressedElevation = 0.dp, + hoveredElevation = 0.dp, + focusedElevation = 0.dp, + ), ) { Text(text = stringResource(R.string.create_account_confirm_action_create), color = Color.White) } diff --git a/app/src/main/java/one/mixin/android/ui/logs/LogViewerScreen.kt b/app/src/main/java/one/mixin/android/ui/logs/LogViewerScreen.kt index e502278e87..1326546514 100644 --- a/app/src/main/java/one/mixin/android/ui/logs/LogViewerScreen.kt +++ b/app/src/main/java/one/mixin/android/ui/logs/LogViewerScreen.kt @@ -124,6 +124,12 @@ fun LogViewerScreen( } }, shape = RoundedCornerShape(32.dp), + elevation = ButtonDefaults.elevation( + defaultElevation = 0.dp, + pressedElevation = 0.dp, + hoveredElevation = 0.dp, + focusedElevation = 0.dp, + ), ) { Text(text = stringResource(R.string.Share), color = MixinAppTheme.colors.accent) } @@ -139,6 +145,12 @@ fun LogViewerScreen( toast(R.string.copied_to_clipboard) }, shape = RoundedCornerShape(32.dp), + elevation = ButtonDefaults.elevation( + defaultElevation = 0.dp, + pressedElevation = 0.dp, + hoveredElevation = 0.dp, + focusedElevation = 0.dp, + ), ) { Text(text = stringResource(R.string.Copy), color = Color.White) } @@ -159,6 +171,12 @@ fun LogViewerScreen( colors = ButtonDefaults.outlinedButtonColors( backgroundColor = MixinAppTheme.colors.accent ), + elevation = ButtonDefaults.elevation( + defaultElevation = 0.dp, + pressedElevation = 0.dp, + hoveredElevation = 0.dp, + focusedElevation = 0.dp, + ), ) { Text(text = stringResource(R.string.Done), color = Color.White) } diff --git a/app/src/main/java/one/mixin/android/ui/tip/wc/sessionproposal/SessionProposalPage.kt b/app/src/main/java/one/mixin/android/ui/tip/wc/sessionproposal/SessionProposalPage.kt index 0ee02ff332..119baa957b 100644 --- a/app/src/main/java/one/mixin/android/ui/tip/wc/sessionproposal/SessionProposalPage.kt +++ b/app/src/main/java/one/mixin/android/ui/tip/wc/sessionproposal/SessionProposalPage.kt @@ -227,6 +227,12 @@ fun SessionProposalPage( ), shape = RoundedCornerShape(20.dp), contentPadding = PaddingValues(horizontal = 36.dp, vertical = 11.dp), + elevation = ButtonDefaults.elevation( + defaultElevation = 0.dp, + pressedElevation = 0.dp, + hoveredElevation = 0.dp, + focusedElevation = 0.dp, + ), ) { Text(text = stringResource(id = R.string.Done), color = Color.White) } diff --git a/app/src/main/java/one/mixin/android/ui/tip/wc/sessionproposal/WCPinBoard.kt b/app/src/main/java/one/mixin/android/ui/tip/wc/sessionproposal/WCPinBoard.kt index 5c40a27324..3030dc7a02 100644 --- a/app/src/main/java/one/mixin/android/ui/tip/wc/sessionproposal/WCPinBoard.kt +++ b/app/src/main/java/one/mixin/android/ui/tip/wc/sessionproposal/WCPinBoard.kt @@ -188,6 +188,12 @@ fun WCPinBoard( ), contentPadding = PaddingValues(horizontal = 28.dp), shape = RoundedCornerShape(40.dp), + elevation = ButtonDefaults.elevation( + defaultElevation = 0.dp, + pressedElevation = 0.dp, + hoveredElevation = 0.dp, + focusedElevation = 0.dp, + ), ) { Text( text = stringResource(id = R.string.Done), @@ -218,6 +224,12 @@ fun WCPinBoard( ), contentPadding = PaddingValues(horizontal = 28.dp, vertical = 11.dp), shape = RoundedCornerShape(40.dp), + elevation = ButtonDefaults.elevation( + defaultElevation = 0.dp, + pressedElevation = 0.dp, + hoveredElevation = 0.dp, + focusedElevation = 0.dp, + ), ) { if (signUnavailable) { CircularProgressIndicator( diff --git a/app/src/main/java/one/mixin/android/ui/tip/wc/sessionrequest/SessionRequestPage.kt b/app/src/main/java/one/mixin/android/ui/tip/wc/sessionrequest/SessionRequestPage.kt index 26629b37c3..ca1b9020a8 100644 --- a/app/src/main/java/one/mixin/android/ui/tip/wc/sessionrequest/SessionRequestPage.kt +++ b/app/src/main/java/one/mixin/android/ui/tip/wc/sessionrequest/SessionRequestPage.kt @@ -387,6 +387,12 @@ fun SessionRequestPage( ), shape = RoundedCornerShape(20.dp), contentPadding = PaddingValues(horizontal = 36.dp, vertical = 11.dp), + elevation = ButtonDefaults.elevation( + defaultElevation = 0.dp, + pressedElevation = 0.dp, + hoveredElevation = 0.dp, + focusedElevation = 0.dp, + ), ) { Text(text = stringResource(id = R.string.Done), color = Color.White) } diff --git a/app/src/main/java/one/mixin/android/ui/wallet/CrossWalletFeeFreeBottomSheetDialogFragment.kt b/app/src/main/java/one/mixin/android/ui/wallet/CrossWalletFeeFreeBottomSheetDialogFragment.kt index 42daf91d8c..d3ccb4bfd5 100644 --- a/app/src/main/java/one/mixin/android/ui/wallet/CrossWalletFeeFreeBottomSheetDialogFragment.kt +++ b/app/src/main/java/one/mixin/android/ui/wallet/CrossWalletFeeFreeBottomSheetDialogFragment.kt @@ -121,6 +121,12 @@ class CrossWalletFeeFreeBottomSheetDialogFragment : BottomSheetDialogFragment() ), shape = RoundedCornerShape(20.dp), contentPadding = PaddingValues(horizontal = 36.dp, vertical = 11.dp), + elevation = ButtonDefaults.elevation( + defaultElevation = 0.dp, + pressedElevation = 0.dp, + hoveredElevation = 0.dp, + focusedElevation = 0.dp, + ), ) { Text(text = stringResource(R.string.Got_it), color = Color.White) } diff --git a/app/src/main/java/one/mixin/android/ui/wallet/LightningAddressBottomSheetDialogFragment.kt b/app/src/main/java/one/mixin/android/ui/wallet/LightningAddressBottomSheetDialogFragment.kt index b1edbd2c65..e6c64eeb50 100644 --- a/app/src/main/java/one/mixin/android/ui/wallet/LightningAddressBottomSheetDialogFragment.kt +++ b/app/src/main/java/one/mixin/android/ui/wallet/LightningAddressBottomSheetDialogFragment.kt @@ -131,6 +131,12 @@ class LightningAddressBottomSheetDialogFragment : MixinComposeBottomSheetDialogF ), shape = RoundedCornerShape(20.dp), contentPadding = PaddingValues(horizontal = 36.dp, vertical = 11.dp), + elevation = ButtonDefaults.elevation( + defaultElevation = 0.dp, + pressedElevation = 0.dp, + hoveredElevation = 0.dp, + focusedElevation = 0.dp, + ), ) { Text(text = stringResource(R.string.Copy_Address), color = Color.White) } 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 1c2f01b00b..01432e9ac3 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 @@ -389,6 +389,12 @@ class LimitTransferBottomSheetDialogFragment : MixinComposeBottomSheetDialogFrag colors = ButtonDefaults.outlinedButtonColors(backgroundColor = MixinAppTheme.colors.accent), shape = androidx.compose.foundation.shape.RoundedCornerShape(20.dp), contentPadding = androidx.compose.foundation.layout.PaddingValues(horizontal = 36.dp, vertical = 11.dp), + elevation = ButtonDefaults.elevation( + defaultElevation = 0.dp, + pressedElevation = 0.dp, + hoveredElevation = 0.dp, + focusedElevation = 0.dp, + ), ) { Text(text = stringResource(id = R.string.Done), color = Color.White) } } } 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 3168bf3d91..83caa52626 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 @@ -543,6 +543,12 @@ class SwapTransferBottomSheetDialogFragment : MixinComposeBottomSheetDialogFragm ), shape = RoundedCornerShape(20.dp), contentPadding = PaddingValues(horizontal = 36.dp, vertical = 11.dp), + elevation = ButtonDefaults.elevation( + defaultElevation = 0.dp, + pressedElevation = 0.dp, + hoveredElevation = 0.dp, + focusedElevation = 0.dp, + ), ) { Text(text = stringResource(id = R.string.Done), color = Color.White) } diff --git a/app/src/main/java/one/mixin/android/widget/components/MaterialButton.kt b/app/src/main/java/one/mixin/android/widget/components/MaterialButton.kt index cb0546b81a..e9d663c63f 100644 --- a/app/src/main/java/one/mixin/android/widget/components/MaterialButton.kt +++ b/app/src/main/java/one/mixin/android/widget/components/MaterialButton.kt @@ -1,5 +1,6 @@ package one.mixin.android.widget.components +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.shape.RoundedCornerShape @@ -8,6 +9,8 @@ import androidx.compose.material.ButtonDefaults import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import one.mixin.android.compose.theme.MixinAppTheme @@ -36,4 +39,37 @@ fun MaterialWindowButton(onClick: () -> Unit, title: String) { color = MixinAppTheme.colors.textBlue ) } +} + +@Composable +fun MixinButton( + onClick: () -> Unit, + modifier: Modifier = Modifier, + enabled: Boolean = true, + shape: RoundedCornerShape = RoundedCornerShape(20.dp), + contentPadding: PaddingValues = PaddingValues(horizontal = 36.dp, vertical = 11.dp), + elevation: Dp = 0.dp, + backgroundColor: Color = MixinAppTheme.colors.accent, + contentColor: Color = Color.White, + content: @Composable () -> Unit, +) { + Button( + onClick = onClick, + modifier = modifier, + enabled = enabled, + colors = ButtonDefaults.outlinedButtonColors( + backgroundColor = backgroundColor, + contentColor = contentColor, + ), + shape = shape, + contentPadding = contentPadding, + elevation = ButtonDefaults.elevation( + defaultElevation = elevation, + pressedElevation = 0.dp, + hoveredElevation = elevation, + focusedElevation = elevation, + ), + ) { + content() + } } \ No newline at end of file From e625f6a3754a85afd405fe82bc10f2567379a344 Mon Sep 17 00:00:00 2001 From: SeniorZhai Date: Wed, 25 Mar 2026 10:21:50 +0800 Subject: [PATCH 05/35] refactor: reuse mixin compose button --- .../address/components/FetchAddressContent.kt | 41 ++++++------------- .../page/TransferDestinationInputPage.kt | 19 +++------ .../compose/AuthBottomSheetDialogCompose.kt | 12 +----- .../android/ui/auth/compose/PinKeyBoard.kt | 15 +------ .../mixin/android/ui/home/web3/BrowserPage.kt | 15 +------ .../PerpsCloseBottomSheetDialogFragment.kt | 14 +------ .../PerpsConfirmBottomSheetDialogFragment.kt | 14 +------ ...AccountConfirmBottomSheetDialogFragment.kt | 12 +----- .../mixin/android/ui/logs/LogViewerScreen.kt | 12 +----- .../wc/sessionproposal/SessionProposalPage.kt | 15 +------ .../ui/tip/wc/sessionproposal/WCPinBoard.kt | 27 ++---------- .../wc/sessionrequest/SessionRequestPage.kt | 15 +------ ...sWalletFeeFreeBottomSheetDialogFragment.kt | 14 +------ .../mixin/android/ui/wallet/ImportKeyPage.kt | 16 +------- ...ghtningAddressBottomSheetDialogFragment.kt | 15 +------ .../LimitTransferBottomSheetDialogFragment.kt | 12 +----- .../SwapTransferBottomSheetDialogFragment.kt | 15 +------ .../widget/components/MaterialButton.kt | 13 ++++-- 18 files changed, 58 insertions(+), 238 deletions(-) diff --git a/app/src/main/java/one/mixin/android/ui/address/components/FetchAddressContent.kt b/app/src/main/java/one/mixin/android/ui/address/components/FetchAddressContent.kt index f772604d3f..b0aa539a27 100644 --- a/app/src/main/java/one/mixin/android/ui/address/components/FetchAddressContent.kt +++ b/app/src/main/java/one/mixin/android/ui/address/components/FetchAddressContent.kt @@ -9,8 +9,6 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.Button -import androidx.compose.material.ButtonDefaults import androidx.compose.material.CircularProgressIndicator import androidx.compose.material.Icon import androidx.compose.material.Text @@ -25,6 +23,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import one.mixin.android.R import one.mixin.android.compose.theme.MixinAppTheme +import one.mixin.android.widget.components.MixinButton @Composable fun FetchAddressContent( @@ -96,25 +95,17 @@ fun FetchAddressContent( } FetchAddressState.RETRY -> { - Button( + MixinButton( modifier = Modifier .fillMaxWidth() .padding(horizontal = 48.dp) .height(48.dp), onClick = onRetry, - colors = ButtonDefaults.buttonColors( - backgroundColor = MixinAppTheme.colors.accent, - disabledBackgroundColor = MixinAppTheme.colors.backgroundGrayLight, - contentColor = Color.White, - disabledContentColor = Color.White - ), shape = RoundedCornerShape(32.dp), - elevation = ButtonDefaults.elevation( - pressedElevation = 0.dp, - defaultElevation = 0.dp, - hoveredElevation = 0.dp, - focusedElevation = 0.dp, - ), + backgroundColor = MixinAppTheme.colors.accent, + contentColor = Color.White, + disabledBackgroundColor = MixinAppTheme.colors.backgroundGrayLight, + disabledContentColor = Color.White, ) { Text( text = stringResource(id = R.string.Retry), @@ -124,25 +115,17 @@ fun FetchAddressContent( } FetchAddressState.ERROR -> { - Button( + MixinButton( modifier = Modifier .fillMaxWidth() .padding(horizontal = 48.dp) .height(48.dp), onClick = onRetry, - colors = ButtonDefaults.buttonColors( - backgroundColor = MixinAppTheme.colors.accent, - disabledBackgroundColor = MixinAppTheme.colors.backgroundGrayLight, - contentColor = Color.White, - disabledContentColor = Color.White - ), shape = RoundedCornerShape(32.dp), - elevation = ButtonDefaults.elevation( - pressedElevation = 0.dp, - defaultElevation = 0.dp, - hoveredElevation = 0.dp, - focusedElevation = 0.dp, - ), + backgroundColor = MixinAppTheme.colors.accent, + contentColor = Color.White, + disabledBackgroundColor = MixinAppTheme.colors.backgroundGrayLight, + disabledContentColor = Color.White, ) { Text( text = stringResource(id = R.string.Retry), @@ -157,4 +140,4 @@ fun FetchAddressContent( Spacer(modifier = Modifier.height(24.dp)) } } -} \ No newline at end of file +} diff --git a/app/src/main/java/one/mixin/android/ui/address/page/TransferDestinationInputPage.kt b/app/src/main/java/one/mixin/android/ui/address/page/TransferDestinationInputPage.kt index b4de64f357..5f4e86a433 100644 --- a/app/src/main/java/one/mixin/android/ui/address/page/TransferDestinationInputPage.kt +++ b/app/src/main/java/one/mixin/android/ui/address/page/TransferDestinationInputPage.kt @@ -19,8 +19,6 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.verticalScroll -import androidx.compose.material.Button -import androidx.compose.material.ButtonDefaults import androidx.compose.material.Icon import androidx.compose.material.IconButton import androidx.compose.material.ModalBottomSheetLayout @@ -73,6 +71,7 @@ import one.mixin.android.ui.address.AddressViewModel import one.mixin.android.ui.address.component.DestinationMenu import one.mixin.android.ui.address.component.TokenInfoHeader import one.mixin.android.ui.wallet.alert.components.cardBackground +import one.mixin.android.widget.components.MixinButton import one.mixin.android.vo.Address import one.mixin.android.vo.WalletCategory import one.mixin.android.vo.safe.TokenItem @@ -401,7 +400,8 @@ fun TransferDestinationInputPage( .align(Alignment.CenterHorizontally) .alpha(if (errorInfo.isNullOrBlank()) 0f else 1f) ) - Button( + val isDisabled = text.isBlank().not() && isLoading + MixinButton( modifier = Modifier .fillMaxWidth() .height(48.dp), @@ -409,16 +409,10 @@ fun TransferDestinationInputPage( onSend.invoke(text) }, enabled = text.isBlank().not() && !isLoading, - colors = ButtonDefaults.outlinedButtonColors( - backgroundColor = if (text.isBlank().not()) MixinAppTheme.colors.accent else MixinAppTheme.colors.backgroundGrayLight, - ), shape = RoundedCornerShape(32.dp), - elevation = ButtonDefaults.elevation( - pressedElevation = 0.dp, - defaultElevation = 0.dp, - hoveredElevation = 0.dp, - focusedElevation = 0.dp, - ), + backgroundColor = MixinAppTheme.colors.accent, + disabledBackgroundColor = if (text.isBlank()) MixinAppTheme.colors.backgroundGrayLight else MixinAppTheme.colors.accent.copy(alpha = 0.6f), + disabledContentColor = MixinAppTheme.colors.textAssist, ) { if (isLoading) { CircularProgressIndicator( @@ -438,4 +432,3 @@ fun TransferDestinationInputPage( } } } - diff --git a/app/src/main/java/one/mixin/android/ui/auth/compose/AuthBottomSheetDialogCompose.kt b/app/src/main/java/one/mixin/android/ui/auth/compose/AuthBottomSheetDialogCompose.kt index 2e5c326990..25a3abe463 100644 --- a/app/src/main/java/one/mixin/android/ui/auth/compose/AuthBottomSheetDialogCompose.kt +++ b/app/src/main/java/one/mixin/android/ui/auth/compose/AuthBottomSheetDialogCompose.kt @@ -41,8 +41,6 @@ import androidx.compose.foundation.pager.PagerState import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.Button -import androidx.compose.material.ButtonDefaults import androidx.compose.material.ContentAlpha import androidx.compose.material.Text import androidx.compose.runtime.Composable @@ -74,6 +72,7 @@ import one.mixin.android.vo.Scope import one.mixin.android.vo.getScopeGroupIcon import one.mixin.android.vo.getScopeGroupName import one.mixin.android.vo.groupScope +import one.mixin.android.widget.components.MixinButton import kotlin.math.abs @Composable @@ -304,18 +303,11 @@ fun ScopesContent( ) Spacer(modifier = Modifier.height(16.dp)) } - Button( + MixinButton( modifier = Modifier .align(CenterHorizontally), shape = RoundedCornerShape(20.dp), - colors = ButtonDefaults.buttonColors(backgroundColor = MixinAppTheme.colors.accent), - elevation = ButtonDefaults.elevation( - pressedElevation = 0.dp, - defaultElevation = 0.dp, - hoveredElevation = 0.dp, - focusedElevation = 0.dp, - ), contentPadding = PaddingValues(vertical = 8.dp, horizontal = 28.dp), onClick = { if (pagerState.currentPage < scopeGroup.keys.size - 1) { diff --git a/app/src/main/java/one/mixin/android/ui/auth/compose/PinKeyBoard.kt b/app/src/main/java/one/mixin/android/ui/auth/compose/PinKeyBoard.kt index 5fcb1364bc..909120ba7f 100644 --- a/app/src/main/java/one/mixin/android/ui/auth/compose/PinKeyBoard.kt +++ b/app/src/main/java/one/mixin/android/ui/auth/compose/PinKeyBoard.kt @@ -39,8 +39,6 @@ import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.Button -import androidx.compose.material.ButtonDefaults import androidx.compose.material.CircularProgressIndicator import androidx.compose.material.Icon import androidx.compose.material.Text @@ -73,6 +71,7 @@ import one.mixin.android.extension.pxToDp import one.mixin.android.extension.tickVibrate import one.mixin.android.session.Session import one.mixin.android.util.BiometricUtil +import one.mixin.android.widget.components.MixinButton @Composable fun PinKeyBoard( @@ -190,20 +189,10 @@ fun PinKeyBoard( textAlign = TextAlign.Center, fontSize = 14.sp, ) - Button( + MixinButton( onClick = { onResetClick?.invoke() }, - colors = - ButtonDefaults.buttonColors( - backgroundColor = MixinAppTheme.colors.accent, - ), - elevation = ButtonDefaults.elevation( - pressedElevation = 0.dp, - defaultElevation = 0.dp, - hoveredElevation = 0.dp, - focusedElevation = 0.dp, - ), contentPadding = PaddingValues(horizontal = 20.dp), shape = RoundedCornerShape(20.dp), ) { diff --git a/app/src/main/java/one/mixin/android/ui/home/web3/BrowserPage.kt b/app/src/main/java/one/mixin/android/ui/home/web3/BrowserPage.kt index 9d9a043e08..3d10885f48 100644 --- a/app/src/main/java/one/mixin/android/ui/home/web3/BrowserPage.kt +++ b/app/src/main/java/one/mixin/android/ui/home/web3/BrowserPage.kt @@ -17,8 +17,6 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll -import androidx.compose.material.Button -import androidx.compose.material.ButtonDefaults import androidx.compose.material.CircularProgressIndicator import androidx.compose.material.Icon import androidx.compose.material.Text @@ -68,6 +66,7 @@ import one.mixin.android.ui.tip.wc.sessionrequest.FeeInfo import one.mixin.android.ui.tip.wc.sessionrequest.SessionRequestViewModel import one.mixin.android.ui.wallet.components.WalletLabel import one.mixin.android.util.ErrorHandler +import one.mixin.android.widget.components.MixinButton import one.mixin.android.vo.User import one.mixin.android.vo.priceUSD import one.mixin.android.vo.safe.Token @@ -439,20 +438,10 @@ fun BrowserPage( .fillMaxWidth(), horizontalArrangement = Arrangement.Center, ) { - Button( + MixinButton( onClick = onDismissRequest, - colors = - ButtonDefaults.outlinedButtonColors( - backgroundColor = MixinAppTheme.colors.accent, - ), shape = RoundedCornerShape(20.dp), contentPadding = PaddingValues(horizontal = 36.dp, vertical = 11.dp), - elevation = ButtonDefaults.elevation( - defaultElevation = 0.dp, - pressedElevation = 0.dp, - hoveredElevation = 0.dp, - focusedElevation = 0.dp, - ), ) { Text(text = stringResource(id = if (step == WalletConnectBottomSheetDialogFragment.Step.Done) R.string.Done else R.string.Got_it), color = Color.White) } diff --git a/app/src/main/java/one/mixin/android/ui/home/web3/trade/perps/PerpsCloseBottomSheetDialogFragment.kt b/app/src/main/java/one/mixin/android/ui/home/web3/trade/perps/PerpsCloseBottomSheetDialogFragment.kt index 6d0358fc7a..237c1b69b7 100644 --- a/app/src/main/java/one/mixin/android/ui/home/web3/trade/perps/PerpsCloseBottomSheetDialogFragment.kt +++ b/app/src/main/java/one/mixin/android/ui/home/web3/trade/perps/PerpsCloseBottomSheetDialogFragment.kt @@ -23,8 +23,6 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll -import androidx.compose.material.Button -import androidx.compose.material.ButtonDefaults import androidx.compose.material.CircularProgressIndicator import androidx.compose.material.Icon import androidx.compose.material.Text @@ -71,6 +69,7 @@ import one.mixin.android.ui.tip.wc.compose.ItemWalletContent import one.mixin.android.ui.wallet.ItemUserContent import one.mixin.android.ui.wallet.components.WalletLabel import one.mixin.android.util.SystemUIManager +import one.mixin.android.widget.components.MixinButton import one.mixin.android.vo.Fiats import one.mixin.android.vo.User import one.mixin.android.vo.safe.TokenItem @@ -475,22 +474,13 @@ class PerpsCloseBottomSheetDialogFragment : MixinComposeBottomSheetDialogFragmen .fillMaxWidth(), horizontalArrangement = Arrangement.Center, ) { - Button( + MixinButton( onClick = { onDoneAction?.invoke() dismiss() }, - colors = ButtonDefaults.outlinedButtonColors( - backgroundColor = MixinAppTheme.colors.accent, - ), shape = RoundedCornerShape(20.dp), contentPadding = PaddingValues(horizontal = 36.dp, vertical = 11.dp), - elevation = ButtonDefaults.elevation( - defaultElevation = 0.dp, - pressedElevation = 0.dp, - hoveredElevation = 0.dp, - focusedElevation = 0.dp, - ), ) { Text(text = stringResource(id = R.string.Done), color = Color.White) } diff --git a/app/src/main/java/one/mixin/android/ui/home/web3/trade/perps/PerpsConfirmBottomSheetDialogFragment.kt b/app/src/main/java/one/mixin/android/ui/home/web3/trade/perps/PerpsConfirmBottomSheetDialogFragment.kt index faf11ddfbb..2d9a3819b8 100644 --- a/app/src/main/java/one/mixin/android/ui/home/web3/trade/perps/PerpsConfirmBottomSheetDialogFragment.kt +++ b/app/src/main/java/one/mixin/android/ui/home/web3/trade/perps/PerpsConfirmBottomSheetDialogFragment.kt @@ -25,8 +25,6 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll -import androidx.compose.material.Button -import androidx.compose.material.ButtonDefaults import androidx.compose.material.CircularProgressIndicator import androidx.compose.material.Icon import androidx.compose.material.Text @@ -79,6 +77,7 @@ import one.mixin.android.ui.tip.wc.compose.ItemWalletContent import one.mixin.android.ui.wallet.ItemUserContent import one.mixin.android.ui.wallet.components.WalletLabel import one.mixin.android.util.SystemUIManager +import one.mixin.android.widget.components.MixinButton import one.mixin.android.vo.Fiats import one.mixin.android.vo.User import one.mixin.android.vo.toUser @@ -423,21 +422,12 @@ class PerpsConfirmBottomSheetDialogFragment : MixinComposeBottomSheetDialogFragm .fillMaxWidth(), horizontalArrangement = Arrangement.Center, ) { - Button( + MixinButton( onClick = { onDoneAction?.invoke() dismiss() }, - colors = ButtonDefaults.outlinedButtonColors( - backgroundColor = MixinAppTheme.colors.accent, - ), shape = RoundedCornerShape(20.dp), - elevation = ButtonDefaults.elevation( - pressedElevation = 0.dp, - defaultElevation = 0.dp, - hoveredElevation = 0.dp, - focusedElevation = 0.dp - ), contentPadding = PaddingValues(horizontal = 36.dp, vertical = 11.dp), ) { Text(text = stringResource(id = R.string.Done), color = Color.White) diff --git a/app/src/main/java/one/mixin/android/ui/landing/CreateAccountConfirmBottomSheetDialogFragment.kt b/app/src/main/java/one/mixin/android/ui/landing/CreateAccountConfirmBottomSheetDialogFragment.kt index e7486db140..ff11776290 100644 --- a/app/src/main/java/one/mixin/android/ui/landing/CreateAccountConfirmBottomSheetDialogFragment.kt +++ b/app/src/main/java/one/mixin/android/ui/landing/CreateAccountConfirmBottomSheetDialogFragment.kt @@ -18,8 +18,6 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.Button -import androidx.compose.material.ButtonDefaults import androidx.compose.material.Icon import androidx.compose.material.Text import androidx.compose.runtime.Composable @@ -44,6 +42,7 @@ import one.mixin.android.extension.screenHeight import one.mixin.android.extension.toast import one.mixin.android.ui.common.MixinComposeBottomSheetDialogFragment import one.mixin.android.ui.landing.components.HighlightedTextWithClick +import one.mixin.android.widget.components.MixinButton import one.mixin.android.util.SystemUIManager @AndroidEntryPoint @@ -134,22 +133,15 @@ class CreateAccountConfirmBottomSheetDialogFragment : MixinComposeBottomSheetDia descriptionResId = R.string.feature_all_in_one_description, ) Spacer(modifier = Modifier.weight(1f)) - Button( + MixinButton( modifier = Modifier .fillMaxWidth(), onClick = { onCreateAccount?.invoke() dismiss() }, - colors = ButtonDefaults.outlinedButtonColors(backgroundColor = MixinAppTheme.colors.accent), shape = androidx.compose.foundation.shape.RoundedCornerShape(20.dp), contentPadding = androidx.compose.foundation.layout.PaddingValues(horizontal = 36.dp, vertical = 11.dp), - elevation = ButtonDefaults.elevation( - defaultElevation = 0.dp, - pressedElevation = 0.dp, - hoveredElevation = 0.dp, - focusedElevation = 0.dp, - ), ) { Text(text = stringResource(R.string.create_account_confirm_action_create), color = Color.White) } diff --git a/app/src/main/java/one/mixin/android/ui/logs/LogViewerScreen.kt b/app/src/main/java/one/mixin/android/ui/logs/LogViewerScreen.kt index 1326546514..9f56a6efe1 100644 --- a/app/src/main/java/one/mixin/android/ui/logs/LogViewerScreen.kt +++ b/app/src/main/java/one/mixin/android/ui/logs/LogViewerScreen.kt @@ -44,6 +44,7 @@ import one.mixin.android.R import one.mixin.android.compose.theme.MixinAppTheme import one.mixin.android.extension.toast +import one.mixin.android.widget.components.MixinButton import java.io.File @Composable @@ -133,24 +134,15 @@ fun LogViewerScreen( ) { Text(text = stringResource(R.string.Share), color = MixinAppTheme.colors.accent) } - Button( + MixinButton( modifier = Modifier .weight(1f) .height(48.dp), - colors = ButtonDefaults.outlinedButtonColors( - backgroundColor = MixinAppTheme.colors.accent - ), onClick = { clipboardManager.setText(AnnotatedString(state.content)) toast(R.string.copied_to_clipboard) }, shape = RoundedCornerShape(32.dp), - elevation = ButtonDefaults.elevation( - defaultElevation = 0.dp, - pressedElevation = 0.dp, - hoveredElevation = 0.dp, - focusedElevation = 0.dp, - ), ) { Text(text = stringResource(R.string.Copy), color = Color.White) } diff --git a/app/src/main/java/one/mixin/android/ui/tip/wc/sessionproposal/SessionProposalPage.kt b/app/src/main/java/one/mixin/android/ui/tip/wc/sessionproposal/SessionProposalPage.kt index 119baa957b..743635ed87 100644 --- a/app/src/main/java/one/mixin/android/ui/tip/wc/sessionproposal/SessionProposalPage.kt +++ b/app/src/main/java/one/mixin/android/ui/tip/wc/sessionproposal/SessionProposalPage.kt @@ -13,8 +13,6 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.Button -import androidx.compose.material.ButtonDefaults import androidx.compose.material.CircularProgressIndicator import androidx.compose.material.Icon import androidx.compose.material.Text @@ -51,6 +49,7 @@ import one.mixin.android.ui.tip.wc.WalletConnectBottomSheetDialogFragment import one.mixin.android.ui.tip.wc.compose.ItemContent import one.mixin.android.ui.tip.wc.compose.Loading import one.mixin.android.ui.wallet.components.WalletLabel +import one.mixin.android.widget.components.MixinButton import one.mixin.android.web3.js.Web3Signer @Composable @@ -219,20 +218,10 @@ fun SessionProposalPage( .fillMaxWidth(), horizontalArrangement = Arrangement.Center, ) { - Button( + MixinButton( onClick = onDismissRequest, - colors = - ButtonDefaults.outlinedButtonColors( - backgroundColor = MixinAppTheme.colors.accent, - ), shape = RoundedCornerShape(20.dp), contentPadding = PaddingValues(horizontal = 36.dp, vertical = 11.dp), - elevation = ButtonDefaults.elevation( - defaultElevation = 0.dp, - pressedElevation = 0.dp, - hoveredElevation = 0.dp, - focusedElevation = 0.dp, - ), ) { Text(text = stringResource(id = R.string.Done), color = Color.White) } diff --git a/app/src/main/java/one/mixin/android/ui/tip/wc/sessionproposal/WCPinBoard.kt b/app/src/main/java/one/mixin/android/ui/tip/wc/sessionproposal/WCPinBoard.kt index 3030dc7a02..5b5384cecb 100644 --- a/app/src/main/java/one/mixin/android/ui/tip/wc/sessionproposal/WCPinBoard.kt +++ b/app/src/main/java/one/mixin/android/ui/tip/wc/sessionproposal/WCPinBoard.kt @@ -37,8 +37,6 @@ import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.Button -import androidx.compose.material.ButtonDefaults import androidx.compose.material.CircularProgressIndicator import androidx.compose.material.Icon import androidx.compose.material.Text @@ -78,6 +76,7 @@ import one.mixin.android.extension.toast import one.mixin.android.session.Session import one.mixin.android.ui.tip.wc.WalletConnectBottomSheetDialogFragment.Step import one.mixin.android.util.BiometricUtil +import one.mixin.android.widget.components.MixinButton @Composable fun WCPinBoard( @@ -180,20 +179,10 @@ fun WCPinBoard( Box(modifier = Modifier.height(12.dp)) Text(text = stringResource(R.string.Success), color = MixinAppTheme.colors.textMinor) Box(modifier = Modifier.height(40.dp)) - Button( + MixinButton( onClick = { onDoneClick.invoke() }, - colors = - ButtonDefaults.outlinedButtonColors( - backgroundColor = MixinAppTheme.colors.accent, - ), contentPadding = PaddingValues(horizontal = 28.dp), shape = RoundedCornerShape(40.dp), - elevation = ButtonDefaults.elevation( - defaultElevation = 0.dp, - pressedElevation = 0.dp, - hoveredElevation = 0.dp, - focusedElevation = 0.dp, - ), ) { Text( text = stringResource(id = R.string.Done), @@ -211,25 +200,15 @@ fun WCPinBoard( verticalArrangement = Arrangement.SpaceEvenly, ) { Box(modifier = Modifier.height(20.dp)) - Button( + MixinButton( modifier = Modifier.widthIn(min = 100.dp), onClick = { if (!signUnavailable) { onPositiveClick.invoke() } }, - colors = - ButtonDefaults.outlinedButtonColors( - backgroundColor = MixinAppTheme.colors.accent, - ), contentPadding = PaddingValues(horizontal = 28.dp, vertical = 11.dp), shape = RoundedCornerShape(40.dp), - elevation = ButtonDefaults.elevation( - defaultElevation = 0.dp, - pressedElevation = 0.dp, - hoveredElevation = 0.dp, - focusedElevation = 0.dp, - ), ) { if (signUnavailable) { CircularProgressIndicator( diff --git a/app/src/main/java/one/mixin/android/ui/tip/wc/sessionrequest/SessionRequestPage.kt b/app/src/main/java/one/mixin/android/ui/tip/wc/sessionrequest/SessionRequestPage.kt index ca1b9020a8..d4b1be2e25 100644 --- a/app/src/main/java/one/mixin/android/ui/tip/wc/sessionrequest/SessionRequestPage.kt +++ b/app/src/main/java/one/mixin/android/ui/tip/wc/sessionrequest/SessionRequestPage.kt @@ -17,8 +17,6 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll -import androidx.compose.material.Button -import androidx.compose.material.ButtonDefaults import androidx.compose.material.CircularProgressIndicator import androidx.compose.material.Icon import androidx.compose.material.Text @@ -72,6 +70,7 @@ import one.mixin.android.ui.tip.wc.WalletConnectBottomSheetDialogFragment import one.mixin.android.ui.tip.wc.compose.ItemContent import one.mixin.android.ui.tip.wc.compose.Loading import one.mixin.android.ui.wallet.components.WalletLabel +import one.mixin.android.widget.components.MixinButton import one.mixin.android.vo.priceUSD import one.mixin.android.vo.safe.Token import one.mixin.android.web3.js.Web3Signer @@ -379,20 +378,10 @@ fun SessionRequestPage( .fillMaxWidth(), horizontalArrangement = Arrangement.Center, ) { - Button( + MixinButton( onClick = onDismissRequest, - colors = - ButtonDefaults.outlinedButtonColors( - backgroundColor = MixinAppTheme.colors.accent, - ), shape = RoundedCornerShape(20.dp), contentPadding = PaddingValues(horizontal = 36.dp, vertical = 11.dp), - elevation = ButtonDefaults.elevation( - defaultElevation = 0.dp, - pressedElevation = 0.dp, - hoveredElevation = 0.dp, - focusedElevation = 0.dp, - ), ) { Text(text = stringResource(id = R.string.Done), color = Color.White) } diff --git a/app/src/main/java/one/mixin/android/ui/wallet/CrossWalletFeeFreeBottomSheetDialogFragment.kt b/app/src/main/java/one/mixin/android/ui/wallet/CrossWalletFeeFreeBottomSheetDialogFragment.kt index d3ccb4bfd5..f2c443714b 100644 --- a/app/src/main/java/one/mixin/android/ui/wallet/CrossWalletFeeFreeBottomSheetDialogFragment.kt +++ b/app/src/main/java/one/mixin/android/ui/wallet/CrossWalletFeeFreeBottomSheetDialogFragment.kt @@ -14,8 +14,6 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.Button -import androidx.compose.material.ButtonDefaults import androidx.compose.material.Text import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -41,6 +39,7 @@ import one.mixin.android.extension.roundTopOrBottom import one.mixin.android.ui.landing.components.HighlightedTextWithClick import one.mixin.android.ui.landing.components.NumberedText import one.mixin.android.util.SystemUIManager +import one.mixin.android.widget.components.MixinButton import one.mixin.android.extension.dp as dip class CrossWalletFeeFreeBottomSheetDialogFragment : BottomSheetDialogFragment() { @@ -114,19 +113,10 @@ class CrossWalletFeeFreeBottomSheetDialogFragment : BottomSheetDialogFragment() context.openUrl(requireContext().getString(R.string.url_cross_wallet_transaction_free)) } Spacer(modifier = Modifier.height(24.dp)) - Button( + MixinButton( onClick = { dismiss() }, - colors = ButtonDefaults.outlinedButtonColors( - backgroundColor = MixinAppTheme.colors.accent, - ), shape = RoundedCornerShape(20.dp), contentPadding = PaddingValues(horizontal = 36.dp, vertical = 11.dp), - elevation = ButtonDefaults.elevation( - defaultElevation = 0.dp, - pressedElevation = 0.dp, - hoveredElevation = 0.dp, - focusedElevation = 0.dp, - ), ) { Text(text = stringResource(R.string.Got_it), color = Color.White) } diff --git a/app/src/main/java/one/mixin/android/ui/wallet/ImportKeyPage.kt b/app/src/main/java/one/mixin/android/ui/wallet/ImportKeyPage.kt index 823c0f16fc..43f727b77a 100644 --- a/app/src/main/java/one/mixin/android/ui/wallet/ImportKeyPage.kt +++ b/app/src/main/java/one/mixin/android/ui/wallet/ImportKeyPage.kt @@ -10,8 +10,6 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.Button -import androidx.compose.material.ButtonDefaults import androidx.compose.material.Text import androidx.compose.material.TextButton import androidx.compose.runtime.Composable @@ -28,6 +26,7 @@ import one.mixin.android.R import one.mixin.android.compose.theme.MixinAppTheme import one.mixin.android.ui.landing.components.HighlightedTextWithClick +import one.mixin.android.widget.components.MixinButton @Composable fun ImportKeyPage( @@ -69,23 +68,12 @@ fun ImportKeyPage( } Spacer(modifier = Modifier.height(50.dp)) Spacer(modifier = Modifier.height(24.dp)) - Button( + MixinButton( modifier = Modifier .fillMaxWidth() .height(48.dp), onClick = action, - colors = - ButtonDefaults.outlinedButtonColors( - backgroundColor = MixinAppTheme.colors.accent - ), shape = RoundedCornerShape(32.dp), - elevation = - ButtonDefaults.elevation( - pressedElevation = 0.dp, - defaultElevation = 0.dp, - hoveredElevation = 0.dp, - focusedElevation = 0.dp, - ), ) { Text(text = stringResource(id = R.string.Import), color = Color.White) } diff --git a/app/src/main/java/one/mixin/android/ui/wallet/LightningAddressBottomSheetDialogFragment.kt b/app/src/main/java/one/mixin/android/ui/wallet/LightningAddressBottomSheetDialogFragment.kt index e6c64eeb50..ad761cf898 100644 --- a/app/src/main/java/one/mixin/android/ui/wallet/LightningAddressBottomSheetDialogFragment.kt +++ b/app/src/main/java/one/mixin/android/ui/wallet/LightningAddressBottomSheetDialogFragment.kt @@ -13,8 +13,6 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.Button -import androidx.compose.material.ButtonDefaults import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment @@ -39,6 +37,7 @@ import one.mixin.android.ui.common.MixinComposeBottomSheetDialogFragment import one.mixin.android.ui.landing.components.HighlightedTextWithClick import one.mixin.android.ui.landing.components.NumberedText import one.mixin.android.util.SystemUIManager +import one.mixin.android.widget.components.MixinButton import one.mixin.android.extension.dp as dip class LightningAddressBottomSheetDialogFragment : MixinComposeBottomSheetDialogFragment() { @@ -118,25 +117,15 @@ class LightningAddressBottomSheetDialogFragment : MixinComposeBottomSheetDialogF context?.openUrl(getString(R.string.Lightning_link)) } Spacer(modifier = Modifier.height(120.dp)) - Button( + MixinButton( onClick = { context?.heavyClickVibrate() context?.getClipboardManager()?.setPrimaryClip(ClipData.newPlainText(null, address)) toast(R.string.copied_to_clipboard) dismiss() }, - colors = - ButtonDefaults.outlinedButtonColors( - backgroundColor = MixinAppTheme.colors.accent, - ), shape = RoundedCornerShape(20.dp), contentPadding = PaddingValues(horizontal = 36.dp, vertical = 11.dp), - elevation = ButtonDefaults.elevation( - defaultElevation = 0.dp, - pressedElevation = 0.dp, - hoveredElevation = 0.dp, - focusedElevation = 0.dp, - ), ) { Text(text = stringResource(R.string.Copy_Address), color = Color.White) } 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 01432e9ac3..99d1548adb 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 @@ -25,8 +25,6 @@ import androidx.compose.foundation.layout.wrapContentWidth import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.verticalScroll -import androidx.compose.material.Button -import androidx.compose.material.ButtonDefaults import androidx.compose.material.CircularProgressIndicator import androidx.compose.material.Icon import androidx.compose.material.Text @@ -100,6 +98,7 @@ import one.mixin.android.ui.tip.wc.compose.ItemWalletContent import one.mixin.android.ui.url.UrlInterpreterActivity import one.mixin.android.util.ErrorHandler import one.mixin.android.util.SystemUIManager +import one.mixin.android.widget.components.MixinButton import one.mixin.android.util.analytics.AnalyticsTracker import one.mixin.android.util.getMixinErrorStringByCode import one.mixin.android.util.reportException @@ -384,17 +383,10 @@ class LimitTransferBottomSheetDialogFragment : MixinComposeBottomSheetDialogFrag .fillMaxWidth(), horizontalArrangement = androidx.compose.foundation.layout.Arrangement.Center, ) { - Button( + MixinButton( onClick = { onDoneAction?.invoke(); dismiss() }, - colors = ButtonDefaults.outlinedButtonColors(backgroundColor = MixinAppTheme.colors.accent), shape = androidx.compose.foundation.shape.RoundedCornerShape(20.dp), contentPadding = androidx.compose.foundation.layout.PaddingValues(horizontal = 36.dp, vertical = 11.dp), - elevation = ButtonDefaults.elevation( - defaultElevation = 0.dp, - pressedElevation = 0.dp, - hoveredElevation = 0.dp, - focusedElevation = 0.dp, - ), ) { Text(text = stringResource(id = R.string.Done), color = Color.White) } } } 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 83caa52626..48f1c83856 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 @@ -31,8 +31,6 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll -import androidx.compose.material.Button -import androidx.compose.material.ButtonDefaults import androidx.compose.material.CircularProgressIndicator import androidx.compose.material.Icon import androidx.compose.material.Text @@ -118,6 +116,7 @@ import one.mixin.android.ui.url.UrlInterpreterActivity import one.mixin.android.ui.wallet.components.WalletLabel import one.mixin.android.util.ErrorHandler import one.mixin.android.util.SystemUIManager +import one.mixin.android.widget.components.MixinButton import one.mixin.android.util.analytics.AnalyticsTracker import one.mixin.android.util.getMixinErrorStringByCode import one.mixin.android.util.reportException @@ -532,23 +531,13 @@ class SwapTransferBottomSheetDialogFragment : MixinComposeBottomSheetDialogFragm .fillMaxWidth(), horizontalArrangement = Arrangement.Center, ) { - Button( + MixinButton( onClick = { onDoneAction?.invoke() dismiss() }, - colors = - ButtonDefaults.outlinedButtonColors( - backgroundColor = MixinAppTheme.colors.accent, - ), shape = RoundedCornerShape(20.dp), contentPadding = PaddingValues(horizontal = 36.dp, vertical = 11.dp), - elevation = ButtonDefaults.elevation( - defaultElevation = 0.dp, - pressedElevation = 0.dp, - hoveredElevation = 0.dp, - focusedElevation = 0.dp, - ), ) { Text(text = stringResource(id = R.string.Done), color = Color.White) } diff --git a/app/src/main/java/one/mixin/android/widget/components/MaterialButton.kt b/app/src/main/java/one/mixin/android/widget/components/MaterialButton.kt index e9d663c63f..40f77f5161 100644 --- a/app/src/main/java/one/mixin/android/widget/components/MaterialButton.kt +++ b/app/src/main/java/one/mixin/android/widget/components/MaterialButton.kt @@ -10,6 +10,7 @@ import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Shape import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import one.mixin.android.compose.theme.MixinAppTheme @@ -46,30 +47,34 @@ fun MixinButton( onClick: () -> Unit, modifier: Modifier = Modifier, enabled: Boolean = true, - shape: RoundedCornerShape = RoundedCornerShape(20.dp), + shape: Shape = RoundedCornerShape(20.dp), contentPadding: PaddingValues = PaddingValues(horizontal = 36.dp, vertical = 11.dp), elevation: Dp = 0.dp, backgroundColor: Color = MixinAppTheme.colors.accent, contentColor: Color = Color.White, + disabledBackgroundColor: Color = backgroundColor.copy(alpha = 0.4f), + disabledContentColor: Color = contentColor.copy(alpha = 0.6f), content: @Composable () -> Unit, ) { Button( onClick = onClick, modifier = modifier, enabled = enabled, - colors = ButtonDefaults.outlinedButtonColors( + colors = ButtonDefaults.buttonColors( backgroundColor = backgroundColor, contentColor = contentColor, + disabledBackgroundColor = disabledBackgroundColor, + disabledContentColor = disabledContentColor, ), shape = shape, contentPadding = contentPadding, elevation = ButtonDefaults.elevation( defaultElevation = elevation, - pressedElevation = 0.dp, + pressedElevation = elevation, hoveredElevation = elevation, focusedElevation = elevation, ), ) { content() } -} \ No newline at end of file +} From 4d59c47d5fb1ca7b798d98a1d99a53f7774d3578 Mon Sep 17 00:00:00 2001 From: SeniorZhai Date: Wed, 25 Mar 2026 11:15:52 +0800 Subject: [PATCH 06/35] style: drop explicit normal button weights --- .../home/web3/trade/perps/PerpetualGuidePage.kt | 16 ++-------------- .../ui/wallet/components/FetchWalletPage.kt | 1 - 2 files changed, 2 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/one/mixin/android/ui/home/web3/trade/perps/PerpetualGuidePage.kt b/app/src/main/java/one/mixin/android/ui/home/web3/trade/perps/PerpetualGuidePage.kt index 33126a3648..5b3708fa83 100644 --- a/app/src/main/java/one/mixin/android/ui/home/web3/trade/perps/PerpetualGuidePage.kt +++ b/app/src/main/java/one/mixin/android/ui/home/web3/trade/perps/PerpetualGuidePage.kt @@ -19,8 +19,6 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll -import androidx.compose.material.Button -import androidx.compose.material.ButtonDefaults import androidx.compose.material.Icon import androidx.compose.material.Text import androidx.compose.runtime.Composable @@ -51,6 +49,7 @@ import one.mixin.android.extension.defaultSharedPreferences import one.mixin.android.ui.home.web3.components.OutlinedTab import one.mixin.android.ui.wallet.alert.components.cardBackground import one.mixin.android.widget.components.DotText +import one.mixin.android.widget.components.MixinButton import java.math.BigDecimal import java.math.RoundingMode import kotlin.math.roundToInt @@ -541,25 +540,14 @@ private fun GuideNavigationButton( modifier: Modifier = Modifier, onClick: () -> Unit, ) { - Button( + MixinButton( modifier = modifier.height(48.dp), onClick = onClick, - colors = ButtonDefaults.outlinedButtonColors( - backgroundColor = MixinAppTheme.colors.accent, - contentColor = Color.White, - ), shape = RoundedCornerShape(32.dp), - elevation = ButtonDefaults.elevation( - pressedElevation = 0.dp, - defaultElevation = 0.dp, - hoveredElevation = 0.dp, - focusedElevation = 0.dp, - ), ) { Text( text = text, fontSize = 16.sp, - fontWeight = FontWeight.Bold, color = Color.White, ) } diff --git a/app/src/main/java/one/mixin/android/ui/wallet/components/FetchWalletPage.kt b/app/src/main/java/one/mixin/android/ui/wallet/components/FetchWalletPage.kt index 10e6f91a79..32b3cea6fc 100644 --- a/app/src/main/java/one/mixin/android/ui/wallet/components/FetchWalletPage.kt +++ b/app/src/main/java/one/mixin/android/ui/wallet/components/FetchWalletPage.kt @@ -342,7 +342,6 @@ fun ImportErrorContent( Text( text = stringResource(id = R.string.Done), color = MixinAppTheme.colors.accent, - fontWeight = FontWeight.Medium, ) } } else { From 262d179b4ca5190cf6b774999d6dd3e286ed4b18 Mon Sep 17 00:00:00 2001 From: SeniorZhai Date: Wed, 25 Mar 2026 11:45:01 +0800 Subject: [PATCH 07/35] fix: exclude zero-volume perps markets --- .../java/one/mixin/android/db/perps/PerpsMarketDao.kt | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/one/mixin/android/db/perps/PerpsMarketDao.kt b/app/src/main/java/one/mixin/android/db/perps/PerpsMarketDao.kt index f3209c08cd..62fa5bb224 100644 --- a/app/src/main/java/one/mixin/android/db/perps/PerpsMarketDao.kt +++ b/app/src/main/java/one/mixin/android/db/perps/PerpsMarketDao.kt @@ -23,10 +23,10 @@ interface PerpsMarketDao : BaseDao { insertAll(markets) } - @Query("SELECT * FROM markets ORDER BY rowid ASC") + @Query("SELECT * FROM markets WHERE CAST(volume AS REAL) > 0 ORDER BY rowid ASC") suspend fun getAllMarkets(): List - @Query("SELECT * FROM markets ORDER BY rowid ASC") + @Query("SELECT * FROM markets WHERE CAST(volume AS REAL) > 0 ORDER BY rowid ASC") fun observeAllMarkets(): Flow> @Query("SELECT * FROM markets WHERE market_id = :marketId") @@ -35,9 +35,12 @@ interface PerpsMarketDao : BaseDao { @Query( """ SELECT * FROM markets - WHERE display_symbol LIKE '%' || :query || '%' + WHERE CAST(volume AS REAL) > 0 + AND ( + display_symbol LIKE '%' || :query || '%' OR token_symbol LIKE '%' || :query || '%' OR quote_symbol LIKE '%' || :query || '%' + ) ORDER BY CAST(volume AS REAL) DESC """ ) From b1bc4fbaa5bc2158142c8a1726f5db3922bfa91a Mon Sep 17 00:00:00 2001 From: SeniorZhai Date: Wed, 25 Mar 2026 12:35:23 +0800 Subject: [PATCH 08/35] fix: convert perps market change to percentage correctly --- .../android/ui/home/web3/trade/perps/PerpsMarketDetailPage.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/one/mixin/android/ui/home/web3/trade/perps/PerpsMarketDetailPage.kt b/app/src/main/java/one/mixin/android/ui/home/web3/trade/perps/PerpsMarketDetailPage.kt index 2e04f0fbeb..7f979bb540 100644 --- a/app/src/main/java/one/mixin/android/ui/home/web3/trade/perps/PerpsMarketDetailPage.kt +++ b/app/src/main/java/one/mixin/android/ui/home/web3/trade/perps/PerpsMarketDetailPage.kt @@ -515,14 +515,14 @@ private fun MarketDetailCard( val fiatSymbol = Fiats.getSymbol() val change = try { - BigDecimal(market.change) + BigDecimal(market.change).multiply(BigDecimal(100)) } catch (e: Exception) { BigDecimal.ZERO } val isPositive = change >= BigDecimal.ZERO val changeColor = if (isPositive) risingColor else fallingColor - val changeText = "${if (isPositive) "+" else ""}${market.change}%" + val changeText = "${if (isPositive) "+" else ""}${change}%" val displayTokenSymbol = tokenSymbol .takeIf { it.isNotBlank() } ?: market.tokenSymbol.takeIf { it.isNotBlank() } From e3a451e48e6d0b0314a7431880168b360105481c Mon Sep 17 00:00:00 2001 From: SeniorZhai Date: Wed, 25 Mar 2026 13:12:46 +0800 Subject: [PATCH 09/35] fix: use upsert to preserve rowid in perps tables --- .../java/one/mixin/android/db/perps/PerpsMarketDao.kt | 6 ------ .../ui/home/web3/trade/perps/PerpetualViewModel.kt | 10 +++++----- .../ui/home/web3/trade/perps/PerpsMarketDetailPage.kt | 2 +- 3 files changed, 6 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/one/mixin/android/db/perps/PerpsMarketDao.kt b/app/src/main/java/one/mixin/android/db/perps/PerpsMarketDao.kt index 62fa5bb224..0cb7fee00c 100644 --- a/app/src/main/java/one/mixin/android/db/perps/PerpsMarketDao.kt +++ b/app/src/main/java/one/mixin/android/db/perps/PerpsMarketDao.kt @@ -17,12 +17,6 @@ interface PerpsMarketDao : BaseDao { @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insertAll(markets: List) - @Transaction - suspend fun replaceAll(markets: List) { - deleteAll() - insertAll(markets) - } - @Query("SELECT * FROM markets WHERE CAST(volume AS REAL) > 0 ORDER BY rowid ASC") suspend fun getAllMarkets(): List diff --git a/app/src/main/java/one/mixin/android/ui/home/web3/trade/perps/PerpetualViewModel.kt b/app/src/main/java/one/mixin/android/ui/home/web3/trade/perps/PerpetualViewModel.kt index 43a94380c9..aeff70161c 100644 --- a/app/src/main/java/one/mixin/android/ui/home/web3/trade/perps/PerpetualViewModel.kt +++ b/app/src/main/java/one/mixin/android/ui/home/web3/trade/perps/PerpetualViewModel.kt @@ -102,7 +102,7 @@ class PerpetualViewModel @Inject constructor( Timber.d("Perps markets loaded: ${data.size} markets") val orderedMarkets = withContext(Dispatchers.IO) { - perpsMarketDao.replaceAll(data) + perpsMarketDao.upsertList(data) perpsMarketDao.getAllMarkets() } @@ -144,7 +144,7 @@ class PerpetualViewModel @Inject constructor( val data = response.data if (response.isSuccess && data != null) { withContext(Dispatchers.IO) { - perpsMarketDao.replaceAll(data) + perpsMarketDao.upsertList(data) } Timber.d("Perps markets refreshed: ${data.size} markets") } else { @@ -332,7 +332,7 @@ class PerpetualViewModel @Inject constructor( ) withContext(Dispatchers.IO) { - perpsPositionDao.insert(position) + perpsPositionDao.upsertSuspend(position) } onSuccess(data) @@ -390,7 +390,7 @@ class PerpetualViewModel @Inject constructor( val remotePosition = response.data withContext(Dispatchers.IO) { if (remotePosition != null) { - perpsPositionDao.insert( + perpsPositionDao.upsertSuspend( remotePosition.copy( walletId = walletId ?: remotePosition.walletId ) @@ -661,7 +661,7 @@ class PerpetualViewModel @Inject constructor( val positionForDb = data.copy(walletId = resolvedWalletId) withContext(Dispatchers.IO) { - perpsPositionDao.insert(positionForDb) + perpsPositionDao.upsertSuspend(positionForDb) } onSuccess(positionForDb) diff --git a/app/src/main/java/one/mixin/android/ui/home/web3/trade/perps/PerpsMarketDetailPage.kt b/app/src/main/java/one/mixin/android/ui/home/web3/trade/perps/PerpsMarketDetailPage.kt index 7f979bb540..374dc9c0a4 100644 --- a/app/src/main/java/one/mixin/android/ui/home/web3/trade/perps/PerpsMarketDetailPage.kt +++ b/app/src/main/java/one/mixin/android/ui/home/web3/trade/perps/PerpsMarketDetailPage.kt @@ -515,7 +515,7 @@ private fun MarketDetailCard( val fiatSymbol = Fiats.getSymbol() val change = try { - BigDecimal(market.change).multiply(BigDecimal(100)) + BigDecimal(market.change).multiply(BigDecimal(100)).stripTrailingZeros() } catch (e: Exception) { BigDecimal.ZERO } From 38d5ef5f6c7a42165c87ebf26cacf90150f605d7 Mon Sep 17 00:00:00 2001 From: SeniorZhai Date: Wed, 25 Mar 2026 13:24:05 +0800 Subject: [PATCH 10/35] refactor: unify perps market change percent calculation and formatting Co-Authored-By: Claude Opus 4.6 --- .../android/ui/home/web3/trade/perps/PerpsFormat.kt | 9 +++++++++ .../ui/home/web3/trade/perps/PerpsMarketDetailPage.kt | 11 +++-------- .../ui/home/web3/trade/perps/PerpsMarketItem.kt | 10 ++-------- .../home/web3/trade/perps/PerpsMarketListAdapter.kt | 10 ++-------- 4 files changed, 16 insertions(+), 24 deletions(-) diff --git a/app/src/main/java/one/mixin/android/ui/home/web3/trade/perps/PerpsFormat.kt b/app/src/main/java/one/mixin/android/ui/home/web3/trade/perps/PerpsFormat.kt index 9ba70f522c..9c726a2f02 100644 --- a/app/src/main/java/one/mixin/android/ui/home/web3/trade/perps/PerpsFormat.kt +++ b/app/src/main/java/one/mixin/android/ui/home/web3/trade/perps/PerpsFormat.kt @@ -1,10 +1,19 @@ package one.mixin.android.ui.home.web3.trade.perps +import one.mixin.android.api.response.perps.PerpsMarket import java.math.BigDecimal import java.math.RoundingMode private val perpsMinDisplayValue = BigDecimal("0.01") +fun PerpsMarket.changePercent(): BigDecimal { + return try { + BigDecimal(change).multiply(BigDecimal(100)) + } catch (e: Exception) { + BigDecimal.ZERO + } +} + fun formatPerpsDisplayDecimal(value: BigDecimal?): String { val safeValue = value ?: BigDecimal.ZERO val absValue = safeValue.abs() diff --git a/app/src/main/java/one/mixin/android/ui/home/web3/trade/perps/PerpsMarketDetailPage.kt b/app/src/main/java/one/mixin/android/ui/home/web3/trade/perps/PerpsMarketDetailPage.kt index 374dc9c0a4..230a49c319 100644 --- a/app/src/main/java/one/mixin/android/ui/home/web3/trade/perps/PerpsMarketDetailPage.kt +++ b/app/src/main/java/one/mixin/android/ui/home/web3/trade/perps/PerpsMarketDetailPage.kt @@ -514,15 +514,10 @@ private fun MarketDetailCard( val fiatRate = BigDecimal(Fiats.getRate()) val fiatSymbol = Fiats.getSymbol() - val change = try { - BigDecimal(market.change).multiply(BigDecimal(100)).stripTrailingZeros() - } catch (e: Exception) { - BigDecimal.ZERO - } - - val isPositive = change >= BigDecimal.ZERO + val changePercent = market.changePercent() + val isPositive = changePercent >= BigDecimal.ZERO val changeColor = if (isPositive) risingColor else fallingColor - val changeText = "${if (isPositive) "+" else ""}${change}%" + val changeText = formatPerpsSignedPercent(changePercent) val displayTokenSymbol = tokenSymbol .takeIf { it.isNotBlank() } ?: market.tokenSymbol.takeIf { it.isNotBlank() } diff --git a/app/src/main/java/one/mixin/android/ui/home/web3/trade/perps/PerpsMarketItem.kt b/app/src/main/java/one/mixin/android/ui/home/web3/trade/perps/PerpsMarketItem.kt index 7b2de25137..cb2d93ce23 100644 --- a/app/src/main/java/one/mixin/android/ui/home/web3/trade/perps/PerpsMarketItem.kt +++ b/app/src/main/java/one/mixin/android/ui/home/web3/trade/perps/PerpsMarketItem.kt @@ -36,14 +36,8 @@ fun PerpsMarketItem( quoteColorReversed: Boolean = false, onClick: () -> Unit = {} ) { - val change = try { - BigDecimal(market.change) - } catch (e: Exception) { - BigDecimal.ZERO - } - val changePercent = change.multiply(BigDecimal(100)) - - val isPositive = change >= BigDecimal.ZERO + val changePercent = market.changePercent() + val isPositive = changePercent >= BigDecimal.ZERO val changeColor = if (isPositive) { if (quoteColorReversed) { MixinAppTheme.colors.walletRed diff --git a/app/src/main/java/one/mixin/android/ui/home/web3/trade/perps/PerpsMarketListAdapter.kt b/app/src/main/java/one/mixin/android/ui/home/web3/trade/perps/PerpsMarketListAdapter.kt index c425905eb3..e5adce44de 100644 --- a/app/src/main/java/one/mixin/android/ui/home/web3/trade/perps/PerpsMarketListAdapter.kt +++ b/app/src/main/java/one/mixin/android/ui/home/web3/trade/perps/PerpsMarketListAdapter.kt @@ -60,14 +60,8 @@ class PerpsMarketListAdapter( } priceTv.text = "$fiatSymbol$formattedPrice" - val change = try { - BigDecimal(market.change) - } catch (e: Exception) { - BigDecimal.ZERO - } - val changePercent = change.multiply(BigDecimal(100)) - - val isPositive = change >= BigDecimal.ZERO + val changePercent = market.changePercent() + val isPositive = changePercent >= BigDecimal.ZERO val changeColor = ContextCompat.getColor( root.context, if (isPositive) { From 77a00814ac85c6508dd564753f3721f7f2f59d3b Mon Sep 17 00:00:00 2001 From: SeniorZhai Date: Wed, 25 Mar 2026 13:58:55 +0800 Subject: [PATCH 11/35] Update strings --- .../ui/home/web3/trade/SpotTradeGuidePage.kt | 32 +++++------ app/src/main/res/values-zh-rCN/strings.xml | 48 ++++++++-------- app/src/main/res/values/strings.xml | 56 +++++++++---------- 3 files changed, 63 insertions(+), 73 deletions(-) diff --git a/app/src/main/java/one/mixin/android/ui/home/web3/trade/SpotTradeGuidePage.kt b/app/src/main/java/one/mixin/android/ui/home/web3/trade/SpotTradeGuidePage.kt index 53bdabab66..df550e09b6 100644 --- a/app/src/main/java/one/mixin/android/ui/home/web3/trade/SpotTradeGuidePage.kt +++ b/app/src/main/java/one/mixin/android/ui/home/web3/trade/SpotTradeGuidePage.kt @@ -83,7 +83,7 @@ fun SpotTradeGuidePage( ) { val coroutineScope = rememberCoroutineScope() val tabs = listOf( - stringResource(R.string.Overview), + stringResource(R.string.Brief_Introduction), stringResource(R.string.Trade_Simple), stringResource(R.string.Trade_Advanced), ) @@ -173,16 +173,15 @@ fun SpotTradeGuidePage( @Composable private fun OverviewContent() { TradeGuideInfoCard( - title = stringResource(R.string.Perpetual_Guide_Overview_Title), + title = stringResource(R.string.Overview), description = stringResource(R.string.Spot_Trade_Guide_Overview_Desc), sections = listOf( stringResource(R.string.Product_Features) to listOf( stringResource(R.string.Spot_Trade_Guide_Feature_1), stringResource(R.string.Spot_Trade_Guide_Feature_2), ), - stringResource(R.string.Spot_Trade_Guide_Additional_Notes) to listOf( + stringResource(R.string.Spot_Trade_Guide_Fees) to listOf( stringResource(R.string.Spot_Trade_Guide_Note_1), - stringResource(R.string.Spot_Trade_Guide_Note_2), ), ) ) @@ -193,19 +192,19 @@ private fun SimpleSwapContent() { SpotTradeExampleCard(limitStrategy = null) Spacer(modifier = Modifier.height(16.dp)) TradeGuideInfoCard( - title = stringResource(R.string.Brief_Introduction), + title = stringResource(R.string.Overview), description = stringResource(R.string.Spot_Trade_Guide_Swap_Desc), sections = listOf( - stringResource(R.string.Spot_Trade_Guide_Suitable_Scenarios) to listOf( + stringResource(R.string.Spot_Trade_Guide_Use_Cases) to listOf( stringResource(R.string.Spot_Trade_Guide_Swap_Scenario_1), stringResource(R.string.Spot_Trade_Guide_Swap_Scenario_2), stringResource(R.string.Spot_Trade_Guide_Swap_Scenario_3), ), - stringResource(R.string.Spot_Trade_Guide_Quote_Explanation) to listOf( + stringResource(R.string.Spot_Trade_Guide_Pricing) to listOf( stringResource(R.string.Spot_Trade_Guide_Swap_Quote_1), stringResource(R.string.Spot_Trade_Guide_Swap_Quote_2), ), - stringResource(R.string.Perpetual_Risk_Warning) to listOf( + stringResource(R.string.Risk_Notice) to listOf( stringResource(R.string.Spot_Trade_Guide_Swap_Risk), ), ) @@ -217,16 +216,15 @@ private fun LimitTradeContent() { SpotTradeExampleCard(limitStrategy = LimitStrategy.BuyLow) Spacer(modifier = Modifier.height(16.dp)) TradeGuideInfoCard( - title = stringResource(R.string.Brief_Introduction), + title = stringResource(R.string.Overview), description = stringResource(R.string.Spot_Trade_Guide_Limit_Desc), sections = listOf( - stringResource(R.string.Spot_Trade_Guide_Suitable_Scenarios) to listOf( + stringResource(R.string.Spot_Trade_Guide_Use_Cases) to listOf( stringResource(R.string.Spot_Trade_Guide_Limit_Scenario_1), stringResource(R.string.Spot_Trade_Guide_Limit_Scenario_2), stringResource(R.string.Spot_Trade_Guide_Limit_Scenario_3), - stringResource(R.string.Spot_Trade_Guide_Limit_Scenario_4), ), - stringResource(R.string.Perpetual_Risk_Warning) to listOf( + stringResource(R.string.Risk_Notice) to listOf( stringResource(R.string.Spot_Trade_Guide_Limit_Risk), ), ) @@ -331,7 +329,7 @@ private fun SpotTradeExampleCard( if (limitStrategy != null) { Spacer(modifier = Modifier.height(16.dp)) ExampleValueRow( - title = stringResource(R.string.Trade_Guide_Order_Price), + title = stringResource(R.string.Trade_Guide_Limit_Price), value = { OrderPriceStepper( price = effectivePrice, @@ -349,7 +347,7 @@ private fun SpotTradeExampleCard( } Spacer(modifier = Modifier.height(16.dp)) Text( - text = if (limitStrategy == null) stringResource(R.string.Trade_Guide_Exchange_Price) else stringResource(R.string.Trade_Guide_Current_Price), + text = stringResource(R.string.Trade_Guide_Market_Price), fontSize = 14.sp, lineHeight = 20.sp, color = MixinAppTheme.colors.textPrimary, @@ -373,7 +371,7 @@ private fun SpotTradeExampleCard( } Spacer(modifier = Modifier.height(16.dp)) ExampleValueRow( - title = stringResource(R.string.Estimated_Receive), + title = stringResource(R.string.Spot_Trade_Guide_You_Receive), value = { Row( verticalAlignment = Alignment.CenterVertically, @@ -407,7 +405,7 @@ private fun StrategyRow( onStrategySelected: (LimitStrategy) -> Unit, ) { ExampleValueRow( - title = stringResource(R.string.Trade_Guide_Trading_Strategy), + title = stringResource(R.string.Trade_Guide_Strategy), value = { Row(modifier = Modifier .clip(RoundedCornerShape(6.dp)) @@ -711,7 +709,7 @@ private fun TradeGuideInfoCard( Spacer(modifier = Modifier.height(16.dp)) Text( text = sectionTitle, - fontSize = 16.sp, + fontSize = 14.sp, fontWeight = FontWeight.W500, color = MixinAppTheme.colors.textPrimary, ) diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 992d403d11..c56b8457ac 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -2337,44 +2337,40 @@ https://support.mixin.one/zh/article/pin-1vb2rc7/ 切换账号 永续合约指南 - 具体说明 - Mixin 交易支持简单闪兑和专业模式,简单易用,支持多链、多币种,并聚合多个 DEX 与 CEX 的深度。 - 最优聚合:支持 Uniswap、1inch、Jupiter、MixPay、BigONE 等 DEX 和 CEX。 + Mixin 交易支持简单闪兑和专业模式,支持多链、多币种交易,并聚合多个 DEX 与 CEX 的流动性。 + 最优聚合:接入 Uniswap、 1inch、Jupiter、MixPay、BigONE 等 DEX 和 CEX。 跨链交易:支持 Bitcoin、Ethereum、Solana 等多链资产交易。 补充说明 - 交易手续费主要由交易所和网络提现手续费构成,Mixin 不额外收取交易手续费。 - 交易返佣主要来自交易所的手续费分润。 + 费用说明 + 交易手续费主要由交易所和网络提现手续费构成,Mixin 不额外收取交易手续费 举例说明 - 交易币种 + 交易对 支付金额 兑换价格 - 当前价格 - 交易策略 - 成交价格 + 交易策略 + 成交价格 市场价格 - 适合场景 - 报价说明 - 闪兑是一种操作简单、按当前最优市场价格立即成交的兑换方式。 - 希望立即成交,追求操作效率和确定性。 - 兑换主流币或流动性充足的交易对,市价成交通常不会产生明显价差。 - 新手友好,操作简单、直观。 - 交易报价为 DEX 和 CEX 聚合最优价。 - 交易报价已包含交易所手续费和网络提现手续费。 - 市场剧烈波动时,实际成交价格可能与预期有差异。 - 低价买入\n高价卖出 + 适合场景 + 报价说明 + 闪兑是一种按当前最优市场价格立即成交的兑换方式。 + 需要快速成交 + 交易主流或流动性充足的交易对 + 适合简单兑换需求 + 价格来自 DEX 与 CEX 的聚合最优报价 + 报价已包含交易所手续费和网络手续费 + 市场波动较大时,实际成交价格可能与报价存在差异 低价买入 高价卖出 - 专业模式支持限价挂单,这是一种由用户自行设定成交价格,在市场价格达到预期时才会成交的交易方式。 - 有明确交易策略,例如区间交易、低买高卖等。 - 对成交价格敏感,希望严格按照指定价格或更优价格成交。 - 进行大额交易,希望通过挂单方式逐步成交,避免对市场造成冲击或产生明显滑点。 - 不急于立即成交,希望在价格到达预期区间后,由系统自动撮合完成交易。 - 市场剧烈波动时,可能会出现快速跳过挂单价格,导致无法成交。 - 风险提示 + 专业模式支持限价挂单,用户可自行设定成交价格,达到预期价格时才会成交。 + 有明确交易策略,如低买高卖 + 对成交价格敏感 + 适合大额交易 + 市场波动较大时,价格可能快速越过挂单价格,订单可能无法成交 钱包之间免费提现 直接使用 Mixin 转账给朋友,无需手续费。 导入到 Mixin 安全吗? Mixin 是开源的、可复现构建的,并经过独立审计。 您的数据会被安全加密,并仅存储在您的本地设备上。 Mixin 绝不会访问您的隐私信息,也不会与第三方共享。 + 预计获得 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 8cda760e88..1173e93599 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -2295,7 +2295,7 @@ No available quotes found. Please try a different token or amount. Safes, watch wallets, and hidden assets are excluded from the total Co-managed safes are excluded from the total - Trading Guide + Spot Trading Guide Overview <%1$s %1$s> @@ -2407,44 +2407,40 @@ https://support.mixin.one/en/article/what-should-i-do-if-i-forget-my-pin-g12pmo/ Switch Account Perpetual Futures Guide - Instructions - Mixin Trade supports both simple swaps and a professional limit-order mode. It is easy to use, supports multiple chains and assets, and aggregates liquidity across DEXs and CEXs. - Best aggregation: Supports Uniswap, 1inch, Jupiter, MixPay, BigONE, and more across DEXs and CEXs. - Cross-chain trading: Supports assets across Bitcoin, Ethereum, Solana, and other networks. + Mixin supports both Simple and Advanced trading modes, allowing you to trade across multiple chains and assets with aggregated liquidity from DEXs and CEXs. + Liquidity aggregation: access to liquidity from Uniswap, 1inch, Jupiter, MixPay, BigONE, and other DEXs and CEXs. + Cross-chain trading: supports assets across Bitcoin, Ethereum, Solana, and more. Additional Notes - Trading costs mainly come from exchange fees and network withdrawal fees. Mixin does not charge additional trading fees. - Trading rebates mainly come from fee-sharing arrangements with exchanges. + Fees + Trading fees mainly consist of exchange fees and network fees. Mixin does not charge additional platform fees. Example Trading Pair - Pay Amount + You Pay Exchange Price - Current Price - Trading Strategy - Order Price + Strategy + Limit Price Market Price - Suitable Scenarios - Quote Details - Simple Swap is an easy-to-use trading mode that executes immediately at the current best available market price. - Ideal when you want immediate execution with a straightforward flow and predictable completion. - Well suited for mainstream assets or trading pairs with strong liquidity, where market execution usually keeps price differences limited. - Beginner-friendly, with a simple and intuitive experience. - Quotes are aggregated from the best prices available across DEXs and CEXs. - Quotes already include exchange fees and network withdrawal fees. - During sharp market movements, the final execution price may differ from the expected quote. - Buy low\nSell high - Buy low - Sell high - Professional mode supports limit orders, letting you set your own execution price so the order is matched only when the market reaches your target price or better. - Useful when you have a clear trading strategy, such as range trading or buying low and selling high. - Suitable when you are sensitive to execution price and want the order filled strictly at your target or better. - Helpful for larger orders when you want to place orders gradually and avoid large market impact or obvious slippage. - Suitable when you do not need immediate execution and prefer the system to match automatically once the price enters your target range. - During sharp market movements, the market may jump over your limit price quickly and the order may remain unfilled. - Risk Warning + Use Cases + Pricing + Simple mode enables instant swaps at the best available market price. + When you need fast execution + For major pairs or high-liquidity markets + For quick and easy swaps + Prices are aggregated from DEXs and CEXs to provide the best available quote + Quotes include exchange and network fees + Actual execution price may differ from the quoted price during high volatility. + Buy Low + Sell High + Advanced supports limit orders, allowing you to set your preferred execution price. Orders are executed only when the market reaches your specified price. + For strategic trading + For precise pricing + For large trades + The market price may move past your limit price, and your order may not be filled. Free transfers between wallets Send crypto to friends using Mixin directly without fees. Is it safe to import into Mixin? Mixin is open source, reproducible, and independently audited. Your data is securely encrypted and stored locally on your device. Mixin never accesses your private information or shares it with third parties. + You Receive From ea1822874725fa1e9227ee19856c5d870c4c919e Mon Sep 17 00:00:00 2001 From: SeniorZhai Date: Wed, 25 Mar 2026 16:26:56 +0800 Subject: [PATCH 12/35] Revert small screen layout --- .../android/ui/home/web3/trade/ClosedPositionItem.kt | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/one/mixin/android/ui/home/web3/trade/ClosedPositionItem.kt b/app/src/main/java/one/mixin/android/ui/home/web3/trade/ClosedPositionItem.kt index 7dbcb51e00..acda7b5aa9 100644 --- a/app/src/main/java/one/mixin/android/ui/home/web3/trade/ClosedPositionItem.kt +++ b/app/src/main/java/one/mixin/android/ui/home/web3/trade/ClosedPositionItem.kt @@ -47,7 +47,6 @@ fun ClosedPositionItem( onClick: () -> Unit = {}, ) { val context = LocalContext.current - val isSmallScreen = context.resources.configuration.screenWidthDp <= SMALL_SCREEN_WIDTH_DP val quoteColorPref = context.defaultSharedPreferences .getBoolean(Constants.Account.PREF_QUOTE_COLOR, false) val fiatRate = BigDecimal(Fiats.getRate()) @@ -128,15 +127,8 @@ fun ClosedPositionItem( stringResource(R.string.Short) } Text( - text = buildAnnotatedString { - withStyle( - SpanStyle( - fontSize = if (isSmallScreen) 12.sp else 14.sp - ) - ) { - append(sideText) - } - }, + text = sideText, + fontSize = 16.sp, color = MixinAppTheme.colors.textPrimary, ) Spacer(modifier = Modifier.width(6.dp)) From 4511d6419a0a70d73f0bfa4d9906d800ac281214 Mon Sep 17 00:00:00 2001 From: SeniorZhai Date: Wed, 25 Mar 2026 16:30:18 +0800 Subject: [PATCH 13/35] feat: auto-set limit price 1% below market price --- .../mixin/android/ui/home/web3/components/PriceInputArea.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/one/mixin/android/ui/home/web3/components/PriceInputArea.kt b/app/src/main/java/one/mixin/android/ui/home/web3/components/PriceInputArea.kt index 8a6cb45c9d..1d99a12354 100644 --- a/app/src/main/java/one/mixin/android/ui/home/web3/components/PriceInputArea.kt +++ b/app/src/main/java/one/mixin/android/ui/home/web3/components/PriceInputArea.kt @@ -97,7 +97,7 @@ fun PriceInputArea( var fromP = fromMarket?.currentPrice?.toBigDecimalOrNull() var toP = toMarket?.currentPrice?.toBigDecimalOrNull() if (fromP != null && toP != null && toP > BigDecimal.ZERO) { - val price = fromP.divide(toP, 8, RoundingMode.HALF_UP) + val price = fromP.multiply(BigDecimal(0.99)).divide(toP, 8, RoundingMode.HALF_UP) marketPrice = price val priceString = price.stripTrailingZeros().toPlainString() onStandardPriceChanged(priceString) @@ -113,7 +113,7 @@ fun PriceInputArea( fromP = fromMarket?.currentPrice?.toBigDecimalOrNull() toP = toMarket?.currentPrice?.toBigDecimalOrNull() if (fromP != null && toP != null && toP > BigDecimal.ZERO) { - val updatedPrice = fromP.divide(toP, 8, RoundingMode.HALF_UP) + val updatedPrice = fromP.multiply(BigDecimal(0.99)).divide(toP, 8, RoundingMode.HALF_UP) if (price != updatedPrice) { marketPrice = updatedPrice val updatedPriceString = updatedPrice.stripTrailingZeros().toPlainString() From c6809bcbe131ae3a1beb8351c6e1e923ee419f0d Mon Sep 17 00:00:00 2001 From: SeniorZhai Date: Thu, 26 Mar 2026 10:52:39 +0800 Subject: [PATCH 14/35] fix limit order price shortcut direction --- .../home/web3/components/FloatingActions.kt | 32 +++++++++++++++---- .../ui/home/web3/components/PriceInputArea.kt | 8 ++--- .../ui/home/web3/trade/LimitOrderContent.kt | 5 ++- 3 files changed, 33 insertions(+), 12 deletions(-) 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 4c772fdf3d..1c52f69609 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,7 +1,6 @@ package one.mixin.android.ui.home.web3.components - import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row @@ -17,6 +16,7 @@ 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 +import java.math.RoundingMode @Composable fun FloatingActions( @@ -24,6 +24,7 @@ fun FloatingActions( fromBalance: String?, fromToken: SwapToken?, toToken: SwapToken?, + isPriceInverted: Boolean, onSetInput: (String) -> Unit, onSetPriceMultiplier: (Float?) -> Unit, onDone: () -> Unit, @@ -77,17 +78,27 @@ fun FloatingActions( onMarketPriceClick?.invoke() } + 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) { - InputAction("+10%", showBorder = true) { onSetPriceMultiplier(1.1f) } - InputAction("+20%", showBorder = true) { onSetPriceMultiplier(1.2f) } + if (isToUsd && !isFromUsd) { + InputAction("+10%", showBorder = true) { + onSetPriceMultiplier(displayPriceMultiplier(1.1f, isPriceInverted)) + } + InputAction("+20%", showBorder = true) { + onSetPriceMultiplier(displayPriceMultiplier(1.2f, isPriceInverted)) + } } else { - // from is USD or other cases -> -10% / -20% - InputAction("-10%", showBorder = true) { onSetPriceMultiplier(0.9f) } - InputAction("-20%", showBorder = true) { onSetPriceMultiplier(0.8f) } + InputAction("-10%", showBorder = true) { + onSetPriceMultiplier(displayPriceMultiplier(0.9f, isPriceInverted)) + } + InputAction("-20%", showBorder = true) { + onSetPriceMultiplier(displayPriceMultiplier(0.8f, isPriceInverted)) + } } InputAction(stringResource(R.string.Done), showBorder = false) { onDone() } } @@ -96,3 +107,10 @@ fun FloatingActions( } } +private fun displayPriceMultiplier(displayMultiplier: Float, isPriceInverted: Boolean): Float { + if (!isPriceInverted) return displayMultiplier + + return BigDecimal.ONE + .divide(BigDecimal(displayMultiplier.toString()), 8, RoundingMode.HALF_UP) + .toFloat() +} diff --git a/app/src/main/java/one/mixin/android/ui/home/web3/components/PriceInputArea.kt b/app/src/main/java/one/mixin/android/ui/home/web3/components/PriceInputArea.kt index 1d99a12354..ee0a026909 100644 --- a/app/src/main/java/one/mixin/android/ui/home/web3/components/PriceInputArea.kt +++ b/app/src/main/java/one/mixin/android/ui/home/web3/components/PriceInputArea.kt @@ -43,13 +43,13 @@ fun PriceInputArea( toToken: SwapToken?, lastOrderTime: Long?, priceMultiplier: Float?, + isPriceInverted: Boolean, + onPriceInvertedChange: (Boolean) -> Unit, onStandardPriceChanged: (String) -> Unit, ) { val viewModel = hiltViewModel() val context = LocalContext.current - - var isPriceInverted by remember { mutableStateOf(false) } // Display price shown in the input field, initialized from market price var displayPrice by remember { mutableStateOf("") } @@ -66,7 +66,7 @@ fun PriceInputArea( val isToUsd = toToken?.assetId?.let { id -> Constants.AssetId.usdtAssets.containsKey(id) || Constants.AssetId.usdcAssets.containsKey(id) } == true - isPriceInverted = isFromUsd && !isToUsd + onPriceInvertedChange(isFromUsd && !isToUsd) } LaunchedEffect(priceMultiplier) { @@ -216,7 +216,7 @@ fun PriceInputArea( tint = MixinAppTheme.colors.textAssist, modifier = Modifier .size(16.dp) - .clickable { isPriceInverted = !isPriceInverted } + .clickable { onPriceInvertedChange(!isPriceInverted) } ) } } diff --git a/app/src/main/java/one/mixin/android/ui/home/web3/trade/LimitOrderContent.kt b/app/src/main/java/one/mixin/android/ui/home/web3/trade/LimitOrderContent.kt index a932388b12..02b496f480 100644 --- a/app/src/main/java/one/mixin/android/ui/home/web3/trade/LimitOrderContent.kt +++ b/app/src/main/java/one/mixin/android/ui/home/web3/trade/LimitOrderContent.kt @@ -141,6 +141,7 @@ fun LimitOrderContent( var limitPriceText by remember { mutableStateOf("") } var marketPriceClickTime by remember { mutableStateOf(lastOrderTime) } var priceMultiplier by remember { mutableStateOf(null) } + var isPriceInverted by remember { mutableStateOf(false) } var isReverse by remember { mutableStateOf(false) } val walletId = if (inMixin) Session.getAccountId()!! else Web3Signer.currentWalletId @@ -375,6 +376,8 @@ fun LimitOrderContent( toToken = toToken, lastOrderTime = marketPriceClickTime, priceMultiplier = priceMultiplier, + isPriceInverted = isPriceInverted, + onPriceInvertedChange = { isPriceInverted = it }, onStandardPriceChanged = { limitPriceText = it }, ) Spacer(modifier = Modifier.height(10.dp)) @@ -591,6 +594,7 @@ fun LimitOrderContent( fromBalance = fromBalance, fromToken = fromToken, toToken = toToken, + isPriceInverted = isPriceInverted, onSetPriceMultiplier = { priceMultiplier = it }, onSetInput = { inputText = it @@ -649,4 +653,3 @@ fun Modifier.verticalScrollbar( } } } - From a2033f0d4927bb6a574c6db204b20834dadb2121 Mon Sep 17 00:00:00 2001 From: SeniorZhai Date: Thu, 26 Mar 2026 11:16:05 +0800 Subject: [PATCH 15/35] refactor perps open position card layout --- .../web3/trade/perps/PerpsMarketDetailPage.kt | 109 ++++++++++-------- 1 file changed, 58 insertions(+), 51 deletions(-) diff --git a/app/src/main/java/one/mixin/android/ui/home/web3/trade/perps/PerpsMarketDetailPage.kt b/app/src/main/java/one/mixin/android/ui/home/web3/trade/perps/PerpsMarketDetailPage.kt index d7e474553b..72ba7cf763 100644 --- a/app/src/main/java/one/mixin/android/ui/home/web3/trade/perps/PerpsMarketDetailPage.kt +++ b/app/src/main/java/one/mixin/android/ui/home/web3/trade/perps/PerpsMarketDetailPage.kt @@ -652,33 +652,36 @@ private fun OpenPositionCard( ) } - Spacer(modifier = Modifier.height(12.dp)) + Spacer(modifier = Modifier.height(20.dp)) - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween - ) { - Column { + Column { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { Text( text = stringResource(R.string.PnL).uppercase(), fontSize = 12.sp, color = MixinAppTheme.colors.textAssist ) - Spacer(modifier = Modifier.height(4.dp)) - - Text( - text = "${formatPerpsSignedFiatDecimal(pnl.multiply(fiatRate), fiatSymbol)}(${formatPerpsSignedPercent(roe)})", - fontSize = 14.sp, - color = pnlColor - ) - } - Column(horizontalAlignment = Alignment.End) { Text( text = stringResource(R.string.Direction).uppercase(), fontSize = 12.sp, color = MixinAppTheme.colors.textAssist ) - Spacer(modifier = Modifier.height(4.dp)) + } + Spacer(modifier = Modifier.height(7.dp)) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = "${formatPerpsSignedFiatDecimal(pnl.multiply(fiatRate), fiatSymbol)}(${formatPerpsSignedPercent(roe)})", + fontSize = 14.sp, + color = pnlColor + ) Row(verticalAlignment = Alignment.CenterVertically) { Box( modifier = Modifier @@ -703,20 +706,21 @@ private fun OpenPositionCard( } } - Spacer(modifier = Modifier.height(12.dp)) + Spacer(modifier = Modifier.height(20.dp)) - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween - ) { - Column { + Column { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { Row(verticalAlignment = Alignment.CenterVertically) { Text( text = stringResource(R.string.position_size).uppercase(), fontSize = 12.sp, color = MixinAppTheme.colors.textAssist ) - Spacer(modifier = Modifier.width(4.dp)) + Spacer(modifier = Modifier.width(9.dp)) Icon( painter = painterResource(id = R.drawable.ic_tip), contentDescription = null, @@ -731,22 +735,23 @@ private fun OpenPositionCard( tint = MixinAppTheme.colors.textAssist ) } + Text( + text = stringResource(R.string.Margin).uppercase(), + fontSize = 12.sp, + color = MixinAppTheme.colors.textAssist + ) + } + Spacer(modifier = Modifier.height(10.dp)) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { Text( text = "${quantity.stripTrailingZeros().toPlainString()} ${position.tokenSymbol}", fontSize = 14.sp, color = MixinAppTheme.colors.textPrimary ) - } - - Column(horizontalAlignment = Alignment.End) { - Row(verticalAlignment = Alignment.CenterVertically) { - Text( - text = stringResource(R.string.Margin).uppercase(), - fontSize = 12.sp, - color = MixinAppTheme.colors.textAssist - ) - - } Text( text = formatPerpsFiatDecimal(amountValue, fiatSymbol), fontSize = 14.sp, @@ -755,34 +760,36 @@ private fun OpenPositionCard( } } - Spacer(modifier = Modifier.height(12.dp)) + Spacer(modifier = Modifier.height(20.dp)) - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween - ) { - Column { + Column { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { Text( text = stringResource(R.string.Entry_Price).uppercase(), fontSize = 12.sp, color = MixinAppTheme.colors.textAssist ) - Spacer(modifier = Modifier.height(4.dp)) + Text( + text = stringResource(R.string.Liquidation_Price).uppercase(), + fontSize = 12.sp, + color = MixinAppTheme.colors.textAssist + ) + } + Spacer(modifier = Modifier.height(10.dp)) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { Text( text = "${fiatSymbol}${entryPrice.multiply(fiatRate).priceFormat()}", fontSize = 14.sp, color = MixinAppTheme.colors.textPrimary ) - } - - Column(horizontalAlignment = Alignment.End) { - Row(verticalAlignment = Alignment.CenterVertically) { - Text( - text = stringResource(R.string.Liquidation_Price).uppercase(), - fontSize = 12.sp, - color = MixinAppTheme.colors.textAssist - ) - } Text( text = "${fiatSymbol}${liquidationPrice.multiply(fiatRate).priceFormat()}", fontSize = 14.sp, From b1013b9b895e3bdc349c6f76d0a4d2136200c40b Mon Sep 17 00:00:00 2001 From: SeniorZhai Date: Thu, 26 Mar 2026 11:56:29 +0800 Subject: [PATCH 16/35] fix candle chart drag after zoom --- .../android/ui/home/web3/trade/CandleChart.kt | 127 +++++++++++++----- .../ui/home/web3/trade/SpotTradeGuidePage.kt | 18 --- 2 files changed, 96 insertions(+), 49 deletions(-) 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 6f36fed696..0a9b9de70a 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 @@ -3,8 +3,9 @@ package one.mixin.android.ui.home.web3.trade import androidx.compose.foundation.Canvas import androidx.compose.foundation.background import androidx.compose.foundation.border -import androidx.compose.foundation.gestures.detectTransformGestures import androidx.compose.foundation.gestures.detectDragGesturesAfterLongPress +import androidx.compose.foundation.gestures.awaitEachGesture +import androidx.compose.foundation.gestures.awaitFirstDown import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -40,6 +41,8 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathEffect import androidx.compose.ui.graphics.drawscope.DrawScope import androidx.compose.ui.graphics.drawscope.clipRect +import androidx.compose.ui.input.pointer.PointerEvent +import androidx.compose.ui.input.pointer.PointerInputChange import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity @@ -69,6 +72,7 @@ import org.threeten.bp.format.DateTimeFormatter import java.math.BigDecimal import kotlinx.coroutines.launch import kotlin.math.abs +import kotlin.math.hypot import kotlin.math.max import kotlin.math.min import kotlin.math.roundToInt @@ -78,6 +82,50 @@ private const val DEFAULT_CANDLE_SCALE = 1f private const val MIN_CANDLE_SCALE = 0.5f private const val MAX_CANDLE_SCALE = 3f +private fun PointerEvent.currentPressedChanges(): List = + changes.filter { it.pressed } + +private fun PointerEvent.compatCalculateCentroid(): Offset { + val pressed = currentPressedChanges() + if (pressed.isEmpty()) return Offset.Zero + + val x = pressed.sumOf { it.position.x.toDouble() } / pressed.size + val y = pressed.sumOf { it.position.y.toDouble() } / pressed.size + return Offset(x.toFloat(), y.toFloat()) +} + +private fun PointerEvent.compatCalculatePan(): Offset { + val pressed = currentPressedChanges() + if (pressed.isEmpty()) return Offset.Zero + + val currentCentroid = compatCalculateCentroid() + val previousX = pressed.sumOf { it.previousPosition.x.toDouble() } / pressed.size + val previousY = pressed.sumOf { it.previousPosition.y.toDouble() } / pressed.size + val previousCentroid = Offset(previousX.toFloat(), previousY.toFloat()) + return currentCentroid - previousCentroid +} + +private fun PointerEvent.compatCalculateZoom(): Float { + val pressed = currentPressedChanges() + if (pressed.size < 2) return 1f + + val currentCentroid = compatCalculateCentroid() + val previousX = pressed.sumOf { it.previousPosition.x.toDouble() } / pressed.size + val previousY = pressed.sumOf { it.previousPosition.y.toDouble() } / pressed.size + val previousCentroid = Offset(previousX.toFloat(), previousY.toFloat()) + + val currentAverageDistance = pressed + .map { hypot((it.position.x - currentCentroid.x).toDouble(), (it.position.y - currentCentroid.y).toDouble()) } + .average() + .toFloat() + val previousAverageDistance = pressed + .map { hypot((it.previousPosition.x - previousCentroid.x).toDouble(), (it.previousPosition.y - previousCentroid.y).toDouble()) } + .average() + .toFloat() + + return if (previousAverageDistance > 0f) currentAverageDistance / previousAverageDistance else 1f +} + @Composable fun CandleChart( marketId: String, @@ -255,39 +303,56 @@ private fun ScrollableCandleChart( baseCandleWidthPx, baseSpacingPx, ) { - detectTransformGestures( - panZoomLock = true, - onGesture = { centroid, pan, zoom, _ -> - if (abs(zoom - 1f) < 0.0001f && abs(pan.x) < 0.0001f) { - return@detectTransformGestures + awaitEachGesture { + awaitFirstDown(requireUnconsumed = false) + var gestureHandled = false + + do { + val event = awaitPointerEvent() + val zoom = event.compatCalculateZoom() + val pan = event.compatCalculatePan() + val centroid = event.compatCalculateCentroid() + val pressedCount = event.changes.count { it.pressed } + + if (pressedCount > 1 && (abs(zoom - 1f) >= 0.0001f || abs(pan.x) >= 0.0001f)) { + gestureHandled = true + isPinching = true + isTouching = false + touchXOnChart = null + + val oldScale = candleScale + val newScale = (oldScale * zoom).coerceIn(MIN_CANDLE_SCALE, MAX_CANDLE_SCALE) + val oldStepPx = (baseCandleWidthPx + baseSpacingPx) * oldScale + val newStepPx = (baseCandleWidthPx + baseSpacingPx) * newScale + val contentX = scrollState.value + centroid.x - chartStartPaddingPx + val stepIndex = if (oldStepPx > 0f) contentX / oldStepPx else 0f + + candleScale = newScale + + val newTotalWidthPx = chartStartPaddingPx + + (baseCandleWidthPx * newScale * items.size) + + (baseSpacingPx * newScale * (items.size - 1).coerceAtLeast(0)) + val maxScroll = (newTotalWidthPx - viewportWidthPx).coerceAtLeast(0f) + val anchoredScroll = (stepIndex * newStepPx) - (centroid.x - chartStartPaddingPx) + val targetScroll = (anchoredScroll - pan.x).roundToInt() + .coerceIn(0, maxScroll.roundToInt()) + + coroutineScope.launch { + scrollState.scrollTo(targetScroll) + } + + event.changes.forEach { change -> + if (change.pressed) { + change.consume() + } + } } + } while (event.changes.any { it.pressed }) - isPinching = true - isTouching = false - touchXOnChart = null - - val oldScale = candleScale - val newScale = (oldScale * zoom).coerceIn(MIN_CANDLE_SCALE, MAX_CANDLE_SCALE) - val oldStepPx = (baseCandleWidthPx + baseSpacingPx) * oldScale - val newStepPx = (baseCandleWidthPx + baseSpacingPx) * newScale - val contentX = scrollState.value + centroid.x - chartStartPaddingPx - val stepIndex = if (oldStepPx > 0f) contentX / oldStepPx else 0f - - candleScale = newScale - - val newTotalWidthPx = chartStartPaddingPx + - (baseCandleWidthPx * newScale * items.size) + - (baseSpacingPx * newScale * (items.size - 1).coerceAtLeast(0)) - val maxScroll = (newTotalWidthPx - viewportWidthPx).coerceAtLeast(0f) - val anchoredScroll = (stepIndex * newStepPx) - (centroid.x - chartStartPaddingPx) - val targetScroll = (anchoredScroll - pan.x).roundToInt() - .coerceIn(0, maxScroll.roundToInt()) - - coroutineScope.launch { - scrollState.scrollTo(targetScroll) - } + if (gestureHandled) { + isPinching = false } - ) + } } .pointerInput(items.size, totalChartWidthPx, isPinching) { if (isPinching) return@pointerInput diff --git a/app/src/main/java/one/mixin/android/ui/home/web3/trade/SpotTradeGuidePage.kt b/app/src/main/java/one/mixin/android/ui/home/web3/trade/SpotTradeGuidePage.kt index 05a001e8b7..f6790961db 100644 --- a/app/src/main/java/one/mixin/android/ui/home/web3/trade/SpotTradeGuidePage.kt +++ b/app/src/main/java/one/mixin/android/ui/home/web3/trade/SpotTradeGuidePage.kt @@ -856,24 +856,6 @@ private fun calculateReceiveAmount( } } -private fun convertPayAmount( - amount: BigDecimal, - currentFromPrice: BigDecimal, - newFromPrice: BigDecimal, -): BigDecimal { - if (currentFromPrice <= BigDecimal.ZERO || newFromPrice <= BigDecimal.ZERO) { - return amount - } - return amount - .multiply(currentFromPrice) - .divide(newFromPrice, 8, RoundingMode.HALF_UP) - .stripTrailingZeros() -} - -private fun TokenItem?.safePrice(): BigDecimal { - return this?.priceUsd?.toBigDecimalOrNull()?.takeIf { it > BigDecimal.ZERO } ?: BigDecimal.ONE -} - private fun guideTokenIconRes( token: TokenItem?, fallbackSymbol: String, From add5726f2401254367c13995f13812e2063bf9dd Mon Sep 17 00:00:00 2001 From: SeniorZhai Date: Thu, 26 Mar 2026 12:06:57 +0800 Subject: [PATCH 17/35] adjust candle chart price precision --- .../android/ui/home/web3/trade/CandleChart.kt | 45 ++++++++++++++----- 1 file changed, 33 insertions(+), 12 deletions(-) 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 0a9b9de70a..1332e45d65 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 @@ -279,13 +279,14 @@ private fun ScrollableCandleChart( val maxPrice = prices.maxOrNull() ?: BigDecimal.ZERO val minPrice = prices.minOrNull() ?: BigDecimal.ZERO val midPrice = (maxPrice + minPrice) / BigDecimal(2) - val maxPriceText = formatPrice(maxPrice) - val midPriceText = formatPrice(midPrice) - val minPriceText = formatPrice(minPrice) + val priceScale = resolveChartPriceScale(maxPrice, minPrice, midPrice) + val maxPriceText = formatPrice(maxPrice, priceScale) + val midPriceText = formatPrice(midPrice, priceScale) + val minPriceText = formatPrice(minPrice, priceScale) val selectedPrice = selectedItem?.close?.toBigDecimalOrNull() val showCurrentPrice = selectedPrice == null && latestPrice != null - val currentPriceText = latestPrice?.let { formatPrice(it) } + val currentPriceText = latestPrice?.let { formatPrice(it, priceScale) } val isCurrentPriceInRange = latestPrice?.let { it >= minPrice && it <= maxPrice } == true val isCurrentPriceOverlapping = currentPriceText != null && currentPriceText in setOf(maxPriceText, midPriceText, minPriceText) @@ -459,7 +460,7 @@ private fun ScrollableCandleChart( contentAlignment = Alignment.CenterEnd ) { Text( - text = formatPrice(currentPrice), + text = formatPrice(currentPrice, priceScale), fontSize = 10.sp, color = MixinAppTheme.colors.textPrimary, textAlign = TextAlign.End, @@ -497,7 +498,7 @@ private fun ScrollableCandleChart( contentAlignment = Alignment.CenterEnd ) { Text( - text = formatPrice(selectedPrice), + text = formatPrice(selectedPrice, priceScale), fontSize = 10.sp, color = MixinAppTheme.colors.textPrimary, textAlign = TextAlign.End, @@ -737,13 +738,33 @@ private fun DrawScope.drawTouchCrosshair( ) } -private fun formatPrice(price: BigDecimal): String { - val scaledPrice = when { - price >= BigDecimal("100") -> price.setScale(0, java.math.RoundingMode.HALF_UP) - price >= BigDecimal("1") -> price.setScale(2, java.math.RoundingMode.HALF_UP) - else -> price.setScale(6, java.math.RoundingMode.HALF_UP) +private fun resolveChartPriceScale( + maxPrice: BigDecimal, + minPrice: BigDecimal, + midPrice: BigDecimal, +): Int { + if (maxPrice < BigDecimal.ONE && minPrice < BigDecimal.ONE) { + return 6 + } + + var scale = 2 + while (scale < 4) { + val maxText = formatPrice(maxPrice, scale) + val minText = formatPrice(minPrice, scale) + val midText = formatPrice(midPrice, scale) + if (setOf(maxText, minText, midText).size == 3) { + break + } + scale++ } - return scaledPrice.stripTrailingZeros().toPlainString() + return scale +} + +private fun formatPrice( + price: BigDecimal, + scale: Int, +): String { + return price.setScale(scale, java.math.RoundingMode.HALF_UP).toPlainString() } private fun formatCandleTime(timestamp: Long, timeFrame: String): String { From ef32d61e58d531ff26ba38f574270bf5180b296f Mon Sep 17 00:00:00 2001 From: SeniorZhai Date: Thu, 26 Mar 2026 12:21:18 +0800 Subject: [PATCH 18/35] update guide navigation buttons --- .../ui/home/web3/trade/SpotTradeGuidePage.kt | 57 +++++++++++++------ .../web3/trade/perps/PerpetualGuidePage.kt | 57 +++++++++++++------ app/src/main/res/drawable/ic_guide_next.xml | 13 +++++ .../main/res/drawable/ic_guide_previous.xml | 13 +++++ app/src/main/res/values-zh-rCN/strings.xml | 2 - app/src/main/res/values/strings.xml | 2 - 6 files changed, 104 insertions(+), 40 deletions(-) create mode 100644 app/src/main/res/drawable/ic_guide_next.xml create mode 100644 app/src/main/res/drawable/ic_guide_previous.xml diff --git a/app/src/main/java/one/mixin/android/ui/home/web3/trade/SpotTradeGuidePage.kt b/app/src/main/java/one/mixin/android/ui/home/web3/trade/SpotTradeGuidePage.kt index f6790961db..0180df37e3 100644 --- a/app/src/main/java/one/mixin/android/ui/home/web3/trade/SpotTradeGuidePage.kt +++ b/app/src/main/java/one/mixin/android/ui/home/web3/trade/SpotTradeGuidePage.kt @@ -731,15 +731,14 @@ private fun SpotTradeGuideBottomNavigation( horizontalArrangement = Arrangement.spacedBy(12.dp), ) { SpotTradeGuideNavigationButton( - text = stringResource(R.string.Perpetual_Guide_Previous_Tab, tabs[previousTab]), + text = tabs[previousTab], + isPrevious = true, modifier = Modifier.weight(1f), onClick = { onSelect(previousTab) }, ) SpotTradeGuideNavigationButton( - text = stringResource( - R.string.Perpetual_Guide_Next_Tab, - stringResource(R.string.Start) - ), + text = stringResource(R.string.Start), + isPrevious = false, modifier = Modifier.weight(1f), onClick = onClose, ) @@ -754,12 +753,14 @@ private fun SpotTradeGuideBottomNavigation( horizontalArrangement = Arrangement.spacedBy(12.dp), ) { SpotTradeGuideNavigationButton( - text = stringResource(R.string.Perpetual_Guide_Previous_Tab, tabs[previousTab]), + text = tabs[previousTab], + isPrevious = true, modifier = Modifier.weight(1f), onClick = { onSelect(previousTab) }, ) SpotTradeGuideNavigationButton( - text = stringResource(R.string.Perpetual_Guide_Next_Tab, tabs[nextTab]), + text = tabs[nextTab], + isPrevious = false, modifier = Modifier.weight(1f), onClick = { onSelect(nextTab) }, ) @@ -767,17 +768,15 @@ private fun SpotTradeGuideBottomNavigation( return } val targetIndex = previousTab ?: nextTab ?: return - val buttonText = if (previousTab != null) { - stringResource(R.string.Perpetual_Guide_Previous_Tab, tabs[targetIndex]) - } else { - stringResource(R.string.Perpetual_Guide_Next_Tab, tabs[targetIndex]) - } + val buttonText = tabs[targetIndex] + val isPrevious = previousTab != null Box( modifier = Modifier.fillMaxWidth(), contentAlignment = Alignment.Center, ) { SpotTradeGuideNavigationButton( text = buttonText, + isPrevious = isPrevious, modifier = Modifier.fillMaxWidth(0.5f), onClick = { onSelect(targetIndex) }, ) @@ -787,6 +786,7 @@ private fun SpotTradeGuideBottomNavigation( @Composable private fun SpotTradeGuideNavigationButton( text: String, + isPrevious: Boolean, modifier: Modifier = Modifier, onClick: () -> Unit, ) { @@ -805,12 +805,33 @@ private fun SpotTradeGuideNavigationButton( focusedElevation = 0.dp, ), ) { - Text( - text = text, - fontSize = 16.sp, - fontWeight = FontWeight.Bold, - color = Color.White, - ) + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center, + ) { + if (isPrevious) { + Icon( + painter = painterResource(id = R.drawable.ic_guide_previous), + contentDescription = null, + tint = Color.Unspecified, + ) + Spacer(modifier = Modifier.width(4.dp)) + } + Text( + text = text, + fontSize = 16.sp, + fontWeight = FontWeight.Bold, + color = Color.White, + ) + if (!isPrevious) { + Spacer(modifier = Modifier.width(4.dp)) + Icon( + painter = painterResource(id = R.drawable.ic_guide_next), + contentDescription = null, + tint = Color.Unspecified, + ) + } + } } } diff --git a/app/src/main/java/one/mixin/android/ui/home/web3/trade/perps/PerpetualGuidePage.kt b/app/src/main/java/one/mixin/android/ui/home/web3/trade/perps/PerpetualGuidePage.kt index 0c13dde382..78ab1e9416 100644 --- a/app/src/main/java/one/mixin/android/ui/home/web3/trade/perps/PerpetualGuidePage.kt +++ b/app/src/main/java/one/mixin/android/ui/home/web3/trade/perps/PerpetualGuidePage.kt @@ -481,15 +481,14 @@ private fun GuideBottomNavigation( horizontalArrangement = Arrangement.spacedBy(12.dp), ) { GuideNavigationButton( - text = stringResource(R.string.Perpetual_Guide_Previous_Tab, tabs[previousTab]), + text = tabs[previousTab], + isPrevious = true, modifier = Modifier.weight(1f), onClick = { onSelect(previousTab) }, ) GuideNavigationButton( - text = stringResource( - R.string.Perpetual_Guide_Next_Tab, - stringResource(R.string.Start) - ), + text = stringResource(R.string.Start), + isPrevious = false, modifier = Modifier.weight(1f), onClick = onClose, ) @@ -504,12 +503,14 @@ private fun GuideBottomNavigation( horizontalArrangement = Arrangement.spacedBy(12.dp), ) { GuideNavigationButton( - text = stringResource(R.string.Perpetual_Guide_Previous_Tab, tabs[previousTab]), + text = tabs[previousTab], + isPrevious = true, modifier = Modifier.weight(1f), onClick = { onSelect(previousTab) }, ) GuideNavigationButton( - text = stringResource(R.string.Perpetual_Guide_Next_Tab, tabs[nextTab]), + text = tabs[nextTab], + isPrevious = false, modifier = Modifier.weight(1f), onClick = { onSelect(nextTab) }, ) @@ -517,17 +518,15 @@ private fun GuideBottomNavigation( return } val targetIndex = previousTab ?: nextTab ?: return - val buttonText = if (previousTab != null) { - stringResource(R.string.Perpetual_Guide_Previous_Tab, tabs[targetIndex]) - } else { - stringResource(R.string.Perpetual_Guide_Next_Tab, tabs[targetIndex]) - } + val buttonText = tabs[targetIndex] + val isPrevious = previousTab != null Box( modifier = Modifier.fillMaxWidth(), contentAlignment = Alignment.Center, ) { GuideNavigationButton( text = buttonText, + isPrevious = isPrevious, modifier = Modifier.fillMaxWidth(0.5f), onClick = { onSelect(targetIndex) }, ) @@ -537,6 +536,7 @@ private fun GuideBottomNavigation( @Composable private fun GuideNavigationButton( text: String, + isPrevious: Boolean, modifier: Modifier = Modifier, onClick: () -> Unit, ) { @@ -545,12 +545,33 @@ private fun GuideNavigationButton( onClick = onClick, shape = RoundedCornerShape(32.dp), ) { - Text( - text = text, - fontSize = 14.sp, - fontWeight = FontWeight.W500, - color = Color.White, - ) + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center, + ) { + if (isPrevious) { + Icon( + painter = painterResource(id = R.drawable.ic_guide_previous), + contentDescription = null, + tint = Color.Unspecified, + ) + Spacer(modifier = Modifier.width(4.dp)) + } + Text( + text = text, + fontSize = 14.sp, + fontWeight = FontWeight.W500, + color = Color.White, + ) + if (!isPrevious) { + Spacer(modifier = Modifier.width(4.dp)) + Icon( + painter = painterResource(id = R.drawable.ic_guide_next), + contentDescription = null, + tint = Color.Unspecified, + ) + } + } } } diff --git a/app/src/main/res/drawable/ic_guide_next.xml b/app/src/main/res/drawable/ic_guide_next.xml new file mode 100644 index 0000000000..efe9635c1c --- /dev/null +++ b/app/src/main/res/drawable/ic_guide_next.xml @@ -0,0 +1,13 @@ + + + diff --git a/app/src/main/res/drawable/ic_guide_previous.xml b/app/src/main/res/drawable/ic_guide_previous.xml new file mode 100644 index 0000000000..d5d8408379 --- /dev/null +++ b/app/src/main/res/drawable/ic_guide_previous.xml @@ -0,0 +1,13 @@ + + + diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index d01fe5f692..eaca2a49de 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -2235,8 +2235,6 @@ 共管的 Safe 金库不计入统计 现货交易指南 简介 - < %1$s - %1$s > 永续合约允许您使用杠杆交易加密货币,从而放大您的潜在利润(和损失)。您可以做多(押注价格上涨)或做空(押注价格下跌),而无需拥有标的资产。 您可以随时平仓以实现盈亏。平仓价格基于当前市场价格。请务必监控您的仓位以避免爆仓。 具体说明 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2e8917876f..295ad85575 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -2297,8 +2297,6 @@ Co-managed safes are excluded from the total Spot Trading Guide Overview - < %1$s - %1$s > Perpetual contracts allow you to trade cryptocurrency with leverage, enabling you to amplify your potential profits (and losses). You can go long (bet on price increase) or short (bet on price decrease) without owning the underlying asset. Leverage allows you to control a larger position with less capital. You can close your position at any time to realize your profit or loss. The closing price is based on the current market price. Make sure to monitor your positions to avoid liquidation. From 7f4ab6ebde6ce974f753488d69728d1808307748 Mon Sep 17 00:00:00 2001 From: SeniorZhai Date: Thu, 26 Mar 2026 12:51:29 +0800 Subject: [PATCH 19/35] adjust spot guide example amounts --- .../ui/home/web3/trade/SpotTradeGuidePage.kt | 30 +++++++++++++++++-- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/one/mixin/android/ui/home/web3/trade/SpotTradeGuidePage.kt b/app/src/main/java/one/mixin/android/ui/home/web3/trade/SpotTradeGuidePage.kt index 0180df37e3..efecc89e8c 100644 --- a/app/src/main/java/one/mixin/android/ui/home/web3/trade/SpotTradeGuidePage.kt +++ b/app/src/main/java/one/mixin/android/ui/home/web3/trade/SpotTradeGuidePage.kt @@ -243,15 +243,26 @@ private fun SpotTradeExampleCard( calculateMarketPrice(usdtToken, btcToken) } var isPriceDisplayReversed by remember(limitStrategy) { mutableStateOf(false) } - var payAmount by remember(limitStrategy) { mutableStateOf(BigDecimal("1000")) } var strategy by remember(limitStrategy) { mutableStateOf(limitStrategy ?: LimitStrategy.BuyLow) } + var payAmount by remember(limitStrategy, strategy) { + mutableStateOf(defaultPayAmount(limitStrategy, strategy)) + } var limitPriceOffset by remember(limitStrategy, strategy) { mutableStateOf(BigDecimal.ZERO) } val isPairReversed = limitStrategy != null && strategy == LimitStrategy.SellHigh val fromToken = if (isPairReversed) btcToken else usdtToken val toToken = if (isPairReversed) usdtToken else btcToken - val amountStep = remember(isPairReversed) { - if (isPairReversed) BigDecimal("0.001") else BigDecimal("100") + val amountStep = remember(limitStrategy, strategy, isPairReversed) { + if (limitStrategy != null) { + when (strategy) { + LimitStrategy.BuyLow -> BigDecimal("100") + LimitStrategy.SellHigh -> BigDecimal.ONE + } + } else if (isPairReversed) { + BigDecimal("0.001") + } else { + BigDecimal("100") + } } val limitBasePrice = remember(marketPrice, strategy) { calculateLimitBasePrice( @@ -865,6 +876,19 @@ private fun calculateLimitBasePrice( return basePrice.max(LIMIT_PRICE_STEP) } +private fun defaultPayAmount( + limitStrategy: LimitStrategy?, + strategy: LimitStrategy, +): BigDecimal { + if (limitStrategy == null) { + return BigDecimal("1000") + } + return when (strategy) { + LimitStrategy.BuyLow -> BigDecimal("1000") + LimitStrategy.SellHigh -> BigDecimal.ONE + } +} + private fun calculateReceiveAmount( amount: BigDecimal, price: BigDecimal, From 450a1d304f57d17fd1bf9627dcc7b7ad51c00d71 Mon Sep 17 00:00:00 2001 From: SeniorZhai Date: Thu, 26 Mar 2026 13:19:41 +0800 Subject: [PATCH 20/35] Update position card --- .../web3/trade/perps/PerpsMarketDetailPage.kt | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/app/src/main/java/one/mixin/android/ui/home/web3/trade/perps/PerpsMarketDetailPage.kt b/app/src/main/java/one/mixin/android/ui/home/web3/trade/perps/PerpsMarketDetailPage.kt index 72ba7cf763..caad259f44 100644 --- a/app/src/main/java/one/mixin/android/ui/home/web3/trade/perps/PerpsMarketDetailPage.kt +++ b/app/src/main/java/one/mixin/android/ui/home/web3/trade/perps/PerpsMarketDetailPage.kt @@ -42,6 +42,8 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.PlatformTextStyle +import androidx.compose.ui.text.TextStyle import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.fragment.app.FragmentActivity @@ -622,6 +624,7 @@ private fun OpenPositionCard( val entryPrice = position.entryPrice.toBigDecimalOrNull() ?: BigDecimal.ZERO val liquidationPrice = calculateLiquidationPriceValue(entryPrice, position.leverage, isLong) + val compactTextStyle = TextStyle(platformStyle = PlatformTextStyle(includeFontPadding = false)) Column( modifier = Modifier @@ -638,6 +641,8 @@ private fun OpenPositionCard( Text( text = stringResource(R.string.perps_position), fontSize = 16.sp, + lineHeight = 16.sp, + style = compactTextStyle, fontWeight = FontWeight.Medium, color = MixinAppTheme.colors.textPrimary ) @@ -663,11 +668,15 @@ private fun OpenPositionCard( Text( text = stringResource(R.string.PnL).uppercase(), fontSize = 12.sp, + lineHeight = 14.sp, + style = compactTextStyle, color = MixinAppTheme.colors.textAssist ) Text( text = stringResource(R.string.Direction).uppercase(), fontSize = 12.sp, + lineHeight = 14.sp, + style = compactTextStyle, color = MixinAppTheme.colors.textAssist ) } @@ -680,6 +689,8 @@ private fun OpenPositionCard( Text( text = "${formatPerpsSignedFiatDecimal(pnl.multiply(fiatRate), fiatSymbol)}(${formatPerpsSignedPercent(roe)})", fontSize = 14.sp, + lineHeight = 17.sp, + style = compactTextStyle, color = pnlColor ) Row(verticalAlignment = Alignment.CenterVertically) { @@ -693,6 +704,7 @@ private fun OpenPositionCard( text = if (isLong) stringResource(R.string.Long) else stringResource(R.string.Short), fontSize = 10.sp, lineHeight = 12.sp, + style = compactTextStyle, color = Color.White ) } @@ -700,6 +712,8 @@ private fun OpenPositionCard( Text( text = "${position.leverage}x", fontSize = 14.sp, + lineHeight = 17.sp, + style = compactTextStyle, color = MixinAppTheme.colors.textPrimary ) } @@ -718,6 +732,8 @@ private fun OpenPositionCard( Text( text = stringResource(R.string.position_size).uppercase(), fontSize = 12.sp, + lineHeight = 14.sp, + style = compactTextStyle, color = MixinAppTheme.colors.textAssist ) Spacer(modifier = Modifier.width(9.dp)) @@ -738,6 +754,8 @@ private fun OpenPositionCard( Text( text = stringResource(R.string.Margin).uppercase(), fontSize = 12.sp, + lineHeight = 14.sp, + style = compactTextStyle, color = MixinAppTheme.colors.textAssist ) } @@ -750,11 +768,15 @@ private fun OpenPositionCard( Text( text = "${quantity.stripTrailingZeros().toPlainString()} ${position.tokenSymbol}", fontSize = 14.sp, + lineHeight = 17.sp, + style = compactTextStyle, color = MixinAppTheme.colors.textPrimary ) Text( text = formatPerpsFiatDecimal(amountValue, fiatSymbol), fontSize = 14.sp, + lineHeight = 17.sp, + style = compactTextStyle, color = MixinAppTheme.colors.textPrimary ) } @@ -771,11 +793,15 @@ private fun OpenPositionCard( Text( text = stringResource(R.string.Entry_Price).uppercase(), fontSize = 12.sp, + lineHeight = 14.sp, + style = compactTextStyle, color = MixinAppTheme.colors.textAssist ) Text( text = stringResource(R.string.Liquidation_Price).uppercase(), fontSize = 12.sp, + lineHeight = 14.sp, + style = compactTextStyle, color = MixinAppTheme.colors.textAssist ) } @@ -788,11 +814,15 @@ private fun OpenPositionCard( Text( text = "${fiatSymbol}${entryPrice.multiply(fiatRate).priceFormat()}", fontSize = 14.sp, + lineHeight = 17.sp, + style = compactTextStyle, color = MixinAppTheme.colors.textPrimary ) Text( text = "${fiatSymbol}${liquidationPrice.multiply(fiatRate).priceFormat()}", fontSize = 14.sp, + lineHeight = 17.sp, + style = compactTextStyle, color = MixinAppTheme.colors.textPrimary ) } From 587961c1574696b77fc53f50a4d66f4834f9ccf2 Mon Sep 17 00:00:00 2001 From: SeniorZhai Date: Thu, 26 Mar 2026 13:45:03 +0800 Subject: [PATCH 21/35] fix web header icon for fixed title --- .../ui/home/web3/trade/SpotTradeGuidePage.kt | 24 ------------------- ...nMemberUpgradeBottomSheetDialogFragment.kt | 1 - .../one/mixin/android/ui/web/WebFragment.kt | 10 +++++--- .../mixin/android/widget/WebControlView.kt | 13 ++++++++-- 4 files changed, 18 insertions(+), 30 deletions(-) diff --git a/app/src/main/java/one/mixin/android/ui/home/web3/trade/SpotTradeGuidePage.kt b/app/src/main/java/one/mixin/android/ui/home/web3/trade/SpotTradeGuidePage.kt index efecc89e8c..552fe4835b 100644 --- a/app/src/main/java/one/mixin/android/ui/home/web3/trade/SpotTradeGuidePage.kt +++ b/app/src/main/java/one/mixin/android/ui/home/web3/trade/SpotTradeGuidePage.kt @@ -595,30 +595,6 @@ private fun OrderPriceStepper( } } -@Composable -private fun StepperButton( - text: String, - enabled: Boolean, - onClick: () -> Unit, -) { - Surface( - color = if (enabled) Color.Transparent else MixinAppTheme.colors.backgroundWindow, - shape = CircleShape, - border = BorderStroke(1.dp, MixinAppTheme.colors.borderColor), - modifier = Modifier - .size(24.dp) - .clickable(enabled = enabled, onClick = onClick), - ) { - Box(contentAlignment = Alignment.Center) { - Text( - text = text, - fontSize = 14.sp, - color = if (enabled) MixinAppTheme.colors.textPrimary else MixinAppTheme.colors.textAssist, - ) - } - } -} - @Composable private fun PriceSubtitle( marketPrice: BigDecimal, diff --git a/app/src/main/java/one/mixin/android/ui/setting/member/MixinMemberUpgradeBottomSheetDialogFragment.kt b/app/src/main/java/one/mixin/android/ui/setting/member/MixinMemberUpgradeBottomSheetDialogFragment.kt index 2af5919948..be909cda24 100644 --- a/app/src/main/java/one/mixin/android/ui/setting/member/MixinMemberUpgradeBottomSheetDialogFragment.kt +++ b/app/src/main/java/one/mixin/android/ui/setting/member/MixinMemberUpgradeBottomSheetDialogFragment.kt @@ -27,7 +27,6 @@ import one.mixin.android.job.RefreshAccountJob import one.mixin.android.session.Session import one.mixin.android.ui.common.BottomSheetViewModel import one.mixin.android.ui.common.MixinComposeBottomSheetDialogFragment -import one.mixin.android.ui.conversation.link.parser.NewSchemeParser import one.mixin.android.ui.setting.ui.page.MixinMemberUpgradePage import one.mixin.android.ui.viewmodel.MemberViewModel import one.mixin.android.ui.web.WebActivity diff --git a/app/src/main/java/one/mixin/android/ui/web/WebFragment.kt b/app/src/main/java/one/mixin/android/ui/web/WebFragment.kt index 67275313bf..c2e49603a3 100644 --- a/app/src/main/java/one/mixin/android/ui/web/WebFragment.kt +++ b/app/src/main/java/one/mixin/android/ui/web/WebFragment.kt @@ -684,7 +684,7 @@ class WebFragment : BaseFragment() { icon: Bitmap?, ) { super.onReceivedIcon(view, icon) - if (!isBot()) { + if (!isBot() && fixedTitle == null) { icon?.let { _binding?.apply { iconIv.isVisible = true @@ -911,8 +911,12 @@ class WebFragment : BaseFragment() { } } app?.name?.let { binding.titleTv.text = it } - fixedTitle?.let { binding.titleTv.text = it } - app?.iconUrl?.let { + fixedTitle?.let { + binding.titleTv.text = it + binding.iconIv.isVisible = false + binding.webControl.hideMore() + } + if (fixedTitle == null) app?.iconUrl?.let { binding.iconIv.isVisible = true binding.iconIv.loadImage(it) binding.titleTv.updateLayoutParams { diff --git a/app/src/main/java/one/mixin/android/widget/WebControlView.kt b/app/src/main/java/one/mixin/android/widget/WebControlView.kt index d2ce72136b..3388c2e2c0 100644 --- a/app/src/main/java/one/mixin/android/widget/WebControlView.kt +++ b/app/src/main/java/one/mixin/android/widget/WebControlView.kt @@ -10,6 +10,8 @@ import one.mixin.android.databinding.ViewWebControlBinding import one.mixin.android.extension.dp class WebControlView(context: Context, attrs: AttributeSet) : LinearLayout(context, attrs) { + private var isMoreHidden = false + var mode = false set(value) { if (value != field) { @@ -33,20 +35,27 @@ class WebControlView(context: Context, attrs: AttributeSet) : LinearLayout(conte if (dark) { setBackgroundResource(R.drawable.bg_view_web_control_black) binding.moreIv.setImageResource(R.drawable.ic_more_horiz_white_24dp) - binding.closeIv.setImageResource(R.drawable.ic_close_white_24dp) + if (!isMoreHidden) { + binding.closeIv.setImageResource(R.drawable.ic_close_white_24dp) + } binding.divide.setBackgroundColor(context.getColor(R.color.bgWhiteNight)) } else { setBackgroundResource(R.drawable.bg_view_web_control_white) binding.moreIv.setImageResource(R.drawable.ic_more_horiz_black_24dp) - binding.closeIv.setImageResource(R.drawable.ic_close_dark_24dp) + if (!isMoreHidden) { + binding.closeIv.setImageResource(R.drawable.ic_close_dark_24dp) + } binding.divide.setBackgroundColor(context.getColor(R.color.bgWhite)) } } fun hideMore() { + isMoreHidden = true binding.divide.isVisible = false binding.moreFl.isVisible = false layoutParams = layoutParams.apply { width = 36.dp } + background = null + binding.closeIv.setImageResource(R.drawable.ic_circle_close) weightSum = 1f } From 14f9cf53b70dcbb93baa756283a4b3e2ca94a29a Mon Sep 17 00:00:00 2001 From: SeniorZhai Date: Thu, 26 Mar 2026 14:08:11 +0800 Subject: [PATCH 22/35] Update spot button --- .../ui/home/web3/trade/SpotTradeGuidePage.kt | 63 ++----------------- .../web3/trade/perps/PerpetualGuidePage.kt | 2 +- 2 files changed, 7 insertions(+), 58 deletions(-) diff --git a/app/src/main/java/one/mixin/android/ui/home/web3/trade/SpotTradeGuidePage.kt b/app/src/main/java/one/mixin/android/ui/home/web3/trade/SpotTradeGuidePage.kt index 552fe4835b..b99a6f5239 100644 --- a/app/src/main/java/one/mixin/android/ui/home/web3/trade/SpotTradeGuidePage.kt +++ b/app/src/main/java/one/mixin/android/ui/home/web3/trade/SpotTradeGuidePage.kt @@ -56,6 +56,7 @@ import one.mixin.android.compose.theme.MixinAppTheme import one.mixin.android.extension.numberFormat8 import one.mixin.android.extension.priceFormat import one.mixin.android.ui.home.web3.components.OutlinedTab +import one.mixin.android.ui.home.web3.trade.perps.GuideNavigationButton import one.mixin.android.ui.wallet.alert.components.cardBackground import one.mixin.android.vo.safe.TokenItem import one.mixin.android.widget.components.DotText @@ -717,13 +718,13 @@ private fun SpotTradeGuideBottomNavigation( .fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(12.dp), ) { - SpotTradeGuideNavigationButton( + GuideNavigationButton( text = tabs[previousTab], isPrevious = true, modifier = Modifier.weight(1f), onClick = { onSelect(previousTab) }, ) - SpotTradeGuideNavigationButton( + GuideNavigationButton( text = stringResource(R.string.Start), isPrevious = false, modifier = Modifier.weight(1f), @@ -739,13 +740,13 @@ private fun SpotTradeGuideBottomNavigation( .fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(12.dp), ) { - SpotTradeGuideNavigationButton( + GuideNavigationButton( text = tabs[previousTab], isPrevious = true, modifier = Modifier.weight(1f), onClick = { onSelect(previousTab) }, ) - SpotTradeGuideNavigationButton( + GuideNavigationButton( text = tabs[nextTab], isPrevious = false, modifier = Modifier.weight(1f), @@ -761,7 +762,7 @@ private fun SpotTradeGuideBottomNavigation( modifier = Modifier.fillMaxWidth(), contentAlignment = Alignment.Center, ) { - SpotTradeGuideNavigationButton( + GuideNavigationButton( text = buttonText, isPrevious = isPrevious, modifier = Modifier.fillMaxWidth(0.5f), @@ -770,58 +771,6 @@ private fun SpotTradeGuideBottomNavigation( } } -@Composable -private fun SpotTradeGuideNavigationButton( - text: String, - isPrevious: Boolean, - modifier: Modifier = Modifier, - onClick: () -> Unit, -) { - Button( - modifier = modifier.height(48.dp), - onClick = onClick, - colors = ButtonDefaults.outlinedButtonColors( - backgroundColor = MixinAppTheme.colors.accent, - contentColor = Color.White, - ), - shape = RoundedCornerShape(32.dp), - elevation = ButtonDefaults.elevation( - pressedElevation = 0.dp, - defaultElevation = 0.dp, - hoveredElevation = 0.dp, - focusedElevation = 0.dp, - ), - ) { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.Center, - ) { - if (isPrevious) { - Icon( - painter = painterResource(id = R.drawable.ic_guide_previous), - contentDescription = null, - tint = Color.Unspecified, - ) - Spacer(modifier = Modifier.width(4.dp)) - } - Text( - text = text, - fontSize = 16.sp, - fontWeight = FontWeight.Bold, - color = Color.White, - ) - if (!isPrevious) { - Spacer(modifier = Modifier.width(4.dp)) - Icon( - painter = painterResource(id = R.drawable.ic_guide_next), - contentDescription = null, - tint = Color.Unspecified, - ) - } - } - } -} - private fun calculateMarketPrice( usdtToken: TokenItem?, btcToken: TokenItem?, diff --git a/app/src/main/java/one/mixin/android/ui/home/web3/trade/perps/PerpetualGuidePage.kt b/app/src/main/java/one/mixin/android/ui/home/web3/trade/perps/PerpetualGuidePage.kt index 78ab1e9416..44d7db8e99 100644 --- a/app/src/main/java/one/mixin/android/ui/home/web3/trade/perps/PerpetualGuidePage.kt +++ b/app/src/main/java/one/mixin/android/ui/home/web3/trade/perps/PerpetualGuidePage.kt @@ -534,7 +534,7 @@ private fun GuideBottomNavigation( } @Composable -private fun GuideNavigationButton( +fun GuideNavigationButton( text: String, isPrevious: Boolean, modifier: Modifier = Modifier, From 9543956c52c893ee06066d344fefe2a818e1aa3c Mon Sep 17 00:00:00 2001 From: SeniorZhai Date: Thu, 26 Mar 2026 14:10:16 +0800 Subject: [PATCH 23/35] Update font weight --- .../android/ui/home/web3/components/Review.kt | 3 +- .../ui/home/web3/trade/ClosedPositionItem.kt | 1 - .../AddWalletBottomSheetDialogFragment.kt | 36 +- .../wallet/components/AssetDashboardScreen.kt | 211 +------ .../wallet/components/WalletCategoryFilter.kt | 6 +- .../fragment_add_wallet_bottom_sheet.xml | 574 +++++------------- 6 files changed, 186 insertions(+), 645 deletions(-) diff --git a/app/src/main/java/one/mixin/android/ui/home/web3/components/Review.kt b/app/src/main/java/one/mixin/android/ui/home/web3/components/Review.kt index 94be2d930a..8df83ca674 100644 --- a/app/src/main/java/one/mixin/android/ui/home/web3/components/Review.kt +++ b/app/src/main/java/one/mixin/android/ui/home/web3/components/Review.kt @@ -735,7 +735,8 @@ fun ActionButton( Text( text = text, color = if (enabled) contentColor else disabledContentColor, - fontSize = 16.sp + fontSize = 16.sp, + fontWeight = FontWeight.W400 ) } } diff --git a/app/src/main/java/one/mixin/android/ui/home/web3/trade/ClosedPositionItem.kt b/app/src/main/java/one/mixin/android/ui/home/web3/trade/ClosedPositionItem.kt index acda7b5aa9..a1018a7e8a 100644 --- a/app/src/main/java/one/mixin/android/ui/home/web3/trade/ClosedPositionItem.kt +++ b/app/src/main/java/one/mixin/android/ui/home/web3/trade/ClosedPositionItem.kt @@ -178,4 +178,3 @@ fun ClosedPositionItem( } } -private const val SMALL_SCREEN_WIDTH_DP = 360 diff --git a/app/src/main/java/one/mixin/android/ui/wallet/AddWalletBottomSheetDialogFragment.kt b/app/src/main/java/one/mixin/android/ui/wallet/AddWalletBottomSheetDialogFragment.kt index 2d246e81e8..0a7657a5c5 100644 --- a/app/src/main/java/one/mixin/android/ui/wallet/AddWalletBottomSheetDialogFragment.kt +++ b/app/src/main/java/one/mixin/android/ui/wallet/AddWalletBottomSheetDialogFragment.kt @@ -3,12 +3,7 @@ package one.mixin.android.ui.wallet import android.annotation.SuppressLint import android.app.Dialog import android.view.LayoutInflater -import android.view.View -import one.mixin.android.R import one.mixin.android.databinding.FragmentAddWalletBottomSheetBinding -import one.mixin.android.extension.defaultSharedPreferences -import one.mixin.android.extension.openUrl -import one.mixin.android.extension.putBoolean import one.mixin.android.ui.common.MixinBottomSheetDialogFragment import one.mixin.android.widget.BottomSheet @@ -22,7 +17,6 @@ class AddWalletBottomSheetDialogFragment : MixinBottomSheetDialogFragment() { companion object { const val TAG = "AddWalletBottomSheetDialogFragment" - private const val PREF_FREE_TRANSFER_CARD_DISMISSED = "pref_free_transfer_card_dismissed" fun newInstance() = AddWalletBottomSheetDialogFragment() } @@ -40,11 +34,6 @@ class AddWalletBottomSheetDialogFragment : MixinBottomSheetDialogFragment() { setCustomView(contentView) } binding.apply { - val openFreeTransferDoc = { - requireContext().openUrl(getString(R.string.url_cross_wallet_transaction_free)) - } - val preferences = requireContext().defaultSharedPreferences - rightIv.setOnClickListener { dismiss() } addWatchAddress.setOnClickListener { callback?.invoke(Action.ADD_WATCH_ADDRESS) @@ -62,30 +51,7 @@ class AddWalletBottomSheetDialogFragment : MixinBottomSheetDialogFragment() { callback?.invoke(Action.CREATE_WALLET) dismiss() } - freeTransferCard.visibility = if (preferences.getBoolean(PREF_FREE_TRANSFER_CARD_DISMISSED, false)) { - View.GONE - } else { - View.VISIBLE - } - freeTransferCloseIv.setOnClickListener { - preferences.putBoolean(PREF_FREE_TRANSFER_CARD_DISMISSED, true) - freeTransferCard.visibility = View.GONE - } - bindOptionalView("free_transfer_card") { - openFreeTransferDoc() - } - bindOptionalView("free_transfer_learn_more") { - openFreeTransferDoc() - } } } - - private fun bindOptionalView( - idName: String, - onClick: () -> Unit, - ) { - val id = binding.root.resources.getIdentifier(idName, "id", requireContext().packageName) - if (id == 0) return - binding.root.findViewById(id)?.setOnClickListener { onClick() } - } } + diff --git a/app/src/main/java/one/mixin/android/ui/wallet/components/AssetDashboardScreen.kt b/app/src/main/java/one/mixin/android/ui/wallet/components/AssetDashboardScreen.kt index 2280d32336..876ad33672 100644 --- a/app/src/main/java/one/mixin/android/ui/wallet/components/AssetDashboardScreen.kt +++ b/app/src/main/java/one/mixin/android/ui/wallet/components/AssetDashboardScreen.kt @@ -82,13 +82,6 @@ fun AssetDashboardScreen( val context = LocalContext.current val safeCreateGuidelineUrl: String = stringResource(R.string.safe_create_guideline_url) val safeLearnMoreUrl: String = stringResource(R.string.safe_learn_more_url) - val importWalletTitle = context.stringByName("import_wallet_title") - val importWalletDescription = context.stringByName("import_wallet_empty_description") - val importWalletGuideUrl = context.stringByName("import_wallet_guide_url") - val watchWalletDescription = context.stringByName("watch_wallet_empty_description") - val watchWalletGuideUrl = context.stringByName("watch_wallet_guide_url") - val importWalletIconRes = context.drawableByName("ic_import_wallet") - val watchWalletIconRes = context.drawableByName("ic_add_watch_wallet") val prefs = remember { context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE) } val hidePrivacyWalletInfo = remember { mutableStateOf(prefs.getBoolean(KEY_HIDE_PRIVACY_WALLET_INFO, false)) } val hideCommonWalletInfo = remember { mutableStateOf(prefs.getBoolean(KEY_HIDE_COMMON_WALLET_INFO, false)) } @@ -171,27 +164,17 @@ fun AssetDashboardScreen( .align(Alignment.TopEnd) .size(8.dp) .background(color = Color.Red, shape = CircleShape) - ) + ) } } } - val hasImportedWallets = wallets.any { it.isImported() } - val hasWatchWallets = wallets.any { it.isWatch() } - val hasSafeWallets = wallets.any { it.category == WalletCategory.MIXIN_SAFE.value } - val filteredWallets = remember(wallets, selectedCategory) { - wallets.filter { wallet -> - shouldShowWallet( - wallet = wallet, - selectedCategory = selectedCategory, - ) - } - } - val showTotalAssetsCard = selectedCategory == null || filteredWallets.isNotEmpty() + val hasImported = wallets.any { it.isImported() } + val hasWatch = wallets.any { it.isWatch() } WalletCategoryFilter( selectedCategory = selectedCategory, - hasImported = true, - hasWatch = true, + hasImported = hasImported, + hasWatch = hasWatch, hasSafe = true, showSafeBadge = !hasSeenSafeCategoryBadge.value, onCategorySelected = { @@ -216,38 +199,8 @@ fun AssetDashboardScreen( .fillMaxSize() .verticalScroll(rememberScrollState()) ) { - if (showTotalAssetsCard) { - TotalAssetsCard(selectedCategory = selectedCategory) - Spacer(modifier = Modifier.height(20.dp)) - } - - if (selectedCategory == "import" && !hasImportedWallets) { - WalletEmptyGuideCard( - title = importWalletTitle, - description = importWalletDescription, - iconRes = importWalletIconRes, - primaryActionText = stringResource(R.string.Import), - onPrimaryActionClick = onAddWalletClick, - onLearnMoreClick = { - context.openUrl(importWalletGuideUrl) - }, - ) - Spacer(modifier = Modifier.height(10.dp)) - } - - if (selectedCategory == "watch" && !hasWatchWallets) { - WalletEmptyGuideCard( - title = stringResource(R.string.add_watch_address), - description = watchWalletDescription, - iconRes = watchWalletIconRes, - primaryActionText = stringResource(R.string.Add), - onPrimaryActionClick = onAddWalletClick, - onLearnMoreClick = { - context.openUrl(watchWalletGuideUrl) - }, - ) - Spacer(modifier = Modifier.height(10.dp)) - } + TotalAssetsCard(selectedCategory = selectedCategory) + Spacer(modifier = Modifier.height(20.dp)) // Privacy wallet - always show if no filter or "all" selected if (selectedCategory == null) { @@ -258,7 +211,7 @@ fun AssetDashboardScreen( Spacer(modifier = Modifier.height(10.dp)) } - if (selectedCategory == WalletCategory.MIXIN_SAFE.value && !hasSafeWallets) { + if (selectedCategory == WalletCategory.MIXIN_SAFE.value && wallets.any { it.category == WalletCategory.MIXIN_SAFE.value }.not()) { if (Session.getAccount()?.membership?.isMembership() == true) { CreateSafeCard( onCreateClick = { @@ -278,7 +231,18 @@ fun AssetDashboardScreen( Spacer(modifier = Modifier.height(10.dp)) } - filteredWallets.forEach { wallet -> + wallets.forEach { wallet -> + val shouldShow = when (selectedCategory) { + null -> wallet.category != WalletCategory.MIXIN_SAFE.value && wallet.category != WalletCategory.WATCH_ADDRESS.value // Exclude safe, watching wallets when no category filter is selected + WalletCategory.MIXIN_SAFE.value -> wallet.category == WalletCategory.MIXIN_SAFE.value + WalletCategory.CLASSIC.value -> wallet.category == WalletCategory.CLASSIC.value + "import" -> wallet.isImported() + "watch" -> wallet.isWatch() + else -> true + } + + if (!shouldShow) return@forEach + if (wallet.category == WalletCategory.MIXIN_SAFE.value) { WalletCard( name = wallet.name, @@ -334,30 +298,6 @@ fun AssetDashboardScreen( } } -private fun shouldShowWallet( - wallet: one.mixin.android.db.web3.vo.WalletItem, - selectedCategory: String?, -): Boolean { - return when (selectedCategory) { - null -> wallet.category != WalletCategory.MIXIN_SAFE.value && wallet.category != WalletCategory.WATCH_ADDRESS.value - WalletCategory.MIXIN_SAFE.value -> wallet.category == WalletCategory.MIXIN_SAFE.value - WalletCategory.CLASSIC.value -> wallet.category == WalletCategory.CLASSIC.value - "import" -> wallet.isImported() - "watch" -> wallet.isWatch() - else -> true - } -} - -private fun Context.stringByName(name: String): String { - val resourceId = resources.getIdentifier(name, "string", packageName) - return if (resourceId != 0) getString(resourceId) else "" -} - -private fun Context.drawableByName(name: String): Int? { - return resources.getIdentifier(name, "drawable", packageName) - .takeIf { it != 0 } -} - @OptIn(ExperimentalFoundationApi::class) @Composable fun WalletInfoCard( @@ -444,115 +384,6 @@ fun WalletInfoCard( } } -@Composable -fun WalletEmptyGuideCard( - title: String, - description: String, - iconRes: Int?, - primaryActionText: String, - onPrimaryActionClick: () -> Unit, - onLearnMoreClick: () -> Unit, -) { - Column( - modifier = Modifier - .fillMaxWidth() - .cardBackground(MixinAppTheme.colors.background, MixinAppTheme.colors.borderColor) - .padding(16.dp) - ) { - Row(verticalAlignment = Alignment.CenterVertically) { - Column(modifier = Modifier.weight(1f)) { - Text( - text = title, - fontSize = 16.sp, - fontWeight = FontWeight.Bold, - color = MixinAppTheme.colors.textPrimary, - ) - - Spacer(modifier = Modifier.height(12.dp)) - - Text( - text = description, - fontSize = 14.sp, - lineHeight = 17.5.sp, - color = MixinAppTheme.colors.textMinor, - ) - } - - Spacer(modifier = Modifier.width(8.dp)) - - iconRes?.let { icon -> - Image( - painter = painterResource(id = icon), - contentDescription = null, - modifier = Modifier.size(48.dp) - ) - } - } - Spacer(modifier = Modifier.height(20.dp)) - - Row( - modifier = Modifier - .fillMaxWidth() - .background( - color = MixinAppTheme.colors.backgroundWindow, - shape = RoundedCornerShape(16.dp) - ), - ) { - Box( - modifier = Modifier - .weight(1f) - .clickable { onPrimaryActionClick() } - .padding(6.dp), - contentAlignment = Alignment.Center - ) { - Text( - text = primaryActionText, - fontSize = 14.sp, - fontWeight = FontWeight.SemiBold, - color = MixinAppTheme.colors.accent, - modifier = Modifier - .padding(6.dp) - .clip(RoundedCornerShape(bottomStart = 16.dp, topStart = 16.dp)) - ) - } - Spacer( - modifier = Modifier - .width(2.dp) - .height(24.dp) - .background(Color.Black.copy(alpha = 0.05f)) - .align(Alignment.CenterVertically) - ) - - Box( - modifier = Modifier - .weight(1f) - .clickable { onLearnMoreClick() } - .padding(6.dp), - contentAlignment = Alignment.Center - ) { - Text( - text = stringResource(R.string.Learn_More), - fontSize = 14.sp, - fontWeight = FontWeight.SemiBold, - color = MixinAppTheme.colors.textPrimary, - modifier = Modifier - .padding(6.dp) - .clip(RoundedCornerShape(topEnd = 16.dp, bottomEnd = 16.dp)) - ) - - Icon( - painter = painterResource(id = R.drawable.ic_arrow_top_right_small), - contentDescription = null, - tint = MixinAppTheme.colors.backgroundDark, - modifier = Modifier - .size(8.dp) - .align(Alignment.TopEnd) - ) - } - } - } -} - @Composable fun PrivacyWalletInfo( onLearnMoreClick: () -> Unit, @@ -942,4 +773,4 @@ fun CardPreview() { Spacer(modifier = Modifier.height(8.dp)) UpgradeSafeCard({}, {}) } -} +} \ No newline at end of file diff --git a/app/src/main/java/one/mixin/android/ui/wallet/components/WalletCategoryFilter.kt b/app/src/main/java/one/mixin/android/ui/wallet/components/WalletCategoryFilter.kt index 4ada5b9019..116241e003 100644 --- a/app/src/main/java/one/mixin/android/ui/wallet/components/WalletCategoryFilter.kt +++ b/app/src/main/java/one/mixin/android/ui/wallet/components/WalletCategoryFilter.kt @@ -28,6 +28,9 @@ fun WalletCategoryFilter( if (hasAll) { tabs.add(WalletCategoryTab(category = null, textResId = R.string.All, showBadge = false)) } + if (hasSafe) { + tabs.add(WalletCategoryTab(category = WalletCategory.MIXIN_SAFE.value, textResId = R.string.Wallet_Safe, showBadge = showSafeBadge)) + } if (hasCreated) { tabs.add(WalletCategoryTab(category = WalletCategory.CLASSIC.value, textResId = R.string.Wallet_Created, showBadge = false)) } @@ -37,9 +40,6 @@ fun WalletCategoryFilter( if (hasWatch) { tabs.add(WalletCategoryTab(category = "watch", textResId = R.string.Wallet_Watching, showBadge = false)) } - if (hasSafe) { - tabs.add(WalletCategoryTab(category = WalletCategory.MIXIN_SAFE.value, textResId = R.string.Wallet_Safe, showBadge = showSafeBadge)) - } val selectedIndex: Int = tabs.indexOfFirst { tab: WalletCategoryTab -> tab.category == selectedCategory }.let { index: Int -> if (index >= 0) index else 0 } diff --git a/app/src/main/res/layout/fragment_add_wallet_bottom_sheet.xml b/app/src/main/res/layout/fragment_add_wallet_bottom_sheet.xml index b08bef4856..c2e6c8cc87 100644 --- a/app/src/main/res/layout/fragment_add_wallet_bottom_sheet.xml +++ b/app/src/main/res/layout/fragment_add_wallet_bottom_sheet.xml @@ -1,11 +1,12 @@ + android:background="@drawable/bg_upper_round" + android:paddingBottom="16dp"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + android:layout_height="64dp" + android:layout_marginStart="16dp" + android:layout_marginEnd="16dp" + android:background="?attr/bg_market_card" + android:layout_marginBottom="10dp"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - + - + - + - + - - - + - + - - + - + - + - - + - + - + - + - + + - + - + - + - + + - + - - - - - - - - + + + \ No newline at end of file From e3f9c97067119594506b6d1658e6a2e297f627a2 Mon Sep 17 00:00:00 2001 From: SeniorZhai Date: Thu, 26 Mar 2026 15:21:59 +0800 Subject: [PATCH 24/35] Update title --- .../one/mixin/android/ui/home/web3/trade/SpotTradeGuidePage.kt | 2 +- app/src/main/res/values-zh-rCN/strings.xml | 3 ++- app/src/main/res/values/strings.xml | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/one/mixin/android/ui/home/web3/trade/SpotTradeGuidePage.kt b/app/src/main/java/one/mixin/android/ui/home/web3/trade/SpotTradeGuidePage.kt index b99a6f5239..9adf480b6f 100644 --- a/app/src/main/java/one/mixin/android/ui/home/web3/trade/SpotTradeGuidePage.kt +++ b/app/src/main/java/one/mixin/android/ui/home/web3/trade/SpotTradeGuidePage.kt @@ -103,7 +103,7 @@ fun SpotTradeGuidePage( .padding(horizontal = 20.dp, vertical = 20.dp), ) { Text( - text = stringResource(R.string.Trading_Guide), + text = stringResource(R.string.Spot_Trading_Guide), fontSize = 18.sp, fontWeight = FontWeight.W500, color = MixinAppTheme.colors.textPrimary, diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index eaca2a49de..e900f51408 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -2233,7 +2233,8 @@ 未找到可用报价。请尝试其他代币或金额。 Safe 金库、观察钱包和隐藏的资产不计入统计 共管的 Safe 金库不计入统计 - 现货交易指南 + 交易说明 + 现货交易指南 简介 永续合约允许您使用杠杆交易加密货币,从而放大您的潜在利润(和损失)。您可以做多(押注价格上涨)或做空(押注价格下跌),而无需拥有标的资产。 您可以随时平仓以实现盈亏。平仓价格基于当前市场价格。请务必监控您的仓位以避免爆仓。 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 295ad85575..45f2ab760f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -2295,7 +2295,8 @@ No available quotes found. Please try a different token or amount. Safes, watch wallets, and hidden assets are excluded from the total Co-managed safes are excluded from the total - Spot Trading Guide + Trading Guide + Spot Trading Guide Overview Perpetual contracts allow you to trade cryptocurrency with leverage, enabling you to amplify your potential profits (and losses). You can go long (bet on price increase) or short (bet on price decrease) without owning the underlying asset. Leverage allows you to control a larger position with less capital. From 10c55d4b8b91171945ad98402df382ba47b729af Mon Sep 17 00:00:00 2001 From: SeniorZhai Date: Thu, 26 Mar 2026 15:31:20 +0800 Subject: [PATCH 25/35] show trading guides once per category --- .../ui/home/web3/trade/TradeFragment.kt | 33 +++++++++++++++++++ .../android/ui/home/web3/trade/TradePage.kt | 19 ++++++++++- 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/one/mixin/android/ui/home/web3/trade/TradeFragment.kt b/app/src/main/java/one/mixin/android/ui/home/web3/trade/TradeFragment.kt index 0b21fafacf..df2e98fd52 100644 --- a/app/src/main/java/one/mixin/android/ui/home/web3/trade/TradeFragment.kt +++ b/app/src/main/java/one/mixin/android/ui/home/web3/trade/TradeFragment.kt @@ -112,6 +112,7 @@ class TradeFragment : BaseFragment() { const val maxLeftAmount = 0.01 const val PREF_TRADE_SELECTED_TAB_PREFIX: String = "pref_trade_selected_tab_" + const val PREF_TRADE_SPOT_GUIDE_SHOWN: String = "pref_trade_spot_guide_shown" inline fun newInstance( input: String? = null, @@ -274,6 +275,10 @@ class TradeFragment : BaseFragment() { var isPerpetualOrderBadgeDismissed by remember(currentWalletId) { mutableStateOf(defaultSharedPreferences.getBoolean(perpetualOrderBadgePrefKey, false)) } + var hasShownSpotGuide by remember { + mutableStateOf(defaultSharedPreferences.getBoolean(PREF_TRADE_SPOT_GUIDE_SHOWN, false)) + } + val hasShownPerpetualGuide = isPerpetualTabBadgeDismissed TradePage( walletId = walletId, @@ -369,6 +374,34 @@ class TradeFragment : BaseFragment() { this@apply.hideKeyboard() navTo(OrderDetailFragment.newInstance(orderId), OrderDetailFragment.TAG) }, + hasShownSpotGuide = hasShownSpotGuide, + hasShownPerpetualGuide = hasShownPerpetualGuide, + onShowTradingGuideIfNeeded = { tabIndex -> + this@apply.hideKeyboard() + when { + walletId == null && tabIndex >= SpotTradeGuideBottomSheetDialogFragment.TAB_LIMIT -> { + if (!hasShownPerpetualGuide) { + isPerpetualTabBadgeDismissed = true + defaultSharedPreferences.putBoolean(perpetualBadgePrefKey, true) + PerpetualGuideBottomSheetDialogFragment.newInstance() + .show(parentFragmentManager, PerpetualGuideBottomSheetDialogFragment.TAG) + } + } + tabIndex == 1 || tabIndex == 0 -> { + if (!hasShownSpotGuide) { + hasShownSpotGuide = true + defaultSharedPreferences.putBoolean(PREF_TRADE_SPOT_GUIDE_SHOWN, true) + val initialGuideTab = if (tabIndex == 1) { + SpotTradeGuideBottomSheetDialogFragment.TAB_LIMIT + } else { + SpotTradeGuideBottomSheetDialogFragment.TAB_SWAP + } + SpotTradeGuideBottomSheetDialogFragment.newInstance(initialGuideTab) + .show(parentFragmentManager, SpotTradeGuideBottomSheetDialogFragment.TAG) + } + } + } + }, onShowTradingGuide = { tabIndex -> this@apply.hideKeyboard() when { diff --git a/app/src/main/java/one/mixin/android/ui/home/web3/trade/TradePage.kt b/app/src/main/java/one/mixin/android/ui/home/web3/trade/TradePage.kt index 4e0ce666b4..bc1ab94ae3 100644 --- a/app/src/main/java/one/mixin/android/ui/home/web3/trade/TradePage.kt +++ b/app/src/main/java/one/mixin/android/ui/home/web3/trade/TradePage.kt @@ -98,6 +98,9 @@ fun TradePage( onSwitchToLimitOrder: (String, SwapToken, SwapToken) -> Unit, pop: () -> Unit, onLimitOrderClick: (String) -> Unit, + hasShownSpotGuide: Boolean, + hasShownPerpetualGuide: Boolean, + onShowTradingGuideIfNeeded: (Int) -> Unit, onShowTradingGuide: (Int) -> Unit, onShowMarketList: (Boolean) -> Unit, onShowAllMarkets: () -> Unit, @@ -214,6 +217,21 @@ fun TradePage( pageCount = { tabCount }, ) + LaunchedEffect( + pagerState.currentPage, + perpetualTabIndex, + hasShownSpotGuide, + hasShownPerpetualGuide, + ) { + val currentPage = pagerState.currentPage + val isSpotGuideTab = currentPage == 0 || currentPage == 1 + val isPerpetualGuideTab = perpetualTabIndex != null && currentPage == perpetualTabIndex + when { + isSpotGuideTab && !hasShownSpotGuide -> onShowTradingGuideIfNeeded(currentPage) + isPerpetualGuideTab && !hasShownPerpetualGuide -> onShowTradingGuideIfNeeded(currentPage) + } + } + // When SwapContent requests switching to Limit tab, animate to it LaunchedEffect(switchToLimitRequested.value) { if (switchToLimitRequested.value) { @@ -395,7 +413,6 @@ fun TradePage( } if (isPerpetualTab && !isPerpetualTabBadgeDismissed) { onDismissPerpetualTabBadge() - onShowTradingGuide(index) } onTabChanged(index) }, From 3ff0d8bdad50eb23785762df1b98edcde12012c3 Mon Sep 17 00:00:00 2001 From: SeniorZhai Date: Thu, 26 Mar 2026 17:39:42 +0800 Subject: [PATCH 26/35] Update padding --- .../mixin/android/ui/home/web3/trade/SpotTradeGuidePage.kt | 4 ++-- .../android/ui/home/web3/trade/perps/PerpetualGuidePage.kt | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/one/mixin/android/ui/home/web3/trade/SpotTradeGuidePage.kt b/app/src/main/java/one/mixin/android/ui/home/web3/trade/SpotTradeGuidePage.kt index 9adf480b6f..0838903116 100644 --- a/app/src/main/java/one/mixin/android/ui/home/web3/trade/SpotTradeGuidePage.kt +++ b/app/src/main/java/one/mixin/android/ui/home/web3/trade/SpotTradeGuidePage.kt @@ -714,7 +714,7 @@ private fun SpotTradeGuideBottomNavigation( if (previousTab != null && nextTab == null) { Row( modifier = Modifier - .padding(horizontal = 20.dp) + .padding(horizontal = 18.dp) .fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(12.dp), ) { @@ -736,7 +736,7 @@ private fun SpotTradeGuideBottomNavigation( if (previousTab != null && nextTab != null) { Row( modifier = Modifier - .padding(horizontal = 20.dp) + .padding(horizontal = 18.dp) .fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(12.dp), ) { diff --git a/app/src/main/java/one/mixin/android/ui/home/web3/trade/perps/PerpetualGuidePage.kt b/app/src/main/java/one/mixin/android/ui/home/web3/trade/perps/PerpetualGuidePage.kt index 44d7db8e99..8a27ffc8b6 100644 --- a/app/src/main/java/one/mixin/android/ui/home/web3/trade/perps/PerpetualGuidePage.kt +++ b/app/src/main/java/one/mixin/android/ui/home/web3/trade/perps/PerpetualGuidePage.kt @@ -476,7 +476,7 @@ private fun GuideBottomNavigation( if (previousTab != null && nextTab == null) { Row( modifier = Modifier - .padding(horizontal = 20.dp) + .padding(horizontal = 18.dp) .fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(12.dp), ) { @@ -498,7 +498,7 @@ private fun GuideBottomNavigation( if (previousTab != null && nextTab != null) { Row( modifier = Modifier - .padding(horizontal = 20.dp) + .padding(horizontal = 18.dp) .fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(12.dp), ) { From 4b8d4827c9a236785d8c96287a12b493271f950b Mon Sep 17 00:00:00 2001 From: Crossle Song Date: Thu, 26 Mar 2026 13:52:13 +0400 Subject: [PATCH 27/35] fix button padding --- .../java/one/mixin/android/ui/home/web3/components/Review.kt | 2 +- .../java/one/mixin/android/widget/components/MaterialButton.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/one/mixin/android/ui/home/web3/components/Review.kt b/app/src/main/java/one/mixin/android/ui/home/web3/components/Review.kt index 8df83ca674..893f9eb00b 100644 --- a/app/src/main/java/one/mixin/android/ui/home/web3/components/Review.kt +++ b/app/src/main/java/one/mixin/android/ui/home/web3/components/Review.kt @@ -723,7 +723,7 @@ fun ActionButton( contentColor = if (enabled) contentColor else disabledContentColor, ), shape = RoundedCornerShape(30.dp), - contentPadding = PaddingValues(horizontal = 35.dp, vertical = 10.dp), + contentPadding = PaddingValues(0.dp), elevation = ButtonDefaults.elevation( pressedElevation = 0.dp, defaultElevation = 0.dp, diff --git a/app/src/main/java/one/mixin/android/widget/components/MaterialButton.kt b/app/src/main/java/one/mixin/android/widget/components/MaterialButton.kt index ba2486d8d6..048dd2104a 100644 --- a/app/src/main/java/one/mixin/android/widget/components/MaterialButton.kt +++ b/app/src/main/java/one/mixin/android/widget/components/MaterialButton.kt @@ -48,7 +48,7 @@ fun MixinButton( modifier: Modifier = Modifier, enabled: Boolean = true, shape: Shape = RoundedCornerShape(32.dp), - contentPadding: PaddingValues = PaddingValues(horizontal = 35.dp, vertical = 10.dp), + contentPadding: PaddingValues = PaddingValues(0.dp), elevation: Dp = 0.dp, backgroundColor: Color = MixinAppTheme.colors.accent, contentColor: Color = Color.White, From 732a88e36f5f7c38a65d34c91cffdbc0d7ec46d0 Mon Sep 17 00:00:00 2001 From: Crossle Song Date: Thu, 26 Mar 2026 13:58:34 +0400 Subject: [PATCH 28/35] Revert "fix button padding" This reverts commit 4b8d4827c9a236785d8c96287a12b493271f950b. --- .../java/one/mixin/android/ui/home/web3/components/Review.kt | 2 +- .../java/one/mixin/android/widget/components/MaterialButton.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/one/mixin/android/ui/home/web3/components/Review.kt b/app/src/main/java/one/mixin/android/ui/home/web3/components/Review.kt index 893f9eb00b..8df83ca674 100644 --- a/app/src/main/java/one/mixin/android/ui/home/web3/components/Review.kt +++ b/app/src/main/java/one/mixin/android/ui/home/web3/components/Review.kt @@ -723,7 +723,7 @@ fun ActionButton( contentColor = if (enabled) contentColor else disabledContentColor, ), shape = RoundedCornerShape(30.dp), - contentPadding = PaddingValues(0.dp), + contentPadding = PaddingValues(horizontal = 35.dp, vertical = 10.dp), elevation = ButtonDefaults.elevation( pressedElevation = 0.dp, defaultElevation = 0.dp, diff --git a/app/src/main/java/one/mixin/android/widget/components/MaterialButton.kt b/app/src/main/java/one/mixin/android/widget/components/MaterialButton.kt index 048dd2104a..ba2486d8d6 100644 --- a/app/src/main/java/one/mixin/android/widget/components/MaterialButton.kt +++ b/app/src/main/java/one/mixin/android/widget/components/MaterialButton.kt @@ -48,7 +48,7 @@ fun MixinButton( modifier: Modifier = Modifier, enabled: Boolean = true, shape: Shape = RoundedCornerShape(32.dp), - contentPadding: PaddingValues = PaddingValues(0.dp), + contentPadding: PaddingValues = PaddingValues(horizontal = 35.dp, vertical = 10.dp), elevation: Dp = 0.dp, backgroundColor: Color = MixinAppTheme.colors.accent, contentColor: Color = Color.White, From 351a264479932920ca9710c3d3820b8563050d1d Mon Sep 17 00:00:00 2001 From: SeniorZhai Date: Thu, 26 Mar 2026 17:39:42 +0800 Subject: [PATCH 29/35] Update padding --- .../main/java/one/mixin/android/compose/InputAmountScreen.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/one/mixin/android/compose/InputAmountScreen.kt b/app/src/main/java/one/mixin/android/compose/InputAmountScreen.kt index d5508c6e9a..af7cf0212f 100644 --- a/app/src/main/java/one/mixin/android/compose/InputAmountScreen.kt +++ b/app/src/main/java/one/mixin/android/compose/InputAmountScreen.kt @@ -815,7 +815,7 @@ fun InputAmountPreviewScreen( Row( modifier = Modifier .fillMaxWidth() - .padding(horizontal = 50.dp, vertical = 16.dp), + .padding(horizontal = 45.dp, vertical = 16.dp), horizontalArrangement = Arrangement.spacedBy(28.dp) ) { if (invoiceUri != null) { From 513285af0ae99e4e05910229e6bb17a99efebe88 Mon Sep 17 00:00:00 2001 From: SeniorZhai Date: Thu, 26 Mar 2026 18:05:51 +0800 Subject: [PATCH 30/35] customize trade guide menu titles --- .../one/mixin/android/ui/home/web3/trade/HelpBottomSheet.kt | 3 ++- .../java/one/mixin/android/ui/home/web3/trade/TradePage.kt | 6 ++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/one/mixin/android/ui/home/web3/trade/HelpBottomSheet.kt b/app/src/main/java/one/mixin/android/ui/home/web3/trade/HelpBottomSheet.kt index 228091a9d2..c519260839 100644 --- a/app/src/main/java/one/mixin/android/ui/home/web3/trade/HelpBottomSheet.kt +++ b/app/src/main/java/one/mixin/android/ui/home/web3/trade/HelpBottomSheet.kt @@ -27,6 +27,7 @@ import one.mixin.android.compose.theme.MixinAppTheme @Composable fun HelpBottomSheetContent( hideGuide: Boolean = false, + guideTitle: String = stringResource(R.string.Trading_Guide), onContactSupport: () -> Unit, onTradingGuide: () -> Unit, onDismiss: () -> Unit, @@ -44,7 +45,7 @@ fun HelpBottomSheetContent( if (!hideGuide){ HelpOption( - title = stringResource(R.string.Trading_Guide), + title = guideTitle, onClick = onTradingGuide ) } diff --git a/app/src/main/java/one/mixin/android/ui/home/web3/trade/TradePage.kt b/app/src/main/java/one/mixin/android/ui/home/web3/trade/TradePage.kt index bc1ab94ae3..fafaaa8278 100644 --- a/app/src/main/java/one/mixin/android/ui/home/web3/trade/TradePage.kt +++ b/app/src/main/java/one/mixin/android/ui/home/web3/trade/TradePage.kt @@ -249,7 +249,13 @@ fun TradePage( sheetShape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp), sheetBackgroundColor = MixinAppTheme.colors.background, sheetContent = { + val currentGuideTitle = if (perpetualTabIndex != null && pagerState.currentPage == perpetualTabIndex) { + stringResource(R.string.Perpetual_Futures_Guide) + } else { + stringResource(R.string.Spot_Trading_Guide) + } HelpBottomSheetContent( + guideTitle = currentGuideTitle, onContactSupport = { coroutineScope.launch { bottomSheetState.hide() From 8e8d54adeec920a42bdca679f181273216f701ff Mon Sep 17 00:00:00 2001 From: SeniorZhai Date: Thu, 26 Mar 2026 18:28:20 +0800 Subject: [PATCH 31/35] Update padding --- .../main/java/one/mixin/android/compose/InputAmountScreen.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/one/mixin/android/compose/InputAmountScreen.kt b/app/src/main/java/one/mixin/android/compose/InputAmountScreen.kt index af7cf0212f..3522d47f7f 100644 --- a/app/src/main/java/one/mixin/android/compose/InputAmountScreen.kt +++ b/app/src/main/java/one/mixin/android/compose/InputAmountScreen.kt @@ -815,7 +815,7 @@ fun InputAmountPreviewScreen( Row( modifier = Modifier .fillMaxWidth() - .padding(horizontal = 45.dp, vertical = 16.dp), + .padding(horizontal = 42.dp, vertical = 16.dp), horizontalArrangement = Arrangement.spacedBy(28.dp) ) { if (invoiceUri != null) { From e645a70991a12ea4b6a87bf54d0a3e3e66048576 Mon Sep 17 00:00:00 2001 From: SeniorZhai Date: Thu, 26 Mar 2026 18:39:16 +0800 Subject: [PATCH 32/35] Update horizontal arrangement --- .../main/java/one/mixin/android/compose/InputAmountScreen.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/one/mixin/android/compose/InputAmountScreen.kt b/app/src/main/java/one/mixin/android/compose/InputAmountScreen.kt index 3522d47f7f..1bc6fe036e 100644 --- a/app/src/main/java/one/mixin/android/compose/InputAmountScreen.kt +++ b/app/src/main/java/one/mixin/android/compose/InputAmountScreen.kt @@ -815,8 +815,8 @@ fun InputAmountPreviewScreen( Row( modifier = Modifier .fillMaxWidth() - .padding(horizontal = 42.dp, vertical = 16.dp), - horizontalArrangement = Arrangement.spacedBy(28.dp) + .padding(vertical = 16.dp, horizontal = 20.dp), + horizontalArrangement = Arrangement.spacedBy(28.dp, Alignment.CenterHorizontally) ) { if (invoiceUri != null) { ActionButton( From 6c2b72b41838face364ec311ac77ffe1c9a3f6b6 Mon Sep 17 00:00:00 2001 From: SeniorZhai Date: Thu, 26 Mar 2026 18:48:26 +0800 Subject: [PATCH 33/35] Update horizontal arrangement --- .../android/ui/home/web3/trade/SpotTradeGuidePage.kt | 8 ++++---- .../ui/home/web3/trade/perps/PerpetualGuidePage.kt | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/one/mixin/android/ui/home/web3/trade/SpotTradeGuidePage.kt b/app/src/main/java/one/mixin/android/ui/home/web3/trade/SpotTradeGuidePage.kt index 0838903116..d7f3a998cd 100644 --- a/app/src/main/java/one/mixin/android/ui/home/web3/trade/SpotTradeGuidePage.kt +++ b/app/src/main/java/one/mixin/android/ui/home/web3/trade/SpotTradeGuidePage.kt @@ -714,9 +714,9 @@ private fun SpotTradeGuideBottomNavigation( if (previousTab != null && nextTab == null) { Row( modifier = Modifier - .padding(horizontal = 18.dp) + .padding(horizontal = 16.dp) .fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(12.dp), + horizontalArrangement = Arrangement.spacedBy(12.dp, Alignment.CenterHorizontally) ) { GuideNavigationButton( text = tabs[previousTab], @@ -736,9 +736,9 @@ private fun SpotTradeGuideBottomNavigation( if (previousTab != null && nextTab != null) { Row( modifier = Modifier - .padding(horizontal = 18.dp) + .padding(horizontal = 16.dp) .fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(12.dp), + horizontalArrangement = Arrangement.spacedBy(12.dp, Alignment.CenterHorizontally) ) { GuideNavigationButton( text = tabs[previousTab], diff --git a/app/src/main/java/one/mixin/android/ui/home/web3/trade/perps/PerpetualGuidePage.kt b/app/src/main/java/one/mixin/android/ui/home/web3/trade/perps/PerpetualGuidePage.kt index 8a27ffc8b6..521bdf5595 100644 --- a/app/src/main/java/one/mixin/android/ui/home/web3/trade/perps/PerpetualGuidePage.kt +++ b/app/src/main/java/one/mixin/android/ui/home/web3/trade/perps/PerpetualGuidePage.kt @@ -15,6 +15,7 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape @@ -541,7 +542,7 @@ fun GuideNavigationButton( onClick: () -> Unit, ) { MixinButton( - modifier = modifier.height(48.dp), + modifier = modifier.wrapContentSize(), onClick = onClick, shape = RoundedCornerShape(32.dp), ) { From fd544daf160667e40efd2abfb4d9aed0c5bc09b0 Mon Sep 17 00:00:00 2001 From: Crossle Song Date: Thu, 26 Mar 2026 14:58:42 +0400 Subject: [PATCH 34/35] fix help menu text --- .../one/mixin/android/ui/home/web3/trade/HelpBottomSheet.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/src/main/java/one/mixin/android/ui/home/web3/trade/HelpBottomSheet.kt b/app/src/main/java/one/mixin/android/ui/home/web3/trade/HelpBottomSheet.kt index c519260839..241e579d20 100644 --- a/app/src/main/java/one/mixin/android/ui/home/web3/trade/HelpBottomSheet.kt +++ b/app/src/main/java/one/mixin/android/ui/home/web3/trade/HelpBottomSheet.kt @@ -71,9 +71,7 @@ private fun HelpOption( ) { Text( text = title, - fontSize = 16.sp, color = MixinAppTheme.colors.textPrimary, - fontWeight = FontWeight.Medium ) } } From 83f57124ea74989150d4f1f855e28d9cc0e32645 Mon Sep 17 00:00:00 2001 From: Crossle Song Date: Thu, 26 Mar 2026 15:02:53 +0400 Subject: [PATCH 35/35] fix typo --- .../mixin/android/ui/home/web3/trade/SpotTradeGuidePage.kt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/app/src/main/java/one/mixin/android/ui/home/web3/trade/SpotTradeGuidePage.kt b/app/src/main/java/one/mixin/android/ui/home/web3/trade/SpotTradeGuidePage.kt index d7f3a998cd..784dd45fe4 100644 --- a/app/src/main/java/one/mixin/android/ui/home/web3/trade/SpotTradeGuidePage.kt +++ b/app/src/main/java/one/mixin/android/ui/home/web3/trade/SpotTradeGuidePage.kt @@ -1,6 +1,5 @@ package one.mixin.android.ui.home.web3.trade -import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.horizontalScroll @@ -20,11 +19,8 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll -import androidx.compose.material.Button -import androidx.compose.material.ButtonDefaults import androidx.compose.material.CircularProgressIndicator import androidx.compose.material.Icon -import androidx.compose.material.Surface import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect