Handle Android API authorization setup

This commit is contained in:
Codex
2026-06-27 08:12:54 +03:00
parent ab2557ea26
commit f1f1cbb1e0
3 changed files with 56 additions and 12 deletions
+2 -2
View File
@@ -10,7 +10,7 @@ android {
applicationId = "xyz.kusoft.tradebotmonitor"
minSdk = 26
targetSdk = 36
versionCode = 3
versionName = "0.2.0"
versionCode = 4
versionName = "0.2.1"
}
}
@@ -33,7 +33,6 @@ class MainActivity : Activity() {
private val executor = Executors.newSingleThreadExecutor()
private val mainHandler = Handler(Looper.getMainLooper())
private val scrollPositions = mutableMapOf<String, Int>()
private val clockFormat = SimpleDateFormat("HH:mm", Locale("ru", "RU"))
private var snapshot: BotSnapshot? = null
private var lastError: String = ""
@@ -78,10 +77,10 @@ class MainActivity : Activity() {
bottomNav = LinearLayout(this).apply {
orientation = LinearLayout.HORIZONTAL
gravity = Gravity.CENTER
setPadding(dp(10), dp(7), dp(10), dp(9))
setPadding(dp(10), dp(7), dp(10), dp(9) + navigationBarInset())
background = boxDrawable(palette.panel, palette.line, 0)
}
root.addView(bottomNav, LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, dp(64)))
root.addView(bottomNav, LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, dp(64) + navigationBarInset()))
renderBottomNav()
render(saveScroll = false)
@@ -187,7 +186,7 @@ class MainActivity : Activity() {
box.addView(statusLine("AI Торговля", modeBadgeText()))
val data = snapshot
if (data == null) {
box.addView(emptyState("Нет данных от API. ${lastError.ifBlank { "Проверь подключение." }}").top(dp(18)))
box.addView(connectionStatePanel().top(dp(18)))
return wrapScroll(box)
}
@@ -211,7 +210,7 @@ class MainActivity : Activity() {
box.addView(statusLine("Рынки", "12 spot-пар"))
val data = snapshot
if (data == null) {
box.addView(emptyState("Нет данных от API. ${lastError.ifBlank { "Проверь подключение." }}").top(dp(18)))
box.addView(connectionStatePanel().top(dp(18)))
return wrapScroll(box)
}
@@ -232,7 +231,7 @@ class MainActivity : Activity() {
box.addView(statusLine("Сделка и риск", prefs.selectedSymbol))
val data = snapshot
if (data == null) {
box.addView(emptyState("Нет данных от API. ${lastError.ifBlank { "Проверь подключение." }}").top(dp(18)))
box.addView(connectionStatePanel().top(dp(18)))
return wrapScroll(box)
}
@@ -255,7 +254,7 @@ class MainActivity : Activity() {
box.addView(statusLine("Активы", modeBadgeText()))
val data = snapshot
if (data == null) {
box.addView(emptyState("Нет данных от API. ${lastError.ifBlank { "Проверь подключение." }}").top(dp(18)))
box.addView(connectionStatePanel().top(dp(18)))
return wrapScroll(box)
}
@@ -662,15 +661,46 @@ class MainActivity : Activity() {
addView(LinearLayout(this@MainActivity).apply {
orientation = LinearLayout.HORIZONTAL
gravity = Gravity.CENTER_VERTICAL
addView(text(clockFormat.format(Date()), 13f, Typeface.BOLD), LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f))
addView(text(title, 21f, Typeface.BOLD), LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f))
addView(statusBadge(badge, if (snapshot?.running == true) "ok" else "warn"))
})
addView(text(title, 21f, Typeface.BOLD).top(dp(18)))
if (lastError.isNotBlank() && snapshot == null) {
addView(text(lastError.take(120), 12f, Typeface.NORMAL, palette.red).top(dp(6)))
}
}
private fun connectionStatePanel(): View {
if (!isAuthError()) {
return emptyState("Нет данных от API. ${lastError.ifBlank { "Проверь подключение." }}")
}
val apiInput = input("Адрес API бота", prefs.apiBaseUrl)
val authInput = input("Логин и пароль: login:password", prefs.commandToken).apply {
inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD
}
return section("Нужен вход в API", LinearLayout(this).apply {
orientation = LinearLayout.VERTICAL
addView(text("Сервер tb.kusoft.xyz отвечает 401, значит он доступен, но требует авторизацию. Введите доступ в формате login:password, приложение само отправит Basic Auth.", 13f, Typeface.NORMAL, palette.muted))
addView(apiInput.top(dp(12)))
addView(authInput.top(dp(8)))
addView(actionRow(
"Подключиться" to {
prefs.apiBaseUrl = apiInput.text.toString()
prefs.commandToken = authInput.text.toString()
toast("Доступ сохранен, проверяю API")
refreshData(silent = false)
},
"Настройки" to {
activeTab = "settings"
renderBottomNav()
render(saveScroll = false)
},
).top(dp(10)))
})
}
private fun isAuthError(): Boolean =
lastError.contains("401")
private fun searchPlaceholder(value: String): View =
TextView(this).apply {
text = value
@@ -828,7 +858,7 @@ class MainActivity : Activity() {
private fun screenContent(): LinearLayout =
LinearLayout(this).apply {
orientation = LinearLayout.VERTICAL
setPadding(dp(18), dp(16), dp(18), dp(22))
setPadding(dp(18), dp(16) + statusBarInset(), dp(18), dp(22))
}
private fun wrapScroll(content: View): View =
@@ -1042,6 +1072,17 @@ class MainActivity : Activity() {
private fun dp(value: Int): Int =
(value * resources.displayMetrics.density).toInt()
private fun statusBarInset(): Int =
systemBarInset("status_bar_height")
private fun navigationBarInset(): Int =
systemBarInset("navigation_bar_height")
private fun systemBarInset(name: String): Int {
val id = resources.getIdentifier(name, "dimen", "android")
return if (id > 0) resources.getDimensionPixelSize(id) else 0
}
private fun toast(message: String) {
Toast.makeText(this, message, Toast.LENGTH_LONG).show()
}
@@ -76,6 +76,9 @@ class TradeBotApi(
val text = stream?.bufferedReader(StandardCharsets.UTF_8)?.use(BufferedReader::readText).orEmpty()
connection.disconnect()
if (code !in 200..299) {
if (code == HttpURLConnection.HTTP_UNAUTHORIZED) {
throw IllegalStateException("HTTP 401: сервер требует логин и пароль")
}
throw IllegalStateException("HTTP $code: ${text.take(240)}")
}
return if (text.isBlank()) JSONObject() else JSONObject(text)