Merge branch 'feature/3616_privacy_on_login' into 'develop'

Feature/3616 privacy on login

See merge request bwmessenger/bundesmessenger/bundesmessenger-ios!57
This commit is contained in:
Frank Rotermund
2022-12-04 10:47:42 +00:00
16 changed files with 261 additions and 63 deletions

View File

@@ -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";
}

View File

@@ -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";

View File

@@ -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";

View File

@@ -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")

View File

@@ -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;

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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 {

View File

@@ -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 }

View File

@@ -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

View File

@@ -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)
}
}

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)
}
}

View File

@@ -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
}
}
}

View File

@@ -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"
}
}