fix(creator): 채널 라이브 진입을 보강한다

This commit is contained in:
2026-06-17 10:58:49 +09:00
parent 34876cf46f
commit f2f2a3143d
5 changed files with 330 additions and 30 deletions

View File

@@ -1,6 +1,7 @@
package kr.co.vividnext.sodalive.explorer.profile.creator_community.write
import android.Manifest
import android.app.Activity
import android.graphics.Bitmap
import android.os.Build
import android.os.Bundle
@@ -26,9 +27,11 @@ import kr.co.vividnext.sodalive.extensions.dpToPx
import org.koin.android.ext.android.inject
import java.io.File
class CreatorCommunityWriteActivity : BaseActivity<ActivityCreatorCommunityWriteBinding>(
ActivityCreatorCommunityWriteBinding::inflate
), RecordingVoiceFragment.OnAudioRecordedListener {
class CreatorCommunityWriteActivity :
BaseActivity<ActivityCreatorCommunityWriteBinding>(
ActivityCreatorCommunityWriteBinding::inflate
),
RecordingVoiceFragment.OnAudioRecordedListener {
private val viewModel: CreatorCommunityWriteViewModel by inject()
@@ -62,7 +65,8 @@ class CreatorCommunityWriteActivity : BaseActivity<ActivityCreatorCommunityWrite
context = this,
isEnabledFreeStyleCrop = true,
config = ImagePickerCropper.Config(
aspectX = 1f, aspectY = 1f,
aspectX = 1f,
aspectY = 1f,
compressFormat = Bitmap.CompressFormat.JPEG,
compressQuality = 90
),
@@ -112,7 +116,10 @@ class CreatorCommunityWriteActivity : BaseActivity<ActivityCreatorCommunityWrite
binding.llPriceFree.setOnClickListener { viewModel.setPriceFree(true) }
binding.tvCancel.setOnClickListener { finish() }
binding.tvUpload.setOnClickListener {
viewModel.createCommunityPost { finish() }
viewModel.createCommunityPost {
setResult(Activity.RESULT_OK)
finish()
}
}
}

View File

@@ -6,11 +6,13 @@ import android.animation.ValueAnimator
import android.content.Context
import android.content.Intent
import android.graphics.Color
import android.view.Gravity
import android.view.animation.Interpolator
import android.view.LayoutInflater
import android.view.View
import android.view.View.MeasureSpec
import android.widget.LinearLayout
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.ViewCompat
@@ -22,6 +24,7 @@ import androidx.core.view.updatePadding
import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager2.widget.ViewPager2
import com.google.android.material.tabs.TabLayoutMediator
import com.google.gson.Gson
import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.audio_content.detail.AudioContentDetailActivity
import kr.co.vividnext.sodalive.audio_content.series.detail.SeriesDetailActivity
@@ -29,18 +32,27 @@ import kr.co.vividnext.sodalive.live.room.create.LiveRoomCreateActivity
import kr.co.vividnext.sodalive.explorer.profile.creator_community.write.CreatorCommunityWriteActivity
import kr.co.vividnext.sodalive.audio_content.upload.AudioContentUploadActivity
import kr.co.vividnext.sodalive.base.BaseActivity
import kr.co.vividnext.sodalive.base.SodaDialog
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
import kr.co.vividnext.sodalive.databinding.ActivityCreatorChannelBinding
import kr.co.vividnext.sodalive.extensions.dpToPx
import kr.co.vividnext.sodalive.extensions.loadUrl
import kr.co.vividnext.sodalive.extensions.moneyFormat
import kr.co.vividnext.sodalive.live.room.detail.LiveRoomDetailFragment
import kr.co.vividnext.sodalive.live.LiveViewModel
import kr.co.vividnext.sodalive.live.room.donation.LiveRoomDonationDialog
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.report.ProfileReportDialog
import kr.co.vividnext.sodalive.report.UserReportDialog
import kr.co.vividnext.sodalive.settings.ContentSettingsActivity
import kr.co.vividnext.sodalive.v2.common.CreatorActivityType
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelAudioContentResponse
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelLiveResponse
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelScheduleResponse
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelSeriesResponse
import kr.co.vividnext.sodalive.v2.creator.channel.model.CreatorChannelHeaderUiModel
@@ -49,11 +61,16 @@ import kr.co.vividnext.sodalive.v2.creator.channel.model.CreatorChannelTab
import kr.co.vividnext.sodalive.v2.creator.channel.model.CreatorChannelTitleBarState
import kr.co.vividnext.sodalive.v2.main.MainV2Activity
import kr.co.vividnext.sodalive.v2.main.chat.dm.DmChatRoomActivity
import kr.co.vividnext.sodalive.splash.SplashActivity
import kr.co.vividnext.sodalive.user.login.LoginActivity
import org.koin.android.ext.android.inject
class CreatorChannelActivity :
BaseActivity<ActivityCreatorChannelBinding>(ActivityCreatorChannelBinding::inflate),
CreatorChannelHomeFragment.Host {
private val liveViewModel: LiveViewModel by inject()
private val myPageViewModel: MyPageViewModel by inject()
private var creatorId: Long = 0L
private var currentHeader: CreatorChannelHeaderUiModel? = null
private var homeActionDelegate: CreatorChannelHomeFragment.HomeActionDelegate? = null
@@ -63,7 +80,39 @@ class CreatorChannelActivity :
private var pageChangeCallback: ViewPager2.OnPageChangeCallback? = null
private var isOwnerFabExpanded: Boolean = false
private var isOwnerFabAnimating: Boolean = false
private lateinit var loadingDialog: LoadingDialog
private val baseTitleBarHeight: Int by lazy { 60.dpToPx().toInt() }
private val liveCoordinator: CreatorChannelLiveCoordinator by lazy {
CreatorChannelLiveCoordinator(
activity = this,
layoutInflater = layoutInflater,
fragmentManager = supportFragmentManager,
liveViewModel = liveViewModel,
screenWidthProvider = { screenWidth },
refreshHome = { homeActionDelegate?.refreshHome() }
)
}
private val communityWriteLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { result ->
if (result.resultCode == RESULT_OK) {
homeActionDelegate?.refreshHome()
}
}
private val liveRoomCreateLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { result ->
if (result.resultCode == RESULT_OK) {
homeActionDelegate?.refreshHome()
val roomId = result.data?.getLongExtra(Constants.EXTRA_ROOM_ID, 0L)
val channelName = result.data?.getStringExtra(Constants.EXTRA_ROOM_CHANNEL_NAME)
if (channelName != null) {
roomId?.takeIf { it > 0L }?.let(liveCoordinator::enterLiveRoom)
} else {
showToast(getString(R.string.creator_channel_live_created_message))
}
}
}
override val shouldApplySystemBarTopInset: Boolean = false
@@ -80,6 +129,21 @@ class CreatorChannelActivity :
setupOwnerFabInsets()
setupScrollListener()
setupClickListeners()
setupLiveEntryObservers()
}
private fun setupLiveEntryObservers() {
loadingDialog = LoadingDialog(this, layoutInflater)
liveViewModel.toastLiveData.observe(this) {
it?.let(::showToast)
}
liveViewModel.isLoading.observe(this) {
if (it) {
loadingDialog.show(screenWidth, getString(R.string.screen_live_loading))
} else {
loadingDialog.dismiss()
}
}
}
private fun setupClickListeners() {
@@ -423,7 +487,7 @@ class CreatorChannelActivity :
private fun onOwnerFabCommunityClicked() {
collapseOwnerFab(animate = false)
startActivity(Intent(this, CreatorCommunityWriteActivity::class.java))
communityWriteLauncher.launch(Intent(this, CreatorCommunityWriteActivity::class.java))
}
private fun onOwnerFabAudioClicked() {
@@ -433,7 +497,7 @@ class CreatorChannelActivity :
private fun onOwnerFabLiveClicked() {
collapseOwnerFab(animate = false)
startActivity(Intent(this, LiveRoomCreateActivity::class.java))
liveRoomCreateLauncher.launch(Intent(this, LiveRoomCreateActivity::class.java))
}
override fun onCreatorChannelDonationClicked() {
@@ -453,6 +517,77 @@ class CreatorChannelActivity :
dialog.show(screenWidth - 26.7f.dpToPx().toInt())
}
override fun onCreatorChannelCurrentLiveClicked(live: CreatorChannelLiveResponse) {
ensureLoginAndAdultAuth(isAdult = live.isAdult) {
liveCoordinator.enterLiveRoom(live.liveId)
}
}
private fun ensureLoginAndAdultAuth(isAdult: Boolean, onAuthed: () -> Unit) {
if (SharedPreferenceManager.token.isBlank()) {
showLoginActivity()
return
}
if (isAdult) {
val isKoreanCountry = SharedPreferenceManager.countryCode.ifBlank { "KR" } == "KR"
if (isKoreanCountry && !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
}
if (!SharedPreferenceManager.isAdultContentVisible) {
startActivity(
Intent(this, ContentSettingsActivity::class.java).apply {
putExtra(Constants.EXTRA_SHOW_SENSITIVE_CONTENT_GUIDE, true)
}
)
return
}
}
onAuthed()
}
private fun showLoginActivity() {
if (SharedPreferenceManager.token.isBlank()) {
startActivity(
Intent(this, 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, this) { json ->
val bootpayResponse = Gson().fromJson(json, BootpayResponse::class.java)
val request = AuthVerifyRequest(receiptId = bootpayResponse.data.receiptId)
runOnUiThread {
myPageViewModel.authVerify(request) {
startActivity(
Intent(this, SplashActivity::class.java).apply {
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK)
}
)
finish()
}
}
}
}
private fun updateViewPagerHeight() {
binding.viewPager.post {
val recyclerView = binding.viewPager.getChildAt(0) as? RecyclerView ?: return@post
@@ -478,7 +613,7 @@ class CreatorChannelActivity :
}
)
CreatorActivityType.Live -> showLiveRoomDetail(schedule.targetId)
CreatorActivityType.Live -> liveCoordinator.showLiveRoomDetail(schedule.targetId)
CreatorActivityType.Community -> Unit
}
@@ -500,20 +635,6 @@ class CreatorChannelActivity :
)
}
private fun showLiveRoomDetail(roomId: Long) {
val detailFragment = LiveRoomDetailFragment(
roomId,
onClickParticipant = {},
onClickReservation = {},
onClickModify = {},
onClickStart = {},
onClickCancel = {}
)
if (detailFragment.isAdded) return
detailFragment.show(supportFragmentManager, detailFragment.tag)
}
override fun onDestroy() {
tabLayoutMediator?.detach()
pageChangeCallback?.let { callback ->

View File

@@ -7,6 +7,7 @@ import androidx.recyclerview.widget.LinearLayoutManager
import kr.co.vividnext.sodalive.base.BaseFragment
import kr.co.vividnext.sodalive.databinding.FragmentCreatorChannelHomeBinding
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelAudioContentResponse
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelLiveResponse
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelScheduleResponse
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelSeriesResponse
import kr.co.vividnext.sodalive.v2.creator.channel.model.CreatorChannelHeaderUiModel
@@ -20,6 +21,7 @@ class CreatorChannelHomeFragment : BaseFragment<FragmentCreatorChannelHomeBindin
private val viewModel: CreatorChannelHomeViewModel by viewModel()
private val sectionAdapter = CreatorChannelHomeSectionAdapter(
onLiveClick = ::onCurrentLiveClicked,
onScheduleClick = ::onScheduleClicked,
onAudioContentClick = ::onAudioContentClicked,
onSeriesClick = ::onSeriesClicked,
@@ -59,6 +61,12 @@ class CreatorChannelHomeFragment : BaseFragment<FragmentCreatorChannelHomeBindin
override fun postChannelDonation(can: Int, isSecret: Boolean, message: String) {
viewModel.postChannelDonation(can = can, isSecret = isSecret, message = message)
}
override fun refreshHome() {
if (creatorId > 0L) {
viewModel.loadHome(creatorId)
}
}
}
)
if (creatorId > 0L) {
@@ -115,6 +123,10 @@ class CreatorChannelHomeFragment : BaseFragment<FragmentCreatorChannelHomeBindin
host.onCreatorChannelDonationClicked()
}
private fun onCurrentLiveClicked(live: CreatorChannelLiveResponse) {
host.onCreatorChannelCurrentLiveClicked(live)
}
interface Host {
fun onCreatorChannelHeaderChanged(header: CreatorChannelHeaderUiModel)
fun onCreatorChannelFollowProgressChanged(inProgress: Boolean)
@@ -125,6 +137,7 @@ class CreatorChannelHomeFragment : BaseFragment<FragmentCreatorChannelHomeBindin
fun onCreatorChannelHomeActionDelegateReady(delegate: HomeActionDelegate?)
fun onCreatorChannelHomeContentChanged()
fun onCreatorChannelDonationClicked()
fun onCreatorChannelCurrentLiveClicked(live: CreatorChannelLiveResponse)
}
interface HomeActionDelegate {
@@ -134,6 +147,7 @@ class CreatorChannelHomeFragment : BaseFragment<FragmentCreatorChannelHomeBindin
fun reportUser(reason: String)
fun reportProfile()
fun postChannelDonation(can: Int, isSecret: Boolean, message: String)
fun refreshHome()
}
companion object {

View File

@@ -23,6 +23,7 @@ import kr.co.vividnext.sodalive.extensions.loadUrl
import kr.co.vividnext.sodalive.extensions.moneyFormat
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelAudioContentResponse
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelCommunityPostResponse
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelLiveResponse
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelScheduleResponse
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelSeriesResponse
import kr.co.vividnext.sodalive.v2.creator.channel.model.CreatorChannelHomeSection
@@ -35,6 +36,7 @@ import java.util.TimeZone
import kotlin.math.roundToInt
class CreatorChannelHomeSectionAdapter(
private val onLiveClick: (CreatorChannelLiveResponse) -> Unit = {},
private val onScheduleClick: (CreatorChannelScheduleResponse) -> Unit = {},
private val onAudioContentClick: (CreatorChannelAudioContentResponse) -> Unit = {},
private val onSeriesClick: (CreatorChannelSeriesResponse) -> Unit = {},
@@ -52,7 +54,7 @@ class CreatorChannelHomeSectionAdapter(
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SectionViewHolder {
val view = LayoutInflater.from(parent.context).inflate(viewType, parent, false)
return SectionViewHolder(view, onScheduleClick, onAudioContentClick, onSeriesClick, onDonationClick)
return SectionViewHolder(view, onLiveClick, onScheduleClick, onAudioContentClick, onSeriesClick, onDonationClick)
}
override fun onBindViewHolder(holder: SectionViewHolder, position: Int) {
@@ -63,6 +65,7 @@ class CreatorChannelHomeSectionAdapter(
class SectionViewHolder(
view: View,
private val onLiveClick: (CreatorChannelLiveResponse) -> Unit,
private val onScheduleClick: (CreatorChannelScheduleResponse) -> Unit,
private val onAudioContentClick: (CreatorChannelAudioContentResponse) -> Unit,
private val onSeriesClick: (CreatorChannelSeriesResponse) -> Unit,
@@ -74,6 +77,7 @@ class CreatorChannelHomeSectionAdapter(
private val currentLivePrice: TextView? = view.findViewById(R.id.tv_current_live_price)
private val currentLiveAdult: TextView? = view.findViewById(R.id.tv_current_live_adult)
private val currentLivePriceLayout: View? = view.findViewById(R.id.layout_current_live_price)
private val currentLiveCard: View? = view.findViewById(R.id.layout_current_live_card)
private val latestAudioThumbnail: ImageView? = view.findViewById(R.id.iv_latest_audio_thumbnail)
private val latestAudioPointTag: ImageView? = view.findViewById(R.id.iv_latest_audio_point_tag)
private val latestAudioTitle: TextView? = view.findViewById(R.id.tv_latest_audio_title)
@@ -142,10 +146,11 @@ class CreatorChannelHomeSectionAdapter(
private fun bindCurrentLive(item: CreatorChannelHomeSection.CurrentLive) {
currentLiveTitle?.text = item.live.title
currentLiveStartTime?.text = item.live.beginDateTimeUtc
currentLiveStartTime?.text = formatCreatorChannelLiveDateTime(item.live.beginDateTimeUtc)
currentLivePrice?.text = item.live.price.toString()
currentLivePriceLayout?.isVisible = item.live.price > 0
currentLiveAdult?.isVisible = item.live.isAdult
currentLiveCard?.setOnClickListener { onLiveClick(item.live) }
}
private fun bindLatestAudioContent(item: CreatorChannelHomeSection.LatestAudioContent) {
@@ -614,6 +619,12 @@ internal fun formatCreatorChannelScheduleTime(
locale: Locale = Locale.getDefault()
): String = formatCreatorChannelScheduleUtc(scheduledAtUtc, "a hh:mm", timeZone, locale)
internal fun formatCreatorChannelLiveDateTime(
beginDateTimeUtc: String,
timeZone: TimeZone = TimeZone.getDefault(),
locale: Locale = Locale.getDefault()
): String = formatCreatorChannelUtcOrNull(beginDateTimeUtc, "yyyy.MM.dd HH:mm:ss", timeZone, locale).orEmpty()
internal fun formatCreatorChannelDebutActivityValue(
debutDateUtc: String?,
dDay: String,