From f8348a66d04c37f5496eebfc61164d64d8a64a5a Mon Sep 17 00:00:00 2001 From: JanNiklas Grabowski Date: Wed, 14 Jun 2023 06:14:17 +0000 Subject: [PATCH] Feature/4772 add accessibility declaration --- Config/BWIBuildSettings.swift | 7 +++ Riot/Assets/de.lproj/Bwi.strings | 3 + Riot/Assets/en.lproj/Bwi.strings | 2 + Riot/Generated/BWIStrings.swift | 4 ++ .../Modules/Settings/SettingsViewController.m | 27 +++++++- .../Login/AuthenticationLoginModels.swift | 6 ++ .../Login/AuthenticationLoginViewModel.swift | 2 + .../AuthenticationLoginCoordinator.swift | 9 +++ .../View/AuthenticationLoginScreen.swift | 20 ++++++ .../AccessibilityDeclarationView.swift | 46 ++++++++++++++ bwi/MarkDown/MarkDownView.swift | 62 +++++++++++++++++++ 11 files changed, 187 insertions(+), 1 deletion(-) create mode 100644 bwi/AccessibilityDeclaration/AccessibilityDeclarationView.swift create mode 100644 bwi/MarkDown/MarkDownView.swift diff --git a/Config/BWIBuildSettings.swift b/Config/BWIBuildSettings.swift index 6f2328cb6..12ee3861b 100644 --- a/Config/BWIBuildSettings.swift +++ b/Config/BWIBuildSettings.swift @@ -622,6 +622,13 @@ class BWIBuildSettings: NSObject { // MARK: Sessions Manager var enableNewSessionManagerByDefault = false + // MARK: Accessibility declaration + // bwi flag for showing accessibility declaration on login screen and in settings + var bwiShowAccessibilityDeclaration = false + // internal markdown file for accessibility declaration in en and de. + var accessibilityDeclarationFileDe = "" + var accessibilityDeclarationFileEn = "" + // MARK: Voice Broadcast var enableLabFeatureVoiceBroadcasts = false } diff --git a/Riot/Assets/de.lproj/Bwi.strings b/Riot/Assets/de.lproj/Bwi.strings index e282f89a7..40345b21f 100644 --- a/Riot/Assets/de.lproj/Bwi.strings +++ b/Riot/Assets/de.lproj/Bwi.strings @@ -620,3 +620,6 @@ // MARK: - Voice Over "textfield_reveal_secret" = "Texteingabe anzeigen"; "textfield_hide_secret" = "Texteingabe verbergen"; + +// MARK: - Accessibility declaration +"bwi_accessibility_declaration_button_title" = "Barrierefreiheitserklärung"; diff --git a/Riot/Assets/en.lproj/Bwi.strings b/Riot/Assets/en.lproj/Bwi.strings index 2fe98d8ad..f7ace9edb 100644 --- a/Riot/Assets/en.lproj/Bwi.strings +++ b/Riot/Assets/en.lproj/Bwi.strings @@ -531,3 +531,5 @@ "textfield_reveal_secret" = "reveal text input"; "textfield_hide_secret" = "hide text input"; +// MARK: - Accessibility declaration +"bwi_accessibility_declaration_button_title" = "Accessibility declaration"; diff --git a/Riot/Generated/BWIStrings.swift b/Riot/Generated/BWIStrings.swift index 85319c276..5d67dcb14 100644 --- a/Riot/Generated/BWIStrings.swift +++ b/Riot/Generated/BWIStrings.swift @@ -99,6 +99,10 @@ public class BWIL10n: NSObject { public static var bumAutheticationTitle: String { return BWIL10n.tr("Bwi", "bum_authetication_title") } + /// Barrierefreiheitserklärung + public static var bwiAccessibilityDeclarationButtonTitle: String { + return BWIL10n.tr("Bwi", "bwi_accessibility_declaration_button_title") + } /// Wir brauchen Deine Hilfe, um Fehler im %@ besser analysieren zu können. Dazu würden wir gerne anonymisierte Diagnosedaten erfassen. Es werden keine Daten an Dritte übermittelt. Details findest Du in der Datenschutzerklärung.\n\nFalls Du nicht mehr mithelfen möchtest, kannst Du dies in den Einstellungen jederzeit wieder deaktivieren.\n\nMöchtest du bei der Fehler-Analyse unterstützen? public static func bwiAnalyticsAlertBody(_ p1: String) -> String { return BWIL10n.tr("Bwi", "bwi_analytics_alert_body", p1) diff --git a/Riot/Modules/Settings/SettingsViewController.m b/Riot/Modules/Settings/SettingsViewController.m index ff086336d..404b8a217 100644 --- a/Riot/Modules/Settings/SettingsViewController.m +++ b/Riot/Modules/Settings/SettingsViewController.m @@ -216,7 +216,8 @@ typedef NS_ENUM(NSUInteger, ABOUT) ABOUT_MARK_ALL_AS_READ_INDEX, ABOUT_CLEAR_CACHE_INDEX, ABOUT_REPORT_BUG_INDEX, - ABOUT_NETIQUETTE_INDEX + ABOUT_NETIQUETTE_INDEX, + ABOUT_ACCESSIBILITY_DECLARATION_INDEX }; typedef NS_ENUM(NSUInteger, LABS_ENABLE) @@ -725,6 +726,10 @@ ChangePasswordCoordinatorBridgePresenterDelegate> { [sectionAbout addRowWithTag:ABOUT_PRIVACY_INDEX]; } + // bwi 4772 - show accessibility declaration + if (BWIBuildSettings.shared.bwiShowAccessibilityDeclaration) { + [sectionAbout addRowWithTag:ABOUT_ACCESSIBILITY_DECLARATION_INDEX]; + } [sectionAbout addRowWithTag:ABOUT_THIRD_PARTY_INDEX]; sectionAbout.headerTitle = VectorL10n.settingsAbout; @@ -2837,6 +2842,16 @@ ChangePasswordCoordinatorBridgePresenterDelegate> cell = privacyPolicyCell; } + else if (row == ABOUT_ACCESSIBILITY_DECLARATION_INDEX) + { + MXKTableViewCell *accessibilityDeclarationCell = [self getDefaultTableViewCell:tableView]; + + accessibilityDeclarationCell.textLabel.text = [BWIL10n bwiAccessibilityDeclarationButtonTitle]; + + [accessibilityDeclarationCell vc_setAccessoryDisclosureIndicatorWithCurrentTheme]; + + cell = accessibilityDeclarationCell; + } else if (row == ABOUT_THIRD_PARTY_INDEX) { MXKTableViewCell *thirdPartyCell = [self getDefaultTableViewCell:tableView]; @@ -3305,6 +3320,10 @@ ChangePasswordCoordinatorBridgePresenterDelegate> { [[UIApplication sharedApplication] openURL:[NSURL URLWithString:BWIBuildSettings.shared.applicationPrivacyPolicyUrlString] options:@{} completionHandler:nil]; } + else if (row == ABOUT_ACCESSIBILITY_DECLARATION_INDEX) + { + [self showAccessibilityDeclaration]; + } else if (row == ABOUT_THIRD_PARTY_INDEX) { NSString *htmlFile = [[NSBundle mainBundle] pathForResource:@"third_party_licenses" ofType:@"html" inDirectory:nil]; @@ -4493,6 +4512,12 @@ ChangePasswordCoordinatorBridgePresenterDelegate> UIViewController *developerSettingsViewController = [DeveloperSettingsViewController makeViewControllerWithSession:self.mainSession]; [self pushViewController:developerSettingsViewController]; } + +- (void)showAccessibilityDeclaration +{ + UIViewController *accessibilityDeclarationViewController = [AccessibilityDeclarationViewController makeViewController]; + [self pushViewController:accessibilityDeclarationViewController]; +} - (void)showPersonalStateSettings { diff --git a/RiotSwiftUI/Modules/Authentication/Login/AuthenticationLoginModels.swift b/RiotSwiftUI/Modules/Authentication/Login/AuthenticationLoginModels.swift index dac56608d..356bd5fd3 100644 --- a/RiotSwiftUI/Modules/Authentication/Login/AuthenticationLoginModels.swift +++ b/RiotSwiftUI/Modules/Authentication/Login/AuthenticationLoginModels.swift @@ -35,6 +35,8 @@ enum AuthenticationLoginViewModelResult: CustomStringConvertible { case qrLogin /// bwi: register info case register + /// bwi #4772: accessibility declaration + case accessibilityDeclaration /// A string representation of the result, ignoring any associated values that could leak PII. var description: String { @@ -55,6 +57,8 @@ enum AuthenticationLoginViewModelResult: CustomStringConvertible { return "qrLogin" case .register: return "register" + case .accessibilityDeclaration: + return "accessibilityDeclaration" } } } @@ -118,6 +122,8 @@ enum AuthenticationLoginViewAction { case qrLogin /// bwi: register info case register + /// bwi #4772: accessibility declaration + case accessibilityDeclaration } enum AuthenticationLoginErrorType: Hashable { diff --git a/RiotSwiftUI/Modules/Authentication/Login/AuthenticationLoginViewModel.swift b/RiotSwiftUI/Modules/Authentication/Login/AuthenticationLoginViewModel.swift index 64ee83363..07879621c 100644 --- a/RiotSwiftUI/Modules/Authentication/Login/AuthenticationLoginViewModel.swift +++ b/RiotSwiftUI/Modules/Authentication/Login/AuthenticationLoginViewModel.swift @@ -54,6 +54,8 @@ class AuthenticationLoginViewModel: AuthenticationLoginViewModelType, Authentica Task { await callback?(.qrLogin) } case .register: Task { await callback?(.register) } + case .accessibilityDeclaration: + Task { await callback?(.accessibilityDeclaration) } } } diff --git a/RiotSwiftUI/Modules/Authentication/Login/Coordinator/AuthenticationLoginCoordinator.swift b/RiotSwiftUI/Modules/Authentication/Login/Coordinator/AuthenticationLoginCoordinator.swift index f71f6c0c9..df7f688f4 100644 --- a/RiotSwiftUI/Modules/Authentication/Login/Coordinator/AuthenticationLoginCoordinator.swift +++ b/RiotSwiftUI/Modules/Authentication/Login/Coordinator/AuthenticationLoginCoordinator.swift @@ -140,6 +140,8 @@ final class AuthenticationLoginCoordinator: Coordinator, Presentable { case .register: // bwi: show info alert self.authenticationLoginViewModel.displayInfoAlert(.register) + case .accessibilityDeclaration: + self.showAccessibilityDeclaration() } } } @@ -325,6 +327,13 @@ final class AuthenticationLoginCoordinator: Coordinator, Presentable { } } + /// bwi #4772 show accessibility declaration + @MainActor private func showAccessibilityDeclaration() { + MXLog.debug("[AuthenticationLoginCoordinator] showAccessibilityDeclaration") + let accessibilityDeclarationViewController = AccessibilityDeclarationViewController.makeViewController() + navigationRouter.push(accessibilityDeclarationViewController, animated: true, popCompletion: nil) + } + /// Updates the view model to reflect any changes made to the homeserver. @MainActor private func updateViewModel() { let homeserver = authenticationService.state.homeserver diff --git a/RiotSwiftUI/Modules/Authentication/Login/View/AuthenticationLoginScreen.swift b/RiotSwiftUI/Modules/Authentication/Login/View/AuthenticationLoginScreen.swift index 662c26383..60e1b2d35 100644 --- a/RiotSwiftUI/Modules/Authentication/Login/View/AuthenticationLoginScreen.swift +++ b/RiotSwiftUI/Modules/Authentication/Login/View/AuthenticationLoginScreen.swift @@ -93,6 +93,13 @@ struct AuthenticationLoginScreen: View { .readableFrame() .padding(.horizontal, 16) } + + if BWIBuildSettings.shared.bumLoginFlowLayout && BWIBuildSettings.shared.bwiShowAccessibilityDeclaration { + accessibilityDeclaration + .frame(alignment: .bottom) + .padding(.bottom, 10) + } + if BWIBuildSettings.shared.bumLoginFlowLayout { dataPrivacyForm .frame(alignment: .bottom) @@ -305,6 +312,19 @@ struct AuthenticationLoginScreen: View { return BWIBuildSettings.shared.bwiLoginFlowLayout ? BWIL10n.authUserIdPlaceholder : BWIL10n.authenticationLoginUsername } } + + // bwi: Accessibility declaration + var accessibilityDeclaration: some View { + Button(action: { + viewModel.send(viewAction: .accessibilityDeclaration) + }, label: { + Text(BWIL10n.bwiAccessibilityDeclarationButtonTitle) + .font(theme.fonts.footnote) + .foregroundColor(.blue) + .underline() + }) + .padding([.horizontal], 20) + } } // MARK: - Previews diff --git a/bwi/AccessibilityDeclaration/AccessibilityDeclarationView.swift b/bwi/AccessibilityDeclaration/AccessibilityDeclarationView.swift new file mode 100644 index 000000000..0dd71f2d7 --- /dev/null +++ b/bwi/AccessibilityDeclaration/AccessibilityDeclarationView.swift @@ -0,0 +1,46 @@ +// +/* + * Copyright (c) 2023 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 + +@objcMembers class AccessibilityDeclarationViewController: NSObject { + + @available(iOS 14.0, *) + class func makeViewController() -> UIViewController { + var accessibilityDeclarationFilePath: URL? = nil + if !BWIBuildSettings.shared.accessibilityDeclarationFileDe.isEmpty && Bundle.main.preferredLocalizations[0].elementsEqual("de") { + accessibilityDeclarationFilePath = Bundle.main.url(forResource: BWIBuildSettings.shared.accessibilityDeclarationFileDe, withExtension: "md") + } else if !BWIBuildSettings.shared.accessibilityDeclarationFileEn.isEmpty { + accessibilityDeclarationFilePath = Bundle.main.url(forResource: BWIBuildSettings.shared.accessibilityDeclarationFileEn, withExtension: "md") + } + + if let url = accessibilityDeclarationFilePath { + guard let string = try? String(contentsOf: url) else { + return UIHostingController(rootView: EmptyView()) + } + let vc = UIHostingController(rootView: MarkDownView(markdownString: string)) + vc.title = BWIL10n.bwiAccessibilityDeclarationButtonTitle + vc.view.backgroundColor = ThemeService.shared().theme.backgroundColor + vc.navigationItem.largeTitleDisplayMode = .never + return vc + } else { + return UIHostingController(rootView: EmptyView()) + } + } +} + + diff --git a/bwi/MarkDown/MarkDownView.swift b/bwi/MarkDown/MarkDownView.swift new file mode 100644 index 000000000..7256b24a9 --- /dev/null +++ b/bwi/MarkDown/MarkDownView.swift @@ -0,0 +1,62 @@ +// +/* + * Copyright (c) 2023 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 +import Down + +struct MarkDownView: View { + var markdownString: String + @State var labelHeight: CGFloat = .zero + + var body: some View { + GeometryReader { geometry in + ScrollView(.vertical) { + UIMarkDownWrapper(markDownString: markdownString, height: $labelHeight) + .frame(width: geometry.size.width - 20) + .frame(minHeight: labelHeight) + .padding(10) + } + .background(Color(ThemeService.shared().theme.backgroundColor)) + } + } +} + +struct UIMarkDownWrapper: UIViewRepresentable { + var markDownString: String + @Binding var height: CGFloat + + func makeUIView(context: Context) -> UILabel { + let label = UILabel(frame: .zero) + label.numberOfLines = 0 + label.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) + return label + } + + func updateUIView(_ uiView: UILabel, context: Context) { + let down = Down(markdownString: markDownString) + + guard let attributedString = try? down.toAttributedString() else { return } + let mutableString = NSMutableAttributedString(attributedString: attributedString) + mutableString.addAttributes([.foregroundColor: ThemeService.shared().theme.textPrimaryColor], range: NSRange(location: 0, length: attributedString.length)) + + uiView.attributedText = mutableString + DispatchQueue.main.async { + height = uiView.sizeThatFits(CGSize(width: uiView.bounds.width, height: CGFloat.greatestFiniteMagnitude)).height + } + } + +}