diff --git a/GEON-PPANG-iOS.xcodeproj/project.pbxproj b/GEON-PPANG-iOS.xcodeproj/project.pbxproj index cc90b6d4..feddcab3 100644 --- a/GEON-PPANG-iOS.xcodeproj/project.pbxproj +++ b/GEON-PPANG-iOS.xcodeproj/project.pbxproj @@ -29,6 +29,13 @@ 0915C1752A5C533900ACB8D4 /* HomeReviewCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0915C1742A5C533900ACB8D4 /* HomeReviewCollectionViewCell.swift */; }; 091AFD422ABD79380001DD02 /* Settings.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 091AFD412ABD79380001DD02 /* Settings.bundle */; }; 0924632F2A5BA0F200B6F65D /* HomeBakeryCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0924632E2A5BA0F200B6F65D /* HomeBakeryCollectionViewCell.swift */; }; + 092698832C3CF2F200A9349D /* HomeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 092698822C3CF2F200A9349D /* HomeViewModel.swift */; }; + 092698882C3CF93F00A9349D /* BestReviewResponseDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 092698862C3CF93F00A9349D /* BestReviewResponseDTO.swift */; }; + 092698892C3CF93F00A9349D /* BestBakeryResponseDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 092698872C3CF93F00A9349D /* BestBakeryResponseDTO.swift */; }; + 0926988B2C3CF94C00A9349D /* BestEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0926988A2C3CF94C00A9349D /* BestEndpoint.swift */; }; + 0926988F2C3CF96100A9349D /* BestRepositoryImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0926988E2C3CF96100A9349D /* BestRepositoryImpl.swift */; }; + 092698932C3CF98B00A9349D /* HomeUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 092698922C3CF98B00A9349D /* HomeUseCase.swift */; }; + 092698952C3CFD3800A9349D /* BestRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 092698942C3CFD3800A9349D /* BestRepository.swift */; }; 092EFCA42A6195D900E7C262 /* CommonTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 092EFCA32A6195D900E7C262 /* CommonTextView.swift */; }; 0930D25B2A928B8000A75C7E /* CertificationMarkResponseType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0930D25A2A928B8000A75C7E /* CertificationMarkResponseType.swift */; }; 0930D25D2A928BF200A75C7E /* NearStationResponseDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0930D25C2A928BF200A75C7E /* NearStationResponseDTO.swift */; }; @@ -37,6 +44,8 @@ 0930D2632A93292F00A75C7E /* IconWithTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0930D2622A93292F00A75C7E /* IconWithTextView.swift */; }; 093214022A5AE27800875EF6 /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 093214012A5AE27700875EF6 /* Utils.swift */; }; 093214082A5AE40900875EF6 /* SearchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 093214072A5AE40900875EF6 /* SearchViewController.swift */; }; + 093DC89F2C6B95A100DE6EA2 /* CollectionViewSubscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = 093DC89E2C6B95A100DE6EA2 /* CollectionViewSubscription.swift */; }; + 093DC8A22C6B95FC00DE6EA2 /* CollectionViewPublisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 093DC8A12C6B95FC00DE6EA2 /* CollectionViewPublisher.swift */; }; 093EFA592A61F62D003228CD /* SignInViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 093EFA582A61F62D003228CD /* SignInViewController.swift */; }; 094392CF2A84E68700984310 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 094392CE2A84E68700984310 /* GoogleService-Info.plist */; }; 0943B9302AB610A8006EC1DE /* AnalyticEventType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0943B92F2AB610A8006EC1DE /* AnalyticEventType.swift */; }; @@ -74,6 +83,7 @@ 098716B62A60F52200538D05 /* BakeryTypeProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 098716B52A60F52200538D05 /* BakeryTypeProtocol.swift */; }; 098716B82A6138BD00538D05 /* MyReviewsHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 098716B72A6138BD00538D05 /* MyReviewsHeaderView.swift */; }; 0987288D2A5BA1F000A29402 /* BookmarkButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0987288C2A5BA1F000A29402 /* BookmarkButton.swift */; }; + 098800A82C4E3A62009E6F43 /* UIViewController+LifeCyclePublisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 098800A72C4E3A62009E6F43 /* UIViewController+LifeCyclePublisher.swift */; }; 098F32EA2A4200FE0092D09A /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 098F32E92A4200FE0092D09A /* Assets.xcassets */; }; 098F32ED2A4200FE0092D09A /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 098F32EB2A4200FE0092D09A /* LaunchScreen.storyboard */; }; 09980CFB2A99A9800098550C /* SignInPropertyType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09980CFA2A99A9800098550C /* SignInPropertyType.swift */; }; @@ -90,6 +100,7 @@ 09C6264E2A5B29F8002C8110 /* SearchEnum.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09C6264D2A5B29F8002C8110 /* SearchEnum.swift */; }; 09C626562A5B3572002C8110 /* HomeBestReviewResponseDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09C626552A5B3572002C8110 /* HomeBestReviewResponseDTO.swift */; }; 09C626582A5B4269002C8110 /* HomeHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09C626572A5B4269002C8110 /* HomeHeaderView.swift */; }; + 09C8C11E2C3CEFF2009C4213 /* NewHomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09C8C11D2C3CEFF2009C4213 /* NewHomeViewController.swift */; }; 09CA3EF82A5676AA0063897A /* CommonButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09CA3EF72A5676AA0063897A /* CommonButton.swift */; }; 09CA3EFC2A569E4A0063897A /* URLConstant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09CA3EFB2A569E4A0063897A /* URLConstant.swift */; }; 09CD07902A620D7D00A078DE /* NickNameViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09CD078F2A620D7D00A078DE /* NickNameViewController.swift */; }; @@ -167,6 +178,8 @@ 3E3DE9E52C25AF7600CA5999 /* ReviewPercentage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E3DE9E42C25AF7600CA5999 /* ReviewPercentage.swift */; }; 3E3DE9E72C25AF8500CA5999 /* Reviews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E3DE9E62C25AF8500CA5999 /* Reviews.swift */; }; 3E3DE9E92C25AFA100CA5999 /* Review.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E3DE9E82C25AFA100CA5999 /* Review.swift */; }; + 3E3DEBE42C31609500CA5999 /* GBNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3E3DEBE12C31608C00CA5999 /* GBNetwork.framework */; }; + 3E3DEBE52C31609500CA5999 /* GBNetwork.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3E3DEBE12C31608C00CA5999 /* GBNetwork.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 3E452B672A60E68000EA456D /* MyPageResponseDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E452B662A60E68000EA456D /* MyPageResponseDTO.swift */; }; 3E452B692A60FF7900EA456D /* MyPageCollectionViewFooter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E452B682A60FF7900EA456D /* MyPageCollectionViewFooter.swift */; }; 3E452B6B2A614AAE00EA456D /* ImageWithSubtitleButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E452B6A2A614AAD00EA456D /* ImageWithSubtitleButton.swift */; }; @@ -254,7 +267,6 @@ 3EF9F8AE2BBD118700F3E366 /* DummyUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3EF9F8AD2BBD118700F3E366 /* DummyUseCase.swift */; }; 3EF9F8B02BBD118C00F3E366 /* DummyInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3EF9F8AF2BBD118C00F3E366 /* DummyInterface.swift */; }; 3EF9F8B72BBD141600F3E366 /* DummyEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3EF9F8B62BBD141600F3E366 /* DummyEndpoint.swift */; }; - 3EF9F8BB2BBD228C00F3E366 /* InfrastructureDummy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3EF9F8BA2BBD228C00F3E366 /* InfrastructureDummy.swift */; }; 3EFA97862A5EBCA9002E70FA /* LineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3EFA97852A5EBCA9002E70FA /* LineView.swift */; }; 3EFA97882A5EBCED002E70FA /* CustomNavigationBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3EFA97872A5EBCED002E70FA /* CustomNavigationBar.swift */; }; DF483ACC2A652E0100FCD507 /* BookmarkReviewNumberStackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF483ACB2A652E0100FCD507 /* BookmarkReviewNumberStackView.swift */; }; @@ -289,6 +301,37 @@ DFFBE3872AB8B4C100D27548 /* DetailReasonTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFFBE3862AB8B4C100D27548 /* DetailReasonTextView.swift */; }; /* End PBXBuildFile section */ +/* Begin PBXContainerItemProxy section */ + 3E3DEBE02C31608C00CA5999 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 3E3DEBDB2C31608C00CA5999 /* GBNetwork.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 3E3DEB332C3117A300CA5999; + remoteInfo = GBNetwork; + }; + 3E3DEBE22C31608C00CA5999 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 3E3DEBDB2C31608C00CA5999 /* GBNetwork.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 3E3DEB3D2C3117A400CA5999; + remoteInfo = GBNetworkTests; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 3E3DEA002C26981200CA5999 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 3E3DEBE52C31609500CA5999 /* GBNetwork.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + /* Begin PBXFileReference section */ 0905561F2A51DB2C00752067 /* UIImage+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+.swift"; sourceTree = ""; }; 090556212A51DB3300752067 /* UIFont+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIFont+.swift"; sourceTree = ""; }; @@ -309,6 +352,13 @@ 0915C1742A5C533900ACB8D4 /* HomeReviewCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeReviewCollectionViewCell.swift; sourceTree = ""; }; 091AFD412ABD79380001DD02 /* Settings.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = Settings.bundle; sourceTree = ""; }; 0924632E2A5BA0F200B6F65D /* HomeBakeryCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeBakeryCollectionViewCell.swift; sourceTree = ""; }; + 092698822C3CF2F200A9349D /* HomeViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeViewModel.swift; sourceTree = ""; }; + 092698862C3CF93F00A9349D /* BestReviewResponseDTO.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BestReviewResponseDTO.swift; sourceTree = ""; }; + 092698872C3CF93F00A9349D /* BestBakeryResponseDTO.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BestBakeryResponseDTO.swift; sourceTree = ""; }; + 0926988A2C3CF94C00A9349D /* BestEndpoint.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BestEndpoint.swift; sourceTree = ""; }; + 0926988E2C3CF96100A9349D /* BestRepositoryImpl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BestRepositoryImpl.swift; sourceTree = ""; }; + 092698922C3CF98B00A9349D /* HomeUseCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeUseCase.swift; sourceTree = ""; }; + 092698942C3CFD3800A9349D /* BestRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BestRepository.swift; sourceTree = ""; }; 092EFCA32A6195D900E7C262 /* CommonTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommonTextView.swift; sourceTree = ""; }; 0930D25A2A928B8000A75C7E /* CertificationMarkResponseType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CertificationMarkResponseType.swift; sourceTree = ""; }; 0930D25C2A928BF200A75C7E /* NearStationResponseDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NearStationResponseDTO.swift; sourceTree = ""; }; @@ -317,6 +367,8 @@ 0930D2622A93292F00A75C7E /* IconWithTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconWithTextView.swift; sourceTree = ""; }; 093214012A5AE27700875EF6 /* Utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utils.swift; sourceTree = ""; }; 093214072A5AE40900875EF6 /* SearchViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchViewController.swift; sourceTree = ""; }; + 093DC89E2C6B95A100DE6EA2 /* CollectionViewSubscription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionViewSubscription.swift; sourceTree = ""; }; + 093DC8A12C6B95FC00DE6EA2 /* CollectionViewPublisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionViewPublisher.swift; sourceTree = ""; }; 093EFA582A61F62D003228CD /* SignInViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInViewController.swift; sourceTree = ""; }; 094392CE2A84E68700984310 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; 0943B92F2AB610A8006EC1DE /* AnalyticEventType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticEventType.swift; sourceTree = ""; }; @@ -352,6 +404,7 @@ 098716B52A60F52200538D05 /* BakeryTypeProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BakeryTypeProtocol.swift; sourceTree = ""; }; 098716B72A6138BD00538D05 /* MyReviewsHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyReviewsHeaderView.swift; sourceTree = ""; }; 0987288C2A5BA1F000A29402 /* BookmarkButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkButton.swift; sourceTree = ""; }; + 098800A72C4E3A62009E6F43 /* UIViewController+LifeCyclePublisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+LifeCyclePublisher.swift"; sourceTree = ""; }; 098F32DD2A4200FD0092D09A /* GEON-PPANG-iOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "GEON-PPANG-iOS.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 098F32E02A4200FD0092D09A /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 098F32E22A4200FD0092D09A /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; @@ -372,6 +425,7 @@ 09C6264D2A5B29F8002C8110 /* SearchEnum.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchEnum.swift; sourceTree = ""; }; 09C626552A5B3572002C8110 /* HomeBestReviewResponseDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeBestReviewResponseDTO.swift; sourceTree = ""; }; 09C626572A5B4269002C8110 /* HomeHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeHeaderView.swift; sourceTree = ""; }; + 09C8C11D2C3CEFF2009C4213 /* NewHomeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewHomeViewController.swift; sourceTree = ""; }; 09CA3EF72A5676AA0063897A /* CommonButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommonButton.swift; sourceTree = ""; }; 09CA3EFB2A569E4A0063897A /* URLConstant.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLConstant.swift; sourceTree = ""; }; 09CD078F2A620D7D00A078DE /* NickNameViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NickNameViewController.swift; sourceTree = ""; }; @@ -444,6 +498,7 @@ 3E3DE9E42C25AF7600CA5999 /* ReviewPercentage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReviewPercentage.swift; sourceTree = ""; }; 3E3DE9E62C25AF8500CA5999 /* Reviews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Reviews.swift; sourceTree = ""; }; 3E3DE9E82C25AFA100CA5999 /* Review.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Review.swift; sourceTree = ""; }; + 3E3DEBDB2C31608C00CA5999 /* GBNetwork.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = GBNetwork.xcodeproj; path = GBNetwork/GBNetwork.xcodeproj; sourceTree = ""; }; 3E452B662A60E68000EA456D /* MyPageResponseDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyPageResponseDTO.swift; sourceTree = ""; }; 3E452B682A60FF7900EA456D /* MyPageCollectionViewFooter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyPageCollectionViewFooter.swift; sourceTree = ""; }; 3E452B6A2A614AAD00EA456D /* ImageWithSubtitleButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageWithSubtitleButton.swift; sourceTree = ""; }; @@ -531,7 +586,6 @@ 3EF9F8AD2BBD118700F3E366 /* DummyUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DummyUseCase.swift; sourceTree = ""; }; 3EF9F8AF2BBD118C00F3E366 /* DummyInterface.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DummyInterface.swift; sourceTree = ""; }; 3EF9F8B62BBD141600F3E366 /* DummyEndpoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DummyEndpoint.swift; sourceTree = ""; }; - 3EF9F8BA2BBD228C00F3E366 /* InfrastructureDummy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfrastructureDummy.swift; sourceTree = ""; }; 3EFA97852A5EBCA9002E70FA /* LineView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LineView.swift; sourceTree = ""; }; 3EFA97872A5EBCED002E70FA /* CustomNavigationBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomNavigationBar.swift; sourceTree = ""; }; DF483ACB2A652E0100FCD507 /* BookmarkReviewNumberStackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkReviewNumberStackView.swift; sourceTree = ""; }; @@ -577,6 +631,7 @@ 090556382A51E16B00752067 /* Then in Frameworks */, 098227EA2AB8B17700872135 /* Sentry in Frameworks */, 0905563B2A51E17800752067 /* SnapKit in Frameworks */, + 3E3DEBE42C31609500CA5999 /* GBNetwork.framework in Frameworks */, 090556432A51E6FC00752067 /* Moya in Frameworks */, 095FB8802AB3427D00C69BD1 /* Amplitude in Frameworks */, 3E36D7D12A8DE65D00B2C1CC /* KakaoSDKUser in Frameworks */, @@ -636,6 +691,14 @@ path = Label; sourceTree = ""; }; + 092698812C3CF2EA00A9349D /* ViewModel */ = { + isa = PBXGroup; + children = ( + 092698822C3CF2F200A9349D /* HomeViewModel.swift */, + ); + path = ViewModel; + sourceTree = ""; + }; 092F11B12A69A6CC00852CA5 /* Cell */ = { isa = PBXGroup; children = ( @@ -711,6 +774,31 @@ path = ViewController; sourceTree = ""; }; + 093DC89C2C6B950900DE6EA2 /* Combine */ = { + isa = PBXGroup; + children = ( + 093DC8A02C6B95F100DE6EA2 /* Publisher */, + 093DC89D2C6B958F00DE6EA2 /* Subscription */, + ); + path = Combine; + sourceTree = ""; + }; + 093DC89D2C6B958F00DE6EA2 /* Subscription */ = { + isa = PBXGroup; + children = ( + 093DC89E2C6B95A100DE6EA2 /* CollectionViewSubscription.swift */, + ); + path = Subscription; + sourceTree = ""; + }; + 093DC8A02C6B95F100DE6EA2 /* Publisher */ = { + isa = PBXGroup; + children = ( + 093DC8A12C6B95FC00DE6EA2 /* CollectionViewPublisher.swift */, + ); + path = Publisher; + sourceTree = ""; + }; 093EFA542A61F5E4003228CD /* SignIn */ = { isa = PBXGroup; children = ( @@ -971,6 +1059,22 @@ path = View; sourceTree = ""; }; + 098800A52C4E3A3B009E6F43 /* Extension */ = { + isa = PBXGroup; + children = ( + 098800A62C4E3A4D009E6F43 /* UIViewController */, + ); + path = Extension; + sourceTree = ""; + }; + 098800A62C4E3A4D009E6F43 /* UIViewController */ = { + isa = PBXGroup; + children = ( + 098800A72C4E3A62009E6F43 /* UIViewController+LifeCyclePublisher.swift */, + ); + path = UIViewController; + sourceTree = ""; + }; 098F32D42A4200FD0092D09A = { isa = PBXGroup; children = ( @@ -978,6 +1082,7 @@ 09E30E812BFEF16F001E107B /* ci_scripts */, 098F32DF2A4200FD0092D09A /* GEON-PPANG-iOS */, 098F32DE2A4200FD0092D09A /* Products */, + 3E3DE9FD2C26981200CA5999 /* Frameworks */, ); sourceTree = ""; }; @@ -997,7 +1102,6 @@ 3EF9F89A2BBD109E00F3E366 /* Domain */, 3EF9F89B2BBD10A000F3E366 /* Data */, 3E8C47CE2BC385A800919E06 /* Core */, - 3E1A4EDD2BCD2343004CA603 /* Infrastructure */, 0961C36B2A501F050031A822 /* Resource */, 0961C3602A501E9F0031A822 /* Global */, ); @@ -1015,6 +1119,7 @@ 09B13F492A593C5900C0C723 /* Home */ = { isa = PBXGroup; children = ( + 092698812C3CF2EA00A9349D /* ViewModel */, 092F11B22A69A6E200852CA5 /* Cell */, 093213FC2A5A74C200875EF6 /* View */, 3E93BCA12BE3744C005B658F /* ViewController */, @@ -1166,14 +1271,6 @@ path = Scene; sourceTree = ""; }; - 3E1A4EDD2BCD2343004CA603 /* Infrastructure */ = { - isa = PBXGroup; - children = ( - 3EF9F8B82BBD17F400F3E366 /* Network */, - ); - path = Infrastructure; - sourceTree = ""; - }; 3E1E73FF2B78AD460082386A /* Model */ = { isa = PBXGroup; children = ( @@ -1322,6 +1419,22 @@ path = BakeryDetail; sourceTree = ""; }; + 3E3DE9FD2C26981200CA5999 /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; + 3E3DEBDC2C31608C00CA5999 /* Products */ = { + isa = PBXGroup; + children = ( + 3E3DEBE12C31608C00CA5999 /* GBNetwork.framework */, + 3E3DEBE32C31608C00CA5999 /* GBNetworkTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; 3E61EE8F2AA9530D004F25DB /* Review */ = { isa = PBXGroup; children = ( @@ -1398,6 +1511,7 @@ 3E8C47CE2BC385A800919E06 /* Core */ = { isa = PBXGroup; children = ( + 3E3DEBDB2C31608C00CA5999 /* GBNetwork.xcodeproj */, 3EF9F8892BBCE9D400F3E366 /* DesignSystem */, ); path = Core; @@ -1422,6 +1536,7 @@ 3E8C47D82BC3B54700919E06 /* Endpoints */ = { isa = PBXGroup; children = ( + 0926988A2C3CF94C00A9349D /* BestEndpoint.swift */, 3EF9F8B62BBD141600F3E366 /* DummyEndpoint.swift */, ); path = Endpoints; @@ -1430,6 +1545,8 @@ 3E8C47E02BC3B81B00919E06 /* Utils */ = { isa = PBXGroup; children = ( + 093DC89C2C6B950900DE6EA2 /* Combine */, + 098800A52C4E3A3B009E6F43 /* Extension */, 3E8C47E12BC3B82500919E06 /* DummyUIUtils.swift */, ); path = Utils; @@ -1560,6 +1677,7 @@ isa = PBXGroup; children = ( 09B13F4B2A593C6F00C0C723 /* HomeViewController.swift */, + 09C8C11D2C3CEFF2009C4213 /* NewHomeViewController.swift */, ); path = ViewController; sourceTree = ""; @@ -1845,6 +1963,7 @@ 3EF9F89C2BBD10AC00F3E366 /* UseCase */ = { isa = PBXGroup; children = ( + 092698922C3CF98B00A9349D /* HomeUseCase.swift */, 3EF9F8AD2BBD118700F3E366 /* DummyUseCase.swift */, ); path = UseCase; @@ -1854,6 +1973,7 @@ isa = PBXGroup; children = ( 3EF9F8AF2BBD118C00F3E366 /* DummyInterface.swift */, + 092698942C3CFD3800A9349D /* BestRepository.swift */, ); path = Interface; sourceTree = ""; @@ -1886,6 +2006,7 @@ 3EF9F8A02BBD10D200F3E366 /* Repository */ = { isa = PBXGroup; children = ( + 0926988E2C3CF96100A9349D /* BestRepositoryImpl.swift */, 3EF9F8A92BBD117400F3E366 /* DummyRepository.swift */, ); path = Repository; @@ -1911,6 +2032,8 @@ 3EF9F8A32BBD115800F3E366 /* ResponseDTO */ = { isa = PBXGroup; children = ( + 092698872C3CF93F00A9349D /* BestBakeryResponseDTO.swift */, + 092698862C3CF93F00A9349D /* BestReviewResponseDTO.swift */, 3EF9F8A72BBD116F00F3E366 /* DummyResDTO.swift */, ); path = ResponseDTO; @@ -1933,14 +2056,6 @@ path = Network; sourceTree = ""; }; - 3EF9F8B82BBD17F400F3E366 /* Network */ = { - isa = PBXGroup; - children = ( - 3EF9F8BA2BBD228C00F3E366 /* InfrastructureDummy.swift */, - ); - path = Network; - sourceTree = ""; - }; DF697C082A686ABC008CE4CF /* Cell */ = { isa = PBXGroup; children = ( @@ -2049,6 +2164,7 @@ isa = PBXNativeTarget; buildConfigurationList = 098F32F12A4200FE0092D09A /* Build configuration list for PBXNativeTarget "GEON-PPANG-iOS" */; buildPhases = ( + 3E3DEA002C26981200CA5999 /* Embed Frameworks */, 098F32D92A4200FD0092D09A /* Sources */, 098F32DA2A4200FD0092D09A /* Frameworks */, 098F32DB2A4200FD0092D09A /* Resources */, @@ -2109,6 +2225,12 @@ ); productRefGroup = 098F32DE2A4200FD0092D09A /* Products */; projectDirPath = ""; + projectReferences = ( + { + ProductGroup = 3E3DEBDC2C31608C00CA5999 /* Products */; + ProjectRef = 3E3DEBDB2C31608C00CA5999 /* GBNetwork.xcodeproj */; + }, + ); projectRoot = ""; targets = ( 098F32DC2A4200FD0092D09A /* GEON-PPANG-iOS */, @@ -2116,6 +2238,23 @@ }; /* End PBXProject section */ +/* Begin PBXReferenceProxy section */ + 3E3DEBE12C31608C00CA5999 /* GBNetwork.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = GBNetwork.framework; + remoteRef = 3E3DEBE02C31608C00CA5999 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 3E3DEBE32C31608C00CA5999 /* GBNetworkTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = GBNetworkTests.xctest; + remoteRef = 3E3DEBE22C31608C00CA5999 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; +/* End PBXReferenceProxy section */ + /* Begin PBXResourcesBuildPhase section */ 098F32DB2A4200FD0092D09A /* Resources */ = { isa = PBXResourcesBuildPhase; @@ -2229,6 +2368,7 @@ 09B71C022A59D99200076AC2 /* SearchTextField.swift in Sources */, 093EFA592A61F62D003228CD /* SignInViewController.swift in Sources */, 09B13F482A5931DC00C0C723 /* TabBarController.swift in Sources */, + 092698832C3CF2F200A9349D /* HomeViewModel.swift in Sources */, 3E2EEF752A5C93010093BCA9 /* BottomView.swift in Sources */, 3EA69B382B557954008AE23B /* BakeriesAPI.swift in Sources */, 0961C3642A501EBF0031A822 /* Strings.swift in Sources */, @@ -2252,9 +2392,12 @@ 3E3DE9C92C25AE0100CA5999 /* PasswordValidationError.swift in Sources */, 0959F1702A658CA400E77CAF /* HomeBestBakeryResponseDTO.swift in Sources */, 3E7B21C42A621A9A00C8F8B4 /* SortBakeryCollectionViewCell.swift in Sources */, + 098800A82C4E3A62009E6F43 /* UIViewController+LifeCyclePublisher.swift in Sources */, DFB587BB2A5D588500704B6C /* DrawDashLineView.swift in Sources */, 090556222A51DB3300752067 /* UIFont+.swift in Sources */, + 092698892C3CF93F00A9349D /* BestBakeryResponseDTO.swift in Sources */, 09CA3EFC2A569E4A0063897A /* URLConstant.swift in Sources */, + 092698882C3CF93F00A9349D /* BestReviewResponseDTO.swift in Sources */, 3EA69B652B557A7A008AE23B /* MemberService.swift in Sources */, 0959F1502A65840F00E77CAF /* NetworkBase.swift in Sources */, 3EE343142A6440EB0010C187 /* UICollectionViewCell+.swift in Sources */, @@ -2287,6 +2430,7 @@ DF697C052A671A7B008CE4CF /* BakeryDetailCollectionViewFooter.swift in Sources */, 3EF9F8AA2BBD117400F3E366 /* DummyRepository.swift in Sources */, 097682DA2A5C829D0008F4FB /* GradientImageView.swift in Sources */, + 0926988F2C3CF96100A9349D /* BestRepositoryImpl.swift in Sources */, 097716EB2AA57713001EE6D5 /* LayoutUtils.swift in Sources */, DF697C032A671A6D008CE4CF /* BakeryDetailCollectionViewHeader.swift in Sources */, DFB585FB2A5F565F0077D272 /* BreadTypeStackView.swift in Sources */, @@ -2298,9 +2442,11 @@ 3E3DE9E32C25AF5E00CA5999 /* BakeryFigure.swift in Sources */, 3E8C47F72BC3CA0100919E06 /* MyPageCoordinator.swift in Sources */, 3EFA97862A5EBCA9002E70FA /* LineView.swift in Sources */, + 092698952C3CFD3800A9349D /* BestRepository.swift in Sources */, 3E3543302AAC700600BD926A /* SignUpRequestDTO.swift in Sources */, 3E8C47EF2BC3C9EA00919E06 /* MainCoordinator.swift in Sources */, 0987288D2A5BA1F000A29402 /* BookmarkButton.swift in Sources */, + 093DC8A22C6B95FC00DE6EA2 /* CollectionViewPublisher.swift in Sources */, 3E3DE9D02C25AE5900CA5999 /* BestBakery.swift in Sources */, 090556202A51DB2C00752067 /* UIImage+.swift in Sources */, 3E1E74012B78AD530082386A /* BookmarkBakeryListResponseDTO.swift in Sources */, @@ -2327,6 +2473,7 @@ 093214022A5AE27800875EF6 /* Utils.swift in Sources */, 3E8B30002A67A614001BD057 /* BreadRequestType.swift in Sources */, 090556452A51E79B00752067 /* UITextField+.swift in Sources */, + 09C8C11E2C3CEFF2009C4213 /* NewHomeViewController.swift in Sources */, 3E3DE9AD2C25AC6100CA5999 /* Certifications.swift in Sources */, 0959F14E2A65840300E77CAF /* NetworkResult.swift in Sources */, 0959F16C2A658C9500E77CAF /* AuthResponseDTO.swift in Sources */, @@ -2355,8 +2502,10 @@ 3E3DE9D22C25AE6A00CA5999 /* BestReview.swift in Sources */, 3EA69B3E2B557991008AE23B /* MemberAPI.swift in Sources */, 3EB798212AB87188004EC35D /* SetNicknameResponseDTO.swift in Sources */, + 0926988B2C3CF94C00A9349D /* BestEndpoint.swift in Sources */, 090556472A51E7D900752067 /* UITextView+.swift in Sources */, 3EA69B422B5579A7008AE23B /* ReportAPI.swift in Sources */, + 092698932C3CF98B00A9349D /* HomeUseCase.swift in Sources */, 3EDC05D02A5E4FF900EAD950 /* String+.swift in Sources */, 09FD47E42A5E82BB002020BD /* BottomSheetAppearView.swift in Sources */, 3EB798102AB2F448004EC35D /* HeaderEnum.swift in Sources */, @@ -2364,7 +2513,6 @@ 3EB798182AB7D42B004EC35D /* DeleteUserResponseDTO.swift in Sources */, 0905562B2A51DBCE00752067 /* UIStackView+.swift in Sources */, 3E8C47F52BC3C9FC00919E06 /* BakeryListCoordinator.swift in Sources */, - 3EF9F8BB2BBD228C00F3E366 /* InfrastructureDummy.swift in Sources */, 09B13F562A59410C00C0C723 /* CALayer+.swift in Sources */, 09CD07902A620D7D00A078DE /* NickNameViewController.swift in Sources */, DFB1343D2A6284BB00BAB52E /* ReviewProgressView.swift in Sources */, @@ -2376,6 +2524,7 @@ 095FB8842AB3435300C69BD1 /* AnalyticManagerProtocol.swift in Sources */, 3E3DE9E72C25AF8500CA5999 /* Reviews.swift in Sources */, 3E3DE9BF2C25AD7000CA5999 /* NutrientType.swift in Sources */, + 093DC89F2C6B95A100DE6EA2 /* CollectionViewSubscription.swift in Sources */, 3E162D182A692C1400590C6F /* BookmarkRequestDTO.swift in Sources */, 3E3DE9DD2C25AEF400CA5999 /* Menu.swift in Sources */, 0930D25F2A928C2300A75C7E /* RecommendKeywordResponseDTO.swift in Sources */, @@ -2444,6 +2593,7 @@ /* Begin XCBuildConfiguration section */ 098F32EF2A4200FE0092D09A /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 09E30E892BFF123C001E107B /* Config.xcconfig */; buildSettings = { ACCESS_TOKEN = "${ACCESS_TOKEN}"; ALWAYS_SEARCH_USER_PATHS = NO; @@ -2506,6 +2656,7 @@ }; 098F32F02A4200FE0092D09A /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 09E30E892BFF123C001E107B /* Config.xcconfig */; buildSettings = { ACCESS_TOKEN = "${ACCESS_TOKEN}"; ALWAYS_SEARCH_USER_PATHS = NO; diff --git a/GEON-PPANG-iOS.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/GEON-PPANG-iOS.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 1b2650ff..6355ebd2 100644 --- a/GEON-PPANG-iOS.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/GEON-PPANG-iOS.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,13 +1,12 @@ { - "originHash" : "a32b0a1c0ad6f6a91a39529043231e700bfb3a573e73a0172a5e40c9106b8bba", "pins" : [ { "identity" : "alamofire", "kind" : "remoteSourceControl", "location" : "https://github.com/Alamofire/Alamofire.git", "state" : { - "revision" : "3dc6a42c7727c49bf26508e29b0a0b35f9c7e1ad", - "version" : "5.8.1" + "revision" : "f455c2975872ccd2d9c81594c658af65716e9b9a", + "version" : "5.9.1" } }, { @@ -16,7 +15,7 @@ "location" : "https://github.com/amplitude/Amplitude-iOS", "state" : { "branch" : "main", - "revision" : "82fc62448292c13fa0a6b0b11c4524df83fd3f3b" + "revision" : "bfdec453a31fd35942a619ef472fcf2f09e2313a" } }, { @@ -33,8 +32,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/kakao/kakao-ios-sdk", "state" : { - "revision" : "ae3c60cbd4e3b348775f8c766e5b908fa1e66c5a", - "version" : "2.20.0" + "revision" : "e9e649d3ba823c3673867d3d09010fc77005a940", + "version" : "2.22.3" } }, { @@ -42,8 +41,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/onevcat/Kingfisher.git", "state" : { - "revision" : "5b92f029fab2cce44386d28588098b5be0824ef5", - "version" : "7.11.0" + "revision" : "2ef543ee21d63734e1c004ad6c870255e8716c50", + "version" : "7.12.0" } }, { @@ -69,8 +68,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/ReactiveX/RxSwift.git", "state" : { - "revision" : "9dcaa4b333db437b0fbfaf453fad29069044a8b4", - "version" : "6.6.0" + "revision" : "b06a8c8596e4c3e8e7788e08e720e3248563ce6a", + "version" : "6.7.1" } }, { @@ -78,8 +77,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/getsentry/sentry-cocoa.git", "state" : { - "revision" : "b847a202a517a90763e8fd0656d8028aeee7b78d", - "version" : "8.20.0" + "revision" : "8fd4e804f2e72e0b9c1b189ce4e8349c4d10b6a2", + "version" : "8.30.0" } }, { @@ -87,8 +86,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/SnapKit/SnapKit", "state" : { - "revision" : "e74fe2a978d1216c3602b129447c7301573cc2d8", - "version" : "5.7.0" + "revision" : "2842e6e84e82eb9a8dac0100ca90d9444b0307f4", + "version" : "5.7.1" } }, { @@ -101,5 +100,5 @@ } } ], - "version" : 3 + "version" : 2 } diff --git a/GEON-PPANG-iOS/Core/GBNetwork/GBNetwork.xcodeproj/project.pbxproj b/GEON-PPANG-iOS/Core/GBNetwork/GBNetwork.xcodeproj/project.pbxproj new file mode 100644 index 00000000..0e332b8a --- /dev/null +++ b/GEON-PPANG-iOS/Core/GBNetwork/GBNetwork.xcodeproj/project.pbxproj @@ -0,0 +1,514 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 56; + objects = { + +/* Begin PBXBuildFile section */ + 3E3DEB382C3117A400CA5999 /* GBNetwork.docc in Sources */ = {isa = PBXBuildFile; fileRef = 3E3DEB372C3117A400CA5999 /* GBNetwork.docc */; }; + 3E3DEB3E2C3117A400CA5999 /* GBNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3E3DEB332C3117A300CA5999 /* GBNetwork.framework */; }; + 3E3DEB432C3117A400CA5999 /* GBNetworkTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E3DEB422C3117A400CA5999 /* GBNetworkTests.swift */; }; + 3E3DEB442C3117A400CA5999 /* GBNetwork.h in Headers */ = {isa = PBXBuildFile; fileRef = 3E3DEB362C3117A300CA5999 /* GBNetwork.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 3E3DEB562C3117F600CA5999 /* GBNetworkError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E3DEB4D2C3117F500CA5999 /* GBNetworkError.swift */; }; + 3E3DEB572C3117F600CA5999 /* APIClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E3DEB4E2C3117F500CA5999 /* APIClient.swift */; }; + 3E3DEB582C3117F600CA5999 /* URLRequest+Encoding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E3DEB4F2C3117F600CA5999 /* URLRequest+Encoding.swift */; }; + 3E3DEB592C3117F600CA5999 /* Response.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E3DEB502C3117F600CA5999 /* Response.swift */; }; + 3E3DEB5B2C3117F600CA5999 /* RequestType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E3DEB522C3117F600CA5999 /* RequestType.swift */; }; + 3E3DEB5C2C3117F600CA5999 /* HTTPMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E3DEB532C3117F600CA5999 /* HTTPMethod.swift */; }; + 3E3DEB5D2C3117F600CA5999 /* HTTPTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E3DEB542C3117F600CA5999 /* HTTPTask.swift */; }; + 3E3DEB5E2C3117F600CA5999 /* HTTPHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E3DEB552C3117F600CA5999 /* HTTPHeader.swift */; }; + 3E3DEB9C2C315DE000CA5999 /* RawResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E3DEB9B2C315DE000CA5999 /* RawResponse.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 3E3DEB3F2C3117A400CA5999 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 3E3DEB2A2C3117A300CA5999 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 3E3DEB322C3117A300CA5999; + remoteInfo = GBNetwork; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 3E3DEB332C3117A300CA5999 /* GBNetwork.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = GBNetwork.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 3E3DEB362C3117A300CA5999 /* GBNetwork.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GBNetwork.h; sourceTree = ""; }; + 3E3DEB372C3117A400CA5999 /* GBNetwork.docc */ = {isa = PBXFileReference; lastKnownFileType = folder.documentationcatalog; path = GBNetwork.docc; sourceTree = ""; }; + 3E3DEB3D2C3117A400CA5999 /* GBNetworkTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = GBNetworkTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 3E3DEB422C3117A400CA5999 /* GBNetworkTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GBNetworkTests.swift; sourceTree = ""; }; + 3E3DEB4D2C3117F500CA5999 /* GBNetworkError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GBNetworkError.swift; sourceTree = ""; }; + 3E3DEB4E2C3117F500CA5999 /* APIClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = APIClient.swift; sourceTree = ""; }; + 3E3DEB4F2C3117F600CA5999 /* URLRequest+Encoding.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "URLRequest+Encoding.swift"; sourceTree = ""; }; + 3E3DEB502C3117F600CA5999 /* Response.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Response.swift; sourceTree = ""; }; + 3E3DEB522C3117F600CA5999 /* RequestType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequestType.swift; sourceTree = ""; }; + 3E3DEB532C3117F600CA5999 /* HTTPMethod.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPMethod.swift; sourceTree = ""; }; + 3E3DEB542C3117F600CA5999 /* HTTPTask.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPTask.swift; sourceTree = ""; }; + 3E3DEB552C3117F600CA5999 /* HTTPHeader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPHeader.swift; sourceTree = ""; }; + 3E3DEB9B2C315DE000CA5999 /* RawResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RawResponse.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 3E3DEB302C3117A300CA5999 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 3E3DEB3A2C3117A400CA5999 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 3E3DEB3E2C3117A400CA5999 /* GBNetwork.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 3E3DEB292C3117A300CA5999 = { + isa = PBXGroup; + children = ( + 3E3DEB352C3117A300CA5999 /* GBNetwork */, + 3E3DEB412C3117A400CA5999 /* GBNetworkTests */, + 3E3DEB342C3117A300CA5999 /* Products */, + ); + sourceTree = ""; + }; + 3E3DEB342C3117A300CA5999 /* Products */ = { + isa = PBXGroup; + children = ( + 3E3DEB332C3117A300CA5999 /* GBNetwork.framework */, + 3E3DEB3D2C3117A400CA5999 /* GBNetworkTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 3E3DEB352C3117A300CA5999 /* GBNetwork */ = { + isa = PBXGroup; + children = ( + 3E3DEB362C3117A300CA5999 /* GBNetwork.h */, + 3E3DEB4E2C3117F500CA5999 /* APIClient.swift */, + 3E3DEB4D2C3117F500CA5999 /* GBNetworkError.swift */, + 3E3DEB552C3117F600CA5999 /* HTTPHeader.swift */, + 3E3DEB532C3117F600CA5999 /* HTTPMethod.swift */, + 3E3DEB542C3117F600CA5999 /* HTTPTask.swift */, + 3E3DEB522C3117F600CA5999 /* RequestType.swift */, + 3E3DEB9B2C315DE000CA5999 /* RawResponse.swift */, + 3E3DEB502C3117F600CA5999 /* Response.swift */, + 3E3DEB4F2C3117F600CA5999 /* URLRequest+Encoding.swift */, + 3E3DEB372C3117A400CA5999 /* GBNetwork.docc */, + ); + path = GBNetwork; + sourceTree = ""; + }; + 3E3DEB412C3117A400CA5999 /* GBNetworkTests */ = { + isa = PBXGroup; + children = ( + 3E3DEB422C3117A400CA5999 /* GBNetworkTests.swift */, + ); + path = GBNetworkTests; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + 3E3DEB2E2C3117A300CA5999 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 3E3DEB442C3117A400CA5999 /* GBNetwork.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + 3E3DEB322C3117A300CA5999 /* GBNetwork */ = { + isa = PBXNativeTarget; + buildConfigurationList = 3E3DEB472C3117A400CA5999 /* Build configuration list for PBXNativeTarget "GBNetwork" */; + buildPhases = ( + 3E3DEB2E2C3117A300CA5999 /* Headers */, + 3E3DEB2F2C3117A300CA5999 /* Sources */, + 3E3DEB302C3117A300CA5999 /* Frameworks */, + 3E3DEB312C3117A300CA5999 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = GBNetwork; + productName = GBNetwork; + productReference = 3E3DEB332C3117A300CA5999 /* GBNetwork.framework */; + productType = "com.apple.product-type.framework"; + }; + 3E3DEB3C2C3117A400CA5999 /* GBNetworkTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 3E3DEB4A2C3117A400CA5999 /* Build configuration list for PBXNativeTarget "GBNetworkTests" */; + buildPhases = ( + 3E3DEB392C3117A400CA5999 /* Sources */, + 3E3DEB3A2C3117A400CA5999 /* Frameworks */, + 3E3DEB3B2C3117A400CA5999 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 3E3DEB402C3117A400CA5999 /* PBXTargetDependency */, + ); + name = GBNetworkTests; + productName = GBNetworkTests; + productReference = 3E3DEB3D2C3117A400CA5999 /* GBNetworkTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 3E3DEB2A2C3117A300CA5999 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1540; + LastUpgradeCheck = 1540; + TargetAttributes = { + 3E3DEB322C3117A300CA5999 = { + CreatedOnToolsVersion = 15.4; + }; + 3E3DEB3C2C3117A400CA5999 = { + CreatedOnToolsVersion = 15.4; + }; + }; + }; + buildConfigurationList = 3E3DEB2D2C3117A300CA5999 /* Build configuration list for PBXProject "GBNetwork" */; + compatibilityVersion = "Xcode 14.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 3E3DEB292C3117A300CA5999; + productRefGroup = 3E3DEB342C3117A300CA5999 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 3E3DEB322C3117A300CA5999 /* GBNetwork */, + 3E3DEB3C2C3117A400CA5999 /* GBNetworkTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 3E3DEB312C3117A300CA5999 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 3E3DEB3B2C3117A400CA5999 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 3E3DEB2F2C3117A300CA5999 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 3E3DEB5E2C3117F600CA5999 /* HTTPHeader.swift in Sources */, + 3E3DEB562C3117F600CA5999 /* GBNetworkError.swift in Sources */, + 3E3DEB382C3117A400CA5999 /* GBNetwork.docc in Sources */, + 3E3DEB9C2C315DE000CA5999 /* RawResponse.swift in Sources */, + 3E3DEB5C2C3117F600CA5999 /* HTTPMethod.swift in Sources */, + 3E3DEB582C3117F600CA5999 /* URLRequest+Encoding.swift in Sources */, + 3E3DEB5D2C3117F600CA5999 /* HTTPTask.swift in Sources */, + 3E3DEB572C3117F600CA5999 /* APIClient.swift in Sources */, + 3E3DEB592C3117F600CA5999 /* Response.swift in Sources */, + 3E3DEB5B2C3117F600CA5999 /* RequestType.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 3E3DEB392C3117A400CA5999 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 3E3DEB432C3117A400CA5999 /* GBNetworkTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 3E3DEB402C3117A400CA5999 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 3E3DEB322C3117A300CA5999 /* GBNetwork */; + targetProxy = 3E3DEB3F2C3117A400CA5999 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 3E3DEB452C3117A400CA5999 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 3E3DEB462C3117A400CA5999 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + 3E3DEB482C3117A400CA5999 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUILD_LIBRARY_FOR_DISTRIBUTION = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = S9G6YGQ76H; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20"; + PRODUCT_BUNDLE_IDENTIFIER = "com-id.GBNetwork"; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_INSTALL_OBJC_HEADER = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 3E3DEB492C3117A400CA5999 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUILD_LIBRARY_FOR_DISTRIBUTION = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = S9G6YGQ76H; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20"; + PRODUCT_BUNDLE_IDENTIFIER = "com-id.GBNetwork"; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_INSTALL_OBJC_HEADER = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 3E3DEB4B2C3117A400CA5999 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = S9G6YGQ76H; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "com-id.GBNetworkTests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 3E3DEB4C2C3117A400CA5999 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = S9G6YGQ76H; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "com-id.GBNetworkTests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 3E3DEB2D2C3117A300CA5999 /* Build configuration list for PBXProject "GBNetwork" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 3E3DEB452C3117A400CA5999 /* Debug */, + 3E3DEB462C3117A400CA5999 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 3E3DEB472C3117A400CA5999 /* Build configuration list for PBXNativeTarget "GBNetwork" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 3E3DEB482C3117A400CA5999 /* Debug */, + 3E3DEB492C3117A400CA5999 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 3E3DEB4A2C3117A400CA5999 /* Build configuration list for PBXNativeTarget "GBNetworkTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 3E3DEB4B2C3117A400CA5999 /* Debug */, + 3E3DEB4C2C3117A400CA5999 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 3E3DEB2A2C3117A300CA5999 /* Project object */; +} diff --git a/GEON-PPANG-iOS/Core/GBNetwork/GBNetwork.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/GEON-PPANG-iOS/Core/GBNetwork/GBNetwork.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..919434a6 --- /dev/null +++ b/GEON-PPANG-iOS/Core/GBNetwork/GBNetwork.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/GEON-PPANG-iOS/Core/GBNetwork/GBNetwork.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/GEON-PPANG-iOS/Core/GBNetwork/GBNetwork.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/GEON-PPANG-iOS/Core/GBNetwork/GBNetwork.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/GEON-PPANG-iOS/Core/GBNetwork/GBNetwork/APIClient.swift b/GEON-PPANG-iOS/Core/GBNetwork/GBNetwork/APIClient.swift new file mode 100644 index 00000000..402fd33f --- /dev/null +++ b/GEON-PPANG-iOS/Core/GBNetwork/GBNetwork/APIClient.swift @@ -0,0 +1,37 @@ +// +// APIClient.swift +// GBNetwork +// +// Created by 이성민 on 6/29/24. +// + +import Foundation + +public final class APIClient { + + public init() {} + + public func send( + _ request: Request + ) async throws -> RawResponse { + let configuration = URLSessionConfiguration.default + let session = URLSession(configuration: configuration) + + let (data, urlResponse) = try await session.data(for: request.toURLRequest()) + + guard let httpResponse = urlResponse as? HTTPURLResponse else { + throw GBNetworkError.decodingFailure(.httpDecodingError) + } + + switch httpResponse.statusCode { + case 200..<300: + return .init(data: data, httpResponse: httpResponse) + case 400..<500: + throw GBNetworkError.responseFailure(.clientError(httpResponse)) + case 500..<600: + throw GBNetworkError.responseFailure(.serverError(httpResponse)) + default: + throw GBNetworkError.responseFailure(.invalidError(httpResponse)) + } + } +} diff --git a/GEON-PPANG-iOS/Core/GBNetwork/GBNetwork/GBNetwork.docc/GBNetwork.md b/GEON-PPANG-iOS/Core/GBNetwork/GBNetwork/GBNetwork.docc/GBNetwork.md new file mode 100755 index 00000000..c81ecc33 --- /dev/null +++ b/GEON-PPANG-iOS/Core/GBNetwork/GBNetwork/GBNetwork.docc/GBNetwork.md @@ -0,0 +1,13 @@ +# ``GBNetwork`` + +Summary + +## Overview + +Text + +## Topics + +### Group + +- ``Symbol`` \ No newline at end of file diff --git a/GEON-PPANG-iOS/Core/GBNetwork/GBNetwork/GBNetwork.h b/GEON-PPANG-iOS/Core/GBNetwork/GBNetwork/GBNetwork.h new file mode 100644 index 00000000..d8191aaa --- /dev/null +++ b/GEON-PPANG-iOS/Core/GBNetwork/GBNetwork/GBNetwork.h @@ -0,0 +1,18 @@ +// +// GBNetwork.h +// GBNetwork +// +// Created by 이성민 on 6/30/24. +// + +#import + +//! Project version number for GBNetwork. +FOUNDATION_EXPORT double GBNetworkVersionNumber; + +//! Project version string for GBNetwork. +FOUNDATION_EXPORT const unsigned char GBNetworkVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + diff --git a/GEON-PPANG-iOS/Core/GBNetwork/GBNetwork/GBNetworkError.swift b/GEON-PPANG-iOS/Core/GBNetwork/GBNetwork/GBNetworkError.swift new file mode 100644 index 00000000..22d83625 --- /dev/null +++ b/GEON-PPANG-iOS/Core/GBNetwork/GBNetwork/GBNetworkError.swift @@ -0,0 +1,52 @@ +// +// GBNetworkError.swift +// GBNetwork +// +// Created by 이성민 on 6/29/24. +// + +import Foundation + +public enum GBNetworkError: Error { + + // MARK: - Request related errors + + /// Creating `URL` from `String` failure + case urlCreationFailure(urlString: String) + + /// Encoding to `URLRequest` failure + case requestEncodingFailure(EncodingFailureReason) + + // MARK: - Response related errors + + /// Failure received from response + case responseFailure(ResponseFailureReason) + + /// Decoding `Data` to response type failure + case decodingFailure(DecodingFailureReason) + + /// Failure other than the failures above + case unknownFailure(description: String) +} + + +public extension GBNetworkError { + + enum EncodingFailureReason { + case pathError(String) + case jsonError(String) + } + + /// detailed reason why `.responseFailure` occurred + enum ResponseFailureReason { + case clientError(HTTPURLResponse) + case serverError(HTTPURLResponse) + case invalidError(HTTPURLResponse) + } + + enum DecodingFailureReason { + case httpDecodingError + case dtoDecodingError + } +} + diff --git a/GEON-PPANG-iOS/Core/GBNetwork/GBNetwork/HTTPHeader.swift b/GEON-PPANG-iOS/Core/GBNetwork/GBNetwork/HTTPHeader.swift new file mode 100644 index 00000000..74998033 --- /dev/null +++ b/GEON-PPANG-iOS/Core/GBNetwork/GBNetwork/HTTPHeader.swift @@ -0,0 +1,54 @@ +// +// HTTPHeader.swift +// GBNetwork +// +// Created by 이성민 on 6/29/24. +// + +import Foundation + +public struct HTTPHeader { + private(set) var headers: [HTTPHeaderField] = [] + + public init(headers: [HTTPHeaderField?]) { + self.headers = headers.compactMap { $0 } + } +} + +extension HTTPHeader { + var fields: [String: String] { + .init(uniqueKeysWithValues: headers.map { ($0.key, $0.value) }) + } +} + +public struct HTTPHeaderField { + let key: String + let value: String +} + +public extension HTTPHeaderField { + static func contentType(value: String) -> HTTPHeaderField { + .init(key: "Content-Type", value: value) + } + + static func accessToken(value: String) -> HTTPHeaderField { + .init(key: "Authorization", value: "Bearer \(value)") + } + + static func refreshToken(value: String) -> HTTPHeaderField { + .init(key: "Authorization-refresh", value: "Bearer \(value)") + } + + static func appleRefreshToken(value: String) -> HTTPHeaderField { + .init(key: "Apple-refresh", value: value) + } + + static func platformToken(value: String) -> HTTPHeaderField { + .init(key: "Platform-Token", value: value) + } + + static func optionalToken(value: String?) -> HTTPHeaderField? { + guard let value else { return nil } + return .init(key: "Authorization", value: value) + } +} diff --git a/GEON-PPANG-iOS/Core/GBNetwork/GBNetwork/HTTPMethod.swift b/GEON-PPANG-iOS/Core/GBNetwork/GBNetwork/HTTPMethod.swift new file mode 100644 index 00000000..2b707d9b --- /dev/null +++ b/GEON-PPANG-iOS/Core/GBNetwork/GBNetwork/HTTPMethod.swift @@ -0,0 +1,17 @@ +// +// HTTPMethod.swift +// GBNetwork +// +// Created by 이성민 on 6/29/24. +// + +import Foundation + +public enum HTTPMethod: String { + /// HTTP `GET` method + case GET + /// HTTP `POST` method + case POST + /// HTTP `DELETE` method + case DELETE +} diff --git a/GEON-PPANG-iOS/Core/GBNetwork/GBNetwork/HTTPTask.swift b/GEON-PPANG-iOS/Core/GBNetwork/GBNetwork/HTTPTask.swift new file mode 100644 index 00000000..b1119856 --- /dev/null +++ b/GEON-PPANG-iOS/Core/GBNetwork/GBNetwork/HTTPTask.swift @@ -0,0 +1,17 @@ +// +// HTTPTask.swift +// GBNetwork +// +// Created by 이성민 on 6/29/24. +// + +import Foundation + +public enum HTTPTask { + /// plain request + case requestPlain + /// request with query parameters + case requestQuery(_ query: [String: Any]) + /// request with encodable body + case requestEncodable(_ body: Encodable) +} diff --git a/GEON-PPANG-iOS/Core/GBNetwork/GBNetwork/RawResponse.swift b/GEON-PPANG-iOS/Core/GBNetwork/GBNetwork/RawResponse.swift new file mode 100644 index 00000000..dd0b1aa9 --- /dev/null +++ b/GEON-PPANG-iOS/Core/GBNetwork/GBNetwork/RawResponse.swift @@ -0,0 +1,28 @@ +// +// RawResponse.swift +// GBNetwork +// +// Created by 이성민 on 6/30/24. +// + +import Foundation + +public struct RawResponse { + public let data: Data + public let httpResponse: HTTPURLResponse + + init(data: Data, httpResponse: HTTPURLResponse) { + self.data = data + self.httpResponse = httpResponse + } +} + +extension RawResponse { + public func decode(to type: DTO.Type) throws -> Response { + do { + return try JSONDecoder().decode(Response.self, from: self.data) + } catch { + throw GBNetworkError.decodingFailure(.dtoDecodingError) + } + } +} diff --git a/GEON-PPANG-iOS/Core/GBNetwork/GBNetwork/RequestType.swift b/GEON-PPANG-iOS/Core/GBNetwork/GBNetwork/RequestType.swift new file mode 100644 index 00000000..c069831a --- /dev/null +++ b/GEON-PPANG-iOS/Core/GBNetwork/GBNetwork/RequestType.swift @@ -0,0 +1,57 @@ +// +// RequestType.swift +// GBNetwork +// +// Created by 이성민 on 6/29/24. +// + +import Foundation + +public protocol RequestType { + + /// Request를 보내는 Base URL + var baseURL: String { get } + + /// Base URL 뒤에 붙는 URL Path + var path: String { get } + + /// 실제로 사용하는 HTTP Method만 정의 - Get, Post, Delete + /// + /// - Request의 HTTP Method: + /// 현재는 GET, POST, DELETE만 사용하기 때문에 세 가지만 설정 + /// + /// - computed property로 한 이유는 encodable에 걸리지 않기 때문 + var method: HTTPMethod { get } + + /// 어떤 방식으로 Request를 보낼지에 대한 정보 + /// + /// - `.requestPlain` - 추가적인 body 또는 query 없이 요청 + /// - `.requestQuery(_ [String: Any])` - url path 뒤에 query 추가하여 요청 + /// - `.requestEncodable(_ : Encodable)` - encodable 한 body와 함께 요청 + var task: HTTPTask { get } + + /// Request 마다 필요한 Header Fields + var headers: HTTPHeader { get } +} + +extension RequestType { + + func toURLRequest() throws -> URLRequest { + guard let url = URL(string: baseURL + path) else { + throw URLError(.badURL) + } + + var urlRequest = URLRequest(url: url) + urlRequest.httpMethod = self.method.rawValue + urlRequest.allHTTPHeaderFields = self.headers.fields + + switch task { + case .requestPlain: + return urlRequest + case let .requestQuery(queries): + return try urlRequest.encode(queries: queries) + case let .requestEncodable(body): + return try urlRequest.encode(body: body) + } + } +} diff --git a/GEON-PPANG-iOS/Core/GBNetwork/GBNetwork/Response.swift b/GEON-PPANG-iOS/Core/GBNetwork/GBNetwork/Response.swift new file mode 100644 index 00000000..1db359d1 --- /dev/null +++ b/GEON-PPANG-iOS/Core/GBNetwork/GBNetwork/Response.swift @@ -0,0 +1,14 @@ +// +// ResponseType.swift +// GBNetwork +// +// Created by 이성민 on 6/29/24. +// + +import Foundation + +public struct Response: Decodable { + public let code: Int + public let message: String + public let data: DTO +} diff --git a/GEON-PPANG-iOS/Core/GBNetwork/GBNetwork/URLRequest+Encoding.swift b/GEON-PPANG-iOS/Core/GBNetwork/GBNetwork/URLRequest+Encoding.swift new file mode 100644 index 00000000..d51c16be --- /dev/null +++ b/GEON-PPANG-iOS/Core/GBNetwork/GBNetwork/URLRequest+Encoding.swift @@ -0,0 +1,40 @@ +// +// URLRequest+Encoding.swift +// GBNetwork +// +// Created by 이성민 on 6/29/24. +// + +import Foundation + +extension URLRequest { + func encode(queries: [String: Any]) throws -> URLRequest { + guard let url = self.url else { + throw GBNetworkError.requestEncodingFailure(.pathError("missing url")) + } + + var components = URLComponents(string: url.absoluteString) + components?.queryItems = queries.map(toQueryItem) + + guard let encodedURL = components?.url else { + throw GBNetworkError.requestEncodingFailure(.pathError("query encoding fail")) + } + return .init(url: encodedURL) + } + + func encode(body: Encodable) throws -> URLRequest { + do { + var request = self + request.httpBody = try JSONEncoder().encode(body) + return request + } catch { + throw GBNetworkError.requestEncodingFailure(.jsonError("incorrect body type")) + } + } +} + +private extension URLRequest { + func toQueryItem(_ query: (key: String, value: Any)) -> URLQueryItem { + .init(name: query.key, value: query.value as? String) + } +} diff --git a/GEON-PPANG-iOS/Core/GBNetwork/GBNetworkTests/GBNetworkTests.swift b/GEON-PPANG-iOS/Core/GBNetwork/GBNetworkTests/GBNetworkTests.swift new file mode 100644 index 00000000..200e9da3 --- /dev/null +++ b/GEON-PPANG-iOS/Core/GBNetwork/GBNetworkTests/GBNetworkTests.swift @@ -0,0 +1,36 @@ +// +// GBNetworkTests.swift +// GBNetworkTests +// +// Created by 이성민 on 6/30/24. +// + +import XCTest +@testable import GBNetwork + +final class GBNetworkTests: XCTestCase { + + override func setUpWithError() throws { + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDownWithError() throws { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testExample() throws { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. + // Any test you write for XCTest can be annotated as throws and async. + // Mark your test throws to produce an unexpected failure when your test encounters an uncaught error. + // Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards. + } + + func testPerformanceExample() throws { + // This is an example of a performance test case. + self.measure { + // Put the code you want to measure the time of here. + } + } + +} diff --git a/GEON-PPANG-iOS/Data/Network/DataMapping/ResponseDTO/BestBakeryResponseDTO.swift b/GEON-PPANG-iOS/Data/Network/DataMapping/ResponseDTO/BestBakeryResponseDTO.swift new file mode 100644 index 00000000..2c242286 --- /dev/null +++ b/GEON-PPANG-iOS/Data/Network/DataMapping/ResponseDTO/BestBakeryResponseDTO.swift @@ -0,0 +1,58 @@ +// +// BestBakeryDTO.swift +// GEON-PPANG-iOS +// +// Created by JEONGEUN KIM on 6/29/24. +// +import Foundation + +struct BestBakeryResponseDTO: Decodable { + + let bakeryID: Int + let bakeryName: String + let bakeryPicture: String + let isHACCP, isVegan, isNonGMO: Bool + let firstNearStation: String + let secondNearStation: String + let reviewCount, bookMarkCount: Int + + enum CodingKeys: String, CodingKey { + case bakeryID = "bakeryId" + case bakeryName + case bakeryPicture + case isHACCP, isVegan, isNonGMO + case firstNearStation, secondNearStation + case reviewCount + case bookMarkCount + } + +} + +extension BestBakeryResponseDTO { + + func toDomain() -> BestBakery { + let overview: BakeryOverview = .init( + id: bakeryID, + name: bakeryName, + image: bakeryPicture + ) + let certifications: Certifications = .init( + isHaccp: isHACCP, + isVegan: isVegan, + isNonGMO: isNonGMO + ) + let regions: Regions = .init( + firstRegion: firstNearStation, + secondRegion: secondNearStation + ) + + return BestBakery( + overview: overview, + certifications: certifications, + bookmarkCount: bookMarkCount, + reviewCount: reviewCount, + regions: regions + ) + } +} + diff --git a/GEON-PPANG-iOS/Data/Network/DataMapping/ResponseDTO/BestReviewResponseDTO.swift b/GEON-PPANG-iOS/Data/Network/DataMapping/ResponseDTO/BestReviewResponseDTO.swift new file mode 100644 index 00000000..cc4edb1e --- /dev/null +++ b/GEON-PPANG-iOS/Data/Network/DataMapping/ResponseDTO/BestReviewResponseDTO.swift @@ -0,0 +1,39 @@ +// +// BestReviewDTO.swift +// GEON-PPANG-iOS +// +// Created by JEONGEUN KIM on 6/29/24. +// + +import Foundation + +struct BestReviewResponseDTO: Decodable { + let bakeryID: Int + let bakeryName: String + let bakeryPicture: String + let isHACCP, isVegan, isNonGMO: Bool + let firstNearStation, secondNearStation: String + let reviewCount: Int + let reviewText: String + let firstMaxRecommendKeyword: String + let secondMaxRecommendKeyword: String? + let bookMarkCount: Int + + enum CodingKeys: String, CodingKey { + case bakeryID = "bakeryId" + case bakeryName, bakeryPicture, isHACCP, isVegan, isNonGMO, firstNearStation, secondNearStation, reviewCount, reviewText, firstMaxRecommendKeyword, secondMaxRecommendKeyword, bookMarkCount + } +} + +extension BestReviewResponseDTO { + + func toDomain() -> BestReview { + let overview: BakeryOverview = .init(id: bakeryID, name: bakeryName, image: bakeryPicture) + + return BestReview(overview: overview, + reviewOverview: reviewText, + recommendKeywords: [firstMaxRecommendKeyword, secondNearStation], + bookmarkCount: bookMarkCount, + reviewCount: reviewCount) + } +} diff --git a/GEON-PPANG-iOS/Data/Network/Endpoints/BestEndpoint.swift b/GEON-PPANG-iOS/Data/Network/Endpoints/BestEndpoint.swift new file mode 100644 index 00000000..a73f84d7 --- /dev/null +++ b/GEON-PPANG-iOS/Data/Network/Endpoints/BestEndpoint.swift @@ -0,0 +1,51 @@ +// +// BestEndpoint.swift +// GEON-PPANG-iOS +// +// Created by JEONGEUN KIM on 6/29/24. +// + +import Foundation + +import GBNetwork + +enum BestEndpoint { + case getBestBakeries + case getBestReviews +} + +extension BestEndpoint: RequestType { + var baseURL: String { + return Config.baseURL + } + + var path: String { + switch self { + case .getBestBakeries: "/best/bakeries" + case .getBestReviews: "/best/reviews" + } + } + + var method: GBNetwork.HTTPMethod { + switch self { + case .getBestBakeries: .GET + case .getBestReviews: .GET + } + } + + var task: GBNetwork.HTTPTask { + switch self { + case .getBestBakeries: .requestPlain + case .getBestReviews: .requestPlain + } + } + + var headers: HTTPHeader { + switch self { + case .getBestBakeries, .getBestReviews: + return .init(headers: [ + .contentType(value: "application/json"), .optionalToken(value: nil) + ]) + } + } +} diff --git a/GEON-PPANG-iOS/Data/Repository/BestRepositoryImpl.swift b/GEON-PPANG-iOS/Data/Repository/BestRepositoryImpl.swift new file mode 100644 index 00000000..8cc0227e --- /dev/null +++ b/GEON-PPANG-iOS/Data/Repository/BestRepositoryImpl.swift @@ -0,0 +1,60 @@ +// +// BestRepository.swift +// GEON-PPANG-iOS +// +// Created by JEONGEUN KIM on 6/29/24. +// + +import Foundation + +import GBNetwork + +final class BestRepositoryImpl: BestRepository { + + private let apiClient: APIClient = .init() + + func getBestBakeries() async throws -> [BestBakery] { + let rawResponse = try await apiClient.send(.getBestBakeries) + let response = try rawResponse.decode(to: [BestBakeryResponseDTO].self) + + switch response.code { + case 200..<300: + return response.data.map { $0.toDomain() } + case 401: + if response.message == MemberError.invalidToken.rawValue { + throw MemberError.invalidToken + } else if response.message == MemberError.expiredToken.rawValue { + throw MemberError.expiredToken + } else { throw MemberError.otherError } + default: + throw MemberError.otherError + } + } + + func getBestReviews() async throws -> [BestReview] { + let rawResponse = try await apiClient.send(.getBestReviews) + let response = try rawResponse.decode(to: [BestReviewResponseDTO].self) + + switch response.code { + case 200..<300: + return response.data.map { $0.toDomain() } + case 401: + if response.message == MemberError.invalidToken.rawValue { + throw MemberError.invalidToken + } else if response.message == MemberError.expiredToken.rawValue { + throw MemberError.expiredToken + } else { throw MemberError.otherError } + default: + throw MemberError.otherError + } + } +} + +enum MemberError: String, Error { + /// 유효하지 않은 토큰 ( 401 ) + case invalidToken = "엑세스 토큰이 없습니다" + /// 만료된 토큰 ( 401 ) + case expiredToken = "만료된 토큰입니다" + /// 그 외의 오류 ( 404, 500번대 ) + case otherError +} diff --git a/GEON-PPANG-iOS/Domain/Entity/Base/BakeryOverview.swift b/GEON-PPANG-iOS/Domain/Entity/Base/BakeryOverview.swift index 6a5bdc50..c6ff7310 100644 --- a/GEON-PPANG-iOS/Domain/Entity/Base/BakeryOverview.swift +++ b/GEON-PPANG-iOS/Domain/Entity/Base/BakeryOverview.swift @@ -7,7 +7,7 @@ import Foundation -struct BakeryOverview { +struct BakeryOverview: Hashable { let id: Int let name: String let image: String diff --git a/GEON-PPANG-iOS/Domain/Entity/Base/Certifications.swift b/GEON-PPANG-iOS/Domain/Entity/Base/Certifications.swift index 9c2cbfac..aa907746 100644 --- a/GEON-PPANG-iOS/Domain/Entity/Base/Certifications.swift +++ b/GEON-PPANG-iOS/Domain/Entity/Base/Certifications.swift @@ -7,7 +7,7 @@ import Foundation -struct Certifications { +struct Certifications: Hashable { let isHaccp: Bool let isVegan: Bool let isNonGMO: Bool diff --git a/GEON-PPANG-iOS/Domain/Entity/Base/Regions.swift b/GEON-PPANG-iOS/Domain/Entity/Base/Regions.swift index 25e349b1..4f64f422 100644 --- a/GEON-PPANG-iOS/Domain/Entity/Base/Regions.swift +++ b/GEON-PPANG-iOS/Domain/Entity/Base/Regions.swift @@ -7,7 +7,7 @@ import Foundation -struct Regions { +struct Regions: Hashable { let firstRegion: String let secondRegion: String? } diff --git a/GEON-PPANG-iOS/Domain/Entity/Home/BestBakery.swift b/GEON-PPANG-iOS/Domain/Entity/Home/BestBakery.swift index fac8d31f..7f7ca489 100644 --- a/GEON-PPANG-iOS/Domain/Entity/Home/BestBakery.swift +++ b/GEON-PPANG-iOS/Domain/Entity/Home/BestBakery.swift @@ -7,7 +7,7 @@ import Foundation -struct BestBakery { +struct BestBakery: Hashable { let overview: BakeryOverview let certifications: Certifications let bookmarkCount: Int diff --git a/GEON-PPANG-iOS/Domain/Entity/Home/BestReview.swift b/GEON-PPANG-iOS/Domain/Entity/Home/BestReview.swift index 0b1a7605..161786bf 100644 --- a/GEON-PPANG-iOS/Domain/Entity/Home/BestReview.swift +++ b/GEON-PPANG-iOS/Domain/Entity/Home/BestReview.swift @@ -7,7 +7,7 @@ import Foundation -struct BestReview { +struct BestReview: Hashable { let overview: BakeryOverview let reviewOverview: String let recommendKeywords: [String] diff --git a/GEON-PPANG-iOS/Domain/Interface/BestRepository.swift b/GEON-PPANG-iOS/Domain/Interface/BestRepository.swift new file mode 100644 index 00000000..892602b3 --- /dev/null +++ b/GEON-PPANG-iOS/Domain/Interface/BestRepository.swift @@ -0,0 +1,14 @@ +// +// BestRepository.swift +// GEON-PPANG-iOS +// +// Created by JEONGEUN KIM on 7/9/24. +// + +import Foundation + +protocol BestRepository { + func getBestBakeries() async throws -> [BestBakery] + func getBestReviews() async throws -> [BestReview] +} + diff --git a/GEON-PPANG-iOS/Domain/UseCase/HomeUseCase.swift b/GEON-PPANG-iOS/Domain/UseCase/HomeUseCase.swift new file mode 100644 index 00000000..0209622e --- /dev/null +++ b/GEON-PPANG-iOS/Domain/UseCase/HomeUseCase.swift @@ -0,0 +1,48 @@ +// +// HomeUseCase.swift +// GEON-PPANG-iOS +// +// Created by JEONGEUN KIM on 6/29/24. +// + +import Foundation +import Combine + +protocol HomeUseCase { + func fetchBestBakeries() async throws -> [BestBakery] + func fetchBestReviews() async throws -> [BestReview] +} + + +final class HomeUseCaseImpl: HomeUseCase { + + let bestRepository: BestRepository + + init(bestRepository: BestRepository) { + self.bestRepository = bestRepository + } + + func fetchBestBakeries() async throws -> [BestBakery] { + do { + return try await bestRepository.getBestBakeries() + } + catch MemberError.expiredToken { + throw MemberError.expiredToken + } + catch MemberError.invalidToken { + throw MemberError.invalidToken + } + } + + func fetchBestReviews() async throws -> [BestReview] { + do { + return try await bestRepository.getBestReviews() + } + catch MemberError.expiredToken { + throw MemberError.expiredToken + } + catch MemberError.invalidToken { + throw MemberError.invalidToken + } + } +} diff --git a/GEON-PPANG-iOS/Infrastructure/Network/InfrastructureDummy.swift b/GEON-PPANG-iOS/Infrastructure/Network/InfrastructureDummy.swift deleted file mode 100644 index 33f4c75d..00000000 --- a/GEON-PPANG-iOS/Infrastructure/Network/InfrastructureDummy.swift +++ /dev/null @@ -1,17 +0,0 @@ -// -// Endpoint.swift -// GEON-PPANG-iOS -// -// Created by 이성민 on 4/3/24. -// - -import Foundation - -/// `Domain 레이어를 모르면서` Data 레이어에서 신경쓰지 않아도 될 요소들 구현 -/// -/// e.g. -/// - APIClient: API 통신하는 실질적인 부분, request 등과 같은 것들 구현하면 좋을듯 -/// - 외부 DB랑 내부 DB랑 따로 구분해야 할수도 -/// - APIConfiguration도 여기서 정의해두는게 좋을듯, header 등 -/// - Endpoint: 실질적인 `요청을 보내는` 형태 -/// - 그 외에도 현재 `Global/Network/Base`에 있는 몇가지 요소들 등 여기에 구현 diff --git a/GEON-PPANG-iOS/Presentation/Common/StackView/RegionStackView.swift b/GEON-PPANG-iOS/Presentation/Common/StackView/RegionStackView.swift index c4d78b50..40aed7be 100644 --- a/GEON-PPANG-iOS/Presentation/Common/StackView/RegionStackView.swift +++ b/GEON-PPANG-iOS/Presentation/Common/StackView/RegionStackView.swift @@ -65,6 +65,13 @@ final class RegionStackView: UIStackView { setUI(!data.stations[1].isEmpty) } + func configureRegion(_ data: Regions) { + + regionFirstTag.text = data.firstRegion + regionSecondTag.text = data.secondRegion + setUI(data.secondRegion != nil) + } + func configureChipCornerRadius(_ radius: CGFloat) { [regionFirstTag, regionSecondTag].forEach { diff --git a/GEON-PPANG-iOS/Presentation/Scene/Home/Home/HomeBakeryCollectionViewCell.swift b/GEON-PPANG-iOS/Presentation/Scene/Home/Home/HomeBakeryCollectionViewCell.swift index 52a7aed9..ed5a70c1 100644 --- a/GEON-PPANG-iOS/Presentation/Scene/Home/Home/HomeBakeryCollectionViewCell.swift +++ b/GEON-PPANG-iOS/Presentation/Scene/Home/Home/HomeBakeryCollectionViewCell.swift @@ -8,27 +8,37 @@ import UIKit import SnapKit -import Then import Kingfisher final class HomeBakeryCollectionViewCell: UICollectionViewCell { // MARK: - UI Property - private let bakeryImage = UIImageView() - private let markStackView = MarkStackView() - private let bakeryTitle = UILabel() private let reviewCount = IconWithTextView(.reviews) private let bookmarkCount = IconWithTextView(.bookmark) private let regionStackView = RegionStackView() + // private var markStackView: GBStackView? + private let bakeryImage: UIImageView = { + let view = UIImageView() + view.contentMode = .scaleAspectFill + view.clipsToBounds = true + return view + }() - // MARK: - Life Cycle + private let bakeryTitle: UILabel = { + let label = UILabel() + label.numberOfLines = 1 + label.font = .bodyB1 + label.textColor = .gbbGray700 + label.sizeToFit() + return label + }() + + // MARK: - Init override func prepareForReuse() { super.prepareForReuse() - markStackView.getMarkStatus(false, false, false) - markStackView.configureIconImage(.bigHACCPMark, .bigVeganMark, .bigGMOMark) regionStackView.arrangedSubviews.forEach { regionStackView.removeArrangedSubview($0) } @@ -36,7 +46,6 @@ final class HomeBakeryCollectionViewCell: UICollectionViewCell { override init(frame: CGRect) { super.init(frame: .zero) - setLayout() setUI() } @@ -79,59 +88,48 @@ final class HomeBakeryCollectionViewCell: UICollectionViewCell { $0.leading.equalTo(bakeryTitle.snp.leading) $0.bottom.equalToSuperview().inset(16) } - - bakeryImage.addSubview(markStackView) - markStackView.snp.makeConstraints { - $0.top.leading.equalToSuperview().offset(10) - $0.size.equalTo(CGSize(width: heightConsideringNotch(68), height: heightConsideringNotch(28))) - } } private func setUI() { - - self.do { - $0.layer.applyShadow(alpha: 0.1, x: 0, y: 0, blur: 10) - $0.contentView.backgroundColor = .white - $0.contentView.makeCornerRound(radius: 5) - $0.contentView.clipsToBounds = true - } - - bakeryImage.do { - $0.contentMode = .scaleAspectFill - $0.clipsToBounds = true - } - - markStackView.do { - $0.configureIconImage(.bigHACCPMark, .bigVeganMark, .bigGMOMark) - } - - bakeryTitle.do { - $0.numberOfLines = 1 - $0.basic(font: .bodyB1!, color: .gbbGray700!) - $0.sizeToFit() - } - + layer.applyShadow(alpha: 0.1, x: 0, y: 0, blur: 10) + contentView.backgroundColor = .white + contentView.makeCornerRound(radius: 5) + contentView.clipsToBounds = true } // MARK: - Custom Method - func configureCellUI(data: HomeBestBakeryResponseDTO) { + func configureCellUI(data: BestBakery) { - let url = URL(string: data.bakeries.picture) + let url = URL(string: data.overview.image) bakeryImage.kf.setImage(with: url, placeholder: UIImage.loading_large) - bakeryTitle.setLineHeight(by: 1.08, with: data.bakeries.name) + bakeryTitle.setLineHeight(by: 1.08, with: data.overview.name) bakeryTitle.lineBreakMode = .byTruncatingTail - - bookmarkCount.configureHomeCell(count: data.bakeries.bookmarkCount) - reviewCount.configureHomeCell(count: data.bakeries.reviewCount) - markStackView.getMarkStatus(data.bakeries.mark.isHACCP, - data.bakeries.mark.isVegan, - data.bakeries.mark.isNonGMO) - if data.bakeries.station.second == "" { + bookmarkCount.configureHomeCell(count: data.bookmarkCount) + reviewCount.configureHomeCell(count: data.reviewCount) + + self.configureStackView(with: data.certifications) + + // cell builder 머지 후 변경 + + if data.regions.secondRegion == "" { regionStackView.removeSecondRegion() } - regionStackView.configureRegion(data.bakeries.station) + regionStackView.configureRegion(data.regions) + } + + func configureStackView(with certifications: Certifications) { + +// self.markStackView = GBStackView(type: .big, data: data) +// +// if let markStackView = markStackView { +// bakeryImage.addSubview(markStackView) +// markStackView.snp.makeConstraints { +// $0.top.leading.equalToSuperview().offset(10) +// $0.size.equalTo(CGSize(width: heightConsideringNotch(68), height: heightConsideringNotch(28))) +// } +// } } } diff --git a/GEON-PPANG-iOS/Presentation/Scene/Home/Home/HomeBottomCollectionViewCell.swift b/GEON-PPANG-iOS/Presentation/Scene/Home/Home/HomeBottomCollectionViewCell.swift index aa72937a..f1777df7 100644 --- a/GEON-PPANG-iOS/Presentation/Scene/Home/Home/HomeBottomCollectionViewCell.swift +++ b/GEON-PPANG-iOS/Presentation/Scene/Home/Home/HomeBottomCollectionViewCell.swift @@ -8,21 +8,27 @@ import UIKit import SnapKit -import Then final class HomeBottomCollectionViewCell: UICollectionViewCell { // MARK: - UI Property - private let footerLabel = UILabel() + private let footerLabel: UILabel = { + let label = UILabel() + label.font = .captionM2 + label.textColor = .gbbGray300 + label.textAlignment = .left + label.numberOfLines = 4 + label.setLineHeight(by: 1.37, with: I18N.Home.bottomSectionTitle) + return label + }() - // MARK: - Life Cycle + // MARK: - init override init(frame: CGRect) { super.init(frame: .zero) setLayout() - setUI() } required init?(coder: NSCoder) { @@ -38,15 +44,4 @@ final class HomeBottomCollectionViewCell: UICollectionViewCell { $0.top.directionalHorizontalEdges.equalToSuperview() } } - - private func setUI() { - - footerLabel.do { - $0.textAlignment = .left - $0.numberOfLines = 4 - $0.basic(font: .captionM2!, - color: .gbbGray300!) - $0.setLineHeight(by: 1.37, with: I18N.Home.bottomSectionTitle) - } - } } diff --git a/GEON-PPANG-iOS/Presentation/Scene/Home/Home/HomeReviewCollectionViewCell.swift b/GEON-PPANG-iOS/Presentation/Scene/Home/Home/HomeReviewCollectionViewCell.swift index c205f1e9..b61bf311 100644 --- a/GEON-PPANG-iOS/Presentation/Scene/Home/Home/HomeReviewCollectionViewCell.swift +++ b/GEON-PPANG-iOS/Presentation/Scene/Home/Home/HomeReviewCollectionViewCell.swift @@ -9,7 +9,6 @@ import UIKit import Kingfisher import SnapKit -import Then final class HomeReviewCollectionViewCell: UICollectionViewCell { @@ -20,21 +19,52 @@ final class HomeReviewCollectionViewCell: UICollectionViewCell { // MARK: - UI Property - private lazy var bakeryImage = GradientImageView(colors: [UIColor.clear.cgColor, UIColor.black.withAlphaComponent(0.5).cgColor]) - private let reviewTitle = UILabel() - private let bakeryTitle = UILabel() + private lazy var bakeryImage: GradientImageView = { + let view = GradientImageView(colors: [UIColor.clear.cgColor, UIColor.black.withAlphaComponent(0.5).cgColor]) + view.contentMode = .scaleAspectFill + view.clipsToBounds = true + return view + }() + + private let reviewTitle: UILabel = { + let label = UILabel() + label.font = .bodyB2 + label.textColor = .gbbWhite + label.textAlignment = .left + label.numberOfLines = 2 + return label + }() + + private let bakeryTitle: UILabel = { + let label = UILabel() + label.font = .bodyB1 + label.textColor = .gbbGray700 + label.textAlignment = .left + label.numberOfLines = 1 + return label + }() + + private lazy var collectionView: UICollectionView = { + let collectionView = UICollectionView(frame: .zero, collectionViewLayout: OptionsCollectionViewFlowLayout()) + collectionView.register(DescriptionCollectionViewCell.self, + forCellWithReuseIdentifier: DescriptionCollectionViewCell.identifier) + collectionView.isScrollEnabled = false + collectionView.backgroundColor = .clear + collectionView.delegate = self + collectionView.dataSource = self + return collectionView + }() + private let reviewCount = IconWithTextView(.reviews) private let bookmarkCount = IconWithTextView(.bookmark) - private lazy var collectionView = UICollectionView(frame: .zero, collectionViewLayout: OptionsCollectionViewFlowLayout()) - // MARK: - Life Cycle + // MARK: - init override init(frame: CGRect) { super.init(frame: .zero) setLayout() setUI() - setRegister() } required init?(coder: NSCoder) { @@ -87,67 +117,30 @@ final class HomeReviewCollectionViewCell: UICollectionViewCell { private func setUI() { - self.do { - $0.layer.applyShadow(alpha: 0.1, x: 0, y: 0, blur: 10) - $0.contentView.backgroundColor = .white - $0.contentView.makeCornerRound(radius: 5) - $0.contentView.clipsToBounds = true - } - - bakeryImage.do { - $0.contentMode = .scaleAspectFill - $0.clipsToBounds = true - } - - reviewTitle.do { - $0.basic(font: .bodyB2!, color: .white) - $0.textAlignment = .left - $0.numberOfLines = 2 - } - - collectionView.do { - $0.isScrollEnabled = false - $0.backgroundColor = .clear - $0.delegate = self - $0.dataSource = self - } - - bakeryTitle.do { - $0.numberOfLines = 1 - $0.basic(font: .bodyB1!, color: .gbbGray700!) - $0.textAlignment = .left - - } - + self.layer.applyShadow(alpha: 0.1, x: 0, y: 0, blur: 10) + self.contentView.backgroundColor = .white + self.contentView.makeCornerRound(radius: 5) + self.contentView.clipsToBounds = true } - func configureCellUI(data: HomeBestReviewResponseDTO) { + func configureCellUI(data: BestReview) { - let url = URL(string: data.reviews.picture) + let url = URL(string: data.overview.image) bakeryImage.kf.setImage(with: url, placeholder: UIImage.loading_large) - reviewTitle.setLineHeight(by: 1.14, with: "\"\(data.text)\"") + reviewTitle.setLineHeight(by: 1.14, with: "\"\(data.reviewOverview)\"") reviewTitle.lineBreakMode = .byTruncatingTail - bakeryTitle.setLineHeight(by: 1.08, with: data.reviews.name) + bakeryTitle.setLineHeight(by: 1.08, with: data.overview.name) bakeryTitle.lineBreakMode = .byTruncatingTail - reviewCount.configureHomeCell(count: data.reviews.reviewCount) - bookmarkCount.configureHomeCell(count: data.reviews.bookmarkCount) + reviewCount.configureHomeCell(count: data.reviewCount) + bookmarkCount.configureHomeCell(count: data.bookmarkCount) - keywords = data.keywords.keywords + keywords = data.recommendKeywords collectionView.reloadData() } } -// MARK: - CollectionView Register - -extension HomeReviewCollectionViewCell { - private func setRegister() { - - collectionView.register(cell: DescriptionCollectionViewCell.self) - } -} - // MARK: - UICollectionViewDataSource extension HomeReviewCollectionViewCell: UICollectionViewDataSource { diff --git a/GEON-PPANG-iOS/Presentation/Scene/Home/Home/View/HomeHeaderView.swift b/GEON-PPANG-iOS/Presentation/Scene/Home/Home/View/HomeHeaderView.swift index cfc57930..8b1a82f7 100644 --- a/GEON-PPANG-iOS/Presentation/Scene/Home/Home/View/HomeHeaderView.swift +++ b/GEON-PPANG-iOS/Presentation/Scene/Home/Home/View/HomeHeaderView.swift @@ -8,7 +8,6 @@ import UIKit import SnapKit -import Then final class HomeHeaderView: UICollectionReusableView { diff --git a/GEON-PPANG-iOS/Presentation/Scene/Home/Home/View/HomeTopView.swift b/GEON-PPANG-iOS/Presentation/Scene/Home/Home/View/HomeTopView.swift index 26a71fec..737e6eb8 100644 --- a/GEON-PPANG-iOS/Presentation/Scene/Home/Home/View/HomeTopView.swift +++ b/GEON-PPANG-iOS/Presentation/Scene/Home/Home/View/HomeTopView.swift @@ -8,7 +8,6 @@ import UIKit import SnapKit -import Then final class HomeTopView: UIView { @@ -18,9 +17,30 @@ final class HomeTopView: UIView { // MARK: - UI Property - private let titleLabel = UILabel() - private let searchTextField = SearchTextField() - private lazy var filterButton = UIButton() + private let titleLabel: UILabel = { + let label = UILabel() + label.font = .title1 + label.textColor = .gbbGray700 + label.numberOfLines = 2 + label.textAlignment = .left + return label + }() + + private lazy var searchTextField: SearchTextField = { + let textField = SearchTextField() + textField.configureViewType(.home) + textField.pushToSearchView = { [weak self] in + self?.pushToSearchView?() + } + return textField + }() + + private let filterButton: UIButton = { + let button = UIButton() + button.setImage(.homeFilterButton, for: .normal) + return button + }() + private let lineView = LineView() // MARK: - Life Cycle @@ -29,7 +49,6 @@ final class HomeTopView: UIView { super.init(frame: .zero) setLayout() - setUI() } required init?(coder: NSCoder) { @@ -69,28 +88,7 @@ final class HomeTopView: UIView { $0.directionalHorizontalEdges.equalToSuperview() } } - - private func setUI() { - - titleLabel.do { - $0.numberOfLines = 2 - $0.textAlignment = .left - $0.basic(font: .title1!, - color: .gbbGray700!) - } - - searchTextField.do { - $0.configureViewType(.home) - $0.pushToSearchView = { - self.pushToSearchView?() - } - } - - filterButton.do { - $0.setImage(.homeFilterButton, for: .normal) - } - } - + func configureTitleText(_ title: String) { titleLabel.text = "\(title)님\n건빵에 오신걸 환영해요!" } @@ -102,5 +100,4 @@ final class HomeTopView: UIView { action() }, for: .touchUpInside) } - } diff --git a/GEON-PPANG-iOS/Presentation/Scene/Home/Home/ViewController/HomeViewController.swift b/GEON-PPANG-iOS/Presentation/Scene/Home/Home/ViewController/HomeViewController.swift index 5e4d24bd..e0d1bc77 100644 --- a/GEON-PPANG-iOS/Presentation/Scene/Home/Home/ViewController/HomeViewController.swift +++ b/GEON-PPANG-iOS/Presentation/Scene/Home/Home/ViewController/HomeViewController.swift @@ -147,11 +147,11 @@ final class HomeViewController: BaseViewController { switch item { case .bakery(let data): let cell: HomeBakeryCollectionViewCell = collectionView.dequeueReusableCell(for: indexPath) - cell.configureCellUI(data: data) +// cell.configureCellUI(data: data) return cell case .reviews(let data): let cell: HomeReviewCollectionViewCell = collectionView.dequeueReusableCell(for: indexPath) - cell.configureCellUI(data: data) +// cell.configureCellUI(data: data) return cell case .bottom: let cell: HomeBottomCollectionViewCell = collectionView.dequeueReusableCell(for: indexPath) diff --git a/GEON-PPANG-iOS/Presentation/Scene/Home/Home/ViewController/NewHomeViewController.swift b/GEON-PPANG-iOS/Presentation/Scene/Home/Home/ViewController/NewHomeViewController.swift new file mode 100644 index 00000000..5702b536 --- /dev/null +++ b/GEON-PPANG-iOS/Presentation/Scene/Home/Home/ViewController/NewHomeViewController.swift @@ -0,0 +1,330 @@ +// +// NewHomeViewController.swift +// GEON-PPANG-iOS +// +// Created by JEONGEUN KIM on 7/9/24. +// + +import Combine + +import UIKit +import SnapKit + +final class NewHomeViewController: UIViewController { + + enum Sections: Int, CaseIterable { + case bakery + case review + case bottom + } + + enum Item: Hashable { + case bakery(BestBakery) + case review(BestReview) + case bottom + } + + // MARK: - Property + + private var dataSource: UICollectionViewDiffableDataSource? + private var snapShot: NSDiffableDataSourceSnapshot? + + private let viewModel: any ViewModelType + private var cancelBag: Set = Set() + + // MARK: - UI Property + + private lazy var safeArea = self.view.safeAreaLayoutGuide + + private let topView: HomeTopView = { + let view = HomeTopView() + return view + }() + + private lazy var collectionView: UICollectionView = { + let collectionView = UICollectionView(frame: .zero, collectionViewLayout: self.layout()) + collectionView.showsVerticalScrollIndicator = false + collectionView.backgroundColor = .gbbBackground1 + collectionView.register(HomeBakeryCollectionViewCell.self, + forCellWithReuseIdentifier: HomeBakeryCollectionViewCell.identifier) + collectionView.register(HomeReviewCollectionViewCell.self, + forCellWithReuseIdentifier: HomeReviewCollectionViewCell.identifier) + collectionView.register(HomeBottomCollectionViewCell.self, + forCellWithReuseIdentifier: HomeBottomCollectionViewCell.identifier) + collectionView.register(HomeHeaderView.self, + forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, + withReuseIdentifier: HomeHeaderView.identifier) + return collectionView + }() + + // MARK: - init + + init(viewModel: any ViewModelType) { + self.viewModel = viewModel + super.init(nibName: nil, bundle: nil) + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Life cycle + + override func viewDidLoad() { + super.viewDidLoad() + + setLayout() + setUI() + setViewModel() + setDataSource() + bindCollectionViewEvents() + } + + private func setLayout() { + view.addSubview(topView) + topView.snp.makeConstraints { + $0.top.equalToSuperview().offset(heightConsideringNotch(44)) + $0.directionalHorizontalEdges.equalTo(safeArea) + $0.height.equalTo(200) + } + + view.addSubview(collectionView) + collectionView.snp.makeConstraints { + $0.top.equalTo(topView.snp.bottom) + $0.directionalHorizontalEdges.equalTo(safeArea) + $0.bottom.equalToSuperview() + } + } + + private func setUI() { + self.view.backgroundColor = .white + } + + private func setViewModel() { + let output = transformedOutput() + self.bindOutputToViewModel(output) + } + + private func transformedOutput() -> HomeViewModel.Output? { + guard let viewModel = self.viewModel as? HomeViewModel + else { return nil } + let input = HomeViewModel.Input(viewDidLoad: self.viewDidLoadPublisher) + return viewModel.transform(input) + } + + private func bindOutputToViewModel(_ output: HomeViewModel.Output?) { + guard let output else { return } + + output.bakery + .receive(on: RunLoop.main) + .sink { completion in + print("completion:\(completion)") + } receiveValue: { [weak self] bakery in + self?.reloadBestList(bakery: bakery) + } + .store(in: &self.cancelBag) + + output.review + .receive(on: RunLoop.main) + .sink { completion in + print("completion:\(completion)") + } receiveValue: { [weak self] review in + self?.reloadBestList(review: review) + } + .store(in: &self.cancelBag) + } +} + +extension NewHomeViewController { + + func setDataSource() { + self.dataSource = self.BestCollectionViewDataSource() + self.configureSnapshot() + self.configureSupplementaryView() + } + + func BestCollectionViewDataSource() -> UICollectionViewDiffableDataSource { + let dataSource = UICollectionViewDiffableDataSource(collectionView: collectionView) { collectionView, indexPath, item in + + switch item { + case .bakery(let data): + let cell: HomeBakeryCollectionViewCell = collectionView.dequeueReusableCell(for: indexPath) + cell.configureCellUI(data: data) + return cell + case .review(let data): + let cell: HomeReviewCollectionViewCell = collectionView.dequeueReusableCell(for: indexPath) + cell.configureCellUI(data: data) + return cell + case .bottom: + let cell: HomeBottomCollectionViewCell = collectionView.dequeueReusableCell(for: indexPath) + return cell + } + } + + return dataSource + } + + private func configureSupplementaryView() { + dataSource?.supplementaryViewProvider = { (collectionView, _, indexPath) in + guard let section = Sections(rawValue: indexPath.section) else { fatalError() } + + let header: HomeHeaderView = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, indexPath: indexPath) + + // header.configureSectionHeaderTitle("", "") + return header + } + } + + func configureSnapshot() { + self.snapShot = NSDiffableDataSourceSnapshot() + snapShot?.appendSections([.bakery,.review,.bottom]) + if let snapShot { + self.dataSource?.apply(snapShot) + } + } + + private func reloadBestList(bakery: [BestBakery] = [], review: [BestReview] = []) { + // 현재 스냅샷을 가져와서 기존의 아이템이 비어있을 때만 update + guard var snapShot = self.dataSource?.snapshot() else { return } + + let previousBakeryData = snapShot.itemIdentifiers(inSection: .bakery) + let previousReviewData = snapShot.itemIdentifiers(inSection: .review) + + if previousBakeryData.isEmpty { + let bakeryItems = bakery.map { Item.bakery($0) } + snapShot.appendItems(bakeryItems, toSection: .bakery) + } + + if previousReviewData.isEmpty { + let reviewItems = review.map { Item.review($0) } + snapShot.appendItems(reviewItems, toSection: .review) + } + + snapShot.appendItems([.bottom], toSection: .bottom) + + self.dataSource?.apply(snapShot, animatingDifferences: false) + } +} + +extension NewHomeViewController { + + private func bindCollectionViewEvents() { + let didSelectPublisher = CollectionViewPublisher(collectionView: collectionView, event: .didSelect) + didSelectPublisher + .sink { [weak self] indexPath in + self?.handleSelection(for: indexPath) + } + .store(in: &cancelBag) + } + + private func handleSelection(for indexPath: IndexPath) { + guard let (id, bakery) = getBakeryData(for: indexPath) else { return } + + self.navigateToDetailViewController(with: id) + self.logAnalytics(for: bakery) + } + + private func getBakeryData(for indexPath: IndexPath) -> (Int, String)? { + guard let item = dataSource?.itemIdentifier(for: indexPath) else { return nil } + + switch item { + case .bakery(let data): + return (data.overview.id, data.overview.name) + case .review(let data): + return (data.overview.id, data.overview.name) + default: + return nil + } + } + + private func navigateToDetailViewController(with id: Int) { + let nextViewController = BakeryDetailViewController() + nextViewController.bakeryID = id + navigationController?.isNavigationBarHidden = true + navigationController?.pushViewController(nextViewController, animated: true) + } + + private func logAnalytics(for bakery: String) { + AnalyticManager.log(event: .home(.clickRecommendStore(bakery: bakery))) + AnalyticManager.log(event: .detail(.viewDetailpageAt(source: AnalyticEventType.HOME.rawValue))) + } +} + +// MARK: - CollectionView Layout + +extension NewHomeViewController { + + private func layout() -> UICollectionViewCompositionalLayout { + return UICollectionViewCompositionalLayout(sectionProvider: { [weak self] sectionIndex, _ in + switch sectionIndex { + case 0: + return self?.bestSection(headerSize: 49) + case 1: + return self?.bestSection(headerSize: 25) + default: + return self?.bottomSection() + } + }) + } + + private func bestSection(headerSize: CGFloat) -> NSCollectionLayoutSection { + + let itemGroupSize = NSCollectionLayoutSize( + widthDimension: .absolute(convertByWidthRatio(192)), + heightDimension: .absolute(heightConsideringNotch(236)) + ) + let item = NSCollectionLayoutItem(layoutSize: itemGroupSize) + + let group = NSCollectionLayoutGroup.horizontal(layoutSize: itemGroupSize, subitems: [item]) + + let section = NSCollectionLayoutSection(group: group) + section.interGroupSpacing = 12 + section.orthogonalScrollingBehavior = .continuous + section.boundarySupplementaryItems = bestSectionHeader(to: headerSize) + section.contentInsets = NSDirectionalEdgeInsets(top: 24, + leading: 24, + bottom: 30, + trailing: 24) + + return section + } + + private func bottomSection() -> NSCollectionLayoutSection { + + let itemGroupSize = NSCollectionLayoutSize( + widthDimension: .fractionalWidth(1), + heightDimension: .absolute(72) + ) + let item = NSCollectionLayoutItem(layoutSize: itemGroupSize) + + let group = NSCollectionLayoutGroup.vertical( + layoutSize: itemGroupSize, + subitem: item, + count: 1 + ) + + let section = NSCollectionLayoutSection(group: group) + section.contentInsets = NSDirectionalEdgeInsets(top: 14, + leading: 24, + bottom: 30, + trailing: 24) + + return section + } + + private func bestSectionHeader(to size: CGFloat) -> [NSCollectionLayoutBoundarySupplementaryItem] { + let headerSize = NSCollectionLayoutSize( + widthDimension: .fractionalWidth(1), + heightDimension: .absolute(size) + ) + + let header = NSCollectionLayoutBoundarySupplementaryItem( + layoutSize: headerSize, + elementKind: UICollectionView.elementKindSectionHeader, + alignment: .top + ) + + return [header] + } +} diff --git a/GEON-PPANG-iOS/Presentation/Scene/Home/Home/ViewModel/HomeViewModel.swift b/GEON-PPANG-iOS/Presentation/Scene/Home/Home/ViewModel/HomeViewModel.swift new file mode 100644 index 00000000..19412625 --- /dev/null +++ b/GEON-PPANG-iOS/Presentation/Scene/Home/Home/ViewModel/HomeViewModel.swift @@ -0,0 +1,77 @@ +// +// HomeViewModel.swift +// GEON-PPANG-iOS +// +// Created by JEONGEUN KIM on 7/9/24. +// + +import Combine + +import Foundation + +final class HomeViewModel: ViewModelType { + + struct Input { + let viewDidLoad: AnyPublisher + } + + struct Output { + let bakery: AnyPublisher<[BestBakery], Never> + let review: AnyPublisher<[BestReview], Never> + } + + // MARK: - Property + + private let usecase: HomeUseCase + private var cancellable: Set = Set() + + private var bakerySubject = PassthroughSubject<[BestBakery], Never>() + private var reviewSubject = PassthroughSubject<[BestReview], Never>() + + init(usecase: HomeUseCase) { + self.usecase = usecase + } + + // MARK: - func + + func transform(_ input: Input) -> Output { + input.viewDidLoad + .sink { [weak self] in + self?.fetchData() + } + .store(in: &cancellable) + + return Output(bakery: bakerySubject.eraseToAnyPublisher(), + review: reviewSubject.eraseToAnyPublisher()) + } + + private func fetchData() { + Task { + do { + let bakeries = try await fetchBestBakery() + bakerySubject.send(bakeries) + } catch { + bakerySubject.send([]) + } + } + + Task { + do { + let reviews = try await fetchBestReview() + reviewSubject.send(reviews) + } catch { + reviewSubject.send([]) + } + } + } +} + +extension HomeViewModel { + private func fetchBestBakery() async throws -> [BestBakery] { + return try await self.usecase.fetchBestBakeries() + } + + private func fetchBestReview() async throws -> [BestReview] { + return try await self.usecase.fetchBestReviews() + } +} diff --git a/GEON-PPANG-iOS/Presentation/Utils/Combine/Publisher/CollectionViewPublisher.swift b/GEON-PPANG-iOS/Presentation/Utils/Combine/Publisher/CollectionViewPublisher.swift new file mode 100644 index 00000000..b9379e46 --- /dev/null +++ b/GEON-PPANG-iOS/Presentation/Utils/Combine/Publisher/CollectionViewPublisher.swift @@ -0,0 +1,28 @@ +// +// CollectionViewPublisher.swift +// GEON-PPANG-iOS +// +// Created by JEONGEUN KIM on 8/13/24. +// + +import Combine + +import UIKit + +struct CollectionViewPublisher: Publisher { + typealias Output = IndexPath + typealias Failure = Never + + let collectionView: UICollectionView + let event: Event + + func receive(subscriber: S) where S: Subscriber, S.Input == IndexPath, S.Failure == Never { + let subscription = CollectionViewSubscription( + subscriber: subscriber, + collectionView: collectionView, + event: event + ) + subscriber.receive(subscription: subscription) + } +} + diff --git a/GEON-PPANG-iOS/Presentation/Utils/Combine/Subscription/CollectionViewSubscription.swift b/GEON-PPANG-iOS/Presentation/Utils/Combine/Subscription/CollectionViewSubscription.swift new file mode 100644 index 00000000..fc53670e --- /dev/null +++ b/GEON-PPANG-iOS/Presentation/Utils/Combine/Subscription/CollectionViewSubscription.swift @@ -0,0 +1,69 @@ +// +// CollectionViewSubscription.swift +// GEON-PPANG-iOS +// +// Created by JEONGEUN KIM on 8/13/24. +// + +import Combine + +import UIKit + + +enum Event { + case didSelect + case didDeselect +} + +final class CollectionViewSubscription: Subscription where SubscriberType.Input == IndexPath { + private var subscriber: SubscriberType? + private weak var collectionView: UICollectionView? + private var delegateProxy: CollectionViewDelegateProxy? + + init(subscriber: SubscriberType, collectionView: UICollectionView, event: Event) { + self.subscriber = subscriber + self.collectionView = collectionView + + let delegateProxy = CollectionViewDelegateProxy( + event: event, + handler: { [weak self] indexPath in + _ = self?.subscriber?.receive(indexPath) + } + ) + + self.delegateProxy = delegateProxy + collectionView.delegate = delegateProxy + } + + func request(_ demand: Subscribers.Demand) { + // 요구 처리 로직 추가 가능 + } + + func cancel() { + subscriber = nil + delegateProxy = nil + } +} + + +private class CollectionViewDelegateProxy: NSObject, UICollectionViewDelegate { + private let event: Event + private let handler: (IndexPath) -> Void + + init(event: Event, handler: @escaping (IndexPath) -> Void) { + self.event = event + self.handler = handler + } + + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + if event == .didSelect { + handler(indexPath) + } + } + + func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) { + if event == .didDeselect { + handler(indexPath) + } + } +} diff --git a/GEON-PPANG-iOS/Presentation/Utils/Extension/UIViewController/UIViewController+LifeCyclePublisher.swift b/GEON-PPANG-iOS/Presentation/Utils/Extension/UIViewController/UIViewController+LifeCyclePublisher.swift new file mode 100644 index 00000000..9d87349c --- /dev/null +++ b/GEON-PPANG-iOS/Presentation/Utils/Extension/UIViewController/UIViewController+LifeCyclePublisher.swift @@ -0,0 +1,18 @@ +// +// UIViewController+.swift +// GEON-PPANG-iOS +// +// Created by JEONGEUN KIM on 7/22/24. +// + +import Combine +import UIKit + +extension UIViewController { + var viewDidLoadPublisher: AnyPublisher { + let selector = #selector(UIViewController.viewDidLoad) + return Just(selector) + .map { _ in Void() } + .eraseToAnyPublisher() + } +}