* feat(#471): implement DataStore-based server configuration and self-healing * refactor(#471): address Sourcery feedback on ANR risk and reset method splitting * refactor(#471): implement synchronous ServerStore initialization to fully remove ANR risk * feat(#471): show profile name, host and port on add profile, redirect to login on save, and support shortcut login buttons for existing profiles
This commit is contained in:
20 files changed
+774
-288
No files matched your search
@@ -51,6 +51,7 @@ import androidx.compose.material3.Tab
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
@@ -84,11 +85,20 @@ enum class SettingsTab {
|
||||
@Composable
|
||||
fun SettingsScreen(
|
||||
onBack: () -> Unit,
|
||||
onNavigateToLogin: () -> Unit = {},
|
||||
onLogout: () -> Unit = {},
|
||||
modifier: Modifier = Modifier,
|
||||
viewModel: SettingsViewModel = viewModel { SettingsViewModel() },
|
||||
) {
|
||||
val state by viewModel.uiState.collectAsStateWithLifecycle()
|
||||
|
||||
LaunchedEffect(state.navigateToLogin) {
|
||||
if (state.navigateToLogin) {
|
||||
onNavigateToLogin()
|
||||
viewModel.clearNavigateToLogin()
|
||||
}
|
||||
}
|
||||
|
||||
var passwordVisible by remember { mutableStateOf(false) }
|
||||
var selectedTab by remember { mutableStateOf(SettingsTab.CONNECTION) }
|
||||
|
||||
@@ -615,19 +625,21 @@ private fun ProfileEditorDialog(
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
OutlinedTextField(
|
||||
value = token,
|
||||
onValueChange = onTokenChange,
|
||||
label = { Text(stringResource(R.string.settings_field_token)) },
|
||||
singleLine = true,
|
||||
visualTransformation =
|
||||
if (token.isNotEmpty()) {
|
||||
PasswordVisualTransformation()
|
||||
} else {
|
||||
VisualTransformation.None
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
if (isEditing) {
|
||||
OutlinedTextField(
|
||||
value = token,
|
||||
onValueChange = onTokenChange,
|
||||
label = { Text(stringResource(R.string.settings_field_token)) },
|
||||
singleLine = true,
|
||||
visualTransformation =
|
||||
if (token.isNotEmpty()) {
|
||||
PasswordVisualTransformation()
|
||||
} else {
|
||||
VisualTransformation.None
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.m57.hermescontrol.ui.settings
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.m57.hermescontrol.ScreenRegistry
|
||||
import com.m57.hermescontrol.data.config.ConnectionProfile
|
||||
import com.m57.hermescontrol.data.local.AuthManager
|
||||
import com.m57.hermescontrol.data.remote.ApiClient
|
||||
import com.m57.hermescontrol.data.remote.NetworkResult
|
||||
@@ -34,7 +35,7 @@ data class SettingsUiState(
|
||||
val selectedNavItems: List<String> = emptyList(),
|
||||
val typingEffectEnabled: Boolean = false,
|
||||
val typingEffectDelayMs: Int = 30,
|
||||
val profiles: List<com.m57.hermescontrol.data.model.ConnectionProfile> = emptyList(),
|
||||
val profiles: List<ConnectionProfile> = emptyList(),
|
||||
val selectedProfileId: String? = null,
|
||||
val renameProfileName: String = "",
|
||||
val bottomNavDisplayMode: BottomNavDisplayMode = BottomNavDisplayMode.ICON_AND_TEXT,
|
||||
@@ -49,6 +50,7 @@ data class SettingsUiState(
|
||||
val showDeleteConfirm: Boolean = false,
|
||||
val profileToDeleteId: String? = null,
|
||||
val profileToDeleteName: String = "",
|
||||
val navigateToLogin: Boolean = false,
|
||||
)
|
||||
|
||||
class SettingsViewModel(
|
||||
@@ -209,14 +211,16 @@ class SettingsViewModel(
|
||||
} else {
|
||||
// Add new profile
|
||||
val newProfile =
|
||||
com.m57.hermescontrol.data.model.ConnectionProfile(
|
||||
ConnectionProfile(
|
||||
name = name,
|
||||
host = host,
|
||||
port = port,
|
||||
)
|
||||
profiles.add(newProfile)
|
||||
AuthManager.saveConnectionProfiles(profiles)
|
||||
AuthManager.setProfileToken(newProfile.id, token)
|
||||
AuthManager.setProfileToken(newProfile.id, "")
|
||||
AuthManager.setSelectedProfileId(newProfile.id)
|
||||
_uiState.update { it.copy(navigateToLogin = true) }
|
||||
}
|
||||
|
||||
closeProfileDialog()
|
||||
@@ -224,6 +228,10 @@ class SettingsViewModel(
|
||||
ApiClient.rebuild()
|
||||
}
|
||||
|
||||
fun clearNavigateToLogin() {
|
||||
_uiState.update { it.copy(navigateToLogin = false) }
|
||||
}
|
||||
|
||||
// ── Delete confirmation ──────────────────────────────────────────────
|
||||
|
||||
fun requestDeleteProfile(profileId: String) {
|
||||
|
||||
Reference in new issue
Block a user