성인 라이브 입장에 본인인증 흐름 추가

This commit is contained in:
2026-02-03 11:14:10 +09:00
parent 9496a57b3c
commit 666424f79b
6 changed files with 176 additions and 20 deletions

View File

@@ -201,7 +201,7 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>(FragmentHomeBinding::infl
private fun setupLiveView() { private fun setupLiveView() {
liveAdapter = HomeLiveAdapter { liveAdapter = HomeLiveAdapter {
if (SharedPreferenceManager.token.isNotBlank()) { ensureLoginAndAdultAuth(isAdult = it.isAdult) {
val detailFragment = LiveRoomDetailFragment( val detailFragment = LiveRoomDetailFragment(
it.roomId, it.roomId,
onClickParticipant = { enterLiveRoom(it.roomId) }, onClickParticipant = { enterLiveRoom(it.roomId) },
@@ -210,14 +210,12 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>(FragmentHomeBinding::infl
onClickStart = {}, onClickStart = {},
onClickCancel = {} onClickCancel = {}
) )
if (detailFragment.isAdded) return@HomeLiveAdapter if (detailFragment.isAdded) return@ensureLoginAndAdultAuth
detailFragment.show( detailFragment.show(
requireActivity().supportFragmentManager, requireActivity().supportFragmentManager,
detailFragment.tag detailFragment.tag
) )
} else {
(requireActivity() as MainActivity).showLoginActivity()
} }
} }
@@ -1358,6 +1356,30 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>(FragmentHomeBinding::infl
onAuthed() onAuthed()
} }
private fun ensureLoginAndAdultAuth(isAdult: Boolean, onAuthed: () -> Unit) {
if (SharedPreferenceManager.token.isBlank()) {
(requireActivity() as MainActivity).showLoginActivity()
return
}
if (isAdult && !SharedPreferenceManager.isAuth) {
SodaDialog(
activity = requireActivity(),
layoutInflater = layoutInflater,
title = getString(R.string.auth_title),
desc = getString(R.string.auth_desc_live),
confirmButtonTitle = getString(R.string.auth_go),
confirmButtonClick = { startAuthFlow() },
cancelButtonTitle = getString(R.string.cancel),
cancelButtonClick = {},
descGravity = Gravity.CENTER
).show(screenWidth)
return
}
onAuthed()
}
private fun startAuthFlow() { private fun startAuthFlow() {
Auth.auth(requireActivity(), requireContext()) { json -> Auth.auth(requireActivity(), requireContext()) { json ->
val bootpayResponse = Gson().fromJson( val bootpayResponse = Gson().fromJson(

View File

@@ -8,6 +8,7 @@ import android.graphics.Rect
import android.os.Bundle import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.view.Gravity
import android.view.View import android.view.View
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.Toast import android.widget.Toast
@@ -18,6 +19,7 @@ import androidx.media3.common.util.UnstableApi
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.google.gson.Gson
import com.zhpan.bannerview.BaseBannerAdapter import com.zhpan.bannerview.BaseBannerAdapter
import com.zhpan.indicator.enums.IndicatorSlideMode import com.zhpan.indicator.enums.IndicatorSlideMode
import com.zhpan.indicator.enums.IndicatorStyle import com.zhpan.indicator.enums.IndicatorStyle
@@ -26,6 +28,7 @@ import kr.co.vividnext.sodalive.audio_content.AudioContentPlayService
import kr.co.vividnext.sodalive.audio_content.detail.AudioContentDetailActivity import kr.co.vividnext.sodalive.audio_content.detail.AudioContentDetailActivity
import kr.co.vividnext.sodalive.audio_content.player.AudioContentPlayerService import kr.co.vividnext.sodalive.audio_content.player.AudioContentPlayerService
import kr.co.vividnext.sodalive.base.BaseFragment import kr.co.vividnext.sodalive.base.BaseFragment
import kr.co.vividnext.sodalive.base.SodaDialog
import kr.co.vividnext.sodalive.common.Constants import kr.co.vividnext.sodalive.common.Constants
import kr.co.vividnext.sodalive.common.LoadingDialog import kr.co.vividnext.sodalive.common.LoadingDialog
import kr.co.vividnext.sodalive.common.SharedPreferenceManager import kr.co.vividnext.sodalive.common.SharedPreferenceManager
@@ -54,11 +57,16 @@ import kr.co.vividnext.sodalive.live.room.dialog.LiveRoomPasswordDialog
import kr.co.vividnext.sodalive.live.room.update.LiveRoomEditActivity import kr.co.vividnext.sodalive.live.room.update.LiveRoomEditActivity
import kr.co.vividnext.sodalive.main.MainActivity import kr.co.vividnext.sodalive.main.MainActivity
import kr.co.vividnext.sodalive.message.MessageActivity import kr.co.vividnext.sodalive.message.MessageActivity
import kr.co.vividnext.sodalive.mypage.MyPageViewModel
import kr.co.vividnext.sodalive.mypage.auth.Auth
import kr.co.vividnext.sodalive.mypage.auth.AuthVerifyRequest
import kr.co.vividnext.sodalive.mypage.auth.BootpayResponse
import kr.co.vividnext.sodalive.mypage.can.charge.CanChargeActivity import kr.co.vividnext.sodalive.mypage.can.charge.CanChargeActivity
import kr.co.vividnext.sodalive.search.SearchActivity import kr.co.vividnext.sodalive.search.SearchActivity
import kr.co.vividnext.sodalive.settings.language.LanguageManager import kr.co.vividnext.sodalive.settings.language.LanguageManager
import kr.co.vividnext.sodalive.settings.language.LocaleHelper import kr.co.vividnext.sodalive.settings.language.LocaleHelper
import kr.co.vividnext.sodalive.settings.notification.MemberRole import kr.co.vividnext.sodalive.settings.notification.MemberRole
import kr.co.vividnext.sodalive.splash.SplashActivity
import org.koin.android.ext.android.inject import org.koin.android.ext.android.inject
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Date import java.util.Date
@@ -69,6 +77,7 @@ import kotlin.math.roundToInt
@UnstableApi @UnstableApi
class LiveFragment : BaseFragment<FragmentLiveBinding>(FragmentLiveBinding::inflate) { class LiveFragment : BaseFragment<FragmentLiveBinding>(FragmentLiveBinding::inflate) {
private val viewModel: LiveViewModel by inject() private val viewModel: LiveViewModel by inject()
private val myPageViewModel: MyPageViewModel by inject()
private lateinit var liveNowAdapter: LiveNowAdapter private lateinit var liveNowAdapter: LiveNowAdapter
private lateinit var liveReservationAdapter: LiveReservationAdapter private lateinit var liveReservationAdapter: LiveReservationAdapter
@@ -510,7 +519,7 @@ class LiveFragment : BaseFragment<FragmentLiveBinding>(FragmentLiveBinding::infl
.rvLiveNow .rvLiveNow
liveNowAdapter = LiveNowAdapter { liveNowAdapter = LiveNowAdapter {
if (SharedPreferenceManager.token.isNotBlank()) { ensureLoginAndAdultAuth(isAdult = it.isAdult) {
val detailFragment = LiveRoomDetailFragment( val detailFragment = LiveRoomDetailFragment(
it.roomId, it.roomId,
onClickParticipant = { enterLiveRoom(it.roomId) }, onClickParticipant = { enterLiveRoom(it.roomId) },
@@ -519,14 +528,12 @@ class LiveFragment : BaseFragment<FragmentLiveBinding>(FragmentLiveBinding::infl
onClickStart = {}, onClickStart = {},
onClickCancel = {} onClickCancel = {}
) )
if (detailFragment.isAdded) return@LiveNowAdapter if (detailFragment.isAdded) return@ensureLoginAndAdultAuth
detailFragment.show( detailFragment.show(
requireActivity().supportFragmentManager, requireActivity().supportFragmentManager,
detailFragment.tag detailFragment.tag
) )
} else {
(requireActivity() as MainActivity).showLoginActivity()
} }
} }
@@ -857,6 +864,56 @@ class LiveFragment : BaseFragment<FragmentLiveBinding>(FragmentLiveBinding::infl
) )
} }
private fun ensureLoginAndAdultAuth(isAdult: Boolean, onAuthed: () -> Unit) {
if (SharedPreferenceManager.token.isBlank()) {
(requireActivity() as MainActivity).showLoginActivity()
return
}
if (isAdult && !SharedPreferenceManager.isAuth) {
SodaDialog(
activity = requireActivity(),
layoutInflater = layoutInflater,
title = getString(R.string.auth_title),
desc = getString(R.string.auth_desc_live),
confirmButtonTitle = getString(R.string.auth_go),
confirmButtonClick = { startAuthFlow() },
cancelButtonTitle = getString(R.string.cancel),
cancelButtonClick = {},
descGravity = Gravity.CENTER
).show(screenWidth)
return
}
onAuthed()
}
private fun startAuthFlow() {
Auth.auth(requireActivity(), requireContext()) { json ->
val bootpayResponse = Gson().fromJson(
json,
BootpayResponse::class.java
)
val request = AuthVerifyRequest(receiptId = bootpayResponse.data.receiptId)
requireActivity().runOnUiThread {
myPageViewModel.authVerify(request) {
startActivity(
Intent(
requireContext(),
SplashActivity::class.java
).apply {
addFlags(
Intent.FLAG_ACTIVITY_CLEAR_TASK or
Intent.FLAG_ACTIVITY_NEW_TASK
)
}
)
requireActivity().finish()
}
}
}
}
@UnstableApi @UnstableApi
fun enterLiveRoom(roomId: Long) { fun enterLiveRoom(roomId: Long) {
requireContext().startService( requireContext().startService(

View File

@@ -3,16 +3,19 @@ package kr.co.vividnext.sodalive.live.now.all
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.Gravity
import android.view.View import android.view.View
import android.widget.Toast import android.widget.Toast
import androidx.media3.common.util.UnstableApi import androidx.media3.common.util.UnstableApi
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.google.gson.Gson
import kr.co.vividnext.sodalive.R import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.audio_content.AudioContentPlayService import kr.co.vividnext.sodalive.audio_content.AudioContentPlayService
import kr.co.vividnext.sodalive.audio_content.player.AudioContentPlayerService import kr.co.vividnext.sodalive.audio_content.player.AudioContentPlayerService
import kr.co.vividnext.sodalive.base.BaseActivity import kr.co.vividnext.sodalive.base.BaseActivity
import kr.co.vividnext.sodalive.base.SodaDialog
import kr.co.vividnext.sodalive.common.Constants import kr.co.vividnext.sodalive.common.Constants
import kr.co.vividnext.sodalive.common.GridSpacingItemDecoration import kr.co.vividnext.sodalive.common.GridSpacingItemDecoration
import kr.co.vividnext.sodalive.common.LoadingDialog import kr.co.vividnext.sodalive.common.LoadingDialog
@@ -24,8 +27,14 @@ import kr.co.vividnext.sodalive.live.room.LiveRoomActivity
import kr.co.vividnext.sodalive.live.room.detail.LiveRoomDetailFragment import kr.co.vividnext.sodalive.live.room.detail.LiveRoomDetailFragment
import kr.co.vividnext.sodalive.live.room.dialog.LivePaymentDialog import kr.co.vividnext.sodalive.live.room.dialog.LivePaymentDialog
import kr.co.vividnext.sodalive.live.room.dialog.LiveRoomPasswordDialog import kr.co.vividnext.sodalive.live.room.dialog.LiveRoomPasswordDialog
import kr.co.vividnext.sodalive.mypage.MyPageViewModel
import kr.co.vividnext.sodalive.mypage.auth.Auth
import kr.co.vividnext.sodalive.mypage.auth.AuthVerifyRequest
import kr.co.vividnext.sodalive.mypage.auth.BootpayResponse
import kr.co.vividnext.sodalive.settings.language.LanguageManager import kr.co.vividnext.sodalive.settings.language.LanguageManager
import kr.co.vividnext.sodalive.settings.language.LocaleHelper import kr.co.vividnext.sodalive.settings.language.LocaleHelper
import kr.co.vividnext.sodalive.splash.SplashActivity
import kr.co.vividnext.sodalive.user.login.LoginActivity
import org.koin.android.ext.android.inject import org.koin.android.ext.android.inject
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Date import java.util.Date
@@ -37,6 +46,7 @@ class LiveNowAllActivity : BaseActivity<ActivityLiveNowAllBinding>(
ActivityLiveNowAllBinding::inflate ActivityLiveNowAllBinding::inflate
) { ) {
private val viewModel: LiveViewModel by inject() private val viewModel: LiveViewModel by inject()
private val myPageViewModel: MyPageViewModel by inject()
private lateinit var adapter: LiveNowAllAdapter private lateinit var adapter: LiveNowAllAdapter
private lateinit var loadingDialog: LoadingDialog private lateinit var loadingDialog: LoadingDialog
@@ -57,19 +67,21 @@ class LiveNowAllActivity : BaseActivity<ActivityLiveNowAllBinding>(
val spacing = 16.dpToPx().toInt() val spacing = 16.dpToPx().toInt()
val recyclerView = binding.rvLive val recyclerView = binding.rvLive
adapter = LiveNowAllAdapter(itemWidth = (screenWidth - (spacing * (spanCount + 1))) / 2) { adapter = LiveNowAllAdapter(itemWidth = (screenWidth - (spacing * (spanCount + 1))) / 2) {
val detailFragment = LiveRoomDetailFragment( ensureLoginAndAdultAuth(isAdult = it.isAdult) {
it.roomId, val detailFragment = LiveRoomDetailFragment(
onClickParticipant = { enterLiveRoom(it.roomId) }, it.roomId,
onClickReservation = {}, onClickParticipant = { enterLiveRoom(it.roomId) },
onClickModify = {}, onClickReservation = {},
onClickStart = {}, onClickModify = {},
onClickCancel = {} onClickStart = {},
) onClickCancel = {}
)
detailFragment.show( detailFragment.show(
supportFragmentManager, supportFragmentManager,
detailFragment.tag detailFragment.tag
) )
}
} }
recyclerView.layoutManager = GridLayoutManager(this, spanCount) recyclerView.layoutManager = GridLayoutManager(this, spanCount)
@@ -101,6 +113,68 @@ class LiveNowAllActivity : BaseActivity<ActivityLiveNowAllBinding>(
} }
} }
private fun ensureLoginAndAdultAuth(isAdult: Boolean, onAuthed: () -> Unit) {
if (SharedPreferenceManager.token.isBlank()) {
showLoginActivity()
return
}
if (isAdult && !SharedPreferenceManager.isAuth) {
SodaDialog(
activity = this,
layoutInflater = layoutInflater,
title = getString(R.string.auth_title),
desc = getString(R.string.auth_desc_live),
confirmButtonTitle = getString(R.string.auth_go),
confirmButtonClick = { startAuthFlow() },
cancelButtonTitle = getString(R.string.cancel),
cancelButtonClick = {},
descGravity = Gravity.CENTER
).show(screenWidth)
return
}
onAuthed()
}
private fun showLoginActivity() {
if (SharedPreferenceManager.token.isBlank()) {
startActivity(
Intent(applicationContext, LoginActivity::class.java).apply {
putExtra(Constants.EXTRA_DATA, intent.extras)
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
}
)
}
}
private fun startAuthFlow() {
Auth.auth(this, applicationContext) { json ->
val bootpayResponse = Gson().fromJson(
json,
BootpayResponse::class.java
)
val request = AuthVerifyRequest(receiptId = bootpayResponse.data.receiptId)
runOnUiThread {
myPageViewModel.authVerify(request) {
startActivity(
Intent(
applicationContext,
SplashActivity::class.java
).apply {
addFlags(
Intent.FLAG_ACTIVITY_CLEAR_TASK or
Intent.FLAG_ACTIVITY_NEW_TASK
)
}
)
finish()
}
}
}
}
private fun enterLiveRoom(roomId: Long) { private fun enterLiveRoom(roomId: Long) {
startService( startService(
Intent(applicationContext, AudioContentPlayService::class.java).apply { Intent(applicationContext, AudioContentPlayService::class.java).apply {

View File

@@ -155,6 +155,7 @@
<string name="live_paid_confirm">Enter after payment</string> <string name="live_paid_confirm">Enter after payment</string>
<string name="auth_title">Identity verification</string> <string name="auth_title">Identity verification</string>
<string name="auth_desc">VoiceOns Open World Character Chat\nis available only to adults who have completed identity verification to protect minors.\nTo use the character chat service, please complete identity verification.</string> <string name="auth_desc">VoiceOns Open World Character Chat\nis available only to adults who have completed identity verification to protect minors.\nTo use the character chat service, please complete identity verification.</string>
<string name="auth_desc_live">To protect minors,\nonly adults who have completed identity verification\ncan enter live rooms.\nPlease complete identity verification\nto enter the live room.</string>
<string name="auth_go">Verify now</string> <string name="auth_go">Verify now</string>
<string name="deeplink_not_found">Failed to find deep link.</string> <string name="deeplink_not_found">Failed to find deep link.</string>
<string name="deeplink_error">Deep link processing error: %1$s</string> <string name="deeplink_error">Deep link processing error: %1$s</string>

View File

@@ -155,6 +155,7 @@
<string name="live_paid_confirm">決済して入室</string> <string name="live_paid_confirm">決済して入室</string>
<string name="auth_title">本人確認</string> <string name="auth_title">本人確認</string>
<string name="auth_desc">ボイスオンのオープンワールド・キャラトークは、\n青少年保護のため本人確認済みの\n成人の方のみご利用いただけます。\nサービスを利用するには本人確認をお願いします。</string> <string name="auth_desc">ボイスオンのオープンワールド・キャラトークは、\n青少年保護のため本人確認済みの\n成人の方のみご利用いただけます。\nサービスを利用するには本人確認をお願いします。</string>
<string name="auth_desc_live">青少年保護のため、\n本人確認を完了した成人の方のみ\nライブ入場が可能です。\nライブ入場のために\n本人確認を行ってください。</string>
<string name="auth_go">本人確認へ進む</string> <string name="auth_go">本人確認へ進む</string>
<string name="deeplink_not_found">ディープリンクが見つかりませんでした。</string> <string name="deeplink_not_found">ディープリンクが見つかりませんでした。</string>
<string name="deeplink_error">ディープリンク処理中にエラーが発生しました: %1$s</string> <string name="deeplink_error">ディープリンク処理中にエラーが発生しました: %1$s</string>

View File

@@ -154,6 +154,7 @@
<string name="live_paid_confirm">결제 후 입장</string> <string name="live_paid_confirm">결제 후 입장</string>
<string name="auth_title">본인인증</string> <string name="auth_title">본인인증</string>
<string name="auth_desc">보이스온의 오픈월드 캐릭터톡은\n청소년 보호를 위해 본인인증한\n성인만 이용이 가능합니다.\n캐릭터톡 서비스를 이용하시려면\n본인인증을 하고 이용해주세요.</string> <string name="auth_desc">보이스온의 오픈월드 캐릭터톡은\n청소년 보호를 위해 본인인증한\n성인만 이용이 가능합니다.\n캐릭터톡 서비스를 이용하시려면\n본인인증을 하고 이용해주세요.</string>
<string name="auth_desc_live">청소년 보호를 위해\n본인인증을 완료한\n성인만 라이브 입장이 가능합니다.\n라이브 입장을 위해\n본인인증을 진행해 주세요.</string>
<string name="auth_go">본인인증 하러가기</string> <string name="auth_go">본인인증 하러가기</string>
<string name="deeplink_not_found">딥링크를 찾을 수 없습니다.</string> <string name="deeplink_not_found">딥링크를 찾을 수 없습니다.</string>
<string name="deeplink_error">딥링크 처리 중 오류 발생: %1$s</string> <string name="deeplink_error">딥링크 처리 중 오류 발생: %1$s</string>