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/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/Contents.json b/Riot/Assets/Images.xcassets/BWI/migration_detail_1.imageset/Contents.json new file mode 100644 index 000000000..33469fecf --- /dev/null +++ b/Riot/Assets/Images.xcassets/BWI/migration_detail_1.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "Apple iPhone.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} 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 000000000..7b4cd0a18 Binary files /dev/null and b/Riot/Assets/SharedImages.xcassets/bumx_logo.imageset/BuM_Appicon-next_iOS_1024.png differ 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 982420c10..e8e8df2e3 100644 --- a/Riot/Assets/de.lproj/Bwi.strings +++ b/Riot/Assets/de.lproj/Bwi.strings @@ -747,6 +747,35 @@ "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"; + +"bwi_a11y_close_button" = "Ansicht schließen"; + // MARK owner handling "room_member_power_level_owner_in" = "Inhaber von %@"; "room_member_power_level_short_owner" = "Inhaber"; diff --git a/Riot/Assets/en.lproj/Bwi.strings b/Riot/Assets/en.lproj/Bwi.strings index 29ecb4cf5..6ea85f761 100644 --- a/Riot/Assets/en.lproj/Bwi.strings +++ b/Riot/Assets/en.lproj/Bwi.strings @@ -660,6 +660,34 @@ "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"; + +"bwi_a11y_close_button" = "Close window"; + // MARK owner handling "room_member_power_level_owner_in" = "Owner in %@"; "room_member_power_level_short_owner" = "Owner"; diff --git a/Riot/Generated/BWIStrings.swift b/Riot/Generated/BWIStrings.swift index 8d7040374..5faff94c8 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") @@ -299,6 +303,82 @@ 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") + } + /// 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") + } + /// 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") + } + /// 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/Generated/Images.swift b/Riot/Generated/Images.swift index 375895ffe..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") @@ -411,6 +412,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 725965e62..1f6644d15 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) { + 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 [[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]; + 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/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/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 8362b2e11..0a259070e 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,62 +91,69 @@ 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() - }) + let migrationInfoView = MigrationInfoView(username: username).environmentObject(BWIThemeService.shared).interactiveDismissDisabled(true) + let hostingViewController = UIHostingController(rootView: migrationInfoView) + 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) { - closeButton header advertisementText - if !BWIL10n.bwiFeatureBannerShowMoreButton.isEmpty { + HStack(alignment: .center, spacing: 12) { + remindMeLaterButton showMoreButton - } else { - Spacer() - .frame(height: 25) } } - .background(Color(ThemeService.shared().theme.tintColor)) + .padding(.horizontal, 12) + .background(ThemeService.shared().isCurrentThemeDark() ? Color(uiColor: darkmodeBackground) : Color(ThemeService.shared().theme.tintColor)) .cornerRadius(12) .padding(16) } 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)) + .accessibilityHidden(true) + 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 +174,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(ThemeService.shared().isCurrentThemeDark() ? Color(uiColor: darkmodeBackground) : Color.black) + 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: 18)) + .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/FeatureBanner/MigrationInfoView.swift b/bwi/FeatureBanner/MigrationInfoView.swift new file mode 100644 index 000000000..62e6e2b0b --- /dev/null +++ b/bwi/FeatureBanner/MigrationInfoView.swift @@ -0,0 +1,313 @@ +// +/* + * 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 + @EnvironmentObject var themeService: BWIThemeService + @State var redrawKey = UUID() + + let username: String + + var body: some View { + TabView(selection: $selectedTab) { + MigrationInfoViewOne() + .tag(1) + MigrationInfoViewTwo(username: username) + .tag(2) + } + .id(redrawKey) + .tabViewStyle(.page) + .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"]) + } + .overlay { + VStack() { + HStack() { + Spacer() + Button { + dismissView() + } label: { + Image(Asset.Images.closeButton.name) + .renderingMode(.template) + .resizable() + .frame(width: 36, height: 36) + .foregroundColor(Color(themeService.theme.colors.tertiaryContent)) + .padding(20) + } + .accessibilityLabel(BWIL10n.bwiA11yCloseButton) + } + Spacer() + HStack() { + Button { + withAnimation { + selectedTab = 1 + } + } label: { + Image(systemName: "arrow.left") + .resizable() + .frame(width: 24, height: 24) + .foregroundStyle(Color(themeService.theme.colors.tertiaryContent)) + .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.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) { + 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(minHeight: geo.size.height) + .frame(width: geo.size.width) + } + .frame(width: geo.size.width, height: geo.size.height) + } + } + + var infoText: some View { + VStack(alignment: .leading) { + Text(BWIL10n.bwiMobileDialogMMoreTitle) + .font(.title) + .bold() + .lineLimit(nil) + .fixedSize(horizontal: false, vertical: true) + .padding(.bottom, 20) + + 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: 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 + @EnvironmentObject var themeService: BWIThemeService + + var body: some View { + GeometryReader { geo in + ScrollView(.vertical) { + 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(maxWidth: 350) + .frame(width: geo.size.width - 100) + .padding(.bottom, 100) + } + .frame(minHeight: geo.size.height) + .frame(width: geo.size.width) + + } + .frame(width: geo.size.width, height: geo.size.height) + } + } + + var instructionsList: some View { + VStack() { + VStack(alignment: .center) { + Text(BWIL10n.bwiMobileDialogMMore2Title) + .font(.title) + .bold() + .padding(.bottom, 20) + } + 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() + .foregroundColor(Color(themeService.theme.textPrimaryColor)) + Image(systemName: "square.on.square") + .resizable() + .foregroundColor(Color(themeService.theme.textPrimaryColor)) + .frame(width: 15, height: 15) + }) + } + + HStack() { + Text("2.") + Text(BWIL10n.bwiMobileDialogMMore2Text2) + } + .padding(.bottom, 1) + HStack() { + Text("3.") + Text(BWIL10n.bwiMobileDialogMMore2Text3) + } + } + } + .lineLimit(nil) + .fixedSize(horizontal: false, vertical: true) + .padding(EdgeInsets(top: 10, leading: 30, bottom: 0, trailing: 30)) + } + + 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(Color(ThemeService.shared().theme.backgroundColor)) + }) + .buttonStyle(PrimaryActionButtonStyle()) + } + + var successToast: some View { + HStack() { + Image(systemName: "checkmark") + .resizable() + .frame(width: 15, height: 15) + .accessibilityHidden(true) + Text(BWIL10n.bwiMobileDialogMMoreSuccess) + } + .foregroundColor(themeService.isCurrentThemeDark ? .black : .white) + .padding(10) + .background(themeService.isCurrentThemeDark ? .white : .black) + .clipShape(RoundedRectangle(cornerRadius: 10)) + .opacity(showSuccessToast ? 1 : 0) + .transition(.opacity) + } +} diff --git a/bwi/TopBanner/FeatureBannerViewController.swift b/bwi/TopBanner/FeatureBannerViewController.swift index 3843908f9..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(version: self.version) + 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 81d5b0d3e..d840af96c 100644 --- a/bwi/TopBanner/FeatureBannerVisibilityService.swift +++ b/bwi/TopBanner/FeatureBannerVisibilityService.swift @@ -29,45 +29,26 @@ 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 } - func markAsRead( version: String ) -> MXHTTPOperation? { - // Update only the "widgets" field in the account data - var featureDict = self.session.accountData.accountData(forEventType: AccountDataTypes.featureVisibility) ?? [:] + func markAsUnread( version: String ) { + BWIBuildSettings.shared.didShowBuMXMigrationInfoLevel = 0 + } + + func isUnread(completion: @escaping (_ unread : Bool) -> Void) { + guard BWIBuildSettings.shared.BuMXMigrationInfoLevel < 3 else { + completion(true) + return + } - featureDict[version] = false - - return session.setAccountData(featureDict, forType: AccountDataTypes.featureVisibility, success: nil, failure: nil) - } - - 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 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) + 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 + } +}