Skip to content

Commit

Permalink
Merge pull request #40 from boscojwho/load-feed-in-background-task
Browse files Browse the repository at this point in the history
Load feed in background task
  • Loading branch information
Athlon007 authored Jul 22, 2023
2 parents d1117a5 + 9bb2588 commit 3a3104d
Show file tree
Hide file tree
Showing 3 changed files with 209 additions and 160 deletions.
149 changes: 76 additions & 73 deletions Leomard/Services/RequestHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,88 +27,91 @@ final class RequestHandler {
public final let VERSION = "v3"

public func makeApiRequest(host: String, request: String, method: HTTPMethod, headers: [String:String]? = nil, body: Codable? = nil, completion: @escaping (Result<APIResponse, Error>) -> Void) {
var urlString = host.containsAny("github.com", "imgur.com") ? "\(host)\(request)" : "\(host)/api/\(self.VERSION)\(request)"

if !urlString.starts(with: "http") {
urlString = "https://\(urlString)"
}

if SessionStorage.getInstance.isSessionActive() && method == .get {
let jwt = SessionStorage.getInstance.getCurrentSession()?.loginResponse.jwt
if !urlString.contains("?") {
urlString += "?auth=\(jwt!)"
} else {
urlString += "&auth=\(jwt!)"
Task(priority: .userInitiated) {
var urlString = host.containsAny("github.com", "imgur.com") ? "\(host)\(request)" : "\(host)/api/\(self.VERSION)\(request)"

if !urlString.starts(with: "http") {
urlString = "https://\(urlString)"
}
}

guard let url = URL(string: urlString) else {
completion(.failure(NSError(domain: "Invalid URL", code: 0, userInfo: nil)))
return
}

var request = URLRequest(url: url)
request.httpMethod = method.rawValue

// Body is set? Include the Auth in body
if let body = body {
do {
let encoder = JSONEncoder()
encoder.keyEncodingStrategy = .convertToSnakeCase
var jsonData = try encoder.encode(body)

// Add the authentication to body, as it's needed for POSTs.
// To do that, we first must decode back the object, add "auth" key, and re-encode it.
guard var jsonDictionary = try JSONSerialization.jsonObject(with: jsonData, options: []) as? [String: Any] else {

if SessionStorage.getInstance.isSessionActive() && method == .get {
let jwt = SessionStorage.getInstance.getCurrentSession()?.loginResponse.jwt
if !urlString.contains("?") {
urlString += "?auth=\(jwt!)"
} else {
urlString += "&auth=\(jwt!)"
}
}

guard let url = URL(string: urlString) else {
completion(.failure(NSError(domain: "Invalid URL", code: 0, userInfo: nil)))
return
}

var request = URLRequest(url: url)
request.httpMethod = method.rawValue

// Body is set? Include the Auth in body
if let body = body {
do {
let encoder = JSONEncoder()
encoder.keyEncodingStrategy = .convertToSnakeCase
var jsonData = try encoder.encode(body)

// Add the authentication to body, as it's needed for POSTs.
// To do that, we first must decode back the object, add "auth" key, and re-encode it.
guard var jsonDictionary = try JSONSerialization.jsonObject(with: jsonData, options: []) as? [String: Any] else {
let error = NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "Failed to decode JSON data."])
completion(.failure(error))
return
}

if SessionStorage.getInstance.isSessionActive() {
jsonDictionary["auth"] = SessionStorage.getInstance.getCurrentSession()?.loginResponse.jwt
jsonData = try JSONSerialization.data(withJSONObject: jsonDictionary, options: [])

if SessionStorage.getInstance.isSessionActive() {
jsonDictionary["auth"] = SessionStorage.getInstance.getCurrentSession()?.loginResponse.jwt
jsonData = try JSONSerialization.data(withJSONObject: jsonDictionary, options: [])
}
request.httpBody = jsonData
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
} catch {
completion(.failure(error))
return
}
} else if method != .get && SessionStorage.getInstance.getCurrentSession()?.loginResponse.jwt != nil {
// If no body is set, but the method is **NOT** GET, then add the "NoBodyPost" object with auth key.
let encoder = JSONEncoder()
encoder.keyEncodingStrategy = .convertToSnakeCase
do {
let jsonData = try encoder.encode(NoBodyPost(auth: SessionStorage.getInstance.getCurrentSession()!.loginResponse.jwt!))
request.httpBody = jsonData
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
} catch {
completion(.failure(error))
return
}
request.httpBody = jsonData
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
} catch {
completion(.failure(error))
return
}
} else if method != .get && SessionStorage.getInstance.getCurrentSession()?.loginResponse.jwt != nil {
// If no body is set, but the method is **NOT** GET, then add the "NoBodyPost" object with auth key.
let encoder = JSONEncoder()
encoder.keyEncodingStrategy = .convertToSnakeCase
do {
let jsonData = try encoder.encode(NoBodyPost(auth: SessionStorage.getInstance.getCurrentSession()!.loginResponse.jwt!))
request.httpBody = jsonData
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
} catch {
completion(.failure(error))
return
}
}

if let _headers = headers {
for header in _headers {
request.setValue(header.value, forHTTPHeaderField: header.key)
}
}

URLSession.shared.dataTask(with: request, completionHandler: { (data, response, error) in
if let error = error {
completion(.failure(error))
return
}

guard let httpResponse = response as? HTTPURLResponse else {
completion(.failure(NSError(domain: "Invalid response", code: 0, userInfo: nil)))
return
// If headers are set, add them too.
if let _headers = headers {
for header in _headers {
request.setValue(header.value, forHTTPHeaderField: header.key)
}
}

let statusCode = httpResponse.statusCode
let apiResponse = APIResponse(statusCode: statusCode, data: data)
completion(.success(apiResponse))
}).resume()
URLSession.shared.dataTask(with: request, completionHandler: { (data, response, error) in
if let error = error {
completion(.failure(error))
return
}

guard let httpResponse = response as? HTTPURLResponse else {
completion(.failure(NSError(domain: "Invalid response", code: 0, userInfo: nil)))
return
}

let statusCode = httpResponse.statusCode
let apiResponse = APIResponse(statusCode: statusCode, data: data)
completion(.success(apiResponse))
}).resume()
}
}
}
80 changes: 55 additions & 25 deletions Leomard/Views/Components/PostUIView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -75,44 +75,74 @@ struct PostUIView: View {
)

.task {
self.postBody = self.postView.post.body
if self.postBody != nil {
self.postBody = postBody!.replacingOccurrences(of: "\r", with: "<br>")
postBody = await postBodyTask()
url = await postUrlTask()
updatedTimeAsString = await updatedTimeAsStringTask()
}
.onTapGesture {
self.contentView.openPost(postView: self.postView)
}
.contextMenu {
PostContextMenu(postView: self.postView)
}
}
}

nonisolated
private func postBodyTask() async -> String? {
return await withCheckedContinuation { continuation in
Task(priority: .background) {
var newPostBody = await self.postView.post.body
if newPostBody != nil {
newPostBody = newPostBody!.replacingOccurrences(of: "\r", with: "<br>")
let regex = try! NSRegularExpression(pattern: #"!\[\]\((.*?)\)"#, options: .caseInsensitive)
let range = NSRange(location: 0, length: postBody!.utf16.count)
let range = NSRange(location: 0, length: newPostBody!.utf16.count)

let matches = regex.matches(in: postBody!, options: [], range: range)
let matches = regex.matches(in: newPostBody!, options: [], range: range)

var imageUrls: [String] = []

for match in matches {
if let urlRange = Range(match.range(at: 1), in: postBody!) {
let imageUrl = String(postBody![urlRange])
if let urlRange = Range(match.range(at: 1), in: newPostBody!) {
let imageUrl = String(newPostBody![urlRange])
imageUrls.append(imageUrl)
}
}
}
if shortBody && postBody != nil && postBody!.count > PostUIView.maxPostLength {
postBody = String(postBody!.prefix(PostUIView.maxPostLength))
postBody = postBody!.trimmingCharacters(in: .whitespacesAndNewlines)
postBody = postBody! + "... **Read More**"
}

if postView.post.url != nil {
url = URL(string: postView.post.url!)
}

if postView.post.updated != nil {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm"
updatedTimeAsString = dateFormatter.string(from: postView.post.updated!)
if shortBody && newPostBody != nil && newPostBody!.count > PostUIView.maxPostLength {
newPostBody = String(newPostBody!.prefix(PostUIView.maxPostLength))
newPostBody = newPostBody!.trimmingCharacters(in: .whitespacesAndNewlines)
newPostBody = newPostBody! + "... **Read More**"
}
return continuation.resume(returning: newPostBody)
}
.onTapGesture {
self.contentView.openPost(postView: self.postView)
}
}

nonisolated
private func postUrlTask() async -> URL? {
return await withCheckedContinuation { continuation in
Task(priority: .background) {
guard let postUrl = await self.postView.post.url else {
return continuation.resume(returning: nil)
}
return continuation.resume(returning: URL(string: postUrl))
}
.contextMenu {
PostContextMenu(postView: self.postView)
}
}

nonisolated
private func updatedTimeAsStringTask() async -> String {
return await withCheckedContinuation { continuation in
Task(priority: .background) {
guard let updatedDate = await self.postView.post.updated else {
return continuation.resume(returning: "")
}

let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm"
let dateString = dateFormatter.string(from: updatedDate)
return continuation.resume(returning: dateString)
}
}
}
Expand Down
Loading

0 comments on commit 3a3104d

Please sign in to comment.