android: add avatar editors and localized group properties dialog
This commit is contained in:
1 parent
73019cfd83
commit
b9d41d179d
3 files changed
+149
No files matched your search
@@ -0,0 +1,123 @@
|
||||
package com.utun.chat.ui.components
|
||||
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.utun.chat.ChatApplication
|
||||
import com.utun.chat.R
|
||||
import com.utun.chat.data.*
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/** Один диалог для меню списка и кнопки в шапке группового чата. */
|
||||
@Composable
|
||||
fun GroupPropertiesDialog(channel: Channel, onDismiss: () -> Unit) {
|
||||
val repository = ChatApplication.instance.avatars
|
||||
val groups by repository.groups.collectAsState()
|
||||
val edits by repository.edits.collectAsState()
|
||||
val state = groups[channel.id]
|
||||
val editState = edits["group:${channel.id}"] ?: AvatarEditState.IDLE
|
||||
val working = editState == AvatarEditState.WORKING
|
||||
var soundOn by rememberSaveable(channel.id) { mutableStateOf(!NotificationSound.isMuted(channel.id)) }
|
||||
var pickedAccount by rememberSaveable { mutableStateOf(0L) }
|
||||
var pickedGroup by rememberSaveable { mutableStateOf("") }
|
||||
val scope = rememberCoroutineScope()
|
||||
val picker = rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { uri ->
|
||||
if (uri != null) repository.editGroup(pickedGroup, uri, pickedAccount)
|
||||
}
|
||||
LaunchedEffect(channel.id) { repository.ensureGroup(channel.id, true) }
|
||||
AlertDialog(onDismissRequest = onDismiss,
|
||||
title = { Text(stringResource(R.string.group_properties)) },
|
||||
text = {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||
GroupAvatar(channel.id, channel.name, size = 64.dp)
|
||||
Column { Text(channel.name, style = MaterialTheme.typography.titleMedium); Text(channel.id) }
|
||||
}
|
||||
if (state?.loading == true || working) LinearProgressIndicator(Modifier.fillMaxWidth())
|
||||
if (state?.error == true) {
|
||||
Text(stringResource(R.string.avatar_load_failed), color = MaterialTheme.colorScheme.error)
|
||||
TextButton(onClick = { repository.ensureGroup(channel.id, true) }) { Text(stringResource(R.string.avatar_retry)) }
|
||||
}
|
||||
if (state?.canEdit == true) {
|
||||
Text(stringResource(R.string.group_avatar_limits), style = MaterialTheme.typography.bodySmall)
|
||||
Row {
|
||||
TextButton(enabled = !working, onClick = {
|
||||
pickedAccount = repository.accountId; pickedGroup = channel.id; picker.launch("image/*")
|
||||
}) { Text(stringResource(R.string.avatar_upload)) }
|
||||
TextButton(enabled = !working && state.hasImage, onClick = {
|
||||
repository.editGroup(channel.id, null, repository.accountId)
|
||||
}) { Text(stringResource(R.string.avatar_remove)) }
|
||||
}
|
||||
} else Text(stringResource(R.string.group_avatar_key_required), style = MaterialTheme.typography.bodySmall)
|
||||
if (editState == AvatarEditState.ERROR)
|
||||
Text(stringResource(R.string.avatar_edit_failed), color = MaterialTheme.colorScheme.error)
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text(stringResource(R.string.sound), modifier = Modifier.weight(1f))
|
||||
Switch(checked = soundOn, onCheckedChange = {
|
||||
soundOn = it; NotificationSound.setMuted(channel.id, !it)
|
||||
scope.launch { ChatApplication.instance.configProvider.setChannelMuted(channel.id, !it) }
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
confirmButton = { TextButton(onClick = onDismiss) { Text(stringResource(R.string.close)) } })
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ProfileAvatarControls() {
|
||||
val repository = ChatApplication.instance.avatars
|
||||
val members by repository.members.collectAsState()
|
||||
val edits by repository.edits.collectAsState()
|
||||
val servers = remember(members) { repository.storageServers() }
|
||||
var selectedServer by rememberSaveable { mutableStateOf(0L) }
|
||||
var menu by remember { mutableStateOf(false) }
|
||||
var pickedAccount by rememberSaveable { mutableStateOf(0L) }
|
||||
var pickedServer by rememberSaveable { mutableStateOf(0L) }
|
||||
val state = edits["profile"] ?: AvatarEditState.IDLE
|
||||
LaunchedEffect(servers) {
|
||||
if (servers.none { it.nodeId == selectedServer }) selectedServer = servers.firstOrNull()?.nodeId ?: 0L
|
||||
}
|
||||
val picker = rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { uri ->
|
||||
if (uri != null) repository.editProfile(pickedServer, uri, pickedAccount)
|
||||
}
|
||||
Column(verticalArrangement = Arrangement.spacedBy(6.dp)) {
|
||||
Text(stringResource(R.string.profile_avatar), style = MaterialTheme.typography.titleMedium)
|
||||
UserAvatar(null, repository.accountId, "", size = 64.dp, profile = true)
|
||||
Text(stringResource(R.string.profile_avatar_hint), style = MaterialTheme.typography.bodySmall)
|
||||
if (servers.isEmpty()) Text(stringResource(R.string.avatar_no_storage), style = MaterialTheme.typography.bodySmall)
|
||||
else Box {
|
||||
TextButton(onClick = { menu = true }, enabled = state != AvatarEditState.WORKING) {
|
||||
Text(stringResource(R.string.avatar_storage_server, servers.firstOrNull { it.nodeId == selectedServer }?.let {
|
||||
"${it.name.ifBlank { AvatarProtocol.hex(it.nodeId) }} (${AvatarProtocol.hex(it.nodeId)})"
|
||||
} ?: "…"))
|
||||
}
|
||||
DropdownMenu(expanded = menu, onDismissRequest = { menu = false }) {
|
||||
servers.forEach { server ->
|
||||
DropdownMenuItem(text = { Text("${server.name} (${AvatarProtocol.hex(server.nodeId)})") },
|
||||
onClick = { selectedServer = server.nodeId; menu = false })
|
||||
}
|
||||
}
|
||||
}
|
||||
Row {
|
||||
TextButton(enabled = selectedServer != 0L && state != AvatarEditState.WORKING, onClick = {
|
||||
pickedAccount = repository.accountId; pickedServer = selectedServer; picker.launch("image/*")
|
||||
}) { Text(stringResource(R.string.avatar_upload)) }
|
||||
TextButton(enabled = repository.accountId != 0L && state != AvatarEditState.WORKING, onClick = {
|
||||
repository.editProfile(0, null, repository.accountId)
|
||||
}) { Text(stringResource(R.string.avatar_remove)) }
|
||||
}
|
||||
when (state) {
|
||||
AvatarEditState.WORKING -> LinearProgressIndicator(Modifier.fillMaxWidth())
|
||||
AvatarEditState.ERROR -> Text(stringResource(R.string.avatar_edit_failed), color = MaterialTheme.colorScheme.error)
|
||||
AvatarEditState.SAVED -> Text(stringResource(R.string.profile_avatar_saved), style = MaterialTheme.typography.bodySmall)
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user