The wallpaper-picker preview already draws the SIMPLEX sunrise gradient
as its background, but the sample message bubbles stayed flat: bubbles
anchor their gradient to the chat viewport, which the preview never
provided. Give the preview its own viewport (with the same expanded
axis the preview background uses), so the sample bubbles show the same
gradient they do in a real chat, aligned with the background.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A chat whose last message is a file showed the file icon in the chat
wallpaper's warm tint, so it turned yellow under the "cats" wallpaper.
In the chat list the icon should match the neighbouring list icons, so
on SIMPLEX it now uses the theme's secondary colour there; inside the
chat, over the gradient, the icon keeps the warm tint.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The group banner and the feature-change notices show feature icons; a
feature that is off draws in the model's secondary colour — a blue on
SIMPLEX — which reads cold over the sunrise gradient while the label
beside it already uses the warm tint. On SIMPLEX, route the off
(secondary) feature icon through simplexSecondaryTint(), in both the
collapsed icon row and the full notice. The green "on" and yellow
"partly on" colours, and every other theme, are untouched. This also
warms the "you changed a feature" notice icon, which passed a flat
secondary.
The website link-preview card shows the page URL under the title in
the flat secondary colour — a blue on SIMPLEX — which reads cold over
the sunrise gradient while the card's title uses content colour and
the message link itself the accent. Route that URL line through
simplexSecondaryTint() on SIMPLEX; other themes keep secondary. The
compose-bar preview is left as-is — it sits on the flat sent-message
colour, not the gradient.
Sweeping the message area for the remaining secondary-role content
that read cold over the sunrise gradient — SIMPLEX secondary is a
blue (#8B99D6) and the file/link icons used the grey FileDark /
FileLight. This covers the chat-link preview card (avatar, short
description, info line) and the file / voice / chat-link icons shown
inside a quoted reply. Route them through simplexSecondaryTint() on
SIMPLEX; every other theme keeps its exact colours.
On the SIMPLEX theme the raw secondary colour is a cool blue
(#8B99D6) and the framed-header icons drew in the grey FileDark /
FileLight — both read cold over the warm sunrise gradient, while the
text around them already uses the warm tint. Route the framed-header
icon (forward / flag / moderation / deleted markers) and the resting
go-to arrow (jump-to-source on forwarded items, jump-to search hit)
through simplexSecondaryTint() on SIMPLEX. Every other theme keeps
its exact colours.
On the SIMPLEX theme these two elements still drew in the flat
secondary colour over the sunrise gradient, while every sibling —
the quoted author above the caption, the feature label beside the
icon — already switched to the warm tint made to stay legible on
that gradient. Route both through simplexSecondaryTint(), which
returns the unchanged secondary colour for every other theme.
Making links a static accent left LocalSimplexLinkColor providing
exactly what linkStyle already falls back to (the primary colour),
so the CompositionLocal, its provider in FramedItemView, and the
elvis fallback no longer change anything. Remove all three.
Also clean up two comments the earlier subtraction left behind: the
axis comment still said secondary text samples the gradient (it uses
a flat tint now), and an orphaned line that had introduced the
removed LINK_STOPS.
Links now use the flat accent colour and default avatars the same
semi-transparent warm tint as dates and delivery ticks, instead of
sampling the sunrise gradient by on-screen position.
The sampling machinery did real work on every frame: link colour
recomposed as items scrolled, and each default avatar rendered
through an offscreen compositing layer to mask the gradient onto the
icon. Both are gone — links are a plain span colour, avatars set
their icon tint directly. Bubbles keep the position-sampled gradient
(they can't be flat-tinted: the wallpaper pattern would show through
and hurt text legibility). Non-SIMPLEX themes are unaffected.
Paints the SIMPLEX chat over one screen-spanning "sunrise" gradient:
- bubbles, avatars and links sample the gradient by on-screen position (a shared
20°-tilted axis anchored to the chat viewport);
- small text (dates, delivery checks, names, quoted authors, event notices,
call/voice/deleted secondary text) uses a per-wallpaper semi-transparent warm
tint, so it stays evenly legible over the gradient;
- decryption/integrity-error and deleted/marked-deleted bubbles use the received
gradient instead of a flat grey;
- wallpaper stops and secondary colours resolve from the composition, so per-chat
wallpaper overrides and the wallpaper-picker preview render correctly.
Side effects for all themes (details in the PR): event/feature text follows the
active theme instead of a start-up-frozen colour, and the via-proxy meta icon
matches the meta row on media bubbles.
Each preset wallpaper in the SIMPLEX theme now paints a per-wallpaper colour
gradient behind the chat (the pattern still shows on top) instead of a flat
background — the "sunrise" look the theme was designed around. Other themes
are unchanged. Bubble/text colours and the pattern-image refresh follow in
later PRs.
Opening a video full screen could crash with NoSuchElementException from
VLC native-library discovery. Each MediaPlayerFactory() runs a JDK
ServiceLoader (not thread-safe), and the second preview factory added in
#6924 let the render thread and preview thread construct factories
concurrently. Serialize the two constructions behind a shared lock.
* android, desktop, ios: remove left padding on consecutive received messages in channels
In channels, a received message that does not show an avatar (a consecutive post from the same sender) drops the avatar-sized left padding and sits flush-left. Applies to both owner broadcasts (ChannelRcv) and contributor posts (GroupRcv); the first message of each run still shows the avatar. Gated on ChatInfo.isChannel, so regular groups, business and direct chats, sent messages, and avatar-shown messages are unchanged.
* docs: add plan justifying removing left padding on consecutive received messages in channels
* ios: fix right gap on consecutive received messages in channels
Removing the avatar-sized left padding from no-avatar received messages
(this PR) shifted those bubbles ~44pt left, but maxWidth still reserved
the avatar inset, so consecutive messages stopped ~44pt short of the
first (avatar) message on the right.
Widen maxWidth for no-avatar channel-received items so their right edge
matches the avatar-shown first message. The no-avatar predicate reuses
the exact shouldShowAvatar expression from the render path (lifted to a
file-scope function so the maxWidth site can call it), so the width and
the rendered layout can never disagree.
Android is unaffected: Compose derives content width from padding, so
reducing the start padding already widened the row there.
* ios: increase left padding
* kotlin: increase left gap
---------
Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
* android, desktop: fix crash on opening chat with extremely wide image
An image with an extreme aspect ratio (e.g. 4000x1) made the chat
unopenable: the framed item's Box clamped its aspectRatio only on the
low side (coerceAtLeast(1f / 2.33f)), leaving very wide images
unbounded. During an intrinsic measure pass Compose derives
width = height * ratio, which for a 4000:1 image overflows Constraints
and throws IllegalArgumentException on every render.
Add the symmetric upper bound (coerceIn(1f / 2.33f, 2.33f)), matching
the existing tall-image height cap in PriorityLayout
(constraints.maxWidth * 2.33f).
* docs: add plan justifying wide-image chat crash fix
* desktop: fix updater deleting the download before "Open file location"
The in-app updater downloads to a temp UUID file via createTmpFileAndDelete,
then relies on `file.renameTo(newFile)` to move the bytes to the asset name so
they survive that helper's `finally { tmpFile.delete() }`. The rename's return
value was ignored: if it failed, the bytes stayed at the UUID path and the
finally block deleted the only copy, so the "Download completed" dialog appeared
but "Open file location" opened an empty /tmp/simplex.
Use Files.move with REPLACE_EXISTING instead. It performs the same in-place
rename when possible (verified: inode preserved, no copy), falls back to
copy+delete when an atomic rename isn't possible, and throws on genuine failure
- which the existing outer catch handles - instead of silently losing the file.
* docs: plan for updater open-file-location fix
* docs: plan - note Whonix compatibility (updater previously failed there)
* android, desktop, ios: remove right gap on received messages in channels
In channels received messages now use the full row width instead of the chat-bubble right gap, matching the broadcast/feed style. Gated on ChatInfo.isChannel (useRelays), the always-present channel flag used across the channel UI; sent messages and non-channel groups, business and direct chats are unchanged.
* docs: add plan justifying removing right gap on received messages in channels
---------
Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
* ios: open SimpleX links in chat messages via in-app connect flow
Tapping an inline SimpleX connection link in message text was dispatched
through UIApplication.shared.open. iOS drops an open() of a URL owned by
the same app while it is in the foreground (the simplex: scheme and the
simplex.chat universal links both belong to this app), so the tap was
ignored and never reached the connection flow. Web links (Safari) and
mailto:/tel: (other apps) were unaffected, which is why only SimpleX
links appeared dead.
Route SimpleX links to ChatModel.appOpenUrl instead - the same sink
onOpenURL feeds, leading to connectViaUrl/planAndConnect. This matches
the connection-link card and the multiplatform clients, which connect
in-process rather than via an OS round-trip.
Also fixes the same problem for the "Send questions and ideas" and
"connect to SimpleX Chat developers" buttons, which open simplexTeamURL
(a simplex: link) the same broken way.
* docs: plan - justify iOS in-app dispatch for SimpleX links in messages
Root cause and justification for opening inline SimpleX links via the
in-app connect flow instead of UIApplication.shared.open (undefined
re-entry of the same foreground app for a self-owned simplex: URL).
The API.Error branch in apiUpdateGroup rendered "$r.err", printing the API.Error object reference plus a literal ".err" instead of the error message. Use "${r.err.string}" so the actual error is shown.
A long file name took all the row width and squeezed the cancel (X)
icon to zero, so the file could not be dismissed before sending.
Give the file-name text the layout weight and a single line (Compose),
and lineLimit(1) on iOS, so it truncates and the close icon keeps its
space. Affects Android, Desktop and iOS.
In groups and channels with thousands of members, opening any screen that
shows the member list could briefly freeze the app. The most noticeable case
was the "Chats with members" screen: closing a member's chat and returning to
the list reloaded everyone and stuttered each time.
The app was re-checking every member against every other member while loading
the list - work that grows with the square of the group size, so it got
dramatically slower as groups grew. It now does this in a single pass, so
member lists, @-mentions, channel relays and adding members all stay
responsive even in very large groups. You see exactly the same members, in
the same order - just without the lag.
* android, desktop: skip media with no decodable preview to fix IndexOutOfBounds on send
processPickedMedia appended to MediaPreview.content unconditionally for videos
(and size-ok animated images) but only appended to images when a preview bitmap
existed. getBitmapFromVideo returns a null preview for undecodable/corrupted
videos without throwing, desyncing the two lists; sendMessageAsync then indexes
images[index] past its end and crashes. Pair both appends behind a non-null
bitmap so the lists stay equal-length and index-aligned, and skip only the bad
item so the rest of the picked batch still sends.
A video skipped this way shows showVideoDecodingException(), guarded by
hasAlertsShown() so it neither stacks across multiple bad items nor duplicates
the alert already shown on getBitmapFromVideo's exception path. Image decode
failures are already surfaced earlier by getBitmapFromUri.
* docs: add plan justifying media preview alignment fix
* Authenticate desktop call websocket
* call: patch ui.ts source for ws token and add server auth integration test
---------
Co-authored-by: Paul Bottinelli <paul.bottinelli@trailofbits.com>
* android, desktop, ios: show chat name in delete / leave / clear confirmation dialogs
The dialogs confirming delete contact, delete/leave group/channel and
clear chat now show the chat's name on its own line above the existing
warning, so the user can see which chat the destructive action will
affect.
Pure code change: no new translation strings, no signature changes, no
new helpers. The name is read via existing displayName accessors on
GroupInfo / Contact / ChatInfo.
clearNoteFolderDialog is intentionally unchanged - the notes folder is a
single-instance object and its existing warning already identifies it.
* android, desktop, ios: also show chat name when deleting pending connection
deleteContactConnectionAlert was missed in the original inventory.
Same pattern as the other dispatchers - prepend the connection's
displayName on its own line above the existing warning - so a user
who set a custom name on a pending connection can see which one
they are about to delete.
* android: use <br> instead of \n for newline in delete confirmation dialog body
On Android, the alert body goes through HtmlCompat.fromHtml which
treats the input as HTML and collapses literal \n to a single space -
so "Tech Talk\n\nGroup will be deleted..." rendered as
"Tech Talk Group will be deleted...". Switch to <br><br>, which both
HtmlCompat (Android) and the Desktop parser at Utils.desktop.kt:75
correctly render as a newline.
* android, desktop: skip HTML parsing for delete confirmation dialog text
Add parseHtml: Boolean = true parameter to showAlertDialog and
showAlertDialogButtonsColumn; when false, the body text is wrapped as
AnnotatedString and routed through the existing AnnotatedString
AlertContent overload, bypassing escapedHtmlToAnnotatedString entirely.
The 8 dispatchers that embed the user-controlled chat displayName now
opt out (parseHtml = false). This means:
- displayName is rendered as literal text - no HTML interpretation,
so a contact whose alias is "<b>X</b>" or "<font color=..." no
longer renders bold or coloured in the confirmation dialog
- the platforms align: both iOS and Kotlin now use plain "\n\n" for
the separator (no more <br><br> Kotlin-only workaround)
- removes coupling to escapedHtmlToAnnotatedString in this path
* android, desktop: shorten parseHtml comment in AlertManager
* remove extra text
---------
Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>