mirror of
https://github.com/JohnEstropia/CoreStore.git
synced 2026-03-14 14:21:38 +01:00
pokedex demo
This commit is contained in:
@@ -12,6 +12,7 @@
|
||||
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 */; };
|
||||
B566C8E624ED6B98001134A1 /* NetworkImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B566C8E524ED6B98001134A1 /* NetworkImageView.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 */; };
|
||||
@@ -61,7 +62,7 @@
|
||||
B5A391AC24E9143B00E7E8BD /* Modern.ColorsDemo.UIKit.DetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A391AB24E9143B00E7E8BD /* Modern.ColorsDemo.UIKit.DetailView.swift */; };
|
||||
B5A391AE24E9150F00E7E8BD /* Modern.ColorsDemo.UIKit.DetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A391AD24E9150F00E7E8BD /* Modern.ColorsDemo.UIKit.DetailViewController.swift */; };
|
||||
B5A391B124E96AF600E7E8BD /* Modern.PokedexDemo.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A391B024E96AF600E7E8BD /* Modern.PokedexDemo.swift */; };
|
||||
B5A391B424E96C0A00E7E8BD /* Modern.PokedexDemo.PokemonSpecies.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A391B324E96C0A00E7E8BD /* Modern.PokedexDemo.PokemonSpecies.swift */; };
|
||||
B5A391B424E96C0A00E7E8BD /* Modern.PokedexDemo.PokemonDisplay.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A391B324E96C0A00E7E8BD /* Modern.PokedexDemo.PokemonDisplay.swift */; };
|
||||
B5A391B624E96C5500E7E8BD /* Modern.PokedexDemo.Move.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A391B524E96C5500E7E8BD /* Modern.PokedexDemo.Move.swift */; };
|
||||
B5A391B924E96F8500E7E8BD /* Modern.PokedexDemo.PokemonForm.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A391B824E96F8500E7E8BD /* Modern.PokedexDemo.PokemonForm.swift */; };
|
||||
B5A391BB24E970A400E7E8BD /* Modern.PokedexDemo.PokemonType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A391BA24E970A400E7E8BD /* Modern.PokedexDemo.PokemonType.swift */; };
|
||||
@@ -91,6 +92,7 @@
|
||||
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>"; };
|
||||
B566C8E524ED6B98001134A1 /* NetworkImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkImageView.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>"; };
|
||||
@@ -139,7 +141,7 @@
|
||||
B5A391AB24E9143B00E7E8BD /* Modern.ColorsDemo.UIKit.DetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.ColorsDemo.UIKit.DetailView.swift; sourceTree = "<group>"; };
|
||||
B5A391AD24E9150F00E7E8BD /* Modern.ColorsDemo.UIKit.DetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.ColorsDemo.UIKit.DetailViewController.swift; sourceTree = "<group>"; };
|
||||
B5A391B024E96AF600E7E8BD /* Modern.PokedexDemo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.PokedexDemo.swift; sourceTree = "<group>"; };
|
||||
B5A391B324E96C0A00E7E8BD /* Modern.PokedexDemo.PokemonSpecies.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.PokedexDemo.PokemonSpecies.swift; sourceTree = "<group>"; };
|
||||
B5A391B324E96C0A00E7E8BD /* Modern.PokedexDemo.PokemonDisplay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.PokedexDemo.PokemonDisplay.swift; sourceTree = "<group>"; };
|
||||
B5A391B524E96C5500E7E8BD /* Modern.PokedexDemo.Move.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.PokedexDemo.Move.swift; sourceTree = "<group>"; };
|
||||
B5A391B824E96F8500E7E8BD /* Modern.PokedexDemo.PokemonForm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.PokedexDemo.PokemonForm.swift; sourceTree = "<group>"; };
|
||||
B5A391BA24E970A400E7E8BD /* Modern.PokedexDemo.PokemonType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.PokedexDemo.PokemonType.swift; sourceTree = "<group>"; };
|
||||
@@ -300,6 +302,7 @@
|
||||
children = (
|
||||
B5A3917B24E6A76C00E7E8BD /* LazyView.swift */,
|
||||
B5A3917F24E787D900E7E8BD /* InstructionsView.swift */,
|
||||
B566C8E524ED6B98001134A1 /* NetworkImageView.swift */,
|
||||
B5A391A724E90F1000E7E8BD /* UIImage+Extensions.swift */,
|
||||
B5A3915424E6857F00E7E8BD /* Menu */,
|
||||
);
|
||||
@@ -387,8 +390,8 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B531EFE824EB5A52005F247D /* Modern.PokedexDemo.PokedexEntry.swift */,
|
||||
B5A391B324E96C0A00E7E8BD /* Modern.PokedexDemo.PokemonSpecies.swift */,
|
||||
B5A391B824E96F8500E7E8BD /* Modern.PokedexDemo.PokemonForm.swift */,
|
||||
B5A391B324E96C0A00E7E8BD /* Modern.PokedexDemo.PokemonDisplay.swift */,
|
||||
B5A391B524E96C5500E7E8BD /* Modern.PokedexDemo.Move.swift */,
|
||||
B5A391BC24E977E500E7E8BD /* Modern.PokedexDemo.Ability.swift */,
|
||||
B5A391B724E96E8600E7E8BD /* Attributes */,
|
||||
@@ -490,6 +493,7 @@
|
||||
B5A3915E24E6922E00E7E8BD /* Modern.PlacemarksDemo.swift in Sources */,
|
||||
B5A391B124E96AF600E7E8BD /* Modern.PokedexDemo.swift in Sources */,
|
||||
B5A3918324E7A21800E7E8BD /* Modern.TimeZonesDemo.swift in Sources */,
|
||||
B566C8E624ED6B98001134A1 /* NetworkImageView.swift in Sources */,
|
||||
B531EFEB24EB5ECD005F247D /* Modern.PokedexDemo.Service.swift in Sources */,
|
||||
B5A3919824E7E67000E7E8BD /* Modern.ColorsDemo.Filter.swift in Sources */,
|
||||
B5A391A624E8F4EA00E7E8BD /* Modern.ColorsDemo.MainView.swift in Sources */,
|
||||
@@ -505,7 +509,7 @@
|
||||
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 */,
|
||||
B5A391B424E96C0A00E7E8BD /* Modern.PokedexDemo.PokemonDisplay.swift in Sources */,
|
||||
B5A391BB24E970A400E7E8BD /* Modern.PokedexDemo.PokemonType.swift in Sources */,
|
||||
B5A3918C24E7B44B00E7E8BD /* Modern.TimeZonesDemo.ItemView.swift in Sources */,
|
||||
B5A3918A24E7AD1800E7E8BD /* Modern.TimeZonesDemo.ListView.swift in Sources */,
|
||||
|
||||
@@ -32,12 +32,7 @@ extension Modern.PlacemarksDemo {
|
||||
"annotation",
|
||||
customGetter: { object, field in
|
||||
|
||||
Modern.PlacemarksDemo.Place.Annotation(
|
||||
latitude: object.$latitude.value,
|
||||
longitude: object.$longitude.value,
|
||||
title: object.$title.value,
|
||||
subtitle: object.$subtitle.value
|
||||
)
|
||||
Modern.PlacemarksDemo.Place.Annotation(object)
|
||||
},
|
||||
customSetter: { object, field, newValue in
|
||||
|
||||
@@ -115,6 +110,16 @@ extension Modern.PlacemarksDemo {
|
||||
self.title = title
|
||||
self.subtitle = subtitle
|
||||
}
|
||||
|
||||
fileprivate init(_ object: ObjectProxy<Modern.PlacemarksDemo.Place>) {
|
||||
|
||||
self.coordinate = .init(
|
||||
latitude: object.$latitude.value,
|
||||
longitude: object.$longitude.value
|
||||
)
|
||||
self.title = object.$title.value
|
||||
self.subtitle = object.$subtitle.value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,22 +32,52 @@ extension Modern.PokedexDemo {
|
||||
var body: some View {
|
||||
|
||||
let pokedexEntry = self.pokedexEntry.snapshot
|
||||
let form = pokedexEntry?.$form
|
||||
let placeholderColor = Color.init(.sRGB, white: 0.95, opacity: 1)
|
||||
let pokemonForm = pokedexEntry?.$pokemonForm?.snapshot
|
||||
let pokemonDisplay = pokemonForm?.$pokemonDisplay?.snapshot
|
||||
|
||||
return HStack(spacing: 10) {
|
||||
placeholderColor
|
||||
.frame(width: 70, height: 70)
|
||||
.cornerRadius(10)
|
||||
|
||||
LazyView {
|
||||
|
||||
NetworkImageView(url: pokemonDisplay?.$spriteURL)
|
||||
.frame(width: 70, height: 70)
|
||||
.id(pokemonDisplay)
|
||||
}
|
||||
ZStack {
|
||||
|
||||
if let pokemonForm = pokemonForm {
|
||||
|
||||
Text(form?.$name ?? pokedexEntry?.$id ?? "")
|
||||
.foregroundColor(form == nil ? placeholderColor : .init(.darkText))
|
||||
.fontWeight(form == nil ? .heavy : .regular)
|
||||
.frame(maxWidth: .infinity)
|
||||
VStack(alignment: .leading) {
|
||||
|
||||
HStack {
|
||||
Text(pokemonDisplay?.$displayName ?? pokemonForm.$name)
|
||||
Spacer()
|
||||
}
|
||||
HStack {
|
||||
self.view(for: pokemonForm.$pokemonType1)
|
||||
if let pokemonType2 = pokemonForm.$pokemonType2 {
|
||||
|
||||
self.view(for: pokemonType2)
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
Text(pokedexEntry?.$id ?? "")
|
||||
.foregroundColor(Color(UIColor.placeholderText))
|
||||
.fontWeight(.heavy)
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
.padding()
|
||||
.onAppear {
|
||||
|
||||
if let pokedexEntry = pokedexEntry, form == nil {
|
||||
if let pokedexEntry = pokedexEntry {
|
||||
|
||||
self.service.fetchPokemonForm(for: pokedexEntry)
|
||||
}
|
||||
@@ -61,6 +91,19 @@ extension Modern.PokedexDemo {
|
||||
private var pokedexEntry: ObjectPublisher<Modern.PokedexDemo.PokedexEntry>
|
||||
|
||||
private let service: Modern.PokedexDemo.Service
|
||||
|
||||
private func view(for pokemonType: Modern.PokedexDemo.PokemonType) -> some View {
|
||||
ZStack {
|
||||
Color(pokemonType.color)
|
||||
.cornerRadius(5)
|
||||
Text(pokemonType.rawValue)
|
||||
.font(.subheadline)
|
||||
.fontWeight(.bold)
|
||||
.foregroundColor(.white)
|
||||
.padding(.horizontal, 5)
|
||||
.padding(.vertical, 2)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,7 +125,7 @@ struct _Demo_Modern_PokedexDemo_ItemView_Preview: PreviewProvider {
|
||||
}
|
||||
let pokedexEntry = transaction.create(Into<Modern.PokedexDemo.PokedexEntry>())
|
||||
pokedexEntry.id = "bulbasaur"
|
||||
pokedexEntry.url = URL(string: "https://pokeapi.co/api/v2/pokemon/1/")!
|
||||
pokedexEntry.pokemonFormURL = URL(string: "https://pokeapi.co/api/v2/pokemon/1/")!
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -83,7 +83,6 @@ extension Modern.PokedexDemo {
|
||||
}
|
||||
}
|
||||
}
|
||||
.id(pokedexEntries)
|
||||
}
|
||||
}
|
||||
.navigationBarTitle("Pokedex")
|
||||
|
||||
@@ -21,14 +21,20 @@ extension Modern.PokedexDemo {
|
||||
var id: String = ""
|
||||
|
||||
@Field.Stored(
|
||||
"url",
|
||||
"pokemonFormURL",
|
||||
dynamicInitialValue: { URL(string: "data:application/json,%7B%7D")! }
|
||||
)
|
||||
var url: URL
|
||||
var pokemonFormURL: URL
|
||||
|
||||
@Field.Stored(
|
||||
"updateHash",
|
||||
dynamicInitialValue: { UUID() }
|
||||
)
|
||||
var updateHash: UUID
|
||||
|
||||
|
||||
@Field.Relationship("form")
|
||||
var form: Modern.PokedexDemo.PokemonForm?
|
||||
@Field.Relationship("pokemonForm")
|
||||
var pokemonForm: Modern.PokedexDemo.PokemonForm?
|
||||
|
||||
|
||||
// MARK: ImportableObject
|
||||
@@ -48,7 +54,7 @@ extension Modern.PokedexDemo {
|
||||
set { self.id = newValue }
|
||||
}
|
||||
|
||||
static func uniqueID(from source: ImportSource, in transaction: BaseDataTransaction) throws -> String? {
|
||||
static func uniqueID(from source: ImportSource, in transaction: BaseDataTransaction) throws -> UniqueIDType? {
|
||||
|
||||
let json = source.json
|
||||
return try Modern.PokedexDemo.Service.parseJSON(json["name"])
|
||||
@@ -58,7 +64,7 @@ extension Modern.PokedexDemo {
|
||||
|
||||
let json = source.json
|
||||
self.index = source.index
|
||||
self.url = try Modern.PokedexDemo.Service.parseJSON(json["url"], transformer: URL.init(string:))
|
||||
self.pokemonFormURL = try Modern.PokedexDemo.Service.parseJSON(json["url"], transformer: URL.init(string:))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
//
|
||||
// Demo
|
||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
||||
|
||||
import CoreStore
|
||||
import UIKit
|
||||
|
||||
// MARK: - Modern.PokedexDemo
|
||||
|
||||
extension Modern.PokedexDemo {
|
||||
|
||||
// MARK: - Modern.PokedexDemo.PokemonDisplay
|
||||
|
||||
final class PokemonDisplay: CoreStoreObject, ImportableUniqueObject {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
@Field.Stored("id")
|
||||
var id: Int = 0
|
||||
|
||||
@Field.Stored("displayName")
|
||||
var displayName: String?
|
||||
|
||||
@Field.Stored("spriteURL")
|
||||
var spriteURL: URL?
|
||||
|
||||
|
||||
@Field.Relationship("form")
|
||||
var pokedexForm: Modern.PokedexDemo.PokemonForm?
|
||||
|
||||
|
||||
// MARK: ImportableObject
|
||||
|
||||
typealias ImportSource = Dictionary<String, Any>
|
||||
|
||||
|
||||
// MARK: ImportableUniqueObject
|
||||
|
||||
typealias UniqueIDType = Int
|
||||
|
||||
static let uniqueIDKeyPath: String = String(keyPath: \Modern.PokedexDemo.PokemonDisplay.$id)
|
||||
|
||||
var uniqueIDValue: UniqueIDType {
|
||||
|
||||
get { return self.id }
|
||||
set { self.id = newValue }
|
||||
}
|
||||
|
||||
static func uniqueID(from source: ImportSource, in transaction: BaseDataTransaction) throws -> UniqueIDType? {
|
||||
|
||||
let json = source
|
||||
return try Modern.PokedexDemo.Service.parseJSON(json["id"])
|
||||
}
|
||||
|
||||
func update(from source: ImportSource, in transaction: BaseDataTransaction) throws {
|
||||
|
||||
typealias Service = Modern.PokedexDemo.Service
|
||||
let json = source
|
||||
|
||||
for json in try Service.parseJSON(json["names"]) as [Dictionary<String, Any>] {
|
||||
|
||||
let displayName: String = try Service.parseJSON(json["name"])
|
||||
let language: String = try Service.parseJSON(
|
||||
json["language"],
|
||||
transformer: { (json: Dictionary<String, Any>) in
|
||||
try Service.parseJSON(json["name"])
|
||||
}
|
||||
)
|
||||
switch language {
|
||||
|
||||
case "en": self.displayName = displayName
|
||||
default: break
|
||||
}
|
||||
}
|
||||
self.spriteURL = try Service.parseJSON(
|
||||
json["sprites"],
|
||||
transformer: { (json: Dictionary<String, Any>) in
|
||||
try Service.parseJSON(json["front_default"], transformer: URL.init(string:))
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ extension Modern.PokedexDemo {
|
||||
|
||||
// MARK: - Modern.PokedexDemo.PokemonForm
|
||||
|
||||
final class PokemonForm: CoreStoreObject {
|
||||
final class PokemonForm: CoreStoreObject, ImportableUniqueObject {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
@@ -28,9 +28,6 @@ extension Modern.PokedexDemo {
|
||||
|
||||
@Field.Stored("pokemonType2")
|
||||
var pokemonType2: Modern.PokedexDemo.PokemonType?
|
||||
|
||||
@Field.Stored("spriteURL")
|
||||
var spriteURL: URL?
|
||||
|
||||
|
||||
@Field.Stored("statHitPoints")
|
||||
@@ -52,6 +49,16 @@ extension Modern.PokedexDemo {
|
||||
var statSpeed: Int = 0
|
||||
|
||||
|
||||
@Field.Stored(
|
||||
"pokemonDisplayURL",
|
||||
dynamicInitialValue: { URL(string: "data:application/json,%7B%7D")! }
|
||||
)
|
||||
var pokemonDisplayURL: URL
|
||||
|
||||
|
||||
@Field.Relationship("display", inverse: \.$pokedexForm)
|
||||
var pokemonDisplay: Modern.PokedexDemo.PokemonDisplay?
|
||||
|
||||
@Field.Relationship("abilities", inverse: \.$learners)
|
||||
var abilities: Set<Modern.PokedexDemo.Ability>
|
||||
|
||||
@@ -59,10 +66,99 @@ extension Modern.PokedexDemo {
|
||||
var moves: Set<Modern.PokedexDemo.Move>
|
||||
|
||||
|
||||
@Field.Relationship("pokedexEntry", inverse: \.$form)
|
||||
@Field.Relationship("pokedexEntry", inverse: \.$pokemonForm)
|
||||
var pokedexEntry: Modern.PokedexDemo.PokedexEntry?
|
||||
|
||||
@Field.Relationship("species", inverse: \.$forms)
|
||||
var species: Modern.PokedexDemo.PokemonSpecies?
|
||||
|
||||
// MARK: ImportableObject
|
||||
|
||||
typealias ImportSource = Dictionary<String, Any>
|
||||
|
||||
|
||||
// MARK: ImportableUniqueObject
|
||||
|
||||
typealias UniqueIDType = Int
|
||||
|
||||
static let uniqueIDKeyPath: String = String(keyPath: \Modern.PokedexDemo.PokemonForm.$id)
|
||||
|
||||
var uniqueIDValue: UniqueIDType {
|
||||
|
||||
get { return self.id }
|
||||
set { self.id = newValue }
|
||||
}
|
||||
|
||||
static func uniqueID(from source: ImportSource, in transaction: BaseDataTransaction) throws -> UniqueIDType? {
|
||||
|
||||
let json = source
|
||||
return try Modern.PokedexDemo.Service.parseJSON(json["id"])
|
||||
}
|
||||
|
||||
func update(from source: ImportSource, in transaction: BaseDataTransaction) throws {
|
||||
|
||||
typealias Service = Modern.PokedexDemo.Service
|
||||
let json = source
|
||||
|
||||
self.name = try Service.parseJSON(json["name"])
|
||||
self.weight = try Service.parseJSON(json["weight"])
|
||||
|
||||
for json in try Service.parseJSON(json["types"]) as [Dictionary<String, Any>] {
|
||||
|
||||
let slot: Int = try Service.parseJSON(json["slot"])
|
||||
let pokemonType = try Service.parseJSON(
|
||||
json["type"],
|
||||
transformer: { (json: Dictionary<String, Any>) in
|
||||
Modern.PokedexDemo.PokemonType(rawValue: try Service.parseJSON(json["name"]))
|
||||
}
|
||||
)
|
||||
switch slot {
|
||||
|
||||
case 1: self.pokemonType1 = pokemonType
|
||||
case 2: self.pokemonType2 = pokemonType
|
||||
default: continue
|
||||
}
|
||||
}
|
||||
|
||||
for json in try Service.parseJSON(json["stats"]) as [Dictionary<String, Any>] {
|
||||
|
||||
let baseStat: Int = try Service.parseJSON(json["base_stat"])
|
||||
let name: String = try Service.parseJSON(
|
||||
json["stat"],
|
||||
transformer: { (json: Dictionary<String, Any>) in
|
||||
try Service.parseJSON(json["name"])
|
||||
}
|
||||
)
|
||||
switch name {
|
||||
|
||||
case "hp": self.statHitPoints = baseStat
|
||||
case "attack": self.statAttack = baseStat
|
||||
case "defense": self.statDefense = baseStat
|
||||
case "special-attack": self.statSpecialAttack = baseStat
|
||||
case "special-defense": self.statSpecialDefense = baseStat
|
||||
case "speed": self.statSpeed = baseStat
|
||||
default: continue
|
||||
}
|
||||
}
|
||||
|
||||
do {
|
||||
|
||||
let abilities: [Dictionary<String, Any>] = try Service.parseJSON(json["abilities"])
|
||||
}
|
||||
do {
|
||||
|
||||
let moves: [Dictionary<String, Any>] = try Service.parseJSON(json["moves"])
|
||||
}
|
||||
|
||||
for json in try Service.parseJSON(json["forms"]) as [Dictionary<String, Any>] {
|
||||
|
||||
let name: String = try Service.parseJSON(json["name"])
|
||||
let pokemonDisplayURL = try Service.parseJSON(json["url"], transformer: URL.init(string:))
|
||||
|
||||
guard name == self.name else {
|
||||
|
||||
continue
|
||||
}
|
||||
self.pokemonDisplayURL = pokemonDisplayURL
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
//
|
||||
// Demo
|
||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
||||
|
||||
import CoreStore
|
||||
|
||||
// MARK: - Modern.PokedexDemo
|
||||
|
||||
extension Modern.PokedexDemo {
|
||||
|
||||
// MARK: - Modern.PokedexDemo.PokemonSpecies
|
||||
|
||||
final class PokemonSpecies: CoreStoreObject {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
@Field.Stored("id")
|
||||
var id: Int = 0
|
||||
|
||||
@Field.Stored("name")
|
||||
var name: String = ""
|
||||
|
||||
@Field.Stored("weight")
|
||||
var weight: Int = 0
|
||||
|
||||
|
||||
@Field.Relationship("forms")
|
||||
var forms: Set<Modern.PokedexDemo.PokemonForm>
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
||||
|
||||
import CoreStore
|
||||
import UIKit
|
||||
|
||||
// MARK: - Modern.PokedexDemo
|
||||
|
||||
@@ -32,5 +33,30 @@ extension Modern.PokedexDemo {
|
||||
case rock
|
||||
case steel
|
||||
case water
|
||||
|
||||
var color: UIColor {
|
||||
|
||||
switch self {
|
||||
|
||||
case .bug: return #colorLiteral(red: 0.568627450980392, green: 0.749019607843137, blue: 0.231372549019608, alpha: 1.0) // #91BF3B, a: 1.0
|
||||
case .dark: return #colorLiteral(red: 0.392156862745098, green: 0.388235294117647, blue: 0.454901960784314, alpha: 1.0) // #646374, a: 1.0
|
||||
case .dragon: return #colorLiteral(red: 0.0823529411764706, green: 0.423529411764706, blue: 0.741176470588235, alpha: 1.0) // #156CBD, a: 1.0
|
||||
case .electric: return #colorLiteral(red: 0.949019607843137, green: 0.819607843137255, blue: 0.298039215686275, alpha: 1.0) // #F2D14C, a: 1.0
|
||||
case .fairy: return #colorLiteral(red: 0.913725490196078, green: 0.56078431372549, blue: 0.882352941176471, alpha: 1.0) // #E98FE1, a: 1.0
|
||||
case .fighting: return #colorLiteral(red: 0.8, green: 0.254901960784314, blue: 0.423529411764706, alpha: 1.0) // #CC416C, a: 1.0
|
||||
case .fire: return #colorLiteral(red: 0.992156862745098, green: 0.607843137254902, blue: 0.352941176470588, alpha: 1.0) // #FD9B5A, a: 1.0
|
||||
case .flying: return #colorLiteral(red: 0.619607843137255, green: 0.701960784313725, blue: 0.886274509803922, alpha: 1.0) // #9EB3E2, a: 1.0
|
||||
case .ghost: return #colorLiteral(red: 0.333333333333333, green: 0.419607843137255, blue: 0.670588235294118, alpha: 1.0) // #556BAB, a: 1.0
|
||||
case .grass: return #colorLiteral(red: 0.38823529411764707, green: 0.7215686274509804, blue: 0.3803921568627451, alpha: 1.0) // #63B861, a: 1.0
|
||||
case .ground: return #colorLiteral(red: 0.847058823529412, green: 0.458823529411765, blue: 0.298039215686275, alpha: 1.0) // #D8754C, a: 1.0
|
||||
case .ice: return #colorLiteral(red: 0.466666666666667, green: 0.803921568627451, blue: 0.756862745098039, alpha: 1.0) // #77CDC1, a: 1.0
|
||||
case .normal: return #colorLiteral(red: 0.564705882352941, green: 0.603921568627451, blue: 0.627450980392157, alpha: 1.0) // #909AA0, a: 1.0
|
||||
case .poison: return #colorLiteral(red: 0.647058823529412, green: 0.411764705882353, blue: 0.768627450980392, alpha: 1.0) // #A569C4, a: 1.0
|
||||
case .psychic: return #colorLiteral(red: 0.9764705882, green: 0.5058823529, blue: 0.5019607843, alpha: 1) // #F98180, a: 1.0
|
||||
case .rock: return #colorLiteral(red: 0.776470588235294, green: 0.717647058823529, blue: 0.556862745098039, alpha: 1.0) // #C6B78E, a: 1.0
|
||||
case .steel: return #colorLiteral(red: 0.329411764705882, green: 0.529411764705882, blue: 0.607843137254902, alpha: 1.0) // #54879B, a: 1.0
|
||||
case .water: return #colorLiteral(red: 0.325490196078431, green: 0.576470588235294, blue: 0.823529411764706, alpha: 1.0) // #5393D2, a: 1.0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import Foundation
|
||||
import Combine
|
||||
import CoreStore
|
||||
import UIKit
|
||||
|
||||
|
||||
// MARK: - Modern.PokedexDemo
|
||||
@@ -35,7 +36,11 @@ extension Modern.PokedexDemo {
|
||||
|
||||
init() {}
|
||||
|
||||
static func parseJSON<Output>(_ json: Any?) throws -> Output {
|
||||
static func parseJSON<Output>(
|
||||
_ json: Any?,
|
||||
file: StaticString = #file,
|
||||
line: Int = #line
|
||||
) throws -> Output {
|
||||
|
||||
switch json {
|
||||
|
||||
@@ -45,30 +50,38 @@ extension Modern.PokedexDemo {
|
||||
case let any:
|
||||
throw Modern.PokedexDemo.Service.Error.parseError(
|
||||
expected: Output.self,
|
||||
actual: type(of: any)
|
||||
actual: type(of: any),
|
||||
file: "\(file):\(line)"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
static func parseJSON<JSONType, Output>(_ json: Any?, transformer: (JSONType) -> Output?) throws -> Output {
|
||||
static func parseJSON<JSONType, Output>(
|
||||
_ json: Any?,
|
||||
transformer: (JSONType) throws -> Output?,
|
||||
file: StaticString = #file,
|
||||
line: Int = #line
|
||||
) throws -> Output {
|
||||
|
||||
switch json {
|
||||
|
||||
case let json as JSONType:
|
||||
let transformed = transformer(json)
|
||||
let transformed = try transformer(json)
|
||||
if let json = transformed {
|
||||
|
||||
return json
|
||||
}
|
||||
throw Modern.PokedexDemo.Service.Error.parseError(
|
||||
expected: Output.self,
|
||||
actual: type(of: transformed)
|
||||
actual: type(of: transformed),
|
||||
file: "\(file):\(line)"
|
||||
)
|
||||
|
||||
case let any:
|
||||
throw Modern.PokedexDemo.Service.Error.parseError(
|
||||
expected: Output.self,
|
||||
actual: type(of: any)
|
||||
actual: type(of: any),
|
||||
file: "\(file):\(line)"
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -76,6 +89,7 @@ extension Modern.PokedexDemo {
|
||||
func fetchPokedexEntries() {
|
||||
|
||||
self.cancellable["pokedexEntries"] = self.pokedexEntries
|
||||
.receive(on: DispatchQueue.main)
|
||||
.handleEvents(
|
||||
receiveSubscription: { [weak self] _ in
|
||||
|
||||
@@ -101,6 +115,7 @@ extension Modern.PokedexDemo {
|
||||
self.lastError = nil
|
||||
|
||||
case .failure(let error):
|
||||
print(error)
|
||||
self.lastError = (
|
||||
error: error,
|
||||
retry: { [weak self] in
|
||||
@@ -116,44 +131,157 @@ extension Modern.PokedexDemo {
|
||||
|
||||
func fetchPokemonForm(for pokedexEntry: ObjectSnapshot<Modern.PokedexDemo.PokedexEntry>) {
|
||||
|
||||
if let pokedexForm = pokedexEntry.$pokemonForm?.snapshot {
|
||||
|
||||
self.fetchPokemonDisplay(for: pokedexForm)
|
||||
return
|
||||
}
|
||||
self.cancellable["pokemonForm.\(pokedexEntry.$id)"] = URLSession.shared
|
||||
.dataTaskPublisher(for: pokedexEntry.$url)
|
||||
.eraseToAnyPublisher()
|
||||
.dataTaskPublisher(for: pokedexEntry.$pokemonFormURL)
|
||||
.mapError({ .networkError($0) })
|
||||
.flatMap(
|
||||
{ output in
|
||||
|
||||
return Future<ObjectSnapshot<Modern.PokedexDemo.PokemonForm>, Modern.PokedexDemo.Service.Error> { promise in
|
||||
|
||||
Modern.PokedexDemo.dataStack.perform(
|
||||
asynchronous: { transaction -> Modern.PokedexDemo.PokemonForm in
|
||||
|
||||
let json: Dictionary<String, Any> = try Self.parseJSON(
|
||||
try JSONSerialization.jsonObject(with: output.data, options: [])
|
||||
)
|
||||
guard let pokedexForm = try transaction.importUniqueObject(
|
||||
Into<Modern.PokedexDemo.PokemonForm>(),
|
||||
source: json
|
||||
) else {
|
||||
|
||||
throw Modern.PokedexDemo.Service.Error.unexpected
|
||||
}
|
||||
if let pokedexEntry = pokedexEntry.asEditable(in: transaction) {
|
||||
|
||||
pokedexForm.pokedexEntry = pokedexEntry
|
||||
pokedexEntry.updateHash = .init()
|
||||
}
|
||||
return pokedexForm
|
||||
},
|
||||
success: { pokemonForm in
|
||||
|
||||
promise(.success(pokemonForm.asSnapshot(in: Modern.PokedexDemo.dataStack)!))
|
||||
},
|
||||
failure: { error in
|
||||
|
||||
switch error {
|
||||
|
||||
case .userError(let error):
|
||||
switch error {
|
||||
|
||||
case let error as Modern.PokedexDemo.Service.Error:
|
||||
promise(.failure(error))
|
||||
|
||||
case let error:
|
||||
promise(.failure(.otherError(error)))
|
||||
}
|
||||
|
||||
case let error:
|
||||
promise(.failure(.saveError(error)))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
.sink(
|
||||
receiveCompletion: { _ in },
|
||||
receiveCompletion: { completion in
|
||||
|
||||
switch completion {
|
||||
|
||||
case .finished:
|
||||
break
|
||||
|
||||
case .failure(let error):
|
||||
print(error)
|
||||
}
|
||||
},
|
||||
receiveValue: { pokemonForm in
|
||||
|
||||
self.fetchPokemonDisplay(for: pokemonForm)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
func fetchPokemonDisplay(for pokemonForm: ObjectSnapshot<Modern.PokedexDemo.PokemonForm>) {
|
||||
|
||||
if let pokemonDisplay = pokemonForm.$pokemonDisplay?.snapshot {
|
||||
|
||||
return
|
||||
}
|
||||
self.cancellable["pokemonDisplay.\(pokemonForm.$id)"] = URLSession.shared
|
||||
.dataTaskPublisher(for: pokemonForm.$pokemonDisplayURL)
|
||||
.mapError({ .networkError($0) })
|
||||
.flatMap(
|
||||
{ output in
|
||||
|
||||
return Future<Void, Modern.PokedexDemo.Service.Error> { promise in
|
||||
|
||||
Modern.PokedexDemo.dataStack.perform(
|
||||
asynchronous: { transaction -> Void in
|
||||
|
||||
let json: Dictionary<String, Any> = try Self.parseJSON(
|
||||
try JSONSerialization.jsonObject(with: output.data, options: [])
|
||||
)
|
||||
guard let pokemonDisplay = try transaction.importUniqueObject(
|
||||
Into<Modern.PokedexDemo.PokemonDisplay>(),
|
||||
source: json
|
||||
) else {
|
||||
|
||||
throw Modern.PokedexDemo.Service.Error.unexpected
|
||||
}
|
||||
if let pokemonForm = pokemonForm.asEditable(in: transaction) {
|
||||
|
||||
pokemonDisplay.pokedexForm = pokemonForm
|
||||
pokemonForm.pokedexEntry?.updateHash = .init()
|
||||
}
|
||||
},
|
||||
success: {
|
||||
|
||||
promise(.success(()))
|
||||
},
|
||||
failure: { error in
|
||||
|
||||
switch error {
|
||||
|
||||
case .userError(let error):
|
||||
switch error {
|
||||
|
||||
case let error as Modern.PokedexDemo.Service.Error:
|
||||
promise(.failure(error))
|
||||
|
||||
case let error:
|
||||
promise(.failure(.otherError(error)))
|
||||
}
|
||||
|
||||
case let error:
|
||||
promise(.failure(.saveError(error)))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
.sink(
|
||||
receiveCompletion: { completion in
|
||||
|
||||
switch completion {
|
||||
|
||||
case .finished:
|
||||
break
|
||||
|
||||
case .failure(let error):
|
||||
print(error)
|
||||
}
|
||||
},
|
||||
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 {
|
||||
//
|
||||
// }
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -212,7 +340,6 @@ extension Modern.PokedexDemo {
|
||||
}
|
||||
}
|
||||
)
|
||||
.receive(on: DispatchQueue.main)
|
||||
.eraseToAnyPublisher()
|
||||
|
||||
|
||||
@@ -221,9 +348,10 @@ extension Modern.PokedexDemo {
|
||||
enum Error: Swift.Error {
|
||||
|
||||
case networkError(URLError)
|
||||
case parseError(expected: Any.Type, actual: Any.Type)
|
||||
case parseError(expected: Any.Type, actual: Any.Type, file: String)
|
||||
case saveError(CoreStoreError)
|
||||
case otherError(Swift.Error)
|
||||
case unexpected
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,8 +24,8 @@ extension Modern {
|
||||
modelVersion: "V1",
|
||||
entities: [
|
||||
Entity<Modern.PokedexDemo.PokedexEntry>("PokedexEntry"),
|
||||
Entity<Modern.PokedexDemo.PokemonSpecies>("PokemonSpecies"),
|
||||
Entity<Modern.PokedexDemo.PokemonForm>("PokemonForm"),
|
||||
Entity<Modern.PokedexDemo.PokemonDisplay>("PokemonDisplay"),
|
||||
Entity<Modern.PokedexDemo.Move>("Move"),
|
||||
Entity<Modern.PokedexDemo.Ability>("Ability")
|
||||
]
|
||||
|
||||
107
Demo/Sources/Helpers/NetworkImageView.swift
Normal file
107
Demo/Sources/Helpers/NetworkImageView.swift
Normal file
@@ -0,0 +1,107 @@
|
||||
//
|
||||
// Demo
|
||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
||||
|
||||
import Combine
|
||||
import SwiftUI
|
||||
|
||||
// MARK: - NetworkImageView
|
||||
|
||||
struct NetworkImageView: View {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
init(url: URL?) {
|
||||
|
||||
self.imageDownloader = .init(url: url)
|
||||
}
|
||||
|
||||
|
||||
// MARK: View
|
||||
|
||||
var body: some View {
|
||||
if let image = self.imageDownloader.image {
|
||||
|
||||
Image(uiImage: image)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
}
|
||||
else {
|
||||
|
||||
Circle()
|
||||
.colorMultiply(Color(UIColor.placeholderText))
|
||||
.onAppear {
|
||||
|
||||
self.imageDownloader.fetchImage()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
@ObservedObject
|
||||
private var imageDownloader: ImageDownloader
|
||||
|
||||
|
||||
// MARK: - NetworkImageView.ImageDownloader
|
||||
|
||||
fileprivate final class ImageDownloader: ObservableObject {
|
||||
|
||||
// MARK: FilePrivate
|
||||
|
||||
private(set) var image: UIImage?
|
||||
|
||||
let url: URL?
|
||||
|
||||
init(url: URL?) {
|
||||
|
||||
self.url = url
|
||||
guard let url = url else {
|
||||
|
||||
return
|
||||
}
|
||||
if let image = Self.cache[url] {
|
||||
|
||||
self.image = image
|
||||
}
|
||||
}
|
||||
|
||||
func fetchImage() {
|
||||
|
||||
guard let url = url else {
|
||||
|
||||
return
|
||||
}
|
||||
if let image = Self.cache[url] {
|
||||
|
||||
self.objectWillChange.send()
|
||||
self.image = image
|
||||
return
|
||||
}
|
||||
self.cancellable = URLSession.shared
|
||||
.dataTaskPublisher(for: url)
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink(
|
||||
receiveCompletion: { _ in },
|
||||
receiveValue: { output in
|
||||
|
||||
if let image = UIImage(data: output.data) {
|
||||
|
||||
Self.cache[url] = image
|
||||
self.objectWillChange.send()
|
||||
self.image = image
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private static var cache: [URL: UIImage] = [:]
|
||||
|
||||
private var cancellable: AnyCancellable?
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user