Skip to content

Commit

Permalink
EULA link and checkbox for "send marketing messages" (#273)
Browse files Browse the repository at this point in the history
* chore: add eula

* chore: add marketing checkbox

* chore: add strings

* chore: reorder plaintext honor code

* chore: remove animation for plaintext

* chore: add colors eula texts and fix tests

* chore:  change strings

* chore: resolve PR comments

* chore: back created by name
  • Loading branch information
eyatsenkoperpetio authored Feb 6, 2024
1 parent 1309f7a commit 48029c9
Show file tree
Hide file tree
Showing 26 changed files with 506 additions and 71 deletions.
71 changes: 59 additions & 12 deletions Authorization/Authorization/Presentation/Base/FieldsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import SwiftUI
import Core
import Theme

struct FieldsView: View {

Expand All @@ -17,7 +18,8 @@ struct FieldsView: View {
let proxy: GeometryProxy
@Environment(\.colorScheme) var colorScheme
@State private var text: String = ""

@State private var sendMarketing: Bool = true

var body: some View {
ForEach(0..<fields.count, id: \.self) { index in
let config = fields[index]
Expand Down Expand Up @@ -54,23 +56,68 @@ struct FieldsView: View {
case .checkbox:
EmptyView()
.id(index)
Text("Checkbox is not support")
case .plaintext:
HTMLFormattedText(
cssInjector.injectCSS(
colorScheme: colorScheme,
html: config.field.label,
type: .discovery,
fontSize: 90, screenWidth: proxy.size.width)
)
.id(UUID())
.padding(.horizontal, -6)

plaintext(fieldConfig: config)
case .unknown:
Text("This field not support")
}
}
}

@ViewBuilder
private func plaintext(fieldConfig: FieldConfiguration) -> some View {
if fieldConfig.field.isHonorCode,
let eulaURL = config.agreement.eulaURL,
let tosURL = config.agreement.tosURL,
let policy = config.agreement.privacyPolicyURL {
let text = AuthLocalization.SignUp.agreement(
"\(config.platformName)",
eulaURL,
"\(config.platformName)",
tosURL,
"\(config.platformName)",
policy
)
let checkBox = fields.first(where: { $0.field.type == .checkbox })
checkBox.flatMap { _ in
CheckBoxView(
checked: $sendMarketing,
text: AuthLocalization.SignUp.marketingEmailTitle("\(config.platformName)"),
font: Theme.Fonts.labelSmall
)
.padding(.vertical, 10)
.onAppear {
checkBox?.text = "\(sendMarketing)"
}
.onChange(of: sendMarketing) { newValue in
checkBox?.text = "\(newValue)"
}
}
Text(.init(text))
.tint(Theme.Colors.accentColor)
.foregroundStyle(Theme.Colors.textSecondary)
.font(Theme.Fonts.labelSmall)
.padding(.vertical, 3)
.id(UUID())
.environment(\.openURL, OpenURLAction(handler: handleURL))
Divider()
} else {
HTMLFormattedText(
cssInjector.injectCSS(
colorScheme: colorScheme,
html: fieldConfig.field.label,
type: .discovery,
fontSize: 90, screenWidth: proxy.size.width)
)
.id(UUID())
.padding(.horizontal, -6)
}
}

private func handleURL(_ url: URL) -> OpenURLAction.Result {
router.showWebBrowser(title: url.host ?? "", url: url)
return .handled
}
}

#if DEBUG
Expand Down
29 changes: 29 additions & 0 deletions Authorization/Authorization/Presentation/Login/SignInView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ public struct SignInView: View {
}
)
}
agreements
Spacer()
}
.padding(.horizontal, 24)
Expand Down Expand Up @@ -198,6 +199,34 @@ public struct SignInView: View {
.ignoresSafeArea(.all, edges: .horizontal)
.background(Theme.Colors.background.ignoresSafeArea(.all))
}

@ViewBuilder
private var agreements: some View {
if let eulaURL = viewModel.config.agreement.eulaURL,
let tosURL = viewModel.config.agreement.tosURL,
let policy = viewModel.config.agreement.privacyPolicyURL {
let text = AuthLocalization.SignIn.agreement(
"\(viewModel.config.platformName)",
eulaURL,
"\(viewModel.config.platformName)",
tosURL,
"\(viewModel.config.platformName)",
policy
)
Text(.init(text))
.tint(Theme.Colors.accentColor)
.foregroundStyle(Theme.Colors.textSecondary)
.font(Theme.Fonts.labelSmall)
.padding(.top, viewModel.socialAuthEnabled ? 0 : 15)
.padding(.bottom, 15)
.environment(\.openURL, OpenURLAction(handler: handleURL))
}
}

private func handleURL(_ url: URL) -> OpenURLAction.Result {
viewModel.router.showWebBrowser(title: url.host ?? "", url: url)
return .handled
}
}

#if DEBUG
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,30 +86,47 @@ public struct SignUpView: View {
.accessibilityIdentifier("social_auth_success_subtext_text")
}

let requiredFields = viewModel.fields.filter {$0.field.required}
let nonRequiredFields = viewModel.fields.filter {!$0.field.required}

FieldsView(fields: requiredFields,
router: viewModel.router,
config: viewModel.config,
cssInjector: viewModel.cssInjector,
proxy: proxy)

let requiredFields = viewModel.requiredFields
let optionalFields = viewModel.optionalFields

FieldsView(
fields: requiredFields,
router: viewModel.router,
config: viewModel.config,
cssInjector: viewModel.cssInjector,
proxy: proxy
)

if !viewModel.isShowProgress {
DisclosureGroup(isExpanded: $disclosureGroupOpen, content: {
FieldsView(fields: nonRequiredFields,
DisclosureGroup(isExpanded: $disclosureGroupOpen) {
FieldsView(
fields: optionalFields,
router: viewModel.router,
config: viewModel.config,
cssInjector: viewModel.cssInjector,
proxy: proxy).padding(.horizontal, 1)
}, label: {
proxy: proxy
)
.padding(.horizontal, 1)
} label: {
Text(disclosureGroupOpen
? AuthLocalization.SignUp.hideFields
: AuthLocalization.SignUp.showFields)
})
}
.accessibilityLabel("optional_fields_text")
.padding(.top, 10)
}


FieldsView(
fields: viewModel.agreementsFields,
router: viewModel.router,
config: viewModel.config,
cssInjector: viewModel.cssInjector,
proxy: proxy
)
.transaction { transaction in
transaction.animation = nil
}

if viewModel.isShowProgress {
HStack(alignment: .center) {
ProgressBar(size: 40, lineWidth: 8)
Expand All @@ -124,7 +141,7 @@ public struct SignUpView: View {
}
viewModel.trackCreateAccountClicked()
}
.padding(.top, 40)
.padding(.top, 30)
.frame(maxWidth: .infinity)
.accessibilityLabel("signup_button")
}
Expand All @@ -145,10 +162,11 @@ public struct SignUpView: View {
.padding(.horizontal, 24)
.padding(.top, 24)

}.roundedBackground(Theme.Colors.background)
.onRightSwipeGesture {
viewModel.router.back()
}
}
.roundedBackground(Theme.Colors.background)
.onRightSwipeGesture {
viewModel.router.back()
}
.scrollAvoidKeyboard(dismissKeyboardByTap: true)
.onChange(of: viewModel.scrollTo, perform: { index in
withAnimation {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,31 @@ public class SignUpViewModel: ObservableObject {
}

@Published var fields: [FieldConfiguration] = []

var requiredFields: [FieldConfiguration] {
fields.filter {
$0.field.required &&
!$0.field.isHonorCode &&
$0.field.type != .checkbox
}
}
var agreementsFields: [FieldConfiguration] {
fields.filter {
$0.field.isHonorCode ||
$0.field.type == .checkbox
}
}
var optionalFields: [FieldConfiguration] {
fields.filter { !$0.field.required }
}

let router: AuthorizationRouter
let config: ConfigProtocol
let cssInjector: CSSInjector

private let interactor: AuthInteractorProtocol
private let analytics: AuthorizationAnalytics
private let validator: Validator

public init(
interactor: AuthInteractorProtocol,
router: AuthorizationRouter,
Expand Down
13 changes: 13 additions & 0 deletions Authorization/Authorization/SwiftGen/Strings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ public enum AuthLocalization {
public static let title = AuthLocalization.tr("Localizable", "FORGOT.TITLE", fallback: "Forgot password")
}
public enum SignIn {
/// By signing in to this app, you agree to the [%@ End User License Agreement](%@) and [%@ Terms of Service and Honor Code](%@) and you acknowledge that %@ and each Member process your personal data in
/// accordance with the [Privacy Policy.](%@)
public static func agreement(_ p1: Any, _ p2: Any, _ p3: Any, _ p4: Any, _ p5: Any, _ p6: Any) -> String {
return AuthLocalization.tr("Localizable", "SIGN_IN.AGREEMENT", String(describing: p1), String(describing: p2), String(describing: p3), String(describing: p4), String(describing: p5), String(describing: p6), fallback: "By signing in to this app, you agree to the [%@ End User License Agreement](%@) and [%@ Terms of Service and Honor Code](%@) and you acknowledge that %@ and each Member process your personal data in\naccordance with the [Privacy Policy.](%@)")
}
/// Email
public static let email = AuthLocalization.tr("Localizable", "SIGN_IN.EMAIL", fallback: "Email")
/// Email or username
Expand All @@ -68,10 +73,18 @@ public enum AuthLocalization {
public static let welcomeBack = AuthLocalization.tr("Localizable", "SIGN_IN.WELCOME_BACK", fallback: "Welcome back! Please authorize to continue.")
}
public enum SignUp {
/// By creating an account, you agree to the [%@ End User License Agreement](%@) and [%@ Terms of Service and Honor Code](%@) and you acknowledge that %@ and each Member process your personal data inaccordance with the [Privacy Policy.](%@)
public static func agreement(_ p1: Any, _ p2: Any, _ p3: Any, _ p4: Any, _ p5: Any, _ p6: Any) -> String {
return AuthLocalization.tr("Localizable", "SIGN_UP.AGREEMENT", String(describing: p1), String(describing: p2), String(describing: p3), String(describing: p4), String(describing: p5), String(describing: p6), fallback: "By creating an account, you agree to the [%@ End User License Agreement](%@) and [%@ Terms of Service and Honor Code](%@) and you acknowledge that %@ and each Member process your personal data inaccordance with the [Privacy Policy.](%@)")
}
/// Create account
public static let createAccountBtn = AuthLocalization.tr("Localizable", "SIGN_UP.CREATE_ACCOUNT_BTN", fallback: "Create account")
/// Hide optional Fields
public static let hideFields = AuthLocalization.tr("Localizable", "SIGN_UP.HIDE_FIELDS", fallback: "Hide optional Fields")
/// I agree that %@ may send me marketing messages.
public static func marketingEmailTitle(_ p1: Any) -> String {
return AuthLocalization.tr("Localizable", "SIGN_UP.MARKETING_EMAIL_TITLE", String(describing: p1), fallback: "I agree that %@ may send me marketing messages.")
}
/// Show optional Fields
public static let showFields = AuthLocalization.tr("Localizable", "SIGN_UP.SHOW_FIELDS", fallback: "Show optional Fields")
/// Create new account.
Expand Down
5 changes: 5 additions & 0 deletions Authorization/Authorization/en.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
"SIGN_IN.EMAIL_OR_USERNAME" = "Email or username";
"SIGN_IN.PASSWORD" = "Password";
"SIGN_IN.FORGOT_PASS_BTN" = "Forgot password?";
"SIGN_IN.AGREEMENT" = "By signing in to this app, you agree to the [%@ End User License Agreement](%@) and [%@ Terms of Service and Honor Code](%@) and you acknowledge that %@ and each Member process your personal data in
accordance with the [Privacy Policy.](%@)";


"ERROR.INVALID_EMAIL_ADDRESS" = "Invalid email address";
"ERROR.INVALID_PASSWORD_LENGHT" = "Invalid password lenght";
Expand All @@ -26,6 +29,8 @@
"SIGN_UP.SHOW_FIELDS" = "Show optional Fields";
"SIGN_UP.SUCCESS_SIGNIN_LABEL" = "You've successfully signed in.";
"SIGN_UP.SUCCESS_SIGNIN_SUBLABEL" = "We just need a little more information before you start learning.";
"SIGN_UP.AGREEMENT" = "By creating an account, you agree to the [%@ End User License Agreement](%@) and [%@ Terms of Service and Honor Code](%@) and you acknowledge that %@ and each Member process your personal data inaccordance with the [Privacy Policy.](%@)";
"SIGN_UP.MARKETING_EMAIL_TITLE" = "I agree that %@ may send me marketing messages.";

"FORGOT.TITLE"= "Forgot password";
"FORGOT.DESCRIPTION" = "Please enter your log-in or recovery email address below and we will send you an email with instructions.";
Expand Down
4 changes: 4 additions & 0 deletions Authorization/Authorization/uk.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
"SIGN_IN.EMAIL" = "Пошта";
"SIGN_IN.PASSWORD" = "Пароль";
"SIGN_IN.FORGOT_PASS_BTN" = "Забули пароль?";
"SIGN_IN.AGREEMENT" = "By signing in to this app, you agree to the [%@ End User License Agreement](%@) and [%@ Terms of Service and Honor Code](%@) and you acknowledge that %@ and each Member process your personal data in
accordance with the [Privacy Policy.](%@)";

"ERROR.INVALID_EMAIL_ADDRESS" = "невірна адреса електронної пошти";
"ERROR.INVALID_PASSWORD_LENGHT" = "Пароль занадто короткий або занадто довгий";
Expand All @@ -24,6 +26,8 @@
"SIGN_UP.SHOW_FIELDS" = "Показати необовʼязкові поля";
"SIGN_UP.SUCCESS_SIGNIN_LABEL" = "You've successfully signed in.";
"SIGN_UP.SUCCESS_SIGNIN_SUBLABEL" = "We just need a little more information before you start learning.";
"SIGN_UP.AGREEMENT" = "By creating an account, you agree to the [%@ End User License Agreement](%@) and [%@ Terms of Service and Honor Code](%@) and you acknowledge that %@ and each Member process your personal data inaccordance with the [Privacy Policy.](%@)";
"SIGN_UP.MARKETING_EMAIL_TITLE" = "I agree that %@ may send me marketing messages.";

"FORGOT.TITLE"= "Відновлення паролю";
"FORGOT.DESCRIPTION" = "Будь ласка, введіть свою адресу електронної пошти для входу або відновлення нижче, і ми надішлемо вам електронний лист з інструкціями.";
Expand Down
Loading

0 comments on commit 48029c9

Please sign in to comment.