test: add Sage client sample app
This commit is contained in:
parent
4993e550db
commit
d9b545577c
65
test/sageclient/README.md
Normal file
65
test/sageclient/README.md
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
# Sage desktop client sample
|
||||||
|
|
||||||
|
This directory is an independent Compose Desktop sample app for Sage on top of the
|
||||||
|
root `bricks-mp` shared module. It is intentionally **not** included from the
|
||||||
|
root `settings.gradle.kts`, so normal library builds are unchanged.
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
From any directory:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
/path/to/bricks-mp/test/sageclient/build.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
The script:
|
||||||
|
|
||||||
|
1. resolves its own directory,
|
||||||
|
2. checks that `java` is available and is JDK 17+,
|
||||||
|
3. uses the repository Gradle wrapper when present, otherwise system `gradle`,
|
||||||
|
4. runs `gradle build` for this standalone sample.
|
||||||
|
|
||||||
|
Extra Gradle arguments can be appended, for example:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./test/sageclient/build.sh --info
|
||||||
|
```
|
||||||
|
|
||||||
|
## Run
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /path/to/bricks-mp/test/sageclient
|
||||||
|
../../gradlew run \
|
||||||
|
-Dsage.baseUrl=http://localhost:8080 \
|
||||||
|
-Dsage.centerUi=/center.ui
|
||||||
|
```
|
||||||
|
|
||||||
|
Optional properties:
|
||||||
|
|
||||||
|
- `sage.baseUrl` defaults to `http://localhost:8080`
|
||||||
|
- `sage.centerUi` defaults to `/center.ui`
|
||||||
|
- `sage.loginAction` defaults to `/rbac/user/login`
|
||||||
|
- `sage.loginUi` defaults to `/rbac/user/login.ui`
|
||||||
|
- `sage.username` / `sage.password` prefill the login form
|
||||||
|
- `sage.lang` overrides the default JVM locale tag
|
||||||
|
|
||||||
|
## What the sample demonstrates
|
||||||
|
|
||||||
|
- Uses the generic `BricksHttp` client from the shared module for Sage login and
|
||||||
|
`center.ui` loading. Sage-specific bootstrapping lives here, not in the shared
|
||||||
|
library package.
|
||||||
|
- Loads `center.ui` via `ActionDispatcher.dispatch(BricksBind(actiontype =
|
||||||
|
"urlwidget", ...))`, so HTTP handling stays in the shared ActionDispatcher /
|
||||||
|
BricksHttp flow.
|
||||||
|
- `BricksHttp` automatically appends the correct WebBricks query parameters for
|
||||||
|
`.ui` / `.dspy` backend requests:
|
||||||
|
`_webbricks_`, `_width`, `_height`, `_is_mobile`, `_lang`.
|
||||||
|
- `BricksHttp` surfaces HTTP 403, 401 and 3xx (including 301) as
|
||||||
|
`BricksHttpException`. `ActionDispatcher` handles them generically:
|
||||||
|
- 403 loads the configured login UI (`sage.loginUi`) in a dialog,
|
||||||
|
- 401 shows the server response as an unauthorized message,
|
||||||
|
- 3xx follows the `Location` header as a UI navigation target.
|
||||||
|
|
||||||
|
The sample is wired as a Gradle composite build through `includeBuild("../..")`
|
||||||
|
and depends on the root project module using `implementation("com.bricks.mp:shared")`
|
||||||
|
with dependency substitution to `:shared`.
|
||||||
34
test/sageclient/build.gradle.kts
Normal file
34
test/sageclient/build.gradle.kts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import org.jetbrains.compose.desktop.application.dsl.TargetFormat
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
alias(libs.plugins.kotlin.multiplatform)
|
||||||
|
alias(libs.plugins.compose.compiler)
|
||||||
|
alias(libs.plugins.jetbrains.compose)
|
||||||
|
}
|
||||||
|
|
||||||
|
kotlin {
|
||||||
|
jvm()
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
jvmMain.dependencies {
|
||||||
|
implementation("com.bricks.mp:shared")
|
||||||
|
implementation(compose.desktop.currentOs)
|
||||||
|
implementation(compose.runtime)
|
||||||
|
implementation(compose.foundation)
|
||||||
|
implementation(compose.material3)
|
||||||
|
implementation(compose.ui)
|
||||||
|
implementation(libs.kotlinx.coroutines.core)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
compose.desktop {
|
||||||
|
application {
|
||||||
|
mainClass = "com.bricks.test.sageclient.MainKt"
|
||||||
|
nativeDistributions {
|
||||||
|
targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
|
||||||
|
packageName = "sageclient"
|
||||||
|
packageVersion = "1.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
38
test/sageclient/build.sh
Executable file
38
test/sageclient/build.sh
Executable file
@ -0,0 +1,38 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
PROJECT_DIR="$SCRIPT_DIR"
|
||||||
|
ROOT_DIR="$(cd -- "$SCRIPT_DIR/../.." && pwd)"
|
||||||
|
|
||||||
|
if ! command -v java >/dev/null 2>&1; then
|
||||||
|
cat >&2 <<'EOF'
|
||||||
|
ERROR: java was not found in PATH.
|
||||||
|
Please install JDK 17 or newer, then rerun this script.
|
||||||
|
EOF
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
JAVA_VERSION_OUTPUT="$(java -version 2>&1 | head -n 1)"
|
||||||
|
JAVA_VERSION="$(printf '%s\n' "$JAVA_VERSION_OUTPUT" | sed -E 's/.*version "([0-9]+)(\.[0-9]+)?.*/\1/')"
|
||||||
|
if ! [[ "$JAVA_VERSION" =~ ^[0-9]+$ ]]; then
|
||||||
|
echo "WARNING: Unable to parse Java version from: $JAVA_VERSION_OUTPUT" >&2
|
||||||
|
elif [ "$JAVA_VERSION" -lt 17 ]; then
|
||||||
|
echo "ERROR: JDK 17 or newer is required. Found: $JAVA_VERSION_OUTPUT" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -x "$ROOT_DIR/gradlew" ]; then
|
||||||
|
GRADLE_CMD=("$ROOT_DIR/gradlew")
|
||||||
|
elif command -v gradle >/dev/null 2>&1; then
|
||||||
|
GRADLE_CMD=(gradle)
|
||||||
|
else
|
||||||
|
cat >&2 <<'EOF'
|
||||||
|
ERROR: Neither root gradlew nor system gradle was found.
|
||||||
|
Run from a checkout that contains the Gradle wrapper, or install Gradle.
|
||||||
|
EOF
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd "$PROJECT_DIR"
|
||||||
|
"${GRADLE_CMD[@]}" --no-configuration-cache build "$@"
|
||||||
13
test/sageclient/gradle/libs.versions.toml
Normal file
13
test/sageclient/gradle/libs.versions.toml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
[versions]
|
||||||
|
compose = "1.7.3"
|
||||||
|
compose-plugin = "1.7.3"
|
||||||
|
kotlin = "2.1.0"
|
||||||
|
coroutines = "1.9.0"
|
||||||
|
|
||||||
|
[libraries]
|
||||||
|
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" }
|
||||||
|
|
||||||
|
[plugins]
|
||||||
|
compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
|
||||||
|
jetbrains-compose = { id = "org.jetbrains.compose", version.ref = "compose-plugin" }
|
||||||
|
kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
|
||||||
26
test/sageclient/settings.gradle.kts
Normal file
26
test/sageclient/settings.gradle.kts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
rootProject.name = "bricks-mp-sageclient-sample"
|
||||||
|
|
||||||
|
pluginManagement {
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
gradlePluginPortal()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencyResolutionManagement {
|
||||||
|
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
|
||||||
|
|
||||||
|
includeBuild("../..") {
|
||||||
|
dependencySubstitution {
|
||||||
|
substitute(module("com.bricks.mp:shared")).using(project(":shared"))
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,294 @@
|
|||||||
|
package com.bricks.test.sageclient
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.OutlinedTextField
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
|
import androidx.compose.material3.TopAppBar
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.DisposableEffect
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
|
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||||
|
import androidx.compose.ui.unit.IntSize
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.window.Window
|
||||||
|
import androidx.compose.ui.window.WindowState
|
||||||
|
import androidx.compose.ui.window.application
|
||||||
|
import com.bricks.mp.actions.ActionDispatcher
|
||||||
|
import com.bricks.mp.core.BricksBind
|
||||||
|
import com.bricks.mp.core.BricksContext
|
||||||
|
import com.bricks.mp.core.BricksHttp
|
||||||
|
import com.bricks.mp.core.BricksHttpException
|
||||||
|
import com.bricks.mp.core.BricksWidget
|
||||||
|
import com.bricks.mp.core.RenderWidget
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import java.awt.event.ComponentAdapter
|
||||||
|
import java.awt.event.ComponentEvent
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
private const val DEFAULT_BASE_URL = "http://localhost:8080"
|
||||||
|
private const val DEFAULT_CENTER_UI = "/center.ui"
|
||||||
|
private const val DEFAULT_LOGIN_ACTION = "/rbac/user/login"
|
||||||
|
private const val DEFAULT_LOGIN_UI = "/rbac/user/login.ui"
|
||||||
|
|
||||||
|
fun main() = application {
|
||||||
|
val context = remember { BricksContext() }
|
||||||
|
val http = remember { BricksHttp(context) }
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
val windowState = remember { WindowState(width = 1280.dp, height = 800.dp) }
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
context.baseUrl = System.getProperty("sage.baseUrl", DEFAULT_BASE_URL)
|
||||||
|
}
|
||||||
|
|
||||||
|
Window(
|
||||||
|
onCloseRequest = {
|
||||||
|
http.close()
|
||||||
|
exitApplication()
|
||||||
|
},
|
||||||
|
title = "Sage Bricks Client",
|
||||||
|
state = windowState
|
||||||
|
) {
|
||||||
|
MaterialTheme {
|
||||||
|
val density = LocalDensity.current
|
||||||
|
val lang = remember { System.getProperty("sage.lang", Locale.getDefault().toLanguageTag()) }
|
||||||
|
val fallbackWidthPx = with(density) { windowState.size.width.roundToPx() }
|
||||||
|
val fallbackHeightPx = with(density) { windowState.size.height.roundToPx() }
|
||||||
|
var windowSizePx by remember { mutableStateOf(IntSize(fallbackWidthPx, fallbackHeightPx)) }
|
||||||
|
var dialogWidget by remember { mutableStateOf<BricksWidget?>(null) }
|
||||||
|
var message by remember { mutableStateOf<Triple<String, String, Boolean>?>(null) }
|
||||||
|
|
||||||
|
DisposableEffect(window) {
|
||||||
|
fun updateWindowSize() {
|
||||||
|
val size = window.size
|
||||||
|
if (size.width > 0 && size.height > 0) {
|
||||||
|
windowSizePx = IntSize(size.width, size.height)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val listener = object : ComponentAdapter() {
|
||||||
|
override fun componentResized(e: ComponentEvent) = updateWindowSize()
|
||||||
|
override fun componentShown(e: ComponentEvent) = updateWindowSize()
|
||||||
|
}
|
||||||
|
|
||||||
|
updateWindowSize()
|
||||||
|
window.addComponentListener(listener)
|
||||||
|
onDispose { window.removeComponentListener(listener) }
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(windowSizePx, lang) {
|
||||||
|
http.updateRequestContext(
|
||||||
|
width = windowSizePx.width.takeIf { it > 0 } ?: fallbackWidthPx,
|
||||||
|
height = windowSizePx.height.takeIf { it > 0 } ?: fallbackHeightPx,
|
||||||
|
isMobile = false,
|
||||||
|
lang = lang
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val actionDispatcher = remember(context, http, scope) {
|
||||||
|
ActionDispatcher(context = context, http = http, scope = scope).apply {
|
||||||
|
loginUiPath = System.getProperty("sage.loginUi", DEFAULT_LOGIN_UI)
|
||||||
|
onWidgetLoaded = { widget -> context.setCurrentWidget(widget) }
|
||||||
|
onDialog = { widget, show -> dialogWidget = if (show) widget else null }
|
||||||
|
onMessage = { title, body, isError -> message = Triple(title, body, isError) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val currentWidget by context.currentWidget.collectAsState()
|
||||||
|
SageClientScreen(
|
||||||
|
baseUrl = context.baseUrl,
|
||||||
|
widget = currentWidget,
|
||||||
|
actionDispatcher = actionDispatcher,
|
||||||
|
onBaseUrlChange = { context.baseUrl = it.trimEnd('/') },
|
||||||
|
onLoadCenter = {
|
||||||
|
actionDispatcher.dispatch(
|
||||||
|
BricksBind(
|
||||||
|
event = "click",
|
||||||
|
actiontype = "urlwidget",
|
||||||
|
url = System.getProperty("sage.centerUi", DEFAULT_CENTER_UI)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onLogin = { username, password ->
|
||||||
|
scope.launch {
|
||||||
|
loginAndLoadCenter(
|
||||||
|
context = context,
|
||||||
|
http = http,
|
||||||
|
actionDispatcher = actionDispatcher,
|
||||||
|
username = username,
|
||||||
|
password = password,
|
||||||
|
onMessage = { title, body, isError -> message = Triple(title, body, isError) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
dialogWidget?.let { widget ->
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = { dialogWidget = null },
|
||||||
|
title = { Text("Login required") },
|
||||||
|
text = { RenderWidget(widget, actionDispatcher) },
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(onClick = { dialogWidget = null }) { Text("Close") }
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
message?.let { (title, body, _) ->
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = { message = null },
|
||||||
|
title = { Text(title) },
|
||||||
|
text = { Text(body) },
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(onClick = { message = null }) { Text("OK") }
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
private fun SageClientScreen(
|
||||||
|
baseUrl: String,
|
||||||
|
widget: BricksWidget?,
|
||||||
|
actionDispatcher: ActionDispatcher,
|
||||||
|
onBaseUrlChange: (String) -> Unit,
|
||||||
|
onLoadCenter: () -> Unit,
|
||||||
|
onLogin: (String, String) -> Unit
|
||||||
|
) {
|
||||||
|
var editableBaseUrl by remember(baseUrl) { mutableStateOf(baseUrl) }
|
||||||
|
var username by remember { mutableStateOf(System.getProperty("sage.username", "")) }
|
||||||
|
var password by remember { mutableStateOf(System.getProperty("sage.password", "")) }
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
topBar = {
|
||||||
|
TopAppBar(title = { Text("Sage Bricks Client") })
|
||||||
|
}
|
||||||
|
) { padding ->
|
||||||
|
Column(modifier = Modifier.padding(padding).fillMaxSize()) {
|
||||||
|
Row(modifier = Modifier.fillMaxWidth().padding(12.dp)) {
|
||||||
|
OutlinedTextField(
|
||||||
|
value = editableBaseUrl,
|
||||||
|
onValueChange = {
|
||||||
|
editableBaseUrl = it
|
||||||
|
onBaseUrlChange(it)
|
||||||
|
},
|
||||||
|
label = { Text("Sage base URL") },
|
||||||
|
singleLine = true,
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
)
|
||||||
|
Spacer(Modifier.width(8.dp))
|
||||||
|
Button(onClick = onLoadCenter, modifier = Modifier.padding(top = 8.dp)) {
|
||||||
|
Text("Load center.ui")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Row(modifier = Modifier.fillMaxWidth().padding(horizontal = 12.dp, vertical = 4.dp)) {
|
||||||
|
OutlinedTextField(
|
||||||
|
value = username,
|
||||||
|
onValueChange = { username = it },
|
||||||
|
label = { Text("Username") },
|
||||||
|
singleLine = true,
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
)
|
||||||
|
Spacer(Modifier.width(8.dp))
|
||||||
|
OutlinedTextField(
|
||||||
|
value = password,
|
||||||
|
onValueChange = { password = it },
|
||||||
|
label = { Text("Password") },
|
||||||
|
visualTransformation = PasswordVisualTransformation(),
|
||||||
|
singleLine = true,
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
)
|
||||||
|
Spacer(Modifier.width(8.dp))
|
||||||
|
Button(onClick = { onLogin(username, password) }, modifier = Modifier.padding(top = 8.dp)) {
|
||||||
|
Text("Login + load")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Box(modifier = Modifier.fillMaxSize().padding(12.dp)) {
|
||||||
|
if (widget == null) {
|
||||||
|
Text(
|
||||||
|
"Click Load center.ui to fetch Sage UI. " +
|
||||||
|
"BricksHttp automatically appends WebBricks params " +
|
||||||
|
"(_webbricks_, _width, _height, _is_mobile, _lang) for .ui/.dspy requests. " +
|
||||||
|
"HTTP 403/401/301 responses are surfaced to ActionDispatcher/BricksHttp handling."
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
RenderWidget(
|
||||||
|
widget = widget,
|
||||||
|
actionDispatcher = actionDispatcher,
|
||||||
|
modifier = Modifier.fillMaxSize()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun loginAndLoadCenter(
|
||||||
|
context: BricksContext,
|
||||||
|
http: BricksHttp,
|
||||||
|
actionDispatcher: ActionDispatcher,
|
||||||
|
username: String,
|
||||||
|
password: String,
|
||||||
|
onMessage: (String, String, Boolean) -> Unit
|
||||||
|
) {
|
||||||
|
val loginAction = System.getProperty("sage.loginAction", DEFAULT_LOGIN_ACTION)
|
||||||
|
runCatching {
|
||||||
|
http.postForm(
|
||||||
|
url = context.entireUrl(loginAction),
|
||||||
|
form = mapOf(
|
||||||
|
"username" to username,
|
||||||
|
"password" to password
|
||||||
|
),
|
||||||
|
authToken = context.authToken
|
||||||
|
)
|
||||||
|
}.onSuccess {
|
||||||
|
onMessage("Login", "Login request completed; loading center.ui", false)
|
||||||
|
actionDispatcher.dispatch(
|
||||||
|
BricksBind(
|
||||||
|
event = "login-success",
|
||||||
|
actiontype = "urlwidget",
|
||||||
|
url = System.getProperty("sage.centerUi", DEFAULT_CENTER_UI)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}.onFailure { error ->
|
||||||
|
handleLoadFailure("Login failed", error, onMessage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleLoadFailure(
|
||||||
|
title: String,
|
||||||
|
error: Throwable,
|
||||||
|
onMessage: (String, String, Boolean) -> Unit,
|
||||||
|
hint: String = ""
|
||||||
|
) {
|
||||||
|
if (error is BricksHttpException) {
|
||||||
|
val location = error.location?.let { "\nLocation: $it" }.orEmpty()
|
||||||
|
val extra = if (hint.isBlank()) "" else "\n$hint"
|
||||||
|
onMessage(title, "HTTP ${error.statusCode}: ${error.responseBody}$location$extra", true)
|
||||||
|
} else {
|
||||||
|
onMessage(title, error.message ?: error.toString(), true)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user