This commit is contained in:
John Estropia
2020-08-19 18:49:08 +09:00
parent 5e536556da
commit 2c0cadf2fa
7 changed files with 229 additions and 56 deletions

View File

@@ -11,6 +11,7 @@
B531EFE924EB5A53005F247D /* Modern.PokedexDemo.PokedexEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = B531EFE824EB5A52005F247D /* Modern.PokedexDemo.PokedexEntry.swift */; };
B531EFEB24EB5ECD005F247D /* Modern.PokedexDemo.Service.swift in Sources */ = {isa = PBXBuildFile; fileRef = B531EFEA24EB5ECD005F247D /* Modern.PokedexDemo.Service.swift */; };
B531EFED24EB7453005F247D /* Modern.PokedexDemo.MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B531EFEC24EB7453005F247D /* Modern.PokedexDemo.MainView.swift */; };
B54269C624ED190C00A66C23 /* Modern.PokedexDemo.ItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B54269C524ED190C00A66C23 /* Modern.PokedexDemo.ItemView.swift */; };
B5A3911D24E5429200E7E8BD /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A3911C24E5429200E7E8BD /* AppDelegate.swift */; };
B5A3911F24E5429200E7E8BD /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A3911E24E5429200E7E8BD /* SceneDelegate.swift */; };
B5A3912124E5429200E7E8BD /* Menu.MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A3912024E5429200E7E8BD /* Menu.MainView.swift */; };
@@ -89,6 +90,7 @@
B531EFE824EB5A52005F247D /* Modern.PokedexDemo.PokedexEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.PokedexDemo.PokedexEntry.swift; sourceTree = "<group>"; };
B531EFEA24EB5ECD005F247D /* Modern.PokedexDemo.Service.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.PokedexDemo.Service.swift; sourceTree = "<group>"; };
B531EFEC24EB7453005F247D /* Modern.PokedexDemo.MainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.PokedexDemo.MainView.swift; sourceTree = "<group>"; };
B54269C524ED190C00A66C23 /* Modern.PokedexDemo.ItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.PokedexDemo.ItemView.swift; sourceTree = "<group>"; };
B5A3911924E5429200E7E8BD /* Demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Demo.app; sourceTree = BUILT_PRODUCTS_DIR; };
B5A3911C24E5429200E7E8BD /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
B5A3911E24E5429200E7E8BD /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
@@ -375,6 +377,7 @@
B5A391B024E96AF600E7E8BD /* Modern.PokedexDemo.swift */,
B531EFEA24EB5ECD005F247D /* Modern.PokedexDemo.Service.swift */,
B531EFEC24EB7453005F247D /* Modern.PokedexDemo.MainView.swift */,
B54269C524ED190C00A66C23 /* Modern.PokedexDemo.ItemView.swift */,
B5A391B224E96B7400E7E8BD /* Models */,
);
path = PokedexDemo;
@@ -499,6 +502,7 @@
B5A3916524E698C700E7E8BD /* Modern.PlacemarksDemo.Place.swift in Sources */,
B5A391BD24E977E500E7E8BD /* Modern.PokedexDemo.Ability.swift in Sources */,
B5A391B624E96C5500E7E8BD /* Modern.PokedexDemo.Move.swift in Sources */,
B54269C624ED190C00A66C23 /* Modern.PokedexDemo.ItemView.swift in Sources */,
B531EFE924EB5A53005F247D /* Modern.PokedexDemo.PokedexEntry.swift in Sources */,
B5A391B924E96F8500E7E8BD /* Modern.PokedexDemo.PokemonForm.swift in Sources */,
B5A391B424E96C0A00E7E8BD /* Modern.PokedexDemo.PokemonSpecies.swift in Sources */,

View File

@@ -0,0 +1,96 @@
//
// Demo
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
import CoreStore
import SwiftUI
// MARK: - Modern.PokedexDemo
extension Modern.PokedexDemo {
// MARK: - Modern.PokedexDemo.ItemView
struct ItemView: View {
// MARK: Internal
static let preferredHeight: CGFloat = 100
init(
pokedexEntry: ObjectPublisher<Modern.PokedexDemo.PokedexEntry>,
service: Modern.PokedexDemo.Service
) {
self.pokedexEntry = pokedexEntry
self.service = service
}
// MARK: View
var body: some View {
let pokedexEntry = self.pokedexEntry.snapshot
let form = pokedexEntry?.$form
let placeholderColor = Color.init(.sRGB, white: 0.95, opacity: 1)
return HStack(spacing: 10) {
placeholderColor
.frame(width: 70, height: 70)
.cornerRadius(10)
Text(form?.$name ?? pokedexEntry?.$id ?? "")
.foregroundColor(form == nil ? placeholderColor : .init(.darkText))
.fontWeight(form == nil ? .heavy : .regular)
.frame(maxWidth: .infinity)
}
.padding()
.onAppear {
if let pokedexEntry = pokedexEntry, form == nil {
self.service.fetchPokemonForm(for: pokedexEntry)
}
}
}
// MARK: Private
@ObservedObject
private var pokedexEntry: ObjectPublisher<Modern.PokedexDemo.PokedexEntry>
private let service: Modern.PokedexDemo.Service
}
}
#if DEBUG
struct _Demo_Modern_PokedexDemo_ItemView_Preview: PreviewProvider {
// MARK: PreviewProvider
static let service = Modern.PokedexDemo.Service()
static var previews: some View {
try! Modern.PokedexDemo.dataStack.perform(
synchronous: { transaction in
guard (try transaction.fetchCount(From<Modern.PokedexDemo.PokedexEntry>())) <= 0 else {
return
}
let pokedexEntry = transaction.create(Into<Modern.PokedexDemo.PokedexEntry>())
pokedexEntry.id = "bulbasaur"
pokedexEntry.url = URL(string: "https://pokeapi.co/api/v2/pokemon/1/")!
}
)
return Modern.PokedexDemo.ItemView(
pokedexEntry: Modern.PokedexDemo.pokedexEntries.snapshot.first!,
service: Modern.PokedexDemo.Service()
)
}
}
#endif

View File

@@ -32,33 +32,58 @@ extension Modern.PokedexDemo {
// MARK: View
var body: some View {
ZStack {
ScrollView {
ForEach(self.pokedexEntries.snapshot.prefix(self.visibleItems), id: \.self) { pokedexEntry in
LazyView {
Text(pokedexEntry.snapshot?.$name ?? "")
}
.frame(height: 100)
.frame(minWidth: 0, maxWidth: /*@START_MENU_TOKEN@*/.infinity/*@END_MENU_TOKEN@*/)
}
Button(
action: {
self.visibleItems = min(
self.visibleItems + 50,
self.pokedexEntries.snapshot.count
)
},
label: { Text("Load more") }
)
}
if self.service.isLoading {
Color(.sRGB, white: 0, opacity: 0.3)
.overlay(
let pokedexEntries = self.pokedexEntries.snapshot
let visibleItems = self.visibleItems
return ZStack {
if pokedexEntries.isEmpty {
VStack(alignment: .center, spacing: 20) {
Text("This demo needs to make a network connection to download Pokedex entries")
if self.service.isLoading {
Text("Fetching Pokedex…")
.foregroundColor(.white),
alignment: .center
)
.edgesIgnoringSafeArea(.bottom)
}
else {
Button(
action: { self.service.fetchPokedexEntries() },
label: {
Text("Download Pokedex Entries")
}
)
}
}
.padding()
}
else {
List {
ForEach(0 ..< min(visibleItems, pokedexEntries.count), id: \.self) { index in
LazyView {
Modern.PokedexDemo.ItemView(
pokedexEntry: pokedexEntries[index],
service: self.service
)
}
.frame(height: Modern.PokedexDemo.ItemView.preferredHeight)
.frame(minWidth: 0, maxWidth: /*@START_MENU_TOKEN@*/.infinity/*@END_MENU_TOKEN@*/)
}
if visibleItems < pokedexEntries.count {
Spacer(minLength: Modern.PokedexDemo.ItemView.preferredHeight)
.onAppear {
self.visibleItems = min(
visibleItems + 50,
pokedexEntries.count
)
}
}
}
.id(pokedexEntries)
}
}
.navigationBarTitle("Pokedex")

View File

@@ -13,15 +13,18 @@ extension Modern.PokedexDemo {
final class PokedexEntry: CoreStoreObject, ImportableUniqueObject {
// MARK: Internal
@Field.Stored("index")
var index: Int = 0
@Field.Stored("id")
var id: Int = 0
var id: String = ""
@Field.Stored("name")
var name: String = ""
@Field.Stored("url")
var url: URL!
@Field.Stored(
"url",
dynamicInitialValue: { URL(string: "data:application/json,%7B%7D")! }
)
var url: URL
@Field.Relationship("form")
@@ -35,32 +38,27 @@ extension Modern.PokedexDemo {
// MARK: ImportableUniqueObject
typealias UniqueIDType = Int
typealias UniqueIDType = String
static let uniqueIDKeyPath: String = String(keyPath: \Modern.PokedexDemo.PokedexEntry.$id)
var uniqueIDValue: UniqueIDType {
get {
return self.id
}
set {
self.id = newValue
}
get { return self.id }
set { self.id = newValue }
}
static func uniqueID(from source: ImportSource, in transaction: BaseDataTransaction) throws -> UniqueIDType? {
static func uniqueID(from source: ImportSource, in transaction: BaseDataTransaction) throws -> String? {
return source.index + 1
let json = source.json
return try Modern.PokedexDemo.Service.parseJSON(json["name"])
}
func update(from source: ImportSource, in transaction: BaseDataTransaction) throws {
let json = source.json
self.name = try Modern.PokedexDemo.Service.parseJSON(json["name"])
self.url = URL(string: try Modern.PokedexDemo.Service.parseJSON(json["url"]))
self.index = source.index
self.url = try Modern.PokedexDemo.Service.parseJSON(json["url"], transformer: URL.init(string:))
}
}
}

View File

@@ -17,7 +17,7 @@ extension Modern.PokedexDemo {
// MARK: Internal
private(set) var isLoading: Bool = true {
private(set) var isLoading: Bool = false {
willSet {
@@ -33,10 +33,7 @@ extension Modern.PokedexDemo {
}
}
init() {
self.fetchPokedexEntries()
}
init() {}
static func parseJSON<Output>(_ json: Any?) throws -> Output {
@@ -53,6 +50,29 @@ extension Modern.PokedexDemo {
}
}
static func parseJSON<JSONType, Output>(_ json: Any?, transformer: (JSONType) -> Output?) throws -> Output {
switch json {
case let json as JSONType:
let transformed = transformer(json)
if let json = transformed {
return json
}
throw Modern.PokedexDemo.Service.Error.parseError(
expected: Output.self,
actual: type(of: transformed)
)
case let any:
throw Modern.PokedexDemo.Service.Error.parseError(
expected: Output.self,
actual: type(of: any)
)
}
}
func fetchPokedexEntries() {
self.cancellable["pokedexEntries"] = self.pokedexEntries
@@ -96,14 +116,44 @@ extension Modern.PokedexDemo {
func fetchPokemonForm(for pokedexEntry: ObjectSnapshot<Modern.PokedexDemo.PokedexEntry>) {
self.cancellable["pokedexEntry.\(pokedexEntry.$id)"] = URLSession.shared
.dataTaskPublisher(for: pokedexEntry.$url!)
.receive(on: DispatchQueue.main)
self.cancellable["pokemonForm.\(pokedexEntry.$id)"] = URLSession.shared
.dataTaskPublisher(for: pokedexEntry.$url)
.eraseToAnyPublisher()
.sink(
receiveCompletion: { _ in },
receiveValue: { _ in
receiveValue: { output in
// do {
//
// let json: Dictionary<String, Any> = try Self.parseJSON(
// try JSONSerialization.jsonObject(with: output.data, options: [])
// )
// let results: [Dictionary<String, Any>] = try Self.parseJSON(
// json["results"]
// )
// Modern.PokedexDemo.dataStack.perform(
// asynchronous: { transaction -> Void in
//
// _ = try transaction.importUniqueObjects(
// Into<Modern.PokedexDemo.PokedexEntry>(),
// sourceArray: results.enumerated().map { (index, json) in
// (index: index, json: json)
// }
// )
// },
// success: { result in
//
// promise(.success(result))
// },
// failure: { error in
//
// promise(.failure(.saveError(error)))
// }
// )
// }
// catch {
//
// }
}
)
}

View File

@@ -46,7 +46,7 @@ extension Modern {
static let pokedexEntries: ListPublisher<Modern.PokedexDemo.PokedexEntry> = Modern.PokedexDemo.dataStack.publishList(
From<Modern.PokedexDemo.PokedexEntry>()
.orderBy(.ascending(\.$id))
.orderBy(.ascending(\.$index))
)
}
}

View File

@@ -43,7 +43,7 @@ extension Modern.TimeZonesDemo {
#if DEBUG
struct _Demo_Modern_TimeZone_ItemView_Preview: PreviewProvider {
struct _Demo_Modern_TimeZonesDemo_ItemView_Preview: PreviewProvider {
// MARK: PreviewProvider