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 */; }; 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 */; }; 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 */; }; 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 */; }; B5A3911D24E5429200E7E8BD /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A3911C24E5429200E7E8BD /* AppDelegate.swift */; };
B5A3911F24E5429200E7E8BD /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A3911E24E5429200E7E8BD /* SceneDelegate.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 */; }; 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>"; }; 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>"; }; 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>"; }; 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; }; 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>"; }; 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>"; }; B5A3911E24E5429200E7E8BD /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
@@ -375,6 +377,7 @@
B5A391B024E96AF600E7E8BD /* Modern.PokedexDemo.swift */, B5A391B024E96AF600E7E8BD /* Modern.PokedexDemo.swift */,
B531EFEA24EB5ECD005F247D /* Modern.PokedexDemo.Service.swift */, B531EFEA24EB5ECD005F247D /* Modern.PokedexDemo.Service.swift */,
B531EFEC24EB7453005F247D /* Modern.PokedexDemo.MainView.swift */, B531EFEC24EB7453005F247D /* Modern.PokedexDemo.MainView.swift */,
B54269C524ED190C00A66C23 /* Modern.PokedexDemo.ItemView.swift */,
B5A391B224E96B7400E7E8BD /* Models */, B5A391B224E96B7400E7E8BD /* Models */,
); );
path = PokedexDemo; path = PokedexDemo;
@@ -499,6 +502,7 @@
B5A3916524E698C700E7E8BD /* Modern.PlacemarksDemo.Place.swift in Sources */, B5A3916524E698C700E7E8BD /* Modern.PlacemarksDemo.Place.swift in Sources */,
B5A391BD24E977E500E7E8BD /* Modern.PokedexDemo.Ability.swift in Sources */, B5A391BD24E977E500E7E8BD /* Modern.PokedexDemo.Ability.swift in Sources */,
B5A391B624E96C5500E7E8BD /* Modern.PokedexDemo.Move.swift in Sources */, B5A391B624E96C5500E7E8BD /* Modern.PokedexDemo.Move.swift in Sources */,
B54269C624ED190C00A66C23 /* Modern.PokedexDemo.ItemView.swift in Sources */,
B531EFE924EB5A53005F247D /* Modern.PokedexDemo.PokedexEntry.swift in Sources */, B531EFE924EB5A53005F247D /* Modern.PokedexDemo.PokedexEntry.swift in Sources */,
B5A391B924E96F8500E7E8BD /* Modern.PokedexDemo.PokemonForm.swift in Sources */, B5A391B924E96F8500E7E8BD /* Modern.PokedexDemo.PokemonForm.swift in Sources */,
B5A391B424E96C0A00E7E8BD /* Modern.PokedexDemo.PokemonSpecies.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 // MARK: View
var body: some View { var body: some View {
ZStack { let pokedexEntries = self.pokedexEntries.snapshot
ScrollView { let visibleItems = self.visibleItems
ForEach(self.pokedexEntries.snapshot.prefix(self.visibleItems), id: \.self) { pokedexEntry in return ZStack {
LazyView {
Text(pokedexEntry.snapshot?.$name ?? "") if pokedexEntries.isEmpty {
}
.frame(height: 100) VStack(alignment: .center, spacing: 20) {
.frame(minWidth: 0, maxWidth: /*@START_MENU_TOKEN@*/.infinity/*@END_MENU_TOKEN@*/) Text("This demo needs to make a network connection to download Pokedex entries")
} if self.service.isLoading {
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(
Text("Fetching Pokedex…") Text("Fetching Pokedex…")
.foregroundColor(.white), }
alignment: .center else {
)
.edgesIgnoringSafeArea(.bottom) 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") .navigationBarTitle("Pokedex")

View File

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

View File

@@ -17,7 +17,7 @@ extension Modern.PokedexDemo {
// MARK: Internal // MARK: Internal
private(set) var isLoading: Bool = true { private(set) var isLoading: Bool = false {
willSet { willSet {
@@ -33,10 +33,7 @@ extension Modern.PokedexDemo {
} }
} }
init() { init() {}
self.fetchPokedexEntries()
}
static func parseJSON<Output>(_ json: Any?) throws -> Output { 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() { func fetchPokedexEntries() {
self.cancellable["pokedexEntries"] = self.pokedexEntries self.cancellable["pokedexEntries"] = self.pokedexEntries
@@ -96,14 +116,44 @@ extension Modern.PokedexDemo {
func fetchPokemonForm(for pokedexEntry: ObjectSnapshot<Modern.PokedexDemo.PokedexEntry>) { func fetchPokemonForm(for pokedexEntry: ObjectSnapshot<Modern.PokedexDemo.PokedexEntry>) {
self.cancellable["pokedexEntry.\(pokedexEntry.$id)"] = URLSession.shared self.cancellable["pokemonForm.\(pokedexEntry.$id)"] = URLSession.shared
.dataTaskPublisher(for: pokedexEntry.$url!) .dataTaskPublisher(for: pokedexEntry.$url)
.receive(on: DispatchQueue.main)
.eraseToAnyPublisher() .eraseToAnyPublisher()
.sink( .sink(
receiveCompletion: { _ in }, 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( static let pokedexEntries: ListPublisher<Modern.PokedexDemo.PokedexEntry> = Modern.PokedexDemo.dataStack.publishList(
From<Modern.PokedexDemo.PokedexEntry>() From<Modern.PokedexDemo.PokedexEntry>()
.orderBy(.ascending(\.$id)) .orderBy(.ascending(\.$index))
) )
} }
} }

View File

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