Skip to content

Commit 84db186

Browse files
committed
Chapter 15 finished
1 parent 8f1c1b2 commit 84db186

File tree

9 files changed

+119
-9
lines changed

9 files changed

+119
-9
lines changed

Chapter 15/MyProjectClient/iOS/Sources/Modules/Account/AccountInteractor.swift

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,19 @@
66
//
77

88
import Foundation
9+
import Combine
910

10-
final class AccountInteractor: InteractorInterface {
11+
final class AccountInteractor: ServiceInteractor, InteractorInterface {
1112

1213
weak var presenter: AccountPresenterInteractorInterface!
1314
}
1415

1516
extension AccountInteractor: AccountInteractorPresenterInterface {
1617

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+
}
1724
}

Chapter 15/MyProjectClient/iOS/Sources/Modules/Account/AccountModule.swift

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
//
77
import Foundation
88
import UIKit
9+
import Combine
910

1011
// MARK: - router
1112

@@ -26,18 +27,21 @@ protocol AccountPresenterInteractorInterface: PresenterInteractorInterface {
2627
protocol AccountPresenterViewInterface: PresenterViewInterface {
2728
func start()
2829
func close()
30+
func signIn(token: String)
31+
func logout()
2932
}
3033

3134
// MARK: - interactor
3235

3336
protocol AccountInteractorPresenterInterface: InteractorPresenterInterface {
34-
37+
func signIn(token: String) -> AnyPublisher<String, Error>
3538
}
3639

3740
// MARK: - view
3841

3942
protocol AccountViewPresenterInterface: ViewPresenterInterface {
40-
43+
func displayLogin()
44+
func displayLogout()
4145
}
4246

4347

Chapter 15/MyProjectClient/iOS/Sources/Modules/Account/AccountPresenter.swift

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,15 @@
66
//
77

88
import Foundation
9+
import Combine
910

1011
final class AccountPresenter: PresenterInterface {
1112

1213
var router: AccountRouterPresenterInterface!
1314
var interactor: AccountInteractorPresenterInterface!
1415
weak var view: AccountViewPresenterInterface!
1516

17+
var operations: [String: AnyCancellable] = [:]
1618
}
1719

1820
extension AccountPresenter: AccountPresenterRouterInterface {
@@ -26,11 +28,40 @@ extension AccountPresenter: AccountPresenterInteractorInterface {
2628
extension AccountPresenter: AccountPresenterViewInterface {
2729

2830
func start() {
29-
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+
}
3038
}
31-
39+
3240
func close() {
3341
self.router.dismiss()
3442
}
3543

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+
}
3667
}

Chapter 15/MyProjectClient/iOS/Sources/Modules/Account/AccountView.swift

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ final class AccountView: UIViewController, ViewInterface {
1313

1414
var presenter: AccountPresenterViewInterface!
1515
weak var siwaButton: ASAuthorizationAppleIDButton!
16+
weak var logoutButton: UIButton!
1617

1718
override func loadView() {
1819
super.loadView()
@@ -28,6 +29,18 @@ final class AccountView: UIViewController, ViewInterface {
2829
self.siwaButton.bottomAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.bottomAnchor, constant: -70.0),
2930
self.siwaButton.heightAnchor.constraint(equalToConstant: 50.0)
3031
])
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+
])
3144
}
3245

3346
override func viewDidLoad() {
@@ -37,7 +50,9 @@ final class AccountView: UIViewController, ViewInterface {
3750
self.view.backgroundColor = .systemBackground
3851

3952
self.navigationItem.rightBarButtonItem = .init(barButtonSystemItem: .close, target: self, action: #selector(self.close))
40-
53+
54+
self.logoutButton.setTitle("Logout", for: .normal)
55+
self.logoutButton.addTarget(self, action: #selector(self.logout), for: .touchUpInside)
4156
self.siwaButton.addTarget(self, action: #selector(self.siwa), for: .touchUpInside)
4257

4358
self.presenter.start()
@@ -46,7 +61,11 @@ final class AccountView: UIViewController, ViewInterface {
4661
@objc func close() {
4762
self.presenter.close()
4863
}
49-
64+
65+
@objc func logout() {
66+
self.presenter.logout()
67+
}
68+
5069
@objc func siwa() {
5170
let provider = ASAuthorizationAppleIDProvider()
5271
let request = provider.createRequest()
@@ -60,6 +79,15 @@ final class AccountView: UIViewController, ViewInterface {
6079

6180
extension AccountView: AccountViewPresenterInterface {
6281

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+
}
6391
}
6492

6593
extension AccountView: ASAuthorizationControllerPresentationContextProviding {
@@ -79,7 +107,7 @@ extension AccountView: ASAuthorizationControllerDelegate {
79107
else {
80108
return
81109
}
82-
print(token)
110+
self.presenter.signIn(token: token)
83111
}
84112

85113
func authorizationController(controller: ASAuthorizationController,

Chapter 15/MyProjectClient/iOS/Sources/Modules/Root/RootInteractor.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ extension RootInteractor: RootInteractorPresenterInterface {
3030
}
3131
.mapError { $0 as Error }
3232
.eraseToAnyPublisher()
33-
3433
}
3534

3635
}

Chapter 15/MyProjectClient/iOS/Sources/Services/Api/ApiServiceInterface.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,12 @@ struct Page<T: Decodable>: Decodable {
2020
let items: [T]
2121
}
2222

23+
struct UserToken: Decodable {
24+
let value: String
25+
}
26+
2327
protocol ApiServiceInterface: ServiceInterface {
2428

2529
func getBlogPosts() -> AnyPublisher<Page<BlogPostListObject>, HTTP.Error>
30+
func siwa(token: String) -> AnyPublisher<UserToken, HTTP.Error>
2631
}

Chapter 15/MyProjectClient/iOS/Sources/Services/Api/MyProject/MyProjectApiService.swift

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,38 @@ final class MyProjectApiService: ApiServiceInterface {
4545
}
4646
.eraseToAnyPublisher()
4747
}
48+
49+
func siwa(token: String) -> AnyPublisher<UserToken, HTTP.Error> {
50+
struct Body: Codable {
51+
enum CodingKeys: String, CodingKey {
52+
case idToken = "id_token"
53+
}
54+
55+
let idToken: String
56+
}
57+
58+
let url = URL(string: self.baseUrl + "/user/sign-in-with-apple")!
59+
var request = URLRequest(url: url)
60+
request.httpMethod = HTTP.Method.post.rawValue.uppercased()
61+
request.httpBody = try! JSONEncoder().encode(Body(idToken: token))
62+
63+
return URLSession.shared.dataTaskPublisher(for: request)
64+
.tryMap { data, response in
65+
guard let httpResponse = response as? HTTPURLResponse else {
66+
throw HTTP.Error.invalidResponse
67+
}
68+
guard httpResponse.statusCode == 200 else {
69+
throw HTTP.Error.statusCode(httpResponse.statusCode)
70+
}
71+
return data
72+
}
73+
.decode(type: UserToken.self, decoder: JSONDecoder())
74+
.mapError { error -> HTTP.Error in
75+
if let httpError = error as? HTTP.Error {
76+
return httpError
77+
}
78+
return HTTP.Error.unknown(error)
79+
}
80+
.eraseToAnyPublisher()
81+
}
4882
}

Chapter 15/myProject/Sources/App/Extensions/Environment+App.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ extension Environment {
1313
static let awsSecret = Self.get("AWS_SECRET")!
1414

1515
static let siwaId = Self.get("SIWA_ID")!
16+
//...
1617
static let siwaAppId = Self.get("SIWA_APP_ID")!
1718
static let siwaRedirectUrl = Self.get("SIWA_REDIRECT_URL")!
1819
static let siwaTeamId = Self.get("SIWA_TEAM_ID")!

Chapter 15/myProject/Sources/App/Modules/User/UserRouter.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ struct UserRouter: ViperRouter {
2121
api.grouped(UserModelCredentialsAuthenticator())
2222
.post("login", use: self.apiController.login)
2323

24+
//...
2425
routes.post("redirect", use: self.controller.signInWithApple)
2526
api.post("sign-in-with-apple", use: self.apiController.signInWithApple)
2627
}

0 commit comments

Comments
 (0)