Handle Android API authorization setup
This commit is contained in:
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
+51
-10
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user