Keep open positions carousel stable

This commit is contained in:
Codex
2026-07-09 22:50:33 +03:00
parent 77a063d263
commit 6fb79ee2a9
2 changed files with 62 additions and 8 deletions
+2 -2
View File
@@ -10,7 +10,7 @@ android {
applicationId = "xyz.kusoft.tradebotmonitor"
minSdk = 26
targetSdk = 36
versionCode = 16
versionName = "0.2.13"
versionCode = 17
versionName = "0.2.14"
}
}
@@ -62,10 +62,12 @@ class MainActivity : Activity() {
var positionsScroller: HorizontalScrollView? = null,
var positionsList: LinearLayout? = null,
var closedTradesContainer: LinearLayout? = null,
val positionOrder: MutableList<String> = mutableListOf(),
val positions: MutableMap<String, PositionBinding> = linkedMapOf(),
)
private data class PositionBinding(
var index: TextView? = null,
var value: TextView? = null,
var quantity: TextView? = null,
var entryPrice: TextView? = null,
@@ -165,7 +167,7 @@ class MainActivity : Activity() {
trainingStatusSignature = newTrainingStatusSignature
lastError = ""
ensureSelectedSymbol()
if (silent && hadSnapshot && !hadError && updateVisibleScreen(data)) {
if (silent && hadSnapshot && updateVisibleScreen(data)) {
return@post
}
if (shouldRenderAfterRefresh(silent, hadSnapshot, hadError, trainingChanged)) {
@@ -178,6 +180,9 @@ class MainActivity : Activity() {
val hadError = lastError.isNotBlank()
lastError = error.message ?: "ошибка подключения"
if (!silent) toast(lastError)
if (silent && hadSnapshot) {
return@post
}
if (shouldRenderAfterRefresh(silent, hadSnapshot, hadError, trainingChanged = false)) {
render()
}
@@ -258,10 +263,8 @@ class MainActivity : Activity() {
val binding = assetsBindings ?: return false
val positionSignature = positionsSignature(data.positions)
if (binding.positionSignature != positionSignature) {
binding.positionsContainer?.let { container ->
rebuildPositionsContent(container, data.positions, binding.positionsScroller?.scrollX ?: 0)
binding.positionSignature = positionSignature
} ?: return false
if (!syncPositionsList(binding, data.positions)) return false
binding.positionSignature = positionSignature
}
val closedSignature = closedTradesSignature(data.closedTrades, data.closedTradesSummary)
if (binding.closedTradesSignature != closedSignature) {
@@ -837,6 +840,7 @@ class MainActivity : Activity() {
private fun rebuildPositionsContent(container: LinearLayout, positions: List<PositionData>, preserveScrollX: Int = 0) {
container.removeAllViews()
assetsBindings?.positions?.clear()
assetsBindings?.positionOrder?.clear()
assetsBindings?.positionsScroller = null
assetsBindings?.positionsList = null
if (positions.isEmpty()) {
@@ -868,13 +872,61 @@ class MainActivity : Activity() {
cardWidth: Int = (resources.displayMetrics.widthPixels - dp(64)).coerceAtLeast(dp(260)),
) {
list.removeAllViews()
assetsBindings?.positionOrder?.clear()
positions.forEachIndexed { index, position ->
assetsBindings?.positionOrder?.add(positionKey(position))
list.addView(positionCard(position, index + 1, positions.size), LinearLayout.LayoutParams(cardWidth, ViewGroup.LayoutParams.WRAP_CONTENT).apply {
if (index < positions.lastIndex) setMargins(0, 0, dp(12), 0)
})
}
}
private fun syncPositionsList(binding: AssetsBindings, positions: List<PositionData>): Boolean {
val container = binding.positionsContainer ?: return false
val list = binding.positionsList
if (list == null || positions.isEmpty() || binding.positionOrder.isEmpty()) {
rebuildPositionsContent(container, positions, binding.positionsScroller?.scrollX ?: 0)
return true
}
val byKey = positions.associateBy(::positionKey)
var index = 0
while (index < binding.positionOrder.size) {
val key = binding.positionOrder[index]
if (!byKey.containsKey(key)) {
binding.positionOrder.removeAt(index)
binding.positions.remove(key)
list.removeViewAt(index)
} else {
index += 1
}
}
val cardWidth = (resources.displayMetrics.widthPixels - dp(64)).coerceAtLeast(dp(260))
positions.forEach { position ->
val key = positionKey(position)
if (!binding.positionOrder.contains(key)) {
binding.positionOrder.add(key)
list.addView(positionCard(position, binding.positionOrder.size, positions.size), LinearLayout.LayoutParams(cardWidth, ViewGroup.LayoutParams.WRAP_CONTENT))
}
}
updatePositionCardChrome(binding)
return true
}
private fun updatePositionCardChrome(binding: AssetsBindings) {
val total = binding.positionOrder.size
binding.positionOrder.forEachIndexed { index, key ->
setText(binding.positions[key]?.index, "${index + 1}/$total")
}
val list = binding.positionsList ?: return
for (i in 0 until list.childCount) {
val params = (list.getChildAt(i).layoutParams as? LinearLayout.LayoutParams) ?: continue
params.setMargins(0, 0, if (i < list.childCount - 1) dp(12) else 0, 0)
list.getChildAt(i).layoutParams = params
}
}
private fun positionCard(position: PositionData, index: Int, total: Int): View =
LinearLayout(this).apply {
orientation = LinearLayout.VERTICAL
@@ -884,7 +936,9 @@ class MainActivity : Activity() {
orientation = LinearLayout.HORIZONTAL
gravity = Gravity.CENTER_VERTICAL
addView(text(position.symbol, 20f, Typeface.BOLD), LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f))
addView(text("$index/$total", 12f, Typeface.BOLD, palette.muted))
val indexText = text("$index/$total", 12f, Typeface.BOLD, palette.muted)
assetsBindings?.positions?.getOrPut(positionKey(position)) { PositionBinding() }?.index = indexText
addView(indexText)
})
val valueText = text("${money(position.marketValue)} · ${signedPercent(position.unrealizedPnlPercent, 2)}", 15f, Typeface.BOLD, colorForSigned(position.unrealizedPnlPercent))
addView(valueText.top(dp(8)))