Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

mathieu/feature/Add Consent Flow on account creation #1636

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ struct AccountCreationView: View {
.disableAutocorrection(true)

Button {
self.submitForm()
self.showConsentView = true
} label: {
Text(String(l10n.AccountCreationView.connectionButton.characters))
.loadingIndicator(
Expand All @@ -52,7 +52,8 @@ struct AccountCreationView: View {
}
.onChange(of: self.authManagerViewModel.userAuthenticationState) { newValue in
if newValue == .loggedIn {
self.rootAccountManager.createRootAccount(rootAccount: RootAccount())
let rootAccount = RootAccount(consentInfo: [self.userConsentInfo!])
self.rootAccountManager.createRootAccount(rootAccount: rootAccount)
self.rootAccountManager.initializeRootAccountListener()
self.isVerificationEmailAlertPresented = true
}
Expand All @@ -71,6 +72,18 @@ struct AccountCreationView: View {
self.navigation.navigateToAccountCreationProcess = true
})
}
.sheet(isPresented: self.$showConsentView) {
ConsentView(
onCancel: {
self.showConsentView = false
},
onAccept: {
self.userConsentInfo = ConsentInfo(policyVersion: "1.0.0")
self.showConsentView = false
self.submitForm()
}
)
}
}

// MARK: Private
Expand All @@ -81,6 +94,8 @@ struct AccountCreationView: View {
@ObservedObject private var navigation: Navigation = .shared

@State private var isVerificationEmailAlertPresented: Bool = false
@State private var showConsentView: Bool = false
@State private var userConsentInfo: ConsentInfo?

private var authManager = AuthManager.shared
private var rootAccountManager = RootAccountManager.shared
Expand Down
64 changes: 64 additions & 0 deletions Apps/LekaApp/Sources/Views/ConsentView/ConsentView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// Leka - iOS Monorepo
// Copyright APF France handicap
// SPDX-License-Identifier: Apache-2.0

import SwiftUI

struct ConsentView: View {
// MARK: Internal

let onCancel: () -> Void
let onAccept: () -> Void

var body: some View {
NavigationStack {
VStack(spacing: 0) {
ScrollView {
PrivacyPolicyView()
.padding(.bottom)
}
.frame(maxWidth: .infinity, alignment: .leading)

VStack(spacing: 14) {
Divider()
Group {
Toggle(isOn: self.$isConsentGiven) {
Label("I have read and agree to the terms and privacy policy.", systemImage: "checkmark.shield.fill")
}

Button("Continue", action: self.onAccept)
.buttonStyle(.borderedProminent)
.disabled(!self.isConsentGiven)
}
.padding([.bottom, .horizontal])
}
.background(Color(.systemGray6))
.edgesIgnoringSafeArea(.bottom)
}
.navigationTitle("Privacy Policy")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button("Cancel", action: self.onCancel)
}
}
}
}

// MARK: Private

@State private var isConsentGiven: Bool = false
}

// MARK: - ConsentView_Previews

#Preview {
ConsentView(
onCancel: {
print("Content was declined")
},
onAccept: {
print("Consent was given")
}
)
}
70 changes: 70 additions & 0 deletions Apps/LekaApp/Sources/Views/ConsentView/PrivacyPolicyView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// Leka - iOS Monorepo
// Copyright APF France handicap
// SPDX-License-Identifier: Apache-2.0

import MarkdownUI
import SwiftUI

struct PrivacyPolicyView: View {
// MARK: Internal

var body: some View {
VStack(alignment: .leading, spacing: 16) {
Markdown(self.markdownText)
.markdownTheme(.gitHub)
.padding()
.multilineTextAlignment(.leading)
}
}

// MARK: Private

// swiftlint:disable line_length
private let markdownText = """
# Who we are
The address of our site: [https://leka.io](https://leka.io)

## Comments
When you leave a comment on our site, the data entered in the comment form as well as your IP address and your browser's user-agent are collected to help us detect undesirable comments.

An anonymized string created from your email address (also called a hash) may be sent to the Gravatar service to verify your use of the service. The Gravatar service privacy policy is available [here](https://automattic.com/privacy/). After validation of your comment, your profile picture will be publicly visible beside your comment.

## Media
If you upload images to the site, we advise you to avoid uploading images that contain EXIF data of GPS coordinates. People visiting your site may download and extract location data from these images.

## Cookies
If you leave a comment on our site, you will be asked to save your name, email address, and site in cookies. This is only for your convenience so that you do not have to enter this information if you leave another comment later. These cookies expire after one year.

If you go to the login page, a temporary cookie will be created to determine if your browser accepts cookies. It does not contain any personal information and will be deleted automatically when you close your browser.

When you log in, we will set a number of cookies to store your login information and screen preferences. The lifetime of a login cookie is two days, and the lifetime of a screen option cookie is one year. If you tick “Remember me”, your login cookie will be kept for two weeks. If you log out of your account, the login cookie will be deleted.

By editing or publishing a post, an additional cookie will be stored in your browser. This cookie does not include any personal information. It simply indicates the ID of the publication you have just modified. It expires after one day.

## Embedded content from other sites
Articles on this site may include embedded content (e.g. videos, images, articles, etc.). Embedded content from other sites behaves in the same way as if the visitor were on that other site.

These websites may collect data about you, use cookies, embed third-party tracking tools, and track your interactions with such embedded content if you have an account with their website.

## Use and disclosure of your personal data
If you request a password reset, your IP address will be included in the reset email.

## How long we store your data
If you leave a comment, the comment and its metadata are kept indefinitely. This allows us to automatically recognize and approve subsequent comments instead of leaving them in the moderation queue.

For accounts that register with our site (if any), we also store the personal data listed in their profile. All accounts can view, edit, or delete their personal information at any time (except for their user ID). Site managers can also view and edit this information.

## The rights you have over your data
If you have an account or if you have left comments on the site, you can request to receive a file containing all the personal data that we have about you, including those that you have provided to us. You may also request the deletion of your personal data. This does not include data stored for administrative, legal, or security purposes.

## Disclosure of your personal data
Visitors' comments can be checked using an automated service to detect undesirable comments.
"""
// swiftlint:enable line_length
}

// MARK: - PrivacyPolicyView_Previews

#Preview {
PrivacyPolicyView()
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ import SwiftUI
public extension RootAccount {
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)

self.id = try container.decodeIfPresent(String.self, forKey: .id)
self.rootOwnerUid = try container.decode(String.self, forKey: .rootOwnerUid)
self.library = try container.decodeIfPresent(Library.self, forKey: .library) ?? Library()
self.consentInfo = try container.decode([ConsentInfo].self, forKey: .consentInfo)
self.createdAt = try container.decodeIfPresent(Date.self, forKey: .createdAt)
self.lastEditedAt = try container.decodeIfPresent(Date.self, forKey: .lastEditedAt)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Leka - iOS Monorepo
// Copyright APF France handicap
// SPDX-License-Identifier: Apache-2.0

import SwiftUI

public extension ConsentInfo {
init(
policyVersion: String
) {
self.policyVersion = policyVersion
self.acceptedAt = Date()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Leka - iOS Monorepo
// Copyright APF France handicap
// SPDX-License-Identifier: Apache-2.0

import SwiftUI

public struct ConsentInfo: Codable {
// MARK: Public

public var policyVersion: String
public var acceptedAt: Date

// MARK: Internal

enum CodingKeys: String, CodingKey {
case policyVersion = "policy_version"
case acceptedAt = "accepted_at"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ public extension RootAccount {
init(
id: String = "",
rootOwnerUid: String = "",
library: Library = Library()
library: Library = Library(),
consentInfo: [ConsentInfo] = []
) {
self.id = id
self.rootOwnerUid = rootOwnerUid
self.library = library
self.consentInfo = consentInfo
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@ public struct RootAccount: AccountDocument {
@ServerTimestamp public var lastEditedAt: Date?
public var rootOwnerUid: String
public var library: Library
public var consentInfo: [ConsentInfo]

// MARK: Internal

enum CodingKeys: String, CodingKey {
case id = "uuid"
case rootOwnerUid = "root_owner_uid"
case library
case consentInfo = "consent_info"
case createdAt = "created_at"
case lastEditedAt = "last_edited_at"
}
Expand Down
Loading