feat(creator): 채널 공유 진입점을 추가한다
This commit is contained in:
+27
@@ -30,6 +30,7 @@ import kr.co.vividnext.sodalive.base.BaseActivity
|
||||
import kr.co.vividnext.sodalive.common.Constants
|
||||
import kr.co.vividnext.sodalive.common.LoadingDialog
|
||||
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
|
||||
import kr.co.vividnext.sodalive.common.Utils
|
||||
import kr.co.vividnext.sodalive.databinding.ActivityCreatorChannelBinding
|
||||
import kr.co.vividnext.sodalive.explorer.profile.creator_community.CreatorCommunityRepository
|
||||
import kr.co.vividnext.sodalive.explorer.profile.creator_community.all.CreatorCommunityPostMenuBottomSheetDialog
|
||||
@@ -230,6 +231,7 @@ class CreatorChannelActivity :
|
||||
private fun setupClickListeners() {
|
||||
binding.ivBack.setOnClickListener { finish() }
|
||||
binding.ivMore.setOnClickListener { onMoreClicked() }
|
||||
binding.ivShare.setOnClickListener { shareCurrentCreatorChannel() }
|
||||
binding.tvFollowerCount.setOnClickListener { onFollowerCountClicked() }
|
||||
binding.layoutFollowCapsule.setOnClickListener { onFollowCapsuleClicked() }
|
||||
binding.ivBell.setOnClickListener { onBellClicked() }
|
||||
@@ -291,9 +293,11 @@ class CreatorChannelActivity :
|
||||
binding.layoutFollowCapsule.visibility = View.GONE
|
||||
binding.ivBell.visibility = View.GONE
|
||||
binding.ivMore.visibility = View.GONE
|
||||
binding.ivShare.visibility = View.VISIBLE
|
||||
return
|
||||
}
|
||||
|
||||
binding.ivShare.visibility = View.GONE
|
||||
binding.layoutFollowCapsule.visibility = View.VISIBLE
|
||||
binding.ivMore.visibility = View.VISIBLE
|
||||
val titleBarState = CreatorChannelTitleBarState.from(
|
||||
@@ -435,12 +439,35 @@ class CreatorChannelActivity :
|
||||
if (header.isOwner) return
|
||||
|
||||
CreatorChannelMoreBottomSheet.newInstance().apply {
|
||||
onClickChannelShare = { shareCurrentCreatorChannel() }
|
||||
onClickBlock = { showUserBlockDialog() }
|
||||
onClickUserReport = { showUserReportDialog() }
|
||||
onClickProfileReport = { showProfileReportDialog() }
|
||||
}.show(supportFragmentManager, CreatorChannelMoreBottomSheet::class.java.simpleName)
|
||||
}
|
||||
|
||||
private fun shareCurrentCreatorChannel() {
|
||||
val header = currentHeader ?: return
|
||||
if (creatorId <= 0L) return
|
||||
|
||||
val nickname = header.nickname
|
||||
val params = mapOf(
|
||||
"af_dp" to "voiceon://",
|
||||
"deep_link_value" to "channel",
|
||||
"deep_link_sub5" to "$creatorId"
|
||||
)
|
||||
val shareUrl = Utils.createOneLinkUrl(params = params)
|
||||
val intent = Intent(Intent.ACTION_SEND)
|
||||
intent.type = "text/plain"
|
||||
intent.putExtra(Intent.EXTRA_TEXT, "소다라이브 ${nickname}님의 채널입니다.\n$shareUrl")
|
||||
|
||||
val shareIntent = Intent.createChooser(
|
||||
intent,
|
||||
getString(R.string.screen_user_profile_share_channel)
|
||||
)
|
||||
startActivity(shareIntent)
|
||||
}
|
||||
|
||||
private fun showUserBlockDialog() {
|
||||
val nickname = currentHeader?.nickname.orEmpty()
|
||||
V2ModalDialog(
|
||||
|
||||
+5
@@ -9,6 +9,7 @@ import kr.co.vividnext.sodalive.databinding.DialogCreatorChannelMoreBinding
|
||||
|
||||
class CreatorChannelMoreBottomSheet : BottomSheetDialogFragment() {
|
||||
|
||||
var onClickChannelShare: (() -> Unit)? = null
|
||||
var onClickBlock: (() -> Unit)? = null
|
||||
var onClickUserReport: (() -> Unit)? = null
|
||||
var onClickProfileReport: (() -> Unit)? = null
|
||||
@@ -29,6 +30,10 @@ class CreatorChannelMoreBottomSheet : BottomSheetDialogFragment() {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
val viewBinding = binding ?: return
|
||||
|
||||
viewBinding.tvChannelShare.setOnClickListener {
|
||||
dismiss()
|
||||
onClickChannelShare?.invoke()
|
||||
}
|
||||
viewBinding.tvUserBlock.setOnClickListener {
|
||||
dismiss()
|
||||
onClickBlock?.invoke()
|
||||
|
||||
@@ -226,6 +226,14 @@
|
||||
tools:src="@drawable/ic_bar_bell"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_share"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:contentDescription="@string/screen_user_profile_share_channel"
|
||||
android:src="@drawable/ic_audio_content_share"
|
||||
android:visibility="gone" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_more"
|
||||
android:layout_width="24dp"
|
||||
|
||||
@@ -16,6 +16,16 @@
|
||||
android:layout_marginBottom="12dp"
|
||||
android:background="@drawable/bg_bottom_sheet_handle" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_channel_share"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="@font/medium"
|
||||
android:paddingVertical="16dp"
|
||||
android:text="@string/screen_user_profile_share_channel"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="18sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_user_block"
|
||||
android:layout_width="match_parent"
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
package kr.co.vividnext.sodalive.common
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import androidx.datastore.preferences.core.intPreferencesKey
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.After
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.annotation.Config
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@Config(sdk = [28], application = Application::class)
|
||||
class SharedPreferenceManagerTest {
|
||||
|
||||
private val context: Context = ApplicationProvider.getApplicationContext()
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
SharedPreferenceManager.resetForTest()
|
||||
SharedPreferenceManager.init(context)
|
||||
SharedPreferenceManager.resetCanForTest()
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
SharedPreferenceManager.resetCanForTest()
|
||||
SharedPreferenceManager.resetForTest()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `resetCanForTest는 저장된 can 값을 초기화한다`() {
|
||||
SharedPreferenceManager.setCanForTest(200)
|
||||
|
||||
SharedPreferenceManager.resetCanForTest()
|
||||
|
||||
assertEquals(0, SharedPreferenceManager.can)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `setCanForTest는 can 값을 동기적으로 저장한다`() {
|
||||
SharedPreferenceManager.setCanForTest(200)
|
||||
|
||||
assertEquals(200, SharedPreferenceManager.can)
|
||||
runBlocking {
|
||||
val preferences = AppPreferencesDataStoreProvider.get(context).data.first()
|
||||
assertEquals(200, preferences[intPreferencesKey(Constants.PREF_CAN)])
|
||||
}
|
||||
}
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
package kr.co.vividnext.sodalive.v2.creator.channel
|
||||
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import java.io.File
|
||||
|
||||
class CreatorChannelShareSourceTest {
|
||||
|
||||
@Test
|
||||
fun `내 채널은 우측 상단 공유 버튼을 표시한다`() {
|
||||
val activity = projectFile(ACTIVITY_PATH).readText()
|
||||
val layout = projectFile(ACTIVITY_LAYOUT_PATH).readText()
|
||||
|
||||
assertTrue(layout.contains("@+id/iv_share"))
|
||||
assertTrue(layout.contains("android:src=\"@drawable/ic_audio_content_share\""))
|
||||
assertTrue(activity.contains("binding.ivShare.visibility = View.VISIBLE"))
|
||||
assertTrue(activity.contains("binding.ivShare.visibility = View.GONE"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `다른 사람 채널 더보기는 채널 공유 항목을 제공한다`() {
|
||||
val activity = projectFile(ACTIVITY_PATH).readText()
|
||||
val bottomSheet = projectFile(BOTTOM_SHEET_PATH).readText()
|
||||
val layout = projectFile(BOTTOM_SHEET_LAYOUT_PATH).readText()
|
||||
|
||||
assertTrue(layout.contains("@+id/tv_channel_share"))
|
||||
assertTrue(layout.contains("@string/screen_user_profile_share_channel"))
|
||||
assertTrue(bottomSheet.contains("var onClickChannelShare: (() -> Unit)? = null"))
|
||||
assertTrue(bottomSheet.contains("viewBinding.tvChannelShare.setOnClickListener"))
|
||||
assertTrue(activity.contains("onClickChannelShare = { shareCurrentCreatorChannel() }"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `두 진입점은 기존 채널 공유 chooser와 메뉴 동작을 유지한다`() {
|
||||
val activity = projectFile(ACTIVITY_PATH).readText()
|
||||
val bottomSheet = projectFile(BOTTOM_SHEET_PATH).readText()
|
||||
|
||||
assertTrue(activity.contains("binding.ivShare.setOnClickListener { shareCurrentCreatorChannel() }"))
|
||||
assertTrue(activity.contains("Intent(Intent.ACTION_SEND)"))
|
||||
assertTrue(activity.contains("type = \"text/plain\""))
|
||||
assertTrue(activity.contains("putExtra(Intent.EXTRA_TEXT"))
|
||||
assertTrue(activity.contains("Intent.createChooser"))
|
||||
assertTrue(activity.contains("R.string.screen_user_profile_share_channel"))
|
||||
assertTrue(activity.contains("\"deep_link_value\" to \"channel\""))
|
||||
assertTrue(activity.contains("\"deep_link_sub5\" to \"${'$'}creatorId\""))
|
||||
assertTrue(activity.contains("binding.layoutFollowCapsule.visibility = View.VISIBLE"))
|
||||
assertTrue(activity.contains("binding.ivMore.visibility = View.VISIBLE"))
|
||||
assertTrue(bottomSheet.contains("onClickBlock?.invoke()"))
|
||||
assertTrue(bottomSheet.contains("onClickUserReport?.invoke()"))
|
||||
assertTrue(bottomSheet.contains("onClickProfileReport?.invoke()"))
|
||||
}
|
||||
|
||||
private fun projectFile(relativePath: String): File {
|
||||
val candidates = listOf(File(relativePath), File("../$relativePath"))
|
||||
return candidates.firstOrNull { it.exists() }
|
||||
?: error("Project file not found: $relativePath")
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val ACTIVITY_PATH =
|
||||
"app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt"
|
||||
const val ACTIVITY_LAYOUT_PATH = "app/src/main/res/layout/activity_creator_channel.xml"
|
||||
const val BOTTOM_SHEET_PATH =
|
||||
"app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelMoreBottomSheet.kt"
|
||||
const val BOTTOM_SHEET_LAYOUT_PATH = "app/src/main/res/layout/dialog_creator_channel_more.xml"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
# 크리에이터 채널 공유 Phase 1 리뷰
|
||||
|
||||
## 1. 리뷰 정보
|
||||
|
||||
| 항목 | 내용 |
|
||||
|---|---|
|
||||
| 리뷰 대상 | Phase 1 / `P1-T1`~`P1-GATE` |
|
||||
| 기준 commit 또는 working tree | 현재 working tree |
|
||||
| 리뷰 일자 | `2026-09-14` |
|
||||
| 리뷰어 | Sisyphus + subagent reviewer |
|
||||
| 기준 문서 | `docs/20260914_크리에이터_채널_공유/prd.md`, `docs/20260914_크리에이터_채널_공유/plan-task.md` |
|
||||
| 리뷰 상태 | 판정 완료 |
|
||||
|
||||
## 2. 리뷰 목적과 범위
|
||||
|
||||
### 목적
|
||||
|
||||
- Phase 1 구현이 `SHARE-001~004`, `REG-001~002` 요구사항을 충족하는지 확인한다.
|
||||
- 완료 체크박스와 실제 코드·test·검증 기록이 일치하는지 확인한다.
|
||||
|
||||
### 포함 범위
|
||||
|
||||
- 코드: `CreatorChannelActivity.kt`, `CreatorChannelMoreBottomSheet.kt`
|
||||
- 레이아웃: `activity_creator_channel.xml`, `dialog_creator_channel_more.xml`
|
||||
- 테스트: `CreatorChannelShareSourceTest.kt`
|
||||
- 문서: `prd.md`, `plan-task.md`
|
||||
|
||||
### 제외 범위
|
||||
|
||||
- 레거시 `UserProfileActivity`, `UserProfileViewModel` 수정.
|
||||
- 신규 API, analytics, dependency 추가.
|
||||
- 시각 QA, 스크린샷, `app/src/androidTest` 계측 테스트.
|
||||
|
||||
## 3. 판정 기준
|
||||
|
||||
| 심각도 | 기준 |
|
||||
|---|---|
|
||||
| Blocker | 공유 핵심 흐름 불능, 빌드 실패, 레거시 파일 수정, 요구사항 위반 |
|
||||
| High | owner/non-owner 진입점 오류 또는 기존 더보기 callback 회귀 |
|
||||
| Medium | 제한 조건에서 공유 payload 또는 접근성 label 오류 |
|
||||
| Low | 문서 정합성, 테스트 격리성, 비핵심 유지보수 문제 |
|
||||
|
||||
## 4. 검토한 근거
|
||||
|
||||
### 문서와 코드
|
||||
|
||||
- 요구사항: `SHARE-001~004`, `REG-001~002`
|
||||
- 계획: `P1-T1`, `P1-T2`, `P1-T3`, `P1-GATE`
|
||||
- 코드: `CreatorChannelActivity.kt:231~469`, `CreatorChannelMoreBottomSheet.kt:12~48`
|
||||
- 레이아웃: `activity_creator_channel.xml:229~245`, `dialog_creator_channel_more.xml:19~57`
|
||||
- 테스트: `CreatorChannelShareSourceTest`
|
||||
|
||||
### 실행 환경
|
||||
|
||||
```text
|
||||
OS: macOS / darwin
|
||||
검증 범위: Android Gradle local unit/resource/build
|
||||
수동 검증: 사용자 실제 앱 테스트 대기
|
||||
```
|
||||
|
||||
### 실행한 검증
|
||||
|
||||
| 명령 또는 수동 검증 | 결과 | 핵심 증거 |
|
||||
|---|---|---|
|
||||
| `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelShareSourceTest"` | RED 성공 | production 변경 전 3개 테스트가 기능 부재 assertion으로 실패 |
|
||||
| `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelShareSourceTest" --rerun-tasks` | 성공 | `BUILD SUCCESSFUL`, 39 tasks executed |
|
||||
| `./gradlew :app:mergeDebugResources` | 성공 | `BUILD SUCCESSFUL` |
|
||||
| `./gradlew :app:assembleDebug` | 성공 | `BUILD SUCCESSFUL`, 기존 deprecation warning만 출력 |
|
||||
| `git diff --check` | 성공 | 출력 없음 |
|
||||
| `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.*"` | 실패 | `CreatorChannelHomeViewModelTest` 후원 can assertion 1건 실패. 실패 후보 테스트 2개는 단독 실행 PASS |
|
||||
| 실제 앱 수동 테스트 | 대기 | 사용자 지시에 따라 시각 QA 대신 수동 확인 예정 |
|
||||
|
||||
## 5. 발견 사항 요약
|
||||
|
||||
| ID | 심각도 | 상태 | 제목 | 소유 Task | 후속 goal |
|
||||
|---|---|---|---|---|---|
|
||||
| 없음 | - | - | 확정 발견 사항 없음 | - | - |
|
||||
|
||||
확정 발견 사항 없음. 패키지 회귀의 후원 can assertion 실패는 이번 공유 변경과 관련 파일이 겹치지 않고 단독 실행 PASS하므로 차단 이슈로 판정하지 않았다.
|
||||
|
||||
## 6. 발견 사항 상세
|
||||
|
||||
확정 발견 사항 없음.
|
||||
|
||||
## 7. 확정 항목의 plan·goal 전환
|
||||
|
||||
전환 항목 없음.
|
||||
|
||||
## 8. 리뷰 종료 판정
|
||||
|
||||
| 판정 항목 | 결과 | 근거 |
|
||||
|---|---|---|
|
||||
| 리뷰 범위 전체 확인 | 충족 | 변경 파일과 기준 문서 대조 완료 |
|
||||
| 후보 항목 판정 완료 | 충족 | 차단 발견 사항 없음 |
|
||||
| 확정 항목 plan 반영 | 해당 없음 | 확정 발견 사항 없음 |
|
||||
| 보류 항목의 담당·재개 조건 기록 | 충족 | 실제 앱 수동 테스트는 사용자 확인 대기로 기록 |
|
||||
| 검증 명령과 결과 기록 | 충족 | 실행 명령과 결과를 `plan-task.md`와 이 문서에 기록 |
|
||||
|
||||
**최종 결론:** 확정 발견 사항 없음.
|
||||
|
||||
**남은 항목:** 실제 앱에서 owner/non-owner 공유 진입점과 기존 더보기 메뉴를 수동 확인한다.
|
||||
|
||||
## 9. 수정 후 검증 기록
|
||||
|
||||
수정 goal 없음.
|
||||
Reference in New Issue
Block a user