* Add a developer flag for the search tab
Introduces a globalSearchEnabled user preference (off by default) with a toggle in Developer Options, used to gate the new search experience.
* Support search role tabs in the tab coordinator
Migrates the NavigationTabCoordinator to SwiftUI's value-based Tab API and adds an isSearch option so a tab can adopt the iOS 26 .search role, which detaches it into the trailing search button in the tab bar.
* Moves room search to a new screen
Adds a new screen that filters the alternate room summary provider as you type, shows an empty state, and lists matching rooms with their matrix identifier. Supports hardware keyboard selection (arrow keys to move through results, return to open) by reaching into the system search field.
* Present search in a dedicated tab
Adds the search tab to the chats flow on iOS 26 when the flag is enabled, rendered as the detached .search role button. The Cmd+K shortcut now selects this tab, and the home screen's inline search is hidden while the tab is available.
* Remove the old global search screen
The dedicated search tab replaces the Cmd+K floating window, so the GlobalSearchScreen, its window plumbing in the WindowManager, and its presentation in the chats flow are all removed.
* Enable Swift 6.2 with default main actor isolation for the app target
Turn on SWIFT_VERSION 6.2, SWIFT_APPROACHABLE_CONCURRENCY and SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor for the ElementX target, matching the NSE and Share Extension (which already use 6.2 without the main actor default).
# Conflicts:
# ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
* Update the code generation templates for Swift 6
Emit nonisolated/@unchecked Sendable mocks and nonisolated string enums, and mark async mock methods @concurrent so they keep suspending like the real implementations (otherwise intermediate states are unobservable in tests). Includes the regenerated mocks and strings.
# Conflicts:
# ElementX/Sources/Mocks/Generated/GeneratedMocks.swift
* Make the data layer and services nonisolated and Sendable
Annotate the value-type models, Rust SDK proxy wrappers, timeline item graph, settings, logging and NSE-shared types as nonisolated, and conform the service protocols to Sendable so their existentials can cross isolation boundaries. These types run off the main actor and no longer inherit the target's default main actor isolation.
* Adopt the new isolation across call sites, mocks and signatures
Update call sites, closure signatures and mock configuration helpers to match the nonisolated/Sendable data layer, including the main actor isolated mock inits and the @Sendable filter on recentlyVisitedRooms.
^ Conflicts:
^ ElementX/Sources/Services/SecureBackup/SecureBackupController.swift
* Remove redundant @MainActor annotations
With the app target defaulting to main actor isolation, the explicit @MainActor on view models, coordinators, views and their protocols is no longer required.
* Fix concurrency hotspots surfaced by the migration
Resolve the room summary main-thread deadlock by moving diff processing onto AsyncStreams, add the SDKListener.onMainActor FIFO bridge for SDK callbacks, hop KVO/timer/UIKit callbacks to the main actor via assumeIsolated, add isolated deinits, and apply the few justified nonisolated(unsafe) escapes for queue-confined Apple APIs.
* Fix the unit tests for Swift 6 concurrency
Hop the waitForConfirmation body onto the caller's actor (Swift Testing runs confirmation closures off-actor), and adjust the affected suites and async signatures.
* offload diffs on concurrent threads instead of main actor
* make room info protocols sendable
* use a task group for fetching data so that everything runs concurrently in the TextRoomTimelineView when getting the preview.
* use the generic onMainActor implementation, which already makes a stream and send the continuation on the main actor.
* small pr comments issues being addressed
* update enterprise
* Update StaticCode to 1.1.0 and specify the required Swift isolation.
* fix ElementCallServiceTests
* update enterprise
* re-generate mocks
* update enterprise
* enterprise new code conformed to swift 6
---------
Co-authored-by: Doug <douglase@element.io>
update generateRecoveryKey to take a passphrase option
Wire up AppHooks with recovery key hook
Update xcodeproj
Review fixups
Review fixups
Make the `SecureBackupRecoveryState.viewMode` extension private
Bump the enterprise submodule
The send queue auto-enable listener fires whenever the queue is believed
off and the homeserver is reachable. While the client is suspended this
could re-enable the queue and potentially fall straight back into a re-enable
loop.
Gate the listener on the desired service state so queues are only restarted
when we intend to be running, and nudge the listener on resume so the queue
recovers reliably. The SDK only re-enables queues when client.resume() runs,
which is gated behind clientPausingAndResumingEnabled, so the nudge keeps
recovery uniform across builds rather than depending on the flag.
`pauseServices` took a completion block only because it was historically
called from `deinit`, which can't await (the SE-0371 note). `deinit` now
stops the sync itself, so the sole remaining caller is the AppCoordinator,
which can await instead.
Make `pauseServices` async and update the AppCoordinator background-task
and lifecycle call sites to await it, calling the OS-facing completions
(endBackgroundTask / setTaskCompleted) afterwards. This also removes the
`MainActor.assumeIsolated` workaround — awaiting a @MainActor method hops
onto the main actor cleanly — and fixes a latent bug where a nil `self` in
the background app refresh expiration handler would skip setTaskCompleted.
Pause and resume previously ran as independent, unsynchronised tasks:
`pauseServices` stopped the sync and paused the client from a detached
`Task`, while `resumeServices` resumed and started the sync directly.
With no ordering between them, a late background pause could land after a
foreground resume — leaving the client suspended and the sync stopped
while the app was active, which broke everything until the next relaunch.
Route both through a single serialised reconciler: each request records
the desired state and chains onto the previous transition, and the
reconcile always drives the SDK to the *latest* desired state. Transitions
can no longer interleave and the most recent intent always wins.
On deinit we no longer pause the client — it's being deallocated, so the
SDK tears it down anyway; we just stop the sync and cancel the delegate.
iOS kills the app if a VoIP push isn't followed by reportNewIncomingCall, so
report an immediately ended call on every early-return path, named after the
room. A duplicate of an ongoing call is reported as answered elsewhere and an
expired push as a missed call that can be tapped to call back.
Signed-off-by: Daniel Anderson <daniel.anderson@toptal.com>
* added a new tracking mode for following a marker and the logic for switching such tracking mode
* implemented the follow marker tracking mode
* improved and added new tests
* Use our standard XYZMock(.init()) pattern everywhere instead of XYZMock.default.
* Remove the configuration parameter label on mock convenience inits.
* Update the enterprise submodule.
* create DependenciesProtocol and replace ServiceLocator registration with it
replace mocking calls for ServiceLocator to Dependencies
replace ServiceLocator with instance level calls
replace ServiceLocator configs with TestDependencies config in Tests
replace servicelocator usage in analytics service
replace remaining ServiceLocator calls in unit tests
that depended on the UnitTestAppCoordinator configuration with per suite dependencies instances
fix swiftlint warning
remove remaining references to ServiceLocator
make dependencies properties consistent in tests
delete Dependencies and DependenciesProtocol
fix whitespace modifications
updates from PR feedback
a pass over converting AppSettings and AnalyticsService test instance properties to per test instantiation
fix a missed PR feedback
Sync up after rebase
create DependenciesProtocol and replace ServiceLocator registration with it
replace mocking calls for ServiceLocator to Dependencies
replace ServiceLocator with instance level calls
replace ServiceLocator configs with TestDependencies config in Tests
replace servicelocator usage in analytics service
replace remaining ServiceLocator calls in unit tests
that depended on the UnitTestAppCoordinator configuration with per suite dependencies instances
fix swiftlint warning
remove remaining references to ServiceLocator
make dependencies properties consistent in tests
delete Dependencies and DependenciesProtocol
* AppSettings is instance level mockable now
* replace mocked and preview usages of reset + AppSettings global init with
AppSettings(store: UserDefaultsMock())
* AppSettings.UserDefaultsKeys is now a RawRep struct, making it extensible
* update tests with new AppSettings so it builds
* update remaining AppSettings test/mock inits
* AppSettings.UserDefaultsKey conformed to ExpressibleByStringLiteral
* addressed issues found in first pass ai review
* update AppSettings instantiations after rebase
* fix accessibility tests coordinator after rebase
* Rename to VolatileUserDefaults
* AppSettings init uses mandatory parameter
* Revert UserDefaultsKey to enum
* Add TrackedUserDefaults to be able to reset the defaults object
* Added volatile static func to AppSettings
* Use .volatile() static func instead of full init
* review fixups
* Resolve build issues after rebase
* updated enterprise module
* remove the static reset settings call from the preview tests
---------
Co-authored-by: Mauro Romito <mauro.romito@element.io>
* Reduce warnings in AttributedStringBuilderTests
(cherry picked from commit e1f32b2b1df147c93207e01df5c8fec4046b0f48)
* Fixed Sentry warning
(cherry picked from commit e45c3002b2331a6f2d363de61a12377316078602)
* JoinRule and AllowRule conform to Sendable
(cherry picked from commit 27c67c27b944df11adc17bdd405e442e8c50b0d6)
* AudioConverterProtocol conform to Sendable
(cherry picked from commit 352bdab15db424c632538eb3140858eae9fdfb15)
* TimelineControllerProtocol conform to Sendable
(cherry picked from commit 07a927745624db4bab3cf68842027e12177be70b)
* Resolved some AI review notes
(cherry picked from commit 8d3ac820754a6126785e639a98c8c1fcf86ef77f)
# Conflicts:
# ElementX/Sources/Other/SendableConformance/SendableBox.swift
# ElementX/Sources/Other/SendableConformance/WeakSendableBox.swift
* swift 6 support for NSE and concurrency safe implementations for it
* enforcing required Sendable conformance for swift 6 migration in NSE
* use Mutex to access the string builder cache
* use an async stream to update timeline items diffs
* fix for Sendable appHooks
* Timeline: add a Jump to Unread button and a new-message badge on the scroll-to-bottom button.
* Rename scrollToFirstUnread to scrollToReadMarker for consistency with isReadMarkerVisible.
* Cache readMarkerUniqueID and unreadMessageCount as stored properties on TimelineState.
* TimelineViewModel: rename newTimelineItems and drop redundant ordering comment.
* Use Compound chevron icons in TimelineScrollButton.
* Fix TimelineScrollButton layout at large dynamic type sizes.
* Add a trailingAccessory slot to topBanners and use it to dodge banners with the jump-to-read-marker button.
* Put jump-to-read-marker behind a developer-options feature flag.
* Fine-tune TimelineScrollButton sizing and badge colour.
* Remove TimelineScrollButton chevron offset that was compensating for SF Symbol centering.
* Drop the new-messages count, replace with a presence dot.
* Move both timeline scroll buttons to bottom-right and revert topBanners trailingAccessory slot.
* Position TimelineScrollButton badge above for up direction and below for down direction.
* Add long-press mark-as-read pill to the timeline scroll buttons
* Read isSwitchingTimelines from state in updateHasNewMessagesAtBottom.
* Use assertionFailure when scrollToReadMarker is called with no read marker.
* Add TimelineScrollButton preview snapshots.
* (fix) design and various self-review issues
* (fix) suppress scroll button tap after long-press
* (feat) fall back to RoomInfo.fullyReadEventID for jump-to-unread
* (fix) hide jump-to-unread immediately after mark-as-read
* (fix) align with newer matrix-rust-sdk surface
* Bump the RustSDK to v26.05.20
---------
Signed-off-by: Daniel Anderson <daniel.anderson@toptal.com>
Co-authored-by: Stefan Ceriu <stefan.ceriu@gmail.com>
* added foundation for custom notifications
* feat: add localizations for tone names
also, if available, include the pro sound in the element x sound list
* use a static file location to store the notification tone
and update the file at that location when selected
* if there's a problem previewing or setting the alert tone, toast the user with the error
* rename tone directory
* create NotificationToneManager to handle tone business logic
* conform to project formatting
* assign custom sound to actual notifications
* dry up notification generation in the nse
* add compilation condition for including notification alert tone and remove from share extension
* swift format had some opinions
* flatten the notification tone ui, removing system/elementx designations
* fix stored file url breaking due to sandbox path changes
* tone manager can import custom tones and provide a list of imported tones
* ui support for custom tones
* can delete custom imported tones
* sort the custom tones
* add additional preset sounds
* can choose caf files for custom sounds and fix location persistence issues
* store sound locations with relative links
this is a better solution than storing bookmarks since bookmark resolution can be variable and differ from freshly derived urls
* add docs for new notification features
* run tone conversion in background
* resolved several ai found issues
* added protocol and mocks for notification tone manager
* added tests for notification settings screen vm interactions with the tone manager
* add tests for notification tone manager
* rename element tone names per feedback
* move custom tone option to the top of the notification tone list
* rename Tone to Sound for consistency with Android
* remove additional bundled tones
* restore accidentally deleted Element Default sound file
* system alert tones are filtered by what's available on the system prior to finalizing the collection
* Add a couple more caf file conversion log statements
* Simplify the notification sound conversion logic
* Update notification sound strings in sync with Android
* update xcodeproj after rebase
* remove conditional compilation and include NotificationAlertSound in share extension
* Use AudioPlayer instead of custom tone previewer and remove the previewer
* rename NotificationTone
* Consolidate and move errors to top of NotificationToneManager
* tone manager no longer depends on userinterfacecontroller
* Move static Tone properties to NotificationToneManager
* resolve view model initializer feedback
* update xcodeproj
* fatalerror when tone manager cannot init
* Remove banner icon on alert tone error
* fixup review feedback
* Moved URL handling to NotificationToneManager
* Create AnalyticsServiceProtocol and conform AnalyticsService
* Replace AnalyticsService properties and arguments with AnalyticsServiceProtocol
* Inline AnalyticsServiceMock replacing old mock
* handle more complex AnalyticsServiceMock callsites
* move documentation from AnalyticsService to AnalyticsServiceProtocol
* handle the details to invite users flow.
* implemented the room creation in the invite screen, and its dismissal + navigaton to the newly created room
* updated preview and unit tests
* pr suggestions
Removes the ServiceLocator singleton and instead directly injects the dependencies it was holding for more control over instantiation of the various component subtrees and better visibility over how they're being used.
This will use an underlying SDK method and send a private read receipt plus a fully read marker in every single room the user is part of. It will most likely be a long operation that will get rate limited so the task is set to run detached for as long as it takes.
* Hook up Rust's InvalidCheckCode failure to the right state when linking a new device.
* Automatically regenerate a new QR code instead of ending the flow when it expires.
* feat: Render declined calls in DM timeline
* Record preview snapshots
* review: Use CallIntent constant
* review: clean-up
* Record preview snapshots
* review: Move private field to the bottom with MARK: - Private
* review: Male CallIntent a string enum to use rawValue
---------
Co-authored-by: Element CI <ci@element.io>