diff --git a/app/src/main/java/kr/co/vividnext/sodalive/di/AppDI.kt b/app/src/main/java/kr/co/vividnext/sodalive/di/AppDI.kt index caf72b46..42dfaec3 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/di/AppDI.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/di/AppDI.kt @@ -463,13 +463,13 @@ class AppDI(private val context: Context, isDebugMode: Boolean) { viewModel { CreatorChannelLiveViewModel(get()) } viewModel { CreatorChannelAudioViewModel(get()) } viewModel { CreatorChannelSeriesViewModel(get()) } - viewModel { CreatorChannelCommunityViewModel(get(), get()) } - viewModel { CreatorChannelCommunityDetailViewModel(get(), get(), get()) } - viewModel { CreatorChannelCommunityReplyViewModel(get(), get(), get()) } - viewModel { CreatorChannelFanTalkViewModel(get(), get()) } - viewModel { CreatorChannelFanTalkDetailViewModel(get(), get()) } + viewModel { CreatorChannelCommunityViewModel(get()) } + viewModel { CreatorChannelCommunityDetailViewModel(get(), get()) } + viewModel { CreatorChannelCommunityReplyViewModel(get(), get()) } + viewModel { CreatorChannelFanTalkViewModel(get()) } + viewModel { CreatorChannelFanTalkDetailViewModel(get()) } viewModel { CreatorChannelFanTalkWriteViewModel(get()) } - viewModel { CreatorChannelDonationViewModel(get(), get()) } + viewModel { CreatorChannelDonationViewModel(get()) } viewModel { PushNotificationListViewModel(get()) } viewModel { CharacterTabViewModel(get()) } viewModel { CharacterDetailViewModel(get()) } diff --git a/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/CreatorChannelCommunityViewModel.kt b/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/CreatorChannelCommunityViewModel.kt index 02562c1a..e06be200 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/CreatorChannelCommunityViewModel.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/CreatorChannelCommunityViewModel.kt @@ -7,15 +7,13 @@ import io.reactivex.rxjava3.schedulers.Schedulers import kr.co.vividnext.sodalive.base.BaseViewModel import kr.co.vividnext.sodalive.common.ApiResponse import kr.co.vividnext.sodalive.common.SharedPreferenceManager -import kr.co.vividnext.sodalive.common.UtcRelativeTimeTextFormatter import kr.co.vividnext.sodalive.v2.creator.channel.community.data.CreatorChannelCommunityTabResponse import kr.co.vividnext.sodalive.v2.creator.channel.community.model.CreatorChannelCommunityPostUiModel import kr.co.vividnext.sodalive.v2.creator.channel.community.model.toCommunityPostUiModels import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelRepository class CreatorChannelCommunityViewModel( - private val repository: CreatorChannelRepository, - private val relativeTimeTextFormatter: UtcRelativeTimeTextFormatter + private val repository: CreatorChannelRepository ) : BaseViewModel() { private val _communityStateLiveData = MutableLiveData() @@ -158,7 +156,6 @@ class CreatorChannelCommunityViewModel( private fun CreatorChannelCommunityTabResponse.toCommunityPostUiModels(): List = communityPosts.toCommunityPostUiModels( - relativeTimeTextFormatter = relativeTimeTextFormatter, isOwner = isOwner, currentUserId = SharedPreferenceManager.userId ) diff --git a/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/detail/CreatorChannelCommunityDetailActivity.kt b/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/detail/CreatorChannelCommunityDetailActivity.kt index 7638638c..06f5673b 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/detail/CreatorChannelCommunityDetailActivity.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/detail/CreatorChannelCommunityDetailActivity.kt @@ -24,6 +24,7 @@ import com.bumptech.glide.request.target.Target import kr.co.vividnext.sodalive.R import kr.co.vividnext.sodalive.base.BaseActivity import kr.co.vividnext.sodalive.common.Constants +import kr.co.vividnext.sodalive.common.formatUtcRelativeTimeText import kr.co.vividnext.sodalive.databinding.ActivityCreatorChannelCommunityDetailBinding import kr.co.vividnext.sodalive.explorer.profile.creator_community.all.CreatorCommunityPostMenuBottomSheetDialog import kr.co.vividnext.sodalive.explorer.profile.creator_community.all.CreatorCommunityReportDialog @@ -171,7 +172,8 @@ class CreatorChannelCommunityDetailActivity : BaseActivity() @@ -536,7 +534,7 @@ class CreatorChannelCommunityDetailViewModel( creatorId = creatorId, creatorNickname = creatorNickname, creatorProfileUrl = creatorProfileUrl, - createdAtText = relativeTimeTextFormatter.format(createdAtUtc), + createdAtUtc = createdAtUtc, content = content, imageUrl = imageUrl.takeUnless { isLocked() }, audioUrl = audioUrl.takeUnless { isLocked() }, @@ -566,7 +564,6 @@ class CreatorChannelCommunityDetailViewModel( memberNickname = writerNickname, memberProfileUrl = writerProfileImageUrl, createdAtUtc = createdAtUtc, - createdAtText = relativeTimeTextFormatter.format(createdAtUtc), comment = content, likeCount = 0, replyCount = if (latestReply != null) 1 else 0, @@ -586,7 +583,6 @@ class CreatorChannelCommunityDetailViewModel( memberNickname = writerNickname, memberProfileUrl = writerProfileImageUrl, createdAtUtc = createdAtUtc, - createdAtText = relativeTimeTextFormatter.format(createdAtUtc), comment = content, likeCount = 0, isLiked = false, @@ -625,7 +621,7 @@ data class CreatorChannelCommunityPostDetailUiModel( val creatorId: Long, val creatorNickname: String, val creatorProfileUrl: String?, - val createdAtText: String, + val createdAtUtc: String, val content: String, val imageUrl: String?, val audioUrl: String?, @@ -648,7 +644,6 @@ data class CreatorChannelCommunityCommentUiModel( val memberNickname: String, val memberProfileUrl: String?, val createdAtUtc: String, - val createdAtText: String, val comment: String, val likeCount: Int, val replyCount: Int, @@ -665,7 +660,6 @@ data class CreatorChannelCommunityReplyUiModel( val memberNickname: String, val memberProfileUrl: String?, val createdAtUtc: String, - val createdAtText: String, val comment: String, val likeCount: Int, val isLiked: Boolean, diff --git a/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/detail/reply/CreatorChannelCommunityReplyActivity.kt b/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/detail/reply/CreatorChannelCommunityReplyActivity.kt index e5d0b910..585f8d5e 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/detail/reply/CreatorChannelCommunityReplyActivity.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/detail/reply/CreatorChannelCommunityReplyActivity.kt @@ -12,6 +12,7 @@ import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import kr.co.vividnext.sodalive.R import kr.co.vividnext.sodalive.base.BaseActivity +import kr.co.vividnext.sodalive.common.formatUtcRelativeTimeText import kr.co.vividnext.sodalive.databinding.ActivityCreatorChannelCommunityReplyBinding import kr.co.vividnext.sodalive.v2.components.modal.V2ModalDialog import kr.co.vividnext.sodalive.v2.creator.channel.community.detail.CreatorChannelCommunityCommentUiModel @@ -110,6 +111,11 @@ class CreatorChannelCommunityReplyActivity : BaseActivity viewModel.startParentCommentEdit(comment.comment) }, onDeleteClick = { showDeleteParentCommentDialog() } ) + layoutCreatorChannelCommunityReplyParent.tvCreatorChannelCommunityCommentTime.text = + formatUtcRelativeTimeText( + layoutCreatorChannelCommunityReplyParent.tvCreatorChannelCommunityCommentTime.context, + content.parentComment.createdAtUtc + ) layoutCreatorChannelCommunityReplyParent.layoutCreatorChannelCommunityCommentLatestReply.isVisible = false replyAdapter.submitItems(content.replies) if (etCreatorChannelCommunityReply.text.toString() != content.replyInput) { @@ -183,7 +189,6 @@ class CreatorChannelCommunityReplyActivity : BaseActivity() @@ -57,20 +55,17 @@ class CreatorChannelCommunityReplyViewModel( private var isModifyingReply = false fun loadReplies(postId: Long, parentComment: CreatorChannelCommunityCommentUiModel) { - val formattedParentComment = parentComment.copy( - createdAtText = relativeTimeTextFormatter.format(parentComment.createdAtUtc) - ) this.postId = postId - this.parentComment = formattedParentComment + this.parentComment = parentComment replyRequestGeneration++ isLoadingReplies = false _replyStateLiveData.value = CreatorChannelCommunityReplyUiState.Content( - parentComment = formattedParentComment, + parentComment = parentComment, replies = emptyList(), replyPage = FIRST_PAGE, hasNextReply = false ) - if (formattedParentComment.commentId > 0) { + if (parentComment.commentId > 0) { loadReplyPage(page = FIRST_PAGE, append = false) } } @@ -486,7 +481,6 @@ class CreatorChannelCommunityReplyViewModel( memberNickname = writerNickname, memberProfileUrl = writerProfileImageUrl, createdAtUtc = createdAtUtc, - createdAtText = relativeTimeTextFormatter.format(createdAtUtc), comment = content, likeCount = 0, isLiked = false, diff --git a/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/detail/reply/ui/CreatorChannelCommunityReplyAdapter.kt b/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/detail/reply/ui/CreatorChannelCommunityReplyAdapter.kt index ac8ddad5..be99ad14 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/detail/reply/ui/CreatorChannelCommunityReplyAdapter.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/detail/reply/ui/CreatorChannelCommunityReplyAdapter.kt @@ -7,6 +7,7 @@ import androidx.core.view.isVisible import androidx.recyclerview.widget.RecyclerView import coil.transform.CircleCropTransformation import kr.co.vividnext.sodalive.R +import kr.co.vividnext.sodalive.common.formatUtcRelativeTimeText import kr.co.vividnext.sodalive.databinding.ItemCreatorChannelCommunityReplyBinding import kr.co.vividnext.sodalive.extensions.loadUrl import kr.co.vividnext.sodalive.v2.creator.channel.community.detail.CreatorChannelCommunityReplyUiModel @@ -51,7 +52,8 @@ class CreatorChannelCommunityReplyAdapter( transformations(CircleCropTransformation()) } tvCreatorChannelCommunityReplyItemNickname.text = item.memberNickname - tvCreatorChannelCommunityReplyItemTime.text = item.createdAtText + tvCreatorChannelCommunityReplyItemTime.text = + formatUtcRelativeTimeText(tvCreatorChannelCommunityReplyItemTime.context, item.createdAtUtc) tvCreatorChannelCommunityReplyItemBody.text = item.comment ivCreatorChannelCommunityReplyItemMore.isVisible = item.canShowMore ivCreatorChannelCommunityReplyItemMore.setOnClickListener { anchor -> diff --git a/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/detail/ui/CreatorChannelCommunityCommentAdapter.kt b/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/detail/ui/CreatorChannelCommunityCommentAdapter.kt index 53646870..e33433d8 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/detail/ui/CreatorChannelCommunityCommentAdapter.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/detail/ui/CreatorChannelCommunityCommentAdapter.kt @@ -7,6 +7,7 @@ import androidx.core.view.isVisible import androidx.recyclerview.widget.RecyclerView import coil.transform.CircleCropTransformation import kr.co.vividnext.sodalive.R +import kr.co.vividnext.sodalive.common.formatUtcRelativeTimeText import kr.co.vividnext.sodalive.databinding.ItemCreatorChannelCommunityCommentBinding import kr.co.vividnext.sodalive.extensions.loadUrl import kr.co.vividnext.sodalive.v2.creator.channel.community.detail.CreatorChannelCommunityCommentUiModel @@ -70,7 +71,8 @@ class CreatorChannelCommunityCommentAdapter( transformations(CircleCropTransformation()) } tvCreatorChannelCommunityCommentNickname.text = item.memberNickname - tvCreatorChannelCommunityCommentTime.text = item.createdAtText + tvCreatorChannelCommunityCommentTime.text = + formatUtcRelativeTimeText(tvCreatorChannelCommunityCommentTime.context, item.createdAtUtc) tvCreatorChannelCommunityCommentBody.text = item.comment ivCreatorChannelCommunityCommentMore.isVisible = item.canShowMore && onDeleteClick != null ivCreatorChannelCommunityCommentMore.setOnClickListener { anchor -> diff --git a/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/model/CreatorChannelCommunityMappers.kt b/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/model/CreatorChannelCommunityMappers.kt index 73e58a94..be05987e 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/model/CreatorChannelCommunityMappers.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/model/CreatorChannelCommunityMappers.kt @@ -1,20 +1,17 @@ package kr.co.vividnext.sodalive.v2.creator.channel.community.model -import kr.co.vividnext.sodalive.common.UtcRelativeTimeTextFormatter import kr.co.vividnext.sodalive.v2.creator.channel.community.data.CreatorChannelCommunityPostResponse private const val GRID_PREVIEW_MAX_LENGTH = 24 fun List.toCommunityPostUiModels( - relativeTimeTextFormatter: UtcRelativeTimeTextFormatter, isOwner: Boolean, currentUserId: Long ): List = map { - it.toCommunityPostUiModel(relativeTimeTextFormatter, isOwner, currentUserId) + it.toCommunityPostUiModel(isOwner, currentUserId) } private fun CreatorChannelCommunityPostResponse.toCommunityPostUiModel( - relativeTimeTextFormatter: UtcRelativeTimeTextFormatter, isOwner: Boolean, currentUserId: Long ): CreatorChannelCommunityPostUiModel { @@ -26,7 +23,7 @@ private fun CreatorChannelCommunityPostResponse.toCommunityPostUiModel( creatorId = creatorId, creatorNickname = creatorNickname, creatorProfileUrl = creatorProfileUrl, - createdAtText = relativeTimeTextFormatter.format(createdAtUtc), + createdAtUtc = createdAtUtc, content = content, imageUrl = visibleImageUrl, price = price, diff --git a/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/model/CreatorChannelCommunityUiModels.kt b/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/model/CreatorChannelCommunityUiModels.kt index 9c1e72fc..81987545 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/model/CreatorChannelCommunityUiModels.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/model/CreatorChannelCommunityUiModels.kt @@ -29,7 +29,7 @@ data class CreatorChannelCommunityPostUiModel( val creatorId: Long, val creatorNickname: String, val creatorProfileUrl: String, - val createdAtText: String, + val createdAtUtc: String, val content: String, val imageUrl: String?, val price: Int, diff --git a/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/ui/CreatorChannelCommunityListAdapter.kt b/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/ui/CreatorChannelCommunityListAdapter.kt index f8337f3c..1e544676 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/ui/CreatorChannelCommunityListAdapter.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/ui/CreatorChannelCommunityListAdapter.kt @@ -15,6 +15,7 @@ import com.bumptech.glide.load.engine.GlideException import com.bumptech.glide.request.RequestListener import com.bumptech.glide.request.target.Target import kr.co.vividnext.sodalive.R +import kr.co.vividnext.sodalive.common.formatUtcRelativeTimeText import kr.co.vividnext.sodalive.databinding.ItemCreatorChannelCommunityListBinding import kr.co.vividnext.sodalive.extensions.dpToPx import kr.co.vividnext.sodalive.extensions.loadUrl @@ -68,7 +69,8 @@ class CreatorChannelCommunityListAdapter( transformations(CircleCropTransformation()) } tvCreatorChannelCommunityListNickname.text = item.creatorNickname - tvCreatorChannelCommunityListTime.text = item.createdAtText + tvCreatorChannelCommunityListTime.text = + formatUtcRelativeTimeText(tvCreatorChannelCommunityListTime.context, item.createdAtUtc) layoutCreatorChannelCommunityListNotice.isVisible = item.showNotice tvCreatorChannelCommunityListBody.text = item.content layoutCreatorChannelCommunityListReaction.isVisible = item.showReaction || item.showComment diff --git a/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/donation/CreatorChannelDonationFragment.kt b/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/donation/CreatorChannelDonationFragment.kt index dc529fba..448eb996 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/donation/CreatorChannelDonationFragment.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/donation/CreatorChannelDonationFragment.kt @@ -194,7 +194,7 @@ private data class CreatorChannelDonationItemLayoutKey( val nickname: String, val can: Int, val message: String, - val createdAtText: String + val createdAtUtc: String ) private fun CreatorChannelDonationUiState.Content.toContentLayoutKey(): CreatorChannelDonationContentLayoutKey { @@ -206,7 +206,7 @@ private fun CreatorChannelDonationUiState.Content.toContentLayoutKey(): CreatorC nickname = donation.nickname, can = donation.can, message = donation.message, - createdAtText = donation.createdAtText + createdAtUtc = donation.createdAtUtc ) } ) diff --git a/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/donation/CreatorChannelDonationViewModel.kt b/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/donation/CreatorChannelDonationViewModel.kt index 89a33de3..0e81b320 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/donation/CreatorChannelDonationViewModel.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/donation/CreatorChannelDonationViewModel.kt @@ -7,8 +7,6 @@ import io.reactivex.rxjava3.schedulers.Schedulers import kr.co.vividnext.sodalive.base.BaseViewModel import kr.co.vividnext.sodalive.common.ApiResponse import kr.co.vividnext.sodalive.common.SharedPreferenceManager -import kr.co.vividnext.sodalive.common.SodaLiveApplicationHolder -import kr.co.vividnext.sodalive.common.UtcRelativeTimeTextFormatter import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelRepository import kr.co.vividnext.sodalive.v2.creator.channel.donation.data.CreatorChannelDonationTabResponse import kr.co.vividnext.sodalive.v2.creator.channel.donation.model.CreatorChannelDonationRankingUiModel @@ -17,15 +15,13 @@ import kr.co.vividnext.sodalive.v2.creator.channel.donation.model.toDonationRank import kr.co.vividnext.sodalive.v2.creator.channel.donation.model.toDonationUiModels class CreatorChannelDonationViewModel( - private val repository: CreatorChannelRepository, - private val relativeTimeTextFormatter: UtcRelativeTimeTextFormatter + private val repository: CreatorChannelRepository ) : BaseViewModel() { private val _donationStateLiveData = MutableLiveData() val donationStateLiveData: LiveData get() = _donationStateLiveData - private val context = SodaLiveApplicationHolder.get() private var creatorId: Long = 0L private var isOwner: Boolean = false private var requestGeneration: Int = 0 @@ -222,7 +218,7 @@ class CreatorChannelDonationViewModel( rankings.toDonationRankingUiModels() private fun CreatorChannelDonationTabResponse.toDonationUiModels(): List = - donations.toDonationUiModels(context, relativeTimeTextFormatter) + donations.toDonationUiModels() private fun authToken(): String = "Bearer ${SharedPreferenceManager.token}" diff --git a/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/donation/model/CreatorChannelDonationMappers.kt b/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/donation/model/CreatorChannelDonationMappers.kt index 69eb4c60..f8fe8f59 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/donation/model/CreatorChannelDonationMappers.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/donation/model/CreatorChannelDonationMappers.kt @@ -1,19 +1,15 @@ package kr.co.vividnext.sodalive.v2.creator.channel.donation.model -import android.content.Context import androidx.annotation.ColorRes import kr.co.vividnext.sodalive.R -import kr.co.vividnext.sodalive.common.UtcRelativeTimeTextFormatter import kr.co.vividnext.sodalive.v2.creator.channel.donation.data.CreatorChannelDonationResponse import kr.co.vividnext.sodalive.v2.creator.channel.donation.data.MemberDonationRankingResponse fun List.toDonationRankingUiModels(): List = mapIndexed { index, response -> response.toDonationRankingUiModel(rank = index + 1) } -fun List.toDonationUiModels( - context: Context, - relativeTimeTextFormatter: UtcRelativeTimeTextFormatter -): List = map { it.toDonationUiModel(context, relativeTimeTextFormatter) } +fun List.toDonationUiModels(): List = + map { it.toDonationUiModel() } private fun MemberDonationRankingResponse.toDonationRankingUiModel(rank: Int) = CreatorChannelDonationRankingUiModel( rank = rank, @@ -23,15 +19,12 @@ private fun MemberDonationRankingResponse.toDonationRankingUiModel(rank: Int) = donationCan = donationCan ) -private fun CreatorChannelDonationResponse.toDonationUiModel( - context: Context, - relativeTimeTextFormatter: UtcRelativeTimeTextFormatter -) = CreatorChannelDonationUiModel( +private fun CreatorChannelDonationResponse.toDonationUiModel() = CreatorChannelDonationUiModel( nickname = nickname, profileImageUrl = profileImageUrl, can = can, message = message, - createdAtText = createdAtUtc, + createdAtUtc = createdAtUtc, headerColorResId = calculateDonationHeaderColorRes(can) ) diff --git a/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/donation/model/CreatorChannelDonationUiModels.kt b/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/donation/model/CreatorChannelDonationUiModels.kt index 56699085..9deb78f4 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/donation/model/CreatorChannelDonationUiModels.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/donation/model/CreatorChannelDonationUiModels.kt @@ -15,6 +15,6 @@ data class CreatorChannelDonationUiModel( val profileImageUrl: String, val can: Int, val message: String, - val createdAtText: String, + val createdAtUtc: String, @param:ColorRes val headerColorResId: Int ) diff --git a/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/donation/ui/CreatorChannelDonationAdapter.kt b/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/donation/ui/CreatorChannelDonationAdapter.kt index 8df6cd62..f5103c71 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/donation/ui/CreatorChannelDonationAdapter.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/donation/ui/CreatorChannelDonationAdapter.kt @@ -128,7 +128,7 @@ class CreatorChannelDonationAdapter( headerColorResId = item.headerColorResId, profileImageUrl = item.profileImageUrl, nickname = item.nickname, - createdAtText = formatUtcRelativeTimeText(context, item.createdAtText), + createdAtText = formatUtcRelativeTimeText(context, item.createdAtUtc), can = item.can, message = item.message.takeUnless { it.isBlank() } ?: context.getString(R.string.creator_channel_donation_fallback_message, item.can) diff --git a/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/CreatorChannelFanTalkViewModel.kt b/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/CreatorChannelFanTalkViewModel.kt index 55f3ab90..8b4645e4 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/CreatorChannelFanTalkViewModel.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/CreatorChannelFanTalkViewModel.kt @@ -9,15 +9,13 @@ import kr.co.vividnext.sodalive.common.SodaLiveApplicationHolder import kr.co.vividnext.sodalive.base.BaseViewModel import kr.co.vividnext.sodalive.common.ApiResponse import kr.co.vividnext.sodalive.common.SharedPreferenceManager -import kr.co.vividnext.sodalive.common.UtcRelativeTimeTextFormatter import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelRepository import kr.co.vividnext.sodalive.v2.creator.channel.fantalk.data.CreatorChannelFanTalkTabResponse import kr.co.vividnext.sodalive.v2.creator.channel.fantalk.model.CreatorChannelFanTalkUiModel import kr.co.vividnext.sodalive.v2.creator.channel.fantalk.model.toFanTalkUiModels class CreatorChannelFanTalkViewModel( - private val repository: CreatorChannelRepository, - private val relativeTimeTextFormatter: UtcRelativeTimeTextFormatter + private val repository: CreatorChannelRepository ) : BaseViewModel() { private val _fanTalkStateLiveData = MutableLiveData() @@ -198,7 +196,6 @@ class CreatorChannelFanTalkViewModel( private fun CreatorChannelFanTalkTabResponse.toFanTalkUiModels(): List = fanTalks.toFanTalkUiModels( - relativeTimeTextFormatter = relativeTimeTextFormatter, isOwner = isOwner, currentUserId = SharedPreferenceManager.userId ) diff --git a/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/detail/CreatorChannelFanTalkDetailActivity.kt b/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/detail/CreatorChannelFanTalkDetailActivity.kt index 7fd3f777..2174b480 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/detail/CreatorChannelFanTalkDetailActivity.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/detail/CreatorChannelFanTalkDetailActivity.kt @@ -19,6 +19,7 @@ import androidx.core.widget.doAfterTextChanged import coil.transform.CircleCropTransformation import kr.co.vividnext.sodalive.R import kr.co.vividnext.sodalive.base.BaseActivity +import kr.co.vividnext.sodalive.common.formatUtcRelativeTimeText import kr.co.vividnext.sodalive.databinding.ActivityCreatorChannelFantalkDetailBinding import kr.co.vividnext.sodalive.databinding.ViewCreatorChannelFantalkMorePopupBinding import kr.co.vividnext.sodalive.extensions.loadUrl @@ -107,7 +108,8 @@ class CreatorChannelFanTalkDetailActivity : BaseActivity 0L ivCreatorChannelFantalkDetailReplyMore.setOnClickListener( diff --git a/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/detail/CreatorChannelFanTalkDetailUiModels.kt b/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/detail/CreatorChannelFanTalkDetailUiModels.kt index 52395b08..b197dc25 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/detail/CreatorChannelFanTalkDetailUiModels.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/detail/CreatorChannelFanTalkDetailUiModels.kt @@ -15,7 +15,7 @@ data class CreatorChannelFanTalkDetailPayload( val writerNickname: String, val writerProfileImageUrl: String, val content: String, - val createdAtText: String, + val createdAtUtc: String, val reply: CreatorChannelFanTalkDetailReplyPayload?, val showEdit: Boolean, val showDelete: Boolean @@ -26,7 +26,7 @@ data class CreatorChannelFanTalkDetailPayload( writerNickname = writerNickname, writerProfileImageUrl = writerProfileImageUrl, content = content, - createdAtText = createdAtText, + createdAtUtc = createdAtUtc, reply = reply?.toUiModel(), rightAction = CreatorChannelFanTalkRightAction.OwnerMore(showEdit = showEdit, showDelete = showDelete) ) @@ -39,7 +39,7 @@ data class CreatorChannelFanTalkDetailReplyPayload( val writerNickname: String, val writerProfileImageUrl: String, val content: String, - val createdAtText: String + val createdAtUtc: String ) : Parcelable { fun toUiModel(): CreatorChannelFanTalkReplyUiModel = CreatorChannelFanTalkReplyUiModel( fanTalkId = fanTalkId, @@ -47,7 +47,7 @@ data class CreatorChannelFanTalkDetailReplyPayload( writerNickname = writerNickname, writerProfileImageUrl = writerProfileImageUrl, content = content, - createdAtText = createdAtText + createdAtUtc = createdAtUtc ) } @@ -59,7 +59,7 @@ fun CreatorChannelFanTalkUiModel.toFanTalkDetailPayload(): CreatorChannelFanTalk writerNickname = writerNickname, writerProfileImageUrl = writerProfileImageUrl, content = content, - createdAtText = createdAtText, + createdAtUtc = createdAtUtc, reply = reply?.toFanTalkDetailReplyPayload(), showEdit = ownerMore?.showEdit == true, showDelete = ownerMore?.showDelete == true @@ -73,7 +73,7 @@ fun CreatorChannelFanTalkReplyUiModel.toFanTalkDetailReplyPayload(): CreatorChan writerNickname = writerNickname, writerProfileImageUrl = writerProfileImageUrl, content = content, - createdAtText = createdAtText + createdAtUtc = createdAtUtc ) } diff --git a/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/detail/CreatorChannelFanTalkDetailViewModel.kt b/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/detail/CreatorChannelFanTalkDetailViewModel.kt index 38c52c26..514bdb0f 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/detail/CreatorChannelFanTalkDetailViewModel.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/detail/CreatorChannelFanTalkDetailViewModel.kt @@ -11,15 +11,13 @@ import kr.co.vividnext.sodalive.base.BaseViewModel import kr.co.vividnext.sodalive.common.ApiResponse import kr.co.vividnext.sodalive.common.SharedPreferenceManager import kr.co.vividnext.sodalive.common.ToastMessage -import kr.co.vividnext.sodalive.common.UtcRelativeTimeTextFormatter import kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelEvent import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelRepository import kr.co.vividnext.sodalive.v2.creator.channel.fantalk.data.CreatorChannelFanTalkReplyResponse import kr.co.vividnext.sodalive.v2.creator.channel.fantalk.model.toFanTalkReplyUiModel class CreatorChannelFanTalkDetailViewModel( - private val repository: CreatorChannelRepository, - private val relativeTimeTextFormatter: UtcRelativeTimeTextFormatter + private val repository: CreatorChannelRepository ) : BaseViewModel() { private val _detailStateLiveData = MutableLiveData() @@ -191,7 +189,7 @@ class CreatorChannelFanTalkDetailViewModel( val current = currentContent() ?: return if (response.success) { _detailStateLiveData.value = current.copy( - reply = response.data?.toFanTalkReplyUiModel(relativeTimeTextFormatter) ?: current.reply, + reply = response.data?.toFanTalkReplyUiModel() ?: current.reply, replyInput = "", isSubmitting = false, editingTarget = null diff --git a/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/model/CreatorChannelFanTalkMappers.kt b/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/model/CreatorChannelFanTalkMappers.kt index d6fad5eb..e9eb65b4 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/model/CreatorChannelFanTalkMappers.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/model/CreatorChannelFanTalkMappers.kt @@ -1,19 +1,16 @@ package kr.co.vividnext.sodalive.v2.creator.channel.fantalk.model -import kr.co.vividnext.sodalive.common.UtcRelativeTimeTextFormatter import kr.co.vividnext.sodalive.v2.creator.channel.fantalk.data.CreatorChannelFanTalkReplyResponse import kr.co.vividnext.sodalive.v2.creator.channel.fantalk.data.CreatorChannelFanTalkResponse fun List.toFanTalkUiModels( - relativeTimeTextFormatter: UtcRelativeTimeTextFormatter, isOwner: Boolean, currentUserId: Long ): List = map { - it.toFanTalkUiModel(relativeTimeTextFormatter, isOwner, currentUserId) + it.toFanTalkUiModel(isOwner, currentUserId) } private fun CreatorChannelFanTalkResponse.toFanTalkUiModel( - relativeTimeTextFormatter: UtcRelativeTimeTextFormatter, isOwner: Boolean, currentUserId: Long ) = CreatorChannelFanTalkUiModel( @@ -22,31 +19,27 @@ private fun CreatorChannelFanTalkResponse.toFanTalkUiModel( writerNickname = writerNickname, writerProfileImageUrl = writerProfileImageUrl, content = content, - createdAtText = relativeTimeTextFormatter.format(createdAtUtc), - reply = creatorReplies.orEmpty().firstOrNull()?.toFanTalkReplyUiModel(relativeTimeTextFormatter), + createdAtUtc = createdAtUtc, + reply = creatorReplies.orEmpty().firstOrNull()?.toFanTalkReplyUiModel(), rightAction = toRightAction(isOwner = isOwner, currentUserId = currentUserId) ) -fun CreatorChannelFanTalkReplyResponse.toFanTalkReplyUiModel( - relativeTimeTextFormatter: UtcRelativeTimeTextFormatter -) = CreatorChannelFanTalkReplyUiModel( +fun CreatorChannelFanTalkReplyResponse.toFanTalkReplyUiModel() = CreatorChannelFanTalkReplyUiModel( fanTalkId = fanTalkId, writerId = writerId, writerNickname = writerNickname, writerProfileImageUrl = writerProfileImageUrl, content = content, - createdAtText = relativeTimeTextFormatter.format(createdAtUtc) + createdAtUtc = createdAtUtc ) -fun CreatorChannelFanTalkResponse.toFanTalkReplyUiModel( - relativeTimeTextFormatter: UtcRelativeTimeTextFormatter -) = CreatorChannelFanTalkReplyUiModel( +fun CreatorChannelFanTalkResponse.toFanTalkReplyUiModel() = CreatorChannelFanTalkReplyUiModel( fanTalkId = fanTalkId, writerId = writerId, writerNickname = writerNickname, writerProfileImageUrl = writerProfileImageUrl, content = content, - createdAtText = relativeTimeTextFormatter.format(createdAtUtc) + createdAtUtc = createdAtUtc ) private fun CreatorChannelFanTalkResponse.toRightAction( diff --git a/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/model/CreatorChannelFanTalkUiModels.kt b/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/model/CreatorChannelFanTalkUiModels.kt index c8078b46..de59d460 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/model/CreatorChannelFanTalkUiModels.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/model/CreatorChannelFanTalkUiModels.kt @@ -6,7 +6,7 @@ data class CreatorChannelFanTalkUiModel( val writerNickname: String, val writerProfileImageUrl: String, val content: String, - val createdAtText: String, + val createdAtUtc: String, val reply: CreatorChannelFanTalkReplyUiModel?, val rightAction: CreatorChannelFanTalkRightAction ) @@ -17,7 +17,7 @@ data class CreatorChannelFanTalkReplyUiModel( val writerNickname: String, val writerProfileImageUrl: String, val content: String, - val createdAtText: String + val createdAtUtc: String ) sealed interface CreatorChannelFanTalkRightAction { diff --git a/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/ui/CreatorChannelFanTalkAdapter.kt b/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/ui/CreatorChannelFanTalkAdapter.kt index 106439f3..09f50626 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/ui/CreatorChannelFanTalkAdapter.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/ui/CreatorChannelFanTalkAdapter.kt @@ -7,6 +7,7 @@ import androidx.core.view.isVisible import androidx.recyclerview.widget.RecyclerView import coil.transform.CircleCropTransformation import kr.co.vividnext.sodalive.R +import kr.co.vividnext.sodalive.common.formatUtcRelativeTimeText import kr.co.vividnext.sodalive.databinding.ItemCreatorChannelFantalkBinding import kr.co.vividnext.sodalive.extensions.loadUrl import kr.co.vividnext.sodalive.v2.creator.channel.fantalk.model.CreatorChannelFanTalkReplyUiModel @@ -51,7 +52,8 @@ class CreatorChannelFanTalkAdapter( fun bind(item: CreatorChannelFanTalkUiModel) = with(binding) { ivCreatorChannelFantalkProfile.loadProfile(item.writerProfileImageUrl) tvCreatorChannelFantalkNickname.text = item.writerNickname - tvCreatorChannelFantalkTime.text = item.createdAtText + tvCreatorChannelFantalkTime.text = + formatUtcRelativeTimeText(tvCreatorChannelFantalkTime.context, item.createdAtUtc) tvCreatorChannelFantalkContent.text = item.content root.setOnClickListener { onItemClick(item) } tvCreatorChannelFantalkContent.setOnClickListener { onItemClick(item) } @@ -63,6 +65,7 @@ class CreatorChannelFanTalkAdapter( when (item.rightAction) { CreatorChannelFanTalkRightAction.Report -> { tvCreatorChannelFantalkReport.isVisible = true + tvCreatorChannelFantalkReport.text = root.context.getString(R.string.creator_channel_fantalk_report) tvCreatorChannelFantalkReport.setOnClickListener { onReportClick(item) } ivCreatorChannelFantalkMore.isVisible = false ivCreatorChannelFantalkMore.setOnClickListener(null) @@ -84,7 +87,8 @@ class CreatorChannelFanTalkAdapter( ivCreatorChannelFantalkReplyProfile.loadProfile(reply.writerProfileImageUrl) tvCreatorChannelFantalkReplyNickname.text = reply.writerNickname - tvCreatorChannelFantalkReplyTime.text = reply.createdAtText + tvCreatorChannelFantalkReplyTime.text = + formatUtcRelativeTimeText(tvCreatorChannelFantalkReplyTime.context, reply.createdAtUtc) tvCreatorChannelFantalkReplyContent.text = reply.content } diff --git a/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/ui/CreatorChannelDonationCardBinder.kt b/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/ui/CreatorChannelDonationCardBinder.kt index b2ab092f..3c8cb95a 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/ui/CreatorChannelDonationCardBinder.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/ui/CreatorChannelDonationCardBinder.kt @@ -33,7 +33,7 @@ internal fun bindCreatorChannelDonationCard( } nicknameView.text = nickname createdAtView.text = createdAtText - canView.text = context.getString(R.string.creator_channel_donation_can_format, can.moneyFormat()) + canView.text = can.moneyFormat() messageView.text = message if (headerColorResId == R.color.red_400) { nicknameView.setTextColor(context.getColor(R.color.white)) diff --git a/app/src/main/res/layout/item_creator_channel_donation.xml b/app/src/main/res/layout/item_creator_channel_donation.xml index 428c154b..e543e6a0 100644 --- a/app/src/main/res/layout/item_creator_channel_donation.xml +++ b/app/src/main/res/layout/item_creator_channel_donation.xml @@ -77,7 +77,7 @@ android:layout_height="wrap_content" android:layout_marginStart="2dp" android:textColor="@color/white" - tools:text="20캔" /> + tools:text="20" /> diff --git a/app/src/main/res/values-en/strings.xml b/app/src/main/res/values-en/strings.xml index c489f48e..5570d8e7 100644 --- a/app/src/main/res/values-en/strings.xml +++ b/app/src/main/res/values-en/strings.xml @@ -411,7 +411,6 @@ Be the first to\nsupport this creator! No support yet.\nBe the first to support this creator! No donation history yet. - %1$s cans Supported with %1$d cans. Create content Post community diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 5937a974..e8d9169d 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -411,7 +411,6 @@ 最初のサポートをしてみましょう! まだサポートがありません。\n最初のサポートをしてみましょう! 応援履歴がありません - %1$sCAN %1$dCANを応援しました。 コンテンツ作成 コミュニティ投稿 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ff2bd0fd..ebccaae7 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -410,7 +410,6 @@ 처음으로 크리에이터를\n후원해 보세요! 아직 후원이 없습니다.\n처음으로 크리에이터를 후원해 보세요! 후원 내역이 없습니다 - %1$s캔 %1$d캔을 후원하였습니다. 콘텐츠 만들기 커뮤니티 글 올리기 diff --git a/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/community/CreatorChannelCommunityDetailViewModelTest.kt b/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/community/CreatorChannelCommunityDetailViewModelTest.kt index 319ddb20..6f4e2b83 100644 --- a/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/community/CreatorChannelCommunityDetailViewModelTest.kt +++ b/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/community/CreatorChannelCommunityDetailViewModelTest.kt @@ -13,7 +13,6 @@ import io.reactivex.rxjava3.plugins.RxJavaPlugins import io.reactivex.rxjava3.schedulers.Schedulers import kr.co.vividnext.sodalive.common.ApiResponse import kr.co.vividnext.sodalive.common.SharedPreferenceManager -import kr.co.vividnext.sodalive.common.UtcRelativeTimeTextFormatter import kr.co.vividnext.sodalive.audio_content.comment.ModifyCommentRequest import kr.co.vividnext.sodalive.explorer.profile.creator_community.CreatorCommunityRepository import kr.co.vividnext.sodalive.v2.creator.channel.community.detail.CreatorChannelCommunityDetailUiState @@ -58,7 +57,7 @@ class CreatorChannelCommunityDetailViewModelTest { SharedPreferenceManager.userId = 10L repository = org.mockito.kotlin.mock() legacyRepository = org.mockito.kotlin.mock() - viewModel = CreatorChannelCommunityDetailViewModel(repository, legacyRepository, testFormatter) + viewModel = CreatorChannelCommunityDetailViewModel(repository, legacyRepository) } @After @@ -84,6 +83,7 @@ class CreatorChannelCommunityDetailViewModelTest { val state = viewModel.detailStateLiveData.requireValue() as CreatorChannelCommunityDetailUiState.Content assertEquals(POST_ID, state.post.postId) + assertEquals("2026-07-08T00:00:00Z", state.post.createdAtUtc) assertEquals("body", state.post.content) assertEquals("image.png", state.post.imageUrl) assertEquals("audio.mp3", state.post.audioUrl) @@ -729,6 +729,5 @@ class CreatorChannelCommunityDetailViewModelTest { const val POST_ID = 200L const val COMMENT_ID = 11L const val AUTH_TOKEN = "Bearer test-token" - val testFormatter = UtcRelativeTimeTextFormatter { "방금 전" } } } diff --git a/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/community/CreatorChannelCommunityMapperTest.kt b/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/community/CreatorChannelCommunityMapperTest.kt index f2579395..c81f5b1f 100644 --- a/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/community/CreatorChannelCommunityMapperTest.kt +++ b/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/community/CreatorChannelCommunityMapperTest.kt @@ -1,10 +1,6 @@ package kr.co.vividnext.sodalive.v2.creator.channel.community -import android.app.Application -import android.content.Context -import androidx.test.core.app.ApplicationProvider import kr.co.vividnext.sodalive.R -import kr.co.vividnext.sodalive.common.AndroidUtcRelativeTimeTextFormatter import kr.co.vividnext.sodalive.v2.creator.channel.community.data.CreatorChannelCommunityPostResponse import kr.co.vividnext.sodalive.v2.creator.channel.community.model.CreatorChannelCommunityImageMode import kr.co.vividnext.sodalive.v2.creator.channel.community.model.CreatorChannelCommunityViewMode @@ -14,17 +10,9 @@ 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.RobolectricTestRunner -import org.robolectric.annotation.Config -@RunWith(RobolectricTestRunner::class) -@Config(sdk = [28], application = Application::class) class CreatorChannelCommunityMapperTest { - private val context: Context = ApplicationProvider.getApplicationContext() - private val relativeTimeTextFormatter = AndroidUtcRelativeTimeTextFormatter(context) - @Test fun `보기 방식은 label과 icon resource를 가진다`() { assertEquals(R.string.creator_channel_community_view_mode_list, CreatorChannelCommunityViewMode.List.labelResId) @@ -34,21 +22,22 @@ class CreatorChannelCommunityMapperTest { } @Test - fun `게시글 기본 필드와 상대 시간 notice 댓글 표시 상태를 매핑한다`() { + fun `게시글 기본 필드와 raw UTC notice 댓글 표시 상태를 매핑한다`() { + val createdAtUtc = "2026-06-21T00:00:00Z" val item = listOf( communityPost( creatorProfileUrl = "profile.png", - createdAtUtc = System.currentTimeMillis().toString(), + createdAtUtc = createdAtUtc, isPinned = true, isCommentAvailable = true ) - ).toCommunityPostUiModels(relativeTimeTextFormatter, isOwner = false, currentUserId = 99L).single() + ).toCommunityPostUiModels(isOwner = false, currentUserId = 99L).single() assertEquals(1L, item.postId) assertEquals(10L, item.creatorId) assertEquals("creator", item.creatorNickname) assertEquals("profile.png", item.creatorProfileUrl) - assertEquals(context.getString(R.string.character_comment_time_just_now), item.createdAtText) + assertEquals(createdAtUtc, item.createdAtUtc) assertEquals("hello community", item.content) assertEquals(3, item.likeCount) assertEquals(4, item.commentCount) @@ -59,7 +48,7 @@ class CreatorChannelCommunityMapperTest { @Test fun `댓글 불가 게시글은 댓글 표시 상태가 false다`() { val item = listOf(communityPost(isCommentAvailable = false)) - .toCommunityPostUiModels(relativeTimeTextFormatter, isOwner = false, currentUserId = 99L) + .toCommunityPostUiModels(isOwner = false, currentUserId = 99L) .single() assertFalse(item.showComment) @@ -69,7 +58,7 @@ class CreatorChannelCommunityMapperTest { fun `유료 미구매 타인 게시글은 잠금 상태이고 이미지를 숨긴다`() { val item = listOf( communityPost(price = 100, existOrdered = false, imageUrl = "image.png", audioUrl = "audio.mp3") - ).toCommunityPostUiModels(relativeTimeTextFormatter, isOwner = false, currentUserId = 99L).single() + ).toCommunityPostUiModels(isOwner = false, currentUserId = 99L).single() assertTrue(item.isLocked) assertNull(item.imageUrl) @@ -84,7 +73,7 @@ class CreatorChannelCommunityMapperTest { existOrdered = false, isCommentAvailable = true ) - ).toCommunityPostUiModels(relativeTimeTextFormatter, isOwner = false, currentUserId = 99L).single() + ).toCommunityPostUiModels(isOwner = false, currentUserId = 99L).single() assertTrue(item.isLocked) assertFalse(item.showReaction) @@ -94,10 +83,10 @@ class CreatorChannelCommunityMapperTest { @Test fun `고정 게시글 여부는 UI 모델에 보존한다`() { val pinnedItem = listOf(communityPost(isPinned = true)) - .toCommunityPostUiModels(relativeTimeTextFormatter, isOwner = true, currentUserId = 10L) + .toCommunityPostUiModels(isOwner = true, currentUserId = 10L) .single() val normalItem = listOf(communityPost(isPinned = false)) - .toCommunityPostUiModels(relativeTimeTextFormatter, isOwner = true, currentUserId = 10L) + .toCommunityPostUiModels(isOwner = true, currentUserId = 10L) .single() assertTrue(pinnedItem.isPinned) @@ -109,16 +98,16 @@ class CreatorChannelCommunityMapperTest { @Test fun `메뉴 권한은 작성자 무료 구매 여부에 따라 계산하고 고정 상태를 보존한다`() { val authorPaidNotOrdered = listOf(communityPost(price = 100, existOrdered = false, isPinned = true)) - .toCommunityPostUiModels(relativeTimeTextFormatter, isOwner = false, currentUserId = 10L) + .toCommunityPostUiModels(isOwner = false, currentUserId = 10L) .single() val visitorFree = listOf(communityPost(price = 0, existOrdered = false)) - .toCommunityPostUiModels(relativeTimeTextFormatter, isOwner = false, currentUserId = 99L) + .toCommunityPostUiModels(isOwner = false, currentUserId = 99L) .single() val visitorPaidOrdered = listOf(communityPost(price = 100, existOrdered = true)) - .toCommunityPostUiModels(relativeTimeTextFormatter, isOwner = false, currentUserId = 99L) + .toCommunityPostUiModels(isOwner = false, currentUserId = 99L) .single() val visitorPaidNotOrdered = listOf(communityPost(price = 100, existOrdered = false)) - .toCommunityPostUiModels(relativeTimeTextFormatter, isOwner = false, currentUserId = 99L) + .toCommunityPostUiModels(isOwner = false, currentUserId = 99L) .single() assertTrue(authorPaidNotOrdered.menuItem.isOwner) @@ -134,16 +123,16 @@ class CreatorChannelCommunityMapperTest { @Test fun `본인 채널에 본인이 쓴 유료 게시글에서만 top price를 표시한다`() { val ownerPaid = listOf(communityPost(price = 100, creatorId = 10L)) - .toCommunityPostUiModels(relativeTimeTextFormatter, isOwner = true, currentUserId = 10L) + .toCommunityPostUiModels(isOwner = true, currentUserId = 10L) .single() val ownerFree = listOf(communityPost(price = 0, creatorId = 10L)) - .toCommunityPostUiModels(relativeTimeTextFormatter, isOwner = true, currentUserId = 10L) + .toCommunityPostUiModels(isOwner = true, currentUserId = 10L) .single() val otherCreator = listOf(communityPost(price = 100, creatorId = 11L)) - .toCommunityPostUiModels(relativeTimeTextFormatter, isOwner = true, currentUserId = 10L) + .toCommunityPostUiModels(isOwner = true, currentUserId = 10L) .single() val otherChannel = listOf(communityPost(price = 100, creatorId = 10L)) - .toCommunityPostUiModels(relativeTimeTextFormatter, isOwner = false, currentUserId = 10L) + .toCommunityPostUiModels(isOwner = false, currentUserId = 10L) .single() assertTrue(ownerPaid.showOwnerTopPrice) @@ -155,7 +144,7 @@ class CreatorChannelCommunityMapperTest { @Test fun `grid text-only preview는 줄바꿈을 공백으로 바꾸고 trim 후 24자까지만 사용한다`() { val item = listOf(communityPost(content = "\n123456789012345678901234567890\n")) - .toCommunityPostUiModels(relativeTimeTextFormatter, isOwner = false, currentUserId = 99L) + .toCommunityPostUiModels(isOwner = false, currentUserId = 99L) .single() assertEquals("123456789012345678901234", item.gridPreviewText) diff --git a/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/community/CreatorChannelCommunityPaginationTest.kt b/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/community/CreatorChannelCommunityPaginationTest.kt index 880182a5..4265593f 100644 --- a/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/community/CreatorChannelCommunityPaginationTest.kt +++ b/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/community/CreatorChannelCommunityPaginationTest.kt @@ -12,7 +12,6 @@ import io.reactivex.rxjava3.plugins.RxJavaPlugins import io.reactivex.rxjava3.schedulers.Schedulers import io.reactivex.rxjava3.subjects.SingleSubject import kr.co.vividnext.sodalive.common.ApiResponse -import kr.co.vividnext.sodalive.common.AndroidUtcRelativeTimeTextFormatter import kr.co.vividnext.sodalive.common.SharedPreferenceManager import kr.co.vividnext.sodalive.v2.creator.channel.community.data.CreatorChannelCommunityPostResponse import kr.co.vividnext.sodalive.v2.creator.channel.community.data.CreatorChannelCommunityTabResponse @@ -45,7 +44,7 @@ class CreatorChannelCommunityPaginationTest { SharedPreferenceManager.init(context) SharedPreferenceManager.token = "test-token" repository = org.mockito.kotlin.mock() - viewModel = CreatorChannelCommunityViewModel(repository, AndroidUtcRelativeTimeTextFormatter(context)) + viewModel = CreatorChannelCommunityViewModel(repository) } @After diff --git a/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/community/CreatorChannelCommunityRelativeTimeLocaleTest.kt b/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/community/CreatorChannelCommunityRelativeTimeLocaleTest.kt new file mode 100644 index 00000000..ebc79c22 --- /dev/null +++ b/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/community/CreatorChannelCommunityRelativeTimeLocaleTest.kt @@ -0,0 +1,179 @@ +package kr.co.vividnext.sodalive.v2.creator.channel.community + +import android.app.Application +import android.content.Context +import android.content.res.Configuration +import android.widget.FrameLayout +import android.widget.TextView +import androidx.test.core.app.ApplicationProvider +import kr.co.vividnext.sodalive.R +import kr.co.vividnext.sodalive.common.ImageLoaderProvider +import kr.co.vividnext.sodalive.common.formatUtcRelativeTimeText +import kr.co.vividnext.sodalive.v2.creator.channel.community.detail.CreatorChannelCommunityCommentUiModel +import kr.co.vividnext.sodalive.v2.creator.channel.community.detail.CreatorChannelCommunityReplyUiModel +import kr.co.vividnext.sodalive.v2.creator.channel.community.detail.reply.ui.CreatorChannelCommunityReplyAdapter +import kr.co.vividnext.sodalive.v2.creator.channel.community.detail.ui.CreatorChannelCommunityCommentAdapter +import kr.co.vividnext.sodalive.v2.creator.channel.community.model.CreatorChannelCommunityImageMode +import kr.co.vividnext.sodalive.v2.creator.channel.community.model.CreatorChannelCommunityMenuItem +import kr.co.vividnext.sodalive.v2.creator.channel.community.model.CreatorChannelCommunityPostUiModel +import kr.co.vividnext.sodalive.v2.creator.channel.community.ui.CreatorChannelCommunityListAdapter +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.RuntimeEnvironment +import org.robolectric.annotation.Config +import java.io.File +import java.util.Locale + +@RunWith(RobolectricTestRunner::class) +@Config(sdk = [28], application = Application::class) +class CreatorChannelCommunityRelativeTimeLocaleTest { + + @Before + fun setUp() { + if (!ImageLoaderProvider.isInitialized) { + ImageLoaderProvider.init(RuntimeEnvironment.getApplication()) + } + } + + @Test + fun `영어와 일본어 목록 댓글 답글 bind는 raw UTC를 현재 View locale 상대 시간으로 표시한다`() { + listOf(Locale.ENGLISH, Locale.JAPANESE).forEach(::assertAdapterRelativeTime) + } + + @Test + fun `영어와 일본어 상세와 답글 화면은 raw UTC를 현재 View locale 상대 시간으로 표시한다`() { + val detailSource = projectFile( + "app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/detail/" + + "CreatorChannelCommunityDetailActivity.kt" + ).readText() + val replySource = projectFile( + "app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/detail/reply/" + + "CreatorChannelCommunityReplyActivity.kt" + ).readText() + + assertTrue( + Regex( + "tvCreatorChannelCommunityDetailTime\\.text\\s*=\\s*" + + "formatUtcRelativeTimeText\\(tvCreatorChannelCommunityDetailTime\\.context, post\\.createdAtUtc\\)" + ).containsMatchIn(detailSource) + ) + assertTrue( + Regex( + "layoutCreatorChannelCommunityReplyParent\\.tvCreatorChannelCommunityCommentTime\\.text\\s*=\\s*" + + "formatUtcRelativeTimeText\\(\\s*" + + "layoutCreatorChannelCommunityReplyParent\\.tvCreatorChannelCommunityCommentTime\\.context,\\s*" + + "content\\.parentComment\\.createdAtUtc\\s*\\)" + ).containsMatchIn(replySource) + ) + listOf(Locale.ENGLISH, Locale.JAPANESE).forEach { locale -> + val context = localizedContext(locale) + assertEquals( + context.getString(R.string.character_comment_time_just_now), + formatUtcRelativeTimeText(context, System.currentTimeMillis().toString()) + ) + } + } + + private fun assertAdapterRelativeTime(locale: Locale) { + val context = localizedContext(locale) + val nowUtc = System.currentTimeMillis().toString() + val expected = context.getString(R.string.character_comment_time_just_now) + + val listAdapter = CreatorChannelCommunityListAdapter() + val listHolder = listAdapter.onCreateViewHolder(FrameLayout(context), 0) + listAdapter.submitItems(listOf(post(nowUtc))) + listAdapter.onBindViewHolder(listHolder, 0) + assertEquals( + expected, + listHolder.itemView.findViewById(R.id.tv_creator_channel_community_list_time).text.toString() + ) + + val commentAdapter = CreatorChannelCommunityCommentAdapter(onReplyClick = {}) + val commentHolder = commentAdapter.onCreateViewHolder(FrameLayout(context), 0) + commentAdapter.submitItems(listOf(comment(nowUtc))) + commentAdapter.onBindViewHolder(commentHolder, 0) + assertEquals( + expected, + commentHolder.itemView.findViewById(R.id.tv_creator_channel_community_comment_time).text.toString() + ) + + val replyAdapter = CreatorChannelCommunityReplyAdapter(onModifyClick = {}, onDeleteClick = {}) + val replyHolder = replyAdapter.onCreateViewHolder(FrameLayout(context), 0) + replyAdapter.submitItems(listOf(reply(nowUtc))) + replyAdapter.onBindViewHolder(replyHolder, 0) + assertEquals( + expected, + replyHolder.itemView.findViewById(R.id.tv_creator_channel_community_reply_item_time).text.toString() + ) + } + + private fun post(createdAtUtc: String) = CreatorChannelCommunityPostUiModel( + postId = 1L, + creatorId = 2L, + creatorNickname = "creator", + creatorProfileUrl = "", + createdAtUtc = createdAtUtc, + content = "post", + imageUrl = null, + price = 0, + existOrdered = true, + likeCount = 0, + commentCount = 0, + showReaction = true, + showComment = true, + showNotice = false, + isPinned = false, + isLocked = false, + showOwnerTopPrice = false, + gridPreviewText = "post", + imageMode = CreatorChannelCommunityImageMode.TextPreview, + menuItem = CreatorChannelCommunityMenuItem(1L, 2L, 3L, 0, true, false) + ) + + private fun comment(createdAtUtc: String) = CreatorChannelCommunityCommentUiModel( + commentId = 1L, + memberId = 2L, + memberNickname = "member", + memberProfileUrl = "", + createdAtUtc = createdAtUtc, + comment = "comment", + likeCount = 0, + replyCount = 0, + isLiked = false, + isMine = false, + isCreatorOwner = false, + latestReply = null + ) + + private fun reply(createdAtUtc: String) = CreatorChannelCommunityReplyUiModel( + replyId = 1L, + commentId = 2L, + memberId = 3L, + memberNickname = "member", + memberProfileUrl = "", + createdAtUtc = createdAtUtc, + comment = "reply", + likeCount = 0, + isLiked = false, + isMine = false, + isCreatorOwner = false + ) + + private fun localizedContext(locale: Locale): Context { + val context = ApplicationProvider.getApplicationContext() + val configuration = Configuration(context.resources.configuration) + configuration.setLocale(locale) + return context.createConfigurationContext(configuration) + } + + private fun projectFile(path: String): File = File(projectRoot(), path) + + private fun projectRoot(): File { + return generateSequence(File(System.getProperty("user.dir") ?: ".").absoluteFile) { it.parentFile } + .first { File(it, "settings.gradle").exists() } + } +} diff --git a/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/community/CreatorChannelCommunityReplyViewModelTest.kt b/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/community/CreatorChannelCommunityReplyViewModelTest.kt index 4faf6672..1424b00c 100644 --- a/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/community/CreatorChannelCommunityReplyViewModelTest.kt +++ b/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/community/CreatorChannelCommunityReplyViewModelTest.kt @@ -14,7 +14,6 @@ import io.reactivex.rxjava3.subjects.PublishSubject import kr.co.vividnext.sodalive.audio_content.comment.ModifyCommentRequest import kr.co.vividnext.sodalive.common.ApiResponse import kr.co.vividnext.sodalive.common.SharedPreferenceManager -import kr.co.vividnext.sodalive.common.UtcRelativeTimeTextFormatter import kr.co.vividnext.sodalive.explorer.profile.creator_community.CreatorCommunityRepository import kr.co.vividnext.sodalive.v2.creator.channel.community.detail.CreatorChannelCommunityCommentUiModel import kr.co.vividnext.sodalive.v2.creator.channel.community.detail.reply.CreatorChannelCommunityReplyUiState @@ -46,7 +45,6 @@ class CreatorChannelCommunityReplyViewModelTest { private lateinit var repository: CreatorChannelRepository private lateinit var legacyRepository: CreatorCommunityRepository private lateinit var viewModel: CreatorChannelCommunityReplyViewModel - private lateinit var formattedInputs: MutableList @Before fun setUp() { @@ -57,15 +55,7 @@ class CreatorChannelCommunityReplyViewModelTest { SharedPreferenceManager.userId = 10L repository = org.mockito.kotlin.mock() legacyRepository = org.mockito.kotlin.mock() - formattedInputs = mutableListOf() - viewModel = CreatorChannelCommunityReplyViewModel( - repository, - legacyRepository, - UtcRelativeTimeTextFormatter { value -> - formattedInputs.add(value) - "relative:$value" - } - ) + viewModel = CreatorChannelCommunityReplyViewModel(repository, legacyRepository) } @After @@ -84,9 +74,9 @@ class CreatorChannelCommunityReplyViewModelTest { val state = viewModel.replyStateLiveData.requireValue() as CreatorChannelCommunityReplyUiState.Content assertEquals(COMMENT_ID, state.parentComment.commentId) assertEquals("부모 댓글", state.parentComment.comment) - assertEquals("relative:2026-07-08T00:00:00Z", state.parentComment.createdAtText) + assertEquals("2026-07-08T00:00:00Z", state.parentComment.createdAtUtc) assertEquals(listOf(101L, 102L), state.replies.map { it.replyId }) - assertTrue(formattedInputs.contains("2026-07-08T00:00:00Z")) + assertEquals(listOf("2026-07-08T00:00:00Z", "2026-07-08T00:00:00Z"), state.replies.map { it.createdAtUtc }) assertEquals(0, state.replyPage) assertTrue(state.hasNextReply) verify(repository).getCommunityCommentReplies( @@ -499,7 +489,6 @@ class CreatorChannelCommunityReplyViewModelTest { memberId = 10L, memberNickname = "member", memberProfileUrl = "member.png", - createdAtText = "이미 포맷된 시간", createdAtUtc = "2026-07-08T00:00:00Z", comment = "부모 댓글", likeCount = 1, diff --git a/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/community/CreatorChannelCommunityViewModelTest.kt b/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/community/CreatorChannelCommunityViewModelTest.kt index b81e3afc..aa9313b2 100644 --- a/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/community/CreatorChannelCommunityViewModelTest.kt +++ b/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/community/CreatorChannelCommunityViewModelTest.kt @@ -11,7 +11,6 @@ import io.reactivex.rxjava3.core.Single import io.reactivex.rxjava3.plugins.RxJavaPlugins import io.reactivex.rxjava3.schedulers.Schedulers import kr.co.vividnext.sodalive.common.ApiResponse -import kr.co.vividnext.sodalive.common.AndroidUtcRelativeTimeTextFormatter import kr.co.vividnext.sodalive.common.SharedPreferenceManager import kr.co.vividnext.sodalive.v2.creator.channel.community.data.CreatorChannelCommunityPostResponse import kr.co.vividnext.sodalive.v2.creator.channel.community.data.CreatorChannelCommunityTabResponse @@ -45,7 +44,7 @@ class CreatorChannelCommunityViewModelTest { SharedPreferenceManager.init(context) SharedPreferenceManager.token = "test-token" repository = org.mockito.kotlin.mock() - viewModel = CreatorChannelCommunityViewModel(repository, AndroidUtcRelativeTimeTextFormatter(context)) + viewModel = CreatorChannelCommunityViewModel(repository) } @After @@ -66,6 +65,7 @@ class CreatorChannelCommunityViewModelTest { assertEquals(0, state.page) assertEquals(CreatorChannelCommunityViewModel.DEFAULT_PAGE_SIZE, state.size) assertEquals(listOf(1L), state.communityPosts.map { it.postId }) + assertEquals("2026-06-21T00:00:00Z", state.communityPosts.single().createdAtUtc) verifyGetCommunity() } diff --git a/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/donation/CreatorChannelDonationActionTest.kt b/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/donation/CreatorChannelDonationActionTest.kt index 36f2a360..e4cedd23 100644 --- a/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/donation/CreatorChannelDonationActionTest.kt +++ b/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/donation/CreatorChannelDonationActionTest.kt @@ -94,7 +94,7 @@ class CreatorChannelDonationActionTest { assertTrue(adapter.contains("if (!isOwner) View.OnClickListener { onEmptyDonationClick() } else null")) assertTrue(adapter.contains("bindCreatorChannelDonationCard(")) assertTrue(adapter.contains("headerColorResId = item.headerColorResId")) - assertTrue(adapter.contains("createdAtText = formatUtcRelativeTimeText(context, item.createdAtText)")) + assertTrue(adapter.contains("createdAtText = formatUtcRelativeTimeText(context, item.createdAtUtc)")) assertTrue(adapter.contains("R.string.creator_channel_donation_fallback_message")) assertTrue(adapter.contains("GridLayoutManager(itemView.context, 4)")) assertTrue(rankingAdapter.contains("item.profileImageUrl")) diff --git a/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/donation/CreatorChannelDonationFragmentLayoutTest.kt b/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/donation/CreatorChannelDonationFragmentLayoutTest.kt index a4cd2743..2041efbc 100644 --- a/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/donation/CreatorChannelDonationFragmentLayoutTest.kt +++ b/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/donation/CreatorChannelDonationFragmentLayoutTest.kt @@ -176,9 +176,9 @@ class CreatorChannelDonationFragmentLayoutTest { } @Test - fun `후원 can binder는 한국어 영어 일본어에서 1,000 단위 표시를 사용한다`() { - listOf(Locale.KOREAN to "1,000캔", Locale.ENGLISH to "1,000 cans", Locale.JAPANESE to "1,000CAN") - .forEach { (locale, expected) -> + fun `후원 can badge는 한국어 영어 일본어에서 단위 없이 포맷된 숫자만 표시한다`() { + listOf(Locale.KOREAN, Locale.ENGLISH, Locale.JAPANESE) + .forEach { locale -> val application = ApplicationProvider.getApplicationContext() if (!ImageLoaderProvider.isInitialized) ImageLoaderProvider.init(application) val context = application.createConfigurationContext( @@ -189,7 +189,7 @@ class CreatorChannelDonationFragmentLayoutTest { View(context), ImageView(context), TextView(context), TextView(context), canView, TextView(context), R.color.gray_200, "", "member", "time", 1000, "message" ) - assertEquals(expected, canView.text.toString()) + assertEquals("1,000", canView.text.toString()) } } @@ -212,7 +212,7 @@ class CreatorChannelDonationFragmentLayoutTest { profileImageUrl = "", can = 50, message = "", - createdAtText = twoMinutesAgoMillis.toString(), + createdAtUtc = twoMinutesAgoMillis.toString(), headerColorResId = R.color.gray_200 ) ) diff --git a/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/donation/CreatorChannelDonationMapperTest.kt b/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/donation/CreatorChannelDonationMapperTest.kt index 30b701a8..3d49005f 100644 --- a/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/donation/CreatorChannelDonationMapperTest.kt +++ b/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/donation/CreatorChannelDonationMapperTest.kt @@ -1,29 +1,15 @@ package kr.co.vividnext.sodalive.v2.creator.channel.donation -import android.app.Application -import android.content.Context -import android.content.res.Configuration -import androidx.test.core.app.ApplicationProvider import kr.co.vividnext.sodalive.R -import kr.co.vividnext.sodalive.common.AndroidUtcRelativeTimeTextFormatter import kr.co.vividnext.sodalive.v2.creator.channel.donation.data.CreatorChannelDonationResponse import kr.co.vividnext.sodalive.v2.creator.channel.donation.data.MemberDonationRankingResponse import kr.co.vividnext.sodalive.v2.creator.channel.donation.model.toDonationRankingUiModels import kr.co.vividnext.sodalive.v2.creator.channel.donation.model.toDonationUiModels import org.junit.Assert.assertEquals import org.junit.Test -import org.junit.runner.RunWith -import org.robolectric.RobolectricTestRunner -import org.robolectric.annotation.Config -import java.util.Locale -@RunWith(RobolectricTestRunner::class) -@Config(sdk = [28], application = Application::class) class CreatorChannelDonationMapperTest { - private val context: Context = ApplicationProvider.getApplicationContext() - private val relativeTimeTextFormatter = AndroidUtcRelativeTimeTextFormatter(context) - @Test fun `ranking rank는 응답 순서 기준 1부터 시작하고 profileImage를 profileImageUrl로 매핑한다`() { val rankings = listOf( @@ -39,43 +25,38 @@ class CreatorChannelDonationMapperTest { @Test fun `후원 mapper는 message 원문을 보존한다`() { val item = listOf(donation(can = 50, message = "")) - .toDonationUiModels(context, relativeTimeTextFormatter) + .toDonationUiModels() .single() assertEquals("", item.message) } @Test - fun `message는 한국어 영어 일본어 context와 관계없이 API 원문을 유지한다`() { - listOf(Locale.KOREAN, Locale.ENGLISH, Locale.JAPANESE).forEach { locale -> - val localizedContext = context.createConfigurationContext( - Configuration(context.resources.configuration).apply { setLocale(locale) } - ) - val items = listOf( - donation(can = 50, message = ""), - donation(can = 50, message = "응원합니다") - ).toDonationUiModels(localizedContext) { "time" } + fun `message는 API 원문을 유지한다`() { + val items = listOf( + donation(can = 50, message = ""), + donation(can = 50, message = "응원합니다") + ).toDonationUiModels() - assertEquals("", items[0].message) - assertEquals("응원합니다", items[1].message) - } + assertEquals("", items[0].message) + assertEquals("응원합니다", items[1].message) } @Test fun `후원 mapper는 createdAtUtc 원문을 보존하고 can 기준 header color를 계산한다`() { val createdAtUtc = System.currentTimeMillis().toString() val item = listOf(donation(can = 501, createdAtUtc = createdAtUtc)) - .toDonationUiModels(context, relativeTimeTextFormatter) + .toDonationUiModels() .single() - assertEquals(createdAtUtc, item.createdAtText) + assertEquals(createdAtUtc, item.createdAtUtc) assertEquals(R.color.red_400, item.headerColorResId) } @Test fun `후원 header color는 501캔부터 red_400으로 매핑한다`() { val items = listOf(donation(can = 500), donation(can = 501)) - .toDonationUiModels(context, relativeTimeTextFormatter) + .toDonationUiModels() assertEquals(R.color.creator_channel_donation_cyan, items[0].headerColorResId) assertEquals(R.color.red_400, items[1].headerColorResId) diff --git a/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/donation/CreatorChannelDonationPaginationTest.kt b/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/donation/CreatorChannelDonationPaginationTest.kt index bc1ed6a2..46d8adaa 100644 --- a/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/donation/CreatorChannelDonationPaginationTest.kt +++ b/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/donation/CreatorChannelDonationPaginationTest.kt @@ -12,7 +12,6 @@ import io.reactivex.rxjava3.plugins.RxJavaPlugins import io.reactivex.rxjava3.schedulers.Schedulers import io.reactivex.rxjava3.subjects.SingleSubject import kr.co.vividnext.sodalive.common.ApiResponse -import kr.co.vividnext.sodalive.common.AndroidUtcRelativeTimeTextFormatter import kr.co.vividnext.sodalive.common.SharedPreferenceManager import kr.co.vividnext.sodalive.common.SodaLiveApplicationHolder import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelRepository @@ -48,7 +47,7 @@ class CreatorChannelDonationPaginationTest { SodaLiveApplicationHolder.init(context as Application) SharedPreferenceManager.token = "test-token" repository = org.mockito.kotlin.mock() - viewModel = CreatorChannelDonationViewModel(repository, AndroidUtcRelativeTimeTextFormatter(context)) + viewModel = CreatorChannelDonationViewModel(repository) } @After diff --git a/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/donation/CreatorChannelDonationViewModelTest.kt b/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/donation/CreatorChannelDonationViewModelTest.kt index ee1c163e..5acaa245 100644 --- a/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/donation/CreatorChannelDonationViewModelTest.kt +++ b/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/donation/CreatorChannelDonationViewModelTest.kt @@ -11,7 +11,6 @@ import io.reactivex.rxjava3.core.Single import io.reactivex.rxjava3.plugins.RxJavaPlugins import io.reactivex.rxjava3.schedulers.Schedulers import kr.co.vividnext.sodalive.common.ApiResponse -import kr.co.vividnext.sodalive.common.AndroidUtcRelativeTimeTextFormatter import kr.co.vividnext.sodalive.common.SharedPreferenceManager import kr.co.vividnext.sodalive.common.SodaLiveApplicationHolder import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelRepository @@ -48,7 +47,7 @@ class CreatorChannelDonationViewModelTest { SharedPreferenceManager.token = "test-token" SharedPreferenceManager.can = 200 repository = org.mockito.kotlin.mock() - viewModel = CreatorChannelDonationViewModel(repository, AndroidUtcRelativeTimeTextFormatter(context)) + viewModel = CreatorChannelDonationViewModel(repository) } @After diff --git a/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/CreatorChannelFanTalkActionTest.kt b/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/CreatorChannelFanTalkActionTest.kt index ea7fa129..85ced850 100644 --- a/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/CreatorChannelFanTalkActionTest.kt +++ b/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/CreatorChannelFanTalkActionTest.kt @@ -13,7 +13,6 @@ import io.reactivex.rxjava3.schedulers.Schedulers import kr.co.vividnext.sodalive.R import kr.co.vividnext.sodalive.common.SodaLiveApplicationHolder import kr.co.vividnext.sodalive.common.ApiResponse -import kr.co.vividnext.sodalive.common.AndroidUtcRelativeTimeTextFormatter import kr.co.vividnext.sodalive.common.SharedPreferenceManager import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelRepository import kr.co.vividnext.sodalive.v2.creator.channel.fantalk.data.CreatorChannelFanTalkReplyResponse @@ -47,7 +46,7 @@ class CreatorChannelFanTalkActionTest { SharedPreferenceManager.token = "test-token" SharedPreferenceManager.userId = 10L repository = org.mockito.kotlin.mock() - viewModel = CreatorChannelFanTalkViewModel(repository, AndroidUtcRelativeTimeTextFormatter(context)) + viewModel = CreatorChannelFanTalkViewModel(repository) } @After diff --git a/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/CreatorChannelFanTalkAdapterLocaleTest.kt b/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/CreatorChannelFanTalkAdapterLocaleTest.kt new file mode 100644 index 00000000..da828285 --- /dev/null +++ b/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/CreatorChannelFanTalkAdapterLocaleTest.kt @@ -0,0 +1,106 @@ +package kr.co.vividnext.sodalive.v2.creator.channel.fantalk + +import android.app.Application +import android.content.Context +import android.content.res.Configuration +import android.widget.FrameLayout +import android.widget.TextView +import androidx.test.core.app.ApplicationProvider +import kr.co.vividnext.sodalive.R +import kr.co.vividnext.sodalive.common.ImageLoaderProvider +import kr.co.vividnext.sodalive.v2.creator.channel.fantalk.model.CreatorChannelFanTalkReplyUiModel +import kr.co.vividnext.sodalive.v2.creator.channel.fantalk.model.CreatorChannelFanTalkRightAction +import kr.co.vividnext.sodalive.v2.creator.channel.fantalk.model.CreatorChannelFanTalkUiModel +import kr.co.vividnext.sodalive.v2.creator.channel.fantalk.ui.CreatorChannelFanTalkAdapter +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.RuntimeEnvironment +import org.robolectric.annotation.Config +import java.util.Locale + +@RunWith(RobolectricTestRunner::class) +@Config(sdk = [28], application = Application::class) +class CreatorChannelFanTalkAdapterLocaleTest { + + @Before + fun setUp() { + if (!ImageLoaderProvider.isInitialized) { + ImageLoaderProvider.init(RuntimeEnvironment.getApplication()) + } + } + + @Test + fun `영어와 일본어 bind는 오염된 신고 문구를 현재 locale 문자열로 복원한다`() { + assertReportText(Locale.ENGLISH, "Report") + assertReportText(Locale.JAPANESE, "通報") + } + + @Test + fun `영어와 일본어 목록 bind는 원글과 답글 raw UTC를 현재 View locale 상대 시간으로 표시한다`() { + assertRelativeTimeText(Locale.ENGLISH) + assertRelativeTimeText(Locale.JAPANESE) + } + + private fun assertRelativeTimeText(locale: Locale) { + val context = localizedContext(locale) + val parent = FrameLayout(context) + val adapter = CreatorChannelFanTalkAdapter() + val holder = adapter.onCreateViewHolder(parent, 0) + val nowUtc = System.currentTimeMillis().toString() + adapter.submitItems(listOf(fanTalk(nowUtc))) + + adapter.onBindViewHolder(holder, 0) + + val expected = context.getString(R.string.character_comment_time_just_now) + assertEquals( + expected, + holder.itemView.findViewById(R.id.tv_creator_channel_fantalk_time).text.toString() + ) + assertEquals( + expected, + holder.itemView.findViewById(R.id.tv_creator_channel_fantalk_reply_time).text.toString() + ) + } + + private fun assertReportText(locale: Locale, expected: String) { + val context = localizedContext(locale) + val parent = FrameLayout(context) + val adapter = CreatorChannelFanTalkAdapter() + val holder = adapter.onCreateViewHolder(parent, 0) + val report = holder.itemView.findViewById(R.id.tv_creator_channel_fantalk_report) + report.text = "신고" + adapter.submitItems(listOf(fanTalk())) + + adapter.onBindViewHolder(holder, 0) + + assertEquals(expected, report.text.toString()) + } + + private fun localizedContext(locale: Locale): Context { + val context = ApplicationProvider.getApplicationContext() + val configuration = Configuration(context.resources.configuration) + configuration.setLocale(locale) + return context.createConfigurationContext(configuration) + } + + private fun fanTalk(createdAtUtc: String = "2026-06-21T00:00:00Z") = CreatorChannelFanTalkUiModel( + fanTalkId = 1L, + writerId = 2L, + writerNickname = "writer", + writerProfileImageUrl = "", + content = "fan talk", + createdAtUtc = createdAtUtc, + reply = CreatorChannelFanTalkReplyUiModel( + fanTalkId = 3L, + writerId = 4L, + writerNickname = "creator", + writerProfileImageUrl = "", + content = "reply", + createdAtUtc = createdAtUtc + ), + rightAction = CreatorChannelFanTalkRightAction.Report + ) +} diff --git a/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/CreatorChannelFanTalkMapperTest.kt b/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/CreatorChannelFanTalkMapperTest.kt index 47f667c5..9453ebbd 100644 --- a/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/CreatorChannelFanTalkMapperTest.kt +++ b/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/CreatorChannelFanTalkMapperTest.kt @@ -1,10 +1,5 @@ package kr.co.vividnext.sodalive.v2.creator.channel.fantalk -import android.app.Application -import android.content.Context -import androidx.test.core.app.ApplicationProvider -import kr.co.vividnext.sodalive.R -import kr.co.vividnext.sodalive.common.AndroidUtcRelativeTimeTextFormatter import kr.co.vividnext.sodalive.v2.creator.channel.fantalk.data.CreatorChannelFanTalkReplyResponse import kr.co.vividnext.sodalive.v2.creator.channel.fantalk.data.CreatorChannelFanTalkResponse import kr.co.vividnext.sodalive.v2.creator.channel.fantalk.model.CreatorChannelFanTalkRightAction @@ -13,35 +8,33 @@ import org.junit.Assert.assertEquals import org.junit.Assert.assertNull import org.junit.Assert.assertTrue import org.junit.Test -import org.junit.runner.RunWith -import org.robolectric.RobolectricTestRunner -import org.robolectric.annotation.Config -@RunWith(RobolectricTestRunner::class) -@Config(sdk = [28], application = Application::class) class CreatorChannelFanTalkMapperTest { - private val context: Context = ApplicationProvider.getApplicationContext() - private val relativeTimeTextFormatter = AndroidUtcRelativeTimeTextFormatter(context) - @Test - fun `기본 필드와 상대 시간을 매핑한다`() { - val item = listOf(fanTalk(createdAtUtc = System.currentTimeMillis().toString())) - .toFanTalkUiModels(relativeTimeTextFormatter, isOwner = false, currentUserId = 99L) + fun `기본 필드와 원글 답글 raw UTC를 변경 없이 매핑한다`() { + val item = listOf( + fanTalk( + createdAtUtc = "2026-06-21T00:00:00Z", + creatorReplies = listOf(reply(createdAtUtc = "2026-06-21T00:01:00Z")) + ) + ) + .toFanTalkUiModels(isOwner = false, currentUserId = 99L) .single() assertEquals(1L, item.fanTalkId) assertEquals(10L, item.writerId) assertEquals("writer", item.writerNickname) assertEquals("profile.png", item.writerProfileImageUrl) - assertEquals(context.getString(R.string.character_comment_time_just_now), item.createdAtText) + assertEquals("2026-06-21T00:00:00Z", item.createdAtUtc) + assertEquals("2026-06-21T00:01:00Z", item.reply?.createdAtUtc) assertEquals("hello fan talk", item.content) } @Test fun `creatorReplies가 비어 있으면 reply는 null이다`() { val item = listOf(fanTalk(creatorReplies = emptyList())) - .toFanTalkUiModels(relativeTimeTextFormatter, isOwner = false, currentUserId = 99L) + .toFanTalkUiModels(isOwner = false, currentUserId = 99L) .single() assertNull(item.reply) @@ -56,7 +49,7 @@ class CreatorChannelFanTalkMapperTest { reply(fanTalkId = 3L, content = "second reply") ) ) - ).toFanTalkUiModels(relativeTimeTextFormatter, isOwner = false, currentUserId = 99L).single() + ).toFanTalkUiModels(isOwner = false, currentUserId = 99L).single() assertEquals(2L, item.reply?.fanTalkId) assertEquals("first reply", item.reply?.content) @@ -65,7 +58,7 @@ class CreatorChannelFanTalkMapperTest { @Test fun `내가 쓴 글이면 수정과 삭제 owner more action이다`() { val item = listOf(fanTalk(writerId = 10L)) - .toFanTalkUiModels(relativeTimeTextFormatter, isOwner = false, currentUserId = 10L) + .toFanTalkUiModels(isOwner = false, currentUserId = 10L) .single() val action = item.rightAction as CreatorChannelFanTalkRightAction.OwnerMore @@ -76,7 +69,7 @@ class CreatorChannelFanTalkMapperTest { @Test fun `내 채널의 타인 글이면 삭제만 가능한 owner more action이다`() { val item = listOf(fanTalk(writerId = 11L)) - .toFanTalkUiModels(relativeTimeTextFormatter, isOwner = true, currentUserId = 10L) + .toFanTalkUiModels(isOwner = true, currentUserId = 10L) .single() val action = item.rightAction as CreatorChannelFanTalkRightAction.OwnerMore @@ -87,7 +80,7 @@ class CreatorChannelFanTalkMapperTest { @Test fun `내가 쓴 글도 아니고 내 채널도 아니면 신고 action이다`() { val item = listOf(fanTalk(writerId = 11L)) - .toFanTalkUiModels(relativeTimeTextFormatter, isOwner = false, currentUserId = 10L) + .toFanTalkUiModels(isOwner = false, currentUserId = 10L) .single() assertTrue(item.rightAction is CreatorChannelFanTalkRightAction.Report) @@ -96,7 +89,7 @@ class CreatorChannelFanTalkMapperTest { @Test fun `원글과 답글 content가 빈 문자열이어도 item을 유지한다`() { val item = listOf(fanTalk(content = "", creatorReplies = listOf(reply(content = "")))) - .toFanTalkUiModels(relativeTimeTextFormatter, isOwner = false, currentUserId = 99L) + .toFanTalkUiModels(isOwner = false, currentUserId = 99L) .single() assertEquals("", item.content) @@ -123,13 +116,14 @@ class CreatorChannelFanTalkMapperTest { private fun reply( fanTalkId: Long = 2L, writerId: Long = 20L, - content: String = "reply" + content: String = "reply", + createdAtUtc: String = "2026-06-21T00:00:00Z" ) = CreatorChannelFanTalkReplyResponse( fanTalkId = fanTalkId, writerId = writerId, writerNickname = "creator", writerProfileImageUrl = "creator.png", content = content, - createdAtUtc = "2026-06-21T00:00:00Z" + createdAtUtc = createdAtUtc ) } diff --git a/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/CreatorChannelFanTalkPaginationTest.kt b/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/CreatorChannelFanTalkPaginationTest.kt index 265bc3a5..5fbdbee8 100644 --- a/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/CreatorChannelFanTalkPaginationTest.kt +++ b/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/CreatorChannelFanTalkPaginationTest.kt @@ -12,7 +12,6 @@ import io.reactivex.rxjava3.plugins.RxJavaPlugins import io.reactivex.rxjava3.schedulers.Schedulers import io.reactivex.rxjava3.subjects.SingleSubject import kr.co.vividnext.sodalive.common.ApiResponse -import kr.co.vividnext.sodalive.common.AndroidUtcRelativeTimeTextFormatter import kr.co.vividnext.sodalive.common.SharedPreferenceManager import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelRepository import kr.co.vividnext.sodalive.v2.creator.channel.fantalk.data.CreatorChannelFanTalkResponse @@ -46,7 +45,7 @@ class CreatorChannelFanTalkPaginationTest { SharedPreferenceManager.token = "test-token" SharedPreferenceManager.userId = 10L repository = org.mockito.kotlin.mock() - viewModel = CreatorChannelFanTalkViewModel(repository, AndroidUtcRelativeTimeTextFormatter(context)) + viewModel = CreatorChannelFanTalkViewModel(repository) } @After diff --git a/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/CreatorChannelFanTalkViewModelTest.kt b/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/CreatorChannelFanTalkViewModelTest.kt index 9aeb012b..85767e9d 100644 --- a/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/CreatorChannelFanTalkViewModelTest.kt +++ b/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/CreatorChannelFanTalkViewModelTest.kt @@ -11,7 +11,6 @@ import io.reactivex.rxjava3.core.Single import io.reactivex.rxjava3.plugins.RxJavaPlugins import io.reactivex.rxjava3.schedulers.Schedulers import kr.co.vividnext.sodalive.common.ApiResponse -import kr.co.vividnext.sodalive.common.AndroidUtcRelativeTimeTextFormatter import kr.co.vividnext.sodalive.common.SharedPreferenceManager import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelRepository import kr.co.vividnext.sodalive.v2.creator.channel.fantalk.data.CreatorChannelFanTalkResponse @@ -45,7 +44,7 @@ class CreatorChannelFanTalkViewModelTest { SharedPreferenceManager.token = "test-token" SharedPreferenceManager.userId = 10L repository = org.mockito.kotlin.mock() - viewModel = CreatorChannelFanTalkViewModel(repository, AndroidUtcRelativeTimeTextFormatter(context)) + viewModel = CreatorChannelFanTalkViewModel(repository) } @After diff --git a/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/detail/CreatorChannelFanTalkDetailUiContractTest.kt b/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/detail/CreatorChannelFanTalkDetailUiContractTest.kt index ff461fc5..ac08389d 100644 --- a/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/detail/CreatorChannelFanTalkDetailUiContractTest.kt +++ b/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/detail/CreatorChannelFanTalkDetailUiContractTest.kt @@ -1,12 +1,23 @@ package kr.co.vividnext.sodalive.v2.creator.channel.fantalk.detail +import android.app.Application +import android.content.Context +import android.content.res.Configuration +import androidx.test.core.app.ApplicationProvider import kr.co.vividnext.sodalive.R +import kr.co.vividnext.sodalive.common.formatUtcRelativeTimeText import org.junit.Assert.assertFalse import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config import java.io.File +import java.util.Locale +@RunWith(RobolectricTestRunner::class) +@Config(sdk = [28], application = Application::class) class CreatorChannelFanTalkDetailUiContractTest { @Test @@ -50,6 +61,33 @@ class CreatorChannelFanTalkDetailUiContractTest { assertTrue(source.contains("R.drawable.ic_new_arrow_up_gray")) } + @Test + fun `영어와 일본어 상세 bind는 원글과 답글 raw UTC를 현재 View locale 상대 시간으로 표시한다`() { + val source = projectFile( + "app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/detail/CreatorChannelFanTalkDetailActivity.kt" + ).readText() + assertTrue( + Regex( + "tvCreatorChannelFantalkDetailTime\\.text\\s*=\\s*" + + "formatUtcRelativeTimeText\\(tvCreatorChannelFantalkDetailTime\\.context, item\\.createdAtUtc\\)" + ).containsMatchIn(source) + ) + assertTrue( + Regex( + "tvCreatorChannelFantalkDetailReplyTime\\.text\\s*=\\s*" + + "formatUtcRelativeTimeText\\(tvCreatorChannelFantalkDetailReplyTime\\.context, reply\\.createdAtUtc\\)" + ).containsMatchIn(source) + ) + + listOf(Locale.ENGLISH, Locale.JAPANESE).forEach { locale -> + val context = localizedContext(locale) + assertEquals( + context.getString(R.string.character_comment_time_just_now), + formatUtcRelativeTimeText(context, System.currentTimeMillis().toString()) + ) + } + } + @Test fun `상세 layout은 커뮤니티 답글 구조와 FanTalk 답글 card 토큰을 사용한다`() { val layout = projectFile("app/src/main/res/layout/activity_creator_channel_fantalk_detail.xml").readText() @@ -113,7 +151,7 @@ class CreatorChannelFanTalkDetailUiContractTest { "CreatorChannelFanTalkDetailViewModel" ) ) - assertTrue(appDi.contains("viewModel { CreatorChannelFanTalkDetailViewModel(get(), get()) }")) + assertTrue(appDi.contains("viewModel { CreatorChannelFanTalkDetailViewModel(get()) }")) assertTrue(viewModel.contains("fun cancelReplyEdit()")) assertTrue(viewModel.contains("fun consumeReplyChangedEvent()")) } @@ -153,6 +191,13 @@ class CreatorChannelFanTalkDetailUiContractTest { private fun projectFile(path: String): File = File(projectRoot(), path) + private fun localizedContext(locale: Locale): Context { + val context = ApplicationProvider.getApplicationContext() + val configuration = Configuration(context.resources.configuration) + configuration.setLocale(locale) + return context.createConfigurationContext(configuration) + } + private fun projectRoot(): File { return generateSequence(File(System.getProperty("user.dir") ?: ".").absoluteFile) { it.parentFile } .first { File(it, "settings.gradle").exists() } diff --git a/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/detail/CreatorChannelFanTalkDetailViewModelTest.kt b/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/detail/CreatorChannelFanTalkDetailViewModelTest.kt index 6c035f24..1c68fa0b 100644 --- a/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/detail/CreatorChannelFanTalkDetailViewModelTest.kt +++ b/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/detail/CreatorChannelFanTalkDetailViewModelTest.kt @@ -15,7 +15,6 @@ import kr.co.vividnext.sodalive.common.ApiResponse import kr.co.vividnext.sodalive.common.SharedPreferenceManager import kr.co.vividnext.sodalive.common.SodaLiveApplicationHolder import kr.co.vividnext.sodalive.common.ToastMessage -import kr.co.vividnext.sodalive.common.UtcRelativeTimeTextFormatter import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelRepository import kr.co.vividnext.sodalive.v2.creator.channel.fantalk.data.CreatorChannelFanTalkReplyResponse import kr.co.vividnext.sodalive.v2.creator.channel.fantalk.model.CreatorChannelFanTalkReplyUiModel @@ -52,10 +51,7 @@ class CreatorChannelFanTalkDetailViewModelTest { SharedPreferenceManager.init(context) SharedPreferenceManager.token = "test-token" repository = org.mockito.kotlin.mock() - viewModel = CreatorChannelFanTalkDetailViewModel( - repository, - UtcRelativeTimeTextFormatter { value -> "relative:$value" } - ) + viewModel = CreatorChannelFanTalkDetailViewModel(repository) } @After @@ -109,7 +105,7 @@ class CreatorChannelFanTalkDetailViewModelTest { assertEquals("server-creator", state.reply?.writerNickname) assertEquals("server-creator.png", state.reply?.writerProfileImageUrl) assertEquals("새 답글", state.reply?.content) - assertEquals("relative:2026-07-09T01:02:03Z", state.reply?.createdAtText) + assertEquals("2026-07-09T01:02:03Z", state.reply?.createdAtUtc) assertEquals(null, viewModel.toastLiveData.requireValue()?.consume()) assertTrue(viewModel.replyChangedEventLiveData.requireValue() == true) } @@ -292,7 +288,7 @@ class CreatorChannelFanTalkDetailViewModelTest { writerNickname = "fan", writerProfileImageUrl = "fan.png", content = "응원 원문", - createdAtText = "방금 전", + createdAtUtc = "2026-07-09T00:00:00Z", reply = reply, rightAction = CreatorChannelFanTalkRightAction.OwnerMore(showEdit = false, showDelete = true) ) @@ -303,7 +299,7 @@ class CreatorChannelFanTalkDetailViewModelTest { writerNickname = "creator", writerProfileImageUrl = "creator.png", content = content, - createdAtText = "방금 전" + createdAtUtc = "2026-07-09T00:01:00Z" ) private fun LiveData.requireContent(): CreatorChannelFanTalkDetailUiState.Content { diff --git a/docs/20260826_크리에이터_채널_표시와_상대_시간_다국어_정리/reviews/phase1-creator-channel-display-contract-review.md b/docs/20260826_크리에이터_채널_표시와_상대_시간_다국어_정리/reviews/phase1-creator-channel-display-contract-review.md new file mode 100644 index 00000000..14308b3f --- /dev/null +++ b/docs/20260826_크리에이터_채널_표시와_상대_시간_다국어_정리/reviews/phase1-creator-channel-display-contract-review.md @@ -0,0 +1,97 @@ +# Phase 1 크리에이터 채널 표시 계약 리뷰 + +## 1. 리뷰 정보 + +| 항목 | 내용 | +|---|---| +| 리뷰 대상 | Phase 1 / `P1-T1`~`P1-GATE` | +| 기준 commit 또는 working tree | 현재 working tree | +| 리뷰 일자 | 2026-08-26 | +| 리뷰어 | Sisyphus / Oracle | +| 기준 문서 | `prd.md`, `plan-task.md` | +| 리뷰 상태 | 판정 완료 | + +## 2. 리뷰 목적과 범위 + +### 목적 + +- FanTalk report locale, empty count, Donation badge 숫자-only 계약이 PRD와 일치하는지 확인한다. +- 완료 체크박스와 실제 코드·test·검증 기록이 일치하는지 확인한다. + +### 포함 범위 + +- 코드: FanTalk adapter, Donation card binder, Donation item layout, ko/en/ja strings +- 테스트: FanTalk locale/action/ViewModel, Donation layout/action/mapper/ViewModel +- 문서: PRD Phase 1 요구사항, `plan-task.md` Phase 1 +- 수동 검증: 사용자 직접 확인한 FanTalk report 문구 변경 + +### 제외 범위 + +- 상대 시간 통합 Phase 2 +- 계측 test, 기기·에뮬레이터 UI 조작, 스크린샷, 시각 QA + +## 3. 판정 기준 + +| 심각도 | 기준 | +|---|---| +| Blocker | 완료 판정을 무효화하는 요구사항 위반 | +| High | 주요 기능 회귀 | +| Medium | 제한 조건의 표시 오류 | +| Low | 문서·유지보수성 문제 | + +## 4. 검토한 근거 + +### 문서와 코드 + +- 요구사항: `FAN-001`, `FAN-002`, `DON-001`, `DON-002`, `DON-003`, `LOC-001`, `LOC-002` +- 계획: `P1-T1`, `P1-T2`, `P1-T3`, `P1-GATE` +- 코드: `CreatorChannelFanTalkAdapter.kt`, `CreatorChannelDonationCardBinder.kt`, `item_creator_channel_donation.xml`, `values*/strings.xml` +- 테스트: `CreatorChannelFanTalkAdapterLocaleTest`, `CreatorChannelFanTalkActionTest`, `CreatorChannelFanTalkViewModelTest`, `CreatorChannelDonationFragmentLayoutTest`, `CreatorChannelDonationActionTest`, `CreatorChannelDonationMapperTest`, `CreatorChannelDonationViewModelTest` + +### 실행 환경 + +```text +OS: macOS / darwin +검증 방식: Gradle local unit test, resource merge, Kotlin compile, ktlint, git diff check +제외: androidTest, device/emulator, screenshot, visual QA +``` + +### 실행한 검증 + +| 명령 또는 검증 | 결과 | 핵심 증거 | +|---|---|---| +| `CreatorChannelFanTalkAdapterLocaleTest` RED | 성공 | `expected:<[Report]> but was:<[신고]>` | +| `CreatorChannelDonationFragmentLayoutTest` RED | 성공 | `expected:<1,000[]> but was:<1,000[캔]>` | +| FanTalk/Donation focused tests | 성공 | `BUILD SUCCESSFUL` | +| Phase 1 Gate 6개 명령 | 성공 | FanTalk.*, Donation.*, resource merge, compile, ktlint, diff check PASS | +| 사용자 수동 확인 | 성공 | 신고 버튼 문구 변경 확인 | + +## 5. 발견 사항 요약 + +확정 발견 사항 없음. + +## 6. 발견 사항 상세 + +확정 발견 사항 없음. FanTalk report는 현재 `root.context` locale로 재바인딩되고, Donation badge는 숫자만 표시하며 fallback message는 유지된다. Empty count 계약은 production 변경 없이 기존 ViewModel test와 Fragment source 대조로 확인됐다. + +## 7. 확정 항목의 plan·goal 전환 + +전환 항목 없음. + +## 8. 리뷰 종료 판정 + +| 판정 항목 | 결과 | 근거 | +|---|---|---| +| 리뷰 범위 전체 확인 | 충족 | Phase 1 code/test/docs 검토 | +| 후보 항목 판정 완료 | 충족 | 확정 발견 사항 없음 | +| 확정 항목 plan 반영 | 해당 없음 | 전환 항목 없음 | +| 보류 항목의 담당·재개 조건 기록 | 해당 없음 | 보류 항목 없음 | +| 검증 명령과 결과 기록 | 충족 | `plan-task.md` Task별 기록과 Verification Log | + +**최종 결론:** 확정 발견 사항 없음 + +**남은 항목:** 없음 + +## 9. 수정 후 검증 기록 + +수정 항목 없음. diff --git a/docs/20260826_크리에이터_채널_표시와_상대_시간_다국어_정리/reviews/phase2-relative-time-unification-review.md b/docs/20260826_크리에이터_채널_표시와_상대_시간_다국어_정리/reviews/phase2-relative-time-unification-review.md new file mode 100644 index 00000000..99ecbbac --- /dev/null +++ b/docs/20260826_크리에이터_채널_표시와_상대_시간_다국어_정리/reviews/phase2-relative-time-unification-review.md @@ -0,0 +1,103 @@ +# Phase 2 상대 시간 통합 리뷰 + +## 1. 리뷰 정보 + +| 항목 | 내용 | +|---|---| +| 리뷰 대상 | Phase 2 / `P2-T1`~`P2-GATE` | +| 기준 commit 또는 working tree | 현재 working tree | +| 리뷰 일자 | 2026-08-26 | +| 리뷰어 | Sisyphus / Oracle | +| 기준 문서 | `prd.md`, `plan-task.md` | +| 리뷰 상태 | 판정 완료 | + +## 2. 리뷰 목적과 범위 + +### 목적 + +- `v2` 크리에이터 채널 상대 시간이 raw UTC와 표시 View/Activity Context의 공통 formatter 경로로 통일됐는지 확인한다. +- 완료 체크박스와 실제 코드·test·검증 기록이 일치하는지 확인한다. + +### 포함 범위 + +- 코드: FanTalk, Community, Donation, Home 표시부와 `AppDI.kt`의 `v2` ViewModel 등록 +- 테스트: FanTalk/Community/Donation mapper, ViewModel, adapter/activity locale, pagination/action 회귀, `RelativeTimeFormatterTest` +- 문서: PRD Phase 2 요구사항, `plan-task.md` Phase 2 +- 수동 검증: 없음 + +### 제외 범위 + +- `RelativeTimeFormatter.kt` 구현 변경 +- 레거시 화면 상대 시간 호출부 +- 계측 test, 기기·에뮬레이터 UI 조작, 스크린샷, 시각 QA + +## 3. 판정 기준 + +| 심각도 | 기준 | +|---|---| +| Blocker | 완료 판정을 무효화하는 요구사항 위반 | +| High | 주요 기능 회귀 또는 DI/navigation 오류 | +| Medium | 특정 화면의 locale 표시 오류 | +| Low | 문서·유지보수성 문제 | + +## 4. 검토한 근거 + +### 문서와 코드 + +- 요구사항: `TIME-001`~`TIME-004`, `LOC-001`, `LOC-002` +- 계획: `P2-T1`, `P2-T2`, `P2-T3`, `P2-GATE` +- 코드: FanTalk/Community/Donation UI models, mappers, ViewModels, adapters, Activities, `AppDI.kt`, Home section adapter +- 테스트: FanTalk.*, Community.*, Donation.*, `CreatorChannelHomeMapperTest`, `RelativeTimeFormatterTest` + +### 실행 환경 + +```text +OS: macOS / darwin +검증 방식: Gradle local unit test, resource merge, Kotlin compile, ktlint, git diff check, rg inventory +제외: androidTest, device/emulator, screenshot, visual QA +``` + +### 실행한 검증 + +| 명령 또는 검증 | 결과 | 핵심 증거 | +|---|---|---| +| FanTalk RED | 성공 | `createdAtUtc`/생성자 compile 실패 | +| Community RED | 성공 | `relativeTimeTextFormatter`, `createdAtUtc`, `createdAtText` compile 실패 | +| Donation RED | 성공 | old mapper 인자와 `createdAtUtc` compile 실패 | +| `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.common.RelativeTimeFormatterTest" --tests "kr.co.vividnext.sodalive.v2.creator.channel.*"` | 성공 | 최종 단독 재실행 `BUILD SUCCESSFUL` | +| `./gradlew :app:mergeDebugResources` | 성공 | `BUILD SUCCESSFUL` | +| `./gradlew :app:compileDebugKotlin` | 성공 | `BUILD SUCCESSFUL` | +| `./gradlew :app:ktlintCheck` | 성공 | line length 수정 후 `BUILD SUCCESSFUL` | +| `git diff --check` | 성공 | 출력 없음 | +| `rg -n "UtcRelativeTimeTextFormatter|relativeTimeTextFormatter\.format" app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel` | 성공 | 출력 없음 | +| `rg -n "formatUtcRelativeTimeText" app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel` | 성공 | FanTalk, Community, Donation, Home 표시부 포함 | + +## 5. 발견 사항 요약 + +확정 발견 사항 없음. + +## 6. 발견 사항 상세 + +확정 발견 사항 없음. FanTalk·Community·Donation presentation data는 raw UTC를 보존하고, 각 adapter/Activity가 현재 View Context로 `formatUtcRelativeTimeText()`를 호출한다. Home section의 기존 bind-time formatter 호출은 유지됐다. `RelativeTimeFormatter.kt`와 레거시 호출부는 수정하지 않았다. + +## 7. 확정 항목의 plan·goal 전환 + +전환 항목 없음. + +## 8. 리뷰 종료 판정 + +| 판정 항목 | 결과 | 근거 | +|---|---|---| +| 리뷰 범위 전체 확인 | 충족 | Phase 2 code/test/docs 검토 | +| 후보 항목 판정 완료 | 충족 | 확정 발견 사항 없음 | +| 확정 항목 plan 반영 | 해당 없음 | 전환 항목 없음 | +| 보류 항목의 담당·재개 조건 기록 | 해당 없음 | 보류 항목 없음 | +| 검증 명령과 결과 기록 | 충족 | `plan-task.md` Task별 기록과 Verification Log | + +**최종 결론:** 확정 발견 사항 없음 + +**남은 항목:** 없음 + +## 9. 수정 후 검증 기록 + +수정 항목 없음.