Fix Android refresh flicker and pin training host

This commit is contained in:
Codex
2026-06-27 08:38:45 +03:00
parent f1f1cbb1e0
commit 98d6277d65
3 changed files with 58 additions and 7 deletions
+2 -2
View File
@@ -10,7 +10,7 @@ android {
applicationId = "xyz.kusoft.tradebotmonitor"
minSdk = 26
targetSdk = 36
versionCode = 4
versionName = "0.2.1"
versionCode = 5
versionName = "0.2.2"
}
}
@@ -10,6 +10,9 @@ class AppPrefs(context: Context) {
if (saved.isNullOrBlank() || saved == LEGACY_PI_API_BASE_URL) {
prefs.edit().putString("api_base_url", DEFAULT_API_BASE_URL).apply()
}
if (prefs.getString("training_computer_name", null).isNullOrBlank()) {
pinDefaultTrainingComputer()
}
}
var apiBaseUrl: String
@@ -40,6 +43,23 @@ class AppPrefs(context: Context) {
get() = prefs.getInt("retrain_interval_hours", 6)
set(value) = prefs.edit().putInt("retrain_interval_hours", value.coerceAtLeast(1)).apply()
val trainingComputerName: String
get() = prefs.getString("training_computer_name", DEFAULT_TRAINING_COMPUTER_NAME) ?: DEFAULT_TRAINING_COMPUTER_NAME
val trainingComputerPath: String
get() = prefs.getString("training_computer_path", DEFAULT_TRAINING_COMPUTER_PATH) ?: DEFAULT_TRAINING_COMPUTER_PATH
val trainingComputerPinned: Boolean
get() = prefs.getBoolean("training_computer_pinned", true)
fun pinDefaultTrainingComputer() {
prefs.edit()
.putString("training_computer_name", DEFAULT_TRAINING_COMPUTER_NAME)
.putString("training_computer_path", DEFAULT_TRAINING_COMPUTER_PATH)
.putBoolean("training_computer_pinned", true)
.apply()
}
private fun normalizeBaseUrl(value: String): String {
val trimmed = value.trim().trimEnd('/')
if (trimmed.isBlank()) return DEFAULT_API_BASE_URL
@@ -53,5 +73,7 @@ class AppPrefs(context: Context) {
private companion object {
const val DEFAULT_API_BASE_URL = "https://tb.kusoft.xyz"
const val LEGACY_PI_API_BASE_URL = "http://192.168.0.185:8787"
const val DEFAULT_TRAINING_COMPUTER_NAME = "DESKTOP-TMFDL0H"
const val DEFAULT_TRAINING_COMPUTER_PATH = "C:\\Repos\\TradeBot"
}
}
@@ -101,18 +101,22 @@ class MainActivity : Activity() {
try {
val data = TradeBotApi(prefs.apiBaseUrl, prefs.commandToken).fetchSnapshot()
mainHandler.post {
val hadSnapshot = snapshot != null
val hadError = lastError.isNotBlank()
snapshot = data
lastError = ""
ensureSelectedSymbol()
if (shouldRenderAfterRefresh()) {
if (shouldRenderAfterRefresh(silent, hadSnapshot, hadError)) {
render()
}
}
} catch (error: Exception) {
mainHandler.post {
val hadSnapshot = snapshot != null
val hadError = lastError.isNotBlank()
lastError = error.message ?: "ошибка подключения"
if (!silent) toast(lastError)
if (shouldRenderAfterRefresh()) {
if (shouldRenderAfterRefresh(silent, hadSnapshot, hadError)) {
render()
}
}
@@ -120,9 +124,15 @@ class MainActivity : Activity() {
}
}
private fun shouldRenderAfterRefresh(): Boolean {
private fun shouldRenderAfterRefresh(silent: Boolean, hadSnapshot: Boolean, hadError: Boolean): Boolean {
val focused = contentHost.findFocus()
return activeTab != "settings" || focused !is EditText
if (activeTab == "settings" && focused is EditText) {
return false
}
if (!silent) {
return true
}
return !hadSnapshot || hadError
}
private fun renderBottomNav() {
@@ -555,12 +565,31 @@ class MainActivity : Activity() {
return section("Открытые позиции", box)
}
private fun trainingComputerPanel(): View =
LinearLayout(this).apply {
orientation = LinearLayout.VERTICAL
addView(text("Компьютер обучения", 12f, Typeface.NORMAL, palette.muted))
addView(text(prefs.trainingComputerName, 18f, Typeface.BOLD, palette.green).top(dp(5)))
addView(text(prefs.trainingComputerPath, 12f, Typeface.NORMAL, palette.muted).top(dp(4)))
addView(keyValueLine("Статус", if (prefs.trainingComputerPinned) "закреплен" else "не закреплен", if (prefs.trainingComputerPinned) palette.green else palette.amber).top(dp(8)))
addView(text("Переобучение Torch закреплено за этой Windows-машиной. Телефон только управляет запуском и расписанием, Pi только исполняет бота.", 12f, Typeface.NORMAL, palette.muted).top(dp(8)))
addView(actionRow(
"Закрепить эту машину" to {
prefs.pinDefaultTrainingComputer()
toast("Компьютер обучения закреплен")
render()
},
).top(dp(10)))
}
private fun retrainSettingsBlock(retrain: JSONObject, backtest: JSONObject): View {
val webhookInput = input("URL запуска retrain на Windows", prefs.retrainWebhookUrl)
return LinearLayout(this).apply {
orientation = LinearLayout.VERTICAL
addView(keyValueLine("Доступно", yesNo(retrain.optBoolean("available", false))))
addView(keyValueLine("Принята новая модель", yesNo(retrain.optBoolean("accepted", false))).top(dp(4)))
addView(trainingComputerPanel().top(dp(12)))
addView(thinDivider().top(dp(12)))
val replay = backtest.optJSONObject("full_replay") ?: JSONObject()
addView(keyValueLine("Replay сделок", replay.optInt("trades", 0).toString()).top(dp(4)))
addView(keyValueLine("Replay PnL", signedMoney(replay.optDouble("net_pnl", 0.0)), colorForSigned(replay.optDouble("net_pnl", 0.0))).top(dp(4)))
@@ -942,7 +971,7 @@ class MainActivity : Activity() {
if (stop) api.stopBot() else api.startBot()
mainHandler.post {
toast(if (stop) "Команда остановки отправлена" else "Команда запуска отправлена")
refreshData(silent = true)
refreshData(silent = false)
}
} catch (error: Exception) {
mainHandler.post { toast(error.message ?: "Команда не выполнена") }