Skip to content

Commit cbe91cf

Browse files
committed
Chapter 14 updates
1 parent 27d2578 commit cbe91cf

39 files changed

+787
-159
lines changed

Chapter 14/MyProjectClient/MyProjectClient.xcodeproj/project.pbxproj

Lines changed: 158 additions & 124 deletions
Large diffs are not rendered by default.

Chapter 14/MyProjectClient/iOS/Assets/Info.plist

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@
2020
<string>1</string>
2121
<key>LSRequiresIPhoneOS</key>
2222
<true/>
23+
<key>NSAppTransportSecurity</key>
24+
<dict>
25+
<key>NSAllowsArbitraryLoads</key>
26+
<true/>
27+
</dict>
2328
<key>UIApplicationSceneManifest</key>
2429
<dict>
2530
<key>UIApplicationSupportsMultipleScenes</key>
@@ -56,10 +61,5 @@
5661
<string>UIInterfaceOrientationLandscapeLeft</string>
5762
<string>UIInterfaceOrientationLandscapeRight</string>
5863
</array>
59-
<key>NSAppTransportSecurity</key>
60-
<dict>
61-
<key>NSAllowsArbitraryLoads</key>
62-
<true/>
63-
</dict>
6464
</dict>
6565
</plist>
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<plist version="1.0">
4+
<dict>
5+
<key>com.apple.developer.applesignin</key>
6+
<array>
7+
<string>Default</string>
8+
</array>
9+
</dict>
10+
</plist>
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
//
2+
// AccountInteractor.swift
3+
// MyProjectClient
4+
//
5+
// Created by Tibor Bödecs on 2020. 05. 20..
6+
//
7+
8+
import Foundation
9+
import Combine
10+
11+
final class AccountInteractor: ServiceInteractor, InteractorInterface {
12+
13+
weak var presenter: AccountPresenterInteractorInterface!
14+
}
15+
16+
extension AccountInteractor: AccountInteractorPresenterInterface {
17+
18+
func signIn(token: String) -> AnyPublisher<String, Error> {
19+
self.services.api.siwa(token: token)
20+
.map { $0.value }
21+
.mapError { $0 as Error }
22+
.eraseToAnyPublisher()
23+
}
24+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
//
2+
// AccountModule.swift
3+
// MyProjectClient
4+
//
5+
// Created by Tibor Bödecs on 2020. 05. 20..
6+
//
7+
import Foundation
8+
import UIKit
9+
import Combine
10+
11+
// MARK: - router
12+
13+
protocol AccountRouterPresenterInterface: RouterPresenterInterface {
14+
func dismiss()
15+
}
16+
17+
// MARK: - presenter
18+
19+
protocol AccountPresenterRouterInterface: PresenterRouterInterface {
20+
21+
}
22+
23+
protocol AccountPresenterInteractorInterface: PresenterInteractorInterface {
24+
25+
}
26+
27+
protocol AccountPresenterViewInterface: PresenterViewInterface {
28+
func start()
29+
func close()
30+
func signIn(token: String)
31+
func logout()
32+
}
33+
34+
// MARK: - interactor
35+
36+
protocol AccountInteractorPresenterInterface: InteractorPresenterInterface {
37+
func signIn(token: String) -> AnyPublisher<String, Error>
38+
}
39+
40+
// MARK: - view
41+
42+
protocol AccountViewPresenterInterface: ViewPresenterInterface {
43+
func displayLogin()
44+
func displayLogout()
45+
}
46+
47+
48+
// MARK: - module builder
49+
50+
final class AccountModule: ModuleInterface {
51+
52+
typealias View = AccountView
53+
typealias Presenter = AccountPresenter
54+
typealias Router = AccountRouter
55+
typealias Interactor = AccountInteractor
56+
57+
func build() -> UIViewController {
58+
let view = View()
59+
let interactor = Interactor()
60+
let presenter = Presenter()
61+
let router = Router()
62+
63+
self.assemble(view: view, presenter: presenter, router: router, interactor: interactor)
64+
65+
router.viewController = view
66+
67+
return view
68+
}
69+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
//
2+
// AccountPresenter.swift
3+
// MyProjectClient
4+
//
5+
// Created by Tibor Bödecs on 2020. 05. 20..
6+
//
7+
8+
import Foundation
9+
import Combine
10+
11+
final class AccountPresenter: PresenterInterface {
12+
13+
var router: AccountRouterPresenterInterface!
14+
var interactor: AccountInteractorPresenterInterface!
15+
weak var view: AccountViewPresenterInterface!
16+
17+
var operations: [String: AnyCancellable] = [:]
18+
}
19+
20+
extension AccountPresenter: AccountPresenterRouterInterface {
21+
22+
}
23+
24+
extension AccountPresenter: AccountPresenterInteractorInterface {
25+
26+
}
27+
28+
extension AccountPresenter: AccountPresenterViewInterface {
29+
30+
func start() {
31+
let token = UserDefaults.standard.string(forKey: "user-token")
32+
if token == nil {
33+
self.view.displayLogin()
34+
}
35+
else {
36+
self.view.displayLogout()
37+
}
38+
}
39+
40+
func close() {
41+
self.router.dismiss()
42+
}
43+
44+
func signIn(token: String) {
45+
self.operations["siwa"] = self.interactor.signIn(token: token)
46+
.receive(on: DispatchQueue.main)
47+
.sink(receiveCompletion: { [weak self] completion in
48+
switch completion {
49+
case .finished:
50+
break
51+
case .failure(let error):
52+
print(error)
53+
}
54+
self?.operations.removeValue(forKey: "siwa")
55+
}) { [weak self] token in
56+
UserDefaults.standard.set(token, forKey: "user-token")
57+
UserDefaults.standard.synchronize()
58+
self?.view.displayLogout()
59+
}
60+
}
61+
62+
func logout() {
63+
UserDefaults.standard.set(nil, forKey: "user-token")
64+
UserDefaults.standard.synchronize()
65+
self.view.displayLogin()
66+
}
67+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
//
2+
// AccountRouter.swift
3+
// MyProjectClient
4+
//
5+
// Created by Tibor Bödecs on 2020. 05. 20..
6+
//
7+
8+
import Foundation
9+
import UIKit
10+
11+
final class AccountRouter: RouterInterface {
12+
13+
weak var presenter: AccountPresenterRouterInterface!
14+
15+
weak var viewController: UIViewController?
16+
}
17+
18+
extension AccountRouter: AccountRouterPresenterInterface {
19+
20+
func dismiss() {
21+
self.viewController?.dismiss(animated: true)
22+
}
23+
}
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
//
2+
// AccountView.swift
3+
// MyProjectClient
4+
//
5+
// Created by Tibor Bödecs on 2020. 05. 20..
6+
//
7+
8+
import Foundation
9+
import UIKit
10+
import AuthenticationServices
11+
12+
final class AccountView: UIViewController, ViewInterface {
13+
14+
var presenter: AccountPresenterViewInterface!
15+
weak var siwaButton: ASAuthorizationAppleIDButton!
16+
weak var logoutButton: UIButton!
17+
18+
override func loadView() {
19+
super.loadView()
20+
21+
let siwaButton = ASAuthorizationAppleIDButton()
22+
siwaButton.translatesAutoresizingMaskIntoConstraints = false
23+
self.view.addSubview(siwaButton)
24+
self.siwaButton = siwaButton
25+
26+
NSLayoutConstraint.activate([
27+
self.siwaButton.leadingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.leadingAnchor, constant: 50.0),
28+
self.siwaButton.trailingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.trailingAnchor, constant: -50.0),
29+
self.siwaButton.bottomAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.bottomAnchor, constant: -70.0),
30+
self.siwaButton.heightAnchor.constraint(equalToConstant: 50.0)
31+
])
32+
33+
let logoutButton = UIButton(type: .system)
34+
logoutButton.translatesAutoresizingMaskIntoConstraints = false
35+
self.view.addSubview(logoutButton)
36+
self.logoutButton = logoutButton
37+
38+
NSLayoutConstraint.activate([
39+
self.logoutButton.leadingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.leadingAnchor, constant: 50.0),
40+
self.logoutButton.trailingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.trailingAnchor, constant: -50.0),
41+
self.logoutButton.bottomAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.bottomAnchor, constant: -70.0),
42+
self.logoutButton.heightAnchor.constraint(equalToConstant: 50.0)
43+
])
44+
}
45+
46+
override func viewDidLoad() {
47+
super.viewDidLoad()
48+
49+
self.title = "Account"
50+
self.view.backgroundColor = .systemBackground
51+
52+
self.navigationItem.rightBarButtonItem = .init(barButtonSystemItem: .close, target: self, action: #selector(self.close))
53+
54+
self.logoutButton.setTitle("Logout", for: .normal)
55+
self.logoutButton.addTarget(self, action: #selector(self.logout), for: .touchUpInside)
56+
self.siwaButton.addTarget(self, action: #selector(self.siwa), for: .touchUpInside)
57+
58+
self.presenter.start()
59+
}
60+
61+
@objc func close() {
62+
self.presenter.close()
63+
}
64+
65+
@objc func logout() {
66+
self.presenter.logout()
67+
}
68+
69+
@objc func siwa() {
70+
let provider = ASAuthorizationAppleIDProvider()
71+
let request = provider.createRequest()
72+
request.requestedScopes = [.fullName, .email]
73+
let authController = ASAuthorizationController(authorizationRequests: [request])
74+
authController.presentationContextProvider = self
75+
authController.delegate = self
76+
authController.performRequests()
77+
}
78+
}
79+
80+
extension AccountView: AccountViewPresenterInterface {
81+
82+
func displayLogin() {
83+
self.siwaButton.isHidden = false
84+
self.logoutButton.isHidden = true
85+
}
86+
87+
func displayLogout() {
88+
self.siwaButton.isHidden = true
89+
self.logoutButton.isHidden = false
90+
}
91+
}
92+
93+
extension AccountView: ASAuthorizationControllerPresentationContextProviding {
94+
func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor {
95+
return self.view.window!
96+
}
97+
}
98+
99+
extension AccountView: ASAuthorizationControllerDelegate {
100+
101+
func authorizationController(controller: ASAuthorizationController,
102+
didCompleteWithAuthorization authorization: ASAuthorization) {
103+
guard
104+
let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential,
105+
let tokenData = appleIDCredential.identityToken,
106+
let token = String(bytes: tokenData, encoding: .utf8)
107+
else {
108+
return
109+
}
110+
self.presenter.signIn(token: token)
111+
}
112+
113+
func authorizationController(controller: ASAuthorizationController,
114+
didCompleteWithError error: Error) {
115+
guard let error = error as? ASAuthorizationError else {
116+
return
117+
}
118+
switch error.code {
119+
case .canceled:
120+
print("Canceled")
121+
case .invalidResponse:
122+
print("Invalid respone")
123+
case .notHandled:
124+
print("Not handled")
125+
case .failed:
126+
print("Failed")
127+
case .unknown:
128+
print("Unknown")
129+
@unknown default:
130+
print("Default")
131+
}
132+
}
133+
}

Chapter 14/MyProjectClient/iOS/Sources/Modules/ModuleBuilder.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,8 @@ final class ModuleBuilder: ModuleBuilderInterface {
1313
func root() -> UIViewController {
1414
RootModule().build()
1515
}
16+
17+
func account() -> UIViewController {
18+
AccountModule().build()
19+
}
1620
}

Chapter 14/MyProjectClient/iOS/Sources/Modules/ModuleBuilderInterface.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,5 @@ import UIKit
1111
protocol ModuleBuilderInterface {
1212

1313
func root() -> UIViewController
14+
func account() -> UIViewController
1415
}

0 commit comments

Comments
 (0)