mirror of
https://github.com/JohnEstropia/CoreStore.git
synced 2026-04-23 17:28:32 +02:00
added demo for classic ListMonitor
This commit is contained in:
@@ -1,27 +0,0 @@
|
||||
//
|
||||
// Demo
|
||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
||||
|
||||
import CoreStore
|
||||
import UIKit
|
||||
|
||||
// MARK: - Modern.PokedexDemo
|
||||
|
||||
extension Modern.PokedexDemo {
|
||||
|
||||
// MARK: - Modern.PokedexDemo.Details
|
||||
|
||||
final class Details: CoreStoreObject {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
@Field.Relationship("pokedexEntry", inverse: \.$details)
|
||||
var pokedexEntry: Modern.PokedexDemo.PokedexEntry?
|
||||
|
||||
@Field.Relationship("species")
|
||||
var species: Modern.PokedexDemo.Species?
|
||||
|
||||
@Field.Relationship("forms")
|
||||
var forms: [Modern.PokedexDemo.Form]
|
||||
}
|
||||
}
|
||||
@@ -1,387 +0,0 @@
|
||||
//
|
||||
// Demo
|
||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
||||
|
||||
import Combine
|
||||
import CoreStore
|
||||
import UIKit
|
||||
|
||||
|
||||
// MARK: - Modern.PokedexDemo
|
||||
|
||||
extension Modern.PokedexDemo {
|
||||
|
||||
// MARK: - Modern.PokedexDemo.ItemCell
|
||||
|
||||
final class ItemCell: UICollectionViewCell {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
static let reuseIdentifier: String = NSStringFromClass(Modern.PokedexDemo.ItemCell.self)
|
||||
|
||||
func setPokedexEntry(
|
||||
_ pokedexEntry: Modern.PokedexDemo.PokedexEntry,
|
||||
service: Modern.PokedexDemo.Service
|
||||
) {
|
||||
|
||||
guard let pokedexEntry = pokedexEntry.asPublisher() else {
|
||||
|
||||
return self.pokedexEntry = nil
|
||||
}
|
||||
guard self.pokedexEntry != pokedexEntry else {
|
||||
|
||||
return
|
||||
}
|
||||
self.service = service
|
||||
self.pokedexEntry = pokedexEntry
|
||||
|
||||
self.didUpdateData(animated: false)
|
||||
|
||||
if let snapshot = pokedexEntry.snapshot {
|
||||
|
||||
service.fetchDetails(for: snapshot)
|
||||
}
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) {
|
||||
|
||||
fatalError()
|
||||
}
|
||||
|
||||
|
||||
// MARK: UITableViewCell
|
||||
|
||||
override init(frame: CGRect) {
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
let contentView = self.contentView
|
||||
do {
|
||||
|
||||
contentView.backgroundColor = UIColor.placeholderText.withAlphaComponent(0.1)
|
||||
contentView.layer.cornerRadius = 10
|
||||
contentView.layer.masksToBounds = true
|
||||
}
|
||||
|
||||
let typesContainerView = UIStackView()
|
||||
do {
|
||||
|
||||
typesContainerView.translatesAutoresizingMaskIntoConstraints = false
|
||||
typesContainerView.axis = .horizontal
|
||||
typesContainerView.alignment = .fill
|
||||
typesContainerView.distribution = .fillEqually
|
||||
typesContainerView.spacing = 0
|
||||
|
||||
typesContainerView.addArrangedSubview(self.type1View)
|
||||
typesContainerView.addArrangedSubview(self.type2View)
|
||||
|
||||
contentView.addSubview(typesContainerView)
|
||||
}
|
||||
|
||||
let spriteView = self.spriteView
|
||||
do {
|
||||
|
||||
spriteView.translatesAutoresizingMaskIntoConstraints = false
|
||||
spriteView.contentMode = .scaleAspectFill
|
||||
|
||||
contentView.addSubview(spriteView)
|
||||
}
|
||||
let placeholderLabel = self.placeholderLabel
|
||||
do {
|
||||
|
||||
placeholderLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||
placeholderLabel.textColor = UIColor.placeholderText
|
||||
placeholderLabel.font = UIFont.systemFont(ofSize: 20, weight: .heavy)
|
||||
placeholderLabel.numberOfLines = 0
|
||||
placeholderLabel.textAlignment = .center
|
||||
|
||||
contentView.addSubview(placeholderLabel)
|
||||
}
|
||||
let nameLabel = self.nameLabel
|
||||
do {
|
||||
|
||||
nameLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||
nameLabel.textColor = UIColor.white
|
||||
nameLabel.font = UIFont.systemFont(ofSize: 14, weight: .bold)
|
||||
nameLabel.numberOfLines = 0
|
||||
nameLabel.textAlignment = .center
|
||||
|
||||
contentView.addSubview(nameLabel)
|
||||
}
|
||||
|
||||
layout: do {
|
||||
|
||||
NSLayoutConstraint.activate(
|
||||
[
|
||||
typesContainerView.topAnchor.constraint(
|
||||
equalTo: contentView.topAnchor
|
||||
),
|
||||
typesContainerView.leadingAnchor.constraint(
|
||||
equalTo: contentView.leadingAnchor
|
||||
),
|
||||
typesContainerView.bottomAnchor.constraint(
|
||||
equalTo: contentView.bottomAnchor
|
||||
),
|
||||
typesContainerView.trailingAnchor.constraint(
|
||||
equalTo: contentView.trailingAnchor
|
||||
),
|
||||
|
||||
spriteView.topAnchor.constraint(
|
||||
equalTo: contentView.topAnchor
|
||||
),
|
||||
spriteView.leadingAnchor.constraint(
|
||||
equalTo: contentView.leadingAnchor
|
||||
),
|
||||
spriteView.bottomAnchor.constraint(
|
||||
equalTo: contentView.bottomAnchor
|
||||
),
|
||||
spriteView.trailingAnchor.constraint(
|
||||
equalTo: contentView.trailingAnchor
|
||||
),
|
||||
|
||||
placeholderLabel.topAnchor.constraint(
|
||||
equalTo: contentView.topAnchor,
|
||||
constant: 10
|
||||
),
|
||||
placeholderLabel.leadingAnchor.constraint(
|
||||
equalTo: contentView.leadingAnchor,
|
||||
constant: 10
|
||||
),
|
||||
placeholderLabel.bottomAnchor.constraint(
|
||||
equalTo: contentView.bottomAnchor,
|
||||
constant: -10
|
||||
),
|
||||
placeholderLabel.trailingAnchor.constraint(
|
||||
equalTo: contentView.trailingAnchor,
|
||||
constant: -10
|
||||
),
|
||||
|
||||
nameLabel.leadingAnchor.constraint(
|
||||
equalTo: contentView.leadingAnchor,
|
||||
constant: 10
|
||||
),
|
||||
nameLabel.bottomAnchor.constraint(
|
||||
equalTo: contentView.bottomAnchor,
|
||||
constant: -10
|
||||
),
|
||||
nameLabel.trailingAnchor.constraint(
|
||||
equalTo: contentView.trailingAnchor,
|
||||
constant: -10
|
||||
),
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override func prepareForReuse() {
|
||||
|
||||
super.prepareForReuse()
|
||||
|
||||
self.service = nil
|
||||
self.pokedexEntry = nil
|
||||
self.didUpdateData(animated: false)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let spriteView: UIImageView = .init()
|
||||
private let placeholderLabel: UILabel = .init()
|
||||
private let nameLabel: UILabel = .init()
|
||||
private let type1View: UIView = .init()
|
||||
private let type2View: UIView = .init()
|
||||
|
||||
private var service: Modern.PokedexDemo.Service?
|
||||
|
||||
private var imageURL: URL? {
|
||||
|
||||
didSet {
|
||||
|
||||
let newValue = self.imageURL
|
||||
guard newValue != oldValue else {
|
||||
|
||||
return
|
||||
}
|
||||
self.imageDownloader = ImageDownloader(url: newValue)
|
||||
}
|
||||
}
|
||||
|
||||
private var imageDownloader: ImageDownloader = .init(url: nil) {
|
||||
|
||||
didSet {
|
||||
|
||||
let url = self.imageDownloader.url
|
||||
if url == nil {
|
||||
|
||||
self.spriteView.image = nil
|
||||
return
|
||||
}
|
||||
self.imageDownloader.fetchImage { [weak self] in
|
||||
|
||||
guard let self = self, url == self.imageURL else {
|
||||
|
||||
return
|
||||
}
|
||||
self.spriteView.image = $0
|
||||
self.spriteView.layer.add(CATransition(), forKey: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var pokedexEntry: ObjectPublisher<Modern.PokedexDemo.PokedexEntry>? {
|
||||
|
||||
didSet {
|
||||
|
||||
let newValue = self.pokedexEntry
|
||||
guard newValue != oldValue else {
|
||||
|
||||
return
|
||||
}
|
||||
oldValue?.removeObserver(self)
|
||||
newValue?.addObserver(self) { [weak self] newValue in
|
||||
|
||||
guard let self = self else {
|
||||
|
||||
return
|
||||
}
|
||||
self.details = newValue.snapshot?.$details
|
||||
|
||||
self.didUpdateData(animated: true)
|
||||
}
|
||||
|
||||
self.details = newValue?.snapshot?.$details
|
||||
}
|
||||
}
|
||||
|
||||
private var details: ObjectPublisher<Modern.PokedexDemo.Details>? {
|
||||
|
||||
didSet {
|
||||
|
||||
let newValue = self.details
|
||||
guard newValue != oldValue else {
|
||||
|
||||
return
|
||||
}
|
||||
oldValue?.removeObserver(self)
|
||||
newValue?.addObserver(self) { [weak self] newValue in
|
||||
|
||||
guard let self = self else {
|
||||
|
||||
return
|
||||
}
|
||||
let details = newValue.snapshot
|
||||
self.species = details?.$species
|
||||
self.forms = details?.$forms
|
||||
|
||||
self.didUpdateData(animated: true)
|
||||
}
|
||||
|
||||
let details = newValue?.snapshot
|
||||
self.species = details?.$species
|
||||
self.forms = details?.$forms
|
||||
}
|
||||
}
|
||||
|
||||
private var species: ObjectPublisher<Modern.PokedexDemo.Species>? {
|
||||
|
||||
didSet {
|
||||
|
||||
let newValue = self.species
|
||||
guard newValue != oldValue else {
|
||||
|
||||
return
|
||||
}
|
||||
oldValue?.removeObserver(self)
|
||||
newValue?.addObserver(self) { [weak self] _ in
|
||||
|
||||
self?.didUpdateData(animated: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var formsRotationCancellable: AnyCancellable?
|
||||
private var forms: [ObjectPublisher<Modern.PokedexDemo.Form>]? {
|
||||
|
||||
didSet {
|
||||
|
||||
let newValue = self.forms
|
||||
guard newValue != oldValue else {
|
||||
|
||||
return
|
||||
}
|
||||
self.currentForm = newValue?.first
|
||||
|
||||
self.formsRotationCancellable = newValue.flatMap { newValue in
|
||||
|
||||
guard !newValue.isEmpty else {
|
||||
|
||||
return nil
|
||||
}
|
||||
return Timer
|
||||
.publish(every: 0.5, on: .main, in: .common)
|
||||
.autoconnect()
|
||||
.scan(1, { (index, _) in index + 1 })
|
||||
.sink(
|
||||
receiveValue: { [weak self] (index) in
|
||||
|
||||
guard let self = self else {
|
||||
|
||||
return
|
||||
}
|
||||
self.currentForm = newValue[index % newValue.count]
|
||||
self.didUpdateData(animated: true)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var currentForm: ObjectPublisher<Modern.PokedexDemo.Form>? {
|
||||
|
||||
didSet {
|
||||
|
||||
let newValue = self.currentForm
|
||||
guard newValue != oldValue else {
|
||||
|
||||
return
|
||||
}
|
||||
oldValue?.removeObserver(self)
|
||||
newValue?.addObserver(self) { [weak self] _ in
|
||||
|
||||
self?.didUpdateData(animated: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func didUpdateData(animated: Bool) {
|
||||
|
||||
let pokedexEntry = self.pokedexEntry?.snapshot
|
||||
let species = self.species?.snapshot
|
||||
let currentForm = self.currentForm?.snapshot
|
||||
|
||||
self.placeholderLabel.text = pokedexEntry?.$id
|
||||
self.placeholderLabel.isHidden = species != nil
|
||||
|
||||
self.type1View.backgroundColor = species?.$pokemonType1.color
|
||||
?? UIColor.clear
|
||||
self.type1View.isHidden = species == nil
|
||||
|
||||
self.type2View.backgroundColor = species?.$pokemonType2?.color
|
||||
?? species?.$pokemonType1.color
|
||||
?? UIColor.clear
|
||||
self.type2View.isHidden = species == nil
|
||||
|
||||
self.nameLabel.text = currentForm?.$name ?? species?.$name
|
||||
self.nameLabel.isHidden = currentForm == nil && species == nil
|
||||
|
||||
self.imageURL = currentForm?.$spriteURL
|
||||
|
||||
guard animated else {
|
||||
|
||||
return
|
||||
}
|
||||
self.contentView.layer.add(CATransition(), forKey: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
//
|
||||
// Demo
|
||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
||||
|
||||
import CoreStore
|
||||
import SwiftUI
|
||||
|
||||
// MARK: - Modern.PokedexDemo
|
||||
|
||||
extension Modern.PokedexDemo {
|
||||
|
||||
// MARK: - Modern.PokedexDemo.ListView
|
||||
|
||||
struct ListView: UIViewControllerRepresentable {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
init(
|
||||
service: Modern.PokedexDemo.Service,
|
||||
listPublisher: ListPublisher<Modern.PokedexDemo.PokedexEntry>
|
||||
) {
|
||||
|
||||
self.service = service
|
||||
self.listPublisher = listPublisher
|
||||
}
|
||||
|
||||
|
||||
// MARK: UIViewControllerRepresentable
|
||||
|
||||
typealias UIViewControllerType = Modern.PokedexDemo.ListViewController
|
||||
|
||||
func makeUIViewController(context: Self.Context) -> UIViewControllerType {
|
||||
|
||||
return UIViewControllerType(
|
||||
service: self.service,
|
||||
listPublisher: self.listPublisher
|
||||
)
|
||||
}
|
||||
|
||||
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Self.Context) {}
|
||||
|
||||
static func dismantleUIViewController(_ uiViewController: UIViewControllerType, coordinator: Void) {}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
@ObservedObject
|
||||
private var service: Modern.PokedexDemo.Service
|
||||
|
||||
private let listPublisher: ListPublisher<Modern.PokedexDemo.PokedexEntry>
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
|
||||
struct _Demo_Modern_PokedexDemo_ListView_Preview: PreviewProvider {
|
||||
|
||||
// MARK: PreviewProvider
|
||||
|
||||
static var previews: some View {
|
||||
|
||||
let service = Modern.PokedexDemo.Service()
|
||||
service.fetchPokedexEntries()
|
||||
|
||||
return Modern.PokedexDemo.ListView(
|
||||
service: service,
|
||||
listPublisher: Modern.PokedexDemo.pokedexEntries
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,111 +0,0 @@
|
||||
//
|
||||
// Demo
|
||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
||||
|
||||
import CoreStore
|
||||
import UIKit
|
||||
|
||||
|
||||
// MARK: - Modern.PokedexDemo
|
||||
|
||||
extension Modern.PokedexDemo {
|
||||
|
||||
// MARK: - Modern.PokedexDemo.ListViewController
|
||||
|
||||
final class ListViewController: UICollectionViewController {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
init(
|
||||
service: Modern.PokedexDemo.Service,
|
||||
listPublisher: ListPublisher<Modern.PokedexDemo.PokedexEntry>
|
||||
) {
|
||||
|
||||
self.service = service
|
||||
self.listPublisher = listPublisher
|
||||
|
||||
let layout = UICollectionViewFlowLayout()
|
||||
layout.sectionInset = .init(
|
||||
top: 10, left: 10, bottom: 10, right: 10
|
||||
)
|
||||
layout.minimumInteritemSpacing = 10
|
||||
layout.minimumLineSpacing = 10
|
||||
|
||||
let screenWidth = UIScreen.main.bounds.inset(by: layout.sectionInset).width
|
||||
let cellsPerRow: CGFloat = 3
|
||||
let cellWidth = min(
|
||||
230,
|
||||
floor((screenWidth - ((cellsPerRow - 1) * layout.minimumInteritemSpacing)) / cellsPerRow)
|
||||
)
|
||||
layout.itemSize = .init(
|
||||
width: cellWidth,
|
||||
height: ceil(cellWidth * (4 / 3))
|
||||
)
|
||||
super.init(collectionViewLayout: layout)
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) {
|
||||
|
||||
fatalError()
|
||||
}
|
||||
|
||||
deinit {
|
||||
|
||||
self.listPublisher.removeObserver(self)
|
||||
}
|
||||
|
||||
|
||||
// MARK: UIViewController
|
||||
|
||||
override func viewDidLoad() {
|
||||
|
||||
super.viewDidLoad()
|
||||
|
||||
self.collectionView.backgroundColor = UIColor.systemBackground
|
||||
|
||||
self.collectionView.register(
|
||||
Modern.PokedexDemo.ItemCell.self,
|
||||
forCellWithReuseIdentifier: Modern.PokedexDemo.ItemCell.reuseIdentifier
|
||||
)
|
||||
|
||||
self.startObservingList()
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let service: Modern.PokedexDemo.Service
|
||||
private let listPublisher: ListPublisher<Modern.PokedexDemo.PokedexEntry>
|
||||
|
||||
private lazy var dataSource: DiffableDataSource.CollectionViewAdapter<Modern.PokedexDemo.PokedexEntry> = .init(
|
||||
collectionView: self.collectionView,
|
||||
dataStack: Modern.PokedexDemo.dataStack,
|
||||
cellProvider: { (collectionView, indexPath, pokedexEntry) in
|
||||
|
||||
let cell = collectionView.dequeueReusableCell(
|
||||
withReuseIdentifier: Modern.PokedexDemo.ItemCell.reuseIdentifier,
|
||||
for: indexPath
|
||||
) as! Modern.PokedexDemo.ItemCell
|
||||
cell.setPokedexEntry(pokedexEntry, service: self.service)
|
||||
return cell
|
||||
}
|
||||
)
|
||||
|
||||
private func startObservingList() {
|
||||
|
||||
self.listPublisher.addObserver(self) { (listPublisher) in
|
||||
|
||||
self.dataSource.apply(
|
||||
listPublisher.snapshot,
|
||||
animatingDifferences: true
|
||||
)
|
||||
}
|
||||
self.dataSource.apply(
|
||||
self.listPublisher.snapshot,
|
||||
animatingDifferences: false
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
//
|
||||
// Demo
|
||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
||||
|
||||
import Combine
|
||||
import CoreStore
|
||||
import SwiftUI
|
||||
|
||||
// MARK: - Modern.PokedexDemo
|
||||
|
||||
extension Modern.PokedexDemo {
|
||||
|
||||
// MARK: - Modern.PokedexDemo.MainView
|
||||
|
||||
struct MainView: View {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
init() {
|
||||
|
||||
self.pokedexEntries = Modern.PokedexDemo.pokedexEntries
|
||||
}
|
||||
|
||||
|
||||
// MARK: View
|
||||
|
||||
var body: some View {
|
||||
let pokedexEntries = self.pokedexEntries.snapshot
|
||||
return ZStack {
|
||||
|
||||
Modern.PokedexDemo.ListView(
|
||||
service: self.service,
|
||||
listPublisher: self.pokedexEntries
|
||||
)
|
||||
.frame(minHeight: 0, maxHeight: .infinity)
|
||||
.edgesIgnoringSafeArea(.vertical)
|
||||
|
||||
if pokedexEntries.isEmpty {
|
||||
|
||||
VStack(alignment: .center, spacing: 30) {
|
||||
Text("This demo needs to make a network connection to download Pokedex entries")
|
||||
.multilineTextAlignment(.center)
|
||||
if self.service.isLoading {
|
||||
|
||||
Text("Fetching Pokedex…")
|
||||
}
|
||||
else {
|
||||
|
||||
Button(
|
||||
action: { self.service.fetchPokedexEntries() },
|
||||
label: {
|
||||
|
||||
Text("Download Pokedex Entries")
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
.navigationBarTitle("Pokedex")
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
@ObservedObject
|
||||
private var pokedexEntries: ListPublisher<Modern.PokedexDemo.PokedexEntry>
|
||||
|
||||
@ObservedObject
|
||||
private var service: Modern.PokedexDemo.Service = .init()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#if DEBUG
|
||||
|
||||
@available(iOS 14.0, *)
|
||||
struct _Demo_Modern_PokedexDemo_MainView_Preview: PreviewProvider {
|
||||
|
||||
// MARK: PreviewProvider
|
||||
|
||||
static var previews: some View {
|
||||
|
||||
Modern.PokedexDemo.MainView()
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,57 +0,0 @@
|
||||
//
|
||||
// Demo
|
||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
||||
|
||||
import CoreStore
|
||||
|
||||
// MARK: - Modern
|
||||
|
||||
extension Modern {
|
||||
|
||||
// MARK: - Modern.PokedexDemo
|
||||
|
||||
/**
|
||||
Sample usages for importing external data into `CoreStoreObject` attributes
|
||||
*/
|
||||
enum PokedexDemo {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
static let dataStack: DataStack = {
|
||||
|
||||
let dataStack = DataStack(
|
||||
CoreStoreSchema(
|
||||
modelVersion: "V1",
|
||||
entities: [
|
||||
Entity<Modern.PokedexDemo.PokedexEntry>("PokedexEntry"),
|
||||
Entity<Modern.PokedexDemo.Details>("Details"),
|
||||
Entity<Modern.PokedexDemo.Species>("Species"),
|
||||
Entity<Modern.PokedexDemo.Form>("Form")
|
||||
],
|
||||
versionLock: [
|
||||
"Details": [0x1cce0e9508eaa960, 0x74819067b54bd5c6, 0xc30c837f48811f10, 0x622bead2d27dea95],
|
||||
"Form": [0x7cb78e58bbb79e3c, 0x149557c60be8427, 0x6b30ad511d1d2d33, 0xb9f1319657b988dc],
|
||||
"PokedexEntry": [0xc212013c9be094eb, 0x3fd8f513e363194a, 0x8693cfb8988d3e75, 0x12717c1cc2645816],
|
||||
"Species": [0xda257fcd856bbf94, 0x1d556c6d7d2f52c5, 0xc46dd65d582a6e48, 0x943b1e876293ae1]
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
/**
|
||||
- Important: `addStorageAndWait(_:)` was used here to simplify initializing the demo, but in practice the asynchronous function variants are recommended.
|
||||
*/
|
||||
try! dataStack.addStorageAndWait(
|
||||
SQLiteStore(
|
||||
fileName: "Modern.PokedexDemo.sqlite",
|
||||
localStorageOptions: .recreateStoreOnModelMismatch
|
||||
)
|
||||
)
|
||||
return dataStack
|
||||
}()
|
||||
|
||||
static let pokedexEntries: ListPublisher<Modern.PokedexDemo.PokedexEntry> = Modern.PokedexDemo.dataStack.publishList(
|
||||
From<Modern.PokedexDemo.PokedexEntry>()
|
||||
.orderBy(.ascending(\.$index))
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
//
|
||||
// Demo
|
||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
||||
|
||||
import CoreStore
|
||||
import UIKit
|
||||
|
||||
// MARK: - Modern.PokedexDemo
|
||||
|
||||
extension Modern.PokedexDemo {
|
||||
|
||||
// MARK: - Modern.PokedexDemo.Form
|
||||
|
||||
/**
|
||||
⭐️ Sample 1: This sample shows how to declare `CoreStoreObject` subclasses that implement `ImportableUniqueObject`. For this class the `ImportSource` is a JSON `Dictionary`.
|
||||
*/
|
||||
final class Form: CoreStoreObject, ImportableUniqueObject {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
@Field.Stored("id")
|
||||
var id: Int = 0
|
||||
|
||||
@Field.Stored("name")
|
||||
var name: String?
|
||||
|
||||
@Field.Stored("spriteURL")
|
||||
var spriteURL: URL?
|
||||
|
||||
|
||||
@Field.Relationship("details", inverse: \.$forms)
|
||||
var details: Modern.PokedexDemo.Details?
|
||||
|
||||
|
||||
// MARK: ImportableObject
|
||||
|
||||
typealias ImportSource = Dictionary<String, Any>
|
||||
|
||||
|
||||
// MARK: ImportableUniqueObject
|
||||
|
||||
typealias UniqueIDType = Int
|
||||
|
||||
static let uniqueIDKeyPath: String = String(keyPath: \Modern.PokedexDemo.Form.$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.spriteURL = try? Service.parseJSON(
|
||||
json["sprites"],
|
||||
transformer: { (json: Dictionary<String, Any>) in
|
||||
try Service.parseJSON(json["front_default"], transformer: URL.init(string:))
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
//
|
||||
// Demo
|
||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
||||
|
||||
import CoreStore
|
||||
|
||||
// MARK: - Modern.PokedexDemo
|
||||
|
||||
extension Modern.PokedexDemo {
|
||||
|
||||
// MARK: - Modern.PokedexDemo.PokedexEntry
|
||||
|
||||
/**
|
||||
⭐️ Sample 1: This sample shows how to declare `CoreStoreObject` subclasses that implement `ImportableUniqueObject`. For this class the `ImportSource` is a tuple.
|
||||
*/
|
||||
final class PokedexEntry: CoreStoreObject, ImportableUniqueObject {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
@Field.Stored("index")
|
||||
var index: Int = 0
|
||||
|
||||
@Field.Stored("id")
|
||||
var id: String = ""
|
||||
|
||||
@Field.Stored(
|
||||
"speciesURL",
|
||||
dynamicInitialValue: { URL(string: "data:application/json,%7B%7D")! }
|
||||
)
|
||||
var speciesURL: URL
|
||||
|
||||
|
||||
@Field.Relationship("details")
|
||||
var details: Modern.PokedexDemo.Details?
|
||||
|
||||
|
||||
// MARK: ImportableObject
|
||||
|
||||
typealias ImportSource = (index: Int, json: Dictionary<String, Any>)
|
||||
|
||||
func didInsert(from source: ImportSource, in transaction: BaseDataTransaction) throws {
|
||||
|
||||
self.details = transaction.create(Into<Modern.PokedexDemo.Details>())
|
||||
|
||||
try self.update(from: source, in: transaction)
|
||||
}
|
||||
|
||||
|
||||
// MARK: ImportableUniqueObject
|
||||
|
||||
typealias UniqueIDType = String
|
||||
|
||||
static let uniqueIDKeyPath: String = String(keyPath: \Modern.PokedexDemo.PokedexEntry.$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.json
|
||||
return try Modern.PokedexDemo.Service.parseJSON(json["name"])
|
||||
}
|
||||
|
||||
func update(from source: ImportSource, in transaction: BaseDataTransaction) throws {
|
||||
|
||||
let json = source.json
|
||||
self.index = source.index
|
||||
self.speciesURL = try Modern.PokedexDemo.Service.parseJSON(json["url"], transformer: URL.init(string:))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
//
|
||||
// Demo
|
||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
||||
|
||||
import CoreStore
|
||||
import UIKit
|
||||
|
||||
// MARK: - Modern.PokedexDemo
|
||||
|
||||
extension Modern.PokedexDemo {
|
||||
|
||||
// MARK: - Modern.PokedexDemo.Move
|
||||
|
||||
/**
|
||||
⭐️ Sample 1: Types that will be used with `@Field.Stored` need to implement both `ImportableAttributeType` and `FieldStorableType`. In this case, `RawRepresentable` types with primitive `RawValue`s have built-in implementations so we only have to declare conformance to `ImportableAttributeType` and `FieldStorableType`.
|
||||
*/
|
||||
enum PokemonType: String, CaseIterable, ImportableAttributeType, FieldStorableType {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
case bug
|
||||
case dark
|
||||
case dragon
|
||||
case electric
|
||||
case fairy
|
||||
case fighting
|
||||
case fire
|
||||
case flying
|
||||
case ghost
|
||||
case grass
|
||||
case ground
|
||||
case ice
|
||||
case normal
|
||||
case poison
|
||||
case psychic
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,390 +0,0 @@
|
||||
//
|
||||
// Demo
|
||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import Combine
|
||||
import CoreStore
|
||||
import UIKit
|
||||
|
||||
|
||||
// MARK: - Modern.PokedexDemo
|
||||
|
||||
extension Modern.PokedexDemo {
|
||||
|
||||
// MARK: - Modern.PokedexDemo.Service
|
||||
|
||||
final class Service: ObservableObject {
|
||||
|
||||
/**
|
||||
⭐️ Sample 1: Importing a list of JSON data into `ImportableUniqueObject`s whose `ImportSource` are tuples
|
||||
*/
|
||||
private static func importPokedexEntries(
|
||||
from output: URLSession.DataTaskPublisher.Output
|
||||
) -> Future<Void, Modern.PokedexDemo.Service.Error> {
|
||||
|
||||
return .init { 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: [])
|
||||
)
|
||||
let results: [Dictionary<String, Any>] = try self.parseJSON(
|
||||
json["results"]
|
||||
)
|
||||
_ = 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
|
||||
|
||||
switch error {
|
||||
|
||||
case .userError(let error as Modern.PokedexDemo.Service.Error):
|
||||
promise(.failure(error))
|
||||
|
||||
case .userError(let error):
|
||||
promise(.failure(.otherError(error)))
|
||||
|
||||
case let error:
|
||||
promise(.failure(.saveError(error)))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
⭐️ Sample 2: Importing a single JSON data into an `ImportableUniqueObject` whose `ImportSource` is a JSON `Dictionary`
|
||||
*/
|
||||
private static func importSpecies(
|
||||
for details: ObjectSnapshot<Modern.PokedexDemo.Details>,
|
||||
from output: URLSession.DataTaskPublisher.Output
|
||||
) -> Future<ObjectSnapshot<Modern.PokedexDemo.Species>, Modern.PokedexDemo.Service.Error> {
|
||||
|
||||
return .init { promise in
|
||||
|
||||
Modern.PokedexDemo.dataStack.perform(
|
||||
asynchronous: { transaction -> Modern.PokedexDemo.Species in
|
||||
|
||||
let json: Dictionary<String, Any> = try self.parseJSON(
|
||||
try JSONSerialization.jsonObject(with: output.data, options: [])
|
||||
)
|
||||
guard
|
||||
let species = try transaction.importUniqueObject(
|
||||
Into<Modern.PokedexDemo.Species>(),
|
||||
source: json
|
||||
)
|
||||
else {
|
||||
|
||||
throw Modern.PokedexDemo.Service.Error.unexpected
|
||||
}
|
||||
details.asEditable(in: transaction)?.species = species
|
||||
return species
|
||||
},
|
||||
success: { species in
|
||||
|
||||
promise(.success(species.asSnapshot(in: Modern.PokedexDemo.dataStack)!))
|
||||
},
|
||||
failure: { error in
|
||||
|
||||
switch error {
|
||||
|
||||
case .userError(let error as Modern.PokedexDemo.Service.Error):
|
||||
promise(.failure(error))
|
||||
|
||||
case .userError(let error):
|
||||
promise(.failure(.otherError(error)))
|
||||
|
||||
case let error:
|
||||
promise(.failure(.saveError(error)))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
⭐️ Sample 3: Importing a list of JSON data into `ImportableUniqueObject`s whose `ImportSource` are JSON `Dictionary`s
|
||||
*/
|
||||
private static func importForms(
|
||||
for details: ObjectSnapshot<Modern.PokedexDemo.Details>,
|
||||
from outputs: [URLSession.DataTaskPublisher.Output]
|
||||
) -> Future<Void, Modern.PokedexDemo.Service.Error> {
|
||||
|
||||
return .init { promise in
|
||||
|
||||
Modern.PokedexDemo.dataStack.perform(
|
||||
asynchronous: { transaction -> Void in
|
||||
|
||||
let forms = try transaction.importUniqueObjects(
|
||||
Into<Modern.PokedexDemo.Form>(),
|
||||
sourceArray: outputs.map { output in
|
||||
|
||||
return try self.parseJSON(
|
||||
try JSONSerialization.jsonObject(with: output.data, options: [])
|
||||
)
|
||||
}
|
||||
)
|
||||
guard !forms.isEmpty else {
|
||||
|
||||
throw Modern.PokedexDemo.Service.Error.unexpected
|
||||
}
|
||||
details.asEditable(in: transaction)?.forms = forms
|
||||
},
|
||||
success: {
|
||||
|
||||
promise(.success(()))
|
||||
},
|
||||
failure: { error in
|
||||
|
||||
switch error {
|
||||
|
||||
case .userError(let error as Modern.PokedexDemo.Service.Error):
|
||||
promise(.failure(error))
|
||||
|
||||
case .userError(let error):
|
||||
promise(.failure(.otherError(error)))
|
||||
|
||||
case let error:
|
||||
promise(.failure(.saveError(error)))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
private(set) var isLoading: Bool = false {
|
||||
|
||||
willSet {
|
||||
|
||||
self.objectWillChange.send()
|
||||
}
|
||||
}
|
||||
|
||||
private(set) var lastError: (error: Modern.PokedexDemo.Service.Error, retry: () -> Void)? {
|
||||
|
||||
willSet {
|
||||
|
||||
self.objectWillChange.send()
|
||||
}
|
||||
}
|
||||
|
||||
init() {}
|
||||
|
||||
static func parseJSON<Output>(
|
||||
_ json: Any?,
|
||||
file: StaticString = #file,
|
||||
line: Int = #line
|
||||
) throws -> Output {
|
||||
|
||||
switch json {
|
||||
|
||||
case let json as Output:
|
||||
return json
|
||||
|
||||
case let any:
|
||||
throw Modern.PokedexDemo.Service.Error.parseError(
|
||||
expected: Output.self,
|
||||
actual: type(of: any),
|
||||
file: "\(file):\(line)"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
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 = try transformer(json)
|
||||
if let json = transformed {
|
||||
|
||||
return json
|
||||
}
|
||||
throw Modern.PokedexDemo.Service.Error.parseError(
|
||||
expected: Output.self,
|
||||
actual: type(of: transformed),
|
||||
file: "\(file):\(line)"
|
||||
)
|
||||
|
||||
case let any:
|
||||
throw Modern.PokedexDemo.Service.Error.parseError(
|
||||
expected: Output.self,
|
||||
actual: type(of: any),
|
||||
file: "\(file):\(line)"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func fetchPokedexEntries() {
|
||||
|
||||
self.cancellable["pokedexEntries"] = self.pokedexEntries
|
||||
.receive(on: DispatchQueue.main)
|
||||
.handleEvents(
|
||||
receiveSubscription: { [weak self] _ in
|
||||
|
||||
guard let self = self else {
|
||||
|
||||
return
|
||||
}
|
||||
self.lastError = nil
|
||||
self.isLoading = true
|
||||
}
|
||||
)
|
||||
.sink(
|
||||
receiveCompletion: { [weak self] completion in
|
||||
|
||||
guard let self = self else {
|
||||
|
||||
return
|
||||
}
|
||||
self.isLoading = false
|
||||
switch completion {
|
||||
|
||||
case .finished:
|
||||
self.lastError = nil
|
||||
|
||||
case .failure(let error):
|
||||
print(error)
|
||||
self.lastError = (
|
||||
error: error,
|
||||
retry: { [weak self] in
|
||||
|
||||
self?.fetchPokedexEntries()
|
||||
}
|
||||
)
|
||||
}
|
||||
},
|
||||
receiveValue: {}
|
||||
)
|
||||
}
|
||||
|
||||
func fetchDetails(for pokedexEntry: ObjectSnapshot<Modern.PokedexDemo.PokedexEntry>) {
|
||||
|
||||
self.fetchSpeciesIfNeeded(for: pokedexEntry)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private var cancellable: Dictionary<String, AnyCancellable> = [:]
|
||||
|
||||
private lazy var pokedexEntries: AnyPublisher<Void, Modern.PokedexDemo.Service.Error> = URLSession.shared
|
||||
.dataTaskPublisher(
|
||||
for: URL(string: "https://pokeapi.co/api/v2/pokemon?limit=10000&offset=0")!
|
||||
)
|
||||
.mapError({ .networkError($0) })
|
||||
.flatMap(Self.importPokedexEntries(from:))
|
||||
.eraseToAnyPublisher()
|
||||
|
||||
private func fetchSpeciesIfNeeded(for pokedexEntry: ObjectSnapshot<Modern.PokedexDemo.PokedexEntry>) {
|
||||
|
||||
guard let details = pokedexEntry.$details?.snapshot else {
|
||||
|
||||
return
|
||||
}
|
||||
if let species = details.$species?.snapshot {
|
||||
|
||||
self.fetchFormsIfNeeded(for: species)
|
||||
return
|
||||
}
|
||||
self.cancellable["species.\(pokedexEntry.$id)"] = URLSession.shared
|
||||
.dataTaskPublisher(for: pokedexEntry.$speciesURL)
|
||||
.mapError({ .networkError($0) })
|
||||
.flatMap({ Self.importSpecies(for: details, from: $0) })
|
||||
.sink(
|
||||
receiveCompletion: { completion in
|
||||
|
||||
switch completion {
|
||||
|
||||
case .finished:
|
||||
break
|
||||
|
||||
case .failure(let error):
|
||||
print(error)
|
||||
}
|
||||
},
|
||||
receiveValue: { species in
|
||||
|
||||
self.fetchFormsIfNeeded(for: species)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private func fetchFormsIfNeeded(for species: ObjectSnapshot<Modern.PokedexDemo.Species>) {
|
||||
|
||||
guard
|
||||
let details = species.$details?.snapshot,
|
||||
details.$forms.isEmpty
|
||||
else {
|
||||
|
||||
return
|
||||
}
|
||||
self.cancellable["forms.\(species.$id)"] = species
|
||||
.$formsURLs
|
||||
.map(
|
||||
{
|
||||
URLSession.shared
|
||||
.dataTaskPublisher(for: $0)
|
||||
.mapError({ Modern.PokedexDemo.Service.Error.networkError($0) })
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
)
|
||||
.reduce(
|
||||
into: Just<[URLSession.DataTaskPublisher.Output]>([])
|
||||
.setFailureType(to: Modern.PokedexDemo.Service.Error.self)
|
||||
.eraseToAnyPublisher(),
|
||||
{ (result, publisher) in
|
||||
result = result
|
||||
.zip(publisher, { $0 + [$1] })
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
)
|
||||
.flatMap({ Self.importForms(for: details, from: $0) })
|
||||
.sink(
|
||||
receiveCompletion: { completion in
|
||||
|
||||
switch completion {
|
||||
|
||||
case .finished:
|
||||
break
|
||||
|
||||
case .failure(let error):
|
||||
print(error)
|
||||
}
|
||||
},
|
||||
receiveValue: { _ in }
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Modern.PokedexDemo.Service.Error
|
||||
|
||||
enum Error: Swift.Error {
|
||||
|
||||
case networkError(URLError)
|
||||
case parseError(expected: Any.Type, actual: Any.Type, file: String)
|
||||
case saveError(CoreStoreError)
|
||||
case otherError(Swift.Error)
|
||||
case unexpected
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,139 +0,0 @@
|
||||
//
|
||||
// Demo
|
||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
||||
|
||||
import CoreStore
|
||||
|
||||
// MARK: - Modern.PokedexDemo
|
||||
|
||||
extension Modern.PokedexDemo {
|
||||
|
||||
// MARK: - Modern.PokedexDemo.Species
|
||||
|
||||
/**
|
||||
⭐️ Sample 1: This sample shows how to declare `CoreStoreObject` subclasses that implement `ImportableUniqueObject`. For this class the `ImportSource` is a JSON `Dictionary`.
|
||||
*/
|
||||
final class Species: CoreStoreObject, ImportableUniqueObject {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
@Field.Stored("id")
|
||||
var id: Int = 0
|
||||
|
||||
@Field.Stored("name")
|
||||
var name: String = ""
|
||||
|
||||
@Field.Stored("weight")
|
||||
var weight: Int = 0
|
||||
|
||||
@Field.Stored("pokemonType1")
|
||||
var pokemonType1: Modern.PokedexDemo.PokemonType = .normal
|
||||
|
||||
@Field.Stored("pokemonType2")
|
||||
var pokemonType2: Modern.PokedexDemo.PokemonType?
|
||||
|
||||
|
||||
@Field.Stored("statHitPoints")
|
||||
var statHitPoints: Int = 0
|
||||
|
||||
@Field.Stored("statAttack")
|
||||
var statAttack: Int = 0
|
||||
|
||||
@Field.Stored("statDefense")
|
||||
var statDefense: Int = 0
|
||||
|
||||
@Field.Stored("statSpecialAttack")
|
||||
var statSpecialAttack: Int = 0
|
||||
|
||||
@Field.Stored("statSpecialDefense")
|
||||
var statSpecialDefense: Int = 0
|
||||
|
||||
@Field.Stored("statSpeed")
|
||||
var statSpeed: Int = 0
|
||||
|
||||
|
||||
@Field.Coded(
|
||||
"formsURLs",
|
||||
coder: FieldCoders.Json.self
|
||||
)
|
||||
var formsURLs: [URL] = []
|
||||
|
||||
|
||||
@Field.Relationship("details", inverse: \.$species)
|
||||
var details: Modern.PokedexDemo.Details?
|
||||
|
||||
|
||||
// MARK: ImportableObject
|
||||
|
||||
typealias ImportSource = Dictionary<String, Any>
|
||||
|
||||
|
||||
// MARK: ImportableUniqueObject
|
||||
|
||||
typealias UniqueIDType = Int
|
||||
|
||||
static let uniqueIDKeyPath: String = String(keyPath: \Modern.PokedexDemo.Species.$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
|
||||
}
|
||||
}
|
||||
|
||||
self.formsURLs = try (Service.parseJSON(json["forms"]) as [Dictionary<String, Any>])
|
||||
.map({ try Service.parseJSON($0["url"], transformer: URL.init(string:)) })
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user