From 5757705b1201655b0e27f6d436b4cc7395e66809 Mon Sep 17 00:00:00 2001 From: Yu Sung Date: Fri, 26 Jun 2026 22:32:36 +0900 Subject: [PATCH] =?UTF-8?q?refactor(home):=20=EC=B6=94=EC=B2=9C=20?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EA=B3=84=EC=B8=B5=EC=9D=84=20?= =?UTF-8?q?=EC=A0=95=EB=A6=AC=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SodaLive.xcodeproj/project.pbxproj | 100 +++++---- .../V2/Main/Home/MainHomeViewModel.swift | 2 +- .../FollowRecommendedCreatorsRequest.swift | 0 .../MainHomeRecommendationResponse.swift | 45 ++-- .../Models/RecommendedActivityType.swift | 0 .../MainHomeRecommendationApi.swift} | 4 +- .../MainHomeRecommendationRepository.swift} | 4 +- .../plan-task.md | 206 ++++++++++++------ 8 files changed, 218 insertions(+), 143 deletions(-) rename SodaLive/Sources/V2/Main/Home/{ => Recommendation}/Models/FollowRecommendedCreatorsRequest.swift (100%) rename SodaLive/Sources/V2/Main/Home/{ => Recommendation}/Models/MainHomeRecommendationResponse.swift (68%) rename SodaLive/Sources/V2/Main/Home/{ => Recommendation}/Models/RecommendedActivityType.swift (100%) rename SodaLive/Sources/V2/Main/Home/{Repository/MainHomeApi.swift => Recommendation/Repository/MainHomeRecommendationApi.swift} (92%) rename SodaLive/Sources/V2/Main/Home/{Repository/MainHomeRepository.swift => Recommendation/Repository/MainHomeRecommendationRepository.swift} (78%) diff --git a/SodaLive.xcodeproj/project.pbxproj b/SodaLive.xcodeproj/project.pbxproj index bf8987e5..1a3f9851 100644 --- a/SodaLive.xcodeproj/project.pbxproj +++ b/SodaLive.xcodeproj/project.pbxproj @@ -8,7 +8,6 @@ /* Begin PBXBuildFile section */ 00C796627A6758D271215EC5 /* CreatorProfileItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26881ED30292E28D2E5F84CD /* CreatorProfileItem.swift */; }; - 040031FA8CF08BDE260ECB52 /* RecommendedActivityType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36E28A8DFC2D34DA99AE7209 /* RecommendedActivityType.swift */; }; 042BEE0C973700FB5F4540CD /* I18n.swift in Sources */ = {isa = PBXBuildFile; fileRef = 826D626B7714E5C28ED31074 /* I18n.swift */; }; 0470E68C6F302A131264E3AF /* ChannelDonationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DF2BFD2E09F509C44C92214 /* ChannelDonationViewModel.swift */; }; 109DD8670CD0F25C0F436455 /* V2vRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = E56C8151F551B091940F16C4 /* V2vRepository.swift */; }; @@ -16,7 +15,6 @@ 14A88B232976EAD54B876195 /* LanguageSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB63E7E31B75FF2E6F1C9073 /* LanguageSettingsView.swift */; }; 1621CE301E98653402C6F553 /* LanguageSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 716260E8214A329EE07CAAFA /* LanguageSettingsViewModel.swift */; }; 163579EB669F557DEA367556 /* AcceptLanguagePlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C076E80A2C92FEFFA5AB752 /* AcceptLanguagePlugin.swift */; }; - 164177B97BCE9CB0C1042E23 /* MainHomeRecommendationResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF693ADD4B6C386882D5DD53 /* MainHomeRecommendationResponse.swift */; }; 1690309E86928580115E94E2 /* CapsuleTabBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74990A0798E64A97237DD113 /* CapsuleTabBar.swift */; }; 176AE05194A71107EA461B7B /* AiCharacterCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = A575789542808C0C87AC6D82 /* AiCharacterCard.swift */; }; 1DD00C45C183A84D0CD5553F /* CreatorProfileItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26881ED30292E28D2E5F84CD /* CreatorProfileItem.swift */; }; @@ -192,6 +190,16 @@ 401A3B312FEE3E2D00A5BBA3 /* NotoSansJP-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 401A3B252FEE3E2D00A5BBA3 /* NotoSansJP-Regular.ttf */; }; 401A3B322FEE3E2D00A5BBA3 /* Phosphate-Solid.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 401A3B272FEE3E2D00A5BBA3 /* Phosphate-Solid.ttf */; }; 401A3B332FEE3E2D00A5BBA3 /* NotoSansJP-Light.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 401A3B232FEE3E2D00A5BBA3 /* NotoSansJP-Light.ttf */; }; + 401A3F9E2FEEAFA000A5BBA3 /* MainHomeRecommendationApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 401A3F9A2FEEAFA000A5BBA3 /* MainHomeRecommendationApi.swift */; }; + 401A3F9F2FEEAFA000A5BBA3 /* MainHomeRecommendationResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 401A3F972FEEAFA000A5BBA3 /* MainHomeRecommendationResponse.swift */; }; + 401A3FA02FEEAFA000A5BBA3 /* FollowRecommendedCreatorsRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 401A3F962FEEAFA000A5BBA3 /* FollowRecommendedCreatorsRequest.swift */; }; + 401A3FA12FEEAFA000A5BBA3 /* MainHomeRecommendationRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 401A3F9B2FEEAFA000A5BBA3 /* MainHomeRecommendationRepository.swift */; }; + 401A3FA22FEEAFA000A5BBA3 /* RecommendedActivityType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 401A3F982FEEAFA000A5BBA3 /* RecommendedActivityType.swift */; }; + 401A3FA32FEEAFA000A5BBA3 /* MainHomeRecommendationApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 401A3F9A2FEEAFA000A5BBA3 /* MainHomeRecommendationApi.swift */; }; + 401A3FA42FEEAFA000A5BBA3 /* MainHomeRecommendationResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 401A3F972FEEAFA000A5BBA3 /* MainHomeRecommendationResponse.swift */; }; + 401A3FA52FEEAFA000A5BBA3 /* FollowRecommendedCreatorsRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 401A3F962FEEAFA000A5BBA3 /* FollowRecommendedCreatorsRequest.swift */; }; + 401A3FA62FEEAFA000A5BBA3 /* MainHomeRecommendationRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 401A3F9B2FEEAFA000A5BBA3 /* MainHomeRecommendationRepository.swift */; }; + 401A3FA72FEEAFA000A5BBA3 /* RecommendedActivityType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 401A3F982FEEAFA000A5BBA3 /* RecommendedActivityType.swift */; }; 40235DED2E3797EB00B09A87 /* RecentContent+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40235DEC2E3797EB00B09A87 /* RecentContent+CoreDataClass.swift */; }; 40235DEE2E3797EB00B09A87 /* RecentContent+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40235DEC2E3797EB00B09A87 /* RecentContent+CoreDataClass.swift */; }; 40235DF02E37983D00B09A87 /* PersistenceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40235DEF2E37983D00B09A87 /* PersistenceController.swift */; }; @@ -1500,10 +1508,8 @@ 45436503B5AA5530DBBD3481 /* KakaoSDKCommon in Frameworks */ = {isa = PBXBuildFile; productRef = 00EE224A913C7F8ADC48C15E /* KakaoSDKCommon */; }; 4572B5004B70CED10BDDB4C3 /* FollowAllButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 348933CFF7DC1B9DB6E5D1A1 /* FollowAllButton.swift */; }; 4A22DB69D003B207E35CFF51 /* FontModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25D89184D67BD63E46E90281 /* FontModifier.swift */; }; - 4A5CAD092A52205860D9AF5B /* MainHomeApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1BD0F6AD90B449C2FE021496 /* MainHomeApi.swift */; }; 4A951EA6E07524A0B650F4BF /* DateParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 375C41DF9D62AF5B9C3C1FAC /* DateParser.swift */; }; 4D1649A808884B53416FCC0E /* UpdateContentPreferenceResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4AAC943F77B6FE089696909 /* UpdateContentPreferenceResponse.swift */; }; - 4ECF7D9BF3A958E9FDE3F5E0 /* RecommendedActivityType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36E28A8DFC2D34DA99AE7209 /* RecommendedActivityType.swift */; }; 508015CDE6EB31076EBF695A /* CommunityPostCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE82912F2F111593BE42E33C /* CommunityPostCard.swift */; }; 50958256B935286787BB9011 /* V2vModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C4AF495E30F2DB00C2FCB36 /* V2vModels.swift */; }; 53E20A90F5D084820C7166E2 /* ChannelDonationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DF2BFD2E09F509C44C92214 /* ChannelDonationViewModel.swift */; }; @@ -1534,7 +1540,6 @@ 843C3C5366C9D9DEE37878BF /* ChannelDonationItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A7650B281CE20A6F29F6253 /* ChannelDonationItemView.swift */; }; 85934EA071477BC71BFCDF5E /* Pods_SodaLive.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 19E2FD56B42EBD353B9D2016 /* Pods_SodaLive.framework */; }; 895286621DBE8EC8C825A9B5 /* GetPushNotificationCategoryResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = B54EBFBCC66E5AC79197E1FC /* GetPushNotificationCategoryResponse.swift */; }; - 89816791946CE71177F4BC2A /* FollowRecommendedCreatorsRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 896A4A8EF6F7D15071F74D6D /* FollowRecommendedCreatorsRequest.swift */; }; 89AF41E6A46CD84BA469F272 /* YandexAdSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = F064EC9D5711B91979F45C05 /* YandexAdSupport.swift */; }; 89BA3654EB2D35AC48F99472 /* LanguageService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7132AA3D131A8DF7B8D783BE /* LanguageService.swift */; }; 8A3E98E8F5E3862EAED3B5F9 /* FontModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25D89184D67BD63E46E90281 /* FontModifier.swift */; }; @@ -1567,7 +1572,6 @@ AEFFD4DC842837ACF98BE697 /* PostChannelDonationRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BBDC3ECBBE049EAA4057D3A /* PostChannelDonationRequest.swift */; }; B11681AC02BF51C6E2FA6FF3 /* MainTabBarButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8F49E8BB9BACFCA8878B70D /* MainTabBarButton.swift */; }; B3AF650899B8FC84E0C0CDA0 /* MainPlaceholderTabView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7900EA9FD1150BABE64CF1B3 /* MainPlaceholderTabView.swift */; }; - B5E5018F6144BF60C528BB8C /* MainHomeApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1BD0F6AD90B449C2FE021496 /* MainHomeApi.swift */; }; B5E9FA82CF4238F640702D4A /* CreatorDetailDialogView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7406AE69F7667FAA2E6DD23 /* CreatorDetailDialogView.swift */; }; BA2B76FDCAF81D15E2D4E258 /* MainHomeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76506E56D017236824FE9B8F /* MainHomeViewModel.swift */; }; BA511503C1E037162697D771 /* DonationRankingPeriod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44A42B63A1021DD9F8AA3F30 /* DonationRankingPeriod.swift */; }; @@ -1588,20 +1592,16 @@ CAE33B321E81B312063B11E0 /* MainViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5420BBA14C00EC0D927BDAB /* MainViewModel.swift */; }; D1C65856889B16DFDB69A004 /* PushNotificationRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FDDF6BBD0192819B1F5B3FE /* PushNotificationRepository.swift */; }; D2A268C7346E84E6B455E25A /* Pods_SodaLive_dev.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6F4E5BB7961B7E6BB68C3379 /* Pods_SodaLive_dev.framework */; }; - D81E265AB5DD18BF2AE18F22 /* FollowRecommendedCreatorsRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 896A4A8EF6F7D15071F74D6D /* FollowRecommendedCreatorsRequest.swift */; }; DD202E48D61F1CE8108AD164 /* LineSDK in Frameworks */ = {isa = PBXBuildFile; productRef = 6DBA0C325ACEC3FCDB22490D /* LineSDK */; }; DD2B6C23087BCA5591CD4D68 /* UpdateContentPreferenceResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4AAC943F77B6FE089696909 /* UpdateContentPreferenceResponse.swift */; }; - DF27817A4DE429C45EA028A6 /* MainHomeRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E934A5940340AE1E8A5FFFC /* MainHomeRepository.swift */; }; E260227D30A9E44894BE3B27 /* V2vApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 613FD451377C66254773CF47 /* V2vApi.swift */; }; E2AA285902E7E95C792E164E /* I18n.swift in Sources */ = {isa = PBXBuildFile; fileRef = 826D626B7714E5C28ED31074 /* I18n.swift */; }; E2C133B589E2DA7596B3BAEF /* ChannelDonationAllView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 872F4F3506F36E0EAAE312ED /* ChannelDonationAllView.swift */; }; E316EBA3E7125AFDA6CBE68D /* MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72CD656CAEE376701F2B563E /* MainView.swift */; }; - E45131345D75F0F038D138D1 /* MainHomeRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E934A5940340AE1E8A5FFFC /* MainHomeRepository.swift */; }; E71A5D3008A7706B4F425CA6 /* DefaultTitleBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7F1421DBAB56E2F6891625E /* DefaultTitleBar.swift */; }; F0F1F71CCA586A0CF5194FAB /* ChannelDonationItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A7650B281CE20A6F29F6253 /* ChannelDonationItemView.swift */; }; F357D803F7BEC1DBF3F76493 /* PushNotificationListItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FA7A276BF4A47206ED702A7 /* PushNotificationListItemView.swift */; }; F6DA2AAF5186543C8486A3F9 /* UserProfileChannelDonationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 214C32BF44DF384FD6925E0B /* UserProfileChannelDonationView.swift */; }; - F75DBAD90E73E65C084DB474 /* MainHomeRecommendationResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF693ADD4B6C386882D5DD53 /* MainHomeRecommendationResponse.swift */; }; F8DB0226F6F44A7DBEF8B4CF /* FollowButtonImageAsset.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DC033E531A9420363D017CE /* FollowButtonImageAsset.swift */; }; FA5F18B3EE4CBD02D3807BD1 /* MainViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5420BBA14C00EC0D927BDAB /* MainViewModel.swift */; }; FB404343AB7668FB7D020C02 /* PushNotificationRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FDDF6BBD0192819B1F5B3FE /* PushNotificationRepository.swift */; }; @@ -1617,7 +1617,6 @@ 189350EA101CF12EEBA6EC1D /* MainTabBarView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = MainTabBarView.swift; sourceTree = ""; }; 19E2FD56B42EBD353B9D2016 /* Pods_SodaLive.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SodaLive.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 1BB17B6A2E9D9CAEA3B185D8 /* PushNotificationListViewModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PushNotificationListViewModel.swift; sourceTree = ""; }; - 1BD0F6AD90B449C2FE021496 /* MainHomeApi.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = MainHomeApi.swift; sourceTree = ""; }; 1DF2BFD2E09F509C44C92214 /* ChannelDonationViewModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ChannelDonationViewModel.swift; sourceTree = ""; }; 1FDDF6BBD0192819B1F5B3FE /* PushNotificationRepository.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PushNotificationRepository.swift; sourceTree = ""; }; 214C32BF44DF384FD6925E0B /* UserProfileChannelDonationView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = UserProfileChannelDonationView.swift; sourceTree = ""; }; @@ -1625,7 +1624,6 @@ 25D89184D67BD63E46E90281 /* FontModifier.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = FontModifier.swift; sourceTree = ""; }; 26881ED30292E28D2E5F84CD /* CreatorProfileItem.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CreatorProfileItem.swift; sourceTree = ""; }; 348933CFF7DC1B9DB6E5D1A1 /* FollowAllButton.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = FollowAllButton.swift; sourceTree = ""; }; - 36E28A8DFC2D34DA99AE7209 /* RecommendedActivityType.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RecommendedActivityType.swift; sourceTree = ""; }; 375C41DF9D62AF5B9C3C1FAC /* DateParser.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DateParser.swift; sourceTree = ""; }; 3FA7A276BF4A47206ED702A7 /* PushNotificationListItemView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PushNotificationListItemView.swift; sourceTree = ""; }; 4004671E2CDE08E6004DB9B5 /* PurchaseOption.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PurchaseOption.swift; sourceTree = ""; }; @@ -1706,6 +1704,11 @@ 401A3B252FEE3E2D00A5BBA3 /* NotoSansJP-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "NotoSansJP-Regular.ttf"; sourceTree = ""; }; 401A3B262FEE3E2D00A5BBA3 /* Pattaya-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Pattaya-Regular.ttf"; sourceTree = ""; }; 401A3B272FEE3E2D00A5BBA3 /* Phosphate-Solid.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Phosphate-Solid.ttf"; sourceTree = ""; }; + 401A3F962FEEAFA000A5BBA3 /* FollowRecommendedCreatorsRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowRecommendedCreatorsRequest.swift; sourceTree = ""; }; + 401A3F972FEEAFA000A5BBA3 /* MainHomeRecommendationResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainHomeRecommendationResponse.swift; sourceTree = ""; }; + 401A3F982FEEAFA000A5BBA3 /* RecommendedActivityType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecommendedActivityType.swift; sourceTree = ""; }; + 401A3F9A2FEEAFA000A5BBA3 /* MainHomeRecommendationApi.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainHomeRecommendationApi.swift; sourceTree = ""; }; + 401A3F9B2FEEAFA000A5BBA3 /* MainHomeRecommendationRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainHomeRecommendationRepository.swift; sourceTree = ""; }; 40235DEC2E3797EB00B09A87 /* RecentContent+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RecentContent+CoreDataClass.swift"; sourceTree = ""; }; 40235DEF2E37983D00B09A87 /* PersistenceController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersistenceController.swift; sourceTree = ""; }; 40235DF22E37987300B09A87 /* RecentContentService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecentContentService.swift; sourceTree = ""; }; @@ -2370,10 +2373,8 @@ 7900EA9FD1150BABE64CF1B3 /* MainPlaceholderTabView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = MainPlaceholderTabView.swift; sourceTree = ""; }; 7C21D69F717F8D80644F6AEE /* PushNotificationListView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PushNotificationListView.swift; sourceTree = ""; }; 7C22ACAC456B3BCB2D9C568D /* AcceptLanguageDebugLogger.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AcceptLanguageDebugLogger.swift; sourceTree = ""; }; - 7E934A5940340AE1E8A5FFFC /* MainHomeRepository.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = MainHomeRepository.swift; sourceTree = ""; }; 826D626B7714E5C28ED31074 /* I18n.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = I18n.swift; sourceTree = ""; }; 872F4F3506F36E0EAAE312ED /* ChannelDonationAllView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ChannelDonationAllView.swift; sourceTree = ""; }; - 896A4A8EF6F7D15071F74D6D /* FollowRecommendedCreatorsRequest.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = FollowRecommendedCreatorsRequest.swift; sourceTree = ""; }; 8A7650B281CE20A6F29F6253 /* ChannelDonationItemView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ChannelDonationItemView.swift; sourceTree = ""; }; 8E138A248EE3AFAFAF01E245 /* EntityInfo-SodaLive.generated.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = "EntityInfo-SodaLive.generated.swift"; sourceTree = ""; }; 959B90DBB4BB5911AB10C442 /* Pods-SodaLive-dev.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SodaLive-dev.debug.xcconfig"; path = "Target Support Files/Pods-SodaLive-dev/Pods-SodaLive-dev.debug.xcconfig"; sourceTree = ""; }; @@ -2401,7 +2402,6 @@ E56C8151F551B091940F16C4 /* V2vRepository.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = V2vRepository.swift; sourceTree = ""; }; E61608194B084224DB6B78B5 /* TextTabBar.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = TextTabBar.swift; sourceTree = ""; }; E92246F5C1254CBED72AD9AD /* AudioContentCard.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AudioContentCard.swift; sourceTree = ""; }; - EF693ADD4B6C386882D5DD53 /* MainHomeRecommendationResponse.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = MainHomeRecommendationResponse.swift; sourceTree = ""; }; F064EC9D5711B91979F45C05 /* YandexAdSupport.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = YandexAdSupport.swift; sourceTree = ""; }; F23D0834690CFE9FBDC10510 /* GetPushNotificationListResponse.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = GetPushNotificationListResponse.swift; sourceTree = ""; }; F26CB443DA0ED4C45A18B258 /* PushNotificationApi.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PushNotificationApi.swift; sourceTree = ""; }; @@ -2515,23 +2515,12 @@ 1BA796BAC773D01D80F8F9CE /* Home */ = { isa = PBXGroup; children = ( + 401A3F9D2FEEAFA000A5BBA3 /* Recommendation */, 76506E56D017236824FE9B8F /* MainHomeViewModel.swift */, - 235E44A77529D0BA4F758812 /* Models */, - B4BF53CF51C8A4AE001B2A49 /* Repository */, ); path = Home; sourceTree = ""; }; - 235E44A77529D0BA4F758812 /* Models */ = { - isa = PBXGroup; - children = ( - 896A4A8EF6F7D15071F74D6D /* FollowRecommendedCreatorsRequest.swift */, - EF693ADD4B6C386882D5DD53 /* MainHomeRecommendationResponse.swift */, - 36E28A8DFC2D34DA99AE7209 /* RecommendedActivityType.swift */, - ); - path = Models; - sourceTree = ""; - }; 2D9C7222B946512FD4C4B499 /* Banner */ = { isa = PBXGroup; children = ( @@ -2667,6 +2656,34 @@ path = Like; sourceTree = ""; }; + 401A3F992FEEAFA000A5BBA3 /* Models */ = { + isa = PBXGroup; + children = ( + 401A3F962FEEAFA000A5BBA3 /* FollowRecommendedCreatorsRequest.swift */, + 401A3F972FEEAFA000A5BBA3 /* MainHomeRecommendationResponse.swift */, + 401A3F982FEEAFA000A5BBA3 /* RecommendedActivityType.swift */, + ); + path = Models; + sourceTree = ""; + }; + 401A3F9C2FEEAFA000A5BBA3 /* Repository */ = { + isa = PBXGroup; + children = ( + 401A3F9A2FEEAFA000A5BBA3 /* MainHomeRecommendationApi.swift */, + 401A3F9B2FEEAFA000A5BBA3 /* MainHomeRecommendationRepository.swift */, + ); + path = Repository; + sourceTree = ""; + }; + 401A3F9D2FEEAFA000A5BBA3 /* Recommendation */ = { + isa = PBXGroup; + children = ( + 401A3F992FEEAFA000A5BBA3 /* Models */, + 401A3F9C2FEEAFA000A5BBA3 /* Repository */, + ); + path = Recommendation; + sourceTree = ""; + }; 40235DEA2E3797B000B09A87 /* Recent */ = { isa = PBXGroup; children = ( @@ -4797,15 +4814,6 @@ path = generated; sourceTree = ""; }; - B4BF53CF51C8A4AE001B2A49 /* Repository */ = { - isa = PBXGroup; - children = ( - 1BD0F6AD90B449C2FE021496 /* MainHomeApi.swift */, - 7E934A5940340AE1E8A5FFFC /* MainHomeRepository.swift */, - ); - path = Repository; - sourceTree = ""; - }; B67DE694AAC1DD7D0A3C5761 /* Services */ = { isa = PBXGroup; children = ( @@ -5261,6 +5269,11 @@ 40B37EEC2A90A3CF000DE782 /* ProfileUpdateViewModel.swift in Sources */, 407EF21A2B4532BF00D7118D /* CanCouponViewModel.swift in Sources */, 400EA78F2A89F119004992CF /* LiveCancelDialog.swift in Sources */, + 401A3F9E2FEEAFA000A5BBA3 /* MainHomeRecommendationApi.swift in Sources */, + 401A3F9F2FEEAFA000A5BBA3 /* MainHomeRecommendationResponse.swift in Sources */, + 401A3FA02FEEAFA000A5BBA3 /* FollowRecommendedCreatorsRequest.swift in Sources */, + 401A3FA12FEEAFA000A5BBA3 /* MainHomeRecommendationRepository.swift in Sources */, + 401A3FA22FEEAFA000A5BBA3 /* RecommendedActivityType.swift in Sources */, 409143302A85652F00EEBFB1 /* CanChargeView.swift in Sources */, 40975CA32B572298004E2F1B /* LiveRoomOverlayStrokeTextButton.swift in Sources */, 40235DED2E3797EB00B09A87 /* RecentContent+CoreDataClass.swift in Sources */, @@ -6003,11 +6016,6 @@ 38D73A7B6BFB9A984834984B /* TextTabBar.swift in Sources */, 372837FE9857C16B6B5087FF /* TitleBar.swift in Sources */, BA2B76FDCAF81D15E2D4E258 /* MainHomeViewModel.swift in Sources */, - 89816791946CE71177F4BC2A /* FollowRecommendedCreatorsRequest.swift in Sources */, - 164177B97BCE9CB0C1042E23 /* MainHomeRecommendationResponse.swift in Sources */, - 040031FA8CF08BDE260ECB52 /* RecommendedActivityType.swift in Sources */, - 4A5CAD092A52205860D9AF5B /* MainHomeApi.swift in Sources */, - E45131345D75F0F038D138D1 /* MainHomeRepository.swift in Sources */, B3AF650899B8FC84E0C0CDA0 /* MainPlaceholderTabView.swift in Sources */, CA65AD0D677B4A8E9CAC0E15 /* MainTab.swift in Sources */, A0643C829BE7377DC09112CB /* MainTabBarButton.swift in Sources */, @@ -6030,6 +6038,11 @@ 40B37EED2A90A3CF000DE782 /* ProfileUpdateViewModel.swift in Sources */, 407EF21B2B4532BF00D7118D /* CanCouponViewModel.swift in Sources */, 400EA7902A89F119004992CF /* LiveCancelDialog.swift in Sources */, + 401A3FA32FEEAFA000A5BBA3 /* MainHomeRecommendationApi.swift in Sources */, + 401A3FA42FEEAFA000A5BBA3 /* MainHomeRecommendationResponse.swift in Sources */, + 401A3FA52FEEAFA000A5BBA3 /* FollowRecommendedCreatorsRequest.swift in Sources */, + 401A3FA62FEEAFA000A5BBA3 /* MainHomeRecommendationRepository.swift in Sources */, + 401A3FA72FEEAFA000A5BBA3 /* RecommendedActivityType.swift in Sources */, 409143312A85652F00EEBFB1 /* CanChargeView.swift in Sources */, 40975CA42B572298004E2F1B /* LiveRoomOverlayStrokeTextButton.swift in Sources */, 40235DEE2E3797EB00B09A87 /* RecentContent+CoreDataClass.swift in Sources */, @@ -6772,11 +6785,6 @@ 7A1DDAAE227BF176BDEF1CB9 /* TextTabBar.swift in Sources */, 5EEE98FE0EC4039761C604EE /* TitleBar.swift in Sources */, A4EB669DFFFB64681E045EF3 /* MainHomeViewModel.swift in Sources */, - D81E265AB5DD18BF2AE18F22 /* FollowRecommendedCreatorsRequest.swift in Sources */, - F75DBAD90E73E65C084DB474 /* MainHomeRecommendationResponse.swift in Sources */, - 4ECF7D9BF3A958E9FDE3F5E0 /* RecommendedActivityType.swift in Sources */, - B5E5018F6144BF60C528BB8C /* MainHomeApi.swift in Sources */, - DF27817A4DE429C45EA028A6 /* MainHomeRepository.swift in Sources */, ACD71056E97E13E48941ABBB /* MainPlaceholderTabView.swift in Sources */, 33BA3AB731849284E62AE04B /* MainTab.swift in Sources */, B11681AC02BF51C6E2FA6FF3 /* MainTabBarButton.swift in Sources */, diff --git a/SodaLive/Sources/V2/Main/Home/MainHomeViewModel.swift b/SodaLive/Sources/V2/Main/Home/MainHomeViewModel.swift index 5c7c31cd..2dd29b1f 100644 --- a/SodaLive/Sources/V2/Main/Home/MainHomeViewModel.swift +++ b/SodaLive/Sources/V2/Main/Home/MainHomeViewModel.swift @@ -2,7 +2,7 @@ import Foundation import Combine final class MainHomeViewModel: ObservableObject { - private let repository = MainHomeRepository() + private let repository = MainHomeRecommendationRepository() private var subscription = Set() @Published var isLoading = false diff --git a/SodaLive/Sources/V2/Main/Home/Models/FollowRecommendedCreatorsRequest.swift b/SodaLive/Sources/V2/Main/Home/Recommendation/Models/FollowRecommendedCreatorsRequest.swift similarity index 100% rename from SodaLive/Sources/V2/Main/Home/Models/FollowRecommendedCreatorsRequest.swift rename to SodaLive/Sources/V2/Main/Home/Recommendation/Models/FollowRecommendedCreatorsRequest.swift diff --git a/SodaLive/Sources/V2/Main/Home/Models/MainHomeRecommendationResponse.swift b/SodaLive/Sources/V2/Main/Home/Recommendation/Models/MainHomeRecommendationResponse.swift similarity index 68% rename from SodaLive/Sources/V2/Main/Home/Models/MainHomeRecommendationResponse.swift rename to SodaLive/Sources/V2/Main/Home/Recommendation/Models/MainHomeRecommendationResponse.swift index 3c62bcff..fc00ec55 100644 --- a/SodaLive/Sources/V2/Main/Home/Models/MainHomeRecommendationResponse.swift +++ b/SodaLive/Sources/V2/Main/Home/Recommendation/Models/MainHomeRecommendationResponse.swift @@ -2,7 +2,7 @@ import Foundation struct HomeRecommendationResponse: Decodable { let lives: [HomeLiveItem] - let banners: [HomeBannerItem] + let banners: [RecommendationBannerResponse] let recentlyActiveCreators: [HomeActiveCreatorItem] let recentDebutCreators: [HomeCreatorItem] let firstAudioContents: [HomeFirstAudioContentItem] @@ -13,19 +13,12 @@ struct HomeRecommendationResponse: Decodable { } struct HomeLiveItem: Decodable, Hashable { - let liveRoomId: Int? let roomId: Int? - let creatorId: Int? let creatorNickname: String? - let title: String? - let profileImageUrl: String? - let thumbnailUrl: String? - let imageUrl: String? - let beginDateTime: String? - let participantCount: Int? + let creatorProfileImage: String? } -struct HomeBannerItem: Decodable, Hashable { +struct RecommendationBannerResponse: Decodable, Hashable { let bannerId: Int? let type: String? let title: String? @@ -37,49 +30,42 @@ struct HomeBannerItem: Decodable, Hashable { } struct HomeActiveCreatorItem: Decodable, Hashable { - let creatorId: Int? let creatorNickname: String? - let profileImageUrl: String? + let creatorProfileImage: String? let activityType: RecommendedActivityType? let activityAt: String? + let targetId: Int? } struct HomeCreatorItem: Decodable, Hashable { let creatorId: Int? let creatorNickname: String? - let nickname: String? - let profileImageUrl: String? - let imageUrl: String? - let description: String? - let followerCount: Int? - let cheerCount: Int? - let debutDate: String? + let creatorProfileImage: String? } struct HomeFirstAudioContentItem: Decodable, Hashable { let contentId: Int? let creatorId: Int? let creatorNickname: String? + let creatorProfileImage: String? let title: String? - let imageUrl: String? - let coverImageUrl: String? - let releaseDate: String? + let price: Int? + let coverImage: String? + let isPointAvailable: Bool? } struct HomeAiCharacterItem: Decodable, Hashable { let characterId: Int? + let creatorId: Int? let name: String? let description: String? - let profileImageUrl: String? - let imageUrl: String? - let chatCount: Int? - let originalTitle: String? + let profileImage: String? + let totalChatCount: Int? + let originalWorkTitle: String? } struct HomeGenreCreatorGroupItem: Decodable, Hashable { - let genreId: Int? let genreName: String? - let title: String? let creators: [HomeCreatorItem]? } @@ -87,9 +73,10 @@ struct HomePopularCommunityPostItem: Decodable, Hashable { let postId: Int? let creatorId: Int? let creatorNickname: String? - let creatorProfileImageUrl: String? + let creatorProfileImage: String? let content: String? let imageUrl: String? + let audioUrl: String? let price: Int? let existOrdered: Bool? let likeCount: Int? diff --git a/SodaLive/Sources/V2/Main/Home/Models/RecommendedActivityType.swift b/SodaLive/Sources/V2/Main/Home/Recommendation/Models/RecommendedActivityType.swift similarity index 100% rename from SodaLive/Sources/V2/Main/Home/Models/RecommendedActivityType.swift rename to SodaLive/Sources/V2/Main/Home/Recommendation/Models/RecommendedActivityType.swift diff --git a/SodaLive/Sources/V2/Main/Home/Repository/MainHomeApi.swift b/SodaLive/Sources/V2/Main/Home/Recommendation/Repository/MainHomeRecommendationApi.swift similarity index 92% rename from SodaLive/Sources/V2/Main/Home/Repository/MainHomeApi.swift rename to SodaLive/Sources/V2/Main/Home/Recommendation/Repository/MainHomeRecommendationApi.swift index bf83494b..43d24906 100644 --- a/SodaLive/Sources/V2/Main/Home/Repository/MainHomeApi.swift +++ b/SodaLive/Sources/V2/Main/Home/Recommendation/Repository/MainHomeRecommendationApi.swift @@ -1,12 +1,12 @@ import Foundation import Moya -enum MainHomeApi { +enum MainHomeRecommendationApi { case getRecommendations case followRecommendedCreators(request: FollowRecommendedCreatorsRequest) } -extension MainHomeApi: TargetType { +extension MainHomeRecommendationApi: TargetType { var baseURL: URL { return URL(string: BASE_URL)! } diff --git a/SodaLive/Sources/V2/Main/Home/Repository/MainHomeRepository.swift b/SodaLive/Sources/V2/Main/Home/Recommendation/Repository/MainHomeRecommendationRepository.swift similarity index 78% rename from SodaLive/Sources/V2/Main/Home/Repository/MainHomeRepository.swift rename to SodaLive/Sources/V2/Main/Home/Recommendation/Repository/MainHomeRecommendationRepository.swift index f0135154..e3ae2f53 100644 --- a/SodaLive/Sources/V2/Main/Home/Repository/MainHomeRepository.swift +++ b/SodaLive/Sources/V2/Main/Home/Recommendation/Repository/MainHomeRecommendationRepository.swift @@ -3,8 +3,8 @@ import CombineMoya import Combine import Moya -final class MainHomeRepository { - private let api = MoyaProvider() +final class MainHomeRecommendationRepository { + private let api = MoyaProvider() func getRecommendations() -> AnyPublisher { return api.requestPublisher(.getRecommendations) diff --git a/docs/20260602_메인_홈_추천_UI_API_연동/plan-task.md b/docs/20260602_메인_홈_추천_UI_API_연동/plan-task.md index 3b430c09..d4960dbb 100644 --- a/docs/20260602_메인_홈_추천_UI_API_연동/plan-task.md +++ b/docs/20260602_메인_홈_추천_UI_API_연동/plan-task.md @@ -60,7 +60,7 @@ ### Phase 1: 기준 스펙과 기존 구현 점검 -- [ ] **Task 1.1: 문서/코드 기준점 확인** +- [x] **Task 1.1: 문서/코드 기준점 확인** - 대상 파일: - 확인: `docs/20260602_메인_홈_추천_UI_API_연동/prd.md` - 확인: `SodaLive/Sources/V2/Main/Home/**` @@ -74,7 +74,7 @@ - 실행 명령: `find SodaLive/Sources/V2/Main/Home SodaLive/Sources/V2/Component -maxdepth 4 -type f | sort` - 기대 결과: 현재 생성된 V2 홈/공용 컴포넌트 목록을 확인할 수 있다. -- [ ] **Task 1.2: 제외 범위 점검** +- [x] **Task 1.2: 제외 범위 점검** - 대상 파일: - 확인: `SodaLive/Sources/V2/Main/Home/**` - 확인: `SodaLive/Sources/V2/Component/**` @@ -87,7 +87,7 @@ ### Phase 2: 신규 API와 응답 모델 -- [ ] **Task 2.1: 추천 API endpoint 확인/보완** +- [x] **Task 2.1: 추천 API endpoint 확인/보완** - 대상 파일: - 생성/보완: `SodaLive/Sources/V2/Main/Home/Recommendation/Repository/MainHomeRecommendationApi.swift` - 생성/보완: `SodaLive/Sources/V2/Main/Home/Recommendation/Repository/MainHomeRecommendationRepository.swift` @@ -103,7 +103,7 @@ - 실행 명령: `rg "/api/v2/home/recommendations|getRecommendations" SodaLive/Sources/V2/Main/Home/Recommendation/Repository SodaLive/Sources/Home` - 기대 결과: 신규 경로는 `SodaLive/Sources/V2/Main/Home/Recommendation/Repository`에서만 검색된다. -- [ ] **Task 2.2: 신규 Response 모델 보완** +- [x] **Task 2.2: 신규 Response 모델 보완** - 대상 파일: - 생성/보완: `SodaLive/Sources/V2/Main/Home/Recommendation/Models/MainHomeRecommendationResponse.swift` - 이동/정리: `SodaLive/Sources/V2/Main/Home/Models/MainHomeRecommendationResponse.swift` @@ -119,7 +119,7 @@ ### Phase 3: 활동 타입과 I18n -- [ ] **Task 3.1: RecommendedActivityType 변환 보완** +- [x] **Task 3.1: RecommendedActivityType 변환 보완** - 대상 파일: - 생성/보완: `SodaLive/Sources/V2/Main/Home/Recommendation/Models/RecommendedActivityType.swift` - 이동/정리: `SodaLive/Sources/V2/Main/Home/Models/RecommendedActivityType.swift` @@ -133,47 +133,25 @@ - 실행 명령: `rg "LIVE_REPLAY|activityLive|activityAudio|activityCommunity|ライブ|オーディオ|コミュニティ" SodaLive/Sources/V2/Main/Home/Recommendation/Models/RecommendedActivityType.swift SodaLive/Sources/I18n/I18n.swift` - 기대 결과: enum 변환과 I18n 문구가 확인된다. -### Phase 4: MainHomeView 탭 shell과 탭별 루트 View +### Phase 4: 추천 데이터 상태 계층 정리 -- [ ] **Task 4.1: MainHomeView 탭 shell 작성/보완** +- [ ] **Task 4.1: 추천 ViewModel 데이터 로딩 계층 정리** - 대상 파일: - - 생성/보완: `SodaLive/Sources/V2/Main/Home/MainHomeView.swift` - - 확인: `SodaLive/Sources/V2/Component/HomeTitleBar.swift` - - 작업 내용: - - `MainHomeView`는 `HomeTitleBar`, 추천/랭킹/팔로잉 상단 탭, 선택된 탭 콘텐츠 조합만 담당한다. - - 추천 탭 섹션 조립과 추천 API 호출을 `MainHomeView`에 직접 넣지 않는다. - - 하단 `MainTabBarView`는 기존 `MainView` 구조에서 유지하고 `MainHomeView` 내부에 중복 생성하지 않는다. - - 검증 기준: - - 실행 명령: `rg "struct MainHomeView|HomeTitleBar|MainHomeRecommendationView|MainHomeRankingView|MainHomeFollowingView|MainTabBarView|fetchRecommendations" SodaLive/Sources/V2/Main/Home/MainHomeView.swift SodaLive/Sources/V2/Main/MainView.swift` - - 기대 결과: `MainHomeView`는 탭별 루트 View를 조합하고, `fetchRecommendations`를 직접 호출하지 않으며, 탭바 중복 생성은 없어야 한다. - -- [ ] **Task 4.2: 추천/랭킹/팔로잉 하위 폴더와 루트 View 준비** - - 대상 파일: - - 생성/보완: `SodaLive/Sources/V2/Main/Home/Recommendation/MainHomeRecommendationView.swift` - 생성/보완: `SodaLive/Sources/V2/Main/Home/Recommendation/MainHomeRecommendationViewModel.swift` - - 생성/보완: `SodaLive/Sources/V2/Main/Home/Ranking/MainHomeRankingView.swift` - - 생성/보완: `SodaLive/Sources/V2/Main/Home/Following/MainHomeFollowingView.swift` - 이동/정리: `SodaLive/Sources/V2/Main/Home/MainHomeViewModel.swift` + - 확인/보완: `SodaLive/Sources/V2/Main/Home/Recommendation/Repository/MainHomeRecommendationRepository.swift` - 작업 내용: - - 추천 탭 콘텐츠 조립은 `MainHomeRecommendationView`에서 담당한다. - 추천 API 로딩/에러/섹션 상태는 `MainHomeRecommendationViewModel`에서 담당한다. - - 랭킹/팔로잉 탭은 이번 추천 API 범위 밖이므로 placeholder 또는 최소 루트 View만 준비하고 추천 섹션 코드를 넣지 않는다. - 기존 `MainHomeViewModel`이 추천 전용 상태만 가진다면 `MainHomeRecommendationViewModel`로 이동/이름 변경한다. + - 추천 API 응답 데이터가 섹션별 View에서 바로 사용할 수 있는 상태로 노출되는지 확인한다. + - UI 섹션 구현은 이 Phase에서 진행하지 않는다. - 검증 기준: - - 실행 명령: `find SodaLive/Sources/V2/Main/Home -maxdepth 3 -type f | sort` - - 기대 결과: `Recommendation`, `Ranking`, `Following` 하위 폴더의 루트 View 파일이 확인된다. + - 실행 명령: `rg "final class MainHomeRecommendationViewModel|fetchRecommendations|HomeRecommendationResponse|MainHomeRecommendationRepository|isLoading|errorMessage" SodaLive/Sources/V2/Main/Home` + - 기대 결과: 추천 API 로딩/에러/응답 상태가 `MainHomeRecommendationViewModel` 중심으로 정리되어야 한다. -- [ ] **Task 4.3: MainView 홈 탭 연결** - - 대상 파일: - - 수정: `SodaLive/Sources/V2/Main/MainView.swift` - - 작업 내용: - - `.home` 분기에서 placeholder 대신 `MainHomeView()`를 표시한다. - - 다른 탭 분기와 기존 이벤트 팝업/회원 정보 초기화 흐름은 변경하지 않는다. - - 검증 기준: - - 실행 명령: `rg "case \\.home|MainHomeView|MainPlaceholderTabView" SodaLive/Sources/V2/Main/MainView.swift` - - 기대 결과: `.home`은 `MainHomeView()`를 표시하고, 다른 탭은 기존 동작을 유지한다. +### Phase 5: 추천 상세 진입 데이터/라우팅 guard -- [ ] **Task 4.4: 추천 탭 상세 진입 guard 적용** +- [ ] **Task 5.1: 추천 탭 상세 진입 guard 적용** - 대상 파일: - 수정: `SodaLive/Sources/V2/Main/MainView.swift` - 생성/보완: `SodaLive/Sources/V2/Main/Home/MainHomeView.swift` @@ -194,9 +172,62 @@ - 기대 결과: 신규 추천 탭 상세 진입 callback이 기존 인증/민감 콘텐츠 guard 인프라를 재사용한다. - 수동 확인: `MainHomeView`, `MainHomeRecommendationView` 내부에 Bootpay 인증 UI를 중복 구현하지 않아야 한다. -### Phase 5: 현재 라이브 섹션 +### Phase 6: 홈 탭 shell과 탭별 루트 연결 -- [ ] **Task 5.1: 현재 라이브 섹션 작성/보완** +- [ ] **Task 6.1: 추천/랭킹/팔로잉 하위 폴더와 루트 View 준비** + - 대상 파일: + - 생성/보완: `SodaLive/Sources/V2/Main/Home/Recommendation/MainHomeRecommendationView.swift` + - 생성/보완: `SodaLive/Sources/V2/Main/Home/Ranking/MainHomeRankingView.swift` + - 생성/보완: `SodaLive/Sources/V2/Main/Home/Following/MainHomeFollowingView.swift` + - 작업 내용: + - 추천 탭 콘텐츠 조립은 `MainHomeRecommendationView`에서 담당한다. + - 랭킹/팔로잉 탭은 이번 추천 API 범위 밖이므로 placeholder 또는 최소 루트 View만 준비하고 추천 섹션 코드를 넣지 않는다. + - 섹션별 UI 구현은 이후 섹션 UI Phase에서 진행한다. + - 검증 기준: + - 실행 명령: `find SodaLive/Sources/V2/Main/Home -maxdepth 3 -type f | sort` + - 기대 결과: `Recommendation`, `Ranking`, `Following` 하위 폴더의 루트 View 파일이 확인된다. + +- [ ] **Task 6.2: MainHomeView 탭 shell 작성/보완** + - 대상 파일: + - 생성/보완: `SodaLive/Sources/V2/Main/Home/MainHomeView.swift` + - 확인: `SodaLive/Sources/V2/Component/HomeTitleBar.swift` + - 작업 내용: + - `MainHomeView`는 `HomeTitleBar`, 추천/랭킹/팔로잉 상단 탭, 선택된 탭 콘텐츠 조합만 담당한다. + - 추천 탭 섹션 조립과 추천 API 호출을 `MainHomeView`에 직접 넣지 않는다. + - 하단 `MainTabBarView`는 기존 `MainView` 구조에서 유지하고 `MainHomeView` 내부에 중복 생성하지 않는다. + - 검증 기준: + - 실행 명령: `rg "struct MainHomeView|HomeTitleBar|MainHomeRecommendationView|MainHomeRankingView|MainHomeFollowingView|MainTabBarView|fetchRecommendations" SodaLive/Sources/V2/Main/Home/MainHomeView.swift SodaLive/Sources/V2/Main/MainView.swift` + - 기대 결과: `MainHomeView`는 탭별 루트 View를 조합하고, `fetchRecommendations`를 직접 호출하지 않으며, 탭바 중복 생성은 없어야 한다. + +- [ ] **Task 6.3: MainView 홈 탭 연결** + - 대상 파일: + - 수정: `SodaLive/Sources/V2/Main/MainView.swift` + - 작업 내용: + - `.home` 분기에서 placeholder 대신 `MainHomeView()`를 표시한다. + - 다른 탭 분기와 기존 이벤트 팝업/회원 정보 초기화 흐름은 변경하지 않는다. + - 검증 기준: + - 실행 명령: `rg "case \\.home|MainHomeView|MainPlaceholderTabView" SodaLive/Sources/V2/Main/MainView.swift` + - 기대 결과: `.home`은 `MainHomeView()`를 표시하고, 다른 탭은 기존 동작을 유지한다. + +### Phase 7: 데이터 기반 프로젝트 포함과 중간 빌드 검증 + +- [ ] **Task 7.1: 데이터 기반 신규 Swift 파일 프로젝트 포함 여부 확인** + - 대상 파일: + - 확인/보완: `SodaLive.xcodeproj/project.pbxproj` + - 확인: `SodaLive/Sources/V2/Main/Home/**` + - 작업 내용: + - Phase 4~6에서 생성/이동한 데이터 상태, 라우팅, 탭 루트 Swift 파일이 빌드 대상에 포함되는지 확인한다. + - 파일 시스템 동기화 방식으로 자동 포함되면 프로젝트 파일을 수정하지 않는다. + - 프로젝트 파일 수정이 필요한 경우에만 최소 변경한다. + - 검증 기준: + - 실행 명령: `plutil -lint SodaLive.xcodeproj/project.pbxproj` + - 기대 결과: `OK` + - 실행 명령: `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build` + - 기대 결과: `BUILD SUCCEEDED` + +### Phase 8: 현재 라이브 섹션 UI + +- [ ] **Task 8.1: 현재 라이브 섹션 작성/보완** - 대상 파일: - 생성/보완: `SodaLive/Sources/V2/Main/Home/Recommendation/Components/MainHomeLiveSection.swift` - 작업 내용: @@ -207,9 +238,9 @@ - 실행 명령: `rg "MainHomeLiveSection|roomId|creatorNickname|creatorProfileImage" SodaLive/Sources/V2/Main/Home/Recommendation` - 기대 결과: 라이브 섹션이 신규 응답 필드를 기준으로 구성된다. -### Phase 6: 배너 섹션 +### Phase 9: 배너 섹션 UI -- [ ] **Task 6.1: 추천 배너 섹션 작성/보완** +- [ ] **Task 9.1: 추천 배너 섹션 작성/보완** - 대상 파일: - 생성/보완: `SodaLive/Sources/V2/Main/Home/Recommendation/Components/MainHomeBannerSection.swift` - 확인/보완: `SodaLive/Sources/V2/Component/Banner/BannerCarousel.swift` @@ -222,9 +253,9 @@ - 실행 명령: `rg "RecommendationBannerResponse|HomeBanner|BannerCarousel|MainHomeBannerSection" SodaLive/Sources/V2/Main/Home/Recommendation SodaLive/Sources/V2/Component/Banner` - 기대 결과: 배너 응답 모델과 화면 조합이 확인된다. -### Phase 7: 최근 활동 크리에이터 섹션 +### Phase 10: 최근 활동 크리에이터 섹션 UI -- [ ] **Task 7.1: 최근 활동 크리에이터 섹션 작성/보완** +- [ ] **Task 10.1: 최근 활동 크리에이터 섹션 작성/보완** - 대상 파일: - 생성/보완: `SodaLive/Sources/V2/Main/Home/Recommendation/Components/MainHomeActiveCreatorSection.swift` - 작업 내용: @@ -235,9 +266,9 @@ - 실행 명령: `rg "MainHomeActiveCreatorSection|activityType|activityAt|targetId|RecommendedActivityType" SodaLive/Sources/V2/Main/Home/Recommendation` - 기대 결과: 최근 활동 섹션이 신규 응답 필드와 활동 타입 변환을 사용한다. -### Phase 8: 최근 데뷔한 크리에이터 섹션 +### Phase 11: 최근 데뷔한 크리에이터 섹션 UI -- [ ] **Task 8.1: 최근 데뷔 크리에이터 섹션 작성/보완** +- [ ] **Task 11.1: 최근 데뷔 크리에이터 섹션 작성/보완** - 대상 파일: - 생성/보완: `SodaLive/Sources/V2/Main/Home/Recommendation/Components/MainHomeRecentDebutCreatorSection.swift` - 확인/보완: `SodaLive/Sources/V2/Component/Creator/CreatorProfileGrid.swift` @@ -250,9 +281,9 @@ - 실행 명령: `rg "MainHomeRecentDebutCreatorSection|CreatorProfileGrid|CreatorProfileItem|creatorProfileImage" SodaLive/Sources/V2/Main/Home/Recommendation SodaLive/Sources/V2/Component/Creator` - 기대 결과: 최근 데뷔 섹션이 공용 Creator widget을 재사용한다. -### Phase 9: 처음 만나는 오디오 섹션 +### Phase 12: 처음 만나는 오디오 섹션 UI -- [ ] **Task 9.1: AudioContentCardView 조건부 태그 보완** +- [ ] **Task 12.1: AudioContentCardView 조건부 태그 보완** - 대상 파일: - 확인/보완: `SodaLive/Sources/V2/Component/AudioContentCard.swift` - 생성/보완: `SodaLive/Sources/V2/Main/Home/Recommendation/Components/MainHomeFirstAudioContentSection.swift` @@ -267,9 +298,9 @@ - 실행 명령: `rg "ic_content_tag_point|ic_content_tag_original|isPointAvailable|무료|FIRST|MainHomeFirstAudioContentSection" SodaLive/Sources/V2` - 기대 결과: 오디오 카드 조건부 시각요소와 섹션 조합이 확인된다. -### Phase 10: AI 캐릭터 섹션 +### Phase 13: AI 캐릭터 섹션 UI -- [ ] **Task 10.1: AI 캐릭터 섹션 작성/보완** +- [ ] **Task 13.1: AI 캐릭터 섹션 작성/보완** - 대상 파일: - 생성/보완: `SodaLive/Sources/V2/Main/Home/Recommendation/Components/MainHomeAiCharacterSection.swift` - 확인/보완: `SodaLive/Sources/V2/Component/Card/AiCharacterCard.swift` @@ -281,9 +312,9 @@ - 실행 명령: `rg "MainHomeAiCharacterSection|AiCharacterCard|totalChatCount|originalWorkTitle|profileImage" SodaLive/Sources/V2` - 기대 결과: AI 캐릭터 섹션이 신규 응답 필드를 기준으로 구성된다. -### Phase 11: 장르의 크리에이터 섹션 +### Phase 14: 장르의 크리에이터 섹션 UI -- [ ] **Task 11.1: 장르 크리에이터 그룹 섹션 작성/보완** +- [ ] **Task 14.1: 장르 크리에이터 그룹 섹션 작성/보완** - 대상 파일: - 생성/보완: `SodaLive/Sources/V2/Main/Home/Recommendation/Components/MainHomeGenreCreatorSection.swift` - 생성/보완: `SodaLive/Sources/V2/Main/Home/Recommendation/Components/MainHomeCreatorGroupSection.swift` @@ -297,9 +328,9 @@ - 실행 명령: `rg "MainHomeGenreCreatorSection|MainHomeCreatorGroupSection|genreName|creators|followAll" SodaLive/Sources/V2/Main/Home/Recommendation` - 기대 결과: 장르 그룹 섹션과 그룹 재사용 구조가 확인된다. -### Phase 12: 최근 응원이 많은 크리에이터 섹션 +### Phase 15: 최근 응원이 많은 크리에이터 섹션 UI -- [ ] **Task 12.1: 응원 크리에이터 섹션 작성/보완** +- [ ] **Task 15.1: 응원 크리에이터 섹션 작성/보완** - 대상 파일: - 생성/보완: `SodaLive/Sources/V2/Main/Home/Recommendation/Components/MainHomeCheerCreatorSection.swift` - 확인/보완: `SodaLive/Sources/V2/Main/Home/Recommendation/Components/MainHomeCreatorGroupSection.swift` @@ -313,9 +344,9 @@ - 실행 명령: `rg "MainHomeCheerCreatorSection|cheerCreators|MainHomeCreatorGroupSection|CreatorProfileGrid" SodaLive/Sources/V2/Main/Home/Recommendation` - 기대 결과: 응원 섹션이 장르 그룹 UI를 재사용한다. -### Phase 13: 인기 커뮤니티 섹션과 FeedCommunityView +### Phase 16: 인기 커뮤니티 섹션 UI와 FeedCommunityView -- [ ] **Task 13.1: FeedCommunityView/CommunityPostCard 요구 보완** +- [ ] **Task 16.1: FeedCommunityView/CommunityPostCard 요구 보완** - 대상 파일: - 확인/보완: `SodaLive/Sources/V2/Component/Card/CommunityPostCard.swift` - 생성/보완: `SodaLive/Sources/V2/Main/Home/Recommendation/Components/MainHomePopularCommunitySection.swift` @@ -331,9 +362,9 @@ - 실행 명령: `rg "MainHomePopularCommunitySection|CommunityPostCard|imageUrl|audioUrl|existOrdered|구매완료" SodaLive/Sources/V2` - 기대 결과: 커뮤니티 섹션과 카드 조건 분기가 확인되고 `구매완료` 표시 구현은 없어야 한다. -### Phase 14: 사업자 정보 섹션 +### Phase 17: 사업자 정보 섹션 UI -- [ ] **Task 14.1: 사업자 정보 더보기/접기 섹션 작성/보완** +- [ ] **Task 17.1: 사업자 정보 더보기/접기 섹션 작성/보완** - 대상 파일: - 생성/보완: `SodaLive/Sources/V2/Main/Home/Recommendation/Components/MainHomeBusinessInfoSection.swift` - 확인/보완: `SodaLive/Sources/V2/Component/Text/ExpandableTextView.swift` @@ -347,9 +378,9 @@ - 실행 명령: `rg "MainHomeBusinessInfoSection|ExpandableTextView|lineLimit|더보기|접기" SodaLive/Sources/V2` - 기대 결과: 사업자 정보 섹션과 확장 텍스트 구현이 확인된다. -### Phase 15: 섹션 조립 순서와 빈 섹션 처리 +### Phase 18: 섹션 조립 순서와 빈 섹션 처리 -- [ ] **Task 15.1: MainHomeRecommendationView에 섹션 순서 반영** +- [ ] **Task 18.1: MainHomeRecommendationView에 섹션 순서 반영** - 대상 파일: - 확인/보완: `SodaLive/Sources/V2/Main/Home/Recommendation/MainHomeRecommendationView.swift` - 작업 내용: @@ -360,9 +391,9 @@ - 실행 명령: `rg "MainHomeLiveSection|MainHomeBannerSection|MainHomeActiveCreatorSection|MainHomeRecentDebutCreatorSection|MainHomeFirstAudioContentSection|MainHomeAiCharacterSection|MainHomeGenreCreatorSection|MainHomeCheerCreatorSection|MainHomePopularCommunitySection|MainHomeBusinessInfoSection" SodaLive/Sources/V2/Main/Home/Recommendation/MainHomeRecommendationView.swift` - 기대 결과: 모든 구현 대상 섹션이 올바른 순서로 조립된다. -### Phase 16: 프로젝트 포함과 빌드 검증 +### Phase 19: 최종 프로젝트 포함과 빌드 검증 -- [ ] **Task 16.1: Xcode 프로젝트 포함 여부 확인** +- [ ] **Task 19.1: Xcode 프로젝트 포함 여부 확인** - 대상 파일: - 확인/보완: `SodaLive.xcodeproj/project.pbxproj` - 확인: `SodaLive/Sources/V2/Main/Home/**` @@ -375,7 +406,7 @@ - 실행 명령: `plutil -lint SodaLive.xcodeproj/project.pbxproj` - 기대 결과: `OK` -- [ ] **Task 16.2: 정적 점검과 빌드** +- [ ] **Task 19.2: 정적 점검과 빌드** - 대상 파일: - 확인: `SodaLive/Sources/V2/Main/Home/**` - 확인: `SodaLive/Sources/V2/Component/**` @@ -395,9 +426,9 @@ - 실행 명령: `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build` - 기대 결과: `BUILD SUCCEEDED` -### Phase 17: 문서와 검증 기록 갱신 +### Phase 20: 문서와 검증 기록 갱신 -- [ ] **Task 17.1: PRD/계획 문서 갱신** +- [ ] **Task 20.1: PRD/계획 문서 갱신** - 대상 파일: - 수정: `docs/20260602_메인_홈_추천_UI_API_연동/prd.md` - 수정: `docs/20260602_메인_홈_추천_UI_API_연동/plan-task.md` @@ -411,6 +442,55 @@ ## 검증 기록 +### 2026-06-26 Phase 1~3 코드 리뷰 보완 + +- 목적: Phase 2.2 리뷰에서 지적된 과거 추정 응답 필드 혼재를 제거하고, 추천 전용 request 모델 위치를 `Recommendation/Models` 구조로 일치 +- 수행 내용: + - `MainHomeRecommendationResponse.swift`에서 PRD 응답 스펙에 없는 legacy/추정 필드(`liveRoomId`, `profileImageUrl`, `thumbnailUrl`, `coverImageUrl`, `releaseDate`, `originalTitle` 등) 제거 + - 배너는 상세 PRD 스펙이 별도로 없으므로 기존 표시/이동 판단에 필요한 `RecommendationBannerResponse` 최소 필드 유지 + - `FollowRecommendedCreatorsRequest.swift`를 `SodaLive/Sources/V2/Main/Home/Recommendation/Models` 아래로 이동 + - `SodaLive.xcodeproj/project.pbxproj`의 request/model/API/Repository 참조 경로 갱신 +- 검증: + - `rg "liveRoomId|profileImageUrl|thumbnailUrl|coverImageUrl|releaseDate|originalTitle|chatCount|creatorProfileImageUrl|genreId|debutDate|followerCount|cheerCount|nickname" SodaLive/Sources/V2/Main/Home/Recommendation/Models/MainHomeRecommendationResponse.swift` 실행, 검색 결과 없음 확인 + - `rg "FollowRecommendedCreatorsRequest" SodaLive/Sources/V2/Main/Home SodaLive.xcodeproj/project.pbxproj` 실행, request 모델이 `Recommendation/Models`에 있고 호출부/프로젝트 참조가 유지됨 확인 + - `rg --files SodaLive/Sources/V2/Main/Home | sort` 실행, 추천 전용 모델/API 파일이 `Recommendation` 하위에 위치함 확인 + - `plutil -lint SodaLive.xcodeproj/project.pbxproj` 실행, `OK` 확인 + - `git diff --check` 실행, 출력 없이 성공 확인 + - `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build` 실행, `BUILD SUCCEEDED` 확인 + +### 2026-06-26 Phase 4 이후 데이터 선행 순서 재배치 + +- 목적: Figma 기준 UI는 직접 확인하며 처리할 수 있도록, Phase 4 이후 작업을 네트워크 통신/상태/라우팅/중간 빌드 검증 선행 후 섹션 UI 구현 순서로 재배치 +- 수행 내용: + - Phase 4를 추천 ViewModel 데이터 로딩 계층 정리로 변경 + - Phase 5를 상세 진입 데이터/라우팅 guard로 배치 + - Phase 6을 홈 탭 shell과 탭별 루트 연결로 배치하되, 섹션 UI 구현은 이후 Phase로 미룸 + - Phase 7에 데이터 기반 프로젝트 포함과 중간 빌드 검증을 추가 + - 기존 섹션별 UI Phase를 Phase 8~17로 뒤로 이동하고, 섹션 조립/최종 검증/문서 갱신을 Phase 18~20으로 재번호 지정 +- 검증: + - `rg "^### Phase [0-9]+:|- \\[[ x]\\] \\*\\*Task [0-9]+\\.[0-9]+:" docs/20260602_메인_홈_추천_UI_API_연동/plan-task.md` 실행, Phase/Task 번호 형식 확인 + - `git diff --check -- docs/20260602_메인_홈_추천_UI_API_연동/plan-task.md` 실행, 출력 없이 성공 확인 + +### 2026-06-26 Phase 1~3 재점검 및 보완 + +- 목적: 신규 재분류 기준의 Phase 1~3에 맞춰 추천 API/모델/활동 타입 위치와 응답 필드를 `Recommendation` 하위 구조로 정리 +- 수행 내용: + - PRD와 현재 `SodaLive/Sources/V2/Main/Home`, `SodaLive/Sources/V2/Component` 구현 상태를 비교해 Phase 1 기준점과 제외 범위 확인 + - 추천 API/Repository를 `SodaLive/Sources/V2/Main/Home/Recommendation/Repository` 아래로 이동하고 `MainHomeRecommendationApi`, `MainHomeRecommendationRepository`로 이름 정리 + - 추천 응답 모델과 활동 타입을 `SodaLive/Sources/V2/Main/Home/Recommendation/Models` 아래로 이동 + - `HomeRecommendationResponse.banners` 타입을 `RecommendationBannerResponse`로 명확히 하고 PRD 핵심 필드(`roomId`, `creatorProfileImage`, `targetId`, `price`, `coverImage`, `isPointAvailable`, `totalChatCount`, `originalWorkTitle`, `audioUrl`) 반영 + - `RecommendedActivityType`의 `LIVE`, `LIVE_REPLAY`, `AUDIO`, `COMMUNITY` 변환과 `I18n.HomeRecommendation` ko/en/ja 문구 유지 확인 + - `SodaLive.xcodeproj/project.pbxproj`의 이동/이름 변경된 Swift 파일 참조 갱신 +- 검증: + - `rg --files SodaLive/Sources/V2/Main/Home SodaLive/Sources/V2/Component SodaLive/Sources/Home SodaLive/Sources/I18n | sort` 실행, V2 홈/공용 컴포넌트 및 기존 홈 파일 목록 확인 + - `rg "추천 필모그래피|또 다른 모습|figma.com|localhost:3845" SodaLive/Sources/V2/Main/Home SodaLive/Sources/V2/Component` 실행, 검색 결과 없음 확인 + - `rg "/api/v2/home/recommendations|getRecommendations" SodaLive/Sources/V2/Main/Home/Recommendation/Repository SodaLive/Sources/Home` 실행, 신규 추천 endpoint가 `Recommendation/Repository`에서만 검색되고 기존 `SodaLive/Sources/Home`에는 없음 확인 + - `rg "roomId|creatorProfileImage|targetId|price|coverImage|isPointAvailable|totalChatCount|originalWorkTitle|audioUrl" SodaLive/Sources/V2/Main/Home/Recommendation/Models/MainHomeRecommendationResponse.swift` 실행, PRD 핵심 필드 반영 확인 + - `rg "LIVE_REPLAY|activityLive|activityAudio|activityCommunity|ライブ|オーディオ|コミュニティ" SodaLive/Sources/V2/Main/Home/Recommendation/Models/RecommendedActivityType.swift SodaLive/Sources/I18n/I18n.swift` 실행, 활동 타입 변환과 I18n 문구 확인 + - `plutil -lint SodaLive.xcodeproj/project.pbxproj` 실행, `OK` 확인 + - `git diff --check` 실행, 출력 없이 성공 확인 + - `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build` 실행, `BUILD SUCCEEDED` 확인 + ### 2026-06-26 MainHome 탭별 폴더 구조 반영 - 목적: `MainHomeView`에 추천/랭킹/팔로잉 콘텐츠 조립이 누적되지 않도록 탭별 하위 폴더와 루트 View 구조를 PRD/계획 문서에 반영