From bcf3098bfea9bf4aae5528f91dcd593bfe2f1bcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dr=2E=20Julian-Steffen=20M=C3=BCller?= Date: Fri, 14 May 2021 17:42:35 +0200 Subject: [PATCH] New Baseline -- Added basic Cloudkit support and changed to julian.steffen@me.com Apple Developer Account --- Cartfile | 1 - Zockerhoehle.xcodeproj/project.pbxproj | 252 +++++++++----- .../contents.xcworkspacedata | 2 +- .../xcshareddata/swiftpm/Package.resolved | 25 ++ .../xcschemes/xcschememanagement.plist | 10 + Zockerhoehle/AppDelegate.swift | 11 +- .../basket.imageset/basket-supermarket.png | Bin 1924 -> 0 bytes .../Contents.json | 2 +- .../digitalGame.imageset/Image.png | Bin 0 -> 13465 bytes .../Assets.xcassets/star.imageset/star.png | Bin 6884 -> 0 bytes .../Contents.json | 3 +- .../wishlist.imageset/Image-1.png | Bin 0 -> 3803 bytes .../wishlist.imageset/Image.png | Bin 0 -> 2463 bytes Zockerhoehle/CDManager.swift | 9 +- .../CDModel/Accessory+CoreDataClass.swift | 34 ++ .../Accessory+CoreDataProperties.swift | 2 + .../CDModel/Console+CoreDataClass.swift | 84 ++++- .../CDModel/Console+CoreDataProperties.swift | 13 +- .../CDModel/Cover+CoreDataClass.swift | 1 - .../CDModel/Cover+CoreDataProperties.swift | 2 +- Zockerhoehle/CDModel/Game+CoreDataClass.swift | 74 ++++ .../CDModel/Game+CoreDataProperties.swift | 16 +- .../CDModel/GameSeries+CoreDataClass.swift | 54 ++- .../GameSeries+CoreDataProperties.swift | 7 +- .../GameSeriesCover+CoreDataClass.swift | 16 + .../GameSeriesCover+CoreDataProperties.swift | 23 ++ Zockerhoehle/Info.plist | 8 + Zockerhoehle/Model/Attachment.swift | 31 -- Zockerhoehle/Model/ConsoleEntry.swift | 44 --- Zockerhoehle/Model/FlockeEntry.swift | 103 ------ Zockerhoehle/Model/GameCollection.swift | 206 ----------- Zockerhoehle/SceneDelegate.swift | 10 +- .../{Lib => Utils}/CodableExtensionAny.swift | 0 Zockerhoehle/Utils/DateConversion.swift | 27 ++ Zockerhoehle/Utils/FlockeConnector.swift | 45 --- Zockerhoehle/Utils/FlockeWS.swift | 152 -------- Zockerhoehle/Utils/ImagePicker.swift | 63 ++++ Zockerhoehle/Utils/LibraryExport.swift | 100 ++++++ Zockerhoehle/Utils/LibraryImport.swift | 328 ++++++++++++++++++ .../ConsoleLibraryViewController.swift | 213 ------------ Zockerhoehle/ViewModel/AccessoryStore.swift | 4 + .../ViewModel/AccessoryViewModel.swift | 71 ++-- Zockerhoehle/ViewModel/ConsoleViewModel.swift | 55 +++ Zockerhoehle/ViewModel/FeaturedStore.swift | 54 +++ Zockerhoehle/ViewModel/GameSeriesStore.swift | 48 +++ .../ViewModel/GameSeriesViewModel.swift | 55 +++ Zockerhoehle/ViewModel/GameStore.swift | 48 ++- Zockerhoehle/ViewModel/GameViewModel.swift | 115 ++++-- Zockerhoehle/Views/AccessoryDetailView.swift | 8 - Zockerhoehle/Views/ConsoleAllView.swift | 108 ++++++ Zockerhoehle/Views/ConsoleEditView.swift | 42 +++ Zockerhoehle/Views/ConsoleLibraryView.swift | 144 ++++++-- Zockerhoehle/Views/ConsolesListView.swift | 37 -- Zockerhoehle/Views/GameDetailView.swift | 156 +++++++-- Zockerhoehle/Views/GamePickupsView.swift | 23 ++ Zockerhoehle/Views/GameSeriesAllView.swift | 89 +++++ Zockerhoehle/Views/GameSeriesEditView.swift | 42 +++ .../Views/GameSeriesLibraryView.swift | 50 +++ Zockerhoehle/Views/MainView.swift | 27 ++ Zockerhoehle/Views/Overview.swift | 206 +++++++++++ Zockerhoehle/Views/SettingsView.swift | 54 +++ Zockerhoehle/Zockerhoehle.entitlements | 16 + .../Zockerhoehle.xcdatamodel/contents | 41 ++- 63 files changed, 2378 insertions(+), 1086 deletions(-) delete mode 100644 Cartfile create mode 100644 Zockerhoehle.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved delete mode 100644 Zockerhoehle/Assets.xcassets/basket.imageset/basket-supermarket.png rename Zockerhoehle/Assets.xcassets/{star.imageset => digitalGame.imageset}/Contents.json (89%) create mode 100644 Zockerhoehle/Assets.xcassets/digitalGame.imageset/Image.png delete mode 100644 Zockerhoehle/Assets.xcassets/star.imageset/star.png rename Zockerhoehle/Assets.xcassets/{basket.imageset => wishlist.imageset}/Contents.json (80%) create mode 100644 Zockerhoehle/Assets.xcassets/wishlist.imageset/Image-1.png create mode 100644 Zockerhoehle/Assets.xcassets/wishlist.imageset/Image.png create mode 100644 Zockerhoehle/CDModel/GameSeriesCover+CoreDataClass.swift create mode 100644 Zockerhoehle/CDModel/GameSeriesCover+CoreDataProperties.swift delete mode 100644 Zockerhoehle/Model/Attachment.swift delete mode 100644 Zockerhoehle/Model/ConsoleEntry.swift delete mode 100644 Zockerhoehle/Model/FlockeEntry.swift delete mode 100644 Zockerhoehle/Model/GameCollection.swift rename Zockerhoehle/{Lib => Utils}/CodableExtensionAny.swift (100%) create mode 100644 Zockerhoehle/Utils/DateConversion.swift delete mode 100644 Zockerhoehle/Utils/FlockeConnector.swift delete mode 100644 Zockerhoehle/Utils/FlockeWS.swift create mode 100644 Zockerhoehle/Utils/ImagePicker.swift create mode 100644 Zockerhoehle/Utils/LibraryExport.swift create mode 100644 Zockerhoehle/Utils/LibraryImport.swift delete mode 100644 Zockerhoehle/ViewController/ConsoleLibraryViewController.swift create mode 100644 Zockerhoehle/ViewModel/ConsoleViewModel.swift create mode 100644 Zockerhoehle/ViewModel/FeaturedStore.swift create mode 100644 Zockerhoehle/ViewModel/GameSeriesStore.swift create mode 100644 Zockerhoehle/ViewModel/GameSeriesViewModel.swift create mode 100644 Zockerhoehle/Views/ConsoleAllView.swift create mode 100644 Zockerhoehle/Views/ConsoleEditView.swift delete mode 100644 Zockerhoehle/Views/ConsolesListView.swift create mode 100644 Zockerhoehle/Views/GamePickupsView.swift create mode 100644 Zockerhoehle/Views/GameSeriesAllView.swift create mode 100644 Zockerhoehle/Views/GameSeriesEditView.swift create mode 100644 Zockerhoehle/Views/GameSeriesLibraryView.swift create mode 100644 Zockerhoehle/Views/MainView.swift create mode 100644 Zockerhoehle/Views/Overview.swift create mode 100644 Zockerhoehle/Views/SettingsView.swift create mode 100644 Zockerhoehle/Zockerhoehle.entitlements diff --git a/Cartfile b/Cartfile deleted file mode 100644 index 07354f2..0000000 --- a/Cartfile +++ /dev/null @@ -1 +0,0 @@ -github "saoudrizwan/Disk" diff --git a/Zockerhoehle.xcodeproj/project.pbxproj b/Zockerhoehle.xcodeproj/project.pbxproj index 0a0cc7d..5e15b4d 100644 --- a/Zockerhoehle.xcodeproj/project.pbxproj +++ b/Zockerhoehle.xcodeproj/project.pbxproj @@ -3,61 +3,87 @@ archiveVersion = 1; classes = { }; - objectVersion = 50; + objectVersion = 52; objects = { /* Begin PBXBuildFile section */ - B926F12D2149B264004D36B7 /* FlockeEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = B926F12C2149B264004D36B7 /* FlockeEntry.swift */; }; - B926F131214AD9E4004D36B7 /* GameCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = B926F130214AD9E4004D36B7 /* GameCollection.swift */; }; - B926F139214AE884004D36B7 /* FlockeWS.swift in Sources */ = {isa = PBXBuildFile; fileRef = B926F138214AE884004D36B7 /* FlockeWS.swift */; }; - B926F13C214C44FE004D36B7 /* Attachment.swift in Sources */ = {isa = PBXBuildFile; fileRef = B926F13B214C44FE004D36B7 /* Attachment.swift */; }; + B90E03EB238557D900E79643 /* LibraryImport.swift in Sources */ = {isa = PBXBuildFile; fileRef = B90E03EA238557D900E79643 /* LibraryImport.swift */; }; B926F14721502D53004D36B7 /* CodableExtensionAny.swift in Sources */ = {isa = PBXBuildFile; fileRef = B926F14621502D53004D36B7 /* CodableExtensionAny.swift */; }; - B926F14A21502DE1004D36B7 /* ConsoleEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = B926F14921502DE1004D36B7 /* ConsoleEntry.swift */; }; B93C1B9D21496BFD0014FD6E /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B93C1B9C21496BFD0014FD6E /* AppDelegate.swift */; }; B93C1BA421496BFE0014FD6E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B93C1BA321496BFE0014FD6E /* Assets.xcassets */; }; B93D60CE22D88F5700DD390F /* AccessoryDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B93D60CD22D88F5700DD390F /* AccessoryDetailView.swift */; }; B93D60D122E5009700DD390F /* GameViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B93D60D022E5009700DD390F /* GameViewModel.swift */; }; + B94112DE233A37DD00159AE4 /* DateConversion.swift in Sources */ = {isa = PBXBuildFile; fileRef = B94112DD233A37DD00159AE4 /* DateConversion.swift */; }; + B94112E0233A4EF800159AE4 /* GamePickupsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B94112DF233A4EF800159AE4 /* GamePickupsView.swift */; }; + B94112E2233B55B100159AE4 /* ConsoleViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B94112E1233B55B100159AE4 /* ConsoleViewModel.swift */; }; + B94112E4233B597D00159AE4 /* ConsoleEditView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B94112E3233B597D00159AE4 /* ConsoleEditView.swift */; }; B94CB4FF22D1352F0029BFAD /* Accessory+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = B94CB4F322D1352F0029BFAD /* Accessory+CoreDataClass.swift */; }; B94CB50022D1352F0029BFAD /* Accessory+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = B94CB4F422D1352F0029BFAD /* Accessory+CoreDataProperties.swift */; }; - B94CB50122D1352F0029BFAD /* Cover+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = B94CB4F522D1352F0029BFAD /* Cover+CoreDataClass.swift */; }; B94CB50222D1352F0029BFAD /* Cover+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = B94CB4F622D1352F0029BFAD /* Cover+CoreDataProperties.swift */; }; B94CB50322D1352F0029BFAD /* Game+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = B94CB4F722D1352F0029BFAD /* Game+CoreDataClass.swift */; }; B94CB50422D1352F0029BFAD /* Game+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = B94CB4F822D1352F0029BFAD /* Game+CoreDataProperties.swift */; }; - B94CB50522D1352F0029BFAD /* Console+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = B94CB4F922D1352F0029BFAD /* Console+CoreDataClass.swift */; }; - B94CB50622D1352F0029BFAD /* Console+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = B94CB4FA22D1352F0029BFAD /* Console+CoreDataProperties.swift */; }; - B94CB50722D1352F0029BFAD /* GameSeries+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = B94CB4FB22D1352F0029BFAD /* GameSeries+CoreDataClass.swift */; }; - B94CB50822D1352F0029BFAD /* GameSeries+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = B94CB4FC22D1352F0029BFAD /* GameSeries+CoreDataProperties.swift */; }; B94CB50922D1352F0029BFAD /* Logo+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = B94CB4FD22D1352F0029BFAD /* Logo+CoreDataClass.swift */; }; B94CB50A22D1352F0029BFAD /* Logo+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = B94CB4FE22D1352F0029BFAD /* Logo+CoreDataProperties.swift */; }; B94CB53722D3B3CC0029BFAD /* GameDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B94CB53622D3B3CC0029BFAD /* GameDetailView.swift */; }; + B97AD2E0244CE4B4004AF00D /* Disk in Frameworks */ = {isa = PBXBuildFile; productRef = B97AD2DF244CE4B4004AF00D /* Disk */; }; + B9839983233A086A002F9946 /* Overview.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9839982233A086A002F9946 /* Overview.swift */; }; + B983998C233A0BC9002F9946 /* Cover+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = B94CB4F522D1352F0029BFAD /* Cover+CoreDataClass.swift */; }; + B9839991233A0E16002F9946 /* GameSeriesCover+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9839985233A0ADB002F9946 /* GameSeriesCover+CoreDataProperties.swift */; }; + B9839992233A0E19002F9946 /* GameSeriesCover+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9839984233A0ADB002F9946 /* GameSeriesCover+CoreDataClass.swift */; }; B98A734D22BAD27D00FB3410 /* Zockerhoehle.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = B98A731722BA9E4600FB3410 /* Zockerhoehle.xcdatamodeld */; }; - B98A735E22BFAA4B00FB3410 /* ConsoleLibraryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B926F12E2149B6F5004D36B7 /* ConsoleLibraryViewController.swift */; }; B98A736022C1738800FB3410 /* CDManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B98A735F22C1738800FB3410 /* CDManager.swift */; }; + B98B2FAA2328DF3400606DC4 /* GameSeriesStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = B98B2FA92328DF3400606DC4 /* GameSeriesStore.swift */; }; + B98B2FAC232C0F8C00606DC4 /* ImagePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = B98B2FAB232C0F8C00606DC4 /* ImagePicker.swift */; }; + B98CBBD2264E933A00B1B7AC /* CloudKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B98CBBD1264E933A00B1B7AC /* CloudKit.framework */; }; + B98CBBDA264E98DD00B1B7AC /* GameSeries+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = B98CBBD7264E98DD00B1B7AC /* GameSeries+CoreDataProperties.swift */; }; + B98CBBDB264E98DE00B1B7AC /* Console+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = B98CBBD8264E98DD00B1B7AC /* Console+CoreDataProperties.swift */; }; + B98CBBDC264E98DE00B1B7AC /* GameSeries+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = B98CBBD9264E98DD00B1B7AC /* GameSeries+CoreDataClass.swift */; }; + B98CBBDD264E98F300B1B7AC /* Console+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = B94CB4F922D1352F0029BFAD /* Console+CoreDataClass.swift */; }; + B9A0550122F8C22D0054D9A0 /* GameSeriesAllView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9A0550022F8C22D0054D9A0 /* GameSeriesAllView.swift */; }; + B9A0550322F8C2740054D9A0 /* MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9A0550222F8C2740054D9A0 /* MainView.swift */; }; + B9A0550522F8CB400054D9A0 /* GameSeriesLibraryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9A0550422F8CB400054D9A0 /* GameSeriesLibraryView.swift */; }; B9BCF4CA2168ACB600ECBAAC /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B9BCF4C92168ACB600ECBAAC /* LaunchScreen.storyboard */; }; B9D2C6F722E98ED800797F67 /* AccessoryViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9D2C6F622E98ED800797F67 /* AccessoryViewModel.swift */; }; - B9F002E52187AA3200E12B0A /* FlockeConnector.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9F002E42187AA3200E12B0A /* FlockeConnector.swift */; }; + B9E2A079233B69D400EAEB14 /* GameSeriesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9E2A078233B69D400EAEB14 /* GameSeriesViewModel.swift */; }; + B9E2A07B233B6A8F00EAEB14 /* GameSeriesEditView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9E2A07A233B6A8F00EAEB14 /* GameSeriesEditView.swift */; }; + B9E2A07E233B6E4F00EAEB14 /* ConsoleAllView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9E2A07C233B6E4F00EAEB14 /* ConsoleAllView.swift */; }; + B9E2A081233BA62100EAEB14 /* FeaturedStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9E2A080233BA62100EAEB14 /* FeaturedStore.swift */; }; + B9EC09822383F94B004BC9AB /* LibraryExport.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9EC09812383F94B004BC9AB /* LibraryExport.swift */; }; + B9EC098523854C24004BC9AB /* QGrid in Frameworks */ = {isa = PBXBuildFile; productRef = B9EC098423854C24004BC9AB /* QGrid */; }; + B9EC0987238555BF004BC9AB /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9EC0986238555BF004BC9AB /* SettingsView.swift */; }; B9F44ABA22F312E600FC6B29 /* ConsoleLibraryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9F44AB922F312E600FC6B29 /* ConsoleLibraryView.swift */; }; B9F44ABE22F31DEF00FC6B29 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9F44ABD22F31DEF00FC6B29 /* SceneDelegate.swift */; }; - B9F44AE322F3216F00FC6B29 /* ConsolesListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9F44AE222F3216F00FC6B29 /* ConsolesListView.swift */; }; B9F44AE522F418F600FC6B29 /* ConsoleStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9F44AE422F418F600FC6B29 /* ConsoleStore.swift */; }; B9F44AE722F429D300FC6B29 /* GameStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9F44AE622F429D300FC6B29 /* GameStore.swift */; }; B9F44AE922F4655600FC6B29 /* AccessoryStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9F44AE822F4655600FC6B29 /* AccessoryStore.swift */; }; /* End PBXBuildFile section */ +/* Begin PBXCopyFilesBuildPhase section */ + B983997A233A0295002F9946 /* Embed Watch Content */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = "$(CONTENTS_FOLDER_PATH)/Watch"; + dstSubfolderSpec = 16; + files = ( + ); + name = "Embed Watch Content"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + /* Begin PBXFileReference section */ - B926F12C2149B264004D36B7 /* FlockeEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlockeEntry.swift; sourceTree = ""; }; - B926F12E2149B6F5004D36B7 /* ConsoleLibraryViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConsoleLibraryViewController.swift; sourceTree = ""; }; - B926F130214AD9E4004D36B7 /* GameCollection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameCollection.swift; sourceTree = ""; }; - B926F138214AE884004D36B7 /* FlockeWS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlockeWS.swift; sourceTree = ""; }; - B926F13B214C44FE004D36B7 /* Attachment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Attachment.swift; sourceTree = ""; }; + B90E03EA238557D900E79643 /* LibraryImport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryImport.swift; sourceTree = ""; }; B926F14621502D53004D36B7 /* CodableExtensionAny.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodableExtensionAny.swift; sourceTree = ""; }; - B926F14921502DE1004D36B7 /* ConsoleEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConsoleEntry.swift; sourceTree = ""; }; B93C1B9921496BFD0014FD6E /* Zockerhoehle.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Zockerhoehle.app; sourceTree = BUILT_PRODUCTS_DIR; }; B93C1B9C21496BFD0014FD6E /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; B93C1BA321496BFE0014FD6E /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; B93C1BA821496BFE0014FD6E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; B93D60CD22D88F5700DD390F /* AccessoryDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessoryDetailView.swift; sourceTree = ""; }; B93D60D022E5009700DD390F /* GameViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameViewModel.swift; sourceTree = ""; }; + B94112DD233A37DD00159AE4 /* DateConversion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateConversion.swift; sourceTree = ""; }; + B94112DF233A4EF800159AE4 /* GamePickupsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GamePickupsView.swift; sourceTree = ""; }; + B94112E1233B55B100159AE4 /* ConsoleViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConsoleViewModel.swift; sourceTree = ""; }; + B94112E3233B597D00159AE4 /* ConsoleEditView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConsoleEditView.swift; sourceTree = ""; }; B94CB4F322D1352F0029BFAD /* Accessory+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Accessory+CoreDataClass.swift"; sourceTree = ""; }; B94CB4F422D1352F0029BFAD /* Accessory+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Accessory+CoreDataProperties.swift"; sourceTree = ""; }; B94CB4F522D1352F0029BFAD /* Cover+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Cover+CoreDataClass.swift"; sourceTree = ""; }; @@ -66,20 +92,35 @@ B94CB4F822D1352F0029BFAD /* Game+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Game+CoreDataProperties.swift"; sourceTree = ""; }; B94CB4F922D1352F0029BFAD /* Console+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Console+CoreDataClass.swift"; sourceTree = ""; }; B94CB4FA22D1352F0029BFAD /* Console+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Console+CoreDataProperties.swift"; sourceTree = ""; }; - B94CB4FB22D1352F0029BFAD /* GameSeries+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "GameSeries+CoreDataClass.swift"; sourceTree = ""; }; - B94CB4FC22D1352F0029BFAD /* GameSeries+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "GameSeries+CoreDataProperties.swift"; sourceTree = ""; }; B94CB4FD22D1352F0029BFAD /* Logo+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Logo+CoreDataClass.swift"; sourceTree = ""; }; B94CB4FE22D1352F0029BFAD /* Logo+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Logo+CoreDataProperties.swift"; sourceTree = ""; }; B94CB53522D3708F0029BFAD /* Zockerhoehle copy-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = "Zockerhoehle copy-Info.plist"; path = "/Users/julian/Entwicklung/Zockerhoehle/Zockerhoehle copy-Info.plist"; sourceTree = ""; }; B94CB53622D3B3CC0029BFAD /* GameDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameDetailView.swift; sourceTree = ""; }; + B9839982233A086A002F9946 /* Overview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Overview.swift; sourceTree = ""; }; + B9839984233A0ADB002F9946 /* GameSeriesCover+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "GameSeriesCover+CoreDataClass.swift"; sourceTree = ""; }; + B9839985233A0ADB002F9946 /* GameSeriesCover+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "GameSeriesCover+CoreDataProperties.swift"; sourceTree = ""; }; B98A731822BA9E4600FB3410 /* Zockerhoehle.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Zockerhoehle.xcdatamodel; sourceTree = ""; }; B98A735F22C1738800FB3410 /* CDManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CDManager.swift; sourceTree = ""; }; + B98B2FA92328DF3400606DC4 /* GameSeriesStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameSeriesStore.swift; sourceTree = ""; }; + B98B2FAB232C0F8C00606DC4 /* ImagePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagePicker.swift; sourceTree = ""; }; + B98CBBD0264E933400B1B7AC /* Zockerhoehle.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Zockerhoehle.entitlements; sourceTree = ""; }; + B98CBBD1264E933A00B1B7AC /* CloudKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CloudKit.framework; path = System/Library/Frameworks/CloudKit.framework; sourceTree = SDKROOT; }; + B98CBBD7264E98DD00B1B7AC /* GameSeries+CoreDataProperties.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "GameSeries+CoreDataProperties.swift"; sourceTree = ""; }; + B98CBBD8264E98DD00B1B7AC /* Console+CoreDataProperties.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Console+CoreDataProperties.swift"; sourceTree = ""; }; + B98CBBD9264E98DD00B1B7AC /* GameSeries+CoreDataClass.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "GameSeries+CoreDataClass.swift"; sourceTree = ""; }; + B9A0550022F8C22D0054D9A0 /* GameSeriesAllView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameSeriesAllView.swift; sourceTree = ""; }; + B9A0550222F8C2740054D9A0 /* MainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainView.swift; sourceTree = ""; }; + B9A0550422F8CB400054D9A0 /* GameSeriesLibraryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameSeriesLibraryView.swift; sourceTree = ""; }; B9BCF4C92168ACB600ECBAAC /* LaunchScreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; }; B9D2C6F622E98ED800797F67 /* AccessoryViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessoryViewModel.swift; sourceTree = ""; }; - B9F002E42187AA3200E12B0A /* FlockeConnector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlockeConnector.swift; sourceTree = ""; }; + B9E2A078233B69D400EAEB14 /* GameSeriesViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameSeriesViewModel.swift; sourceTree = ""; }; + B9E2A07A233B6A8F00EAEB14 /* GameSeriesEditView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameSeriesEditView.swift; sourceTree = ""; }; + B9E2A07C233B6E4F00EAEB14 /* ConsoleAllView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConsoleAllView.swift; sourceTree = ""; }; + B9E2A080233BA62100EAEB14 /* FeaturedStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeaturedStore.swift; sourceTree = ""; }; + B9EC09812383F94B004BC9AB /* LibraryExport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryExport.swift; sourceTree = ""; }; + B9EC0986238555BF004BC9AB /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; B9F44AB922F312E600FC6B29 /* ConsoleLibraryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConsoleLibraryView.swift; sourceTree = ""; }; B9F44ABD22F31DEF00FC6B29 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; - B9F44AE222F3216F00FC6B29 /* ConsolesListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConsolesListView.swift; sourceTree = ""; }; B9F44AE422F418F600FC6B29 /* ConsoleStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConsoleStore.swift; sourceTree = ""; }; B9F44AE622F429D300FC6B29 /* GameStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameStore.swift; sourceTree = ""; }; B9F44AE822F4655600FC6B29 /* AccessoryStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessoryStore.swift; sourceTree = ""; }; @@ -90,6 +131,9 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + B98CBBD2264E933A00B1B7AC /* CloudKit.framework in Frameworks */, + B9EC098523854C24004BC9AB /* QGrid in Frameworks */, + B97AD2E0244CE4B4004AF00D /* Disk in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -99,36 +143,26 @@ B90B64A12235909900E54BA3 /* Frameworks */ = { isa = PBXGroup; children = ( + B98CBBD1264E933A00B1B7AC /* CloudKit.framework */, ); name = Frameworks; sourceTree = ""; }; - B926F134214AE2C0004D36B7 /* Model */ = { - isa = PBXGroup; - children = ( - B926F130214AD9E4004D36B7 /* GameCollection.swift */, - B926F13B214C44FE004D36B7 /* Attachment.swift */, - B926F12C2149B264004D36B7 /* FlockeEntry.swift */, - B926F14921502DE1004D36B7 /* ConsoleEntry.swift */, - ); - path = Model; - sourceTree = ""; - }; - B926F135214AE2D4004D36B7 /* ViewController */ = { - isa = PBXGroup; - children = ( - B926F12E2149B6F5004D36B7 /* ConsoleLibraryViewController.swift */, - ); - path = ViewController; - sourceTree = ""; - }; B926F136214AE2E3004D36B7 /* Views */ = { isa = PBXGroup; children = ( - B94CB53622D3B3CC0029BFAD /* GameDetailView.swift */, + B9E2A07A233B6A8F00EAEB14 /* GameSeriesEditView.swift */, + B9A0550422F8CB400054D9A0 /* GameSeriesLibraryView.swift */, + B9A0550022F8C22D0054D9A0 /* GameSeriesAllView.swift */, B93D60CD22D88F5700DD390F /* AccessoryDetailView.swift */, + B94CB53622D3B3CC0029BFAD /* GameDetailView.swift */, + B94112E3233B597D00159AE4 /* ConsoleEditView.swift */, B9F44AB922F312E600FC6B29 /* ConsoleLibraryView.swift */, - B9F44AE222F3216F00FC6B29 /* ConsolesListView.swift */, + B9E2A07C233B6E4F00EAEB14 /* ConsoleAllView.swift */, + B94112DF233A4EF800159AE4 /* GamePickupsView.swift */, + B9A0550222F8C2740054D9A0 /* MainView.swift */, + B9EC0986238555BF004BC9AB /* SettingsView.swift */, + B9839982233A086A002F9946 /* Overview.swift */, ); path = Views; sourceTree = ""; @@ -136,20 +170,15 @@ B926F13A214AF21B004D36B7 /* Utils */ = { isa = PBXGroup; children = ( - B926F138214AE884004D36B7 /* FlockeWS.swift */, - B9F002E42187AA3200E12B0A /* FlockeConnector.swift */, + B926F14621502D53004D36B7 /* CodableExtensionAny.swift */, + B94112DD233A37DD00159AE4 /* DateConversion.swift */, + B98B2FAB232C0F8C00606DC4 /* ImagePicker.swift */, + B9EC09812383F94B004BC9AB /* LibraryExport.swift */, + B90E03EA238557D900E79643 /* LibraryImport.swift */, ); path = Utils; sourceTree = ""; }; - B926F14821502D7F004D36B7 /* Lib */ = { - isa = PBXGroup; - children = ( - B926F14621502D53004D36B7 /* CodableExtensionAny.swift */, - ); - path = Lib; - sourceTree = ""; - }; B93C1B9021496BFD0014FD6E = { isa = PBXGroup; children = ( @@ -171,13 +200,11 @@ B93C1B9B21496BFD0014FD6E /* Zockerhoehle */ = { isa = PBXGroup; children = ( + B98CBBD0264E933400B1B7AC /* Zockerhoehle.entitlements */, B93D60CF22E5006F00DD390F /* ViewModel */, B98A734622BACA9C00FB3410 /* CDModel */, - B926F14821502D7F004D36B7 /* Lib */, B926F13A214AF21B004D36B7 /* Utils */, B926F136214AE2E3004D36B7 /* Views */, - B926F135214AE2D4004D36B7 /* ViewController */, - B926F134214AE2C0004D36B7 /* Model */, B93C1B9C21496BFD0014FD6E /* AppDelegate.swift */, B9F44ABD22F31DEF00FC6B29 /* SceneDelegate.swift */, B9BCF4C92168ACB600ECBAAC /* LaunchScreen.storyboard */, @@ -192,11 +219,15 @@ B93D60CF22E5006F00DD390F /* ViewModel */ = { isa = PBXGroup; children = ( + B94112E1233B55B100159AE4 /* ConsoleViewModel.swift */, B93D60D022E5009700DD390F /* GameViewModel.swift */, + B9E2A078233B69D400EAEB14 /* GameSeriesViewModel.swift */, B9D2C6F622E98ED800797F67 /* AccessoryViewModel.swift */, B9F44AE422F418F600FC6B29 /* ConsoleStore.swift */, B9F44AE622F429D300FC6B29 /* GameStore.swift */, B9F44AE822F4655600FC6B29 /* AccessoryStore.swift */, + B98B2FA92328DF3400606DC4 /* GameSeriesStore.swift */, + B9E2A080233BA62100EAEB14 /* FeaturedStore.swift */, ); path = ViewModel; sourceTree = ""; @@ -204,6 +235,11 @@ B98A734622BACA9C00FB3410 /* CDModel */ = { isa = PBXGroup; children = ( + B98CBBD8264E98DD00B1B7AC /* Console+CoreDataProperties.swift */, + B98CBBD9264E98DD00B1B7AC /* GameSeries+CoreDataClass.swift */, + B98CBBD7264E98DD00B1B7AC /* GameSeries+CoreDataProperties.swift */, + B9839984233A0ADB002F9946 /* GameSeriesCover+CoreDataClass.swift */, + B9839985233A0ADB002F9946 /* GameSeriesCover+CoreDataProperties.swift */, B94CB4F322D1352F0029BFAD /* Accessory+CoreDataClass.swift */, B94CB4F422D1352F0029BFAD /* Accessory+CoreDataProperties.swift */, B94CB4F522D1352F0029BFAD /* Cover+CoreDataClass.swift */, @@ -212,8 +248,6 @@ B94CB4F822D1352F0029BFAD /* Game+CoreDataProperties.swift */, B94CB4F922D1352F0029BFAD /* Console+CoreDataClass.swift */, B94CB4FA22D1352F0029BFAD /* Console+CoreDataProperties.swift */, - B94CB4FB22D1352F0029BFAD /* GameSeries+CoreDataClass.swift */, - B94CB4FC22D1352F0029BFAD /* GameSeries+CoreDataProperties.swift */, B94CB4FD22D1352F0029BFAD /* Logo+CoreDataClass.swift */, B94CB4FE22D1352F0029BFAD /* Logo+CoreDataProperties.swift */, ); @@ -231,12 +265,17 @@ B93C1B9621496BFD0014FD6E /* Frameworks */, B93C1B9721496BFD0014FD6E /* Resources */, B90B64A4223590DA00E54BA3 /* ShellScript */, + B983997A233A0295002F9946 /* Embed Watch Content */, ); buildRules = ( ); dependencies = ( ); name = Zockerhoehle; + packageProductDependencies = ( + B9EC098423854C24004BC9AB /* QGrid */, + B97AD2DF244CE4B4004AF00D /* Disk */, + ); productName = Zockerhoehle; productReference = B93C1B9921496BFD0014FD6E /* Zockerhoehle.app */; productType = "com.apple.product-type.application"; @@ -247,8 +286,8 @@ B93C1B9121496BFD0014FD6E /* Project object */ = { isa = PBXProject; attributes = { - LastSwiftUpdateCheck = 0940; - LastUpgradeCheck = 0940; + LastSwiftUpdateCheck = 1100; + LastUpgradeCheck = 1250; ORGANIZATIONNAME = "Julian-Steffen Müller"; TargetAttributes = { B93C1B9821496BFD0014FD6E = { @@ -266,6 +305,10 @@ Base, ); mainGroup = B93C1B9021496BFD0014FD6E; + packageReferences = ( + B9EC098323854C24004BC9AB /* XCRemoteSwiftPackageReference "QGrid" */, + B97AD2DE244CE4B4004AF00D /* XCRemoteSwiftPackageReference "Disk" */, + ); productRefGroup = B93C1B9A21496BFD0014FD6E /* Products */; projectDirPath = ""; projectRoot = ""; @@ -296,7 +339,6 @@ inputFileListPaths = ( ); inputPaths = ( - "$(SRCROOT)/Carthage/Build/iOS/Disk.framework", ); outputFileListPaths = ( ); @@ -304,7 +346,8 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "/usr/local/bin/carthage copy-frameworks\n"; + shellScript = " +"; }; /* End PBXShellScriptBuildPhase section */ @@ -313,39 +356,50 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - B926F139214AE884004D36B7 /* FlockeWS.swift in Sources */, - B926F13C214C44FE004D36B7 /* Attachment.swift in Sources */, + B98CBBDD264E98F300B1B7AC /* Console+CoreDataClass.swift in Sources */, + B98CBBDB264E98DE00B1B7AC /* Console+CoreDataProperties.swift in Sources */, + B98B2FAA2328DF3400606DC4 /* GameSeriesStore.swift in Sources */, + B9839991233A0E16002F9946 /* GameSeriesCover+CoreDataProperties.swift in Sources */, + B983998C233A0BC9002F9946 /* Cover+CoreDataClass.swift in Sources */, + B9839992233A0E19002F9946 /* GameSeriesCover+CoreDataClass.swift in Sources */, + B94112E0233A4EF800159AE4 /* GamePickupsView.swift in Sources */, + B94112E2233B55B100159AE4 /* ConsoleViewModel.swift in Sources */, + B9A0550522F8CB400054D9A0 /* GameSeriesLibraryView.swift in Sources */, + B9839983233A086A002F9946 /* Overview.swift in Sources */, B93C1B9D21496BFD0014FD6E /* AppDelegate.swift in Sources */, B94CB53722D3B3CC0029BFAD /* GameDetailView.swift in Sources */, B94CB50322D1352F0029BFAD /* Game+CoreDataClass.swift in Sources */, - B94CB50822D1352F0029BFAD /* GameSeries+CoreDataProperties.swift in Sources */, + B98B2FAC232C0F8C00606DC4 /* ImagePicker.swift in Sources */, B9F44ABA22F312E600FC6B29 /* ConsoleLibraryView.swift in Sources */, B9F44ABE22F31DEF00FC6B29 /* SceneDelegate.swift in Sources */, + B9A0550322F8C2740054D9A0 /* MainView.swift in Sources */, B9D2C6F722E98ED800797F67 /* AccessoryViewModel.swift in Sources */, - B9F002E52187AA3200E12B0A /* FlockeConnector.swift in Sources */, + B98CBBDA264E98DD00B1B7AC /* GameSeries+CoreDataProperties.swift in Sources */, B94CB50A22D1352F0029BFAD /* Logo+CoreDataProperties.swift in Sources */, B94CB4FF22D1352F0029BFAD /* Accessory+CoreDataClass.swift in Sources */, B98A734D22BAD27D00FB3410 /* Zockerhoehle.xcdatamodeld in Sources */, B93D60D122E5009700DD390F /* GameViewModel.swift in Sources */, B9F44AE722F429D300FC6B29 /* GameStore.swift in Sources */, - B926F12D2149B264004D36B7 /* FlockeEntry.swift in Sources */, - B98A735E22BFAA4B00FB3410 /* ConsoleLibraryViewController.swift in Sources */, - B94CB50522D1352F0029BFAD /* Console+CoreDataClass.swift in Sources */, + B90E03EB238557D900E79643 /* LibraryImport.swift in Sources */, B94CB50922D1352F0029BFAD /* Logo+CoreDataClass.swift in Sources */, - B94CB50622D1352F0029BFAD /* Console+CoreDataProperties.swift in Sources */, - B926F131214AD9E4004D36B7 /* GameCollection.swift in Sources */, + B98CBBDC264E98DE00B1B7AC /* GameSeries+CoreDataClass.swift in Sources */, + B9A0550122F8C22D0054D9A0 /* GameSeriesAllView.swift in Sources */, + B94112E4233B597D00159AE4 /* ConsoleEditView.swift in Sources */, + B94112DE233A37DD00159AE4 /* DateConversion.swift in Sources */, + B9E2A081233BA62100EAEB14 /* FeaturedStore.swift in Sources */, B93D60CE22D88F5700DD390F /* AccessoryDetailView.swift in Sources */, - B926F14A21502DE1004D36B7 /* ConsoleEntry.swift in Sources */, - B94CB50722D1352F0029BFAD /* GameSeries+CoreDataClass.swift in Sources */, + B9EC09822383F94B004BC9AB /* LibraryExport.swift in Sources */, B94CB50022D1352F0029BFAD /* Accessory+CoreDataProperties.swift in Sources */, + B9E2A07B233B6A8F00EAEB14 /* GameSeriesEditView.swift in Sources */, B926F14721502D53004D36B7 /* CodableExtensionAny.swift in Sources */, B94CB50422D1352F0029BFAD /* Game+CoreDataProperties.swift in Sources */, B9F44AE922F4655600FC6B29 /* AccessoryStore.swift in Sources */, B98A736022C1738800FB3410 /* CDManager.swift in Sources */, B94CB50222D1352F0029BFAD /* Cover+CoreDataProperties.swift in Sources */, - B9F44AE322F3216F00FC6B29 /* ConsolesListView.swift in Sources */, - B94CB50122D1352F0029BFAD /* Cover+CoreDataClass.swift in Sources */, + B9E2A07E233B6E4F00EAEB14 /* ConsoleAllView.swift in Sources */, + B9EC0987238555BF004BC9AB /* SettingsView.swift in Sources */, B9F44AE522F418F600FC6B29 /* ConsoleStore.swift in Sources */, + B9E2A079233B69D400EAEB14 /* GameSeriesViewModel.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -379,6 +433,7 @@ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; @@ -440,6 +495,7 @@ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; @@ -472,10 +528,10 @@ isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CODE_SIGN_ENTITLEMENTS = ""; + CODE_SIGN_ENTITLEMENTS = Zockerhoehle/Zockerhoehle.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = M9N7K3KZX9; + DEVELOPMENT_TEAM = 85J8CBD673; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Carthage/Build/iOS", @@ -485,7 +541,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = "de.mm-neuemedien.Zockerhoehle"; + PRODUCT_BUNDLE_IDENTIFIER = haus.mueller.zockerhoehle; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_VERSION = 5.0; @@ -497,7 +553,7 @@ isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CODE_SIGN_ENTITLEMENTS = ""; + CODE_SIGN_ENTITLEMENTS = Zockerhoehle/Zockerhoehle.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = M9N7K3KZX9; @@ -510,7 +566,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = "de.mm-neuemedien.Zockerhoehle"; + PRODUCT_BUNDLE_IDENTIFIER = haus.mueller.zockerhoehle; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_VERSION = 5.0; @@ -541,6 +597,38 @@ }; /* End XCConfigurationList section */ +/* Begin XCRemoteSwiftPackageReference section */ + B97AD2DE244CE4B4004AF00D /* XCRemoteSwiftPackageReference "Disk" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/saoudrizwan/Disk"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 0.6.4; + }; + }; + B9EC098323854C24004BC9AB /* XCRemoteSwiftPackageReference "QGrid" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/Q-Mobile/QGrid"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 0.1.3; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + B97AD2DF244CE4B4004AF00D /* Disk */ = { + isa = XCSwiftPackageProductDependency; + package = B97AD2DE244CE4B4004AF00D /* XCRemoteSwiftPackageReference "Disk" */; + productName = Disk; + }; + B9EC098423854C24004BC9AB /* QGrid */ = { + isa = XCSwiftPackageProductDependency; + package = B9EC098323854C24004BC9AB /* XCRemoteSwiftPackageReference "QGrid" */; + productName = QGrid; + }; +/* End XCSwiftPackageProductDependency section */ + /* Begin XCVersionGroup section */ B98A731722BA9E4600FB3410 /* Zockerhoehle.xcdatamodeld */ = { isa = XCVersionGroup; diff --git a/Zockerhoehle.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Zockerhoehle.xcodeproj/project.xcworkspace/contents.xcworkspacedata index 8709c34..919434a 100644 --- a/Zockerhoehle.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ b/Zockerhoehle.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -2,6 +2,6 @@ + location = "self:"> diff --git a/Zockerhoehle.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Zockerhoehle.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 0000000..ca77360 --- /dev/null +++ b/Zockerhoehle.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,25 @@ +{ + "object": { + "pins": [ + { + "package": "Disk", + "repositoryURL": "https://github.com/saoudrizwan/Disk", + "state": { + "branch": null, + "revision": "b0cb4fdf23e51849cc2460bdc6de795c3bcca99d", + "version": "0.6.4" + } + }, + { + "package": "QGrid", + "repositoryURL": "https://github.com/Q-Mobile/QGrid", + "state": { + "branch": null, + "revision": "40607aec336a8097c94bcadb81762592d89073ac", + "version": "0.1.3" + } + } + ] + }, + "version": 1 +} diff --git a/Zockerhoehle.xcodeproj/xcuserdata/julian.xcuserdatad/xcschemes/xcschememanagement.plist b/Zockerhoehle.xcodeproj/xcuserdata/julian.xcuserdatad/xcschemes/xcschememanagement.plist index 381e402..47eae8c 100644 --- a/Zockerhoehle.xcodeproj/xcuserdata/julian.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/Zockerhoehle.xcodeproj/xcuserdata/julian.xcuserdatad/xcschemes/xcschememanagement.plist @@ -14,6 +14,16 @@ orderHint 0 + ZockerhoehleWatch (Notification).xcscheme_^#shared#^_ + + orderHint + 2 + + ZockerhoehleWatch.xcscheme_^#shared#^_ + + orderHint + 1 + SuppressBuildableAutocreation diff --git a/Zockerhoehle/AppDelegate.swift b/Zockerhoehle/AppDelegate.swift index 390dfb5..f429f64 100644 --- a/Zockerhoehle/AppDelegate.swift +++ b/Zockerhoehle/AppDelegate.swift @@ -23,8 +23,17 @@ class AppDelegate: UIResponder, UIApplicationDelegate { #endif print("disFinishLaunchung") - FlockeWS.fetchEntries(for: GameCollection.consoleID) + + //FlockeWS.fetchEntries(for: GameCollection.consoleID) + //TODO Game Serien entfernen + /*let gameSeriesEntities = ["Assasins Creed", "Mario", "Zelda", "Pikmin", "Heroes of Might and Magic", "Gears of War"] + + for series in gameSeriesEntities { + let cdSeries = GameSeries(context: CDManager.shared.viewContext)@ + cdSeries.uuid = UUID() + cdSeries.name = series + }*/ return true } } diff --git a/Zockerhoehle/Assets.xcassets/basket.imageset/basket-supermarket.png b/Zockerhoehle/Assets.xcassets/basket.imageset/basket-supermarket.png deleted file mode 100644 index 13f478df069b9ac182dadfea9652956042729488..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1924 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 literal 0 HcmV?d00001 diff --git a/Zockerhoehle/Assets.xcassets/star.imageset/star.png b/Zockerhoehle/Assets.xcassets/star.imageset/star.png deleted file mode 100644 index 3f1caa54bfc47c618722506a25d097725b6444f6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6884 zcmbVQc{r49+rMXwO!i4q(B(j7oPu7_p zm9j*#M?96CNm<8i-*rz<@Atmn`@G+AeBU4UG3Rw|zw>wA*L7a^F>%(1Ohg2w1tAC$ zF*79|h9D#mkq{aMeop#t_6I+xv-+0$5LA{Z#B${aYk4=*!RFnw_ zx}XL@Q!WsseGh`9{L*V|4gdr4l!XZqf(qgl)WK@4pQ%G2KHBcV)-qk|%aYfnahr5#I9s!?wgw*S zpP_%>x?xw6v~AMn^{V#K&>DXK+k`tx5_g_aY&WVs`qQ_Lq9VQiiC}zd+qysaZyE6& z6ZAiKaM$s{Yr1@TP8*{{0z{CMlC1j1ux)uT62sSgHt82RbKYsrIGRvV|TzJZm( zi!t4IQBp}t`}P62FK1YBd!D+8cF7&X9}HqYzZ?b197vi&2Jnv?vt1WYcM83cOcw$% z7ICWbc7DOY@w_6Ha^gk5HQ9c)#Sc+^c~1_fG)$p||Eu7;XaK`F2q)JC$wLS3&$>B1 zVpd<$Wxry~Tk`mPgJxoIQJ7-+`ESyOtw%guI!4JuC&S*6a)j znS>me=ih=%NTza#kZ7Rml8`BC^E(@0$=djKF4z|5awZc{U3qEV2;B5WpWrbRt8-hC zMF$qQA?M1DEQ(@Wz7_ywVn)`C-4P5T%C9>MJyB_eXK-^87n~X;E;((R9+n)uQaazw zU9YRp#>P(|T7*vt?R#TKu7XT&886C%6oq&UWJaWX%>NCC93i4YFbFjTWZfmq9C|V4 zYu#lcg@~dhpyzN%7vo>XSm+)GagqqNt~^eb!a#d)wCiZfCd`Ntigx2ac*a7iIOq(K z(wqO=)_;ZBz8FJ9J^zXNkHo*+|Bu;!x_jFuiX;4R$hx2D8qj^FY%B)h3kv?{yu(_# zvCvx?gd4pv2Sud(E!1o0^6!7k{8u8tNAX|zY{WouXg`I&YuB%!zlHku?*4M`&ELE3 zpZNTw;fy+mL(2bRRk9lgo%}Zrt;@aVVnH<&*` zK1KJHtX+O@v&I>9z#R2P3h_~suBY8hK!sG}Mzoh-*a+fiEfR=}m2blRk}sp@q(8V_ z9T@(sx3^gz6|x`a^7~kIVtfEdlz<+&h}b8L8M%W;_S7Wacm{OU3ND3A+IA73)IvU9 z*$L@gN6$6OAS?!(j6!3fl5|YzAK~`Nu|O$>P#Q8Zx(}2mFa*eD4UuA(i5{VX%?5%M zkSjR(GXO9*0csG5-49ZGDp0hLu7`kIGkT<$wh;nuD##UAdEscd$j7M4(Wc1VYB-vb z5vnLqPEZqztUF~w`%QLQZ{3suD&$o>#e1+xKO6vtVCHHz)QCk%gMICfbICdsD2J*9 zJLnXVrbsu;1{>F|7yW!F9O?o`LHdKN>_8S?Ea6=&^n3Tf@bh4%JqII%8QJ?_HhyYJ z+j-USU`|*vx>UON!hnwTtECC)hVLr7bmi56nguM1C}1eRbErE zf|j0zJzkgP7;k-EvLT1~DXfzRh}4d^n~8Ew}L(LK6N6N`5uV%At@*4 zjcCR=Xifo$|JxI23{mHhrT?4D|HmfN4ZtVBnV!)6pAXnY#waL)2z7Y>dfbT6l%{$U zRqk)^oXLoVQ#U5DY}${TT9df&2|c~5A89MwLgn(gZg^-H3D z?3115%Q+h^qiG%XxfvoVBWE(EB1UlHh>b|M)zB|OD+%vN_+U&dWb0d&>2(1iHeuZG zn>-FWdc3Gt`@{0$9Dk#lr;`4+hxocyY+wb}ATwL{zpxAy@E&-KU*z<(O~xNQ601x&C1g6fnQeaFDsalI zzNjTAnfcv(=~9)FDaw>cI5)qjP_N$fcG)B2kRRUT$#3l;{Bm0X(U*H3W_k5e&SrJ7 z9cLm&)(xdD0y4KW9yi5O-X-)%h*)t=!W-Egqx*H{CAT2A;IeDo!>V6R4u9aM3#hh9 zEvjH#2&f?ctDMds3j#qB)EBF!iD#+2E>$f1SO#lzedv72qXvyXqcd_Ui@!{YO&nat znycUXbCyP=EyPmB=-2#KwH6eYcbh-!gnRgQ6C$nP2IZTHHd}Q_U`NXt9PKK4<6ugq z!oVaWg64XtTZc#y!KmfyC}f7mY^X^*a%=uJ+e;1S;!C8--}NGQl{-)BSv&Y@{CtaR z%JhD`B|YQY>`N3AM;H@nGpE|Q^1+n$jond13I?N=);Eujl^qE*UKGc;5K*mLUa`bZ z39M{}ueEz8uQB9aimHi#U2TAhMc+GA@N*3tSBh-mNEb(V^7=hb{_n-ylb;JP!+^zl ziWkEN$0T*~z&F7cC?SETSHRH(Gk8jo^$b+M5~X*)+`gxNsqt`Qd~*7|xOirlzY-;5%MA0iNQcrtkjq^nX(HIvkSu z(o5C~2OzGtViE}BOmttNQNLB#LsyOff3ZLLnuQ4}BosGtcV;}O748sX1vg@YBLKhR zP>1#jUQK+Urp=h7<%@`?yyW6wypmBxZ6Vs@aMw1X!4TF#R-HMJ76|hZh6lS0;z)mk z;7BISi4RQG*A+Y5+EPwy-?_+h0pASGn-EIR((|KvDdC9?XtoAqu7hb4_`vjiihhHE za(Pv)0rUC*Cg}?mUT0@(^&{4Rx<_Lv3(lxro^VDDFx+z!D4U1Z{Iv$3HM#MWh4ZML zK0G_Tz5&+!DnHkI3ru36oFGinYD}bgI$UNvct4&vd3GXS%06yXAHs1C9=8NfoQu=v zvnm(hEEV8_e3d}a)!zGEV%X>W{C(#cNEuv9{t6?hFRhOMV2KIVG_xm*!0jceiacqh zhp^yvKzY9cjjc6nF1`)9G+FVo-C-4OoJSmb?ivP>vW48-IS#Y-{(0xW5ddFpUbE*<)P`@m%MJU7D!NLm;N$dL>H^7H8QziE5;&UAiC)id z1j&y%-s(<^sXW(hM9MI3=;2U#kJs-#isatmJTKl0mc<|x@8zv6?mlKIYx7`-Pq(Ax zX|@ohWoH`P7hoN|m#66X;Ev-HNyl3ZEAnr)9|6;_BVXuNCx(m{w_~90{cye73P|~% z5)SmvjYz{&I~I)Jia-TZw#QN; z%yqfF7TQ|hMQR3|Uj9iyExgw?!h` zdW`x~)Ea}!R{haNj+4#C5eaf%)4A((2uEWnf2NQ)7EeeNrx|EB2GOXz%u4A)>@-9P zmsiY_15z2PX6;+q`7_YXcrOKvTJqs`Zta{w*s{a+`xidt{NxvV_r zL1B5{Vz=gaXa+=@n*Tc(*W8Ur>zMl~5@~*Z?XdDNFwgxZ5oYg7sc{v9DP9=F1tm9* z#SGMqLwZ%7Se|Hnl=VP=oSGaV(DyKKh+pnl1NwPm_=1=62i>>a6_eX=om;4??HHuz z3eKgHIU(8no~5r((z^7tynJw@f&5xK)J$(QbK>aRZdMyeeng-nx4d4shlQ5BZZb!VT83Eqvt!g`5{O?n zN2Dp!-K1LHvt~ici$L`-kVx}w3zVxRWR8h>;kk-UqJDFWyRhQd!S~up+D(9@@x(nN zbEu3@9BDI}NU5YNNS(eFlVDC&OoOhi?kHjA@yHfmj>f$6PPl`PW>SboRwE%-i);<{ z+MRmP73CYAM=o3aF~`~GC(@SX%dyD)2FSbZz`0~Ft}+p|-Y=)?&*Bw;z78GYBs3HgmtQp| zw)#z&oIQJ(xnphQ#AhN+FJny4RAW(-+z}DJ$HIrx7;)G@zGfrFFODgGb60UGBkJSk5V^1Nf%tVCWvJsrNyjI593Lp&(P|*h=jo%fks4(4Xh?9$eaLKno83k9xt>Y-I^30Sc9)dB zJvoL`qLE^iJ*;|3VsHihnRDS%8<}*pjr<1bbkZMPmw#l4pSWxI>C(mS%Dr5yW(-Zh zF$Y6JydNU%3@4Lk+sLME*ttEV?;YLh>{k)-1Df18dZ$qZA!WLKsh}#fI3PvTX>)PP ztX=*$;G2OhJ)cj0%TAJcFR>&ljp8@;01r&%GwwK zWzN#6WVH*?v@p{Bj@FVHyQ^bzgRIYnBkX1}!}4354^@3n{gSutuzl+5OHMs=zuSz3 zhxa5#t^N|B zw&A0m*tv@&eYNMzncmTkX`@tX6r-k_v+2M>7?m@8^`l(vkl0+Dm!4|EC0!G=M#TvkL8g%kcT#2T8q#BSjW}}lPA@#|YFaM8P%d|tGRV$Q zPs4AY@FIDtJ%4YYiovR-1;C0+q|gpxKUHwXA>)gRwQI94I3BkL6%8a(`c;LoYWY9X z@Do~e+=Kf$6-^PI=*a4BFl_Y4tA(yS?&((*!K&3drs5~WSjT9q>DF&Yr_Q3gJ^%-N zaEg%6;Cbr4Xj;$|IC8q$%}(kSW%lF>=gc!DXM?J0J>t51So(6`x!kaD!*Bhn;#fb^ z@HV;iLB+M_CO#u&OP#R>**#-oVI5BUf-c-S7UR6L% z0oB=xmri;w{U3iSb2Va#b7&j?5$9Q8K5|>%r5mLrh7TG4cF*hL%TlZM5lianmbfCW`3DK5%q)q*BS~ zNPCY6+kz$W6;*b8^<{GIN6(0^BD(m!F1%+$aq#%0)skQbYXWFOjGVQ)X0{J`<0TwPZm~|GmS^1>GugU$g?2wqN&RH^H-|@z_YO|j zxzr6?i(mUJN$mCHl+>t-T{X1CCjBVsQz`lCMApDR1WCg_L{yGP%yx!6lRj6Pl`s}# zBa2P)64*}Wd|;VAWr>?s@+IqXJtIyZ2}3{XLzriBHSo`r_M<)>Eagkq=H>;-wTUHO zsVV3Xf4=jFSxY)F&om21_URBp=d;F4cAE=F-qRs?!m_`)P^6YZ!?w~#46FGZd-p4Z zBH8S8SPmJDhUFWuEdAw5G%Xc^XVL>-)O z@%4qu-KgN;LByqbvN<)Iv3qkmw!>Y(uMITJut^f>6bv0jnvHfG=zJQ)RO>Uy~?V`B_$0r`FJ`HO+y(SRKL?gDXUO5UH+f z_fj+|j8o3y`>@khz=gxNmm^DIi1j`Cgiy))@?{Sfq4Z6Z#u6w>*Ma8cN-b*nSfO`$ z9^g}B3~!BUhppF=!UR^{YRKJYh^<%P@-#Ou!r6?>>iJsnpL2F|BOiVpMBL*N{4?9C z_|&+OpJ4>%$`>Hwa)%IAkR`@hzVa9L4V}YP%C5+t-sssVI4n z%AFq+qnxbiXG5|FvVG_#keA)GsG9A3mI$PxRV$aRIEEKDir1kfwUPHiD}yr;lno%I zM&&GYWVN97G;Pk59YN_+ZQzO#kA8q_#T9zOwZ89u2A6>H>W52Mr#S;kkiRJn(@Z)z z3DY;tUx2A^S-esI_CA+l`Ie`A+6605`&HF<=aGk|RfIdl6zOp6?R6wL*3uJpdy`+m zb357zL#iwBAp7sYkcg%$$HT zZl`$jS{#zf5L@VV0R3tY;I&P@nnV$y^YQIF@eYn&zm*qs?KaPyzq#_C8vP&uW|?jO z1g_M&w2PP5v6&a9G74i|OYwqHB*9q&&DjZ#+1C49g6v^loOBs4?q(kxm*0G54Bz3L zH{Ixg@GE4hL8*>1PuS!xsM zAQJJ@9h`RWI3@(uIOK*G4{-GxRs@8j*3NqMvE(Klmks%1-JhKF=-^xo%49|&UTQoH zTzn+j+Rq|)Bwk2nx*{IUX%_b~_w41K56UziOtjxA81<)LQA6$#@E6Pb9gdHO!j6_q z9<47J_D*BIH(%m*3fjs27(J5$L6BmQk$uo<*C2Oow*YtW1F5U+-lwd#Us+wvR$X0N zO-ox{LrDz?YE^F*Oa3Lm=lp5UGok-Epy`gK1PIs!BOEz@CWz!3;0}>UBo$9z??5+K aKX;Y$0cX?b2c&@$WM*`T_|(AV@_zvev|yG1 diff --git a/Zockerhoehle/Assets.xcassets/basket.imageset/Contents.json b/Zockerhoehle/Assets.xcassets/wishlist.imageset/Contents.json similarity index 80% rename from Zockerhoehle/Assets.xcassets/basket.imageset/Contents.json rename to Zockerhoehle/Assets.xcassets/wishlist.imageset/Contents.json index 63f8852..724a224 100644 --- a/Zockerhoehle/Assets.xcassets/basket.imageset/Contents.json +++ b/Zockerhoehle/Assets.xcassets/wishlist.imageset/Contents.json @@ -2,15 +2,16 @@ "images" : [ { "idiom" : "universal", - "filename" : "basket-supermarket.png", "scale" : "1x" }, { "idiom" : "universal", + "filename" : "Image.png", "scale" : "2x" }, { "idiom" : "universal", + "filename" : "Image-1.png", "scale" : "3x" } ], diff --git a/Zockerhoehle/Assets.xcassets/wishlist.imageset/Image-1.png b/Zockerhoehle/Assets.xcassets/wishlist.imageset/Image-1.png new file mode 100644 index 0000000000000000000000000000000000000000..cad3646c74a563dcba1065789646c16443cfe6f8 GIT binary patch literal 3803 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 literal 0 HcmV?d00001 diff --git a/Zockerhoehle/Assets.xcassets/wishlist.imageset/Image.png b/Zockerhoehle/Assets.xcassets/wishlist.imageset/Image.png new file mode 100644 index 0000000000000000000000000000000000000000..f9dc14d9e6f55117bc8fe95389d4dc343677f40a GIT binary patch literal 2463 zcmY*bc|6oxAO3MIV{C(Bt~)VCvd>~Sh8m3e7(*kHqv6}SLBZrxG+P8>+vuP6*Ewm#@c%kRQ~xeYR3K`52ZceR zQU7R*QgPcT_7H^_CUV}^x5MDRGyf0!+Xsi*HvgZ){8j09R8*=R7>D}j+U&qCUAn#i zAl=|ZzEm4BGYlTsV+S`1zTXdf_MTS7CVai6`+Nxb@+xTZ8_IpKgu$ zw{{K|kWZhUn9UzrR1r2b1gtx(pMWi2r)fegz-qFUA&;LD%a$0bgtNk5gofRlJu3zuwL#{6sbCSgqVTH{7L`($dq}> zDrh>tQDtf96Mf{FieIP(_M)Y2%i}nuYWL7mcNO)Ra27;*%j>QG8Ao|=fXpL7&6UY| zRrLMH2UnGQ8m>0Q-CerLOB=QVc`G;u{1kh`*j2mpq#B5zAYG?sA@g$RxN)H|q`W6p ztf)_)nu-E5lP7&63uH%Nt|?n^@Pu87Q%Brv`bnv%MJf} z$jY6EhFv##rGQ9(MV4@`#t7OP)3T79pc%K)BPDTnRiRTA`yp0}c{0;7{<}?HIVQwlk#zl6rw-s zH*)d%Lk63+XFTE@Pd2ixLMz*jn$&w7-u8~!r>FW7S-PJ%`sIRhoq9Ee2Ntl71{g){ zjA7XP+VrM9v2xu{+|t=oozF~6#Xdtj#}_%gFo19Zab|jNptK0o%TU_y)H6wJ|DrVhN);15QgqaM+rxwU_I4ZOogI|c{|<}Uy{jT)om+XKqyDV>LLx5mIt)ISyJwC%QSol|eM)o1gE6Nf#W6y% zoA7e&Dt$_2mw}X-=Ccvz{z)e_*@!QatVorkyOU5$YGWp1E18Qf%P*#DLtdqKcn*|0 zWa+)u*>_VtVW3R5$W)@*OeNMGX|X~OBS0$`haRI&X+y z0C`(OJkNQ^X=V=+8(Fb;m;$x4*`TFIQv`i)>o<~5Qjhg$2VABWtA4#&aSMMrN3rI$ zF7zR^C-H*$heg&J@%PS;jc1k`N=(mC=9C<|@mcZ(tJ7w~T`}J#u)7KNqv1X;@N;YQ@Lp?ua({%2j8Jb^UvH){p8pXt%Ag(03Myvn}Z0wQx( z40GWpLJ_MyK+Zn)n{|I-VX5rOFiaysIo(lH$y95~dT>P9soF9z>&5gj z58=C~w+Gc{1Reb;H_s*`)<`28HN{oDBKyqYTy#FjL)@)uIyWpUmJxN?aPFF%*66*9 zW==LCFSqIY{Pt3CQgg?Ut6358VPp#+s5ZTS7YWboZkcsh&LQ<-vv^5swcNM>3(j6N zgAyTbf!8RTJePevJLTHkpyZ|{c*y>(%^A$Kk9>iShHcId7yre-^mxl4{&rl%!h%K3 zN1GW-`SbVoL5dQ5lVOHgduuSBoaN6fz1T%#r54$8d7V2leNy_Z9pjk(`Qd-%Lz;Nz z?T+JnxQG&tb3?4e(2;59WsW3^4l~k0`PcheNT-y*JM~M(rn>~1 zqvJ5Ku9p0JQ4Q-X@gE8=^z`IG1zN`(5?&ZvvQ7IBKtsDTw?@xalWq1EI@T{zOetTJ03iw;;B^ OK~4^CggW~`=Dz`umPil) literal 0 HcmV?d00001 diff --git a/Zockerhoehle/CDManager.swift b/Zockerhoehle/CDManager.swift index d53a697..22862fd 100644 --- a/Zockerhoehle/CDManager.swift +++ b/Zockerhoehle/CDManager.swift @@ -26,7 +26,8 @@ class CDManager { application to it. This property is optional since there are legitimate error conditions that could cause the creation of the store to fail. */ - let container = NSPersistentContainer(name: "Zockerhoehle") + let container = NSPersistentCloudKitContainer(name: "Zockerhoehle") + container.loadPersistentStores(completionHandler: { (storeDescription, error) in if let error = error as NSError? { // Replace this implementation with code to handle the error appropriately. @@ -43,6 +44,7 @@ class CDManager { fatalError("Unresolved error \(error), \(error.userInfo)") } }) + container.viewContext.automaticallyMergesChangesFromParent = true return container }() @@ -53,10 +55,13 @@ class CDManager { // MARK: - Core Data Saving support func saveContext () { - persistentContainer.performBackgroundTask({(context) in + print("CHANGES: \(self.persistentContainer.viewContext.hasChanges)") + self.persistentContainer.performBackgroundTask({(context) in + print("Context \(context.hasChanges)") if context.hasChanges { do { try context.save() + print("--------------------saved-------") } catch { // Replace this implementation with code to handle the error appropriately. // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. diff --git a/Zockerhoehle/CDModel/Accessory+CoreDataClass.swift b/Zockerhoehle/CDModel/Accessory+CoreDataClass.swift index 57ede58..28ba832 100644 --- a/Zockerhoehle/CDModel/Accessory+CoreDataClass.swift +++ b/Zockerhoehle/CDModel/Accessory+CoreDataClass.swift @@ -18,3 +18,37 @@ public class Accessory: NSManagedObject, Identifiable { return fetchRequest } } + +/* + @NSManaged public var color: String? + @NSManaged public var manufacturer: String? + @NSManaged public var name: String + @NSManaged public var inWishlist: Bool + @NSManaged public var console: Console? + @NSManaged public var lentTo : String? + @NSManaged public var uuid : UUID + */ + +extension Accessory : Encodable { + + enum CodingKeys: String, CodingKey { + case uuid + case color + case manufacturer + case name + case inWishlist + case console + case lentTo + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(uuid, forKey: .uuid) + try container.encode(color, forKey: .color) + try container.encode(manufacturer, forKey: .manufacturer) + try container.encode(name, forKey: .name) + try container.encode(inWishlist, forKey: .inWishlist) + try container.encode(console?.uuid!.uuidString ?? "", forKey: .console) + try container.encode(lentTo, forKey: .lentTo) + } +} diff --git a/Zockerhoehle/CDModel/Accessory+CoreDataProperties.swift b/Zockerhoehle/CDModel/Accessory+CoreDataProperties.swift index 4c25759..48067d6 100644 --- a/Zockerhoehle/CDModel/Accessory+CoreDataProperties.swift +++ b/Zockerhoehle/CDModel/Accessory+CoreDataProperties.swift @@ -22,5 +22,7 @@ extension Accessory { @NSManaged public var name: String @NSManaged public var inWishlist: Bool @NSManaged public var console: Console? + @NSManaged public var lentTo : String? + @NSManaged public var uuid : UUID } diff --git a/Zockerhoehle/CDModel/Console+CoreDataClass.swift b/Zockerhoehle/CDModel/Console+CoreDataClass.swift index c9afa8f..c3f7422 100644 --- a/Zockerhoehle/CDModel/Console+CoreDataClass.swift +++ b/Zockerhoehle/CDModel/Console+CoreDataClass.swift @@ -9,14 +9,96 @@ import Foundation import CoreData +import SwiftUI +import UIKit @objc(Console) public class Console: NSManagedObject, Identifiable { + var logoAsUIImage : UIImage? { + get { + if let logoImage = self.logo?.image { + return logoImage + } + + return .none + } + + set { + if let logoImage = newValue { + let newLogo = Logo(context: CDManager.shared.viewContext) + newLogo.console = self + newLogo.image = logoImage + self.logo = newLogo + }/*else{ + //This deletes the cover? + if let logo = self.logo { + print("Console::logoImage::set DELETE Logo") + CDManager.shared.viewContext.delete(logo) + } + }*/ + } + } + + public static func sortConsoleByNewestGame(consoleA : Console, consoleB : Console) -> Bool { + guard let newestGameConsoleA = (consoleA.games!.allObjects as! [Game]).max(by:{ + Game.compareByCreationDate(gameA: $0, gameB: $1) + }) else { return false } + guard let newestGameConsoleB = (consoleB.games!.allObjects as! [Game]).max(by:{ + Game.compareByCreationDate(gameA: $0, gameB: $1) + }) else { return false } + + guard let gameACreated = newestGameConsoleA.createdAt else { return true } + guard let gameBCreated = newestGameConsoleB.createdAt else { return false } + + return gameACreated > gameBCreated + } + public var id : NSManagedObjectID { get { return self.objectID } } - +} + +extension Console : Encodable { + + enum CodingKeys: String, CodingKey { + case uuid + case name + case shortName + case generation + case manufacturer + case accessories + case games + case logo + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(uuid, forKey: .uuid) + try container.encode(name, forKey: .name) + try container.encode(shortName ?? "", forKey: .shortName) + try container.encode(generation, forKey: .generation) + try container.encode(manufacturer ?? "", forKey: .manufacturer) + + var accessoryList : [String] = [] + for accessory in accessories! { + if let accessory = accessory as? Accessory { + accessoryList.append(accessory.uuid.uuidString) + } + } + try container.encode(accessoryList, forKey: .accessories) + + var gamesList : [String] = [] + for game in games! { + if let game = game as? Game { + gamesList.append(game.uuid!.uuidString) + } + } + try container.encode(gamesList, forKey: .games) + + let logoBase64 = logo?.image?.pngData()?.base64EncodedString() ?? "" + try container.encode(logoBase64, forKey: .logo) + } } diff --git a/Zockerhoehle/CDModel/Console+CoreDataProperties.swift b/Zockerhoehle/CDModel/Console+CoreDataProperties.swift index 1fb12f8..30b9851 100644 --- a/Zockerhoehle/CDModel/Console+CoreDataProperties.swift +++ b/Zockerhoehle/CDModel/Console+CoreDataProperties.swift @@ -2,8 +2,8 @@ // Console+CoreDataProperties.swift // Zockerhoehle // -// Created by Julian-Steffen Müller on 06.07.19. -// Copyright © 2019 Julian-Steffen Müller. All rights reserved. +// Created by Julian-Steffen Müller on 14.05.21. +// Copyright © 2021 Julian-Steffen Müller. All rights reserved. // // @@ -17,11 +17,14 @@ extension Console { return NSFetchRequest(entityName: "Console") } + @NSManaged public var circumstances: String? @NSManaged public var generation: Int64 @NSManaged public var manufacturer: String? - @NSManaged public var name: String - @NSManaged public var accessories: NSSet - @NSManaged public var games: NSSet + @NSManaged public var name: String? + @NSManaged public var shortName: String? + @NSManaged public var uuid: UUID? + @NSManaged public var accessories: NSSet? + @NSManaged public var games: NSSet? @NSManaged public var logo: Logo? } diff --git a/Zockerhoehle/CDModel/Cover+CoreDataClass.swift b/Zockerhoehle/CDModel/Cover+CoreDataClass.swift index 3eeba26..b9e1480 100644 --- a/Zockerhoehle/CDModel/Cover+CoreDataClass.swift +++ b/Zockerhoehle/CDModel/Cover+CoreDataClass.swift @@ -12,5 +12,4 @@ import CoreData @objc(Cover) public class Cover: NSManagedObject { - } diff --git a/Zockerhoehle/CDModel/Cover+CoreDataProperties.swift b/Zockerhoehle/CDModel/Cover+CoreDataProperties.swift index 25e0dba..c8dff2e 100644 --- a/Zockerhoehle/CDModel/Cover+CoreDataProperties.swift +++ b/Zockerhoehle/CDModel/Cover+CoreDataProperties.swift @@ -18,6 +18,6 @@ extension Cover { } @NSManaged public var image: UIImage? - @NSManaged public var game: Game? + @NSManaged public var game: Game } diff --git a/Zockerhoehle/CDModel/Game+CoreDataClass.swift b/Zockerhoehle/CDModel/Game+CoreDataClass.swift index 31e50c5..4cf6bfc 100644 --- a/Zockerhoehle/CDModel/Game+CoreDataClass.swift +++ b/Zockerhoehle/CDModel/Game+CoreDataClass.swift @@ -19,4 +19,78 @@ public class Game: NSManagedObject, Identifiable { return fetchRequest } + @nonobjc public class func fetchRequest(gameSeries : GameSeries) -> NSFetchRequest { + let fetchRequest = NSFetchRequest(entityName: "Game") + fetchRequest.predicate = NSPredicate(format: "series == %@", gameSeries) + return fetchRequest + } + + public func addGameSeries(by objectIDStringified : String) { + if let url = URL(string: objectIDStringified) { + let persistentStoreCoordinator = CDManager.shared.persistentContainer.persistentStoreCoordinator + + if let objectID = persistentStoreCoordinator.managedObjectID(forURIRepresentation: url) { + if let gameSeries = CDManager.shared.viewContext.object(with: objectID) as? GameSeries { + self.series = gameSeries + } + } + } + } + + public static func compareByCreationDate(gameA : Game, gameB : Game) -> Bool { + guard let gameACreated = gameA.createdAt else { return true } + guard let gameBCreated = gameB.createdAt else { return false } + + return gameACreated < gameBCreated + } + + init(context: NSManagedObjectContext) { + super.init(entity: Game.entity(), insertInto: context) + + if self.createdAt == .none { + self.createdAt = Date() + } + } + @objc + private override init(entity: NSEntityDescription, insertInto context: NSManagedObjectContext?) { + super.init(entity: entity, insertInto: context) + } +} + +extension Game : Encodable { + enum CodingKeys: String, CodingKey { + case uuid + case name + case notes + case isDigital + case lentTo + case createdAt + case publisher + case isFinished + case inWishlist + case console + case gameSeries + case cover + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(uuid, forKey: .uuid) + try container.encode(name, forKey: .name) + try container.encode(notes ?? "", forKey: .notes) + try container.encode(isDigital, forKey: .isDigital) + try container.encode(lentTo ?? "", forKey: .lentTo) + try container.encode(createdAt?.formattedInTimeZone(), forKey: .createdAt) + try container.encode(publisher ?? "", forKey: .publisher) + try container.encode(isFinished, forKey: .isFinished) + try container.encode(inWishlist, forKey: .inWishlist) + + let consoleUUID : String = console?.uuid?.uuidString ?? "" + let gameSeriesUUID : String = series?.uuid.uuidString ?? "" + try container.encode(consoleUUID, forKey: .console) + try container.encode(gameSeriesUUID, forKey: .gameSeries) + + let imgBase64 = cover?.image?.pngData()?.base64EncodedString() ?? "" + try container.encode(imgBase64, forKey: .cover) + } } diff --git a/Zockerhoehle/CDModel/Game+CoreDataProperties.swift b/Zockerhoehle/CDModel/Game+CoreDataProperties.swift index 520fcaa..9ec8f67 100644 --- a/Zockerhoehle/CDModel/Game+CoreDataProperties.swift +++ b/Zockerhoehle/CDModel/Game+CoreDataProperties.swift @@ -2,8 +2,8 @@ // Game+CoreDataProperties.swift // Zockerhoehle // -// Created by Julian-Steffen Müller on 06.07.19. -// Copyright © 2019 Julian-Steffen Müller. All rights reserved. +// Created by Julian-Steffen Müller on 14.05.21. +// Copyright © 2021 Julian-Steffen Müller. All rights reserved. // // @@ -11,20 +11,24 @@ import Foundation import CoreData -extension Game{ +extension Game { @nonobjc public class func fetchRequest() -> NSFetchRequest { return NSFetchRequest(entityName: "Game") } - @NSManaged public var isFinished: Bool + @NSManaged public var circumstances: String? + @NSManaged public var createdAt: Date? @NSManaged public var inWishlist: Bool @NSManaged public var isDigital: Bool - @NSManaged public var name: String + @NSManaged public var isFinished: Bool + @NSManaged public var lentTo: String? + @NSManaged public var name: String? @NSManaged public var notes: String? @NSManaged public var publisher: String? + @NSManaged public var uuid: UUID? @NSManaged public var console: Console? - @NSManaged public var series: GameSeries? @NSManaged public var cover: Cover? + @NSManaged public var series: GameSeries? } diff --git a/Zockerhoehle/CDModel/GameSeries+CoreDataClass.swift b/Zockerhoehle/CDModel/GameSeries+CoreDataClass.swift index 887afef..d52e80a 100644 --- a/Zockerhoehle/CDModel/GameSeries+CoreDataClass.swift +++ b/Zockerhoehle/CDModel/GameSeries+CoreDataClass.swift @@ -2,15 +2,65 @@ // GameSeries+CoreDataClass.swift // Zockerhoehle // -// Created by Julian-Steffen Müller on 06.07.19. +// Created by Julian-Steffen Müller on 24.09.19. // Copyright © 2019 Julian-Steffen Müller. All rights reserved. // // import Foundation import CoreData +import UIKit @objc(GameSeries) -public class GameSeries: NSManagedObject { +public class GameSeries: NSManagedObject, Identifiable { + public var id : String { + return objectID.uriRepresentation().absoluteString + } + + var coverAsUIImage : UIImage? { + get { + if let coverImage = self.cover?.image { + return coverImage + } + + return .none + } + + set { + if let coverImage = newValue { + let newCover = GameSeriesCover(context: CDManager.shared.viewContext) + newCover.gameSeries = self + newCover.image = coverImage + self.cover = newCover + } + } + } +} + +extension GameSeries : Encodable { + + enum CodingKeys: String, CodingKey { + case uuid + case name + case games + case cover + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(uuid, forKey: .uuid) + try container.encode(name, forKey: .name) + + var gamesList : [String] = [] + for game in games { + if let game = game as? Game { + gamesList.append(game.objectID.uriRepresentation().absoluteString) + } + } + try container.encode(gamesList, forKey: .games) + + let coverBase64 = cover?.image?.pngData()?.base64EncodedString() ?? "" + try container.encode(coverBase64, forKey: .cover) + } } diff --git a/Zockerhoehle/CDModel/GameSeries+CoreDataProperties.swift b/Zockerhoehle/CDModel/GameSeries+CoreDataProperties.swift index 52fc4e6..6cc481d 100644 --- a/Zockerhoehle/CDModel/GameSeries+CoreDataProperties.swift +++ b/Zockerhoehle/CDModel/GameSeries+CoreDataProperties.swift @@ -2,7 +2,7 @@ // GameSeries+CoreDataProperties.swift // Zockerhoehle // -// Created by Julian-Steffen Müller on 06.07.19. +// Created by Julian-Steffen Müller on 24.09.19. // Copyright © 2019 Julian-Steffen Müller. All rights reserved. // // @@ -17,9 +17,10 @@ extension GameSeries { return NSFetchRequest(entityName: "GameSeries") } - @NSManaged public var cover: Data? - @NSManaged public var name: String? + @NSManaged public var name: String + @NSManaged public var uuid : UUID @NSManaged public var games: NSSet + @NSManaged public var cover: GameSeriesCover? } diff --git a/Zockerhoehle/CDModel/GameSeriesCover+CoreDataClass.swift b/Zockerhoehle/CDModel/GameSeriesCover+CoreDataClass.swift new file mode 100644 index 0000000..fe115f3 --- /dev/null +++ b/Zockerhoehle/CDModel/GameSeriesCover+CoreDataClass.swift @@ -0,0 +1,16 @@ +// +// GameSeriesCover+CoreDataClass.swift +// Zockerhoehle +// +// Created by Julian-Steffen Müller on 24.09.19. +// Copyright © 2019 Julian-Steffen Müller. All rights reserved. +// +// + +import Foundation +import CoreData + +@objc(GameSeriesCover) +public class GameSeriesCover: NSManagedObject { + +} diff --git a/Zockerhoehle/CDModel/GameSeriesCover+CoreDataProperties.swift b/Zockerhoehle/CDModel/GameSeriesCover+CoreDataProperties.swift new file mode 100644 index 0000000..dbbbdda --- /dev/null +++ b/Zockerhoehle/CDModel/GameSeriesCover+CoreDataProperties.swift @@ -0,0 +1,23 @@ +// +// GameSeriesCover+CoreDataProperties.swift +// Zockerhoehle +// +// Created by Julian-Steffen Müller on 24.09.19. +// Copyright © 2019 Julian-Steffen Müller. All rights reserved. +// +// + +import Foundation +import CoreData +import UIKit + +extension GameSeriesCover { + + @nonobjc public class func fetchRequest() -> NSFetchRequest { + return NSFetchRequest(entityName: "GameSeriesCover") + } + + @NSManaged public var image: UIImage? + @NSManaged public var gameSeries: GameSeries? + +} diff --git a/Zockerhoehle/Info.plist b/Zockerhoehle/Info.plist index b946932..fe4f05d 100644 --- a/Zockerhoehle/Info.plist +++ b/Zockerhoehle/Info.plist @@ -20,6 +20,8 @@ 1.0 CFBundleVersion 1 + LSApplicationCategoryType + LSRequiresIPhoneOS NSAppTransportSecurity @@ -46,6 +48,12 @@ + UIBackgroundModes + + remote-notification + + UIFileSharingEnabled + UILaunchStoryboardName LaunchScreen UIRequiredDeviceCapabilities diff --git a/Zockerhoehle/Model/Attachment.swift b/Zockerhoehle/Model/Attachment.swift deleted file mode 100644 index 23b36c4..0000000 --- a/Zockerhoehle/Model/Attachment.swift +++ /dev/null @@ -1,31 +0,0 @@ -// -// Attachment.swift -// Zockerhoehle -// -// Created by Julian-Steffen Müller on 14.09.18. -// Copyright © 2018 Julian-Steffen Müller. All rights reserved. -// - -import Foundation - -typealias AttachmentID = String - -struct Attachment : Codable { - var id : AttachmentID - var type : String - var file : Data? - var isDeleted : Bool - var description : String - var createdOn : String - var changedOn : String - - enum CodingKeys : String, CodingKey { - case id = "_id" - case type - case file - case isDeleted - case description - case createdOn = "created" - case changedOn = "changed" - } -} diff --git a/Zockerhoehle/Model/ConsoleEntry.swift b/Zockerhoehle/Model/ConsoleEntry.swift deleted file mode 100644 index ea97868..0000000 --- a/Zockerhoehle/Model/ConsoleEntry.swift +++ /dev/null @@ -1,44 +0,0 @@ -// -// ConsoleEntry.swift -// Zockerhoehle -// -// Created by Julian-Steffen Müller on 17.09.18. -// Copyright © 2018 Julian-Steffen Müller. All rights reserved. -// - -import Foundation - -class ConsoleEntry : FlockeEntry { - var generation : Int? { - get { - return self.content["Generation"] as? Int - } - } - - var manufacturer : String? { - get { - return self.content["Manufacturer"] as? String - } - } - - override init(entry: FlockeEntry) { - super.init(entry: entry) - } - - required init(from decoder: Decoder) throws { - try super.init(from: decoder) - } - - override func encodeContent(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - - var contentContainer = container.nestedContainer(keyedBy: CodingKeysContentConsoleEntry.self, forKey: CodingKeys.content) - try contentContainer.encode(self.generation, forKey: CodingKeysContentConsoleEntry.Generation) - try contentContainer.encode(self.manufacturer, forKey: CodingKeysContentConsoleEntry.Manufacturer) - } - - enum CodingKeysContentConsoleEntry : String, CodingKey { - case Generation = "Generation" - case Manufacturer - } -} diff --git a/Zockerhoehle/Model/FlockeEntry.swift b/Zockerhoehle/Model/FlockeEntry.swift deleted file mode 100644 index 4b1595c..0000000 --- a/Zockerhoehle/Model/FlockeEntry.swift +++ /dev/null @@ -1,103 +0,0 @@ -// -// Console.swift -// Zockerhoehle -// -// Created by Julian-Steffen Müller on 12.09.18. -// Copyright © 2018 Julian-Steffen Müller. All rights reserved. -// - -import Foundation - -typealias FlockeEntryID = String - -class FlockeEntry : Codable { - let id : FlockeEntryID - let isDeleted : Bool - let name : String - let createdOn : String - let changedOn : String - let content : [String : Any] - var iconAttachment : Attachment? - var parents : [FlockeEntryID] - - init (entry : FlockeEntry) { - self.id = entry.id - self.isDeleted = entry.isDeleted - self.name = entry.name - self.createdOn = entry.createdOn - self.changedOn = entry.changedOn - self.content = entry.content - self.iconAttachment = entry.iconAttachment - self.parents = entry.parents - } - - required init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - - id = try container.decode(String.self, forKey: .id) - isDeleted = try container.decode(Bool.self, forKey: .isDeleted) - name = try container.decode(String.self, forKey: .name) - createdOn = try container.decode(String.self, forKey: .createdOn) - changedOn = try container.decode(String.self, forKey: .changedOn) - - parents = try container.decode([FlockeEntryID].self, forKey: .parents) - - do { - content = try container.decode([String : Any].self, forKey: .content) - }catch{ - content = [:] - } - - do { - let attachmentContainer = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: .attachments) - - iconAttachment = try? attachmentContainer.decode(Attachment.self, forKey: .icon) - }catch { - iconAttachment = nil - } - } - - func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - - try container.encode(self.id, forKey: .id) - try container.encode(self.isDeleted, forKey: .isDeleted) - try container.encode(self.name, forKey: .name) - try container.encode(self.createdOn, forKey: .createdOn) - try container.encode(self.changedOn, forKey: .changedOn) - - try container.encode(self.parents, forKey: .parents) - - var attachmentContainer = container.nestedContainer(keyedBy: CodingKeys.self, forKey: .attachments) - - try attachmentContainer.encode(self.iconAttachment, forKey: .icon) - - if (!self.content.isEmpty) { - try encodeContent(to: encoder); - } - } - - func encodeContent(to encoder: Encoder) throws {} - - enum CodingKeys : String, CodingKey { - case id = "_id" - case isDeleted - case name = "name" - case createdOn = "created" - case changedOn = "changed" - case content - case manufacturer = "Manufacturer" - case generation = "Generation" - case attachments - case icon - case parents - } -} - -extension FlockeEntry: Equatable { - static func == (lhs: FlockeEntry, rhs: FlockeEntry) -> Bool { - return lhs.id == rhs.id - } - - -} diff --git a/Zockerhoehle/Model/GameCollection.swift b/Zockerhoehle/Model/GameCollection.swift deleted file mode 100644 index 172fb4d..0000000 --- a/Zockerhoehle/Model/GameCollection.swift +++ /dev/null @@ -1,206 +0,0 @@ -// -// Consoles.swift -// Zockerhoehle -// -// Created by Julian-Steffen Müller on 13.09.18. -// Copyright © 2018 Julian-Steffen Müller. All rights reserved. -// - -import Foundation -import Disk -import CoreData - -protocol ConsoleItemObserver { - func consoleItemDataUpdate(for parent : FlockeEntryID) -} - -protocol AttachmentObserver { - func attachmentUpdate(attachmentID : AttachmentID) -} - -class GameCollection { - static let shared : GameCollection = GameCollection() - - //TODO: Zusammenfuehren von consoles und consoleItemsByParent - var consoles : [ConsoleEntry] = [] - var consoleItemsByParent : [FlockeEntryID : [FlockeEntry]] = [:] - var attachments : [AttachmentID : Attachment] = [:] - - private var consoleItemObservers : [ConsoleItemObserver] = [] - private var attachmentObservers : [AttachmentObserver] = [] - - init() { - do { - self.consoles = try Disk.retrieve("consoles.json", from: .caches, as: [ConsoleEntry].self) - self.consoleItemsByParent = try Disk.retrieve("consoleItems.json", from: .caches, as: [FlockeEntryID : [FlockeEntry]].self) - self.attachments = try Disk.retrieve("attachments.json", from: .caches, as: [AttachmentID : Attachment].self) - - //Fill core data on Startup - - print("Game Collection import begin"); - - let cdm = CDManager.shared - - var tmpConsole : Console? = .none - for console in self.consoles { - let cdConsole = Console(entity: Console.entity(), insertInto: cdm.viewContext) - cdConsole.name = console.name - - if cdConsole.name == "Nintendo Entertainment System" { - tmpConsole = cdConsole - } - - cdConsole.generation = Int64(console.generation ?? 0) - cdConsole.manufacturer = console.manufacturer - - guard let consoleItems = self.consoleItemsByParent[console.id] else { - print("No games: \(console.name)") - continue - } - - if let imgID = console.iconAttachment?.id , let img = self.attachments[imgID]?.file { - let cdLogo = Logo(entity: Logo.entity(), insertInto: cdm.viewContext) - cdLogo.image = UIImage(data: img) - cdConsole.logo = cdLogo - } - - for item in consoleItems { - if (item.parents.contains(GameCollection.accessoryID)) { - let cdAccessory = Accessory(entity: Accessory.entity(), insertInto: cdm.viewContext) - - cdAccessory.name = item.name - cdAccessory.inWishlist = item.parents.contains(GameCollection.wishlistID) - cdConsole.addToAccessories(cdAccessory) - - }else if (item.parents.contains(GameCollection.videogameID)) { - let cdGame = Game(entity: Game.entity(), insertInto: cdm.viewContext) - - cdGame.name = item.name - cdGame.inWishlist = item.parents.contains(GameCollection.wishlistID) - - cdConsole.addToGames(cdGame) - } - } - } - - guard let con = tmpConsole else { - print("No Console") - return - } - let fetchReq = NSFetchRequest(entityName: "Game") - fetchReq.predicate = NSPredicate(format: "console=%@", con) - do { - let fetchRes = try CDManager.shared.viewContext.fetch(fetchReq) - - for g in fetchRes { - print("\(g.name)") - } - }catch { - print(error) - } - - }catch let error as NSError { - print("Load of Chached not possible \(error.localizedDescription)"); - } - } - - func addFlockeEntryObserver(observer: ConsoleItemObserver) { - self.consoleItemObservers.append(observer) - } - - func addAttachmentObserver(observer: AttachmentObserver) { - self.attachmentObservers.append(observer) - } - - fileprivate func notifyConSoleItemObservers(_ parent: FlockeEntryID) { - for observer in self.consoleItemObservers { - observer.consoleItemDataUpdate(for: parent) - } - } - - func updateGameItems(items : [FlockeEntry], with parent : FlockeEntryID) { - if parent == GameCollection.consoleID { - updateConsoles(entrys: items) - }else { - self.consoleItemsByParent[parent] = items - - do { - try Disk.save(self.consoleItemsByParent, to: .caches, as: "consoleItems.json") - }catch let error as NSError { - print("Write of GameItems not possible \(error.localizedDescription)"); - } - } - - notifyConSoleItemObservers(parent) - } - - func remove(from: FlockeEntry, parentID: FlockeEntryID) { - from.parents.removeAll(where: {$0 == parentID}) - - for parent in from.parents { - notifyConSoleItemObservers(parent) - } - } - - func removeGameItem(from console : FlockeEntry, item: FlockeEntry) { - guard let consoleItems : [FlockeEntry] = self.consoleItemsByParent[console.id] else { - return - } - - if let index = consoleItems.firstIndex(of: item) { - consoleItemsByParent[console.id]!.remove(at: index); - - notifyConSoleItemObservers(console.id) - } - } - - func updateAttachment(attachment : Attachment) { - self.attachments[attachment.id] = attachment - - do { - try Disk.save(self.attachments, to: .caches, as: "attachments.json") - }catch let error as NSError { - print("Write of Attachments not possible \(error.localizedDescription)"); - } - - for observer in self.attachmentObservers { - observer.attachmentUpdate(attachmentID: attachment.id) - } - } - - func updateConsoles(entrys: [FlockeEntry]) { - self.consoles = [] - for entry in entrys { - self.consoles.append(ConsoleEntry(entry: entry)) - } - - //IN-Place Sort - self.consoles.sort { - //1. Sort by manufacturer - if ($0.manufacturer == $1.manufacturer) { -// //2. Sort by console generation - if ($0.generation == $1.generation) { -// //3. Sort by console name - return $0.name < $1.name - } - - return $0.generation! < $1.generation! - } - - return $0.manufacturer! < $1.manufacturer! - } - - do { - try Disk.save(self.consoles, to: .caches, as: "consoles.json") - }catch let error as NSError { - print("Write of ConsoleItem not possible \(error.localizedDescription)"); - } - } -} - -extension GameCollection { - static let consoleID : FlockeEntryID = "5b0c2ff8ba0c0b4ae7559911" - static let videogameID : FlockeEntryID = "5b116282ba0c0b4ae75599be" - static let wishlistID : FlockeEntryID = "5b11628dba0c0b4ae75599bf" - static let accessoryID : FlockeEntryID = "5b1162ccba0c0b4ae75599c1" -} diff --git a/Zockerhoehle/SceneDelegate.swift b/Zockerhoehle/SceneDelegate.swift index 567ba00..0ea0984 100644 --- a/Zockerhoehle/SceneDelegate.swift +++ b/Zockerhoehle/SceneDelegate.swift @@ -13,8 +13,8 @@ import SwiftUI class SceneDelegate: UIResponder, UIWindowSceneDelegate, UIApplicationDelegate { var window: UIWindow? - let gameStore = GameStore(console: .none) - let consoleStore = ConsoleStore() + let consolesStore = ConsoleStore() + let gameSeriesStore = GameSeriesStore() func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. @@ -24,8 +24,10 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate, UIApplicationDelegate { // Use a UIHostingController as window root view controller if let windowScene = scene as? UIWindowScene { let window = UIWindow(windowScene: windowScene) - let view = ConsolesListView(gameStore: self.gameStore) - window.rootViewController = UIHostingController(rootView: view) + + window.rootViewController = UIHostingController(rootView: MainView() + .environmentObject(self.gameSeriesStore) + .environmentObject(self.consolesStore)) self.window = window window.makeKeyAndVisible() } diff --git a/Zockerhoehle/Lib/CodableExtensionAny.swift b/Zockerhoehle/Utils/CodableExtensionAny.swift similarity index 100% rename from Zockerhoehle/Lib/CodableExtensionAny.swift rename to Zockerhoehle/Utils/CodableExtensionAny.swift diff --git a/Zockerhoehle/Utils/DateConversion.swift b/Zockerhoehle/Utils/DateConversion.swift new file mode 100644 index 0000000..4592852 --- /dev/null +++ b/Zockerhoehle/Utils/DateConversion.swift @@ -0,0 +1,27 @@ +// +// DateConversion.swift +// Zockerhoehle +// +// Created by Julian-Steffen Müller on 24.09.19. +// Copyright © 2019 Julian-Steffen Müller. All rights reserved. +// + +import Foundation + +extension Date { + func formattedInTimeZone(timezone : TimeZone = .current) -> String { + // 1) Create a DateFormatter() object. + let format = DateFormatter() + + // 2) Set the current timezone to .current, or America/Chicago. + format.timeZone = timezone + + // 3) Set the format of the altered date. + format.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ" + + // 4) Set the current date, altered by timezone. + let dateString = format.string(from: self) + + return dateString + } +} diff --git a/Zockerhoehle/Utils/FlockeConnector.swift b/Zockerhoehle/Utils/FlockeConnector.swift deleted file mode 100644 index 2e5eedf..0000000 --- a/Zockerhoehle/Utils/FlockeConnector.swift +++ /dev/null @@ -1,45 +0,0 @@ -// -// FlockeConnector.swift -// Zockerhoehle -// -// Created by Julian-Steffen Müller on 29.10.18. -// Copyright © 2018 Julian-Steffen Müller. All rights reserved. -// - -import Foundation - -class FlockeConnector { - let flockeURL : URL? - let bodyDict : NSDictionary? - let requestType : String - let completionHandler : (Data?, URLResponse?, Error?) -> Void - - init(url : String, requestType : String = "GET", bodyDict : NSDictionary?, _ completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) { - self.flockeURL = URL.init(string: url) - self.bodyDict = bodyDict - self.requestType = requestType - self.completionHandler = completionHandler - } - - func doRequest() { - guard let url = self.flockeURL else { - print("DO Request: Creating URL fails") - return - } - - - do { - let requestBodyJSON = try JSONSerialization.data(withJSONObject: self.bodyDict!) - - var req = URLRequest.init(url: url) - req.httpMethod = self.requestType - req.httpBody = requestBodyJSON - req.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type") - - URLSession.shared.dataTask(with: req, completionHandler: self.completionHandler).resume() - }catch { - print("Cannot convert to JSON") - return - } - } -} diff --git a/Zockerhoehle/Utils/FlockeWS.swift b/Zockerhoehle/Utils/FlockeWS.swift deleted file mode 100644 index d26d868..0000000 --- a/Zockerhoehle/Utils/FlockeWS.swift +++ /dev/null @@ -1,152 +0,0 @@ -// -// FlockeLoader.swift -// Zockerhoehle -// -// Created by Julian-Steffen Müller on 13.09.18. -// Copyright © 2018 Julian-Steffen Müller. All rights reserved. -// - -import Foundation -import Disk - -extension Notification.Name { - static let FlockeWSDeleteFinished = Notification.Name("flockeWSDeleteFinished") - static let FlockeWSAddFinished = Notification.Name("flockeWSAddFinished") -} - -class FlockeWS { - @objc enum ERROR_STATE : Int { - case NO_ERROR - case REQUEST_FAILED - } - - static func fetchEntries(for parent: FlockeEntryID) { - guard let fetchEntriesURL = URL.init(string: "http://flocke.mueller.haus:8081/entry/parent/\(parent)") else { - print("FETCH ENTRIES: Creating URL fails") - return - } - - let task = URLSession.shared.dataTask(with: fetchEntriesURL) { (data, resp, err) in - if (err == nil) { - let consoles : [FlockeEntry] = try! JSONDecoder().decode([FlockeEntry].self, from: data!) - - DispatchQueue.main.async { - GameCollection.shared.updateGameItems(items: consoles, with: parent) - for console in consoles { - FlockeWS.fetchEntries(for: console.id) - - if let att = console.iconAttachment { - FlockeWS.fetchAttachment(attachment: att.id) - } - } - } - }else{ - print("FLOCKEWS::fetchEntries; \(err?.localizedDescription ?? "N/A")") - } - } - task.resume() - } - - static func fetchAttachment(attachment attachmentId : AttachmentID) { - guard let fetchAttachmentURL = URL.init(string: "http://flocke.mueller.haus:8081/attachment/\(attachmentId)") else { - print("FETCH ATTACHMENT: Creating URL fails") - return - } - - URLSession.shared.dataTask(with: fetchAttachmentURL) { (data, resp, err) in - if (err == nil) { - let attachment : Attachment = try! JSONDecoder().decode(Attachment.self, from: data!) - - - DispatchQueue.main.async { - GameCollection.shared.updateAttachment(attachment: attachment) - } - } - - }.resume() - } - - static func deleteEntry(by entry: FlockeEntryID) { - guard let flockeDeleteEntryURL = URL.init(string: "http://flocke.mueller.haus:8081/entry/\(entry)") else { - print("DELETE ENTRY: Creating URL fails") - return - } - - var req = URLRequest.init(url: flockeDeleteEntryURL) - req.httpMethod = "DELETE" - - URLSession.shared.dataTask(with: req) { (data, resp, err) in - if (err == nil) { - DispatchQueue.main.async { - //TODO GameCollection Update - NotificationCenter.default.post(name: .FlockeWSDeleteFinished, object: nil) - } - } - }.resume() - } - - static func deleteParents(entry entryID : FlockeEntryID, parents : [FlockeEntryID]) { - print("DELETE PARENTS WS \(entryID) - \(parents)") - - let addJSON : NSMutableDictionary = NSMutableDictionary() - addJSON.setValue(entryID, forKey: "id") - addJSON.setValue(parents, forKey: "parents") - - do { - let jsonBodyData = try JSONSerialization.data(withJSONObject: addJSON, options: []) - let jsonBodyString = String(data: jsonBodyData, encoding: .utf8) ?? "{}" - print(jsonBodyString) - - let connector : FlockeConnector = FlockeConnector(url: "http://flocke.mueller.haus:8081/entry/deleteParents", - requestType: "PUT", - bodyDict: addJSON) { (data, resp, err) in -// DispatchQueue.main.async { -// //NOP -// } - } - - connector.doRequest() - }catch { - return; - } - - return; - } - - - static func addEntry(name : String, consoleID : FlockeEntryID, isVideogame : Bool, isOnWishlist : Bool) { - print("ADD WS \(name) \(isVideogame) \(isOnWishlist)") - - var parents : [String] = [consoleID] - - if (isVideogame) { - parents.append(GameCollection.videogameID) - }else{ - parents.append(GameCollection.accessoryID) - } - - if isOnWishlist { - parents.append(GameCollection.wishlistID) - } - - let addJSON : NSMutableDictionary = NSMutableDictionary() - addJSON.setValue(name, forKey: "name") - addJSON.setValue(parents, forKey: "parents") - - let connector : FlockeConnector = FlockeConnector(url: "http://flocke.mueller.haus:8081/entry/", - requestType: "POST", - bodyDict: addJSON) { (data, resp, err) in - var requestError = FlockeWS.ERROR_STATE.NO_ERROR - - if (err != nil) { requestError = FlockeWS.ERROR_STATE.REQUEST_FAILED } - - - DispatchQueue.main.async { - let userInfo:[String: FlockeWS.ERROR_STATE] = ["error_state": requestError] - NotificationCenter.default.post(name: .FlockeWSAddFinished, object: nil, userInfo: userInfo) - } - } - - connector.doRequest() - } -} diff --git a/Zockerhoehle/Utils/ImagePicker.swift b/Zockerhoehle/Utils/ImagePicker.swift new file mode 100644 index 0000000..c1ce4ef --- /dev/null +++ b/Zockerhoehle/Utils/ImagePicker.swift @@ -0,0 +1,63 @@ +import SwiftUI +import Combine +import UIKit + +final class ImagePicker : ObservableObject { + + static let shared : ImagePicker = ImagePicker() + + private init() {} //force using the singleton: ImagePicker.shared + let view = ImagePicker.View() + let coordinator = ImagePicker.Coordinator() + + // Bindable Object part + let objectWillChange = PassthroughSubject() + + var image: UIImage? = .none { + didSet { + objectWillChange.send(image) + print("ImagePicker::image Image Changed") + } + } +} + + +extension ImagePicker { + + class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate { + + // UIImagePickerControllerDelegate + func imagePickerController(_ picker: UIImagePickerController, + didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { + let uiImage = info[UIImagePickerController.InfoKey.originalImage] as! UIImage + ImagePicker.shared.image = uiImage + picker.dismiss(animated:true) + } + + func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { + picker.dismiss(animated:true) + } + } + + + struct View: UIViewControllerRepresentable { + + func makeCoordinator() -> Coordinator { + ImagePicker.shared.coordinator + } + + func makeUIViewController(context: UIViewControllerRepresentableContext) -> UIImagePickerController { + let picker = UIImagePickerController() + picker.delegate = context.coordinator + + return picker + } + + func updateUIViewController(_ uiViewController: UIImagePickerController, + context: UIViewControllerRepresentableContext) { + + } + + } + +} diff --git a/Zockerhoehle/Utils/LibraryExport.swift b/Zockerhoehle/Utils/LibraryExport.swift new file mode 100644 index 0000000..e1c8625 --- /dev/null +++ b/Zockerhoehle/Utils/LibraryExport.swift @@ -0,0 +1,100 @@ +// +// LibraryExport.swift +// Zockerhoehle +// +// Created by Julian-Steffen Müller on 19.11.19. +// Copyright © 2019 Julian-Steffen Müller. All rights reserved. +// + +import Foundation +import Compression + +struct LibraryExporter : Encodable { + private var games : [Game] + private var consoles : [Console] + private var gameSeries : [GameSeries] + private var accessories : [Accessory] + + init (games: [Game], consoles: [Console], gameSeries: [GameSeries], accessories : [Accessory]) { + self.games = games + self.consoles = consoles + self.gameSeries = gameSeries + self.accessories = accessories + } + + enum CodingKeys: String, CodingKey { + case games + case consoles + case gameSeries + case accessories + } + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(games, forKey: .games) + try container.encode(consoles, forKey: .consoles) + try container.encode(gameSeries, forKey: .gameSeries) + try container.encode(accessories, forKey: .accessories) + } + + func export() -> Data? { + let jsonEncoder = JSONEncoder() + + do { + let exportJSON = try jsonEncoder.encode(self) + return exportJSON + }catch { + return Data?.none + } + } + + func export(name : String) { + if let data = export() { + //let exportFileName = "libExp_\(Date().formattedInTimeZone()).json" + let exportFileName = "libExp_\(name).json" + let byteCount = data.count // replace with data.count + let bcf = ByteCountFormatter() + bcf.allowedUnits = [.useMB] // optional: restricts the units to MB only + bcf.countStyle = .file + let fileSizeAsString = bcf.string(fromByteCount: Int64(byteCount)) + print("Exported Library to '\(exportFileName)' (Size: \(fileSizeAsString))") + + do { + let documentDirectory = try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor:nil, create:false) + let encodedFileURL = documentDirectory.appendingPathComponent(exportFileName) + + try data.write(to: encodedFileURL) + }catch { + + } + } + } + + //Currently python decoder missing 15 characters + func exportCompressedZLIB() -> Data? { + if let data = self.export() { + let exportString = String(data: data, encoding: .utf8)! + + var sourceBuffer = Array(exportString.utf8) + let destinationBuffer = UnsafeMutablePointer.allocate(capacity: exportString.count) + + let algorithm = COMPRESSION_ZLIB + + let compressedSize = compression_encode_buffer(destinationBuffer, exportString.count, + &sourceBuffer, exportString.count, + nil, + algorithm) + + if compressedSize > 0 { + let exportData = NSData(bytesNoCopy: destinationBuffer, length: compressedSize) as Data + + return exportData + }else { + print("LibraryExport::exportCompressedZLIB - Compression failed 'compressedSite == 0'") + return Data?.none + } + } + + return Data?.none + } +} diff --git a/Zockerhoehle/Utils/LibraryImport.swift b/Zockerhoehle/Utils/LibraryImport.swift new file mode 100644 index 0000000..217b74e --- /dev/null +++ b/Zockerhoehle/Utils/LibraryImport.swift @@ -0,0 +1,328 @@ +// +// LibraryImport.swift +// Zockerhoehle +// +// Created by Julian-Steffen Müller on 20.11.19. +// Copyright © 2019 Julian-Steffen Müller. All rights reserved. +// + +import Foundation +import UIKit +import CoreData + +// MARK: - Library Import + +class LibraryImport { + static func backupName(from filename : String) -> String? { + if filename.hasPrefix("libExp_") && filename.hasSuffix(".json") { + var backupName = filename + backupName.removeFirst("libExp_".count) + backupName.removeLast(".json".count) + return backupName + }else{ + return .none + } + } + func backupFiles() -> [String] { + do { + let documentDirectory = try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor:nil, create:false) + + let filesInDocumentsDir = try FileManager.default.contentsOfDirectory(at: documentDirectory, includingPropertiesForKeys: .none, options: []) + + let backupFiles = filesInDocumentsDir.filter{ + $0.isFileURL + }.map { + $0.lastPathComponent + }.filter { + $0.hasPrefix("libExp_") && $0.hasSuffix(".json") + }.sorted(by: { $0 > $1 }) + + return backupFiles + + }catch let error { + print(error) + print("LibraryImport::backupFiles - Error reading files from Document directory!") + } + + return [] + } + + private func resetDatabase() { + print(GameSeriesStore().gameSeries) + CDManager.shared.viewContext.reset() + CDManager.shared.viewContext.performAndWait { + let deleteRequests = + [NSBatchDeleteRequest(fetchRequest: Game.fetchRequest()), + NSBatchDeleteRequest(fetchRequest: Accessory.fetchRequest()), + NSBatchDeleteRequest(fetchRequest: Console.fetchRequest()), + NSBatchDeleteRequest(fetchRequest: GameSeries.fetchRequest()), + NSBatchDeleteRequest(fetchRequest: Logo.fetchRequest()), + NSBatchDeleteRequest(fetchRequest: Cover.fetchRequest())] + + let storeCoordinator = CDManager.shared.persistentContainer.persistentStoreCoordinator + do { + for deleteRequest in deleteRequests { + try storeCoordinator.execute(deleteRequest, with: CDManager.shared.viewContext) + } + }catch let error { + print(error) + print("LibraryImport::resetDatabase - Reset of Database failes!") + } + } + print(GameSeriesStore().gameSeries) + } + + private func makeCDGame(from game: BHLGame, _ gameDict: inout [UUID : Game], _ cdConsole: Console) { + let cdGame = Game(context: CDManager.shared.viewContext) + gameDict[game.uuid] = cdGame + cdGame.name = game.name + cdGame.uuid = game.uuid + cdGame.inWishlist = game.inWishlist + cdGame.isFinished = game.isFinished + cdGame.lentTo = game.lentTo + //TODO: cdGame.createdAt = game.createdAt. + + let cdCover = Cover(context: CDManager.shared.viewContext) + cdCover.image = game.cover + cdCover.game = cdGame + cdGame.cover = cdCover + + cdConsole.addToGames(cdGame) + cdGame.console = cdConsole + print("Imported: \(cdGame.name) for \(cdConsole.name)") + } + + private func makeCDAccessory(from accessory: BHLAccessory, _ accessoryDict: inout [UUID : Accessory], _ cdConsole: Console) { + let cdAccessory = Accessory(context: CDManager.shared.viewContext) + accessoryDict[accessory.uuid] = cdAccessory + cdAccessory.name = accessory.name + cdAccessory.uuid = accessory.uuid + cdAccessory.lentTo = accessory.lentTo + cdAccessory.color = accessory.color + cdAccessory.manufacturer = accessory.manufacturer + cdAccessory.inWishlist = accessory.inWishlist + + cdConsole.addToAccessories(cdAccessory) + cdAccessory.console = cdConsole + } + + private func makeCDConsole(from console : BHLConsole) -> Console { + let cdConsole = Console(context: CDManager.shared.viewContext) + cdConsole.name = console.name + cdConsole.uuid = console.uuid + cdConsole.manufacturer = console.manufacturer + cdConsole.shortName = console.shortName + cdConsole.generation = Int64(console.generation) + + let cdLogo = Logo(context: CDManager.shared.viewContext) + cdLogo.image = console.logo + cdLogo.console = cdConsole + cdConsole.logo = cdLogo + + return cdConsole + } + + private func makeCDGameSeries(from gameSeries: BHLGameSeries, _ gameDict: inout [UUID : Game]) { + let cdGameSeries = GameSeries(context: CDManager.shared.viewContext) + cdGameSeries.name = gameSeries.name + + let cdCover = GameSeriesCover(context: CDManager.shared.viewContext) + cdCover.image = gameSeries.cover + cdCover.gameSeries = cdGameSeries + cdGameSeries.cover = cdCover + + for uuid in gameSeries.games { + if let game = gameDict[uuid] { + cdGameSeries.addToGames(game) + game.series = cdGameSeries + } + } + } + + func importLIB(from filename: String) { + do { + let documentDirectory = try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor:nil, create:false) + + let file = documentDirectory.appendingPathComponent(filename) + + if file.isFileURL { + let library = try JSONDecoder().decode(BHLibrary.self, from: Data(contentsOf: file)) + + resetDatabase() + + var gameDict = [UUID:Game]() + var accessoryDict = [UUID:Accessory]() + + for console in library.consoles { + let cdConsole = makeCDConsole(from: console) + + print("CONSOLE: \(cdConsole.name) with \(console.games.count) games") + for uuid in console.games { + + if let game = library.games.first(where: {$0.uuid == uuid}) { + makeCDGame(from: game, &gameDict, cdConsole) + }else{ + print("LibraryImport::importLIB - Game with UUID '\(uuid)' not found") + } + } + + for uuid in console.accessories { + if let accessory = library.accessories.first(where: {$0.uuid == uuid}) { + makeCDAccessory(from: accessory, &accessoryDict, cdConsole) + }else{ + print("LibraryImport::importLIB - Accessory with UUID '\(uuid)' not found") + } + } + } + + for gameSeries in library.gameSeries { + makeCDGameSeries(from: gameSeries, &gameDict) + } + } + + }catch let error { + print(error) + print("LibraryImport::importLIB - Error while importing!") + } + } +} + +// MARK: - BHLLibrary Classes + +struct BHLibrary : Decodable { + let games : [BHLGame] + let consoles : [BHLConsole] + let gameSeries : [BHLGameSeries] + let accessories : [BHLAccessory] +} + +struct BHLGame : Decodable { + let uuid : UUID + let name : String + let lentTo : String? + let isDigital : Bool + let inWishlist : Bool + let isFinished : Bool + let notes : String? + let createdAt : String? + let publisher : String? + let console : UUID? + let series : UUID? + let cover : UIImage? + + enum CodingKeys: String, CodingKey { + case uuid + case name + case lentTo + case isDigital + case inWishlist + case isFinished + case notes + case createdAt + case publisher + case console + case series + case cover + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + uuid = try container.decode(UUID.self, forKey: .uuid) + name = try container.decode(String.self, forKey: .name) + lentTo = try container.decode(String?.self, forKey: .lentTo) + isDigital = try container.decode(Bool.self, forKey: .isDigital) + inWishlist = try container.decode(Bool.self, forKey: .inWishlist) + isFinished = try container.decode(Bool.self, forKey: .isFinished) + notes = try container.decode(String?.self, forKey: .notes) + createdAt = try container.decode(String?.self, forKey: .notes) + publisher = try container.decode(String?.self, forKey: .publisher) + console = try container.decode(UUID?.self, forKey: .console) + series = try container.decode(UUID?.self, forKey: .console) + + + if let coverBase64 = try container.decode(String?.self, forKey: .cover), + let coverData = Data(base64Encoded: coverBase64) { + cover = UIImage(data: coverData) + }else { + cover = .none + } + } +} + +struct BHLAccessory : Decodable { + let uuid : UUID + let name : String + let color : String? + let manufacturer : String? + let inWishlist : Bool + let lentTo : String? + let console : UUID? +} + +struct BHLGameSeries : Decodable { + let uuid : UUID + let name : String + let cover : UIImage? + let games : [UUID] + + enum CodingKeys: String, CodingKey { + case uuid + case name + case cover + case games + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + uuid = try container.decode(UUID.self, forKey: .uuid) + name = try container.decode(String.self, forKey: .name) + games = try container.decode([UUID].self, forKey: .games) + + if let coverBase64 = try container.decode(String?.self, forKey: .cover), + let coverData = Data(base64Encoded: coverBase64) { + cover = UIImage(data: coverData) + }else { + cover = .none + } + } +} + +struct BHLConsole : Decodable { + let uuid : UUID + let name : String + let logo : UIImage? + let generation : Int + let manufacturer : String? + let shortName : String? + let accessories : [UUID] + let games : [UUID] + + enum CodingKeys: String, CodingKey { + case uuid + case name + case logo + case generation + case manufacturer + case shortName + case accessories + case games + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + uuid = try container.decode(UUID.self, forKey: .uuid) + name = try container.decode(String.self, forKey: .name) + generation = try container.decode(Int.self, forKey: .generation) + manufacturer = try container.decode(String?.self, forKey: .manufacturer) + shortName = try container.decode(String?.self, forKey: .shortName) + accessories = try container.decode([UUID].self, forKey: .accessories) + games = try container.decode([UUID].self, forKey: .games) + + if let coverBase64 = try container.decode(String?.self, forKey: .logo), + let coverData = Data(base64Encoded: coverBase64) { + logo = UIImage(data: coverData) + }else { + logo = .none + } + } +} diff --git a/Zockerhoehle/ViewController/ConsoleLibraryViewController.swift b/Zockerhoehle/ViewController/ConsoleLibraryViewController.swift deleted file mode 100644 index a27735e..0000000 --- a/Zockerhoehle/ViewController/ConsoleLibraryViewController.swift +++ /dev/null @@ -1,213 +0,0 @@ -// -// ConsoleViewController.swift -// Zockerhoehle -// -// Created by Julian-Steffen Müller on 12.09.18. -// Copyright © 2018 Julian-Steffen Müller. All rights reserved. -// -/* -import UIKit -import CoreData -import Combine - -class ConsoleLibraryViewController: UIViewController { - @IBOutlet weak var consoleItemTable: UITableView! - @IBOutlet weak var category: UISegmentedControl! - @IBOutlet weak var toggleWishList: UIBarButtonItem! - - var subsriber : Subscribers.Sink? - var videoGamesInLibrary : [Bool : [Game]] = [:] - var accessoriesinLibrary : [Bool : [Accessory]] = [:] - var showWishlist : Bool = false - - var console : Console? { - didSet { - self.title = console?.name ?? "N/A" - - //Remove subscription to old console object - subsriber?.cancel() - - //subsriber = console?.publisher(for: \.games, options: .new).sink(receiveValue: {_ in - // self.refreshConsoleLibrary() - //}) - - refreshConsoleLibrary() - } - } - - override func prepare(for segue: UIStoryboardSegue, sender: Any?) { - if segue.identifier == "detail" { - guard let indexpath = self.consoleItemTable.indexPathForSelectedRow else { - print("ConsoleLibraryViewController::prepare::detail IndexPath for selectedRow could not be retreived") - return - } - - if let dest = segue.destination as? GameDetailController { - dest.game = self.videoGamesInLibrary[self.showWishlist]?[indexpath.row] - }else if let dest = segue.destination as? AccessoryDetailController { - dest.accessory = self.accessoriesinLibrary[self.showWishlist]?[indexpath.row] - } - }else if (segue.identifier == "consoleEntryAdd") { - guard let addPopup = segue.destination as? AddEntryPopUpViewController else { - print("ConsoleLibraryViewController::prepare::consoleEntryAdd Destination is no AddEntryPopUpViewController") - return - } - - addPopup.console = self.console - addPopup.isWishlist = self.showWishlist - addPopup.isVideogame = self.category.selectedSegmentIndex == 0 - } - - } - - @IBAction func categoryChanged(_ sender: Any) { - self.consoleItemTable.reloadData() - } - - override func viewWillAppear(_ animated: Bool) { - if self.showWishlist { - self.toggleWishList.tintColor = UIColor.red - }else{ - self.toggleWishList.tintColor = UIColor.black - } - - self.toggleWishList.tintColor = UIColor.black - } - - func refreshConsoleLibrary() { - let videogames = self.console?.games.map({$0 as! Game}) ?? [] - self.videoGamesInLibrary[true] = videogames.filter({$0.inWishlist}) - self.videoGamesInLibrary[false] = videogames.filter({!$0.inWishlist}) - - let accessories = self.console?.accessories.map({$0 as! Accessory}) ?? [] - self.accessoriesinLibrary[true] = accessories.filter({$0.inWishlist}) - self.accessoriesinLibrary[false] = accessories.filter({!$0.inWishlist}) - - consoleItemTable?.reloadData() - } - - @IBAction func toggleWishlist(_ sender: Any) { - self.showWishlist = !self.showWishlist - - if self.showWishlist { - self.toggleWishList.tintColor = UIColor.red - }else{ - self.toggleWishList.tintColor = UIColor.black - } - - consoleItemTable.reloadData() - } -} - -extension ConsoleLibraryViewController: UITableViewDataSource { - func numberOfSections(in tableView: UITableView) -> Int { - return 1 - } - - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - if self.category.selectedSegmentIndex == 0 { - return self.videoGamesInLibrary[self.showWishlist]?.count ?? 0 - }else{ - return self.accessoriesinLibrary[self.showWishlist]?.count ?? 0 - } - } - - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - if self.category.selectedSegmentIndex == 0 { - let gameCell = tableView.dequeueReusableCell(withIdentifier: "gameCell") as! GameCell - - gameCell.game = self.videoGamesInLibrary[self.showWishlist]?[indexPath.row] - - return gameCell - }else{ - let accessoryCell = tableView.dequeueReusableCell(withIdentifier: "accessoryCell") as! AccessoryCell - - - accessoryCell.accessory = self.accessoriesinLibrary[self.showWishlist]?[indexPath.row] - - return accessoryCell - } - - } -} - - -// Swipe Action - Delete / move console items from / into the library -extension ConsoleLibraryViewController: UITableViewDelegate { - - // Move console item to library - func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { - let bought = UIContextualAction(style: .destructive, title: "") { (action, view, actionDone) in - actionDone(true) - if self.category.selectedSegmentIndex == 0 { - let game = self.videoGamesInLibrary[self.showWishlist]?[indexPath.row] - game?.inWishlist = false - }else{ - let accessory = self.accessoriesinLibrary[self.showWishlist]?[indexPath.row] - accessory?.inWishlist = false - } - - self.refreshConsoleLibrary() - } - - bought.image = #imageLiteral(resourceName: "cave") - bought.backgroundColor = #colorLiteral(red: 0.4666666687, green: 0.7647058964, blue: 0.2666666806, alpha: 1) - - var actions : [UIContextualAction] = [] - if (self.showWishlist) { - actions.append(bought) - } - - let swipeConf = UISwipeActionsConfiguration(actions: actions) - swipeConf.performsFirstActionWithFullSwipe = false - - return swipeConf - } - - // Delete console item from library - func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { - let delete = UIContextualAction(style: .destructive, title: "Delete") { (action, view, actionDone) in - - var nso : NSManagedObject? = .none - var name : String? - if self.category.selectedSegmentIndex == 0 { - nso = self.videoGamesInLibrary[self.showWishlist]?[indexPath.row] - name = self.videoGamesInLibrary[self.showWishlist]?[indexPath.row].name - - }else{ - nso = self.accessoriesinLibrary[self.showWishlist]?[indexPath.row] - name = self.accessoriesinLibrary[self.showWishlist]?[indexPath.row].name - } - - let deleteWarning = UIAlertController( title: "Aus Zockerhöhle entfernen", - message: "Willst du '\(name ?? "N/A")' wirklich aus der Zockerhöhle tragen?", - preferredStyle: .alert) - - deleteWarning.addAction(UIAlertAction(title: "Ja", style: .destructive, handler: { (action) in - actionDone(true) - if nso != nil { - CDManager.shared.viewContext.delete(nso!) - } - })) - - deleteWarning.addAction(UIAlertAction(title: "Nein", style: .cancel, handler: { (action) in - actionDone(false) - })) - - - - self.present(deleteWarning, animated: true, completion: nil) - - - } - - delete.image = #imageLiteral(resourceName: "delete") - delete.backgroundColor = #colorLiteral(red: 0.7450980544, green: 0.1568627506, blue: 0.07450980693, alpha: 1) - - let swipeConf = UISwipeActionsConfiguration(actions: [delete]) - swipeConf.performsFirstActionWithFullSwipe = false - - return swipeConf - } -} -*/ diff --git a/Zockerhoehle/ViewModel/AccessoryStore.swift b/Zockerhoehle/ViewModel/AccessoryStore.swift index d4ad59f..aadda09 100644 --- a/Zockerhoehle/ViewModel/AccessoryStore.swift +++ b/Zockerhoehle/ViewModel/AccessoryStore.swift @@ -49,6 +49,10 @@ class AccessoryStore : NSObject, ObservableObject, NSFetchedResultsControllerDel self.consoleFilter = console } + override init() { + super.init() + } + func controllerDidChangeContent(_ controller: NSFetchedResultsController) { print("GameStore::controllerDidChangeContent") self.objectWillChange.send() diff --git a/Zockerhoehle/ViewModel/AccessoryViewModel.swift b/Zockerhoehle/ViewModel/AccessoryViewModel.swift index 89c0ec0..36acfbd 100644 --- a/Zockerhoehle/ViewModel/AccessoryViewModel.swift +++ b/Zockerhoehle/ViewModel/AccessoryViewModel.swift @@ -10,52 +10,62 @@ import Foundation import UIKit import Combine import SwiftUI +import CoreData class AccessoryViewModel : ObservableObject { var objectWillChange = ObservableObjectPublisher() var inWishlist : Bool { didSet { - if let accessory = self.accessory { - accessory.inWishlist = inWishlist - } + guard let accessory = self.accessory else { return } + + accessory.inWishlist = inWishlist } } var manufacturer : String? { didSet { - if let accessory = self.accessory { - accessory.manufacturer = manufacturer - } + guard let accessory = self.accessory else { return } + + accessory.manufacturer = manufacturer } } var color : String? { didSet { - if let accessory = self.accessory { - accessory.color = color - } + guard let accessory = self.accessory else { return } + + accessory.color = color } } var name : String { didSet { - if let accessory = self.accessory { - accessory.name = name - } + guard let accessory = self.accessory else { return } + + accessory.name = name } } - private var accessory : Accessory? { + var lentTo : String? { didSet { - if accessory != nil { - self.name = accessory!.name - self.inWishlist = accessory!.inWishlist - self.color = accessory!.color - self.manufacturer = accessory!.manufacturer - } + guard let accessory = self.accessory else { return } + + accessory.lentTo = lentTo; } } + private var accessory : Accessory? { + didSet { + guard let accessory = accessory else { return } + + self.name = accessory.name + self.inWishlist = accessory.inWishlist + self.color = accessory.color + self.manufacturer = accessory.manufacturer + } + } + + var NSManagedObjectChangedObserver : AnyCancellable? = .none init(accessory : Accessory) { self.accessory = accessory @@ -65,11 +75,24 @@ class AccessoryViewModel : ObservableObject { self.color = accessory.color self.manufacturer = accessory.manufacturer - _ = NotificationCenter.default.publisher(for: .NSManagedObjectContextObjectsDidChange, object: CDManager.shared.viewContext).sink { - //TODO - self.objectWillChange.send() - if let accessory = self.accessory { - print("Accessory: \(accessory.name) Notification: \($0.description)") + self.NSManagedObjectChangedObserver = NotificationCenter.default.publisher(for: .NSManagedObjectContextObjectsDidChange, object: CDManager.shared.viewContext).first().sink { notification in + guard let accessory = self.accessory else { return } + guard let userInfo = notification.userInfo else { return } + + var managedObjectIsMatching = false + if let inserts = userInfo[NSInsertedObjectsKey] as? Set { + if inserts.contains(accessory) { managedObjectIsMatching = true } + } + if let updates = userInfo[NSUpdatedObjectsKey] as? Set { + if updates.contains(accessory) { managedObjectIsMatching = true } + } + if let deletes = userInfo[NSDeletedObjectsKey] as? Set { + if deletes.contains(accessory) { managedObjectIsMatching = true } + } + + if managedObjectIsMatching { + self.objectWillChange.send() + print("AccessoryViewModel::NSMangedObjectChanged MY Accessory") } } } diff --git a/Zockerhoehle/ViewModel/ConsoleViewModel.swift b/Zockerhoehle/ViewModel/ConsoleViewModel.swift new file mode 100644 index 0000000..526a49a --- /dev/null +++ b/Zockerhoehle/ViewModel/ConsoleViewModel.swift @@ -0,0 +1,55 @@ +// +// ConsoleViewModel.swift +// Zockerhoehle +// +// Created by Julian-Steffen Müller on 25.09.19. +// Copyright © 2019 Julian-Steffen Müller. All rights reserved. +// + +import SwiftUI +import Combine + +class ConsoleViewModel : ObservableObject { + var objectWillChange = ObservableObjectPublisher() + + init(_ console: Console) { + self.console = console + self.name = console.name! + } + + private var console : Console? { + didSet { + guard let console = console else { return } + + self.name = console.name! + } + } + + var name : String { + didSet { + guard let console = console else { return } + console.name = name + } + } + + var logo : UIImage? { + get { + if let logoImage = self.console?.logo?.image { + return logoImage + } + + return .none + } + + set { + if let console = self.console { + if let logoImage = newValue { + let newLogo = Logo(context: CDManager.shared.viewContext) + newLogo.console = console + newLogo.image = logoImage + console.logo = newLogo + } + } + } + } +} diff --git a/Zockerhoehle/ViewModel/FeaturedStore.swift b/Zockerhoehle/ViewModel/FeaturedStore.swift new file mode 100644 index 0000000..89bf495 --- /dev/null +++ b/Zockerhoehle/ViewModel/FeaturedStore.swift @@ -0,0 +1,54 @@ +// +// FeaturedStore.swift +// Zockerhoehle +// +// Created by Julian-Steffen Müller on 25.09.19. +// Copyright © 2019 Julian-Steffen Müller. All rights reserved. +// + +import Foundation +import Combine + +class FeaturedStore : ObservableObject { + var objectWillChange = PassthroughSubject() + + var gameStore = GameStore(sortDescriptors: [NSSortDescriptor(key: "createdAt", ascending: false), NSSortDescriptor(key: "name", ascending: true)], fetchLimit: 1) + + + var consoleStore = ConsoleStore() + + var consoleStoreSubscriber : AnyCancellable? + var gameStoreSubscriber : AnyCancellable? + + init() { + _ = self.consoleStore.consoles + if (self.gameStore.games.count > 0) { + self.featuredGame = self.gameStore.games.first + } + + self.featuredConsole = self.featuredGame?.console + + gameStoreSubscriber = self.gameStore.objectWillChange.sink { _ in + self.featuredGame = self.gameStore.games.first + self.featuredConsole = self.featuredGame?.console + } + } + + var featuredGame : Game? { + willSet { + objectWillChange.send() + } + } + + var featuredAccessory : Accessory? { + willSet { + objectWillChange.send() + } + } + + var featuredConsole : Console? { + willSet { + objectWillChange.send() + } + } +} diff --git a/Zockerhoehle/ViewModel/GameSeriesStore.swift b/Zockerhoehle/ViewModel/GameSeriesStore.swift new file mode 100644 index 0000000..a939e6d --- /dev/null +++ b/Zockerhoehle/ViewModel/GameSeriesStore.swift @@ -0,0 +1,48 @@ +// +// ConsoleStore.swift +// Zockerhoehle +// +// Created by Julian-Steffen Müller on 02.08.19. +// Copyright © 2019 Julian-Steffen Müller. All rights reserved. +// + +import Foundation +import Combine +import SwiftUI +import CoreData + +class GameSeriesStore : NSObject, ObservableObject, NSFetchedResultsControllerDelegate { + var objectWillChange = ObservableObjectPublisher() + + var gameSeries : [GameSeries] { + get { + if self.fetchResultsController.fetchedObjects == nil { + do { + try fetchResultsController.performFetch() + }catch { + print("ConsoleStore::consoles Perform Fetch not possible '\(error)'") + } + } + + return self.fetchResultsController.fetchedObjects ?? [] + } + } + + lazy var fetchResultsController : NSFetchedResultsController = { + let fetch : NSFetchRequest = GameSeries.fetchRequest() + + fetch.sortDescriptors = [NSSortDescriptor(key: "name", ascending: true)] + + var fetchRC = NSFetchedResultsController(fetchRequest: fetch, managedObjectContext: CDManager.shared.viewContext, sectionNameKeyPath: nil, cacheName: nil) + + fetchRC.delegate = self + + return fetchRC + + }() + + func controllerDidChangeContent(_ controller: NSFetchedResultsController) { + print("AllConsolesViewController::controllerDidChangeContent") + self.objectWillChange.send() + } +} diff --git a/Zockerhoehle/ViewModel/GameSeriesViewModel.swift b/Zockerhoehle/ViewModel/GameSeriesViewModel.swift new file mode 100644 index 0000000..9c2b08c --- /dev/null +++ b/Zockerhoehle/ViewModel/GameSeriesViewModel.swift @@ -0,0 +1,55 @@ +// +// ConsoleViewModel.swift +// Zockerhoehle +// +// Created by Julian-Steffen Müller on 25.09.19. +// Copyright © 2019 Julian-Steffen Müller. All rights reserved. +// + +import SwiftUI +import Combine + +class GameSeriesViewModel : ObservableObject { + var objectWillChange = ObservableObjectPublisher() + + init(_ gameSeries: GameSeries) { + self.gameSeries = gameSeries + self.name = gameSeries.name + } + + private var gameSeries : GameSeries? { + didSet { + guard let gameSeries = gameSeries else { return } + + self.name = gameSeries.name + } + } + + var name : String { + didSet { + guard let gameSeries = gameSeries else { return } + gameSeries.name = name + } + } + + var cover : UIImage? { + get { + if let logoImage = self.gameSeries?.cover?.image { + return logoImage + } + + return .none + } + + set { + if let gameSeries = self.gameSeries { + if let image = newValue { + let newCover = GameSeriesCover(context: CDManager.shared.viewContext) + newCover.gameSeries = gameSeries + newCover.image = image + gameSeries.cover = newCover + } + } + } + } +} diff --git a/Zockerhoehle/ViewModel/GameStore.swift b/Zockerhoehle/ViewModel/GameStore.swift index 6d5da0d..dc9002f 100644 --- a/Zockerhoehle/ViewModel/GameStore.swift +++ b/Zockerhoehle/ViewModel/GameStore.swift @@ -14,10 +14,14 @@ import Combine class GameStore : NSObject, ObservableObject, NSFetchedResultsControllerDelegate { var objectWillChange = PassthroughSubject() var consoleFilter : Console? + var gameSeriesFilter : GameSeries? + var sortDescriptors : [NSSortDescriptor] = [NSSortDescriptor(key: "name", ascending: true)] + var fetchLimit : Int = 0 var games : [Game] { get { if self.fetchResultsController.fetchedObjects == nil { + do { try fetchResultsController.performFetch() }catch { @@ -25,7 +29,21 @@ class GameStore : NSObject, ObservableObject, NSFetchedResultsControllerDelegate } } - return self.fetchResultsController.fetchedObjects ?? [] + let result = self.fetchResultsController.fetchedObjects ?? [] + + //Anscheinend ein Bug im FetchResultController das er das fetchLimit manchmal ignoriert + if self.fetchLimit > 0 && result.count > self.fetchLimit { + print("GameStore::games Fetch limit of ignored. Expected: \(self.fetchLimit) Delivered: \(result.count)") + + var clampedResult : [Game] = [] + for index in 0.. = Game.fetchRequest() if let console = consoleFilter { gamesFetch = Game.fetchRequest(console: console) + }else if let gameSeries = gameSeriesFilter { + gamesFetch = Game.fetchRequest(gameSeries: gameSeries) + }else { + print("No filter: fetch Limit: \(self.fetchLimit)") } - gamesFetch.sortDescriptors = [NSSortDescriptor(key: "name", ascending: true)] + gamesFetch.fetchLimit = self.fetchLimit + gamesFetch.sortDescriptors = self.sortDescriptors let gamesFetchRC = NSFetchedResultsController(fetchRequest: gamesFetch, managedObjectContext: CDManager.shared.viewContext, sectionNameKeyPath: nil, cacheName: nil) @@ -44,13 +67,30 @@ class GameStore : NSObject, ObservableObject, NSFetchedResultsControllerDelegate return gamesFetchRC }() - init(console : Console?) { + init(console : Console?, fetchLimit : Int = 0) { super.init() self.consoleFilter = console + self.fetchLimit = fetchLimit + } + + init(gameSeries: GameSeries?, fetchLimit : Int = 0) { + super.init() + self.gameSeriesFilter = gameSeries + self.fetchLimit = fetchLimit } + init(sortDescriptors : [NSSortDescriptor], fetchLimit : Int = 0) { + super.init() + self.sortDescriptors = sortDescriptors + self.fetchLimit = fetchLimit + } + + init(fetchLimit : Int = 0) { + super.init() + self.fetchLimit = fetchLimit + } + func controllerDidChangeContent(_ controller: NSFetchedResultsController) { - print("GameStore::controllerDidChangeContent") self.objectWillChange.send() } } diff --git a/Zockerhoehle/ViewModel/GameViewModel.swift b/Zockerhoehle/ViewModel/GameViewModel.swift index 35f41ec..46dc9d3 100644 --- a/Zockerhoehle/ViewModel/GameViewModel.swift +++ b/Zockerhoehle/ViewModel/GameViewModel.swift @@ -13,64 +13,133 @@ import CoreData import UIKit class GameViewModel : ObservableObject { - var objectWillChange = ObservableObjectPublisher() + var objectWillChange = ObservableObjectPublisher() private var game : Game? { didSet { - if game != nil { - self.name = game!.name - self.inWishlist = game!.inWishlist - self.isDigital = game!.isDigital - self.isFinished = game!.isFinished + guard let game = game else { return } + + self.name = game.name! + self.inWishlist = game.inWishlist + self.isDigital = game.isDigital + self.isFinished = game.isFinished + } + } + + var lentTo : String { + didSet { + guard let game = self.game else { return } + + if lentTo != "" { + game.lentTo = lentTo; } } } var name : String { didSet { - if let game = self.game { - game.name = name - } + guard let game = self.game else { return } + + game.name = name } } var inWishlist : Bool { didSet { - if let game = self.game { - game.inWishlist = inWishlist - } + guard let game = self.game else { return } + + game.inWishlist = inWishlist } } var isDigital : Bool { didSet { - if let game = self.game { - game.isDigital = isDigital - } + guard let game = self.game else { return } + + game.isDigital = isDigital } } var isFinished : Bool { didSet { - if let game = self.game { - game.isFinished = isFinished + guard let game = self.game else { return } + + game.isFinished = isFinished + } + } + + var gameSeries : String { + willSet { + if newValue != "" , let game = self.game { + game.addGameSeries(by: newValue) } } } + var console : Console? { + get { + return self.game?.console + } + } + + var cover : UIImage? { + get { + if let coverImage = self.game?.cover?.image { + return coverImage + } + + return .none + } + + set { + if let game = self.game { + if let coverImage = newValue { + let newCover = Cover(context: CDManager.shared.viewContext) + newCover.game = game + newCover.image = coverImage + game.cover = newCover + } + } + } + } + + func removeGame() { + if let game = self.game { + CDManager.shared.viewContext.delete(game) + } + } + + var NSManagedObjectChangedObserver : AnyCancellable? = .none + init(game : Game) { self.game = game - self.name = game.name + self.name = game.name! self.inWishlist = game.inWishlist self.isDigital = game.isDigital self.isFinished = game.isFinished + self.gameSeries = game.series?.id ?? "" + self.lentTo = game.lentTo ?? "" - _ = NotificationCenter.default.publisher(for: .NSManagedObjectContextObjectsDidChange, object: CDManager.shared.viewContext).sink { - //TODO - self.objectWillChange.send() - if let game = self.game { - print("Game: \(game.name) Notification: \($0.description)") + self.NSManagedObjectChangedObserver = NotificationCenter.default.publisher(for: .NSManagedObjectContextObjectsDidChange, object: CDManager.shared.viewContext).first().sink { notification in + + guard let game = self.game else { return } + guard let userInfo = notification.userInfo else { return } + + var managedObjectIsMatching = false + if let inserts = userInfo[NSInsertedObjectsKey] as? Set { + if inserts.contains(game) { managedObjectIsMatching = true } + } + if let updates = userInfo[NSUpdatedObjectsKey] as? Set { + if updates.contains(game) { managedObjectIsMatching = true } + } + if let deletes = userInfo[NSDeletedObjectsKey] as? Set { + if deletes.contains(game) { managedObjectIsMatching = true } + } + + if managedObjectIsMatching { + self.objectWillChange.send() + print("GameViewModel::NSMangedObjectChanged Update of \(game.name)") } } } diff --git a/Zockerhoehle/Views/AccessoryDetailView.swift b/Zockerhoehle/Views/AccessoryDetailView.swift index d3fa6f0..62150e7 100644 --- a/Zockerhoehle/Views/AccessoryDetailView.swift +++ b/Zockerhoehle/Views/AccessoryDetailView.swift @@ -28,11 +28,3 @@ struct AccessoryDetailView : View { self.accessoryVM = accessoryVM! } } - -#if DEBUG -struct AccessoryDetailView_Previews : PreviewProvider { - static var previews: some View { - AccessoryDetailView(accessoryVM: nil) - } -} -#endif diff --git a/Zockerhoehle/Views/ConsoleAllView.swift b/Zockerhoehle/Views/ConsoleAllView.swift new file mode 100644 index 0000000..7611bde --- /dev/null +++ b/Zockerhoehle/Views/ConsoleAllView.swift @@ -0,0 +1,108 @@ +// +// ConsolesListView.swift +// Zockerhoehle +// +// Created by Julian-Steffen Müller on 01.08.19. +// Copyright © 2019 Julian-Steffen Müller. All rights reserved. +// + +import Foundation +import SwiftUI +import QGrid + +struct ModalAddConsoleToLibrary : View { + @Environment(\.presentationMode) var presentationMode: Binding + + @State var modalConsoleName : String = "" + + private func addConsoleToLibrary() { + //TODO + } + + var addButton : some View { + return Button(action: { + addConsoleToLibrary() + self.presentationMode.wrappedValue.dismiss()}, + label: { Text("Add") }) + .disabled(self.modalConsoleName.trimmingCharacters(in: .whitespacesAndNewlines) == "" ) + } + + var body: some View { + NavigationView { + Form { + TextField("Name", text: $modalConsoleName) + } + .font(.caption) + .navigationBarTitle(Text("New Console")) + .navigationBarItems(leading: Button(action: { self.presentationMode.wrappedValue.dismiss() }, + label: {Text("Cancel")}), + //Add Button is disabled if no name is entered + trailing: addButton) + .navigationViewStyle(StackNavigationViewStyle()) + } + } +} + +struct ConsolesListView : View { + @EnvironmentObject var consoleStore : ConsoleStore + @State var showAddConsoleToLibraryModal: Bool = false + + var body: some View { + /*List(consoleStore.consoles) {console in + NavigationLink(destination: ConsoleLibraryView(console: console)) { + HStack { + if console.logo?.image != nil { + Image(uiImage: console.logo!.image!).resizable().frame(width: 50.0, height: 50.0) + }else { + Image(systemName: "stop").resizable().frame(width: 50.0, height: 50.0) + } + + VStack(alignment: .leading) { + Text("\(console.name)") + Text("Spiele: \(console.games.filter({($0 as! Game).inWishlist == false}).count), Zubehör: \(console.accessories.filter({($0 as! Accessory).inWishlist == false}).count)") + } + } + } + }*/ + QGrid(consoleStore.consoles, columns: 4, columnsInLandscape: 6) { + GridCell(console: $0) + } + .padding() + .navigationBarTitle(Text("Zockerhöhle")) + .navigationBarItems(trailing: + Button(action: { + self.showAddConsoleToLibraryModal = true + }) { + Image(systemName: "plus") + }) + .sheet(isPresented: $showAddConsoleToLibraryModal) { + ModalAddConsoleToLibrary() + } + } +} + +struct GridCell: View { + var console: Console + + var body: some View { + VStack() { + if console.logo?.image != nil { + NavigationLink(destination: ConsoleLibraryView(console: console)) { + VStack { + Image(uiImage: (console.logo?.image)!) + .resizable() + .scaledToFit() + //.shadow(color: .primary, radius: 5) + .padding([.horizontal, .top], 7) + Text(console.shortName ?? console.name!).lineLimit(1) + } + }.buttonStyle(PlainButtonStyle()) + + }else{ + Text("mu") + } + // + //Text(person.lastName).lineLimit(1) + } + } +} diff --git a/Zockerhoehle/Views/ConsoleEditView.swift b/Zockerhoehle/Views/ConsoleEditView.swift new file mode 100644 index 0000000..cef18ae --- /dev/null +++ b/Zockerhoehle/Views/ConsoleEditView.swift @@ -0,0 +1,42 @@ +// +// ConsoleDetailView.swift +// Zockerhoehle +// +// Created by Julian-Steffen Müller on 25.09.19. +// Copyright © 2019 Julian-Steffen Müller. All rights reserved. +// + +import SwiftUI + +struct ConsoleEditView: View { + @ObservedObject var consoleViewModel : ConsoleViewModel + + @State var showLogoImagePicker = false + + var body: some View { + Form { + TextField("Name der Konsole", text: $consoleViewModel.name) + + consoleViewModel.logo.map { + Image (uiImage: $0) + .resizable() + .frame(width:100, height: 100) + } + + Button(action: { self.showLogoImagePicker = true }, label: { Text("Pick Image") }) + } + .navigationBarTitle("Editiere Konsole") + .sheet(isPresented: $showLogoImagePicker) { + ImagePicker.shared.view + } + .onReceive(ImagePicker.shared.objectWillChange, perform: { image in + if let image = image { + self.consoleViewModel.logo = image + } + }) + } + + init(_ console : Console) { + self.consoleViewModel = ConsoleViewModel(console) + } +} diff --git a/Zockerhoehle/Views/ConsoleLibraryView.swift b/Zockerhoehle/Views/ConsoleLibraryView.swift index a5287e6..402d407 100644 --- a/Zockerhoehle/Views/ConsoleLibraryView.swift +++ b/Zockerhoehle/Views/ConsoleLibraryView.swift @@ -8,6 +8,68 @@ import SwiftUI +struct ModalAddToConsoleLibrary : View { + @Environment(\.presentationMode) var presentationMode: Binding + + @State var modalAddName : String = "" + @State var modalAddToWishlist : Bool + @State var modalIsDigital : Bool = false + + @State var isVideogamesSelected : Bool + + var console : Console; + + private func addGameToLibrary() { + let game = Game(context: CDManager.shared.viewContext) + game.name = self.modalAddName + game.console = self.console; + game.inWishlist = self.modalAddToWishlist + game.isDigital = self.modalIsDigital + } + + private func addAccessoryToLibrary() { + let accessory = Accessory(context: CDManager.shared.viewContext) + accessory.name = self.modalAddName + accessory.console = self.console + accessory.inWishlist = self.modalAddToWishlist + } + + var addButton : some View { + return Button(action: { + if (self.isVideogamesSelected) { + self.addGameToLibrary() + + }else{ + self.addAccessoryToLibrary() + } + self.presentationMode.wrappedValue.dismiss()}, + label: { Text("Add") }) + .disabled(self.modalAddName.trimmingCharacters(in: .whitespacesAndNewlines) == "" ) + } + + var body: some View { + NavigationView { + Form { + TextField("Name", text: $modalAddName) + if self.isVideogamesSelected { + Toggle(isOn: $modalIsDigital, label: { + Text("Ist Digital") + }) + } + Toggle(isOn: $modalAddToWishlist, label: { + Text("Auf die Wunschliste") + }) + } + .font(.caption) + .navigationBarTitle(self.isVideogamesSelected ? Text("New Game") : Text("New Accessory")) + .navigationBarItems(leading: Button(action: { self.presentationMode.wrappedValue.dismiss() }, + label: {Text("Cancel")}), + //Add Button is disabled if no name is entered + trailing: addButton) + .navigationViewStyle(StackNavigationViewStyle()) + } + } +} struct ConsoleLibraryView : View { @State var isVideogamesSelected = true @@ -16,20 +78,9 @@ struct ConsoleLibraryView : View { @ObservedObject var accessoryStore : AccessoryStore @State var showWishlist = false + @State var showAddToConsoleLibraryModal: Bool = false + @State var showLogoImagePicker = false - @State var isModal: Bool = false - - var modal: some View { - NavigationView { - Form { - Text("bl") - } - .font(.caption) - .navigationBarTitle(self.isVideogamesSelected ? Text("New Game") : Text("New Accessory")) - .navigationBarItems(trailing: Button(action: { self.isModal = false } ) { Text("Done") }) - } - } - var body: some View { VStack { HStack { @@ -44,14 +95,29 @@ struct ConsoleLibraryView : View { if self.isVideogamesSelected { List { ForEach(gameStore.games.filter({$0.inWishlist == self.showWishlist})) { game in - NavigationLink(destination: GameDetailView(gameVM: GameViewModel(game: game))) { - Text("\(game.name)") + NavigationLink(destination: GameDetailView(game: game)) { + HStack { + /*game.cover.map { + Image(uiImage: $0.image) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(height: 75) + }*/ + + Text("\(game.name!)") + + if game.isDigital { + Image("digitalGame") + .resizable() + .aspectRatio(contentMode: .fit) + .frame(height: 15) + } + } } } } }else { List { - ForEach(accessoryStore.accessories.filter({$0.inWishlist == self.showWishlist})) { accessory in NavigationLink(destination: AccessoryDetailView(accessoryVM: AccessoryViewModel(accessory: accessory))) { Text("\(accessory.name)") @@ -59,23 +125,33 @@ struct ConsoleLibraryView : View { } } } - }.navigationBarTitle(Text("\(self.console?.name ?? "N/A")"), displayMode: .automatic) + } + .navigationBarTitle(Text("\(self.console?.name ?? "N/A")"), displayMode: .automatic) .navigationBarItems(trailing: HStack { - Button(action: { - self.isModal = true - }) { - Image(systemName: "plus") - }.padding(5) - Button(action: { - self.showWishlist.toggle() - }) { - Image(systemName: "star") - }.accentColor(self.showWishlist ? Color.red : Color.blue) + NavigationLink(destination: ConsoleEditView(self.console!)) { + Image(systemName: "pencil.and.ellipsis.rectangle") + } + + Button(action: { self.showAddToConsoleLibraryModal = true }, + label: { Image(systemName: "plus")}) + .padding(5) + + Button(action: { self.showWishlist.toggle()}, + label: { Image("wishlist") + .resizable() + .aspectRatio(contentMode: .fit) + }) + .accentColor(self.showWishlist ? Color.red : Color.blue) }) - .sheet(isPresented: $isModal, onDismiss: {}, content: { - self.modal - }) + .sheet(isPresented: $showAddToConsoleLibraryModal) { + if self.console != nil { + ModalAddToConsoleLibrary(modalAddToWishlist: self.showWishlist, isVideogamesSelected: true, console: self.console!) + }else{ + Text("Fehler: Keine Konsole übergeben") + } + + } } init(console : Console?) { @@ -84,11 +160,3 @@ struct ConsoleLibraryView : View { self.accessoryStore = AccessoryStore(console: console) } } - -#if DEBUG -struct ConsoleLibraryView_Previews : PreviewProvider { - static var previews: some View { - ConsoleLibraryView(console: .none) - } -} -#endif diff --git a/Zockerhoehle/Views/ConsolesListView.swift b/Zockerhoehle/Views/ConsolesListView.swift deleted file mode 100644 index 99e8d0f..0000000 --- a/Zockerhoehle/Views/ConsolesListView.swift +++ /dev/null @@ -1,37 +0,0 @@ -// -// ConsolesListView.swift -// Zockerhoehle -// -// Created by Julian-Steffen Müller on 01.08.19. -// Copyright © 2019 Julian-Steffen Müller. All rights reserved. -// - -import Foundation -import SwiftUI - -struct ConsolesListView : View { - @ObservedObject var consoleStore : ConsoleStore = ConsoleStore() - @ObservedObject var gameStore : GameStore - - var body: some View { - NavigationView { - List { - ForEach(consoleStore.consoles) {console in - //TODO environmentObject should not be passed through the hierarchy - //This is only for fixin a swiftui bug - NavigationLink(destination: ConsoleLibraryView(console: console)) { - Text("\(console.name)") - } - } - }.navigationBarTitle(Text("Zockerhöhle")) - } - } -} - -#if DEBUG -struct ConsolesListView_Previews : PreviewProvider { - static var previews: some View { - ConsolesListView(gameStore: GameStore(console: .none)) - } -} -#endif diff --git a/Zockerhoehle/Views/GameDetailView.swift b/Zockerhoehle/Views/GameDetailView.swift index a817d63..7e00e17 100644 --- a/Zockerhoehle/Views/GameDetailView.swift +++ b/Zockerhoehle/Views/GameDetailView.swift @@ -9,54 +9,146 @@ import SwiftUI struct GameDetailView : View { - //@Binding var g : Game @ObservedObject var gameVM : GameViewModel + @State private var showDeleteAlert : Bool = false @State var hasFinishedDate : Bool = false @State var playthroughDate : Date = Date() + @EnvironmentObject var gameSeriesStore : GameSeriesStore + @Environment(\.presentationMode) var presentationMode: Binding + + + @State var showingPicker = false + + @State var image : Image? = nil + + @State var isLent : Bool = false + + var gameSeriesPicker : some View { + Picker(selection: $gameVM.gameSeries, label: + Text("Spieleserie") + , content: { + Text("Keine").tag("") + ForEach(gameSeriesStore.gameSeries) { gameSeries in + Text("\(gameSeries.name)").tag(gameSeries.id) + } + }) + } + + var imageCoverSection : some View { + Section { + VStack{ + Button("Show image picker") { + self.showingPicker = true + } + + if self.gameVM.cover != nil { + Image(uiImage: self.gameVM.cover!) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(height: 100) + } + } + } + } + + var body: some View { Form { - TextField("Videogame name", text: $gameVM.name) + Section { + TextField("Videogame name", text: $gameVM.name) - Toggle(isOn: $gameVM.isDigital, label: { - Text("Nur Digital") - }) - - Toggle(isOn: $gameVM.inWishlist, label: { - Text("In Wunschliste") - }) - - Toggle(isOn: $gameVM.isFinished , label: { - Text("Durchgezockt") - }) - - if gameVM.isFinished { - Toggle(isOn: $hasFinishedDate, label: { - Text("Gibts ein Datum") + //Gray color should indicate immutable data + Text("\(gameVM.console?.name ?? "No console")").foregroundColor(.gray) + + Toggle(isOn: $gameVM.isDigital, label: { + Text("Nur Digital") }) + + Toggle(isOn: $gameVM.inWishlist, label: { + Text("In Wunschliste") + }) + + Toggle(isOn: $isLent, label: { + Text("Verliehen?") + }).onReceive(gameVM.objectWillChange) { _ in + self.isLent = self.gameVM.lentTo != ""; + }.onAppear() { + self.isLent = self.gameVM.lentTo != ""; + } + + if isLent { + TextField("Verliehen an", text: $gameVM.lentTo) + } + + gameSeriesPicker + + Toggle(isOn: $gameVM.isFinished , label: { + Text("Durchgezockt") + }) + + if gameVM.isFinished { + Toggle(isOn: $hasFinishedDate, label: { + Text("Gibts ein Datum") + }) + } + + if hasFinishedDate && gameVM.isFinished { + DatePicker("Durchgezockt am", + selection: $playthroughDate, + in: ...Date(), + displayedComponents: [.date]) + } } - if hasFinishedDate && gameVM.isFinished { - DatePicker("Durchgezockt am", - selection: $playthroughDate, - in: ...Date(), - displayedComponents: [.date]) + imageCoverSection + + Section{ + gameDeleteButton } - - }.navigationBarTitle(Text("\(gameVM.name)"), displayMode: .automatic) - + + } + .navigationBarTitle(Text("\(gameVM.name)"), displayMode: .automatic) + .sheet(isPresented: $showingPicker, + onDismiss: { + // do whatever you need here + }, content: { + ImagePicker.shared.view + }) + .onReceive(ImagePicker.shared.objectWillChange) { image in + if let image = image { + self.gameVM.cover = image + } + } + } + + //Irgendwie gibt es einen Bug der den Alert im navigationBarItem zwei mal aufruft. + //Deswegen muss der delete Button is auf weiteres in die Form + var gameDeleteButton : some View { + Button(action: { + self.showDeleteAlert = true + }, label: { + Spacer() + Text("Spiel löschen") + Spacer() + //Image(systemName: "trash").foregroundColor(.red) + }) + .accentColor(.red) + .alert(isPresented: $showDeleteAlert, content: { + Alert(title: Text("Aus Zockerhöhle entfernen"), message: Text("Willst du '\(self.gameVM.name)' wirklich aus der Zockerhöhle werfen?"), primaryButton: Alert.Button.destructive(Text("Ja!"), action: { + //self.presentationMode.value.dismiss() + self.gameVM.removeGame() + }), secondaryButton: Alert.Button.cancel(Text("Lieber doch nicht"))) + }) } init(gameVM : GameViewModel?) { self.gameVM = gameVM! } -} - -#if DEBUG -struct GameDetailView_Previews : PreviewProvider { - static var previews: some View { - GameDetailView(gameVM: nil) + + init(game : Game) { + self.gameVM = GameViewModel(game: game) } + } -#endif diff --git a/Zockerhoehle/Views/GamePickupsView.swift b/Zockerhoehle/Views/GamePickupsView.swift new file mode 100644 index 0000000..f104e27 --- /dev/null +++ b/Zockerhoehle/Views/GamePickupsView.swift @@ -0,0 +1,23 @@ +// +// LatestGamePickupsView.swift +// Zockerhoehle +// +// Created by Julian-Steffen Müller on 24.09.19. +// Copyright © 2019 Julian-Steffen Müller. All rights reserved. +// + +import SwiftUI + +struct GamePickupsView: View { + @ObservedObject var gameStore = GameStore(sortDescriptors: [NSSortDescriptor(key: "createdAt", ascending: false), NSSortDescriptor(key: "name", ascending: true)], fetchLimit: 50) + + var body: some View { + List(gameStore.games) { game in + NavigationLink(destination: GameDetailView(game: game)) { + Text("\(game.name!)") + } + } + .padding(.top) // Workaround, damit die Liste beim scrollen nicht unter der NavigationView angezeigt wird + .navigationBarTitle(Text("Latest Pickups")) + } +} diff --git a/Zockerhoehle/Views/GameSeriesAllView.swift b/Zockerhoehle/Views/GameSeriesAllView.swift new file mode 100644 index 0000000..ab50bdd --- /dev/null +++ b/Zockerhoehle/Views/GameSeriesAllView.swift @@ -0,0 +1,89 @@ +// +// GameSeriesListView.swift +// Zockerhoehle +// +// Created by Julian-Steffen Müller on 05.08.19. +// Copyright © 2019 Julian-Steffen Müller. All rights reserved. +// + +import SwiftUI + +struct GameSeriesAllView: View { + @EnvironmentObject var gameSeriesStore : GameSeriesStore + + @State var modalVisible : Bool = false + @State var modalAddName : String = "" + + @State var showingPicker : Bool = false + + @State var image : UIImage? + + var modalAddGameSeries: some View { + NavigationView { + Form { + TextField("Name", text: $modalAddName) + self.image.map { Image(uiImage: $0).resizable().frame(width: 100, height: 100)} + + Button(action: { self.showingPicker = true}, label: { Text("Wähle Bild") }) + } + .navigationBarTitle(Text("New Game Series")) + .navigationBarItems(leading: Button(action: {self.modalVisible = false }, label: {Text("Cancel")}) + //Add Button is disabled if no name is entered + , trailing: Button(action: { + let gameSeries = GameSeries(context: CDManager.shared.viewContext) + gameSeries.name = self.modalAddName + if let image = self.image { + let cover = GameSeriesCover(context: CDManager.shared.viewContext) + cover.image = image + cover.gameSeries = gameSeries + gameSeries.cover = cover + } + self.modalVisible = false + }) { Text("Add") }.disabled(self.modalAddName.trimmingCharacters(in: .whitespacesAndNewlines) == "" )) + .sheet(isPresented: $showingPicker, + onDismiss: { + + }, content: { + ImagePicker.shared.view + }) + } + } + + func resetModalVariables() { + self.modalAddName = "" + } + + var body: some View { + List(gameSeriesStore.gameSeries) { gameSeries in + NavigationLink(destination: GameSeriesLibraryView(gameSeries: gameSeries)) { + HStack { + gameSeries.cover?.image.map { + Image(uiImage: $0) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(height: 50) + } + VStack(alignment: .leading) { + Text(gameSeries.name) + //Sonst wird der TExt kurioser Weise abgeschnitten wenn ein Bild davor ist. Hier nochmal später rein + //schauen + .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .leading) + Text("Spiele: \(gameSeries.games.filter({!($0 as! Game).inWishlist}).count ?? 0)/\(gameSeries.games.count ?? 0)").font(.caption) + } + + } + } + } + .padding() + .navigationBarTitle(Text("Videospielserien")) + .navigationBarItems(trailing: + Button(action: { + self.modalVisible = true + }) { + Image(systemName: "plus") + }) + .sheet(isPresented: $modalVisible, onDismiss: {}, content: { + self.modalAddGameSeries + }) + } +} diff --git a/Zockerhoehle/Views/GameSeriesEditView.swift b/Zockerhoehle/Views/GameSeriesEditView.swift new file mode 100644 index 0000000..f36dd66 --- /dev/null +++ b/Zockerhoehle/Views/GameSeriesEditView.swift @@ -0,0 +1,42 @@ +// +// ConsoleDetailView.swift +// Zockerhoehle +// +// Created by Julian-Steffen Müller on 25.09.19. +// Copyright © 2019 Julian-Steffen Müller. All rights reserved. +// + +import SwiftUI + +struct GameSeriesEditView: View { + @ObservedObject var gameSeriesViewModel : GameSeriesViewModel + + @State var showCoverImagePicker = false + + var body: some View { + Form { + TextField("Name der Konsole", text: $gameSeriesViewModel.name) + + gameSeriesViewModel.cover.map { + Image (uiImage: $0) + .resizable() + .frame(width:100, height: 100) + } + + Button(action: { self.showCoverImagePicker = true }, label: { Text("Pick Image") }) + } + .navigationBarTitle("Editiere Spieleserie") + .sheet(isPresented: $showCoverImagePicker) { + ImagePicker.shared.view + } + .onReceive(ImagePicker.shared.objectWillChange, perform: { image in + if let image = image { + self.gameSeriesViewModel.cover = image + } + }) + } + + init(_ gameSeries : GameSeries) { + self.gameSeriesViewModel = GameSeriesViewModel(gameSeries) + } +} diff --git a/Zockerhoehle/Views/GameSeriesLibraryView.swift b/Zockerhoehle/Views/GameSeriesLibraryView.swift new file mode 100644 index 0000000..a6294e9 --- /dev/null +++ b/Zockerhoehle/Views/GameSeriesLibraryView.swift @@ -0,0 +1,50 @@ +// +// GameSeriesDetailView.swift +// Zockerhoehle +// +// Created by Julian-Steffen Müller on 05.08.19. +// Copyright © 2019 Julian-Steffen Müller. All rights reserved. +// + +import SwiftUI + +struct GameSeriesLibraryView: View { + @ObservedObject var gameStore : GameStore + @State var isDetailActivated : Bool = false + + var gameSeries : GameSeries? + + var body: some View { + List(gameStore.games) { game in + NavigationLink(destination: GameDetailView(game: game)) { + Text("\(game.name!)") + + if game.isDigital { + Image("digitalGame") + .resizable() + .aspectRatio(contentMode: .fit) + .frame(height: 15) + } + + if game.inWishlist { + Image("wishlist") + .resizable() + .aspectRatio(contentMode: .fit) + .frame(height: 15) + } + } + } + .navigationBarTitle(Text(self.gameSeries?.name ?? "n/a")) + .navigationBarItems(trailing: + NavigationLink(destination: GameSeriesEditView(self.gameSeries!)) { + Image(systemName: "pencil.and.ellipsis.rectangle") + } + ) + .padding() + } + + init(gameSeries: GameSeries?) { + self.gameSeries = gameSeries + gameStore = GameStore(gameSeries: gameSeries) + } +} diff --git a/Zockerhoehle/Views/MainView.swift b/Zockerhoehle/Views/MainView.swift new file mode 100644 index 0000000..508fb97 --- /dev/null +++ b/Zockerhoehle/Views/MainView.swift @@ -0,0 +1,27 @@ +// +// MainView.swift +// Zockerhoehle +// +// Created by Julian-Steffen Müller on 05.08.19. +// Copyright © 2019 Julian-Steffen Müller. All rights reserved. +// + +import SwiftUI + +struct MainView: View { + var body: some View { + Overview() +// TabView { +// ConsolesListView() +// .tabItem({ Text("Konsolen") }) +// .tag(1) +// GameSeriesAllView() +// .tabItem({ Text("Serien") }) +// .tag(2) +// Overview() +// .tabItem({ Text("Überischt") }) +// .tag(0) +// } + + } +} diff --git a/Zockerhoehle/Views/Overview.swift b/Zockerhoehle/Views/Overview.swift new file mode 100644 index 0000000..ffa9794 --- /dev/null +++ b/Zockerhoehle/Views/Overview.swift @@ -0,0 +1,206 @@ +// +// Overview.swift +// Zockerhoehle +// +// Created by Julian-Steffen Müller on 24.09.19. +// Copyright © 2019 Julian-Steffen Müller. All rights reserved. +// + +import SwiftUI + +struct OverviewHeader : View { + private var destination : Destination + private var title : String + + init(title : String, destination : Destination) { + self.title = title + self.destination = destination + } + + var body : some View { + HStack { + Text("\(self.title)") + .font(.headline) + .padding(.leading, 15) + .padding(.top, 5) + Spacer() + NavigationLink(destination: self.destination) { + Text("Alle anzeigen") + .font(.caption) + .padding(.top, 10) + .padding(.trailing, 15) + } + } + } +} + +struct Overview: View { + @EnvironmentObject var consoleStore : ConsoleStore + @ObservedObject var gameStore : GameStore = GameStore(sortDescriptors: [NSSortDescriptor(key: "createdAt", ascending: false), NSSortDescriptor(key: "name", ascending: true)], fetchLimit: 10) + @EnvironmentObject var gameSeriesStore : GameSeriesStore + @ObservedObject var featuredStore = FeaturedStore() + + var ConsolesOverview : some View { + VStack(alignment: .leading) { + OverviewHeader(title: "Aktivste Konsolen", destination: ConsolesListView()) + + if consoleStore.consoles.count == 0 { + Text("No consoles") + }else{ + ScrollView(.horizontal, showsIndicators: false) { + HStack(alignment: .top, spacing: 0) { + ForEach(consoleStore.consoles.sorted(by: { Console.sortConsoleByNewestGame(consoleA: $0, consoleB: $1)})) { console in + NavigationLink(destination: ConsoleLibraryView(console: console)) { + VStack(alignment: .leading) { + Group { + console.logo?.image.map { + Image(uiImage: $0) + .resizable() + .padding(10) + .cornerRadius(5) + } + } + .frame(width: 100, height: 100) + //.background(Color(UIColor.lightGray)) + //.cornerRadius(5) + + Text("\(console.name!)") + .font(.caption) + .frame(width: 100) + + }.padding(.leading, 15) + }.buttonStyle(PlainButtonStyle()) + } + }.padding(.trailing, 15) + } + } + } + } + + var GamesOverview : some View { + VStack(alignment: .leading) { + //TODO + OverviewHeader(title: "Latest Game Pickups", destination: GamePickupsView()) + + if gameStore.games.count == 0 { + Text("No Games") + }else { + ScrollView(.horizontal, showsIndicators: false) { + HStack(alignment: .top, spacing: 0) { + ForEach(gameStore.games) { game in + NavigationLink(destination: GameDetailView(game: game)) { + VStack(alignment: .leading) { + Group { + /*game.cover?.image.map { + Image(uiImage: $0) + .resizable() + .padding(10) + .cornerRadius(5) + }*/ + Text("\(self.gameStore.fetchLimit)") + } + .frame(width: 100, height: 100) + .background(Color(UIColor.lightGray)) + .cornerRadius(5) + + Text("\(game.name!)") + .font(.caption) + .frame(width: 100) + + }.padding(.leading, 15) + }.buttonStyle(PlainButtonStyle()) + } + }.padding(.trailing, 15) + } + } + } + } + + var GameSeriesOverview : some View { + VStack(alignment: .leading) { + OverviewHeader(title: "Spielserien", destination: GameSeriesAllView()) + + if gameSeriesStore.gameSeries.count == 0 { + Text("No game series") + }else{ + ScrollView(.horizontal, showsIndicators: false) { + HStack(alignment: .top, spacing: 0) { + ForEach(gameSeriesStore.gameSeries) { gameSeries in + NavigationLink(destination: GameSeriesLibraryView(gameSeries: gameSeries)) { + VStack(alignment: .leading) { + Group { + /*game.cover?.image.map { + Image(uiImage: $0) + .resizable() + .padding(10) + .cornerRadius(5) + }*/ + Text("S") + } + .frame(width: 100, height: 100) + .background(Color(UIColor.lightGray)) + .cornerRadius(5) + + Text("\(gameSeries.name)") + .font(.caption) + .frame(width: 100) + + }.padding(.leading, 15) + }.buttonStyle(PlainButtonStyle()) + } + }.padding(.trailing, 15) + } + } + } + } + + var featured : some View { + VStack(alignment: .leading) { + HStack { + Text("Gerade Aktuell") + .font(.headline) + .padding(.leading, 15) + .padding(.top, 5) + Spacer() + } + HStack { + Spacer() + if featuredStore.featuredConsole != nil { + NavigationLink(destination: ConsoleLibraryView(console: featuredStore.featuredConsole!)) { + if featuredStore.featuredConsole?.logo?.image != nil { + featuredStore.featuredConsole?.logo?.image.map { + Image(uiImage: $0).resizable().frame(width: 200, height: 200) + } + }else{ + //TODO: Symbol für fehlendes Bild + Image(systemName: "cave").frame(width: 200, height: 200) + } + }.buttonStyle(PlainButtonStyle()) + }else { + Text("No featured console") + } + Spacer() + } + + } + } + + var body: some View { + NavigationView { + ScrollView(.vertical, showsIndicators: true) { + VStack { + featured + Divider() + ConsolesOverview + Divider() + GamesOverview + Divider() + GameSeriesOverview + }.navigationBarTitle("Zockerhöhle") + }.navigationBarItems(trailing: + NavigationLink(destination: SettingsView(), label: { Image(systemName: "gear") }) + ) + }.navigationViewStyle(StackNavigationViewStyle()) + + } +} diff --git a/Zockerhoehle/Views/SettingsView.swift b/Zockerhoehle/Views/SettingsView.swift new file mode 100644 index 0000000..3170d52 --- /dev/null +++ b/Zockerhoehle/Views/SettingsView.swift @@ -0,0 +1,54 @@ +// +// Settings.swift +// Zockerhoehle +// +// Created by Julian-Steffen Müller on 20.11.19. +// Copyright © 2019 Julian-Steffen Müller. All rights reserved. +// + +import SwiftUI + +//extension String: Identifiable { +// public var id: String { +// return self +// } +//} + +struct SettingsView: View { + @State var backupImportFileIndex = -1 + @State var backupImportUpdateExistingEntrys = false + @State var importFiles : [String] = LibraryImport().backupFiles() + + func exportLibrary() { + let games = GameStore().games + let consoles = ConsoleStore().consoles + let gameSeries = GameSeriesStore().gameSeries + let accessories = AccessoryStore().accessories + + let libExport = LibraryExporter(games: games, consoles: consoles, gameSeries: gameSeries, accessories: accessories) + + libExport.export(name: Date().formattedInTimeZone()) + importFiles = LibraryImport().backupFiles() + } + + var body: some View { + Form { + Section(header: Text("Backup Library")) { + Button(action: { self.exportLibrary() }, label: { Text("Do Backup") }) + } + + Section(header: Text("Restore Library")) { + Picker(selection: $backupImportFileIndex, label: Text("Choose Backup")) { + Text("None").tag(-1) + ForEach(0 ..< (importFiles.count), id: \.self) { + Text(LibraryImport.backupName(from: self.importFiles[$0]) ?? "n/a").tag($0) + }.navigationBarTitle("Backups") + } + Toggle("Yes, I want to reset my Database", isOn: $backupImportUpdateExistingEntrys) + Button(action: { + LibraryImport().importLIB(from: self.importFiles[self.backupImportFileIndex]) + }, label: { Text("Reset & Import") }).disabled(backupImportFileIndex < 0 || !backupImportUpdateExistingEntrys) + } + }.navigationBarTitle("Einstellungen") + } +} diff --git a/Zockerhoehle/Zockerhoehle.entitlements b/Zockerhoehle/Zockerhoehle.entitlements new file mode 100644 index 0000000..8497878 --- /dev/null +++ b/Zockerhoehle/Zockerhoehle.entitlements @@ -0,0 +1,16 @@ + + + + + aps-environment + development + com.apple.developer.icloud-container-identifiers + + iCloud.Zockerhoehle + + com.apple.developer.icloud-services + + CloudKit + + + diff --git a/Zockerhoehle/Zockerhoehle.xcdatamodeld/Zockerhoehle.xcdatamodel/contents b/Zockerhoehle/Zockerhoehle.xcdatamodeld/Zockerhoehle.xcdatamodel/contents index 0f3e4d3..937d382 100644 --- a/Zockerhoehle/Zockerhoehle.xcdatamodeld/Zockerhoehle.xcdatamodel/contents +++ b/Zockerhoehle/Zockerhoehle.xcdatamodeld/Zockerhoehle.xcdatamodel/contents @@ -1,20 +1,24 @@ - + + - + + - - - + + + + + @@ -23,12 +27,15 @@ + - + + + @@ -37,20 +44,26 @@ - - - + + + + + + + + - - - - - + + + + + + \ No newline at end of file