This commit is contained in:
John Estropia
2020-08-18 12:05:20 +09:00
parent 72f36e7237
commit 0f3455a4a4
13 changed files with 557 additions and 104 deletions

View File

@@ -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 {

View File

@@ -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

View File

@@ -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"]))
}
}
}

View File

@@ -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?
}
}

View File

@@ -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>
}
}

View File

@@ -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)
}
}
}

View File

@@ -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))
)
}
}
}
}

View File

@@ -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

View 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