Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions app/src/main/java/one/mixin/android/db/perps/PerpsOrderDao.kt
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,17 @@ interface PerpsOrderDao : BaseDao<PerpsOrder> {
""")
suspend fun getOrder(orderId: String): PerpsOrderItem?

@Query("""
SELECT o.*, m.display_symbol, m.icon_url, m.token_symbol
FROM perps_orders o
LEFT JOIN markets m ON m.market_id = o.market_id
WHERE o.position_id = :positionId
AND o.order_type = 'close'
ORDER BY o.updated_at DESC
LIMIT 1
""")
suspend fun getCloseOrderByPositionId(positionId: String): PerpsOrderItem?

@Query("DELETE FROM perps_orders")
suspend fun deleteAll()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,8 @@ fun ClosedActivityItem(
}
val pnlPercent = order.roe.toBigDecimalOrNull()?.multiply(BigDecimal(100))
val titleRes = when {
isFailed && isLong -> R.string.Close_Long_Failed
isFailed -> R.string.Close_Short_Failed
isFailed && isLong -> R.string.Closed_Long_Failed
isFailed -> R.string.Closed_Short_Failed
isLong -> R.string.Closed_Long
else -> R.string.Closed_Short
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,9 @@ private fun resolveCurrentToken(
preferredAssetIds: List<String>,
): TokenItem? {
if (selectedToken == null) {
return availableTokens.firstOrNull()
return availableTokens
.sortedByDescending { it.balance.toBigDecimalOrNull() ?: BigDecimal.ZERO }
.firstOrNull()
}

val matchedToken = availableTokens.firstOrNull { it.assetId == selectedToken.assetId }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,20 @@ 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.widthIn
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicText
import androidx.compose.foundation.text.TextAutoSize
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
Expand All @@ -29,6 +34,8 @@ import one.mixin.android.api.response.perps.PerpsOrderItem
import one.mixin.android.compose.CoilImage
import one.mixin.android.compose.theme.MixinAppTheme
import one.mixin.android.extension.defaultSharedPreferences
import java.math.BigDecimal
import java.math.RoundingMode

@Composable
fun OpenedOrderItem(
Expand Down Expand Up @@ -59,15 +66,24 @@ fun OpenedOrderItem(
val leverageBackgroundColor = leverageColor.copy(alpha = 0.1f)
val title = when {
isIncrease && isFailed ->
stringResource(if (isLong) R.string.Add_Long_Failed else R.string.Add_Short_Failed)
stringResource(if (isLong) R.string.Added_Long_Failed else R.string.Added_Short_Failed)
isIncrease ->
stringResource(if (isLong) R.string.Added_Long else R.string.Added_Short)
isFailed ->
stringResource(if (isLong) R.string.Open_Long_Failed else R.string.Open_Short_Failed)
stringResource(if (isLong) R.string.Opened_Long_Failed else R.string.Opened_Short_Failed)
else ->
stringResource(if (isLong) R.string.Opened_Long else R.string.Opened_Short)
}

val amountValue = if (!isFailed && order.leverage > 0) {
val quantityDecimal = order.quantity.toBigDecimalOrNull()?.abs() ?: BigDecimal.ZERO
val entryPriceDecimal = order.entryPrice.toBigDecimalOrNull() ?: BigDecimal.ZERO
quantityDecimal.multiply(entryPriceDecimal)
.divide(BigDecimal(order.leverage), 8, RoundingMode.HALF_UP)
} else {
null
}

Row(
modifier = Modifier
.fillMaxWidth()
Expand Down Expand Up @@ -122,5 +138,26 @@ fun OpenedOrderItem(
overflow = TextOverflow.Ellipsis,
)
}

if (amountValue != null) {
Spacer(modifier = Modifier.width(8.dp))
BasicText(
text = formatPerpsUsdDecimal(amountValue),
modifier = Modifier.widthIn(max = 160.dp),
style = TextStyle(
fontSize = 14.sp,
color = MixinAppTheme.colors.textPrimary,
textAlign = TextAlign.End,
),
maxLines = 1,
softWrap = false,
overflow = TextOverflow.Ellipsis,
autoSize = TextAutoSize.StepBased(
minFontSize = 8.sp,
maxFontSize = 14.sp,
stepSize = 0.5.sp,
),
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -881,6 +881,12 @@ class PerpetualViewModel @Inject constructor(
}
}

suspend fun getCloseOrderFromDb(positionId: String): PerpsOrderItem? {
return withContext(Dispatchers.IO) {
perpsOrderDao.getCloseOrderByPositionId(positionId)
}
}

private suspend fun upsertSyncedOrders(orders: List<PerpsOrder>) {
if (orders.isEmpty()) return

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.delay
import one.mixin.android.Constants
import one.mixin.android.R
import one.mixin.android.api.response.perps.PerpsMarket
Expand Down Expand Up @@ -92,6 +93,8 @@ import one.mixin.android.widget.components.MixinButton
import java.math.BigDecimal
import java.math.RoundingMode

private const val PERPS_ADD_REFRESH_INTERVAL_MS = 3_000L

@AndroidEntryPoint
class PerpsAddBottomSheetDialogFragment : MixinComposeBottomSheetDialogFragment() {
companion object {
Expand Down Expand Up @@ -158,8 +161,21 @@ class PerpsAddBottomSheetDialogFragment : MixinComposeBottomSheetDialogFragment(
var selectedToken by remember { mutableStateOf<TokenItem?>(null) }
var market by remember { mutableStateOf<PerpsMarket?>(null) }

LaunchedEffect(acceptedPerpAssetIds) {
LaunchedEffect(position.marketId) {
market = viewModel.getMarketFromDb(position.marketId)
while (true) {
viewModel.loadMarketDetail(
marketId = position.marketId,
onSuccess = { data ->
market = data
},
onError = {},
)
delay(PERPS_ADD_REFRESH_INTERVAL_MS)
}
}

LaunchedEffect(acceptedPerpAssetIds) {
viewModel.loadUsdTokens { tokens ->
val supportedTokens = if (acceptedPerpAssetIds.isEmpty()) {
tokens
Expand Down Expand Up @@ -242,7 +258,11 @@ private fun PerpsAddContent(
val insufficientBalance = amountValue != null && amountValue > BigDecimal.ZERO && amountValue > tokenBalance
val canAdd = selectedToken != null && hasInputAmount && !insufficientBalance && !belowMinimumMargin && !aboveMaximumMargin
val marketSymbol = position.tokenSymbol ?: position.displaySymbol.orEmpty()
val currentPrice = position.markPrice.orEmpty().ifBlank { position.entryPrice }
val currentPrice = market?.last.orEmpty()
.ifBlank { market?.markPrice.orEmpty() }
.ifBlank { position.markPrice.orEmpty() }
.ifBlank { position.entryPrice }
val priceScale = market?.priceScale ?: position.priceScale

val minimumMarginError = stringResource(
R.string.perps_minimum_margin,
Expand All @@ -260,10 +280,10 @@ private fun PerpsAddContent(
else -> null
}

val currentPriceText = formatPerpsPrice(currentPrice, position.priceScale)
val currentPriceText = formatPerpsPrice(currentPrice, priceScale)
val entryPriceText = position.entryPrice
.takeIf { it.isNotBlank() }
?.let { formatPerpsPrice(it, position.priceScale) }
?.let { formatPerpsPrice(it, priceScale) }
?: "--"
val subtitleRawText = stringResource(R.string.auto_close_subtitle_after_open, entryPriceText, currentPriceText)
val subtitleLabelColor = MixinAppTheme.colors.textRemarks
Expand Down Expand Up @@ -512,6 +532,7 @@ private fun PerpsAddContent(
position = position,
amount = amount,
currentPrice = currentPrice,
priceScale = priceScale,
),
onTipClick = {
showPerpsGuide(PerpetualGuideBottomSheetDialogFragment.TAB_LIQUIDATION)
Expand Down Expand Up @@ -741,7 +762,9 @@ private fun resolveCurrentToken(
availableTokens: List<TokenItem>,
): TokenItem? {
if (selectedToken == null) {
return availableTokens.firstOrNull()
return availableTokens
.sortedByDescending { it.balance.toBigDecimalOrNull() ?: BigDecimal.ZERO }
.firstOrNull()
}

return availableTokens.firstOrNull { it.assetId == selectedToken.assetId } ?: selectedToken
Expand Down Expand Up @@ -803,10 +826,11 @@ private fun calculateEstimatedLiquidationPrice(
position: PerpsPositionItem,
amount: String,
currentPrice: String,
priceScale: Int,
): String {
val existingLiquidationPrice = position.liquidationPrice
?.takeIf { it.isNotBlank() }
?.let { formatPerpsPrice(it, position.priceScale) }
?.let { formatPerpsPrice(it, priceScale) }
?: "--"
val addMargin = amount.toBigDecimalOrNull()?.takeIf { it > BigDecimal.ZERO } ?: return existingLiquidationPrice
val currentQuantity = position.quantity.toBigDecimalOrNull()?.abs() ?: BigDecimal.ZERO
Expand All @@ -827,5 +851,5 @@ private fun calculateEstimatedLiquidationPrice(
} else {
averageEntry.multiply(BigDecimal.ONE.subtract(liquidationRatio))
}
return formatPerpsPrice(estimatedPrice, position.priceScale)
return formatPerpsPrice(estimatedPrice, priceScale)
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import one.mixin.android.R
import one.mixin.android.api.referral.ReferralShareInfo
import one.mixin.android.api.referral.buildReferralCopyUrl
import one.mixin.android.api.referral.buildReferralShareUrl
import one.mixin.android.api.response.perps.PerpsOrder
import one.mixin.android.api.response.perps.PerpsOrderItem
import one.mixin.android.api.response.perps.PerpsPositionItem
import one.mixin.android.databinding.FragmentPerpsPositionShareBottomBinding
Expand Down Expand Up @@ -110,7 +111,7 @@ class PerpsPositionShareBottomFragment : MixinBottomSheetDialogFragment() {

private var referralShareInfo: ReferralShareInfo? = null
private var isLoading = false
private var currentDisplayMetric = ShareDisplayMetric.ROE
private var currentDisplayMetric = ShareDisplayMetric.PNL
private var currentPosterStyle = SharePosterStyle.CLASSIC
private lateinit var shareData: ShareCardData
private lateinit var posterAdapter: PosterAdapter
Expand Down Expand Up @@ -205,20 +206,17 @@ class PerpsPositionShareBottomFragment : MixinBottomSheetDialogFragment() {
}

val closed = closeOrder ?: return false
if (closed.orderType != PerpsOrder.TYPE_CLOSE) return false
val pnlAmount = closed.realizedPnl.toBigDecimalSafely() ?: BigDecimal.ZERO
val effectiveLeverage = if (closed.leverage > 0) closed.leverage else 1
val pnlPercent = (closed.roe.toBigDecimalSafely() ?: BigDecimal.ZERO).multiply(BigDecimal(100))
bindCardData(
marketId = closed.marketId,
iconUrl = closed.iconUrl,
side = closed.side,
leverage = effectiveLeverage,
pnlAmount = pnlAmount,
pnlPercent = calculateClosedRoe(
entryPrice = closed.entryPrice,
closePrice = closed.closePrice,
side = closed.side,
leverage = effectiveLeverage,
),
pnlPercent = pnlPercent,
tokenSymbol = closed.tokenSymbol.orEmpty(),
displaySymbol = closed.displaySymbol.orEmpty(),
entryPrice = closed.entryPrice,
Expand Down Expand Up @@ -564,8 +562,8 @@ class PerpsPositionShareBottomFragment : MixinBottomSheetDialogFragment() {
)

private enum class ShareDisplayMetric(val labelRes: Int) {
ROE(R.string.RoE),
PNL(R.string.PnL),
ROE(R.string.perps_share_roe),
PNL(R.string.perps_share_pnl),
}

private enum class SharePosterStyle {
Expand Down Expand Up @@ -599,8 +597,8 @@ class PerpsPositionShareBottomFragment : MixinBottomSheetDialogFragment() {
itemBinding.topCard.setBackgroundResource(cardBackground(useProfitStyle))
itemBinding.assetIcon.loadImage(data.iconUrl, R.drawable.ic_avatar_place_holder)
itemBinding.pnlTv.text = when (currentDisplayMetric) {
ShareDisplayMetric.ROE -> formatSignedPercent(data.pnlPercent)
ShareDisplayMetric.PNL -> formatSignedAmount(data.pnlAmount)
ShareDisplayMetric.ROE -> formatSignedAmount(data.pnlAmount)
ShareDisplayMetric.PNL -> formatSignedPercent(data.pnlPercent)
}
itemBinding.pnlLabelTv.text = getString(currentDisplayMetric.labelRes)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,17 +184,15 @@ class PositionDetailFragment : BaseFragment() {
openViewMarket(closeOrder)
},
onShare = {
val active = cachedPosition.value
if (active != null) {
sharePosition(active)
} else {
lifecycleScope.launch {
val fromDb = viewModel.getPositionFromDb(closeOrder.positionId)
if (fromDb != null) {
sharePosition(fromDb)
} else {
sharePosition(closeOrder, closeOrder.leverage)
}
lifecycleScope.launch {
val closed = viewModel.getCloseOrderFromDb(closeOrder.positionId)
if (closed != null && closed.orderType == PerpsOrder.TYPE_CLOSE) {
sharePosition(closed, closed.leverage)
return@launch
}
val active = cachedPosition.value ?: viewModel.getPositionFromDb(closeOrder.positionId)
if (active != null) {
sharePosition(active)
}
}
},
Expand Down
Loading