From d67c3f49d1e0449d28033ae818ced2f1ff16f20b Mon Sep 17 00:00:00 2001 From: Frank Rotermund Date: Sun, 4 Dec 2022 10:47:42 +0000 Subject: [PATCH] Feature/3616 privacy on login --- Config/BWIBuildSettings.swift | 5 +- Riot/Assets/de.lproj/Bwi.strings | 3 + Riot/Assets/en.lproj/Bwi.strings | 3 + Riot/Generated/BWIStrings.swift | 12 ++ .../Members/RoomParticipantsViewController.m | 16 +-- .../AuthenticationServerInfoSection.swift | 4 +- .../MatrixSDK/AuthenticationService.swift | 3 + .../Login/AuthenticationLoginModels.swift | 7 + .../View/AuthenticationLoginScreen.swift | 120 ++++++++++++------ ...thenticationServerSelectionViewModel.swift | 3 +- ...enticationServerSelectionCoordinator.swift | 2 +- .../AuthenticationServerSelectionScreen.swift | 8 +- .../Onboarding/Common/OnboardingIcon.swift | 4 +- bwi/CommonUI/ServerIcon.swift | 65 ++++++++++ bwi/Wellknown/Wellknown+Bwi.swift | 35 ++++- bwi/Wellknown/WellknownBWI.swift | 34 +++++ 16 files changed, 261 insertions(+), 63 deletions(-) create mode 100644 bwi/CommonUI/ServerIcon.swift create mode 100644 bwi/Wellknown/WellknownBWI.swift diff --git a/Config/BWIBuildSettings.swift b/Config/BWIBuildSettings.swift index f95788173..4348ead67 100644 --- a/Config/BWIBuildSettings.swift +++ b/Config/BWIBuildSettings.swift @@ -209,7 +209,7 @@ class BWIBuildSettings: NSObject { // Note: Set empty strings to hide the related entry in application settings var applicationCopyrightUrlString = "https://messenger.bwi.de/copyright" - var applicationPrivacyPolicyUrlString = "" + var applicationPrivacyPolicyUrlString = "https://messenger.bwi.de/datenschutz" var applicationTermsConditionsUrlString = "" // MARk: - Matrix permalinks @@ -510,4 +510,7 @@ class BWIBuildSettings: NSObject { // one flag for all layout changes to the element login flow var bumLoginFlowLayout = true + + // website for users in public service that want a backend + var bumAdvertizementURLString = "https://messenger.bwi.de/ich-will-bum"; } diff --git a/Riot/Assets/de.lproj/Bwi.strings b/Riot/Assets/de.lproj/Bwi.strings index 5918a0242..9f6672b7e 100644 --- a/Riot/Assets/de.lproj/Bwi.strings +++ b/Riot/Assets/de.lproj/Bwi.strings @@ -545,3 +545,6 @@ // MARK - new login flow "authentication_server_selection_login_title" = "Willkommen!"; "authentication_login_username" = "Nutzername"; +"authentication_login_description" = "Mit deinem Konto anmelden"; +"authentication_dataprivacy_text" = "Hinweise zur Verarbeitung Ihrer Daten und Ihrer Rechte erhalten Sie in unseren "; +"authentication_dataprivacy_link" = "Datenschutzhinweisen"; diff --git a/Riot/Assets/en.lproj/Bwi.strings b/Riot/Assets/en.lproj/Bwi.strings index 81c294d83..3be69cd9b 100644 --- a/Riot/Assets/en.lproj/Bwi.strings +++ b/Riot/Assets/en.lproj/Bwi.strings @@ -413,3 +413,6 @@ // MARK: - new login flow "authentication_server_selection_login_title" = "Welcome!"; "authentication_login_username" = "Username"; +"authentication_login_description" = "Login to your account"; +"authentication_dataprivacy_text" = "You can see our data usage and your rights in our "; +"authentication_dataprivacy_link" = "privacy agreement"; diff --git a/Riot/Generated/BWIStrings.swift b/Riot/Generated/BWIStrings.swift index 3dd8286f5..8ad5c4bd0 100644 --- a/Riot/Generated/BWIStrings.swift +++ b/Riot/Generated/BWIStrings.swift @@ -27,6 +27,18 @@ public class BWIL10n: NSObject { public static var authUserIdPlaceholder: String { return BWIL10n.tr("Bwi", "auth_user_id_placeholder") } + /// Datenschutzhinweisen + public static var authenticationDataprivacyLink: String { + return BWIL10n.tr("Bwi", "authentication_dataprivacy_link") + } + /// Hinweise zur Verarbeitung Ihrer Daten und Ihrer Rechte erhalten Sie in unseren + public static var authenticationDataprivacyText: String { + return BWIL10n.tr("Bwi", "authentication_dataprivacy_text") + } + /// Mit deinem Konto anmelden + public static var authenticationLoginDescription: String { + return BWIL10n.tr("Bwi", "authentication_login_description") + } /// Nutzername public static var authenticationLoginUsername: String { return BWIL10n.tr("Bwi", "authentication_login_username") diff --git a/Riot/Modules/Room/Members/RoomParticipantsViewController.m b/Riot/Modules/Room/Members/RoomParticipantsViewController.m index 38cf561fc..801a1aae4 100644 --- a/Riot/Modules/Room/Members/RoomParticipantsViewController.m +++ b/Riot/Modules/Room/Members/RoomParticipantsViewController.m @@ -615,10 +615,7 @@ self->userParticipant.mxMember = [roomState.members memberWithUserId:userId]; } } - else - { - [self handleRoomMember:mxMember]; - } + [self handleRoomMember:mxMember]; } for (MXRoomThirdPartyInvite *roomThirdPartyInvite in roomThirdPartyInvites) @@ -1004,7 +1001,7 @@ modSection = count++; } - if (userParticipant || actualParticipants.count) + if (actualParticipants.count) { participantsSection = count++; } @@ -1031,10 +1028,6 @@ else { count = actualParticipants.count; - if (userParticipant) - { - count++; - } } } else if (section == adminSection) @@ -1560,11 +1553,6 @@ filteredModParticipants = [NSMutableArray arrayWithArray:modParticipants]; filteredAdminParticipants = [NSMutableArray arrayWithArray:adminParticipants]; - // Add the current user if he belongs to the room members. - if (userParticipant) - { - [filteredActualParticipants addObject:userParticipant]; - } } currentSearchText = searchText; diff --git a/RiotSwiftUI/Modules/Authentication/Common/AuthenticationServerInfoSection.swift b/RiotSwiftUI/Modules/Authentication/Common/AuthenticationServerInfoSection.swift index bdaebb945..251efea7a 100644 --- a/RiotSwiftUI/Modules/Authentication/Common/AuthenticationServerInfoSection.swift +++ b/RiotSwiftUI/Modules/Authentication/Common/AuthenticationServerInfoSection.swift @@ -49,10 +49,10 @@ struct AuthenticationServerInfoSection: View { .font(theme.fonts.body) .foregroundColor(theme.colors.primaryContent) - Spacer() - if !BWIBuildSettings.shared.bumLoginFlowLayout { + Spacer() + Button(action: editAction) { Text(VectorL10n.edit) .font(theme.fonts.body) diff --git a/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/AuthenticationService.swift b/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/AuthenticationService.swift index ec20615ca..5802166bb 100644 --- a/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/AuthenticationService.swift +++ b/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/AuthenticationService.swift @@ -68,6 +68,7 @@ class AuthenticationService: NSObject { /// The type of client to use during the flow. var clientType: AuthenticationRestClient.Type = MXRestClient.self + var wellknown: MXWellKnown? // MARK: - Setup init(sessionCreator: SessionCreatorProtocol = SessionCreator()) { @@ -236,6 +237,8 @@ class AuthenticationService: NSObject { var identityServerURL: URL? if let wellKnown = try? await wellKnown(for: homeserverURL) { + self.wellknown = wellKnown + if let baseURL = URL(string: wellKnown.homeServer.baseUrl) { homeserverURL = baseURL } diff --git a/RiotSwiftUI/Modules/Authentication/Login/AuthenticationLoginModels.swift b/RiotSwiftUI/Modules/Authentication/Login/AuthenticationLoginModels.swift index eadd28e68..204eddd57 100644 --- a/RiotSwiftUI/Modules/Authentication/Login/AuthenticationLoginModels.swift +++ b/RiotSwiftUI/Modules/Authentication/Login/AuthenticationLoginModels.swift @@ -79,6 +79,13 @@ struct AuthenticationLoginViewState: BindableState { var canSubmit: Bool { hasValidCredentials && !isLoading } + + var dataPrivacyString: String { + guard let urlString = AuthenticationService.shared.wellknown?.dataPrivacyURL() else { + return BWIBuildSettings.shared.applicationPrivacyPolicyUrlString + } + return urlString + } } struct AuthenticationLoginBindings { diff --git a/RiotSwiftUI/Modules/Authentication/Login/View/AuthenticationLoginScreen.swift b/RiotSwiftUI/Modules/Authentication/Login/View/AuthenticationLoginScreen.swift index 8077254c2..b7c8d9d55 100644 --- a/RiotSwiftUI/Modules/Authentication/Login/View/AuthenticationLoginScreen.swift +++ b/RiotSwiftUI/Modules/Authentication/Login/View/AuthenticationLoginScreen.swift @@ -32,53 +32,70 @@ struct AuthenticationLoginScreen: View { @ObservedObject var viewModel: AuthenticationLoginViewModel.Context var body: some View { - ScrollView { - VStack(spacing: 0) { - header - .padding(.top, OnboardingMetrics.topPaddingToNavigationBar) - .padding(.bottom, 28) - - serverInfo - .padding(.leading, 12) - .padding(.bottom, 16) - - Rectangle() - .fill(theme.colors.quinaryContent) - .frame(height: 1) - .padding(.bottom, 22) - - if viewModel.viewState.homeserver.showLoginForm { - loginForm - } + VStack { + ScrollView { + VStack(spacing: 0) { + if BWIBuildSettings.shared.bumLoginFlowLayout { + ServerIcon(image: Asset.Images.welcomeExperience1, size: OnboardingMetrics.iconSize-18) + .padding(.top, OnboardingMetrics.topPaddingToNavigationBar) + .padding(.bottom, 16) + } else { + header + .padding(.top, OnboardingMetrics.topPaddingToNavigationBar) + .padding(.bottom, 28) + } + + serverInfo + .padding(.leading, 12) + .padding(.bottom, 16) + + Rectangle() + .fill(theme.colors.quinaryContent) + .frame(height: 1) + .padding(.bottom, 22) + + if BWIBuildSettings.shared.bumLoginFlowLayout { + loginDescription + .padding(.bottom, 22) + } + + if viewModel.viewState.homeserver.showLoginForm { + loginForm + } - if viewModel.viewState.homeserver.showQRLogin { - qrLoginButton - } - - if viewModel.viewState.homeserver.showLoginForm, viewModel.viewState.showSSOButtons { - Text(VectorL10n.or) - .foregroundColor(theme.colors.secondaryContent) - .padding(.top, 16) - } - - if viewModel.viewState.showSSOButtons { - ssoButtons - .padding(.top, 16) - } + if viewModel.viewState.homeserver.showQRLogin { + qrLoginButton + } + + if viewModel.viewState.homeserver.showLoginForm, viewModel.viewState.showSSOButtons { + Text(VectorL10n.or) + .foregroundColor(theme.colors.secondaryContent) + .padding(.top, 16) + } + + if viewModel.viewState.showSSOButtons { + ssoButtons + .padding(.top, 16) + } - if !viewModel.viewState.homeserver.showLoginForm, !viewModel.viewState.showSSOButtons { - fallbackButton + if !viewModel.viewState.homeserver.showLoginForm, !viewModel.viewState.showSSOButtons { + fallbackButton + } } + .readableFrame() + .padding(.horizontal, 16) + } + if BWIBuildSettings.shared.bumLoginFlowLayout { + dataPrivacyForm + .frame(alignment: .bottom) + .padding(.bottom, 10) } - .readableFrame() - .padding(.horizontal, 16) - .padding(.bottom, 16) } .background(theme.colors.background.ignoresSafeArea()) .alert(item: $viewModel.alertInfo) { $0.alert } .accentColor(theme.colors.accent) } - + /// The header containing a Welcome Back title. var header: some View { Text(VectorL10n.authenticationLoginTitle) @@ -167,6 +184,33 @@ struct AuthenticationLoginScreen: View { .accessibilityIdentifier("fallbackButton") } + var dataPrivacyForm: some View { + Button(action: { + let tosURL = URL.init(string: viewModel.viewState.dataPrivacyString)! // add your link here + if UIApplication.shared.canOpenURL(tosURL) { + UIApplication.shared.open(tosURL) + } + }, label: { + Text(BWIL10n.authenticationDataprivacyText) + .font(theme.fonts.footnote) + .foregroundColor(theme.colors.primaryContent) + + + Text(BWIL10n.authenticationDataprivacyLink) + .font(theme.fonts.footnote) + .foregroundColor(.blue) + .underline() + }) + .padding([.horizontal], 20) + } + + var loginDescription: some View { + HStack(spacing: 0) { + Text(BWIL10n.authenticationLoginDescription) + .font(theme.fonts.callout) + .foregroundColor(theme.colors.primaryContent) + } + } + /// Parses the username for a homeserver. func usernameEditingChanged(isEditing: Bool) { guard !isEditing, !viewModel.username.isEmpty else { return } diff --git a/RiotSwiftUI/Modules/Authentication/ServerSelection/AuthenticationServerSelectionViewModel.swift b/RiotSwiftUI/Modules/Authentication/ServerSelection/AuthenticationServerSelectionViewModel.swift index 791f2b20e..3d9b792b1 100644 --- a/RiotSwiftUI/Modules/Authentication/ServerSelection/AuthenticationServerSelectionViewModel.swift +++ b/RiotSwiftUI/Modules/Authentication/ServerSelection/AuthenticationServerSelectionViewModel.swift @@ -65,7 +65,8 @@ class AuthenticationServerSelectionViewModel: AuthenticationServerSelectionViewM @MainActor func displayInfo(_ message: String, buttonTitle: String, completion: (()->Void)? ) { state.bindings.alertInfo = AlertInfo(id: .openInvalidServerInfo, title: message, - primaryButton: (buttonTitle, completion)) + primaryButton: (buttonTitle, completion), + secondaryButton: (VectorL10n.cancel, nil)) } // MARK: - Private diff --git a/RiotSwiftUI/Modules/Authentication/ServerSelection/Coordinator/AuthenticationServerSelectionCoordinator.swift b/RiotSwiftUI/Modules/Authentication/ServerSelection/Coordinator/AuthenticationServerSelectionCoordinator.swift index e7ef1aaa2..f4ebbc004 100644 --- a/RiotSwiftUI/Modules/Authentication/ServerSelection/Coordinator/AuthenticationServerSelectionCoordinator.swift +++ b/RiotSwiftUI/Modules/Authentication/ServerSelection/Coordinator/AuthenticationServerSelectionCoordinator.swift @@ -123,7 +123,7 @@ final class AuthenticationServerSelectionCoordinator: Coordinator, Presentable { guard protectionService.isValid(homeserverAddress) else { stopLoading() let primaryButtonCompletion: (() -> Void)? = { () in - if let url = URL(string: "https://messenger.bwi.de/ich-will-bum") { + if let url = URL(string: BWIBuildSettings.shared.bumAdvertizementURLString) { UIApplication.shared.open(url) } } diff --git a/RiotSwiftUI/Modules/Authentication/ServerSelection/View/AuthenticationServerSelectionScreen.swift b/RiotSwiftUI/Modules/Authentication/ServerSelection/View/AuthenticationServerSelectionScreen.swift index 510359ee9..1586e5d8a 100644 --- a/RiotSwiftUI/Modules/Authentication/ServerSelection/View/AuthenticationServerSelectionScreen.swift +++ b/RiotSwiftUI/Modules/Authentication/ServerSelection/View/AuthenticationServerSelectionScreen.swift @@ -58,8 +58,12 @@ struct AuthenticationServerSelectionScreen: View { /// The title, message and icon at the top of the screen. var header: some View { VStack(spacing: 8) { - OnboardingIconImage(image: Asset.Images.welcomeExperience1) - .padding(.bottom, 8) + if BWIBuildSettings.shared.bumLoginFlowLayout { + ServerIcon(image: Asset.Images.welcomeExperience1, size: OnboardingMetrics.iconSize) + } else { + OnboardingIconImage(image: Asset.Images.welcomeExperience1) + .padding(.bottom, 8) + } Text(viewModel.viewState.headerTitle) .font(theme.fonts.title2B) diff --git a/RiotSwiftUI/Modules/Onboarding/Common/OnboardingIcon.swift b/RiotSwiftUI/Modules/Onboarding/Common/OnboardingIcon.swift index 73a158168..4993ce002 100644 --- a/RiotSwiftUI/Modules/Onboarding/Common/OnboardingIcon.swift +++ b/RiotSwiftUI/Modules/Onboarding/Common/OnboardingIcon.swift @@ -24,8 +24,8 @@ struct OnboardingIconImage: View { var body: some View { Image(image.name) .resizable() - //.renderingMode(.template) - //.foregroundColor(theme.colors.accent) + .renderingMode(.template) + .foregroundColor(theme.colors.accent) .frame(width: OnboardingMetrics.iconSize, height: OnboardingMetrics.iconSize) .background(Circle().foregroundColor(.white).padding(2)) .accessibilityHidden(true) diff --git a/bwi/CommonUI/ServerIcon.swift b/bwi/CommonUI/ServerIcon.swift new file mode 100644 index 000000000..4c2d81716 --- /dev/null +++ b/bwi/CommonUI/ServerIcon.swift @@ -0,0 +1,65 @@ +// +/* + * Copyright (c) 2022 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. + */ + +/* + struct OnboardingIconImage: View { + @Environment(\.theme) private var theme + + let image: ImageAsset + + var body: some View { + Image(image.name) + .resizable() + .renderingMode(.template) + .foregroundColor(theme.colors.accent) + .frame(width: OnboardingMetrics.iconSize, height: OnboardingMetrics.iconSize) + .background(Circle().foregroundColor(.white).padding(2)) + .accessibilityHidden(true) + } + } + + // MARK: - Previews + + struct OnboardingIconImage_Previews: PreviewProvider { + static var previews: some View { + OnboardingIconImage(image: Asset.Images.authenticationEmailIcon) + } + } + */ + +import SwiftUI + +struct ServerIcon: View { + @Environment(\.theme) private var theme + + let image: ImageAsset + let size: CGFloat + + var body: some View { + Image(image.name) + .resizable() + .frame(width: size, height: size * 1.3) + .background(Circle().foregroundColor(.white).padding(2)) + .accessibilityHidden(true) + } +} + +struct ServerIcon_Previews: PreviewProvider { + static var previews: some View { + ServerIcon(image: Asset.Images.authenticationEmailIcon, size: 72) + } +} diff --git a/bwi/Wellknown/Wellknown+Bwi.swift b/bwi/Wellknown/Wellknown+Bwi.swift index 76886dfde..0fc55e224 100644 --- a/bwi/Wellknown/Wellknown+Bwi.swift +++ b/bwi/Wellknown/Wellknown+Bwi.swift @@ -67,7 +67,38 @@ public extension MXWellKnown { catch { return .whenTyping } - - return .whenTyping + } + + // returns true if there is a valid url or no url, only the case "is url" but not valid is wrong + @objc func isValidDataPrivacyURL() -> Bool { + do { + let bwi = try WellknownBWI(dict: self.jsonDictionary()["de.bwi"] as! [String : Any]) + + if let urlString = bwi.dataPrivacyURL { + if URL(string: urlString) != nil { + return true + } else { + return false + } + } else { + return true + } + } catch { + return true + } + } + + @objc func dataPrivacyURL() -> String? { + do { + guard let bwiDict = self.jsonDictionary()["de.bwi"] as? [String : Any] else { + return nil + } + + let bwi = try WellknownBWI(dict: bwiDict) + return bwi.dataPrivacyURL + } + catch { + return nil + } } } diff --git a/bwi/Wellknown/WellknownBWI.swift b/bwi/Wellknown/WellknownBWI.swift new file mode 100644 index 000000000..6cfc4bc92 --- /dev/null +++ b/bwi/Wellknown/WellknownBWI.swift @@ -0,0 +1,34 @@ +// +/* + * Copyright (c) 2022 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 Foundation + +struct WellknownBWI { + let dataPrivacyURL: String? + + init(dict: [String: Any]) throws { + let jsonData = try JSONSerialization.data(withJSONObject: dict, options: []) + let decoder = JSONDecoder() + self = try decoder.decode(Self.self, from: jsonData) + } +} + +extension WellknownBWI: Decodable { + enum CodingKeys: String, CodingKey { + case dataPrivacyURL = "data_privacy_url" + } +}