Fix Android refresh flicker and pin training host
This commit is contained in:
@@ -10,7 +10,7 @@ android {
|
|||||||
applicationId = "xyz.kusoft.tradebotmonitor"
|
applicationId = "xyz.kusoft.tradebotmonitor"
|
||||||
minSdk = 26
|
minSdk = 26
|
||||||
targetSdk = 36
|
targetSdk = 36
|
||||||
versionCode = 4
|
versionCode = 5
|
||||||
versionName = "0.2.1"
|
versionName = "0.2.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ class AppPrefs(context: Context) {
|
|||||||
if (saved.isNullOrBlank() || saved == LEGACY_PI_API_BASE_URL) {
|
if (saved.isNullOrBlank() || saved == LEGACY_PI_API_BASE_URL) {
|
||||||
prefs.edit().putString("api_base_url", DEFAULT_API_BASE_URL).apply()
|
prefs.edit().putString("api_base_url", DEFAULT_API_BASE_URL).apply()
|
||||||
}
|
}
|
||||||
|
if (prefs.getString("training_computer_name", null).isNullOrBlank()) {
|
||||||
|
pinDefaultTrainingComputer()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var apiBaseUrl: String
|
var apiBaseUrl: String
|
||||||
@@ -40,6 +43,23 @@ class AppPrefs(context: Context) {
|
|||||||
get() = prefs.getInt("retrain_interval_hours", 6)
|
get() = prefs.getInt("retrain_interval_hours", 6)
|
||||||
set(value) = prefs.edit().putInt("retrain_interval_hours", value.coerceAtLeast(1)).apply()
|
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 {
|
private fun normalizeBaseUrl(value: String): String {
|
||||||
val trimmed = value.trim().trimEnd('/')
|
val trimmed = value.trim().trimEnd('/')
|
||||||
if (trimmed.isBlank()) return DEFAULT_API_BASE_URL
|
if (trimmed.isBlank()) return DEFAULT_API_BASE_URL
|
||||||
@@ -53,5 +73,7 @@ class AppPrefs(context: Context) {
|
|||||||
private companion object {
|
private companion object {
|
||||||
const val DEFAULT_API_BASE_URL = "https://tb.kusoft.xyz"
|
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 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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+34
-5
@@ -101,18 +101,22 @@ class MainActivity : Activity() {
|
|||||||
try {
|
try {
|
||||||
val data = TradeBotApi(prefs.apiBaseUrl, prefs.commandToken).fetchSnapshot()
|
val data = TradeBotApi(prefs.apiBaseUrl, prefs.commandToken).fetchSnapshot()
|
||||||
mainHandler.post {
|
mainHandler.post {
|
||||||
|
val hadSnapshot = snapshot != null
|
||||||
|
val hadError = lastError.isNotBlank()
|
||||||
snapshot = data
|
snapshot = data
|
||||||
lastError = ""
|
lastError = ""
|
||||||
ensureSelectedSymbol()
|
ensureSelectedSymbol()
|
||||||
if (shouldRenderAfterRefresh()) {
|
if (shouldRenderAfterRefresh(silent, hadSnapshot, hadError)) {
|
||||||
render()
|
render()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error: Exception) {
|
} catch (error: Exception) {
|
||||||
mainHandler.post {
|
mainHandler.post {
|
||||||
|
val hadSnapshot = snapshot != null
|
||||||
|
val hadError = lastError.isNotBlank()
|
||||||
lastError = error.message ?: "ошибка подключения"
|
lastError = error.message ?: "ошибка подключения"
|
||||||
if (!silent) toast(lastError)
|
if (!silent) toast(lastError)
|
||||||
if (shouldRenderAfterRefresh()) {
|
if (shouldRenderAfterRefresh(silent, hadSnapshot, hadError)) {
|
||||||
render()
|
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()
|
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() {
|
private fun renderBottomNav() {
|
||||||
@@ -555,12 +565,31 @@ class MainActivity : Activity() {
|
|||||||
return section("Открытые позиции", box)
|
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 {
|
private fun retrainSettingsBlock(retrain: JSONObject, backtest: JSONObject): View {
|
||||||
val webhookInput = input("URL запуска retrain на Windows", prefs.retrainWebhookUrl)
|
val webhookInput = input("URL запуска retrain на Windows", prefs.retrainWebhookUrl)
|
||||||
return LinearLayout(this).apply {
|
return LinearLayout(this).apply {
|
||||||
orientation = LinearLayout.VERTICAL
|
orientation = LinearLayout.VERTICAL
|
||||||
addView(keyValueLine("Доступно", yesNo(retrain.optBoolean("available", false))))
|
addView(keyValueLine("Доступно", yesNo(retrain.optBoolean("available", false))))
|
||||||
addView(keyValueLine("Принята новая модель", yesNo(retrain.optBoolean("accepted", false))).top(dp(4)))
|
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()
|
val replay = backtest.optJSONObject("full_replay") ?: JSONObject()
|
||||||
addView(keyValueLine("Replay сделок", replay.optInt("trades", 0).toString()).top(dp(4)))
|
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)))
|
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()
|
if (stop) api.stopBot() else api.startBot()
|
||||||
mainHandler.post {
|
mainHandler.post {
|
||||||
toast(if (stop) "Команда остановки отправлена" else "Команда запуска отправлена")
|
toast(if (stop) "Команда остановки отправлена" else "Команда запуска отправлена")
|
||||||
refreshData(silent = true)
|
refreshData(silent = false)
|
||||||
}
|
}
|
||||||
} catch (error: Exception) {
|
} catch (error: Exception) {
|
||||||
mainHandler.post { toast(error.message ?: "Команда не выполнена") }
|
mainHandler.post { toast(error.message ?: "Команда не выполнена") }
|
||||||
|
|||||||
Reference in New Issue
Block a user