feat(home): 홈 탭 shell을 연결한다

This commit is contained in:
Yu Sung
2026-06-27 00:09:04 +09:00
parent 5757705b12
commit 394e7ebc34
8 changed files with 354 additions and 13 deletions

View File

@@ -195,11 +195,24 @@
401A3FA02FEEAFA000A5BBA3 /* FollowRecommendedCreatorsRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 401A3F962FEEAFA000A5BBA3 /* FollowRecommendedCreatorsRequest.swift */; }; 401A3FA02FEEAFA000A5BBA3 /* FollowRecommendedCreatorsRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 401A3F962FEEAFA000A5BBA3 /* FollowRecommendedCreatorsRequest.swift */; };
401A3FA12FEEAFA000A5BBA3 /* MainHomeRecommendationRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 401A3F9B2FEEAFA000A5BBA3 /* MainHomeRecommendationRepository.swift */; }; 401A3FA12FEEAFA000A5BBA3 /* MainHomeRecommendationRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 401A3F9B2FEEAFA000A5BBA3 /* MainHomeRecommendationRepository.swift */; };
401A3FA22FEEAFA000A5BBA3 /* RecommendedActivityType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 401A3F982FEEAFA000A5BBA3 /* RecommendedActivityType.swift */; }; 401A3FA22FEEAFA000A5BBA3 /* RecommendedActivityType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 401A3F982FEEAFA000A5BBA3 /* RecommendedActivityType.swift */; };
401A3FBA2FEEAFA000A5BBA3 /* MainHomeRecommendationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76506E56D017236824FE9B8F /* MainHomeRecommendationViewModel.swift */; };
401A3FA82FEEAFA000A5BBA3 /* MainHomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 401A3FB42FEEAFA000A5BBA3 /* MainHomeView.swift */; };
401A3FA92FEEAFA000A5BBA3 /* MainHomeRecommendationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 401A3FB52FEEAFA000A5BBA3 /* MainHomeRecommendationView.swift */; };
401A3FAA2FEEAFA000A5BBA3 /* MainHomeRankingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 401A3FB62FEEAFA000A5BBA3 /* MainHomeRankingView.swift */; };
401A3FAB2FEEAFA000A5BBA3 /* MainHomeFollowingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 401A3FB72FEEAFA000A5BBA3 /* MainHomeFollowingView.swift */; };
401A3FA32FEEAFA000A5BBA3 /* MainHomeRecommendationApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 401A3F9A2FEEAFA000A5BBA3 /* MainHomeRecommendationApi.swift */; }; 401A3FA32FEEAFA000A5BBA3 /* MainHomeRecommendationApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 401A3F9A2FEEAFA000A5BBA3 /* MainHomeRecommendationApi.swift */; };
401A3FA42FEEAFA000A5BBA3 /* MainHomeRecommendationResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 401A3F972FEEAFA000A5BBA3 /* MainHomeRecommendationResponse.swift */; }; 401A3FA42FEEAFA000A5BBA3 /* MainHomeRecommendationResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 401A3F972FEEAFA000A5BBA3 /* MainHomeRecommendationResponse.swift */; };
401A3FA52FEEAFA000A5BBA3 /* FollowRecommendedCreatorsRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 401A3F962FEEAFA000A5BBA3 /* FollowRecommendedCreatorsRequest.swift */; }; 401A3FA52FEEAFA000A5BBA3 /* FollowRecommendedCreatorsRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 401A3F962FEEAFA000A5BBA3 /* FollowRecommendedCreatorsRequest.swift */; };
401A3FA62FEEAFA000A5BBA3 /* MainHomeRecommendationRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 401A3F9B2FEEAFA000A5BBA3 /* MainHomeRecommendationRepository.swift */; }; 401A3FA62FEEAFA000A5BBA3 /* MainHomeRecommendationRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 401A3F9B2FEEAFA000A5BBA3 /* MainHomeRecommendationRepository.swift */; };
401A3FA72FEEAFA000A5BBA3 /* RecommendedActivityType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 401A3F982FEEAFA000A5BBA3 /* RecommendedActivityType.swift */; }; 401A3FA72FEEAFA000A5BBA3 /* RecommendedActivityType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 401A3F982FEEAFA000A5BBA3 /* RecommendedActivityType.swift */; };
401A3FAC2FEEAFA000A5BBA3 /* MainHomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 401A3FB42FEEAFA000A5BBA3 /* MainHomeView.swift */; };
401A3FAD2FEEAFA000A5BBA3 /* MainHomeRecommendationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 401A3FB52FEEAFA000A5BBA3 /* MainHomeRecommendationView.swift */; };
401A3FAE2FEEAFA000A5BBA3 /* MainHomeRankingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 401A3FB62FEEAFA000A5BBA3 /* MainHomeRankingView.swift */; };
401A3FAF2FEEAFA000A5BBA3 /* MainHomeFollowingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 401A3FB72FEEAFA000A5BBA3 /* MainHomeFollowingView.swift */; };
401A3FB02FEEAFA000A5BBA3 /* MainHomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 401A3FB42FEEAFA000A5BBA3 /* MainHomeView.swift */; };
401A3FB12FEEAFA000A5BBA3 /* MainHomeRecommendationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 401A3FB52FEEAFA000A5BBA3 /* MainHomeRecommendationView.swift */; };
401A3FB22FEEAFA000A5BBA3 /* MainHomeRankingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 401A3FB62FEEAFA000A5BBA3 /* MainHomeRankingView.swift */; };
401A3FB32FEEAFA000A5BBA3 /* MainHomeFollowingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 401A3FB72FEEAFA000A5BBA3 /* MainHomeFollowingView.swift */; };
40235DED2E3797EB00B09A87 /* RecentContent+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40235DEC2E3797EB00B09A87 /* RecentContent+CoreDataClass.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 */; }; 40235DEE2E3797EB00B09A87 /* RecentContent+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40235DEC2E3797EB00B09A87 /* RecentContent+CoreDataClass.swift */; };
40235DF02E37983D00B09A87 /* PersistenceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40235DEF2E37983D00B09A87 /* PersistenceController.swift */; }; 40235DF02E37983D00B09A87 /* PersistenceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40235DEF2E37983D00B09A87 /* PersistenceController.swift */; };
@@ -1565,7 +1578,7 @@
A376733CDBF21EBAF81C8BE2 /* SocialLoginRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D0050039EBD24D2FF6D0BD0 /* SocialLoginRequest.swift */; }; A376733CDBF21EBAF81C8BE2 /* SocialLoginRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D0050039EBD24D2FF6D0BD0 /* SocialLoginRequest.swift */; };
A3BDD0D3E14CD72B022AF6A7 /* FollowButtonImageAsset.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DC033E531A9420363D017CE /* FollowButtonImageAsset.swift */; }; A3BDD0D3E14CD72B022AF6A7 /* FollowButtonImageAsset.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DC033E531A9420363D017CE /* FollowButtonImageAsset.swift */; };
A3EB81FD727C63FE034048B9 /* Spacing.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6B18C00DE256DCC53030630 /* Spacing.swift */; }; A3EB81FD727C63FE034048B9 /* Spacing.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6B18C00DE256DCC53030630 /* Spacing.swift */; };
A4EB669DFFFB64681E045EF3 /* MainHomeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76506E56D017236824FE9B8F /* MainHomeViewModel.swift */; }; A4EB669DFFFB64681E045EF3 /* MainHomeRecommendationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76506E56D017236824FE9B8F /* MainHomeRecommendationViewModel.swift */; };
A840136779D6CC95F94CB681 /* HomeTitleBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAA81E8424A575A0F8546D31 /* HomeTitleBar.swift */; }; A840136779D6CC95F94CB681 /* HomeTitleBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAA81E8424A575A0F8546D31 /* HomeTitleBar.swift */; };
ACD71056E97E13E48941ABBB /* MainPlaceholderTabView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7900EA9FD1150BABE64CF1B3 /* MainPlaceholderTabView.swift */; }; ACD71056E97E13E48941ABBB /* MainPlaceholderTabView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7900EA9FD1150BABE64CF1B3 /* MainPlaceholderTabView.swift */; };
AD6424211AA898794BBE8470 /* SectionTitle.swift in Sources */ = {isa = PBXBuildFile; fileRef = D16C12DFB83FE164B38CC066 /* SectionTitle.swift */; }; AD6424211AA898794BBE8470 /* SectionTitle.swift in Sources */ = {isa = PBXBuildFile; fileRef = D16C12DFB83FE164B38CC066 /* SectionTitle.swift */; };
@@ -1573,7 +1586,7 @@
B11681AC02BF51C6E2FA6FF3 /* MainTabBarButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8F49E8BB9BACFCA8878B70D /* MainTabBarButton.swift */; }; B11681AC02BF51C6E2FA6FF3 /* MainTabBarButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8F49E8BB9BACFCA8878B70D /* MainTabBarButton.swift */; };
B3AF650899B8FC84E0C0CDA0 /* MainPlaceholderTabView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7900EA9FD1150BABE64CF1B3 /* MainPlaceholderTabView.swift */; }; B3AF650899B8FC84E0C0CDA0 /* MainPlaceholderTabView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7900EA9FD1150BABE64CF1B3 /* MainPlaceholderTabView.swift */; };
B5E9FA82CF4238F640702D4A /* CreatorDetailDialogView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7406AE69F7667FAA2E6DD23 /* CreatorDetailDialogView.swift */; }; B5E9FA82CF4238F640702D4A /* CreatorDetailDialogView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7406AE69F7667FAA2E6DD23 /* CreatorDetailDialogView.swift */; };
BA2B76FDCAF81D15E2D4E258 /* MainHomeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76506E56D017236824FE9B8F /* MainHomeViewModel.swift */; }; BA2B76FDCAF81D15E2D4E258 /* MainHomeRecommendationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76506E56D017236824FE9B8F /* MainHomeRecommendationViewModel.swift */; };
BA511503C1E037162697D771 /* DonationRankingPeriod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44A42B63A1021DD9F8AA3F30 /* DonationRankingPeriod.swift */; }; BA511503C1E037162697D771 /* DonationRankingPeriod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44A42B63A1021DD9F8AA3F30 /* DonationRankingPeriod.swift */; };
BCA6CD9E57528C5F4203F1A5 /* CapsuleTabBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74990A0798E64A97237DD113 /* CapsuleTabBar.swift */; }; BCA6CD9E57528C5F4203F1A5 /* CapsuleTabBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74990A0798E64A97237DD113 /* CapsuleTabBar.swift */; };
BEAAA523CAD3198065A557BC /* EntityInfo-SodaLive-dev.generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0ED95AC08C6481751AE9D97 /* EntityInfo-SodaLive-dev.generated.swift */; }; BEAAA523CAD3198065A557BC /* EntityInfo-SodaLive-dev.generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0ED95AC08C6481751AE9D97 /* EntityInfo-SodaLive-dev.generated.swift */; };
@@ -1709,6 +1722,10 @@
401A3F982FEEAFA000A5BBA3 /* RecommendedActivityType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecommendedActivityType.swift; sourceTree = "<group>"; }; 401A3F982FEEAFA000A5BBA3 /* RecommendedActivityType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecommendedActivityType.swift; sourceTree = "<group>"; };
401A3F9A2FEEAFA000A5BBA3 /* MainHomeRecommendationApi.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainHomeRecommendationApi.swift; sourceTree = "<group>"; }; 401A3F9A2FEEAFA000A5BBA3 /* MainHomeRecommendationApi.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainHomeRecommendationApi.swift; sourceTree = "<group>"; };
401A3F9B2FEEAFA000A5BBA3 /* MainHomeRecommendationRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainHomeRecommendationRepository.swift; sourceTree = "<group>"; }; 401A3F9B2FEEAFA000A5BBA3 /* MainHomeRecommendationRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainHomeRecommendationRepository.swift; sourceTree = "<group>"; };
401A3FB42FEEAFA000A5BBA3 /* MainHomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainHomeView.swift; sourceTree = "<group>"; };
401A3FB52FEEAFA000A5BBA3 /* MainHomeRecommendationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainHomeRecommendationView.swift; sourceTree = "<group>"; };
401A3FB62FEEAFA000A5BBA3 /* MainHomeRankingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainHomeRankingView.swift; sourceTree = "<group>"; };
401A3FB72FEEAFA000A5BBA3 /* MainHomeFollowingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainHomeFollowingView.swift; sourceTree = "<group>"; };
40235DEC2E3797EB00B09A87 /* RecentContent+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RecentContent+CoreDataClass.swift"; sourceTree = "<group>"; }; 40235DEC2E3797EB00B09A87 /* RecentContent+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RecentContent+CoreDataClass.swift"; sourceTree = "<group>"; };
40235DEF2E37983D00B09A87 /* PersistenceController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersistenceController.swift; sourceTree = "<group>"; }; 40235DEF2E37983D00B09A87 /* PersistenceController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersistenceController.swift; sourceTree = "<group>"; };
40235DF22E37987300B09A87 /* RecentContentService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecentContentService.swift; sourceTree = "<group>"; }; 40235DF22E37987300B09A87 /* RecentContentService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecentContentService.swift; sourceTree = "<group>"; };
@@ -2369,7 +2386,7 @@
716260E8214A329EE07CAAFA /* LanguageSettingsViewModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = LanguageSettingsViewModel.swift; sourceTree = "<group>"; }; 716260E8214A329EE07CAAFA /* LanguageSettingsViewModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = LanguageSettingsViewModel.swift; sourceTree = "<group>"; };
72CD656CAEE376701F2B563E /* MainView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = MainView.swift; sourceTree = "<group>"; }; 72CD656CAEE376701F2B563E /* MainView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = MainView.swift; sourceTree = "<group>"; };
74990A0798E64A97237DD113 /* CapsuleTabBar.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CapsuleTabBar.swift; sourceTree = "<group>"; }; 74990A0798E64A97237DD113 /* CapsuleTabBar.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CapsuleTabBar.swift; sourceTree = "<group>"; };
76506E56D017236824FE9B8F /* MainHomeViewModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = MainHomeViewModel.swift; sourceTree = "<group>"; }; 76506E56D017236824FE9B8F /* MainHomeRecommendationViewModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = MainHomeRecommendationViewModel.swift; sourceTree = "<group>"; };
7900EA9FD1150BABE64CF1B3 /* MainPlaceholderTabView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = MainPlaceholderTabView.swift; sourceTree = "<group>"; }; 7900EA9FD1150BABE64CF1B3 /* MainPlaceholderTabView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = MainPlaceholderTabView.swift; sourceTree = "<group>"; };
7C21D69F717F8D80644F6AEE /* PushNotificationListView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PushNotificationListView.swift; sourceTree = "<group>"; }; 7C21D69F717F8D80644F6AEE /* PushNotificationListView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PushNotificationListView.swift; sourceTree = "<group>"; };
7C22ACAC456B3BCB2D9C568D /* AcceptLanguageDebugLogger.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AcceptLanguageDebugLogger.swift; sourceTree = "<group>"; }; 7C22ACAC456B3BCB2D9C568D /* AcceptLanguageDebugLogger.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AcceptLanguageDebugLogger.swift; sourceTree = "<group>"; };
@@ -2516,7 +2533,9 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
401A3F9D2FEEAFA000A5BBA3 /* Recommendation */, 401A3F9D2FEEAFA000A5BBA3 /* Recommendation */,
76506E56D017236824FE9B8F /* MainHomeViewModel.swift */, 401A3FB82FEEAFA000A5BBA3 /* Ranking */,
401A3FB92FEEAFA000A5BBA3 /* Following */,
401A3FB42FEEAFA000A5BBA3 /* MainHomeView.swift */,
); );
path = Home; path = Home;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -2680,10 +2699,28 @@
children = ( children = (
401A3F992FEEAFA000A5BBA3 /* Models */, 401A3F992FEEAFA000A5BBA3 /* Models */,
401A3F9C2FEEAFA000A5BBA3 /* Repository */, 401A3F9C2FEEAFA000A5BBA3 /* Repository */,
401A3FB52FEEAFA000A5BBA3 /* MainHomeRecommendationView.swift */,
76506E56D017236824FE9B8F /* MainHomeRecommendationViewModel.swift */,
); );
path = Recommendation; path = Recommendation;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
401A3FB82FEEAFA000A5BBA3 /* Ranking */ = {
isa = PBXGroup;
children = (
401A3FB62FEEAFA000A5BBA3 /* MainHomeRankingView.swift */,
);
path = Ranking;
sourceTree = "<group>";
};
401A3FB92FEEAFA000A5BBA3 /* Following */ = {
isa = PBXGroup;
children = (
401A3FB72FEEAFA000A5BBA3 /* MainHomeFollowingView.swift */,
);
path = Following;
sourceTree = "<group>";
};
40235DEA2E3797B000B09A87 /* Recent */ = { 40235DEA2E3797B000B09A87 /* Recent */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@@ -5274,6 +5311,11 @@
401A3FA02FEEAFA000A5BBA3 /* FollowRecommendedCreatorsRequest.swift in Sources */, 401A3FA02FEEAFA000A5BBA3 /* FollowRecommendedCreatorsRequest.swift in Sources */,
401A3FA12FEEAFA000A5BBA3 /* MainHomeRecommendationRepository.swift in Sources */, 401A3FA12FEEAFA000A5BBA3 /* MainHomeRecommendationRepository.swift in Sources */,
401A3FA22FEEAFA000A5BBA3 /* RecommendedActivityType.swift in Sources */, 401A3FA22FEEAFA000A5BBA3 /* RecommendedActivityType.swift in Sources */,
401A3FBA2FEEAFA000A5BBA3 /* MainHomeRecommendationViewModel.swift in Sources */,
401A3FA82FEEAFA000A5BBA3 /* MainHomeView.swift in Sources */,
401A3FA92FEEAFA000A5BBA3 /* MainHomeRecommendationView.swift in Sources */,
401A3FAA2FEEAFA000A5BBA3 /* MainHomeRankingView.swift in Sources */,
401A3FAB2FEEAFA000A5BBA3 /* MainHomeFollowingView.swift in Sources */,
409143302A85652F00EEBFB1 /* CanChargeView.swift in Sources */, 409143302A85652F00EEBFB1 /* CanChargeView.swift in Sources */,
40975CA32B572298004E2F1B /* LiveRoomOverlayStrokeTextButton.swift in Sources */, 40975CA32B572298004E2F1B /* LiveRoomOverlayStrokeTextButton.swift in Sources */,
40235DED2E3797EB00B09A87 /* RecentContent+CoreDataClass.swift in Sources */, 40235DED2E3797EB00B09A87 /* RecentContent+CoreDataClass.swift in Sources */,
@@ -6015,7 +6057,11 @@
AD6424211AA898794BBE8470 /* SectionTitle.swift in Sources */, AD6424211AA898794BBE8470 /* SectionTitle.swift in Sources */,
38D73A7B6BFB9A984834984B /* TextTabBar.swift in Sources */, 38D73A7B6BFB9A984834984B /* TextTabBar.swift in Sources */,
372837FE9857C16B6B5087FF /* TitleBar.swift in Sources */, 372837FE9857C16B6B5087FF /* TitleBar.swift in Sources */,
BA2B76FDCAF81D15E2D4E258 /* MainHomeViewModel.swift in Sources */, BA2B76FDCAF81D15E2D4E258 /* MainHomeRecommendationViewModel.swift in Sources */,
401A3FAC2FEEAFA000A5BBA3 /* MainHomeView.swift in Sources */,
401A3FAD2FEEAFA000A5BBA3 /* MainHomeRecommendationView.swift in Sources */,
401A3FAE2FEEAFA000A5BBA3 /* MainHomeRankingView.swift in Sources */,
401A3FAF2FEEAFA000A5BBA3 /* MainHomeFollowingView.swift in Sources */,
B3AF650899B8FC84E0C0CDA0 /* MainPlaceholderTabView.swift in Sources */, B3AF650899B8FC84E0C0CDA0 /* MainPlaceholderTabView.swift in Sources */,
CA65AD0D677B4A8E9CAC0E15 /* MainTab.swift in Sources */, CA65AD0D677B4A8E9CAC0E15 /* MainTab.swift in Sources */,
A0643C829BE7377DC09112CB /* MainTabBarButton.swift in Sources */, A0643C829BE7377DC09112CB /* MainTabBarButton.swift in Sources */,
@@ -6784,7 +6830,11 @@
32595EDE75871A7B2FDCC5AB /* SectionTitle.swift in Sources */, 32595EDE75871A7B2FDCC5AB /* SectionTitle.swift in Sources */,
7A1DDAAE227BF176BDEF1CB9 /* TextTabBar.swift in Sources */, 7A1DDAAE227BF176BDEF1CB9 /* TextTabBar.swift in Sources */,
5EEE98FE0EC4039761C604EE /* TitleBar.swift in Sources */, 5EEE98FE0EC4039761C604EE /* TitleBar.swift in Sources */,
A4EB669DFFFB64681E045EF3 /* MainHomeViewModel.swift in Sources */, A4EB669DFFFB64681E045EF3 /* MainHomeRecommendationViewModel.swift in Sources */,
401A3FB02FEEAFA000A5BBA3 /* MainHomeView.swift in Sources */,
401A3FB12FEEAFA000A5BBA3 /* MainHomeRecommendationView.swift in Sources */,
401A3FB22FEEAFA000A5BBA3 /* MainHomeRankingView.swift in Sources */,
401A3FB32FEEAFA000A5BBA3 /* MainHomeFollowingView.swift in Sources */,
ACD71056E97E13E48941ABBB /* MainPlaceholderTabView.swift in Sources */, ACD71056E97E13E48941ABBB /* MainPlaceholderTabView.swift in Sources */,
33BA3AB731849284E62AE04B /* MainTab.swift in Sources */, 33BA3AB731849284E62AE04B /* MainTab.swift in Sources */,
B11681AC02BF51C6E2FA6FF3 /* MainTabBarButton.swift in Sources */, B11681AC02BF51C6E2FA6FF3 /* MainTabBarButton.swift in Sources */,

View File

@@ -0,0 +1,13 @@
import SwiftUI
struct MainHomeFollowingView: View {
var body: some View {
MainPlaceholderTabView(title: "팔로잉")
}
}
struct MainHomeFollowingView_Previews: PreviewProvider {
static var previews: some View {
MainHomeFollowingView()
}
}

View File

@@ -0,0 +1,91 @@
import SwiftUI
struct MainHomeView: View {
let onTapCanCharge: () -> Void
let onTapSearch: () -> Void
let onTapNotificationList: () -> Void
let onTapLive: (Int) -> Void
let onTapCreator: (Int) -> Void
let onTapContent: (Int) -> Void
let onTapCharacter: (Int) -> Void
let onTapCommunity: (Int) -> Void
@State private var selectedTab: MainHomeTab = .recommendation
var body: some View {
VStack(spacing: 0) {
HomeTitleBar {
HStack(spacing: 14) {
Image("ic_bar_cash")
.onTapGesture { onTapCanCharge() }
Image("ic_bar_search")
.onTapGesture { onTapSearch() }
Image("ic_bar_bell")
.onTapGesture { onTapNotificationList() }
}
}
TextTabBar(
items: MainHomeTab.allCases,
selectedItem: $selectedTab,
title: { $0.title }
)
tabContent
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
.background(Color.black.ignoresSafeArea())
}
@ViewBuilder
private var tabContent: some View {
switch selectedTab {
case .recommendation:
MainHomeRecommendationView(
onTapLive: onTapLive,
onTapCreator: onTapCreator,
onTapContent: onTapContent,
onTapCharacter: onTapCharacter,
onTapCommunity: onTapCommunity
)
case .ranking:
MainHomeRankingView()
case .following:
MainHomeFollowingView()
}
}
}
private enum MainHomeTab: CaseIterable, Hashable {
case recommendation
case ranking
case following
var title: String {
switch self {
case .recommendation:
return "추천"
case .ranking:
return "랭킹"
case .following:
return "팔로잉"
}
}
}
struct MainHomeView_Previews: PreviewProvider {
static var previews: some View {
MainHomeView(
onTapCanCharge: {},
onTapSearch: {},
onTapNotificationList: {},
onTapLive: { _ in },
onTapCreator: { _ in },
onTapContent: { _ in },
onTapCharacter: { _ in },
onTapCommunity: { _ in }
)
}
}

View File

@@ -0,0 +1,13 @@
import SwiftUI
struct MainHomeRankingView: View {
var body: some View {
MainPlaceholderTabView(title: "랭킹")
}
}
struct MainHomeRankingView_Previews: PreviewProvider {
static var previews: some View {
MainHomeRankingView()
}
}

View File

@@ -0,0 +1,45 @@
import SwiftUI
struct MainHomeRecommendationView: View {
let onTapLive: (Int) -> Void
let onTapCreator: (Int) -> Void
let onTapContent: (Int) -> Void
let onTapCharacter: (Int) -> Void
let onTapCommunity: (Int) -> Void
@StateObject private var viewModel = MainHomeRecommendationViewModel()
var body: some View {
ZStack {
Color.black
.ignoresSafeArea()
if viewModel.isLoading && viewModel.recommendations == nil {
ProgressView()
.progressViewStyle(CircularProgressViewStyle(tint: .white))
}
}
.onAppear {
if viewModel.recommendations == nil {
viewModel.fetchRecommendations()
}
}
.sodaToast(
isPresented: $viewModel.isShowPopup,
message: viewModel.errorMessage,
autohideIn: 2
)
}
}
struct MainHomeRecommendationView_Previews: PreviewProvider {
static var previews: some View {
MainHomeRecommendationView(
onTapLive: { _ in },
onTapCreator: { _ in },
onTapContent: { _ in },
onTapCharacter: { _ in },
onTapCommunity: { _ in }
)
}
}

View File

@@ -1,7 +1,7 @@
import Foundation import Foundation
import Combine import Combine
final class MainHomeViewModel: ObservableObject { final class MainHomeRecommendationViewModel: ObservableObject {
private let repository = MainHomeRecommendationRepository() private let repository = MainHomeRecommendationRepository()
private var subscription = Set<AnyCancellable>() private var subscription = Set<AnyCancellable>()

View File

@@ -153,7 +153,16 @@ struct MainView: View {
private var contentView: some View { private var contentView: some View {
switch viewModel.currentTab { switch viewModel.currentTab {
case .home: case .home:
MainPlaceholderTabView(title: MainTab.home.title) MainHomeView(
onTapCanCharge: handleHomeCanChargeTap,
onTapSearch: handleHomeSearchTap,
onTapNotificationList: handleHomeNotificationListTap,
onTapLive: handleRecommendationLiveTap,
onTapCreator: handleRecommendationCreatorTap,
onTapContent: handleRecommendationContentTap,
onTapCharacter: handleRecommendationCharacterTap,
onTapCommunity: handleRecommendationCommunityTap
)
case .content: case .content:
MainPlaceholderTabView(title: MainTab.content.title) MainPlaceholderTabView(title: MainTab.content.title)
case .chat: case .chat:
@@ -416,6 +425,106 @@ struct MainView: View {
navigationAction() navigationAction()
} }
private func handleHomeCanChargeTap() {
appState.setAppStep(step: .canCharge(refresh: {}))
}
private func handleHomeSearchTap() {
appState.setAppStep(step: .search)
}
private func handleHomeNotificationListTap() {
appState.setAppStep(step: .notificationList)
}
private func handleRecommendationLiveTap(roomId: Int) {
guard roomId > 0 else { return }
performRecommendationDetailAction {
openLiveDetail(roomId: roomId)
}
}
private func handleRecommendationCreatorTap(creatorId: Int) {
guard creatorId > 0 else { return }
performRecommendationDetailAction {
appState.setAppStep(step: .creatorDetail(userId: creatorId))
}
}
private func handleRecommendationContentTap(contentId: Int) {
guard contentId > 0 else { return }
performRecommendationDetailAction {
appState.setAppStep(step: .contentDetail(contentId: contentId))
}
}
private func handleRecommendationCharacterTap(characterId: Int) {
guard characterId > 0 else { return }
performRecommendationDetailAction {
appState.setAppStep(step: .characterDetail(characterId: characterId))
}
}
private func handleRecommendationCommunityTap(creatorId: Int) {
guard creatorId > 0 else { return }
performRecommendationDetailAction {
appState.setAppStep(step: .creatorCommunityAll(creatorId: creatorId))
}
}
private func performRecommendationDetailAction(
requiresAdultContent: Bool = false,
action: @escaping () -> Void
) {
let trimmed = token.trimmingCharacters(in: .whitespacesAndNewlines)
guard !trimmed.isEmpty else {
AppState.shared.setAppStep(step: .login)
return
}
if requiresAdultContent {
let normalizedCountryCode = UserDefaults
.string(forKey: .countryCode)
.trimmingCharacters(in: .whitespacesAndNewlines)
.uppercased()
let isKoreanCountry = normalizedCountryCode.isEmpty || normalizedCountryCode == "KR"
if isKoreanCountry && auth == false {
pendingAction = action
isShowAuthConfirmView = true
return
}
if !UserDefaults.isAdultContentVisible() {
pendingAction = nil
moveToContentSettingsWithGuideToast()
return
}
}
action()
}
private func openLiveDetail(roomId: Int) {
appState.setAppStep(
step: .liveDetail(
roomId: roomId,
onClickParticipant: {
appState.isShowPlayer = false
liveViewModel.enterLiveRoom(roomId: roomId)
},
onClickReservation: {},
onClickStart: {},
onClickCancel: {}
)
)
}
private func moveToContentSettingsWithGuideToast() {
AppState.shared.setPendingContentSettingsGuideMessage(I18n.Settings.adultContentEnableGuide)
AppState.shared.setAppStep(step: .contentViewSettings)
}
private func confirmExternalNavigation() { private func confirmExternalNavigation() {
guard pendingExternalNavigationAction != nil else { guard pendingExternalNavigationAction != nil else {
isShowLeaveLiveNavigationDialog = false isShowLeaveLiveNavigationDialog = false

View File

@@ -135,7 +135,7 @@
### Phase 4: 추천 데이터 상태 계층 정리 ### Phase 4: 추천 데이터 상태 계층 정리
- [ ] **Task 4.1: 추천 ViewModel 데이터 로딩 계층 정리** - [x] **Task 4.1: 추천 ViewModel 데이터 로딩 계층 정리**
- 대상 파일: - 대상 파일:
- 생성/보완: `SodaLive/Sources/V2/Main/Home/Recommendation/MainHomeRecommendationViewModel.swift` - 생성/보완: `SodaLive/Sources/V2/Main/Home/Recommendation/MainHomeRecommendationViewModel.swift`
- 이동/정리: `SodaLive/Sources/V2/Main/Home/MainHomeViewModel.swift` - 이동/정리: `SodaLive/Sources/V2/Main/Home/MainHomeViewModel.swift`
@@ -151,7 +151,7 @@
### Phase 5: 추천 상세 진입 데이터/라우팅 guard ### Phase 5: 추천 상세 진입 데이터/라우팅 guard
- [ ] **Task 5.1: 추천 탭 상세 진입 guard 적용** - [x] **Task 5.1: 추천 탭 상세 진입 guard 적용**
- 대상 파일: - 대상 파일:
- 수정: `SodaLive/Sources/V2/Main/MainView.swift` - 수정: `SodaLive/Sources/V2/Main/MainView.swift`
- 생성/보완: `SodaLive/Sources/V2/Main/Home/MainHomeView.swift` - 생성/보완: `SodaLive/Sources/V2/Main/Home/MainHomeView.swift`
@@ -174,7 +174,7 @@
### Phase 6: 홈 탭 shell과 탭별 루트 연결 ### Phase 6: 홈 탭 shell과 탭별 루트 연결
- [ ] **Task 6.1: 추천/랭킹/팔로잉 하위 폴더와 루트 View 준비** - [x] **Task 6.1: 추천/랭킹/팔로잉 하위 폴더와 루트 View 준비**
- 대상 파일: - 대상 파일:
- 생성/보완: `SodaLive/Sources/V2/Main/Home/Recommendation/MainHomeRecommendationView.swift` - 생성/보완: `SodaLive/Sources/V2/Main/Home/Recommendation/MainHomeRecommendationView.swift`
- 생성/보완: `SodaLive/Sources/V2/Main/Home/Ranking/MainHomeRankingView.swift` - 생성/보완: `SodaLive/Sources/V2/Main/Home/Ranking/MainHomeRankingView.swift`
@@ -187,7 +187,7 @@
- 실행 명령: `find SodaLive/Sources/V2/Main/Home -maxdepth 3 -type f | sort` - 실행 명령: `find SodaLive/Sources/V2/Main/Home -maxdepth 3 -type f | sort`
- 기대 결과: `Recommendation`, `Ranking`, `Following` 하위 폴더의 루트 View 파일이 확인된다. - 기대 결과: `Recommendation`, `Ranking`, `Following` 하위 폴더의 루트 View 파일이 확인된다.
- [ ] **Task 6.2: MainHomeView 탭 shell 작성/보완** - [x] **Task 6.2: MainHomeView 탭 shell 작성/보완**
- 대상 파일: - 대상 파일:
- 생성/보완: `SodaLive/Sources/V2/Main/Home/MainHomeView.swift` - 생성/보완: `SodaLive/Sources/V2/Main/Home/MainHomeView.swift`
- 확인: `SodaLive/Sources/V2/Component/HomeTitleBar.swift` - 확인: `SodaLive/Sources/V2/Component/HomeTitleBar.swift`
@@ -199,7 +199,7 @@
- 실행 명령: `rg "struct MainHomeView|HomeTitleBar|MainHomeRecommendationView|MainHomeRankingView|MainHomeFollowingView|MainTabBarView|fetchRecommendations" SodaLive/Sources/V2/Main/Home/MainHomeView.swift SodaLive/Sources/V2/Main/MainView.swift` - 실행 명령: `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`를 직접 호출하지 않으며, 탭바 중복 생성은 없어야 한다. - 기대 결과: `MainHomeView`는 탭별 루트 View를 조합하고, `fetchRecommendations`를 직접 호출하지 않으며, 탭바 중복 생성은 없어야 한다.
- [ ] **Task 6.3: MainView 홈 탭 연결** - [x] **Task 6.3: MainView 홈 탭 연결**
- 대상 파일: - 대상 파일:
- 수정: `SodaLive/Sources/V2/Main/MainView.swift` - 수정: `SodaLive/Sources/V2/Main/MainView.swift`
- 작업 내용: - 작업 내용:
@@ -442,6 +442,26 @@
## 검증 기록 ## 검증 기록
### 2026-06-26 Phase 4~6 구현 완료
- 목적: 추천 ViewModel 데이터 로딩 계층을 `Recommendation` 하위로 정리하고, 추천 탭 상세 진입 callback guard와 홈 탭 shell/루트 View를 연결
- 수행 내용:
- `MainHomeViewModel.swift``SodaLive/Sources/V2/Main/Home/Recommendation/MainHomeRecommendationViewModel.swift`로 이동하고 `MainHomeRecommendationViewModel`로 이름 변경
- `MainHomeRecommendationView`를 추가해 추천 API 로딩/에러 toast 상태를 추천 탭 루트에서 관리하도록 구성
- `MainHomeView`를 추가해 `HomeTitleBar`, 추천/랭킹/팔로잉 `TextTabBar`, 탭별 루트 View 조합만 담당하도록 구성
- `MainHomeRankingView`, `MainHomeFollowingView`는 이번 추천 API 범위 밖이므로 최소 placeholder 루트 View만 추가
- `MainView``.home` 분기를 `MainHomeView`로 교체하고, 추천 상세 진입 callback을 기존 `token`, `auth`, `pendingAction`, `isShowAuthConfirmView`, `authView` 인프라를 재사용하는 guard helper에 연결
- `HomeLiveItem`에 성인 여부 필드가 없어 라이브 항목 성인 여부는 임의 판단하지 않고, 향후 성인 여부가 명확한 호출에서 `requiresAdultContent`를 사용할 수 있게 중앙 helper만 준비
- 신규/이동 Swift 파일을 `SodaLive.xcodeproj/project.pbxproj` 빌드 대상에 포함
- 검증:
- `rg "final class MainHomeRecommendationViewModel|fetchRecommendations|HomeRecommendationResponse|MainHomeRecommendationRepository|isLoading|errorMessage" SodaLive/Sources/V2/Main/Home` 실행, 추천 API 로딩/에러/응답 상태가 `MainHomeRecommendationViewModel` 중심으로 검색됨 확인
- `rg "pendingAction|isShowAuthConfirmView|setPendingContentSettingsGuideMessage|adultContentEnableGuide|contentViewSettings|MainHomeView\(|MainHomeRecommendationView\(" SodaLive/Sources/V2/Main/MainView.swift SodaLive/Sources/V2/Main/Home` 실행, 신규 추천 탭 callback이 `MainView` guard 인프라를 재사용함 확인
- `find SodaLive/Sources/V2/Main/Home -maxdepth 3 -type f | sort` 실행, `Recommendation`, `Ranking`, `Following` 하위 루트 파일 확인
- `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`를 직접 호출하지 않음 확인
- `rg "case \\.home|MainHomeView|MainPlaceholderTabView" SodaLive/Sources/V2/Main/MainView.swift` 실행, `.home``MainHomeView`를 표시하고 다른 placeholder 탭은 유지됨 확인
- `plutil -lint SodaLive.xcodeproj/project.pbxproj` 실행, `OK` 확인
- `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build` 실행, `BUILD SUCCEEDED` 확인
### 2026-06-26 Phase 1~3 코드 리뷰 보완 ### 2026-06-26 Phase 1~3 코드 리뷰 보완
- 목적: Phase 2.2 리뷰에서 지적된 과거 추정 응답 필드 혼재를 제거하고, 추천 전용 request 모델 위치를 `Recommendation/Models` 구조로 일치 - 목적: Phase 2.2 리뷰에서 지적된 과거 추정 응답 필드 혼재를 제거하고, 추천 전용 request 모델 위치를 `Recommendation/Models` 구조로 일치