diff --git a/android/TradeBotMonitor/app/build.gradle.kts b/android/TradeBotMonitor/app/build.gradle.kts index 55e79f0..5af793b 100644 --- a/android/TradeBotMonitor/app/build.gradle.kts +++ b/android/TradeBotMonitor/app/build.gradle.kts @@ -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" } } diff --git a/android/TradeBotMonitor/app/src/main/java/xyz/kusoft/tradebotmonitor/MainActivity.kt b/android/TradeBotMonitor/app/src/main/java/xyz/kusoft/tradebotmonitor/MainActivity.kt index f477e3a..7ca3655 100644 --- a/android/TradeBotMonitor/app/src/main/java/xyz/kusoft/tradebotmonitor/MainActivity.kt +++ b/android/TradeBotMonitor/app/src/main/java/xyz/kusoft/tradebotmonitor/MainActivity.kt @@ -62,10 +62,12 @@ class MainActivity : Activity() { var positionsScroller: HorizontalScrollView? = null, var positionsList: LinearLayout? = null, var closedTradesContainer: LinearLayout? = null, + val positionOrder: MutableList = mutableListOf(), val positions: MutableMap = 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, 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): 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)))