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": "Rename group",
"groupsLeave": "Leave group",
"groupsLeftBanner": "You're no longer in this group. Your history stays, but new messages won't arrive.",
"groupsMembersWithCount": "Members ({count})",
"groupsYou": "You",
"groupsFounder": "Founder",
"groupsAdmin": "Admin",
"groupsMakeAdmin": "Make admin",
"groupsDismissAdmin": "Remove admin role",
"groupsRemoveMember": "Remove from group",
"groupsRemoveMemberConfirmTitle": "Remove {name} from the group?",
"groupsRemoveMemberConfirmBody": "They will stop getting new messages from this group. Their existing history stays.",
"groupsRemoveAction": "Remove",
"groupsViewContact": "View contact",
"groupsAdminOnlyHint": "Only admins can change the group name, members and admins.",
"groupsAdminOnlyCompatHint": "Only admins can rename the group or change admins. Anyone can still add people, because someone in the group has an older version of Šepot.",
"groupsAdminActionFailed": "The change failed: {error}",
"groupsResyncNotice": "The group is catching up with its admin. Member changes may show up with a delay.",
"groupsRejectedChangeNotice": "Someone without permission tried to change the group. The change was not applied.",
"errorGroupNotAdmin": "Only a group admin can do this.",
"errorGroupLastAdmin": "You're the only admin. Make someone else an admin first.",
"groupsNameHint": "Group name",
"commonCancel": "Cancel",
"commonSave": "Save",
@@ -51,6 +65,12 @@
"chatEditingMessage": "Editing message",
"chatReply": "Reply",
"chatActionReply": "Reply",
"chatActionForward": "Forward",
"forwardTitle": "Forward to…",
"forwardSearchHint": "Search contact or group",
"forwardEmpty": "Nothing here",
"forwardDone": "Forwarded to {name}",
"forwardFailed": "Forwarding failed: {error}",
"commonCopy": "Copy",
"commonEdit": "Edit",
"chatDeleteForMe": "Delete for me",
@@ -68,6 +88,9 @@
"attachmentTakePhoto": "Take photo",
"attachmentDocument": "Document",
"attachmentTooLarge": "File is too large (max {maxMb} MB)",
"attachmentKeepsMetadataTitle": "Photo will include location",
"attachmentKeepsMetadataBody": "I can't remove metadata from this photo format (e.g. HEIC). It would be sent as the original, including where it was taken and details about the phone. Sending it via Gallery is safer, as metadata is removed there.",
"attachmentSendOriginal": "Send original",
"attachmentPickFailed": "Picking attachment failed: {error}",
"attachmentSendFailed": "Sending attachment failed: {error}",
"chatSendFailed": "Sending failed: {error}",
@@ -79,6 +102,7 @@
"errorContactLinkIncomplete": "This link is incomplete — the other person created it before connecting. Ask them for a new sepot:// link.",
"errorAttachmentTooLarge": "Attachment is too large (max {maxMb} MB)",
"errorGroupNeedsMember": "A group needs at least 1 member",
"errorAttachmentUnavailable": "The attachment is no longer on this phone",
"errorGroupMaxMembers": "A group can have at most {maxMembers} members",
"errorMemberNoDeliveryAddress": "{displayName} has no delivery address — ask them for a new link",
"errorContactNoDeliveryAddress": "This contact has no delivery address — ask them for a new sepot:// link",
@@ -163,9 +187,36 @@
"attachmentOpenFailed": "Opening attachment failed: {error}",
"commonFile": "File",
"chatListNewConversation": "New conversation",
"chatListPhotoWithCaptionPreview": "📷 {caption}",
"chatListFilePreview": "📎 {fileName}",
"chatListReadReceiptPreview": "Read",
"chatListDraft": "Draft",
"notesTitle": "Note to self",
"notesSubtitle": "Only on this phone, encrypted",
"callVoice": "Call",
"callVideo": "Video call",
"callStatusRinging": "Ringing…",
"callIncomingVoice": "Incoming call",
"callIncomingVideo": "Incoming video call",
"callStatusConnecting": "Connecting…",
"callEnded": "Call ended",
"callEndedBusy": "Busy",
"callEndedRejected": "Call declined",
"callEndedUnanswered": "No answer",
"callEndedFailed": "Couldn't connect",
"callEncrypted": "End-to-end encrypted",
"callAccept": "Accept",
"callReject": "Decline",
"callMute": "Mute",
"callSpeaker": "Speaker",
"callCamera": "Turn off camera",
"callSwitchCamera": "Switch camera",
"callHangUp": "Hang up",
"callLogMissed": "Missed call",
"callLogUnanswered": "Unanswered call",
"callLogRejected": "Declined call",
"callLogBusy": "Busy",
"callLogFailed": "Call failed",
"callLogVoice": "Call · {duration}",
"callLogVideo": "Video call · {duration}",
"chatListTimeNow": "now",
"chatListTimeMinutes": "{minutes} min",
"qrScanTitle": "Scan QR code",
@@ -266,9 +317,9 @@
"pinUnlockError": "Unlock error",
"pushTitle": "Push notifications",
"pushModeOffDesc": "Messages arrive only after you open the app.",
"pushModeUp": "UnifiedPush (recommended)",
"pushModeUp": "UnifiedPush (no Google)",
"pushModeUpDesc": "De-Googled — needs the ntfy app (F-Droid/Play).",
"pushModeFcm": "Google (FCM)",
"pushModeFcm": "Google (FCM, default)",
"pushModeFcmDesc": "Reliable without an extra app. The device push token goes to Google; message content never does.",
"pushSetupFailed": "Setting up push failed: {error}",
"pushHintOff": "Messages arrive only when you open Šepot — wake-ups aren’t set up.",
@@ -280,7 +331,7 @@
"pushPickDistributorTitle": "Choose a distributor",
"pushSubtitleUnifiedPush": "UnifiedPush · {distributor}",
"pushNoDistributorFallback": "no distributor",
"pushSubtitleFcm": "Google (FCM) — fast, opt-in",
"pushSubtitleFcm": "Google (FCM)",
"restoreErrorNameRequired": "Enter a display name.",
"restoreErrorWordCount": "{wordCount, plural, one{The phrase has 1 word, {requiredWords} are needed.} other{The phrase has {wordCount} words, {requiredWords} are needed.}}",
"restoreErrorInvalidPhrase": "The phrase isn't valid — check the words and their order.",
@@ -364,5 +415,75 @@
"photoPreviewSend": "Send",
"photoPreviewTextDialogTitle": "Text on photo",
"photoPreviewRemoveText": "Remove",
"photoPreviewDecodeFailed": "Couldn't load the photo"
"photoPreviewDecodeFailed": "Couldn't load the photo",
"chatActionInfo": "Message info",
"messageInfoSent": "Sent",
"messageInfoDelivered": "Delivered",
"messageInfoRead": "Read",
"messageInfoPending": "not yet",
"messageInfoReadReceiptsOff": "You only see when people read your messages if you share read receipts yourself (Settings).",
"chatStatusReadAt": "Read {time}",
"chatStatusDeliveredAt": "Delivered {time}",
"groupsAddMembers": "Add members",
"groupsAddMembersAction": "Add",
"groupsAddMembersFailed": "Could not add members: {error}",
"groupsNoContactsToAdd": "All your contacts are already in this group. Add someone new to your contacts via QR code first.",
"navChats": "Chats",
"chatListNewChat": "New chat",
"chatListSearchHint": "Search chats and messages",
"chatFilterAll": "All",
"chatFilterUnread": "Unread",
"chatFilterGroups": "Groups",
"chatFilterArchive": "Archive",
"chatListNoResults": "Nothing found.",
"chatListFilterEmpty": "Nothing here yet.",
"chatListPin": "Pin",
"chatListUnpin": "Unpin",
"chatOpenProfile": "View profile",
"groupsProfile": "Group info",
"chatSearchHint": "Search in chat",
"chatSearchNoMatches": "None",
"chatSearchCounter": "{current} of {total}",
"chatSearchOlder": "Older match",
"chatSearchNewer": "Newer match",
"chatUnreadDivider": "{count, plural, one{1 new message} other{{count} new messages}}",
"chatJumpToLatest": "Jump to latest",
"chatAttach": "Attach",
"chatSend": "Send",
"chatRecordVoice": "Record voice message",
"profileSharedMedia": "Shared media",
"profileMessage": "Message",
"profileSearch": "Search",
"profileUnmute": "Unmute",
"profileDisappearing": "Disappear",
"profileCommonGroups": "Groups in common",
"profileSafetyNumber": "Safety number",
"profileSafetyNumberHint": "Compare in person or by QR code",
"profileVerifiedInPerson": "Verified in person",
"profileVerifyNow": "Verify contact",
"profileAdd": "Add",
"profileGroupSubtitle": "Group · {members} · created {date}",
"profileMembersOfMax": "{count} of {max}",
"chatTyping": "typing…",
"chatTypingNamed": "{name} is typing…",
"chatTypingSeveral": "Several people are typing…",
"settingsTypingTitle": "Typing indicators",
"settingsTypingSubtitle": "Mutual — if you don't share it, you don't see it (off by default)",
"pollAttach": "Poll",
"pollCreateTitle": "New poll",
"pollQuestion": "Question",
"pollOptionHint": "Option {number}",
"pollAddOption": "Add option",
"pollRemoveOption": "Remove option",
"pollAllowMultiple": "Allow multiple answers",
"pollSingle": "Poll",
"pollMulti": "Poll, multiple choice",
"pollVoters": "{count, plural, =0{no votes yet} one{1 vote} other{{count} votes}}",
"pollFallback": "Poll: {question}",
"pollFallbackUpdate": "Update Šepot to vote.",
"profilePhotoEdit": "Change profile photo",
"profilePhotoPick": "Choose photo",
"profilePhotoRemove": "Remove photo",
"profilePhotoHint": "Only people you chat with will see it — end-to-end encrypted like your messages. No location or photo metadata is sent.",
"profilePhotoFailed": "Could not set photo: {error}"
}