refactor(v2): 도메인 액션 경로를 통합한다

This commit is contained in:
2026-07-16 16:57:39 +09:00
parent 4373452478
commit de90f771c1
34 changed files with 1780 additions and 264 deletions

View File

@@ -5,19 +5,22 @@ import android.net.Uri
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import kr.co.vividnext.sodalive.audio_content.detail.AudioContentDetailActivity
import kr.co.vividnext.sodalive.audio_content.series.detail.SeriesDetailActivity
import kr.co.vividnext.sodalive.app.SodaLiveApp
import kr.co.vividnext.sodalive.audition.AuditionActivity
import kr.co.vividnext.sodalive.common.Constants
import kr.co.vividnext.sodalive.explorer.profile.creator_community.all.CreatorCommunityAllActivity
import kr.co.vividnext.sodalive.live.room.LiveRoomActivity
import kr.co.vividnext.sodalive.message.MessageActivity
import kr.co.vividnext.sodalive.mypage.can.payment.CanPaymentActivity
import kr.co.vividnext.sodalive.splash.SplashActivity
import kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivity
import kr.co.vividnext.sodalive.v2.chat.action.ChatActionCommand
import kr.co.vividnext.sodalive.v2.chat.action.handleChatAction
import kr.co.vividnext.sodalive.v2.community.action.CommunityActionCommand
import kr.co.vividnext.sodalive.v2.community.action.handleCommunityAction
import kr.co.vividnext.sodalive.v2.content.action.ContentActionCommand
import kr.co.vividnext.sodalive.v2.content.action.handleContentAction
import kr.co.vividnext.sodalive.v2.creator.action.CreatorActionCommand
import kr.co.vividnext.sodalive.v2.creator.action.handleCreatorAction
import kr.co.vividnext.sodalive.v2.main.MainV2Activity
import kr.co.vividnext.sodalive.v2.main.chat.dm.DmChatRoomActivity
import java.util.Locale
class DeepLinkActivity : AppCompatActivity() {
@@ -276,7 +279,7 @@ class DeepLinkActivity : AppCompatActivity() {
?: bundle.getLong(Constants.EXTRA_COMMUNITY_POST_ID).takeIf { it > 0 }
if (isDmChatDeepLink(bundle) && roomId != null && roomId > 0) {
startActivity(DmChatRoomActivity.newIntentByRoomId(applicationContext, roomId))
handleChatAction(ChatActionCommand.DmRoom(roomId))
return true
}
@@ -286,36 +289,29 @@ class DeepLinkActivity : AppCompatActivity() {
return true
}
communityPostId != null && communityPostId > 0 -> {
handleCommunityAction(CommunityActionCommand.PostDetail(communityPostId))
return true
}
channelId != null && channelId > 0 -> {
startActivity(
CreatorChannelActivity.newIntent(applicationContext, channelId)
)
handleCreatorAction(CreatorActionCommand.Profile(channelId))
return true
}
contentId != null && contentId > 0 -> {
startActivity(
Intent(applicationContext, AudioContentDetailActivity::class.java).apply {
putExtra(Constants.EXTRA_AUDIO_CONTENT_ID, contentId)
}
)
handleContentAction(ContentActionCommand.AudioDetail(contentId))
return true
}
messageId != null && messageId > 0 -> {
// messageId는 DM room ID가 아니므로 과거 알림 수신 호환만 유지한다.
startActivity(Intent(applicationContext, MessageActivity::class.java))
return true
}
communityCreatorId != null && communityCreatorId > 0 -> {
startActivity(
Intent(applicationContext, CreatorCommunityAllActivity::class.java).apply {
putExtra(Constants.EXTRA_COMMUNITY_CREATOR_ID, communityCreatorId)
if (communityPostId != null && communityPostId > 0) {
putExtra(Constants.EXTRA_COMMUNITY_POST_ID, communityPostId)
}
}
)
handleCreatorAction(CreatorActionCommand.Profile(communityCreatorId))
return true
}
@@ -345,11 +341,7 @@ class DeepLinkActivity : AppCompatActivity() {
return false
}
startActivity(
Intent(applicationContext, SeriesDetailActivity::class.java).apply {
putExtra(Constants.EXTRA_SERIES_ID, deepLinkValueId)
}
)
handleContentAction(ContentActionCommand.SeriesDetail(deepLinkValueId))
true
}
@@ -358,11 +350,7 @@ class DeepLinkActivity : AppCompatActivity() {
return false
}
startActivity(
Intent(applicationContext, AudioContentDetailActivity::class.java).apply {
putExtra(Constants.EXTRA_AUDIO_CONTENT_ID, deepLinkValueId)
}
)
handleContentAction(ContentActionCommand.AudioDetail(deepLinkValueId))
true
}
@@ -371,9 +359,7 @@ class DeepLinkActivity : AppCompatActivity() {
return false
}
startActivity(
CreatorChannelActivity.newIntent(applicationContext, deepLinkValueId)
)
handleCreatorAction(CreatorActionCommand.Profile(deepLinkValueId))
true
}
@@ -391,15 +377,13 @@ class DeepLinkActivity : AppCompatActivity() {
return false
}
startActivity(
Intent(applicationContext, CreatorCommunityAllActivity::class.java).apply {
putExtra(Constants.EXTRA_COMMUNITY_CREATOR_ID, deepLinkValueId)
}
)
// deepLinkValueId는 legacy creatorId이며 postId는 routeForegroundDeepLink에서 먼저 처리한다.
handleCreatorAction(CreatorActionCommand.Profile(deepLinkValueId))
true
}
"message" -> {
// 현재 DM은 chat/{roomId} 계약을 사용하며 message는 legacy fallback이다.
startActivity(Intent(applicationContext, MessageActivity::class.java))
true
}

View File

@@ -0,0 +1,54 @@
package kr.co.vividnext.sodalive.v2.chat.action
import kr.co.vividnext.sodalive.v2.access.AccessRequirement
class ChatAction {
fun execute(
command: ChatActionCommand,
ensureAccess: (AccessRequirement) -> Boolean
): ChatActionResult {
return when (command) {
is ChatActionCommand.AiRoom -> {
if (command.roomId <= 0L) return ChatActionResult.Ignored
if (!ensureAccess(AccessRequirement.Login)) return ChatActionResult.Blocked(AccessRequirement.Login)
ChatActionResult.NavigateToAiRoom(command.roomId)
}
is ChatActionCommand.DmRoom -> {
if (command.roomId <= 0L) return ChatActionResult.Ignored
if (!ensureAccess(AccessRequirement.Login)) return ChatActionResult.Blocked(AccessRequirement.Login)
ChatActionResult.NavigateToDmRoom(command.roomId)
}
is ChatActionCommand.DmCreator -> {
if (command.creatorId <= 0L) return ChatActionResult.Ignored
if (!ensureAccess(AccessRequirement.Login)) return ChatActionResult.Blocked(AccessRequirement.Login)
ChatActionResult.NavigateToCreatorDm(command.creatorId)
}
ChatActionCommand.OwnerDmList -> {
if (!ensureAccess(AccessRequirement.Login)) return ChatActionResult.Blocked(AccessRequirement.Login)
ChatActionResult.NavigateToOwnerDmList
}
}
}
}
sealed interface ChatActionResult {
data object Ignored : ChatActionResult
data class Blocked(
val requirement: AccessRequirement
) : ChatActionResult
data class NavigateToAiRoom(
val roomId: Long
) : ChatActionResult
data class NavigateToDmRoom(
val roomId: Long
) : ChatActionResult
data class NavigateToCreatorDm(
val creatorId: Long
) : ChatActionResult
data object NavigateToOwnerDmList : ChatActionResult
}

View File

@@ -0,0 +1,17 @@
package kr.co.vividnext.sodalive.v2.chat.action
sealed interface ChatActionCommand {
data class AiRoom(
val roomId: Long
) : ChatActionCommand
data class DmRoom(
val roomId: Long
) : ChatActionCommand
data class DmCreator(
val creatorId: Long
) : ChatActionCommand
data object OwnerDmList : ChatActionCommand
}

View File

@@ -0,0 +1,48 @@
package kr.co.vividnext.sodalive.v2.chat.action
import android.app.Activity
import android.content.Context
import androidx.annotation.OptIn
import androidx.fragment.app.Fragment
import androidx.media3.common.util.UnstableApi
import kr.co.vividnext.sodalive.chat.talk.room.ChatRoomActivity
import kr.co.vividnext.sodalive.v2.access.AccessRequirement
import kr.co.vividnext.sodalive.v2.access.ensureV2Access
import kr.co.vividnext.sodalive.v2.main.MainV2Activity
import kr.co.vividnext.sodalive.v2.main.chat.dm.DmChatRoomActivity
internal class ChatActionHandler(
private val action: ChatAction = ChatAction()
) {
@OptIn(UnstableApi::class)
fun handle(
context: Context,
command: ChatActionCommand,
ensureAccess: (AccessRequirement) -> Boolean
): ChatActionResult {
val result = action.execute(command, ensureAccess)
when (result) {
is ChatActionResult.NavigateToAiRoom -> context.startActivity(
ChatRoomActivity.newIntent(context, result.roomId)
)
is ChatActionResult.NavigateToDmRoom -> context.startActivity(
DmChatRoomActivity.newIntentByRoomId(context, result.roomId)
)
is ChatActionResult.NavigateToCreatorDm -> context.startActivity(
DmChatRoomActivity.newIntentByCreatorId(context, result.creatorId)
)
ChatActionResult.NavigateToOwnerDmList -> context.startActivity(MainV2Activity.newChatDmIntent(context))
is ChatActionResult.Blocked,
ChatActionResult.Ignored -> Unit
}
return result
}
}
fun Activity.handleChatAction(command: ChatActionCommand): ChatActionResult {
return ChatActionHandler().handle(this, command, ::ensureV2Access)
}
fun Fragment.handleChatAction(command: ChatActionCommand): ChatActionResult {
return ChatActionHandler().handle(requireContext(), command, ::ensureV2Access)
}

View File

@@ -0,0 +1,34 @@
package kr.co.vividnext.sodalive.v2.community.action
import kr.co.vividnext.sodalive.v2.access.AccessRequirement
class CommunityAction {
fun execute(
command: CommunityActionCommand,
ensureAccess: (AccessRequirement) -> Boolean
): CommunityActionResult {
val postId = when (command) {
is CommunityActionCommand.PostDetail -> command.postId
}
if (postId <= 0L) return CommunityActionResult.Ignored
val requirement = AccessRequirement.Login
if (!ensureAccess(requirement)) return CommunityActionResult.Blocked(requirement)
return when (command) {
is CommunityActionCommand.PostDetail -> CommunityActionResult.NavigateToPostDetail(postId)
}
}
}
sealed interface CommunityActionResult {
data object Ignored : CommunityActionResult
data class Blocked(
val requirement: AccessRequirement
) : CommunityActionResult
data class NavigateToPostDetail(
val postId: Long
) : CommunityActionResult
}

View File

@@ -0,0 +1,7 @@
package kr.co.vividnext.sodalive.v2.community.action
sealed interface CommunityActionCommand {
data class PostDetail(
val postId: Long
) : CommunityActionCommand
}

View File

@@ -0,0 +1,42 @@
package kr.co.vividnext.sodalive.v2.community.action
import android.app.Activity
import android.content.Context
import android.content.Intent
import androidx.fragment.app.Fragment
import kr.co.vividnext.sodalive.v2.access.AccessRequirement
import kr.co.vividnext.sodalive.v2.access.ensureV2Access
import kr.co.vividnext.sodalive.v2.creator.channel.community.detail.CreatorChannelCommunityDetailActivity
internal class CommunityActionHandler(
private val action: CommunityAction = CommunityAction()
) {
fun handle(
context: Context,
command: CommunityActionCommand,
ensureAccess: (AccessRequirement) -> Boolean,
launchIntent: ((Intent) -> Unit)? = null
): CommunityActionResult {
val result = action.execute(command, ensureAccess)
if (result is CommunityActionResult.NavigateToPostDetail) {
val intent = CreatorChannelCommunityDetailActivity.newIntent(context, result.postId)
if (launchIntent != null) {
launchIntent(intent)
} else {
context.startActivity(intent)
}
}
return result
}
}
fun Activity.handleCommunityAction(
command: CommunityActionCommand,
launchIntent: ((Intent) -> Unit)? = null
): CommunityActionResult {
return CommunityActionHandler().handle(this, command, ::ensureV2Access, launchIntent)
}
fun Fragment.handleCommunityAction(command: CommunityActionCommand): CommunityActionResult {
return CommunityActionHandler().handle(requireContext(), command, ::ensureV2Access)
}

View File

@@ -0,0 +1,21 @@
package kr.co.vividnext.sodalive.v2.community.action
import android.app.Activity
enum class CommunityActivityResultSource {
Write,
Modify,
Detail
}
fun resolveCommunityActivityResult(
source: CommunityActivityResultSource,
resultCode: Int
): CommunityChange {
if (resultCode != Activity.RESULT_OK) return CommunityChange.Ignored
return when (source) {
CommunityActivityResultSource.Write -> CommunityChange.Created
CommunityActivityResultSource.Modify,
CommunityActivityResultSource.Detail -> CommunityChange.Updated
}
}

View File

@@ -0,0 +1,17 @@
package kr.co.vividnext.sodalive.v2.community.action
sealed interface CommunityChange {
data object Ignored : CommunityChange
data object Created : CommunityChange
data object Updated : CommunityChange
data class Deleted(
val postId: Long
) : CommunityChange
data class PinChanged(
val postId: Long
) : CommunityChange
}

View File

@@ -0,0 +1,34 @@
package kr.co.vividnext.sodalive.v2.creator.action
import kr.co.vividnext.sodalive.v2.access.AccessRequirement
class CreatorAction {
fun execute(
command: CreatorActionCommand,
ensureAccess: (AccessRequirement) -> Boolean
): CreatorActionResult {
val creatorId = when (command) {
is CreatorActionCommand.Profile -> command.creatorId
}
if (creatorId <= 0L) return CreatorActionResult.Ignored
val requirement = AccessRequirement.Login
if (!ensureAccess(requirement)) return CreatorActionResult.Blocked(requirement)
return when (command) {
is CreatorActionCommand.Profile -> CreatorActionResult.NavigateToCreatorProfile(creatorId)
}
}
}
sealed interface CreatorActionResult {
data object Ignored : CreatorActionResult
data class Blocked(
val requirement: AccessRequirement
) : CreatorActionResult
data class NavigateToCreatorProfile(
val creatorId: Long
) : CreatorActionResult
}

View File

@@ -0,0 +1,7 @@
package kr.co.vividnext.sodalive.v2.creator.action
sealed interface CreatorActionCommand {
data class Profile(
val creatorId: Long
) : CreatorActionCommand
}

View File

@@ -0,0 +1,32 @@
package kr.co.vividnext.sodalive.v2.creator.action
import android.app.Activity
import android.content.Context
import androidx.fragment.app.Fragment
import kr.co.vividnext.sodalive.v2.access.AccessRequirement
import kr.co.vividnext.sodalive.v2.access.ensureV2Access
import kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivity
internal class CreatorActionHandler(
private val action: CreatorAction = CreatorAction()
) {
fun handle(
context: Context,
command: CreatorActionCommand,
ensureAccess: (AccessRequirement) -> Boolean
): CreatorActionResult {
val result = action.execute(command, ensureAccess)
if (result is CreatorActionResult.NavigateToCreatorProfile) {
context.startActivity(CreatorChannelActivity.newIntent(context, result.creatorId))
}
return result
}
}
fun Activity.handleCreatorAction(command: CreatorActionCommand): CreatorActionResult {
return CreatorActionHandler().handle(this, command, ::ensureV2Access)
}
fun Fragment.handleCreatorAction(command: CreatorActionCommand): CreatorActionResult {
return CreatorActionHandler().handle(requireContext(), command, ::ensureV2Access)
}

View File

@@ -27,7 +27,6 @@ import io.reactivex.rxjava3.schedulers.Schedulers
import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.audio_content.upload.AudioContentUploadActivity
import kr.co.vividnext.sodalive.base.BaseActivity
import kr.co.vividnext.sodalive.chat.talk.room.ChatRoomActivity
import kr.co.vividnext.sodalive.common.Constants
import kr.co.vividnext.sodalive.common.LoadingDialog
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
@@ -47,13 +46,19 @@ import kr.co.vividnext.sodalive.live.room.donation.LiveRoomDonationDialog
import kr.co.vividnext.sodalive.report.UserReportDialog
import kr.co.vividnext.sodalive.v2.access.AccessRequirement
import kr.co.vividnext.sodalive.v2.access.ensureV2Access
import kr.co.vividnext.sodalive.v2.chat.action.ChatActionCommand
import kr.co.vividnext.sodalive.v2.chat.action.handleChatAction
import kr.co.vividnext.sodalive.v2.community.action.CommunityActionCommand
import kr.co.vividnext.sodalive.v2.community.action.CommunityActivityResultSource
import kr.co.vividnext.sodalive.v2.community.action.CommunityChange
import kr.co.vividnext.sodalive.v2.community.action.handleCommunityAction
import kr.co.vividnext.sodalive.v2.community.action.resolveCommunityActivityResult
import kr.co.vividnext.sodalive.v2.content.action.ContentActionCommand
import kr.co.vividnext.sodalive.v2.content.action.handleContentAction
import kr.co.vividnext.sodalive.v2.components.modal.V2ModalDialog
import kr.co.vividnext.sodalive.v2.common.CreatorActivityType
import kr.co.vividnext.sodalive.v2.creator.channel.audio.CreatorChannelAudioFragment
import kr.co.vividnext.sodalive.v2.creator.channel.community.CreatorChannelCommunityFragment
import kr.co.vividnext.sodalive.v2.creator.channel.community.detail.CreatorChannelCommunityDetailActivity
import kr.co.vividnext.sodalive.v2.creator.channel.community.model.CreatorChannelCommunityPostUiModel
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelAudioContentResponse
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelLiveResponse
@@ -74,8 +79,6 @@ import kr.co.vividnext.sodalive.v2.creator.channel.series.CreatorChannelSeriesFr
import kr.co.vividnext.sodalive.v2.live.action.LiveActionCoordinator
import kr.co.vividnext.sodalive.v2.live.action.LiveCreationResult
import kr.co.vividnext.sodalive.v2.live.action.resolveLiveCreationResult
import kr.co.vividnext.sodalive.v2.main.MainV2Activity
import kr.co.vividnext.sodalive.v2.main.chat.dm.DmChatRoomActivity
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.RequestBody.Companion.toRequestBody
import org.koin.android.ext.android.inject
@@ -118,24 +121,23 @@ class CreatorChannelActivity :
private val communityWriteLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { result ->
if (result.resultCode == RESULT_OK) {
homeActionDelegate?.refreshHome()
refreshCreatorChannelCommunity()
}
handleCommunityChange(
resolveCommunityActivityResult(CommunityActivityResultSource.Write, result.resultCode)
)
}
private val communityPostModifyLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { result ->
if (result.resultCode == RESULT_OK) {
refreshCreatorChannelCommunity()
}
handleCommunityChange(
resolveCommunityActivityResult(CommunityActivityResultSource.Modify, result.resultCode)
)
}
private val communityDetailLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { result ->
if (result.resultCode == RESULT_OK) {
refreshCreatorChannelCommunity()
}
handleCommunityChange(
resolveCommunityActivityResult(CommunityActivityResultSource.Detail, result.resultCode)
)
}
private val fanTalkWriteLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
@@ -242,9 +244,9 @@ class CreatorChannelActivity :
}
binding.tvDmButton.setOnClickListener {
if (currentHeader?.isOwner == true) {
startActivity(MainV2Activity.newChatDmIntent(this))
handleChatAction(ChatActionCommand.OwnerDmList)
} else {
startActivity(DmChatRoomActivity.newIntentByCreatorId(this, creatorId))
handleChatAction(ChatActionCommand.DmCreator(creatorId))
}
}
}
@@ -538,7 +540,7 @@ class CreatorChannelActivity :
}
override fun onCreatorChannelChatRoomCreated(chatRoomId: Long) {
startActivity(ChatRoomActivity.newIntent(this, chatRoomId))
handleChatAction(ChatActionCommand.AiRoom(chatRoomId))
}
override fun onCreatorChannelScheduleClicked(schedule: CreatorChannelScheduleResponse) {
@@ -612,10 +614,7 @@ class CreatorChannelActivity :
}
override fun onCreatorChannelCommunityPostClicked(postId: Long) {
if (postId <= 0L) return
ensureV2Access(AccessRequirement.Login) {
communityDetailLauncher.launch(CreatorChannelCommunityDetailActivity.newIntent(this, postId))
}
handleCommunityAction(CommunityActionCommand.PostDetail(postId), communityDetailLauncher::launch)
}
override fun onCreatorChannelFanTalkContentChanged() {
@@ -736,7 +735,7 @@ class CreatorChannelActivity :
.subscribe(
{ response ->
if (response.success) {
refreshCreatorChannelCommunity()
handleCommunityChange(CommunityChange.PinChanged(item.postId))
} else {
response.message?.let(::showToast)
}
@@ -763,7 +762,7 @@ class CreatorChannelActivity :
.subscribe(
{ response ->
if (response.success) {
refreshCreatorChannelCommunity()
handleCommunityChange(CommunityChange.Deleted(item.postId))
} else {
response.message?.let(::showToast)
}
@@ -779,6 +778,19 @@ class CreatorChannelActivity :
findCommunityFragment()?.onCreatorChannelCommunityRefreshRequested()
}
private fun handleCommunityChange(change: CommunityChange) {
when (change) {
CommunityChange.Ignored -> Unit
CommunityChange.Created -> {
homeActionDelegate?.refreshHome()
refreshCreatorChannelCommunity()
}
CommunityChange.Updated,
is CommunityChange.Deleted,
is CommunityChange.PinChanged -> refreshCreatorChannelCommunity()
}
}
private fun refreshCreatorChannelFanTalk() {
findFanTalkFragment()?.onCreatorChannelFanTalkRefreshRequested()
}

View File

@@ -37,14 +37,14 @@ import kr.co.vividnext.sodalive.audio_content.AudioContentPlayService
import kr.co.vividnext.sodalive.audio_content.detail.AudioContentDetailActivity
import kr.co.vividnext.sodalive.audio_content.player.AudioContentPlayerFragment
import kr.co.vividnext.sodalive.audio_content.player.AudioContentPlayerService
import kr.co.vividnext.sodalive.audio_content.series.detail.SeriesDetailActivity
import kr.co.vividnext.sodalive.audition.AuditionActivity
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.extensions.dpToPx
import kr.co.vividnext.sodalive.databinding.ActivityMainV2Binding
import kr.co.vividnext.sodalive.explorer.profile.creator_community.all.CreatorCommunityAllActivity
import kr.co.vividnext.sodalive.extensions.dpToPx
import kr.co.vividnext.sodalive.live.LiveViewModel
import kr.co.vividnext.sodalive.main.EventPopupDialogFragment
import kr.co.vividnext.sodalive.message.MessageActivity
import kr.co.vividnext.sodalive.mypage.MyPageFragment
@@ -53,10 +53,17 @@ import kr.co.vividnext.sodalive.settings.notification.NotificationSettingsDialog
import kr.co.vividnext.sodalive.v2.access.AccessRequirement
import kr.co.vividnext.sodalive.v2.access.ensureV2Access
import kr.co.vividnext.sodalive.v2.access.isV2AccessAllowed
import kr.co.vividnext.sodalive.v2.chat.action.ChatActionCommand
import kr.co.vividnext.sodalive.v2.chat.action.handleChatAction
import kr.co.vividnext.sodalive.v2.common.data.ContentSort
import kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivity
import kr.co.vividnext.sodalive.v2.community.action.CommunityActionCommand
import kr.co.vividnext.sodalive.v2.community.action.handleCommunityAction
import kr.co.vividnext.sodalive.v2.content.action.ContentActionCommand
import kr.co.vividnext.sodalive.v2.content.action.handleContentAction
import kr.co.vividnext.sodalive.v2.creator.action.CreatorActionCommand
import kr.co.vividnext.sodalive.v2.creator.action.handleCreatorAction
import kr.co.vividnext.sodalive.v2.live.action.LiveActionCoordinator
import kr.co.vividnext.sodalive.v2.main.chat.ChatMainFragment
import kr.co.vividnext.sodalive.v2.main.chat.dm.DmChatRoomActivity
import kr.co.vividnext.sodalive.v2.main.chat.model.ChatRoomFilter
import kr.co.vividnext.sodalive.v2.main.content.ContentMainFragment
import kr.co.vividnext.sodalive.v2.main.content.data.MainContentAllType
@@ -72,19 +79,37 @@ import kotlin.math.max
class MainV2Activity : BaseActivity<ActivityMainV2Binding>(ActivityMainV2Binding::inflate) {
private val viewModel: MainV2ViewModel by inject()
private val liveViewModel: LiveViewModel by inject()
private val liveActionCoordinator: LiveActionCoordinator by lazy {
LiveActionCoordinator(
activity = this,
layoutInflater = layoutInflater,
fragmentManager = supportFragmentManager,
liveViewModel = liveViewModel,
screenWidthProvider = { screenWidth },
refreshHome = {}
)
}
private lateinit var notificationSettingsDialog: NotificationSettingsDialog
private lateinit var routeLoadingDialog: LoadingDialog
private var mediaController: MediaController? = null
private var mediaControllerFuture: ListenableFuture<MediaController>? = null
private val handler = Handler(Looper.getMainLooper())
private val showMiniPlayerRunnable = Runnable { initAndVisibleMiniPlayer() }
private val audioContentReceiver = AudioContentReceiver()
private var playerStateJob: Job? = null
private var isDeferredRouteLoading = false
private var isLiveEntryLoading = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
overrideRootWindowInsets()
isDeferredRouteLoading =
intent.hasExtra(Constants.EXTRA_DATA) || intent.hasExtra(EXTRA_AUDIO_NOTIFICATION_ROUTE)
updateRouteLoadingDialog()
checkPermissions()
trackAppLaunchIfNeeded()
pushTokenUpdate()
@@ -93,6 +118,8 @@ class MainV2Activity : BaseActivity<ActivityMainV2Binding>(ActivityMainV2Binding
if (!handleAudioNotificationRoute(intent) && isV2AccessAllowed(AccessRequirement.Login)) {
executeDeeplink(intent)
}
isDeferredRouteLoading = false
updateRouteLoadingDialog()
}, 1000)
if (isV2AccessAllowed(AccessRequirement.Login)) {
@@ -172,12 +199,32 @@ class MainV2Activity : BaseActivity<ActivityMainV2Binding>(ActivityMainV2Binding
)
}
setupLiveEntryObservers()
setupBottomNavigation()
if (intent.hasExtra(EXTRA_CHAT_FILTER)) {
selectChatTabWithLoginGuard()
}
}
private fun setupLiveEntryObservers() {
routeLoadingDialog = LoadingDialog(this, layoutInflater)
liveViewModel.toastLiveData.observe(this) {
it?.let(::showToast)
}
liveViewModel.isLoading.observe(this) { isLoading ->
isLiveEntryLoading = isLoading
updateRouteLoadingDialog()
}
}
private fun updateRouteLoadingDialog() {
if (isDeferredRouteLoading || isLiveEntryLoading) {
routeLoadingDialog.show(screenWidth)
} else {
routeLoadingDialog.dismiss()
}
}
fun openChatTab() {
selectChatTabWithLoginGuard()
}
@@ -433,15 +480,7 @@ class MainV2Activity : BaseActivity<ActivityMainV2Binding>(ActivityMainV2Binding
val contentId = intent.getLongExtra(Constants.EXTRA_AUDIO_CONTENT_ID, 0)
intent.removeExtra(Constants.EXTRA_AUDIO_CONTENT_ID)
if (contentId > 0) {
ensureV2Access(AccessRequirement.Login) {
startActivity(
Intent(applicationContext, AudioContentDetailActivity::class.java).apply {
putExtra(Constants.EXTRA_AUDIO_CONTENT_ID, contentId)
}
)
}
} else if (!isV2AccessAllowed(AccessRequirement.Login)) {
ensureV2Access(AccessRequirement.Login)
handleContentAction(ContentActionCommand.AudioDetail(contentId))
}
contentId > 0
}
@@ -566,9 +605,16 @@ class MainV2Activity : BaseActivity<ActivityMainV2Binding>(ActivityMainV2Binding
putQuery("content_id")
putQuery("deep_link_value")
putQuery("deep_link_sub5")
putQuery("postId")
putQuery(Constants.EXTRA_COMMUNITY_CREATOR_ID)
putQuery(Constants.EXTRA_COMMUNITY_POST_ID)
extras.getString("postId")?.takeIf { it.isNotBlank() }?.let {
if (!extras.containsKey(Constants.EXTRA_COMMUNITY_POST_ID)) {
extras.putString(Constants.EXTRA_COMMUNITY_POST_ID, it)
}
}
applyPathDeepLink(data = data) { key, value ->
if (!value.isNullOrBlank() && !extras.containsKey(key)) {
extras.putString(key, value)
@@ -652,41 +698,39 @@ class MainV2Activity : BaseActivity<ActivityMainV2Binding>(ActivityMainV2Binding
?: bundle.getLong(Constants.EXTRA_COMMUNITY_POST_ID).takeIf { it > 0 }
if (isDmChatDeepLink(bundle) && roomId != null && roomId > 0) {
startActivity(DmChatRoomActivity.newIntentByRoomId(applicationContext, roomId))
handleChatAction(ChatActionCommand.DmRoom(roomId))
return true
}
when {
roomId != null && roomId > 0 -> {
liveActionCoordinator.enterLiveRoom(roomId)
return true
}
communityPostId != null && communityPostId > 0 -> {
handleCommunityAction(CommunityActionCommand.PostDetail(communityPostId))
return true
}
channelId != null && channelId > 0 -> {
startActivity(
CreatorChannelActivity.newIntent(applicationContext, channelId)
)
handleCreatorAction(CreatorActionCommand.Profile(channelId))
return true
}
contentId != null && contentId > 0 -> {
startActivity(
Intent(applicationContext, AudioContentDetailActivity::class.java).apply {
putExtra(Constants.EXTRA_AUDIO_CONTENT_ID, contentId)
}
)
handleContentAction(ContentActionCommand.AudioDetail(contentId))
return true
}
messageId != null && messageId > 0 -> {
// messageId는 DM room ID가 아니므로 과거 알림 수신 호환만 유지한다.
startActivity(Intent(applicationContext, MessageActivity::class.java))
return true
}
communityCreatorId != null && communityCreatorId > 0 -> {
startActivity(
Intent(applicationContext, CreatorCommunityAllActivity::class.java).apply {
putExtra(Constants.EXTRA_COMMUNITY_CREATOR_ID, communityCreatorId)
if (communityPostId != null && communityPostId > 0) {
putExtra(Constants.EXTRA_COMMUNITY_POST_ID, communityPostId)
}
}
)
handleCreatorAction(CreatorActionCommand.Profile(communityCreatorId))
return true
}
@@ -711,11 +755,7 @@ class MainV2Activity : BaseActivity<ActivityMainV2Binding>(ActivityMainV2Binding
if (deepLinkValueId == null || deepLinkValueId <= 0) {
return false
}
startActivity(
Intent(applicationContext, SeriesDetailActivity::class.java).apply {
putExtra(Constants.EXTRA_SERIES_ID, deepLinkValueId)
}
)
handleContentAction(ContentActionCommand.SeriesDetail(deepLinkValueId))
true
}
@@ -723,11 +763,7 @@ class MainV2Activity : BaseActivity<ActivityMainV2Binding>(ActivityMainV2Binding
if (deepLinkValueId == null || deepLinkValueId <= 0) {
return false
}
startActivity(
Intent(applicationContext, AudioContentDetailActivity::class.java).apply {
putExtra(Constants.EXTRA_AUDIO_CONTENT_ID, deepLinkValueId)
}
)
handleContentAction(ContentActionCommand.AudioDetail(deepLinkValueId))
true
}
@@ -735,9 +771,15 @@ class MainV2Activity : BaseActivity<ActivityMainV2Binding>(ActivityMainV2Binding
if (deepLinkValueId == null || deepLinkValueId <= 0) {
return false
}
startActivity(
CreatorChannelActivity.newIntent(applicationContext, deepLinkValueId)
)
handleCreatorAction(CreatorActionCommand.Profile(deepLinkValueId))
true
}
"live" -> {
if (deepLinkValueId == null || deepLinkValueId <= 0) {
return false
}
liveActionCoordinator.enterLiveRoom(deepLinkValueId)
true
}
@@ -745,15 +787,13 @@ class MainV2Activity : BaseActivity<ActivityMainV2Binding>(ActivityMainV2Binding
if (deepLinkValueId == null || deepLinkValueId <= 0) {
return false
}
startActivity(
Intent(applicationContext, CreatorCommunityAllActivity::class.java).apply {
putExtra(Constants.EXTRA_COMMUNITY_CREATOR_ID, deepLinkValueId)
}
)
// deepLinkValueId는 legacy creatorId이며 postId는 executeBundleRoute에서 먼저 처리한다.
handleCreatorAction(CreatorActionCommand.Profile(deepLinkValueId))
true
}
"message" -> {
// 현재 DM은 chat/{roomId} 계약을 사용하며 message는 legacy fallback이다.
startActivity(Intent(applicationContext, MessageActivity::class.java))
true
}

View File

@@ -11,14 +11,14 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.base.BaseFragment
import kr.co.vividnext.sodalive.chat.talk.room.ChatRoomActivity
import kr.co.vividnext.sodalive.common.LoadingDialog
import kr.co.vividnext.sodalive.databinding.FragmentV2MainChatBinding
import kr.co.vividnext.sodalive.mypage.can.charge.CanChargeActivity
import kr.co.vividnext.sodalive.search.SearchActivity
import kr.co.vividnext.sodalive.v2.access.AccessRequirement
import kr.co.vividnext.sodalive.v2.access.ensureV2Access
import kr.co.vividnext.sodalive.v2.main.chat.dm.DmChatRoomActivity
import kr.co.vividnext.sodalive.v2.chat.action.ChatActionCommand
import kr.co.vividnext.sodalive.v2.chat.action.handleChatAction
import kr.co.vividnext.sodalive.v2.main.chat.model.ChatRoomFilter
import kr.co.vividnext.sodalive.v2.main.chat.model.ChatRoomListUiItem
import kr.co.vividnext.sodalive.v2.main.chat.model.ChatRoomListUiState
@@ -173,8 +173,8 @@ class ChatMainFragment : BaseFragment<FragmentV2MainChatBinding>(
private fun onChatRoomClick(item: ChatRoomListUiItem) {
when (item.chatType) {
ChatRoomType.AI -> startActivity(ChatRoomActivity.newIntent(requireContext(), item.roomId))
ChatRoomType.DM -> startActivity(DmChatRoomActivity.newIntentByRoomId(requireContext(), item.roomId))
ChatRoomType.AI -> handleChatAction(ChatActionCommand.AiRoom(item.roomId))
ChatRoomType.DM -> handleChatAction(ChatActionCommand.DmRoom(item.roomId))
}
}

View File

@@ -30,6 +30,8 @@ import kr.co.vividnext.sodalive.v2.access.AccessRequirement
import kr.co.vividnext.sodalive.v2.access.ensureV2Access
import kr.co.vividnext.sodalive.v2.content.action.ContentActionCommand
import kr.co.vividnext.sodalive.v2.content.action.handleContentAction
import kr.co.vividnext.sodalive.v2.creator.action.CreatorActionCommand
import kr.co.vividnext.sodalive.v2.creator.action.handleCreatorAction
import kr.co.vividnext.sodalive.v2.main.content.data.AudioRankingType
import kr.co.vividnext.sodalive.v2.main.content.data.MainContentAllType
import kr.co.vividnext.sodalive.v2.main.content.model.AudioRankingsUiState
@@ -617,6 +619,9 @@ class ContentMainFragment : BaseFragment<FragmentV2MainContentBinding>(
private fun onBannerClick(item: ContentBannerUiModel) {
val route = item.toContentBannerRoute() ?: return
when (route) {
is ContentBannerRoute.Creator -> handleCreatorAction(
CreatorActionCommand.Profile(route.creatorId)
)
is ContentBannerRoute.Series -> handleContentAction(
ContentActionCommand.SeriesDetail(seriesId = route.seriesId)
)

View File

@@ -7,7 +7,6 @@ import kr.co.vividnext.sodalive.BuildConfig
import kr.co.vividnext.sodalive.common.Constants
import kr.co.vividnext.sodalive.settings.event.EventDetailActivity
import kr.co.vividnext.sodalive.settings.event.EventItem
import kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivity
import kr.co.vividnext.sodalive.v2.widget.AudioContentTag
data class ContentBannerSection(
@@ -68,7 +67,7 @@ fun ContentBannerRoute.toContentBannerIntent(context: Context): Intent? {
putExtra(Constants.EXTRA_EVENT, eventItem)
}
is ContentBannerRoute.Creator -> CreatorChannelActivity.newIntent(context, creatorId)
is ContentBannerRoute.Creator -> null
is ContentBannerRoute.Series -> null

View File

@@ -6,28 +6,30 @@ import android.view.View
import androidx.recyclerview.widget.LinearLayoutManager
import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.base.BaseFragment
import kr.co.vividnext.sodalive.common.LoadingDialog
import kr.co.vividnext.sodalive.common.ToastMessage
import kr.co.vividnext.sodalive.common.formatUtcRelativeTimeText
import kr.co.vividnext.sodalive.databinding.FragmentV2MainHomeBinding
import kr.co.vividnext.sodalive.databinding.ViewSectionTitleBinding
import kr.co.vividnext.sodalive.common.LoadingDialog
import kr.co.vividnext.sodalive.following.FollowingCreatorActivity
import kr.co.vividnext.sodalive.chat.talk.room.ChatRoomActivity
import kr.co.vividnext.sodalive.home.pushnotification.PushNotificationListActivity
import kr.co.vividnext.sodalive.live.LiveViewModel
import kr.co.vividnext.sodalive.mypage.can.charge.CanChargeActivity
import kr.co.vividnext.sodalive.search.SearchActivity
import kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivity
import kr.co.vividnext.sodalive.v2.creator.channel.community.detail.CreatorChannelCommunityDetailActivity
import kr.co.vividnext.sodalive.v2.live.onair.HomeOnAirLiveActivity
import kr.co.vividnext.sodalive.v2.live.action.LiveActionCoordinator
import kr.co.vividnext.sodalive.v2.main.chat.dm.DmChatRoomActivity
import kr.co.vividnext.sodalive.v2.main.chat.model.ChatRoomListUiItem
import kr.co.vividnext.sodalive.v2.main.chat.model.ChatRoomType
import kr.co.vividnext.sodalive.v2.access.AccessRequirement
import kr.co.vividnext.sodalive.v2.access.ensureV2Access
import kr.co.vividnext.sodalive.v2.chat.action.ChatActionCommand
import kr.co.vividnext.sodalive.v2.chat.action.handleChatAction
import kr.co.vividnext.sodalive.v2.community.action.CommunityActionCommand
import kr.co.vividnext.sodalive.v2.community.action.handleCommunityAction
import kr.co.vividnext.sodalive.v2.content.action.ContentActionCommand
import kr.co.vividnext.sodalive.v2.content.action.handleContentAction
import kr.co.vividnext.sodalive.v2.creator.action.CreatorActionCommand
import kr.co.vividnext.sodalive.v2.creator.action.handleCreatorAction
import kr.co.vividnext.sodalive.v2.live.action.LiveActionCoordinator
import kr.co.vividnext.sodalive.v2.live.onair.HomeOnAirLiveActivity
import kr.co.vividnext.sodalive.v2.main.chat.model.ChatRoomListUiItem
import kr.co.vividnext.sodalive.v2.main.chat.model.ChatRoomType
import kr.co.vividnext.sodalive.v2.main.home.model.HomeFollowingChatSection
import kr.co.vividnext.sodalive.v2.main.home.model.HomeFollowingCreatorSection
import kr.co.vividnext.sodalive.v2.main.home.model.HomeFollowingLiveSection
@@ -45,6 +47,7 @@ import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationBannerRoute
import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationBannerUiModel
import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationCheerCreatorSection
import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationGenreCreatorSection
import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationAiCharacterRoute
import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationLiveSection
import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationLiveUiModel
import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationPopularCommunityPostSection
@@ -56,7 +59,6 @@ import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationRecentlyAct
import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationUiState
import kr.co.vividnext.sodalive.v2.main.home.model.toHomeRecommendationBannerIntent
import kr.co.vividnext.sodalive.v2.main.home.model.toHomeRecommendationBannerRoute
import kr.co.vividnext.sodalive.v2.main.home.model.toHomeRecommendationAiCharacterIntent
import kr.co.vividnext.sodalive.v2.main.home.model.toHomeRecommendationAiCharacterRoute
import kr.co.vividnext.sodalive.v2.main.home.model.toHomeRecommendationRecentlyActiveCreatorRoute
import kr.co.vividnext.sodalive.v2.main.home.ui.HomeAiCharacterAdapter
@@ -556,9 +558,7 @@ class HomeMainFragment : BaseFragment<FragmentV2MainHomeBinding>(
val community = item as? HomeFollowingNewsUiItem.Community ?: return
val postId = community.postId
if (postId <= 0L) return
ensureV2Access(AccessRequirement.Login) {
startActivity(CreatorChannelCommunityDetailActivity.newIntent(requireContext(), postId))
}
handleCommunityAction(CommunityActionCommand.PostDetail(postId))
}
private fun openFollowingCreatorAll() {
@@ -568,17 +568,18 @@ class HomeMainFragment : BaseFragment<FragmentV2MainHomeBinding>(
}
private fun openFollowingChat(item: ChatRoomListUiItem) {
ensureV2Access(AccessRequirement.Login) {
when (item.chatType) {
ChatRoomType.AI -> startActivity(ChatRoomActivity.newIntent(requireContext(), item.roomId))
ChatRoomType.DM -> startActivity(DmChatRoomActivity.newIntentByRoomId(requireContext(), item.roomId))
}
when (item.chatType) {
ChatRoomType.AI -> handleChatAction(ChatActionCommand.AiRoom(item.roomId))
ChatRoomType.DM -> handleChatAction(ChatActionCommand.DmRoom(item.roomId))
}
}
private fun onBannerClick(item: HomeRecommendationBannerUiModel) {
val route = item.toHomeRecommendationBannerRoute() ?: return
when (route) {
is HomeRecommendationBannerRoute.Creator -> handleCreatorAction(
CreatorActionCommand.Profile(route.creatorId)
)
is HomeRecommendationBannerRoute.Series -> handleContentAction(
ContentActionCommand.SeriesDetail(seriesId = route.seriesId)
)
@@ -594,16 +595,18 @@ class HomeMainFragment : BaseFragment<FragmentV2MainHomeBinding>(
is HomeRecommendationRecentlyActiveCreatorRoute.AudioContent -> handleContentAction(
ContentActionCommand.AudioDetail(audioContentId = route.contentId)
)
is HomeRecommendationRecentlyActiveCreatorRoute.Community -> ensureV2Access(AccessRequirement.Login) {
startActivity(CreatorChannelCommunityDetailActivity.newIntent(requireContext(), route.postId))
}
is HomeRecommendationRecentlyActiveCreatorRoute.Community -> handleCommunityAction(
CommunityActionCommand.PostDetail(route.postId)
)
}
}
private fun onAiCharacterClick(item: HomeRecommendationAiCharacterUiModel) {
val route = item.toHomeRecommendationAiCharacterRoute() ?: return
ensureV2Access(AccessRequirement.Login) {
startActivity(route.toHomeRecommendationAiCharacterIntent(requireContext()))
when (route) {
is HomeRecommendationAiCharacterRoute.Creator -> handleCreatorAction(
CreatorActionCommand.Profile(route.creatorId)
)
}
}
@@ -613,18 +616,12 @@ class HomeMainFragment : BaseFragment<FragmentV2MainHomeBinding>(
}
private fun openCreatorProfile(creatorId: Long) {
ensureV2Access(AccessRequirement.Login) {
startActivity(
CreatorChannelActivity.newIntent(requireContext(), creatorId)
)
}
handleCreatorAction(CreatorActionCommand.Profile(creatorId))
}
private fun openPopularCommunityPost(item: FeedItem.Community) {
val postId = item.postId.toLongOrNull() ?: return
ensureV2Access(AccessRequirement.Login) {
startActivity(CreatorChannelCommunityDetailActivity.newIntent(requireContext(), postId))
}
handleCommunityAction(CommunityActionCommand.PostDetail(postId))
}
private fun showToast(toastMessage: ToastMessage) {

View File

@@ -8,10 +8,10 @@ import kr.co.vividnext.sodalive.common.Constants
import kr.co.vividnext.sodalive.settings.event.EventDetailActivity
import kr.co.vividnext.sodalive.settings.event.EventItem
import kr.co.vividnext.sodalive.v2.common.CreatorActivityType
import kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivity
import kr.co.vividnext.sodalive.v2.widget.characterchatthumbnail.CharacterChatThumbnailItem
import kr.co.vividnext.sodalive.v2.widget.feed.FeedItem
import java.util.Locale
import androidx.core.net.toUri
data class HomeRecommendationLiveSection(
val items: List<HomeRecommendationLiveUiModel>
@@ -105,13 +105,11 @@ fun HomeRecommendationBannerRoute.toHomeRecommendationBannerIntent(context: Cont
}
}
is HomeRecommendationBannerRoute.Creator -> {
CreatorChannelActivity.newIntent(context, creatorId)
}
is HomeRecommendationBannerRoute.Creator -> null
is HomeRecommendationBannerRoute.Series -> null
is HomeRecommendationBannerRoute.Link -> Intent(Intent.ACTION_VIEW, Uri.parse(url))
is HomeRecommendationBannerRoute.Link -> Intent(Intent.ACTION_VIEW, url.toUri())
}
}
@@ -163,12 +161,6 @@ fun HomeRecommendationAiCharacterUiModel.toHomeRecommendationAiCharacterRoute():
return creatorId.takeIf { it > 0L }?.let(HomeRecommendationAiCharacterRoute::Creator)
}
fun HomeRecommendationAiCharacterRoute.toHomeRecommendationAiCharacterIntent(context: Context): Intent {
return when (this) {
is HomeRecommendationAiCharacterRoute.Creator -> CreatorChannelActivity.newIntent(context, creatorId)
}
}
data class HomeRecommendationGenreCreatorGroupUiModel(
val genre: String,
val creators: List<HomeRecommendationCreatorUiModel>,

View File

@@ -8,14 +8,16 @@ import java.io.File
class DeepLinkActivitySourceTest {
@Test
fun `DeepLinkActivity는 chat_type 없이 chat path DM 채팅방으로 라우팅한다`() {
fun `DeepLinkActivity는 chat path DM을 Chat Action으로 라우팅한다`() {
val source = projectFile("app/src/main/java/kr/co/vividnext/sodalive/main/DeepLinkActivity.kt").readText()
assertTrue(source.contains("import kr.co.vividnext.sodalive.v2.main.chat.dm.DmChatRoomActivity"))
assertTrue(source.contains("import kr.co.vividnext.sodalive.v2.chat.action.ChatActionCommand"))
assertTrue(source.contains("import kr.co.vividnext.sodalive.v2.chat.action.handleChatAction"))
assertFalse(source.contains("chat_type"))
assertFalse(source.contains("isUserCreatorChat"))
assertTrue(source.contains("return bundle.getString(\"deep_link_value\") == \"chat\""))
assertTrue(source.contains("DmChatRoomActivity.newIntentByRoomId(applicationContext, roomId)"))
assertTrue(source.contains("handleChatAction(ChatActionCommand.DmRoom(roomId))"))
assertFalse(source.contains("DmChatRoomActivity.newIntentByRoomId(applicationContext, roomId)"))
assertTrue(source.contains("if (isDmChatDeepLink(bundle) && roomId != null && roomId > 0)"))
}
@@ -50,6 +52,47 @@ class DeepLinkActivitySourceTest {
assertTrue(dmRouteIndex < liveRouteIndex)
}
@Test
fun `DeepLinkActivity는 Community post를 creator fallback보다 먼저 Action으로 라우팅한다`() {
val source = projectFile("app/src/main/java/kr/co/vividnext/sodalive/main/DeepLinkActivity.kt").readText()
val postRouteIndex = source.indexOf("communityPostId != null && communityPostId > 0 ->")
val creatorFallbackIndex = source.indexOf("communityCreatorId != null && communityCreatorId > 0 ->")
assertTrue(postRouteIndex >= 0)
assertTrue(creatorFallbackIndex >= 0)
assertTrue(postRouteIndex < creatorFallbackIndex)
assertTrue(
source.contains(
"handleCommunityAction(CommunityActionCommand.PostDetail(communityPostId))"
)
)
assertTrue(
source.contains(
"handleCreatorAction(CreatorActionCommand.Profile(communityCreatorId))"
)
)
}
@Test
fun `DeepLinkActivity는 foreground 도메인 상세를 기존 Action으로 실행한다`() {
val source = projectFile("app/src/main/java/kr/co/vividnext/sodalive/main/DeepLinkActivity.kt").readText()
val routeSource = source.substringAfter("private fun routeByDeepLinkValue(")
.substringBefore("private fun applyPathDeepLink(")
val communityRoute = routeSource.substringAfter("\"community\" ->").substringBefore("\"message\" ->")
val messageRoute = routeSource.substringAfter("\"message\" ->").substringBefore("\"audition\" ->")
assertTrue(source.contains("handleContentAction(ContentActionCommand.AudioDetail(contentId))"))
assertTrue(source.contains("handleContentAction(ContentActionCommand.SeriesDetail(deepLinkValueId))"))
assertTrue(source.contains("handleCreatorAction(CreatorActionCommand.Profile(channelId))"))
assertTrue(source.contains("handleChatAction(ChatActionCommand.DmRoom(roomId))"))
assertTrue(source.contains("routeLiveInMain(roomId)"))
assertTrue(communityRoute.contains("handleCreatorAction(CreatorActionCommand.Profile(deepLinkValueId))"))
assertFalse(communityRoute.contains("CommunityActionCommand.PostDetail"))
assertTrue(messageRoute.contains("MessageActivity::class.java"))
assertFalse(messageRoute.contains("ChatActionCommand.DmRoom"))
}
private fun projectFile(relativePath: String): File {
val candidates = listOf(File(relativePath), File("../$relativePath"))
return candidates.firstOrNull { it.exists() }

View File

@@ -0,0 +1,176 @@
package kr.co.vividnext.sodalive.v2.chat.action
import android.app.Activity
import kr.co.vividnext.sodalive.chat.talk.room.ChatRoomActivity
import kr.co.vividnext.sodalive.v2.access.AccessRequirement
import kr.co.vividnext.sodalive.v2.main.MainV2Activity
import kr.co.vividnext.sodalive.v2.main.chat.dm.DmChatRoomActivity
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.Robolectric
import org.robolectric.RobolectricTestRunner
import org.robolectric.Shadows.shadowOf
import org.robolectric.annotation.Config
import java.io.File
@RunWith(RobolectricTestRunner::class)
@Config(application = android.app.Application::class)
class ChatActionTest {
private val action = ChatAction()
@Test
fun `유효하지 않은 room 또는 creator ID는 무시한다`() {
var accessCalls = 0
val results = listOf(
action.execute(ChatActionCommand.AiRoom(roomId = 0L)) { accessCalls += 1; true },
action.execute(ChatActionCommand.DmRoom(roomId = -1L)) { accessCalls += 1; true },
action.execute(ChatActionCommand.DmCreator(creatorId = 0L)) { accessCalls += 1; true }
)
assertEquals(List(3) { ChatActionResult.Ignored }, results)
assertEquals(0, accessCalls)
}
@Test
fun `AI room command는 AI 채팅방 navigation 결과를 반환한다`() {
val result = action.execute(ChatActionCommand.AiRoom(roomId = 41L)) { requirement ->
assertEquals(AccessRequirement.Login, requirement)
true
}
assertEquals(ChatActionResult.NavigateToAiRoom(roomId = 41L), result)
}
@Test
fun `DM command는 room과 creator 진입을 구분한다`() {
val dmRoom = action.execute(ChatActionCommand.DmRoom(roomId = 42L)) { requirement ->
assertEquals(AccessRequirement.Login, requirement)
true
}
val dmCreator = action.execute(ChatActionCommand.DmCreator(creatorId = 43L)) { requirement ->
assertEquals(AccessRequirement.Login, requirement)
true
}
assertEquals(ChatActionResult.NavigateToDmRoom(roomId = 42L), dmRoom)
assertEquals(ChatActionResult.NavigateToCreatorDm(creatorId = 43L), dmCreator)
}
@Test
fun `Owner DM command는 owner DM 목록 navigation 결과를 반환한다`() {
val result = action.execute(ChatActionCommand.OwnerDmList) { requirement ->
assertEquals(AccessRequirement.Login, requirement)
true
}
assertEquals(ChatActionResult.NavigateToOwnerDmList, result)
}
@Test
fun `로그인 접근이 차단되면 Blocked 결과를 반환한다`() {
val result = action.execute(ChatActionCommand.AiRoom(roomId = 41L)) { requirement ->
assertEquals(AccessRequirement.Login, requirement)
false
}
assertEquals(ChatActionResult.Blocked(AccessRequirement.Login), result)
}
@Test
fun `Handler는 AI room command를 기존 ChatRoomActivity로 연결한다`() {
val activity = Robolectric.buildActivity(Activity::class.java).setup().get()
ChatActionHandler().handle(activity, ChatActionCommand.AiRoom(roomId = 44L)) { true }
val intent = shadowOf(activity).nextStartedActivity
assertEquals(ChatRoomActivity::class.java.name, intent.component?.className)
}
@Test
fun `Handler는 DM room command에 기존 room ID extra를 전달한다`() {
val activity = Robolectric.buildActivity(Activity::class.java).setup().get()
ChatActionHandler().handle(activity, ChatActionCommand.DmRoom(roomId = 45L)) { true }
val intent = shadowOf(activity).nextStartedActivity
assertEquals(DmChatRoomActivity::class.java.name, intent.component?.className)
assertEquals(45L, intent.getLongExtra(DmChatRoomActivity.EXTRA_ROOM_ID, 0L))
}
@Test
fun `Handler는 DM creator command에 기존 creator ID extra를 전달한다`() {
val activity = Robolectric.buildActivity(Activity::class.java).setup().get()
ChatActionHandler().handle(activity, ChatActionCommand.DmCreator(creatorId = 46L)) { true }
val intent = shadowOf(activity).nextStartedActivity
assertEquals(DmChatRoomActivity::class.java.name, intent.component?.className)
assertEquals(46L, intent.getLongExtra(DmChatRoomActivity.EXTRA_CREATOR_ID, 0L))
}
@Test
fun `Handler는 owner DM command를 MainV2 DM 필터 intent로 연결한다`() {
val activity = Robolectric.buildActivity(Activity::class.java).setup().get()
ChatActionHandler().handle(activity, ChatActionCommand.OwnerDmList) { true }
val intent = shadowOf(activity).nextStartedActivity
val expected = MainV2Activity.newChatDmIntent(activity)
assertEquals(expected.component?.className, intent.component?.className)
assertEquals(expected.flags, intent.flags)
}
@Test
fun `Handler는 invalid chat command에서 Activity를 시작하지 않는다`() {
val activity = Robolectric.buildActivity(Activity::class.java).setup().get()
val result = ChatActionHandler().handle(activity, ChatActionCommand.DmRoom(roomId = 0L)) { true }
assertEquals(ChatActionResult.Ignored, result)
assertNull(shadowOf(activity).nextStartedActivity)
}
@Test
fun `Handler는 login 차단 결과에서 Activity를 시작하지 않는다`() {
val activity = Robolectric.buildActivity(Activity::class.java).setup().get()
val result = ChatActionHandler().handle(activity, ChatActionCommand.DmRoom(roomId = 45L)) { false }
assertEquals(ChatActionResult.Blocked(AccessRequirement.Login), result)
assertNull(shadowOf(activity).nextStartedActivity)
}
@Test
fun `대상 화면은 채팅방 Activity 대신 Chat Action을 사용한다`() {
val targetSources = listOf(
"app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragment.kt",
"app/src/main/java/kr/co/vividnext/sodalive/v2/main/chat/ChatMainFragment.kt",
"app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt"
).map { path -> projectFile(path).readText() }
val handlerSource = projectFile(
"app/src/main/java/kr/co/vividnext/sodalive/v2/chat/action/ChatActionHandler.kt"
).readText()
targetSources.forEach { source ->
assertTrue(source.contains("handleChatAction(ChatActionCommand."))
assertFalse(source.contains("ChatRoomActivity.newIntent"))
assertFalse(source.contains("DmChatRoomActivity.newIntentByRoomId"))
assertFalse(source.contains("DmChatRoomActivity.newIntentByCreatorId"))
}
assertTrue(handlerSource.contains("ChatRoomActivity.newIntent"))
assertTrue(handlerSource.contains("DmChatRoomActivity.newIntentByRoomId"))
assertTrue(handlerSource.contains("DmChatRoomActivity.newIntentByCreatorId"))
}
private fun projectFile(relativePath: String): File {
val candidates = listOf(File(relativePath), File("../$relativePath"))
return candidates.firstOrNull { it.exists() }
?: error("Project file not found: $relativePath")
}
}

View File

@@ -0,0 +1,114 @@
package kr.co.vividnext.sodalive.v2.community.action
import android.app.Activity
import kr.co.vividnext.sodalive.v2.access.AccessRequirement
import kr.co.vividnext.sodalive.v2.creator.channel.community.detail.CreatorChannelCommunityDetailActivity
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.Robolectric
import org.robolectric.RobolectricTestRunner
import org.robolectric.Shadows.shadowOf
import org.robolectric.annotation.Config
import java.io.File
@RunWith(RobolectricTestRunner::class)
@Config(application = android.app.Application::class)
class CommunityActionTest {
private val action = CommunityAction()
@Test
fun `유효하지 않은 post ID는 Access를 확인하지 않고 무시한다`() {
var accessCheckCount = 0
val result = action.execute(CommunityActionCommand.PostDetail(postId = 0L)) {
accessCheckCount += 1
true
}
assertEquals(CommunityActionResult.Ignored, result)
assertEquals(0, accessCheckCount)
}
@Test
fun `Community Post Detail은 로그인 Access가 허용되면 navigation 결과를 반환한다`() {
var capturedRequirement: AccessRequirement? = null
val result = action.execute(CommunityActionCommand.PostDetail(postId = 21L)) { requirement ->
capturedRequirement = requirement
true
}
assertEquals(AccessRequirement.Login, capturedRequirement)
assertEquals(CommunityActionResult.NavigateToPostDetail(postId = 21L), result)
}
@Test
fun `Access가 거부되면 Community Post Detail navigation을 차단한다`() {
val result = action.execute(CommunityActionCommand.PostDetail(postId = 22L)) { false }
assertEquals(CommunityActionResult.Blocked(AccessRequirement.Login), result)
}
@Test
fun `Handler는 기본 launch로 기존 Community Detail Activity를 시작한다`() {
val activity = Robolectric.buildActivity(Activity::class.java).setup().get()
val result = CommunityActionHandler().handle(
context = activity,
command = CommunityActionCommand.PostDetail(postId = 23L),
ensureAccess = { true }
)
val intent = shadowOf(activity).nextStartedActivity
assertEquals(CommunityActionResult.NavigateToPostDetail(postId = 23L), result)
assertEquals(CreatorChannelCommunityDetailActivity::class.java.name, intent.component?.className)
}
@Test
fun `Handler는 ActivityResultLauncher용 launch lambda를 사용할 수 있다`() {
val activity = Robolectric.buildActivity(Activity::class.java).setup().get()
var launchedComponent: String? = null
val result = CommunityActionHandler().handle(
context = activity,
command = CommunityActionCommand.PostDetail(postId = 24L),
ensureAccess = { true },
launchIntent = { intent -> launchedComponent = intent.component?.className }
)
assertEquals(CommunityActionResult.NavigateToPostDetail(postId = 24L), result)
assertEquals(CreatorChannelCommunityDetailActivity::class.java.name, launchedComponent)
assertNull(shadowOf(activity).nextStartedActivity)
}
@Test
fun `대상 화면은 Community Detail Activity 대신 Community Action을 사용한다`() {
val homeSource = projectFile(
"app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragment.kt"
).readText()
val activitySource = projectFile(
"app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt"
).readText()
val handlerSource = projectFile(
"app/src/main/java/kr/co/vividnext/sodalive/v2/community/action/CommunityActionHandler.kt"
).readText()
val targetSources = listOf(homeSource, activitySource)
targetSources.forEach { source ->
assertTrue(source.contains("handleCommunityAction(CommunityActionCommand.PostDetail"))
assertFalse(source.contains("CreatorChannelCommunityDetailActivity.newIntent"))
}
assertTrue(handlerSource.contains("CreatorChannelCommunityDetailActivity.newIntent"))
}
private fun projectFile(relativePath: String): File {
val candidates = listOf(File(relativePath), File("../$relativePath"))
return candidates.firstOrNull { it.exists() }
?: error("Project file not found: $relativePath")
}
}

View File

@@ -0,0 +1,56 @@
package kr.co.vividnext.sodalive.v2.community.action
import android.app.Activity
import org.junit.Assert.assertEquals
import org.junit.Test
class CommunityChangeTest {
@Test
fun `작성 Activity RESULT_OK는 Created 변경으로 변환한다`() {
val change = resolveCommunityActivityResult(
source = CommunityActivityResultSource.Write,
resultCode = Activity.RESULT_OK
)
assertEquals(CommunityChange.Created, change)
}
@Test
fun `수정 Activity RESULT_OK는 Updated 변경으로 변환한다`() {
val change = resolveCommunityActivityResult(
source = CommunityActivityResultSource.Modify,
resultCode = Activity.RESULT_OK
)
assertEquals(CommunityChange.Updated, change)
}
@Test
fun `상세 Activity RESULT_OK는 Updated 변경으로 변환한다`() {
val change = resolveCommunityActivityResult(
source = CommunityActivityResultSource.Detail,
resultCode = Activity.RESULT_OK
)
assertEquals(CommunityChange.Updated, change)
}
@Test
fun `취소 또는 실패 결과는 Community 변경을 무시한다`() {
val changes = CommunityActivityResultSource.entries.map { source ->
resolveCommunityActivityResult(source = source, resultCode = Activity.RESULT_CANCELED)
}
assertEquals(List(CommunityActivityResultSource.entries.size) { CommunityChange.Ignored }, changes)
}
@Test
fun `삭제와 고정 변경은 post ID를 가진 명시적 변경으로 표현한다`() {
val deleted = CommunityChange.Deleted(postId = 31L)
val pinChanged = CommunityChange.PinChanged(postId = 32L)
assertEquals(31L, deleted.postId)
assertEquals(32L, pinChanged.postId)
}
}

View File

@@ -0,0 +1,109 @@
package kr.co.vividnext.sodalive.v2.creator.action
import android.app.Activity
import kr.co.vividnext.sodalive.v2.access.AccessRequirement
import kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivity
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.Robolectric
import org.robolectric.RobolectricTestRunner
import org.robolectric.Shadows.shadowOf
import org.robolectric.annotation.Config
import java.io.File
@RunWith(RobolectricTestRunner::class)
@Config(application = android.app.Application::class)
class CreatorActionTest {
private val action = CreatorAction()
@Test
fun `유효하지 않은 creator ID는 Access를 확인하지 않고 무시한다`() {
var accessCheckCount = 0
val result = action.execute(CreatorActionCommand.Profile(creatorId = 0L)) {
accessCheckCount += 1
true
}
assertEquals(CreatorActionResult.Ignored, result)
assertEquals(0, accessCheckCount)
}
@Test
fun `Creator Profile은 로그인 Access가 허용되면 navigation 결과를 반환한다`() {
var capturedRequirement: AccessRequirement? = null
val result = action.execute(CreatorActionCommand.Profile(creatorId = 11L)) { requirement ->
capturedRequirement = requirement
true
}
assertEquals(AccessRequirement.Login, capturedRequirement)
assertEquals(CreatorActionResult.NavigateToCreatorProfile(creatorId = 11L), result)
}
@Test
fun `Access가 거부되면 Creator Profile navigation을 차단한다`() {
val result = action.execute(CreatorActionCommand.Profile(creatorId = 12L)) { false }
assertEquals(CreatorActionResult.Blocked(AccessRequirement.Login), result)
}
@Test
fun `Handler는 허용된 Creator Profile에 기존 creator ID extra를 전달한다`() {
val activity = Robolectric.buildActivity(Activity::class.java).setup().get()
val result = CreatorActionHandler().handle(
context = activity,
command = CreatorActionCommand.Profile(creatorId = 13L),
ensureAccess = { true }
)
val intent = shadowOf(activity).nextStartedActivity
assertEquals(CreatorActionResult.NavigateToCreatorProfile(creatorId = 13L), result)
assertEquals(CreatorChannelActivity::class.java.name, intent.component?.className)
assertEquals(13L, intent.getLongExtra(CreatorChannelActivity.EXTRA_CREATOR_ID, 0L))
}
@Test
fun `Handler는 invalid Creator Profile에서 Activity를 시작하지 않는다`() {
val activity = Robolectric.buildActivity(Activity::class.java).setup().get()
val result = CreatorActionHandler().handle(
context = activity,
command = CreatorActionCommand.Profile(creatorId = -1L),
ensureAccess = { true }
)
assertEquals(CreatorActionResult.Ignored, result)
assertNull(shadowOf(activity).nextStartedActivity)
}
@Test
fun `대상 화면은 Creator Channel Activity 대신 Creator Action을 사용한다`() {
val homeSource = projectFile(
"app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragment.kt"
).readText()
val homeModelSource = projectFile(
"app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/model/HomeRecommendationUiModels.kt"
).readText()
val contentModelSource = projectFile(
"app/src/main/java/kr/co/vividnext/sodalive/v2/main/content/model/AudioRecommendationsUiModels.kt"
).readText()
assertTrue(homeSource.contains("handleCreatorAction(CreatorActionCommand.Profile(creatorId))"))
assertFalse(homeModelSource.contains("CreatorChannelActivity.newIntent"))
assertFalse(contentModelSource.contains("CreatorChannelActivity.newIntent"))
}
private fun projectFile(relativePath: String): File {
val candidates = listOf(File(relativePath), File("../$relativePath"))
return candidates.firstOrNull { it.exists() }
?: error("Project file not found: $relativePath")
}
}

View File

@@ -40,8 +40,8 @@ class CreatorChannelActivitySourceTest {
assertTrue(source.contains("if (creatorId <= 0L)"))
assertTrue(source.contains("finish()"))
assertFalse(source.contains("is CreatorChannelHomeUiState.Error -> showToast"))
assertTrue(source.contains("ChatRoomActivity.newIntent(this, chatRoomId)"))
assertTrue(source.contains("DmChatRoomActivity.newIntentByCreatorId(this, creatorId)"))
assertTrue(source.contains("handleChatAction(ChatActionCommand.AiRoom(chatRoomId))"))
assertTrue(source.contains("handleChatAction(ChatActionCommand.DmCreator(creatorId))"))
assertTrue(source.contains("homeActionDelegate?.createChatRoom(characterId)"))
assertTrue(source.contains("updateActionButtonLayout"))
assertTrue(source.contains("marginStart = if (isChatVisible && isDmVisible)"))
@@ -93,7 +93,7 @@ class CreatorChannelActivitySourceTest {
}
@Test
fun `커뮤니티 게시물 클릭 source는 상세 Activity newIntent로 진입하고 invalid postId를 무시한다`() {
fun `커뮤니티 게시물 클릭 source는 공통 Community Action으로 진입한다`() {
val source = projectFile(
"app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt"
).readText()
@@ -104,27 +104,19 @@ class CreatorChannelActivitySourceTest {
assertTrue(handlerEnd > handlerStart)
val handlerSource = source.substring(handlerStart, handlerEnd)
assertTrue(
assertFalse(
source.contains(
"import kr.co.vividnext.sodalive.v2.creator.channel.community.detail.CreatorChannelCommunityDetailActivity"
)
)
assertTrue(handlerSource.contains("if (postId <= 0L) return"))
assertTrue(handlerSource.contains("ensureV2Access(AccessRequirement.Login)"))
assertTrue(handlerSource.contains("CreatorChannelCommunityDetailActivity.newIntent(this, postId)"))
assertTrue(handlerSource.contains("handleCommunityAction(CommunityActionCommand.PostDetail(postId)"))
assertTrue(source.contains("private val communityDetailLauncher = registerForActivityResult"))
assertTrue(
handlerSource.contains(
"communityDetailLauncher.launch(CreatorChannelCommunityDetailActivity.newIntent(this, postId))"
"handleCommunityAction(CommunityActionCommand.PostDetail(postId), communityDetailLauncher::launch)"
)
)
assertTrue(
handlerSource.indexOf("ensureV2Access(AccessRequirement.Login)") <
handlerSource.indexOf(
"communityDetailLauncher.launch(CreatorChannelCommunityDetailActivity.newIntent(this, postId))"
)
)
assertTrue(source.contains("refreshCreatorChannelCommunity()"))
assertTrue(source.contains("private fun handleCommunityChange(change: CommunityChange)"))
}
@Test
@@ -229,7 +221,7 @@ class CreatorChannelActivitySourceTest {
assertTrue(layout.contains("android:id=\"@+id/tv_chat_button\""))
assertTrue(layout.contains("android:id=\"@+id/tv_dm_button\""))
assertTrue(layout.contains("tools:visibility=\"visible\""))
assertTrue(source.contains("MainV2Activity.newChatDmIntent(this)"))
assertTrue(source.contains("handleChatAction(ChatActionCommand.OwnerDmList)"))
assertTrue(
source.contains(
"if (header.isOwner) R.string.creator_channel_dm_check_button else R.string.creator_channel_dm_button"
@@ -245,7 +237,7 @@ class CreatorChannelActivitySourceTest {
assertTrue(chat.contains("selectedIndex = initialFilter.tabIndex"))
assertTrue(chat.contains("fun selectFilter(filter: ChatRoomFilter)"))
assertTrue(filter.contains("DM(\"DM\", 2)"))
assertTrue(source.contains("DmChatRoomActivity.newIntentByCreatorId(this, creatorId)"))
assertTrue(source.contains("handleChatAction(ChatActionCommand.DmCreator(creatorId))"))
}
@Test
@@ -665,6 +657,86 @@ class CreatorChannelActivitySourceTest {
assertTrue(fragment.contains("mediaPlayerManager?.stopContent()"))
}
@Test
fun `커뮤니티 고정 성공은 실제 post id로 단일 변경 handler를 호출한다`() {
val source = projectFile(
"app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt"
).readText()
val fixedSource = sourceSection(
source = source,
startMarker = "private fun updateCreatorChannelCommunityPostFixed",
endMarker = "private fun deleteCreatorChannelCommunityPost"
)
val successMarker = "if (response.success)"
val beforeSuccessSource = fixedSource.substring(0, fixedSource.indexOf(successMarker))
val successSource = fixedSource.substring(
fixedSource.indexOf(successMarker),
fixedSource.indexOf("} else", fixedSource.indexOf(successMarker))
)
assertTrue(fixedSource.contains("postId = item.postId"))
assertTrue(fixedSource.contains("isFixed = !item.isPinned"))
assertTrue(successSource.contains("handleCommunityChange(CommunityChange.PinChanged(item.postId))"))
assertFalse(beforeSuccessSource.contains("CommunityChange.PinChanged"))
assertEquals(1, fixedSource.split("CommunityChange.PinChanged").size - 1)
assertFalse(fixedSource.contains("CommunityChange.PinChanged(creatorId)"))
assertFalse(fixedSource.contains("refreshCreatorChannelCommunity()"))
}
@Test
fun `커뮤니티 삭제 성공은 실제 post id로 단일 변경 handler를 호출한다`() {
val source = projectFile(
"app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt"
).readText()
val deleteSource = sourceSection(
source = source,
startMarker = "private fun deleteCreatorChannelCommunityPost",
endMarker = "private fun authToken()"
)
val successMarker = "if (response.success)"
val beforeSuccessSource = deleteSource.substring(0, deleteSource.indexOf(successMarker))
val successSource = deleteSource.substring(
deleteSource.indexOf(successMarker),
deleteSource.indexOf("} else", deleteSource.indexOf(successMarker))
)
assertTrue(deleteSource.contains("creatorCommunityId = item.postId"))
assertTrue(deleteSource.contains("isActive = false"))
assertTrue(successSource.contains("handleCommunityChange(CommunityChange.Deleted(item.postId))"))
assertFalse(beforeSuccessSource.contains("CommunityChange.Deleted"))
assertEquals(1, deleteSource.split("CommunityChange.Deleted").size - 1)
assertFalse(deleteSource.contains("CommunityChange.Deleted(creatorId)"))
assertFalse(deleteSource.contains("refreshCreatorChannelCommunity()"))
}
@Test
fun `커뮤니티 변경 handler는 변경 종류별 projection 갱신을 단일 소유한다`() {
val source = projectFile(
"app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt"
).readText()
val handlerSource = sourceSection(
source = source,
startMarker = "private fun handleCommunityChange(change: CommunityChange)",
endMarker = "private fun refreshCreatorChannelFanTalk()"
)
val createdSource = handlerSource.substring(
handlerSource.indexOf("CommunityChange.Created"),
handlerSource.indexOf("CommunityChange.Updated")
)
val mutationSource = handlerSource.substring(handlerSource.indexOf("CommunityChange.Updated"))
assertTrue(createdSource.contains("homeActionDelegate?.refreshHome()"))
assertTrue(createdSource.contains("refreshCreatorChannelCommunity()"))
assertTrue(
mutationSource.contains(
"CommunityChange.Updated,\n" +
" is CommunityChange.Deleted,\n" +
" is CommunityChange.PinChanged -> refreshCreatorChannelCommunity()"
)
)
assertFalse(mutationSource.contains("homeActionDelegate?.refreshHome()"))
}
@Test
fun `FanTalk tab source는 Fragment Host pagination height delete dialog를 Activity에 연결한다`() {
val source = projectFile(
@@ -1100,7 +1172,7 @@ class CreatorChannelActivitySourceTest {
assertTrue(fragment.contains("onCommunityClick = ::onCommunityClicked"))
assertTrue(fragment.contains("host.onCreatorChannelCommunityPostClicked(postId)"))
assertTrue(activity.contains("override fun onCreatorChannelCommunityPostClicked(postId: Long)"))
assertTrue(activity.contains("CreatorChannelCommunityDetailActivity.newIntent(this, postId)"))
assertTrue(activity.contains("handleCommunityAction(CommunityActionCommand.PostDetail(postId)"))
}
@Test

View File

@@ -8,15 +8,17 @@ import java.io.File
class MainV2ActivitySourceTest {
@Test
fun `MainV2Activity는 chat_type 없이 chat path DM 채팅방으로 라우팅한다`() {
fun `MainV2Activity는 chat path DM을 Chat Action으로 라우팅한다`() {
val source = projectFile("app/src/main/java/kr/co/vividnext/sodalive/v2/main/MainV2Activity.kt").readText()
assertTrue(source.contains("import kr.co.vividnext.sodalive.v2.main.chat.dm.DmChatRoomActivity"))
assertTrue(source.contains("import kr.co.vividnext.sodalive.v2.chat.action.ChatActionCommand"))
assertTrue(source.contains("import kr.co.vividnext.sodalive.v2.chat.action.handleChatAction"))
assertFalse(source.contains("chat_type"))
assertFalse(source.contains("isUserCreatorChat"))
assertTrue(source.contains("return bundle.getString(\"deep_link_value\") == \"chat\""))
assertTrue(source.contains("val roomId = bundle.getString(\"room_id\")?.toLongOrNull()"))
assertTrue(source.contains("DmChatRoomActivity.newIntentByRoomId(applicationContext, roomId)"))
assertTrue(source.contains("handleChatAction(ChatActionCommand.DmRoom(roomId))"))
assertFalse(source.contains("DmChatRoomActivity.newIntentByRoomId(applicationContext, roomId)"))
assertTrue(source.contains("if (isDmChatDeepLink(bundle) && roomId != null && roomId > 0)"))
assertFalse(source.contains("private fun isLoggedIn()"))
assertTrue(source.contains("fun showLoginActivity()"))
@@ -36,6 +38,15 @@ class MainV2ActivitySourceTest {
assertTrue(source.contains("putQuery(\"room_id\")"))
}
@Test
fun `MainV2Activity는 community postId query를 canonical extra로 정규화한다`() {
val source = projectFile("app/src/main/java/kr/co/vividnext/sodalive/v2/main/MainV2Activity.kt").readText()
assertTrue(source.contains("putQuery(\"postId\")"))
assertTrue(source.contains("extras.getString(\"postId\")?.takeIf { it.isNotBlank() }?.let"))
assertTrue(source.contains("extras.putString(Constants.EXTRA_COMMUNITY_POST_ID, it)"))
}
@Test
fun `MainV2Activity는 chat path deep_link 단독 payload를 DM 채팅방으로 라우팅한다`() {
val source = projectFile("app/src/main/java/kr/co/vividnext/sodalive/v2/main/MainV2Activity.kt").readText()
@@ -53,6 +64,104 @@ class MainV2ActivitySourceTest {
assertTrue(dmRouteIndex < firstFallbackIndex)
}
@Test
fun `MainV2Activity는 non-DM room을 channel보다 먼저 Live Action으로 라우팅한다`() {
val source = projectFile("app/src/main/java/kr/co/vividnext/sodalive/v2/main/MainV2Activity.kt").readText()
val routeSource = source.substringFrom("private fun executeBundleRoute(bundle: Bundle): Boolean")
assertTrue(source.contains("import kr.co.vividnext.sodalive.v2.live.action.LiveActionCoordinator"))
assertTrue(source.contains("private val liveActionCoordinator: LiveActionCoordinator by lazy"))
assertTrue(routeSource.contains("roomId != null && roomId > 0 ->"))
assertTrue(routeSource.contains("liveActionCoordinator.enterLiveRoom(roomId)"))
assertBefore(
routeSource,
"roomId != null && roomId > 0 ->",
"channelId != null && channelId > 0 ->"
)
}
@Test
fun `MainV2Activity는 cold start 딥링크 공통 대기와 Live 조회에 문구 없는 로딩을 유지한다`() {
val source = projectFile("app/src/main/java/kr/co/vividnext/sodalive/v2/main/MainV2Activity.kt").readText()
val onCreateSource = source.substringFrom("override fun onCreate(savedInstanceState: Bundle?)")
val observerSource = source.substringFrom("private fun setupLiveEntryObservers()")
val updateSource = source.substringFrom("private fun updateRouteLoadingDialog()")
val setupViewSource = source.substringFrom("override fun setupView()")
assertTrue(source.contains("import kr.co.vividnext.sodalive.common.LoadingDialog"))
assertTrue(source.contains("private lateinit var routeLoadingDialog: LoadingDialog"))
assertTrue(source.contains("private var isDeferredRouteLoading = false"))
assertTrue(source.contains("private var isLiveEntryLoading = false"))
assertTrue(observerSource.contains("routeLoadingDialog = LoadingDialog(this, layoutInflater)"))
assertTrue(observerSource.contains("liveViewModel.toastLiveData.observe(this)"))
assertTrue(observerSource.contains("it?.let(::showToast)"))
assertTrue(observerSource.contains("liveViewModel.isLoading.observe(this)"))
assertTrue(observerSource.contains("isLiveEntryLoading = isLoading"))
assertTrue(observerSource.contains("updateRouteLoadingDialog()"))
assertTrue(
onCreateSource.contains(
"intent.hasExtra(Constants.EXTRA_DATA) || intent.hasExtra(EXTRA_AUDIO_NOTIFICATION_ROUTE)"
)
)
assertBefore(onCreateSource, "isDeferredRouteLoading =", "handler.postDelayed")
assertTrue(onCreateSource.contains("isDeferredRouteLoading = false"))
assertTrue(updateSource.contains("if (isDeferredRouteLoading || isLiveEntryLoading)"))
assertTrue(updateSource.contains("routeLoadingDialog.show(screenWidth)"))
assertFalse(updateSource.contains("R.string.screen_live_loading"))
assertTrue(updateSource.contains("routeLoadingDialog.dismiss()"))
assertTrue(setupViewSource.contains("setupLiveEntryObservers()"))
}
@Test
fun `MainV2Activity는 Community post를 creator fallback보다 먼저 Action으로 라우팅한다`() {
val source = projectFile("app/src/main/java/kr/co/vividnext/sodalive/v2/main/MainV2Activity.kt").readText()
val routeSource = source.substringFrom("private fun executeBundleRoute(bundle: Bundle): Boolean")
assertTrue(routeSource.contains("communityPostId != null && communityPostId > 0 ->"))
assertTrue(
routeSource.contains(
"handleCommunityAction(CommunityActionCommand.PostDetail(communityPostId))"
)
)
assertTrue(routeSource.contains("communityCreatorId != null && communityCreatorId > 0 ->"))
assertTrue(
routeSource.contains(
"handleCreatorAction(CreatorActionCommand.Profile(communityCreatorId))"
)
)
assertBefore(
routeSource,
"communityPostId != null && communityPostId > 0 ->",
"communityCreatorId != null && communityCreatorId > 0 ->"
)
}
@Test
fun `MainV2Activity는 deep-link 도메인 상세를 기존 Action으로 실행한다`() {
val source = projectFile("app/src/main/java/kr/co/vividnext/sodalive/v2/main/MainV2Activity.kt").readText()
val routeSource = source.substringFrom("private fun routeByDeepLinkValue(")
val communityRoute = routeSource.substringAfter("\"community\" ->").substringBefore("\"message\" ->")
val messageRoute = routeSource.substringAfter("\"message\" ->").substringBefore("\"audition\" ->")
assertTrue(routeSource.contains("handleContentAction(ContentActionCommand.SeriesDetail(deepLinkValueId))"))
assertTrue(routeSource.contains("handleContentAction(ContentActionCommand.AudioDetail(deepLinkValueId))"))
assertTrue(routeSource.contains("handleCreatorAction(CreatorActionCommand.Profile(deepLinkValueId))"))
assertTrue(routeSource.contains("liveActionCoordinator.enterLiveRoom(deepLinkValueId)"))
assertTrue(communityRoute.contains("handleCreatorAction(CreatorActionCommand.Profile(deepLinkValueId))"))
assertFalse(communityRoute.contains("CommunityActionCommand.PostDetail"))
assertTrue(messageRoute.contains("MessageActivity::class.java"))
assertFalse(messageRoute.contains("ChatActionCommand.DmRoom"))
}
@Test
fun `MainV2Activity는 audio detail notification을 Content Action으로 실행한다`() {
val source = projectFile("app/src/main/java/kr/co/vividnext/sodalive/v2/main/MainV2Activity.kt").readText()
val routeSource = source.substringFrom("private fun handleAudioNotificationRoute(intent: Intent): Boolean")
assertTrue(routeSource.contains("handleContentAction(ContentActionCommand.AudioDetail(contentId))"))
assertFalse(routeSource.contains("Intent(applicationContext, AudioContentDetailActivity::class.java)"))
}
@Test
fun `MainV2Activity 대화 탭 이동은 로그인 가드를 통과한 뒤 탭을 전환한다`() {
val source = projectFile("app/src/main/java/kr/co/vividnext/sodalive/v2/main/MainV2Activity.kt").readText()

View File

@@ -17,7 +17,7 @@ class MainV2AudioNotificationRouteSourceTest {
assertTrue(source.contains("const val ROUTE_AUDIO_DETAIL"))
assertTrue(source.contains("handleAudioNotificationRoute"))
assertTrue(source.contains("showPlayerFragment()"))
assertTrue(source.contains("AudioContentDetailActivity::class.java"))
assertTrue(source.contains("ContentActionCommand.AudioDetail"))
assertTrue(source.contains("Constants.EXTRA_AUDIO_CONTENT_ID"))
assertTrue(source.contains("removeExtra(EXTRA_AUDIO_NOTIFICATION_ROUTE)"))
}
@@ -41,7 +41,11 @@ class MainV2AudioNotificationRouteSourceTest {
assertTrue(routeSource.contains("val contentId = intent.getLongExtra(Constants.EXTRA_AUDIO_CONTENT_ID, 0)"))
assertTrue(routeSource.contains("intent.removeExtra(Constants.EXTRA_AUDIO_CONTENT_ID)"))
assertTrue(routeSource.contains("if (contentId > 0"))
assertBefore(routeSource, "if (contentId > 0", "AudioContentDetailActivity::class.java")
assertBefore(
routeSource,
"if (contentId > 0",
"handleContentAction(ContentActionCommand.AudioDetail(contentId))"
)
}
private fun assertBefore(source: String, expectedBefore: String, expectedAfter: String) {

View File

@@ -132,9 +132,11 @@ class ChatMainFragmentLayoutTest {
assertTrue(source.contains("private fun onChatRoomClick(item: ChatRoomListUiItem)"))
assertTrue(source.contains("when (item.chatType)"))
assertTrue(source.contains("ChatRoomType.AI ->"))
assertTrue(source.contains("ChatRoomActivity.newIntent(requireContext(), item.roomId)"))
assertTrue(source.contains("handleChatAction(ChatActionCommand.AiRoom(item.roomId))"))
assertTrue(source.contains("ChatRoomType.DM ->"))
assertTrue(source.contains("DmChatRoomActivity.newIntentByRoomId(requireContext(), item.roomId)"))
assertTrue(source.contains("handleChatAction(ChatActionCommand.DmRoom(item.roomId))"))
assertFalse(source.contains("ChatRoomActivity.newIntent(requireContext(), item.roomId)"))
assertFalse(source.contains("DmChatRoomActivity.newIntentByRoomId(requireContext(), item.roomId)"))
}
@Test

View File

@@ -6,7 +6,6 @@ import kr.co.vividnext.sodalive.BuildConfig
import kr.co.vividnext.sodalive.common.Constants
import kr.co.vividnext.sodalive.settings.event.EventDetailActivity
import kr.co.vividnext.sodalive.settings.event.EventItem
import kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivity
import kr.co.vividnext.sodalive.v2.main.content.model.ContentBannerRoute
import kr.co.vividnext.sodalive.v2.main.content.model.ContentBannerUiModel
import kr.co.vividnext.sodalive.v2.main.content.model.toContentBannerIntent
@@ -467,7 +466,7 @@ class ContentMainFragmentSourceTest {
val context = RuntimeEnvironment.getApplication() as Context
val eventItem = EventItem(id = 1L, thumbnailImageUrl = "https://example.com/event.png")
val eventIntent = requireNotNull(ContentBannerRoute.Event(eventItem).toContentBannerIntent(context))
val creatorIntent = requireNotNull(ContentBannerRoute.Creator(2L).toContentBannerIntent(context))
val creatorIntent = ContentBannerRoute.Creator(2L).toContentBannerIntent(context)
val seriesIntent = ContentBannerRoute.Series(3L).toContentBannerIntent(context)
val webIntent = requireNotNull(
ContentBannerRoute.Link("https://example.com", isWebUrl = true).toContentBannerIntent(context)
@@ -481,8 +480,7 @@ class ContentMainFragmentSourceTest {
assertEquals(EventDetailActivity::class.java.name, eventIntent.component?.className)
assertEquals(eventItem, eventIntent.getParcelableExtra(Constants.EXTRA_EVENT))
assertEquals(CreatorChannelActivity::class.java.name, creatorIntent.component?.className)
assertEquals(2L, creatorIntent.getLongExtra(CreatorChannelActivity.EXTRA_CREATOR_ID, 0L))
assertNull(creatorIntent)
assertNull(seriesIntent)
assertEquals(android.content.Intent.ACTION_VIEW, webIntent.action)
assertEquals("https://example.com", webIntent.data.toString())

View File

@@ -27,7 +27,6 @@ import kr.co.vividnext.sodalive.common.Constants
import kr.co.vividnext.sodalive.common.formatUtcRelativeTimeText
import kr.co.vividnext.sodalive.settings.event.EventDetailActivity
import kr.co.vividnext.sodalive.settings.event.EventItem
import kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivity
import kr.co.vividnext.sodalive.v2.main.home.data.HomeActiveCreatorItem
import kr.co.vividnext.sodalive.v2.main.home.data.HomeAiCharacterItem
import kr.co.vividnext.sodalive.v2.main.home.data.HomeBannerItem
@@ -56,7 +55,6 @@ import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationRecentlyAct
import kr.co.vividnext.sodalive.v2.common.CreatorActivityType
import kr.co.vividnext.sodalive.v2.main.home.model.toHomeRecommendationBannerIntent
import kr.co.vividnext.sodalive.v2.main.home.model.toHomeRecommendationBannerRoute
import kr.co.vividnext.sodalive.v2.main.home.model.toHomeRecommendationAiCharacterIntent
import kr.co.vividnext.sodalive.v2.main.home.model.toHomeRecommendationAiCharacterRoute
import kr.co.vividnext.sodalive.v2.main.home.model.toHomeRecommendationRecentlyActiveCreatorRoute
import kr.co.vividnext.sodalive.v2.main.home.model.visibleHomePopularCommunityPosts
@@ -872,9 +870,7 @@ class HomeMainFragmentLayoutTest {
val eventIntent = requireNotNull(
HomeRecommendationBannerRoute.Event(eventItem).toHomeRecommendationBannerIntent(context)
)
val creatorIntent = requireNotNull(
HomeRecommendationBannerRoute.Creator(2L).toHomeRecommendationBannerIntent(context)
)
val creatorIntent = HomeRecommendationBannerRoute.Creator(2L).toHomeRecommendationBannerIntent(context)
val seriesIntent = HomeRecommendationBannerRoute.Series(3L).toHomeRecommendationBannerIntent(context)
val webIntent = requireNotNull(
HomeRecommendationBannerRoute.Link(
@@ -891,8 +887,7 @@ class HomeMainFragmentLayoutTest {
assertEquals(EventDetailActivity::class.java.name, eventIntent.component?.className)
assertEquals(eventItem, eventIntent.getParcelableExtra(Constants.EXTRA_EVENT))
assertEquals(CreatorChannelActivity::class.java.name, creatorIntent.component?.className)
assertEquals(2L, creatorIntent.getLongExtra(CreatorChannelActivity.EXTRA_CREATOR_ID, 0L))
assertNull(creatorIntent)
assertNull(seriesIntent)
assertEquals(android.content.Intent.ACTION_VIEW, webIntent.action)
assertEquals("https://example.com", webIntent.data.toString())
@@ -992,15 +987,6 @@ class HomeMainFragmentLayoutTest {
assertEquals(null, aiCharacter(creatorId = -1L).toHomeRecommendationAiCharacterRoute())
}
@Test
fun `home ai character route creates creator channel intent`() {
val context = ApplicationProvider.getApplicationContext<Context>()
val intent = HomeRecommendationAiCharacterRoute.Creator(22L).toHomeRecommendationAiCharacterIntent(context)
assertEquals(CreatorChannelActivity::class.java.name, intent.component?.className)
assertEquals(22L, intent.getLongExtra(CreatorChannelActivity.EXTRA_CREATOR_ID, 0L))
}
@Test
fun `home popular community adapter applies blur when locked paid post image is loaded`() {
val source = projectFile(

View File

@@ -15,15 +15,11 @@ class HomeMainFragmentLoginGuardSourceTest {
assertTrue(source.contains("import kr.co.vividnext.sodalive.v2.access.ensureV2Access"))
assertGuardedStartActivity(source, "private fun openHomeOnAirLive()")
assertGuardedStartActivity(source, "private fun openFollowingChat(item: ChatRoomListUiItem)")
assertGuardedStartActivity(source, "private fun onBannerClick(item: HomeRecommendationBannerUiModel)")
assertGuardedStartActivity(
source,
"private fun onRecentActivityClick(item: HomeRecommendationRecentlyActiveCreatorUiModel)"
)
assertGuardedStartActivity(source, "private fun onAiCharacterClick(item: HomeRecommendationAiCharacterUiModel)")
assertGuardedStartActivity(source, "private fun openCreatorProfile(creatorId: Long)")
assertGuardedStartActivity(source, "private fun openPopularCommunityPost(item: FeedItem.Community)")
assertTrue(source.contains("handleChatAction(ChatActionCommand.AiRoom"))
assertTrue(source.contains("handleChatAction(ChatActionCommand.DmRoom"))
assertTrue(source.contains("handleCommunityAction(CommunityActionCommand.PostDetail"))
assertTrue(source.contains("handleCreatorAction(CreatorActionCommand.Profile"))
}
@Test
@@ -33,9 +29,9 @@ class HomeMainFragmentLoginGuardSourceTest {
).readText()
assertBeforeGuard(source, "val route = item.toHomeRecommendationBannerRoute() ?: return")
assertBeforeGuard(source, "val route = item.toHomeRecommendationRecentlyActiveCreatorRoute() ?: return")
assertBeforeGuard(source, "val route = item.toHomeRecommendationAiCharacterRoute() ?: return")
assertBeforeGuard(source, "val postId = item.postId.toLongOrNull() ?: return")
assertBefore(source, "val route = item.toHomeRecommendationRecentlyActiveCreatorRoute() ?: return", "handle")
assertBefore(source, "val route = item.toHomeRecommendationAiCharacterRoute() ?: return", "handle")
assertBefore(source, "val postId = item.postId.toLongOrNull() ?: return", "handleCommunityAction")
assertFalse(source.contains("requiresAdultContentAccess = true"))
}
@@ -46,7 +42,8 @@ class HomeMainFragmentLoginGuardSourceTest {
).readText()
val clickSource = source.substringFrom("private fun openPopularCommunityPost(item: FeedItem.Community)")
assertTrue(clickSource.contains("CreatorChannelCommunityDetailActivity.newIntent(requireContext(), postId)"))
assertTrue(clickSource.contains("handleCommunityAction(CommunityActionCommand.PostDetail(postId))"))
assertFalse(clickSource.contains("CreatorChannelCommunityDetailActivity.newIntent"))
assertFalse(clickSource.contains("CreatorCommunityAllActivity"))
assertFalse(clickSource.contains("EXTRA_COMMUNITY_CREATOR_ID"))
assertFalse(clickSource.contains("EXTRA_COMMUNITY_POST_ID"))
@@ -65,14 +62,15 @@ class HomeMainFragmentLoginGuardSourceTest {
assertBefore(
clickSource,
"val community = item as? HomeFollowingNewsUiItem.Community ?: return",
"ensureV2Access"
"handleCommunityAction"
)
assertBefore(
clickSource,
"if (postId <= 0L) return",
"ensureV2Access"
"handleCommunityAction"
)
assertTrue(clickSource.contains("CreatorChannelCommunityDetailActivity.newIntent(requireContext(), postId)"))
assertTrue(clickSource.contains("handleCommunityAction(CommunityActionCommand.PostDetail(postId))"))
assertFalse(clickSource.contains("CreatorChannelCommunityDetailActivity.newIntent"))
assertFalse(clickSource.contains("CreatorCommunityAllActivity"))
assertFalse(clickSource.contains("EXTRA_COMMUNITY_POST_ID"))
}
@@ -177,6 +175,14 @@ class HomeMainFragmentLoginGuardSourceTest {
)
}
private fun assertGuardedAction(source: String, functionSignature: String, actionCall: String) {
val functionSource = source.substringFrom(functionSignature)
assertTrue(
"$functionSignature must call ensureV2Access before $actionCall.",
functionSource.indexOf("ensureV2Access") in 0 until functionSource.indexOf(actionCall)
)
}
private fun assertBeforeGuard(source: String, expectedReturn: String) {
val returnIndex = source.indexOf(expectedReturn)
val guardIndex = source.indexOf("ensureV2Access", returnIndex)