Skip to content

Commit

Permalink
fix: make gitimoji fetch more verbose and configurable, closes #18
Browse files Browse the repository at this point in the history
  • Loading branch information
lovetodream committed Feb 22, 2023
1 parent 8334675 commit fb04071
Show file tree
Hide file tree
Showing 6 changed files with 118 additions and 27 deletions.
5 changes: 5 additions & 0 deletions gitimoji/Constants.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ extension KeyboardShortcuts.Name {
enum Constants {
static let helperBundleIdentifier = "com.timozacherl.GitimojiAutoLaunchHelper"

enum DefaultKey: String {
case gitmojiFetchURL
case copyEmoji
}

enum Link {
static let repository: URL = {
guard let url = URL(string: "https://github.com/lovetodream/gitimoji") else {
Expand Down
52 changes: 37 additions & 15 deletions gitimoji/Services/GitmojiFetcher.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,34 +7,56 @@

import CoreData

enum FetchError: LocalizedError {
case failedToSave(reason: String?)
case failedToFetch(reason: String?)

var errorDescription: String? {
switch self {
case .failedToSave(let reason):
return reason ?? "Failed to save Gitmojis to local disk due to an unknown error."
case .failedToFetch(let reason):
return reason ?? "Failed to fetch Gitmojis from the provided fetch URL. Please check the URL and try again."
}
}
}

@MainActor
class GitmojiFetcher: ObservableObject {
@Published private(set) var state: FetchState = .stateless
@Published var lastError: FetchError?
@Published var isShowingError = false

func refetch(on viewContext: NSManagedObjectContext) async {
state = .loading

remove(from: viewContext)

guard let gitmojis = try? await Networking.fetchEmojis() else {
return
}
do {
let gitmojis = try await Networking.fetchEmojis()

gitmojis.forEach { gitmoji in
let newEntity = Gitmoji(context: viewContext)
newEntity.name = gitmoji.name
newEntity.emojiDescription = gitmoji.description
newEntity.emojiEntity = gitmoji.entity
newEntity.code = gitmoji.code
newEntity.emoji = gitmoji.emoji
newEntity.semver = gitmoji.semver?.rawValue
}
gitmojis.forEach { gitmoji in
let newEntity = Gitmoji(context: viewContext)
newEntity.name = gitmoji.name
newEntity.emojiDescription = gitmoji.description
newEntity.emojiEntity = gitmoji.entity
newEntity.code = gitmoji.code
newEntity.emoji = gitmoji.emoji
newEntity.semver = gitmoji.semver?.rawValue
}

do {
try viewContext.save()
state = .success
do {
try viewContext.save()
state = .success
} catch {
state = .error
lastError = .failedToSave(reason: error.localizedDescription)
isShowingError = true
}
} catch {
state = .error
lastError = .failedToFetch(reason: error.localizedDescription)
isShowingError = true
}
}

Expand Down
25 changes: 17 additions & 8 deletions gitimoji/Services/Networking.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import Foundation

fileprivate struct Response: Decodable {
private struct Response: Decodable {
let gitmojis: [GitmojiResponse]
}

Expand All @@ -23,23 +23,32 @@ enum Semver: String, Decodable {
case patch = "patch"
}

enum NetworkingError: Error {
case invalidResponse
enum NetworkingError: LocalizedError {
case invalidResponse(context: String?)

var errorDescription: String? {
switch self {
case .invalidResponse(let context):
return context ?? "Unexpected response received."
}
}
}

struct Networking {
enum Networking {
static func fetchEmojis() async throws -> [GitmojiResponse] {
let url = URL(string: "https://raw.githubusercontent.com/carloscuesta/gitmoji/master/packages/gitmojis/src/gitmojis.json")
let url = UserDefaults.standard.url(forKey: Constants.DefaultKey.gitmojiFetchURL.rawValue) ??
URL(string: "https://raw.githubusercontent.com/carloscuesta/gitmoji/master/packages/gitmojis/src/gitmojis.json")

guard let url = url else {
guard let url else {
preconditionFailure("Gitmoji fetch URL invalid")
}

let (data, response) = try await URLSession.shared.data(from: url)

guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
print("Unexpected response: \(String(describing: response))")
throw NetworkingError.invalidResponse
throw NetworkingError.invalidResponse(
context: "Unexpected response received: \(String(data: data, encoding: .utf8) ?? "empty")"
)
}

return try JSONDecoder().decode(Response.self, from: data).gitmojis
Expand Down
3 changes: 2 additions & 1 deletion gitimoji/Views/Components/EmojiRow.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ struct EmojiRow: View {
@State private var hovering = false
@State private var recentlyCopied = false

@AppStorage("copyEmoji") private var copyEmoji = false
@AppStorage(Constants.DefaultKey.copyEmoji.rawValue)
private var copyEmoji = false

var body: some View {
Button(action: {
Expand Down
27 changes: 25 additions & 2 deletions gitimoji/Views/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ struct ContentView: View {
@FetchRequest(sortDescriptors: [NSSortDescriptor(keyPath: \Gitmoji.name, ascending: true)])
private var gitmojis: FetchedResults<Gitmoji>

@Environment(\.managedObjectContext) private var viewContext

@State private var searchText = ""

private var searchResults: [Gitmoji] {
Expand All @@ -23,13 +25,29 @@ struct ContentView: View {
@State private var showSettings = false
@State private var showAbout = false

@StateObject private var fetcher = GitmojiFetcher()

var body: some View {
VStack(alignment: .leading, spacing: 0) {
GMSearchField(searchText: $searchText)
ScrollView(.vertical, showsIndicators: false) {
VStack {
ForEach(searchResults) { gitmoji in
EmojiRow(gitmoji: gitmoji)
if searchResults.isEmpty {
HStack {
Spacer()
if searchText.isEmpty {
Text("No Gitmojis downloaded, please open Settings to perform the initial Download.")
} else {
Text("No matching Gitmojis found")
}
Spacer()
}
.padding()
.multilineTextAlignment(.center)
} else {
ForEach(searchResults) { gitmoji in
EmojiRow(gitmoji: gitmoji)
}
}
}
.padding(.vertical, 9)
Expand All @@ -48,6 +66,11 @@ struct ContentView: View {
}
.buttonStyle(.plain)
.padding()
.task {
if gitmojis.isEmpty {
await fetcher.refetch(on: viewContext)
}
}
}
}

Expand Down
33 changes: 32 additions & 1 deletion gitimoji/Views/Settings/GeneralSettingsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,13 @@ struct GeneralSettingsView: View {

@Binding var autoLaunchEnabled: Bool

@AppStorage("copyEmoji") private var copyEmoji = false
@AppStorage(Constants.DefaultKey.copyEmoji.rawValue)
private var copyEmoji = false

@AppStorage(Constants.DefaultKey.gitmojiFetchURL.rawValue)
private var url: URL = URL(string: "https://raw.githubusercontent.com/carloscuesta/gitmoji/master/packages/gitmojis/src/gitmojis.json")!

@State private var urlString = ""

@Environment(\.managedObjectContext) private var viewContext

Expand Down Expand Up @@ -55,6 +61,25 @@ struct GeneralSettingsView: View {
.disabled(fetcher.state == .loading)
}

HStack {
Text("Gitmoji Fetch URL")
Group {
if #available(macOS 13.0, *) {
TextField("URL", value: $url, format: .url)
} else {
TextField("URL", text: $urlString)
.onChange(of: urlString) { newValue in
guard let url = URL(string: newValue) else {
return
}
self.url = url
}
}
}
.multilineTextAlignment(.trailing)
.textFieldStyle(.roundedBorder)
}

HStack {
Spacer()
Toggle(isOn: $autoLaunchEnabled) {
Expand All @@ -74,6 +99,12 @@ struct GeneralSettingsView: View {

Spacer()
}
.alert(
isPresented: $fetcher.isShowingError,
error: fetcher.lastError
) {
Button("Ok", role: .cancel) {}
}
}
}

Expand Down

0 comments on commit fb04071

Please sign in to comment.