mirror of
https://github.com/JohnEstropia/CoreStore.git
synced 2026-03-24 02:11:26 +01:00
WIP
This commit is contained in:
@@ -132,7 +132,7 @@ extension Modern.ColorsDemo {
|
||||
|
||||
private static func randomSaturation() -> Float {
|
||||
|
||||
return Float.random(in: 0.0 ... 1.0)
|
||||
return Float.random(in: 0.4 ... 1.0)
|
||||
}
|
||||
|
||||
private static func randomBrightness() -> Float {
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
//
|
||||
// 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 {
|
||||
|
||||
/**
|
||||
⭐️ Sample 1: Setting a sectioned `ListPublisher` declared as an `@ObservedObject`
|
||||
*/
|
||||
@ObservedObject
|
||||
private var pokedexEntries: ListPublisher<Modern.PokedexDemo.PokedexEntry>
|
||||
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
init() {
|
||||
|
||||
self.pokedexEntries = Modern.PokedexDemo.pokedexEntries
|
||||
}
|
||||
|
||||
|
||||
// MARK: View
|
||||
|
||||
var body: some View {
|
||||
List() {
|
||||
ForEach(self.pokedexEntries.snapshot, id: \.self) { pokedexEntry in
|
||||
LazyView {
|
||||
Text(pokedexEntry.snapshot?.$id ?? "")
|
||||
}
|
||||
}
|
||||
}
|
||||
.overlay(
|
||||
InstructionsView(
|
||||
("Random", "Sets random coordinate"),
|
||||
("Tap", "Sets to tapped coordinate")
|
||||
)
|
||||
.padding(.leading, 10)
|
||||
.padding(.bottom, 40),
|
||||
alignment: .bottomLeading
|
||||
)
|
||||
.navigationBarTitle("Pokedex")
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let service: Modern.PokedexDemo.Service = .init()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#if DEBUG
|
||||
|
||||
struct _Demo_Modern_PokedexDemo_MainView_Preview: PreviewProvider {
|
||||
|
||||
// MARK: PreviewProvider
|
||||
|
||||
static var previews: some View {
|
||||
|
||||
Modern.PokedexDemo.MainView()
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,59 @@
|
||||
//
|
||||
// Demo
|
||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
||||
|
||||
import CoreStore
|
||||
|
||||
// MARK: - Modern.PokedexDemo
|
||||
|
||||
extension Modern.PokedexDemo {
|
||||
|
||||
// MARK: - Modern.PokedexDemo.PokedexEntry
|
||||
|
||||
final class PokedexEntry: CoreStoreObject, ImportableUniqueObject {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
@Field.Stored("id")
|
||||
var id: String = ""
|
||||
|
||||
@Field.Stored("url")
|
||||
var url: URL!
|
||||
|
||||
|
||||
@Field.Relationship("form")
|
||||
var form: Modern.PokedexDemo.PokemonForm?
|
||||
|
||||
|
||||
// MARK: ImportableObject
|
||||
|
||||
typealias ImportSource = Dictionary<String, Any>
|
||||
|
||||
|
||||
// MARK: ImportableUniqueObject
|
||||
|
||||
static let uniqueIDKeyPath: String = String(keyPath: \Modern.PokedexDemo.PokedexEntry.$id)
|
||||
|
||||
var uniqueIDValue: String {
|
||||
|
||||
get {
|
||||
|
||||
return self.id
|
||||
}
|
||||
set {
|
||||
|
||||
self.id = newValue
|
||||
}
|
||||
}
|
||||
|
||||
static func uniqueID(from source: ImportSource, in transaction: BaseDataTransaction) throws -> String? {
|
||||
|
||||
return try Modern.PokedexDemo.Service.parseJSON(source["name"])
|
||||
}
|
||||
|
||||
func update(from source: ImportSource, in transaction: BaseDataTransaction) throws {
|
||||
|
||||
self.url = URL(string: try Modern.PokedexDemo.Service.parseJSON(source["url"]))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,15 +19,18 @@ extension Modern.PokedexDemo {
|
||||
|
||||
@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.Relationship("species")
|
||||
var species: Modern.PokedexDemo.PokemonSpecies?
|
||||
|
||||
@Field.Stored("spriteURL")
|
||||
var spriteURL: URL?
|
||||
|
||||
|
||||
@Field.Stored("statHitPoints")
|
||||
@@ -49,23 +52,17 @@ extension Modern.PokedexDemo {
|
||||
var statSpeed: Int = 0
|
||||
|
||||
|
||||
@Field.Stored("spriteFrontURL")
|
||||
var spriteFrontURL: URL?
|
||||
|
||||
@Field.Stored("spriteBackURL")
|
||||
var spriteBackURL: URL?
|
||||
|
||||
@Field.Stored("spriteShinyFrontURL")
|
||||
var spriteShinyFrontURL: URL?
|
||||
|
||||
@Field.Stored("spriteShinyBackURL")
|
||||
var spriteShinyBackURL: URL?
|
||||
|
||||
|
||||
@Field.Relationship("abilities", inverse: \.$learners)
|
||||
var abilities: Set<Modern.PokedexDemo.Ability>
|
||||
|
||||
@Field.Relationship("moves", inverse: \.$learners)
|
||||
var moves: Set<Modern.PokedexDemo.Move>
|
||||
|
||||
|
||||
@Field.Relationship("pokedexEntry", inverse: \.$form)
|
||||
var pokedexEntry: Modern.PokedexDemo.PokedexEntry?
|
||||
|
||||
@Field.Relationship("species", inverse: \.$forms)
|
||||
var species: Modern.PokedexDemo.PokemonSpecies?
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,8 +22,9 @@ extension Modern.PokedexDemo {
|
||||
|
||||
@Field.Stored("weight")
|
||||
var weight: Int = 0
|
||||
|
||||
|
||||
@Field.Relationship("forms", inverse: \.$species)
|
||||
@Field.Relationship("forms")
|
||||
var forms: Set<Modern.PokedexDemo.PokemonForm>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
//
|
||||
// Demo
|
||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import Combine
|
||||
import CoreStore
|
||||
|
||||
|
||||
// MARK: - Modern.PokedexDemo
|
||||
|
||||
extension Modern.PokedexDemo {
|
||||
|
||||
// MARK: - Modern.PokedexDemo.Service
|
||||
|
||||
final class Service {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
@Published
|
||||
var isLoading: Bool = true
|
||||
|
||||
@Published
|
||||
var lastError: (error: Modern.PokedexDemo.Service.Error, retry: () -> Void)?
|
||||
|
||||
init() {
|
||||
|
||||
self.fetchPokedexEntries()
|
||||
}
|
||||
|
||||
static func parseJSON<Output>(_ json: Any?) 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)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func fetchPokedexEntries() {
|
||||
|
||||
self.cancellable["pokedexEntries"] = self.pokedexEntries
|
||||
.handleEvents(
|
||||
receiveSubscription: { [weak self] _ in
|
||||
|
||||
print("Fetching Pokedex Entries")
|
||||
guard let self = self else {
|
||||
|
||||
return
|
||||
}
|
||||
self.lastError = nil
|
||||
self.isLoading = true
|
||||
}
|
||||
)
|
||||
.sink(
|
||||
receiveCompletion: { [weak self] completion in
|
||||
|
||||
print("Result (Fetching Pokedex Entries): \(completion)")
|
||||
guard let self = self else {
|
||||
|
||||
return
|
||||
}
|
||||
self.isLoading = false
|
||||
switch completion {
|
||||
|
||||
case .finished:
|
||||
self.lastError = nil
|
||||
|
||||
case .failure(let error):
|
||||
self.lastError = (
|
||||
error: error,
|
||||
retry: { [weak self] in
|
||||
|
||||
self?.fetchPokedexEntries()
|
||||
}
|
||||
)
|
||||
}
|
||||
},
|
||||
receiveValue: {}
|
||||
)
|
||||
}
|
||||
|
||||
func fetchPokemonForm(for pokedexEntry: ObjectSnapshot<Modern.PokedexDemo.PokedexEntry>) {
|
||||
|
||||
self.cancellable["pokedexEntry.\(pokedexEntry.$id)"] = URLSession.shared
|
||||
.dataTaskPublisher(for: pokedexEntry.$url!)
|
||||
.receive(on: DispatchQueue.main)
|
||||
.eraseToAnyPublisher()
|
||||
.sink(
|
||||
receiveCompletion: { _ in },
|
||||
receiveValue: { _ in
|
||||
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
// 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(
|
||||
{ output in
|
||||
|
||||
return Future<Void, Modern.PokedexDemo.Service.Error> { promise 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
|
||||
)
|
||||
},
|
||||
success: { result in
|
||||
|
||||
promise(.success(result))
|
||||
},
|
||||
failure: { error in
|
||||
|
||||
promise(.failure(.saveError(error)))
|
||||
}
|
||||
)
|
||||
}
|
||||
catch let error as Modern.PokedexDemo.Service.Error {
|
||||
|
||||
promise(.failure(error))
|
||||
}
|
||||
catch {
|
||||
|
||||
promise(.failure(.otherError(error)))
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
.receive(on: DispatchQueue.main)
|
||||
.eraseToAnyPublisher()
|
||||
|
||||
|
||||
// MARK: - Modern.PokedexDemo.Service.Error
|
||||
|
||||
enum Error: Swift.Error {
|
||||
|
||||
case networkError(URLError)
|
||||
case parseError(expected: Any.Type, actual: Any.Type)
|
||||
case saveError(CoreStoreError)
|
||||
case otherError(Swift.Error)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,10 +23,11 @@ extension Modern {
|
||||
CoreStoreSchema(
|
||||
modelVersion: "V1",
|
||||
entities: [
|
||||
Entity<Modern.ColorsDemo.Palette>("Palette")
|
||||
],
|
||||
versionLock: [
|
||||
"Palette": [0xbaf4eaee9353176a, 0xdd6ca918cc2b0c38, 0xd04fad8882d7cc34, 0x3e90ca38c091503f]
|
||||
Entity<Modern.PokedexDemo.PokedexEntry>("PokedexEntry"),
|
||||
Entity<Modern.PokedexDemo.PokemonSpecies>("PokemonSpecies"),
|
||||
Entity<Modern.PokedexDemo.PokemonForm>("PokemonForm"),
|
||||
Entity<Modern.PokedexDemo.Move>("Move"),
|
||||
Entity<Modern.PokedexDemo.Ability>("Ability")
|
||||
]
|
||||
)
|
||||
)
|
||||
@@ -36,31 +37,16 @@ extension Modern {
|
||||
*/
|
||||
try! dataStack.addStorageAndWait(
|
||||
SQLiteStore(
|
||||
fileName: "Modern.ColorsDemo.sqlite",
|
||||
fileName: "Modern.PokedexDemo.sqlite",
|
||||
localStorageOptions: .recreateStoreOnModelMismatch
|
||||
)
|
||||
)
|
||||
return dataStack
|
||||
}()
|
||||
|
||||
static let palettesPublisher: ListPublisher<Modern.ColorsDemo.Palette> = Modern.ColorsDemo.dataStack.publishList(
|
||||
From<Modern.ColorsDemo.Palette>()
|
||||
.sectionBy(\.$colorName)
|
||||
.where(Modern.ColorsDemo.filter.whereClause())
|
||||
.orderBy(.ascending(\.$hue))
|
||||
static let pokedexEntries: ListPublisher<Modern.PokedexDemo.PokedexEntry> = Modern.PokedexDemo.dataStack.publishList(
|
||||
From<Modern.PokedexDemo.PokedexEntry>()
|
||||
.orderBy(.ascending(\.$id))
|
||||
)
|
||||
|
||||
static var filter: Modern.ColorsDemo.Filter = .all {
|
||||
|
||||
didSet {
|
||||
|
||||
try! Modern.ColorsDemo.palettesPublisher.refetch(
|
||||
From<Modern.ColorsDemo.Palette>()
|
||||
.sectionBy(\.$colorName)
|
||||
.where(self.filter.whereClause())
|
||||
.orderBy(.ascending(\.$hue))
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,7 +72,9 @@ extension Menu {
|
||||
Menu.ItemView(
|
||||
title: "Pokedex API",
|
||||
subtitle: "Importing JSON data from external source",
|
||||
destination: { EmptyView() }
|
||||
destination: {
|
||||
Modern.PokedexDemo.MainView()
|
||||
}
|
||||
)
|
||||
}
|
||||
Section(header: Text("Classic (NSManagedObject subclasses)")) {
|
||||
@@ -127,23 +129,11 @@ extension Menu {
|
||||
}
|
||||
.listStyle(GroupedListStyle())
|
||||
.navigationBarTitle("CoreStore Demos")
|
||||
Menu.DetailView()
|
||||
Menu.PlaceholderView()
|
||||
}
|
||||
.navigationViewStyle(DoubleColumnNavigationViewStyle())
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate struct DetailView: View {
|
||||
|
||||
var selectedDate: Date?
|
||||
|
||||
var body: some View {
|
||||
Group {
|
||||
Text("Detail view content goes here")
|
||||
}
|
||||
.navigationBarTitle(Text("Detail"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
|
||||
44
Demo/Sources/Helpers/Menu/Menu.PlaceholderView.swift
Normal file
44
Demo/Sources/Helpers/Menu/Menu.PlaceholderView.swift
Normal file
@@ -0,0 +1,44 @@
|
||||
//
|
||||
// Demo
|
||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
||||
|
||||
import Combine
|
||||
import CoreStore
|
||||
import SwiftUI
|
||||
|
||||
// MARK: - Menu
|
||||
|
||||
extension Menu {
|
||||
|
||||
// MARK: - Menu.PlaceholderView
|
||||
|
||||
struct PlaceholderView: UIViewControllerRepresentable {
|
||||
|
||||
// MARK: UIViewControllerRepresentable
|
||||
|
||||
typealias UIViewControllerType = UIViewController
|
||||
|
||||
func makeUIViewController(context: Self.Context) -> UIViewControllerType {
|
||||
|
||||
return UIStoryboard(name: "LaunchScreen", bundle: nil).instantiateInitialViewController()!
|
||||
}
|
||||
|
||||
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Self.Context) {}
|
||||
|
||||
static func dismantleUIViewController(_ uiViewController: UIViewControllerType, coordinator: Void) {}
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
|
||||
struct _Demo_Menu_PlaceholderView_Preview: PreviewProvider {
|
||||
|
||||
// MARK: PreviewProvider
|
||||
|
||||
static var previews: some View {
|
||||
|
||||
return Menu.PlaceholderView()
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user