* ui: buy a badge in the app through the App Store or Google Play
* core, ios, android: create the badge store purchase record before the store charges
* core, ios, android: fixes from the store lane review
* ios, android: show a store purchase's badge only on the profile it was credited to
In channels members are usually connected via relays, so they are memberCurrent
but not memberActive, and #7255 widened the condition for requesting the member
security code to cover them. It updated GroupChatInfoView and
MemberSupportChatView, but not the same code in ChatView, so "Verify security
code" is not offered when the profile is opened by tapping member avatar in the
chat, while it is offered for the same member in the members list.
- 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>
* desktop: add bounded animated image decoder
Skia's Codec is already on the desktop classpath through skiko and decodes
both GIF and animated WebP. The frames come from a file somebody else
composed, so the decoder is bounded before it allocates: the raster is
measured in bytes with the sides multiplied as Long, each side is capped
separately so an extreme aspect ratio cannot slip under the byte budget, and
the encoded size is checked before the bytes are copied into native memory.
Anything outside the bounds, or any failure, keeps the still image the chat
already renders.
Nothing calls this yet.
* desktop: animate GIFs in chat items and full screen
Both views drew the first frame only. The full screen view also decoded its
still on every recomposition, which an animation recomposes once per frame,
so that decode is remembered against the data it comes from.
The chat list preview stays a still image: it is a 36dp box that the desktop
layout keeps on screen the whole time, so animating it would hold a raster and
spend a frame of work per listed chat, without pause.
Removes the two markers left for this work.
* desktop: don't decode animation frames that cannot be seen
With media blur on, a blurred image is only revealed while the mouse is over
it, so every frame was decoded, uploaded and then blurred away again for
nobody - and the blur is a render effect re-run per frame. Frames now decode
only while the image can be seen, which also stops motion showing through a
blur that is there to hide it.
Passing the blur state to the view is why the shared signature changes; coil
drives its own animation on Android, so there is nothing to pause there.
* docs: move animated images plan to plans/
* docs: drop file path references from animated images plan
* docs: correct animated images plan against the code
* desktop: correct animated image comments
* desktop: reduce animated image comments
* desktop: correct and bound animated image decoding
* docs: correct animated images plan against measurements
* desktop: fuse the animation prior frame decision
* docs: cover desktop animated images in spec and product
* desktop: drop the unused animated image component
* desktop: return the animation frame instead of its state
* docs: correct the animated images documentation
* desktop: don't decode animations under the full screen viewer
* desktop: bound the frames an animation rebuilds
* desktop: pause animations under any full screen modal
* desktop: stop animations that alternate expensive frames
* desktop: read what playing a frame needs only once
* desktop: close the codec of an animation outside the bounds
* desktop: wait out what an animation frame cost to decode
* docs: correct animated images claims against the code
* desktop: bound the frame count where the others are bounded
* desktop: don't wait out a stall an animation frame did not spend
* desktop: say what the slow frame constants stand for
* desktop: don't decode animations behind a minimised window
* desktop: make the animation frame wait testable
* desktop: bound the file size where the others are bounded
* desktop: pin the frame wait clamp in its test
* desktop: keep the frame wait clamp private
* desktop: reduce animated image comments
---------
Co-authored-by: sh <github.shum@liber.li>