Настройки
This commit is contained in:
@@ -1,14 +1,24 @@
|
||||
package core.presentation.settings
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.Save
|
||||
import androidx.compose.material.icons.filled.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import ru.knot.messager.R
|
||||
|
||||
@@ -22,20 +32,38 @@ fun SettingsScreen(
|
||||
val state by viewModel.state.collectAsState()
|
||||
var baseUrl by remember(state.baseUrl) { mutableStateOf(state.baseUrl) }
|
||||
|
||||
val lifecycleOwner = androidx.compose.ui.platform.LocalLifecycleOwner.current
|
||||
DisposableEffect(lifecycleOwner) {
|
||||
val observer = androidx.lifecycle.LifecycleEventObserver { _, event ->
|
||||
if (event == androidx.lifecycle.Lifecycle.Event.ON_RESUME) {
|
||||
viewModel.loadConfig()
|
||||
}
|
||||
}
|
||||
lifecycleOwner.lifecycle.addObserver(observer)
|
||||
onDispose {
|
||||
lifecycleOwner.lifecycle.removeObserver(observer)
|
||||
}
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
containerColor = Color(0xFF0F0F10),
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = { Text(stringResource(R.string.settings)) },
|
||||
title = { Text(stringResource(R.string.settings), fontWeight = FontWeight.Bold) },
|
||||
navigationIcon = {
|
||||
IconButton(onClick = onBack) {
|
||||
Icon(Icons.Default.ArrowBack, contentDescription = stringResource(R.string.back))
|
||||
Icon(Icons.Default.ArrowBack, contentDescription = stringResource(R.string.back), tint = Color.White)
|
||||
}
|
||||
},
|
||||
actions = {
|
||||
IconButton(onClick = { viewModel.saveBaseUrl(baseUrl) }) {
|
||||
Icon(Icons.Default.Save, contentDescription = stringResource(R.string.save))
|
||||
IconButton(onClick = { viewModel.loadConfig() }) {
|
||||
Icon(Icons.Default.Sync, contentDescription = "Refresh", tint = Color.White)
|
||||
}
|
||||
}
|
||||
},
|
||||
colors = TopAppBarDefaults.topAppBarColors(
|
||||
containerColor = Color(0xFF0F0F10),
|
||||
titleContentColor = Color.White
|
||||
)
|
||||
)
|
||||
}
|
||||
) { paddingValues ->
|
||||
@@ -43,60 +71,181 @@ fun SettingsScreen(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(paddingValues)
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(16.dp)
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.server_connection),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
modifier = Modifier.padding(bottom = 8.dp)
|
||||
)
|
||||
|
||||
OutlinedTextField(
|
||||
value = baseUrl,
|
||||
onValueChange = { baseUrl = it },
|
||||
label = { Text(stringResource(R.string.api_base_url)) },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
placeholder = { Text("https://example.com/api/") }
|
||||
)
|
||||
if (state.isLoading) {
|
||||
LinearProgressIndicator(
|
||||
modifier = Modifier.fillMaxWidth().padding(bottom = 16.dp),
|
||||
color = Color(0xFF3390EC)
|
||||
)
|
||||
}
|
||||
|
||||
if (state.error != null) {
|
||||
Text(
|
||||
text = "Ошибка обновления: ${state.error}",
|
||||
color = Color.Red,
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
modifier = Modifier.padding(bottom = 16.dp)
|
||||
)
|
||||
}
|
||||
// Server Section
|
||||
SettingsSection(title = stringResource(R.string.server_connection)) {
|
||||
Column(modifier = Modifier.padding(12.dp)) {
|
||||
Text(
|
||||
text = stringResource(R.string.api_base_url),
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = Color(0xFF3390EC)
|
||||
)
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
TextField(
|
||||
value = baseUrl,
|
||||
onValueChange = { baseUrl = it },
|
||||
modifier = Modifier.weight(1f),
|
||||
colors = TextFieldDefaults.colors(
|
||||
focusedContainerColor = Color.Transparent,
|
||||
unfocusedContainerColor = Color.Transparent,
|
||||
focusedTextColor = Color.White,
|
||||
unfocusedTextColor = Color.White,
|
||||
focusedIndicatorColor = Color(0xFF3390EC),
|
||||
unfocusedIndicatorColor = Color.Gray.copy(alpha = 0.5f)
|
||||
),
|
||||
placeholder = { Text("https://...", color = Color.Gray) }
|
||||
)
|
||||
IconButton(onClick = { viewModel.saveBaseUrl(baseUrl) }) {
|
||||
Icon(Icons.Default.Save, contentDescription = null, tint = Color(0xFF3390EC))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
|
||||
// Features Section
|
||||
SettingsSection(title = stringResource(R.string.server_features)) {
|
||||
Column {
|
||||
SettingsToggleItem(
|
||||
icon = Icons.Default.History,
|
||||
label = stringResource(R.string.stories),
|
||||
enabled = state.config.features.stories
|
||||
)
|
||||
SettingsToggleItem(
|
||||
icon = Icons.Default.Poll,
|
||||
label = stringResource(R.string.polls),
|
||||
enabled = state.config.features.polls
|
||||
)
|
||||
SettingsToggleItem(
|
||||
icon = Icons.Default.Call,
|
||||
label = stringResource(R.string.calls),
|
||||
enabled = state.config.features.calls
|
||||
)
|
||||
SettingsToggleItem(
|
||||
icon = Icons.Default.Group,
|
||||
label = stringResource(R.string.groups),
|
||||
enabled = state.config.features.groups
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
|
||||
// Limits Section
|
||||
SettingsSection(title = stringResource(R.string.limits)) {
|
||||
Column {
|
||||
SettingsInfoItem(
|
||||
icon = Icons.Default.Storage,
|
||||
label = stringResource(R.string.max_file_size),
|
||||
value = "${state.config.limits.maxFileSize / (1024 * 1024)} MB"
|
||||
)
|
||||
SettingsInfoItem(
|
||||
icon = Icons.Default.People,
|
||||
label = stringResource(R.string.max_group_members),
|
||||
value = state.config.limits.maxGroupMembers.toString()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(32.dp))
|
||||
|
||||
// Logout Button
|
||||
Button(
|
||||
onClick = onLogout,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
colors = ButtonDefaults.buttonColors(containerColor = Color(0xFFE53935)),
|
||||
shape = RoundedCornerShape(12.dp)
|
||||
) {
|
||||
Icon(Icons.Default.Logout, contentDescription = null)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Text(stringResource(R.string.log_out), fontWeight = FontWeight.Bold)
|
||||
}
|
||||
|
||||
Text(
|
||||
text = stringResource(R.string.server_features),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
modifier = Modifier.padding(bottom = 8.dp)
|
||||
)
|
||||
|
||||
FeatureStatusItem(stringResource(R.string.stories), state.config.features.stories)
|
||||
FeatureStatusItem(stringResource(R.string.polls), state.config.features.polls)
|
||||
FeatureStatusItem(stringResource(R.string.calls), state.config.features.calls)
|
||||
FeatureStatusItem(stringResource(R.string.groups), state.config.features.groups)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
Text(
|
||||
text = stringResource(R.string.limits),
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
Text("${stringResource(R.string.max_file_size)}: ${state.config.limits.maxFileSize / (1024 * 1024)} MB")
|
||||
Text("${stringResource(R.string.max_group_members)}: ${state.config.limits.maxGroupMembers}")
|
||||
Spacer(modifier = Modifier.height(80.dp)) // Space for bottom bar
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun FeatureStatusItem(name: String, enabled: Boolean) {
|
||||
fun SettingsSection(title: String, content: @Composable () -> Unit) {
|
||||
Column {
|
||||
Text(
|
||||
text = title.uppercase(),
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = Color(0xFF3390EC),
|
||||
fontWeight = FontWeight.Bold,
|
||||
modifier = Modifier.padding(start = 8.dp, bottom = 8.dp)
|
||||
)
|
||||
Surface(
|
||||
color = Color(0xFF1C1C1E),
|
||||
shape = RoundedCornerShape(16.dp),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
content()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SettingsToggleItem(icon: ImageVector, label: String, enabled: Boolean) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 4.dp),
|
||||
.padding(16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
Text(name)
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Icon(icon, contentDescription = null, tint = Color.Gray, modifier = Modifier.size(24.dp))
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
Text(label, color = Color.White, fontSize = 16.sp)
|
||||
}
|
||||
Text(
|
||||
text = if (enabled) stringResource(R.string.enabled) else stringResource(R.string.disabled),
|
||||
color = if (enabled) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.error
|
||||
color = if (enabled) Color(0xFF3390EC) else Color(0xFFE53935),
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 14.sp
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SettingsInfoItem(icon: ImageVector, label: String, value: String) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Icon(icon, contentDescription = null, tint = Color.Gray, modifier = Modifier.size(24.dp))
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
Text(label, color = Color.White, fontSize = 16.sp)
|
||||
}
|
||||
Text(
|
||||
text = value,
|
||||
color = Color.White,
|
||||
fontWeight = FontWeight.Medium,
|
||||
fontSize = 14.sp
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package core.presentation.settings
|
||||
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import core.domain.model.ServerConfigModel
|
||||
@@ -14,18 +15,26 @@ import javax.inject.Inject
|
||||
|
||||
data class SettingsState(
|
||||
val baseUrl: String = "",
|
||||
val config: ServerConfigModel = ServerConfigModel()
|
||||
val config: ServerConfigModel = ServerConfigModel(),
|
||||
val isLoading: Boolean = false,
|
||||
val error: String? = null
|
||||
)
|
||||
|
||||
@HiltViewModel
|
||||
class SettingsViewModel @Inject constructor(
|
||||
private val serverConfig: ServerConfig
|
||||
private val serverConfig: ServerConfig,
|
||||
private val authRepository: auth.domain.repository.AuthRepository
|
||||
) : ViewModel() {
|
||||
|
||||
private val _state = MutableStateFlow(SettingsState())
|
||||
val state: StateFlow<SettingsState> = _state.asStateFlow()
|
||||
|
||||
init {
|
||||
refreshState()
|
||||
loadConfig()
|
||||
}
|
||||
|
||||
private fun refreshState() {
|
||||
_state.update {
|
||||
it.copy(
|
||||
baseUrl = serverConfig.getBaseUrl(),
|
||||
@@ -34,6 +43,20 @@ class SettingsViewModel @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
fun loadConfig() {
|
||||
viewModelScope.launch {
|
||||
_state.update { it.copy(isLoading = true, error = null) }
|
||||
val result = authRepository.fetchConfig()
|
||||
Log.d("SettingsViewModel", "Fetch result: ${result.isSuccess}")
|
||||
if (result.isSuccess) {
|
||||
refreshState()
|
||||
} else {
|
||||
_state.update { it.copy(error = result.exceptionOrNull()?.message) }
|
||||
}
|
||||
_state.update { it.copy(isLoading = false) }
|
||||
}
|
||||
}
|
||||
|
||||
fun saveBaseUrl(url: String) {
|
||||
serverConfig.setBaseUrl(url)
|
||||
_state.update { it.copy(baseUrl = url) }
|
||||
|
||||
Reference in New Issue
Block a user