Skip to content
Snippets Groups Projects
Commit 9a04121d authored by Bruno Muniz's avatar Bruno Muniz :apple:
Browse files

Implements TermsFeature UI

parent c3e18f86
No related branches found
No related tags found
3 merge requests!71Releasing v1.1.5 (214),!68Terms and condition feature,!67v1.1.5 b(203)
Showing
with 361 additions and 1 deletion
...@@ -28,6 +28,7 @@ let package = Package( ...@@ -28,6 +28,7 @@ let package = Package(
.library(name: "PushFeature", targets: ["PushFeature"]), .library(name: "PushFeature", targets: ["PushFeature"]),
.library(name: "SFTPFeature", targets: ["SFTPFeature"]), .library(name: "SFTPFeature", targets: ["SFTPFeature"]),
.library(name: "CrashService", targets: ["CrashService"]), .library(name: "CrashService", targets: ["CrashService"]),
.library(name: "TermsFeature", targets: ["TermsFeature"]),
.library(name: "Presentation", targets: ["Presentation"]), .library(name: "Presentation", targets: ["Presentation"]),
.library(name: "ToastFeature", targets: ["ToastFeature"]), .library(name: "ToastFeature", targets: ["ToastFeature"]),
.library(name: "BackupFeature", targets: ["BackupFeature"]), .library(name: "BackupFeature", targets: ["BackupFeature"]),
...@@ -150,6 +151,7 @@ let package = Package( ...@@ -150,6 +151,7 @@ let package = Package(
.target(name: "MenuFeature"), .target(name: "MenuFeature"),
.target(name: "PushFeature"), .target(name: "PushFeature"),
.target(name: "SFTPFeature"), .target(name: "SFTPFeature"),
.target(name: "TermsFeature"),
.target(name: "ToastFeature"), .target(name: "ToastFeature"),
.target(name: "CrashService"), .target(name: "CrashService"),
.target(name: "BackupFeature"), .target(name: "BackupFeature"),
...@@ -507,6 +509,14 @@ let package = Package( ...@@ -507,6 +509,14 @@ let package = Package(
.target(name: "DependencyInjection"), .target(name: "DependencyInjection"),
] ]
), ),
.target(
name: "TermsFeature",
dependencies: [
.target(name: "Theme"),
.target(name: "Shared"),
.target(name: "Defaults")
]
),
.target( .target(
name: "RequestsFeature", name: "RequestsFeature",
dependencies: [ dependencies: [
......
...@@ -34,6 +34,7 @@ import DependencyInjection ...@@ -34,6 +34,7 @@ import DependencyInjection
import ScanFeature import ScanFeature
import ChatFeature import ChatFeature
import MenuFeature import MenuFeature
import TermsFeature
import BackupFeature import BackupFeature
import SearchFeature import SearchFeature
import LaunchFeature import LaunchFeature
...@@ -111,8 +112,16 @@ struct DependencyRegistrator { ...@@ -111,8 +112,16 @@ struct DependencyRegistrator {
// MARK: Coordinators // MARK: Coordinators
container.register(
TermsCoordinator.live(
usernameFactory: OnboardingUsernameController.init(_:),
chatListFactory: ChatListController.init
)
)
container.register( container.register(
LaunchCoordinator( LaunchCoordinator(
termsFactory: TermsConditionsController.init(_:),
searchFactory: SearchContainerController.init, searchFactory: SearchContainerController.init,
requestsFactory: RequestsContainerController.init, requestsFactory: RequestsContainerController.init,
chatListFactory: ChatListController.init, chatListFactory: ChatListController.init,
...@@ -206,6 +215,7 @@ struct DependencyRegistrator { ...@@ -206,6 +215,7 @@ struct DependencyRegistrator {
searchFactory: SearchContainerController.init, searchFactory: SearchContainerController.init,
welcomeFactory: OnboardingWelcomeController.init, welcomeFactory: OnboardingWelcomeController.init,
chatListFactory: ChatListController.init, chatListFactory: ChatListController.init,
termsFactory: TermsConditionsController.init(_:),
usernameFactory: OnboardingUsernameController.init(_:), usernameFactory: OnboardingUsernameController.init(_:),
restoreListFactory: RestoreListController.init(_:), restoreListFactory: RestoreListController.init(_:),
successFactory: OnboardingSuccessController.init(_:), successFactory: OnboardingSuccessController.init(_:),
......
...@@ -21,6 +21,7 @@ public enum Key: String { ...@@ -21,6 +21,7 @@ public enum Key: String {
// MARK: General // MARK: General
case theme case theme
case acceptedTerms
// MARK: Requests // MARK: Requests
......
...@@ -5,6 +5,7 @@ import Presentation ...@@ -5,6 +5,7 @@ import Presentation
public protocol LaunchCoordinating { public protocol LaunchCoordinating {
func toChats(from: UIViewController) func toChats(from: UIViewController)
func toTerms(from: UIViewController)
func toRequests(from: UIViewController) func toRequests(from: UIViewController)
func toSearch(searching: String, from: UIViewController) func toSearch(searching: String, from: UIViewController)
func toOnboarding(with: String, from: UIViewController) func toOnboarding(with: String, from: UIViewController)
...@@ -15,6 +16,7 @@ public protocol LaunchCoordinating { ...@@ -15,6 +16,7 @@ public protocol LaunchCoordinating {
public struct LaunchCoordinator: LaunchCoordinating { public struct LaunchCoordinator: LaunchCoordinating {
var replacePresenter: Presenting = ReplacePresenter() var replacePresenter: Presenting = ReplacePresenter()
var termsFactory: (String?) -> UIViewController
var searchFactory: (String) -> UIViewController var searchFactory: (String) -> UIViewController
var requestsFactory: () -> UIViewController var requestsFactory: () -> UIViewController
var chatListFactory: () -> UIViewController var chatListFactory: () -> UIViewController
...@@ -23,6 +25,7 @@ public struct LaunchCoordinator: LaunchCoordinating { ...@@ -23,6 +25,7 @@ public struct LaunchCoordinator: LaunchCoordinating {
var groupChatFactory: (GroupInfo) -> UIViewController var groupChatFactory: (GroupInfo) -> UIViewController
public init( public init(
termsFactory: @escaping (String?) -> UIViewController,
searchFactory: @escaping (String) -> UIViewController, searchFactory: @escaping (String) -> UIViewController,
requestsFactory: @escaping () -> UIViewController, requestsFactory: @escaping () -> UIViewController,
chatListFactory: @escaping () -> UIViewController, chatListFactory: @escaping () -> UIViewController,
...@@ -30,6 +33,7 @@ public struct LaunchCoordinator: LaunchCoordinating { ...@@ -30,6 +33,7 @@ public struct LaunchCoordinator: LaunchCoordinating {
singleChatFactory: @escaping (Contact) -> UIViewController, singleChatFactory: @escaping (Contact) -> UIViewController,
groupChatFactory: @escaping (GroupInfo) -> UIViewController groupChatFactory: @escaping (GroupInfo) -> UIViewController
) { ) {
self.termsFactory = termsFactory
self.searchFactory = searchFactory self.searchFactory = searchFactory
self.requestsFactory = requestsFactory self.requestsFactory = requestsFactory
self.chatListFactory = chatListFactory self.chatListFactory = chatListFactory
...@@ -46,6 +50,11 @@ public extension LaunchCoordinator { ...@@ -46,6 +50,11 @@ public extension LaunchCoordinator {
replacePresenter.present(chatListScreen, screen, from: parent) replacePresenter.present(chatListScreen, screen, from: parent)
} }
func toTerms(from parent: UIViewController) {
let screen = termsFactory(nil)
replacePresenter.present(screen, from: parent)
}
func toChats(from parent: UIViewController) { func toChats(from parent: UIViewController) {
let screen = chatListFactory() let screen = chatListFactory()
replacePresenter.present(screen, from: parent) replacePresenter.present(screen, from: parent)
......
...@@ -52,7 +52,7 @@ public final class OnboardingStartController: UIViewController { ...@@ -52,7 +52,7 @@ public final class OnboardingStartController: UIViewController {
super.viewDidLoad() super.viewDidLoad()
screenView.startButton.publisher(for: .touchUpInside) screenView.startButton.publisher(for: .touchUpInside)
.sink { [unowned self] in coordinator.toUsername(with: ndf, from: self) } .sink { [unowned self] in coordinator.toTerms(ndf: ndf, from: self) }
.store(in: &cancellables) .store(in: &cancellables)
} }
} }
...@@ -36,6 +36,7 @@ public final class OnboardingUsernameController: UIViewController { ...@@ -36,6 +36,7 @@ public final class OnboardingUsernameController: UIViewController {
public override func viewDidLoad() { public override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
setupNavigationBar()
setupScrollView() setupScrollView()
setupBindings() setupBindings()
...@@ -48,6 +49,26 @@ public final class OnboardingUsernameController: UIViewController { ...@@ -48,6 +49,26 @@ public final class OnboardingUsernameController: UIViewController {
} }
} }
private func setupNavigationBar() {
navigationItem.backButtonTitle = ""
let backButton = UIButton()
backButton.setImage(Asset.navigationBarBack.image, for: .normal)
backButton.tintColor = Asset.neutralActive.color
backButton.imageView?.contentMode = .center
backButton.snp.makeConstraints { $0.width.equalTo(50) }
backButton
.publisher(for: .touchUpInside)
.receive(on: DispatchQueue.main)
.sink { [unowned self] in
navigationController?.popViewController(animated: true)
}.store(in: &cancellables)
navigationItem.leftBarButtonItem = UIBarButtonItem(
customView: UIStackView(arrangedSubviews: [backButton])
)
}
private func setupScrollView() { private func setupScrollView() {
scrollViewController.scrollView.backgroundColor = .white scrollViewController.scrollView.backgroundColor = .white
......
...@@ -11,6 +11,7 @@ public protocol OnboardingCoordinating { ...@@ -11,6 +11,7 @@ public protocol OnboardingCoordinating {
func toEmail(from: UIViewController) func toEmail(from: UIViewController)
func toPhone(from: UIViewController) func toPhone(from: UIViewController)
func toWelcome(from: UIViewController) func toWelcome(from: UIViewController)
func toTerms(ndf: String, from: UIViewController)
func toUsername(with: String, from: UIViewController) func toUsername(with: String, from: UIViewController)
func toRestoreList(with: String, from: UIViewController) func toRestoreList(with: String, from: UIViewController)
func toDrawer(_: UIViewController, from: UIViewController) func toDrawer(_: UIViewController, from: UIViewController)
...@@ -46,6 +47,7 @@ public struct OnboardingCoordinator: OnboardingCoordinating { ...@@ -46,6 +47,7 @@ public struct OnboardingCoordinator: OnboardingCoordinating {
var chatListFactory: () -> UIViewController var chatListFactory: () -> UIViewController
var usernameFactory: (String) -> UIViewController var usernameFactory: (String) -> UIViewController
var restoreListFactory: (String) -> UIViewController var restoreListFactory: (String) -> UIViewController
var termsFactory: (String?) -> UIViewController
var successFactory: (OnboardingSuccessModel) -> UIViewController var successFactory: (OnboardingSuccessModel) -> UIViewController
var countriesFactory: (@escaping (Country) -> Void) -> UIViewController var countriesFactory: (@escaping (Country) -> Void) -> UIViewController
var phoneConfirmationFactory: (AttributeConfirmation, @escaping AttributeControllerClosure) -> UIViewController var phoneConfirmationFactory: (AttributeConfirmation, @escaping AttributeControllerClosure) -> UIViewController
...@@ -57,6 +59,7 @@ public struct OnboardingCoordinator: OnboardingCoordinating { ...@@ -57,6 +59,7 @@ public struct OnboardingCoordinator: OnboardingCoordinating {
searchFactory: @escaping (String?) -> UIViewController, searchFactory: @escaping (String?) -> UIViewController,
welcomeFactory: @escaping () -> UIViewController, welcomeFactory: @escaping () -> UIViewController,
chatListFactory: @escaping () -> UIViewController, chatListFactory: @escaping () -> UIViewController,
termsFactory: @escaping (String?) -> UIViewController,
usernameFactory: @escaping (String) -> UIViewController, usernameFactory: @escaping (String) -> UIViewController,
restoreListFactory: @escaping (String) -> UIViewController, restoreListFactory: @escaping (String) -> UIViewController,
successFactory: @escaping (OnboardingSuccessModel) -> UIViewController, successFactory: @escaping (OnboardingSuccessModel) -> UIViewController,
...@@ -65,6 +68,7 @@ public struct OnboardingCoordinator: OnboardingCoordinating { ...@@ -65,6 +68,7 @@ public struct OnboardingCoordinator: OnboardingCoordinating {
emailConfirmationFactory: @escaping (AttributeConfirmation, @escaping AttributeControllerClosure) -> UIViewController emailConfirmationFactory: @escaping (AttributeConfirmation, @escaping AttributeControllerClosure) -> UIViewController
) { ) {
self.emailFactory = emailFactory self.emailFactory = emailFactory
self.termsFactory = termsFactory
self.phoneFactory = phoneFactory self.phoneFactory = phoneFactory
self.searchFactory = searchFactory self.searchFactory = searchFactory
self.welcomeFactory = welcomeFactory self.welcomeFactory = welcomeFactory
...@@ -79,6 +83,14 @@ public struct OnboardingCoordinator: OnboardingCoordinating { ...@@ -79,6 +83,14 @@ public struct OnboardingCoordinator: OnboardingCoordinating {
} }
public extension OnboardingCoordinator { public extension OnboardingCoordinator {
func toTerms(
ndf: String,
from parent: UIViewController
) {
let screen = termsFactory(ndf)
pushPresenter.present(screen, from: parent)
}
func toEmail(from parent: UIViewController) { func toEmail(from parent: UIViewController) {
let screen = emailFactory() let screen = emailFactory()
replacePresenter.present(screen, from: parent) replacePresenter.present(screen, from: parent)
......
...@@ -1224,6 +1224,17 @@ public enum Localized { ...@@ -1224,6 +1224,17 @@ public enum Localized {
} }
} }
public enum Terms {
/// Accept and proceed
public static let accept = Localized.tr("Localizable", "terms.accept")
/// By enabling the checkbox on the left, you agree with the terms and conditions.
public static let radio = Localized.tr("Localizable", "terms.radio")
/// Show terms and conditions
public static let show = Localized.tr("Localizable", "terms.show")
/// Terms #&# Conditions
public static let title = Localized.tr("Localizable", "terms.title")
}
public enum Ud { public enum Ud {
/// There are no users with that %@. /// There are no users with that %@.
public static func noneFound(_ p1: Any) -> String { public static func noneFound(_ p1: Any) -> String {
......
...@@ -646,6 +646,17 @@ ...@@ -646,6 +646,17 @@
"backup.SFTP" "backup.SFTP"
= "SFTP"; = "SFTP";
// Terms & Conditions
"terms.title"
= "Terms #&# Conditions";
"terms.radio"
= "By enabling the checkbox on the left, you agree with the terms and conditions.";
"terms.accept"
= "Accept and proceed";
"terms.show"
= "Show terms and conditions";
// Settings - Delete Account // Settings - Delete Account
"settings.delete.title" "settings.delete.title"
......
import UIKit
import Shared
final class RadioButton: UIControl {
private let filledView = UIView()
private let containerView = UIView()
init() {
super.init(frame: .zero)
containerView.layer.borderWidth = 1
containerView.layer.cornerRadius = 15
containerView.layer.masksToBounds = true
containerView.layer.borderColor = UIColor.gray.cgColor
filledView.isHidden = true
filledView.layer.cornerRadius = 10
filledView.layer.masksToBounds = true
filledView.backgroundColor = Asset.brandPrimary.color
containerView.isUserInteractionEnabled = false
filledView.isUserInteractionEnabled = false
addSubview(containerView)
containerView.addSubview(filledView)
setupConstraints()
}
required init?(coder: NSCoder) { nil }
func set(enabled: Bool) {
filledView.isHidden = !enabled
}
private func setupConstraints() {
containerView.snp.makeConstraints {
$0.width.equalTo(30)
$0.height.equalTo(30)
$0.top.equalToSuperview().offset(5)
$0.left.equalToSuperview().offset(5)
$0.right.equalToSuperview().offset(-5)
$0.bottom.equalToSuperview().offset(-5)
}
filledView.snp.makeConstraints {
$0.top.equalToSuperview().offset(5)
$0.left.equalToSuperview().offset(5)
$0.right.equalToSuperview().offset(-5)
$0.bottom.equalToSuperview().offset(-5)
}
}
}
import UIKit
import Shared
final class RadioTextComponent: UIView {
let titleLabel = UILabel()
let radioButton = RadioButton()
var isEnabled: Bool = false {
didSet { radioButton.set(enabled: isEnabled) }
}
init() {
super.init(frame: .zero)
titleLabel.numberOfLines = 0
titleLabel.textColor = Asset.neutralBody.color
titleLabel.font = Fonts.Mulish.regular.font(size: 13.0)
addSubview(titleLabel)
addSubview(radioButton)
setupConstraints()
}
required init?(coder: NSCoder) { nil }
private func setupConstraints() {
titleLabel.snp.makeConstraints {
$0.left.equalTo(radioButton.snp.right).offset(7)
$0.centerY.equalTo(radioButton)
$0.right.equalToSuperview()
}
radioButton.snp.makeConstraints {
$0.left.equalToSuperview()
$0.top.greaterThanOrEqualToSuperview()
$0.bottom.equalToSuperview()
}
}
}
import UIKit
import Theme
import Shared
import Combine
import Defaults
import DependencyInjection
public final class TermsConditionsController: UIViewController {
@Dependency var coordinator: TermsCoordinator
@Dependency var statusBarController: StatusBarStyleControlling
@KeyObject(.acceptedTerms, defaultValue: false) var didAcceptTerms: Bool
lazy private var screenView = TermsConditionsView()
private let ndf: String?
private var cancellables = Set<AnyCancellable>()
public init(_ ndf: String?) {
self.ndf = ndf
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) { nil }
public override func loadView() {
view = screenView
}
public override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
statusBarController.style.send(.darkContent)
navigationController?.navigationBar.customize(translucent: true)
}
public override func viewDidLoad() {
super.viewDidLoad()
navigationItem.backButtonTitle = ""
let backButton = UIButton()
backButton.setImage(Asset.navigationBarBack.image, for: .normal)
backButton.tintColor = Asset.neutralActive.color
backButton.imageView?.contentMode = .center
backButton.snp.makeConstraints { $0.width.equalTo(50) }
backButton
.publisher(for: .touchUpInside)
.receive(on: DispatchQueue.main)
.sink { [unowned self] in
navigationController?.popViewController(animated: true)
}.store(in: &cancellables)
navigationItem.leftBarButtonItem = UIBarButtonItem(
customView: UIStackView(arrangedSubviews: [backButton])
)
screenView.radioComponent
.radioButton.publisher(for: .touchUpInside)
.receive(on: DispatchQueue.main)
.sink { [unowned self] in
screenView.radioComponent.isEnabled.toggle()
screenView.nextButton.isEnabled = screenView.radioComponent.isEnabled
}.store(in: &cancellables)
screenView.nextButton
.publisher(for: .touchUpInside)
.receive(on: DispatchQueue.main)
.sink { [unowned self] in
didAcceptTerms = true
if let ndf = ndf {
coordinator.presentUsername(ndf, self)
} else {
coordinator.presentChatList(self)
}
}.store(in: &cancellables)
screenView.showTermsButton
.publisher(for: .touchUpInside)
.receive(on: DispatchQueue.main)
.sink { _ in
// TODO
}.store(in: &cancellables)
}
}
import UIKit
import Shared
final class TermsConditionsView: UIView {
let titleLabel = UILabel()
let nextButton = CapsuleButton()
let showTermsButton = CapsuleButton()
let radioComponent = RadioTextComponent()
init() {
super.init(frame: .zero)
backgroundColor = Asset.neutralWhite.color
let attString = NSMutableAttributedString(string: Localized.Terms.title)
let paragraph = NSMutableParagraphStyle()
paragraph.alignment = .left
paragraph.lineHeightMultiple = 1.15
attString.addAttribute(.paragraphStyle, value: paragraph)
attString.addAttribute(.foregroundColor, value: Asset.neutralActive.color)
attString.addAttribute(.font, value: Fonts.Mulish.bold.font(size: 34.0) as Any)
attString.addAttributes(attributes: [
.font: Fonts.Mulish.bold.font(size: 34.0) as Any,
.foregroundColor: Asset.brandPrimary.color
], betweenCharacters: "#")
titleLabel.numberOfLines = 0
titleLabel.attributedText = attString
radioComponent.titleLabel.text = Localized.Terms.radio
nextButton.isEnabled = false
nextButton.set(style: .brandColored, title: Localized.Terms.accept)
showTermsButton.set(style: .seeThrough, title: Localized.Terms.show)
addSubview(titleLabel)
addSubview(nextButton)
addSubview(radioComponent)
addSubview(showTermsButton)
setupConstraints()
}
required init?(coder: NSCoder) { nil }
private func setupConstraints() {
titleLabel.snp.makeConstraints {
$0.top.equalTo(safeAreaLayoutGuide).offset(30)
$0.left.equalToSuperview().offset(38)
$0.right.equalToSuperview().offset(-44)
}
radioComponent.snp.makeConstraints {
$0.left.equalToSuperview().offset(40)
$0.right.equalToSuperview().offset(-40)
$0.bottom.equalTo(nextButton.snp.top).offset(-20)
}
nextButton.snp.makeConstraints {
$0.left.equalToSuperview().offset(40)
$0.right.equalToSuperview().offset(-40)
$0.bottom.equalTo(showTermsButton.snp.top).offset(-10)
}
showTermsButton.snp.makeConstraints {
$0.left.equalToSuperview().offset(40)
$0.right.equalToSuperview().offset(-40)
$0.bottom.equalTo(safeAreaLayoutGuide).offset(-40)
}
}
}
import UIKit
import Presentation
public struct TermsCoordinator {
var presentChatList: (UIViewController) -> Void
var presentUsername: (String, UIViewController) -> Void
}
public extension TermsCoordinator {
static func live(
usernameFactory: @escaping (String) -> UIViewController,
chatListFactory: @escaping () -> UIViewController
) -> Self {
.init(
presentChatList: { parent in
let presenter = ReplacePresenter()
presenter.present(chatListFactory(), from: parent)
},
presentUsername: { ndf, parent in
let presenter = PushPresenter()
presenter.present(usernameFactory(ndf), from: parent)
}
)
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment