From 00d9048124719ff8271a179dd831c8500dd10100 Mon Sep 17 00:00:00 2001 From: Jan Niklas Grabowski Date: Mon, 15 Sep 2025 17:39:24 +0200 Subject: [PATCH 1/7] feat: add migration part 1 banner (MESSENGER-7564) --- Config/BWIBuildSettings.swift | 13 ++- Riot/Assets/de.lproj/Bwi.strings | 7 ++ Riot/Assets/en.lproj/Bwi.strings | 7 ++ Riot/Generated/BWIStrings.swift | 20 +++++ .../Recents/DataSources/RecentsDataSource.m | 14 ++-- bwi/FeatureBanner/FeatureBannerView.swift | 79 ++++++++++++------- .../FeatureBannerViewController.swift | 2 +- .../FeatureBannerVisibilityService.swift | 45 ++++------- bwi/Wellknown/Wellknown+Bwi.swift | 18 +++++ bwi/Wellknown/WellknownBWI.swift | 10 +++ 10 files changed, 148 insertions(+), 67 deletions(-) diff --git a/Config/BWIBuildSettings.swift b/Config/BWIBuildSettings.swift index 02ec6f2b0..3b07bf08e 100644 --- a/Config/BWIBuildSettings.swift +++ b/Config/BWIBuildSettings.swift @@ -38,6 +38,8 @@ class BWIBuildSettings: NSObject { static let additionalBwiHeaderKey = "bwiHeader" static let isWellknownFederationEnabled = "isWellknownFederationEnabled" static let isLabsFederationEnabled = "isLabsFederationEnabled" + static let BuMXMigrationInfoLevelKey = "BuMXMigrationInfoLevel" + static let didBuMXMigrationInfoLevelKey = "didBuMXMigrationInfoLevel" } private struct BwiSettingsConstants { @@ -488,7 +490,7 @@ class BWIBuildSettings: NSObject { var forgotPasswordInformationAlert = true // MARK: Promote new feature within a banner below the navigation view - var showTopBanner = false + var showTopBanner = true var showCustomServerDisplayName = true var customServerDisplayName = "" @@ -674,6 +676,15 @@ class BWIBuildSettings: NSObject { @UserDefault(key: UserDefaultsKeys.isLabsFederationEnabled, defaultValue: false, storage: RiotSettings.defaults) var isLabsFederationEnabled + // Migration status from wellknown config + @UserDefault(key: UserDefaultsKeys.BuMXMigrationInfoLevelKey, defaultValue: 0, storage: RiotSettings.defaults) + var BuMXMigrationInfoLevel + + // Migration level already shown + @UserDefault(key: UserDefaultsKeys.didBuMXMigrationInfoLevelKey, defaultValue: 0, storage: RiotSettings.defaults) + var didShowBuMXMigrationInfoLevel + + // shows the grey/green/red shield for the room avatar / user avatar var showEncryptionStatusBadgeOnAvatar = false diff --git a/Riot/Assets/de.lproj/Bwi.strings b/Riot/Assets/de.lproj/Bwi.strings index 5a5234acf..695ad1f84 100644 --- a/Riot/Assets/de.lproj/Bwi.strings +++ b/Riot/Assets/de.lproj/Bwi.strings @@ -746,3 +746,10 @@ "introduce_federation_start" = "Verstanden"; "room_error_join_failed_federation_disabled_message" = "Du kannst den Raum nicht beitreten. Grund: Die Föderation wurde für diesen Raum aufgehoben."; + +// MARK: migration to new client +"bwi_mobile_dialog_m_banner_title" = "Wechsel zum BundesMessengerX"; +"bwi_mobileMdialog_m1_banner_text" = "Das ist eine neue, technisch optimierte App vom BuM. Du kannst sie jetzt schon ausprobieren, bevor bald alle umsteigen müssen."; +"bwi_mobileMdialog_m1_banner_text_bold" = " Alle deine Kontakte und Nachrichten bleiben erhalten."; +"bwi_mobile_dialog_m_banner_button2" = "Erfahre mehr"; +"bwi_mobile_dialog_m_banner_button1" = "Später erinnern"; diff --git a/Riot/Assets/en.lproj/Bwi.strings b/Riot/Assets/en.lproj/Bwi.strings index 59ea7a88a..b5bbae092 100644 --- a/Riot/Assets/en.lproj/Bwi.strings +++ b/Riot/Assets/en.lproj/Bwi.strings @@ -659,3 +659,10 @@ "introduce_federation_start" = "Understood"; "room_error_join_failed_federation_disabled_message" = "You cannot join the room. The federation for this room has been withdrawn."; + +// MARK: migration to new client +"bwi_mobile_dialog_m_banner_title" = "Change to BundesMessengerX"; +"bwi_mobileMdialog_m1_banner_text" = "This is a new technically optimized app from BuM. You can try it out now before everyone has to switch soon."; +"bwi_mobileMdialog_m1_banner_text_bold" = " All your contacts and messages will be retained."; +"bwi_mobile_dialog_m_banner_button2" = "Learn more"; +"bwi_mobile_dialog_m_banner_button1" = "Remember later"; diff --git a/Riot/Generated/BWIStrings.swift b/Riot/Generated/BWIStrings.swift index 027f53e64..9ffc649cb 100644 --- a/Riot/Generated/BWIStrings.swift +++ b/Riot/Generated/BWIStrings.swift @@ -299,6 +299,26 @@ public class BWIL10n: NSObject { public static var bwiMdmLogoutMessage: String { return BWIL10n.tr("Bwi", "bwi_mdm_logout_message") } + /// Später erinnern + public static var bwiMobileDialogMBannerButton1: String { + return BWIL10n.tr("Bwi", "bwi_mobile_dialog_m_banner_button1") + } + /// Erfahre mehr + public static var bwiMobileDialogMBannerButton2: String { + return BWIL10n.tr("Bwi", "bwi_mobile_dialog_m_banner_button2") + } + /// Wechsel zum BundesMessengerX + public static var bwiMobileDialogMBannerTitle: String { + return BWIL10n.tr("Bwi", "bwi_mobile_dialog_m_banner_title") + } + /// Das ist eine neue, technisch optimierte App vom BuM. Du kannst sie jetzt schon ausprobieren, bevor bald alle umsteigen müssen. + public static var bwiMobileMdialogM1BannerText: String { + return BWIL10n.tr("Bwi", "bwi_mobileMdialog_m1_banner_text") + } + /// Alle deine Kontakte und Nachrichten bleiben erhalten. + public static var bwiMobileMdialogM1BannerTextBold: String { + return BWIL10n.tr("Bwi", "bwi_mobileMdialog_m1_banner_text_bold") + } /// Meine Notizen public static var bwiNotesRoomTitle: String { return BWIL10n.tr("Bwi", "bwi_notes_room_title") diff --git a/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m b/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m index 725965e62..712d626f5 100644 --- a/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m +++ b/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m @@ -202,7 +202,7 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou { NSMutableArray *types = [NSMutableArray array]; // bwi: add new feature banner - if (!shouldHideFeatureBanner && BWIBuildSettings.shared.showTopBanner && BWIBuildSettings.shared.isWellknownFederationEnabled){ + if (!shouldHideFeatureBanner && BWIBuildSettings.shared.showTopBanner && BWIBuildSettings.shared.BuMXMigrationInfoLevel > 0) { [types addObject:@(RecentsDataSourceSectionTypeFeatureBanner)]; } @@ -1890,17 +1890,18 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou - (void) shouldShowFeatureBanner { - if (BWIBuildSettings.shared.showTopBanner && BWIBuildSettings.shared.isWellknownFederationEnabled){ + if (BWIBuildSettings.shared.showTopBanner && BWIBuildSettings.shared.BuMXMigrationInfoLevel > 0){ MXSession* session = [self mxSession]; if(!session) return; - NSString *version = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"]; FeatureBannerVisibilityService *featureBannerService = [[FeatureBannerVisibilityService alloc] initWithMxSession:session]; - [featureBannerService isUnreadWithVersion:version completion:^(BOOL unread) { + [featureBannerService isUnreadWithCompletion: ^(BOOL unread) { if (unread) { // this notification will be called either if the user clicked on the banner or wants to hide it [[NSNotificationCenter defaultCenter] addObserverForName:@"de.bwi.messenger.hide_top_banner" object:NULL queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notification) { - [self markFeatureBannerAsRead]; + self->shouldHideFeatureBanner = TRUE; + [self.delegate dataSource:self didCellChange:nil]; + //[self markFeatureBannerAsRead]; }]; // this notification will be called either if the user clicked on the banner or wants to hide it using a swipe gesture @@ -1931,9 +1932,8 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou MXSession* session = [self mxSession]; if(!session) return; - NSString *version = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"]; FeatureBannerVisibilityService *featureBannerService = [[FeatureBannerVisibilityService alloc] initWithMxSession:session]; - [featureBannerService markAsReadWithVersion:version]; + [featureBannerService markAsRead]; self->shouldHideFeatureBanner = TRUE; [self.delegate dataSource:self didCellChange:nil]; } diff --git a/bwi/FeatureBanner/FeatureBannerView.swift b/bwi/FeatureBanner/FeatureBannerView.swift index 8362b2e11..c073cb2f1 100644 --- a/bwi/FeatureBanner/FeatureBannerView.swift +++ b/bwi/FeatureBanner/FeatureBannerView.swift @@ -90,14 +90,8 @@ protocol FeatureBannerDelegate { } func didPressShowDetails() { - let htmlFile = BWIBuildSettings.shared.bwiFeatureHistoryFilePath - self.webViewController = WebViewViewController(localHTMLFile: htmlFile) - webViewController?.title = BWIL10n.bwiSettingsNewFeaturesHeader - let navigationBar: UINavigationController = UINavigationController(rootViewController: webViewController!) - webViewController?.navigationItem.setLeftBarButton(UIBarButtonItem(title: VectorL10n.close, style: .plain, target: self, action: #selector(closeModal)), animated: false) - hostingController.parent?.present(navigationBar, animated: true, completion: { () -> Void in - self.hideTopBanner() - }) + hideTopBanner() + // Show Details } @objc func closeModal() { @@ -111,17 +105,15 @@ struct FeatureBannerView: View { var body: some View { VStack(alignment: .center) { - closeButton header advertisementText - if !BWIL10n.bwiFeatureBannerShowMoreButton.isEmpty { + HStack(alignment: .center, spacing: 12) { + remindMeLaterButton showMoreButton - } else { - Spacer() - .frame(height: 25) } } + .padding(.horizontal, 12) .background(Color(ThemeService.shared().theme.tintColor)) .cornerRadius(12) .padding(16) @@ -129,23 +121,28 @@ struct FeatureBannerView: View { var header: some View { HStack() { -// Image(Asset.Images.newFeatures.name) - Image(Asset.Images.roomFederatedBumIconDark.name) + Image(Asset.Images.newFeatures.name) // .renderingMode(.template) -// .foregroundColor(Color(ThemeService.shared().theme.backgroundColor)) - Text(BWIL10n.bwiFeatureBannerHeader) + .foregroundColor(Color(ThemeService.shared().theme.backgroundColor)) + Text(BWIL10n.bwiMobileDialogMBannerTitle) .font(.system(size: 20).bold()) .foregroundColor(Color(ThemeService.shared().theme.backgroundColor)) + Spacer() } + .padding(.top, 16) } var advertisementText: some View { - Text(BWIL10n.bwiFeatureBannerAdvertisementText) - .font(.system(size: 15)) - .multilineTextAlignment(.center) - .lineLimit(nil) - .foregroundColor(Color(ThemeService.shared().theme.backgroundColor)) - .padding(5) + VStack { + Text(BWIL10n.bwiMobileMdialogM1BannerText) + + Text(BWIL10n.bwiMobileMdialogM1BannerTextBold) + .bold() + } + .font(.system(size: 15)) + .multilineTextAlignment(.leading) + .lineLimit(nil) + .fixedSize(horizontal: false, vertical: true) + .foregroundColor(Color(ThemeService.shared().theme.backgroundColor)) } var closeButton: some View { @@ -166,13 +163,37 @@ struct FeatureBannerView: View { Button { delegate?.didPressShowDetails() } label: { - Text(BWIL10n.bwiFeatureBannerShowMoreButton) - .font(.system(size: 15)) - .padding(10) - .foregroundColor(Color(ThemeService.shared().theme.backgroundColor)) - .overlay(RoundedRectangle(cornerRadius: 12) - .stroke(Color(ThemeService.shared().theme.backgroundColor), lineWidth: 2)) + HStack() { + Spacer() + Text(BWIL10n.bwiMobileDialogMBannerButton2) + .font(.system(size: 18)) + .bold() + .padding(10) + .foregroundColor(Color(ThemeService.shared().theme.tintColor)) + Spacer() + } } + .background(Color(ThemeService.shared().theme.backgroundColor)) + .cornerRadius(12) + .padding(.top, 10) + .padding(.bottom, 25) + } + + var remindMeLaterButton: some View { + Button { + delegate?.didPressClose() + } label: { + HStack() { + Spacer() + Text(BWIL10n.bwiMobileDialogMBannerButton1) + .font(.system(size: 15)) + .padding(10) + .foregroundColor(Color(ThemeService.shared().theme.backgroundColor)) + Spacer() + } + } + .overlay(RoundedRectangle(cornerRadius: 12) + .stroke(Color(ThemeService.shared().theme.backgroundColor), lineWidth: 2)) .padding(.top, 10) .padding(.bottom, 25) } diff --git a/bwi/TopBanner/FeatureBannerViewController.swift b/bwi/TopBanner/FeatureBannerViewController.swift index 3843908f9..621453777 100644 --- a/bwi/TopBanner/FeatureBannerViewController.swift +++ b/bwi/TopBanner/FeatureBannerViewController.swift @@ -43,7 +43,7 @@ import UIKit override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) - _ = FeatureBannerVisibilityService(mxSession: self.session).markAsRead(version: self.version) + FeatureBannerVisibilityService(mxSession: self.session).markAsRead() NotificationCenter.default.post(name: .bwiMarkTopBannerAsRead, object: self, userInfo: ["type" : "feature_banner"]) } diff --git a/bwi/TopBanner/FeatureBannerVisibilityService.swift b/bwi/TopBanner/FeatureBannerVisibilityService.swift index 81d5b0d3e..838f23e33 100644 --- a/bwi/TopBanner/FeatureBannerVisibilityService.swift +++ b/bwi/TopBanner/FeatureBannerVisibilityService.swift @@ -35,39 +35,26 @@ import Foundation self.session = mxSession } - func markAsRead( version: String ) -> MXHTTPOperation? { - // Update only the "widgets" field in the account data - var featureDict = self.session.accountData.accountData(forEventType: AccountDataTypes.featureVisibility) ?? [:] - - featureDict[version] = false - - return session.setAccountData(featureDict, forType: AccountDataTypes.featureVisibility, success: nil, failure: nil) + func markAsRead() { + BWIBuildSettings.shared.didShowBuMXMigrationInfoLevel = BWIBuildSettings.shared.BuMXMigrationInfoLevel } - func markAsUnread( version: String ) -> MXHTTPOperation? { - // Update only the "widgets" field in the account data - var featureDict = self.session.accountData.accountData(forEventType: AccountDataTypes.featureVisibility) ?? [:] - - featureDict[version] = true - - return session.setAccountData(featureDict, forType: AccountDataTypes.featureVisibility, success: { - // bwi: update tableview - NotificationCenter.default.post(name: .bwiMarkTopBannerAsUnRead, object: nil, userInfo: ["type" : "feature_banner"]) - }, failure: nil) + func markAsUnread( version: String ) { + BWIBuildSettings.shared.didShowBuMXMigrationInfoLevel = 0 } - func isUnread( version: String, completion: @escaping (_ unread : Bool) -> Void) { - session.matrixRestClient.getAccountData(forType: AccountDataTypes.featureVisibility ) { (jsonResponse, error) in - - guard let featureDict = jsonResponse as? [String:Any] else { - completion(true) - return - } - guard let unread = featureDict[version] as? Bool else { - completion(true) - return - } - completion(unread) + func isUnread(completion: @escaping (_ unread : Bool) -> Void) { + guard BWIBuildSettings.shared.BuMXMigrationInfoLevel < 3 else { + completion(true) + return + } + + if BWIBuildSettings.shared.didShowBuMXMigrationInfoLevel < BWIBuildSettings.shared.BuMXMigrationInfoLevel { + completion(true) + return + } else { + completion(false) + return } } } diff --git a/bwi/Wellknown/Wellknown+Bwi.swift b/bwi/Wellknown/Wellknown+Bwi.swift index beb38b7e1..6981b7080 100644 --- a/bwi/Wellknown/Wellknown+Bwi.swift +++ b/bwi/Wellknown/Wellknown+Bwi.swift @@ -94,6 +94,24 @@ public extension MXWellKnown { } } + @objc func migrationInfoLevel() -> Int { + do { + guard let bwiDict = self.jsonDictionary()["de.bwi"] as? [String : Any] else { + return 0 + } + + let bwi = try WellknownBWI(dict: bwiDict) + if let migration = bwi.migrationBuMX { + return migration.level ?? 0 + } else { + return 0 + } + } + catch { + return 0 + } + } + @objc func updateFederationStatus() { do { guard let bwiDict = self.jsonDictionary()["de.bwi"] as? [String : Any] else { diff --git a/bwi/Wellknown/WellknownBWI.swift b/bwi/Wellknown/WellknownBWI.swift index ce4626140..453fea626 100644 --- a/bwi/Wellknown/WellknownBWI.swift +++ b/bwi/Wellknown/WellknownBWI.swift @@ -21,6 +21,7 @@ struct WellknownBWI: Decodable { let dataPrivacyURL: String? let imprintURL: String? let federation: WellknownFederation? + let migrationBuMX: WellknownMigrationBuMX? init(dict: [String: Any]) throws { let jsonData = try JSONSerialization.data(withJSONObject: dict, options: []) @@ -32,6 +33,7 @@ struct WellknownBWI: Decodable { case dataPrivacyURL = "data_privacy_url" case imprintURL = "imprint_url" case federation + case migrationBuMX = "migration_bumx" } } @@ -52,3 +54,11 @@ struct WellknownFederation: Decodable { } } + +struct WellknownMigrationBuMX: Decodable { + let level: Int? + + enum CodingKeys: String, CodingKey { + case level + } +} From bed94efd527667030692b25a8a9f51944d4c419e Mon Sep 17 00:00:00 2001 From: Jan Niklas Grabowski Date: Wed, 17 Sep 2025 17:34:29 +0200 Subject: [PATCH 2/7] feat: add migration dialog 1 (MESSENGER-7553) --- Config/BWIBuildSettings.swift | 2 +- .../BuM_Appicon-next_iOS_1024.png | Bin 0 -> 63349 bytes .../bumx_logo.imageset/Contents.json | 15 ++ Riot/Assets/de.lproj/Bwi.strings | 20 ++ Riot/Assets/en.lproj/Bwi.strings | 19 ++ Riot/Generated/BWIStrings.swift | 56 ++++ Riot/Generated/Images.swift | 1 + .../Recents/DataSources/RecentsDataSource.m | 4 +- .../Home/AllChats/AllChatsCoordinator.swift | 5 + .../MatrixSDK/AuthenticationService.swift | 6 +- .../DeveloperSettingsView.swift | 8 +- bwi/FeatureBanner/FeatureBannerView.swift | 8 +- bwi/FeatureBanner/MigrationInfoView.swift | 243 ++++++++++++++++++ .../FeatureBannerViewController.swift | 2 +- .../FeatureBannerVisibilityService.swift | 6 - 15 files changed, 375 insertions(+), 20 deletions(-) create mode 100644 Riot/Assets/SharedImages.xcassets/bumx_logo.imageset/BuM_Appicon-next_iOS_1024.png create mode 100644 Riot/Assets/SharedImages.xcassets/bumx_logo.imageset/Contents.json create mode 100644 bwi/FeatureBanner/MigrationInfoView.swift diff --git a/Config/BWIBuildSettings.swift b/Config/BWIBuildSettings.swift index 3b07bf08e..4ad67dacb 100644 --- a/Config/BWIBuildSettings.swift +++ b/Config/BWIBuildSettings.swift @@ -677,7 +677,7 @@ class BWIBuildSettings: NSObject { var isLabsFederationEnabled // Migration status from wellknown config - @UserDefault(key: UserDefaultsKeys.BuMXMigrationInfoLevelKey, defaultValue: 0, storage: RiotSettings.defaults) + @UserDefault(key: UserDefaultsKeys.BuMXMigrationInfoLevelKey, defaultValue: 1, storage: RiotSettings.defaults) var BuMXMigrationInfoLevel // Migration level already shown diff --git a/Riot/Assets/SharedImages.xcassets/bumx_logo.imageset/BuM_Appicon-next_iOS_1024.png b/Riot/Assets/SharedImages.xcassets/bumx_logo.imageset/BuM_Appicon-next_iOS_1024.png new file mode 100644 index 0000000000000000000000000000000000000000..7b4cd0a18fb1800cff93cdb6511982109fc97d65 GIT binary patch literal 63349 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 JE3EwQ{131=s4)Nl literal 0 HcmV?d00001 diff --git a/Riot/Assets/SharedImages.xcassets/bumx_logo.imageset/Contents.json b/Riot/Assets/SharedImages.xcassets/bumx_logo.imageset/Contents.json new file mode 100644 index 000000000..905cce3a2 --- /dev/null +++ b/Riot/Assets/SharedImages.xcassets/bumx_logo.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "BuM_Appicon-next_iOS_1024.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/Riot/Assets/de.lproj/Bwi.strings b/Riot/Assets/de.lproj/Bwi.strings index 695ad1f84..55977f55e 100644 --- a/Riot/Assets/de.lproj/Bwi.strings +++ b/Riot/Assets/de.lproj/Bwi.strings @@ -748,8 +748,28 @@ "room_error_join_failed_federation_disabled_message" = "Du kannst den Raum nicht beitreten. Grund: Die Föderation wurde für diesen Raum aufgehoben."; // MARK: migration to new client +// Banner "bwi_mobile_dialog_m_banner_title" = "Wechsel zum BundesMessengerX"; "bwi_mobileMdialog_m1_banner_text" = "Das ist eine neue, technisch optimierte App vom BuM. Du kannst sie jetzt schon ausprobieren, bevor bald alle umsteigen müssen."; "bwi_mobileMdialog_m1_banner_text_bold" = " Alle deine Kontakte und Nachrichten bleiben erhalten."; "bwi_mobile_dialog_m_banner_button2" = "Erfahre mehr"; "bwi_mobile_dialog_m_banner_button1" = "Später erinnern"; + +// Sheet page 1 +"bwi_mobile_dialog_m_more_title" = "BundesMessengerX"; + +"bwi_mobile_dialog_m1_more_text_1" = "Diese App wird bald abgeschaltet und durch eine neue technisch optimierte App ersetzt "; +"bwi_mobile_dialog_m1_more_text_2" = "(alle deine Kontakte und Nachrichten bleiben erhalten)."; +"bwi_mobile_dialog_m1_more_text_3" = "\n\nDich erwartet u.a.:"; +"bwi_mobile_dialog_m1_more_text_bullet_1" = "schnellere Performance"; +"bwi_mobile_dialog_m1_more_text_bullet_2" = "verbessertes Design"; +"bwi_mobile_dialog_m1_more_text_bullet_3" = "neue Funktionen, wie Nachrichten anheften"; +"bwi_mobile_dialog_m1_more_text_bullet_4" = "optimierte Barrierefreiheit"; + +// Sheet page 2 +"bwi_mobile_dialog_m_more2_title" = "Wechsel jetzt:"; +"bwi_mobile_dialog_m_more2_text_1" = "Benutzername hier kopieren"; +"bwi_mobile_dialog_m_more2_text_2" = "App über den Button herunterladen"; +"bwi_mobile_dialog_m_more2_text_3" = "Mit deinen Anmeldedaten einloggen"; +"bwi_mobile_dialog_m_more_button" = "Neue App herunterladen"; +"bwi_mobile_dialog_m_more_success" = "Benutzername kopiert"; diff --git a/Riot/Assets/en.lproj/Bwi.strings b/Riot/Assets/en.lproj/Bwi.strings index b5bbae092..5f84537b4 100644 --- a/Riot/Assets/en.lproj/Bwi.strings +++ b/Riot/Assets/en.lproj/Bwi.strings @@ -661,8 +661,27 @@ "room_error_join_failed_federation_disabled_message" = "You cannot join the room. The federation for this room has been withdrawn."; // MARK: migration to new client +// Banner "bwi_mobile_dialog_m_banner_title" = "Change to BundesMessengerX"; "bwi_mobileMdialog_m1_banner_text" = "This is a new technically optimized app from BuM. You can try it out now before everyone has to switch soon."; "bwi_mobileMdialog_m1_banner_text_bold" = " All your contacts and messages will be retained."; "bwi_mobile_dialog_m_banner_button2" = "Learn more"; "bwi_mobile_dialog_m_banner_button1" = "Remember later"; + +// Sheet page 1 +"bwi_mobile_dialog_m_more_title" = "BundesMessengerX"; +"bwi_mobile_dialog_m1_more_text_1" = "This app will soon be shut down and replaced by a new technically optimized app "; +"bwi_mobile_dialog_m1_more_text_2" = "(all your contacts and messages will remain)."; +"bwi_mobile_dialog_m1_more_text_3" = "\n\nAmong other things, you can expect:"; +"bwi_mobile_dialog_m1_more_text_bullet_1" = "faster performance"; +"bwi_mobile_dialog_m1_more_text_bullet_2" = "improved design"; +"bwi_mobile_dialog_m1_more_text_bullet_3" = "new functions, such as pinning messages"; +"bwi_mobile_dialog_m1_more_text_bullet_4" = "optimized accessibility"; + +// Sheet page 2 +"bwi_mobile_dialog_m_more2_title" = "Change now:"; +"bwi_mobile_dialog_m_more2_text_1" = "Copy user name here"; +"bwi_mobile_dialog_m_more2_text_2" = "Download app via the button"; +"bwi_mobile_dialog_m_more2_text_3" = "Log in with your credentials"; +"bwi_mobile_dialog_m_more_button" = "Download new app"; +"bwi_mobile_dialog_m_more_success" = "user name copied"; diff --git a/Riot/Generated/BWIStrings.swift b/Riot/Generated/BWIStrings.swift index 9ffc649cb..98322363f 100644 --- a/Riot/Generated/BWIStrings.swift +++ b/Riot/Generated/BWIStrings.swift @@ -299,6 +299,34 @@ public class BWIL10n: NSObject { public static var bwiMdmLogoutMessage: String { return BWIL10n.tr("Bwi", "bwi_mdm_logout_message") } + /// Diese App wird bald abgeschaltet und durch eine neue technisch optimierte App ersetzt + public static var bwiMobileDialogM1MoreText1: String { + return BWIL10n.tr("Bwi", "bwi_mobile_dialog_m1_more_text_1") + } + /// (alle deine Kontakte und Nachrichten bleiben erhalten). + public static var bwiMobileDialogM1MoreText2: String { + return BWIL10n.tr("Bwi", "bwi_mobile_dialog_m1_more_text_2") + } + /// \n\nDich erwartet u.a.: + public static var bwiMobileDialogM1MoreText3: String { + return BWIL10n.tr("Bwi", "bwi_mobile_dialog_m1_more_text_3") + } + /// schnellere Performance + public static var bwiMobileDialogM1MoreTextBullet1: String { + return BWIL10n.tr("Bwi", "bwi_mobile_dialog_m1_more_text_bullet_1") + } + /// verbessertes Design + public static var bwiMobileDialogM1MoreTextBullet2: String { + return BWIL10n.tr("Bwi", "bwi_mobile_dialog_m1_more_text_bullet_2") + } + /// neue Funktionen, wie Nachrichten anheften + public static var bwiMobileDialogM1MoreTextBullet3: String { + return BWIL10n.tr("Bwi", "bwi_mobile_dialog_m1_more_text_bullet_3") + } + /// optimierte Barrierefreiheit + public static var bwiMobileDialogM1MoreTextBullet4: String { + return BWIL10n.tr("Bwi", "bwi_mobile_dialog_m1_more_text_bullet_4") + } /// Später erinnern public static var bwiMobileDialogMBannerButton1: String { return BWIL10n.tr("Bwi", "bwi_mobile_dialog_m_banner_button1") @@ -311,6 +339,34 @@ public class BWIL10n: NSObject { public static var bwiMobileDialogMBannerTitle: String { return BWIL10n.tr("Bwi", "bwi_mobile_dialog_m_banner_title") } + /// Benutzername hier kopieren + public static var bwiMobileDialogMMore2Text1: String { + return BWIL10n.tr("Bwi", "bwi_mobile_dialog_m_more2_text_1") + } + /// App über den Button herunterladen + public static var bwiMobileDialogMMore2Text2: String { + return BWIL10n.tr("Bwi", "bwi_mobile_dialog_m_more2_text_2") + } + /// Mit deinen Anmeldedaten einloggen + public static var bwiMobileDialogMMore2Text3: String { + return BWIL10n.tr("Bwi", "bwi_mobile_dialog_m_more2_text_3") + } + /// Wechsel jetzt: + public static var bwiMobileDialogMMore2Title: String { + return BWIL10n.tr("Bwi", "bwi_mobile_dialog_m_more2_title") + } + /// Neue App herunterladen + public static var bwiMobileDialogMMoreButton: String { + return BWIL10n.tr("Bwi", "bwi_mobile_dialog_m_more_button") + } + /// Benutzername kopiert + public static var bwiMobileDialogMMoreSuccess: String { + return BWIL10n.tr("Bwi", "bwi_mobile_dialog_m_more_success") + } + /// BundesMessengerX + public static var bwiMobileDialogMMoreTitle: String { + return BWIL10n.tr("Bwi", "bwi_mobile_dialog_m_more_title") + } /// Das ist eine neue, technisch optimierte App vom BuM. Du kannst sie jetzt schon ausprobieren, bevor bald alle umsteigen müssen. public static var bwiMobileMdialogM1BannerText: String { return BWIL10n.tr("Bwi", "bwi_mobileMdialog_m1_banner_text") diff --git a/Riot/Generated/Images.swift b/Riot/Generated/Images.swift index 375895ffe..6c84218e5 100644 --- a/Riot/Generated/Images.swift +++ b/Riot/Generated/Images.swift @@ -411,6 +411,7 @@ internal class Asset: NSObject { @objc(AssetSharedImages) internal class SharedImages: NSObject { internal static let cancel = ImageAsset(name: "cancel") internal static let e2eVerified = ImageAsset(name: "e2e_verified") + internal static let bumxLogo = ImageAsset(name: "bumx_logo") internal static let horizontalLogo = ImageAsset(name: "horizontal_logo") internal static let loginFlowLogo = ImageAsset(name: "login_flow_logo") internal static let radioButtonDefault = ImageAsset(name: "radio-button-default") diff --git a/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m b/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m index 712d626f5..1f6644d15 100644 --- a/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m +++ b/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m @@ -1894,7 +1894,7 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou MXSession* session = [self mxSession]; if(!session) return; - FeatureBannerVisibilityService *featureBannerService = [[FeatureBannerVisibilityService alloc] initWithMxSession:session]; + FeatureBannerVisibilityService *featureBannerService = [FeatureBannerVisibilityService alloc]; [featureBannerService isUnreadWithCompletion: ^(BOOL unread) { if (unread) { // this notification will be called either if the user clicked on the banner or wants to hide it @@ -1932,7 +1932,7 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou MXSession* session = [self mxSession]; if(!session) return; - FeatureBannerVisibilityService *featureBannerService = [[FeatureBannerVisibilityService alloc] initWithMxSession:session]; + FeatureBannerVisibilityService *featureBannerService = [FeatureBannerVisibilityService alloc]; [featureBannerService markAsRead]; self->shouldHideFeatureBanner = TRUE; [self.delegate dataSource:self didCellChange:nil]; diff --git a/Riot/Modules/Home/AllChats/AllChatsCoordinator.swift b/Riot/Modules/Home/AllChats/AllChatsCoordinator.swift index 6b8ccbcfb..b27dc860a 100644 --- a/Riot/Modules/Home/AllChats/AllChatsCoordinator.swift +++ b/Riot/Modules/Home/AllChats/AllChatsCoordinator.swift @@ -772,6 +772,11 @@ class AllChatsCoordinator: NSObject, SplitViewMasterCoordinatorProtocol { self.currentMatrixSession?.refreshHomeserverWellknown(false, success: { wellknown in // bwi: #5706 fix crash: only show matomo alert when wellknown is available wellknown?.updateFederationStatus() + // BWI #7564 add migration level + if let migrationLevel = wellknown?.migrationInfoLevel() { + BWIBuildSettings.shared.BuMXMigrationInfoLevel = migrationLevel + } + // BWI #7564 END self.bwiCheckForMatomoPromt() }, failure: nil) } diff --git a/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/AuthenticationService.swift b/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/AuthenticationService.swift index 3dfae2391..bc3c597ce 100644 --- a/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/AuthenticationService.swift +++ b/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/AuthenticationService.swift @@ -250,7 +250,11 @@ class AuthenticationService: NSObject { if let wellKnown = try? await wellKnown(for: homeserverURL) { self.wellknown = wellKnown wellknown?.updateFederationStatus() - + // BWI #7564 add migration level + if let migrationLevel = wellknown?.migrationInfoLevel() { + BWIBuildSettings.shared.BuMXMigrationInfoLevel = migrationLevel + } + // BWI #7564 END if let baseURL = URL(string: wellKnown.homeServer.baseUrl) { homeserverURL = baseURL } diff --git a/bwi/DeveloperSettings/DeveloperSettingsView.swift b/bwi/DeveloperSettings/DeveloperSettingsView.swift index 16aff2aca..ec17137b6 100644 --- a/bwi/DeveloperSettings/DeveloperSettingsView.swift +++ b/bwi/DeveloperSettings/DeveloperSettingsView.swift @@ -229,15 +229,11 @@ fileprivate func unrestrictUser(mxSession: MXSession?) -> Bool { return true } -fileprivate func unmarkBannerVersion(mxSession: MXSession?) -> Bool { - guard let mxSession = mxSession else { - return false - } - +fileprivate func unmarkBannerVersion(mxSession: MXSession?) -> Bool { // NSString *version = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"]; if let version = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String { - _ = FeatureBannerVisibilityService(mxSession: mxSession).markAsUnread(version: version) + FeatureBannerVisibilityService().markAsUnread(version: version) } return true diff --git a/bwi/FeatureBanner/FeatureBannerView.swift b/bwi/FeatureBanner/FeatureBannerView.swift index c073cb2f1..296802e0a 100644 --- a/bwi/FeatureBanner/FeatureBannerView.swift +++ b/bwi/FeatureBanner/FeatureBannerView.swift @@ -90,8 +90,10 @@ protocol FeatureBannerDelegate { } func didPressShowDetails() { - hideTopBanner() - // Show Details + let migrationInfoView = MigrationInfoView().interactiveDismissDisabled(true) + let hostingViewController = UIHostingController(rootView: migrationInfoView) + hostingViewController.modalPresentationStyle = .popover + hostingController.parent?.present(hostingViewController, animated: true, completion: nil) } @objc func closeModal() { @@ -169,7 +171,7 @@ struct FeatureBannerView: View { .font(.system(size: 18)) .bold() .padding(10) - .foregroundColor(Color(ThemeService.shared().theme.tintColor)) + .foregroundColor(ThemeService.shared().isCurrentThemeDark() ? Color(ThemeService.shared().theme.tintColor) : Color.black) Spacer() } } diff --git a/bwi/FeatureBanner/MigrationInfoView.swift b/bwi/FeatureBanner/MigrationInfoView.swift new file mode 100644 index 000000000..c505c644a --- /dev/null +++ b/bwi/FeatureBanner/MigrationInfoView.swift @@ -0,0 +1,243 @@ +// +/* + * Copyright (c) 2025 BWI GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import SwiftUI + +// MARK: Migraion Info View +struct MigrationInfoView: View { + @Environment(\.dismiss) var dismissView + @State private var selectedTab = 1 + + var body: some View { + TabView(selection: $selectedTab) { + MigrationInfoViewOne() + .tag(1) + MigrationInfoViewTwo() + .tag(2) + } + .tabViewStyle(.page) + .indexViewStyle(.page(backgroundDisplayMode: .always)) + //.background(Color.white.ignoresSafeArea()) + .onAppear() { + FeatureBannerVisibilityService().markAsRead() + NotificationCenter.default.post(name: .bwiMarkTopBannerAsRead, object: self, userInfo: ["type" : "feature_banner"]) + } + .overlay { + VStack() { + HStack() { + Spacer() + Button { + dismissView() + } label: { + Image(Asset.Images.closeButton.name) + .renderingMode(.template) + .resizable() + .frame(width: 36, height: 36) + .foregroundColor(Color(ThemeService.shared().theme.tintColor)) + .padding(20) + } + } + Spacer() + HStack() { + Button { + withAnimation { + selectedTab = 1 + } + } label: { + Image(systemName: "arrow.left") + .resizable() + .frame(width: 24, height: 24) + .foregroundStyle(Color(ThemeService.shared().theme.tintColor)) + } + .padding(EdgeInsets(top: 20, leading: 30, bottom: 10, trailing: 30)) + .opacity(selectedTab == 1 ? 0 : 1) + + Spacer() + + Button { + withAnimation { + selectedTab = 2 + } + } label: { + Image(systemName: "arrow.right") + .resizable() + .frame(width: 24, height: 24) + .foregroundStyle(Color(ThemeService.shared().theme.tintColor)) + } + .padding(EdgeInsets(top: 20, leading: 30, bottom: 10, trailing: 30)) + .opacity(selectedTab == 2 ? 0 : 1) + } + } + } + } +} + + +// MARK: Migraion Info View one +struct MigrationInfoViewOne: View { + var body: some View { + GeometryReader { geo in + ScrollView(.vertical) { + VStack(alignment: .center, spacing: 30) { + + Spacer() + + header + + infoText + + Spacer() + + } + .frame(width: geo.size.width, height: geo.size.height) + } + } + } + + var header: some View { + VStack(spacing: 50) { + Image("bumx_logo") + .resizable() + .frame(width: 150, height: 150) + .clipShape(.rect(cornerRadius: 26)) + .overlay( + RoundedRectangle(cornerRadius: 26) + .stroke(.gray, lineWidth: 0.4) + ) + Text(BWIL10n.bwiMobileDialogMMoreTitle) + .font(.title) + .bold() + } + } + + var infoText: some View { + VStack(alignment: .leading) { + Text(BWIL10n.bwiMobileDialogM1MoreText1) + + Text(BWIL10n.bwiMobileDialogM1MoreText2).bold() + + Text(BWIL10n.bwiMobileDialogM1MoreText3) + VStack(alignment: .leading) { + HStack(alignment: .top) { + Text("•") + Text(BWIL10n.bwiMobileDialogM1MoreTextBullet1) + } + HStack(alignment: .top) { + Text("•") + Text(BWIL10n.bwiMobileDialogM1MoreTextBullet2) + } + HStack(alignment: .top) { + Text("•") + Text(BWIL10n.bwiMobileDialogM1MoreTextBullet3) + } + HStack(alignment: .top) { + Text("•") + Text(BWIL10n.bwiMobileDialogM1MoreTextBullet4) + } + } + .multilineTextAlignment(.leading) + .padding(.leading, 16) + } + .lineLimit(nil) + .fixedSize(horizontal: false, vertical: true) + .padding(EdgeInsets(top: 0, leading: 30, bottom: 0, trailing: 30)) + } +} + + +// MARK: Migraion Info View two +struct MigrationInfoViewTwo: View { + + var body: some View { + GeometryReader { geo in + ScrollView(.vertical) { + VStack(alignment: .center, spacing: 30) { + + Spacer() + + header + + instructionsList + + Spacer() + + downloadNewAppButton + } + .frame(width: geo.size.width, height: geo.size.height) + } + } + } + + var header: some View { + VStack(spacing: 50) { + Image("bumx_logo") + .resizable() + .frame(width: 150, height: 150) + .clipShape(.rect(cornerRadius: 26)) + .overlay( + RoundedRectangle(cornerRadius: 26) + .stroke(.gray, lineWidth: 0.4) + ) + Text(BWIL10n.bwiMobileDialogMMore2Title) + .font(.title) + .bold() + } + } + + var instructionsList: some View { + VStack(alignment: .leading, spacing: 10) { + HStack() { + Text("1.") + Button(action: { + + }, label: { + Text(BWIL10n.bwiMobileDialogMMore2Text1) + .underline() + Image(systemName: "square.on.square") + .resizable() + .frame(width: 15, height: 15) + }) + } + HStack() { + Text("2.") + Text(BWIL10n.bwiMobileDialogMMore2Text2) + } + HStack() { + Text("3.") + Text(BWIL10n.bwiMobileDialogMMore2Text3) + } + } + .lineLimit(nil) + .fixedSize(horizontal: false, vertical: true) + } + + var downloadNewAppButton: some View { + Button(action: { + guard let bumxAppStoreURL = URL(string: "itms-apps://itunes.apple.com/app/id6738500048") else { return } + guard UIApplication.shared.canOpenURL(bumxAppStoreURL) else { return } + UIApplication.shared.open(bumxAppStoreURL, options: [:], completionHandler: nil) + }, label: { + Text(BWIL10n.bwiMobileDialogMMoreButton) + .foregroundColor(.white) + .padding(.vertical, 10) + .padding(.horizontal, 50) + .background(Color(ThemeService.shared().theme.tintColor)) + .clipShape(RoundedRectangle(cornerRadius: 10)) + .lineLimit(nil) + .fixedSize(horizontal: false, vertical: true) + }) + .padding(.bottom, 100) + } +} diff --git a/bwi/TopBanner/FeatureBannerViewController.swift b/bwi/TopBanner/FeatureBannerViewController.swift index 621453777..f24d62a90 100644 --- a/bwi/TopBanner/FeatureBannerViewController.swift +++ b/bwi/TopBanner/FeatureBannerViewController.swift @@ -43,7 +43,7 @@ import UIKit override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) - FeatureBannerVisibilityService(mxSession: self.session).markAsRead() + FeatureBannerVisibilityService().markAsRead() NotificationCenter.default.post(name: .bwiMarkTopBannerAsRead, object: self, userInfo: ["type" : "feature_banner"]) } diff --git a/bwi/TopBanner/FeatureBannerVisibilityService.swift b/bwi/TopBanner/FeatureBannerVisibilityService.swift index 838f23e33..d840af96c 100644 --- a/bwi/TopBanner/FeatureBannerVisibilityService.swift +++ b/bwi/TopBanner/FeatureBannerVisibilityService.swift @@ -29,12 +29,6 @@ import Foundation static let featureVisibility = "de.bwi.should_show_ios_release_notes" } - private let session:MXSession - - init(mxSession: MXSession) { - self.session = mxSession - } - func markAsRead() { BWIBuildSettings.shared.didShowBuMXMigrationInfoLevel = BWIBuildSettings.shared.BuMXMigrationInfoLevel } From 4d9a8a4d85cacd82f61c098966c24ef89a25a50e Mon Sep 17 00:00:00 2001 From: Jan Niklas Grabowski Date: Thu, 18 Sep 2025 18:10:53 +0200 Subject: [PATCH 3/7] feat: layout changes and copy to clipboard (MESSENGER-7564) --- .../Bild_Dialog_1.svg | 14 ++ .../migration_detail_1.imageset/Contents.json | 15 ++ Riot/Assets/de.lproj/Bwi.strings | 2 + Riot/Assets/en.lproj/Bwi.strings | 3 + Riot/Generated/BWIStrings.swift | 4 + Riot/Generated/Images.swift | 1 + .../AllChats/AllChatsViewController.swift | 5 + bwi/FeatureBanner/FeatureBannerView.swift | 17 +- bwi/FeatureBanner/MigrationInfoView.swift | 195 +++++++++++------- 9 files changed, 182 insertions(+), 74 deletions(-) create mode 100644 Riot/Assets/Images.xcassets/BWI/migration_detail_1.imageset/Bild_Dialog_1.svg create mode 100644 Riot/Assets/Images.xcassets/BWI/migration_detail_1.imageset/Contents.json diff --git a/Riot/Assets/Images.xcassets/BWI/migration_detail_1.imageset/Bild_Dialog_1.svg b/Riot/Assets/Images.xcassets/BWI/migration_detail_1.imageset/Bild_Dialog_1.svg new file mode 100644 index 000000000..bbd65a39a --- /dev/null +++ b/Riot/Assets/Images.xcassets/BWI/migration_detail_1.imageset/Bild_Dialog_1.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/Riot/Assets/Images.xcassets/BWI/migration_detail_1.imageset/Contents.json b/Riot/Assets/Images.xcassets/BWI/migration_detail_1.imageset/Contents.json new file mode 100644 index 000000000..27957b3d9 --- /dev/null +++ b/Riot/Assets/Images.xcassets/BWI/migration_detail_1.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "Bild_Dialog_1.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/Riot/Assets/de.lproj/Bwi.strings b/Riot/Assets/de.lproj/Bwi.strings index 55977f55e..2733bd82e 100644 --- a/Riot/Assets/de.lproj/Bwi.strings +++ b/Riot/Assets/de.lproj/Bwi.strings @@ -773,3 +773,5 @@ "bwi_mobile_dialog_m_more2_text_3" = "Mit deinen Anmeldedaten einloggen"; "bwi_mobile_dialog_m_more_button" = "Neue App herunterladen"; "bwi_mobile_dialog_m_more_success" = "Benutzername kopiert"; + +"bwi_a11y_close_button" = "Ansicht schließen"; diff --git a/Riot/Assets/en.lproj/Bwi.strings b/Riot/Assets/en.lproj/Bwi.strings index 5f84537b4..0d1e7d598 100644 --- a/Riot/Assets/en.lproj/Bwi.strings +++ b/Riot/Assets/en.lproj/Bwi.strings @@ -685,3 +685,6 @@ "bwi_mobile_dialog_m_more2_text_3" = "Log in with your credentials"; "bwi_mobile_dialog_m_more_button" = "Download new app"; "bwi_mobile_dialog_m_more_success" = "user name copied"; + +"bwi_a11y_close_button" = "Close window"; + diff --git a/Riot/Generated/BWIStrings.swift b/Riot/Generated/BWIStrings.swift index 98322363f..94113045d 100644 --- a/Riot/Generated/BWIStrings.swift +++ b/Riot/Generated/BWIStrings.swift @@ -131,6 +131,10 @@ public class BWIL10n: NSObject { public static var bumAutheticationTitle: String { return BWIL10n.tr("Bwi", "bum_authetication_title") } + /// Ansicht schließen + public static var bwiA11yCloseButton: String { + return BWIL10n.tr("Bwi", "bwi_a11y_close_button") + } /// Barrierefreiheitserklärung public static var bwiAccessibilityDeclarationButtonTitle: String { return BWIL10n.tr("Bwi", "bwi_accessibility_declaration_button_title") diff --git a/Riot/Generated/Images.swift b/Riot/Generated/Images.swift index 6c84218e5..c3989cea8 100644 --- a/Riot/Generated/Images.swift +++ b/Riot/Generated/Images.swift @@ -63,6 +63,7 @@ internal class Asset: NSObject { internal static let introduceFederation2Light = ImageAsset(name: "introduce_federation_2_light") internal static let introduceFederation3 = ImageAsset(name: "introduce_federation_3") internal static let mediaFileUnavailable = ImageAsset(name: "media_file_unavailable") + internal static let migrationDetail1 = ImageAsset(name: "migration_detail_1") internal static let newFeatures = ImageAsset(name: "new_features") internal static let qr = ImageAsset(name: "qr") internal static let qrcodeViewfinder = ImageAsset(name: "qrcode_viewfinder") diff --git a/Riot/Modules/Home/AllChats/AllChatsViewController.swift b/Riot/Modules/Home/AllChats/AllChatsViewController.swift index 8490e9fd5..3e4b3fcc7 100644 --- a/Riot/Modules/Home/AllChats/AllChatsViewController.swift +++ b/Riot/Modules/Home/AllChats/AllChatsViewController.swift @@ -621,10 +621,15 @@ class AllChatsViewController: HomeViewController { } // bwi: feature banner cell if sectionType == .featureBanner { + var username = "" + if let mainSession = self.mainSession { + username = mainSession.myUser.displayname + } guard let cell = tableView.dequeueReusableCell(withIdentifier: "featureBanner", for: indexPath) as? FeatureBannerViewCell else { return UITableViewCell() } cell.selectionStyle = .none + cell.setUsername(username: username) cell.setupView(parent: self, rootView: FeatureBannerView(delegate: cell)) featureBannerViewHeight = cell.calculateHeight() return cell diff --git a/bwi/FeatureBanner/FeatureBannerView.swift b/bwi/FeatureBanner/FeatureBannerView.swift index 296802e0a..135779997 100644 --- a/bwi/FeatureBanner/FeatureBannerView.swift +++ b/bwi/FeatureBanner/FeatureBannerView.swift @@ -28,6 +28,7 @@ protocol FeatureBannerDelegate { private var parentViewController: UIViewController? private let hostingController = UIHostingController(rootView: nil) private var webViewController: WebViewViewController? + private var username: String = "" override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) @@ -90,20 +91,27 @@ protocol FeatureBannerDelegate { } func didPressShowDetails() { - let migrationInfoView = MigrationInfoView().interactiveDismissDisabled(true) + let migrationInfoView = MigrationInfoView(username: username).interactiveDismissDisabled(true) let hostingViewController = UIHostingController(rootView: migrationInfoView) - hostingViewController.modalPresentationStyle = .popover + if hostingViewController.popoverPresentationController != nil { + hostingViewController.modalPresentationStyle = .popover + } hostingController.parent?.present(hostingViewController, animated: true, completion: nil) } @objc func closeModal() { webViewController?.dismiss(animated: true) } + + func setUsername(username: String) { + self.username = username + } } struct FeatureBannerView: View { var delegate: FeatureBannerDelegate? + let darkmodeBackground = UIColor(rgb:0x2394A7) var body: some View { VStack(alignment: .center) { @@ -116,7 +124,7 @@ struct FeatureBannerView: View { } } .padding(.horizontal, 12) - .background(Color(ThemeService.shared().theme.tintColor)) + .background(ThemeService.shared().isCurrentThemeDark() ? Color(uiColor: darkmodeBackground) : Color(ThemeService.shared().theme.tintColor)) .cornerRadius(12) .padding(16) } @@ -126,6 +134,7 @@ struct FeatureBannerView: View { Image(Asset.Images.newFeatures.name) // .renderingMode(.template) .foregroundColor(Color(ThemeService.shared().theme.backgroundColor)) + .accessibilityHidden(true) Text(BWIL10n.bwiMobileDialogMBannerTitle) .font(.system(size: 20).bold()) .foregroundColor(Color(ThemeService.shared().theme.backgroundColor)) @@ -171,7 +180,7 @@ struct FeatureBannerView: View { .font(.system(size: 18)) .bold() .padding(10) - .foregroundColor(ThemeService.shared().isCurrentThemeDark() ? Color(ThemeService.shared().theme.tintColor) : Color.black) + .foregroundColor(ThemeService.shared().isCurrentThemeDark() ? Color(uiColor: darkmodeBackground) : Color.black) Spacer() } } diff --git a/bwi/FeatureBanner/MigrationInfoView.swift b/bwi/FeatureBanner/MigrationInfoView.swift index c505c644a..7ca48255b 100644 --- a/bwi/FeatureBanner/MigrationInfoView.swift +++ b/bwi/FeatureBanner/MigrationInfoView.swift @@ -22,16 +22,18 @@ struct MigrationInfoView: View { @Environment(\.dismiss) var dismissView @State private var selectedTab = 1 + let username: String + var body: some View { TabView(selection: $selectedTab) { MigrationInfoViewOne() .tag(1) - MigrationInfoViewTwo() + MigrationInfoViewTwo(username: username) .tag(2) } .tabViewStyle(.page) .indexViewStyle(.page(backgroundDisplayMode: .always)) - //.background(Color.white.ignoresSafeArea()) + .background(Color.white.ignoresSafeArea()) .onAppear() { FeatureBannerVisibilityService().markAsRead() NotificationCenter.default.post(name: .bwiMarkTopBannerAsRead, object: self, userInfo: ["type" : "feature_banner"]) @@ -50,6 +52,7 @@ struct MigrationInfoView: View { .foregroundColor(Color(ThemeService.shared().theme.tintColor)) .padding(20) } + .accessibilityLabel(BWIL10n.bwiA11yCloseButton) } Spacer() HStack() { @@ -62,8 +65,8 @@ struct MigrationInfoView: View { .resizable() .frame(width: 24, height: 24) .foregroundStyle(Color(ThemeService.shared().theme.tintColor)) + .padding(EdgeInsets(top: 20, leading: 30, bottom: 10, trailing: 30)) } - .padding(EdgeInsets(top: 20, leading: 30, bottom: 10, trailing: 30)) .opacity(selectedTab == 1 ? 0 : 1) Spacer() @@ -77,8 +80,8 @@ struct MigrationInfoView: View { .resizable() .frame(width: 24, height: 24) .foregroundStyle(Color(ThemeService.shared().theme.tintColor)) + .padding(EdgeInsets(top: 20, leading: 30, bottom: 10, trailing: 30)) } - .padding(EdgeInsets(top: 20, leading: 30, bottom: 10, trailing: 30)) .opacity(selectedTab == 2 ? 0 : 1) } } @@ -92,43 +95,44 @@ struct MigrationInfoViewOne: View { var body: some View { GeometryReader { geo in ScrollView(.vertical) { - VStack(alignment: .center, spacing: 30) { - - Spacer() - - header + VStack(alignment: .center, spacing: 20) { + Image("migration_detail_1") + .resizable() + .aspectRatio(contentMode: .fit) + .frame(height: geo.size.height * 0.35, alignment: .bottom) + .frame(maxWidth: geo.size.width - 60) + .padding(EdgeInsets(top: 56, leading: 30, bottom: 0, trailing: 30)) + .accessibilityHidden(true) infoText + .frame(width: geo.size.width) Spacer() } - .frame(width: geo.size.width, height: geo.size.height) + .frame(minHeight: geo.size.height) + .frame(width: geo.size.width) } - } - } - - var header: some View { - VStack(spacing: 50) { - Image("bumx_logo") - .resizable() - .frame(width: 150, height: 150) - .clipShape(.rect(cornerRadius: 26)) - .overlay( - RoundedRectangle(cornerRadius: 26) - .stroke(.gray, lineWidth: 0.4) - ) - Text(BWIL10n.bwiMobileDialogMMoreTitle) - .font(.title) - .bold() + .frame(width: geo.size.width, height: geo.size.height) } } var infoText: some View { VStack(alignment: .leading) { - Text(BWIL10n.bwiMobileDialogM1MoreText1) + - Text(BWIL10n.bwiMobileDialogM1MoreText2).bold() + + Text(BWIL10n.bwiMobileDialogMMoreTitle) + .font(.title) + .bold() + .foregroundColor(Color.black) + .lineLimit(nil) + .fixedSize(horizontal: false, vertical: true) + .padding(.bottom, 20) + + Text(BWIL10n.bwiMobileDialogM1MoreText1) + .foregroundColor(Color.black) + + Text(BWIL10n.bwiMobileDialogM1MoreText2).bold() + .foregroundColor(Color.black) + Text(BWIL10n.bwiMobileDialogM1MoreText3) + .foregroundColor(Color.black) VStack(alignment: .leading) { HStack(alignment: .top) { Text("•") @@ -148,79 +152,114 @@ struct MigrationInfoViewOne: View { } } .multilineTextAlignment(.leading) + .foregroundColor(Color.black) .padding(.leading, 16) } .lineLimit(nil) .fixedSize(horizontal: false, vertical: true) - .padding(EdgeInsets(top: 0, leading: 30, bottom: 0, trailing: 30)) + .padding(EdgeInsets(top: 10, leading: 30, bottom: 0, trailing: 30)) + .accessibilityElement(children: .ignore) + .accessibilityLabel( + BWIL10n.bwiMobileDialogMMoreTitle + ". " + + BWIL10n.bwiMobileDialogM1MoreText1 + + BWIL10n.bwiMobileDialogM1MoreText2 + + BWIL10n.bwiMobileDialogM1MoreText3 + ", " + + BWIL10n.bwiMobileDialogM1MoreTextBullet1 + ", " + + BWIL10n.bwiMobileDialogM1MoreTextBullet2 + ", " + + BWIL10n.bwiMobileDialogM1MoreTextBullet3 + ", " + + BWIL10n.bwiMobileDialogM1MoreTextBullet4) } } // MARK: Migraion Info View two struct MigrationInfoViewTwo: View { + let username: String + @State var showSuccessToast: Bool = false var body: some View { GeometryReader { geo in ScrollView(.vertical) { - VStack(alignment: .center, spacing: 30) { - - Spacer() - - header + VStack(alignment: .center, spacing: 20) { + VStack() { + Image("bumx_logo") + .resizable() + .frame(width: 200, height: 200) + .clipShape(.rect(cornerRadius: 36)) + .overlay( + RoundedRectangle(cornerRadius: 36) + .stroke(.gray, lineWidth: 0.4) + ) + .accessibilityHidden(true) + } + .frame(height: geo.size.height * 0.35, alignment: .center) + .padding(EdgeInsets(top: 56, leading: 30, bottom: 10, trailing: 30)) instructionsList + successToast + Spacer() downloadNewAppButton } - .frame(width: geo.size.width, height: geo.size.height) + .frame(minHeight: geo.size.height) + .frame(width: geo.size.width) + } - } - } - - var header: some View { - VStack(spacing: 50) { - Image("bumx_logo") - .resizable() - .frame(width: 150, height: 150) - .clipShape(.rect(cornerRadius: 26)) - .overlay( - RoundedRectangle(cornerRadius: 26) - .stroke(.gray, lineWidth: 0.4) - ) - Text(BWIL10n.bwiMobileDialogMMore2Title) - .font(.title) - .bold() + .frame(width: geo.size.width, height: geo.size.height) } } var instructionsList: some View { - VStack(alignment: .leading, spacing: 10) { - HStack() { - Text("1.") - Button(action: { - - }, label: { - Text(BWIL10n.bwiMobileDialogMMore2Text1) - .underline() - Image(systemName: "square.on.square") - .resizable() - .frame(width: 15, height: 15) - }) + VStack() { + VStack(alignment: .center) { + Text(BWIL10n.bwiMobileDialogMMore2Title) + .font(.title) + .bold() + .foregroundColor(Color.black) + .padding(.bottom, 20) } - HStack() { - Text("2.") - Text(BWIL10n.bwiMobileDialogMMore2Text2) - } - HStack() { - Text("3.") - Text(BWIL10n.bwiMobileDialogMMore2Text3) + VStack(alignment: .leading) { + HStack() { + Text("1.") + Button(action: { + UIPasteboard.general.string = username + guard !showSuccessToast else { return } + withAnimation { + showSuccessToast = true + } + DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) { + withAnimation { + showSuccessToast = false + } + } + }, label: { + Text(BWIL10n.bwiMobileDialogMMore2Text1) + .underline() + Image(systemName: "square.on.square") + .resizable() + .frame(width: 15, height: 15) + }) + } + .foregroundColor(Color.black) + + HStack() { + Text("2.") + Text(BWIL10n.bwiMobileDialogMMore2Text2) + } + .padding(.bottom, 1) + .foregroundColor(Color.black) + HStack() { + Text("3.") + Text(BWIL10n.bwiMobileDialogMMore2Text3) + } + .foregroundColor(Color.black) } } .lineLimit(nil) .fixedSize(horizontal: false, vertical: true) + .padding(EdgeInsets(top: 10, leading: 30, bottom: 0, trailing: 30)) } var downloadNewAppButton: some View { @@ -240,4 +279,20 @@ struct MigrationInfoViewTwo: View { }) .padding(.bottom, 100) } + + var successToast: some View { + HStack() { + Image(systemName: "checkmark") + .resizable() + .frame(width: 15, height: 15) + .accessibilityHidden(true) + Text(BWIL10n.bwiMobileDialogMMoreSuccess) + } + .foregroundColor(.white) + .padding(10) + .background(Color.black) + .clipShape(RoundedRectangle(cornerRadius: 10)) + .opacity(showSuccessToast ? 1 : 0) + .transition(.opacity) + } } From 90055fd63a52f2507c7f988c867a7237802d7674 Mon Sep 17 00:00:00 2001 From: Jan Niklas Grabowski Date: Thu, 18 Sep 2025 18:27:36 +0200 Subject: [PATCH 4/7] feat: color changes (MESSENGER-7564) --- bwi/FeatureBanner/MigrationInfoView.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bwi/FeatureBanner/MigrationInfoView.swift b/bwi/FeatureBanner/MigrationInfoView.swift index 7ca48255b..f8e60080d 100644 --- a/bwi/FeatureBanner/MigrationInfoView.swift +++ b/bwi/FeatureBanner/MigrationInfoView.swift @@ -49,7 +49,7 @@ struct MigrationInfoView: View { .renderingMode(.template) .resizable() .frame(width: 36, height: 36) - .foregroundColor(Color(ThemeService.shared().theme.tintColor)) + .foregroundColor(Color(ThemeService.shared().theme.colors.tertiaryContent)) .padding(20) } .accessibilityLabel(BWIL10n.bwiA11yCloseButton) @@ -64,7 +64,7 @@ struct MigrationInfoView: View { Image(systemName: "arrow.left") .resizable() .frame(width: 24, height: 24) - .foregroundStyle(Color(ThemeService.shared().theme.tintColor)) + .foregroundStyle(Color(ThemeService.shared().theme.colors.tertiaryContent)) .padding(EdgeInsets(top: 20, leading: 30, bottom: 10, trailing: 30)) } .opacity(selectedTab == 1 ? 0 : 1) @@ -79,7 +79,7 @@ struct MigrationInfoView: View { Image(systemName: "arrow.right") .resizable() .frame(width: 24, height: 24) - .foregroundStyle(Color(ThemeService.shared().theme.tintColor)) + .foregroundStyle(Color(ThemeService.shared().theme.colors.tertiaryContent)) .padding(EdgeInsets(top: 20, leading: 30, bottom: 10, trailing: 30)) } .opacity(selectedTab == 2 ? 0 : 1) From 4987b3c7d48d258039acba967a86e543fc13eb76 Mon Sep 17 00:00:00 2001 From: Jan Niklas Grabowski Date: Mon, 22 Sep 2025 13:37:51 +0200 Subject: [PATCH 5/7] feat: layout changes (MESSENGER-7564) --- .../Apple iPhone.svg | 9 +++++++++ .../Bild_Dialog_1.svg | 14 ------------- .../migration_detail_1.imageset/Contents.json | 2 +- bwi/FeatureBanner/FeatureBannerView.swift | 2 +- bwi/FeatureBanner/MigrationInfoView.swift | 20 ++++++------------- 5 files changed, 17 insertions(+), 30 deletions(-) create mode 100644 Riot/Assets/Images.xcassets/BWI/migration_detail_1.imageset/Apple iPhone.svg delete mode 100644 Riot/Assets/Images.xcassets/BWI/migration_detail_1.imageset/Bild_Dialog_1.svg diff --git a/Riot/Assets/Images.xcassets/BWI/migration_detail_1.imageset/Apple iPhone.svg b/Riot/Assets/Images.xcassets/BWI/migration_detail_1.imageset/Apple iPhone.svg new file mode 100644 index 000000000..a46131564 --- /dev/null +++ b/Riot/Assets/Images.xcassets/BWI/migration_detail_1.imageset/Apple iPhone.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/Riot/Assets/Images.xcassets/BWI/migration_detail_1.imageset/Bild_Dialog_1.svg b/Riot/Assets/Images.xcassets/BWI/migration_detail_1.imageset/Bild_Dialog_1.svg deleted file mode 100644 index bbd65a39a..000000000 --- a/Riot/Assets/Images.xcassets/BWI/migration_detail_1.imageset/Bild_Dialog_1.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/Riot/Assets/Images.xcassets/BWI/migration_detail_1.imageset/Contents.json b/Riot/Assets/Images.xcassets/BWI/migration_detail_1.imageset/Contents.json index 27957b3d9..33469fecf 100644 --- a/Riot/Assets/Images.xcassets/BWI/migration_detail_1.imageset/Contents.json +++ b/Riot/Assets/Images.xcassets/BWI/migration_detail_1.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "Bild_Dialog_1.svg", + "filename" : "Apple iPhone.svg", "idiom" : "universal" } ], diff --git a/bwi/FeatureBanner/FeatureBannerView.swift b/bwi/FeatureBanner/FeatureBannerView.swift index 135779997..b2ebb0c20 100644 --- a/bwi/FeatureBanner/FeatureBannerView.swift +++ b/bwi/FeatureBanner/FeatureBannerView.swift @@ -197,7 +197,7 @@ struct FeatureBannerView: View { HStack() { Spacer() Text(BWIL10n.bwiMobileDialogMBannerButton1) - .font(.system(size: 15)) + .font(.system(size: 18)) .padding(10) .foregroundColor(Color(ThemeService.shared().theme.backgroundColor)) Spacer() diff --git a/bwi/FeatureBanner/MigrationInfoView.swift b/bwi/FeatureBanner/MigrationInfoView.swift index f8e60080d..1d682207c 100644 --- a/bwi/FeatureBanner/MigrationInfoView.swift +++ b/bwi/FeatureBanner/MigrationInfoView.swift @@ -33,7 +33,6 @@ struct MigrationInfoView: View { } .tabViewStyle(.page) .indexViewStyle(.page(backgroundDisplayMode: .always)) - .background(Color.white.ignoresSafeArea()) .onAppear() { FeatureBannerVisibilityService().markAsRead() NotificationCenter.default.post(name: .bwiMarkTopBannerAsRead, object: self, userInfo: ["type" : "feature_banner"]) @@ -122,17 +121,13 @@ struct MigrationInfoViewOne: View { Text(BWIL10n.bwiMobileDialogMMoreTitle) .font(.title) .bold() - .foregroundColor(Color.black) .lineLimit(nil) .fixedSize(horizontal: false, vertical: true) .padding(.bottom, 20) - Text(BWIL10n.bwiMobileDialogM1MoreText1) - .foregroundColor(Color.black) + - Text(BWIL10n.bwiMobileDialogM1MoreText2).bold() - .foregroundColor(Color.black) + + Text(BWIL10n.bwiMobileDialogM1MoreText1) + + Text(BWIL10n.bwiMobileDialogM1MoreText2).bold() + Text(BWIL10n.bwiMobileDialogM1MoreText3) - .foregroundColor(Color.black) VStack(alignment: .leading) { HStack(alignment: .top) { Text("•") @@ -152,7 +147,6 @@ struct MigrationInfoViewOne: View { } } .multilineTextAlignment(.leading) - .foregroundColor(Color.black) .padding(.leading, 16) } .lineLimit(nil) @@ -217,7 +211,6 @@ struct MigrationInfoViewTwo: View { Text(BWIL10n.bwiMobileDialogMMore2Title) .font(.title) .bold() - .foregroundColor(Color.black) .padding(.bottom, 20) } VStack(alignment: .leading) { @@ -237,24 +230,23 @@ struct MigrationInfoViewTwo: View { }, label: { Text(BWIL10n.bwiMobileDialogMMore2Text1) .underline() + .foregroundColor(Color(ThemeService.shared().theme.textPrimaryColor)) Image(systemName: "square.on.square") .resizable() + .foregroundColor(Color(ThemeService.shared().theme.textPrimaryColor)) .frame(width: 15, height: 15) }) } - .foregroundColor(Color.black) HStack() { Text("2.") Text(BWIL10n.bwiMobileDialogMMore2Text2) } .padding(.bottom, 1) - .foregroundColor(Color.black) HStack() { Text("3.") Text(BWIL10n.bwiMobileDialogMMore2Text3) } - .foregroundColor(Color.black) } } .lineLimit(nil) @@ -288,9 +280,9 @@ struct MigrationInfoViewTwo: View { .accessibilityHidden(true) Text(BWIL10n.bwiMobileDialogMMoreSuccess) } - .foregroundColor(.white) + .foregroundColor(ThemeService.shared().isCurrentThemeDark() ? .black : .white) .padding(10) - .background(Color.black) + .background(ThemeService.shared().isCurrentThemeDark() ? .white : .black) .clipShape(RoundedRectangle(cornerRadius: 10)) .opacity(showSuccessToast ? 1 : 0) .transition(.opacity) From 84d4da2e42bb83eda3e5932fe8aac087a6cd96db Mon Sep 17 00:00:00 2001 From: Jan Niklas Grabowski Date: Mon, 22 Sep 2025 14:40:35 +0200 Subject: [PATCH 6/7] feat: layout changes (MESSENGER-7564) --- bwi/FeatureBanner/FeatureBannerView.swift | 2 +- bwi/FeatureBanner/MigrationInfoView.swift | 55 ++++++++++++++++------- 2 files changed, 40 insertions(+), 17 deletions(-) diff --git a/bwi/FeatureBanner/FeatureBannerView.swift b/bwi/FeatureBanner/FeatureBannerView.swift index b2ebb0c20..0a259070e 100644 --- a/bwi/FeatureBanner/FeatureBannerView.swift +++ b/bwi/FeatureBanner/FeatureBannerView.swift @@ -91,7 +91,7 @@ protocol FeatureBannerDelegate { } func didPressShowDetails() { - let migrationInfoView = MigrationInfoView(username: username).interactiveDismissDisabled(true) + let migrationInfoView = MigrationInfoView(username: username).environmentObject(BWIThemeService.shared).interactiveDismissDisabled(true) let hostingViewController = UIHostingController(rootView: migrationInfoView) if hostingViewController.popoverPresentationController != nil { hostingViewController.modalPresentationStyle = .popover diff --git a/bwi/FeatureBanner/MigrationInfoView.swift b/bwi/FeatureBanner/MigrationInfoView.swift index 1d682207c..62e6e2b0b 100644 --- a/bwi/FeatureBanner/MigrationInfoView.swift +++ b/bwi/FeatureBanner/MigrationInfoView.swift @@ -21,6 +21,8 @@ import SwiftUI struct MigrationInfoView: View { @Environment(\.dismiss) var dismissView @State private var selectedTab = 1 + @EnvironmentObject var themeService: BWIThemeService + @State var redrawKey = UUID() let username: String @@ -31,8 +33,13 @@ struct MigrationInfoView: View { MigrationInfoViewTwo(username: username) .tag(2) } + .id(redrawKey) .tabViewStyle(.page) - .indexViewStyle(.page(backgroundDisplayMode: .always)) + .tabViewStyle(.page(indexDisplayMode: .always)) + .background { + Color(themeService.theme.backgroundColor) + .edgesIgnoringSafeArea(/*@START_MENU_TOKEN@*/.all/*@END_MENU_TOKEN@*/) + } .onAppear() { FeatureBannerVisibilityService().markAsRead() NotificationCenter.default.post(name: .bwiMarkTopBannerAsRead, object: self, userInfo: ["type" : "feature_banner"]) @@ -48,7 +55,7 @@ struct MigrationInfoView: View { .renderingMode(.template) .resizable() .frame(width: 36, height: 36) - .foregroundColor(Color(ThemeService.shared().theme.colors.tertiaryContent)) + .foregroundColor(Color(themeService.theme.colors.tertiaryContent)) .padding(20) } .accessibilityLabel(BWIL10n.bwiA11yCloseButton) @@ -63,7 +70,7 @@ struct MigrationInfoView: View { Image(systemName: "arrow.left") .resizable() .frame(width: 24, height: 24) - .foregroundStyle(Color(ThemeService.shared().theme.colors.tertiaryContent)) + .foregroundStyle(Color(themeService.theme.colors.tertiaryContent)) .padding(EdgeInsets(top: 20, leading: 30, bottom: 10, trailing: 30)) } .opacity(selectedTab == 1 ? 0 : 1) @@ -78,19 +85,37 @@ struct MigrationInfoView: View { Image(systemName: "arrow.right") .resizable() .frame(width: 24, height: 24) - .foregroundStyle(Color(ThemeService.shared().theme.colors.tertiaryContent)) + .foregroundStyle(Color(themeService.theme.colors.tertiaryContent)) .padding(EdgeInsets(top: 20, leading: 30, bottom: 10, trailing: 30)) } .opacity(selectedTab == 2 ? 0 : 1) } } } + .onAppear { + setupIndicatorColors() + } + .onChange(of: themeService.isCurrentThemeDark) { _ in + setupIndicatorColors() + redrawKey = UUID() + } + .onChange(of: themeService.isCurrentThemeDark) { _ in + setupIndicatorColors() + redrawKey = UUID() + } + } + + private func setupIndicatorColors() { + UIPageControl.appearance().currentPageIndicatorTintColor = themeService.theme.textPrimaryColor + UIPageControl.appearance().pageIndicatorTintColor = themeService.theme.textSecondaryColor } } // MARK: Migraion Info View one struct MigrationInfoViewOne: View { + @EnvironmentObject var themeService: BWIThemeService + var body: some View { GeometryReader { geo in ScrollView(.vertical) { @@ -170,6 +195,7 @@ struct MigrationInfoViewOne: View { struct MigrationInfoViewTwo: View { let username: String @State var showSuccessToast: Bool = false + @EnvironmentObject var themeService: BWIThemeService var body: some View { GeometryReader { geo in @@ -196,6 +222,9 @@ struct MigrationInfoViewTwo: View { Spacer() downloadNewAppButton + .frame(maxWidth: 350) + .frame(width: geo.size.width - 100) + .padding(.bottom, 100) } .frame(minHeight: geo.size.height) .frame(width: geo.size.width) @@ -230,10 +259,10 @@ struct MigrationInfoViewTwo: View { }, label: { Text(BWIL10n.bwiMobileDialogMMore2Text1) .underline() - .foregroundColor(Color(ThemeService.shared().theme.textPrimaryColor)) + .foregroundColor(Color(themeService.theme.textPrimaryColor)) Image(systemName: "square.on.square") .resizable() - .foregroundColor(Color(ThemeService.shared().theme.textPrimaryColor)) + .foregroundColor(Color(themeService.theme.textPrimaryColor)) .frame(width: 15, height: 15) }) } @@ -261,15 +290,9 @@ struct MigrationInfoViewTwo: View { UIApplication.shared.open(bumxAppStoreURL, options: [:], completionHandler: nil) }, label: { Text(BWIL10n.bwiMobileDialogMMoreButton) - .foregroundColor(.white) - .padding(.vertical, 10) - .padding(.horizontal, 50) - .background(Color(ThemeService.shared().theme.tintColor)) - .clipShape(RoundedRectangle(cornerRadius: 10)) - .lineLimit(nil) - .fixedSize(horizontal: false, vertical: true) + .foregroundColor(Color(ThemeService.shared().theme.backgroundColor)) }) - .padding(.bottom, 100) + .buttonStyle(PrimaryActionButtonStyle()) } var successToast: some View { @@ -280,9 +303,9 @@ struct MigrationInfoViewTwo: View { .accessibilityHidden(true) Text(BWIL10n.bwiMobileDialogMMoreSuccess) } - .foregroundColor(ThemeService.shared().isCurrentThemeDark() ? .black : .white) + .foregroundColor(themeService.isCurrentThemeDark ? .black : .white) .padding(10) - .background(ThemeService.shared().isCurrentThemeDark() ? .white : .black) + .background(themeService.isCurrentThemeDark ? .white : .black) .clipShape(RoundedRectangle(cornerRadius: 10)) .opacity(showSuccessToast ? 1 : 0) .transition(.opacity) From a27286d8d85e25c7223445c75629214073200905 Mon Sep 17 00:00:00 2001 From: Jan Niklas Grabowski Date: Mon, 22 Sep 2025 14:51:05 +0200 Subject: [PATCH 7/7] feat: change default setting (MESSENGER-7564) --- Config/BWIBuildSettings.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Config/BWIBuildSettings.swift b/Config/BWIBuildSettings.swift index 4ad67dacb..3b07bf08e 100644 --- a/Config/BWIBuildSettings.swift +++ b/Config/BWIBuildSettings.swift @@ -677,7 +677,7 @@ class BWIBuildSettings: NSObject { var isLabsFederationEnabled // Migration status from wellknown config - @UserDefault(key: UserDefaultsKeys.BuMXMigrationInfoLevelKey, defaultValue: 1, storage: RiotSettings.defaults) + @UserDefault(key: UserDefaultsKeys.BuMXMigrationInfoLevelKey, defaultValue: 0, storage: RiotSettings.defaults) var BuMXMigrationInfoLevel // Migration level already shown