- redeem errors are typed (CEBadgeRedeemError) instead of matched by text in the apps
- service timeout (A_SERVICE) decodes in the apps and offers Retry via the existing retry alert
- unexpected redeem errors show the error itself instead of a generic message
- service error codes are a typed enum in the apps (BadgeServiceErrorCode)
- CRBadgeRedeemed returns badge state, so the apps skip a second round-trip after redeem
- setBadgeAlertAcked is scoped to user_id
- badgeChanged updates non-active profiles, so other profiles' badges don't go stale
- pitch banner is not shown to a profile that already has a badge
- one badgeTypeName per platform, used by the badge screen and the badge info alert
- BadgeAlertKind and BadgeAlertPrice decode via standard JSON, no custom decoders
- dead "Support ended" title branch removed from Your Badge view
- kotlin: users from badge responses carry remoteHostId
- kotlin: redeem code field keeps the IME's cursor and composition state
- kotlin: "Get your code" shown in all flavours
- kotlin: "Don't show again" -> "Dismiss", matching iOS
- kotlin: parseBadgeCode moved next to its FFI in platform/Core.kt
- kotlin: BadgesView no longer cross-fades on badge state updates
- kotlin: section title not uppercased
- ios: redeem code field parses once per change
- ios: A_SERVICE rejected reason is not decoded
- CLI: "cannot redeem badge code: ..." with the source of the error
- comments clarified
* ios, android, desktop: do not add loaded items of another chat to the opened chat
apiLoadMessages could apply a loaded page to a chat it was not loaded for,
mixing messages of two chats in one list:
- the chat id was not checked at all for .last pagination, and the loading
coroutine is not cancelled when the chat is closed (PreloadItems wraps it
into NonCancellable, apiFindMessages runs in a scope of its own), so a .last
page loaded for the previously opened chat was appended to the items of the
chat opened while it was loading;
- for the other paginations the chat id was checked before the items were
loaded, but they are applied on the main thread after a dispatch, so the chat
could be switched in between.
Check the chat id (and the remote host id in kotlin) right before the items are
applied, in every pagination except .initial and opening around item that set
chat id themselves. Empty chatItems remain allowed for .last that is used for
searching.
* android, desktop: test that loaded items of another chat are not added to the opened chat
Reproduces the bug deterministically, without depending on the timing of the
race: processLoadedChat is called with a page loaded for another chat while the
opened chat has its own items.
Without the fix the items of the two chats end up in one list:
lastPageLoadedForAnotherChatIsNotAddedToOpenedChat
expected:<[101, 102]> but was:<[101, 102, 201, 202]>
beforePageLoadedForAnotherChatIsNotAddedToOpenedChat
expected:<[101, 102]> but was:<[201, 202, 101, 102]>
aroundPageLoadedForAnotherChatIsNotAddedToOpenedChat
expected:<[101, 102]> but was:<[101, 102, 201, 202]>
* ui: show role (if exists) and remove "new" in open channel/group alerts
* restore removed strings
* info color
* docs
---------
Co-authored-by: Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com>
* core, ui: auto-accept group invitations per user profile
Add a per-profile toggle for auto-accepting group invitations, and regroup it
with the existing contact-requests setting under a single Auto-accept section
in Privacy & Security, relabelled "Contact requests in groups".
The join is fully async. processGroupInvitation already had an async accept
path, used when the invitation matches a group link the user opened:
prepareAgentJoin + createMemberConnectionAsync + joinAgentConnectionAsync,
with the outcome reported later against the CFJoinConn command id. Auto-accept
takes that same path instead of going through APIJoinGroup, so it works while
the app is closed and never blocks message processing.
An auto-accepted invitation still records a CIRcvGroupInvitation item in the
chat with the inviting contact, so there is a record of who added the user to
which group.
Two details worth noting for review:
hostContact is reported to clients only for group links. Clients respond to it
by replacing the transient host connection view with the group and removing
that chat - correct for a group link, where the contact is a placeholder, but
wrong for a plain invitation, where it is a real contact.
A resent invitation returns the existing group, because createGroupInvitation
is idempotent on inv_queue_info. The join therefore only runs while the
membership is still GSMemInvited, so a resend cannot open a second connection.
* booldef
* order
* refactor
* update translation key
* query plans
* ios: export translations
---------
Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
* ui: connect when a SimpleX link is pasted as a markdown hyperlink
* remove comments
Co-authored-by: Evgeny <evgeny@poberezkin.com>
---------
Co-authored-by: Evgeny <evgeny@poberezkin.com>
Group chat and its support chats have the same chat id, so a draft saved
in "chat with admins" was restored in the group chat and shown as its draft
in the chat list. Include chat scope in the draft chat id.
Also save the draft when support chat is closed without changing chat id -
in that case KeyChangeEffect that saves drafts doesn't run.
Surface the XFTP servers that hosted a file's chunks in the message info
screen (Android, desktop, iOS), so a user can see which servers they are
downloading from (or, for sent files, uploading to) whenever they want to know.
APIGetChatItemInfo now returns fileXftpServers, derived from the stored file
description (private snd descr for sent items, rcv descr for received items);
extraction is best-effort and never fails the call.