Skip to content

Commit 01eaf64

Browse files
committed
Async/await refactoring
1 parent 5164edc commit 01eaf64

36 files changed

+1099
-843
lines changed

ForPDA.xcodeproj/project.pbxproj

Lines changed: 106 additions & 34 deletions
Large diffs are not rendered by default.

ForPDA/Sources/Application/AppDelegate.swift

Lines changed: 3 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import RouteComposer
1717
class AppDelegate: UIResponder, UIApplicationDelegate {
1818

1919
@Injected(\.settingsService) private var settingsService
20+
@Injected(\.cookiesService) private var cookiesService
2021

2122
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
2223

@@ -27,13 +28,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
2728

2829
return true
2930
}
30-
31-
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
32-
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
33-
}
34-
35-
func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) { }
36-
3731
}
3832

3933
// MARK: - Configuration
@@ -68,28 +62,7 @@ extension AppDelegate {
6862

6963
// MARK: Cookies
7064

71-
func configureCookies() {
72-
// We need 3 cookies to authorize: anonymous, member_id, pass_hash
73-
// There's also __fixmid cookie, but it lasts for a second and then removed
74-
if let cookies = HTTPCookieStorage.shared.cookies, cookies.count < 3 {
75-
// Getting saved cookies if present
76-
if let cookiesData = settingsService.getCookiesAsData() {
77-
// Decoding custom Cookie class since HTTPCookie doesn't support Codable
78-
if let cookies = try? JSONDecoder().decode([Cookie].self, from: cookiesData) {
79-
// Force-casting Cookie to HTTPCookie and setting them for 4pda.to domain
80-
let mappedCookies = cookies.map { $0.httpCookie! }
81-
HTTPCookieStorage.shared.setCookies(mappedCookies, for: URL.fourpda, mainDocumentURL: nil)
82-
// WKWebView cookies are added in SceneDelegate
83-
} else {
84-
// Deleting saved cookies in defaults if we can't decode them and logout
85-
settingsService.removeCookies()
86-
settingsService.removeUser()
87-
}
88-
} else {
89-
// Deleting all cookies in case we don't have them saved to prevent different sources of truth and logout
90-
settingsService.removeCookies()
91-
settingsService.removeUser()
92-
}
93-
}
65+
private func configureCookies() {
66+
cookiesService.configureCookies()
9467
}
9568
}

ForPDA/Sources/Application/SceneDelegate.swift

Lines changed: 24 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -13,33 +13,49 @@ import WebKit
1313
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
1414

1515
@Injected(\.settingsService) private var settingsService
16+
@Injected(\.cookiesService) private var cookiesService
1617

1718
var window: UIWindow?
1819

19-
var webView: WKWebView!
20+
var webView: WKWebView = {
21+
let config = WKWebViewConfiguration()
22+
let webView = WKWebView(frame: .zero, configuration: config)
23+
webView.tag = 666
24+
return webView
25+
}()
2026

2127
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
2228
guard let windowScene = (scene as? UIWindowScene) else { return }
2329
self.window = UIWindow(windowScene: windowScene)
2430

2531
window?.rootViewController = UIViewController()
32+
window?.addSubview(webView)
2633
window?.makeKeyAndVisible()
2734

2835
overrideApplicationThemeStyle(with: settingsService.getAppTheme())
29-
configureWKWebView()
30-
31-
if let url = connectionOptions.urlContexts.first?.url {
36+
cookiesService.syncCookies()
37+
handleNavigation(to: connectionOptions.urlContexts.first?.url)
38+
}
39+
}
40+
41+
extension SceneDelegate {
42+
43+
// MARK: - Navigation
44+
45+
private func handleNavigation(to url: URL?) {
46+
if let url {
3247
// Cold start deeplink
3348
handleDeeplinkUrl(url)
3449
} else {
50+
// Handle 'try?' later (todo)
51+
// handleDeeplinkUrl(URL(string: "forpda://article/2023/10/21/419630/")!)
3552
try? DefaultRouter().navigate(to: RouteMap.newsScreen, with: nil)
3653
}
3754
}
3855

3956
private func handleDeeplinkUrl(_ url: URL) {
4057
if url.absoluteString == "forpda://article//" {
41-
// Если share в браузере не сработал, то открываем новости
42-
// todo обработать нормально
58+
// If share didn't work in browser, fallback to news (also bug with share inside app) (todo)
4359
try? DefaultRouter().navigate(to: RouteMap.newsScreen, with: nil)
4460
} else {
4561
settingsService.setIsDeeplinking(to: true)
@@ -50,36 +66,15 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
5066
}
5167
}
5268

53-
private func configureWKWebView() {
54-
let config = WKWebViewConfiguration()
55-
webView = WKWebView(frame: .zero, configuration: config)
56-
webView.tag = 666
57-
window?.addSubview(webView)
58-
59-
for cookie in HTTPCookieStorage.shared.cookies ?? [] {
60-
WKWebsiteDataStore.default().httpCookieStore.setCookie(cookie)
61-
}
62-
}
63-
6469
// Opens on existing scene
6570
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
6671
if let url = URLContexts.first?.url {
6772
handleDeeplinkUrl(url)
6873
}
6974
}
7075

71-
func sceneDidDisconnect(_ scene: UIScene) { }
72-
73-
func sceneDidBecomeActive(_ scene: UIScene) { }
74-
75-
func sceneWillResignActive(_ scene: UIScene) { }
76-
77-
func sceneWillEnterForeground(_ scene: UIScene) { }
78-
79-
func sceneDidEnterBackground(_ scene: UIScene) { }
80-
}
81-
82-
extension SceneDelegate {
76+
// MARK: - Themes
77+
8378
func overrideApplicationThemeStyle(with theme: AppTheme) {
8479
window?.overrideUserInterfaceStyle = UIUserInterfaceStyle(rawValue: theme.ordinal())!
8580
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
//
2+
// UIApplication+Ext.swift
3+
// ForPDA
4+
//
5+
// Created by Ilia Lubianoi on 20.10.2023.
6+
//
7+
8+
import UIKit
9+
10+
extension UIApplication {
11+
12+
var keyWindow: UIWindow? {
13+
// Get connected scenes
14+
return self.connectedScenes
15+
// Keep only active scenes, onscreen and visible to the user
16+
.filter { $0.activationState == .foregroundActive }
17+
// Keep only the first `UIWindowScene`
18+
.first(where: { $0 is UIWindowScene })
19+
// Get its associated windows
20+
.flatMap({ $0 as? UIWindowScene })?.windows
21+
// Finally, keep only the key window
22+
.first(where: \.isKeyWindow)
23+
}
24+
}

ForPDA/Sources/Models/MultipartFormDataRequest.swift

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,14 @@
77

88
import Foundation
99

10+
// Refactor this struct (todo)
11+
1012
struct MultipartFormDataRequest {
1113
private let boundary: String = UUID().uuidString
1214
private var httpBody = NSMutableData()
13-
let url: URL
15+
let url: URL?
1416

15-
init(url: URL) {
17+
init(url: URL? = nil) {
1618
self.url = url
1719
}
1820

@@ -51,7 +53,7 @@ struct MultipartFormDataRequest {
5153
}
5254

5355
func asURLRequest() -> URLRequest {
54-
var request = URLRequest(url: url)
56+
var request = URLRequest(url: url!)
5557

5658
request.httpMethod = "POST"
5759
request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
@@ -60,6 +62,16 @@ struct MultipartFormDataRequest {
6062
request.httpBody = httpBody as Data
6163
return request
6264
}
65+
66+
func modifyRequest(_ request: inout URLRequest, with multipart: [String: String]) {
67+
for (key, value) in multipart {
68+
addTextField(named: key, value: value)
69+
}
70+
71+
request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
72+
httpBody.append("--\(boundary)--")
73+
request.httpBody = httpBody as Data
74+
}
6375
}
6476

6577
extension NSMutableData {

ForPDA/Sources/Models/User.swift

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,7 @@
77

88
import Foundation
99

10-
protocol UserDefaultsSerializable: Codable {}
11-
12-
struct User: UserDefaultsSerializable {
10+
struct User: Codable {
1311
let id: String
1412
let avatarUrl: String
1513
let nickname: String

ForPDA/Sources/Modules/Article/ArticleBuilder.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,8 @@ final class ArticleBuilder {
172172
button.backgroundColor = .systemBlue
173173
button.layer.cornerRadius = 10
174174
button.clipsToBounds = true
175-
button.contentEdgeInsets = UIEdgeInsets(top: 0, left: 8, bottom: 0, right: 8)
175+
// Remake with configuration (todo)
176+
// button.contentEdgeInsets = UIEdgeInsets(top: 0, left: 8, bottom: 0, right: 8)
176177

177178
let container = UIView()
178179
container.addSubview(button)

ForPDA/Sources/Modules/Article/ArticlePresenter.swift

Lines changed: 31 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -11,56 +11,58 @@ import Factory
1111
protocol ArticlePresenterProtocol {
1212
var article: Article { get }
1313

14-
func loadArticle()
14+
func loadArticle() async
15+
func updateComments() async
1516
}
1617

1718
final class ArticlePresenter: ArticlePresenterProtocol {
1819

1920
// MARK: - Properties
2021

21-
@Injected(\.networkService) private var networkService
22+
@Injected(\.newsService) private var newsService
2223
@Injected(\.parsingService) private var parsingService
2324

2425
weak var view: ArticleVCProtocol?
2526

2627
var article: Article
28+
private var path: [String] = []
2729

2830
// MARK: - Init
2931

3032
init(article: Article) {
3133
self.article = article
34+
self.path = URL(string: article.url)?.pathComponents ?? []
3235
}
3336

34-
// MARK: - Functions
37+
// MARK: - Public Functions
3538

36-
func loadArticle() {
37-
guard let path = URL(string: article.url)?.pathComponents else {
39+
@MainActor
40+
func loadArticle() async {
41+
do {
42+
let response = try await newsService.article(path: path)
43+
44+
// Deeplink case
45+
if article.info == nil {
46+
let articleInfo = parsingService.parseArticleInfo(from: response)
47+
article.info = articleInfo
48+
view?.reconfigureHeader()
49+
}
50+
51+
let elements = parsingService.parseArticle(from: response)
52+
view?.configureArticle(with: elements)
53+
view?.makeComments(from: response)
54+
} catch {
3855
view?.showError()
39-
return
4056
}
41-
42-
networkService.getArticle(path: path) { [weak self] result in
43-
guard let self else { return }
44-
switch result {
45-
case .success(let response):
46-
// Deeplink case
47-
if article.info == nil {
48-
let articleInfo = parsingService.parseArticleInfo(from: response)
49-
article.info = articleInfo
50-
DispatchQueue.main.async {
51-
self.view?.reconfigureHeader()
52-
}
53-
}
54-
55-
let elements = parsingService.parseArticle(from: response)
56-
DispatchQueue.main.async {
57-
self.view?.configureArticle(with: elements)
58-
self.view?.makeComments(from: response)
59-
}
60-
61-
case .failure:
62-
view?.showError()
63-
}
57+
}
58+
59+
@MainActor
60+
func updateComments() async {
61+
do {
62+
let response = try await newsService.article(path: path)
63+
view?.updateComments(with: response)
64+
} catch {
65+
view?.showError()
6466
}
6567
}
6668
}

ForPDA/Sources/Modules/Article/ArticleVC.swift

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ protocol ArticleVCProtocol: AnyObject {
1616
func configureArticle(with elements: [ArticleElement])
1717
func reconfigureHeader()
1818
func makeComments(from page: String)
19+
func updateComments(with document: String)
1920
func showError()
2021
}
2122

@@ -45,8 +46,11 @@ final class ArticleVC: PDAViewController<ArticleView> {
4546
configureView()
4647
myView.delegate = self
4748

49+
// What's this if/else for? (todo)
4850
if presenter.article.url.contains("to/20") {
49-
presenter.loadArticle()
51+
Task {
52+
await presenter.loadArticle()
53+
}
5054
} else {
5155
myView.removeComments()
5256
let elements = ArticleBuilder.makeDefaultArticle(
@@ -198,27 +202,26 @@ extension ArticleVC: ArticleVCProtocol {
198202
alert.addAction(UIAlertAction(title: R.string.localizable.ok(), style: .default))
199203
present(alert, animated: true)
200204
}
205+
206+
func updateComments(with document: String) {
207+
commentsVC?.updateComments(with: document)
208+
}
201209
}
202210

203211
// MARK: - ArticleViewDelegate
204212

205213
extension ArticleVC: ArticleViewDelegate {
214+
206215
func updateCommentsButtonTapped() {
207-
commentsVC?.updateAll()
216+
Task {
217+
await presenter.updateComments()
218+
}
208219
}
209220
}
210221

211222
// MARK: - CommentsVCProtocol
212223

213224
extension ArticleVC: CommentsVCProtocol {
214-
func updateStarted() {
215-
let image = UIImage(
216-
systemSymbol: .arrowTriangle2Circlepath,
217-
withConfiguration: UIImage.SymbolConfiguration(weight: .bold)
218-
)
219-
myView.updateCommentsButton.setImage(image, for: .normal)
220-
myView.updateCommentsButton.rotate360Degrees(duration: 1, repeatCount: .infinity)
221-
}
222225

223226
func updateFinished(_ state: Bool) {
224227
let image = UIImage(
@@ -233,6 +236,7 @@ extension ArticleVC: CommentsVCProtocol {
233236
// MARK: - PDAResizingTextViewDelegate
234237

235238
extension ArticleVC: PDAResizingTextViewDelegate {
239+
236240
func willOpenURL(_ url: URL) {
237241
analyticsService.clickLinkInArticle(currentUrl: presenter.article.url, targetUrl: url.absoluteString)
238242
}

0 commit comments

Comments
 (0)