From 81f36d15c8d8e3c753a7e37412d5e760eb94cd3a Mon Sep 17 00:00:00 2001 From: evgeny Date: Wed, 28 Oct 2026 09:56:49 +0100 Subject: [PATCH] Clarify avatar editor ownership and UI publication state --- .../main/java/com/utun/chat/ui/components/Avatar.kt | 7 ++++++- .../com/utun/chat/ui/components/AvatarCropDialog.kt | 9 ++++++++- .../java/com/utun/chat/ui/components/AvatarEditors.kt | 10 +++++++++- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/components/Avatar.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/components/Avatar.kt index 98c6a6a62..d3fb13baa 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/components/Avatar.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/components/Avatar.kt @@ -24,7 +24,10 @@ import com.utun.chat.ChatApplication import com.utun.chat.R import com.utun.chat.ui.screens.AvatarViewerScreen -/** Короткое нажатие открывает просмотр; удержание можно передать меню содержащей строки. */ +/** Круглое превью участника из app-scoped AvatarRepository с инициалами до загрузки. + * group=null ищет ссылку owner среди доступных групп; revision запускает повторную проверку кеша. + * Короткое нажатие открывает галерею оригиналов, удержание передаётся onLongClick; enabled выключает оба жеста. + * profile=true показывает локальный профиль текущего аккаунта и отключает жесты независимо от enabled. */ @OptIn(ExperimentalFoundationApi::class) @Composable fun UserAvatar(group: String?, owner: Long, name: String, modifier: Modifier = Modifier, @@ -41,6 +44,7 @@ fun UserAvatar(group: String?, owner: Long, name: String, modifier: Modifier = M if (viewing) AvatarViewerScreen(group,owner,name,onClose={ viewing=false }) } +/** Превью отдельного аватара группы, подгружаемое через ensureGroup; клики задаёт содержащий компонент. */ @Composable fun GroupAvatar(group: String, name: String, modifier: Modifier = Modifier, size: Dp = 44.dp) { val repository = ChatApplication.instance.avatars @@ -49,6 +53,7 @@ fun GroupAvatar(group: String, name: String, modifier: Modifier = Modifier, size AvatarImage(repository.groupBitmap(group), name, modifier, size) } +/** Круглый crop Bitmap либо инициалы первых двух слов name; при пустом имени — "#". Bitmap не изменяется. */ @Composable private fun AvatarImage(bitmap: Bitmap?, name: String, modifier: Modifier, size: Dp) { Box(modifier.size(size).clip(CircleShape).background(MaterialTheme.colorScheme.secondaryContainer), diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/components/AvatarCropDialog.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/components/AvatarCropDialog.kt index af4f90029..0a2c5bbd5 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/components/AvatarCropDialog.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/components/AvatarCropDialog.kt @@ -39,7 +39,14 @@ import kotlinx.coroutines.* import java.io.File import kotlin.math.roundToInt -/** Фото перемещается под круглой рамкой аватара. Подтверждение передаёт владение исходным файлом репозиторию. */ +/** + * Редактор пользовательского аватара: копирует URI в cacheDir на IO worker, учитывает EXIF, + * принимает JPEG/PNG/WebP до 32 МиБ и 16×1024×1024 пикселей. Для экрана декодирует уменьшенную копию. + * Сдвиг и zoom 1..16 выбирают квадрат полного ориентированного оригинала; круг — только маска просмотра. + * onConfirm получает временный File и нормализованный AvatarCrop и принимает обязанность удалить файл + * (ProfileAvatarControls передаёт его в AvatarRepository.editProfile). После подтверждения диалог его не удаляет. + * onCancel должен убрать диалог из композиции: при dispose неподтверждённая копия удаляется на IO worker. + */ @Composable fun AvatarCropDialog(uri: Uri, onCancel: () -> Unit, onConfirm: (File, AvatarCrop) -> Unit) { val context=LocalContext.current diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/components/AvatarEditors.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/components/AvatarEditors.kt index 2e4f941cb..e4e6248d9 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/components/AvatarEditors.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/components/AvatarEditors.kt @@ -15,7 +15,11 @@ import com.utun.chat.R import com.utun.chat.data.* import kotlinx.coroutines.launch -/** Один диалог для меню списка и кнопки в шапке группового чата. */ +/** Свойства CHAT-группы: отдельный аватар группы и локальный звук уведомлений. + * При открытии обновляет снимок; canEdit показывает кнопки публикации, окончательный допуск проверяет core. + * Выбранный URI передаётся в editGroup без crop-диалога: репозиторий берёт центральный квадрат. + * Account/group фиксируются до picker, изменения применяются сразу и продолжаются после закрытия диалога. + */ @Composable fun GroupPropertiesDialog(channel: Channel, onDismiss: () -> Unit) { val repository = ChatApplication.instance.avatars @@ -71,6 +75,10 @@ fun GroupPropertiesDialog(channel: Channel, onDismiss: () -> Unit) { confirmButton = { TextButton(onClick = onDismiss) { Text(stringResource(R.string.close)) } }) } +/** Настройки локального профиля: выбор подписанного хранителя, crop-редактор и снятие аватара. + * Список хранителей не требует online; account/server фиксируются до запуска picker. + * Временный файл редактора передаётся во владение editProfile. SAVED подтверждает локальное намерение; + * файлы и ссылки публикуются репозиторием отдельно в подходящих группах, без ожидания в этом UI. */ @Composable fun ProfileAvatarControls() { val repository = ChatApplication.instance.avatars