The one-hand chat list is flipped, so a header emitted before its rows was drawn below them. searchPart puts the header after the rows in that layout; the rows keep their order, nearest the search field. Spec links into ChatListView.swift refreshed.
R1-1: state OnlineSearchModel's main-thread premise, which iOS's
@MainActor carries and Kotlin otherwise leaves implicit.
R1-2: state the @Transient mechanism that keeps the directory link
check from being bypassed or deferred.
Spec line references into the two files shift by one accordingly.
Run: 003-kotlin-directory-search, review round 1
CODE.md asks for every line reference in an affected spec document to be
updated. Most of these were already stale on master. Numbers only: no
wording changes. Kept apart from the feature documentation so it can be
reviewed or dropped on its own.
Run: 003-kotlin-directory-search, slice 7
apps/multiplatform/CODE.md requires spec and product updates with every
code change. Mirror the iOS online search docs in the Kotlin docs' own
formats and numbering, stating where Android and desktop differ, and
fix the ConnectProgressManager signatures the branch's owner parameter
had made stale.
Run: 003-kotlin-directory-search, slice 6
Port the iOS New chat online search: the Search key and the "Search in
Directory" row work as on the chat list, and the Names and Directory
sections follow the contacts, de-duplicated against them. "No filtered
contacts" shows only when the online search found nothing. Archived
contacts are unchanged.
Run: 003-kotlin-directory-search, slice 5
Port the iOS chat list online search: the Search key resolves a typed
SimpleX name online and searches the directory, the "Search in
Directory" row searches the directory only, and the Names and Directory
parts follow the local chats, de-duplicated against them. The onboarding
state now shows the search bar too, with its results in place of the
onboarding pages (on desktop the pages stay in the centre pane). The
empty-state overlays no longer cover online results.
Run: 003-kotlin-directory-search, slice 4
Port OnlineSearchModel and its rows from the iOS DirectorySearchView:
directory pages and SimpleX name resolution share one lifecycle, run on
the main thread like the iOS @MainActor model, and drop late replies by
generation. Add the "Search in Directory" row, the directory and name
rows, the first-search alert, and an onSearch handler on SearchTextField
(Android IME Search action, desktop Enter). Nothing calls them yet.
Run: 003-kotlin-directory-search, slice 3
Port the iOS online-search model: a directory entry now carries a
non-optional link that decoding accepts only for group and channel links
(the core fetches an invitation link even in a store-only plan), plus
planLocalChatId, NameSearchResult, DirectoryResult and OnlineSearchRows.
apiConnectPlan gains onFailure so lookups can log instead of alerting,
and the first-search alert gets its hint preference.
Run: 003-kotlin-directory-search, slice 2
The onboarding state will render the same search bar as the chat list,
as iOS renders ChatListSearchBar in both. Move the three search states
to ChatListView, next to searchText, where iOS keeps them, and extract
the sticky header's search bar and tags into ChatListSearchBarWithTags.
No behaviour change.
Run: 003-kotlin-directory-search, slice 1
apps/ios/CODE.md asks that every #Lxx-Lyy reference in an affected
spec document point at the right lines. Most of these were already
stale on master; this corrects them in the documents the online search
change touched. Numbers only: no wording changes. Kept apart from the
feature documentation so it can be reviewed or dropped on its own.
Run: 002-ios-directory-search-ui, review round 1 (R1-7)
apps/ios/CODE.md asks every iOS change to keep spec/ and product/ in
step with the code. The online search (SimpleX Directory and names, on
the chat list and in New chat) had no documentation, including the
parts added on this branch before: the spec gains a chat-list section
and a New chat section, the API entries for the service request and
the connection plan's failure mode, the impact and index rows and the
Document Map rows for the new source files; the product docs gain the
views, a concept row, rules for what the search may and may not do,
and the known gaps. Only line references moved by this branch's own
code are updated here.
Run: 002-ios-directory-search-ui, review round 1 (R1-7)
R1-1 New chat clears its online results on a profile switch, as the chat list does
R1-3 the search spinner follows its two flags through property observers
R1-4 the join verb keeps one line, so the name truncates instead
Run: 002-ios-directory-search-ui, review round 1
The New chat sheet gets the chat list's online search: a "Search in
Directory" row, the keyboard Search key for names and the directory,
and Names and Directory sections after the contacts. Tapping a result
connects and closes the sheet, as other New chat actions do. A contact
the list already shows is not repeated as a name result, and the
contacts list's empty state stays hidden while a search runs or shows
results. Archived contacts, which share the search bar and the list,
are unchanged.
Run: 002-ios-directory-search-ui, slice 4
The keyboard Search key on the chat list now also resolves a typed
SimpleX name online, alongside the directory search, and shows what it
finds as a Names part between the local chats and the directory
results. Resolution never raises an alert: apiConnectPlan gains an
onFailure mode, and existing callers keep alerting by default.
Each directory entry is looked up in the store (store-only, as entries
are now group or channel links only) so a chat the user already sees
locally, or found by name, is not shown again; names already in the
local list are hidden the same way. A directory row is matched to a
name only by the link the core resolved, never by a profile field the
name's owner writes, so a name cannot hide a genuine directory row.
The "Search in Directory" row, retry and "Show more" keep the names
already shown. One helper drives the spinner from whether anything is
in flight, and every publish checks the search generation, so a late
reply after a text change or cancel writes nothing. The first-search
alert now also names the server that resolves names.
Run: 002-ios-directory-search-ui, slice 3
A directory result is untrusted input: an entry is kept only when its
link is a group or channel link, because the core fetches a one-time
invitation link even when asked for a store-only plan, and the next
slice looks every entry up in the store. The entry's link is no longer
optional, so the tap needs no unwrap and the id no fallback.
The Directory part now renders only when it has a row: no header while
loading, no "No results" row; the empty-state overlay shows when the
search finished with nothing. Its header is a row rather than a sticky
section header, which in the flipped one-hand list would pin at an
unverified position. Rows say "Join channel" or "Join group". A pasted
link in one-hand mode leaves the tags in place.
Run: 002-ios-directory-search-ui, slice 2
The search model is about to hold name results beside directory
results, so it becomes OnlineSearchModel. The "Show more" and retry
rows, the first-search consent check and the contacts list's shown-chat
derivation move to where the New chat sheet can reuse them. No
behaviour change.
Run: 002-ios-directory-search-ui, slice 1
R1-1 add the service response to the exhaustive iOS and Kotlin response matches
R1-3 directory search cursor carries only the sort value of its mode, so the RPC no longer sends registration times
R1-4 a directory search does not take the progress spinner from a connection
R1-6 directory entry type, name and rounded times defined once for listing and RPC
R1-7 restore the directory test harness signatures of master, with link-pair variants for the new tests
R1-8 tests: compressed service request through SREQ, reject without a reason
* rfc: invite member of one group to another group
* update
* update
* implement
* version
* update
* do not show error if contact was removed when joining group
---------
Co-authored-by: Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com>
* ios: delete temp data from storage view
The temp folder is the XFTP agent work directory and holds scratch
files for received-file encryption and roster transfers, so deleting it
under a running chat breaks those writes mid-flight. The button is only
enabled while chat is stopped, the same precondition core deleteStorage
relies on, and the row says how to stop chat.
* ios: show delete temp data alert over settings sheet
AlertManager presents from ContentView, which is behind the settings
sheet, so the confirmation never appeared. The alert now also warns
that interrupted transfers never complete and that videos in unsent
drafts, which live in the temp folder, are lost, and the caption names
the screen where chat is stopped.
* ios: say unsent draft videos are sent without the file
A draft video whose converted file was deleted from the temp folder is
still sent, as a preview with no file, so "lost" understated it.
---------
Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
* android: recognise passphrase fields as passwords for password managers
Passphrase fields used a hand-rolled VisualTransformation lambda, so Compose never
marked them as password fields: the autofill structure carried no hints and no
password inputType, and password managers could not identify them. The raw
passphrase was also exposed through the CopyText/ACTION_COPY accessibility action.
PassphraseField now declares an autofill content type and uses
PasswordVisualTransformation, which sets the password semantics Compose derives
that flag from.
Fields that create a passphrase declare NewPassword. Where no field would carry
plain password (first-time setup, hidden profile password) they declare both, as
managers do not recognise a form whose every field is newPassword. On the change
form the current-passphrase field supplies password, so the new fields declare
only newPassword - adding password there leaves the manager unable to tell which
field holds the new value and suppresses the save prompt.
* android: do not offer to save hidden profile passwords
Hidden profile password fields no longer declare an autofill content type.
Compose commits the autofill session, which shows the password manager's
save prompt, only after the last field with a content type leaves the
screen, so these fields never trigger it. A saved entry would reveal that
the hidden profile exists.
Rewrite the plan to match the code.
* android, desktop: storage breakdown in developer options
Port of the iOS Storage screen (#5529): lists each top-level entry of the app's data, config and temp folders with its recursive size, so it is possible to see what takes space.
* android, desktop: simplify storage view
Use DefaultProgressView, drop the bold total and redundant absoluteFile, and do not follow top-level symlinks (matches iOS; avoids counting the Android lib link).
* android, desktop: storage view: do not crash on names the JVM cannot convert to Path
Under a non-UTF-8 locale on Linux, File.toPath() throws InvalidPathException for a non-ASCII top-level name (e.g. a received file decrypted into tmpDir); log it and show 0 bytes. Also correct plan wording.
* android, desktop: storage view: fold path conversion into treeSize, use produceState
Also correct plan wording (du per-directory overhead, unlistable roots, Windows junctions).
* android, desktop: storage view: open as card screen like other settings screens
* android, desktop: storage view: list top-level entries as Path to keep their exact names
File.listFiles + toPath loses names that are not valid in the JVM file-name encoding (non-ASCII under a C locale, invalid UTF-8), so those entries showed 0 bytes. Files.newDirectoryStream keeps the raw bytes.
* android, desktop: storage view: normalize root paths before dropping nested roots
A root spelled with .. (e.g. XDG_DATA_HOME=/x/data/../tmp) compared as nested in another root and its size was shown nowhere.
* android, desktop: document storage view in spec and product docs
* android, desktop: storage view: single catch when listing a root
* android, desktop: do not log file paths in storage view
The walk's IOException messages contain the full path, which in tmpDir can be a received file's original name.
---------
Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
The core allocates every chat_* response with malloc and passes ownership to
the caller, as iOS does in API.swift. Both JNI bindings copied the bytes into a
Java string and dropped the pointer, so every command, event and file read
leaked its whole response into the C allocator - invisible to both the JVM and
the Haskell heap profiler.
Desktop frees in decode_to_utf8_string, the funnel every entry point already
goes through. Android has no such funnel because NewStringUTF is called inline,
so decode_and_free is added and the call sites routed through it. chatReadFile
frees directly on both.
Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
* core: group fixes/improvements
* Large messages dropped by relays and hosts (the same 15602 limit was applied, without accounting for forwarding header)
* Members below v18 did not receive forwarded messages or history in p2p groups.
* Join confirmations near the size limit could fail.
* SMP servers are limited to 2 host names each.
* simplify
* remove legacy forwarding and history support
* only restrict p2p groups
---------
Co-authored-by: Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com>
* android: create new keystore keys in StrongBox
* android: show keystore key storage level
* ui: save keystore key before marking it stored
* android: show keystore storage level in the footer text instead of a separate row
* android: return keystore storage label, drop enum
---------
Co-authored-by: Narasimha-sc <166327228+Narasimha-sc@users.noreply.github.com>
* core, ui: remove database backups after key changes
* ui: delete database backups on chat start without delaying encryption marker reset
Keeping encryptionStartedAt set until chat start made DatabaseErrorView show
"The attempt to change database passphrase was not completed" with a Restore
button after every successful passphrase change when the passphrase is not
stored. Restore the marker handling from master and delete backups
unconditionally after chat starts.
* another flag
---------
Co-authored-by: Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com>
Co-authored-by: Narasimha-sc <166327228+Narasimha-sc@users.noreply.github.com>
"Blur media" hid images and videos in a chat but showed the same media
sharp as the thumbnail of a quoted message and as the last message's
image, video or link preview in the chat list.
Small views are now blurred with the same reveal as chat media. They are
resampled relative to the width they are drawn at, so a thumbnail is
blurred as much on screen as media in a chat, and drawn as the centre
crop the thumbnail shows. In-chat media keeps the same blur.
Blurring the chat list also needs: each last message gets its own blur
state, loaded chat-list videos use it and hide their full-screen play
button while blurred, a right-click on a thumbnail no longer leaves it
revealed on desktop, and leaving a chat mid-scroll no longer undoes
reveals in the list on Android.
Co-authored-by: Narasimha-sc <166327228+Narasimha-sc@users.noreply.github.com>
* ui: blur media by resampling the preview, not a per-frame effect
Modifier.blur put a BlurEffect on a display-sized graphics layer, so the
Gaussian was re-evaluated on every frame the media was drawn. It also
requires RenderEffect, which Android applies only from API 31, so below
Android 12 media was drawn unblurred while the setting read as on.
A blur and a downscale discard the same thing - detail finer than their
radius - so the preview is now resampled to about one pixel per radius and
stretched back. That runs once when the item composes, needs no
RenderEffect, and removes the detail irreversibly rather than convolving
it. The first resampling step bounds both sides, so no image, however
shaped, can produce a large intermediate.
While the blur hides the media the file is also left unread, so an image
scrolled past is no longer read, decoded at its full size and kept in the
image cache only to be hidden again. blurHidesMedia() is the single
definition that both the drawing and the loading decision use, so they
cannot disagree about whether the media is on screen.
* plans: blur media by resampling the preview
* ui: shorten comments in media blur
* ui: smooth the media blur and match its strength to the old one
Stretching the resampled preview straight to the screen showed its pixel grid. Double it back
up until its longest side is at least half the reference width before drawing, so the last
stretch is short and the tents compose into a bell. The reference width moves from 360 to 400:
fitted against Gaussians, 360 blurred 5-75% more than Modifier.blur did, 400 is within 10% at
every setting.
(cherry picked from commit c0344a9a61)
* core: cancel the chat callback on interrupt
* badges: add multi-use codes
* badges: add the managed group
* badges: document multi-use codes and the group
* core: sign test badges with whole-second expiry
* badges: rework tracker and reply texts