feat(following): 팔로우 해제 확인 dialog를 적용한다

This commit is contained in:
2026-08-18 11:57:21 +09:00
parent 16633b978e
commit 388304dbaa
13 changed files with 234 additions and 101 deletions
@@ -11,8 +11,8 @@ import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.base.BaseActivity
import kr.co.vividnext.sodalive.common.LoadingDialog
import kr.co.vividnext.sodalive.databinding.ActivityFollowingCreatorBinding
import kr.co.vividnext.sodalive.explorer.profile.CreatorFollowNotifyFragment
import kr.co.vividnext.sodalive.extensions.dpToPx
import kr.co.vividnext.sodalive.v2.components.modal.V2ModalDialog
import kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivity
import org.koin.android.ext.android.inject
@@ -43,24 +43,22 @@ class FollowingCreatorActivity : BaseActivity<ActivityFollowingCreatorBinding>(
)
},
onClickFollow = { creatorId, isFollow ->
if (isFollow) {
val notifyFragment = CreatorFollowNotifyFragment(
onClickNotifyAll = {
viewModel.follow(creatorId, follow = true, notify = true)
},
onClickNotifyNone = {
viewModel.follow(creatorId, follow = true, notify = false)
},
onClickUnFollow = {
viewModel.follow(creatorId, follow = false, notify = false)
}
)
if (notifyFragment.isAdded) return@FollowingCreatorAdapter
notifyFragment.show(supportFragmentManager, notifyFragment.tag)
} else {
if (!isFollow) {
viewModel.follow(creatorId)
}
},
onClickUnfollow = { creatorId, creatorName ->
V2ModalDialog(
activity = this,
layoutInflater = layoutInflater,
title = getString(R.string.creator_unfollow_confirm_title),
desc = getString(R.string.creator_unfollow_confirm_desc, creatorName),
confirmButtonTitle = getString(R.string.creator_unfollow_confirm_confirm),
confirmButtonClick = {
viewModel.follow(creatorId, follow = false, notify = false)
},
cancelButtonTitle = getString(R.string.creator_unfollow_confirm_cancel)
).show(screenWidth)
}
)
@@ -9,10 +9,12 @@ import coil.load
import coil.transform.CircleCropTransformation
import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.databinding.ItemFollowerListBinding
import kr.co.vividnext.sodalive.extensions.dpToPx
class FollowingCreatorAdapter(
private val onClickItem: (Long) -> Unit,
private val onClickFollow: (Long, Boolean) -> Unit
private val onClickFollow: (Long, Boolean) -> Unit,
private val onClickUnfollow: ((Long, String) -> Unit)? = null
) : RecyclerView.Adapter<FollowingCreatorAdapter.ViewHolder>() {
private val items = mutableListOf<GetCreatorFollowingAllListItem>()
@@ -31,19 +33,22 @@ class FollowingCreatorAdapter(
binding.ivFollow.visibility = View.VISIBLE
if (item.isFollow) {
binding.ivFollow.setImageResource(
if (item.isNotify) {
R.drawable.btn_following_big
} else {
R.drawable.btn_following_no_alarm_big
}
)
val padding = 8.dpToPx().toInt()
binding.ivFollow.setPadding(padding, padding, padding, padding)
binding.ivFollow.setBackgroundResource(R.drawable.bg_following_circle_gray_800)
binding.ivFollow.setImageResource(R.drawable.ic_new_following_white)
} else {
binding.ivFollow.setPadding(0, 0, 0, 0)
binding.ivFollow.background = null
binding.ivFollow.setImageResource(R.drawable.btn_follow_big)
}
binding.ivFollow.setOnClickListener {
onClickFollow(item.creatorId, item.isFollow)
if (item.isFollow && onClickUnfollow != null) {
onClickUnfollow.invoke(item.creatorId, item.nickname)
} else {
onClickFollow(item.creatorId, item.isFollow)
}
}
binding.root.setOnClickListener { onClickItem(item.creatorId) }
}
@@ -332,7 +332,17 @@ class CreatorChannelActivity :
return
}
homeActionDelegate?.follow(follow = false, notify = false)
V2ModalDialog(
activity = this,
layoutInflater = layoutInflater,
title = getString(R.string.creator_unfollow_confirm_title),
desc = getString(R.string.creator_unfollow_confirm_desc, header.nickname),
confirmButtonTitle = getString(R.string.creator_unfollow_confirm_confirm),
confirmButtonClick = {
homeActionDelegate?.follow(follow = false, notify = false)
},
cancelButtonTitle = getString(R.string.creator_unfollow_confirm_cancel)
).show(screenWidth)
}
private fun onBellClicked() {
Binary file not shown.

After

Width:  |  Height:  |  Size: 381 B

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/gray_800" />
<corners android:radius="100dp" />
</shape>
+4
View File
@@ -345,6 +345,10 @@
<string name="creator_channel_dm_check_button">Check DMs</string>
<string name="creator_channel_follow_button">Follow</string>
<string name="creator_channel_unfollow_success">Following has been canceled.</string>
<string name="creator_unfollow_confirm_title">Unfollow</string>
<string name="creator_unfollow_confirm_desc">Youll no longer receive updates from %1$s.\nDo you want to unfollow?</string>
<string name="creator_unfollow_confirm_cancel">Cancel</string>
<string name="creator_unfollow_confirm_confirm">Unfollow</string>
<string name="creator_channel_block_success">Blocked.</string>
<string name="creator_channel_section_live">Live now</string>
<string name="creator_channel_section_latest_audio">Latest audio</string>
+4
View File
@@ -345,6 +345,10 @@
<string name="creator_channel_dm_check_button">DMを確認</string>
<string name="creator_channel_follow_button">フォロー</string>
<string name="creator_channel_unfollow_success">フォローを解除しました。</string>
<string name="creator_unfollow_confirm_title">フォロー解除</string>
<string name="creator_unfollow_confirm_desc">%1$sからの最新情報を受け取れなくなります。\nフォローを解除しますか?</string>
<string name="creator_unfollow_confirm_cancel">キャンセル</string>
<string name="creator_unfollow_confirm_confirm">解除する</string>
<string name="creator_channel_block_success">ブロックしました。</string>
<string name="creator_channel_section_live">配信中</string>
<string name="creator_channel_section_latest_audio">最新オーディオ</string>
+4
View File
@@ -344,6 +344,10 @@
<string name="creator_channel_dm_check_button">DM 확인하기</string>
<string name="creator_channel_follow_button">팔로우</string>
<string name="creator_channel_unfollow_success">팔로잉이 취소되었습니다.</string>
<string name="creator_unfollow_confirm_title">팔로우 해제</string>
<string name="creator_unfollow_confirm_desc">팔로우를 해제할까요?\n%1$s의 소식을 받을 수 없어요.</string>
<string name="creator_unfollow_confirm_cancel">취소</string>
<string name="creator_unfollow_confirm_confirm">해제하기</string>
<string name="creator_channel_block_success">차단하였습니다.</string>
<string name="creator_channel_section_live">현재 라이브</string>
<string name="creator_channel_section_latest_audio">최신 오디오</string>
@@ -0,0 +1,50 @@
package kr.co.vividnext.sodalive.following
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
import java.io.File
class FollowingCreatorActivitySourceTest {
@Test
fun `팔로잉 리스트는 Bottom Sheet 대신 해제 확인 Dialog를 표시한다`() {
val source = projectFile(
"app/src/main/java/kr/co/vividnext/sodalive/following/FollowingCreatorActivity.kt"
).readText()
assertFalse(source.contains("CreatorFollowNotifyFragment"))
assertTrue(source.contains("onClickUnfollow = { creatorId, creatorName ->"))
assertTrue(source.contains("V2ModalDialog("))
assertTrue(source.contains("R.string.creator_unfollow_confirm_desc, creatorName"))
assertTrue(source.contains("confirmButtonClick = {"))
assertTrue(source.contains("viewModel.follow(creatorId, follow = false, notify = false)"))
}
@Test
fun `팔로우 해제 확인 문구는 세 locale에서 같은 placeholder 계약을 사용한다`() {
val korean = projectFile("app/src/main/res/values/strings.xml").readText()
val english = projectFile("app/src/main/res/values-en/strings.xml").readText()
val japanese = projectFile("app/src/main/res/values-ja/strings.xml").readText()
listOf(korean, english, japanese).forEach { strings ->
assertTrue(strings.contains("name=\"creator_unfollow_confirm_title\""))
assertTrue(strings.contains("name=\"creator_unfollow_confirm_desc\""))
assertTrue(strings.contains("name=\"creator_unfollow_confirm_cancel\""))
assertTrue(strings.contains("name=\"creator_unfollow_confirm_confirm\""))
assertTrue(strings.contains("%1\$s"))
}
assertTrue(
english.contains(
"Youll no longer receive updates from %1\$s.\\nDo you want to unfollow?"
)
)
}
private fun projectFile(path: String): File = File(projectRoot(), path)
private fun projectRoot(): File {
return generateSequence(File(System.getProperty("user.dir") ?: ".").absoluteFile) { it.parentFile }
.first { File(it, "settings.gradle").exists() }
}
}
@@ -0,0 +1,32 @@
package kr.co.vividnext.sodalive.following
import org.junit.Assert.assertTrue
import org.junit.Test
import java.io.File
class FollowingCreatorAdapterSourceTest {
@Test
fun `팔로잉 중 버튼은 흰색 아이콘과 gray 800 원형 배경을 사용한다`() {
val source = projectFile(
"app/src/main/java/kr/co/vividnext/sodalive/following/FollowingCreatorAdapter.kt"
).readText()
val background = projectFile(
"app/src/main/res/drawable/bg_following_circle_gray_800.xml"
)
assertTrue(source.contains("R.drawable.ic_new_following_white"))
assertTrue(source.contains("R.drawable.bg_following_circle_gray_800"))
assertTrue(source.contains("8.dpToPx().toInt()"))
assertTrue(background.exists())
assertTrue(background.readText().contains("@color/gray_800"))
assertTrue(background.readText().contains("android:radius=\"100dp\""))
}
private fun projectFile(path: String): File = File(projectRoot(), path)
private fun projectRoot(): File {
return generateSequence(File(System.getProperty("user.dir") ?: ".").absoluteFile) { it.parentFile }
.first { File(it, "settings.gradle").exists() }
}
}
@@ -283,6 +283,11 @@ class CreatorChannelActivitySourceTest {
"app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt"
).readText()
val layout = projectFile("app/src/main/res/layout/activity_creator_channel.xml").readText()
val followClickSource = sourceSection(
source = source,
startMarker = "private fun onFollowCapsuleClicked()",
endMarker = "private fun onBellClicked()"
)
assertFalse(source.contains("CreatorFollowNotifyFragment"))
assertTrue(layout.contains("android:id=\"@+id/layout_follow_capsule\""))
@@ -294,7 +299,11 @@ class CreatorChannelActivitySourceTest {
assertTrue(source.contains("if (!header.isFollow)"))
assertTrue(source.contains("homeActionDelegate?.follow(follow = true, notify = true)"))
assertTrue(source.contains("homeActionDelegate?.follow(follow = true, notify = !header.isNotify)"))
assertTrue(source.contains("homeActionDelegate?.follow(follow = false, notify = false)"))
assertTrue(followClickSource.contains("V2ModalDialog("))
assertTrue(followClickSource.contains("R.string.creator_unfollow_confirm_title"))
assertTrue(followClickSource.contains("R.string.creator_unfollow_confirm_desc, header.nickname"))
assertTrue(followClickSource.contains("confirmButtonClick = {"))
assertTrue(followClickSource.contains("homeActionDelegate?.follow(follow = false, notify = false)"))
assertFalse(source.contains("showFollowNotifyFragment"))
assertTrue(source.contains("onCreatorChannelFollowProgressChanged"))
assertTrue(source.contains("binding.layoutFollowCapsule.isEnabled = titleBarState.isActionEnabled"))
@@ -1317,7 +1326,12 @@ class CreatorChannelActivitySourceTest {
assertTrue(en.contains("name=\"creator_channel_home_donation_empty_title\">Be the first to\\nsupport this creator!"))
assertTrue(ja.contains("name=\"creator_channel_home_donation_empty_title\">最初のサポートをしてみましょう!"))
assertTrue(ko.contains("name=\"creator_channel_donation_empty_title\">아직 후원이 없습니다.\\n처음으로 크리에이터를 후원해 보세요!"))
assertTrue(en.contains("name=\"creator_channel_donation_empty_title\">No support yet.\\nBe the first to support this creator!"))
assertTrue(
en.contains(
"name=\"creator_channel_donation_empty_title\">No support yet.\\n" +
"Be the first to support this creator!"
)
)
assertTrue(ja.contains("name=\"creator_channel_donation_empty_title\">まだサポートがありません。\\n最初のサポートをしてみましょう!"))
assertTrue(ko.contains("name=\"creator_channel_donation_action\">후원하기"))
assertTrue(en.contains("name=\"creator_channel_donation_action\">Support"))