Skip to content

Commit

Permalink
Merge pull request #96 from worthbak/wb/store-json-fetch
Browse files Browse the repository at this point in the history
v0.2.0: async/await model overhaul; iPad support
  • Loading branch information
worthbak authored Nov 4, 2022
2 parents 299b9d9 + 26ec9c4 commit e707b0b
Show file tree
Hide file tree
Showing 30 changed files with 9,940 additions and 3,483 deletions.
82 changes: 62 additions & 20 deletions InventoryWatch.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

32 changes: 17 additions & 15 deletions InventoryWatch/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import SwiftUI

struct ContentView: View {
@EnvironmentObject var model: Model
@EnvironmentObject var model: ViewModel

@AppStorage("lastUpdateDate") private var lastUpdateDate: String = ""
@AppStorage("preferredProductType") private var preferredProductType: String = "MacBookPro"
Expand Down Expand Up @@ -58,8 +58,8 @@ struct ContentView: View {
}


if let preferredStoreInfo = model.preferredStoreInfo {
Text("\(shouldIncludeNearbyStores ? "near" : "at") \(preferredStoreInfo)")
if let preferredStoreName = model.preferredStoreName {
Text("\(shouldIncludeNearbyStores ? "near" : "at") \(preferredStoreName)")
.font(.title2)
}
}
Expand Down Expand Up @@ -90,9 +90,9 @@ struct ContentView: View {

ForEach(model.availableParts, id: \.0.storeNumber) { data in
Text("\(Text(data.0.storeName).font(storeFont)) \(Text(data.0.locationDescription).font(cityFont))")
let sortedProductNames = data.1.map { model.productName(forSKU: $0.partNumber) }
.sortedNumerically()

let sortedProductNames = data.1.map { $0.partName }
.sortedNumerically()

ForEach(sortedProductNames, id: \.self) { productName in
Text(productName)
Expand Down Expand Up @@ -127,7 +127,7 @@ struct ContentView: View {
}

Button(
action: { model.fetchLatestInventory() },
action: { Task { await model.fetchLatestInventory() } },
label: { Image(systemName: "arrow.clockwise") }
)
.buttonStyle(BorderlessButtonStyle())
Expand All @@ -145,15 +145,17 @@ struct ContentView: View {
alignment: .center
)
.onAppear {
model.fetchLatestInventory()
NotificationManager.shared.requestNotificationPermissions()
Task {
await model.fetchLatestInventory()
NotificationManager.shared.requestNotificationPermissions()
}
}
}
}

struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.environmentObject(Model.testData)
}
}
//struct ContentView_Previews: PreviewProvider {
// static var previews: some View {
// ContentView()
// .environmentObject(Model.testData)
// }
//}
70 changes: 35 additions & 35 deletions InventoryWatch/Countries.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,8 @@ import Foundation

struct Country: Hashable {
let name: String

#warning("storePathComponent is unused currently")
let storePathComponent: String
let shortcode: String
let locale: String
let skuCode: String

private static let GermanyAltCode = "FD"
Expand All @@ -34,7 +33,7 @@ struct Country: Hashable {
default:
return nil
}
case .iPadWifi, .iPadCellular:
case .iPadMiniWifi, .iPadMiniCellular, .iPad10thGenWifi, .iPad10thGenCellular, .iPadProM2_11in_Wifi, .iPadProM2_11in_Cellular, .iPadProM2_13in_Wifi, .iPadProM2_13in_Cellular:
switch name {
case "Germany":
return Country.GermanyAltCode
Expand Down Expand Up @@ -64,6 +63,20 @@ struct Country: Hashable {
default:
return nil
}

case .ApplePencilUSBCAdapter:
switch self.name {
case "United States", "Canada":
return "AM"
case "Germany", "United Kingdom", "France", "Austria", "Netherlands", "Italy":
return "ZM"
case "Thailand":
return "ZA"
case "Australia", "South Korea", "Japan", "Hong Kong":
return "FE"
default:
return nil
}
default:
return nil
}
Expand All @@ -72,38 +85,25 @@ struct Country: Hashable {

let USData = Country(
name: "United States",
storePathComponent: "",
shortcode: "US",
locale: "en_US",
skuCode: "LL"
)

let Countries: [String: Country] = [
"US": USData,
"CA": Country(name: "Canada", storePathComponent: "/ca", skuCode: "LL"),
"AU": Country(name: "Australia", storePathComponent: "/au", skuCode: "X"),
"DE": Country(name: "Germany", storePathComponent: "/de", skuCode: "D"),
"UK": Country(name: "United Kingdom", storePathComponent: "/uk", skuCode: "B"),
"KR": Country(name: "South Korea", storePathComponent: "/kr", skuCode: "KH"),
"HK": Country(name: "Hong Kong", storePathComponent: "/hk", skuCode: "ZP"),
"FR": Country(name: "France", storePathComponent: "/fr", skuCode: "FN"),
"IT": Country(name: "Italy", storePathComponent: "/it", skuCode: "T"),
"JP": Country(name: "Japan", storePathComponent: "/jp", skuCode: "J"),
"AT": Country(name: "Austria", storePathComponent: "/at", skuCode: "D"),
"NL": Country(name: "Netherlands", storePathComponent: "/nl", skuCode: "N"),
"TH": Country(name: "Thailand", storePathComponent: "/th", skuCode: "TH")
];

let OrderedCountries = [
"US",
"CA",
"AU",
"DE",
"UK",
"KR",
"HK",
"FR",
"IT",
"JP",
"AT",
"NL",
"TH"
let AllCountries = [
USData,
Country(name: "Canada", shortcode: "CA", locale: "en_CA", skuCode: "LL"),
Country(name: "Australia", shortcode: "AU", locale: "en_AU", skuCode: "X"),
Country(name: "Germany", shortcode: "DE", locale: "de_DE", skuCode: "D"),
Country(name: "United Kingdom", shortcode: "UK", locale: "en_GB", skuCode: "B"),
Country(name: "South Korea", shortcode: "KR", locale: "ko_KR", skuCode: "KH"),
Country(name: "Hong Kong", shortcode: "HK", locale: "en_HK", skuCode: "ZP"),
Country(name: "France", shortcode: "FR", locale: "fr_FR", skuCode: "FN"),
Country(name: "Italy", shortcode: "IT", locale: "it_IT", skuCode: "T"),
Country(name: "Japan", shortcode: "JP", locale: "ja_JP", skuCode: "J"),
Country(name: "Austria", shortcode: "AT", locale: "de_AT", skuCode: "D"),
Country(name: "Netherlands", shortcode: "NL", locale: "nl_NL", skuCode: "N"),
Country(name: "Thailand", shortcode: "TH", locale: "th_TH", skuCode: "TH"),
]
let Countries: [CountryCode: Country] = Dictionary(uniqueKeysWithValues: AllCountries.map { ($0.shortcode, $0) })
let OrderedCountries: [CountryCode] = AllCountries.map { $0.shortcode }
44 changes: 44 additions & 0 deletions InventoryWatch/Helpers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@

import Foundation

typealias SKUString = String
typealias CountryCode = String

extension Array where Element == String {
func sortedNumerically() -> [Element] {
sorted { lhs, rhs in
Expand All @@ -18,3 +21,44 @@ extension Array where Element == String {
func compareNumeric(_ version1: String, _ version2: String) -> ComparisonResult {
return version1.compare(version2, options: .numeric)
}

// From: https://github.com/JohnSundell/AsyncCompatibilityKit/blob/main/Sources/URLSession%2BAsync.swift
@available(macOS, deprecated: 12.0, message: "AsyncCompatibilityKit is only useful when targeting iOS versions earlier than 15")
public extension URLSession {
/// Start a data task with a URL using async/await.
/// - parameter url: The URL to send a request to.
/// - returns: A tuple containing the binary `Data` that was downloaded,
/// as well as a `URLResponse` representing the server's response.
/// - throws: Any error encountered while performing the data task.
func data(from url: URL) async throws -> (Data, URLResponse) {
try await data(for: URLRequest(url: url))
}

/// Start a data task with a `URLRequest` using async/await.
/// - parameter request: The `URLRequest` that the data task should perform.
/// - returns: A tuple containing the binary `Data` that was downloaded,
/// as well as a `URLResponse` representing the server's response.
/// - throws: Any error encountered while performing the data task.
func data(for request: URLRequest) async throws -> (Data, URLResponse) {
var dataTask: URLSessionDataTask?

return try await withTaskCancellationHandler(
operation: {
try await withCheckedThrowingContinuation { continuation in
dataTask = self.dataTask(with: request) { data, response, error in
guard let data = data, let response = response else {
let error = error ?? URLError(.badServerResponse)
return continuation.resume(throwing: error)
}
continuation.resume(returning: (data, response))
}

dataTask?.resume()
}
},
onCancel: { [dataTask] in
dataTask?.cancel()
}
)
}
}
4 changes: 2 additions & 2 deletions InventoryWatch/InventoryWatchApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import SwiftUI

@main
struct InventoryWatchApp: App {
@StateObject var model = Model()
@StateObject var model = ViewModel()

var body: some Scene {
WindowGroup {
Expand All @@ -20,7 +20,7 @@ struct InventoryWatchApp: App {
.commands {
CommandGroup(replacing: .newItem) {
Button(action: {
model.fetchLatestInventory()
Task { await model.fetchLatestInventory() }
}, label: {
Text("Reload Inventory")
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,24 +111,19 @@ struct AnalyticsData: Codable, Equatable {
return
}

var request = URLRequest(url: url)

let bodyData = data.toJsonData

// Change the URLRequest to a POST request
request.httpMethod = "POST"
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
request.httpBody = bodyData

let session = URLSession.shared
let task = session.dataTask(with: request) { (data, response, error) in

if let error = error {
// Handle HTTP request error
Task {
let bodyData = data.toJsonData

var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
request.httpBody = bodyData
do {
let (_, response) = try await URLSession.shared.data(for: request)
print("successfully updated analytics: response code \((response as? HTTPURLResponse)?.statusCode ?? 0)")
} catch {
print(error)
}
}

task.resume()
}
}
117 changes: 117 additions & 0 deletions InventoryWatch/Model/DefaultsVendor.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
//
// DefaultsVendor.swift
// InventoryWatch
//
// Created by Worth Baker on 10/25/22.
//

import Foundation
import Combine

extension UserDefaults {

@objc dynamic var preferredStoreNumber: String {
get { string(forKey: "preferredStoreNumber") ?? "R032" }
set { setValue(newValue, forKey: "preferredStoreNumber") }
}

@objc dynamic var lastUpdateDate: String? {
get { string(forKey: "lastUpdateDate") }
set { setValue(newValue, forKey: "lastUpdateDate") }
}

@objc dynamic var preferredUpdateInterval: Int {
get {
if let currentValue = object(forKey: "preferredUpdateInterval") as? Int {
return currentValue
} else {
// WB 10/28/22: Changing default update interval from 1min to 5min
let presetValue = 5
set(presetValue, forKey: "preferredUpdateInterval")

return presetValue
}
}
set { setValue(newValue, forKey: "preferredUpdateInterval") }
}
}

struct DefaultsVendor {

var preferredCountry: Country {
let value = UserDefaults.standard.string(forKey: "preferredCountry") ?? "US"

return Countries[value] ?? USData
}

var preferredProductType: ProductType {
let value = UserDefaults.standard.string(forKey: "preferredProductType") ?? "MacBookPro"

return ProductType(rawValue: value) ?? .MacBookPro
}

var countryPathElement: String {
let country = preferredCountry
if country.shortcode == "US" {
return ""
} else {
return country.shortcode + "/"
}
}

var preferredStoreNumber: String {
get { return UserDefaults.standard.preferredStoreNumber }
set { UserDefaults.standard.preferredStoreNumber = newValue }
}

var lastUpdateDate: String? {
get { return UserDefaults.standard.lastUpdateDate }
set { UserDefaults.standard.lastUpdateDate = newValue }
}

// unused - keeping this around as an example implementation
private var preferredStoreNumberStream: AnyPublisher<String, Never> {
return UserDefaults.standard
.publisher(for: \.preferredStoreNumber)
.eraseToAnyPublisher()
}

var preferredUpdateInterval: Int {
return UserDefaults.standard.preferredUpdateInterval
}

var shouldIncludeNearbyStores: Bool {
let value = UserDefaults.standard.object(forKey: "shouldIncludeNearbyStores") as? Bool

return value ?? true
}

var preferredSKUs: Set<String> {
guard let defaults = UserDefaults.standard.string(forKey: "preferredSKUs") else {
return []
}

return defaults.components(separatedBy: ",").reduce(into: Set<String>()) { partialResult, next in
partialResult.insert(next)
}
}

var customSkuData: (sku: String, nickname: String)? {
guard
let sku = UserDefaults.standard.string(forKey: "customSku"),
let name = UserDefaults.standard.string(forKey: "customSkuNickname")
else {
return nil
}

return (sku, name)
}

var showResultsOnlyForPreferredModels: Bool {
UserDefaults.standard.bool(forKey: "showResultsOnlyForPreferredModels")
}

var notifyOnlyForPreferredModels: Bool {
UserDefaults.standard.bool(forKey: "notifyOnlyForPreferredModels")
}
}
Loading

0 comments on commit e707b0b

Please sign in to comment.