From 0fa036f04d391267bf270d9028351cd36a5cfef6 Mon Sep 17 00:00:00 2001 From: Yu Sung Date: Fri, 3 Jul 2026 22:02:11 +0900 Subject: [PATCH] =?UTF-8?q?feat(creator):=20=ED=94=8C=EB=A1=9C=ED=8C=85=20?= =?UTF-8?q?=EC=95=A1=EC=85=98=20=EC=A7=84=EC=9E=85=EC=9D=84=20=EC=97=B0?= =?UTF-8?q?=EA=B2=B0=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SodaLive/Sources/App/AppStep.swift | 2 +- .../Sources/Content/ContentListView.swift | 2 +- .../Content/Create/ContentCreateView.swift | 10 +++- SodaLive/Sources/ContentView.swift | 4 +- .../Profile/UserProfileContentView.swift | 2 +- SodaLive/Sources/Home/HomeTabView.swift | 2 +- .../CreatorChannelFloatingActionMenu.swift | 1 + .../CreatorChannel/CreatorChannelView.swift | 47 +++++++++++++++++++ .../plan-task.md | 11 +++-- 9 files changed, 69 insertions(+), 12 deletions(-) diff --git a/SodaLive/Sources/App/AppStep.swift b/SodaLive/Sources/App/AppStep.swift index d8ff766e..9ec6e40c 100644 --- a/SodaLive/Sources/App/AppStep.swift +++ b/SodaLive/Sources/App/AppStep.swift @@ -62,7 +62,7 @@ enum AppStep { case serviceCenter - case createContent + case createContent(onSuccess: (() -> Void)? = nil) case modifyContent(contentId: Int) diff --git a/SodaLive/Sources/Content/ContentListView.swift b/SodaLive/Sources/Content/ContentListView.swift index c0ad4b80..722d95bd 100644 --- a/SodaLive/Sources/Content/ContentListView.swift +++ b/SodaLive/Sources/Content/ContentListView.swift @@ -55,7 +55,7 @@ struct ContentListView: View { .cornerRadius(5.3) .padding(.top, 13.3) .padding(.horizontal, 13.3) - .onTapGesture { AppState.shared.setAppStep(step: .createContent) } + .onTapGesture { AppState.shared.setAppStep(step: .createContent()) } } HStack(spacing: 13.3) { diff --git a/SodaLive/Sources/Content/Create/ContentCreateView.swift b/SodaLive/Sources/Content/Create/ContentCreateView.swift index 9de3fbd8..feee0b61 100644 --- a/SodaLive/Sources/Content/Create/ContentCreateView.swift +++ b/SodaLive/Sources/Content/Create/ContentCreateView.swift @@ -10,6 +10,7 @@ import Kingfisher struct ContentCreateView: View { @Environment(\.locale) private var locale + let onSuccess: (() -> Void)? @StateObject var keyboardHandler = KeyboardHandler() @StateObject private var viewModel = ContentCreateViewModel() @@ -23,6 +24,10 @@ struct ContentCreateView: View { @State private var isShowSelectThemeView = false @State private var isShowSelectDateView = false @State private var isShowSelectTimeView = false + + init(onSuccess: (() -> Void)? = nil) { + self.onSuccess = onSuccess + } var body: some View { let normalizedCountryCode = UserDefaults @@ -655,7 +660,10 @@ struct ContentCreateView: View { title: I18n.CreateContent.uploadTitle, desc: I18n.CreateContent.uploadDescription, confirmButtonTitle: I18n.Common.confirm, - confirmButtonAction: { AppState.shared.back() }, + confirmButtonAction: { + onSuccess?() + AppState.shared.back() + }, cancelButtonTitle: "", cancelButtonAction: {} ) diff --git a/SodaLive/Sources/ContentView.swift b/SodaLive/Sources/ContentView.swift index e81709cc..47059fd5 100644 --- a/SodaLive/Sources/ContentView.swift +++ b/SodaLive/Sources/ContentView.swift @@ -162,8 +162,8 @@ struct AppStepLayerView: View { case .serviceCenter: ServiceCenterView() - case .createContent: - ContentCreateView() + case .createContent(let onSuccess): + ContentCreateView(onSuccess: onSuccess) case .liveReservationComplete(let response): LiveReservationCompleteView(reservationCompleteData: response) diff --git a/SodaLive/Sources/Explorer/Profile/UserProfileContentView.swift b/SodaLive/Sources/Explorer/Profile/UserProfileContentView.swift index 9884ad7a..af0865d9 100644 --- a/SodaLive/Sources/Explorer/Profile/UserProfileContentView.swift +++ b/SodaLive/Sources/Explorer/Profile/UserProfileContentView.swift @@ -46,7 +46,7 @@ struct UserProfileContentView: View { .frame(maxWidth: .infinity) .background(Color.button) .cornerRadius(12) - .onTapGesture { AppState.shared.setAppStep(step: .createContent) } + .onTapGesture { AppState.shared.setAppStep(step: .createContent()) } } else { collectionInfoView() } diff --git a/SodaLive/Sources/Home/HomeTabView.swift b/SodaLive/Sources/Home/HomeTabView.swift index 64af46f5..71cb28c6 100644 --- a/SodaLive/Sources/Home/HomeTabView.swift +++ b/SodaLive/Sources/Home/HomeTabView.swift @@ -408,7 +408,7 @@ struct HomeTabView: View { .padding(.trailing, 16.7) .padding(.bottom, 16.7) .onTapGesture { - AppState.shared.setAppStep(step: .createContent) + AppState.shared.setAppStep(step: .createContent()) } } diff --git a/SodaLive/Sources/V2/CreatorChannel/Components/CreatorChannelFloatingActionMenu.swift b/SodaLive/Sources/V2/CreatorChannel/Components/CreatorChannelFloatingActionMenu.swift index a01c7f2a..50030e43 100644 --- a/SodaLive/Sources/V2/CreatorChannel/Components/CreatorChannelFloatingActionMenu.swift +++ b/SodaLive/Sources/V2/CreatorChannel/Components/CreatorChannelFloatingActionMenu.swift @@ -70,6 +70,7 @@ struct CreatorChannelFloatingActionMenu: View { .padding(.bottom, SodaSpacing.s32) .animation(animation, value: isPresented) } + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .bottomTrailing) } private func floatingButton( diff --git a/SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift b/SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift index 4515dd1c..c06b723a 100644 --- a/SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift +++ b/SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift @@ -7,6 +7,7 @@ struct CreatorChannelView: View { @StateObject private var viewModel = CreatorChannelViewModel() @StateObject private var channelDonationViewModel = ChannelDonationViewModel() + @StateObject private var liveViewModel = LiveViewModel() @StateObject private var mypageViewModel = MyPageViewModel() @AppStorage("token") private var token: String = UserDefaults.string(forKey: UserDefaultsKey.token) @AppStorage("auth") private var auth: Bool = UserDefaults.bool(forKey: UserDefaultsKey.auth) @@ -15,6 +16,7 @@ struct CreatorChannelView: View { @State private var isShowChannelDonationDialog = false @State private var isShowAuthView = false @State private var isShowAuthConfirmView = false + @State private var isCreatorActionMenuPresented = false @State private var pendingAction: (() -> Void)? = nil @State private var payload = Payload() @@ -84,6 +86,15 @@ struct CreatorChannelView: View { LoadingView() } + if isOwnCreatorChannel && viewModel.selectedTab == .home && !viewModel.isLoading { + CreatorChannelFloatingActionMenu( + isPresented: $isCreatorActionMenuPresented, + onTapCommunityPost: showCommunityWrite, + onTapAudioContent: showAudioContentUpload, + onTapCreateLive: showCreateLive + ) + } + if isShowAuthConfirmView { authConfirmDialog } @@ -123,6 +134,11 @@ struct CreatorChannelView: View { } .sodaToast(isPresented: $viewModel.isShowPopup, message: viewModel.errorMessage, autohideIn: 1) .sodaToast(isPresented: $channelDonationViewModel.isShowPopup, message: channelDonationViewModel.errorMessage, autohideIn: 2) + .onChange(of: viewModel.selectedTab) { selectedTab in + if selectedTab != .home { + isCreatorActionMenuPresented = false + } + } } private func titleBar(backgroundProgress: CGFloat) -> some View { @@ -289,6 +305,37 @@ struct CreatorChannelView: View { AppState.shared.setAppStep(step: .creatorCommunityAll(creatorId: creatorId)) } + private func showCommunityWrite() { + isCreatorActionMenuPresented = false + AppState.shared.setAppStep( + step: .creatorCommunityWrite(onSuccess: { + viewModel.fetchHome(creatorId: creatorId) + }) + ) + } + + private func showAudioContentUpload() { + isCreatorActionMenuPresented = false + AppState.shared.setAppStep( + step: .createContent(onSuccess: { + viewModel.fetchHome(creatorId: creatorId) + }) + ) + } + + private func showCreateLive() { + isCreatorActionMenuPresented = false + AppState.shared.setAppStep(step: .createLive(timeSettingMode: .NOW, onSuccess: handleCreateLiveSuccess)) + } + + private func handleCreateLiveSuccess(response: CreateLiveRoomResponse) { + liveViewModel.getLiveMain() + if let _ = response.channelName { + liveViewModel.enterRoom(roomId: response.id!) + } + viewModel.fetchHome(creatorId: creatorId) + } + private func handleScheduleTap(type: CreatorActivityType, targetId: Int) { guard targetId > 0 else { return } diff --git a/docs/20260703_크리에이터_채널_본인_진입_플로팅_버튼/plan-task.md b/docs/20260703_크리에이터_채널_본인_진입_플로팅_버튼/plan-task.md index 1fa41822..87d54b50 100644 --- a/docs/20260703_크리에이터_채널_본인_진입_플로팅_버튼/plan-task.md +++ b/docs/20260703_크리에이터_채널_본인_진입_플로팅_버튼/plan-task.md @@ -128,7 +128,7 @@ ### Phase 3: CreatorChannelView overlay 연결과 액션 라우팅 -- [ ] **Task 3.1: floating menu 표시 상태 연결** +- [x] **Task 3.1: floating menu 표시 상태 연결** - 대상 파일: - 수정: `SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift` - 작업 내용: @@ -140,7 +140,7 @@ - 실행 명령: `rg "isCreatorActionMenuPresented|CreatorChannelFloatingActionMenu|isOwnCreatorChannel && viewModel\\.selectedTab == \\.home" SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift` - 기대 결과: 본인 채널 홈 탭에서만 floating menu가 연결된다. -- [ ] **Task 3.2: 오디오 콘텐츠 업로드 성공 callback 확장** +- [x] **Task 3.2: 오디오 콘텐츠 업로드 성공 callback 확장** - 대상 파일: - 수정: `SodaLive/Sources/App/AppStep.swift` - 수정: `SodaLive/Sources/ContentView.swift` @@ -157,7 +157,7 @@ - 실행 명령: `rg "createContent|ContentCreateView\\(|onSuccess|isShowCompletePopup|AppState\\.shared\\.back\\(\\)" SodaLive/Sources/App/AppStep.swift SodaLive/Sources/ContentView.swift SodaLive/Sources/Content/Create/ContentCreateView.swift` - 기대 결과: 기존 create content 진입은 유지되고, 업로드 성공 callback 전달과 호출 지점이 확인된다. -- [ ] **Task 3.3: 커뮤니티 글 올리기 액션 연결** +- [x] **Task 3.3: 커뮤니티 글 올리기 액션 연결** - 대상 파일: - 수정: `SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift` - 확인: `SodaLive/Sources/App/AppStep.swift` @@ -169,7 +169,7 @@ - 실행 명령: `rg "showCommunityWrite|creatorCommunityWrite\\(onSuccess:|fetchHome\\(creatorId: creatorId\\)" SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift` - 기대 결과: 커뮤니티 글 작성 진입과 작성 성공 후 홈 갱신이 확인된다. -- [ ] **Task 3.4: 오디오 콘텐츠 올리기 액션 연결** +- [x] **Task 3.4: 오디오 콘텐츠 올리기 액션 연결** - 대상 파일: - 수정: `SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift` - 확인: `SodaLive/Sources/App/AppStep.swift` @@ -182,7 +182,7 @@ - 실행 명령: `rg "showAudioContentUpload|createContent\\(onSuccess:|fetchHome\\(creatorId: creatorId\\)" SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift` - 기대 결과: 오디오 콘텐츠 업로드 진입과 업로드 성공 후 홈 갱신 연결이 확인된다. -- [ ] **Task 3.5: 라이브 만들기 액션 연결** +- [x] **Task 3.5: 라이브 만들기 액션 연결** - 대상 파일: - 수정: `SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift` - 확인: `SodaLive/Sources/Live/LiveView.swift` @@ -242,3 +242,4 @@ - 2026-07-03: 문서 작성 단계. 구현/빌드 검증은 아직 수행하지 않음. - 2026-07-03: Open Questions 결정 사항을 반영함. `ic_plus_no_bg`, 기존 `AppStep.createContent`, 기존 `LiveView.onCreateSuccess` 동일 처리, 커뮤니티/오디오 성공 후 홈 갱신 기준을 문서화함. - 2026-07-03: Phase 1, 2 구현 완료. 계획서의 Task 1.1~2.3 `rg` 검증을 수행했고, `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build` 성공을 확인함. 자동화 테스트 타깃은 `rg "Tests|XCTest|testTarget|PBXNativeTarget.*Tests|\.xctest|unit test|swift test" "SodaLive.xcodeproj" "SodaLive"` 검색 결과 확인되지 않아 별도 RED/GREEN 테스트는 추가하지 않음. +- 2026-07-03: Phase 3 구현 완료. Task 3.1~3.5 `rg` 검증을 수행했고, `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build` 성공을 확인함. 자동화 테스트 타깃 부재로 별도 RED/GREEN 테스트는 추가하지 않았으며, 실제 화면 수동 검증은 Phase 4.3에 남김.