Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
7b851c6
Merge branch 'master' into develop
pixlwave Jun 23, 2026
36a1788
Prepare for new sprint
pixlwave Jun 23, 2026
4e442f3
Update README.md.
pixlwave Sep 9, 2026
64716ee
Display a migration banner after a date or if is activate by the well…
Velin92 Sep 10, 2026
37d9e93
self review improvements
Velin92 Sep 10, 2026
2238184
add close button and make the migration banner higher priority, but m…
Velin92 Sep 11, 2026
2552faa
Merge pull request #8030 from element-hq/mauroromito/migration_banner
Velin92 Sep 11, 2026
0ab0157
Tailor the migration banner content from the homeserver well-known.
Velin92 Sep 11, 2026
29578b2
Hide the migration banner for a week when the user closes it.
Velin92 Sep 11, 2026
86975a8
Add the changelog entries for the migration banner.
Velin92 Sep 11, 2026
c606e80
Translated using Weblate (Ukrainian)
IhorHordiichuk Jun 29, 2026
f54a039
Translated using Weblate (Turkish)
Jul 6, 2026
1567841
Translated using Weblate (Indonesian)
Linerly Jul 31, 2026
340b656
Translated using Weblate (Swedish)
LinAGKar Aug 6, 2026
8a05e5b
Translated using Weblate (Latvian)
Sep 12, 2026
d81afb3
Translated using Weblate (Icelandic)
sveinki Sep 1, 2026
11a8859
Translated using Weblate (Ukrainian)
IhorHordiichuk Sep 14, 2026
745844f
Update date for exclude insecure devices.
mxandreas Sep 18, 2026
cf5e139
Merge pull request #8037 from RiotTranslateBot/weblate-riot-ios-riot-…
pixlwave Sep 22, 2026
cd4bc99
Merge pull request #8036 from RiotTranslateBot/weblate-riot-ios-riot-ios
pixlwave Sep 22, 2026
562566b
Merge pull request #8035 from element-hq/mxandreas/eid-banner-update
pixlwave Sep 22, 2026
0bb4f51
Translated using Weblate (Ukrainian)
IhorHordiichuk Sep 23, 2026
ff4ad51
Merge pull request #8038 from RiotTranslateBot/weblate-riot-ios-riot-ios
Velin92 Sep 24, 2026
7bde51e
version++
Velin92 Sep 24, 2026
5480823
update podfiles
Velin92 Sep 24, 2026
69f769b
update matrix ios sdk
Velin92 Sep 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Brewfile
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
brew "mint"
brew "getsentry/tools/sentry-cli"
cask "sentry-cli"
8 changes: 8 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
## Changes in 1.11.40 (2026-09-24)

✨ Features

- Display a banner in the room list inviting users to migrate to Element X, enabled via the homeserver Well Known file. ([#8030](https://github.com/element-hq/element-ios/issues/8030))
- Migration banner: the title, body, button and target app can be tailored via the homeserver Well Known file, and the banner stays hidden for a week once closed. ([#8034](https://github.com/element-hq/element-ios/issues/8034))


## Changes in 1.11.39 (2026-06-23)

🙌 Improvements
Expand Down
4 changes: 2 additions & 2 deletions Config/AppVersion.xcconfig
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@
//

// Version
MARKETING_VERSION = 1.11.39
CURRENT_PROJECT_VERSION = 1.11.39
MARKETING_VERSION = 1.11.40
CURRENT_PROJECT_VERSION = 1.11.40
11 changes: 11 additions & 0 deletions Config/BuildSettings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,17 @@ final class BuildSettings: NSObject {
/// are ready to migrate your users.
static let replacementApp: ReplacementApp? = .init()

// MARK: - Migration Banner

/// The date from which the migration banner is displayed when the homeserver Well Known has no
/// `io.element.migration_banner` section. Before this date, a missing section hides the banner.
/// When the section is present, its `enabled` value is always used instead.
static let migrationBannerShowWhenNotConfiguredStartDate = DateComponents(calendar: Calendar(identifier: .gregorian),
timeZone: TimeZone(secondsFromGMT: 0),
year: 2026,
month: 11,
day: 15).date!

// MARK: - Verification Required Banner

static let verificationRequiredBannerLearnMoreURL = URL(string: "https://docs.element.io/latest/element-support/device-verification/how-to-verify-devices")!
Expand Down
10 changes: 5 additions & 5 deletions Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,16 @@ PODS:
- LoggerAPI (1.9.200):
- Logging (~> 1.1)
- Logging (1.4.0)
- MatrixSDK (0.27.18):
- MatrixSDK/Core (= 0.27.18)
- MatrixSDK/Core (0.27.18):
- MatrixSDK (0.27.19):
- MatrixSDK/Core (= 0.27.19)
- MatrixSDK/Core (0.27.19):
- AFNetworking (~> 4.0.0)
- GZIP (~> 1.3.0)
- libbase58 (~> 0.1.4)
- MatrixSDKCrypto (= 0.17.0)
- Realm (= 10.27.0)
- SwiftyBeaver (= 1.9.5)
- MatrixSDK/JingleCallStack (0.27.18):
- MatrixSDK/JingleCallStack (0.27.19):
- JitsiMeetSDKLite (= 12.0.0-lite)
- MatrixSDK/Core
- MatrixSDKCrypto (0.17.0)
Expand Down Expand Up @@ -176,7 +176,7 @@ SPEC CHECKSUMS:
libPhoneNumber-iOS: 0a32a9525cf8744fe02c5206eb30d571e38f7d75
LoggerAPI: ad9c4a6f1e32f518fdb43a1347ac14d765ab5e3d
Logging: beeb016c9c80cf77042d62e83495816847ef108b
MatrixSDK: fdccd88405ec9e9f8e9978091bc0ac67cf396542
MatrixSDK: b05f338973ce0ec5d08c195d13f2014f2453171f
MatrixSDKCrypto: aa38e272e573c7798aa3cd761b2b61ec5c7731b8
ReadMoreTextView: 19147adf93abce6d7271e14031a00303fe28720d
Realm: 9ca328bd7e700cc19703799785e37f77d1a130f2
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Element iOS
# Element Classic iOS

![GitHub release (latest by date)](https://img.shields.io/github/v/release/element-hq/element-ios)
![badge-languages](https://img.shields.io/badge/languages-Swift%20%7C%20ObjC-orange.svg)
Expand Down
2 changes: 1 addition & 1 deletion Riot/Assets/de.lproj/Vector.strings
Original file line number Diff line number Diff line change
Expand Up @@ -2798,6 +2798,6 @@

// Verification Required Banner
"verification_required_banner_title" = "Verifiziere dieses Gerät";
"verification_required_banner_description" = "Ab Oktober 2026 können unverifizierte Geräte keine Nachrichten mehr senden und empfangen.";
"verification_required_banner_description" = "Bald können unverifizierte Geräte keine Nachrichten mehr senden und empfangen.";
"verification_required_banner_learn_more" = "Mehr erfahren";
"verification_required_banner_verify_button" = "Jetzt verifizieren";
7 changes: 6 additions & 1 deletion Riot/Assets/en.lproj/Vector.strings
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,11 @@
"sunset_download_banner_message" = "Faster, more secure, and packed with powerful collaboration tools.";
"sunset_download_banner_learn_more" = "Learn more";

// MARK: Migration banner
"migration_banner_title" = "Download Element X";
"migration_banner_body" = "Faster sync, a cleaner design, and new features you won't find here. The Element Classic app will be retired soon, so now's the time to make the move.";
"migration_banner_download_button" = "Download app";

// MARK: Onboarding
"onboarding_splash_register_button_title" = "Create account";
"onboarding_splash_login_button_title" = "I already have an account";
Expand Down Expand Up @@ -2617,7 +2622,7 @@ To enable access, tap Settings> Location and select Always";

// MARK: - Verification Required Banner
"verification_required_banner_title" = "Verify this device";
"verification_required_banner_description" = "As of October 2026 unverified devices will not be able to send and receive messages";
"verification_required_banner_description" = "Soon, unverified devices will not be able to send and receive messages";
"verification_required_banner_learn_more" = "Learn more";
"verification_required_banner_verify_button" = "Verify now";

Expand Down
2 changes: 1 addition & 1 deletion Riot/Assets/fr.lproj/Vector.strings
Original file line number Diff line number Diff line change
Expand Up @@ -2736,6 +2736,6 @@

// Verification Required Banner
"verification_required_banner_title" = "Vérifier cet appareil";
"verification_required_banner_description" = "À partir d’octobre 2026, les appareils non vérifiés ne pourront plus envoyer ni recevoir de messages.";
"verification_required_banner_description" = "Bientôt, les appareils non vérifiés ne pourront plus envoyer ni recevoir de messages.";
"verification_required_banner_learn_more" = "En savoir plus";
"verification_required_banner_verify_button" = "Vérifier maintenant";
2 changes: 1 addition & 1 deletion Riot/Assets/id.lproj/Vector.strings
Original file line number Diff line number Diff line change
Expand Up @@ -2994,6 +2994,6 @@
"room_member_power_level_short_owner" = "Pemilik";
"room_participants_leave_not_allowed_for_last_owner_msg" = "Anda tidak dapat meninggalkan ruangan karena Anda adalah pemilik satu-satunya.";
"verification_required_banner_title" = "Verifikasi perangkat ini";
"verification_required_banner_description" = "Mulai April 2026, perangkat yang belum diverifikasi tidak akan dapat mengirim dan menerima pesan";
"verification_required_banner_description" = "Mulai Oktober 2026, perangkat yang belum diverifikasi tidak akan dapat mengirim dan menerima pesan";
"verification_required_banner_learn_more" = "Pelajari lebih lanjut";
"verification_required_banner_verify_button" = "Verifikasi sekarang";
1 change: 1 addition & 0 deletions Riot/Assets/is.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -173,3 +173,4 @@

/* New voice broadcast from a specific person, not referencing a room. */
"VOICE_BROADCAST_FROM_USER" = "%@ byrjaði talútsendingu";
"UNSUPPORTED_CALL" = "Símtal án stuðnings";
20 changes: 19 additions & 1 deletion Riot/Assets/lv.lproj/Vector.strings
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"create" = "Izveidot";
"start" = "Sākt";
"leave" = "Atstāt";
"remove" = "Dzēst";
"remove" = "Noņemt";
"invite" = "Uzaicināt";
"retry" = "Atgriezties";
"on" = "Ieslēgts";
Expand Down Expand Up @@ -158,3 +158,21 @@
"login_optional_field" = "neobligāts";
"login_user_id_placeholder" = "Matrix ID (piem. @bob:matrix.org vai bobs)";
"login_identity_server_info" = "Matrix nodrošina identifikācijas serverus, lai atšķirtu, kuri e-pasti, u.c., pieder kuram Matrix ID. Patlaban pastāv vienīgi https://matrix.org";
"room_participants_action_invite" = "Uzaicināt";
"search_messages" = "Ziņojumi";
"search_files" = "Datnes";
"search_default_placeholder" = "Meklēt";
"room_event_action_delete" = "Dzēst";
"redact" = "Noņemt";
"room_creation_appearance" = "Izskats";
"room_creation_privacy" = "Konfidencialitāte";
"search_rooms" = "Istabas";
"room_event_action_copy" = "Kopēt";
"room_recents_conversations_section" = "ISTABAS";
"room_recents_people_section" = "CILVĒKI";
"room_creation_account" = "Konts";
"room_participants_title" = "Dalībnieki";
"room_event_action_redact" = "Noņemt";
"room_event_action_save" = "Saglabāt";
"room_event_action_quote" = "Citēt";
"search_people" = "Cilvēki";
2 changes: 1 addition & 1 deletion Riot/Assets/nl.lproj/Vector.strings
Original file line number Diff line number Diff line change
Expand Up @@ -2803,7 +2803,7 @@

// Verification Required Banner
"verification_required_banner_title" = "Dit apparaat verifiëren";
"verification_required_banner_description" = "Vanaf oktober 2026 kunnen niet-geverifieerde apparaten geen berichten meer verzenden en ontvangen.";
"verification_required_banner_description" = "Binnenkort kunnen niet-geverifieerde apparaten geen berichten meer verzenden en ontvangen.";
"verification_required_banner_learn_more" = "Meer informatie";
"verification_required_banner_verify_button" = "Nu verifiëren";
"accessibility_selected" = "seleccionat";
2 changes: 1 addition & 1 deletion Riot/Assets/pl.lproj/Vector.strings
Original file line number Diff line number Diff line change
Expand Up @@ -2871,7 +2871,7 @@

// Verification Required Banner
"verification_required_banner_title" = "Zweryfikuj to urządzenie";
"verification_required_banner_description" = "Od października 2026 r. niezweryfikowane urządzenia nie będą mogły wysyłać ani odbierać wiadomości.";
"verification_required_banner_description" = "Wkrótce niezweryfikowane urządzenia nie będą mogły wysyłać ani odbierać wiadomości.";
"verification_required_banner_learn_more" = "Dowiedz się więcej";
"verification_required_banner_verify_button" = "Zweryfikuj teraz";

4 changes: 4 additions & 0 deletions Riot/Assets/sv.lproj/Vector.strings
Original file line number Diff line number Diff line change
Expand Up @@ -2733,3 +2733,7 @@
"room_member_power_level_short_owner" = "Ägare";
"room_member_power_level_owner_in" = "Ägare i %@";
"room_participants_leave_not_allowed_for_last_owner_msg" = "Du kan inte lämna rummet eftersom du är den enda ägaren av det.";
"verification_required_banner_title" = "Verifiera den här enheten";
"verification_required_banner_description" = "Efter oktober 2026 kommer overifierade enheter inte kunna skicka eller ta emot meddelanden";
"verification_required_banner_learn_more" = "Läs mer";
"verification_required_banner_verify_button" = "Verifiera nu";
6 changes: 6 additions & 0 deletions Riot/Assets/tr.lproj/Vector.strings
Original file line number Diff line number Diff line change
Expand Up @@ -115,3 +115,9 @@
"authentication_forgot_password_input_message" = "%@ size bir doğrulama bağlantısı gönderecektir";
"authentication_verify_msisdn_waiting_message" = "%@ adresine bir kod gönderildi";
"authentication_qr_login_display_subtitle" = "Oturumunuz kapalı olan cihazınızla aşağıdaki QR kodunu tarayın.";
"all_chats_section_title" = "Sohbetler";
"all_chats_edit_layout" = "Arayüz tercihleri";
"all_chats_edit_layout_recents" = "Son Sohbetler";
"all_chats_title" = "Tüm Sohbetler";
"all_chats_edit_layout_unreads" = "Okunmamışlar";
"all_chats_edit_layout_add_filters_title" = "Mesajları filtreleyin";
7 changes: 7 additions & 0 deletions Riot/Assets/uk.lproj/Vector.strings
Original file line number Diff line number Diff line change
Expand Up @@ -2991,3 +2991,10 @@
"room_member_power_level_owner_in" = "Власник у %@";
"room_member_power_level_short_owner" = "Власник";
"room_participants_leave_not_allowed_for_last_owner_msg" = "Ви не можете вийти з кімнати, оскільки ви єдиний її власник.";
"verification_required_banner_title" = "Звірити цей пристрій";
"verification_required_banner_learn_more" = "Докладніше";
"verification_required_banner_verify_button" = "Звірити зараз";
"verification_required_banner_description" = "Невдовзі незвірені пристрої не зможуть надсилати та отримувати повідомлення";
"migration_banner_title" = "Завантажити Element X";
"migration_banner_body" = "Швидша синхронізація, лаконічніший дизайн та нові функції, яких тут немає. Уже незабаром застосунок Element Classic буде виведений з експлуатації, тож саме зараз час перейти на нову версію.";
"migration_banner_download_button" = "Завантажити застосунок";
12 changes: 12 additions & 0 deletions Riot/Generated/Strings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3803,6 +3803,18 @@ public class VectorL10n: NSObject {
public static func microphoneAccessNotGrantedForVoiceMessage(_ p1: String) -> String {
return VectorL10n.tr("Vector", "microphone_access_not_granted_for_voice_message", p1)
}
/// Faster sync, a cleaner design, and new features you won't find here. The Element Classic app will be retired soon, so now's the time to make the move.
public static var migrationBannerBody: String {
return VectorL10n.tr("Vector", "migration_banner_body")
}
/// Download app
public static var migrationBannerDownloadButton: String {
return VectorL10n.tr("Vector", "migration_banner_download_button")
}
/// Download Element X
public static var migrationBannerTitle: String {
return VectorL10n.tr("Vector", "migration_banner_title")
}
/// More
public static var more: String {
return VectorL10n.tr("Vector", "more")
Expand Down
6 changes: 6 additions & 0 deletions Riot/Managers/Settings/RiotSettings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,12 @@ final class RiotSettings: NSObject {
@UserDefault(key: "versionCheckNextDisplayDateTimeInterval", defaultValue: 0.0, storage: defaults)
var versionCheckNextDisplayDateTimeInterval

// MARK: - Migration banner

/// The date before which the migration banner stays hidden after the user closed it. `nil` when never closed.
@UserDefault(key: "migrationBannerNextDisplayDate", storage: defaults)
var migrationBannerNextDisplayDate: Date?

@UserDefault(key: "slideMenuRoomsCoachMessageHasBeenDisplayed", defaultValue: false, storage: defaults)
var slideMenuRoomsCoachMessageHasBeenDisplayed

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,15 @@ final class HomeserverConfiguration: NSObject {
let jitsi: HomeserverJitsiConfiguration
let encryption: HomeserverEncryptionConfiguration
let tileServer: HomeserverTileServerConfiguration
let migrationBanner: HomeserverMigrationBannerConfiguration

init(jitsi: HomeserverJitsiConfiguration,
encryption: HomeserverEncryptionConfiguration,
tileServer: HomeserverTileServerConfiguration) {
tileServer: HomeserverTileServerConfiguration,
migrationBanner: HomeserverMigrationBannerConfiguration) {
self.jitsi = jitsi
self.encryption = encryption
self.tileServer = tileServer
self.migrationBanner = migrationBanner
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,33 @@ final class HomeserverConfigurationBuilder: NSObject {
// MARK: - Properties

private let vectorWellKnownParser = VectorWellKnownParser()
private let currentDateProvider: () -> Date

// MARK: - Setup

override convenience init() {
self.init(currentDateProvider: Date.init)
}

/// - Parameter currentDateProvider: Provides the current date, used for time based configuration. Injectable for tests.
init(currentDateProvider: @escaping () -> Date) {
self.currentDateProvider = currentDateProvider

super.init()
}

// MARK: - Public

/// Create an `HomeserverConfiguration` from an HS Well-Known when possible otherwise it takes hardcoded values from BuildSettings by default.
func build(from wellKnown: MXWellKnown?) -> HomeserverConfiguration {
var vectorWellKnownEncryptionConfiguration: VectorWellKnownEncryptionConfiguration?
var vectorWellKnownJitsiConfiguration: VectorWellKnownJitsiConfiguration?
var vectorWellKnownMigrationBannerConfiguration: VectorWellKnownMigrationBannerConfiguration?

if let wellKnown = wellKnown, let vectorWellKnown = self.vectorWellKnownParser.parse(jsonDictionary: wellKnown.jsonDictionary()) {
vectorWellKnownEncryptionConfiguration = self.getEncryptionConfiguration(from: vectorWellKnown)
vectorWellKnownJitsiConfiguration = self.getJitsiConfiguration(from: vectorWellKnown)
vectorWellKnownMigrationBannerConfiguration = vectorWellKnown.migrationBanner
}

// Encryption configuration
Expand Down Expand Up @@ -77,6 +93,10 @@ final class HomeserverConfigurationBuilder: NSObject {

let tileServerConfiguration = HomeserverTileServerConfiguration(mapStyleURL: tileServerMapStyleURL)

// Migration banner configuration

let migrationBannerConfiguration = self.getMigrationBannerConfiguration(from: vectorWellKnownMigrationBannerConfiguration)

// Create HomeserverConfiguration

let jitsiConfiguration = HomeserverJitsiConfiguration(serverDomain: jitsiPreferredDomain,
Expand All @@ -85,7 +105,8 @@ final class HomeserverConfigurationBuilder: NSObject {

return HomeserverConfiguration(jitsi: jitsiConfiguration,
encryption: encryptionConfiguration,
tileServer: tileServerConfiguration)
tileServer: tileServerConfiguration,
migrationBanner: migrationBannerConfiguration)
}

// MARK: - Private
Expand Down Expand Up @@ -124,6 +145,38 @@ final class HomeserverConfigurationBuilder: NSObject {
return encryptionConfiguration
}

/// Resolve the migration banner configuration.
///
/// - When the `io.element.migration_banner` section is present, the banner follows its `enabled` value (enabled when not provided).
/// - When the section is missing (or the Well Known could not be parsed), the banner is hidden until
/// `BuildSettings.migrationBannerShowWhenNotConfiguredStartDate` and shown from this date on.
/// - Content: blank or missing values fall back to the default texts. A missing target app ID points to the default
/// replacement app, a blank one means that no download button is displayed.
private func getMigrationBannerConfiguration(from vectorWellKnownConfiguration: VectorWellKnownMigrationBannerConfiguration?) -> HomeserverMigrationBannerConfiguration {
let isEnabled: Bool
if let vectorWellKnownConfiguration = vectorWellKnownConfiguration {
isEnabled = vectorWellKnownConfiguration.isEnabled ?? true
} else {
isEnabled = currentDateProvider() >= BuildSettings.migrationBannerShowWhenNotConfiguredStartDate
MXLog.debug("[HomeserverConfigurationBuilder] getMigrationBannerConfiguration - No configuration found, enabled: \(isEnabled)")
}

let defaultTargetAppStoreID = BuildSettings.replacementApp?.productID
let targetAppStoreID: String?
if let customTargetAppID = vectorWellKnownConfiguration?.targetAppID {
targetAppStoreID = customTargetAppID.isBlank ? nil : customTargetAppID
} else {
targetAppStoreID = defaultTargetAppStoreID
}

return HomeserverMigrationBannerConfiguration(isEnabled: isEnabled,
title: vectorWellKnownConfiguration?.title.nonBlank ?? VectorL10n.migrationBannerTitle,
body: vectorWellKnownConfiguration?.body.nonBlank ?? VectorL10n.migrationBannerBody,
buttonText: vectorWellKnownConfiguration?.buttonText.nonBlank ?? VectorL10n.migrationBannerDownloadButton,
targetAppStoreID: targetAppStoreID,
isTargetDefaultReplacementApp: targetAppStoreID != nil && targetAppStoreID == defaultTargetAppStoreID)
}

private func jitsiServerURL(from jitsiServerDomain: String) -> URL? {
let jitsiStringURL: String
if jitsiServerDomain.starts(with: "http") {
Expand All @@ -140,3 +193,19 @@ final class HomeserverConfigurationBuilder: NSObject {
return jitsiServerURL
}
}

private extension String {
var isBlank: Bool {
trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
}
}

private extension Optional where Wrapped == String {
/// The string when it contains something else than whitespaces, `nil` otherwise.
var nonBlank: String? {
guard let string = self, !string.isBlank else {
return nil
}
return string
}
}
Loading
Loading