Skip to content

Commit 08e64c4

Browse files
Mehran KamalifardMehran Kamalifard
Mehran Kamalifard
authored and
Mehran Kamalifard
committed
Refactor some code
1 parent cd15758 commit 08e64c4

File tree

9 files changed

+291
-235
lines changed

9 files changed

+291
-235
lines changed

EasyCrypto/Core/Networking/Client/NetworkClient.swift

+11-7
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,16 @@ final class NetworkClient: NetworkClientProtocol {
1212

1313
/// Initializes a new URL Session Client.
1414
///
15-
/// - parameter urlSession: The URLSession to use.
16-
/// Default: `URLSession(configuration: .shared)`.
15+
/// - Parameters:
16+
/// - session: The URLSession to use. Default: `URLSession.shared`.
17+
/// - logging: The logging utility to use. Default: `APIDebugger()`.
1718
///
1819
let session: URLSession
1920
let logging: Logging
2021

21-
init(session: URLSession = .shared, loggin: Logging = APIDebugger()) {
22+
init(session: URLSession = .shared, logging: Logging = APIDebugger()) {
2223
self.session = session
23-
self.logging = loggin
24+
self.logging = logging
2425
}
2526

2627
@discardableResult
@@ -37,15 +38,19 @@ final class NetworkClient: NetworkClientProtocol {
3738
}
3839
.decode(type: type.self, decoder: decoder)
3940
.mapError { error in
41+
// Improved error handling and logging
42+
if let decodingError = error as? DecodingError {
43+
print("Decoding error: \(decodingError)")
44+
}
4045
return error as? APIError ?? .general
4146
}
4247
.eraseToAnyPublisher()
4348
}
4449

45-
func publisher(request: URLRequest) -> AnyPublisher<(data: Data, response: URLResponse), APIError> {
50+
private func publisher(request: URLRequest) -> AnyPublisher<(data: Data, response: URLResponse), APIError> {
4651
return self.session.dataTaskPublisher(for: request)
4752
.mapError { APIError.urlError($0) }
48-
.map { response -> AnyPublisher<(data: Data, response: URLResponse), APIError> in
53+
.flatMap { response -> AnyPublisher<(data: Data, response: URLResponse), APIError> in
4954
self.logging.logResponse(response: response.response, data: response.data)
5055
guard let httpResponse = response.response as? HTTPURLResponse else {
5156
return Fail(error: APIError.invalidResponse(httpStatusCode: 0))
@@ -62,7 +67,6 @@ final class NetworkClient: NetworkClientProtocol {
6267
.setFailureType(to: APIError.self)
6368
.eraseToAnyPublisher()
6469
}
65-
.switchToLatest()
6670
.eraseToAnyPublisher()
6771
}
6872
}

EasyCrypto/Core/Networking/RequestBuilder/NetworkTarget + Extension.swift

+40-71
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,18 @@ private struct HTTPHeader {
1818
}
1919

2020
struct HttpRequest: RequestBuilder {
21-
21+
2222
var baseURL: BaseURLType
23-
2423
var version: VersionType
25-
2624
var path: String?
27-
2825
var methodType: HTTPMethod
29-
3026
var queryParams: [String: String]?
31-
3227
var queryParamsEncoding: URLEncoding?
28+
var headers: [String: String]?
29+
var parameters: [String: Any]?
30+
var bodyEncoding: BodyEncoding?
31+
var cachePolicy: URLRequest.CachePolicy?
32+
var timeoutInterval: TimeInterval?
3333

3434
init(request: NetworkTarget) {
3535
self.baseURL = request.baseURL
@@ -40,94 +40,63 @@ struct HttpRequest: RequestBuilder {
4040
self.queryParamsEncoding = request.queryParamsEncoding
4141
}
4242

43-
var pathAppendedURL: URL {
43+
internal var pathAppendedURL: URL {
4444
var url = baseURL.desc
4545
url.appendPathComponent(version.desc)
46-
url.appendPathComponent(path ?? .empty)
46+
if let path = path {
47+
url.appendPathComponent(path)
48+
}
4749
return url
4850
}
4951

50-
func setQueryTo(urlRequest: inout URLRequest,
51-
urlEncoding: URLEncoding,
52-
queryParams: [String: String]) {
53-
guard let url = urlRequest.url else {
54-
return
55-
}
56-
var urlComponents = URLComponents.init(url: url, resolvingAgainstBaseURL: false)
57-
switch urlEncoding {
52+
internal func setQuery(to urlRequest: inout URLRequest) {
53+
guard let url = urlRequest.url else { return }
54+
var urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false)
55+
56+
switch queryParamsEncoding {
5857
case .default:
59-
urlComponents?.queryItems = [URLQueryItem]()
60-
for (name, value) in queryParams {
61-
urlComponents?.queryItems?.append(URLQueryItem.init(name: name, value: value))
62-
}
63-
urlRequest.url = urlComponents?.url
58+
urlComponents?.queryItems = queryParams?.map { URLQueryItem(name: $0.key, value: $0.value) }
6459
case .percentEncoded:
65-
urlComponents?.percentEncodedQueryItems = [URLQueryItem]()
66-
for (name, value) in queryParams {
67-
let encodedName = name.addingPercentEncoding(withAllowedCharacters: .nkURLQueryAllowed) ?? name
68-
let encodedValue = value.addingPercentEncoding(withAllowedCharacters: .nkURLQueryAllowed) ?? value
69-
let queryItem = URLQueryItem.init(name: encodedName, value: encodedValue)
70-
urlComponents?.percentEncodedQueryItems?.append(queryItem)
60+
urlComponents?.percentEncodedQueryItems = queryParams?.map {
61+
URLQueryItem(name: $0.key.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? $0.key,
62+
value: $0.value.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? $0.value)
7163
}
72-
urlRequest.url = urlComponents?.url
73-
// Applicable for PUT and POST method.
74-
// When queryParamsEncoding is xWWWFormURLEncoded,
75-
// All query parameters are sent inside body.
7664
case .xWWWFormURLEncoded:
77-
if let queryParamsData = self.queryParams?.urlEncodedQueryParams().data(using: .utf8) {
65+
if let queryParamsData = queryParams?.urlEncodedQueryParams().data(using: .utf8) {
7866
urlRequest.httpBody = queryParamsData
7967
urlRequest.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: HTTPHeader.contentType)
8068
}
69+
default:
70+
break
8171
}
72+
73+
urlRequest.url = urlComponents?.url
8274
}
8375

84-
func encodedBody(bodyEncoding: BodyEncoding,
85-
requestBody: [String: Any]) -> Data? {
76+
internal func encodedBody() -> Data? {
77+
guard let bodyEncoding = bodyEncoding else { return nil }
78+
8679
switch bodyEncoding {
8780
case .JSON:
88-
do {
89-
return try JSONSerialization.data(withJSONObject: requestBody)
90-
} catch {
91-
return nil
92-
}
81+
return try? JSONSerialization.data(withJSONObject: parameters ?? [:])
9382
case .xWWWFormURLEncoded:
94-
do {
95-
return try requestBody.urlEncodedBody()
96-
} catch {
97-
return nil
98-
}
83+
return try? parameters?.urlEncodedBody()
9984
}
10085
}
10186

10287
func buildURLRequest() -> URLRequest {
103-
let url = self.pathAppendedURL
104-
// prepare a url request
105-
var urlRequest = URLRequest(url: url)
106-
// set method for request
107-
urlRequest.httpMethod = self.methodType.name
108-
// set requestHeaders for request
109-
urlRequest.allHTTPHeaderFields = self.headers
110-
111-
// set query parameters for request
112-
if let queryParams = self.queryParams, !queryParams.isEmpty,
113-
let queryParamsEncoding = self.queryParamsEncoding {
114-
self.setQueryTo(urlRequest: &urlRequest,
115-
urlEncoding: queryParamsEncoding,
116-
queryParams: queryParams)
117-
}
118-
// set body for request
119-
if let requestBody = self.parameters {
120-
/// Encoding
121-
if let bodyEncoding = self.bodyEncoding {
122-
urlRequest.httpBody = self.encodedBody(bodyEncoding: bodyEncoding,
123-
requestBody: requestBody)
124-
} else {
125-
urlRequest.httpBody = self.encodedBody(bodyEncoding: .JSON,
126-
requestBody: requestBody)
127-
}
88+
var urlRequest = URLRequest(url: pathAppendedURL)
89+
urlRequest.httpMethod = methodType.name
90+
urlRequest.allHTTPHeaderFields = headers
91+
92+
if let queryParams = queryParams, !queryParams.isEmpty {
93+
setQuery(to: &urlRequest)
12894
}
129-
urlRequest.cachePolicy = self.cachePolicy ?? URLRequest.CachePolicy.useProtocolCachePolicy
130-
urlRequest.timeoutInterval = self.timeoutInterval ?? 60
95+
96+
urlRequest.httpBody = encodedBody()
97+
urlRequest.cachePolicy = cachePolicy ?? .useProtocolCachePolicy
98+
urlRequest.timeoutInterval = timeoutInterval ?? 60
99+
131100
return urlRequest
132101
}
133102
}

EasyCrypto/Core/Networking/RequestBuilder/RequestBuilder.swift

+2-4
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,7 @@ import Foundation
1010
protocol RequestBuilder: NetworkTarget {
1111
init(request: NetworkTarget)
1212
var pathAppendedURL: URL { get }
13-
func setQueryTo(urlRequest: inout URLRequest,
14-
urlEncoding: URLEncoding,
15-
queryParams: [String: String])
16-
func encodedBody(bodyEncoding: BodyEncoding, requestBody: [String: Any]) -> Data?
13+
func setQuery(to urlRequest: inout URLRequest)
14+
func encodedBody() -> Data?
1715
func buildURLRequest() -> URLRequest
1816
}

EasyCrypto/Presentation/CoinDetail/View/CoinDetailHeaderView.swift

+67-45
Original file line numberDiff line numberDiff line change
@@ -10,59 +10,81 @@ import SwiftUI
1010
struct CoinDetailHeaderView: View {
1111

1212
var item: CoinUnit
13-
14-
var url: ((String?) -> Void)
13+
var url: (String?) -> Void
1514

1615
var body: some View {
1716
VStack(alignment: .leading, spacing: 30.0) {
18-
HStack {
19-
ImageDownloaderView(withURL: item.image?.safeImageURL() ?? .empty)
20-
.frame(width: 50.0, height: 50.0)
21-
Spacer()
22-
CoinRankView(image: Assets.hashtag, rank: item.marketCapRank ?? 0)
23-
CoinRankView(image: Assets.coinGeckod, rank: item.coingeckoRank ?? 0)
24-
}
25-
HStack {
26-
VStack(alignment: .leading, spacing: 5) {
27-
Text("Name")
28-
.foregroundColor(Color.gray)
29-
.font(FontManager.body)
30-
Text(item.name.orWhenNilOrEmpty(.empty))
31-
.foregroundColor(Color.white)
32-
.font(FontManager.title)
33-
}
34-
Spacer()
35-
.frame(width: 100)
36-
VStack(alignment: .leading, spacing: 5) {
37-
Text("Symbol")
38-
.foregroundColor(Color.gray)
39-
.font(FontManager.body)
40-
Text(item.symbol.orWhenNilOrEmpty(.empty))
41-
.foregroundColor(Color.white)
42-
.font(FontManager.title)
43-
}
44-
}
17+
headerSection
18+
nameAndSymbolSection
19+
linkSection
20+
descriptionSection
21+
}
22+
}
23+
24+
private var headerSection: some View {
25+
HStack {
26+
ImageDownloaderView(withURL: item.image?.safeImageURL() ?? .empty)
27+
.frame(width: 50.0, height: 50.0)
28+
Spacer()
29+
CoinRankView(image: Assets.hashtag, rank: item.marketCapRank ?? 0)
30+
CoinRankView(image: Assets.coinGeckod, rank: item.coingeckoRank ?? 0)
31+
}
32+
}
33+
34+
private var nameAndSymbolSection: some View {
35+
HStack {
4536
VStack(alignment: .leading, spacing: 5) {
46-
Text("Link")
47-
.foregroundColor(Color.gray)
48-
.font(FontManager.body)
49-
Button {
50-
self.url(item.links?.homepage?.first.orWhenNilOrEmpty(.empty))
51-
} label: {
52-
Text(item.links?.homepage?.first ?? .empty)
53-
.foregroundColor(Color.white)
54-
.font(FontManager.title)
55-
}
37+
CoinDetailLabel(title: "Name", value: item.name.orWhenNilOrEmpty(.empty))
5638
}
39+
Spacer()
40+
.frame(width: 100)
5741
VStack(alignment: .leading, spacing: 5) {
58-
Text("Description")
59-
.foregroundColor(Color.gray)
60-
.font(FontManager.body)
61-
Text(item.description?.en ?? .empty)
62-
.frame(maxWidth: .infinity, alignment: .leading)
63-
.foregroundColor(Color.white)
42+
CoinDetailLabel(title: "Symbol", value: item.symbol.orWhenNilOrEmpty(.empty))
43+
}
44+
}
45+
}
46+
47+
private var linkSection: some View {
48+
VStack(alignment: .leading, spacing: 5) {
49+
Text("Link")
50+
.foregroundColor(.gray)
51+
.font(FontManager.body)
52+
Button {
53+
url(item.links?.homepage?.first.orWhenNilOrEmpty(.empty))
54+
} label: {
55+
Text(item.links?.homepage?.first ?? .empty)
56+
.foregroundColor(.white)
6457
.font(FontManager.title)
6558
}
6659
}
6760
}
61+
62+
private var descriptionSection: some View {
63+
VStack(alignment: .leading, spacing: 5) {
64+
Text("Description")
65+
.foregroundColor(.gray)
66+
.font(FontManager.body)
67+
Text(item.description?.en ?? .empty)
68+
.frame(maxWidth: .infinity, alignment: .leading)
69+
.foregroundColor(.white)
70+
.font(FontManager.title)
71+
}
72+
}
73+
}
74+
75+
private struct CoinDetailLabel: View {
76+
77+
var title: String
78+
var value: String
79+
80+
var body: some View {
81+
VStack(alignment: .leading, spacing: 5) {
82+
Text(title)
83+
.foregroundColor(.gray)
84+
.font(FontManager.body)
85+
Text(value)
86+
.foregroundColor(.white)
87+
.font(FontManager.title)
88+
}
89+
}
6890
}

EasyCrypto/Presentation/CoinDetail/ViewModel/CoinDetailViewModel.swift

+8-6
Original file line numberDiff line numberDiff line change
@@ -39,20 +39,22 @@ extension CoinDetailViewModel: DataFlowProtocol {
3939
func apply(_ input: Load) {
4040
switch input {
4141
case .onAppear(let id):
42-
self.getCoinDetailData(id: id)
42+
getCoinDetailData(id: id)
4343
}
4444
}
4545

4646
func didTapFirst(url: String) {
47-
guard let url = URL(string: url) else {return}
48-
self.navigateSubject.send(.first(url: url))
47+
guard let url = URL(string: url) else { return }
48+
navigateSubject.send(.first(url: url))
4949
}
5050

5151
func getCoinDetailData(id: String) {
52-
guard !String.isNilOrEmpty(string: id) else {return}
53-
self.call(argument: self.coinDetailUsecase.execute(id: id)) { [weak self] data in
54-
guard let data = data else {return}
52+
guard !id.isEmpty else { return }
53+
isShowActivity = true
54+
call(argument: coinDetailUsecase.execute(id: id)) { [weak self] data in
55+
guard let data = data else { return }
5556
self?.coinData = data
57+
self?.isShowActivity = false
5658
}
5759
}
5860
}

0 commit comments

Comments
 (0)