From 611bc53c9a16595daca7a2a697dc5a68fa064fc9 Mon Sep 17 00:00:00 2001 From: John Estropia Date: Sat, 29 Aug 2020 23:05:07 +0900 Subject: [PATCH] cleanup --- Demo/Demo.xcodeproj/project.pbxproj | 124 +++--- ...ift => ⭐️Modern.ColorsDemo.MainView.swift} | 0 ...️Modern.ColorsDemo.SwiftUI.DetailView.swift} | 0 ...️Modern.ColorsDemo.SwiftUI.ItemView.swift} | 0 ...️Modern.ColorsDemo.SwiftUI.ListView.swift} | 0 ...n.ColorsDemo.UIKit.DetailViewController.swift} | 0 ...ern.ColorsDemo.UIKit.ListViewController.swift} | 2 +- ...=> ⭐️Modern.PlacemarksDemo.MainView.swift} | 0 .../Modern.PokedexDemo.Details.swift | 27 ++ .../Modern.PokedexDemo.ItemCell.swift | 63 +-- ...odern.PokedexDemo.ListViewController.swift | 87 ++-- .../Modern.PokedexDemo.MainView.swift | 12 +- .../Modern.PokedexDemo.PokemonDetails.swift | 27 -- .../Modern.PokedexDemo.Service.swift | 399 ------------------ .../PokedexDemo/Modern.PokedexDemo.swift | 6 +- ....swift => ⭐️Modern.PokedexDemo.Form.swift} | 13 +- ...> ⭐️Modern.PokedexDemo.PokedexEntry.swift} | 18 +- ...=> ⭐️Modern.PokedexDemo.PokemonType.swift} | 3 + .../⭐️Modern.PokedexDemo.Service.swift | 390 +++++++++++++++++ ...ift => ⭐️Modern.PokedexDemo.Species.swift} | 24 +- ... => ⭐️Modern.TimeZonesDemo.MainView.swift} | 0 21 files changed, 593 insertions(+), 602 deletions(-) rename Demo/Sources/Demos/Modern/ColorsDemo/{Modern.ColorsDemo.MainView.swift => ⭐️Modern.ColorsDemo.MainView.swift} (100%) rename Demo/Sources/Demos/Modern/ColorsDemo/{Modern.ColorsDemo.SwiftUI.DetailView.swift => ⭐️Modern.ColorsDemo.SwiftUI.DetailView.swift} (100%) rename Demo/Sources/Demos/Modern/ColorsDemo/{Modern.ColorsDemo.SwiftUI.ItemView.swift => ⭐️Modern.ColorsDemo.SwiftUI.ItemView.swift} (100%) rename Demo/Sources/Demos/Modern/ColorsDemo/{Modern.ColorsDemo.SwiftUI.ListView.swift => ⭐️Modern.ColorsDemo.SwiftUI.ListView.swift} (100%) rename Demo/Sources/Demos/Modern/ColorsDemo/{Modern.ColorsDemo.UIKit.DetailViewController.swift => ⭐️Modern.ColorsDemo.UIKit.DetailViewController.swift} (100%) rename Demo/Sources/Demos/Modern/ColorsDemo/{Modern.ColorsDemo.UIKit.ListViewController.swift => ⭐️Modern.ColorsDemo.UIKit.ListViewController.swift} (99%) rename Demo/Sources/Demos/Modern/PlacemarksDemo/{Modern.PlacemarksDemo.MainView.swift => ⭐️Modern.PlacemarksDemo.MainView.swift} (100%) create mode 100644 Demo/Sources/Demos/Modern/PokedexDemo/Modern.PokedexDemo.Details.swift delete mode 100644 Demo/Sources/Demos/Modern/PokedexDemo/Modern.PokedexDemo.PokemonDetails.swift delete mode 100644 Demo/Sources/Demos/Modern/PokedexDemo/Modern.PokedexDemo.Service.swift rename Demo/Sources/Demos/Modern/PokedexDemo/{Modern.PokedexDemo.PokemonDisplay.swift => ⭐️Modern.PokedexDemo.Form.swift} (78%) rename Demo/Sources/Demos/Modern/PokedexDemo/{Modern.PokedexDemo.PokedexEntry.swift => ⭐️Modern.PokedexDemo.PokedexEntry.swift} (74%) rename Demo/Sources/Demos/Modern/PokedexDemo/{Modern.PokedexDemo.PokemonType.swift => ⭐️Modern.PokedexDemo.PokemonType.swift} (91%) create mode 100644 Demo/Sources/Demos/Modern/PokedexDemo/⭐️Modern.PokedexDemo.Service.swift rename Demo/Sources/Demos/Modern/PokedexDemo/{Modern.PokedexDemo.PokemonForm.swift => ⭐️Modern.PokedexDemo.Species.swift} (84%) rename Demo/Sources/Demos/Modern/TimeZonesDemo/{Modern.TimeZonesDemo.MainView.swift => ⭐️Modern.TimeZonesDemo.MainView.swift} (100%) diff --git a/Demo/Demo.xcodeproj/project.pbxproj b/Demo/Demo.xcodeproj/project.pbxproj index f803d35..fef68e1 100644 --- a/Demo/Demo.xcodeproj/project.pbxproj +++ b/Demo/Demo.xcodeproj/project.pbxproj @@ -8,14 +8,14 @@ /* Begin PBXBuildFile section */ B531EFE724EA762D005F247D /* Menu.PlaceholderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B531EFE624EA762D005F247D /* Menu.PlaceholderView.swift */; }; - B531EFE924EB5A53005F247D /* Modern.PokedexDemo.PokedexEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = B531EFE824EB5A52005F247D /* Modern.PokedexDemo.PokedexEntry.swift */; }; - B531EFEB24EB5ECD005F247D /* Modern.PokedexDemo.Service.swift in Sources */ = {isa = PBXBuildFile; fileRef = B531EFEA24EB5ECD005F247D /* Modern.PokedexDemo.Service.swift */; }; + B531EFE924EB5A53005F247D /* ⭐️Modern.PokedexDemo.PokedexEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = B531EFE824EB5A52005F247D /* ⭐️Modern.PokedexDemo.PokedexEntry.swift */; }; + B531EFEB24EB5ECD005F247D /* ⭐️Modern.PokedexDemo.Service.swift in Sources */ = {isa = PBXBuildFile; fileRef = B531EFEA24EB5ECD005F247D /* ⭐️Modern.PokedexDemo.Service.swift */; }; B531EFED24EB7453005F247D /* Modern.PokedexDemo.MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B531EFEC24EB7453005F247D /* Modern.PokedexDemo.MainView.swift */; }; B566C8E624ED6B98001134A1 /* NetworkImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B566C8E524ED6B98001134A1 /* NetworkImageView.swift */; }; B566C8E824F9D406001134A1 /* Modern.PokedexDemo.ListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B566C8E724F9D406001134A1 /* Modern.PokedexDemo.ListView.swift */; }; B566C8EA24F9D412001134A1 /* Modern.PokedexDemo.ListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B566C8E924F9D412001134A1 /* Modern.PokedexDemo.ListViewController.swift */; }; B566C8EC24F9D694001134A1 /* Modern.PokedexDemo.ItemCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B566C8EB24F9D694001134A1 /* Modern.PokedexDemo.ItemCell.swift */; }; - B566C8EE24FA1EA3001134A1 /* Modern.PokedexDemo.PokemonDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = B566C8ED24FA1EA3001134A1 /* Modern.PokedexDemo.PokemonDetails.swift */; }; + B566C8EE24FA1EA3001134A1 /* Modern.PokedexDemo.Details.swift in Sources */ = {isa = PBXBuildFile; fileRef = B566C8ED24FA1EA3001134A1 /* Modern.PokedexDemo.Details.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 */; }; @@ -28,7 +28,7 @@ B5A3915B24E685FE00E7E8BD /* Modern.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A3915A24E685FE00E7E8BD /* Modern.swift */; }; B5A3915E24E6922E00E7E8BD /* Modern.PlacemarksDemo.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A3915D24E6922E00E7E8BD /* Modern.PlacemarksDemo.swift */; }; B5A3916024E6925900E7E8BD /* Modern.PlacemarksDemo.MapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A3915F24E6925900E7E8BD /* Modern.PlacemarksDemo.MapView.swift */; }; - B5A3916224E697BA00E7E8BD /* Modern.PlacemarksDemo.MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A3916124E697BA00E7E8BD /* Modern.PlacemarksDemo.MainView.swift */; }; + B5A3916224E697BA00E7E8BD /* ⭐️Modern.PlacemarksDemo.MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A3916124E697BA00E7E8BD /* ⭐️Modern.PlacemarksDemo.MainView.swift */; }; B5A3916524E698C700E7E8BD /* Modern.PlacemarksDemo.Place.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A3916424E698C700E7E8BD /* Modern.PlacemarksDemo.Place.swift */; }; B5A3916B24E698F900E7E8BD /* CoreStore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5A3916724E698F900E7E8BD /* CoreStore.framework */; }; B5A3916C24E698F900E7E8BD /* CoreStore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = B5A3916724E698F900E7E8BD /* CoreStore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; @@ -46,28 +46,28 @@ B5A3918024E787D900E7E8BD /* InstructionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A3917F24E787D900E7E8BD /* InstructionsView.swift */; }; B5A3918324E7A21800E7E8BD /* Modern.TimeZonesDemo.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A3918224E7A21800E7E8BD /* Modern.TimeZonesDemo.swift */; }; B5A3918624E7A54A00E7E8BD /* Modern.TimeZonesDemo.TimeZone.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A3918524E7A54A00E7E8BD /* Modern.TimeZonesDemo.TimeZone.swift */; }; - B5A3918824E7A8F900E7E8BD /* Modern.TimeZonesDemo.MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A3918724E7A8F900E7E8BD /* Modern.TimeZonesDemo.MainView.swift */; }; + B5A3918824E7A8F900E7E8BD /* ⭐️Modern.TimeZonesDemo.MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A3918724E7A8F900E7E8BD /* ⭐️Modern.TimeZonesDemo.MainView.swift */; }; B5A3918A24E7AD1800E7E8BD /* Modern.TimeZonesDemo.ListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A3918924E7AD1800E7E8BD /* Modern.TimeZonesDemo.ListView.swift */; }; B5A3918C24E7B44B00E7E8BD /* Modern.TimeZonesDemo.ItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A3918B24E7B44B00E7E8BD /* Modern.TimeZonesDemo.ItemView.swift */; }; B5A3918F24E7E06500E7E8BD /* Modern.ColorsDemo.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A3918E24E7E06500E7E8BD /* Modern.ColorsDemo.swift */; }; B5A3919224E7E0C600E7E8BD /* Modern.ColorsDemo.Palette.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A3919124E7E0C600E7E8BD /* Modern.ColorsDemo.Palette.swift */; }; - B5A3919424E7E36700E7E8BD /* Modern.ColorsDemo.SwiftUI.ListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A3919324E7E36700E7E8BD /* Modern.ColorsDemo.SwiftUI.ListView.swift */; }; - B5A3919624E7E4AC00E7E8BD /* Modern.ColorsDemo.SwiftUI.ItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A3919524E7E4AC00E7E8BD /* Modern.ColorsDemo.SwiftUI.ItemView.swift */; }; + B5A3919424E7E36700E7E8BD /* ⭐️Modern.ColorsDemo.SwiftUI.ListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A3919324E7E36700E7E8BD /* ⭐️Modern.ColorsDemo.SwiftUI.ListView.swift */; }; + B5A3919624E7E4AC00E7E8BD /* ⭐️Modern.ColorsDemo.SwiftUI.ItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A3919524E7E4AC00E7E8BD /* ⭐️Modern.ColorsDemo.SwiftUI.ItemView.swift */; }; B5A3919824E7E67000E7E8BD /* Modern.ColorsDemo.Filter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A3919724E7E67000E7E8BD /* Modern.ColorsDemo.Filter.swift */; }; - B5A3919A24E8207A00E7E8BD /* Modern.ColorsDemo.SwiftUI.DetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A3919924E8207A00E7E8BD /* Modern.ColorsDemo.SwiftUI.DetailView.swift */; }; + B5A3919A24E8207A00E7E8BD /* ⭐️Modern.ColorsDemo.SwiftUI.DetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A3919924E8207A00E7E8BD /* ⭐️Modern.ColorsDemo.SwiftUI.DetailView.swift */; }; B5A3919E24E8EEB600E7E8BD /* Modern.ColorsDemo.SwiftUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A3919D24E8EEB600E7E8BD /* Modern.ColorsDemo.SwiftUI.swift */; }; B5A391A024E8F00A00E7E8BD /* Modern.ColorsDemo.UIKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A3919F24E8F00A00E7E8BD /* Modern.ColorsDemo.UIKit.swift */; }; - B5A391A224E8F01F00E7E8BD /* Modern.ColorsDemo.UIKit.ListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A391A124E8F01F00E7E8BD /* Modern.ColorsDemo.UIKit.ListViewController.swift */; }; + B5A391A224E8F01F00E7E8BD /* ⭐️Modern.ColorsDemo.UIKit.ListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A391A124E8F01F00E7E8BD /* ⭐️Modern.ColorsDemo.UIKit.ListViewController.swift */; }; B5A391A424E8F04300E7E8BD /* Modern.ColorsDemo.UIKit.ListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A391A324E8F04300E7E8BD /* Modern.ColorsDemo.UIKit.ListView.swift */; }; - B5A391A624E8F4EA00E7E8BD /* Modern.ColorsDemo.MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A391A524E8F4EA00E7E8BD /* Modern.ColorsDemo.MainView.swift */; }; + B5A391A624E8F4EA00E7E8BD /* ⭐️Modern.ColorsDemo.MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A391A524E8F4EA00E7E8BD /* ⭐️Modern.ColorsDemo.MainView.swift */; }; B5A391A824E90F1000E7E8BD /* UIImage+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A391A724E90F1000E7E8BD /* UIImage+Extensions.swift */; }; B5A391AA24E9104300E7E8BD /* Modern.ColorsDemo.UIKit.ItemCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A391A924E9104300E7E8BD /* Modern.ColorsDemo.UIKit.ItemCell.swift */; }; 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 */; }; + 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.PokemonDisplay.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A391B324E96C0A00E7E8BD /* Modern.PokedexDemo.PokemonDisplay.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 */; }; + B5A391B424E96C0A00E7E8BD /* ⭐️Modern.PokedexDemo.Form.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A391B324E96C0A00E7E8BD /* ⭐️Modern.PokedexDemo.Form.swift */; }; + B5A391B924E96F8500E7E8BD /* ⭐️Modern.PokedexDemo.Species.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A391B824E96F8500E7E8BD /* ⭐️Modern.PokedexDemo.Species.swift */; }; + B5A391BB24E970A400E7E8BD /* ⭐️Modern.PokedexDemo.PokemonType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A391BA24E970A400E7E8BD /* ⭐️Modern.PokedexDemo.PokemonType.swift */; }; B5E32C9024FA41F9003F46AD /* ImageDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E32C8F24FA41F9003F46AD /* ImageDownloader.swift */; }; /* End PBXBuildFile section */ @@ -90,14 +90,14 @@ /* Begin PBXFileReference section */ B531EFE624EA762D005F247D /* Menu.PlaceholderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Menu.PlaceholderView.swift; sourceTree = ""; }; - B531EFE824EB5A52005F247D /* Modern.PokedexDemo.PokedexEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.PokedexDemo.PokedexEntry.swift; sourceTree = ""; }; - B531EFEA24EB5ECD005F247D /* Modern.PokedexDemo.Service.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.PokedexDemo.Service.swift; sourceTree = ""; }; + B531EFE824EB5A52005F247D /* ⭐️Modern.PokedexDemo.PokedexEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "⭐️Modern.PokedexDemo.PokedexEntry.swift"; sourceTree = ""; }; + B531EFEA24EB5ECD005F247D /* ⭐️Modern.PokedexDemo.Service.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "⭐️Modern.PokedexDemo.Service.swift"; sourceTree = ""; }; B531EFEC24EB7453005F247D /* Modern.PokedexDemo.MainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.PokedexDemo.MainView.swift; sourceTree = ""; }; B566C8E524ED6B98001134A1 /* NetworkImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkImageView.swift; sourceTree = ""; }; B566C8E724F9D406001134A1 /* Modern.PokedexDemo.ListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.PokedexDemo.ListView.swift; sourceTree = ""; }; B566C8E924F9D412001134A1 /* Modern.PokedexDemo.ListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.PokedexDemo.ListViewController.swift; sourceTree = ""; }; B566C8EB24F9D694001134A1 /* Modern.PokedexDemo.ItemCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.PokedexDemo.ItemCell.swift; sourceTree = ""; }; - B566C8ED24FA1EA3001134A1 /* Modern.PokedexDemo.PokemonDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.PokedexDemo.PokemonDetails.swift; sourceTree = ""; }; + B566C8ED24FA1EA3001134A1 /* Modern.PokedexDemo.Details.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.PokedexDemo.Details.swift; sourceTree = ""; }; 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 = ""; }; B5A3911E24E5429200E7E8BD /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; @@ -113,7 +113,7 @@ B5A3915A24E685FE00E7E8BD /* Modern.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.swift; sourceTree = ""; }; B5A3915D24E6922E00E7E8BD /* Modern.PlacemarksDemo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.PlacemarksDemo.swift; sourceTree = ""; }; B5A3915F24E6925900E7E8BD /* Modern.PlacemarksDemo.MapView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.PlacemarksDemo.MapView.swift; sourceTree = ""; }; - B5A3916124E697BA00E7E8BD /* Modern.PlacemarksDemo.MainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.PlacemarksDemo.MainView.swift; sourceTree = ""; }; + B5A3916124E697BA00E7E8BD /* ⭐️Modern.PlacemarksDemo.MainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "⭐️Modern.PlacemarksDemo.MainView.swift"; sourceTree = ""; }; B5A3916424E698C700E7E8BD /* Modern.PlacemarksDemo.Place.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.PlacemarksDemo.Place.swift; sourceTree = ""; }; B5A3916724E698F900E7E8BD /* CoreStore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = CoreStore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; B5A3916824E698F900E7E8BD /* CoreStore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = CoreStore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -127,28 +127,28 @@ B5A3917F24E787D900E7E8BD /* InstructionsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstructionsView.swift; sourceTree = ""; }; B5A3918224E7A21800E7E8BD /* Modern.TimeZonesDemo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.TimeZonesDemo.swift; sourceTree = ""; }; B5A3918524E7A54A00E7E8BD /* Modern.TimeZonesDemo.TimeZone.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.TimeZonesDemo.TimeZone.swift; sourceTree = ""; }; - B5A3918724E7A8F900E7E8BD /* Modern.TimeZonesDemo.MainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.TimeZonesDemo.MainView.swift; sourceTree = ""; }; + B5A3918724E7A8F900E7E8BD /* ⭐️Modern.TimeZonesDemo.MainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "⭐️Modern.TimeZonesDemo.MainView.swift"; sourceTree = ""; }; B5A3918924E7AD1800E7E8BD /* Modern.TimeZonesDemo.ListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.TimeZonesDemo.ListView.swift; sourceTree = ""; }; B5A3918B24E7B44B00E7E8BD /* Modern.TimeZonesDemo.ItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.TimeZonesDemo.ItemView.swift; sourceTree = ""; }; B5A3918E24E7E06500E7E8BD /* Modern.ColorsDemo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.ColorsDemo.swift; sourceTree = ""; }; B5A3919124E7E0C600E7E8BD /* Modern.ColorsDemo.Palette.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.ColorsDemo.Palette.swift; sourceTree = ""; }; - B5A3919324E7E36700E7E8BD /* Modern.ColorsDemo.SwiftUI.ListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.ColorsDemo.SwiftUI.ListView.swift; sourceTree = ""; }; - B5A3919524E7E4AC00E7E8BD /* Modern.ColorsDemo.SwiftUI.ItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.ColorsDemo.SwiftUI.ItemView.swift; sourceTree = ""; }; + B5A3919324E7E36700E7E8BD /* ⭐️Modern.ColorsDemo.SwiftUI.ListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "⭐️Modern.ColorsDemo.SwiftUI.ListView.swift"; sourceTree = ""; }; + B5A3919524E7E4AC00E7E8BD /* ⭐️Modern.ColorsDemo.SwiftUI.ItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "⭐️Modern.ColorsDemo.SwiftUI.ItemView.swift"; sourceTree = ""; }; B5A3919724E7E67000E7E8BD /* Modern.ColorsDemo.Filter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.ColorsDemo.Filter.swift; sourceTree = ""; }; - B5A3919924E8207A00E7E8BD /* Modern.ColorsDemo.SwiftUI.DetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.ColorsDemo.SwiftUI.DetailView.swift; sourceTree = ""; }; + B5A3919924E8207A00E7E8BD /* ⭐️Modern.ColorsDemo.SwiftUI.DetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "⭐️Modern.ColorsDemo.SwiftUI.DetailView.swift"; sourceTree = ""; }; B5A3919D24E8EEB600E7E8BD /* Modern.ColorsDemo.SwiftUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.ColorsDemo.SwiftUI.swift; sourceTree = ""; }; B5A3919F24E8F00A00E7E8BD /* Modern.ColorsDemo.UIKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.ColorsDemo.UIKit.swift; sourceTree = ""; }; - B5A391A124E8F01F00E7E8BD /* Modern.ColorsDemo.UIKit.ListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.ColorsDemo.UIKit.ListViewController.swift; sourceTree = ""; }; + B5A391A124E8F01F00E7E8BD /* ⭐️Modern.ColorsDemo.UIKit.ListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "⭐️Modern.ColorsDemo.UIKit.ListViewController.swift"; sourceTree = ""; }; B5A391A324E8F04300E7E8BD /* Modern.ColorsDemo.UIKit.ListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.ColorsDemo.UIKit.ListView.swift; sourceTree = ""; }; - B5A391A524E8F4EA00E7E8BD /* Modern.ColorsDemo.MainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.ColorsDemo.MainView.swift; sourceTree = ""; }; + B5A391A524E8F4EA00E7E8BD /* ⭐️Modern.ColorsDemo.MainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "⭐️Modern.ColorsDemo.MainView.swift"; sourceTree = ""; }; B5A391A724E90F1000E7E8BD /* UIImage+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+Extensions.swift"; sourceTree = ""; }; B5A391A924E9104300E7E8BD /* Modern.ColorsDemo.UIKit.ItemCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.ColorsDemo.UIKit.ItemCell.swift; sourceTree = ""; }; B5A391AB24E9143B00E7E8BD /* Modern.ColorsDemo.UIKit.DetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.ColorsDemo.UIKit.DetailView.swift; sourceTree = ""; }; - B5A391AD24E9150F00E7E8BD /* Modern.ColorsDemo.UIKit.DetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.ColorsDemo.UIKit.DetailViewController.swift; sourceTree = ""; }; + B5A391AD24E9150F00E7E8BD /* ⭐️Modern.ColorsDemo.UIKit.DetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "⭐️Modern.ColorsDemo.UIKit.DetailViewController.swift"; sourceTree = ""; }; B5A391B024E96AF600E7E8BD /* Modern.PokedexDemo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.PokedexDemo.swift; sourceTree = ""; }; - B5A391B324E96C0A00E7E8BD /* Modern.PokedexDemo.PokemonDisplay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.PokedexDemo.PokemonDisplay.swift; sourceTree = ""; }; - B5A391B824E96F8500E7E8BD /* Modern.PokedexDemo.PokemonForm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.PokedexDemo.PokemonForm.swift; sourceTree = ""; }; - B5A391BA24E970A400E7E8BD /* Modern.PokedexDemo.PokemonType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.PokedexDemo.PokemonType.swift; sourceTree = ""; }; + B5A391B324E96C0A00E7E8BD /* ⭐️Modern.PokedexDemo.Form.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "⭐️Modern.PokedexDemo.Form.swift"; sourceTree = ""; }; + B5A391B824E96F8500E7E8BD /* ⭐️Modern.PokedexDemo.Species.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "⭐️Modern.PokedexDemo.Species.swift"; sourceTree = ""; }; + B5A391BA24E970A400E7E8BD /* ⭐️Modern.PokedexDemo.PokemonType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "⭐️Modern.PokedexDemo.PokemonType.swift"; sourceTree = ""; }; B5E32C8F24FA41F9003F46AD /* ImageDownloader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageDownloader.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -271,7 +271,7 @@ isa = PBXGroup; children = ( B5A3915D24E6922E00E7E8BD /* Modern.PlacemarksDemo.swift */, - B5A3916124E697BA00E7E8BD /* Modern.PlacemarksDemo.MainView.swift */, + B5A3916124E697BA00E7E8BD /* ⭐️Modern.PlacemarksDemo.MainView.swift */, B5A3915F24E6925900E7E8BD /* Modern.PlacemarksDemo.MapView.swift */, B5A3917D24E7728400E7E8BD /* Modern.PlacemarksDemo.Geocoder.swift */, B5A3916324E698B300E7E8BD /* Models */, @@ -318,7 +318,7 @@ isa = PBXGroup; children = ( B5A3918224E7A21800E7E8BD /* Modern.TimeZonesDemo.swift */, - B5A3918724E7A8F900E7E8BD /* Modern.TimeZonesDemo.MainView.swift */, + B5A3918724E7A8F900E7E8BD /* ⭐️Modern.TimeZonesDemo.MainView.swift */, B5A3918924E7AD1800E7E8BD /* Modern.TimeZonesDemo.ListView.swift */, B5A3918B24E7B44B00E7E8BD /* Modern.TimeZonesDemo.ItemView.swift */, B5A3918424E7A53300E7E8BD /* Models */, @@ -339,7 +339,7 @@ children = ( B5A3918E24E7E06500E7E8BD /* Modern.ColorsDemo.swift */, B5A3919724E7E67000E7E8BD /* Modern.ColorsDemo.Filter.swift */, - B5A391A524E8F4EA00E7E8BD /* Modern.ColorsDemo.MainView.swift */, + B5A391A524E8F4EA00E7E8BD /* ⭐️Modern.ColorsDemo.MainView.swift */, B5A3919C24E8EE9000E7E8BD /* UIKit */, B5A3919B24E8EE8100E7E8BD /* SwiftUI */, B5A3919024E7E0B000E7E8BD /* Models */, @@ -359,9 +359,9 @@ isa = PBXGroup; children = ( B5A3919D24E8EEB600E7E8BD /* Modern.ColorsDemo.SwiftUI.swift */, - B5A3919324E7E36700E7E8BD /* Modern.ColorsDemo.SwiftUI.ListView.swift */, - B5A3919524E7E4AC00E7E8BD /* Modern.ColorsDemo.SwiftUI.ItemView.swift */, - B5A3919924E8207A00E7E8BD /* Modern.ColorsDemo.SwiftUI.DetailView.swift */, + B5A3919324E7E36700E7E8BD /* ⭐️Modern.ColorsDemo.SwiftUI.ListView.swift */, + B5A3919524E7E4AC00E7E8BD /* ⭐️Modern.ColorsDemo.SwiftUI.ItemView.swift */, + B5A3919924E8207A00E7E8BD /* ⭐️Modern.ColorsDemo.SwiftUI.DetailView.swift */, ); name = SwiftUI; sourceTree = ""; @@ -371,10 +371,10 @@ children = ( B5A3919F24E8F00A00E7E8BD /* Modern.ColorsDemo.UIKit.swift */, B5A391A324E8F04300E7E8BD /* Modern.ColorsDemo.UIKit.ListView.swift */, - B5A391A124E8F01F00E7E8BD /* Modern.ColorsDemo.UIKit.ListViewController.swift */, + B5A391A124E8F01F00E7E8BD /* ⭐️Modern.ColorsDemo.UIKit.ListViewController.swift */, B5A391A924E9104300E7E8BD /* Modern.ColorsDemo.UIKit.ItemCell.swift */, B5A391AB24E9143B00E7E8BD /* Modern.ColorsDemo.UIKit.DetailView.swift */, - B5A391AD24E9150F00E7E8BD /* Modern.ColorsDemo.UIKit.DetailViewController.swift */, + B5A391AD24E9150F00E7E8BD /* ⭐️Modern.ColorsDemo.UIKit.DetailViewController.swift */, ); name = UIKit; sourceTree = ""; @@ -383,7 +383,7 @@ isa = PBXGroup; children = ( B5A391B024E96AF600E7E8BD /* Modern.PokedexDemo.swift */, - B531EFEA24EB5ECD005F247D /* Modern.PokedexDemo.Service.swift */, + B531EFEA24EB5ECD005F247D /* ⭐️Modern.PokedexDemo.Service.swift */, B531EFEC24EB7453005F247D /* Modern.PokedexDemo.MainView.swift */, B566C8E724F9D406001134A1 /* Modern.PokedexDemo.ListView.swift */, B566C8E924F9D412001134A1 /* Modern.PokedexDemo.ListViewController.swift */, @@ -396,10 +396,10 @@ B5A391B224E96B7400E7E8BD /* Models */ = { isa = PBXGroup; children = ( - B531EFE824EB5A52005F247D /* Modern.PokedexDemo.PokedexEntry.swift */, - B566C8ED24FA1EA3001134A1 /* Modern.PokedexDemo.PokemonDetails.swift */, - B5A391B824E96F8500E7E8BD /* Modern.PokedexDemo.PokemonForm.swift */, - B5A391B324E96C0A00E7E8BD /* Modern.PokedexDemo.PokemonDisplay.swift */, + B531EFE824EB5A52005F247D /* ⭐️Modern.PokedexDemo.PokedexEntry.swift */, + B566C8ED24FA1EA3001134A1 /* Modern.PokedexDemo.Details.swift */, + B5A391B824E96F8500E7E8BD /* ⭐️Modern.PokedexDemo.Species.swift */, + B5A391B324E96C0A00E7E8BD /* ⭐️Modern.PokedexDemo.Form.swift */, B5A391B724E96E8600E7E8BD /* Attributes */, ); name = Models; @@ -408,7 +408,7 @@ B5A391B724E96E8600E7E8BD /* Attributes */ = { isa = PBXGroup; children = ( - B5A391BA24E970A400E7E8BD /* Modern.PokedexDemo.PokemonType.swift */, + B5A391BA24E970A400E7E8BD /* ⭐️Modern.PokedexDemo.PokemonType.swift */, ); name = Attributes; sourceTree = ""; @@ -487,12 +487,12 @@ B5A391A824E90F1000E7E8BD /* UIImage+Extensions.swift in Sources */, B5A3911D24E5429200E7E8BD /* AppDelegate.swift in Sources */, B5A3915924E685EC00E7E8BD /* Classic.swift in Sources */, + B5E32C9024FA41F9003F46AD /* ImageDownloader.swift in Sources */, B5A3918024E787D900E7E8BD /* InstructionsView.swift in Sources */, B5A3917C24E6A76C00E7E8BD /* LazyView.swift in Sources */, - B566C8EC24F9D694001134A1 /* Modern.PokedexDemo.ItemCell.swift in Sources */, B5A3913424E6170500E7E8BD /* Menu.swift in Sources */, B5A3915B24E685FE00E7E8BD /* Modern.swift in Sources */, - B566C8EA24F9D412001134A1 /* Modern.PokedexDemo.ListViewController.swift in Sources */, + B566C8E624ED6B98001134A1 /* NetworkImageView.swift in Sources */, B5A3911F24E5429200E7E8BD /* SceneDelegate.swift in Sources */, B5A3915324E6537F00E7E8BD /* Menu.ItemView.swift in Sources */, B5A3912124E5429200E7E8BD /* Menu.MainView.swift in Sources */, @@ -501,37 +501,37 @@ 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 */, B5A3919224E7E0C600E7E8BD /* Modern.ColorsDemo.Palette.swift in Sources */, B5A3919E24E8EEB600E7E8BD /* Modern.ColorsDemo.SwiftUI.swift in Sources */, B5A391A024E8F00A00E7E8BD /* Modern.ColorsDemo.UIKit.swift in Sources */, B5A3917E24E7728400E7E8BD /* Modern.PlacemarksDemo.Geocoder.swift in Sources */, - B5A3916224E697BA00E7E8BD /* Modern.PlacemarksDemo.MainView.swift in Sources */, B5A3916024E6925900E7E8BD /* Modern.PlacemarksDemo.MapView.swift in Sources */, B5A3916524E698C700E7E8BD /* Modern.PlacemarksDemo.Place.swift in Sources */, - B531EFE924EB5A53005F247D /* Modern.PokedexDemo.PokedexEntry.swift in Sources */, - B5A391B924E96F8500E7E8BD /* Modern.PokedexDemo.PokemonForm.swift in Sources */, + B566C8EE24FA1EA3001134A1 /* Modern.PokedexDemo.Details.swift in Sources */, + B566C8EC24F9D694001134A1 /* Modern.PokedexDemo.ItemCell.swift in Sources */, B566C8E824F9D406001134A1 /* Modern.PokedexDemo.ListView.swift in Sources */, - B5A391B424E96C0A00E7E8BD /* Modern.PokedexDemo.PokemonDisplay.swift in Sources */, - B5A391BB24E970A400E7E8BD /* Modern.PokedexDemo.PokemonType.swift in Sources */, - B5A3918C24E7B44B00E7E8BD /* Modern.TimeZonesDemo.ItemView.swift in Sources */, - B5E32C9024FA41F9003F46AD /* ImageDownloader.swift in Sources */, - B5A3918A24E7AD1800E7E8BD /* Modern.TimeZonesDemo.ListView.swift in Sources */, - B5A3918824E7A8F900E7E8BD /* Modern.TimeZonesDemo.MainView.swift in Sources */, + B566C8EA24F9D412001134A1 /* Modern.PokedexDemo.ListViewController.swift in Sources */, B531EFED24EB7453005F247D /* Modern.PokedexDemo.MainView.swift in Sources */, + B5A3918C24E7B44B00E7E8BD /* Modern.TimeZonesDemo.ItemView.swift in Sources */, + B5A3918A24E7AD1800E7E8BD /* Modern.TimeZonesDemo.ListView.swift in Sources */, B5A3918624E7A54A00E7E8BD /* Modern.TimeZonesDemo.TimeZone.swift in Sources */, - B566C8EE24FA1EA3001134A1 /* Modern.PokedexDemo.PokemonDetails.swift in Sources */, - B5A3919A24E8207A00E7E8BD /* Modern.ColorsDemo.SwiftUI.DetailView.swift in Sources */, - B5A3919624E7E4AC00E7E8BD /* Modern.ColorsDemo.SwiftUI.ItemView.swift in Sources */, - B5A3919424E7E36700E7E8BD /* Modern.ColorsDemo.SwiftUI.ListView.swift in Sources */, + B5A391A624E8F4EA00E7E8BD /* ⭐️Modern.ColorsDemo.MainView.swift in Sources */, + B5A3916224E697BA00E7E8BD /* ⭐️Modern.PlacemarksDemo.MainView.swift in Sources */, + B5A391B424E96C0A00E7E8BD /* ⭐️Modern.PokedexDemo.Form.swift in Sources */, + B531EFE924EB5A53005F247D /* ⭐️Modern.PokedexDemo.PokedexEntry.swift in Sources */, + B5A391BB24E970A400E7E8BD /* ⭐️Modern.PokedexDemo.PokemonType.swift in Sources */, + B531EFEB24EB5ECD005F247D /* ⭐️Modern.PokedexDemo.Service.swift in Sources */, + B5A391B924E96F8500E7E8BD /* ⭐️Modern.PokedexDemo.Species.swift in Sources */, + B5A3918824E7A8F900E7E8BD /* ⭐️Modern.TimeZonesDemo.MainView.swift in Sources */, B5A391AC24E9143B00E7E8BD /* Modern.ColorsDemo.UIKit.DetailView.swift in Sources */, - B5A391AE24E9150F00E7E8BD /* Modern.ColorsDemo.UIKit.DetailViewController.swift in Sources */, B5A391AA24E9104300E7E8BD /* Modern.ColorsDemo.UIKit.ItemCell.swift in Sources */, B5A391A424E8F04300E7E8BD /* Modern.ColorsDemo.UIKit.ListView.swift in Sources */, - B5A391A224E8F01F00E7E8BD /* Modern.ColorsDemo.UIKit.ListViewController.swift in Sources */, + B5A3919A24E8207A00E7E8BD /* ⭐️Modern.ColorsDemo.SwiftUI.DetailView.swift in Sources */, + B5A3919624E7E4AC00E7E8BD /* ⭐️Modern.ColorsDemo.SwiftUI.ItemView.swift in Sources */, + B5A3919424E7E36700E7E8BD /* ⭐️Modern.ColorsDemo.SwiftUI.ListView.swift in Sources */, + B5A391AE24E9150F00E7E8BD /* ⭐️Modern.ColorsDemo.UIKit.DetailViewController.swift in Sources */, + B5A391A224E8F01F00E7E8BD /* ⭐️Modern.ColorsDemo.UIKit.ListViewController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Demo/Sources/Demos/Modern/ColorsDemo/Modern.ColorsDemo.MainView.swift b/Demo/Sources/Demos/Modern/ColorsDemo/⭐️Modern.ColorsDemo.MainView.swift similarity index 100% rename from Demo/Sources/Demos/Modern/ColorsDemo/Modern.ColorsDemo.MainView.swift rename to Demo/Sources/Demos/Modern/ColorsDemo/⭐️Modern.ColorsDemo.MainView.swift diff --git a/Demo/Sources/Demos/Modern/ColorsDemo/Modern.ColorsDemo.SwiftUI.DetailView.swift b/Demo/Sources/Demos/Modern/ColorsDemo/⭐️Modern.ColorsDemo.SwiftUI.DetailView.swift similarity index 100% rename from Demo/Sources/Demos/Modern/ColorsDemo/Modern.ColorsDemo.SwiftUI.DetailView.swift rename to Demo/Sources/Demos/Modern/ColorsDemo/⭐️Modern.ColorsDemo.SwiftUI.DetailView.swift diff --git a/Demo/Sources/Demos/Modern/ColorsDemo/Modern.ColorsDemo.SwiftUI.ItemView.swift b/Demo/Sources/Demos/Modern/ColorsDemo/⭐️Modern.ColorsDemo.SwiftUI.ItemView.swift similarity index 100% rename from Demo/Sources/Demos/Modern/ColorsDemo/Modern.ColorsDemo.SwiftUI.ItemView.swift rename to Demo/Sources/Demos/Modern/ColorsDemo/⭐️Modern.ColorsDemo.SwiftUI.ItemView.swift diff --git a/Demo/Sources/Demos/Modern/ColorsDemo/Modern.ColorsDemo.SwiftUI.ListView.swift b/Demo/Sources/Demos/Modern/ColorsDemo/⭐️Modern.ColorsDemo.SwiftUI.ListView.swift similarity index 100% rename from Demo/Sources/Demos/Modern/ColorsDemo/Modern.ColorsDemo.SwiftUI.ListView.swift rename to Demo/Sources/Demos/Modern/ColorsDemo/⭐️Modern.ColorsDemo.SwiftUI.ListView.swift diff --git a/Demo/Sources/Demos/Modern/ColorsDemo/Modern.ColorsDemo.UIKit.DetailViewController.swift b/Demo/Sources/Demos/Modern/ColorsDemo/⭐️Modern.ColorsDemo.UIKit.DetailViewController.swift similarity index 100% rename from Demo/Sources/Demos/Modern/ColorsDemo/Modern.ColorsDemo.UIKit.DetailViewController.swift rename to Demo/Sources/Demos/Modern/ColorsDemo/⭐️Modern.ColorsDemo.UIKit.DetailViewController.swift diff --git a/Demo/Sources/Demos/Modern/ColorsDemo/Modern.ColorsDemo.UIKit.ListViewController.swift b/Demo/Sources/Demos/Modern/ColorsDemo/⭐️Modern.ColorsDemo.UIKit.ListViewController.swift similarity index 99% rename from Demo/Sources/Demos/Modern/ColorsDemo/Modern.ColorsDemo.UIKit.ListViewController.swift rename to Demo/Sources/Demos/Modern/ColorsDemo/⭐️Modern.ColorsDemo.UIKit.ListViewController.swift index c18e543..fdac31c 100644 --- a/Demo/Sources/Demos/Modern/ColorsDemo/Modern.ColorsDemo.UIKit.ListViewController.swift +++ b/Demo/Sources/Demos/Modern/ColorsDemo/⭐️Modern.ColorsDemo.UIKit.ListViewController.swift @@ -15,7 +15,7 @@ extension Modern.ColorsDemo.UIKit { final class ListViewController: UITableViewController { /** - ⭐️ Sample 1: Setting up a `DiffableDataSource.TableViewAdapter` that will manage tableView snapshot updates automatically. We can use the built-in `DiffableDataSource.TableViewAdapter` type directly, but in our case we want to enabled `UITableView` cell deletions so we create a custom subclass `DeletionEnabledDataSource` (see declatation below). + ⭐️ Sample 1: Setting up a `DiffableDataSource.TableViewAdapter` that will manage tableView snapshot updates automatically. We can use the built-in `DiffableDataSource.TableViewAdapter` type directly, but in our case we want to enabled `UITableView` cell deletions so we create a custom subclass `DeletionEnabledDataSource` (see declaration below). */ private lazy var dataSource: DiffableDataSource.TableViewAdapter = DeletionEnabledDataSource( tableView: self.tableView, diff --git a/Demo/Sources/Demos/Modern/PlacemarksDemo/Modern.PlacemarksDemo.MainView.swift b/Demo/Sources/Demos/Modern/PlacemarksDemo/⭐️Modern.PlacemarksDemo.MainView.swift similarity index 100% rename from Demo/Sources/Demos/Modern/PlacemarksDemo/Modern.PlacemarksDemo.MainView.swift rename to Demo/Sources/Demos/Modern/PlacemarksDemo/⭐️Modern.PlacemarksDemo.MainView.swift diff --git a/Demo/Sources/Demos/Modern/PokedexDemo/Modern.PokedexDemo.Details.swift b/Demo/Sources/Demos/Modern/PokedexDemo/Modern.PokedexDemo.Details.swift new file mode 100644 index 0000000..6f4237f --- /dev/null +++ b/Demo/Sources/Demos/Modern/PokedexDemo/Modern.PokedexDemo.Details.swift @@ -0,0 +1,27 @@ +// +// 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] + } +} diff --git a/Demo/Sources/Demos/Modern/PokedexDemo/Modern.PokedexDemo.ItemCell.swift b/Demo/Sources/Demos/Modern/PokedexDemo/Modern.PokedexDemo.ItemCell.swift index a0040ac..22d0d9a 100644 --- a/Demo/Sources/Demos/Modern/PokedexDemo/Modern.PokedexDemo.ItemCell.swift +++ b/Demo/Sources/Demos/Modern/PokedexDemo/Modern.PokedexDemo.ItemCell.swift @@ -102,6 +102,7 @@ extension Modern.PokedexDemo { do { nameLabel.translatesAutoresizingMaskIntoConstraints = false + nameLabel.textColor = UIColor.white nameLabel.font = UIFont.systemFont(ofSize: 14, weight: .bold) nameLabel.numberOfLines = 0 nameLabel.textAlignment = .center @@ -244,20 +245,20 @@ extension Modern.PokedexDemo { return } - self.pokemonDetails = newValue.snapshot?.$pokemonDetails + self.details = newValue.snapshot?.$details self.didUpdateData(animated: true) } - self.pokemonDetails = newValue?.snapshot?.$pokemonDetails + self.details = newValue?.snapshot?.$details } } - private var pokemonDetails: ObjectPublisher? { + private var details: ObjectPublisher? { didSet { - let newValue = self.pokemonDetails + let newValue = self.details guard newValue != oldValue else { return @@ -269,24 +270,24 @@ extension Modern.PokedexDemo { return } - let pokemonDetails = newValue.snapshot - self.pokemonForm = pokemonDetails?.$pokemonForm - self.pokemonDisplays = pokemonDetails?.$pokemonDisplays + let details = newValue.snapshot + self.species = details?.$species + self.forms = details?.$forms self.didUpdateData(animated: true) } - let pokemonDetails = newValue?.snapshot - self.pokemonForm = pokemonDetails?.$pokemonForm - self.pokemonDisplays = pokemonDetails?.$pokemonDisplays + let details = newValue?.snapshot + self.species = details?.$species + self.forms = details?.$forms } } - private var pokemonForm: ObjectPublisher? { + private var species: ObjectPublisher? { didSet { - let newValue = self.pokemonForm + let newValue = self.species guard newValue != oldValue else { return @@ -299,19 +300,19 @@ extension Modern.PokedexDemo { } } - private var rotationCancellable: AnyCancellable? - private var pokemonDisplays: [ObjectPublisher]? { + private var formsRotationCancellable: AnyCancellable? + private var forms: [ObjectPublisher]? { didSet { - let newValue = self.pokemonDisplays + let newValue = self.forms guard newValue != oldValue else { return } - self.pokemonDisplay = newValue?.first + self.currentForm = newValue?.first - self.rotationCancellable = newValue.flatMap { newValue in + self.formsRotationCancellable = newValue.flatMap { newValue in guard !newValue.isEmpty else { @@ -328,7 +329,7 @@ extension Modern.PokedexDemo { return } - self.pokemonDisplay = newValue[index % newValue.count] + self.currentForm = newValue[index % newValue.count] self.didUpdateData(animated: true) } ) @@ -336,11 +337,11 @@ extension Modern.PokedexDemo { } } - private var pokemonDisplay: ObjectPublisher? { + private var currentForm: ObjectPublisher? { didSet { - let newValue = self.pokemonDisplay + let newValue = self.currentForm guard newValue != oldValue else { return @@ -356,25 +357,25 @@ extension Modern.PokedexDemo { private func didUpdateData(animated: Bool) { let pokedexEntry = self.pokedexEntry?.snapshot - let pokemonForm = self.pokemonForm?.snapshot - let pokemonDisplay = self.pokemonDisplay?.snapshot + let species = self.species?.snapshot + let currentForm = self.currentForm?.snapshot self.placeholderLabel.text = pokedexEntry?.$id - self.placeholderLabel.isHidden = pokemonForm != nil + self.placeholderLabel.isHidden = species != nil - self.type1View.backgroundColor = pokemonForm?.$pokemonType1.color + self.type1View.backgroundColor = species?.$pokemonType1.color ?? UIColor.clear - self.type1View.isHidden = pokemonForm == nil + self.type1View.isHidden = species == nil - self.type2View.backgroundColor = pokemonForm?.$pokemonType2?.color - ?? pokemonForm?.$pokemonType1.color + self.type2View.backgroundColor = species?.$pokemonType2?.color + ?? species?.$pokemonType1.color ?? UIColor.clear - self.type2View.isHidden = pokemonForm == nil + self.type2View.isHidden = species == nil - self.nameLabel.text = pokemonDisplay?.$name ?? pokemonForm?.$name - self.nameLabel.isHidden = pokemonDisplay == nil && pokemonForm == nil + self.nameLabel.text = currentForm?.$name ?? species?.$name + self.nameLabel.isHidden = currentForm == nil && species == nil - self.imageURL = pokemonDisplay?.$spriteURL + self.imageURL = currentForm?.$spriteURL guard animated else { diff --git a/Demo/Sources/Demos/Modern/PokedexDemo/Modern.PokedexDemo.ListViewController.swift b/Demo/Sources/Demos/Modern/PokedexDemo/Modern.PokedexDemo.ListViewController.swift index 773445f..772f6f7 100644 --- a/Demo/Sources/Demos/Modern/PokedexDemo/Modern.PokedexDemo.ListViewController.swift +++ b/Demo/Sources/Demos/Modern/PokedexDemo/Modern.PokedexDemo.ListViewController.swift @@ -14,50 +14,6 @@ extension Modern.PokedexDemo { final class ListViewController: UICollectionViewController { - /** - ⭐️ Sample 1: Setting up a `DiffableDataSource.TableViewAdapter` that will manage tableView snapshot updates automatically. We can use the built-in `DiffableDataSource.TableViewAdapter` type directly, but in our case we want to enabled `UITableView` cell deletions so we create a custom subclass `DeletionEnabledDataSource` (see declatation below). - */ - private lazy var dataSource: DiffableDataSource.CollectionViewAdapter = .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 - } - ) - - /** - ⭐️ Sample 2: Once the views are created, we can start binding `ListPublisher` updates to the `DiffableDataSource`. We typically call this at the end of `viewDidLoad`. Note that the `addObserver`'s closure argument will only be called on the succeeding updates, so to immediately display the current values, we need to call `dataSource.apply()` once. - */ - private func startObservingList() { - - self.listPublisher.addObserver(self) { (listPublisher) in - - self.dataSource.apply( - listPublisher.snapshot, - animatingDifferences: true - ) - } - self.dataSource.apply( - self.listPublisher.snapshot, - animatingDifferences: false - ) - } - - /** - ⭐️ Sample 3: We can end monitoring updates anytime. `removeObserver()` was called here for illustration purposes only. `ListPublisher`s safely remove deallocated observers automatically. - */ - deinit { - - self.listPublisher.removeObserver(self) - } - - // MARK: Internal init( @@ -84,6 +40,17 @@ extension Modern.PokedexDemo { ) super.init(collectionViewLayout: layout) } + + @available(*, unavailable) + required init?(coder: NSCoder) { + + fatalError() + } + + deinit { + + self.listPublisher.removeObserver(self) + } // MARK: UIViewController @@ -92,6 +59,8 @@ extension Modern.PokedexDemo { super.viewDidLoad() + self.collectionView.backgroundColor = UIColor.systemBackground + self.collectionView.register( Modern.PokedexDemo.ItemCell.self, forCellWithReuseIdentifier: Modern.PokedexDemo.ItemCell.reuseIdentifier @@ -106,10 +75,34 @@ extension Modern.PokedexDemo { private let service: Modern.PokedexDemo.Service private let listPublisher: ListPublisher - @available(*, unavailable) - required init?(coder: NSCoder) { + private lazy var dataSource: DiffableDataSource.CollectionViewAdapter = .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() { - fatalError() + self.listPublisher.addObserver(self) { (listPublisher) in + + self.dataSource.apply( + listPublisher.snapshot, + animatingDifferences: true + ) + } + self.dataSource.apply( + self.listPublisher.snapshot, + animatingDifferences: false + ) } + } } diff --git a/Demo/Sources/Demos/Modern/PokedexDemo/Modern.PokedexDemo.MainView.swift b/Demo/Sources/Demos/Modern/PokedexDemo/Modern.PokedexDemo.MainView.swift index d877ab0..1a932e3 100644 --- a/Demo/Sources/Demos/Modern/PokedexDemo/Modern.PokedexDemo.MainView.swift +++ b/Demo/Sources/Demos/Modern/PokedexDemo/Modern.PokedexDemo.MainView.swift @@ -14,13 +14,6 @@ extension Modern.PokedexDemo { struct MainView: View { - /** - ⭐️ Sample 1: Setting a sectioned `ListPublisher` declared as an `@ObservedObject` - */ - @ObservedObject - private var pokedexEntries: ListPublisher - - // MARK: Internal init() { @@ -44,7 +37,7 @@ extension Modern.PokedexDemo { if pokedexEntries.isEmpty { - VStack(alignment: .center, spacing: 20) { + VStack(alignment: .center, spacing: 30) { Text("This demo needs to make a network connection to download Pokedex entries") .multilineTextAlignment(.center) if self.service.isLoading { @@ -70,6 +63,9 @@ extension Modern.PokedexDemo { // MARK: Private + + @ObservedObject + private var pokedexEntries: ListPublisher @ObservedObject private var service: Modern.PokedexDemo.Service = .init() diff --git a/Demo/Sources/Demos/Modern/PokedexDemo/Modern.PokedexDemo.PokemonDetails.swift b/Demo/Sources/Demos/Modern/PokedexDemo/Modern.PokedexDemo.PokemonDetails.swift deleted file mode 100644 index 23ef673..0000000 --- a/Demo/Sources/Demos/Modern/PokedexDemo/Modern.PokedexDemo.PokemonDetails.swift +++ /dev/null @@ -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.PokemonDetails - - final class PokemonDetails: CoreStoreObject { - - // MARK: Internal - - @Field.Relationship("pokedexEntry", inverse: \.$pokemonDetails) - var pokedexEntry: Modern.PokedexDemo.PokedexEntry? - - @Field.Relationship("pokemonForm") - var pokemonForm: Modern.PokedexDemo.PokemonForm? - - @Field.Relationship("pokemonDisplays") - var pokemonDisplays: [Modern.PokedexDemo.PokemonDisplay] - } -} diff --git a/Demo/Sources/Demos/Modern/PokedexDemo/Modern.PokedexDemo.Service.swift b/Demo/Sources/Demos/Modern/PokedexDemo/Modern.PokedexDemo.Service.swift deleted file mode 100644 index 39d471b..0000000 --- a/Demo/Sources/Demos/Modern/PokedexDemo/Modern.PokedexDemo.Service.swift +++ /dev/null @@ -1,399 +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 { - - // 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( - _ 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( - _ 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) { - - self.fetchPokemonFormIfNeeded(for: pokedexEntry) - } - - private func fetchPokemonFormIfNeeded(for pokedexEntry: ObjectSnapshot) { - - guard let pokemonDetails = pokedexEntry.$pokemonDetails?.snapshot else { - - return - } - if let pokedexForm = pokemonDetails.$pokemonForm?.snapshot { - - self.fetchPokemonDisplayIfNeeded(for: pokedexForm) - return - } - self.cancellable["pokemonForm.\(pokedexEntry.$id)"] = URLSession.shared - .dataTaskPublisher(for: pokedexEntry.$pokemonFormURL) - .mapError({ .networkError($0) }) - .flatMap( - { output in - - return Future, Modern.PokedexDemo.Service.Error> { promise in - - Modern.PokedexDemo.dataStack.perform( - asynchronous: { transaction -> Modern.PokedexDemo.PokemonForm in - - let json: Dictionary = try Self.parseJSON( - try JSONSerialization.jsonObject(with: output.data, options: []) - ) - guard let pokedexForm = try transaction.importUniqueObject( - Into(), - source: json - ) else { - - throw Modern.PokedexDemo.Service.Error.unexpected - } - pokemonDetails.asEditable(in: transaction)?.pokemonForm = pokedexForm - 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: { completion in - - switch completion { - - case .finished: - break - - case .failure(let error): - print(error) - } - }, - receiveValue: { pokemonForm in - - self.fetchPokemonDisplayIfNeeded(for: pokemonForm) - } - ) - } - - func fetchPokemonDisplayIfNeeded(for pokemonForm: ObjectSnapshot) { - - guard - let pokemonDetails = pokemonForm.$pokemonDetails?.snapshot, - pokemonDetails.$pokemonDisplays.isEmpty - else { - - return - } - - - - self.cancellable["pokemonDisplay.\(pokemonForm.$id)"] = pokemonForm - .$pokemonDisplayURLs - .map( - { url in - URLSession.shared - .dataTaskPublisher(for: url) - .mapError({ Modern.PokedexDemo.Service.Error.networkError($0) }) - .tryMap( - { output -> Dictionary in - - try Self.parseJSON( - try JSONSerialization.jsonObject(with: output.data, options: []) - ) - } - ) - .mapError( - { error -> Modern.PokedexDemo.Service.Error in - switch error { - - case let error as Modern.PokedexDemo.Service.Error: - return error - - case let error: - return Modern.PokedexDemo.Service.Error.otherError(error) - } - } - ) - .eraseToAnyPublisher() - } - ) - .reduce( - into: Just<[Dictionary]>([]) - .setFailureType(to: Modern.PokedexDemo.Service.Error.self) - .eraseToAnyPublisher(), - { (result, publisher) in - result = result - .zip(publisher, { $0 + [$1] }) - .eraseToAnyPublisher() - } - ) - .flatMap( - { outputs in - - return Future { promise in - - Modern.PokedexDemo.dataStack.perform( - asynchronous: { transaction -> Void in - - let pokemonDisplays = try transaction.importUniqueObjects( - Into(), - sourceArray: outputs - ) - guard !pokemonDisplays.isEmpty else { - - throw Modern.PokedexDemo.Service.Error.unexpected - } - pokemonDetails.asEditable(in: transaction)?.pokemonDisplays = pokemonDisplays - }, - 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 - - } - ) - } - - - // MARK: Private - - private var cancellable: Dictionary = [:] - - private lazy var pokedexEntries: AnyPublisher = URLSession.shared - .dataTaskPublisher( - for: URL(string: "https://pokeapi.co/api/v2/pokemon?limit=10000&offset=0")! - ) - .mapError({ .networkError($0) }) - .flatMap( - { output in - - return Future { promise in - - do { - - let json: Dictionary = try Self.parseJSON( - try JSONSerialization.jsonObject(with: output.data, options: []) - ) - let results: [Dictionary] = try Self.parseJSON( - json["results"] - ) - Modern.PokedexDemo.dataStack.perform( - asynchronous: { transaction -> Void in - - _ = try transaction.importUniqueObjects( - Into(), - 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 let error as Modern.PokedexDemo.Service.Error { - - promise(.failure(error)) - } - catch { - - promise(.failure(.otherError(error))) - } - } - } - ) - .eraseToAnyPublisher() - - - // 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 - } - } -} diff --git a/Demo/Sources/Demos/Modern/PokedexDemo/Modern.PokedexDemo.swift b/Demo/Sources/Demos/Modern/PokedexDemo/Modern.PokedexDemo.swift index 0e65d0b..000619b 100644 --- a/Demo/Sources/Demos/Modern/PokedexDemo/Modern.PokedexDemo.swift +++ b/Demo/Sources/Demos/Modern/PokedexDemo/Modern.PokedexDemo.swift @@ -24,9 +24,9 @@ extension Modern { modelVersion: "V1", entities: [ Entity("PokedexEntry"), - Entity("PokemonDetails"), - Entity("PokemonForm"), - Entity("PokemonDisplay") + Entity("Details"), + Entity("Species"), + Entity("Form") ] ) ) diff --git a/Demo/Sources/Demos/Modern/PokedexDemo/Modern.PokedexDemo.PokemonDisplay.swift b/Demo/Sources/Demos/Modern/PokedexDemo/⭐️Modern.PokedexDemo.Form.swift similarity index 78% rename from Demo/Sources/Demos/Modern/PokedexDemo/Modern.PokedexDemo.PokemonDisplay.swift rename to Demo/Sources/Demos/Modern/PokedexDemo/⭐️Modern.PokedexDemo.Form.swift index 82e5fa1..9399083 100644 --- a/Demo/Sources/Demos/Modern/PokedexDemo/Modern.PokedexDemo.PokemonDisplay.swift +++ b/Demo/Sources/Demos/Modern/PokedexDemo/⭐️Modern.PokedexDemo.Form.swift @@ -9,9 +9,12 @@ import UIKit extension Modern.PokedexDemo { - // MARK: - Modern.PokedexDemo.PokemonDisplay + // MARK: - Modern.PokedexDemo.Form - final class PokemonDisplay: CoreStoreObject, ImportableUniqueObject { + /** + ⭐️ 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 @@ -25,8 +28,8 @@ extension Modern.PokedexDemo { var spriteURL: URL? - @Field.Relationship("pokemonDetails", inverse: \.$pokemonDisplays) - var pokemonDetails: Modern.PokedexDemo.PokemonDetails? + @Field.Relationship("details", inverse: \.$forms) + var details: Modern.PokedexDemo.Details? // MARK: ImportableObject @@ -38,7 +41,7 @@ extension Modern.PokedexDemo { typealias UniqueIDType = Int - static let uniqueIDKeyPath: String = String(keyPath: \Modern.PokedexDemo.PokemonDisplay.$id) + static let uniqueIDKeyPath: String = String(keyPath: \Modern.PokedexDemo.Form.$id) var uniqueIDValue: UniqueIDType { diff --git a/Demo/Sources/Demos/Modern/PokedexDemo/Modern.PokedexDemo.PokedexEntry.swift b/Demo/Sources/Demos/Modern/PokedexDemo/⭐️Modern.PokedexDemo.PokedexEntry.swift similarity index 74% rename from Demo/Sources/Demos/Modern/PokedexDemo/Modern.PokedexDemo.PokedexEntry.swift rename to Demo/Sources/Demos/Modern/PokedexDemo/⭐️Modern.PokedexDemo.PokedexEntry.swift index 3f940b9..89c02a1 100644 --- a/Demo/Sources/Demos/Modern/PokedexDemo/Modern.PokedexDemo.PokedexEntry.swift +++ b/Demo/Sources/Demos/Modern/PokedexDemo/⭐️Modern.PokedexDemo.PokedexEntry.swift @@ -9,7 +9,10 @@ import CoreStore 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 @@ -21,14 +24,14 @@ extension Modern.PokedexDemo { var id: String = "" @Field.Stored( - "pokemonFormURL", + "speciesURL", dynamicInitialValue: { URL(string: "data:application/json,%7B%7D")! } ) - var pokemonFormURL: URL + var speciesURL: URL - @Field.Relationship("pokemonDetails") - var pokemonDetails: Modern.PokedexDemo.PokemonDetails? + @Field.Relationship("details") + var details: Modern.PokedexDemo.Details? // MARK: ImportableObject @@ -37,7 +40,8 @@ extension Modern.PokedexDemo { func didInsert(from source: ImportSource, in transaction: BaseDataTransaction) throws { - self.pokemonDetails = transaction.create(Into()) + self.details = transaction.create(Into()) + try self.update(from: source, in: transaction) } @@ -64,7 +68,7 @@ extension Modern.PokedexDemo { let json = source.json self.index = source.index - self.pokemonFormURL = try Modern.PokedexDemo.Service.parseJSON(json["url"], transformer: URL.init(string:)) + self.speciesURL = try Modern.PokedexDemo.Service.parseJSON(json["url"], transformer: URL.init(string:)) } } } diff --git a/Demo/Sources/Demos/Modern/PokedexDemo/Modern.PokedexDemo.PokemonType.swift b/Demo/Sources/Demos/Modern/PokedexDemo/⭐️Modern.PokedexDemo.PokemonType.swift similarity index 91% rename from Demo/Sources/Demos/Modern/PokedexDemo/Modern.PokedexDemo.PokemonType.swift rename to Demo/Sources/Demos/Modern/PokedexDemo/⭐️Modern.PokedexDemo.PokemonType.swift index 8e078f2..408d9b6 100644 --- a/Demo/Sources/Demos/Modern/PokedexDemo/Modern.PokedexDemo.PokemonType.swift +++ b/Demo/Sources/Demos/Modern/PokedexDemo/⭐️Modern.PokedexDemo.PokemonType.swift @@ -11,6 +11,9 @@ 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 diff --git a/Demo/Sources/Demos/Modern/PokedexDemo/⭐️Modern.PokedexDemo.Service.swift b/Demo/Sources/Demos/Modern/PokedexDemo/⭐️Modern.PokedexDemo.Service.swift new file mode 100644 index 0000000..a365b18 --- /dev/null +++ b/Demo/Sources/Demos/Modern/PokedexDemo/⭐️Modern.PokedexDemo.Service.swift @@ -0,0 +1,390 @@ +// +// 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 { + + return .init { promise in + + Modern.PokedexDemo.dataStack.perform( + asynchronous: { transaction -> Void in + + let json: Dictionary = try self.parseJSON( + try JSONSerialization.jsonObject(with: output.data, options: []) + ) + let results: [Dictionary] = try self.parseJSON( + json["results"] + ) + _ = try transaction.importUniqueObjects( + Into(), + 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, + from output: URLSession.DataTaskPublisher.Output + ) -> Future, Modern.PokedexDemo.Service.Error> { + + return .init { promise in + + Modern.PokedexDemo.dataStack.perform( + asynchronous: { transaction -> Modern.PokedexDemo.Species in + + let json: Dictionary = try self.parseJSON( + try JSONSerialization.jsonObject(with: output.data, options: []) + ) + guard + let species = try transaction.importUniqueObject( + Into(), + 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, + from outputs: [URLSession.DataTaskPublisher.Output] + ) -> Future { + + return .init { promise in + + Modern.PokedexDemo.dataStack.perform( + asynchronous: { transaction -> Void in + + let forms = try transaction.importUniqueObjects( + Into(), + 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( + _ 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( + _ 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) { + + self.fetchSpeciesIfNeeded(for: pokedexEntry) + } + + + // MARK: Private + + private var cancellable: Dictionary = [:] + + private lazy var pokedexEntries: AnyPublisher = 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) { + + 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) { + + 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 + } + } +} diff --git a/Demo/Sources/Demos/Modern/PokedexDemo/Modern.PokedexDemo.PokemonForm.swift b/Demo/Sources/Demos/Modern/PokedexDemo/⭐️Modern.PokedexDemo.Species.swift similarity index 84% rename from Demo/Sources/Demos/Modern/PokedexDemo/Modern.PokedexDemo.PokemonForm.swift rename to Demo/Sources/Demos/Modern/PokedexDemo/⭐️Modern.PokedexDemo.Species.swift index 0e4b093..e1ed4e5 100644 --- a/Demo/Sources/Demos/Modern/PokedexDemo/Modern.PokedexDemo.PokemonForm.swift +++ b/Demo/Sources/Demos/Modern/PokedexDemo/⭐️Modern.PokedexDemo.Species.swift @@ -8,9 +8,12 @@ import CoreStore extension Modern.PokedexDemo { - // MARK: - Modern.PokedexDemo.PokemonForm + // MARK: - Modern.PokedexDemo.Species - final class PokemonForm: CoreStoreObject, ImportableUniqueObject { + /** + ⭐️ 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 @@ -50,14 +53,14 @@ extension Modern.PokedexDemo { @Field.Coded( - "pokemonDisplayURLs", + "formsURLs", coder: FieldCoders.Json.self ) - var pokemonDisplayURLs: [URL] = [] + var formsURLs: [URL] = [] - @Field.Relationship("pokemonDetails", inverse: \.$pokemonForm) - var pokemonDetails: Modern.PokedexDemo.PokemonDetails? + @Field.Relationship("details", inverse: \.$species) + var details: Modern.PokedexDemo.Details? // MARK: ImportableObject @@ -69,7 +72,7 @@ extension Modern.PokedexDemo { typealias UniqueIDType = Int - static let uniqueIDKeyPath: String = String(keyPath: \Modern.PokedexDemo.PokemonForm.$id) + static let uniqueIDKeyPath: String = String(keyPath: \Modern.PokedexDemo.Species.$id) var uniqueIDValue: UniqueIDType { @@ -129,11 +132,8 @@ extension Modern.PokedexDemo { } } - self.pokemonDisplayURLs = try (Service.parseJSON(json["forms"]) as [Dictionary]).map { json in - - let pokemonDisplayURL = try Service.parseJSON(json["url"], transformer: URL.init(string:)) - return pokemonDisplayURL - } + self.formsURLs = try (Service.parseJSON(json["forms"]) as [Dictionary]) + .map({ try Service.parseJSON($0["url"], transformer: URL.init(string:)) }) } } } diff --git a/Demo/Sources/Demos/Modern/TimeZonesDemo/Modern.TimeZonesDemo.MainView.swift b/Demo/Sources/Demos/Modern/TimeZonesDemo/⭐️Modern.TimeZonesDemo.MainView.swift similarity index 100% rename from Demo/Sources/Demos/Modern/TimeZonesDemo/Modern.TimeZonesDemo.MainView.swift rename to Demo/Sources/Demos/Modern/TimeZonesDemo/⭐️Modern.TimeZonesDemo.MainView.swift