feat(app): redesigned interface, polls, drafts, forwarding and mentions

A new look across the app and a set of everyday messaging features.

Interface
- Home screen with Chats, Contacts and Settings tabs, a large title,
  search across chats and message text (diacritics-insensitive), filters
  and pinned chats.
- Conversation view with a translucent header and composer, grouped
  bubbles, author avatars and colours in groups, swipe to reply, an
  "N new messages" marker, a jump-to-latest button and in-chat search.
- Contact and group profiles: verification, shared photos, groups in
  common, disappearing messages, mute, block; members, admin badges,
  adding people, rename and leave for groups.
- Full-screen photo viewer that pages through the whole conversation.
- A stable avatar colour per person and a clearly visible read state.

Messaging
- Polls in chats and groups, single or multiple choice, with votes that
  can be changed or withdrawn; older clients see them as text.
- Drafts survive leaving a chat and show in the chat list.
- Forwarding of messages, photos, files and voice messages.
- Notes to self: a local-only, encrypted chat that never reaches the
  relay.
- @mentions in groups that notify even when the group is muted.
- Typing indicators sent as ephemeral messages, opt-in and mutual like
  read receipts.
- Delivery and read times per message and per group member.
- Profile photos, cropped, resized and stripped of metadata, delivered
  encrypted to contacts.
- Voice message playback at 1.5x and 2x.
- Call buttons in 1:1 chats and call log entries.

Fixes
- Muted groups no longer notify: muting was checked against the
  author's chat instead of the group.
- A blocked contact could unblock itself by changing its delivery
  address.
- Read receipts no longer regress to delivered when a late delivery
  receipt arrives.
- A message marked as failed corrects itself once it is delivered.
- Double ticks in groups now mean delivered to every member.
This commit is contained in:
Julius Joska
2026-10-03 22:44:53 +02:00
parent 933a74c900
commit 5246a97db2
56 changed files with 12028 additions and 1668 deletions
+128 -7
View File
@@ -18,9 +18,23 @@
"groupsRename": "Переименовать группу",
"groupsLeave": "Покинуть группу",
"groupsLeftBanner": "Вы больше не в группе. История сохранится, но новые сообщения приходить не будут.",
"groupsMembersWithCount": "Участники ({count})",
"groupsYou": "Вы",
"groupsFounder": "Создатель",
"groupsAdmin": "Администратор",
"groupsMakeAdmin": "Сделать администратором",
"groupsDismissAdmin": "Снять роль администратора",
"groupsRemoveMember": "Удалить из группы",
"groupsRemoveMemberConfirmTitle": "Удалить {name} из группы?",
"groupsRemoveMemberConfirmBody": "Новые сообщения группы перестанут приходить. Прежняя история сохранится.",
"groupsRemoveAction": "Удалить",
"groupsViewContact": "Открыть контакт",
"groupsAdminOnlyHint": "Название, участников и администраторов группы меняют только администраторы.",
"groupsAdminOnlyCompatHint": "Переименовать группу и менять администраторов могут только администраторы. Добавлять людей пока может любой, потому что у кого-то в группе старая версия Šepot.",
"groupsAdminActionFailed": "Не удалось внести изменение: {error}",
"groupsResyncNotice": "Группа сверяется с администратором. Изменения состава могут появиться с задержкой.",
"groupsRejectedChangeNotice": "Кто-то без прав попытался изменить группу. Изменение не применено.",
"errorGroupNotAdmin": "Это может сделать только администратор группы.",
"errorGroupLastAdmin": "Вы единственный администратор. Сначала назначьте администратором кого-то ещё.",
"groupsNameHint": "Название группы",
"commonCancel": "Отмена",
"commonSave": "Сохранить",
@@ -51,6 +65,12 @@
"chatEditingMessage": "Редактирование сообщения",
"chatReply": "Ответ",
"chatActionReply": "Ответить",
"chatActionForward": "Переслать",
"forwardTitle": "Переслать в…",
"forwardSearchHint": "Найти контакт или группу",
"forwardEmpty": "Ничего не найдено",
"forwardDone": "Переслано: {name}",
"forwardFailed": "Не удалось переслать: {error}",
"commonCopy": "Копировать",
"commonEdit": "Изменить",
"chatDeleteForMe": "Удалить у меня",
@@ -68,6 +88,9 @@
"attachmentTakePhoto": "Сделать фото",
"attachmentDocument": "Документ",
"attachmentTooLarge": "Файл слишком большой (макс. {maxMb} МБ)",
"attachmentKeepsMetadataTitle": "Фото будет с геолокацией",
"attachmentKeepsMetadataBody": "Из этого формата фото (например, HEIC) я не могу удалить метаданные. Оно будет отправлено в оригинале, включая место съёмки и данные о телефоне. Безопаснее отправить его через Галерею — там метаданные удаляются.",
"attachmentSendOriginal": "Отправить оригинал",
"attachmentPickFailed": "Не удалось выбрать вложение: {error}",
"attachmentSendFailed": "Не удалось отправить вложение: {error}",
"chatSendFailed": "Не удалось отправить: {error}",
@@ -79,6 +102,7 @@
"errorContactLinkIncomplete": "Ссылка неполная — собеседник создал её до подключения. Попросите у него новую ссылку sepot://.",
"errorAttachmentTooLarge": "Вложение слишком большое (макс. {maxMb} МБ)",
"errorGroupNeedsMember": "В группе должен быть хотя бы 1 участник",
"errorAttachmentUnavailable": "Вложения больше нет на этом телефоне",
"errorGroupMaxMembers": "Максимальное число участников группы — {maxMembers}",
"errorMemberNoDeliveryAddress": "У контакта {displayName} нет адреса доставки — запросите новую ссылку",
"errorContactNoDeliveryAddress": "У контакта нет адреса доставки — попросите у него новую ссылку sepot://",
@@ -163,9 +187,36 @@
"attachmentOpenFailed": "Не удалось открыть вложение: {error}",
"commonFile": "Файл",
"chatListNewConversation": "Новый чат",
"chatListPhotoWithCaptionPreview": "📷 {caption}",
"chatListFilePreview": "📎 {fileName}",
"chatListReadReceiptPreview": "Прочитано",
"chatListDraft": "Черновик",
"notesTitle": "Заметки для себя",
"notesSubtitle": "Только на этом телефоне, в зашифрованном виде",
"callVoice": "Позвонить",
"callVideo": "Видеозвонок",
"callStatusRinging": "Вызов…",
"callIncomingVoice": "Входящий звонок",
"callIncomingVideo": "Входящий видеозвонок",
"callStatusConnecting": "Соединение…",
"callEnded": "Звонок завершён",
"callEndedBusy": "Занято",
"callEndedRejected": "Звонок отклонён",
"callEndedUnanswered": "Нет ответа",
"callEndedFailed": "Не удалось соединиться",
"callEncrypted": "Сквозное шифрование",
"callAccept": "Принять",
"callReject": "Отклонить",
"callMute": "Выключить микрофон",
"callSpeaker": "Динамик",
"callCamera": "Выключить камеру",
"callSwitchCamera": "Сменить камеру",
"callHangUp": "Завершить",
"callLogMissed": "Пропущенный звонок",
"callLogUnanswered": "Звонок без ответа",
"callLogRejected": "Отклонённый звонок",
"callLogBusy": "Занято",
"callLogFailed": "Звонок не удался",
"callLogVoice": "Звонок · {duration}",
"callLogVideo": "Видеозвонок · {duration}",
"chatListTimeNow": "сейчас",
"chatListTimeMinutes": "{minutes} мин",
"qrScanTitle": "Сканировать QR-код",
@@ -266,9 +317,9 @@
"pinUnlockError": "Ошибка разблокировки",
"pushTitle": "Push-уведомления",
"pushModeOffDesc": "Сообщения загрузятся только после открытия приложения.",
"pushModeUp": "UnifiedPush (рекомендуется)",
"pushModeUp": "UnifiedPush (без Google)",
"pushModeUpDesc": "Без сервисов Google — требуется приложение ntfy (F-Droid/Play).",
"pushModeFcm": "Google (FCM)",
"pushModeFcm": "Google (FCM, по умолчанию)",
"pushModeFcmDesc": "Надёжно и без дополнительных приложений. Push-токен устройства передаётся Google; содержимое сообщений — никогда.",
"pushSetupFailed": "Не удалось настроить push: {error}",
"pushHintOff": "Сообщения придут, только когда откроешь Šepot — сигналы не настроены.",
@@ -280,7 +331,7 @@
"pushPickDistributorTitle": "Выберите дистрибьютора",
"pushSubtitleUnifiedPush": "UnifiedPush · {distributor}",
"pushNoDistributorFallback": "без дистрибьютора",
"pushSubtitleFcm": "Google (FCM) — быстро, только по вашему выбору",
"pushSubtitleFcm": "Google (FCM)",
"restoreErrorNameRequired": "Введите отображаемое имя.",
"restoreErrorWordCount": "{wordCount, plural, one{Во фразе {wordCount} слово, а нужно {requiredWords}.} few{Во фразе {wordCount} слова, а нужно {requiredWords}.} many{Во фразе {wordCount} слов, а нужно {requiredWords}.} other{Во фразе {wordCount} слова, а нужно {requiredWords}.}}",
"restoreErrorInvalidPhrase": "Фраза недействительна — проверьте слова и их порядок.",
@@ -364,5 +415,75 @@
"photoPreviewSend": "Отправить",
"photoPreviewTextDialogTitle": "Текст на фото",
"photoPreviewRemoveText": "Удалить",
"photoPreviewDecodeFailed": "Не удалось загрузить фото"
"photoPreviewDecodeFailed": "Не удалось загрузить фото",
"chatActionInfo": "Сведения о сообщении",
"messageInfoSent": "Отправлено",
"messageInfoDelivered": "Доставлено",
"messageInfoRead": "Прочитано",
"messageInfoPending": "ещё нет",
"messageInfoReadReceiptsOff": "Время прочтения видно, только если вы сами делитесь отчётами о прочтении (Настройки).",
"chatStatusReadAt": "Прочитано {time}",
"chatStatusDeliveredAt": "Доставлено {time}",
"groupsAddMembers": "Добавить участников",
"groupsAddMembersAction": "Добавить",
"groupsAddMembersFailed": "Не удалось добавить: {error}",
"groupsNoContactsToAdd": "Все ваши контакты уже в группе. Сначала добавьте нового человека в контакты по QR-коду.",
"navChats": "Чаты",
"chatListNewChat": "Новый чат",
"chatListSearchHint": "Поиск по чатам и сообщениям",
"chatFilterAll": "Все",
"chatFilterUnread": "Непрочитанные",
"chatFilterGroups": "Группы",
"chatFilterArchive": "Архив",
"chatListNoResults": "Ничего не найдено.",
"chatListFilterEmpty": "Здесь пока ничего нет.",
"chatListPin": "Закрепить",
"chatListUnpin": "Открепить",
"chatOpenProfile": "Открыть профиль",
"groupsProfile": "Информация о группе",
"chatSearchHint": "Поиск в чате",
"chatSearchNoMatches": "Нет",
"chatSearchCounter": "{current} из {total}",
"chatSearchOlder": "Предыдущее совпадение",
"chatSearchNewer": "Следующее совпадение",
"chatUnreadDivider": "{count, plural, one{{count} новое сообщение} few{{count} новых сообщения} other{{count} новых сообщений}}",
"chatJumpToLatest": "К последним сообщениям",
"chatAttach": "Прикрепить",
"chatSend": "Отправить",
"chatRecordVoice": "Записать голосовое",
"profileSharedMedia": "Общие медиа",
"profileMessage": "Написать",
"profileSearch": "Поиск",
"profileUnmute": "Включить звук",
"profileDisappearing": "Исчезание",
"profileCommonGroups": "Общие группы",
"profileSafetyNumber": "Код безопасности",
"profileSafetyNumberHint": "Сравните лично или по QR-коду",
"profileVerifiedInPerson": "Проверено лично",
"profileVerifyNow": "Проверить контакт",
"profileAdd": "Добавить",
"profileGroupSubtitle": "Группа · {members} · создана {date}",
"profileMembersOfMax": "{count} из {max}",
"chatTyping": "печатает…",
"chatTypingNamed": "{name} печатает…",
"chatTypingSeveral": "Несколько человек печатают…",
"settingsTypingTitle": "Индикатор набора",
"settingsTypingSubtitle": "Взаимно — не делитесь, не видите (по умолчанию выкл.)",
"pollAttach": "Опрос",
"pollCreateTitle": "Новый опрос",
"pollQuestion": "Вопрос",
"pollOptionHint": "Вариант {number}",
"pollAddOption": "Добавить вариант",
"pollRemoveOption": "Удалить вариант",
"pollAllowMultiple": "Разрешить несколько ответов",
"pollSingle": "Опрос",
"pollMulti": "Опрос, несколько ответов",
"pollVoters": "{count, plural, =0{пока нет голосов} one{{count} голос} few{{count} голоса} other{{count} голосов}}",
"pollFallback": "Опрос: {question}",
"pollFallbackUpdate": "Обновите Šepot, чтобы голосовать.",
"profilePhotoEdit": "Изменить фото профиля",
"profilePhotoPick": "Выбрать фото",
"profilePhotoRemove": "Удалить фото",
"profilePhotoHint": "Фото увидят только ваши собеседники — зашифровано, как и сообщения. Геоданные и метаданные не отправляются.",
"profilePhotoFailed": "Не удалось установить фото: {error}"
}