mirror of
https://github.com/KRTirtho/spotube.git
synced 2026-11-04 10:13:59 +00:00
feat(jam-settings): implement JamSettings screen and view model for broker configuration
This commit is contained in:
parent
df3dd2ad88
commit
a4d157e9c2
@ -182,14 +182,20 @@
|
||||
<string name="settings_jam_broker_username">Username (optional)</string>
|
||||
<string name="settings_jam_broker_password">Password (optional)</string>
|
||||
<string name="settings_jam_broker_client_id">Client ID prefix</string>
|
||||
<string name="settings_jam_broker_client_id_subtitle">Used to identify this Spotube instance</string>
|
||||
<string name="settings_jam_broker_tls_subtitle">Encrypt the broker connection</string>
|
||||
<string name="settings_jam_broker_diagnostics_subtitle">Check the current broker settings before starting a room.</string>
|
||||
<string name="settings_jam_broker_test">Test connection</string>
|
||||
<string name="settings_jam_broker_testing">Testing…</string>
|
||||
<string name="settings_jam_broker_test_ok">Connected in %1$d ms</string>
|
||||
<string name="settings_jam_broker_test_fail">Failed: %1$s</string>
|
||||
<string name="settings_jam_broker_placeholder_note">Placeholder broker — configure your own to self-host</string>
|
||||
<string name="settings_jam_section_connection">Connection</string>
|
||||
<string name="settings_jam_section_credentials">Credentials</string>
|
||||
<string name="settings_jam_section_identity">Identity</string>
|
||||
<string name="settings_jam_section_diagnostics">Diagnostics</string>
|
||||
|
||||
<string name="player_go_to_album">Go to album</string>
|
||||
<string name="player_sleep_timer">Sleep timer</string>
|
||||
<string name="player_track_details">Track details</string>
|
||||
</resources>
|
||||
|
||||
|
||||
@ -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<SettingsProvider>() }
|
||||
viewModelOf(::JamSettingsViewModel)
|
||||
|
||||
// Downloads
|
||||
singleOf(::DownloadManager)
|
||||
|
||||
@ -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<Routes.Jam> {
|
||||
JamScreen(navigationCommands = get())
|
||||
}
|
||||
navigation<Routes.JamSettings> {
|
||||
JamSettingsScreen(viewModel = koinViewModel())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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<String>? = null,
|
||||
)
|
||||
|
||||
class JamSettingsViewModel(
|
||||
private val settingsRepository: SettingsRepository,
|
||||
private val jamRoomClient: JamRoomClient,
|
||||
) : ViewModel() {
|
||||
private val _uiState = MutableStateFlow(JamSettingsUiState())
|
||||
val uiState: StateFlow<JamSettingsUiState> = _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) }
|
||||
}
|
||||
}
|
||||
@ -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) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<JamRoomClient>()
|
||||
val scope = rememberCoroutineScope()
|
||||
var testing by remember { mutableStateOf(false) }
|
||||
var result by remember { mutableStateOf<String?>(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) },
|
||||
)
|
||||
},
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user