mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-08-15 20:00:06 +00:00
stable
111
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
abd5954675 |
ui: send dropped .webm as video only when it has a video track (#7354)
* ui: send dropped .webm as video only when it has a video track .webm is as commonly an audio-only container as a video one, so the extension alone cannot tell whether there is a frame to embed. Read the container to decide: files with a video track are sent as video, the rest as files. Only done for files attached without the user saying how to send them (drag & drop, paste). An explicitly picked video is still sent as one, so .webm is now listed in the video file picker too. * plan: send dropped .webm as video only when it has a video track |
||
|
|
f921bd47bb |
android, desktop: fix live message sent to the chat opened after switching (#7323)
* android, desktop: fix live message sent to the chat opened after switching A live message is sent when the chat is switched, but by then this view already shows the chat that was opened - the effect that sends it runs with that chat, so the message typed in one chat was sent to another. The chat the message was composed in is passed to the send, and it is resolved by the chat id from before the switch. If that chat is no longer there the message is not sent at all, rather than sent to the chat opened instead. The draft cleared after sending is the one of that chat too. * plan: correct references; clear the draft of the chat the message is sent to * plan: note the blast radius and how to resolve the overlap with #7308 * android, desktop: only pass the chat to what a live message can reach A live message has no context item, so the forwarding, editing and reporting branches of the send cannot run for it - they keep using the chat of the view, and the chat it was composed in is passed only to the message send, to the update of an already sent live message, and to clearing the draft after sending. * android, desktop: give the opened chat its own compose state while the live message is sent Sending the live message to the chat it was composed in is not enough on its own: composeState is shared between the chats opened in this view, and the chat switch branch of KeyChangeEffect is the only one that neither resets it nor loads the opened chat's draft - the branch that loads a draft is later in the same if chain and cannot be reached. sendMessageAsync then made it visible. It runs on Dispatchers.Default, so its writes land after the switch: the whole composed state (via cs.copy(liveMessage = null)) and its spinner (sending()) were written to the compose state of a view that already shows another chat, which then displayed the text composed in the previous one until the send completed. The draft it should have shown was still in the model, and the next switch away dropped it. - sendMessageAsync takes composed, and sendMessage takes it as null by default, so only the chat switch passes a state and every other sender reads it inside the coroutine, where the send read it before. The chat switch captures it on the main thread before replacing it - otherwise the send would read the compose state of the chat that was opened and send its draft to the previous chat. - checkLinkPreview takes that state too. It re-read composeState rather than what was passed in, and every text live message reaches it through updateMsgContent, so it would have rebuilt the message from the opened chat's draft instead of committing what was composed. - every composeState write in sendMessageAsync is guarded by composeIsForSend() (toChat.id == chat.id), which compares the two chats instead of checking which one is open, so this send never takes the compose state back if that chat is opened again before it completes. - the chat switch branch then resets composeState to the opened chat's draft, or to an empty state, like the branches below it do. * plan: document the compose state handoff; correct the #7308 overlap resolution The note on resolving the overlap with #7308 said its cs.liveMessage != null clause "already covers the send made by the chat switch". It does not - in #7308 that clause sits outside the chatIsOpen check, which is correct only while a live message is always sent to the chat that is open, the assumption this fix removes. Read as written it exempts the chat switch send from the guard that protects the opened chat, and a merge that follows it reintroduces the leak. Also records what manual test 2 was found failing on, and adds a slow send variant so the window between the switch and the send completing is long enough to type in the chat that was opened. * android, desktop: keep reading the current state where a forward appends it checkLinkPreview taking the composed state is needed where a live message reaches it, but the forwarding branch is not one of those - it cannot run with a chat other than the view's - and forwardItem suspends before it. So there the captured state is stale by a network round trip, and text typed while the forward was in flight stopped being appended to the message it adds, while still being cleared when the send completed. * plan: correct references and the claims that no longer hold Line references were against the base this branch forked from, before #7308 landed. Also: the live message loop no longer exits because the send clears liveMessage - the chat switch replaces the compose state, on the main thread, before the send runs; checkLinkPreview is not passed the captured state everywhere; and chatsCtx.getChat can only return null in a secondary context, which is not how "the chat is gone" reads. Adds the two manual checks the review implied: a live message carrying a link preview, which is what breaks if checkLinkPreview stops reading the state it was given, and returning to the chat before the send completes. * android, desktop: narrow the change to what the fix needs - the draft id a failed message is saved under keeps using chat: that branch is behind !liveSend, which the send made by a chat switch never satisfies, so toChat is always chat where it is read; - the state the chat switch installs no longer carries maxFileSize over. That field is kept in sync on chat switch by LaunchedEffect(chat.chatInfo), which is why the branches below this one construct it without one, and the paths where that effect does not re-run are the ones where the send overwrites the state anyway; - sendMessageAsync takes its cs as a parameter rather than aliasing a separately named one. * plan: follow the narrowed change * android, desktop: shorten the comments The threading mechanism behind cs is explained where it is used, so the function comment only has to say what it is; the rest is rewording. |
||
|
|
7e99e68950 |
android, desktop: fix draft appearing in another chat when switching chats while sending (#7308)
* ios, android, desktop: fix message being sent leaking into another chat Compose state is shared between the chats opened in the same view, and the send is launched in a scope that outlives the chat, so a send that was still in flight when the chat was switched put its message (with the reply context) into the compose state and then the draft of another chat, and a late success cleared whatever was typed in the meantime. The message being sent is no longer kept in the compose state when leaving the chat, and the compose state is only cleared or restored after sending if it still holds the message that was sent - the same check is used by the other senders that show progress in the compose. A message that failed to send is restored in the chat it was composed in, or kept as its draft when another chat is open (iOS has no failed message restore, there the message is dropped as before). * android, desktop: keep only the chat switch fix Revert the iOS changes and the same check in the three senders that connect a prepared chat, leaving the fix for the compose state shared between the chats opened in one view. * plan: document what the narrowed change leaves to the connect senders * android, desktop: use the same check where the sending flag is shared The senders that connect a prepared chat set the same inProgress flag, so a connect completing after the chat was switched cleared it for a send started in the chat opened next, and that sent message was then left in the compose. * android, desktop: keep the live message clauses inside the open chat check live and cs.liveMessage != null were alternatives to chatIsOpen, which holds only while a live message is always sent to the chat that is open. #7323 removes that: the live message committed by a chat switch is sent to the chat it was composed in, while this view already shows another one, so an unguarded clause here clears that chat's compose state - the leak this fix exists to prevent. liveSend stays an alternative to inProgress inside the guard, so a live send behaves exactly as before while its chat is open, and it is excluded from the restore/draft branch, which would otherwise write a draft on every failing keystroke send once that chat is no longer open. On this branch the only behaviour change is a live send completing after its chat was left, which now leaves the opened chat alone. Also load the draft of the chat opened next when the compose state is cleared on switching away from a send in flight: clearState() returns before the branch that loads a draft, so that draft was never shown, and being in the slot but in no compose state it was then dropped by clearPrevDraft on the next chat switch. * plan: explain why live sends are guarded by the open chat check Records that the exemption in "Deliberately unchanged" is from a guard based on inProgress, not from chatIsOpen, and why the earlier form broke once #7323 sends a live message to a chat other than the one open. |
||
|
|
0324517bfe |
android, desktop: keep database passphrase toggle inside the section card (#7326)
The "Save passphrase in settings/Keychain" label had no weight, so Row measured it at full available width, the weighted spacer collapsed to 0 and the trailing DefaultSwitch was placed past the row's right edge. Harmless until #6777: the section card is now inset by CARD_PADDING and clipped with SectionCardShape, which cut the row from 348dp to 316dp on the desktop start pane and made the overflowing switch both invisible and unclickable, since Modifier.clip clips pointer input too. With a custom passphrase set the toggle is enabled but unreachable, so the passphrase stays saved in settings and the app never prompts for it on start. Give the label weight(1f) and drop the weighted spacer, matching what SettingsActionItemWithContent does for every other toggle row. |
||
|
|
02178fb611 |
android, desktop: fix accepting contact request from notification for non-active profile (#7316)
* android, desktop: switch to the request's profile when accepting contact request from notification * plans: justify accepting contact request from notification fix |
||
|
|
1108e87ee2 |
desktop: fix forwarding moving message draft to another chat (#7307)
* desktop: fix forwarding moving message draft to another chat The same ComposeView is reused when switching chats, so the `chat` captured by the desktop-only `onDispose` is the chat that was open when the view was first composed, not the one the message is forwarded from. The draft was saved under that stale chat id, and when it happened to be the chat forwarded to, sending the forward restored the draft there. * plan: justify desktop forward/draft fix and cross-platform findings |
||
|
|
e7c94472c4 |
desktop: fix chat switch deleting the in-app updater's download (#7295)
* desktop: fix chat switch deleting the in-app updater's download
createTmpFileAndDelete registered every temp file it created in
ChatModel.filesToDelete, which ComposeView.deleteUnusedFiles() empties on every
chat open/switch/close. The updater's ~350 MB download lives in such a file for
minutes, so switching chats during it deleted the file. copyTo kept writing
through the still-open fd, so the transfer completed and progress reached 100%;
only the following Files.move failed, with NoSuchFileException on the source,
and that went into a catch that logs to stdout only - no dialog, no error, no
file.
The registration was redundant for every caller: the helper's own
finally { tmpFile.delete() } already removes the file, deleteOnExit() covers a
clean JVM exit, and Main.kt wipes tmpDir at startup for crashes. It only ever
duplicated the finally while arming a trap for long-running lambdas - the same
window also existed for the settings and themes writers.
* desktop: download the app update into a file the updater owns
Per review of #7295: instead of removing the ChatModel.filesToDelete
registration from createTmpFileAndDelete, stop using that helper for the
downloaded file.
downloadAsset now writes into "<asset name>.part" in tmpDir and moves it onto
the asset name when the transfer completes, so the download is never registered
in ChatModel.filesToDelete and ComposeView.deleteUnusedFiles() can no longer
delete it mid-transfer. Cleanup is unchanged in substance: the finally block
removes the partial file when the download fails or is cancelled (and is a
no-op after a successful move, as the helper's own finally was), deleteOnExit
covers a clean exit, and Main.kt wipes tmpDir at startup after a crash.
createTmpFileAndDelete is restored to its previous behaviour, with a comment
warning that its file does not survive a chat switch.
* desktop: reduce comments in updater download fix
|
||
|
|
dc3c106bda |
core: remove SimpleX Status preset contact (#7231)
* core: remove SimpleX Status preset contact Preset contact cards are only created at user record creation (createPresetContactCards), so this affects new profiles only; existing profiles keep their stored SimpleX Status contact. Removing the card shifts contact ids allocated after /create user down by one, hence the test id updates. * plans: justify SimpleX Status preset contact removal |
||
|
|
177a591466 |
core: fix member support chats staying marked unread after they're read (#7281)
* core: don't mark member support chat items read when reading group without scope Reading a group without a scope marked support-scope items read without decrementing the per-member support_chat_items_* counters, so members stayed unread in the support list even after their chat was fully read. Restrict the no-scope group read and its timed-items query to main-scope items. * plans: support chat unread on no-scope group read * core: update query plans for group scope read The main-scope read and timed-items queries now filter on group_scope_tag and group_scope_group_member_id, so they seek via idx_chat_items_group_scope_stats_all (5-column) instead of idx_chat_items_groups_user_mention (3-column). * plans: document query-plan and benchmark performance results * tests: fix unreliable support item id capture in no-scope group read test lastItemId returns the latest item by item_ts, which right after createGroup2 can be the group "connected" event rather than the just-sent support message. The per-item read then targeted the wrong (already-read, main-scope) item and never decremented the support counters, so the test failed regardless of the fix (consistently in CI, flakily locally depending on item ordering). Capture the support item id directly from the member-support scope instead, keeping the change contained to this test. Verified: the test passes with the fix and fails when the fix is reverted. * tests: fix name-shadowing build error in no-scope group read test The local pattern binding `itemId` shadowed the `itemId` helper imported from ChatTests.Utils, which -Wname-shadowing (Werror) rejects. Rename the local binding to `iId`. |
||
|
|
8de39c25de |
android, desktop: fix video preview expanding into empty black area (#7286)
* android, desktop: fix video preview expanding into empty black area The video message box (CHAT_IMAGE_LAYOUT_ID) never gave itself a definite height - it took the height of its tallest child. That child is the player surface, which has no stable intrinsic height until playback starts: on Android an unprepared StyledPlayerView reports no video size, so its AspectRatioFrameLayout (RESIZE_MODE_FIXED_WIDTH, aspect 0) does not shrink. Before #6726 this was masked: the box was measured with unbounded height, so the surface collapsed to 0. #6726 bounded the box to 2.33x width to constrain tall images, and the unprepared surface then expanded to fill that height, showing as a long empty black strip below the preview once a video downloads. Give the box a definite size from the preview aspect ratio, mirroring CIImageView. Height follows the measured (clamped) width via Modifier.layout so wide videos on narrow screens don't leave an empty strip (the #7223 fix). * plans: justify video preview black-area fix |
||
|
|
ab691a4f82 |
android, desktop: remove stray card dividers in server info, connect-to-desktop, appearance and migration screens (#7118)
* android, desktop: remove stray card dividers in server info, connect-to-desktop, appearance and migration screens PR #6777 turned these into card screens, where SectionView draws a 2dp near-black divider between every direct child, but left content patterns that pre-date the card chrome: nested SectionViews (SMP/XFTP server detail), loose Text + Spacer + Text (connect-to-desktop device name and version), and a SectionDividerSpaced() as a middle child (Appearance with an image wallpaper, migrate-from-device "error stopping chat"). Each draws stray lines inside the card, most visible in dark/black themes. Restructure to the sibling pattern already used by DetailedSMPStatsLayout: un-nest the server-detail sub-sections (address its own card, stats/subs/sessions as siblings; subscription rows carded), wrap each device name/version block in a single Column, and drop the in-card spacers (the adjacent-row auto-divider already separates them). Layout-only; iOS is a separate codebase and is unaffected. * docs: add plan justifying removing stray card section dividers |
||
|
|
5a380a751f |
desktop: fix hand cursor over clickable commands; fix command clicks lost on quick successive clicks (#7249)
* desktop: fix pointer cursor not changing to hand over clickable commands (dropped hover events)
* desktop: set hover cursor directly on every hover move; compose pointerHoverIcon is edge-triggered and loses updates when chat items shift under the cursor
* plans: investigation and justification for command hover cursor fix
* desktop: harden hover cursor fix after adversarial review: refresh on release, ignore button-held events, reset icon state on exit, cache canvas lookup failures
* desktop: document two-stage verification, exit icon asymmetry and cache guard rationale in hover cursor fix
* plans: reconcile performance and testing claims with hardened code
* plans: correct recomposition count and note commit subject scoping
* plans: clarify user-facing problem description
* desktop: shorten comments in hover cursor fix
* desktop: clarify comments in hover cursor fix
* multiplatform: fix command clicks lost on quick successive clicks (serialize gesture press scope, don't restart pointerInput on recomposition)
* plans: document lost-click defect (press-scope race, pointerInput restart) and fixes
* multiplatform: don't cancel command click when chat list shifts under the pointer
A press is cancelled when it goes out of the node's bounds, but when a sent
message inserts into the chat the node moves out from under a stationary
pointer, which is not a drag-away. Exempt out-of-bounds cancellation when the
pointer did not move in window coordinates (within touch slop).
* multiplatform: stop pointer handler resets on chat item recomposition (lost clicks, cursor flicker)
bigTouchSlop() created a new ViewConfiguration instance on every recomposition
of every chat item; pointer input nodes observe ViewConfiguration and reset
their handler when it changes, so each inserted message killed all in-flight
presses (lost command clicks) and hover handlers (hand cursor flicker) in the
viewport. Provide a remembered instance instead.
* plans: document list-shift press cancellation and ViewConfiguration reset defects
* desktop: remove imperative hover cursor workaround, superseded by ViewConfiguration fix
The evidence that the declarative path was insufficient was gathered while
every message insertion was resetting all hover handlers (the ViewConfiguration
identity defect, fixed in
|
||
|
|
3615927db5 |
multiplatform: remove unused clipboard state polling that froze desktop UI (#7237)
The desktop app polled full clipboard contents every second on the AWT event thread to update ChatModel.clipboardHasText. On X11, each read blocks up to sun.awt.datatransfer.timeout (10s) when the selection owner does not respond (e.g. after KeePassXC's clipboard auto-clear), making every click/scroll wait ~9s. clipboardHasText has had no readers since its only consumer was removed in #4398, so the whole mechanism (desktop poll, Android clip listener, onResume refresh, expect/actual, state field) is deleted. See plans/2026-07-11-fix-desktop-clipboard-freeze.md. |
||
|
|
7fc2a6e6bd |
android, desktop: fix draft loss when switching to chat where user cannot send messages (#7239)
* android, desktop: save draft when switching to chat where user cannot send messages On desktop, chat and chatId change in the same recomposition when another chat is opened from the always-visible chat list. The effect clearing compose state of a non-sendable chat (observer, channel subscriber, review by admins) was composed before the draft-saving KeyChangeEffect and ran first, wiping the live compose state before it could be saved and then clearing the previously saved draft via clearPrevDraft. Effects launch in composition order, so the clearing effect is moved after KeyChangeEffect: the previous chat's draft is saved first, and clearCurrentDraft is a no-op for it because draftChatId no longer matches the opened chat. Clearing the opened chat's own draft when it cannot send is preserved, as is clearing when the open chat itself becomes non-sendable (only the sendMsgEnabled key changes). * plans: investigation and justification for draft message loss fix |
||
|
|
9586c97439 |
ui: show XFTP servers used for a file in message info (#7088)
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. |
||
|
|
2ea5940e81 |
core, ui: per-server roles for self-hosted servers (#7254)
* core, ui: plan per-server roles for self-hosted servers * core: add per-server roles field to UserServer * core: add nullable role columns to protocol_servers * core: persist per-server roles * core: validate server coverage using per-server roles * test: cover per-server roles resolution and coverage * multiplatform: per-server role toggles for self-hosted servers * ios: per-server role toggles for self-hosted servers * core: per-server role overrides with per-role defaults * test: cover three-state per-server role resolution * fix: derive Eq for ServerRolesOverride * multiplatform: three-state role dropdowns on saved servers * ios: three-state role pickers on saved servers * test: per-server roles independent across two servers * test: enable names role in name-resolution tests * style: trim comments in per-server roles code * chore: rename server_roles migration to 20260716 (last) * core: per-server roles override operator roles, inherit when unset * multiplatform: per-server role default inherits from operator * ios: per-server role default inherits from operator * refactor(servers): tidy per-server roles per review - dedup no-operator default into ServerRoles.noOperatorDefault (Kotlin/Swift) - iOS: move roles-section control flow to the call site via a named gate, and parse the server address once instead of up to three times - Kotlin: collapse redundant derivedState; revert defaultOn->default rename for cross-platform parity - drop unused Hashable conformance on Swift ServerRoles - add agentServerCfgs test for names inheritance from an operator - remove no-op enableNamesRole calls from dormant DirectoryTests - fix ChatClient import ordering * chore(migration): date server_roles migration 20260720 * only show roles when server is enabled, move section above QR code --------- Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com> |
||
|
|
147355b76a |
desktop: don't delete shared temp dirs from a transient second instance (#7168)
On an incoming call the desktop window blinked continuously (FileNotFoundException
from SoundPlayer.start writing into a missing ...\Temp\simplex).
Root cause: Files.desktop top-level val initializers run in any process that
touches the facade class, including a transient second instance (which reaches
acquireSingleInstance -> dataDir). Two of them deleted shared state used by the
running primary:
- tmpDir had .also { deleteOnExit() } - the second instance's normal exit deleted
...\Temp\simplex, so the primary then failed to write call sounds/recordings.
- preferencesTmpDir had .also { deleteRecursively() } - the second instance's
<clinit> wiped configPath\tmp (same anti-pattern, firing even earlier).
Make both declarations pure and do the destructive cleanup in Main, past the
single-instance check, so only the owning instance performs it (preferencesTmpDir
kept early, before any settings write). The remaining val-initializer side effects
are idempotent creations (mkdirs) that destroy nothing and are left in place.
|
||
|
|
c125836d85 |
android, desktop: fix empty area below wide images (beta.3 regression) (#7223)
Wide images size their preview box with a fixed width of DEFAULT_MAX_IMAGE_WIDTH (500dp). #7125 switched the box from .aspectRatio() to a fixed .height() computed from that nominal width; on screens narrower than 500dp, .width(500dp) is clamped to the available width but the fixed height is not, so the top-aligned FillWidth image is shorter than its box, leaving an empty strip below. Compute the height from the width actually granted via a small Modifier.layout so it tracks the clamped width, restoring the self-correcting behaviour .aspectRatio() had. coerceAtMost(w) keeps the box within its nominal width (and bounds the unbounded intrinsic-measurement pass), and coerceAtLeast(0) mirrors what Modifier.width()'s SizeNode does for a negative w on a tiny window - so both dimensions stay in range and the #7123 Constraints overflow crash cannot recur. |
||
|
|
11f0b6bd51 |
desktop: show startup errors in a copyable window instead of bare "Failed to launch JVM" (#7261)
* desktop: show startup errors in a copyable window instead of bare "Failed to launch JVM" When any exception escapes main() before the app window appears - a missing DLL, a failed migration, broken AWT init - the jpackage launcher shows only "Failed to launch JVM" and the cause is recorded nowhere: the launcher runs without a console, so stderr is lost. Every report in #4146 stalled on this. Catch the error and show it in a native Win32 window laid out like a message box: an error icon and message, two clickable report links (the GitHub issue tracker and the support email) above a read-only selectable box with the stack trace, and an OK button. The links are SS_NOTIFY statics opened with ShellExecute (browser for the URL, mail client for the email). Native, not Swing, because broken AWT initialization is one of the failure causes. On Windows the process then exits cleanly so the launcher does not also show its own box; on other systems the error is rethrown to stderr. * docs: plan justifying desktop startup error window (#4146) |
||
|
|
cbd625d57a |
desktop: fix "Failed to launch JVM" on Windows when Java Access Bridge is enabled (one cause of #4146) (#7260)
* desktop: bundle jdk.accessibility to fix "Failed to launch JVM" when assistive technologies are enabled (#4146) The jlinked runtime shipped in desktop packages did not include the jdk.accessibility module. On Windows, when Java Access Bridge is enabled (jabswitch -enable, "Enable Java Access Bridge" in Ease of Access, or a screen reader creating %USERPROFILE%\.accessibility.properties), AWT throws AWTError "Assistive Technology not found: com.sun.java.accessibility.AccessBridge" during Toolkit init, before any window or log output, and the jpackage launcher reports "Failed to launch JVM". * docs: plan justifying jdk.accessibility fix for Windows JVM launch (#4146) * desktop: bundle jdk.accessibility only when building the Windows package |
||
|
|
6375457685 |
core: add optional profile description (#7256)
* core: add optional profile description * bot types * kotlin ui * query plans * postgres schema * fix ui * fix UI * refactor * description in business chats * share address * sign address card when shared by owner * from owner string * remove unused string * refactor * fix * ProfileDescriptionText * refactor modals * ios ui * nix config * correction Co-authored-by: simplex-chat-agent[bot] <287173099+simplex-chat-agent[bot]@users.noreply.github.com> --------- Co-authored-by: Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com> Co-authored-by: simplex-chat-agent[bot] <287173099+simplex-chat-agent[bot]@users.noreply.github.com> |
||
|
|
414f4b6ce1 |
core, ui: show domain for business chat in CLI, test, improve UI (#7235)
* core: preserve domain during group handshake * query plans * ui changes * remove unnecessary change * improve ui * name UI * card layout * fix footers, entry field * error icon * fix height * fix layout * fix layout * remove unused string * focus name field * refactor, fix * improve button * refactor * fix ios race * core: add domain to channel /i output --------- Co-authored-by: Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com> |
||
|
|
51ad366e8b | core, ui: message signing (#7115) | ||
|
|
e79b6ead11 |
desktop: fix Windows in-app updater corrupting install (#7105) (#7136)
* desktop: fix Windows in-app updater corrupting install by exiting before the MSI runs The Windows install path ran msiexec while the app was still running and waited for it. The running JVM holds SimpleX.exe, the JRE and DLLs open, so the per-machine MSI upgrade cannot replace them, defers to a reboot, and the install ends up broken - the app fails to launch (#7105). Launch the installer and exit instead, so the files are unlocked; the user reopens from the Start Menu. Also pass the path via the array form of exec (fixes spaces in the path) and remove a leftover installer before the next download, since the exiting app can no longer delete it itself. * docs: plan justifying the Windows in-app MSI updater fix |
||
|
|
63eaf260a2 |
ui: fix SEInvalidMention when @mention is edited in place (#7018)
* android, desktop, ios: drop stale mention map entry when @name is edited in place The compose-state mentions map was only pruned when fewer @name tokens were parsed from the text than were in the map. Editing an inserted @Name token in place (without re-picking from the picker) keeps the count at 1 while the parsed name no longer matches the stored key, so the stale entry was sent and the core rejected it with SEInvalidMention. Pruning now triggers whenever any map key is absent from the parsed mention names; the modified token is then sent as an unresolved formatted @ token. * android, desktop, ios: filter mentions at send time so in-place edits round-trip Treat the compose-state mentions map as a sticky cache of name -> memberId bindings recorded by the picker. The previous removeUnusedMentions pruned it on every text change, which dropped the binding the instant a letter was removed from an inserted @name token. Now the map is no longer mutated by text edits: memberMentions, the picker max-reached check, and the mentionMemberName disambiguator all filter against the names currently parsed from the message, so deleting and retyping the original characters re-resolves the original member, and the SEInvalidMention error from an in-place edit no longer occurs. * android, desktop, ios: cap memberMentions at MAX so stale cache entries cannot overflow the server limit With the sticky cache, manually typing an @name that happens to match a stale cache entry could push memberMentions above maxSndMentions and have the core reject the send with SEInvalidMention. The getter now walks parsedMessage in text order and stops at MAX_NUMBER_OF_MENTIONS; later @-tokens past the cap become visual-only formatting and the message still goes through. Also hoists the iOS activeMentions computation out of the per-row ForEach so it runs once per picker open rather than once per row. * android, desktop, ios: simplify mention restoration to minimal surgical diff Reverts the unnecessary mentionMemberName changes (the original mentions.containsKey behaviour preserves bindings better than the parsed-only variant), switches the picker checks to memberMentions.size (no helper variable needed since the getter already caps at MAX), and collapses the Kotlin memberMentions getter into a flat chain. * android, desktop, ios: gate picker mention-id and max-reached banner by memberMentions With the sticky cache, the picker's "currently bound member" highlight and the showMaxReachedBox banner-suppression clause read mentions[name] directly, so a stale cache entry made one row clickable (as a no-op) at the MAX limit and could suppress the banner when the user is actually adding a new mention. Both now gate the lookup by membership in the capped memberMentions, restoring the pre-fix UX where at MAX all rows are disabled and the banner shows when adding past the limit. |
||
|
|
6bb1da9e8d | core: request roster (#7121) | ||
|
|
10a814694c |
core, ui: support SimpleX names (#7045)
* deps: bump simplexmq for ConnectTarget * chat: migration adds simplex_name to contacts, groups, connections Nullable TEXT column on all three tables, with partial indexes on contacts(user_id, simplex_name) and groups(user_id, simplex_name) for the upcoming connectPlanName lookup. connections.simplex_name is the transient carrier from APIConnect -> XInfo handler, where the value is copied to contacts.simplex_name at delayed create. No reads or writes yet - column threading lands in subsequent commits. * tests: provide namesConfig = Nothing in smpServerCfg Follow-up to the simplexmq pin bump ( |
||
|
|
2c2337b07c |
android, desktop: keep wide images at natural aspect ratio without crashing (#7125)
* android, desktop: keep wide images at natural aspect ratio without crashing The merged fix clamped the framed image preview's aspect ratio at 2.33, which prevents the Constraints overflow crash but reshapes every image wider than 2.33:1 to 2.33:1. Compute the box height directly (height = w * min(h / w, 2.33f)) instead of deriving it via Modifier.aspectRatio. Very wide images keep their natural ratio (no upper clamp) while taller images stay capped at 2.33, and the overflow-prone width = height * ratio derivation is removed entirely. This mirrors how the iOS app sizes image previews. * docs: update wide-image crash plan for natural-ratio fix * remove comment --------- Co-authored-by: Evgeny <evgeny@poberezkin.com> |
||
|
|
b3944af735 |
desktop: fix crash when opening a video full screen (#7167)
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. |
||
|
|
e979b7efdc |
android, desktop, ios: remove left padding on consecutive received messages in channels (#7108)
* 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> |
||
|
|
96c662d068 |
android, desktop: fix crash on opening chat with extremely wide image (#7123)
* 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 |
||
|
|
0e09b38ea6 | core: public groups - roster of privileged members (#7017) | ||
|
|
8bf571cf5d | Merge branch 'stable' | ||
|
|
9b76742c6e |
desktop: fix in-app updater deleting the download before "Open file location" (#7104)
* 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)
|
||
|
|
134e48fe7e |
android, desktop, ios: remove right gap on received messages in channels (#7106)
* 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> |
||
|
|
b5f0659945 | Merge branch 'stable' | ||
|
|
547595041e |
ios: open SimpleX links in chat messages via in-app connect flow (#7101)
* 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). |
||
|
|
c6122f9637 |
android, desktop, ios: show clear error when saving group profile fails (#7090)
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.
|
||
|
|
feebefcdd7 |
Fix close icon hidden by long file name in compose file preview (#7077)
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. |
||
|
|
e60a012d22 |
android, desktop: fix lag when closing support chat in large groups (#7061)
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. |
||
|
|
cf6a60882f |
Fix IndexOutOfBounds crash when sending media with an undecodable preview (#7050)
* 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 |
||
|
|
a4cfa2ae97 |
android, desktop: fix overlapping warning texts after finalizing migration (#7071)
* android, desktop: fix overlapping warning texts after finalizing migration * plans: justify migration finished screen overlap fix |
||
|
|
8504c0ce98 |
cli: remove help entries for removed commands (#7079)
* cli: remove help entries for commands removed long ago /pq and /pq @<name> were removed in #4049 (PQ encryption for contacts is now automatic); /get stats and /reset stats were removed in #4375 (legacy agent stats). All four were left documented in CLI help, so typing them fails. Remove the stale entries. * plans: justify removal of outdated CLI help entries * plans: drop //get stats mention from CLI help plan |
||
|
|
bcd980127d | docs: allow sign content messages in channels plan (#7049) | ||
|
|
37f7ed3126 | Merge branch 'stable' | ||
|
|
ad23da63d0 |
core: supporter badges using anonymous BBS credentials (#7040)
* core: supporter badges using anonymous BBS credentials * badges in profiles * badge in profiles * process badges * update simplexmq * update simplexmq * change types * fix migration * migration * update simplexmq * fix bot API, schema * fix postgresql build * refactor * postgresql schema * correctly set badges in all cases * badges ffi * plan, bot types * FFI * FFI: export badge symbols * add extra field * refactor badge types to GADT * configurable badge key * add badge to profile, test * ui: badge images * generate badge key and sign badge * badge sign in CLI * fix commands, ui * rename badges * Binary * image size, migration * update badge images, add public key * send badges in more cases * update UI, tests * bot types, schema * postgres schema * tone down badges * revert formula * refactor badges * smaller badges * badge position * better badge position * simpler * position * move position * update simplexmq * show badge after name * badge layout * fix badge * debug badge height * shift badge * fix badge in member name * bigger badge * badge layout * differentiate badge colors * more avatars for the user's profiles * refactor * remove color filter * alerts * multiple keys, old expired * use new BBS api * update badge keys, bot api * presentation header * simplify * parser * update iOS images * update public keys * query plans * update simplexmq * refactor badge types * simplexmq * bot api types * update simplexmq - commoncrypto flag * update simplexmq * pass commoncrypto flag to simplexmq in nix iOS build * ios ui * update core library, fixes * badge layout * badge size * badge gap * remove extensions * simplify * share badge in more events, reverify badge if verification failed * larger files with badges * allow sending larger files * simpler * update simplexmq * better decoder for badge keys * update simplexmq --------- Co-authored-by: Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com> Co-authored-by: shum <github.shum@liber.li> |
||
|
|
503d091ec4 |
android, desktop, ios: show chat name in delete / leave / clear confirmation dialogs (#7021)
* 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> |
||
|
|
7c5406a4e9 |
desktop: fix drag-and-drop videos attached as files (#7015)
onFilesAttached classified URIs by isImage only, so videos fell through to processPickedFile and were attached as generic files. processPickedMedia already handles video correctly; the classifier above it just never reached that branch. Recognise videos as media inline using getFileName. |
||
|
|
7548fdae3b |
android, desktop: fix chat item long-press menu and ripple shape (#6997)
* android, desktop: fix chat item long-press menu and ripple shape clipChatItem clipped the bubble shape with Modifier.clip. Modifier.clip of the bubble GenericShape mis-hit-tests its path on very tall items, so long-press on the lower part of a long message did not reach combinedClickable and the context menu did not open (#6991); on desktop the same clip also left the press ripple rendered as a rectangle. Clip the bubble GenericShape in the draw pass (drawWithCache + clipPath) instead: drawing is clipped identically, the press ripple included, with no effect on hit-test. The RoundRect shape (tail disabled) hit-tests correctly and keeps Modifier.clip. Fixes #6991 * plans: justify chat item long-press and ripple shape fix --------- Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com> |
||
|
|
d657bcbba5 | Merge branch 'stable' |