diff --git a/.gitignore b/.gitignore index 4cc35c4..a377531 100644 --- a/.gitignore +++ b/.gitignore @@ -124,7 +124,5 @@ FullStackCodingBot/Pods !*.xcworkspace/contents.xcworkspacedata **/xcshareddata/WorkspaceSettings.xcsettings -## Identifier -**/IdentifierManager.swift # End of https://www.toptal.com/developers/gitignore/api/xcode,swift diff --git a/FullStackCodingBot/FullStackCodingBot.xcodeproj/project.pbxproj b/FullStackCodingBot/FullStackCodingBot.xcodeproj/project.pbxproj index 3eb74bf..64a2b49 100644 --- a/FullStackCodingBot/FullStackCodingBot.xcodeproj/project.pbxproj +++ b/FullStackCodingBot/FullStackCodingBot.xcodeproj/project.pbxproj @@ -13,6 +13,7 @@ 710D0D422693EA3200244A16 /* GameViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 710D0D412693EA3200244A16 /* GameViewController.swift */; }; 710D0D442693EA6900244A16 /* GameViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 710D0D432693EA6900244A16 /* GameViewModel.swift */; }; 710D0D4F2693F6E500244A16 /* ItemCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 710D0D4D2693F6E500244A16 /* ItemCell.swift */; }; + 7123C74026FD79A70049E6DC /* AlertScene.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7123C73F26FD79A70049E6DC /* AlertScene.swift */; }; 712F500A26A831D900186948 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 712F500926A831D900186948 /* GoogleService-Info.plist */; }; 712F500B26A831D900186948 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 712F500926A831D900186948 /* GoogleService-Info.plist */; }; 71307A8926A529F400C3191F /* LevelUpButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71307A8826A529F400C3191F /* LevelUpButton.swift */; }; @@ -40,15 +41,18 @@ 718F224926C377B700B2648C /* ErrorManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 718F224826C377B700B2648C /* ErrorManager.swift */; }; 719A6A9B26B11208006AC991 /* NetworkDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 719A6A9A26B11208006AC991 /* NetworkDTO.swift */; }; 719AB9DE26B7ADF2001B5913 /* SettingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 719AB9DD26B7ADF2001B5913 /* SettingViewController.swift */; }; + 719D0D9026F1863200913BBE /* MainScene.swift in Sources */ = {isa = PBXBuildFile; fileRef = 719D0D8F26F1863200913BBE /* MainScene.swift */; }; + 719D0D9226F186F100913BBE /* GameScene.swift in Sources */ = {isa = PBXBuildFile; fileRef = 719D0D9126F186F100913BBE /* GameScene.swift */; }; + 719D0D9426F1875100913BBE /* GameHelperScene.swift in Sources */ = {isa = PBXBuildFile; fileRef = 719D0D9326F1875100913BBE /* GameHelperScene.swift */; }; + 719D0D9626F1889700913BBE /* SceneType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 719D0D9526F1889700913BBE /* SceneType.swift */; }; 719DA2B126A7FC9900624C61 /* JoystixMonospace-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 719DA2B026A7FC9800624C61 /* JoystixMonospace-Regular.ttf */; }; - 71A5518026A91C280083A4B0 /* DatabaseManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71A5517F26A91C280083A4B0 /* DatabaseManager.swift */; }; - 71A5518226A9277C0083A4B0 /* DatabaseManagerType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71A5518126A9277C0083A4B0 /* DatabaseManagerType.swift */; }; + 71A5518026A91C280083A4B0 /* FirebaseManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71A5517F26A91C280083A4B0 /* FirebaseManager.swift */; }; + 71A5518226A9277C0083A4B0 /* FirebaseManagerType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71A5518126A9277C0083A4B0 /* FirebaseManagerType.swift */; }; 71BC752E26A940A10021BA1A /* DataFormatManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71BC752D26A940A10021BA1A /* DataFormatManager.swift */; }; 71DF64B92692C13A00557693 /* ShopViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71DF64B82692C13A00557693 /* ShopViewController.swift */; }; 71DF64BD2692C18F00557693 /* RankViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71DF64BC2692C18F00557693 /* RankViewController.swift */; }; 71DF64BF2692C1C600557693 /* ItemViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71DF64BE2692C1C600557693 /* ItemViewController.swift */; }; 71DF653C2692F90100557693 /* TransitionModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71DF653B2692F90100557693 /* TransitionModel.swift */; }; - 71DF653E2692F92700557693 /* Scene.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71DF653D2692F92700557693 /* Scene.swift */; }; 71DF65412692F94E00557693 /* MainViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71DF65402692F94E00557693 /* MainViewModel.swift */; }; 71DF65432692F95800557693 /* ShopViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71DF65422692F95800557693 /* ShopViewModel.swift */; }; 71DF65472692F97000557693 /* RankViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71DF65462692F97000557693 /* RankViewModel.swift */; }; @@ -57,12 +61,8 @@ 71DF65512692FBCC00557693 /* SceneCoordinatorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71DF65502692FBCC00557693 /* SceneCoordinatorType.swift */; }; 71DF65532692FBE500557693 /* SceneCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71DF65522692FBE500557693 /* SceneCoordinator.swift */; }; 71DF65552692FD1400557693 /* CommonViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71DF65542692FD1400557693 /* CommonViewModel.swift */; }; - 71E2B37626AE5E7F00AE496D /* PersistenceStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71E2B37526AE5E7F00AE496D /* PersistenceStorage.swift */; }; - 71E2B37826AE602200AE496D /* PersistenceStorageType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71E2B37726AE602200AE496D /* PersistenceStorageType.swift */; }; 71E2FD9B26B90C5A0032A54B /* SettingSwitchController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71E2FD9A26B90C5A0032A54B /* SettingSwitchController.swift */; }; 71E91E1726BA33190016386E /* Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71E91E1626BA33190016386E /* Extension.swift */; }; - 71F0D3D926CD161700E45283 /* PersistenceStorageType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71E2B37726AE602200AE496D /* PersistenceStorageType.swift */; }; - 71F0D3DA26CD161A00E45283 /* PersistenceStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71E2B37526AE5E7F00AE496D /* PersistenceStorage.swift */; }; 71F0D3DB26CD16D500E45283 /* UnitInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4F4304526CA2E4400F046E9 /* UnitInfo.swift */; }; A0DEDFDADFEC4895FB093823 /* Pods_Full_Stack_Full_StackTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CFA3377E4ACD3B197620DEA8 /* Pods_Full_Stack_Full_StackTests.framework */; }; E4150E9726B8F3E00041DD32 /* TypeWriterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4150E9626B8F3E00041DD32 /* TypeWriterView.swift */; }; @@ -82,6 +82,14 @@ E41C85DA269C4E8F008F2E91 /* PauseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41C85D9269C4E8F008F2E91 /* PauseViewController.swift */; }; E41C85DD269C50F0008F2E91 /* PauseViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41C85DC269C50F0008F2E91 /* PauseViewModel.swift */; }; E41C85DF269C5149008F2E91 /* PauseButtonController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41C85DE269C5149008F2E91 /* PauseButtonController.swift */; }; + E4336F5B26F0638A0049A5D8 /* Storage.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4336F5A26F0638A0049A5D8 /* Storage.swift */; }; + E4336F5D26F083650049A5D8 /* GameStorageType.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4336F5C26F083650049A5D8 /* GameStorageType.swift */; }; + E4336F5F26F083840049A5D8 /* BackUpCenterType.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4336F5E26F083840049A5D8 /* BackUpCenterType.swift */; }; + E4336F6126F083A20049A5D8 /* BackUpCenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4336F6026F083A20049A5D8 /* BackUpCenter.swift */; }; + E4336F6326F083C30049A5D8 /* CoreDataManagerType.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4336F6226F083C30049A5D8 /* CoreDataManagerType.swift */; }; + E4336F6826F1AB8B0049A5D8 /* CoreDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4336F6726F1AB8B0049A5D8 /* CoreDataManager.swift */; }; + E4336F6A26F1B7500049A5D8 /* GameStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4336F6926F1B7500049A5D8 /* GameStorage.swift */; }; + E4336F6C26F1BC1A0049A5D8 /* StorageType.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4336F6B26F1BC1A0049A5D8 /* StorageType.swift */; }; E43B64A226ABD81C001D9896 /* GameBackgroundView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E43B64A126ABD81C001D9896 /* GameBackgroundView.swift */; }; E43B64A426AC2384001D9896 /* TimeBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E43B64A326AC2384001D9896 /* TimeBarView.swift */; }; E45B0AB82692929F006FDE3D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E45B0AB72692929F006FDE3D /* Assets.xcassets */; }; @@ -164,6 +172,7 @@ 710D0D412693EA3200244A16 /* GameViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameViewController.swift; sourceTree = ""; }; 710D0D432693EA6900244A16 /* GameViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameViewModel.swift; sourceTree = ""; }; 710D0D4D2693F6E500244A16 /* ItemCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemCell.swift; sourceTree = ""; }; + 7123C73F26FD79A70049E6DC /* AlertScene.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertScene.swift; sourceTree = ""; }; 712F500926A831D900186948 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; 71307A8826A529F400C3191F /* LevelUpButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LevelUpButton.swift; sourceTree = ""; }; 71307A8A26A52A1F00C3191F /* LevelUpButton.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = LevelUpButton.xib; sourceTree = ""; }; @@ -190,15 +199,18 @@ 718F224826C377B700B2648C /* ErrorManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorManager.swift; sourceTree = ""; }; 719A6A9A26B11208006AC991 /* NetworkDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkDTO.swift; sourceTree = ""; }; 719AB9DD26B7ADF2001B5913 /* SettingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingViewController.swift; sourceTree = ""; }; + 719D0D8F26F1863200913BBE /* MainScene.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainScene.swift; sourceTree = ""; }; + 719D0D9126F186F100913BBE /* GameScene.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameScene.swift; sourceTree = ""; }; + 719D0D9326F1875100913BBE /* GameHelperScene.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameHelperScene.swift; sourceTree = ""; }; + 719D0D9526F1889700913BBE /* SceneType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneType.swift; sourceTree = ""; }; 719DA2B026A7FC9800624C61 /* JoystixMonospace-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "JoystixMonospace-Regular.ttf"; sourceTree = ""; }; - 71A5517F26A91C280083A4B0 /* DatabaseManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatabaseManager.swift; sourceTree = ""; }; - 71A5518126A9277C0083A4B0 /* DatabaseManagerType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatabaseManagerType.swift; sourceTree = ""; }; + 71A5517F26A91C280083A4B0 /* FirebaseManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirebaseManager.swift; sourceTree = ""; }; + 71A5518126A9277C0083A4B0 /* FirebaseManagerType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirebaseManagerType.swift; sourceTree = ""; }; 71BC752D26A940A10021BA1A /* DataFormatManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataFormatManager.swift; sourceTree = ""; }; 71DF64B82692C13A00557693 /* ShopViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShopViewController.swift; sourceTree = ""; }; 71DF64BC2692C18F00557693 /* RankViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RankViewController.swift; sourceTree = ""; }; 71DF64BE2692C1C600557693 /* ItemViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemViewController.swift; sourceTree = ""; }; 71DF653B2692F90100557693 /* TransitionModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransitionModel.swift; sourceTree = ""; }; - 71DF653D2692F92700557693 /* Scene.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Scene.swift; sourceTree = ""; }; 71DF65402692F94E00557693 /* MainViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainViewModel.swift; sourceTree = ""; }; 71DF65422692F95800557693 /* ShopViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShopViewModel.swift; sourceTree = ""; }; 71DF65462692F97000557693 /* RankViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RankViewModel.swift; sourceTree = ""; }; @@ -207,8 +219,6 @@ 71DF65502692FBCC00557693 /* SceneCoordinatorType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneCoordinatorType.swift; sourceTree = ""; }; 71DF65522692FBE500557693 /* SceneCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneCoordinator.swift; sourceTree = ""; }; 71DF65542692FD1400557693 /* CommonViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommonViewModel.swift; sourceTree = ""; }; - 71E2B37526AE5E7F00AE496D /* PersistenceStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersistenceStorage.swift; sourceTree = ""; }; - 71E2B37726AE602200AE496D /* PersistenceStorageType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersistenceStorageType.swift; sourceTree = ""; }; 71E2FD9A26B90C5A0032A54B /* SettingSwitchController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingSwitchController.swift; sourceTree = ""; }; 71E91E1626BA33190016386E /* Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Extension.swift; sourceTree = ""; }; A9E77F924F0C6B24A39E65B2 /* Pods_Full_Stack.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Full_Stack.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -231,6 +241,14 @@ E41C85D9269C4E8F008F2E91 /* PauseViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PauseViewController.swift; sourceTree = ""; }; E41C85DC269C50F0008F2E91 /* PauseViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PauseViewModel.swift; sourceTree = ""; }; E41C85DE269C5149008F2E91 /* PauseButtonController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PauseButtonController.swift; sourceTree = ""; }; + E4336F5A26F0638A0049A5D8 /* Storage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Storage.swift; sourceTree = ""; }; + E4336F5C26F083650049A5D8 /* GameStorageType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameStorageType.swift; sourceTree = ""; }; + E4336F5E26F083840049A5D8 /* BackUpCenterType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackUpCenterType.swift; sourceTree = ""; }; + E4336F6026F083A20049A5D8 /* BackUpCenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackUpCenter.swift; sourceTree = ""; }; + E4336F6226F083C30049A5D8 /* CoreDataManagerType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataManagerType.swift; sourceTree = ""; }; + E4336F6726F1AB8B0049A5D8 /* CoreDataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataManager.swift; sourceTree = ""; }; + E4336F6926F1B7500049A5D8 /* GameStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameStorage.swift; sourceTree = ""; }; + E4336F6B26F1BC1A0049A5D8 /* StorageType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorageType.swift; sourceTree = ""; }; E43B64A126ABD81C001D9896 /* GameBackgroundView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameBackgroundView.swift; sourceTree = ""; }; E43B64A326AC2384001D9896 /* TimeBarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeBarView.swift; sourceTree = ""; }; E45B0AAB2692929D006FDE3D /* Full Stack.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Full Stack.app"; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -335,11 +353,9 @@ 715BB20426943CD100286B31 /* Service */ = { isa = PBXGroup; children = ( + E4336F6426F08A8D0049A5D8 /* Storage */, + 717EAD3B26A656E200E0CDB7 /* GameLogic */, 718F224726C377AA00B2648C /* ErrorManager */, - 71BC752C26A940870021BA1A /* DataFormatManager */, - 71A5517E26A91C160083A4B0 /* FirebaseStorage */, - 717EAD3B26A656E200E0CDB7 /* GameManager */, - 717EAD3A26A656D600E0CDB7 /* PersistenceStorage */, ); path = Service; sourceTree = ""; @@ -378,18 +394,7 @@ path = HowToPlay; sourceTree = ""; }; - 717EAD3A26A656D600E0CDB7 /* PersistenceStorage */ = { - isa = PBXGroup; - children = ( - E4DF613A26A845670061F6E9 /* AdStorageType.swift */, - E4DF613C26A845DA0061F6E9 /* AdStorage.swift */, - 71E2B37526AE5E7F00AE496D /* PersistenceStorage.swift */, - 71E2B37726AE602200AE496D /* PersistenceStorageType.swift */, - ); - path = PersistenceStorage; - sourceTree = ""; - }; - 717EAD3B26A656E200E0CDB7 /* GameManager */ = { + 717EAD3B26A656E200E0CDB7 /* GameLogic */ = { isa = PBXGroup; children = ( E41C85D7269C252D008F2E91 /* GameUnitManagerType.swift */, @@ -399,7 +404,7 @@ E4BF710626AFD51700E8645F /* FeverManagerType.swift */, E4BF710226AFCC7500E8645F /* FeverManager.swift */, ); - path = GameManager; + path = GameLogic; sourceTree = ""; }; 717EAD3C26A6576600E0CDB7 /* Button */ = { @@ -432,13 +437,16 @@ path = ErrorManager; sourceTree = ""; }; - 71A5517E26A91C160083A4B0 /* FirebaseStorage */ = { + 719D0D9726F18C0700913BBE /* SceneType */ = { isa = PBXGroup; children = ( - 71A5517F26A91C280083A4B0 /* DatabaseManager.swift */, - 71A5518126A9277C0083A4B0 /* DatabaseManagerType.swift */, + 719D0D8F26F1863200913BBE /* MainScene.swift */, + 719D0D9126F186F100913BBE /* GameScene.swift */, + 719D0D9326F1875100913BBE /* GameHelperScene.swift */, + 7123C73F26FD79A70049E6DC /* AlertScene.swift */, + 719D0D9526F1889700913BBE /* SceneType.swift */, ); - path = FirebaseStorage; + path = SceneType; sourceTree = ""; }; 71BC752C26A940870021BA1A /* DataFormatManager */ = { @@ -461,8 +469,8 @@ 71DF653A2692F8D400557693 /* Scene */ = { isa = PBXGroup; children = ( + 719D0D9726F18C0700913BBE /* SceneType */, 71DF653B2692F90100557693 /* TransitionModel.swift */, - 71DF653D2692F92700557693 /* Scene.swift */, 71DF65502692FBCC00557693 /* SceneCoordinatorType.swift */, 71DF65522692FBE500557693 /* SceneCoordinator.swift */, ); @@ -637,6 +645,42 @@ path = Reward; sourceTree = ""; }; + E4336F6426F08A8D0049A5D8 /* Storage */ = { + isa = PBXGroup; + children = ( + E4336F5A26F0638A0049A5D8 /* Storage.swift */, + E4336F6B26F1BC1A0049A5D8 /* StorageType.swift */, + E4336F6526F08AD80049A5D8 /* RealDataStorage */, + E4336F6626F08AF10049A5D8 /* BackUpManager */, + ); + path = Storage; + sourceTree = ""; + }; + E4336F6526F08AD80049A5D8 /* RealDataStorage */ = { + isa = PBXGroup; + children = ( + E4DF613C26A845DA0061F6E9 /* AdStorage.swift */, + E4DF613A26A845670061F6E9 /* AdStorageType.swift */, + E4336F6926F1B7500049A5D8 /* GameStorage.swift */, + E4336F5C26F083650049A5D8 /* GameStorageType.swift */, + ); + path = RealDataStorage; + sourceTree = ""; + }; + E4336F6626F08AF10049A5D8 /* BackUpManager */ = { + isa = PBXGroup; + children = ( + E4336F6026F083A20049A5D8 /* BackUpCenter.swift */, + E4336F5E26F083840049A5D8 /* BackUpCenterType.swift */, + E4336F6726F1AB8B0049A5D8 /* CoreDataManager.swift */, + E4336F6226F083C30049A5D8 /* CoreDataManagerType.swift */, + 71A5517F26A91C280083A4B0 /* FirebaseManager.swift */, + 71A5518126A9277C0083A4B0 /* FirebaseManagerType.swift */, + 71BC752C26A940870021BA1A /* DataFormatManager */, + ); + path = BackUpManager; + sourceTree = ""; + }; E45B0AA22692929C006FDE3D = { isa = PBXGroup; children = ( @@ -803,7 +847,7 @@ E45B0AA82692929D006FDE3D /* Frameworks */, E45B0AA92692929D006FDE3D /* Resources */, 7165CDD92695687300891214 /* ShellScript */, - DF44C2E6C913FEAEBC1352D3 /* [CP] Embed Pods Frameworks */, + 9C633EB2541425896BED99AB /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -967,7 +1011,7 @@ shellPath = /bin/sh; shellScript = "${PODS_ROOT}/SwiftLint/swiftlint\n"; }; - DF44C2E6C913FEAEBC1352D3 /* [CP] Embed Pods Frameworks */ = { + 9C633EB2541425896BED99AB /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -996,8 +1040,6 @@ E4BF712526B3E65600E8645F /* AdStorageType.swift in Sources */, 71310C2D26AA596B00733361 /* CoreDataStorage.xcdatamodeld in Sources */, 71307AC126A5461700C3191F /* Unit.swift in Sources */, - 71F0D3DA26CD161A00E45283 /* PersistenceStorage.swift in Sources */, - 71F0D3D926CD161700E45283 /* PersistenceStorageType.swift in Sources */, E4BF712F26B3F22900E8645F /* ShopItem.swift in Sources */, 71F0D3DB26CD16D500E45283 /* UnitInfo.swift in Sources */, E4BF712D26B3E6F300E8645F /* AdsInformation.swift in Sources */, @@ -1012,6 +1054,7 @@ buildActionMask = 2147483647; files = ( 719A6A9B26B11208006AC991 /* NetworkDTO.swift in Sources */, + 7123C74026FD79A70049E6DC /* AlertScene.swift in Sources */, E418F37526DC985B00F35AFB /* AverageColor.swift in Sources */, E43B64A226ABD81C001D9896 /* GameBackgroundView.swift in Sources */, E4C2C34D26A24666007BDF81 /* SkyView.swift in Sources */, @@ -1020,38 +1063,46 @@ E4150E9726B8F3E00041DD32 /* TypeWriterView.swift in Sources */, 71310C2C26AA596B00733361 /* CoreDataStorage.xcdatamodeld in Sources */, E4BF710526AFD45A00E8645F /* TimeManagerType.swift in Sources */, + E4336F5F26F083840049A5D8 /* BackUpCenterType.swift in Sources */, 710D0D4F2693F6E500244A16 /* ItemCell.swift in Sources */, + 719D0D9226F186F100913BBE /* GameScene.swift in Sources */, E4D7C63E2693E79800D58BA2 /* AppDelegate.swift in Sources */, + 719D0D9626F1889700913BBE /* SceneType.swift in Sources */, 715C1C9426BCF9FF0025C681 /* StoryViewController.swift in Sources */, E45B0AC92692F9CF006FDE3D /* Unit.swift in Sources */, E45B0AC72692F42D006FDE3D /* UnitPerspectiveView.swift in Sources */, E41C85D4269BFB84008F2E91 /* GameUnitManager.swift in Sources */, E418F36A26DB5D8000F35AFB /* RewardPopupView.swift in Sources */, - 71A5518026A91C280083A4B0 /* DatabaseManager.swift in Sources */, + 71A5518026A91C280083A4B0 /* FirebaseManager.swift in Sources */, E4779C6926983F8000B92D24 /* GameOverViewModel.swift in Sources */, 7165CDDF26957DE400891214 /* GameButtonController.swift in Sources */, 71DF653C2692F90100557693 /* TransitionModel.swift in Sources */, E4BF710726AFD51700E8645F /* FeverManagerType.swift in Sources */, + E4336F6126F083A20049A5D8 /* BackUpCenter.swift in Sources */, + E4336F6326F083C30049A5D8 /* CoreDataManagerType.swift in Sources */, E4BF70FF26AEF3F700E8645F /* GameStatus.swift in Sources */, E4BF712B26B3E6E000E8645F /* AdsInformation.swift in Sources */, + E4336F5B26F0638A0049A5D8 /* Storage.swift in Sources */, E418F37326DC95B800F35AFB /* Color.swift in Sources */, 718F224626C36AE200B2648C /* HowToPlayButtonController.swift in Sources */, E4BF710126AFB0AD00E8645F /* TimeManager.swift in Sources */, E41C85DA269C4E8F008F2E91 /* PauseViewController.swift in Sources */, E4DF613D26A845DA0061F6E9 /* AdStorage.swift in Sources */, E4D7C64B26953C6200D58BA2 /* ScreenSize.swift in Sources */, - 71DF653E2692F92700557693 /* Scene.swift in Sources */, E4BF714326B7E23A00E8645F /* GameSoundEffectStation.swift in Sources */, 71DF64BF2692C1C600557693 /* ItemViewController.swift in Sources */, - 71A5518226A9277C0083A4B0 /* DatabaseManagerType.swift in Sources */, + E4336F5D26F083650049A5D8 /* GameStorageType.swift in Sources */, + 71A5518226A9277C0083A4B0 /* FirebaseManagerType.swift in Sources */, 710A9DAC2692AD5C0090B38A /* MainButtonController.swift in Sources */, E43B64A426AC2384001D9896 /* TimeBarView.swift in Sources */, E4150EA426BE9A540041DD32 /* Line.swift in Sources */, E4BF710926B00EF600E8645F /* FeverTimeBarView.swift in Sources */, E4DF614326A93FFB0061F6E9 /* ShopCell.swift in Sources */, + E4336F6C26F1BC1A0049A5D8 /* StorageType.swift in Sources */, E41C85DD269C50F0008F2E91 /* PauseViewModel.swift in Sources */, E41C85DF269C5149008F2E91 /* PauseButtonController.swift in Sources */, E418F36F26DC8D4800F35AFB /* UnitInfo - keyColor.swift in Sources */, + E4336F6826F1AB8B0049A5D8 /* CoreDataManager.swift in Sources */, E4779C6726983F5700B92D24 /* GameOverViewController.swift in Sources */, 71DF65532692FBE500557693 /* SceneCoordinator.swift in Sources */, E4DF613B26A845670061F6E9 /* AdStorageType.swift in Sources */, @@ -1071,7 +1122,6 @@ 718F224926C377B700B2648C /* ErrorManager.swift in Sources */, 71307A8926A529F400C3191F /* LevelUpButton.swift in Sources */, E4DF614126A92CE00061F6E9 /* ShopItem.swift in Sources */, - 71E2B37826AE602200AE496D /* PersistenceStorageType.swift in Sources */, E4F4304426C4F8F500F046E9 /* GameStoryManager.swift in Sources */, E4F4304626CA2E4500F046E9 /* UnitInfo.swift in Sources */, E4BF713B26B7D2CC00E8645F /* SingleSoundEffectStation.swift in Sources */, @@ -1086,9 +1136,11 @@ E418F36D26DC8BAC00F35AFB /* BackgroundAnimation.swift in Sources */, E4A40F9926C1038F003E40B2 /* StoryManager.swift in Sources */, 715A83EE269FE96300C9CEFF /* StoryboardType.swift in Sources */, + 719D0D9426F1875100913BBE /* GameHelperScene.swift in Sources */, 71307AB126A53C7B00C3191F /* AlertMessage.swift in Sources */, 71E91E1726BA33190016386E /* Extension.swift in Sources */, 7170CD4526B266BE00F03248 /* VersionErrorViewController.swift in Sources */, + 719D0D9026F1863200913BBE /* MainScene.swift in Sources */, E4CA08B926A8287B00AA6F03 /* Font.swift in Sources */, E4BF710B26B1324500E8645F /* ReadyView.swift in Sources */, 71DF64B92692C13A00557693 /* ShopViewController.swift in Sources */, @@ -1096,6 +1148,7 @@ 71003AD626CFAB22001D5210 /* IdentifierManager.swift in Sources */, E4150E9F26BE44AC0041DD32 /* PersonStoryView.swift in Sources */, E4BF712C26B3E6E300E8645F /* UnitInformation.swift in Sources */, + E4336F6A26F1B7500049A5D8 /* GameStorage.swift in Sources */, E4150E9926BA37C10041DD32 /* FadeInTextView.swift in Sources */, 7166B24026C25B0400E098BE /* Manual.swift in Sources */, E4779C652697E92F00B92D24 /* TimeProgressView.swift in Sources */, @@ -1108,7 +1161,6 @@ E41C85D6269C1C35008F2E91 /* GameOverButtonController.swift in Sources */, 71DF65412692F94E00557693 /* MainViewModel.swift in Sources */, E4DB63EA26CD3EF700E51540 /* LoadingView.swift in Sources */, - 71E2B37626AE5E7F00AE496D /* PersistenceStorage.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/FullStackCodingBot/FullStackCodingBot/AppCycle/AppDelegate.swift b/FullStackCodingBot/FullStackCodingBot/AppCycle/AppDelegate.swift index 602deab..4a11c32 100644 --- a/FullStackCodingBot/FullStackCodingBot/AppCycle/AppDelegate.swift +++ b/FullStackCodingBot/FullStackCodingBot/AppCycle/AppDelegate.swift @@ -7,12 +7,12 @@ import AVFoundation class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? - private let storage = PersistenceStorage() - private let adStorage = AdStorage() + private var storage: StorageType? private let userDefaults = UserDefaults.standard func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { FirebaseApp.configure() + storage = Storage() setAdMobs() setAudioSession() presentMainViewController() @@ -20,10 +20,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { } func applicationDidEnterBackground(_ application: UIApplication) { - let database = DatabaseManager(Database.database().reference()) - let networkDTO = NetworkDTO(units: storage.itemList(), money: storage.myMoney(), score: storage.myHighScore(), ads: adStorage.currentInformation(), date: Date()) - if storage.itemList().isEmpty { return } - database.updateDatabase(networkDTO) + storage?.save() } } @@ -45,8 +42,7 @@ private extension AppDelegate { let hasLaunchedOnce = userDefaults.bool(forKey: IdentifierUD.hasLaunchedOnce) let settings = getSettingInformation(hasLaunchedOnce) let coordinator = SceneCoordinator(window: window!) - let database = DatabaseManager(Database.database().reference()) - let scene = getFirstScene(hasLaunchedOnce, coordinator, database, settings) + let scene = getFirstScene(hasLaunchedOnce, coordinator, settings) coordinator.transition(to: scene, using: .root, with: StoryboardType.main, animated: false) } @@ -58,19 +54,17 @@ private extension AppDelegate { private func getFirstScene(_ hasLaunchedOnce: Bool, _ sceneCoordinator: SceneCoordinator, - _ database: DatabaseManager, - _ settings: SettingInformation) -> Scene { - + _ settings: SettingInformation) -> SceneType { switch hasLaunchedOnce { case true: - let mainViewModel = MainViewModel(sceneCoordinator: sceneCoordinator, storage: storage, adStorage: adStorage, database: database, settings: settings) - let mainScene = Scene.main(mainViewModel) + let mainViewModel = MainViewModel(sceneCoordinator: sceneCoordinator, storage: storage!, settings: settings) + let mainScene = MainScene.main(mainViewModel) return mainScene case false: - let storyViewModel = StoryViewModel(sceneCoordinator: sceneCoordinator, storage: storage, adStorage: adStorage, database: database, settings: settings) - let storyScene = Scene.story(storyViewModel) + let storyViewModel = StoryViewModel(sceneCoordinator: sceneCoordinator, storage: storage!, settings: settings) + let storyScene = GameHelperScene.story(storyViewModel) return storyScene } diff --git a/FullStackCodingBot/FullStackCodingBot/Constant/IdentifierManager.swift b/FullStackCodingBot/FullStackCodingBot/Constant/IdentifierManager.swift new file mode 100644 index 0000000..107b61f --- /dev/null +++ b/FullStackCodingBot/FullStackCodingBot/Constant/IdentifierManager.swift @@ -0,0 +1,30 @@ +import Foundation + +enum IdentifierVC { + static let main = "MainVC" + static let shop = "ShopVC" + static let rank = "RankVC" + static let item = "ItemVC" + static let game = "GameVC" + static let pause = "PauseVC" + static let error = "ErrorVC" + static let gameOver = "GameOverVC" + static let setting = "SettingVC" + static let story = "StoryVC" + static let howTo = "HowToVC" +} + +enum IdentiferAD { + static let reward = "ca-app-pub-3940256099942544/1712485313" + static let banner = "ca-app-pub-3940256099942544/2934735716" +} + +enum IdentifierUD { + static let hasLaunchedOnce = "hasLaunchedOnce" + static let setting = "setting" +} + +enum IdentifierGC { + static let leaderboard = "WeeklyScore" + static let leaderboard2 = "AllTimeScore" +} diff --git a/FullStackCodingBot/FullStackCodingBot/Service/FirebaseStorage/DatabaseManagerType.swift b/FullStackCodingBot/FullStackCodingBot/Service/FirebaseStorage/DatabaseManagerType.swift deleted file mode 100644 index f8c6cbe..0000000 --- a/FullStackCodingBot/FullStackCodingBot/Service/FirebaseStorage/DatabaseManagerType.swift +++ /dev/null @@ -1,10 +0,0 @@ -import Foundation -import RxSwift - -protocol DatabaseManagerType { - - @discardableResult - func getFirebaseData(_ uuid: String) -> Observable - - func updateDatabase(_ info: NetworkDTO) -} diff --git a/FullStackCodingBot/FullStackCodingBot/Service/GameManager/FeverManager.swift b/FullStackCodingBot/FullStackCodingBot/Service/GameLogic/FeverManager.swift similarity index 100% rename from FullStackCodingBot/FullStackCodingBot/Service/GameManager/FeverManager.swift rename to FullStackCodingBot/FullStackCodingBot/Service/GameLogic/FeverManager.swift diff --git a/FullStackCodingBot/FullStackCodingBot/Service/GameManager/FeverManagerType.swift b/FullStackCodingBot/FullStackCodingBot/Service/GameLogic/FeverManagerType.swift similarity index 100% rename from FullStackCodingBot/FullStackCodingBot/Service/GameManager/FeverManagerType.swift rename to FullStackCodingBot/FullStackCodingBot/Service/GameLogic/FeverManagerType.swift diff --git a/FullStackCodingBot/FullStackCodingBot/Service/GameManager/GameUnitManager.swift b/FullStackCodingBot/FullStackCodingBot/Service/GameLogic/GameUnitManager.swift similarity index 100% rename from FullStackCodingBot/FullStackCodingBot/Service/GameManager/GameUnitManager.swift rename to FullStackCodingBot/FullStackCodingBot/Service/GameLogic/GameUnitManager.swift diff --git a/FullStackCodingBot/FullStackCodingBot/Service/GameManager/GameUnitManagerType.swift b/FullStackCodingBot/FullStackCodingBot/Service/GameLogic/GameUnitManagerType.swift similarity index 100% rename from FullStackCodingBot/FullStackCodingBot/Service/GameManager/GameUnitManagerType.swift rename to FullStackCodingBot/FullStackCodingBot/Service/GameLogic/GameUnitManagerType.swift diff --git a/FullStackCodingBot/FullStackCodingBot/Service/GameManager/TimeManager.swift b/FullStackCodingBot/FullStackCodingBot/Service/GameLogic/TimeManager.swift similarity index 100% rename from FullStackCodingBot/FullStackCodingBot/Service/GameManager/TimeManager.swift rename to FullStackCodingBot/FullStackCodingBot/Service/GameLogic/TimeManager.swift diff --git a/FullStackCodingBot/FullStackCodingBot/Service/GameManager/TimeManagerType.swift b/FullStackCodingBot/FullStackCodingBot/Service/GameLogic/TimeManagerType.swift similarity index 100% rename from FullStackCodingBot/FullStackCodingBot/Service/GameManager/TimeManagerType.swift rename to FullStackCodingBot/FullStackCodingBot/Service/GameLogic/TimeManagerType.swift diff --git a/FullStackCodingBot/FullStackCodingBot/Service/PersistenceStorage/PersistenceStorage.swift b/FullStackCodingBot/FullStackCodingBot/Service/PersistenceStorage/PersistenceStorage.swift deleted file mode 100644 index 95b6e98..0000000 --- a/FullStackCodingBot/FullStackCodingBot/Service/PersistenceStorage/PersistenceStorage.swift +++ /dev/null @@ -1,289 +0,0 @@ -import Foundation -import RxSwift -import CoreData -import RxCocoa - -final class PersistenceStorage: PersistenceStorageType { - - private lazy var persistentContainer: NSPersistentContainer = { - let container = NSPersistentContainer(name: "CoreDataStorage") - container.loadPersistentStores { _, error in - if let error = error { - fatalError() - } - } - return container - }() - - private var context: NSManagedObjectContext { - return persistentContainer.viewContext - } - - private var unitStore: [Unit] = [] - private lazy var unitList = BehaviorSubject<[Unit]>(value: unitStore) - private var moneyStore = 0 - private lazy var moneyStatus = BehaviorSubject(value: moneyStore) - private var highScore = 0 - - @discardableResult - func myHighScore() -> Int { - return highScore - } - - @discardableResult - func myMoney() -> Int { - return moneyStore - } - - @discardableResult - func itemList() -> [Unit] { - return unitStore - } - - @discardableResult - func update(units: [Unit]) -> Observable<[Unit]> { - unitStore = filtered(units) - unitList.onNext(unitStore) - - units.forEach { unit in - try? updateUnit(to: unit) - } - return Observable.just(units) - } - - private func filtered(_ units: [Unit]) -> [Unit] { - if units.count == UnitInfo.allCases.count { - return units - } - - // 1.0.2 #180 중복 유닛 생성 버그 -> 업그레이드된 유닛이 있을 경우 레벨이 높은 유닛으로 storage 생성 - var finalUnits = Unit.initialValues() - - units.forEach { unit in - let currentId = unit.uuid - let currentLevel = unit.level - let currentHighestLevel = finalUnits[currentId].level - if currentLevel >= currentHighestLevel { - finalUnits[currentId] = unit - } - } - return finalUnits - } - - @discardableResult - func listUnit() -> Observable<[Unit]> { - return unitList - } - - @discardableResult - func raiseLevel(of unit: Unit, using moeny: Int) -> Unit { - let newUnit = Unit(original: unit, level: unit.level+1) - if let index = unitStore.firstIndex(where: { $0 == unit}) { - unitStore.remove(at: index) - unitStore.insert(newUnit, at: index) - try? updateUnit(to: newUnit) - moneyStore -= moeny - try? updateMoney(money: moneyStore) - } - unitList.onNext(unitStore) - moneyStatus.onNext(moneyStore) - return newUnit - } - - @discardableResult - func availableMoeny() -> Observable { - return moneyStatus - } - - @discardableResult - func raiseMoney(by money: Int) -> Observable { - moneyStore += money - moneyStatus.onNext(moneyStore) - try? updateMoney(money: moneyStore) - return Observable.just(money) - } - - @discardableResult - func updateHighScore(new score: Int) -> Bool { - if score > highScore { - highScore = score - try? updateScore(score: highScore) - return true - } else { - return false - } - } - - @discardableResult - func getCoreDataInfo() -> Completable { - let subject = PublishSubject() - - guard let fetchedUnitInfo = try? fetchUnit() else { - return subject.ignoreElements().asCompletable() - } - - let units = fetchedUnitInfo.map { DataFormatManager.transformToUnit($0) } - update(units: units) - - guard let fetchedMoneyInfo = try? fetchMoneyInfo().first, - let fetchedScoreInfo = try? fetchScoreInfo().first else { - return subject.ignoreElements().asCompletable() - } - raiseMoney(by: Int(fetchedMoneyInfo.myMoney)) - updateHighScore(new: Int(fetchedScoreInfo.myScore)) - - subject.onCompleted() - return subject.ignoreElements().asCompletable() - } - - @discardableResult - func setupInitialData() -> Completable { - let subject = PublishSubject() - - let initialUnits = Unit.initialValues() - update(units: initialUnits) - - try? appendUnitInfos(initialUnits) - try? appendMoenyInfo(0) - try? appendScoreInfo(0) - - subject.onCompleted() - return subject.ignoreElements().asCompletable() - } - - func lastUpdated() -> Date { - let farPastDate = Date.init(timeIntervalSince1970: 0) - guard let fetchedInfo = try? fetchMoneyInfo(), - let currentMoneyInfo = fetchedInfo.first else { - return farPastDate - } - let lastUpdated = currentMoneyInfo.lastUpdated ?? farPastDate - return lastUpdated - } -} - -// MARK: CoreData Method -private extension PersistenceStorage { - private func fetchUnit() throws -> [ItemInformation] { - do { - guard let fetchResult = try context.fetch(ItemInformation.fetchRequest()) as? [ItemInformation] else { return [] } - return fetchResult - } catch { - throw CoreDataError.cannotFetchData - } - } - - private func fetchMoneyInfo() throws -> [MoneyInformation] { - do { - guard let fetchResult = try context.fetch(MoneyInformation.fetchRequest()) as? [MoneyInformation] else { - return [MoneyInformation(context: context)] - } - return fetchResult - } catch { - throw CoreDataError.cannotFetchData - } - } - - private func fetchScoreInfo() throws -> [ScoreInformation] { - do { - guard let fetchResult = try context.fetch(ScoreInformation.fetchRequest()) as? [ScoreInformation] else { - return [ScoreInformation(context: context)] - } - return fetchResult - } catch { - throw CoreDataError.cannotFetchData - } - } - - private func updateUnit(to unit: Unit) throws { - guard let fetchedUnit = try? fetchUnit() else { return } - - for info in fetchedUnit where info.uuid == unit.uuid { - info.setValue(unit.uuid, forKey: "uuid") - info.setValue(unit.image, forKey: "image") - info.setValue(unit.level, forKey: "level") - } - - do { - try context.save() - } catch { - throw CoreDataError.cannotSaveData - } - } - - private func updateMoney(money: Int) throws { - do { - let previousInfo = try fetchMoneyInfo().first ?? MoneyInformation(context: context) - previousInfo.setValue(money, forKey: "myMoney") - previousInfo.setValue(Date(), forKey: "lastUpdated") - try context.save() - } catch { - throw CoreDataError.cannotSaveData - } - } - - private func updateScore(score: Int) throws { - do { - let previousInfo = try fetchScoreInfo().first ?? ScoreInformation(context: context) - previousInfo.setValue(score, forKey: "myScore") - try context.save() - } catch { - throw CoreDataError.cannotSaveData - } - } - - private func appendUnitInfos(_ units: [Unit]) throws { - let fetchedUnit = try? fetchUnit().first - guard fetchedUnit == nil else { return } - - try units.forEach { unit in - if let entity = NSEntityDescription.entity(forEntityName: "ItemInformation", in: context) { - let info = NSManagedObject(entity: entity, insertInto: context) - info.setValue(unit.uuid, forKey: "uuid") - info.setValue(unit.image, forKey: "image") - info.setValue(unit.level, forKey: "level") - - do { - try context.save() - } catch { - throw CoreDataError.cannotSaveData - } - } - } - } - - private func appendMoenyInfo(_ money: Int) throws { - let fetchedMoney = try? fetchMoneyInfo().first - guard fetchedMoney == nil else { return } - - if let entity = NSEntityDescription.entity(forEntityName: "MoneyInformation", in: context) { - let info = NSManagedObject(entity: entity, insertInto: context) - info.setValue(money, forKey: "myMoney") - info.setValue(Date.init(timeIntervalSince1970: 0), forKey: "lastUpdated") - - do { - try context.save() - } catch { - throw CoreDataError.cannotSaveData - } - } - moneyStore = money - moneyStatus.onNext(moneyStore) - } - - private func appendScoreInfo(_ score: Int) throws { - let fetchedScore = try? fetchScoreInfo().first - guard fetchedScore == nil else { return } - - if let entity = NSEntityDescription.entity(forEntityName: "ScoreInformation", in: context) { - let info = NSManagedObject(entity: entity, insertInto: context) - info.setValue(score, forKey: "myScore") - - do { - try context.save() - } catch { - throw CoreDataError.cannotSaveData - } - } - } -} diff --git a/FullStackCodingBot/FullStackCodingBot/Service/Storage/BackUpManager/BackUpCenter.swift b/FullStackCodingBot/FullStackCodingBot/Service/Storage/BackUpManager/BackUpCenter.swift new file mode 100644 index 0000000..192255a --- /dev/null +++ b/FullStackCodingBot/FullStackCodingBot/Service/Storage/BackUpManager/BackUpCenter.swift @@ -0,0 +1,52 @@ +import Foundation +import RxSwift + +final class BackUpCenter: BackUpCenterType { + + private var firebaseManager: FirebaseManagerType + private var coreDataManager: CoreDataManagerType + private var disposeBag = DisposeBag() + + init(firebaseManager: FirebaseManagerType = FirebaseManager(), + coreDataManager: CoreDataManagerType = CoreDataManager()) { + self.firebaseManager = firebaseManager + self.coreDataManager = coreDataManager + } + + func load(with uuid: String?, _ isFirstLaunched: Bool) -> Observable { + Observable.create { [unowned self] observer in + if isFirstLaunched { + self.coreDataManager.setupInitialData() + } + + let localData = self.coreDataManager.load() ?? NetworkDTO.empty() + + guard let uuid = uuid else { + observer.onNext(localData) + observer.onCompleted() + return Disposables.create() + } + + firebaseManager.load(with: uuid) + .observe(on: MainScheduler.asyncInstance) + .subscribe { onlineData in + let onlineUpdate = onlineData.date + let localUpdate = localData.date + onlineUpdate > localUpdate ? observer.onNext(onlineData) : observer.onNext(localData) + observer.onCompleted() + } onError: { error in + observer.onError(error) + }.disposed(by: disposeBag) + + return Disposables.create() + } + } + + func save(gameData newUnit: Unit?, _ newMoney: Int?, _ newScore: Int?) { + coreDataManager.save(gameData: newUnit, newMoney, newScore) + } + + func save(gameData: NetworkDTO) { + firebaseManager.save(gameData: gameData) + } +} diff --git a/FullStackCodingBot/FullStackCodingBot/Service/Storage/BackUpManager/BackUpCenterType.swift b/FullStackCodingBot/FullStackCodingBot/Service/Storage/BackUpManager/BackUpCenterType.swift new file mode 100644 index 0000000..9f59d94 --- /dev/null +++ b/FullStackCodingBot/FullStackCodingBot/Service/Storage/BackUpManager/BackUpCenterType.swift @@ -0,0 +1,12 @@ +import Foundation +import RxSwift + +protocol BackUpCenterType { + + func load(with uuid: String?, _ isFirstLaunched: Bool) -> Observable + + func save(gameData newUnit: Unit?, _ newMoney: Int?, _ newScore: Int?) + + func save(gameData: NetworkDTO) + +} diff --git a/FullStackCodingBot/FullStackCodingBot/Service/Storage/BackUpManager/CoreDataManager.swift b/FullStackCodingBot/FullStackCodingBot/Service/Storage/BackUpManager/CoreDataManager.swift new file mode 100644 index 0000000..aa844e1 --- /dev/null +++ b/FullStackCodingBot/FullStackCodingBot/Service/Storage/BackUpManager/CoreDataManager.swift @@ -0,0 +1,216 @@ +import Foundation +import CoreData +import RxSwift + +final class CoreDataManager: CoreDataManagerType { + + private let storageKey = "CoreDataStorage" + + private lazy var persistentContainer: NSPersistentContainer = { + let container = NSPersistentContainer(name: storageKey) + container.loadPersistentStores { _, error in + if let error = error { + fatalError() + } + } + return container + }() + + private var context: NSManagedObjectContext { + return persistentContainer.viewContext + } + + enum Entity { + static let item = "ItemInformation" + static let money = "MoneyInformation" + static let score = "ScoreInformation" + } + + enum Keys { + static let uuid = "uuid" + static let image = "image" + static let level = "level" + static let myMoney = "myMoney" + static let lastUpdated = "lastUpdated" + static let myScore = "myScore" + } + + @discardableResult + func setupInitialData() -> Completable { + let subject = PublishSubject() + let initialUnits = Unit.initialValues() + + do { + try appendUnitInfos(initialUnits) + try appendMoneyInfo(0) + try appendScoreInfo(0) + } catch { + subject.onError(CoreDataError.cannotFetchData) + } + subject.onCompleted() + return subject.ignoreElements().asCompletable() + } + + func load() -> NetworkDTO? { + guard let fetchedUnitInfo = try? self.fetchUnit(), + let fetchedMoneyInfo = try? self.fetchMoneyInfo().first, + let fetchedScoreInfo = try? self.fetchScoreInfo().first else { + return nil + } + + let units = fetchedUnitInfo.map { DataFormatManager.transformToUnit($0) } + let money = Int(fetchedMoneyInfo.myMoney) + let score = Int(fetchedScoreInfo.myScore) + let date = fetchedMoneyInfo.lastUpdated ?? .init(timeIntervalSince1970: 0) + let localData = NetworkDTO.init(units: units, + money: money, + score: score, + ads: .empty(), + date: date) + return localData + } + + func save(gameData newUnit: Unit?, _ newMoney: Int?, _ newScore: Int?) { + if let newUnit = newUnit { + try? updateUnit(to: newUnit) + } + + if let newMoney = newMoney { + try? updateMoney(money: newMoney) + } + + if let newScore = newScore { + try? updateScore(score: newScore) + } + } +} + +// MARK: new +extension CoreDataManager { + private func appendUnitInfos(_ units: [Unit]) throws { + let fetchedUnit = try? fetchUnit().first + guard fetchedUnit == nil else { return } + + try units.forEach { unit in + if let entity = NSEntityDescription.entity(forEntityName: Entity.item, in: context) { + let info = NSManagedObject(entity: entity, insertInto: context) + info.setValue(unit.uuid, forKey: Keys.uuid) + info.setValue(unit.image, forKey: Keys.image) + info.setValue(unit.level, forKey: Keys.level) + + do { + try context.save() + } catch { + throw CoreDataError.cannotSaveData + } + } + } + } + + private func appendMoneyInfo(_ money: Int) throws { + let fetchedMoney = try? fetchMoneyInfo().first + guard fetchedMoney == nil else { return } + + if let entity = NSEntityDescription.entity(forEntityName: Entity.money, in: context) { + let info = NSManagedObject(entity: entity, insertInto: context) + info.setValue(money, forKey: Keys.myMoney) + info.setValue(Date.init(timeIntervalSince1970: 0), forKey: Keys.lastUpdated) + + do { + try context.save() + } catch { + throw CoreDataError.cannotSaveData + } + } + } + + private func appendScoreInfo(_ score: Int) throws { + let fetchedScore = try? fetchScoreInfo().first + guard fetchedScore == nil else { return } + + if let entity = NSEntityDescription.entity(forEntityName: Entity.score, in: context) { + let info = NSManagedObject(entity: entity, insertInto: context) + info.setValue(score, forKey: Keys.myScore) + + do { + try context.save() + } catch { + throw CoreDataError.cannotSaveData + } + } + } +} + +// MARK: load +extension CoreDataManager { + private func fetchUnit() throws -> [ItemInformation] { + do { + guard let fetchResult = try context.fetch(ItemInformation.fetchRequest()) as? [ItemInformation] else { return [] } + return fetchResult + } catch { + throw CoreDataError.cannotFetchData + } + } + + private func fetchMoneyInfo() throws -> [MoneyInformation] { + do { + guard let fetchResult = try context.fetch(MoneyInformation.fetchRequest()) as? [MoneyInformation] else { + return [MoneyInformation(context: context)] + } + return fetchResult + } catch { + throw CoreDataError.cannotFetchData + } + } + + private func fetchScoreInfo() throws -> [ScoreInformation] { + do { + guard let fetchResult = try context.fetch(ScoreInformation.fetchRequest()) as? [ScoreInformation] else { + return [ScoreInformation(context: context)] + } + return fetchResult + } catch { + throw CoreDataError.cannotFetchData + } + } +} + +// MARK: save +extension CoreDataManager { + private func updateUnit(to unit: Unit) throws { + guard let fetchedUnit = try? fetchUnit() else { return } + + for info in fetchedUnit where info.uuid == unit.uuid { + info.setValue(unit.uuid, forKey: Keys.uuid) + info.setValue(unit.image, forKey: Keys.image) + info.setValue(unit.level, forKey: Keys.level) + } + + do { + try context.save() + } catch { + throw CoreDataError.cannotSaveData + } + } + + private func updateMoney(money: Int) throws { + do { + let previousInfo = try fetchMoneyInfo().first ?? MoneyInformation(context: context) + previousInfo.setValue(money, forKey: Keys.myMoney) + previousInfo.setValue(Date(), forKey: Keys.lastUpdated) + try context.save() + } catch { + throw CoreDataError.cannotSaveData + } + } + + private func updateScore(score: Int) throws { + do { + let previousInfo = try fetchScoreInfo().first ?? ScoreInformation(context: context) + previousInfo.setValue(score, forKey: Keys.myScore) + try context.save() + } catch { + throw CoreDataError.cannotSaveData + } + } +} diff --git a/FullStackCodingBot/FullStackCodingBot/Service/Storage/BackUpManager/CoreDataManagerType.swift b/FullStackCodingBot/FullStackCodingBot/Service/Storage/BackUpManager/CoreDataManagerType.swift new file mode 100644 index 0000000..548768b --- /dev/null +++ b/FullStackCodingBot/FullStackCodingBot/Service/Storage/BackUpManager/CoreDataManagerType.swift @@ -0,0 +1,13 @@ +import Foundation +import RxSwift + +protocol CoreDataManagerType { + + @discardableResult + func setupInitialData() -> Completable + + func load() -> NetworkDTO? + + func save(gameData newUnit: Unit?, _ newMoney: Int?, _ newScore: Int?) + +} diff --git a/FullStackCodingBot/FullStackCodingBot/Service/DataFormatManager/DataFormatManager.swift b/FullStackCodingBot/FullStackCodingBot/Service/Storage/BackUpManager/DataFormatManager/DataFormatManager.swift similarity index 100% rename from FullStackCodingBot/FullStackCodingBot/Service/DataFormatManager/DataFormatManager.swift rename to FullStackCodingBot/FullStackCodingBot/Service/Storage/BackUpManager/DataFormatManager/DataFormatManager.swift diff --git a/FullStackCodingBot/FullStackCodingBot/Service/FirebaseStorage/DatabaseManager.swift b/FullStackCodingBot/FullStackCodingBot/Service/Storage/BackUpManager/FirebaseManager.swift similarity index 56% rename from FullStackCodingBot/FullStackCodingBot/Service/FirebaseStorage/DatabaseManager.swift rename to FullStackCodingBot/FullStackCodingBot/Service/Storage/BackUpManager/FirebaseManager.swift index 006b064..d8b8029 100644 --- a/FullStackCodingBot/FullStackCodingBot/Service/FirebaseStorage/DatabaseManager.swift +++ b/FullStackCodingBot/FullStackCodingBot/Service/Storage/BackUpManager/FirebaseManager.swift @@ -2,32 +2,46 @@ import Foundation import RxSwift import Firebase -final class DatabaseManager: DatabaseManagerType { +final class FirebaseManager: FirebaseManagerType { private var ref: DatabaseReference private let uid = Auth.auth().currentUser?.uid ?? "" private let disposedBag = DisposeBag() - let data = [Unit]() - init(_ ref: DatabaseReference) { + enum Keys { + static let users = "users" + static let info = "info" + static let units = "units" + static let money = "money" + static let score = "score" + static let ads = "ads" + static let date = "date" + } + + init(_ ref: DatabaseReference = Database.database().reference()) { self.ref = ref } - func updateDatabase(_ info: NetworkDTO) { + func save(gameData: NetworkDTO) { guard uid != "" else { return } - let unitData = try? DataFormatManager.transformToString(info.units) - let moneyData = try? DataFormatManager.transformToString(info.money) - let scoreData = try? DataFormatManager.transformToString(info.score) - let adsData = try? DataFormatManager.transformToString(info.ads) - let recentDate = try? DataFormatManager.transformToString(info.date) - ref.child("users").child(uid).setValue(["info": ["units": unitData, "money": moneyData, "score": scoreData, "ads": adsData, "date": recentDate]]) + let unitData = try? DataFormatManager.transformToString(gameData.units) + let moneyData = try? DataFormatManager.transformToString(gameData.money) + let scoreData = try? DataFormatManager.transformToString(gameData.score) + let adsData = try? DataFormatManager.transformToString(gameData.ads) + let recentDate = try? DataFormatManager.transformToString(gameData.date) + + ref.child(Keys.users).child(uid).setValue([Keys.info: [Keys.units: unitData, + Keys.money: moneyData, + Keys.score: scoreData, + Keys.ads: adsData, + Keys.date: recentDate]]) } @discardableResult - func getFirebaseData(_ uuid: String) -> Observable { + func load(with uuid: String) -> Observable { Observable.create { [unowned self] observer in - self.ref.child("users").child(uuid).getData { error, snapshot in + self.ref.child(Keys.users).child(uuid).getData { error, snapshot in if let error = error { observer.onError(error) } diff --git a/FullStackCodingBot/FullStackCodingBot/Service/Storage/BackUpManager/FirebaseManagerType.swift b/FullStackCodingBot/FullStackCodingBot/Service/Storage/BackUpManager/FirebaseManagerType.swift new file mode 100644 index 0000000..0337a1c --- /dev/null +++ b/FullStackCodingBot/FullStackCodingBot/Service/Storage/BackUpManager/FirebaseManagerType.swift @@ -0,0 +1,10 @@ +import Foundation +import RxSwift + +protocol FirebaseManagerType { + + @discardableResult + func load(with uuid: String) -> Observable + + func save(gameData: NetworkDTO) +} diff --git a/FullStackCodingBot/FullStackCodingBot/Service/PersistenceStorage/AdStorage.swift b/FullStackCodingBot/FullStackCodingBot/Service/Storage/RealDataStorage/AdStorage.swift similarity index 100% rename from FullStackCodingBot/FullStackCodingBot/Service/PersistenceStorage/AdStorage.swift rename to FullStackCodingBot/FullStackCodingBot/Service/Storage/RealDataStorage/AdStorage.swift diff --git a/FullStackCodingBot/FullStackCodingBot/Service/PersistenceStorage/AdStorageType.swift b/FullStackCodingBot/FullStackCodingBot/Service/Storage/RealDataStorage/AdStorageType.swift similarity index 100% rename from FullStackCodingBot/FullStackCodingBot/Service/PersistenceStorage/AdStorageType.swift rename to FullStackCodingBot/FullStackCodingBot/Service/Storage/RealDataStorage/AdStorageType.swift diff --git a/FullStackCodingBot/FullStackCodingBot/Service/Storage/RealDataStorage/GameStorage.swift b/FullStackCodingBot/FullStackCodingBot/Service/Storage/RealDataStorage/GameStorage.swift new file mode 100644 index 0000000..54a75d9 --- /dev/null +++ b/FullStackCodingBot/FullStackCodingBot/Service/Storage/RealDataStorage/GameStorage.swift @@ -0,0 +1,98 @@ +import Foundation +import RxSwift + +final class GameStorage: GameStorageType { + + private var unitStore: [Unit] = [] + private lazy var unitList = BehaviorSubject<[Unit]>(value: unitStore) + private var moneyStore = 0 + private lazy var moneyStatus = BehaviorSubject(value: moneyStore) + private var highScore = 0 + + @discardableResult + func myHighScore() -> Int { + return highScore + } + + @discardableResult + func myMoney() -> Int { + return moneyStore + } + + @discardableResult + func itemList() -> [Unit] { + return unitStore + } + + func update(with data: NetworkDTO) { + unitStore = data.units + highScore = data.score + moneyStore = data.money + } + + @discardableResult + func update(units: [Unit]) -> Observable<[Unit]> { + unitStore = filtered(units) + unitList.onNext(unitStore) + return Observable.just(units) + } + + private func filtered(_ units: [Unit]) -> [Unit] { + if units.count == UnitInfo.allCases.count { + return units + } + + // 1.0.2 #180 중복 유닛 생성 버그 -> 업그레이드된 유닛이 있을 경우 레벨이 높은 유닛으로 storage 생성 + var finalUnits = Unit.initialValues() + + units.forEach { unit in + let currentId = unit.uuid + let currentLevel = unit.level + let currentHighestLevel = finalUnits[currentId].level + if currentLevel >= currentHighestLevel { + finalUnits[currentId] = unit + } + } + return finalUnits + } + + @discardableResult + func listUnit() -> Observable<[Unit]> { + return unitList + } + + @discardableResult + func raiseLevel(of unit: Unit, using money: Int) -> Unit { + let newUnit = Unit(original: unit, level: unit.level+1) + if let index = unitStore.firstIndex(where: { $0 == unit}) { + unitStore.remove(at: index) + unitStore.insert(newUnit, at: index) + moneyStore -= money + } + unitList.onNext(unitStore) + moneyStatus.onNext(moneyStore) + return newUnit + } + + @discardableResult + func availableMoney() -> Observable { + return moneyStatus + } + + @discardableResult + func raiseMoney(by money: Int) -> Observable { + moneyStore += money + moneyStatus.onNext(moneyStore) + return Observable.just(money) + } + + @discardableResult + func updateHighScore(new score: Int) -> Bool { + if score > highScore { + highScore = score + return true + } else { + return false + } + } +} diff --git a/FullStackCodingBot/FullStackCodingBot/Service/PersistenceStorage/PersistenceStorageType.swift b/FullStackCodingBot/FullStackCodingBot/Service/Storage/RealDataStorage/GameStorageType.swift similarity index 53% rename from FullStackCodingBot/FullStackCodingBot/Service/PersistenceStorage/PersistenceStorageType.swift rename to FullStackCodingBot/FullStackCodingBot/Service/Storage/RealDataStorage/GameStorageType.swift index c6f9926..6439ea4 100644 --- a/FullStackCodingBot/FullStackCodingBot/Service/PersistenceStorage/PersistenceStorageType.swift +++ b/FullStackCodingBot/FullStackCodingBot/Service/Storage/RealDataStorage/GameStorageType.swift @@ -1,9 +1,7 @@ import Foundation -import CoreData import RxSwift -import RxCocoa -protocol PersistenceStorageType { +protocol GameStorageType { @discardableResult func myHighScore() -> Int @@ -14,29 +12,20 @@ protocol PersistenceStorageType { @discardableResult func itemList() -> [Unit] - @discardableResult - func update(units: [Unit]) -> Observable<[Unit]> + func update(with data: NetworkDTO) @discardableResult func listUnit() -> Observable<[Unit]> @discardableResult - func raiseLevel(of unit: Unit, using moeny: Int) -> Unit + func raiseLevel(of unit: Unit, using money: Int) -> Unit @discardableResult - func availableMoeny() -> Observable + func availableMoney() -> Observable @discardableResult func raiseMoney(by money: Int) -> Observable @discardableResult func updateHighScore(new score: Int) -> Bool - - @discardableResult - func getCoreDataInfo() -> Completable - - @discardableResult - func setupInitialData() -> Completable - - func lastUpdated() -> Date } diff --git a/FullStackCodingBot/FullStackCodingBot/Service/Storage/Storage.swift b/FullStackCodingBot/FullStackCodingBot/Service/Storage/Storage.swift new file mode 100644 index 0000000..482647a --- /dev/null +++ b/FullStackCodingBot/FullStackCodingBot/Service/Storage/Storage.swift @@ -0,0 +1,97 @@ +import Foundation +import RxSwift +import GoogleMobileAds + +final class Storage { + + private var gameStorage: GameStorageType + private var adStorage: AdStorageType + private var backUpCenter: BackUpCenterType + private var disposeBag = DisposeBag() + + init(gameStorage: GameStorageType = GameStorage(), + adStorage: AdStorageType = AdStorage(), + backUpCenter: BackUpCenterType = BackUpCenter()) { + self.gameStorage = gameStorage + self.adStorage = adStorage + self.backUpCenter = backUpCenter + } + + func initializeData(using uuid: String?, isFirstLaunched: Bool) -> Observable { + Observable.create { [unowned self] observer in + self.backUpCenter.load(with: uuid, isFirstLaunched) + .observe(on: MainScheduler.asyncInstance) + .subscribe(onNext: { data in + self.gameStorage.update(with: data) + self.adStorage.setNewRewardsIfPossible(with: data.ads) + .subscribe(onError: { _ in }).disposed(by: disposeBag) + observer.onNext(true) + }, onError: { _ in + observer.onNext(false) + }, onCompleted: { + observer.onNext(true) + }).disposed(by: self.disposeBag) + return Disposables.create() + } + } +} + +extension Storage: StorageType { + func availableRewards() -> Observable<[ShopItem]> { + return adStorage.availableItems() + } + + func availableMoney() -> Observable { + return gameStorage.availableMoney() + } + + func setNewRewardsIfPossible() -> Observable { + return adStorage.setNewRewardsIfPossible(with: .none) + } + + func rewardNeedsToBeGiven(with finishedAd: GADRewardedAd?) -> Int { + if let finishedAd = finishedAd { + adStorage.adDidFinished(finishedAd) + } else { + adStorage.giftTaken() + } + let reward = ShopSetting.reward() + gameStorage.raiseMoney(by: reward) + backUpCenter.save(gameData: .none, gameStorage.myMoney(), .none) + return reward + } + + func itemList() -> [Unit] { + return gameStorage.itemList() + } + + func raiseLevel(of unit: Unit, using money: Int) -> Unit { + let newUnit = gameStorage.raiseLevel(of: unit, using: money) + backUpCenter.save(gameData: newUnit, gameStorage.myMoney(), .none) + return newUnit + } + + func myHighScore() -> Int { + return gameStorage.myHighScore() + } + + func raiseMoney(by amount: Int) { + gameStorage.raiseMoney(by: amount) + backUpCenter.save(gameData: .none, gameStorage.myMoney(), .none) + } + + func updateHighScore(new score: Int) -> Bool { + let isUpdatable = gameStorage.updateHighScore(new: score) + if isUpdatable { backUpCenter.save(gameData: .none, .none, score) } + return isUpdatable + } + + func save() { + let currentData = NetworkDTO(units: gameStorage.itemList(), + money: gameStorage.myMoney(), + score: gameStorage.myHighScore(), + ads: adStorage.currentInformation(), + date: Date()) + backUpCenter.save(gameData: currentData) + } +} diff --git a/FullStackCodingBot/FullStackCodingBot/Service/Storage/StorageType.swift b/FullStackCodingBot/FullStackCodingBot/Service/Storage/StorageType.swift new file mode 100644 index 0000000..235f35d --- /dev/null +++ b/FullStackCodingBot/FullStackCodingBot/Service/Storage/StorageType.swift @@ -0,0 +1,28 @@ +import Foundation +import RxSwift +import GoogleMobileAds + +protocol StorageType { + + func initializeData(using uuid: String?, isFirstLaunched: Bool) -> Observable + + // Shop + func availableRewards() -> Observable<[ShopItem]> + func availableMoney() -> Observable // + Item + func setNewRewardsIfPossible() -> Observable + func rewardNeedsToBeGiven(with finishedAd: GADRewardedAd?) -> Int + + // Item + func itemList() -> [Unit] + func raiseLevel(of unit: Unit, using money: Int) -> Unit + + // Game + func myHighScore() -> Int + + // GameOver + func raiseMoney(by amount: Int) + func updateHighScore(new score: Int) -> Bool + + // Background + func save() +} diff --git a/FullStackCodingBot/FullStackCodingBot/View/Controller/ViewController/Main/MainViewController.swift b/FullStackCodingBot/FullStackCodingBot/View/Controller/ViewController/Main/MainViewController.swift index 41a9bfa..d4db0d6 100644 --- a/FullStackCodingBot/FullStackCodingBot/View/Controller/ViewController/Main/MainViewController.swift +++ b/FullStackCodingBot/FullStackCodingBot/View/Controller/ViewController/Main/MainViewController.swift @@ -42,7 +42,7 @@ final class MainViewController: UIViewController, ViewModelBindableType { self.viewModel.makeMoveAction(to: viewController) } - viewModel.firebaseDidLoad + viewModel.storageDidSetup .subscribe(onNext: { [unowned self] isLoaded in guard isLoaded else { return } self.unsetLoadingView() diff --git a/FullStackCodingBot/FullStackCodingBot/View/Scene/Scene.swift b/FullStackCodingBot/FullStackCodingBot/View/Scene/Scene.swift deleted file mode 100644 index 5f9c4c8..0000000 --- a/FullStackCodingBot/FullStackCodingBot/View/Scene/Scene.swift +++ /dev/null @@ -1,121 +0,0 @@ -import UIKit -import GameKit -import Firebase - -enum Scene { - case main(MainViewModel) - case setting(MainViewModel) - case howToPlay(HowToPlayViewModel) - case story(StoryViewModel) - case shop(ShopViewModel) - case rank(RankViewModel) - case item(ItemViewModel) - case game(GameViewModel) - case pause(PauseViewModel) - case gameOver(GameOverViewModel) - case alert(AlertMessage) - case gameCenter(UIViewController) -} - -extension Scene { - - // swiftlint:disable:next cyclomatic_complexity - func instantiate(from identifier: String) -> UIViewController { - let storyboard = UIStoryboard(name: identifier, bundle: nil) - switch self { - - case .main(let viewModel): - guard var mainVC = storyboard.instantiateViewController(withIdentifier: IdentifierVC.main) as? MainViewController else { - Firebase.Analytics.logEvent("ViewControllerError", parameters: nil) - fatalError() - } - mainVC.bind(viewModel: viewModel) - return mainVC - - case .shop(let viewModel): - guard var shopVC = storyboard.instantiateViewController(withIdentifier: IdentifierVC.shop) as? ShopViewController else { - Firebase.Analytics.logEvent("ViewControllerError", parameters: nil) - fatalError() - } - shopVC.bind(viewModel: viewModel) - return shopVC - - case .rank(let viewModel): - guard var rankVC = storyboard.instantiateViewController(withIdentifier: IdentifierVC.rank) as? RankViewController else { - Firebase.Analytics.logEvent("ViewControllerError", parameters: nil) - fatalError() - } - rankVC.gameCenterDelegate = viewModel - rankVC.leaderboardIdentifier = IdentifierGC.leaderboard - rankVC.viewState = .leaderboards - rankVC.bind(viewModel: viewModel) - return rankVC - - case .item(let viewModel): - guard var itemVC = storyboard.instantiateViewController(withIdentifier: IdentifierVC.item) as? ItemViewController else { - Firebase.Analytics.logEvent("ViewControllerError", parameters: nil) - fatalError() - } - itemVC.bind(viewModel: viewModel) - return itemVC - - case .game(let viewModel): - guard var gameVC = storyboard.instantiateViewController(withIdentifier: IdentifierVC.game) as? GameViewController else { - Firebase.Analytics.logEvent("ViewControllerError", parameters: nil) - fatalError() - } - gameVC.bind(viewModel: viewModel) - return gameVC - - case .pause(let viewModel): - guard var pauseVC = storyboard.instantiateViewController(withIdentifier: IdentifierVC.pause) as? PauseViewController else { - Firebase.Analytics.logEvent("ViewControllerError", parameters: nil) - fatalError() - } - pauseVC.bind(viewModel: viewModel) - return pauseVC - - case .gameOver(let viewModel): - guard var gameOverVC = storyboard.instantiateViewController(withIdentifier: IdentifierVC.gameOver) as? GameOverViewController else { - Firebase.Analytics.logEvent("ViewControllerError", parameters: nil) - fatalError() - } - gameOverVC.bind(viewModel: viewModel) - return gameOverVC - - case .alert(let message): - let alerMessage = message.content.message - let alertScene = UIAlertController(title: alerMessage.title, message: alerMessage.content, preferredStyle: .alert) - let confirmAction = UIAlertAction(title: message.content.confirm, style: .cancel) - alertScene.addAction(confirmAction) - return alertScene - - case .setting(let viewModel): - guard var settingVC = storyboard.instantiateViewController(withIdentifier: IdentifierVC.setting) as? SettingViewController else { - Firebase.Analytics.logEvent("ViewControllerError", parameters: nil) - fatalError() - } - settingVC.bind(viewModel: viewModel) - return settingVC - - case .story(let viewModel): - guard var storyVC = storyboard.instantiateViewController(withIdentifier: IdentifierVC.story) as? StoryViewController else { - Firebase.Analytics.logEvent("ViewControllerError", parameters: nil) - fatalError() - } - storyVC.bind(viewModel: viewModel) - return storyVC - - case .howToPlay(let viewModel): - guard var howToVC = storyboard.instantiateViewController(withIdentifier: IdentifierVC.howTo) as? HowToPlayViewController else { - Firebase.Analytics.logEvent("ViewControllerError", parameters: nil) - fatalError() - } - howToVC.bind(viewModel: viewModel) - return howToVC - - case .gameCenter(let viewController): - return viewController - } - } -} diff --git a/FullStackCodingBot/FullStackCodingBot/View/Scene/SceneCoordinator.swift b/FullStackCodingBot/FullStackCodingBot/View/Scene/SceneCoordinator.swift index 715178a..ca0c70e 100644 --- a/FullStackCodingBot/FullStackCodingBot/View/Scene/SceneCoordinator.swift +++ b/FullStackCodingBot/FullStackCodingBot/View/Scene/SceneCoordinator.swift @@ -14,7 +14,7 @@ final class SceneCoordinator: SceneCoordinatorType { } @discardableResult - func transition(to scene: Scene, using style: TransitionStyle, with type: StoryboardType, animated: Bool) -> Completable { + func transition(to scene: SceneType, using style: TransitionStyle, with type: StoryboardType, animated: Bool) -> Completable { let subject = PublishSubject() let target = scene.instantiate(from: type.name) diff --git a/FullStackCodingBot/FullStackCodingBot/View/Scene/SceneCoordinatorType.swift b/FullStackCodingBot/FullStackCodingBot/View/Scene/SceneCoordinatorType.swift index 90c1ec0..a3a50f5 100644 --- a/FullStackCodingBot/FullStackCodingBot/View/Scene/SceneCoordinatorType.swift +++ b/FullStackCodingBot/FullStackCodingBot/View/Scene/SceneCoordinatorType.swift @@ -4,7 +4,7 @@ import RxSwift protocol SceneCoordinatorType { @discardableResult - func transition(to scene: Scene, using style: TransitionStyle, with type: StoryboardType, animated: Bool) -> Completable + func transition(to scene: SceneType, using style: TransitionStyle, with type: StoryboardType, animated: Bool) -> Completable @discardableResult func close(animated: Bool) -> Completable diff --git a/FullStackCodingBot/FullStackCodingBot/View/Scene/SceneType/AlertScene.swift b/FullStackCodingBot/FullStackCodingBot/View/Scene/SceneType/AlertScene.swift new file mode 100644 index 0000000..789513c --- /dev/null +++ b/FullStackCodingBot/FullStackCodingBot/View/Scene/SceneType/AlertScene.swift @@ -0,0 +1,24 @@ +import UIKit +import GameKit +import Firebase + +enum AlertScene { + case alert(AlertMessage) +} + +extension AlertScene: SceneType { + + func instantiate(from identifier: String) -> UIViewController { + + switch self { + + case .alert(let message): + let alerMessage = message.content.message + let alertScene = UIAlertController(title: alerMessage.title, message: alerMessage.content, preferredStyle: .alert) + let confirmAction = UIAlertAction(title: message.content.confirm, style: .cancel) + alertScene.addAction(confirmAction) + return alertScene + + } + } +} diff --git a/FullStackCodingBot/FullStackCodingBot/View/Scene/SceneType/GameHelperScene.swift b/FullStackCodingBot/FullStackCodingBot/View/Scene/SceneType/GameHelperScene.swift new file mode 100644 index 0000000..ea8a2ec --- /dev/null +++ b/FullStackCodingBot/FullStackCodingBot/View/Scene/SceneType/GameHelperScene.swift @@ -0,0 +1,43 @@ +import UIKit +import GameKit +import Firebase + +enum GameHelperScene { + case setting(MainViewModel) + case howToPlay(HowToPlayViewModel) + case story(StoryViewModel) +} + +extension GameHelperScene: SceneType { + + func instantiate(from identifier: String) -> UIViewController { + let storyboard = UIStoryboard(name: identifier, bundle: nil) + switch self { + + case .setting(let viewModel): + guard var settingVC = storyboard.instantiateViewController(withIdentifier: IdentifierVC.setting) as? SettingViewController else { + Firebase.Analytics.logEvent("ViewControllerError", parameters: nil) + fatalError() + } + settingVC.bind(viewModel: viewModel) + return settingVC + + case .howToPlay(let viewModel): + guard var howToVC = storyboard.instantiateViewController(withIdentifier: IdentifierVC.howTo) as? HowToPlayViewController else { + Firebase.Analytics.logEvent("ViewControllerError", parameters: nil) + fatalError() + } + howToVC.bind(viewModel: viewModel) + return howToVC + + case .story(let viewModel): + guard var storyVC = storyboard.instantiateViewController(withIdentifier: IdentifierVC.story) as? StoryViewController else { + Firebase.Analytics.logEvent("ViewControllerError", parameters: nil) + fatalError() + } + storyVC.bind(viewModel: viewModel) + return storyVC + + } + } +} diff --git a/FullStackCodingBot/FullStackCodingBot/View/Scene/SceneType/GameScene.swift b/FullStackCodingBot/FullStackCodingBot/View/Scene/SceneType/GameScene.swift new file mode 100644 index 0000000..5b6b42b --- /dev/null +++ b/FullStackCodingBot/FullStackCodingBot/View/Scene/SceneType/GameScene.swift @@ -0,0 +1,43 @@ +import UIKit +import GameKit +import Firebase + +enum GameScene { + case game(GameViewModel) + case pause(PauseViewModel) + case gameOver(GameOverViewModel) +} + +extension GameScene: SceneType { + + func instantiate(from identifier: String) -> UIViewController { + let storyboard = UIStoryboard(name: identifier, bundle: nil) + switch self { + + case .game(let viewModel): + guard var gameVC = storyboard.instantiateViewController(withIdentifier: IdentifierVC.game) as? GameViewController else { + Firebase.Analytics.logEvent("ViewControllerError", parameters: nil) + fatalError() + } + gameVC.bind(viewModel: viewModel) + return gameVC + + case .pause(let viewModel): + guard var pauseVC = storyboard.instantiateViewController(withIdentifier: IdentifierVC.pause) as? PauseViewController else { + Firebase.Analytics.logEvent("ViewControllerError", parameters: nil) + fatalError() + } + pauseVC.bind(viewModel: viewModel) + return pauseVC + + case .gameOver(let viewModel): + guard var gameOverVC = storyboard.instantiateViewController(withIdentifier: IdentifierVC.gameOver) as? GameOverViewController else { + Firebase.Analytics.logEvent("ViewControllerError", parameters: nil) + fatalError() + } + gameOverVC.bind(viewModel: viewModel) + return gameOverVC + + } + } +} diff --git a/FullStackCodingBot/FullStackCodingBot/View/Scene/SceneType/MainScene.swift b/FullStackCodingBot/FullStackCodingBot/View/Scene/SceneType/MainScene.swift new file mode 100644 index 0000000..ab7cec5 --- /dev/null +++ b/FullStackCodingBot/FullStackCodingBot/View/Scene/SceneType/MainScene.swift @@ -0,0 +1,59 @@ +import UIKit +import GameKit +import Firebase + +enum MainScene { + case main(MainViewModel) + case shop(ShopViewModel) + case rank(RankViewModel) + case item(ItemViewModel) + case gameCenter(UIViewController) +} + +extension MainScene: SceneType { + + func instantiate(from identifier: String) -> UIViewController { + let storyboard = UIStoryboard(name: identifier, bundle: nil) + switch self { + + case .main(let viewModel): + guard var mainVC = storyboard.instantiateViewController(withIdentifier: IdentifierVC.main) as? MainViewController else { + Firebase.Analytics.logEvent("ViewControllerError", parameters: nil) + fatalError() + } + mainVC.bind(viewModel: viewModel) + return mainVC + + case .shop(let viewModel): + guard var shopVC = storyboard.instantiateViewController(withIdentifier: IdentifierVC.shop) as? ShopViewController else { + Firebase.Analytics.logEvent("ViewControllerError", parameters: nil) + fatalError() + } + shopVC.bind(viewModel: viewModel) + return shopVC + + case .rank(let viewModel): + guard var rankVC = storyboard.instantiateViewController(withIdentifier: IdentifierVC.rank) as? RankViewController else { + Firebase.Analytics.logEvent("ViewControllerError", parameters: nil) + fatalError() + } + rankVC.gameCenterDelegate = viewModel + rankVC.leaderboardIdentifier = IdentifierGC.leaderboard + rankVC.viewState = .leaderboards + rankVC.bind(viewModel: viewModel) + return rankVC + + case .item(let viewModel): + guard var itemVC = storyboard.instantiateViewController(withIdentifier: IdentifierVC.item) as? ItemViewController else { + Firebase.Analytics.logEvent("ViewControllerError", parameters: nil) + fatalError() + } + itemVC.bind(viewModel: viewModel) + return itemVC + + case .gameCenter(let viewController): + return viewController + + } + } +} diff --git a/FullStackCodingBot/FullStackCodingBot/View/Scene/SceneType/SceneType.swift b/FullStackCodingBot/FullStackCodingBot/View/Scene/SceneType/SceneType.swift new file mode 100644 index 0000000..fb28ae1 --- /dev/null +++ b/FullStackCodingBot/FullStackCodingBot/View/Scene/SceneType/SceneType.swift @@ -0,0 +1,5 @@ +import UIKit + +protocol SceneType { + func instantiate(from identifier: String) -> UIViewController +} diff --git a/FullStackCodingBot/FullStackCodingBot/ViewModel/AdViewModel.swift b/FullStackCodingBot/FullStackCodingBot/ViewModel/AdViewModel.swift index f41c748..5c38f66 100644 --- a/FullStackCodingBot/FullStackCodingBot/ViewModel/AdViewModel.swift +++ b/FullStackCodingBot/FullStackCodingBot/ViewModel/AdViewModel.swift @@ -1,11 +1,8 @@ import Foundation +// Ad 부분 인터페이스를 나누는 게 나을까? class AdViewModel: CommonViewModel { - - let adStorage: AdStorageType - - init(sceneCoordinator: SceneCoordinatorType, storage: PersistenceStorageType, adStorage: AdStorageType, database: DatabaseManagerType) { - self.adStorage = adStorage - super.init(sceneCoordinator: sceneCoordinator, storage: storage, database: database) + override init(sceneCoordinator: SceneCoordinatorType, storage: StorageType) { + super.init(sceneCoordinator: sceneCoordinator, storage: storage) } } diff --git a/FullStackCodingBot/FullStackCodingBot/ViewModel/CommonViewModel.swift b/FullStackCodingBot/FullStackCodingBot/ViewModel/CommonViewModel.swift index 96f87d7..0a38487 100644 --- a/FullStackCodingBot/FullStackCodingBot/ViewModel/CommonViewModel.swift +++ b/FullStackCodingBot/FullStackCodingBot/ViewModel/CommonViewModel.swift @@ -3,12 +3,10 @@ import Foundation class CommonViewModel: NSObject { let sceneCoordinator: SceneCoordinatorType - let storage: PersistenceStorageType - let database: DatabaseManagerType + let storage: StorageType - init(sceneCoordinator: SceneCoordinatorType, storage: PersistenceStorageType, database: DatabaseManagerType) { + init(sceneCoordinator: SceneCoordinatorType, storage: StorageType) { self.sceneCoordinator = sceneCoordinator self.storage = storage - self.database = database } } diff --git a/FullStackCodingBot/FullStackCodingBot/ViewModel/GameOverViewModel.swift b/FullStackCodingBot/FullStackCodingBot/ViewModel/GameOverViewModel.swift index 80b34f2..840cfd5 100644 --- a/FullStackCodingBot/FullStackCodingBot/ViewModel/GameOverViewModel.swift +++ b/FullStackCodingBot/FullStackCodingBot/ViewModel/GameOverViewModel.swift @@ -23,12 +23,11 @@ final class GameOverViewModel: CommonViewModel { }() lazy var currentMoney: Driver = { - return storage.availableMoeny().map { String($0) }.asDriver(onErrorJustReturn: "") + return storage.availableMoney().map { String($0) }.asDriver(onErrorJustReturn: "") }() init(sceneCoordinator: SceneCoordinatorType, - storage: PersistenceStorageType, - database: DatabaseManagerType, + storage: StorageType, finalScore: Int, newGameStatus: BehaviorRelay, gameStoryManager: GameStoryManager = GameStoryManager()) { @@ -36,7 +35,7 @@ final class GameOverViewModel: CommonViewModel { self.moneyInfo.accept(finalScore/10) self.newGameStatus = newGameStatus self.gameStoryManager = gameStoryManager - super.init(sceneCoordinator: sceneCoordinator, storage: storage, database: database) + super.init(sceneCoordinator: sceneCoordinator, storage: storage) } func execute() { diff --git a/FullStackCodingBot/FullStackCodingBot/ViewModel/GameViewModel.swift b/FullStackCodingBot/FullStackCodingBot/ViewModel/GameViewModel.swift index cb1fd14..40a9ff9 100644 --- a/FullStackCodingBot/FullStackCodingBot/ViewModel/GameViewModel.swift +++ b/FullStackCodingBot/FullStackCodingBot/ViewModel/GameViewModel.swift @@ -33,8 +33,7 @@ final class GameViewModel: CommonViewModel { } init(sceneCoordinator: SceneCoordinatorType, - storage: PersistenceStorageType, - database: DatabaseManagerType, + storage: StorageType, pauseAction: CocoaAction? = nil, gameUnitManager: GameUnitManagerType, timeManager: TimeManagerType = TimeManager(), @@ -42,7 +41,7 @@ final class GameViewModel: CommonViewModel { self.gameUnitManager = gameUnitManager self.timeManager = timeManager self.gameSoundStation = gameSoundStation - super.init(sceneCoordinator: sceneCoordinator, storage: storage, database: database) + super.init(sceneCoordinator: sceneCoordinator, storage: storage) } } @@ -205,16 +204,16 @@ private extension GameViewModel { @discardableResult private func toGameOverScene() -> Completable { let currentScore = try? currentScore.value() - let gameOverViewModel = GameOverViewModel(sceneCoordinator: sceneCoordinator, storage: storage, database: database, finalScore: currentScore ?? 0, newGameStatus: newGameStatus) - let gameOverScene = Scene.gameOver(gameOverViewModel) + let gameOverViewModel = GameOverViewModel(sceneCoordinator: sceneCoordinator, storage: storage, finalScore: currentScore ?? 0, newGameStatus: newGameStatus) + let gameOverScene = GameScene.gameOver(gameOverViewModel) return self.sceneCoordinator.transition(to: gameOverScene, using: .pop, with: StoryboardType.game, animated: true) } @discardableResult private func toPauseScene() -> Completable { let currentScore = try? currentScore.value() - let pauseViewModel = PauseViewModel(sceneCoordinator: sceneCoordinator, storage: storage, database: database, currentScore: currentScore ?? 0, newGameStatus: newGameStatus) - let pauseScene = Scene.pause(pauseViewModel) + let pauseViewModel = PauseViewModel(sceneCoordinator: sceneCoordinator, storage: storage, currentScore: currentScore ?? 0, newGameStatus: newGameStatus) + let pauseScene = GameScene.pause(pauseViewModel) return self.sceneCoordinator.transition(to: pauseScene, using: .fullScreen, with: StoryboardType.game, animated: false) } } diff --git a/FullStackCodingBot/FullStackCodingBot/ViewModel/HowToPlayViewModel.swift b/FullStackCodingBot/FullStackCodingBot/ViewModel/HowToPlayViewModel.swift index fe25d1f..ecfca50 100644 --- a/FullStackCodingBot/FullStackCodingBot/ViewModel/HowToPlayViewModel.swift +++ b/FullStackCodingBot/FullStackCodingBot/ViewModel/HowToPlayViewModel.swift @@ -9,20 +9,14 @@ final class HowToPlayViewModel: CommonViewModel { let currentPage = BehaviorRelay(value: 0) let currentManual = BehaviorRelay(value: Manual.all[0]) - init(sceneCoordinator: SceneCoordinatorType, - storage: PersistenceStorageType, - adStorage: AdStorageType, - database: DatabaseManagerType, - cancelAction: CocoaAction? = nil) { - + init(sceneCoordinator: SceneCoordinatorType, storage: StorageType, cancelAction: CocoaAction? = nil) { self.cancelAction = CocoaAction { if let action = cancelAction { action.execute(()) } return sceneCoordinator.close(animated: true).asObservable().map { _ in } } - - super.init(sceneCoordinator: sceneCoordinator, storage: storage, database: database) + super.init(sceneCoordinator: sceneCoordinator, storage: storage) } func moveToPage(from direction: DirectionType) { diff --git a/FullStackCodingBot/FullStackCodingBot/ViewModel/ItemViewModel.swift b/FullStackCodingBot/FullStackCodingBot/ViewModel/ItemViewModel.swift index 22f7b77..e8ec06b 100644 --- a/FullStackCodingBot/FullStackCodingBot/ViewModel/ItemViewModel.swift +++ b/FullStackCodingBot/FullStackCodingBot/ViewModel/ItemViewModel.swift @@ -10,11 +10,11 @@ final class ItemViewModel: CommonViewModel { } var itemStorage: Driver<[Unit]> { - return storage.listUnit().asDriver(onErrorJustReturn: []) + return BehaviorRelay<[Unit]>(value: storage.itemList()).asDriver(onErrorJustReturn: []) } var money: Driver { - return storage.availableMoeny().asDriver(onErrorJustReturn: 0) + return storage.availableMoney().asDriver(onErrorJustReturn: 0) } let isPossibleToLevelUp = BehaviorRelay(value: false) @@ -25,8 +25,7 @@ final class ItemViewModel: CommonViewModel { private let soundEffectStation: SingleSoundEffectStation init(sceneCoordinator: SceneCoordinatorType, - storage: PersistenceStorageType, - database: DatabaseManagerType, + storage: StorageType, cancelAction: CocoaAction? = nil, soundEffectType: MainSoundEffect = .upgrade) { self.cancelAction = CocoaAction { @@ -36,11 +35,11 @@ final class ItemViewModel: CommonViewModel { return sceneCoordinator.close(animated: true).asObservable().map { _ in } } self.soundEffectStation = SingleSoundEffectStation(soundEffectType: soundEffectType) - super.init(sceneCoordinator: sceneCoordinator, storage: storage, database: database) + super.init(sceneCoordinator: sceneCoordinator, storage: storage) } func checkLevelUpPrice() { - storage.availableMoeny() + storage.availableMoney() .subscribe(onNext: { [unowned self] availableMoney in if availableMoney >= (selectedUnit.value.level * 100) { self.isPossibleToLevelUp.accept(true) diff --git a/FullStackCodingBot/FullStackCodingBot/ViewModel/MainViewModel.swift b/FullStackCodingBot/FullStackCodingBot/ViewModel/MainViewModel.swift index 52f5cc7..92c723c 100644 --- a/FullStackCodingBot/FullStackCodingBot/ViewModel/MainViewModel.swift +++ b/FullStackCodingBot/FullStackCodingBot/ViewModel/MainViewModel.swift @@ -11,19 +11,19 @@ final class MainViewModel: AdViewModel { private let userDefaults = UserDefaults.standard lazy var settingSwitchState = BehaviorRelay(value: settingInfo) - let firebaseDidLoad = BehaviorRelay(value: false) + let storageDidSetup = BehaviorRelay(value: false) private(set) var rewardAvailable = BehaviorRelay(value: false) - init(sceneCoordinator: SceneCoordinatorType, storage: PersistenceStorageType, adStorage: AdStorageType, database: DatabaseManagerType, settings: SettingInformation) { + init(sceneCoordinator: SceneCoordinatorType, storage: StorageType, settings: SettingInformation) { self.settingInfo = settings - super.init(sceneCoordinator: sceneCoordinator, storage: storage, adStorage: adStorage, database: database) - - bindRewardState() + super.init(sceneCoordinator: sceneCoordinator, storage: storage) + + bindRewardStates() setupAppleGameCenterLogin() } - - private func bindRewardState() { - adStorage.availableItems() + + private func bindRewardStates() { + storage.availableRewards() .subscribe(onNext: { [weak self] items in let rewardState = !ShopItem.isAllTaken(items) self?.rewardAvailable.accept(rewardState) @@ -31,42 +31,42 @@ final class MainViewModel: AdViewModel { } func makeMoveAction(to viewController: ViewControllerType) { - guard firebaseDidLoad.value else { return } + guard storageDidSetup.value else { return } switch viewController { case .giftVC: - let shopViewModel = ShopViewModel(sceneCoordinator: self.sceneCoordinator, storage: self.storage, adStorage: adStorage, database: database) - let shopScene = Scene.shop(shopViewModel) + let shopViewModel = ShopViewModel(sceneCoordinator: self.sceneCoordinator, storage: self.storage) + let shopScene = MainScene.shop(shopViewModel) self.sceneCoordinator.transition(to: shopScene, using: .fullScreen, with: StoryboardType.main, animated: true) case .rankVC: - let rankViewModel = RankViewModel(sceneCoordinator: self.sceneCoordinator, storage: self.storage, database: database) - let rankScene = Scene.rank(rankViewModel) + let rankViewModel = RankViewModel(sceneCoordinator: self.sceneCoordinator, storage: self.storage) + let rankScene = MainScene.rank(rankViewModel) self.sceneCoordinator.transition(to: rankScene, using: .fullScreen, with: StoryboardType.main, animated: true) case .itemVC: - let itemViewModel = ItemViewModel(sceneCoordinator: self.sceneCoordinator, storage: self.storage, database: database) - let itemScene = Scene.item(itemViewModel) + let itemViewModel = ItemViewModel(sceneCoordinator: self.sceneCoordinator, storage: self.storage) + let itemScene = MainScene.item(itemViewModel) self.sceneCoordinator.transition(to: itemScene, using: .fullScreen, with: StoryboardType.main, animated: true) case .gameVC: - let gameUnitManager = GameUnitManager(allKinds: self.storage.itemList()) - let gameViewModel = GameViewModel(sceneCoordinator: self.sceneCoordinator, storage: self.storage, database: database, gameUnitManager: gameUnitManager) - let gameScene = Scene.game(gameViewModel) + let gameUnitManager = GameUnitManager(allKinds: Unit.initialValues()) + let gameViewModel = GameViewModel(sceneCoordinator: self.sceneCoordinator, storage: self.storage, gameUnitManager: gameUnitManager) + let gameScene = GameScene.game(gameViewModel) self.sceneCoordinator.transition(to: gameScene, using: .fullScreen, with: StoryboardType.game, animated: true) case .settingVC: - let settingScene = Scene.setting(self) + let settingScene = GameHelperScene.setting(self) self.sceneCoordinator.transition(to: settingScene, using: .overCurrent, with: StoryboardType.main, animated: true) case .storyVC: - let storyViewModel = StoryViewModel(sceneCoordinator: sceneCoordinator, storage: storage, adStorage: adStorage, database: database, settings: settingInfo, isFirstTimePlay: false) - let storyScene = Scene.story(storyViewModel) + let storyViewModel = StoryViewModel(sceneCoordinator: sceneCoordinator, storage: storage, settings: settingInfo, isFirstTimePlay: false) + let storyScene = GameHelperScene.story(storyViewModel) self.sceneCoordinator.transition(to: storyScene, using: .fullScreen, with: StoryboardType.main, animated: true) case .howToVC: - let howToViewModel = HowToPlayViewModel(sceneCoordinator: sceneCoordinator, storage: storage, adStorage: adStorage, database: database) - let howToScene = Scene.howToPlay(howToViewModel) + let howToViewModel = HowToPlayViewModel(sceneCoordinator: sceneCoordinator, storage: storage) + let howToScene = GameHelperScene.howToPlay(howToViewModel) self.sceneCoordinator.transition(to: howToScene, using: .fullScreen, with: StoryboardType.main, animated: true) } } @@ -91,7 +91,7 @@ final class MainViewModel: AdViewModel { } } -// MARK: Login & Load Data +// MARK: Login extension MainViewModel: GKGameCenterControllerDelegate { func gameCenterViewControllerDidFinish(_ gameCenterViewController: GKGameCenterViewController) { @@ -99,13 +99,12 @@ extension MainViewModel: GKGameCenterControllerDelegate { private func setupAppleGameCenterLogin() { GKLocalPlayer.local.authenticateHandler = { [unowned self] gcViewController, error in - if let gcViewController = gcViewController { - let scene = Scene.gameCenter(gcViewController) + let scene = MainScene.gameCenter(gcViewController) self.sceneCoordinator.transition(to: scene, using: .fullScreen, with: StoryboardType.main, animated: false) } else if let error = error { Firebase.Analytics.logEvent("CancelGameCenter", parameters: ["ErrorMessage": "\(error.localizedDescription)"]) - self.loadOffline() + self.load(with: nil) } else { self.sceneCoordinator.toMain(animated: true) @@ -116,7 +115,7 @@ extension MainViewModel: GKGameCenterControllerDelegate { } guard let credential = credential else { - self.loadOffline() + self.load(with: nil) return } @@ -124,81 +123,48 @@ extension MainViewModel: GKGameCenterControllerDelegate { if let error = error { Firebase.Analytics.logEvent("SignInError", parameters: ["ErrorMessage": "\(error.localizedDescription)"]) - self.loadOffline() + self.load(with: nil) } if let user = user { - loadOnline(user.user.uid) + self.load(with: user.user.uid) } } } } } } - - private func loadOffline() { - loadFromCoredata() - sceneCoordinator.transition(to: .alert(AlertMessage.networkLoad), - using: .alert, - with: .main, - animated: true) - } - - private func loadOnline(_ uuid: String?) { - if !userDefaults.bool(forKey: IdentifierUD.hasLaunchedOnce) { - storage.setupInitialData() - } - loadFromFirebase(uuid) - } - - private func loadFromCoredata() { - switch userDefaults.bool(forKey: IdentifierUD.hasLaunchedOnce) { - case true: - storage.getCoreDataInfo() - case false: - storage.setupInitialData() - } - userDefaults.setValue(true, forKey: IdentifierUD.hasLaunchedOnce) - firebaseDidLoad.accept(true) - } - - private func loadFromFirebase(_ uuid: String?) { - if firebaseDidLoad.value { return } - getUserInformation(uuid) - userDefaults.setValue(true, forKey: IdentifierUD.hasLaunchedOnce) - } - - private func getUserInformation(_ uuid: String?) { - database.getFirebaseData(uuid!) +} + +// MARK: Storage Load +extension MainViewModel { + private func load(with uuid: String?) { + guard !self.storageDidSetup.value else { return } + + let isFirstLaunched = !userDefaults.bool(forKey: IdentifierUD.hasLaunchedOnce) + + storage.initializeData(using: uuid, isFirstLaunched: isFirstLaunched) .observe(on: MainScheduler.asyncInstance) - .subscribe(onNext: { [unowned self] data in - self.updateDatabaseInformation(data) - self.updateAdInformation(data) - }, onError: { _ in - self.loadFromCoredata() - }, onCompleted: { [unowned self] in - self.firebaseDidLoad.accept(true) + .subscribe(onNext: { [unowned self] isInitialized in + self.storageDidSetup.accept(isInitialized) }).disposed(by: rx.disposeBag) - } - - private func updateDatabaseInformation(_ info: NetworkDTO) { - let firebaseUpdate = info.date - let coredataUpdate = storage.lastUpdated() - guard firebaseUpdate > coredataUpdate else { - loadFromCoredata() - return - } + markAsLauncedOnce() - storage.update(units: info.units) - storage.raiseMoney(by: info.money) - storage.updateHighScore(new: info.score) + if uuid == nil { + showNetworkAlert() + } + } + + private func markAsLauncedOnce() { + userDefaults.setValue(true, forKey: IdentifierUD.hasLaunchedOnce) } - private func updateAdInformation(_ info: NetworkDTO) { - adStorage.setNewRewardsIfPossible(with: info.ads) - .subscribe(onError: { error in - Firebase.Analytics.logEvent("RewardsError", parameters: ["ErrorMessage": "\(error)"])}) - .disposed(by: rx.disposeBag) + private func showNetworkAlert() { + let alertScene = AlertScene.alert(AlertMessage.networkLoad) + sceneCoordinator.transition(to: alertScene, + using: .alert, + with: .main, + animated: true) } } diff --git a/FullStackCodingBot/FullStackCodingBot/ViewModel/PauseViewModel.swift b/FullStackCodingBot/FullStackCodingBot/ViewModel/PauseViewModel.swift index aa857e9..8459fec 100644 --- a/FullStackCodingBot/FullStackCodingBot/ViewModel/PauseViewModel.swift +++ b/FullStackCodingBot/FullStackCodingBot/ViewModel/PauseViewModel.swift @@ -11,10 +11,10 @@ final class PauseViewModel: CommonViewModel { return currentScore.map {String($0)}.asDriver(onErrorJustReturn: "") }() - init(sceneCoordinator: SceneCoordinatorType, storage: PersistenceStorageType, database: DatabaseManagerType, currentScore: Int, newGameStatus: BehaviorRelay) { + init(sceneCoordinator: SceneCoordinatorType, storage: StorageType, currentScore: Int, newGameStatus: BehaviorRelay) { self.newGameStatus = newGameStatus self.currentScore.accept(currentScore) - super.init(sceneCoordinator: sceneCoordinator, storage: storage, database: database) + super.init(sceneCoordinator: sceneCoordinator, storage: storage) } func makeMoveAction(to viewController: PauseActionType) { diff --git a/FullStackCodingBot/FullStackCodingBot/ViewModel/RankViewModel.swift b/FullStackCodingBot/FullStackCodingBot/ViewModel/RankViewModel.swift index ff1bdca..f82d073 100644 --- a/FullStackCodingBot/FullStackCodingBot/ViewModel/RankViewModel.swift +++ b/FullStackCodingBot/FullStackCodingBot/ViewModel/RankViewModel.swift @@ -6,21 +6,18 @@ final class RankViewModel: CommonViewModel { let cancelAction: CocoaAction - init(sceneCoordinator: SceneCoordinatorType, storage: PersistenceStorageType, database: DatabaseManagerType, cancelAction: CocoaAction? = nil) { - + init(sceneCoordinator: SceneCoordinatorType, storage: StorageType, cancelAction: CocoaAction? = nil) { self.cancelAction = CocoaAction { if let action = cancelAction { action.execute(()) } return sceneCoordinator.close(animated: true).asObservable().map { _ in } } - - super.init(sceneCoordinator: sceneCoordinator, storage: storage, database: database) + super.init(sceneCoordinator: sceneCoordinator, storage: storage) } } extension RankViewModel: GKGameCenterControllerDelegate { - func gameCenterViewControllerDidFinish(_ gameCenterViewController: GKGameCenterViewController) { sceneCoordinator.close(animated: true) } diff --git a/FullStackCodingBot/FullStackCodingBot/ViewModel/ShopViewModel.swift b/FullStackCodingBot/FullStackCodingBot/ViewModel/ShopViewModel.swift index a0e93b3..d5ffab9 100644 --- a/FullStackCodingBot/FullStackCodingBot/ViewModel/ShopViewModel.swift +++ b/FullStackCodingBot/FullStackCodingBot/ViewModel/ShopViewModel.swift @@ -10,11 +10,11 @@ final class ShopViewModel: AdViewModel { let cancelAction: CocoaAction var itemStorage: Driver<[ShopItem]> { - return adStorage.availableItems().asDriver(onErrorJustReturn: []) + return storage.availableRewards().asDriver(onErrorJustReturn: []) } lazy var currentMoney: Driver = { - return storage.availableMoeny().map {String($0)}.asDriver(onErrorJustReturn: "") + return storage.availableMoney().map { String($0) }.asDriver(onErrorJustReturn: "") }() lazy var selectedItem = BehaviorRelay(value: nil) @@ -22,9 +22,7 @@ final class ShopViewModel: AdViewModel { private let soundEffectStation: SingleSoundEffectStation init(sceneCoordinator: SceneCoordinatorType, - storage: PersistenceStorageType, - adStorage: AdStorageType, - database: DatabaseManagerType, + storage: StorageType, cancelAction: CocoaAction? = nil, soundEffectType: MainSoundEffect = .reward) { self.cancelAction = CocoaAction { @@ -34,7 +32,7 @@ final class ShopViewModel: AdViewModel { return sceneCoordinator.close(animated: true).asObservable().map { _ in } } self.soundEffectStation = SingleSoundEffectStation(soundEffectType: soundEffectType) - super.init(sceneCoordinator: sceneCoordinator, storage: storage, adStorage: adStorage, database: database) + super.init(sceneCoordinator: sceneCoordinator, storage: storage) } func execute() { @@ -42,26 +40,24 @@ final class ShopViewModel: AdViewModel { } private func bindAdStorage() { - adStorage.setNewRewardsIfPossible(with: .none) + storage.setNewRewardsIfPossible() .subscribe(onError: { error in Firebase.Analytics.logEvent("RewardsError", parameters: ["ErrorMessage": "\(error)"])}) .disposed(by: rx.disposeBag) } func adDidFinished(_ finishedAd: GADRewardedAd) { - adStorage.adDidFinished(finishedAd) - addCoin() + let reward = storage.rewardNeedsToBeGiven(with: finishedAd) + addCoin(by: reward) } func giftTaken() { - adStorage.giftTaken() - addCoin() + let reward = storage.rewardNeedsToBeGiven(with: nil) + addCoin(by: reward) } - private func addCoin() { - let moneyToRaise = ShopSetting.reward() - storage.raiseMoney(by: moneyToRaise) - reward.accept(moneyToRaise) + private func addCoin(by amount: Int) { + reward.accept(amount) soundEffectStation.play() } } diff --git a/FullStackCodingBot/FullStackCodingBot/ViewModel/StoryViewModel.swift b/FullStackCodingBot/FullStackCodingBot/ViewModel/StoryViewModel.swift index ab0fc2a..878b771 100644 --- a/FullStackCodingBot/FullStackCodingBot/ViewModel/StoryViewModel.swift +++ b/FullStackCodingBot/FullStackCodingBot/ViewModel/StoryViewModel.swift @@ -10,16 +10,14 @@ final class StoryViewModel: AdViewModel { private let isFirstTimePlay: Bool init(sceneCoordinator: SceneCoordinatorType, - storage: PersistenceStorageType, - adStorage: AdStorageType, - database: DatabaseManagerType, + storage: StorageType, settings: SettingInformation, storyManger: StoryManager = StoryManager(), isFirstTimePlay: Bool = true) { self.settings = settings self.storyManager = storyManger self.isFirstTimePlay = isFirstTimePlay - super.init(sceneCoordinator: sceneCoordinator, storage: storage, adStorage: adStorage, database: database) + super.init(sceneCoordinator: sceneCoordinator, storage: storage) } func setupStoryTimer() { @@ -46,8 +44,8 @@ final class StoryViewModel: AdViewModel { } private func makeMoveActionToMain() { - let mainViewModel = MainViewModel(sceneCoordinator: sceneCoordinator, storage: storage, adStorage: adStorage, database: database, settings: settings) - let mainScene = Scene.main(mainViewModel) + let mainViewModel = MainViewModel(sceneCoordinator: sceneCoordinator, storage: storage, settings: settings) + let mainScene = MainScene.main(mainViewModel) self.sceneCoordinator.transition(to: mainScene, using: .root, with: StoryboardType.main, animated: true) } diff --git a/FullStackCodingBot/Podfile b/FullStackCodingBot/Podfile index f0cdffe..0d85f99 100644 --- a/FullStackCodingBot/Podfile +++ b/FullStackCodingBot/Podfile @@ -6,15 +6,15 @@ target 'Full Stack' do use_frameworks! # Pods for FullStackCodingBot - pod 'RxSwift' - pod 'RxCocoa' - pod 'NSObject+Rx' - pod 'SwiftLint' - pod 'Google-Mobile-Ads-SDK' - pod 'Firebase/Core' - pod 'Firebase/Database' - pod 'Firebase/Auth' - pod 'Action' + pod 'RxSwift' + pod 'RxCocoa' + pod 'NSObject+Rx' + pod 'SwiftLint' + pod 'Google-Mobile-Ads-SDK' + pod 'Firebase/Core' + pod 'Firebase/Database' + pod 'Firebase/Auth' + pod 'Action' target 'Full StackTests' do diff --git a/FullStackCodingBot/Podfile.lock b/FullStackCodingBot/Podfile.lock index e88a09f..8a39c99 100644 --- a/FullStackCodingBot/Podfile.lock +++ b/FullStackCodingBot/Podfile.lock @@ -2,20 +2,19 @@ PODS: - Action (5.0.0): - RxCocoa (~> 6.0) - RxSwift (~> 6.0) - - Alamofire (5.4.3) - - Firebase/Auth (8.6.1): + - Firebase/Auth (8.7.0): - Firebase/CoreOnly - - FirebaseAuth (~> 8.6.0) - - Firebase/Core (8.6.1): + - FirebaseAuth (~> 8.7.0) + - Firebase/Core (8.7.0): - Firebase/CoreOnly - - FirebaseAnalytics (~> 8.6.0) - - Firebase/CoreOnly (8.6.1): - - FirebaseCore (= 8.6.1) - - Firebase/Database (8.6.1): + - FirebaseAnalytics (~> 8.7.0) + - Firebase/CoreOnly (8.7.0): + - FirebaseCore (= 8.7.0) + - Firebase/Database (8.7.0): - Firebase/CoreOnly - - FirebaseDatabase (~> 8.6.0) - - FirebaseAnalytics (8.6.0): - - FirebaseAnalytics/AdIdSupport (= 8.6.0) + - FirebaseDatabase (~> 8.7.0) + - FirebaseAnalytics (8.7.0): + - FirebaseAnalytics/AdIdSupport (= 8.7.0) - FirebaseCore (~> 8.0) - FirebaseInstallations (~> 8.0) - GoogleUtilities/AppDelegateSwizzler (~> 7.4) @@ -23,49 +22,48 @@ PODS: - GoogleUtilities/Network (~> 7.4) - "GoogleUtilities/NSData+zlib (~> 7.4)" - nanopb (~> 2.30908.0) - - FirebaseAnalytics/AdIdSupport (8.6.0): + - FirebaseAnalytics/AdIdSupport (8.7.0): - FirebaseCore (~> 8.0) - FirebaseInstallations (~> 8.0) - - GoogleAppMeasurement (= 8.6.0) + - GoogleAppMeasurement (= 8.7.0) - GoogleUtilities/AppDelegateSwizzler (~> 7.4) - GoogleUtilities/MethodSwizzler (~> 7.4) - GoogleUtilities/Network (~> 7.4) - "GoogleUtilities/NSData+zlib (~> 7.4)" - nanopb (~> 2.30908.0) - - FirebaseAuth (8.6.0): + - FirebaseAuth (8.7.0): - FirebaseCore (~> 8.0) - GoogleUtilities/AppDelegateSwizzler (~> 7.4) - GoogleUtilities/Environment (~> 7.4) - GTMSessionFetcher/Core (~> 1.5) - - FirebaseCore (8.6.1): + - FirebaseCore (8.7.0): - FirebaseCoreDiagnostics (~> 8.0) - GoogleUtilities/Environment (~> 7.4) - GoogleUtilities/Logger (~> 7.4) - - FirebaseCoreDiagnostics (8.6.0): + - FirebaseCoreDiagnostics (8.7.0): - GoogleDataTransport (~> 9.0) - GoogleUtilities/Environment (~> 7.4) - GoogleUtilities/Logger (~> 7.4) - nanopb (~> 2.30908.0) - - FirebaseDatabase (8.6.0): + - FirebaseDatabase (8.7.0): - FirebaseCore (~> 8.0) - leveldb-library (~> 1.22) - - FirebaseInstallations (8.6.0): + - FirebaseInstallations (8.7.0): - FirebaseCore (~> 8.0) - GoogleUtilities/Environment (~> 7.4) - GoogleUtilities/UserDefaults (~> 7.4) - PromisesObjC (< 3.0, >= 1.2) - - GhostTypewriter (2.1.0) - - Google-Mobile-Ads-SDK (8.9.0): + - Google-Mobile-Ads-SDK (8.11.0): - GoogleAppMeasurement (< 9.0, >= 7.0) - GoogleUserMessagingPlatform (>= 1.1) - - GoogleAppMeasurement (8.6.0): - - GoogleAppMeasurement/AdIdSupport (= 8.6.0) + - GoogleAppMeasurement (8.7.0): + - GoogleAppMeasurement/AdIdSupport (= 8.7.0) - GoogleUtilities/AppDelegateSwizzler (~> 7.4) - GoogleUtilities/MethodSwizzler (~> 7.4) - GoogleUtilities/Network (~> 7.4) - "GoogleUtilities/NSData+zlib (~> 7.4)" - nanopb (~> 2.30908.0) - - GoogleAppMeasurement/AdIdSupport (8.6.0): + - GoogleAppMeasurement/AdIdSupport (8.7.0): - GoogleUtilities/AppDelegateSwizzler (~> 7.4) - GoogleUtilities/MethodSwizzler (~> 7.4) - GoogleUtilities/Network (~> 7.4) @@ -76,26 +74,26 @@ PODS: - nanopb (~> 2.30908.0) - PromisesObjC (< 3.0, >= 1.2) - GoogleUserMessagingPlatform (2.0.0) - - GoogleUtilities/AppDelegateSwizzler (7.5.1): + - GoogleUtilities/AppDelegateSwizzler (7.5.2): - GoogleUtilities/Environment - GoogleUtilities/Logger - GoogleUtilities/Network - - GoogleUtilities/Environment (7.5.1): + - GoogleUtilities/Environment (7.5.2): - PromisesObjC (< 3.0, >= 1.2) - - GoogleUtilities/Logger (7.5.1): + - GoogleUtilities/Logger (7.5.2): - GoogleUtilities/Environment - - GoogleUtilities/MethodSwizzler (7.5.1): + - GoogleUtilities/MethodSwizzler (7.5.2): - GoogleUtilities/Logger - - GoogleUtilities/Network (7.5.1): + - GoogleUtilities/Network (7.5.2): - GoogleUtilities/Logger - "GoogleUtilities/NSData+zlib" - GoogleUtilities/Reachability - - "GoogleUtilities/NSData+zlib (7.5.1)" - - GoogleUtilities/Reachability (7.5.1): + - "GoogleUtilities/NSData+zlib (7.5.2)" + - GoogleUtilities/Reachability (7.5.2): - GoogleUtilities/Logger - - GoogleUtilities/UserDefaults (7.5.1): + - GoogleUtilities/UserDefaults (7.5.2): - GoogleUtilities/Logger - - GTMSessionFetcher/Core (1.6.1) + - GTMSessionFetcher/Core (1.7.0) - leveldb-library (1.22.1) - nanopb (2.30908.0): - nanopb/decode (= 2.30908.0) @@ -111,15 +109,13 @@ PODS: - RxRelay (6.2.0): - RxSwift (= 6.2.0) - RxSwift (6.2.0) - - SwiftLint (0.43.1) + - SwiftLint (0.44.0) DEPENDENCIES: - Action - - Alamofire - Firebase/Auth - Firebase/Core - Firebase/Database - - GhostTypewriter - Google-Mobile-Ads-SDK - "NSObject+Rx" - RxCocoa @@ -129,7 +125,6 @@ DEPENDENCIES: SPEC REPOS: trunk: - Action - - Alamofire - Firebase - FirebaseAnalytics - FirebaseAuth @@ -137,7 +132,6 @@ SPEC REPOS: - FirebaseCoreDiagnostics - FirebaseDatabase - FirebaseInstallations - - GhostTypewriter - Google-Mobile-Ads-SDK - GoogleAppMeasurement - GoogleDataTransport @@ -155,21 +149,19 @@ SPEC REPOS: SPEC CHECKSUMS: Action: 47f1825511907b4fcd79dd7a7db9526c20f78018 - Alamofire: e447a2774a40c996748296fa2c55112fdbbc42f9 - Firebase: e84d95f6d3a91f634abc384f0751c434cb3aecfa - FirebaseAnalytics: 8f32ae54ad42754f503354782575c4ddfc1425c3 - FirebaseAuth: 223adeeb2262b417532e89bf06a960e3a0a1e9e4 - FirebaseCore: a753c231801f04f5ccd10cc92119ad1b12a61165 - FirebaseCoreDiagnostics: 3721920bde3a9a6d5aa093c1d25e9d3e47f694af - FirebaseDatabase: 917fbeed577a68eb1ba0eb48c52a3aa10ac13f92 - FirebaseInstallations: 0ede6ffcd215b8f93c19d9b06c1c54e2d4107e98 - GhostTypewriter: 62540772d227fbfe633bfa728d08fe8af5b4d805 - Google-Mobile-Ads-SDK: 8494046b4d339275a381f6bcfafbff7adb2f515f - GoogleAppMeasurement: 2c0c6e2a7ab3fe730ade6379f732bdefb46f50b0 + Firebase: bc9325d5ee2041524bac78a5213d0e530c651309 + FirebaseAnalytics: 52768800c2add1d84b751420cb4caaf8195f2c41 + FirebaseAuth: 2e7d029977648c67a5d51a263d4cbab76d34cf12 + FirebaseCore: f4804c1d3f4bbbefc88904d15653038f2c99ddf7 + FirebaseCoreDiagnostics: b63732f581a1c6a453ec7241f9ab60b3a5bd3450 + FirebaseDatabase: afc82291f9ed94ff949cec1b9505e8c3b944bc0f + FirebaseInstallations: ede6fb72bb6337914e5888b399271259d0c4910c + Google-Mobile-Ads-SDK: be2192b51b74d74a6ed70590c2e8275412f1b71e + GoogleAppMeasurement: 2be61ce546ad074dbe4dd545f222ac6033bb1d9e GoogleDataTransport: 85fd18ff3019bb85d3f2c551d04c481dedf71fc9 GoogleUserMessagingPlatform: ab890ce5f6620f293a21b6bdd82e416a2c73aeca - GoogleUtilities: 3df19e3c24f7bbc291d8b5809aa6b0d41e642437 - GTMSessionFetcher: 36689134877faeb055b27dfa4ccc9ceaa42e029e + GoogleUtilities: 8de2a97a17e15b6b98e38e8770e2d129a57c0040 + GTMSessionFetcher: 43748f93435c2aa068b1cbe39655aaf600652e91 leveldb-library: 50c7b45cbd7bf543c81a468fe557a16ae3db8729 nanopb: a0ba3315591a9ae0a16a309ee504766e90db0c96 "NSObject+Rx": 61cf1f7306a73dcef8b36649198af0813ec18dfd @@ -177,8 +169,8 @@ SPEC CHECKSUMS: RxCocoa: 4baf94bb35f2c0ab31bc0cb9f1900155f646ba42 RxRelay: e72dbfd157807478401ef1982e1c61c945c94b2f RxSwift: d356ab7bee873611322f134c5f9ef379fa183d8f - SwiftLint: 99f82d07b837b942dd563c668de129a03fc3fb52 + SwiftLint: e96c0a8c770c7ebbc4d36c55baf9096bb65c4584 -PODFILE CHECKSUM: 53f52a813cf20cabe3f6bcb0c64867ca42b773ea +PODFILE CHECKSUM: 9dcb07048230100d40f32bab60eefa35b9442eae -COCOAPODS: 1.10.0 +COCOAPODS: 1.10.1