feat(main): 라이브룸 딥링크 지연 처리를 추가한다
This commit is contained in:
@@ -55,6 +55,7 @@ import kr.co.vividnext.sodalive.databinding.ActivityMainV2Binding
|
|||||||
import kr.co.vividnext.sodalive.explorer.profile.creator_community.write.CreatorCommunityWriteActivity
|
import kr.co.vividnext.sodalive.explorer.profile.creator_community.write.CreatorCommunityWriteActivity
|
||||||
import kr.co.vividnext.sodalive.extensions.dpToPx
|
import kr.co.vividnext.sodalive.extensions.dpToPx
|
||||||
import kr.co.vividnext.sodalive.live.LiveViewModel
|
import kr.co.vividnext.sodalive.live.LiveViewModel
|
||||||
|
import kr.co.vividnext.sodalive.live.room.LiveRoomActivity
|
||||||
import kr.co.vividnext.sodalive.live.room.create.LiveRoomCreateActivity
|
import kr.co.vividnext.sodalive.live.room.create.LiveRoomCreateActivity
|
||||||
import kr.co.vividnext.sodalive.main.EventPopupDialogFragment
|
import kr.co.vividnext.sodalive.main.EventPopupDialogFragment
|
||||||
import kr.co.vividnext.sodalive.message.MessageActivity
|
import kr.co.vividnext.sodalive.message.MessageActivity
|
||||||
@@ -107,6 +108,18 @@ class MainV2Activity : BaseActivity<ActivityMainV2Binding>(ActivityMainV2Binding
|
|||||||
private var mediaControllerFuture: ListenableFuture<MediaController>? = null
|
private var mediaControllerFuture: ListenableFuture<MediaController>? = null
|
||||||
private val handler = Handler(Looper.getMainLooper())
|
private val handler = Handler(Looper.getMainLooper())
|
||||||
private val showMiniPlayerRunnable = Runnable { initAndVisibleMiniPlayer() }
|
private val showMiniPlayerRunnable = Runnable { initAndVisibleMiniPlayer() }
|
||||||
|
private val deferredRouteRunnable = Runnable {
|
||||||
|
if (isFinishing || isDestroyed) return@Runnable
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!handleAudioNotificationRoute(intent) && isV2AccessAllowed(AccessRequirement.Login)) {
|
||||||
|
executeDeeplink(intent)
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
isDeferredRouteLoading = false
|
||||||
|
updateRouteLoadingDialog()
|
||||||
|
}
|
||||||
|
}
|
||||||
private val audioContentReceiver = AudioContentReceiver()
|
private val audioContentReceiver = AudioContentReceiver()
|
||||||
private var playerStateJob: Job? = null
|
private var playerStateJob: Job? = null
|
||||||
private var isDeferredRouteLoading = false
|
private var isDeferredRouteLoading = false
|
||||||
@@ -144,13 +157,7 @@ class MainV2Activity : BaseActivity<ActivityMainV2Binding>(ActivityMainV2Binding
|
|||||||
trackAppLaunchIfNeeded()
|
trackAppLaunchIfNeeded()
|
||||||
pushTokenUpdate()
|
pushTokenUpdate()
|
||||||
|
|
||||||
handler.postDelayed({
|
handler.postDelayed(deferredRouteRunnable, 1000)
|
||||||
if (!handleAudioNotificationRoute(intent) && isV2AccessAllowed(AccessRequirement.Login)) {
|
|
||||||
executeDeeplink(intent)
|
|
||||||
}
|
|
||||||
isDeferredRouteLoading = false
|
|
||||||
updateRouteLoadingDialog()
|
|
||||||
}, 1000)
|
|
||||||
|
|
||||||
if (isV2AccessAllowed(AccessRequirement.Login)) {
|
if (isV2AccessAllowed(AccessRequirement.Login)) {
|
||||||
updatePidAndGaid()
|
updatePidAndGaid()
|
||||||
@@ -177,15 +184,27 @@ class MainV2Activity : BaseActivity<ActivityMainV2Binding>(ActivityMainV2Binding
|
|||||||
override fun onNewIntent(intent: Intent) {
|
override fun onNewIntent(intent: Intent) {
|
||||||
super.onNewIntent(intent)
|
super.onNewIntent(intent)
|
||||||
setIntent(intent)
|
setIntent(intent)
|
||||||
|
handler.removeCallbacks(deferredRouteRunnable)
|
||||||
|
isDeferredRouteLoading = false
|
||||||
|
updateRouteLoadingDialog()
|
||||||
|
|
||||||
if (intent.hasExtra(EXTRA_CHAT_FILTER)) {
|
if (intent.hasExtra(EXTRA_CHAT_FILTER)) {
|
||||||
openChatWithInitialFilter()
|
openChatWithInitialFilter()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!handleAudioNotificationRoute(intent) && isV2AccessAllowed(AccessRequirement.Login)) {
|
if (handleAudioNotificationRoute(intent) || !isV2AccessAllowed(AccessRequirement.Login)) {
|
||||||
executeDeeplink(intent)
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (LiveRoomActivity.isForeground && isLiveRoomDeepLink(intent)) {
|
||||||
|
isDeferredRouteLoading = true
|
||||||
|
updateRouteLoadingDialog()
|
||||||
|
handler.postDelayed(deferredRouteRunnable, 1000)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
executeDeeplink(intent)
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("UnspecifiedRegisterReceiverFlag")
|
@SuppressLint("UnspecifiedRegisterReceiverFlag")
|
||||||
@@ -212,6 +231,9 @@ class MainV2Activity : BaseActivity<ActivityMainV2Binding>(ActivityMainV2Binding
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
|
handler.removeCallbacks(deferredRouteRunnable)
|
||||||
|
isDeferredRouteLoading = false
|
||||||
|
routeLoadingDialog.dismiss()
|
||||||
deInitMiniPlayer()
|
deInitMiniPlayer()
|
||||||
playerStateJob?.cancel()
|
playerStateJob?.cancel()
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
@@ -759,19 +781,53 @@ class MainV2Activity : BaseActivity<ActivityMainV2Binding>(ActivityMainV2Binding
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun executeDeeplink(intent: Intent) {
|
private fun executeDeeplink(intent: Intent) {
|
||||||
val bundle = intent.getBundleExtra(Constants.EXTRA_DATA) ?: return
|
val routeBundle = resolveDeepLinkBundle(intent) ?: return
|
||||||
|
|
||||||
|
if (executeBundleRoute(routeBundle)) {
|
||||||
|
clearDeferredDeepLink()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun resolveDeepLinkBundle(intent: Intent): Bundle? {
|
||||||
|
val bundle = intent.getBundleExtra(Constants.EXTRA_DATA) ?: return null
|
||||||
val deepLinkUrl = bundle.getString("deep_link")
|
val deepLinkUrl = bundle.getString("deep_link")
|
||||||
val routeBundle = if (!deepLinkUrl.isNullOrBlank()) {
|
return if (!deepLinkUrl.isNullOrBlank()) {
|
||||||
Bundle(bundle).apply {
|
Bundle(bundle).apply {
|
||||||
buildBundleFromDeepLinkUrl(deepLinkUrl)?.let { putAll(it) }
|
buildBundleFromDeepLinkUrl(deepLinkUrl)?.let { putAll(it) }
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
bundle
|
bundle
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (executeBundleRoute(routeBundle)) {
|
private fun isLiveRoomDeepLink(intent: Intent): Boolean {
|
||||||
clearDeferredDeepLink()
|
val bundle = resolveDeepLinkBundle(intent) ?: return false
|
||||||
|
if (isDmChatDeepLink(bundle)) return false
|
||||||
|
|
||||||
|
val roomId = bundle.getString("room_id")?.toLongOrNull()
|
||||||
|
?: bundle.getLong(Constants.EXTRA_ROOM_ID).takeIf { it > 0 }
|
||||||
|
if (roomId != null && roomId > 0) return true
|
||||||
|
if (hasDirectNonLiveRoute(bundle)) return false
|
||||||
|
|
||||||
|
val deepLinkValueId = bundle.getString("deep_link_sub5")?.toLongOrNull()
|
||||||
|
return bundle.getString("deep_link_value")?.lowercase(Locale.ROOT) == "live" &&
|
||||||
|
deepLinkValueId != null && deepLinkValueId > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun hasDirectNonLiveRoute(bundle: Bundle): Boolean {
|
||||||
|
fun hasPositiveId(stringKey: String, longKey: String): Boolean {
|
||||||
|
return (
|
||||||
|
bundle.getString(stringKey)?.toLongOrNull()
|
||||||
|
?: bundle.getLong(longKey).takeIf { it > 0 }
|
||||||
|
)?.let { it > 0 } == true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return hasPositiveId(Constants.EXTRA_COMMUNITY_POST_ID, Constants.EXTRA_COMMUNITY_POST_ID) ||
|
||||||
|
hasPositiveId("channel_id", Constants.EXTRA_USER_ID) ||
|
||||||
|
hasPositiveId("content_id", Constants.EXTRA_AUDIO_CONTENT_ID) ||
|
||||||
|
hasPositiveId("message_id", Constants.EXTRA_MESSAGE_ID) ||
|
||||||
|
hasPositiveId(Constants.EXTRA_COMMUNITY_CREATOR_ID, Constants.EXTRA_COMMUNITY_CREATOR_ID) ||
|
||||||
|
hasPositiveId("audition_id", Constants.EXTRA_AUDITION_ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildBundleFromDeepLinkUrl(deepLinkUrl: String): Bundle? {
|
private fun buildBundleFromDeepLinkUrl(deepLinkUrl: String): Bundle? {
|
||||||
|
|||||||
@@ -87,6 +87,7 @@ class MainV2ActivitySourceTest {
|
|||||||
val observerSource = source.substringFrom("private fun setupLiveEntryObservers()")
|
val observerSource = source.substringFrom("private fun setupLiveEntryObservers()")
|
||||||
val updateSource = source.substringFrom("private fun updateRouteLoadingDialog()")
|
val updateSource = source.substringFrom("private fun updateRouteLoadingDialog()")
|
||||||
val setupViewSource = source.substringFrom("override fun setupView()")
|
val setupViewSource = source.substringFrom("override fun setupView()")
|
||||||
|
val deferredRouteSource = source.substringFrom("private val deferredRouteRunnable = Runnable")
|
||||||
|
|
||||||
assertTrue(source.contains("import kr.co.vividnext.sodalive.common.LoadingDialog"))
|
assertTrue(source.contains("import kr.co.vividnext.sodalive.common.LoadingDialog"))
|
||||||
assertTrue(source.contains("private lateinit var routeLoadingDialog: LoadingDialog"))
|
assertTrue(source.contains("private lateinit var routeLoadingDialog: LoadingDialog"))
|
||||||
@@ -104,7 +105,7 @@ class MainV2ActivitySourceTest {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
assertBefore(onCreateSource, "isDeferredRouteLoading =", "handler.postDelayed")
|
assertBefore(onCreateSource, "isDeferredRouteLoading =", "handler.postDelayed")
|
||||||
assertTrue(onCreateSource.contains("isDeferredRouteLoading = false"))
|
assertTrue(deferredRouteSource.contains("isDeferredRouteLoading = false"))
|
||||||
assertTrue(updateSource.contains("if (isDeferredRouteLoading || isLiveEntryLoading)"))
|
assertTrue(updateSource.contains("if (isDeferredRouteLoading || isLiveEntryLoading)"))
|
||||||
assertTrue(updateSource.contains("routeLoadingDialog.show(screenWidth)"))
|
assertTrue(updateSource.contains("routeLoadingDialog.show(screenWidth)"))
|
||||||
assertFalse(updateSource.contains("R.string.screen_live_loading"))
|
assertFalse(updateSource.contains("R.string.screen_live_loading"))
|
||||||
@@ -112,6 +113,61 @@ class MainV2ActivitySourceTest {
|
|||||||
assertTrue(setupViewSource.contains("setupLiveEntryObservers()"))
|
assertTrue(setupViewSource.contains("setupLiveEntryObservers()"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `MainV2Activity는 foreground 라이브 딥링크만 로딩과 함께 지연한다`() {
|
||||||
|
val source = projectFile("app/src/main/java/kr/co/vividnext/sodalive/v2/main/MainV2Activity.kt").readText()
|
||||||
|
val onNewIntentSource = source.substringFrom("override fun onNewIntent(intent: Intent)")
|
||||||
|
val liveRouteSource = source.substringFrom("private fun isLiveRoomDeepLink(intent: Intent): Boolean")
|
||||||
|
|
||||||
|
assertTrue(source.contains("import kr.co.vividnext.sodalive.live.room.LiveRoomActivity"))
|
||||||
|
assertBefore(onNewIntentSource, "handleAudioNotificationRoute(intent)", "isLiveRoomDeepLink(intent)")
|
||||||
|
assertTrue(
|
||||||
|
onNewIntentSource.contains(
|
||||||
|
"LiveRoomActivity.isForeground && isLiveRoomDeepLink(intent)"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assertBefore(onNewIntentSource, "isDeferredRouteLoading = true", "updateRouteLoadingDialog()")
|
||||||
|
assertBefore(onNewIntentSource, "updateRouteLoadingDialog()", "handler.postDelayed(deferredRouteRunnable, 1000)")
|
||||||
|
assertBefore(onNewIntentSource, "handler.postDelayed(deferredRouteRunnable, 1000)", "return")
|
||||||
|
assertBefore(onNewIntentSource, "return", "executeDeeplink(intent)")
|
||||||
|
|
||||||
|
assertTrue(liveRouteSource.contains("val bundle = resolveDeepLinkBundle(intent) ?: return false"))
|
||||||
|
assertTrue(liveRouteSource.contains("if (isDmChatDeepLink(bundle)) return false"))
|
||||||
|
assertTrue(liveRouteSource.contains("if (roomId != null && roomId > 0) return true"))
|
||||||
|
assertTrue(liveRouteSource.contains("if (hasDirectNonLiveRoute(bundle)) return false"))
|
||||||
|
assertTrue(liveRouteSource.contains("bundle.getString(\"deep_link_value\")?.lowercase(Locale.ROOT) == \"live\""))
|
||||||
|
assertTrue(liveRouteSource.contains("deepLinkValueId != null && deepLinkValueId > 0"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `MainV2Activity는 종료할 때 지연 딥링크와 로딩을 정리한다`() {
|
||||||
|
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 onDestroySource = source.substringFrom("override fun onDestroy()")
|
||||||
|
val runnableSource = source.substringFrom("private val deferredRouteRunnable = Runnable")
|
||||||
|
|
||||||
|
assertTrue(onCreateSource.contains("handler.postDelayed(deferredRouteRunnable, 1000)"))
|
||||||
|
assertTrue(runnableSource.contains("if (isFinishing || isDestroyed) return@Runnable"))
|
||||||
|
assertBefore(runnableSource, "executeDeeplink(intent)", "isDeferredRouteLoading = false")
|
||||||
|
assertBefore(runnableSource, "isDeferredRouteLoading = false", "updateRouteLoadingDialog()")
|
||||||
|
assertTrue(onDestroySource.contains("handler.removeCallbacks(deferredRouteRunnable)"))
|
||||||
|
assertTrue(onDestroySource.contains("isDeferredRouteLoading = false"))
|
||||||
|
assertTrue(onDestroySource.contains("routeLoadingDialog.dismiss()"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `MainV2Activity의 비라이브 ID 판별은 실제 route와 같은 fallback 순서를 사용한다`() {
|
||||||
|
val source = projectFile("app/src/main/java/kr/co/vividnext/sodalive/v2/main/MainV2Activity.kt").readText()
|
||||||
|
val routeSource = source.substringFrom("private fun hasDirectNonLiveRoute(bundle: Bundle): Boolean")
|
||||||
|
|
||||||
|
assertBefore(
|
||||||
|
routeSource,
|
||||||
|
"bundle.getString(stringKey)?.toLongOrNull()",
|
||||||
|
"?: bundle.getLong(longKey).takeIf { it > 0 }"
|
||||||
|
)
|
||||||
|
assertFalse(routeSource.contains("== true ||"))
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `MainV2Activity는 Community post를 creator fallback보다 먼저 Action으로 라우팅한다`() {
|
fun `MainV2Activity는 Community post를 creator fallback보다 먼저 Action으로 라우팅한다`() {
|
||||||
val source = projectFile("app/src/main/java/kr/co/vividnext/sodalive/v2/main/MainV2Activity.kt").readText()
|
val source = projectFile("app/src/main/java/kr/co/vividnext/sodalive/v2/main/MainV2Activity.kt").readText()
|
||||||
|
|||||||
+4
-2
@@ -30,8 +30,10 @@ class MainV2AudioNotificationRouteSourceTest {
|
|||||||
assertBefore(onNewIntentSource, "handleAudioNotificationRoute(intent)", "executeDeeplink(intent)")
|
assertBefore(onNewIntentSource, "handleAudioNotificationRoute(intent)", "executeDeeplink(intent)")
|
||||||
|
|
||||||
val onCreateSource = source.substringFrom("override fun onCreate(savedInstanceState: Bundle?)")
|
val onCreateSource = source.substringFrom("override fun onCreate(savedInstanceState: Bundle?)")
|
||||||
assertBefore(onCreateSource, "handleAudioNotificationRoute(intent)", "isV2AccessAllowed(AccessRequirement.Login)")
|
val deferredRouteSource = source.substringFrom("private val deferredRouteRunnable = Runnable")
|
||||||
assertBefore(onCreateSource, "handleAudioNotificationRoute(intent)", "executeDeeplink(intent)")
|
assertTrue(onCreateSource.contains("handler.postDelayed(deferredRouteRunnable, 1000)"))
|
||||||
|
assertBefore(deferredRouteSource, "handleAudioNotificationRoute(intent)", "isV2AccessAllowed(AccessRequirement.Login)")
|
||||||
|
assertBefore(deferredRouteSource, "handleAudioNotificationRoute(intent)", "executeDeeplink(intent)")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
Reference in New Issue
Block a user