Skip to content

Commit df2e4ad

Browse files
committed
Chapter 16 sources
1 parent 50670b0 commit df2e4ad

22 files changed

+320
-23
lines changed

Chapter 16/MyProjectClient/iOS/Assets/MyProject.entitlements

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
33
<plist version="1.0">
44
<dict>
5+
<key>aps-environment</key>
6+
<string>development</string>
57
<key>com.apple.developer.applesignin</key>
68
<array>
79
<string>Default</string>

Chapter 16/MyProjectClient/iOS/Sources/AppDelegate.swift

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,16 @@ import UIKit
1010

1111
@UIApplicationMain
1212
class AppDelegate: UIResponder {
13-
13+
var accountView: AccountView?
1414
}
1515

1616
extension AppDelegate: UIApplicationDelegate {
1717

1818
func application(_ application: UIApplication,
1919
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
20+
21+
application.registerForRemoteNotifications()
22+
2023
return true
2124
}
2225

@@ -26,5 +29,20 @@ extension AppDelegate: UIApplicationDelegate {
2629
return .init(name: "Default Configuration",
2730
sessionRole: connectingSceneSession.role)
2831
}
32+
33+
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
34+
let token = deviceToken.map { String(format: "%02.2hhx", $0) }.joined()
35+
print(token)
36+
UserDefaults.standard.set(token, forKey: "device-token")
37+
UserDefaults.standard.synchronize()
38+
self.accountView = App.shared.modules.account() as? AccountView
39+
self.accountView?.presenter?.registerUserDevice() { [unowned self] in
40+
self.accountView = nil
41+
}
42+
}
43+
44+
func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
45+
print(error)
46+
}
2947
}
3048

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,10 @@ extension AccountInteractor: AccountInteractorPresenterInterface {
2121
.mapError { $0 as Error }
2222
.eraseToAnyPublisher()
2323
}
24+
25+
func register(deviceToken: String, bearerToken: String) -> AnyPublisher<Void, Error> {
26+
self.services.api.register(deviceToken: deviceToken, bearerToken: bearerToken)
27+
.mapError { $0 as Error }
28+
.eraseToAnyPublisher()
29+
}
2430
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,14 @@ protocol AccountPresenterViewInterface: PresenterViewInterface {
2929
func close()
3030
func signIn(token: String)
3131
func logout()
32+
func registerUserDevice(_ block: @escaping (() -> Void))
3233
}
3334

3435
// MARK: - interactor
3536

3637
protocol AccountInteractorPresenterInterface: InteractorPresenterInterface {
3738
func signIn(token: String) -> AnyPublisher<String, Error>
39+
func register(deviceToken: String, bearerToken: String) -> AnyPublisher<Void, Error>
3840
}
3941

4042
// MARK: - view

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,4 +64,24 @@ extension AccountPresenter: AccountPresenterViewInterface {
6464
UserDefaults.standard.synchronize()
6565
self.view.displayLogin()
6666
}
67+
68+
func registerUserDevice(_ block: @escaping (() -> Void)) {
69+
guard
70+
let bearerToken = UserDefaults.standard.string(forKey: "user-token"),
71+
let deviceToken = UserDefaults.standard.string(forKey: "device-token")
72+
else {
73+
return
74+
}
75+
self.operations["device"] = self.interactor.register(deviceToken: deviceToken, bearerToken: bearerToken)
76+
.sink(receiveCompletion: { [weak self] completion in
77+
switch completion {
78+
case .finished:
79+
print("Device registered.")
80+
case .failure(let error):
81+
print(error)
82+
}
83+
self?.operations.removeValue(forKey: "device")
84+
block()
85+
}) { _ in }
86+
}
6787
}

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import Foundation
99
import UIKit
1010
import AuthenticationServices
11+
import UserNotifications
1112

1213
final class AccountView: UIViewController, ViewInterface {
1314

@@ -50,6 +51,7 @@ final class AccountView: UIViewController, ViewInterface {
5051
self.view.backgroundColor = .systemBackground
5152

5253
self.navigationItem.rightBarButtonItem = .init(barButtonSystemItem: .close, target: self, action: #selector(self.close))
54+
self.navigationItem.leftBarButtonItem = .init(barButtonSystemItem: .fastForward, target: self, action: #selector(self.notif))
5355

5456
self.logoutButton.setTitle("Logout", for: .normal)
5557
self.logoutButton.addTarget(self, action: #selector(self.logout), for: .touchUpInside)
@@ -65,6 +67,13 @@ final class AccountView: UIViewController, ViewInterface {
6567
@objc func logout() {
6668
self.presenter.logout()
6769
}
70+
71+
@objc func notif() {
72+
let notif = UNUserNotificationCenter.current()
73+
notif.requestAuthorization(options: [.badge, .sound, .alert]) { granted, error in
74+
// check if granted or error happened
75+
}
76+
}
6877

6978
@objc func siwa() {
7079
let provider = ASAuthorizationAppleIDProvider()

Chapter 16/MyProjectClient/iOS/Sources/Modules/Root/RootView.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -133,12 +133,12 @@ extension RootView: UITableViewDataSource {
133133
cell.coverView.image = defaultImage
134134

135135
self.operations["cell-\(indexPath.row)"]?.cancel()
136-
self.operations["cell-\(indexPath.row)"] = URLSession.shared
137-
.downloadTaskPublisher(for: item.imageUrl)
138-
.map { UIImage(contentsOfFile: $0.url.path) ?? defaultImage }
139-
.replaceError(with: defaultImage)
140-
.receive(on: DispatchQueue.main)
141-
.assign(to: \.image, on: cell.coverView)
136+
// self.operations["cell-\(indexPath.row)"] = URLSession.shared
137+
// .downloadTaskPublisher(for: item.imageUrl)
138+
// .map { UIImage(contentsOfFile: $0.url.path) ?? defaultImage }
139+
// .replaceError(with: defaultImage)
140+
// .receive(on: DispatchQueue.main)
141+
// .assign(to: \.image, on: cell.coverView)
142142

143143
return cell
144144
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,5 @@ protocol ApiServiceInterface: ServiceInterface {
2828

2929
func getBlogPosts() -> AnyPublisher<Page<BlogPostListObject>, HTTP.Error>
3030
func siwa(token: String) -> AnyPublisher<UserToken, HTTP.Error>
31+
func register(deviceToken: String, bearerToken: String) -> AnyPublisher<Void, HTTP.Error>
3132
}

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

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,9 @@ final class MyProjectApiService: ApiServiceInterface {
5858
let url = URL(string: self.baseUrl + "/user/sign-in-with-apple")!
5959
var request = URLRequest(url: url)
6060
request.httpMethod = HTTP.Method.post.rawValue.uppercased()
61+
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
6162
request.httpBody = try! JSONEncoder().encode(Body(idToken: token))
62-
63+
6364
return URLSession.shared.dataTaskPublisher(for: request)
6465
.tryMap { data, response in
6566
guard let httpResponse = response as? HTTPURLResponse else {
@@ -79,4 +80,36 @@ final class MyProjectApiService: ApiServiceInterface {
7980
}
8081
.eraseToAnyPublisher()
8182
}
83+
84+
func register(deviceToken: String, bearerToken: String) -> AnyPublisher<Void, HTTP.Error> {
85+
struct Body: Codable {
86+
let token: String
87+
}
88+
89+
let url = URL(string: self.baseUrl + "/user/devices")!
90+
var request = URLRequest(url: url)
91+
request.httpMethod = HTTP.Method.post.rawValue.uppercased()
92+
request.addValue("Bearer \(bearerToken)", forHTTPHeaderField: "Authorization")
93+
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
94+
request.httpBody = try! JSONEncoder().encode(Body(token: deviceToken))
95+
96+
return URLSession.shared.dataTaskPublisher(for: request)
97+
.tryMap { data, response in
98+
guard let httpResponse = response as? HTTPURLResponse else {
99+
throw HTTP.Error.invalidResponse
100+
}
101+
guard httpResponse.statusCode == 200 else {
102+
throw HTTP.Error.statusCode(httpResponse.statusCode)
103+
}
104+
return ()
105+
}
106+
.mapError { error -> HTTP.Error in
107+
if let httpError = error as? HTTP.Error {
108+
return httpError
109+
}
110+
return HTTP.Error.unknown(error)
111+
}
112+
.eraseToAnyPublisher()
113+
}
114+
82115
}

Chapter 16/MyProjectClient/iOS/Sources/Services/ServiceBuilder.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,6 @@ import Foundation
1111
final class ServiceBuilder: ServiceBuilderInterface {
1212

1313
lazy var api: ApiServiceInterface = {
14-
MyProjectApiService(baseUrl: "http://localhost:8080/api")
14+
MyProjectApiService(baseUrl: "http://192.168.0.129:3000/api")
1515
}()
1616
}

0 commit comments

Comments
 (0)