3 changed files with 149 additions and 0 deletions
@ -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 |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
Loading…
Reference in new issue