mirror of
https://github.com/JohnEstropia/CoreStore.git
synced 2026-01-14 21:23:43 +01:00
WIP
This commit is contained in:
@@ -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 */,
|
||||
|
||||
@@ -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
|
||||
@@ -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")
|
||||
|
||||
@@ -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:))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
//
|
||||
// }
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user