From ec7e9cc71c5f181832c5fe98e8124253b79e9f94 Mon Sep 17 00:00:00 2001 From: Yu Sung Date: Mon, 30 Mar 2026 16:58:44 +0900 Subject: [PATCH] =?UTF-8?q?fix(live-room):=20=EC=8A=A4=ED=83=AD=20?= =?UTF-8?q?=EC=BA=A1=EC=B3=90=20=EB=85=B9=ED=99=94=20=EB=B3=B4=ED=98=B8=20?= =?UTF-8?q?=EC=98=88=EC=99=B8=EB=A5=BC=20=EB=B0=98=EC=98=81=ED=95=9C?= =?UTF-8?q?=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Live/Room/V2/LiveRoomViewV2.swift | 14 ++++++- docs/20260330_라이브룸스탭캡쳐녹화권한확장.md | 37 +++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 docs/20260330_라이브룸스탭캡쳐녹화권한확장.md diff --git a/SodaLive/Sources/Live/Room/V2/LiveRoomViewV2.swift b/SodaLive/Sources/Live/Room/V2/LiveRoomViewV2.swift index e6ac829..95b5585 100644 --- a/SodaLive/Sources/Live/Room/V2/LiveRoomViewV2.swift +++ b/SodaLive/Sources/Live/Room/V2/LiveRoomViewV2.swift @@ -70,8 +70,17 @@ struct LiveRoomViewV2: View { return creatorId == UserDefaults.int(forKey: .userId) } + private var isCurrentUserStaff: Bool { + guard let managerList = viewModel.liveRoomInfo?.managerList else { + return false + } + + let currentUserId = UserDefaults.int(forKey: .userId) + return managerList.contains { $0.id == currentUserId } + } + private var shouldEnforceScreenCaptureProtection: Bool { - !isCurrentUserHost + !(isCurrentUserHost || isCurrentUserStaff) } var body: some View { @@ -944,6 +953,9 @@ struct LiveRoomViewV2: View { .onChange(of: viewModel.liveRoomInfo?.creatorId) { _ in syncScreenCaptureProtectionState() } + .onChange(of: viewModel.liveRoomInfo?.managerList) { _ in + syncScreenCaptureProtectionState() + } .onChange(of: viewModel.isChatFrozenForCurrentUser) { isFrozen in if isFrozen { hideKeyboard() diff --git a/docs/20260330_라이브룸스탭캡쳐녹화권한확장.md b/docs/20260330_라이브룸스탭캡쳐녹화권한확장.md new file mode 100644 index 0000000..f3b1189 --- /dev/null +++ b/docs/20260330_라이브룸스탭캡쳐녹화권한확장.md @@ -0,0 +1,37 @@ +# 20260330 라이브룸 스탭 캡쳐/녹화 권한 확장 + +## 작업 체크리스트 +- [x] `LiveRoomViewV2`의 기존 캡쳐/녹화 권한 분기(방장 전용) 확인 +- [x] 스탭(`managerList`/`MANAGER`) 판별 방식 확인 및 적용 기준 확정 +- [x] 캡쳐/녹화 허용 대상을 방장+스탭으로 확장 +- [x] LSP/빌드/테스트/수동 QA 검증 수행 +- [x] 검증 결과 기록 + +## 수용 기준 (Acceptance Criteria) +- [x] 방장 또는 스탭인 경우 라이브룸 화면에서 캡쳐/녹화 보호가 적용되지 않는다. +- [x] 방장/스탭이 아닌 참여자는 기존 캡쳐/녹화 보호가 유지된다. +- [x] 캡쳐 감지 오버레이 및 강제 음소거 로직은 방장/스탭이 아닌 참여자에게만 동작한다. +- [x] 변경 파일 LSP 진단 오류가 없다. +- [x] Debug 빌드가 성공한다 (`SodaLive`, `SodaLive-dev`). + +## 검증 기록 +### 1차 검증 (2026-03-30) +- 무엇/왜/어떻게: + - 무엇: 라이브룸 캡쳐/녹화 보호 예외 대상을 `방장`에서 `방장 + 스탭`으로 확장하고, `managerList` 변경 시 보호 상태를 즉시 재동기화하도록 변경. + - 왜: 스탭 권한이 입장 시 고정이 아니라 방송 중 방장에 의해 동적으로 부여/해제되므로, 중간 권한 변경 시에도 캡쳐/녹화 허용 여부가 즉시 반영되어야 함. + - 어떻게: `LiveRoomViewV2`에 `isCurrentUserStaff` 계산 프로퍼티를 추가하고 `shouldEnforceScreenCaptureProtection`를 `!(isCurrentUserHost || isCurrentUserStaff)`로 변경. 또한 `.onChange(of: viewModel.liveRoomInfo?.managerList)`에서 `syncScreenCaptureProtectionState()`를 호출해 동적 권한 변경을 반영. +- 실행 명령: + - `lsp_diagnostics(filePath: /Users/klaus/Develop/sodalive/iOS/SodaLive/SodaLive/Sources/Live/Room/V2/LiveRoomViewV2.swift, severity: all)` + - `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" -configuration Debug build && xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build` + - `grep("\*\* BUILD SUCCEEDED \*\*", include: tool_d3db4bd4c001vPzVKsa2VZSVFE)` + - `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" test; xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" test` + - `grep("isCurrentUserStaff|shouldEnforceScreenCaptureProtection|onChange\\(of: viewModel.liveRoomInfo\\?\\.managerList\\)", LiveRoomViewV2.swift)` +- 결과: + - `lsp_diagnostics`: `No diagnostics found` + - 빌드: `SodaLive`, `SodaLive-dev` 모두 `** BUILD SUCCEEDED **` + - 테스트: `Scheme SodaLive is not currently configured for the test action.`, `Scheme SodaLive-dev is not currently configured for the test action.` + - 수동 QA(코드 경로): + - 권한 분기 확인: `shouldEnforceScreenCaptureProtection = !(isCurrentUserHost || isCurrentUserStaff)` + - 동적 권한 반영 확인: `.onChange(of: viewModel.liveRoomInfo?.managerList) { syncScreenCaptureProtectionState() }` + - 보호/해제 동작 확인: `syncScreenCaptureProtectionState()`에서 보호 비대상(방장/스탭)일 때 `isScreenCaptureProtected = false` 및 `releaseForcedCaptureMute()` 호출 + - 제한사항: CLI 환경 특성상 실기기에서 실제 스크린샷/화면녹화 버튼 조작 E2E는 수행하지 못했으며, 최종 사용자 시나리오는 실기기 확인이 필요.