diff --git a/composeApp/src/commonMain/composeResources/values/strings.xml b/composeApp/src/commonMain/composeResources/values/strings.xml
index e9b01eb9..ec223223 100644
--- a/composeApp/src/commonMain/composeResources/values/strings.xml
+++ b/composeApp/src/commonMain/composeResources/values/strings.xml
@@ -182,14 +182,20 @@
Username (optional)
Password (optional)
Client ID prefix
+ Used to identify this Spotube instance
+ Encrypt the broker connection
+ Check the current broker settings before starting a room.
Test connection
Testing…
Connected in %1$d ms
Failed: %1$s
Placeholder broker — configure your own to self-host
+ Connection
+ Credentials
+ Identity
+ Diagnostics
Go to album
Sleep timer
Track details
-
diff --git a/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/core/di/Modules.kt b/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/core/di/Modules.kt
index 9c9c7964..511332ba 100644
--- a/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/core/di/Modules.kt
+++ b/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/core/di/Modules.kt
@@ -74,6 +74,7 @@ import dev.krtirtho.spotube.modules.search.SearchScreenViewModel
import dev.krtirtho.spotube.modules.settings.SettingsProvider
import dev.krtirtho.spotube.modules.settings.SettingsRepository
import dev.krtirtho.spotube.modules.settings.SettingsViewModel
+import dev.krtirtho.spotube.modules.settings.JamSettingsViewModel
import dev.krtirtho.spotube.modules.shell.AppShellViewModel
import dev.krtirtho.spotube.modules.shell.PlayerOptionsViewModel
import dev.krtirtho.spotube.modules.shell.alternative_track.AlternativeTrackContentViewModel
@@ -127,6 +128,7 @@ val sharedModules = module {
// Settings
singleOf(::SettingsRepository)
viewModelOf(::SettingsViewModel) { bind() }
+ viewModelOf(::JamSettingsViewModel)
// Downloads
singleOf(::DownloadManager)
diff --git a/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/core/navigation/NavigationModule.kt b/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/core/navigation/NavigationModule.kt
index bfea9743..b3f7f48f 100644
--- a/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/core/navigation/NavigationModule.kt
+++ b/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/core/navigation/NavigationModule.kt
@@ -33,6 +33,7 @@ import dev.krtirtho.spotube.modules.saved_tracks.SAVED_TRACKS_COLLECTION_ID
import dev.krtirtho.spotube.modules.saved_tracks.SavedTracksScreen
import dev.krtirtho.spotube.modules.search.SearchScreen
import dev.krtirtho.spotube.modules.settings.SettingsScreen
+import dev.krtirtho.spotube.modules.settings.JamSettingsScreen
import kotlinx.serialization.Serializable
import org.koin.compose.viewmodel.koinViewModel
import org.koin.core.annotation.KoinExperimentalAPI
@@ -87,6 +88,9 @@ sealed interface Routes : NavKey {
@Serializable
data object Jam : Routes
+
+ @Serializable
+ data object JamSettings : Routes
}
@OptIn(KoinExperimentalAPI::class)
@@ -171,5 +175,7 @@ val navigationModule = module {
navigation {
JamScreen(navigationCommands = get())
}
+ navigation {
+ JamSettingsScreen(viewModel = koinViewModel())
+ }
}
-
diff --git a/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/modules/settings/JamSettingsScreen.kt b/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/modules/settings/JamSettingsScreen.kt
new file mode 100644
index 00000000..3264af61
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/modules/settings/JamSettingsScreen.kt
@@ -0,0 +1,274 @@
+package dev.krtirtho.spotube.modules.settings
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.widthIn
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Text
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import dev.krtirtho.spotube.core.ui.base.PrimaryButton
+import dev.krtirtho.spotube.core.ui.component.ApplicationMainBar
+import dev.krtirtho.spotube.modules.settings.components.SwitchSettingCard
+import dev.krtirtho.spotube.modules.settings.components.TextInputSettingCard
+import dev.krtirtho.spotube.modules.settings.sections.SettingsItemIcon
+import dev.krtirtho.spotube.modules.settings.sections.settingsSectionCard
+import dev.krtirtho.spotube.modules.settings.sections.settingsSectionHeader
+import dev.krtirtho.spotube.resources.iconsax.CustomServer
+import dev.krtirtho.spotube.resources.iconsax.Iconsax
+import dev.krtirtho.spotube.resources.iconsax.IconsaxEye
+import dev.krtirtho.spotube.resources.iconsax.IconsaxInformation
+import dev.krtirtho.spotube.resources.iconsax.IconsaxTag
+import dev.krtirtho.spotube.resources.iconsax.IconsaxWifiSquare
+import dev.krtirtho.spotube.resources.iconsax.User
+import dev.krtirtho.spotube.modules.settings.components.SettingCardItem
+import dev.krtirtho.spotube.modules.shell.LocalAppShellBottomInset
+import org.jetbrains.compose.resources.stringResource
+import org.koin.compose.viewmodel.koinViewModel
+import spotube.composeapp.generated.resources.Res
+import spotube.composeapp.generated.resources.settings_error_port_range
+import spotube.composeapp.generated.resources.settings_error_whole_number
+import spotube.composeapp.generated.resources.settings_jam_broker_client_id
+import spotube.composeapp.generated.resources.settings_jam_broker_client_id_subtitle
+import spotube.composeapp.generated.resources.settings_jam_broker_diagnostics_subtitle
+import spotube.composeapp.generated.resources.settings_jam_broker_host
+import spotube.composeapp.generated.resources.settings_jam_broker_password
+import spotube.composeapp.generated.resources.settings_jam_broker_placeholder_note
+import spotube.composeapp.generated.resources.settings_jam_broker_port
+import spotube.composeapp.generated.resources.settings_jam_broker_test
+import spotube.composeapp.generated.resources.settings_jam_broker_testing
+import spotube.composeapp.generated.resources.settings_jam_broker_tls
+import spotube.composeapp.generated.resources.settings_jam_broker_tls_subtitle
+import spotube.composeapp.generated.resources.settings_jam_section_connection
+import spotube.composeapp.generated.resources.settings_jam_section_credentials
+import spotube.composeapp.generated.resources.settings_jam_section_diagnostics
+import spotube.composeapp.generated.resources.settings_jam_section_identity
+import spotube.composeapp.generated.resources.settings_jam_broker_username
+import spotube.composeapp.generated.resources.settings_section_jam
+
+@Composable
+fun JamSettingsScreen(
+ viewModel: JamSettingsViewModel = koinViewModel(),
+) {
+ val uiState by viewModel.uiState.collectAsStateWithLifecycle()
+ val broker = uiState.settings.jamBroker
+ val shellBottomInset = LocalAppShellBottomInset.current
+ val contentPadding = remember(shellBottomInset) {
+ PaddingValues(top = 16.dp, bottom = 16.dp + shellBottomInset)
+ }
+
+ Scaffold(
+ topBar = {
+ ApplicationMainBar(
+ title = { Text(stringResource(Res.string.settings_section_jam)) },
+ )
+ },
+ ) { innerPadding ->
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(innerPadding),
+ ) {
+ LazyColumn(
+ modifier = Modifier
+ .widthIn(max = 1280.dp)
+ .align(Alignment.TopCenter),
+ contentPadding = contentPadding,
+ ) {
+ settingsSectionHeader(Res.string.settings_jam_section_connection)
+ settingsSectionCard(
+ items = listOf(
+ {
+ TextInputSettingCard(
+ title = stringResource(Res.string.settings_jam_broker_host),
+ value = broker.host,
+ placeholder = "broker.example.com",
+ icon = { SettingsItemIcon(Iconsax.CustomServer, "Broker") },
+ onValueSaved = { host ->
+ viewModel.update {
+ copy(
+ jamBroker = jamBroker.copy(
+ host = host
+ )
+ )
+ }
+ },
+ )
+ },
+ {
+ val errorWholeNumber =
+ stringResource(Res.string.settings_error_whole_number)
+ val errorPortRange =
+ stringResource(Res.string.settings_error_port_range)
+
+ TextInputSettingCard(
+ title = stringResource(Res.string.settings_jam_broker_port),
+ value = broker.port.toString(),
+ placeholder = "1883",
+ normalize = { it.filter(Char::isDigit).take(5) },
+ validate = { value ->
+ val port = value.toIntOrNull()
+ when {
+ port == null -> errorWholeNumber
+ port !in 1..65535 -> errorPortRange
+ else -> null
+ }
+ },
+ onValueSaved = { port ->
+ viewModel.update { copy(jamBroker = jamBroker.copy(port = port.toInt())) }
+ },
+ icon = {
+ SettingsItemIcon(
+ Iconsax.CustomServer,
+ stringResource(Res.string.settings_jam_broker_port)
+ )
+ },
+ )
+ },
+ {
+ SwitchSettingCard(
+ title = stringResource(Res.string.settings_jam_broker_tls),
+ subtitle = stringResource(Res.string.settings_jam_broker_tls_subtitle),
+ checked = broker.useTls,
+ onCheckedChange = { tls ->
+ viewModel.update { copy(jamBroker = jamBroker.copy(useTls = tls)) }
+ },
+ icon = {
+ SettingsItemIcon(
+ Iconsax.IconsaxWifiSquare,
+ stringResource(Res.string.settings_jam_broker_tls)
+ )
+ },
+ )
+ },
+ ),
+ )
+ settingsSectionHeader(Res.string.settings_jam_section_credentials)
+ settingsSectionCard(
+ items = listOf(
+ {
+ TextInputSettingCard(
+ title = stringResource(Res.string.settings_jam_broker_username),
+ value = broker.username.orEmpty(),
+ placeholder = "anonymous",
+ onValueSaved = { username ->
+ viewModel.update {
+ copy(jamBroker = jamBroker.copy(username = username.ifBlank { null }))
+ }
+ },
+ icon = {
+ SettingsItemIcon(
+ Iconsax.User,
+ stringResource(Res.string.settings_jam_broker_username)
+ )
+ },
+ )
+ },
+ {
+ TextInputSettingCard(
+ title = stringResource(Res.string.settings_jam_broker_password),
+ value = broker.password.orEmpty(),
+ placeholder = "••••••••",
+ onValueSaved = { password ->
+ viewModel.update {
+ copy(jamBroker = jamBroker.copy(password = password.ifBlank { null }))
+ }
+ },
+ icon = {
+ SettingsItemIcon(
+ Iconsax.IconsaxEye,
+ stringResource(Res.string.settings_jam_broker_password)
+ )
+ },
+ )
+ },
+ ),
+ )
+ settingsSectionHeader(Res.string.settings_jam_section_identity)
+ settingsSectionCard(
+ items = listOf(
+ {
+ TextInputSettingCard(
+ title = stringResource(Res.string.settings_jam_broker_client_id),
+ subtitle = stringResource(Res.string.settings_jam_broker_client_id_subtitle),
+ value = broker.clientIdPrefix,
+ placeholder = "spotube",
+ onValueSaved = { prefix ->
+ viewModel.update {
+ copy(jamBroker = jamBroker.copy(clientIdPrefix = prefix.ifBlank { "spotube" }))
+ }
+ },
+ icon = {
+ SettingsItemIcon(
+ Iconsax.IconsaxTag,
+ stringResource(Res.string.settings_jam_broker_client_id)
+ )
+ },
+ )
+ },
+ ),
+ )
+ settingsSectionHeader(Res.string.settings_jam_section_diagnostics)
+ settingsSectionCard(
+ items = listOf(
+ {
+ val canTest = broker.host.isNotBlank() && !uiState.isTesting
+ val resultMessage = uiState.testResult?.fold(
+ onSuccess = { it },
+ onFailure = { "Failed: ${it.message ?: "Connection failed"}" },
+ )
+
+ SettingCardItem(
+ enabled = canTest,
+ title = stringResource(Res.string.settings_jam_broker_test),
+ subtitle = when {
+ uiState.isTesting -> stringResource(Res.string.settings_jam_broker_testing)
+ resultMessage != null -> resultMessage
+ else -> stringResource(Res.string.settings_jam_broker_diagnostics_subtitle)
+ },
+ icon = {
+ SettingsItemIcon(
+ Iconsax.IconsaxInformation,
+ stringResource(Res.string.settings_jam_broker_test),
+ )
+ },
+ trailingContent = {
+ PrimaryButton(
+ onClick = viewModel::testConnection,
+ enabled = canTest,
+ ) {
+ Text(
+ if (uiState.isTesting) {
+ stringResource(Res.string.settings_jam_broker_testing)
+ } else {
+ stringResource(Res.string.settings_jam_broker_test)
+ },
+ )
+ }
+ },
+ onClick = viewModel::testConnection,
+ )
+ },
+ ),
+ )
+ item {
+ Text(
+ text = stringResource(Res.string.settings_jam_broker_placeholder_note),
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = 16.dp, vertical = 4.dp),
+ )
+ }
+ }
+ }
+ }
+}
diff --git a/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/modules/settings/JamSettingsViewModel.kt b/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/modules/settings/JamSettingsViewModel.kt
new file mode 100644
index 00000000..010fc6e2
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/modules/settings/JamSettingsViewModel.kt
@@ -0,0 +1,51 @@
+package dev.krtirtho.spotube.modules.settings
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import dev.krtirtho.spotube.core.jam.JamRoomClient
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.update
+import kotlinx.coroutines.launch
+
+data class JamSettingsUiState(
+ val settings: UserSettings = UserSettings(),
+ val isTesting: Boolean = false,
+ val testResult: Result? = null,
+)
+
+class JamSettingsViewModel(
+ private val settingsRepository: SettingsRepository,
+ private val jamRoomClient: JamRoomClient,
+) : ViewModel() {
+ private val _uiState = MutableStateFlow(JamSettingsUiState())
+ val uiState: StateFlow = _uiState.asStateFlow()
+
+ init {
+ viewModelScope.launch {
+ settingsRepository.userSettings.collect { settings ->
+ _uiState.update { it.copy(settings = settings) }
+ }
+ }
+ }
+
+ fun update(transform: UserSettings.() -> UserSettings) {
+ viewModelScope.launch {
+ settingsRepository.updateSettings(uiState.value.settings.transform())
+ }
+ }
+
+ fun testConnection() {
+ if (_uiState.value.isTesting) return
+ _uiState.update { it.copy(isTesting = true, testResult = null) }
+ viewModelScope.launch {
+ val result = jamRoomClient.testConnection(uiState.value.settings.jamBroker)
+ _uiState.update { it.copy(isTesting = false, testResult = result) }
+ }
+ }
+
+ fun clearTestResult() {
+ _uiState.update { it.copy(testResult = null) }
+ }
+}
diff --git a/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/modules/settings/SettingsScreen.kt b/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/modules/settings/SettingsScreen.kt
index 2321ecef..f7119895 100644
--- a/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/modules/settings/SettingsScreen.kt
+++ b/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/modules/settings/SettingsScreen.kt
@@ -114,7 +114,7 @@ fun SettingsScreen(settingsViewModel: SettingsViewModel) {
if (settingsState != null)
jamSection(
settings = settingsState!!,
- settingsViewModel = settingsViewModel,
+ navigationCommands = navigatorCommands,
)
if (settingsState != null)
cacheSection(
@@ -142,4 +142,3 @@ fun SettingsScreen(settingsViewModel: SettingsViewModel) {
}
}
}
-
diff --git a/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/modules/settings/sections/JamSection.kt b/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/modules/settings/sections/JamSection.kt
index a15326da..9feaedf3 100644
--- a/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/modules/settings/sections/JamSection.kt
+++ b/composeApp/src/commonMain/kotlin/dev/krtirtho/spotube/modules/settings/sections/JamSection.kt
@@ -5,204 +5,58 @@
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
*/
package dev.krtirtho.spotube.modules.settings.sections
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyListScope
-import androidx.compose.material3.Button
+import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Text
-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.unit.dp
-import dev.krtirtho.spotube.core.jam.JamRoomClient
-import dev.krtirtho.spotube.modules.settings.SettingsViewModel
+import compose.icons.FeatherIcons
+import compose.icons.feathericons.ChevronRight
+import dev.krtirtho.spotube.core.navigation.NavigationCommands
+import dev.krtirtho.spotube.core.navigation.Routes
import dev.krtirtho.spotube.modules.settings.UserSettings
-import dev.krtirtho.spotube.modules.settings.components.SwitchSettingCard
-import dev.krtirtho.spotube.modules.settings.components.TextInputSettingCard
+import dev.krtirtho.spotube.modules.settings.components.SettingCardItem
import dev.krtirtho.spotube.resources.iconsax.CustomServer
import dev.krtirtho.spotube.resources.iconsax.Iconsax
-import kotlinx.coroutines.launch
import org.jetbrains.compose.resources.stringResource
-import org.koin.compose.koinInject
import spotube.composeapp.generated.resources.Res
-import spotube.composeapp.generated.resources.settings_jam_broker_client_id
-import spotube.composeapp.generated.resources.settings_jam_broker_host
import spotube.composeapp.generated.resources.settings_jam_broker_host_subtitle
-import spotube.composeapp.generated.resources.settings_jam_broker_password
-import spotube.composeapp.generated.resources.settings_jam_broker_placeholder_note
-import spotube.composeapp.generated.resources.settings_jam_broker_port
-import spotube.composeapp.generated.resources.settings_jam_broker_test
-import spotube.composeapp.generated.resources.settings_jam_broker_test_fail
-import spotube.composeapp.generated.resources.settings_jam_broker_test_ok
-import spotube.composeapp.generated.resources.settings_jam_broker_testing
-import spotube.composeapp.generated.resources.settings_jam_broker_tls
-import spotube.composeapp.generated.resources.settings_jam_broker_username
+import spotube.composeapp.generated.resources.settings_jam_broker_title
import spotube.composeapp.generated.resources.settings_section_jam
internal fun LazyListScope.jamSection(
settings: UserSettings,
- settingsViewModel: SettingsViewModel,
+ navigationCommands: NavigationCommands,
) {
- val broker = settings.jamBroker
-
settingsSectionHeader(Res.string.settings_section_jam)
settingsSectionCard(
items = listOf(
{
- TextInputSettingCard(
- title = stringResource(Res.string.settings_jam_broker_host),
+ SettingCardItem(
+ title = stringResource(Res.string.settings_jam_broker_title),
subtitle = stringResource(
Res.string.settings_jam_broker_host_subtitle,
- broker.host.ifBlank { "—" },
- broker.port,
+ settings.jamBroker.host.ifBlank { "—" },
+ settings.jamBroker.port,
),
- value = broker.host,
- onValueSaved = { host ->
- settingsViewModel.updateSettings {
- copy(jamBroker = jamBroker.copy(host = host))
- }
- },
- placeholder = "broker.example.com",
icon = {
SettingsItemIcon(
Iconsax.CustomServer,
- stringResource(Res.string.settings_jam_broker_host),
+ stringResource(Res.string.settings_jam_broker_title),
)
},
- )
- },
- {
- TextInputSettingCard(
- title = stringResource(Res.string.settings_jam_broker_port),
- value = broker.port.toString(),
- onValueSaved = { port ->
- settingsViewModel.updateSettings {
- copy(jamBroker = jamBroker.copy(port = port.toIntOrNull() ?: 1883))
- }
- },
- placeholder = "1883",
- normalize = { it.filter { c -> c.isDigit() }.take(5) },
- validate = { input ->
- val port = input.toIntOrNull()
- if (port == null || port !in 1..65535) "Invalid port" else null
- },
- )
- },
- {
- SwitchSettingCard(
- title = stringResource(Res.string.settings_jam_broker_tls),
- checked = broker.useTls,
- onCheckedChange = { tls ->
- settingsViewModel.updateSettings {
- copy(jamBroker = jamBroker.copy(useTls = tls))
- }
- },
- )
- },
- {
- TextInputSettingCard(
- title = stringResource(Res.string.settings_jam_broker_username),
- value = broker.username.orEmpty(),
- onValueSaved = { username ->
- settingsViewModel.updateSettings {
- copy(jamBroker = jamBroker.copy(username = username.ifBlank { null }))
- }
- },
- placeholder = "anonymous",
- )
- },
- {
- TextInputSettingCard(
- title = stringResource(Res.string.settings_jam_broker_password),
- value = broker.password.orEmpty(),
- onValueSaved = { password ->
- settingsViewModel.updateSettings {
- copy(jamBroker = jamBroker.copy(password = password.ifBlank { null }))
- }
- },
- placeholder = "••••••••",
- )
- },
- {
- TextInputSettingCard(
- title = stringResource(Res.string.settings_jam_broker_client_id),
- value = broker.clientIdPrefix,
- onValueSaved = { prefix ->
- settingsViewModel.updateSettings {
- copy(jamBroker = jamBroker.copy(clientIdPrefix = prefix.ifBlank { "spotube" }))
- }
- },
- placeholder = "spotube",
- )
- },
- {
- val jamClient = koinInject()
- val scope = rememberCoroutineScope()
- var testing by remember { mutableStateOf(false) }
- var result by remember { mutableStateOf(null) }
-
- Box(modifier = Modifier.fillMaxWidth().padding(16.dp, 8.dp)) {
- Button(
- onClick = {
- testing = true
- result = null
- scope.launch {
- val outcome = jamClient.testConnection(broker)
- result = outcome.fold(
- onSuccess = { ok -> "OK: $ok" },
- onFailure = { e -> "ERR: ${e.message ?: "unknown"}" },
- )
- testing = false
- }
- },
- enabled = broker.host.isNotBlank() && !testing,
- ) {
- Text(
- text = if (testing) {
- stringResource(Res.string.settings_jam_broker_testing)
- } else {
- stringResource(Res.string.settings_jam_broker_test)
- },
+ trailingContent = {
+ Icon(
+ imageVector = FeatherIcons.ChevronRight,
+ contentDescription = stringResource(Res.string.settings_jam_broker_title),
+ tint = MaterialTheme.colorScheme.onSurfaceVariant,
)
- }
- result?.let { message ->
- val ok = message.startsWith("OK:")
- Text(
- text = message.removePrefix("OK:").removePrefix("ERR:"),
- style = MaterialTheme.typography.bodySmall,
- color = if (ok) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.error,
- modifier = Modifier.padding(start = 12.dp),
- )
- }
- }
- },
- {
- Text(
- text = stringResource(Res.string.settings_jam_broker_placeholder_note),
- style = MaterialTheme.typography.bodySmall,
- color = MaterialTheme.colorScheme.onSurfaceVariant,
- modifier = Modifier
- .fillMaxWidth()
- .padding(horizontal = 16.dp, vertical = 4.dp),
+ },
+ onClick = { navigationCommands.navigateTo(Routes.JamSettings) },
)
},
)
)
-}
\ No newline at end of file
+}