Handle Android API authorization setup
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 = 3
|
versionCode = 4
|
||||||
versionName = "0.2.0"
|
versionName = "0.2.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+51
-10
@@ -33,7 +33,6 @@ class MainActivity : Activity() {
|
|||||||
private val executor = Executors.newSingleThreadExecutor()
|
private val executor = Executors.newSingleThreadExecutor()
|
||||||
private val mainHandler = Handler(Looper.getMainLooper())
|
private val mainHandler = Handler(Looper.getMainLooper())
|
||||||
private val scrollPositions = mutableMapOf<String, Int>()
|
private val scrollPositions = mutableMapOf<String, Int>()
|
||||||
private val clockFormat = SimpleDateFormat("HH:mm", Locale("ru", "RU"))
|
|
||||||
|
|
||||||
private var snapshot: BotSnapshot? = null
|
private var snapshot: BotSnapshot? = null
|
||||||
private var lastError: String = ""
|
private var lastError: String = ""
|
||||||
@@ -78,10 +77,10 @@ class MainActivity : Activity() {
|
|||||||
bottomNav = LinearLayout(this).apply {
|
bottomNav = LinearLayout(this).apply {
|
||||||
orientation = LinearLayout.HORIZONTAL
|
orientation = LinearLayout.HORIZONTAL
|
||||||
gravity = Gravity.CENTER
|
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)
|
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()
|
renderBottomNav()
|
||||||
render(saveScroll = false)
|
render(saveScroll = false)
|
||||||
@@ -187,7 +186,7 @@ class MainActivity : Activity() {
|
|||||||
box.addView(statusLine("AI Торговля", modeBadgeText()))
|
box.addView(statusLine("AI Торговля", modeBadgeText()))
|
||||||
val data = snapshot
|
val data = snapshot
|
||||||
if (data == null) {
|
if (data == null) {
|
||||||
box.addView(emptyState("Нет данных от API. ${lastError.ifBlank { "Проверь подключение." }}").top(dp(18)))
|
box.addView(connectionStatePanel().top(dp(18)))
|
||||||
return wrapScroll(box)
|
return wrapScroll(box)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,7 +210,7 @@ class MainActivity : Activity() {
|
|||||||
box.addView(statusLine("Рынки", "12 spot-пар"))
|
box.addView(statusLine("Рынки", "12 spot-пар"))
|
||||||
val data = snapshot
|
val data = snapshot
|
||||||
if (data == null) {
|
if (data == null) {
|
||||||
box.addView(emptyState("Нет данных от API. ${lastError.ifBlank { "Проверь подключение." }}").top(dp(18)))
|
box.addView(connectionStatePanel().top(dp(18)))
|
||||||
return wrapScroll(box)
|
return wrapScroll(box)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -232,7 +231,7 @@ class MainActivity : Activity() {
|
|||||||
box.addView(statusLine("Сделка и риск", prefs.selectedSymbol))
|
box.addView(statusLine("Сделка и риск", prefs.selectedSymbol))
|
||||||
val data = snapshot
|
val data = snapshot
|
||||||
if (data == null) {
|
if (data == null) {
|
||||||
box.addView(emptyState("Нет данных от API. ${lastError.ifBlank { "Проверь подключение." }}").top(dp(18)))
|
box.addView(connectionStatePanel().top(dp(18)))
|
||||||
return wrapScroll(box)
|
return wrapScroll(box)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -255,7 +254,7 @@ class MainActivity : Activity() {
|
|||||||
box.addView(statusLine("Активы", modeBadgeText()))
|
box.addView(statusLine("Активы", modeBadgeText()))
|
||||||
val data = snapshot
|
val data = snapshot
|
||||||
if (data == null) {
|
if (data == null) {
|
||||||
box.addView(emptyState("Нет данных от API. ${lastError.ifBlank { "Проверь подключение." }}").top(dp(18)))
|
box.addView(connectionStatePanel().top(dp(18)))
|
||||||
return wrapScroll(box)
|
return wrapScroll(box)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -662,15 +661,46 @@ class MainActivity : Activity() {
|
|||||||
addView(LinearLayout(this@MainActivity).apply {
|
addView(LinearLayout(this@MainActivity).apply {
|
||||||
orientation = LinearLayout.HORIZONTAL
|
orientation = LinearLayout.HORIZONTAL
|
||||||
gravity = Gravity.CENTER_VERTICAL
|
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(statusBadge(badge, if (snapshot?.running == true) "ok" else "warn"))
|
||||||
})
|
})
|
||||||
addView(text(title, 21f, Typeface.BOLD).top(dp(18)))
|
|
||||||
if (lastError.isNotBlank() && snapshot == null) {
|
if (lastError.isNotBlank() && snapshot == null) {
|
||||||
addView(text(lastError.take(120), 12f, Typeface.NORMAL, palette.red).top(dp(6)))
|
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 =
|
private fun searchPlaceholder(value: String): View =
|
||||||
TextView(this).apply {
|
TextView(this).apply {
|
||||||
text = value
|
text = value
|
||||||
@@ -828,7 +858,7 @@ class MainActivity : Activity() {
|
|||||||
private fun screenContent(): LinearLayout =
|
private fun screenContent(): LinearLayout =
|
||||||
LinearLayout(this).apply {
|
LinearLayout(this).apply {
|
||||||
orientation = LinearLayout.VERTICAL
|
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 =
|
private fun wrapScroll(content: View): View =
|
||||||
@@ -1042,6 +1072,17 @@ class MainActivity : Activity() {
|
|||||||
private fun dp(value: Int): Int =
|
private fun dp(value: Int): Int =
|
||||||
(value * resources.displayMetrics.density).toInt()
|
(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) {
|
private fun toast(message: String) {
|
||||||
Toast.makeText(this, message, Toast.LENGTH_LONG).show()
|
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()
|
val text = stream?.bufferedReader(StandardCharsets.UTF_8)?.use(BufferedReader::readText).orEmpty()
|
||||||
connection.disconnect()
|
connection.disconnect()
|
||||||
if (code !in 200..299) {
|
if (code !in 200..299) {
|
||||||
|
if (code == HttpURLConnection.HTTP_UNAUTHORIZED) {
|
||||||
|
throw IllegalStateException("HTTP 401: сервер требует логин и пароль")
|
||||||
|
}
|
||||||
throw IllegalStateException("HTTP $code: ${text.take(240)}")
|
throw IllegalStateException("HTTP $code: ${text.take(240)}")
|
||||||
}
|
}
|
||||||
return if (text.isBlank()) JSONObject() else JSONObject(text)
|
return if (text.isBlank()) JSONObject() else JSONObject(text)
|
||||||
|
|||||||
Reference in New Issue
Block a user