Compare commits
5 Commits
a6e3f71ca5
...
14817ef344
| Author | SHA1 | Date | |
|---|---|---|---|
| 14817ef344 | |||
| 96b385342a | |||
| 2d97328eb7 | |||
| 6393a62410 | |||
| d074f98d24 |
@@ -63,8 +63,8 @@ android {
|
||||
applicationId "kr.co.vividnext.sodalive"
|
||||
minSdk 23
|
||||
targetSdk 35
|
||||
versionCode 234
|
||||
versionName "1.54.0"
|
||||
versionCode 237
|
||||
versionName "1.54.1"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
|
||||
@@ -181,6 +181,8 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
|
||||
private var screenRecordingCallback: Any? = null
|
||||
|
||||
private var isHost = false
|
||||
private var isStaff = false
|
||||
private var isCaptureRecordingAvailable = false
|
||||
|
||||
private var isAvailableLikeHeart = false
|
||||
private var buttonPosition = IntArray(2)
|
||||
@@ -419,7 +421,7 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
|
||||
)
|
||||
|
||||
// 포그라운드 진입 시 API 레벨별 캡처/녹화 감지를 시작한다.
|
||||
syncCaptureSecurityPolicyByRole()
|
||||
syncCaptureSecurityPolicy()
|
||||
|
||||
if (this::layoutManager.isInitialized) {
|
||||
layoutManager.scrollToPosition(chatAdapter.itemCount - 1)
|
||||
@@ -1294,8 +1296,9 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
|
||||
}
|
||||
}
|
||||
|
||||
isHost = response.creatorId == SharedPreferenceManager.userId
|
||||
syncCaptureSecurityPolicyByRole()
|
||||
isCaptureRecordingAvailable = response.isCaptureRecordingAvailable
|
||||
syncRoomRoleState(response)
|
||||
syncCaptureSecurityPolicy()
|
||||
binding.tvChatFreezeSwitch.visibility = if (isHost) {
|
||||
View.VISIBLE
|
||||
} else {
|
||||
@@ -1645,8 +1648,24 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
|
||||
}, 100)
|
||||
}
|
||||
|
||||
private fun syncCaptureSecurityPolicyByRole() {
|
||||
if (isHost) {
|
||||
private fun syncRoomRoleState(response: GetRoomInfoResponse) {
|
||||
val myUserId = SharedPreferenceManager.userId
|
||||
isHost = response.creatorId == myUserId
|
||||
isStaff = response.managerList.any { member ->
|
||||
member.id == myUserId
|
||||
}
|
||||
}
|
||||
|
||||
private fun hasCapturePermissionByRole(): Boolean {
|
||||
return isHost || isStaff
|
||||
}
|
||||
|
||||
private fun hasCapturePermissionByPolicy(): Boolean {
|
||||
return isCaptureRecordingAvailable || hasCapturePermissionByRole()
|
||||
}
|
||||
|
||||
private fun syncCaptureSecurityPolicy() {
|
||||
if (hasCapturePermissionByPolicy()) {
|
||||
window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
|
||||
unregisterScreenRecordingCallback()
|
||||
clearCapturePrivacyMuteState()
|
||||
@@ -1719,7 +1738,7 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
|
||||
}
|
||||
|
||||
private fun syncCapturePrivacyMuteState() {
|
||||
val shouldMute = !isHost && isScreenRecordingActive
|
||||
val shouldMute = !hasCapturePermissionByPolicy() && isScreenRecordingActive
|
||||
if (isCapturePrivacyMuted == shouldMute) {
|
||||
return
|
||||
}
|
||||
@@ -1800,8 +1819,6 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
|
||||
requestType = LiveRoomRequestType.CHANGE_LISTENER
|
||||
) {
|
||||
if (isFromManager) {
|
||||
viewModel.getRoomInfo(roomId)
|
||||
setManagerMessage()
|
||||
releaseManagerMessageToPeer(userId = peerId)
|
||||
|
||||
handler.post {
|
||||
@@ -2318,6 +2335,7 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
|
||||
) {
|
||||
setAudience()
|
||||
viewModel.getRoomInfo(roomId)
|
||||
setManagerMessage()
|
||||
|
||||
if (roomUserProfileDialog.isShowing()) {
|
||||
viewModel.getUserProfile(
|
||||
|
||||
@@ -22,5 +22,6 @@ data class CreateLiveRoomRequest(
|
||||
@SerializedName("menuPanId") val menuPanId: Long = 0,
|
||||
@SerializedName("menuPan") val menuPan: String = "",
|
||||
@SerializedName("isActiveMenuPan") val isActiveMenuPan: Boolean = false,
|
||||
@SerializedName("isAvailableJoinCreator") val isAvailableJoinCreator: Boolean = true
|
||||
@SerializedName("isAvailableJoinCreator") val isAvailableJoinCreator: Boolean = true,
|
||||
@SerializedName("isCaptureRecordingAvailable") val isCaptureRecordingAvailable: Boolean = false
|
||||
)
|
||||
|
||||
@@ -370,6 +370,14 @@ class LiveRoomCreateActivity : BaseActivity<ActivityLiveRoomCreateBinding>(
|
||||
binding.llAvailableJoinCreatorN.setOnClickListener {
|
||||
viewModel.setAvailableJoinCreator(false)
|
||||
}
|
||||
|
||||
binding.llCaptureRecordingAvailableY.setOnClickListener {
|
||||
viewModel.setCaptureRecordingAvailable(true)
|
||||
}
|
||||
|
||||
binding.llCaptureRecordingAvailableN.setOnClickListener {
|
||||
viewModel.setCaptureRecordingAvailable(false)
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
@@ -582,6 +590,46 @@ class LiveRoomCreateActivity : BaseActivity<ActivityLiveRoomCreateBinding>(
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.isCaptureRecordingAvailableLiveData.observe(this) {
|
||||
if (it) {
|
||||
binding.ivCaptureRecordingAvailableN.visibility = View.GONE
|
||||
binding.llCaptureRecordingAvailableN.setBackgroundResource(R.drawable.bg_round_corner_6_7_13181b)
|
||||
binding.tvCaptureRecordingAvailableN.setTextColor(
|
||||
ContextCompat.getColor(
|
||||
applicationContext,
|
||||
R.color.color_3bb9f1
|
||||
)
|
||||
)
|
||||
|
||||
binding.ivCaptureRecordingAvailableY.visibility = View.VISIBLE
|
||||
binding.llCaptureRecordingAvailableY.setBackgroundResource(R.drawable.bg_round_corner_6_7_3bb9f1)
|
||||
binding.tvCaptureRecordingAvailableY.setTextColor(
|
||||
ContextCompat.getColor(
|
||||
applicationContext,
|
||||
R.color.color_eeeeee
|
||||
)
|
||||
)
|
||||
} else {
|
||||
binding.ivCaptureRecordingAvailableY.visibility = View.GONE
|
||||
binding.llCaptureRecordingAvailableY.setBackgroundResource(R.drawable.bg_round_corner_6_7_13181b)
|
||||
binding.tvCaptureRecordingAvailableY.setTextColor(
|
||||
ContextCompat.getColor(
|
||||
applicationContext,
|
||||
R.color.color_3bb9f1
|
||||
)
|
||||
)
|
||||
|
||||
binding.ivCaptureRecordingAvailableN.visibility = View.VISIBLE
|
||||
binding.llCaptureRecordingAvailableN.setBackgroundResource(R.drawable.bg_round_corner_6_7_3bb9f1)
|
||||
binding.tvCaptureRecordingAvailableN.setTextColor(
|
||||
ContextCompat.getColor(
|
||||
applicationContext,
|
||||
R.color.color_eeeeee
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldShowAdultRestrictionSetting()) {
|
||||
binding.llAgeAll.setOnClickListener {
|
||||
viewModel.setAdult(false)
|
||||
|
||||
@@ -88,6 +88,10 @@ class LiveRoomCreateViewModel(
|
||||
val isAvailableJoinCreatorLiveData: LiveData<Boolean>
|
||||
get() = _isAvailableJoinCreatorLiveData
|
||||
|
||||
private val _isCaptureRecordingAvailableLiveData = MutableLiveData(false)
|
||||
val isCaptureRecordingAvailableLiveData: LiveData<Boolean>
|
||||
get() = _isCaptureRecordingAvailableLiveData
|
||||
|
||||
private val _menuLiveData = MutableLiveData("")
|
||||
val menuLiveData: LiveData<String>
|
||||
get() = _menuLiveData
|
||||
@@ -169,7 +173,8 @@ class LiveRoomCreateViewModel(
|
||||
""
|
||||
},
|
||||
isActiveMenuPan = _isActivateMenuLiveData.value!!,
|
||||
isAvailableJoinCreator = _isAvailableJoinCreatorLiveData.value!!
|
||||
isAvailableJoinCreator = _isAvailableJoinCreatorLiveData.value!!,
|
||||
isCaptureRecordingAvailable = _isCaptureRecordingAvailableLiveData.value!!
|
||||
)
|
||||
|
||||
val requestJson = Gson().toJson(request)
|
||||
@@ -296,6 +301,10 @@ class LiveRoomCreateViewModel(
|
||||
_isAvailableJoinCreatorLiveData.value = isAvailableJoinCreator
|
||||
}
|
||||
|
||||
fun setCaptureRecordingAvailable(isCaptureRecordingAvailable: Boolean) {
|
||||
_isCaptureRecordingAvailableLiveData.value = isCaptureRecordingAvailable
|
||||
}
|
||||
|
||||
fun getRecentInfo(onSuccess: (GetRecentRoomInfoResponse) -> Unit) {
|
||||
_isLoading.value = true
|
||||
compositeDisposable.add(
|
||||
|
||||
@@ -27,6 +27,7 @@ data class GetRoomInfoResponse(
|
||||
@SerializedName("menuPan") val menuPan: String,
|
||||
@SerializedName("creatorLanguageCode") val creatorLanguageCode: String?,
|
||||
@SerializedName("isActiveRoulette") val isActiveRoulette: Boolean,
|
||||
@SerializedName("isCaptureRecordingAvailable") val isCaptureRecordingAvailable: Boolean = false,
|
||||
@SerializedName("isChatFrozen") val isChatFrozen: Boolean = false,
|
||||
@SerializedName("isPrivateRoom") val isPrivateRoom: Boolean,
|
||||
@SerializedName("password") val password: String? = null
|
||||
|
||||
@@ -765,6 +765,82 @@
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="13.3dp"
|
||||
android:layout_marginTop="33.3dp"
|
||||
android:fontFamily="@font/bold"
|
||||
android:lineSpacingExtra="5sp"
|
||||
android:text="@string/screen_live_room_create_capture_recording_label"
|
||||
android:textColor="@color/color_eeeeee"
|
||||
android:textSize="16.7sp" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="13.3dp"
|
||||
android:layout_marginTop="13.3dp"
|
||||
android:baselineAligned="false">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/ll_capture_recording_available_y"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:background="@drawable/bg_round_corner_6_7_13181b"
|
||||
android:gravity="center"
|
||||
android:paddingVertical="14.3dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_capture_recording_available_y"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="6.7dp"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/ic_select_check"
|
||||
android:visibility="gone" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_capture_recording_available_y"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="@font/bold"
|
||||
android:text="@string/screen_live_room_create_creator_join_available"
|
||||
android:textColor="@color/color_3bb9f1"
|
||||
android:textSize="14.7sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/ll_capture_recording_available_n"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="13.3dp"
|
||||
android:layout_weight="1"
|
||||
android:background="@drawable/bg_round_corner_6_7_13181b"
|
||||
android:gravity="center"
|
||||
android:paddingVertical="14.3dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_capture_recording_available_n"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="6.7dp"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/ic_select_check"
|
||||
android:visibility="gone" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_capture_recording_available_n"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="@font/bold"
|
||||
android:text="@string/screen_live_room_create_creator_join_unavailable"
|
||||
android:textColor="@color/color_3bb9f1"
|
||||
android:textSize="14.7sp" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/ll_set_adult"
|
||||
android:layout_width="match_parent"
|
||||
|
||||
@@ -635,6 +635,7 @@
|
||||
<string name="screen_live_room_create_creator_join_label">Creator entry</string>
|
||||
<string name="screen_live_room_create_creator_join_available">Allowed</string>
|
||||
<string name="screen_live_room_create_creator_join_unavailable">Not allowed</string>
|
||||
<string name="screen_live_room_create_capture_recording_label">Capture/recording setting</string>
|
||||
<string name="screen_live_room_create_age_label">Age limit</string>
|
||||
<string name="screen_live_room_create_age_all">All ages</string>
|
||||
<string name="screen_live_room_create_gender_restriction_label">Gender restriction</string>
|
||||
|
||||
@@ -635,6 +635,7 @@
|
||||
<string name="screen_live_room_create_creator_join_label">クリエイター入室設定</string>
|
||||
<string name="screen_live_room_create_creator_join_available">可能</string>
|
||||
<string name="screen_live_room_create_creator_join_unavailable">不可</string>
|
||||
<string name="screen_live_room_create_capture_recording_label">キャプチャ/録画設定</string>
|
||||
<string name="screen_live_room_create_age_label">年齢制限</string>
|
||||
<string name="screen_live_room_create_age_all">全年齢</string>
|
||||
<string name="screen_live_room_create_gender_restriction_label">性別制限</string>
|
||||
|
||||
@@ -634,6 +634,7 @@
|
||||
<string name="screen_live_room_create_creator_join_label">크리에이터 입장 설정</string>
|
||||
<string name="screen_live_room_create_creator_join_available">가능</string>
|
||||
<string name="screen_live_room_create_creator_join_unavailable">불가능</string>
|
||||
<string name="screen_live_room_create_capture_recording_label">캡쳐/녹화 설정</string>
|
||||
<string name="screen_live_room_create_age_label">연령 제한</string>
|
||||
<string name="screen_live_room_create_age_all">전체 연령</string>
|
||||
<string name="screen_live_room_create_gender_restriction_label">성별 제한</string>
|
||||
|
||||
23
docs/20260330_라이브룸스탭해제미갱신수정.md
Normal file
23
docs/20260330_라이브룸스탭해제미갱신수정.md
Normal file
@@ -0,0 +1,23 @@
|
||||
# 20260330 라이브룸 스탭 해제 미갱신 수정
|
||||
|
||||
## 작업 체크리스트
|
||||
- [x] 스탭 지정/해제 데이터 흐름을 확인해 해제 시점 UI 갱신 누락 원인을 특정한다.
|
||||
QA: 스탭 해제 직후 `LiveRoomProfileListAdapter` 항목에서 스탭 표시가 사라지는지 코드 경로로 검증.
|
||||
- [x] 원인에 맞춰 최소 수정으로 갱신 로직을 반영한다.
|
||||
QA: 스탭 지정 시 표시 유지 + 스탭 해제 시 즉시 표시 해제 로직이 함께 성립.
|
||||
- [x] 변경 파일 진단/테스트/빌드 검증을 수행한다.
|
||||
QA: `lsp_diagnostics` 무오류, 관련 테스트 통과, `:app:assembleDebug` 성공.
|
||||
|
||||
## 검증 기록
|
||||
- 2026-03-30
|
||||
- 무엇: `LiveRoomRequestType.CHANGE_LISTENER` 수신 후 `setListener` 성공 콜백에서 `setManagerMessage()`를 추가하고, 요청자 쪽의 조기 `getRoomInfo`/`setManagerMessage` 호출을 제거해 역할 변경 완료 시점에만 전체 동기화가 일어나도록 수정했다.
|
||||
- 왜: 기존에는 스탭 해제 요청 직후 방장이 먼저 목록을 새로고침해 구 상태를 다시 받아오고, 실제 역할 전환 완료 시점의 재동기화가 늦어 `LiveRoomProfileListAdapter`에서 스탭 표시가 남는 race가 발생할 수 있었다.
|
||||
- 어떻게:
|
||||
- 수정 파일: `app/src/main/java/kr/co/vividnext/sodalive/live/room/LiveRoomActivity.kt`
|
||||
- 실행 명령: `./gradlew :app:testDebugUnitTest :app:assembleDebug`
|
||||
- 결과: `BUILD SUCCESSFUL` (2회 재실행으로 수정 후 동일 확인)
|
||||
- 실행 명령: `./gradlew :app:lintDebug`
|
||||
- 결과: `AndroidManifest.xml`의 `com.facebook.FacebookActivity` MissingClass 포함 기존 lint 오류(총 20 errors)로 실패
|
||||
- 실행 명령: `./gradlew :app:ktlintCheck`
|
||||
- 결과: `LiveRoomActivity.kt` 전역(기존 구간 포함) ktlint 위반 다수로 실패
|
||||
- 진단 도구: Kotlin(`.kt`)용 LSP 서버 미구성으로 `lsp_diagnostics` 실행 불가 확인
|
||||
51
docs/20260330_라이브룸캡쳐녹화스탭권한확장.md
Normal file
51
docs/20260330_라이브룸캡쳐녹화스탭권한확장.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# 20260330 라이브룸 캡쳐/녹화 스탭 권한 확장
|
||||
|
||||
## 구현 체크리스트
|
||||
- [x] 화면 캡쳐/녹화 정책이 적용되는 분기(`isHost`, `FLAG_SECURE`, 녹화 감지 mute)와 스탭 판별 소스를 확인한다. (QA: `LiveRoomActivity`에서 정책 함수/호출 지점과 스탭 판별 API 확인)
|
||||
- [x] 화면 캡쳐/녹화 허용 대상을 방장에서 방장+스탭으로 확장한다. (QA: 정책 분기에서 방장/스탭 공통 허용, 일반 청취자 차단 유지 확인)
|
||||
- [x] 정책 변경 후 연관 UI/상태 동기화 로직의 정합성을 검증한다. (QA: 룸 정보 수신 시점과 포그라운드 전환 시점 모두에서 동일 정책 적용 확인)
|
||||
- [x] 진단/테스트/빌드 및 수동 QA를 수행하고 결과를 기록한다. (QA: 실행 명령과 결과 로그, 수동 검증 결과 기록)
|
||||
|
||||
## 검증 기록
|
||||
- 2026-03-30
|
||||
- 무엇: 작업 계획 문서를 생성하고 요청 범위를 방장+스탭 캡쳐/녹화 허용으로 고정했다.
|
||||
- 왜: 구현 전 체크리스트 기반으로 정확한 범위와 검증 기준을 고정해 과/미구현을 방지하기 위해서다.
|
||||
- 어떻게:
|
||||
- `docs/20260330_라이브룸캡쳐녹화스탭권한확장.md` 신규 작성
|
||||
- 체크리스트에 QA 기준(정책 분기/동기화/검증)을 명시
|
||||
|
||||
- 2026-03-30
|
||||
- 무엇: 캡쳐/녹화 권한 및 스탭 판별 로직을 병렬 탐색하고 변경 지점을 확정했다.
|
||||
- 왜: 기존 정책이 방장(`isHost`) 기준으로 고정되어 있어 스탭 동적 권한 부여/회수 상황을 반영하려면 정확한 동기화 지점을 먼저 확보해야 했기 때문이다.
|
||||
- 어떻게:
|
||||
- 내부 탐색(`explore`):
|
||||
- `bg_dd2bd641` (캡쳐 보안 플로우 맵)
|
||||
- `bg_9c629910` (host-only guard 분류)
|
||||
- `bg_649ec034` (스탭 판별 canonical predicate)
|
||||
- 외부 탐색(`librarian`):
|
||||
- `bg_22584dd7` (OSS 역할 기반 사례 탐색 시도)
|
||||
- `bg_7d687143` (Android `FLAG_SECURE` 공식 레퍼런스 탐색)
|
||||
- 직접 검색:
|
||||
- `grep`으로 `syncCaptureSecurityPolicyByRole`, `syncCapturePrivacyMuteState`, `isEqualToManagerId`, `managerList` 교차 확인
|
||||
- `ast_grep_search`로 `if (!isHost)` guard 위치 점검
|
||||
- `rg`는 로컬 환경 미설치(`command -v rg` 결과 없음)로 `grep`/`ast-grep` 기반으로 대체 탐색
|
||||
|
||||
- 2026-03-30
|
||||
- 무엇: `LiveRoomActivity`의 캡쳐/녹화 허용 대상을 방장+스탭으로 확장했다.
|
||||
- 왜: 스탭 권한은 입장 시 고정이 아니라 라이브 중간에 부여/해제되므로, 룸 정보 갱신 시점마다 권한을 재계산해 정책을 즉시 재동기화해야 하기 때문이다.
|
||||
- 어떻게:
|
||||
- `isStaff` 상태 필드와 `syncRoomRoleState(response)`를 추가해 `response.managerList` 기반으로 현재 사용자 스탭 여부를 계산
|
||||
- `viewModel.roomInfoLiveData.observe`에서 `syncRoomRoleState(response)` 직후 `syncCaptureSecurityPolicyByRole()`를 호출하도록 변경
|
||||
- `hasCapturePermissionByRole()` 헬퍼를 추가하고 캡쳐 정책/녹화 mute 계산을 `isHost` 단일 조건에서 `isHost || isStaff` 조건으로 확장
|
||||
- `syncCaptureSecurityPolicyByRole()`: 방장+스탭은 `FLAG_SECURE` 해제/녹화 콜백 해제
|
||||
- `syncCapturePrivacyMuteState()`: 일반 청취자만 녹화 감지 시 강제 mute
|
||||
|
||||
- 2026-03-30
|
||||
- 무엇: 진단/테스트/빌드/수동 QA를 수행했다.
|
||||
- 왜: 변경이 컴파일 안정성과 요청 동작(방장+스탭 허용, 일반 차단, 중간 권한 변경 반영)을 만족하는지 증거로 확인하기 위해서다.
|
||||
- 어떻게:
|
||||
- LSP 진단: `.kt` 서버 미구성으로 `lsp_diagnostics` 실행 불가(환경 제약 확인)
|
||||
- 실행 명령: `./gradlew :app:testDebugUnitTest :app:assembleDebug`
|
||||
- 결과: `BUILD SUCCESSFUL`
|
||||
- 수동 QA 명령: `python3` 검증 스크립트로 정책 함수/역할 동기화/조건식을 점검
|
||||
- 수동 QA 결과: `MANUAL_QA_PASS: host+staff 캡쳐/녹화 허용 분기와 런타임 재동기화 경로를 소스 기준으로 확인했습니다.`
|
||||
33
docs/20260330_라이브캡쳐녹화가능여부반영.md
Normal file
33
docs/20260330_라이브캡쳐녹화가능여부반영.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# 20260330 라이브 캡쳐/녹화 가능여부 반영
|
||||
|
||||
## 구현 체크리스트
|
||||
- [x] 라이브 정보 응답 모델(`GetRoomInfoResponse`)에 `isCaptureRecordingAvailable` 필드를 추가한다.
|
||||
- QA: `GetRoomInfoResponse` 역직렬화 시 필드가 누락되어도 기본값으로 동작한다.
|
||||
- [x] `LiveRoomActivity`의 캡쳐/녹화 정책을 `isCaptureRecordingAvailable || isHost || isStaff` 기준으로 적용한다.
|
||||
- QA: 정책 함수와 `FLAG_SECURE`/녹화 콜백/강제 음소거 계산이 동일 기준으로 동작한다.
|
||||
- [x] 라이브 생성 경로(`LiveRoomCreateActivity`, `LiveRoomCreateViewModel`, `CreateLiveRoomRequest`)에 설정 UI/상태/요청 필드를 추가한다.
|
||||
- QA: 생성 화면 선택값이 `CreateLiveRoomRequest.isCaptureRecordingAvailable`로 전송된다.
|
||||
- [x] 라이브 수정 경로에서는 해당 설정을 변경하지 않도록 유지한다(생성 시에만 설정).
|
||||
- QA: `LiveRoomInfoEditDialog`/`EditLiveRoomInfoRequest`에 신규 항목을 추가하지 않는다.
|
||||
- [x] 변경 파일에 대해 정적 진단/테스트/빌드를 수행하고 결과를 기록한다.
|
||||
- QA: `lsp_diagnostics` 오류 0, 관련 테스트 및 빌드 명령 성공.
|
||||
|
||||
## 검증 기록
|
||||
- `lsp_diagnostics` (`.kt`)
|
||||
- 무엇/왜/어떻게: 수정한 Kotlin 파일 정적 진단을 도구로 확인해 타입/문법 오류를 사전 점검했다.
|
||||
- 실행 명령: `lsp_diagnostics(filePath=<modified .kt>, severity="all")`
|
||||
- 결과: 현재 실행 환경에 Kotlin LSP가 없어(`No LSP server configured for extension: .kt`) 도구 기반 진단을 수행할 수 없었다.
|
||||
- Ktlint + 테스트 + 빌드 1차
|
||||
- 무엇/왜/어떻게: 코드 스타일/단위 테스트/디버그 빌드를 한 번에 검증해 회귀 가능성을 확인했다.
|
||||
- 실행 명령: `./gradlew :app:ktlintCheck :app:testDebugUnitTest :app:assembleDebug`
|
||||
- 결과: `:app:ktlintMainSourceSetCheck` 실패. 기존 `LiveRoomActivity.kt` 전역 스타일 위반(다수 라인)과 기존 `LiveRoomCreateViewModel.kt` unused import 경고로 실패했고, 이번 변경 기능 자체 컴파일/테스트 실패는 아님.
|
||||
- 테스트 + 빌드 2차
|
||||
- 무엇/왜/어떻게: 스타일 태스크 제외 후 기능 반영 코드가 실제로 컴파일/테스트를 통과하는지 재검증했다.
|
||||
- 실행 명령: `./gradlew :app:testDebugUnitTest :app:assembleDebug`
|
||||
- 결과: `BUILD SUCCESSFUL`.
|
||||
- 수동 기능 검증(정책 반영 범위 확인)
|
||||
- 무엇/왜/어떻게: 생성 전용 제약과 라이브룸 정책 반영 범위를 텍스트 검색으로 직접 확인했다.
|
||||
- 실행 명령:
|
||||
- `grep("isCaptureRecordingAvailable", app/src/main/java/**/*.kt)`
|
||||
- `grep("isCaptureRecordingAvailable", app/src/main/java/.../live/room/update/*.kt)`
|
||||
- 결과: 신규 필드는 `GetRoomInfoResponse`, `CreateLiveRoomRequest`, `LiveRoomCreateViewModel`, `LiveRoomCreateActivity`, `LiveRoomActivity`에만 존재하며, 수정 경로(`live/room/update`)에는 미추가로 확인됨.
|
||||
Reference in New Issue
Block a user