Compare commits
83 Commits
926e7eabd3
...
79a6ecdc0b
| Author | SHA1 | Date | |
|---|---|---|---|
| 79a6ecdc0b | |||
| 33f42e62b6 | |||
| 1b660fe542 | |||
| d0539b00b0 | |||
| e089ef6f8a | |||
| a8a70df304 | |||
| a207a8f840 | |||
| 6c85f81f98 | |||
| 6a88019851 | |||
| 379263ec87 | |||
| f3d610633c | |||
| 335db22680 | |||
| b71bb43dd6 | |||
| dace1416c5 | |||
| 1baf3ac7e0 | |||
| f3a4578a66 | |||
| b745c3d675 | |||
| f993d343a9 | |||
| 1ba7846f4c | |||
| da26b65a73 | |||
| 04dcb3f09f | |||
| 6916459c9e | |||
| 643fb9fa8e | |||
| f9160ccb02 | |||
| e2b166de11 | |||
| 89f3511864 | |||
| 3271c9e142 | |||
| 1f1069b05d | |||
| a325a1bbe9 | |||
| 255e6dc67d | |||
| b2685959ad | |||
| 536effab40 | |||
| 81537b1e96 | |||
| e691f78e2e | |||
| 6125b9f7d1 | |||
| d10373d265 | |||
| 5c1eb6204a | |||
| c3884ea917 | |||
| bc3fa9546b | |||
| ea66b6da1e | |||
| c9e47214ea | |||
| c04d603ac0 | |||
| 9e524d626a | |||
| e0dc0c2cb7 | |||
| cbd4f04f1b | |||
| eeb777fa01 | |||
| 9272db913c | |||
| ff4c0104c5 | |||
| 31ce0629d2 | |||
| 6ab7878221 | |||
| ddc18386fb | |||
| 809ed5ce23 | |||
| 8e086c4888 | |||
| bc6443e5bd | |||
| 8d205690b3 | |||
| 25af312d91 | |||
| b020b8096b | |||
| 87f6bec138 | |||
| 34266abd90 | |||
| e8e8a63d6d | |||
| d61867af4e | |||
| daeb0f75a2 | |||
| 6c497ed1d2 | |||
| 7b975ec54d | |||
| e3d81792bf | |||
| 5b9b7eafa7 | |||
| 5489c86cdc | |||
| b1f605fda4 | |||
| f9030ffd13 | |||
| f74f5a534d | |||
| 3f3bacebd9 | |||
| 2041036530 | |||
| 81a2b541a0 | |||
| e6cb94179b | |||
| 658b4f0a9d | |||
| 05cbbdfddc | |||
| 3c66ea986f | |||
| d1a0cf27a7 | |||
| 1aef4b1d89 | |||
| c745481ab2 | |||
| 7e281a15a5 | |||
| c5e94bbc92 | |||
| 61fe8a9fee |
@@ -114,6 +114,18 @@
|
|||||||
<activity android:name=".v2.main.MainV2Activity" />
|
<activity android:name=".v2.main.MainV2Activity" />
|
||||||
<activity android:name=".v2.main.content.overview.ContentOverviewActivity" />
|
<activity android:name=".v2.main.content.overview.ContentOverviewActivity" />
|
||||||
<activity android:name=".v2.creator.channel.CreatorChannelActivity" />
|
<activity android:name=".v2.creator.channel.CreatorChannelActivity" />
|
||||||
|
<activity
|
||||||
|
android:name=".v2.creator.channel.community.detail.CreatorChannelCommunityDetailActivity"
|
||||||
|
android:windowSoftInputMode="stateAlwaysHidden|adjustResize" />
|
||||||
|
<activity
|
||||||
|
android:name=".v2.creator.channel.community.detail.reply.CreatorChannelCommunityReplyActivity"
|
||||||
|
android:windowSoftInputMode="stateAlwaysHidden|adjustResize" />
|
||||||
|
<activity
|
||||||
|
android:name=".v2.creator.channel.fantalk.write.CreatorChannelFanTalkWriteActivity"
|
||||||
|
android:windowSoftInputMode="stateVisible|adjustResize" />
|
||||||
|
<activity
|
||||||
|
android:name=".v2.creator.channel.fantalk.detail.CreatorChannelFanTalkDetailActivity"
|
||||||
|
android:windowSoftInputMode="stateAlwaysHidden|adjustResize" />
|
||||||
<activity android:name=".v2.live.onair.HomeOnAirLiveActivity" />
|
<activity android:name=".v2.live.onair.HomeOnAirLiveActivity" />
|
||||||
<activity
|
<activity
|
||||||
android:name=".v2.main.chat.dm.DmChatRoomActivity"
|
android:name=".v2.main.chat.dm.DmChatRoomActivity"
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
@file:Suppress("ktlint:package-name", "ktlint:standard:package-name")
|
||||||
|
|
||||||
package kr.co.vividnext.sodalive.audio_content
|
package kr.co.vividnext.sodalive.audio_content
|
||||||
|
|
||||||
import android.app.NotificationChannel
|
import android.app.NotificationChannel
|
||||||
@@ -471,9 +473,17 @@ class AudioContentPlayService :
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun updateNotification() {
|
private fun updateNotification() {
|
||||||
val intent = Intent(this, MainV2Activity::class.java)
|
val intent = Intent(this, MainV2Activity::class.java).apply {
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
|
addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
|
||||||
|
putExtra(
|
||||||
|
MainV2Activity.EXTRA_AUDIO_NOTIFICATION_ROUTE,
|
||||||
|
MainV2Activity.ROUTE_AUDIO_DETAIL
|
||||||
|
)
|
||||||
|
contentId?.let {
|
||||||
|
putExtra(Constants.EXTRA_AUDIO_CONTENT_ID, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
val pendingIntent = PendingIntent.getActivity(
|
val pendingIntent = PendingIntent.getActivity(
|
||||||
this,
|
this,
|
||||||
0,
|
0,
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
@file:Suppress("ktlint:package-name", "ktlint:standard:package-name")
|
||||||
|
|
||||||
package kr.co.vividnext.sodalive.audio_content.player
|
package kr.co.vividnext.sodalive.audio_content.player
|
||||||
|
|
||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
@@ -155,6 +157,10 @@ class AudioContentPlayerService : MediaSessionService() {
|
|||||||
private fun initMediaSession() {
|
private fun initMediaSession() {
|
||||||
val contextIntent = Intent(applicationContext, MainV2Activity::class.java).apply {
|
val contextIntent = Intent(applicationContext, MainV2Activity::class.java).apply {
|
||||||
flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
|
flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
|
||||||
|
putExtra(
|
||||||
|
MainV2Activity.EXTRA_AUDIO_NOTIFICATION_ROUTE,
|
||||||
|
MainV2Activity.ROUTE_AUDIO_PLAYER
|
||||||
|
)
|
||||||
}
|
}
|
||||||
val pendingIntent = PendingIntent.getActivity(
|
val pendingIntent = PendingIntent.getActivity(
|
||||||
applicationContext,
|
applicationContext,
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
package kr.co.vividnext.sodalive.audio_content.playlist.modify
|
package kr.co.vividnext.sodalive.audio_content.playlist.modify
|
||||||
|
|
||||||
|
import androidx.annotation.Keep
|
||||||
import com.google.gson.annotations.SerializedName
|
import com.google.gson.annotations.SerializedName
|
||||||
import kr.co.vividnext.sodalive.audio_content.playlist.create.PlaylistContentIdAndOrder
|
import kr.co.vividnext.sodalive.audio_content.playlist.create.PlaylistContentIdAndOrder
|
||||||
|
|
||||||
|
@Keep
|
||||||
data class UpdatePlaylistRequest(
|
data class UpdatePlaylistRequest(
|
||||||
@SerializedName("title") val title: String? = null,
|
@SerializedName("title") val title: String? = null,
|
||||||
@SerializedName("desc") val desc: String? = null,
|
@SerializedName("desc") val desc: String? = null,
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import kr.co.vividnext.sodalive.audio_content.series.GetSeriesListResponse
|
|||||||
import kr.co.vividnext.sodalive.audio_content.series.main.by_genre.GetSeriesGenreListResponse
|
import kr.co.vividnext.sodalive.audio_content.series.main.by_genre.GetSeriesGenreListResponse
|
||||||
import kr.co.vividnext.sodalive.audio_content.series.main.home.SeriesHomeResponse
|
import kr.co.vividnext.sodalive.audio_content.series.main.home.SeriesHomeResponse
|
||||||
import kr.co.vividnext.sodalive.common.ApiResponse
|
import kr.co.vividnext.sodalive.common.ApiResponse
|
||||||
import kr.co.vividnext.sodalive.home.SeriesPublishedDaysOfWeek
|
|
||||||
import retrofit2.http.GET
|
import retrofit2.http.GET
|
||||||
import retrofit2.http.Header
|
import retrofit2.http.Header
|
||||||
import retrofit2.http.Query
|
import retrofit2.http.Query
|
||||||
@@ -23,7 +22,7 @@ interface SeriesMainApi {
|
|||||||
|
|
||||||
@GET("/audio-content/series/main/day-of-week")
|
@GET("/audio-content/series/main/day-of-week")
|
||||||
fun getDayOfWeekSeriesList(
|
fun getDayOfWeekSeriesList(
|
||||||
@Query("dayOfWeek") dayOfWeek: SeriesPublishedDaysOfWeek,
|
@Query("dayOfWeek") dayOfWeek: String,
|
||||||
@Query("page") page: Int,
|
@Query("page") page: Int,
|
||||||
@Query("size") size: Int,
|
@Query("size") size: Int,
|
||||||
@Header("Authorization") authHeader: String
|
@Header("Authorization") authHeader: String
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ class SeriesMainRepository(
|
|||||||
size: Int,
|
size: Int,
|
||||||
token: String
|
token: String
|
||||||
) = api.getDayOfWeekSeriesList(
|
) = api.getDayOfWeekSeriesList(
|
||||||
dayOfWeek = dayOfWeek,
|
dayOfWeek = dayOfWeek.queryValue,
|
||||||
page = page - 1,
|
page = page - 1,
|
||||||
size = size,
|
size = size,
|
||||||
authHeader = token
|
authHeader = token
|
||||||
|
|||||||
@@ -1,17 +1,18 @@
|
|||||||
package kr.co.vividnext.sodalive.chat.original
|
package kr.co.vividnext.sodalive.chat.original
|
||||||
|
|
||||||
import androidx.annotation.Keep
|
import androidx.annotation.Keep
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
@Keep
|
@Keep
|
||||||
data class OriginalWorkListResponse(
|
data class OriginalWorkListResponse(
|
||||||
val totalCount: Long,
|
@SerializedName("totalCount") val totalCount: Long,
|
||||||
val content: List<OriginalWorkListItemResponse>
|
@SerializedName("content") val content: List<OriginalWorkListItemResponse>
|
||||||
)
|
)
|
||||||
|
|
||||||
@Keep
|
@Keep
|
||||||
data class OriginalWorkListItemResponse(
|
data class OriginalWorkListItemResponse(
|
||||||
val id: Long,
|
@SerializedName("id") val id: Long,
|
||||||
val imageUrl: String?,
|
@SerializedName("imageUrl") val imageUrl: String?,
|
||||||
val title: String,
|
@SerializedName("title") val title: String,
|
||||||
val contentType: String
|
@SerializedName("contentType") val contentType: String
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package kr.co.vividnext.sodalive.common
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import kr.co.vividnext.sodalive.R
|
import kr.co.vividnext.sodalive.R
|
||||||
import java.text.ParseException
|
import java.text.ParsePosition
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Calendar
|
import java.util.Calendar
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
@@ -10,8 +10,9 @@ import java.util.Locale
|
|||||||
import java.util.TimeZone
|
import java.util.TimeZone
|
||||||
|
|
||||||
fun formatUtcRelativeTimeText(context: Context, utcText: String?): String {
|
fun formatUtcRelativeTimeText(context: Context, utcText: String?): String {
|
||||||
val pastMillis = parseServerUtcToMillis(utcText)
|
val parsedTime = parseServerUtcToMillis(utcText)
|
||||||
?: return context.getString(R.string.character_comment_time_just_now)
|
?: return context.getString(R.string.character_comment_time_just_now)
|
||||||
|
val pastMillis = parsedTime.millis
|
||||||
|
|
||||||
val nowMillis = System.currentTimeMillis()
|
val nowMillis = System.currentTimeMillis()
|
||||||
var diff = nowMillis - pastMillis
|
var diff = nowMillis - pastMillis
|
||||||
@@ -21,6 +22,10 @@ fun formatUtcRelativeTimeText(context: Context, utcText: String?): String {
|
|||||||
val hour = 60 * minute
|
val hour = 60 * minute
|
||||||
val day = 24 * hour
|
val day = 24 * hour
|
||||||
|
|
||||||
|
if (parsedTime.isIsoText && diff < RECENT_ISO_TIME_GRACE_MILLIS) {
|
||||||
|
return context.getString(R.string.character_comment_time_just_now)
|
||||||
|
}
|
||||||
|
|
||||||
if (diff < minute) {
|
if (diff < minute) {
|
||||||
return context.getString(R.string.character_comment_time_just_now)
|
return context.getString(R.string.character_comment_time_just_now)
|
||||||
}
|
}
|
||||||
@@ -75,19 +80,25 @@ class AndroidUtcRelativeTimeTextFormatter(context: Context) : UtcRelativeTimeTex
|
|||||||
override fun format(utcText: String?): String = formatUtcRelativeTimeText(applicationContext, utcText)
|
override fun format(utcText: String?): String = formatUtcRelativeTimeText(applicationContext, utcText)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun parseServerUtcToMillis(utcText: String?): Long? {
|
private fun parseServerUtcToMillis(utcText: String?): ParsedServerTime? {
|
||||||
if (utcText.isNullOrBlank()) return null
|
if (utcText.isNullOrBlank()) return null
|
||||||
|
|
||||||
val value = utcText.trim()
|
val value = utcText.trim().normalizeIsoFraction()
|
||||||
if (value.all { it.isDigit() }) {
|
if (value.all { it.isDigit() }) {
|
||||||
return try {
|
return try {
|
||||||
value.toLong()
|
val number = value.toLong()
|
||||||
|
ParsedServerTime(
|
||||||
|
millis = if (value.length <= EPOCH_SECONDS_MAX_LENGTH) number * 1000 else number,
|
||||||
|
isIsoText = false
|
||||||
|
)
|
||||||
} catch (_: NumberFormatException) {
|
} catch (_: NumberFormatException) {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val patterns = listOf(
|
val patterns = listOf(
|
||||||
|
"yyyy-MM-dd'T'HH:mm:ss.SSSXXX",
|
||||||
|
"yyyy-MM-dd'T'HH:mm:ssXXX",
|
||||||
"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'",
|
"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'",
|
||||||
"yyyy-MM-dd'T'HH:mm:ss'Z'",
|
"yyyy-MM-dd'T'HH:mm:ss'Z'",
|
||||||
"yyyy-MM-dd'T'HH:mm:ss",
|
"yyyy-MM-dd'T'HH:mm:ss",
|
||||||
@@ -99,11 +110,26 @@ private fun parseServerUtcToMillis(utcText: String?): Long? {
|
|||||||
try {
|
try {
|
||||||
val dateFormat = SimpleDateFormat(pattern, Locale.US)
|
val dateFormat = SimpleDateFormat(pattern, Locale.US)
|
||||||
dateFormat.timeZone = TimeZone.getTimeZone("UTC")
|
dateFormat.timeZone = TimeZone.getTimeZone("UTC")
|
||||||
val parsed: Date? = dateFormat.parse(value)
|
val position = ParsePosition(0)
|
||||||
if (parsed != null) return parsed.time
|
val parsed: Date? = dateFormat.parse(value, position)
|
||||||
} catch (_: ParseException) {
|
if (parsed != null && position.index == value.length) {
|
||||||
|
return ParsedServerTime(millis = parsed.time, isIsoText = true)
|
||||||
|
}
|
||||||
|
} catch (_: RuntimeException) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun String.normalizeIsoFraction(): String {
|
||||||
|
return replace(Regex("""\.(\d{3})\d+"""), ".$1")
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class ParsedServerTime(
|
||||||
|
val millis: Long,
|
||||||
|
val isIsoText: Boolean
|
||||||
|
)
|
||||||
|
|
||||||
|
private const val EPOCH_SECONDS_MAX_LENGTH = 10
|
||||||
|
private const val RECENT_ISO_TIME_GRACE_MILLIS = 10 * 60_000L
|
||||||
|
|||||||
@@ -181,10 +181,14 @@ import kr.co.vividnext.sodalive.user.signup.SignUpViewModel
|
|||||||
import kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelHomeViewModel
|
import kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelHomeViewModel
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.audio.CreatorChannelAudioViewModel
|
import kr.co.vividnext.sodalive.v2.creator.channel.audio.CreatorChannelAudioViewModel
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.community.CreatorChannelCommunityViewModel
|
import kr.co.vividnext.sodalive.v2.creator.channel.community.CreatorChannelCommunityViewModel
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.community.detail.CreatorChannelCommunityDetailViewModel
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.community.detail.reply.CreatorChannelCommunityReplyViewModel
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelApi
|
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelApi
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelRepository
|
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelRepository
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.donation.CreatorChannelDonationViewModel
|
import kr.co.vividnext.sodalive.v2.creator.channel.donation.CreatorChannelDonationViewModel
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.fantalk.CreatorChannelFanTalkViewModel
|
import kr.co.vividnext.sodalive.v2.creator.channel.fantalk.CreatorChannelFanTalkViewModel
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.fantalk.detail.CreatorChannelFanTalkDetailViewModel
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.fantalk.write.CreatorChannelFanTalkWriteViewModel
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.live.CreatorChannelLiveViewModel
|
import kr.co.vividnext.sodalive.v2.creator.channel.live.CreatorChannelLiveViewModel
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.series.CreatorChannelSeriesViewModel
|
import kr.co.vividnext.sodalive.v2.creator.channel.series.CreatorChannelSeriesViewModel
|
||||||
import kr.co.vividnext.sodalive.v2.live.onair.HomeOnAirLiveViewModel
|
import kr.co.vividnext.sodalive.v2.live.onair.HomeOnAirLiveViewModel
|
||||||
@@ -452,7 +456,11 @@ class AppDI(private val context: Context, isDebugMode: Boolean) {
|
|||||||
viewModel { CreatorChannelAudioViewModel(get()) }
|
viewModel { CreatorChannelAudioViewModel(get()) }
|
||||||
viewModel { CreatorChannelSeriesViewModel(get()) }
|
viewModel { CreatorChannelSeriesViewModel(get()) }
|
||||||
viewModel { CreatorChannelCommunityViewModel(get(), get()) }
|
viewModel { CreatorChannelCommunityViewModel(get(), get()) }
|
||||||
|
viewModel { CreatorChannelCommunityDetailViewModel(get(), get(), get()) }
|
||||||
|
viewModel { CreatorChannelCommunityReplyViewModel(get(), get(), get()) }
|
||||||
viewModel { CreatorChannelFanTalkViewModel(get(), get()) }
|
viewModel { CreatorChannelFanTalkViewModel(get(), get()) }
|
||||||
|
viewModel { CreatorChannelFanTalkDetailViewModel(get(), get()) }
|
||||||
|
viewModel { CreatorChannelFanTalkWriteViewModel(get()) }
|
||||||
viewModel { CreatorChannelDonationViewModel(get(), get()) }
|
viewModel { CreatorChannelDonationViewModel(get(), get()) }
|
||||||
viewModel { PushNotificationListViewModel(get()) }
|
viewModel { PushNotificationListViewModel(get()) }
|
||||||
viewModel { CharacterTabViewModel(get()) }
|
viewModel { CharacterTabViewModel(get()) }
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import kr.co.vividnext.sodalive.explorer.profile.channel_donation.PostChannelDon
|
|||||||
import kr.co.vividnext.sodalive.explorer.profile.donation.GetDonationAllResponse
|
import kr.co.vividnext.sodalive.explorer.profile.donation.GetDonationAllResponse
|
||||||
import kr.co.vividnext.sodalive.explorer.profile.follow.GetFollowerListResponse
|
import kr.co.vividnext.sodalive.explorer.profile.follow.GetFollowerListResponse
|
||||||
import kr.co.vividnext.sodalive.live.room.detail.GetRoomDetailUser
|
import kr.co.vividnext.sodalive.live.room.detail.GetRoomDetailUser
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.fantalk.data.CreatorChannelFanTalkReplyResponse
|
||||||
import retrofit2.http.Body
|
import retrofit2.http.Body
|
||||||
import retrofit2.http.GET
|
import retrofit2.http.GET
|
||||||
import retrofit2.http.Header
|
import retrofit2.http.Header
|
||||||
@@ -73,13 +74,13 @@ interface ExplorerApi {
|
|||||||
fun writeCheers(
|
fun writeCheers(
|
||||||
@Body request: PostWriteCheersRequest,
|
@Body request: PostWriteCheersRequest,
|
||||||
@Header("Authorization") authHeader: String
|
@Header("Authorization") authHeader: String
|
||||||
): Single<ApiResponse<Any>>
|
): Single<ApiResponse<CreatorChannelFanTalkReplyResponse>>
|
||||||
|
|
||||||
@PUT("/explorer/profile/cheers")
|
@PUT("/explorer/profile/cheers")
|
||||||
fun modifyCheers(
|
fun modifyCheers(
|
||||||
@Body request: PutModifyCheersRequest,
|
@Body request: PutModifyCheersRequest,
|
||||||
@Header("Authorization") authHeader: String
|
@Header("Authorization") authHeader: String
|
||||||
): Single<ApiResponse<Any>>
|
): Single<ApiResponse<CreatorChannelFanTalkReplyResponse>>
|
||||||
|
|
||||||
@POST("/explorer/profile/channel-donation")
|
@POST("/explorer/profile/channel-donation")
|
||||||
fun postChannelDonation(
|
fun postChannelDonation(
|
||||||
|
|||||||
@@ -1,18 +1,26 @@
|
|||||||
package kr.co.vividnext.sodalive.home
|
package kr.co.vividnext.sodalive.home
|
||||||
|
|
||||||
|
import androidx.annotation.Keep
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 콘텐츠 랭킹 정렬 기준
|
* 콘텐츠 랭킹 정렬 기준
|
||||||
*/
|
*/
|
||||||
enum class ContentRankingSortType {
|
@Keep
|
||||||
|
enum class ContentRankingSortType(val queryValue: String) {
|
||||||
// 매출
|
// 매출
|
||||||
REVENUE,
|
@SerializedName("REVENUE")
|
||||||
|
REVENUE("REVENUE"),
|
||||||
|
|
||||||
// 판매량
|
// 판매량
|
||||||
SALES_COUNT,
|
@SerializedName("SALES_COUNT")
|
||||||
|
SALES_COUNT("SALES_COUNT"),
|
||||||
|
|
||||||
// 댓글 수
|
// 댓글 수
|
||||||
COMMENT_COUNT,
|
@SerializedName("COMMENT_COUNT")
|
||||||
|
COMMENT_COUNT("COMMENT_COUNT"),
|
||||||
|
|
||||||
// 좋아요 수
|
// 좋아요 수
|
||||||
LIKE_COUNT
|
@SerializedName("LIKE_COUNT")
|
||||||
|
LIKE_COUNT("LIKE_COUNT")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ interface HomeApi {
|
|||||||
|
|
||||||
@GET("/api/home/day-of-week-series")
|
@GET("/api/home/day-of-week-series")
|
||||||
fun getDayOfWeekSeriesList(
|
fun getDayOfWeekSeriesList(
|
||||||
@Query("dayOfWeek") dayOfWeek: SeriesPublishedDaysOfWeek,
|
@Query("dayOfWeek") dayOfWeek: String,
|
||||||
@Header("Authorization") authHeader: String
|
@Header("Authorization") authHeader: String
|
||||||
): Single<ApiResponse<List<GetSeriesListResponse.SeriesListItem>>>
|
): Single<ApiResponse<List<GetSeriesListResponse.SeriesListItem>>>
|
||||||
|
|
||||||
@@ -34,7 +34,7 @@ interface HomeApi {
|
|||||||
|
|
||||||
@GET("/api/home/content-ranking")
|
@GET("/api/home/content-ranking")
|
||||||
fun getContentRankingBySort(
|
fun getContentRankingBySort(
|
||||||
@Query("sort") sort: ContentRankingSortType,
|
@Query("sort") sort: String,
|
||||||
@Header("Authorization") authHeader: String
|
@Header("Authorization") authHeader: String
|
||||||
): Single<ApiResponse<List<GetAudioContentRankingItem>>>
|
): Single<ApiResponse<List<GetAudioContentRankingItem>>>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,9 +14,10 @@ class HomeRepository(private val api: HomeApi) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
fun getDayOfWeekSeriesList(
|
fun getDayOfWeekSeriesList(
|
||||||
dayOfWeek: SeriesPublishedDaysOfWeek, token: String
|
dayOfWeek: SeriesPublishedDaysOfWeek,
|
||||||
|
token: String
|
||||||
) = api.getDayOfWeekSeriesList(
|
) = api.getDayOfWeekSeriesList(
|
||||||
dayOfWeek = dayOfWeek,
|
dayOfWeek = dayOfWeek.queryValue,
|
||||||
authHeader = token
|
authHeader = token
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -28,7 +29,7 @@ class HomeRepository(private val api: HomeApi) {
|
|||||||
sortType: ContentRankingSortType,
|
sortType: ContentRankingSortType,
|
||||||
token: String
|
token: String
|
||||||
) = api.getContentRankingBySort(
|
) = api.getContentRankingBySort(
|
||||||
sort = sortType,
|
sort = sortType.queryValue,
|
||||||
authHeader = token
|
authHeader = token
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,31 @@
|
|||||||
package kr.co.vividnext.sodalive.home
|
package kr.co.vividnext.sodalive.home
|
||||||
|
|
||||||
enum class SeriesPublishedDaysOfWeek {
|
import androidx.annotation.Keep
|
||||||
SUN, MON, TUE, WED, THU, FRI, SAT, RANDOM
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
enum class SeriesPublishedDaysOfWeek(val queryValue: String) {
|
||||||
|
@SerializedName("SUN")
|
||||||
|
SUN("SUN"),
|
||||||
|
|
||||||
|
@SerializedName("MON")
|
||||||
|
MON("MON"),
|
||||||
|
|
||||||
|
@SerializedName("TUE")
|
||||||
|
TUE("TUE"),
|
||||||
|
|
||||||
|
@SerializedName("WED")
|
||||||
|
WED("WED"),
|
||||||
|
|
||||||
|
@SerializedName("THU")
|
||||||
|
THU("THU"),
|
||||||
|
|
||||||
|
@SerializedName("FRI")
|
||||||
|
FRI("FRI"),
|
||||||
|
|
||||||
|
@SerializedName("SAT")
|
||||||
|
SAT("SAT"),
|
||||||
|
|
||||||
|
@SerializedName("RANDOM")
|
||||||
|
RANDOM("RANDOM")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
package kr.co.vividnext.sodalive.mypage.auth
|
package kr.co.vividnext.sodalive.mypage.auth
|
||||||
|
|
||||||
data class AuthResponse(val gender: Int)
|
import androidx.annotation.Keep
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
data class AuthResponse(
|
||||||
|
@SerializedName("gender") val gender: Int
|
||||||
|
)
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ data class PayverseChargeResponse(
|
|||||||
@SerializedName("payloadJson") val payloadJson: String
|
@SerializedName("payloadJson") val payloadJson: String
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@Keep
|
||||||
data class PayverseVerifyRequest(
|
data class PayverseVerifyRequest(
|
||||||
@SerializedName("transactionId") val transactionId: String,
|
@SerializedName("transactionId") val transactionId: String,
|
||||||
@SerializedName("orderId") val orderId: String
|
@SerializedName("orderId") val orderId: String
|
||||||
|
|||||||
@@ -1,9 +1,22 @@
|
|||||||
package kr.co.vividnext.sodalive.v2.common.data
|
package kr.co.vividnext.sodalive.v2.common.data
|
||||||
|
|
||||||
enum class ContentSort {
|
import androidx.annotation.Keep
|
||||||
LATEST,
|
import com.google.gson.annotations.SerializedName
|
||||||
POPULAR,
|
|
||||||
OWNED,
|
@Keep
|
||||||
PRICE_HIGH,
|
enum class ContentSort(val queryValue: String) {
|
||||||
PRICE_LOW
|
@SerializedName("LATEST")
|
||||||
|
LATEST("LATEST"),
|
||||||
|
|
||||||
|
@SerializedName("POPULAR")
|
||||||
|
POPULAR("POPULAR"),
|
||||||
|
|
||||||
|
@SerializedName("OWNED")
|
||||||
|
OWNED("OWNED"),
|
||||||
|
|
||||||
|
@SerializedName("PRICE_HIGH")
|
||||||
|
PRICE_HIGH("PRICE_HIGH"),
|
||||||
|
|
||||||
|
@SerializedName("PRICE_LOW")
|
||||||
|
PRICE_LOW("PRICE_LOW")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,77 @@
|
|||||||
|
package kr.co.vividnext.sodalive.v2.components.modal
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.WindowManager
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.core.graphics.drawable.toDrawable
|
||||||
|
import kr.co.vividnext.sodalive.databinding.DialogV2ModalBinding
|
||||||
|
import kr.co.vividnext.sodalive.extensions.dpToPx
|
||||||
|
|
||||||
|
class V2ModalDialog(
|
||||||
|
activity: Activity,
|
||||||
|
layoutInflater: LayoutInflater,
|
||||||
|
title: String,
|
||||||
|
desc: String,
|
||||||
|
confirmButtonTitle: String,
|
||||||
|
confirmButtonClick: () -> Unit,
|
||||||
|
cancelButtonTitle: String = "",
|
||||||
|
cancelButtonClick: (() -> Unit)? = null
|
||||||
|
) {
|
||||||
|
|
||||||
|
private val alertDialog: AlertDialog
|
||||||
|
private var isButtonClicked = false
|
||||||
|
|
||||||
|
val dialogView = DialogV2ModalBinding.inflate(layoutInflater)
|
||||||
|
|
||||||
|
init {
|
||||||
|
val dialogBuilder = AlertDialog.Builder(activity)
|
||||||
|
dialogBuilder.setView(dialogView.root)
|
||||||
|
|
||||||
|
alertDialog = dialogBuilder.create()
|
||||||
|
alertDialog.setCancelable(false)
|
||||||
|
alertDialog.window?.setBackgroundDrawable(Color.TRANSPARENT.toDrawable())
|
||||||
|
|
||||||
|
dialogView.tvTitle.text = title
|
||||||
|
dialogView.tvDesc.text = desc
|
||||||
|
|
||||||
|
dialogView.tvCancel.text = cancelButtonTitle
|
||||||
|
dialogView.tvCancel.setOnClickListener {
|
||||||
|
dismissAndRun {
|
||||||
|
cancelButtonClick?.invoke()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dialogView.tvConfirm.text = confirmButtonTitle
|
||||||
|
dialogView.tvConfirm.setOnClickListener {
|
||||||
|
dismissAndRun(confirmButtonClick)
|
||||||
|
}
|
||||||
|
|
||||||
|
dialogView.tvCancel.visibility = if (cancelButtonTitle.isNotBlank()) {
|
||||||
|
View.VISIBLE
|
||||||
|
} else {
|
||||||
|
View.GONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun show(width: Int) {
|
||||||
|
alertDialog.show()
|
||||||
|
|
||||||
|
val lp = WindowManager.LayoutParams()
|
||||||
|
lp.copyFrom(alertDialog.window?.attributes)
|
||||||
|
lp.width = width - (26.7f.dpToPx()).toInt()
|
||||||
|
lp.height = WindowManager.LayoutParams.WRAP_CONTENT
|
||||||
|
|
||||||
|
alertDialog.window?.setBackgroundDrawable(Color.TRANSPARENT.toDrawable())
|
||||||
|
alertDialog.window?.attributes = lp
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun dismissAndRun(action: () -> Unit) {
|
||||||
|
if (isButtonClicked) return
|
||||||
|
isButtonClicked = true
|
||||||
|
alertDialog.dismiss()
|
||||||
|
action()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,14 +6,12 @@ import android.animation.ValueAnimator
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.view.Gravity
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.View.MeasureSpec
|
import android.view.View.MeasureSpec
|
||||||
import android.view.animation.Interpolator
|
import android.view.animation.Interpolator
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.appcompat.app.AlertDialog
|
|
||||||
import androidx.core.view.ViewCompat
|
import androidx.core.view.ViewCompat
|
||||||
import androidx.core.view.WindowCompat
|
import androidx.core.view.WindowCompat
|
||||||
import androidx.core.view.WindowInsetsCompat
|
import androidx.core.view.WindowInsetsCompat
|
||||||
@@ -31,7 +29,6 @@ import kr.co.vividnext.sodalive.audio_content.detail.AudioContentDetailActivity
|
|||||||
import kr.co.vividnext.sodalive.audio_content.series.detail.SeriesDetailActivity
|
import kr.co.vividnext.sodalive.audio_content.series.detail.SeriesDetailActivity
|
||||||
import kr.co.vividnext.sodalive.audio_content.upload.AudioContentUploadActivity
|
import kr.co.vividnext.sodalive.audio_content.upload.AudioContentUploadActivity
|
||||||
import kr.co.vividnext.sodalive.base.BaseActivity
|
import kr.co.vividnext.sodalive.base.BaseActivity
|
||||||
import kr.co.vividnext.sodalive.base.SodaDialog
|
|
||||||
import kr.co.vividnext.sodalive.chat.talk.room.ChatRoomActivity
|
import kr.co.vividnext.sodalive.chat.talk.room.ChatRoomActivity
|
||||||
import kr.co.vividnext.sodalive.common.Constants
|
import kr.co.vividnext.sodalive.common.Constants
|
||||||
import kr.co.vividnext.sodalive.common.LoadingDialog
|
import kr.co.vividnext.sodalive.common.LoadingDialog
|
||||||
@@ -53,18 +50,25 @@ import kr.co.vividnext.sodalive.mypage.MyPageViewModel
|
|||||||
import kr.co.vividnext.sodalive.mypage.auth.Auth
|
import kr.co.vividnext.sodalive.mypage.auth.Auth
|
||||||
import kr.co.vividnext.sodalive.mypage.auth.AuthVerifyRequest
|
import kr.co.vividnext.sodalive.mypage.auth.AuthVerifyRequest
|
||||||
import kr.co.vividnext.sodalive.mypage.auth.BootpayResponse
|
import kr.co.vividnext.sodalive.mypage.auth.BootpayResponse
|
||||||
import kr.co.vividnext.sodalive.report.ProfileReportDialog
|
|
||||||
import kr.co.vividnext.sodalive.report.UserReportDialog
|
import kr.co.vividnext.sodalive.report.UserReportDialog
|
||||||
import kr.co.vividnext.sodalive.settings.ContentSettingsActivity
|
import kr.co.vividnext.sodalive.settings.ContentSettingsActivity
|
||||||
|
import kr.co.vividnext.sodalive.splash.SplashActivity
|
||||||
|
import kr.co.vividnext.sodalive.user.login.LoginActivity
|
||||||
|
import kr.co.vividnext.sodalive.v2.components.modal.V2ModalDialog
|
||||||
import kr.co.vividnext.sodalive.v2.common.CreatorActivityType
|
import kr.co.vividnext.sodalive.v2.common.CreatorActivityType
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.audio.CreatorChannelAudioFragment
|
import kr.co.vividnext.sodalive.v2.creator.channel.audio.CreatorChannelAudioFragment
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.community.CreatorChannelCommunityFragment
|
import kr.co.vividnext.sodalive.v2.creator.channel.community.CreatorChannelCommunityFragment
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.community.detail.CreatorChannelCommunityDetailActivity
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.community.model.CreatorChannelCommunityPostUiModel
|
import kr.co.vividnext.sodalive.v2.creator.channel.community.model.CreatorChannelCommunityPostUiModel
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelAudioContentResponse
|
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelAudioContentResponse
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelLiveResponse
|
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelLiveResponse
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelScheduleResponse
|
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelScheduleResponse
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelSeriesResponse
|
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelSeriesResponse
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.fantalk.CreatorChannelFanTalkFragment
|
import kr.co.vividnext.sodalive.v2.creator.channel.fantalk.CreatorChannelFanTalkFragment
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.fantalk.detail.CreatorChannelFanTalkDetailActivity
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.fantalk.detail.toFanTalkDetailPayload
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.fantalk.model.CreatorChannelFanTalkUiModel
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.fantalk.write.CreatorChannelFanTalkWriteActivity
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.donation.CreatorChannelDonationFragment
|
import kr.co.vividnext.sodalive.v2.creator.channel.donation.CreatorChannelDonationFragment
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.live.CreatorChannelLiveFragment
|
import kr.co.vividnext.sodalive.v2.creator.channel.live.CreatorChannelLiveFragment
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.model.CreatorChannelHeaderUiModel
|
import kr.co.vividnext.sodalive.v2.creator.channel.model.CreatorChannelHeaderUiModel
|
||||||
@@ -74,8 +78,6 @@ import kr.co.vividnext.sodalive.v2.creator.channel.model.CreatorChannelTitleBarS
|
|||||||
import kr.co.vividnext.sodalive.v2.creator.channel.series.CreatorChannelSeriesFragment
|
import kr.co.vividnext.sodalive.v2.creator.channel.series.CreatorChannelSeriesFragment
|
||||||
import kr.co.vividnext.sodalive.v2.main.MainV2Activity
|
import kr.co.vividnext.sodalive.v2.main.MainV2Activity
|
||||||
import kr.co.vividnext.sodalive.v2.main.chat.dm.DmChatRoomActivity
|
import kr.co.vividnext.sodalive.v2.main.chat.dm.DmChatRoomActivity
|
||||||
import kr.co.vividnext.sodalive.splash.SplashActivity
|
|
||||||
import kr.co.vividnext.sodalive.user.login.LoginActivity
|
|
||||||
import okhttp3.MediaType.Companion.toMediaType
|
import okhttp3.MediaType.Companion.toMediaType
|
||||||
import okhttp3.RequestBody.Companion.toRequestBody
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
import org.koin.android.ext.android.inject
|
import org.koin.android.ext.android.inject
|
||||||
@@ -103,6 +105,7 @@ class CreatorChannelActivity :
|
|||||||
private var isOwnerFabExpanded: Boolean = false
|
private var isOwnerFabExpanded: Boolean = false
|
||||||
private var isOwnerFabAnimating: Boolean = false
|
private var isOwnerFabAnimating: Boolean = false
|
||||||
private var isDonationFloatingButtonVisible: Boolean = false
|
private var isDonationFloatingButtonVisible: Boolean = false
|
||||||
|
private var isFanTalkFixedPlusVisible: Boolean = false
|
||||||
private lateinit var loadingDialog: LoadingDialog
|
private lateinit var loadingDialog: LoadingDialog
|
||||||
private val baseTitleBarHeight: Int by lazy { 60.dpToPx().toInt() }
|
private val baseTitleBarHeight: Int by lazy { 60.dpToPx().toInt() }
|
||||||
private val liveCoordinator: CreatorChannelLiveCoordinator by lazy {
|
private val liveCoordinator: CreatorChannelLiveCoordinator by lazy {
|
||||||
@@ -130,6 +133,29 @@ class CreatorChannelActivity :
|
|||||||
refreshCreatorChannelCommunity()
|
refreshCreatorChannelCommunity()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
private val communityDetailLauncher = registerForActivityResult(
|
||||||
|
ActivityResultContracts.StartActivityForResult()
|
||||||
|
) { result ->
|
||||||
|
if (result.resultCode == RESULT_OK) {
|
||||||
|
refreshCreatorChannelCommunity()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private val fanTalkWriteLauncher = registerForActivityResult(
|
||||||
|
ActivityResultContracts.StartActivityForResult()
|
||||||
|
) { result ->
|
||||||
|
if (result.resultCode == RESULT_OK) {
|
||||||
|
refreshCreatorChannelFanTalk()
|
||||||
|
homeActionDelegate?.refreshHome()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private val fanTalkDetailLauncher = registerForActivityResult(
|
||||||
|
ActivityResultContracts.StartActivityForResult()
|
||||||
|
) { result ->
|
||||||
|
if (result.resultCode == RESULT_OK) {
|
||||||
|
refreshCreatorChannelFanTalk()
|
||||||
|
homeActionDelegate?.refreshHome()
|
||||||
|
}
|
||||||
|
}
|
||||||
private val liveRoomCreateLauncher = registerForActivityResult(
|
private val liveRoomCreateLauncher = registerForActivityResult(
|
||||||
ActivityResultContracts.StartActivityForResult()
|
ActivityResultContracts.StartActivityForResult()
|
||||||
) { result ->
|
) { result ->
|
||||||
@@ -192,6 +218,7 @@ class CreatorChannelActivity :
|
|||||||
binding.btnCreatorChannelDonationWrite.setOnClickListener {
|
binding.btnCreatorChannelDonationWrite.setOnClickListener {
|
||||||
findDonationFragment()?.onCreatorChannelDonationFloatingButtonClicked()
|
findDonationFragment()?.onCreatorChannelDonationFloatingButtonClicked()
|
||||||
}
|
}
|
||||||
|
binding.btnCreatorChannelFantalkFixedPlus.setOnClickListener { openFanTalkWrite() }
|
||||||
binding.btnCreatorChannelOwnerCta.setOnClickListener { onOwnerCtaClicked() }
|
binding.btnCreatorChannelOwnerCta.setOnClickListener { onOwnerCtaClicked() }
|
||||||
binding.tvChatButton.setOnClickListener {
|
binding.tvChatButton.setOnClickListener {
|
||||||
currentHeader?.characterId?.let { characterId -> homeActionDelegate?.createChatRoom(characterId) }
|
currentHeader?.characterId?.let { characterId -> homeActionDelegate?.createChatRoom(characterId) }
|
||||||
@@ -372,14 +399,18 @@ class CreatorChannelActivity :
|
|||||||
|
|
||||||
private fun showUserBlockDialog() {
|
private fun showUserBlockDialog() {
|
||||||
val nickname = currentHeader?.nickname.orEmpty()
|
val nickname = currentHeader?.nickname.orEmpty()
|
||||||
AlertDialog.Builder(this)
|
V2ModalDialog(
|
||||||
.setTitle(getString(R.string.screen_live_room_block_title))
|
activity = this,
|
||||||
.setMessage(getString(R.string.screen_live_room_block_message_user, nickname))
|
layoutInflater = layoutInflater,
|
||||||
.setPositiveButton(getString(R.string.screen_live_room_block_confirm)) { _, _ ->
|
title = getString(R.string.screen_live_room_block_title),
|
||||||
|
desc = getString(R.string.screen_live_room_block_message_user, nickname),
|
||||||
|
confirmButtonTitle = getString(R.string.screen_live_room_block_confirm),
|
||||||
|
confirmButtonClick = {
|
||||||
homeActionDelegate?.blockUser()
|
homeActionDelegate?.blockUser()
|
||||||
}
|
},
|
||||||
.setNegativeButton(getString(R.string.cancel)) { _, _ -> }
|
cancelButtonTitle = getString(R.string.cancel),
|
||||||
.show()
|
cancelButtonClick = {}
|
||||||
|
).show(screenWidth)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showUserReportDialog() {
|
private fun showUserReportDialog() {
|
||||||
@@ -389,9 +420,18 @@ class CreatorChannelActivity :
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun showProfileReportDialog() {
|
private fun showProfileReportDialog() {
|
||||||
ProfileReportDialog(this, layoutInflater) {
|
V2ModalDialog(
|
||||||
homeActionDelegate?.reportProfile()
|
activity = this,
|
||||||
}.show(resources.displayMetrics.widthPixels)
|
layoutInflater = layoutInflater,
|
||||||
|
title = getString(R.string.dialog_profile_report_title),
|
||||||
|
desc = getString(R.string.dialog_profile_report_notice),
|
||||||
|
confirmButtonTitle = getString(R.string.report_button),
|
||||||
|
confirmButtonClick = {
|
||||||
|
homeActionDelegate?.reportProfile()
|
||||||
|
},
|
||||||
|
cancelButtonTitle = getString(R.string.cancel),
|
||||||
|
cancelButtonClick = {}
|
||||||
|
).show(resources.displayMetrics.widthPixels)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupTabsAndPager() {
|
private fun setupTabsAndPager() {
|
||||||
@@ -410,6 +450,7 @@ class CreatorChannelActivity :
|
|||||||
}
|
}
|
||||||
updateOwnerFabVisibility()
|
updateOwnerFabVisibility()
|
||||||
updateDonationFloatingButtonVisibility()
|
updateDonationFloatingButtonVisibility()
|
||||||
|
updateFanTalkFixedPlusVisibility()
|
||||||
updateOwnerCtaVisibility()
|
updateOwnerCtaVisibility()
|
||||||
updateCreatorChannelTabViewportHeight()
|
updateCreatorChannelTabViewportHeight()
|
||||||
updateViewPagerHeight()
|
updateViewPagerHeight()
|
||||||
@@ -445,6 +486,7 @@ class CreatorChannelActivity :
|
|||||||
bindTitleBar(header)
|
bindTitleBar(header)
|
||||||
updateOwnerFabVisibility()
|
updateOwnerFabVisibility()
|
||||||
updateDonationFloatingButtonVisibility()
|
updateDonationFloatingButtonVisibility()
|
||||||
|
updateFanTalkFixedPlusVisibility()
|
||||||
updateOwnerCtaVisibility()
|
updateOwnerCtaVisibility()
|
||||||
if (binding.viewPager.currentItem == CreatorChannelTab.Audio.ordinal) {
|
if (binding.viewPager.currentItem == CreatorChannelTab.Audio.ordinal) {
|
||||||
binding.viewPager.post {
|
binding.viewPager.post {
|
||||||
@@ -504,6 +546,11 @@ class CreatorChannelActivity :
|
|||||||
updateViewPagerHeight()
|
updateViewPagerHeight()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onCreatorChannelHomeTabRequested(tab: CreatorChannelTab) {
|
||||||
|
selectCreatorChannelTab(tab)
|
||||||
|
scrollCreatorChannelToTop()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreatorChannelLiveContentChanged() {
|
override fun onCreatorChannelLiveContentChanged() {
|
||||||
updateCreatorChannelTabViewportHeight()
|
updateCreatorChannelTabViewportHeight()
|
||||||
updateViewPagerHeight {
|
updateViewPagerHeight {
|
||||||
@@ -545,12 +592,44 @@ class CreatorChannelActivity :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onCreatorChannelCommunityPostClicked(postId: Long) {
|
||||||
|
if (postId <= 0L) return
|
||||||
|
if (SharedPreferenceManager.token.isBlank()) {
|
||||||
|
showLoginActivity()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
communityDetailLauncher.launch(CreatorChannelCommunityDetailActivity.newIntent(this, postId))
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreatorChannelFanTalkContentChanged() {
|
override fun onCreatorChannelFanTalkContentChanged() {
|
||||||
updateViewPagerHeight {
|
updateViewPagerHeight {
|
||||||
postCheckCreatorChannelCurrentTabNeedsMore()
|
postCheckCreatorChannelCurrentTabNeedsMore()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onCreatorChannelFanTalkWriteClicked() {
|
||||||
|
openFanTalkWrite()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreatorChannelFanTalkItemClicked(item: CreatorChannelFanTalkUiModel) {
|
||||||
|
if (currentHeader?.isOwner != true) return
|
||||||
|
if (creatorId <= 0L || item.fanTalkId <= 0L) return
|
||||||
|
if (SharedPreferenceManager.token.isBlank()) {
|
||||||
|
showLoginActivity()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fanTalkDetailLauncher.launch(
|
||||||
|
CreatorChannelFanTalkDetailActivity.newIntent(this, creatorId, item.toFanTalkDetailPayload())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreatorChannelFanTalkFloatingButtonVisibilityChanged(isVisible: Boolean) {
|
||||||
|
isFanTalkFixedPlusVisible = isVisible
|
||||||
|
updateFanTalkFixedPlusVisibility()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreatorChannelDonationContentChanged() {
|
override fun onCreatorChannelDonationContentChanged() {
|
||||||
updateViewPagerHeight {
|
updateViewPagerHeight {
|
||||||
postCheckCreatorChannelCurrentTabNeedsMore()
|
postCheckCreatorChannelCurrentTabNeedsMore()
|
||||||
@@ -582,7 +661,7 @@ class CreatorChannelActivity :
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreatorChannelFanTalkDeleteClicked(fanTalkId: Long) {
|
override fun onCreatorChannelFanTalkDeleteClicked(fanTalkId: Long) {
|
||||||
SodaDialog(
|
V2ModalDialog(
|
||||||
activity = this,
|
activity = this,
|
||||||
layoutInflater = layoutInflater,
|
layoutInflater = layoutInflater,
|
||||||
title = getString(R.string.screen_user_profile_cheer_delete_title),
|
title = getString(R.string.screen_user_profile_cheer_delete_title),
|
||||||
@@ -618,7 +697,7 @@ class CreatorChannelActivity :
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun showCreatorChannelCommunityDeleteDialog(item: CreatorChannelCommunityPostUiModel) {
|
private fun showCreatorChannelCommunityDeleteDialog(item: CreatorChannelCommunityPostUiModel) {
|
||||||
SodaDialog(
|
V2ModalDialog(
|
||||||
activity = this,
|
activity = this,
|
||||||
layoutInflater = layoutInflater,
|
layoutInflater = layoutInflater,
|
||||||
title = getString(R.string.screen_creator_community_delete_title),
|
title = getString(R.string.screen_creator_community_delete_title),
|
||||||
@@ -822,6 +901,21 @@ class CreatorChannelActivity :
|
|||||||
binding.btnCreatorChannelDonationWrite.isVisible = shouldShowDonationFloatingButton
|
binding.btnCreatorChannelDonationWrite.isVisible = shouldShowDonationFloatingButton
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun updateFanTalkFixedPlusVisibility() {
|
||||||
|
val shouldShowFanTalkFixedPlus =
|
||||||
|
isFanTalkFixedPlusVisible && binding.viewPager.currentItem == CreatorChannelTab.FanTalk.ordinal
|
||||||
|
binding.btnCreatorChannelFantalkFixedPlus.isVisible = shouldShowFanTalkFixedPlus
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun openFanTalkWrite() {
|
||||||
|
if (creatorId <= 0L) return
|
||||||
|
if (SharedPreferenceManager.token.isBlank()) {
|
||||||
|
showLoginActivity()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fanTalkWriteLauncher.launch(CreatorChannelFanTalkWriteActivity.newIntent(this, creatorId))
|
||||||
|
}
|
||||||
|
|
||||||
private fun onOwnerFabCommunityClicked() {
|
private fun onOwnerFabCommunityClicked() {
|
||||||
collapseOwnerFab(animate = false)
|
collapseOwnerFab(animate = false)
|
||||||
communityWriteLauncher.launch(Intent(this, CreatorCommunityWriteActivity::class.java))
|
communityWriteLauncher.launch(Intent(this, CreatorCommunityWriteActivity::class.java))
|
||||||
@@ -940,7 +1034,7 @@ class CreatorChannelActivity :
|
|||||||
if (isAdult) {
|
if (isAdult) {
|
||||||
val isKoreanCountry = SharedPreferenceManager.countryCode.ifBlank { "KR" } == "KR"
|
val isKoreanCountry = SharedPreferenceManager.countryCode.ifBlank { "KR" } == "KR"
|
||||||
if (isKoreanCountry && !SharedPreferenceManager.isAuth) {
|
if (isKoreanCountry && !SharedPreferenceManager.isAuth) {
|
||||||
SodaDialog(
|
V2ModalDialog(
|
||||||
activity = this,
|
activity = this,
|
||||||
layoutInflater = layoutInflater,
|
layoutInflater = layoutInflater,
|
||||||
title = getString(R.string.auth_title),
|
title = getString(R.string.auth_title),
|
||||||
@@ -948,8 +1042,7 @@ class CreatorChannelActivity :
|
|||||||
confirmButtonTitle = getString(R.string.auth_go),
|
confirmButtonTitle = getString(R.string.auth_go),
|
||||||
confirmButtonClick = { startAuthFlow() },
|
confirmButtonClick = { startAuthFlow() },
|
||||||
cancelButtonTitle = getString(R.string.cancel),
|
cancelButtonTitle = getString(R.string.cancel),
|
||||||
cancelButtonClick = {},
|
cancelButtonClick = {}
|
||||||
descGravity = Gravity.CENTER
|
|
||||||
).show(screenWidth)
|
).show(screenWidth)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1096,6 +1189,14 @@ class CreatorChannelActivity :
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun selectCreatorChannelTab(tab: CreatorChannelTab) {
|
||||||
|
binding.viewPager.currentItem = tab.ordinal
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun scrollCreatorChannelToTop() {
|
||||||
|
binding.nestedScrollView.scrollTo(0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
binding.btnCreatorChannelOwnerCta.isEnabled = true
|
binding.btnCreatorChannelOwnerCta.isEnabled = true
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelScheduleRe
|
|||||||
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelSeriesResponse
|
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelSeriesResponse
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.model.CreatorChannelHeaderUiModel
|
import kr.co.vividnext.sodalive.v2.creator.channel.model.CreatorChannelHeaderUiModel
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.model.CreatorChannelHomeUiState
|
import kr.co.vividnext.sodalive.v2.creator.channel.model.CreatorChannelHomeUiState
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.model.CreatorChannelTab
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.ui.CreatorChannelHomeSectionAdapter
|
import kr.co.vividnext.sodalive.v2.creator.channel.ui.CreatorChannelHomeSectionAdapter
|
||||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||||
|
|
||||||
@@ -25,7 +26,9 @@ class CreatorChannelHomeFragment : BaseFragment<FragmentCreatorChannelHomeBindin
|
|||||||
onScheduleClick = ::onScheduleClicked,
|
onScheduleClick = ::onScheduleClicked,
|
||||||
onAudioContentClick = ::onAudioContentClicked,
|
onAudioContentClick = ::onAudioContentClicked,
|
||||||
onSeriesClick = ::onSeriesClicked,
|
onSeriesClick = ::onSeriesClicked,
|
||||||
onDonationClick = ::onDonationClicked
|
onDonationClick = ::onDonationClicked,
|
||||||
|
onSectionChevronClick = ::onSectionChevronClicked,
|
||||||
|
onCommunityClick = ::onCommunityClicked
|
||||||
)
|
)
|
||||||
private val creatorId: Long by lazy { arguments?.getLong(ARG_CREATOR_ID) ?: 0L }
|
private val creatorId: Long by lazy { arguments?.getLong(ARG_CREATOR_ID) ?: 0L }
|
||||||
private val host: Host
|
private val host: Host
|
||||||
@@ -123,6 +126,14 @@ class CreatorChannelHomeFragment : BaseFragment<FragmentCreatorChannelHomeBindin
|
|||||||
host.onCreatorChannelDonationClicked()
|
host.onCreatorChannelDonationClicked()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun onSectionChevronClicked(tab: CreatorChannelTab) {
|
||||||
|
host.onCreatorChannelHomeTabRequested(tab)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onCommunityClicked(postId: Long) {
|
||||||
|
host.onCreatorChannelCommunityPostClicked(postId)
|
||||||
|
}
|
||||||
|
|
||||||
private fun onCurrentLiveClicked(live: CreatorChannelLiveResponse) {
|
private fun onCurrentLiveClicked(live: CreatorChannelLiveResponse) {
|
||||||
host.onCreatorChannelCurrentLiveClicked(live)
|
host.onCreatorChannelCurrentLiveClicked(live)
|
||||||
}
|
}
|
||||||
@@ -137,6 +148,8 @@ class CreatorChannelHomeFragment : BaseFragment<FragmentCreatorChannelHomeBindin
|
|||||||
fun onCreatorChannelHomeActionDelegateReady(delegate: HomeActionDelegate?)
|
fun onCreatorChannelHomeActionDelegateReady(delegate: HomeActionDelegate?)
|
||||||
fun onCreatorChannelHomeContentChanged()
|
fun onCreatorChannelHomeContentChanged()
|
||||||
fun onCreatorChannelDonationClicked()
|
fun onCreatorChannelDonationClicked()
|
||||||
|
fun onCreatorChannelHomeTabRequested(tab: CreatorChannelTab)
|
||||||
|
fun onCreatorChannelCommunityPostClicked(postId: Long)
|
||||||
fun onCreatorChannelCurrentLiveClicked(live: CreatorChannelLiveResponse)
|
fun onCreatorChannelCurrentLiveClicked(live: CreatorChannelLiveResponse)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package kr.co.vividnext.sodalive.v2.creator.channel
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import androidx.fragment.app.FragmentActivity
|
||||||
import androidx.fragment.app.FragmentManager
|
import androidx.fragment.app.FragmentManager
|
||||||
import kr.co.vividnext.sodalive.R
|
import kr.co.vividnext.sodalive.R
|
||||||
import kr.co.vividnext.sodalive.audio_content.AudioContentPlayService
|
import kr.co.vividnext.sodalive.audio_content.AudioContentPlayService
|
||||||
@@ -27,7 +28,7 @@ import java.util.Locale
|
|||||||
import java.util.TimeZone
|
import java.util.TimeZone
|
||||||
|
|
||||||
class CreatorChannelLiveCoordinator(
|
class CreatorChannelLiveCoordinator(
|
||||||
private val activity: CreatorChannelActivity,
|
private val activity: FragmentActivity,
|
||||||
private val layoutInflater: LayoutInflater,
|
private val layoutInflater: LayoutInflater,
|
||||||
private val fragmentManager: FragmentManager,
|
private val fragmentManager: FragmentManager,
|
||||||
private val liveViewModel: LiveViewModel,
|
private val liveViewModel: LiveViewModel,
|
||||||
@@ -65,38 +66,64 @@ class CreatorChannelLiveCoordinator(
|
|||||||
}
|
}
|
||||||
|
|
||||||
liveViewModel.getRoomDetail(roomId) {
|
liveViewModel.getRoomDetail(roomId) {
|
||||||
if (!it.channelName.isNullOrBlank()) {
|
enterLiveRoom(roomId, it, onEnterRoomSuccess)
|
||||||
if (it.manager.id == SharedPreferenceManager.userId) {
|
}
|
||||||
liveViewModel.enterRoom(roomId, onEnterRoomSuccess)
|
}
|
||||||
} else if (it.price == 0 || it.isPaid) {
|
|
||||||
if (it.isPrivateRoom) {
|
fun enterLiveRoom(roomId: Long, roomDetail: GetRoomDetailResponse) {
|
||||||
LiveRoomPasswordDialog(
|
activity.startService(
|
||||||
activity = activity,
|
Intent(activity, AudioContentPlayService::class.java).apply {
|
||||||
layoutInflater = layoutInflater,
|
action = AudioContentPlayService.MusicAction.STOP.name
|
||||||
can = 0,
|
}
|
||||||
confirmButtonClick = { password ->
|
)
|
||||||
liveViewModel.enterRoom(
|
activity.startService(
|
||||||
roomId = roomId,
|
Intent(activity, AudioContentPlayerService::class.java).apply {
|
||||||
onSuccess = onEnterRoomSuccess,
|
action = "STOP_SERVICE"
|
||||||
password = password
|
}
|
||||||
)
|
)
|
||||||
}
|
|
||||||
).show(screenWidthProvider())
|
val onEnterRoomSuccess = {
|
||||||
} else {
|
activity.runOnUiThread { openLiveRoom(roomId) }
|
||||||
liveViewModel.enterRoom(roomId, onEnterRoomSuccess)
|
}
|
||||||
}
|
enterLiveRoom(roomId, roomDetail, onEnterRoomSuccess)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun enterLiveRoom(
|
||||||
|
roomId: Long,
|
||||||
|
roomDetail: GetRoomDetailResponse,
|
||||||
|
onEnterRoomSuccess: () -> Unit
|
||||||
|
) {
|
||||||
|
if (!roomDetail.channelName.isNullOrBlank()) {
|
||||||
|
if (roomDetail.manager.id == SharedPreferenceManager.userId) {
|
||||||
|
liveViewModel.enterRoom(roomId, onEnterRoomSuccess)
|
||||||
|
} else if (roomDetail.price == 0 || roomDetail.isPaid) {
|
||||||
|
if (roomDetail.isPrivateRoom) {
|
||||||
|
LiveRoomPasswordDialog(
|
||||||
|
activity = activity,
|
||||||
|
layoutInflater = layoutInflater,
|
||||||
|
can = 0,
|
||||||
|
confirmButtonClick = { password ->
|
||||||
|
liveViewModel.enterRoom(
|
||||||
|
roomId = roomId,
|
||||||
|
onSuccess = onEnterRoomSuccess,
|
||||||
|
password = password
|
||||||
|
)
|
||||||
|
}
|
||||||
|
).show(screenWidthProvider())
|
||||||
} else {
|
} else {
|
||||||
showPaidLiveEntryDialog(
|
liveViewModel.enterRoom(roomId, onEnterRoomSuccess)
|
||||||
roomId = roomId,
|
|
||||||
beginDateTimeUtc = it.beginDateTimeUtc,
|
|
||||||
price = it.price,
|
|
||||||
isPrivateRoom = it.isPrivateRoom,
|
|
||||||
onEnterRoomSuccess = onEnterRoomSuccess
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
showLiveRoomDetail(roomId)
|
showPaidLiveEntryDialog(
|
||||||
|
roomId = roomId,
|
||||||
|
beginDateTimeUtc = roomDetail.beginDateTimeUtc,
|
||||||
|
price = roomDetail.price,
|
||||||
|
isPrivateRoom = roomDetail.isPrivateRoom,
|
||||||
|
onEnterRoomSuccess = onEnterRoomSuccess
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
showLiveRoomDetail(roomId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,11 +28,14 @@ class CreatorChannelCommunityFragment : BaseFragment<FragmentCreatorChannelCommu
|
|||||||
|
|
||||||
private val viewModel: CreatorChannelCommunityViewModel by viewModel()
|
private val viewModel: CreatorChannelCommunityViewModel by viewModel()
|
||||||
private val listAdapter = CreatorChannelCommunityListAdapter(
|
private val listAdapter = CreatorChannelCommunityListAdapter(
|
||||||
|
onPostClick = { item -> host.onCreatorChannelCommunityPostClicked(item.postId) },
|
||||||
onPlayClick = { item -> toggleCommunityAudio(item) },
|
onPlayClick = { item -> toggleCommunityAudio(item) },
|
||||||
onOwnerMoreClick = { item -> host.onCreatorChannelCommunityOwnerMoreClicked(item) },
|
onOwnerMoreClick = { item -> host.onCreatorChannelCommunityOwnerMoreClicked(item) },
|
||||||
isPlayingContent = { postId -> mediaPlayerManager?.isPlayingContent(postId) == true }
|
isPlayingContent = { postId -> mediaPlayerManager?.isPlayingContent(postId) == true }
|
||||||
)
|
)
|
||||||
private val gridAdapter = CreatorChannelCommunityGridAdapter()
|
private val gridAdapter = CreatorChannelCommunityGridAdapter(
|
||||||
|
onPostClick = { item -> host.onCreatorChannelCommunityPostClicked(item.postId) }
|
||||||
|
)
|
||||||
private var mediaPlayerManager: CreatorCommunityMediaPlayerManager? = null
|
private var mediaPlayerManager: CreatorCommunityMediaPlayerManager? = null
|
||||||
private var currentContentState: CreatorChannelCommunityUiState.Content? = null
|
private var currentContentState: CreatorChannelCommunityUiState.Content? = null
|
||||||
private var lastContentLayoutKey: CreatorChannelCommunityContentLayoutKey? = null
|
private var lastContentLayoutKey: CreatorChannelCommunityContentLayoutKey? = null
|
||||||
@@ -225,6 +228,7 @@ class CreatorChannelCommunityFragment : BaseFragment<FragmentCreatorChannelCommu
|
|||||||
interface Host {
|
interface Host {
|
||||||
fun isCreatorChannelOwner(): Boolean
|
fun isCreatorChannelOwner(): Boolean
|
||||||
fun onCreatorChannelCommunityContentChanged()
|
fun onCreatorChannelCommunityContentChanged()
|
||||||
|
fun onCreatorChannelCommunityPostClicked(postId: Long)
|
||||||
fun onCreatorChannelCommunityOwnerMoreClicked(item: CreatorChannelCommunityPostUiModel)
|
fun onCreatorChannelCommunityOwnerMoreClicked(item: CreatorChannelCommunityPostUiModel)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,241 @@
|
|||||||
|
package kr.co.vividnext.sodalive.v2.creator.channel.community.detail
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.text.method.ScrollingMovementMethod
|
||||||
|
import android.view.inputmethod.InputMethodManager
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import androidx.core.widget.doAfterTextChanged
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import coil.transform.CircleCropTransformation
|
||||||
|
import com.bumptech.glide.Glide
|
||||||
|
import kr.co.vividnext.sodalive.R
|
||||||
|
import kr.co.vividnext.sodalive.base.BaseActivity
|
||||||
|
import kr.co.vividnext.sodalive.databinding.ActivityCreatorChannelCommunityDetailBinding
|
||||||
|
import kr.co.vividnext.sodalive.explorer.profile.creator_community.all.player.CreatorCommunityContentItem
|
||||||
|
import kr.co.vividnext.sodalive.explorer.profile.creator_community.all.player.CreatorCommunityMediaPlayerManager
|
||||||
|
import kr.co.vividnext.sodalive.extensions.loadUrl
|
||||||
|
import kr.co.vividnext.sodalive.extensions.moneyFormat
|
||||||
|
import kr.co.vividnext.sodalive.v2.components.modal.V2ModalDialog
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.community.detail.reply.CreatorChannelCommunityReplyActivity
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.community.detail.ui.CreatorChannelCommunityCommentAdapter
|
||||||
|
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||||
|
|
||||||
|
class CreatorChannelCommunityDetailActivity : BaseActivity<ActivityCreatorChannelCommunityDetailBinding>(
|
||||||
|
ActivityCreatorChannelCommunityDetailBinding::inflate
|
||||||
|
) {
|
||||||
|
|
||||||
|
private val viewModel: CreatorChannelCommunityDetailViewModel by viewModel()
|
||||||
|
private val replyLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||||
|
if (result.resultCode == Activity.RESULT_OK && postId > 0L) {
|
||||||
|
setResult(Activity.RESULT_OK)
|
||||||
|
viewModel.loadDetail(postId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private val commentAdapter = CreatorChannelCommunityCommentAdapter(
|
||||||
|
onReplyClick = { comment -> replyLauncher.launch(CreatorChannelCommunityReplyActivity.newIntent(this, postId, comment)) },
|
||||||
|
onModifyClick = { comment -> viewModel.startCommentEdit(comment.commentId, comment.comment) },
|
||||||
|
onDeleteClick = { comment -> showDeleteCommentDialog(comment.commentId) }
|
||||||
|
)
|
||||||
|
private lateinit var mediaPlayerManager: CreatorCommunityMediaPlayerManager
|
||||||
|
private var postId: Long = 0L
|
||||||
|
private var renderedEditingCommentId: Long? = null
|
||||||
|
|
||||||
|
override fun setupView() {
|
||||||
|
mediaPlayerManager = CreatorCommunityMediaPlayerManager(this) { }
|
||||||
|
postId = intent.getLongExtra(EXTRA_POST_ID, 0L)
|
||||||
|
if (postId <= 0L) {
|
||||||
|
Toast.makeText(this, R.string.creator_channel_community_invalid_post, Toast.LENGTH_SHORT).show()
|
||||||
|
finish()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.btnCreatorChannelCommunityDetailBack.setOnClickListener { finish() }
|
||||||
|
binding.rvCreatorChannelCommunityDetailComments.layoutManager = LinearLayoutManager(this)
|
||||||
|
binding.rvCreatorChannelCommunityDetailComments.adapter = commentAdapter
|
||||||
|
binding.scrollCreatorChannelCommunityDetail.setOnScrollChangeListener { scrollView, _, scrollY, _, oldScrollY ->
|
||||||
|
val contentHeight = binding.scrollCreatorChannelCommunityDetail.getChildAt(0)?.measuredHeight ?: 0
|
||||||
|
val isScrollingDown = scrollY > oldScrollY
|
||||||
|
val isAtBottom = scrollY >= contentHeight - scrollView.measuredHeight
|
||||||
|
if (isScrollingDown && isAtBottom) {
|
||||||
|
viewModel.loadMoreComments()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
binding.etCreatorChannelCommunityDetailComment.movementMethod = ScrollingMovementMethod()
|
||||||
|
binding.etCreatorChannelCommunityDetailComment.doAfterTextChanged { editable ->
|
||||||
|
viewModel.updateCommentInput(editable?.toString().orEmpty())
|
||||||
|
}
|
||||||
|
binding.btnCreatorChannelCommunityDetailSend.setOnClickListener { viewModel.submitComment() }
|
||||||
|
binding.btnCreatorChannelCommunityDetailEditCancel.setOnClickListener { viewModel.cancelCommentEdit() }
|
||||||
|
binding.ivCreatorChannelCommunityDetailHeart.setOnClickListener { viewModel.toggleLike() }
|
||||||
|
|
||||||
|
viewModel.detailStateLiveData.observe(this) { state -> render(state) }
|
||||||
|
viewModel.commentWrittenEventLiveData.observe(this) { written ->
|
||||||
|
if (written == true) {
|
||||||
|
setResult(Activity.RESULT_OK)
|
||||||
|
hideCommentKeyboard()
|
||||||
|
viewModel.consumeCommentWrittenEvent()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
viewModel.postChangedEventLiveData.observe(this) { changed ->
|
||||||
|
if (changed == true) {
|
||||||
|
setResult(Activity.RESULT_OK)
|
||||||
|
val current = viewModel.detailStateLiveData.value as? CreatorChannelCommunityDetailUiState.Content
|
||||||
|
if (current?.editingCommentId != null) {
|
||||||
|
hideCommentKeyboard()
|
||||||
|
}
|
||||||
|
viewModel.consumePostChangedEvent()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
viewModel.toastLiveData.observe(this) { event ->
|
||||||
|
event.consume()?.let { toast ->
|
||||||
|
val message = toast.message ?: toast.resId?.let(::getString)
|
||||||
|
message?.let(::showToast)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
viewModel.loadDetail(postId)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPause() {
|
||||||
|
mediaPlayerManager.pauseContent()
|
||||||
|
super.onPause()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
mediaPlayerManager.stopContent()
|
||||||
|
super.onDestroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun render(state: CreatorChannelCommunityDetailUiState) {
|
||||||
|
when (state) {
|
||||||
|
CreatorChannelCommunityDetailUiState.Loading -> Unit
|
||||||
|
is CreatorChannelCommunityDetailUiState.Error -> {
|
||||||
|
Toast.makeText(
|
||||||
|
this,
|
||||||
|
state.message ?: getString(R.string.creator_channel_community_error_message),
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
is CreatorChannelCommunityDetailUiState.Content -> renderContent(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun renderContent(content: CreatorChannelCommunityDetailUiState.Content) = with(binding) {
|
||||||
|
val post = content.post
|
||||||
|
ivCreatorChannelCommunityDetailProfile.loadUrl(post.creatorProfileUrl) {
|
||||||
|
crossfade(true)
|
||||||
|
placeholder(R.drawable.bg_placeholder)
|
||||||
|
transformations(CircleCropTransformation())
|
||||||
|
}
|
||||||
|
tvCreatorChannelCommunityDetailNickname.text = post.creatorNickname
|
||||||
|
tvCreatorChannelCommunityDetailTime.text = post.createdAtText
|
||||||
|
tvCreatorChannelCommunityDetailBody.text = post.content
|
||||||
|
tvCreatorChannelCommunityDetailLikeCount.text = post.likeCount.moneyFormat()
|
||||||
|
tvCreatorChannelCommunityDetailCommentCount.text = post.commentCount.moneyFormat()
|
||||||
|
tvCreatorChannelCommunityDetailCommentHeader.text = getString(
|
||||||
|
R.string.creator_channel_community_comment_header,
|
||||||
|
post.commentCount.moneyFormat()
|
||||||
|
)
|
||||||
|
ivCreatorChannelCommunityDetailHeart.setImageResource(
|
||||||
|
if (post.isLiked) R.drawable.ic_feed_community_heart_fill else R.drawable.ic_feed_community_heart
|
||||||
|
)
|
||||||
|
layoutCreatorChannelCommunityDetailPrice.isVisible = post.isLocked
|
||||||
|
tvCreatorChannelCommunityDetailPrice.text = post.price.moneyFormat()
|
||||||
|
tvCreatorChannelCommunityDetailPurchased.isVisible = post.existOrdered && post.price > 0
|
||||||
|
layoutCreatorChannelCommunityDetailImageContainer.isVisible = post.imageUrl != null || post.showPaywall
|
||||||
|
layoutCreatorChannelCommunityDetailPaywall.isVisible = post.showPaywall
|
||||||
|
tvCreatorChannelCommunityDetailPaywallPrice.text = post.price.moneyFormat()
|
||||||
|
if (post.imageUrl != null) {
|
||||||
|
Glide.with(ivCreatorChannelCommunityDetailImage)
|
||||||
|
.load(post.imageUrl)
|
||||||
|
.placeholder(R.drawable.ic_place_holder)
|
||||||
|
.into(ivCreatorChannelCommunityDetailImage)
|
||||||
|
} else {
|
||||||
|
Glide.with(ivCreatorChannelCommunityDetailImage).clear(ivCreatorChannelCommunityDetailImage)
|
||||||
|
}
|
||||||
|
tvCreatorChannelCommunityDetailAudio.isVisible = post.audioUrl != null
|
||||||
|
tvCreatorChannelCommunityDetailAudio.text = getString(R.string.creator_channel_tab_audio)
|
||||||
|
tvCreatorChannelCommunityDetailAudio.setOnClickListener {
|
||||||
|
toggleAudio(post)
|
||||||
|
}
|
||||||
|
rvCreatorChannelCommunityDetailComments.isVisible = content.isCommentAvailable
|
||||||
|
tvCreatorChannelCommunityDetailCommentHeader.isVisible = content.isCommentAvailable
|
||||||
|
layoutCreatorChannelCommunityDetailInputBar.isVisible = content.isCommentInputVisible
|
||||||
|
ivCreatorChannelCommunityDetailComment.isVisible = content.isCommentAvailable
|
||||||
|
tvCreatorChannelCommunityDetailCommentCount.isVisible = content.isCommentAvailable
|
||||||
|
commentAdapter.submitItems(content.comments)
|
||||||
|
if (etCreatorChannelCommunityDetailComment.text.toString() != content.commentInput) {
|
||||||
|
etCreatorChannelCommunityDetailComment.setText(content.commentInput)
|
||||||
|
etCreatorChannelCommunityDetailComment.setSelection(content.commentInput.length)
|
||||||
|
}
|
||||||
|
btnCreatorChannelCommunityDetailEditCancel.isVisible = content.editingCommentId != null
|
||||||
|
if (content.editingCommentId != null && content.editingCommentId != renderedEditingCommentId) {
|
||||||
|
showCommentKeyboard()
|
||||||
|
}
|
||||||
|
renderedEditingCommentId = content.editingCommentId
|
||||||
|
updateSendButton(content.commentInput.trim().isNotEmpty() && !content.isSendingComment)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateSendButton(enabled: Boolean) = with(binding.btnCreatorChannelCommunityDetailSend) {
|
||||||
|
isEnabled = enabled
|
||||||
|
setBackgroundResource(
|
||||||
|
if (enabled) {
|
||||||
|
R.drawable.bg_creator_channel_community_send_enabled
|
||||||
|
} else {
|
||||||
|
R.drawable.bg_creator_channel_community_send_disabled
|
||||||
|
}
|
||||||
|
)
|
||||||
|
setImageResource(if (enabled) R.drawable.ic_new_arrow_up_white else R.drawable.ic_new_arrow_up_gray)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun toggleAudio(post: CreatorChannelCommunityPostDetailUiModel) {
|
||||||
|
val audioUrl = post.audioUrl ?: return
|
||||||
|
mediaPlayerManager.toggleContent(CreatorCommunityContentItem(post.postId, audioUrl))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showDeleteCommentDialog(commentId: Long) {
|
||||||
|
V2ModalDialog(
|
||||||
|
activity = this,
|
||||||
|
layoutInflater = layoutInflater,
|
||||||
|
title = getString(R.string.confirm_delete_title),
|
||||||
|
desc = getString(R.string.confirm_delete_message),
|
||||||
|
confirmButtonTitle = getString(R.string.confirm),
|
||||||
|
confirmButtonClick = { viewModel.deleteComment(commentId) },
|
||||||
|
cancelButtonTitle = getString(R.string.cancel)
|
||||||
|
).show(screenWidth)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showCommentKeyboard() {
|
||||||
|
val editText = binding.etCreatorChannelCommunityDetailComment
|
||||||
|
editText.requestFocus()
|
||||||
|
editText.post {
|
||||||
|
val inputMethodManager = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||||
|
inputMethodManager.showSoftInput(editText, InputMethodManager.SHOW_IMPLICIT)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun hideCommentKeyboard() {
|
||||||
|
val editText = binding.etCreatorChannelCommunityDetailComment
|
||||||
|
val inputMethodManager = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||||
|
inputMethodManager.hideSoftInputFromWindow(editText.windowToken, 0)
|
||||||
|
editText.clearFocus()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val EXTRA_POST_ID = "extra_post_id"
|
||||||
|
|
||||||
|
fun newIntent(context: Context, postId: Long): Intent {
|
||||||
|
return Intent(context, CreatorChannelCommunityDetailActivity::class.java)
|
||||||
|
.putExtra(EXTRA_POST_ID, postId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,531 @@
|
|||||||
|
package kr.co.vividnext.sodalive.v2.creator.channel.community.detail
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||||
|
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||||
|
import kr.co.vividnext.sodalive.R
|
||||||
|
import kr.co.vividnext.sodalive.audio_content.comment.ModifyCommentRequest
|
||||||
|
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.explorer.profile.creator_community.CreatorCommunityRepository
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelEvent
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.community.detail.data.CreatorChannelCommunityCommentResponse
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.community.detail.data.CreatorChannelCommunityCommentsResponse
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.community.detail.data.CreatorChannelCommunityPostDetailResponse
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.community.detail.data.CreatorChannelCommunityReplyResponse
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelRepository
|
||||||
|
|
||||||
|
class CreatorChannelCommunityDetailViewModel(
|
||||||
|
private val repository: CreatorChannelRepository,
|
||||||
|
private val legacyRepository: CreatorCommunityRepository,
|
||||||
|
private val relativeTimeTextFormatter: UtcRelativeTimeTextFormatter
|
||||||
|
) : BaseViewModel() {
|
||||||
|
|
||||||
|
private val _detailStateLiveData = MutableLiveData<CreatorChannelCommunityDetailUiState>()
|
||||||
|
val detailStateLiveData: LiveData<CreatorChannelCommunityDetailUiState>
|
||||||
|
get() = _detailStateLiveData
|
||||||
|
|
||||||
|
private val _commentWrittenEventLiveData = MutableLiveData<Boolean>()
|
||||||
|
val commentWrittenEventLiveData: LiveData<Boolean>
|
||||||
|
get() = _commentWrittenEventLiveData
|
||||||
|
|
||||||
|
private val _postChangedEventLiveData = MutableLiveData<Boolean>()
|
||||||
|
val postChangedEventLiveData: LiveData<Boolean>
|
||||||
|
get() = _postChangedEventLiveData
|
||||||
|
|
||||||
|
private val _toastLiveData = MutableLiveData<CreatorChannelEvent<ToastMessage>>()
|
||||||
|
val toastLiveData: LiveData<CreatorChannelEvent<ToastMessage>>
|
||||||
|
get() = _toastLiveData
|
||||||
|
|
||||||
|
private var postId: Long = 0L
|
||||||
|
private var detailRequestGeneration = 0
|
||||||
|
private var commentRequestGeneration = 0
|
||||||
|
private var isLoadingComments = false
|
||||||
|
private var isTogglingLike = false
|
||||||
|
private var isSendingComment = false
|
||||||
|
private var isModifyingComment = false
|
||||||
|
|
||||||
|
fun loadDetail(postId: Long) {
|
||||||
|
if (postId <= 0) return
|
||||||
|
this.postId = postId
|
||||||
|
val generation = ++detailRequestGeneration
|
||||||
|
commentRequestGeneration++
|
||||||
|
isLoadingComments = false
|
||||||
|
_detailStateLiveData.value = CreatorChannelCommunityDetailUiState.Loading
|
||||||
|
compositeDisposable.add(
|
||||||
|
repository.getCommunityPostDetail(postId, authToken())
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(
|
||||||
|
{ response ->
|
||||||
|
if (generation == detailRequestGeneration) {
|
||||||
|
handleDetailResponse(response)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
if (generation == detailRequestGeneration) {
|
||||||
|
_detailStateLiveData.value = CreatorChannelCommunityDetailUiState.Error(it.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadMoreComments() {
|
||||||
|
val content = _detailStateLiveData.value as? CreatorChannelCommunityDetailUiState.Content ?: return
|
||||||
|
if (!content.isCommentAvailable || !content.hasNextComment || isLoadingComments) return
|
||||||
|
|
||||||
|
loadComments(page = content.commentPage + 1, append = true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toggleLike() {
|
||||||
|
val content = _detailStateLiveData.value as? CreatorChannelCommunityDetailUiState.Content ?: return
|
||||||
|
if (isTogglingLike) return
|
||||||
|
|
||||||
|
val previousPost = content.post
|
||||||
|
val nextPost = previousPost.copy(
|
||||||
|
isLiked = !previousPost.isLiked,
|
||||||
|
likeCount = (previousPost.likeCount + if (previousPost.isLiked) -1 else 1).coerceAtLeast(0)
|
||||||
|
)
|
||||||
|
isTogglingLike = true
|
||||||
|
_detailStateLiveData.value = content.copy(post = nextPost)
|
||||||
|
compositeDisposable.add(
|
||||||
|
legacyRepository.communityPostLike(postId, authToken())
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(
|
||||||
|
{ response ->
|
||||||
|
isTogglingLike = false
|
||||||
|
if (response.success) {
|
||||||
|
_postChangedEventLiveData.value = true
|
||||||
|
} else {
|
||||||
|
val current = _detailStateLiveData.value as? CreatorChannelCommunityDetailUiState.Content
|
||||||
|
?: return@subscribe
|
||||||
|
_detailStateLiveData.value = current.copy(post = previousPost)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
isTogglingLike = false
|
||||||
|
val current = _detailStateLiveData.value as? CreatorChannelCommunityDetailUiState.Content
|
||||||
|
?: return@subscribe
|
||||||
|
_detailStateLiveData.value = current.copy(post = previousPost)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateCommentInput(input: String) {
|
||||||
|
val content = _detailStateLiveData.value as? CreatorChannelCommunityDetailUiState.Content ?: return
|
||||||
|
if (!content.isCommentAvailable) return
|
||||||
|
|
||||||
|
_detailStateLiveData.value = content.copy(commentInput = input)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun submitComment() {
|
||||||
|
val content = _detailStateLiveData.value as? CreatorChannelCommunityDetailUiState.Content ?: return
|
||||||
|
val comment = content.commentInput.trim()
|
||||||
|
val editingCommentId = content.editingCommentId
|
||||||
|
if (editingCommentId != null) {
|
||||||
|
submitCommentEdit(content, editingCommentId, comment)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!content.isCommentAvailable || comment.isBlank() || isSendingComment) return
|
||||||
|
|
||||||
|
isSendingComment = true
|
||||||
|
_detailStateLiveData.value = content.copy(isSendingComment = true)
|
||||||
|
compositeDisposable.add(
|
||||||
|
legacyRepository.registerComment(
|
||||||
|
postId = postId,
|
||||||
|
comment = comment,
|
||||||
|
parentId = null,
|
||||||
|
isSecret = false,
|
||||||
|
token = authToken()
|
||||||
|
)
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(
|
||||||
|
{ response ->
|
||||||
|
isSendingComment = false
|
||||||
|
val current = _detailStateLiveData.value as? CreatorChannelCommunityDetailUiState.Content
|
||||||
|
?: return@subscribe
|
||||||
|
if (response.success) {
|
||||||
|
_commentWrittenEventLiveData.value = true
|
||||||
|
_detailStateLiveData.value = current.copy(commentInput = "", isSendingComment = false)
|
||||||
|
loadDetail(postId)
|
||||||
|
} else {
|
||||||
|
showMutationFailureToast(response.message)
|
||||||
|
_detailStateLiveData.value = current.copy(isSendingComment = false)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
isSendingComment = false
|
||||||
|
val current = _detailStateLiveData.value as? CreatorChannelCommunityDetailUiState.Content
|
||||||
|
?: return@subscribe
|
||||||
|
showMutationFailureToast()
|
||||||
|
_detailStateLiveData.value = current.copy(isSendingComment = false)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun consumeCommentWrittenEvent() {
|
||||||
|
_commentWrittenEventLiveData.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun consumePostChangedEvent() {
|
||||||
|
_postChangedEventLiveData.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun startCommentEdit(commentId: Long, comment: String) {
|
||||||
|
val content = _detailStateLiveData.value as? CreatorChannelCommunityDetailUiState.Content ?: return
|
||||||
|
if (commentId <= 0) return
|
||||||
|
|
||||||
|
_detailStateLiveData.value = content.copy(
|
||||||
|
commentInput = comment,
|
||||||
|
editingCommentId = commentId,
|
||||||
|
editingOriginalComment = comment
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun cancelCommentEdit() {
|
||||||
|
val content = _detailStateLiveData.value as? CreatorChannelCommunityDetailUiState.Content ?: return
|
||||||
|
_detailStateLiveData.value = content.copy(
|
||||||
|
commentInput = "",
|
||||||
|
editingCommentId = null,
|
||||||
|
editingOriginalComment = ""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun modifyComment(commentId: Long, comment: String) {
|
||||||
|
val trimmedComment = comment.trim()
|
||||||
|
if (commentId <= 0 || isModifyingComment) return
|
||||||
|
if (trimmedComment.isBlank()) {
|
||||||
|
showBlankCommentToast()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
modifyComment(ModifyCommentRequest(commentId = commentId, comment = trimmedComment))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun deleteComment(commentId: Long) {
|
||||||
|
if (commentId <= 0 || isModifyingComment) return
|
||||||
|
|
||||||
|
modifyComment(ModifyCommentRequest(commentId = commentId, isActive = false))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun submitCommentEdit(
|
||||||
|
content: CreatorChannelCommunityDetailUiState.Content,
|
||||||
|
commentId: Long,
|
||||||
|
comment: String
|
||||||
|
) {
|
||||||
|
if (commentId <= 0 || isModifyingComment) return
|
||||||
|
if (comment.isBlank()) {
|
||||||
|
showBlankCommentToast()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (comment == content.editingOriginalComment.trim()) {
|
||||||
|
showNoChangeToast()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
isModifyingComment = true
|
||||||
|
_detailStateLiveData.value = content.copy(isSendingComment = true)
|
||||||
|
compositeDisposable.add(
|
||||||
|
legacyRepository.modifyComment(ModifyCommentRequest(commentId = commentId, comment = comment), authToken())
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(
|
||||||
|
{ response ->
|
||||||
|
isModifyingComment = false
|
||||||
|
val current = _detailStateLiveData.value as? CreatorChannelCommunityDetailUiState.Content
|
||||||
|
?: return@subscribe
|
||||||
|
if (response.success) {
|
||||||
|
_postChangedEventLiveData.value = true
|
||||||
|
_detailStateLiveData.value = current.copy(
|
||||||
|
commentInput = "",
|
||||||
|
isSendingComment = false,
|
||||||
|
editingCommentId = null,
|
||||||
|
editingOriginalComment = ""
|
||||||
|
)
|
||||||
|
loadDetail(postId)
|
||||||
|
} else {
|
||||||
|
showMutationFailureToast(response.message)
|
||||||
|
_detailStateLiveData.value = current.copy(isSendingComment = false)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
isModifyingComment = false
|
||||||
|
val current = _detailStateLiveData.value as? CreatorChannelCommunityDetailUiState.Content
|
||||||
|
?: return@subscribe
|
||||||
|
showMutationFailureToast()
|
||||||
|
_detailStateLiveData.value = current.copy(isSendingComment = false)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleDetailResponse(response: ApiResponse<CreatorChannelCommunityPostDetailResponse>) {
|
||||||
|
val data = response.data
|
||||||
|
if (!response.success || data == null) {
|
||||||
|
_detailStateLiveData.value = CreatorChannelCommunityDetailUiState.Error(response.message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val isCreatorOwner = data.creatorId == SharedPreferenceManager.userId
|
||||||
|
val comments = if (data.isCommentAvailable) {
|
||||||
|
data.comments.comments.map { it.toUiModel(isCreatorOwner) }
|
||||||
|
} else {
|
||||||
|
emptyList()
|
||||||
|
}
|
||||||
|
_detailStateLiveData.value = CreatorChannelCommunityDetailUiState.Content(
|
||||||
|
post = data.toUiModel(),
|
||||||
|
isCommentAvailable = data.isCommentAvailable,
|
||||||
|
isCommentInputVisible = data.isCommentAvailable,
|
||||||
|
comments = comments,
|
||||||
|
commentPage = data.comments.page,
|
||||||
|
hasNextComment = data.isCommentAvailable && data.comments.hasNext
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadComments(page: Int, append: Boolean) {
|
||||||
|
if (postId <= 0 || isLoadingComments && append) return
|
||||||
|
val generation = if (append) {
|
||||||
|
commentRequestGeneration
|
||||||
|
} else {
|
||||||
|
++commentRequestGeneration
|
||||||
|
}
|
||||||
|
isLoadingComments = true
|
||||||
|
val beforeRequest = _detailStateLiveData.value as? CreatorChannelCommunityDetailUiState.Content
|
||||||
|
if (beforeRequest != null) {
|
||||||
|
_detailStateLiveData.value = beforeRequest.copy(isLoadingComments = true)
|
||||||
|
}
|
||||||
|
compositeDisposable.add(
|
||||||
|
repository.getCommunityPostComments(postId, page, DEFAULT_PAGE_SIZE, authToken())
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(
|
||||||
|
{ response -> handleCommentsResponse(response, append, generation) },
|
||||||
|
{
|
||||||
|
if (generation != commentRequestGeneration) return@subscribe
|
||||||
|
|
||||||
|
isLoadingComments = false
|
||||||
|
val current = _detailStateLiveData.value as? CreatorChannelCommunityDetailUiState.Content
|
||||||
|
?: return@subscribe
|
||||||
|
_detailStateLiveData.value = current.copy(isLoadingComments = false)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleCommentsResponse(
|
||||||
|
response: ApiResponse<CreatorChannelCommunityCommentsResponse>,
|
||||||
|
append: Boolean,
|
||||||
|
generation: Int
|
||||||
|
) {
|
||||||
|
if (generation != commentRequestGeneration) return
|
||||||
|
|
||||||
|
isLoadingComments = false
|
||||||
|
val current = _detailStateLiveData.value as? CreatorChannelCommunityDetailUiState.Content ?: return
|
||||||
|
val data = response.data
|
||||||
|
if (!response.success || data == null) {
|
||||||
|
_detailStateLiveData.value = current.copy(isLoadingComments = false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val isCreatorOwner = current.post.creatorId == SharedPreferenceManager.userId
|
||||||
|
val nextComments = data.comments.map { it.toUiModel(isCreatorOwner) }
|
||||||
|
_detailStateLiveData.value = current.copy(
|
||||||
|
post = current.post.copy(commentCount = data.commentCount),
|
||||||
|
comments = if (append) current.comments + nextComments else nextComments,
|
||||||
|
commentPage = data.page,
|
||||||
|
hasNextComment = data.hasNext,
|
||||||
|
isLoadingComments = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun modifyComment(request: ModifyCommentRequest) {
|
||||||
|
if (postId <= 0) return
|
||||||
|
|
||||||
|
isModifyingComment = true
|
||||||
|
compositeDisposable.add(
|
||||||
|
legacyRepository.modifyComment(request, authToken())
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(
|
||||||
|
{ response ->
|
||||||
|
isModifyingComment = false
|
||||||
|
if (response.success) {
|
||||||
|
_postChangedEventLiveData.value = true
|
||||||
|
loadDetail(postId)
|
||||||
|
} else {
|
||||||
|
showMutationFailureToast(response.message)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
isModifyingComment = false
|
||||||
|
showMutationFailureToast()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showMutationFailureToast(message: String? = null) {
|
||||||
|
_toastLiveData.value = CreatorChannelEvent(
|
||||||
|
if (message.isNullOrBlank()) {
|
||||||
|
ToastMessage(resId = R.string.common_error_unknown)
|
||||||
|
} else {
|
||||||
|
ToastMessage(message = message)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showBlankCommentToast() {
|
||||||
|
_toastLiveData.value = CreatorChannelEvent(
|
||||||
|
ToastMessage(resId = R.string.screen_creator_community_write_content_hint)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showNoChangeToast() {
|
||||||
|
_toastLiveData.value = CreatorChannelEvent(
|
||||||
|
ToastMessage(resId = R.string.audio_content_comment_no_change)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun CreatorChannelCommunityPostDetailResponse.toUiModel() = CreatorChannelCommunityPostDetailUiModel(
|
||||||
|
postId = postId,
|
||||||
|
creatorId = creatorId,
|
||||||
|
creatorNickname = creatorNickname,
|
||||||
|
creatorProfileUrl = creatorProfileUrl,
|
||||||
|
createdAtText = relativeTimeTextFormatter.format(createdAtUtc),
|
||||||
|
content = content,
|
||||||
|
imageUrl = imageUrl.takeUnless { isLocked() },
|
||||||
|
audioUrl = audioUrl.takeUnless { isLocked() },
|
||||||
|
price = price,
|
||||||
|
existOrdered = existOrdered,
|
||||||
|
likeCount = likeCount,
|
||||||
|
commentCount = commentCount,
|
||||||
|
isLiked = isLiked,
|
||||||
|
isPinned = isPinned,
|
||||||
|
isLocked = isLocked(),
|
||||||
|
showPaywall = isLocked()
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun CreatorChannelCommunityPostDetailResponse.isLocked(): Boolean {
|
||||||
|
return price > 0 && !existOrdered && creatorId != SharedPreferenceManager.userId
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun CreatorChannelCommunityCommentResponse.toUiModel(
|
||||||
|
isCreatorOwner: Boolean
|
||||||
|
) = CreatorChannelCommunityCommentUiModel(
|
||||||
|
commentId = commentId,
|
||||||
|
memberId = writerId,
|
||||||
|
memberNickname = writerNickname,
|
||||||
|
memberProfileUrl = writerProfileImageUrl,
|
||||||
|
createdAtUtc = createdAtUtc,
|
||||||
|
createdAtText = relativeTimeTextFormatter.format(createdAtUtc),
|
||||||
|
comment = content,
|
||||||
|
likeCount = 0,
|
||||||
|
replyCount = if (latestReply != null) 1 else 0,
|
||||||
|
isLiked = false,
|
||||||
|
isMine = writerId == SharedPreferenceManager.userId,
|
||||||
|
isCreatorOwner = isCreatorOwner,
|
||||||
|
latestReply = latestReply?.toUiModel(parentCommentId = commentId, isCreatorOwner = isCreatorOwner)
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun CreatorChannelCommunityReplyResponse.toUiModel(
|
||||||
|
parentCommentId: Long,
|
||||||
|
isCreatorOwner: Boolean
|
||||||
|
) = CreatorChannelCommunityReplyUiModel(
|
||||||
|
replyId = commentId,
|
||||||
|
commentId = parentCommentId,
|
||||||
|
memberId = writerId,
|
||||||
|
memberNickname = writerNickname,
|
||||||
|
memberProfileUrl = writerProfileImageUrl,
|
||||||
|
createdAtUtc = createdAtUtc,
|
||||||
|
createdAtText = relativeTimeTextFormatter.format(createdAtUtc),
|
||||||
|
comment = content,
|
||||||
|
likeCount = 0,
|
||||||
|
isLiked = false,
|
||||||
|
isMine = writerId == SharedPreferenceManager.userId,
|
||||||
|
isCreatorOwner = isCreatorOwner
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun authToken(): String = "Bearer ${SharedPreferenceManager.token}"
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val DEFAULT_PAGE_SIZE = 20
|
||||||
|
private const val FIRST_PAGE = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed interface CreatorChannelCommunityDetailUiState {
|
||||||
|
data object Loading : CreatorChannelCommunityDetailUiState
|
||||||
|
data class Error(val message: String?) : CreatorChannelCommunityDetailUiState
|
||||||
|
data class Content(
|
||||||
|
val post: CreatorChannelCommunityPostDetailUiModel,
|
||||||
|
val isCommentAvailable: Boolean,
|
||||||
|
val isCommentInputVisible: Boolean,
|
||||||
|
val comments: List<CreatorChannelCommunityCommentUiModel>,
|
||||||
|
val commentPage: Int,
|
||||||
|
val hasNextComment: Boolean,
|
||||||
|
val isLoadingComments: Boolean = false,
|
||||||
|
val commentInput: String = "",
|
||||||
|
val isSendingComment: Boolean = false,
|
||||||
|
val editingCommentId: Long? = null,
|
||||||
|
val editingOriginalComment: String = ""
|
||||||
|
) : CreatorChannelCommunityDetailUiState
|
||||||
|
}
|
||||||
|
|
||||||
|
data class CreatorChannelCommunityPostDetailUiModel(
|
||||||
|
val postId: Long,
|
||||||
|
val creatorId: Long,
|
||||||
|
val creatorNickname: String,
|
||||||
|
val creatorProfileUrl: String?,
|
||||||
|
val createdAtText: String,
|
||||||
|
val content: String,
|
||||||
|
val imageUrl: String?,
|
||||||
|
val audioUrl: String?,
|
||||||
|
val price: Int,
|
||||||
|
val existOrdered: Boolean,
|
||||||
|
val likeCount: Int,
|
||||||
|
val commentCount: Int,
|
||||||
|
val isLiked: Boolean,
|
||||||
|
val isPinned: Boolean,
|
||||||
|
val isLocked: Boolean,
|
||||||
|
val showPaywall: Boolean
|
||||||
|
)
|
||||||
|
|
||||||
|
data class CreatorChannelCommunityCommentUiModel(
|
||||||
|
val commentId: Long,
|
||||||
|
val memberId: Long,
|
||||||
|
val memberNickname: String,
|
||||||
|
val memberProfileUrl: String?,
|
||||||
|
val createdAtUtc: String,
|
||||||
|
val createdAtText: String,
|
||||||
|
val comment: String,
|
||||||
|
val likeCount: Int,
|
||||||
|
val replyCount: Int,
|
||||||
|
val isLiked: Boolean,
|
||||||
|
val isMine: Boolean,
|
||||||
|
val isCreatorOwner: Boolean,
|
||||||
|
val latestReply: CreatorChannelCommunityReplyUiModel?
|
||||||
|
)
|
||||||
|
|
||||||
|
data class CreatorChannelCommunityReplyUiModel(
|
||||||
|
val replyId: Long,
|
||||||
|
val commentId: Long,
|
||||||
|
val memberId: Long,
|
||||||
|
val memberNickname: String,
|
||||||
|
val memberProfileUrl: String?,
|
||||||
|
val createdAtUtc: String,
|
||||||
|
val createdAtText: String,
|
||||||
|
val comment: String,
|
||||||
|
val likeCount: Int,
|
||||||
|
val isLiked: Boolean,
|
||||||
|
val isMine: Boolean,
|
||||||
|
val isCreatorOwner: Boolean
|
||||||
|
)
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
package kr.co.vividnext.sodalive.v2.creator.channel.community.detail.data
|
||||||
|
|
||||||
|
import androidx.annotation.Keep
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
data class CreatorChannelCommunityPostDetailResponse(
|
||||||
|
@SerializedName("postId") val postId: Long,
|
||||||
|
@SerializedName("creatorId") val creatorId: Long,
|
||||||
|
@SerializedName("creatorNickname") val creatorNickname: String,
|
||||||
|
@SerializedName("creatorProfileUrl") val creatorProfileUrl: String?,
|
||||||
|
@SerializedName("createdAtUtc") val createdAtUtc: String,
|
||||||
|
@SerializedName("content") val content: String,
|
||||||
|
@SerializedName("imageUrl") val imageUrl: String?,
|
||||||
|
@SerializedName("audioUrl") val audioUrl: String?,
|
||||||
|
@SerializedName("price") val price: Int,
|
||||||
|
@SerializedName("existOrdered") val existOrdered: Boolean,
|
||||||
|
@SerializedName("isCommentAvailable") val isCommentAvailable: Boolean,
|
||||||
|
@SerializedName("likeCount") val likeCount: Int,
|
||||||
|
@SerializedName("commentCount") val commentCount: Int,
|
||||||
|
@SerializedName("isLiked") val isLiked: Boolean,
|
||||||
|
@SerializedName("isPinned") val isPinned: Boolean,
|
||||||
|
@SerializedName("comments") val comments: CreatorChannelCommunityCommentsResponse
|
||||||
|
)
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
data class CreatorChannelCommunityCommentsResponse(
|
||||||
|
@SerializedName("commentCount") val commentCount: Int,
|
||||||
|
@SerializedName("comments") val comments: List<CreatorChannelCommunityCommentResponse>,
|
||||||
|
@SerializedName("page") val page: Int,
|
||||||
|
@SerializedName("size") val size: Int,
|
||||||
|
@SerializedName("hasNext") val hasNext: Boolean
|
||||||
|
)
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
data class CreatorChannelCommunityCommentResponse(
|
||||||
|
@SerializedName("commentId") val commentId: Long,
|
||||||
|
@SerializedName("writerId") val writerId: Long,
|
||||||
|
@SerializedName("writerProfileImageUrl") val writerProfileImageUrl: String,
|
||||||
|
@SerializedName("writerNickname") val writerNickname: String,
|
||||||
|
@SerializedName("content") val content: String,
|
||||||
|
@SerializedName("isSecret") val isSecret: Boolean,
|
||||||
|
@SerializedName("createdAtUtc") val createdAtUtc: String,
|
||||||
|
@SerializedName("latestReply") val latestReply: CreatorChannelCommunityReplyResponse?
|
||||||
|
)
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
data class CreatorChannelCommunityReplyResponse(
|
||||||
|
@SerializedName("commentId") val commentId: Long,
|
||||||
|
@SerializedName("writerId") val writerId: Long,
|
||||||
|
@SerializedName("writerProfileImageUrl") val writerProfileImageUrl: String,
|
||||||
|
@SerializedName("writerNickname") val writerNickname: String,
|
||||||
|
@SerializedName("content") val content: String,
|
||||||
|
@SerializedName("createdAtUtc") val createdAtUtc: String
|
||||||
|
)
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
data class CreatorChannelCommunityRepliesResponse(
|
||||||
|
@SerializedName("replies") val replies: List<CreatorChannelCommunityReplyResponse>,
|
||||||
|
@SerializedName("page") val page: Int,
|
||||||
|
@SerializedName("size") val size: Int,
|
||||||
|
@SerializedName("hasNext") val hasNext: Boolean
|
||||||
|
)
|
||||||
@@ -0,0 +1,229 @@
|
|||||||
|
package kr.co.vividnext.sodalive.v2.creator.channel.community.detail.reply
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.text.method.ScrollingMovementMethod
|
||||||
|
import android.view.inputmethod.InputMethodManager
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import androidx.core.widget.doAfterTextChanged
|
||||||
|
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.databinding.ActivityCreatorChannelCommunityReplyBinding
|
||||||
|
import kr.co.vividnext.sodalive.v2.components.modal.V2ModalDialog
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.community.detail.CreatorChannelCommunityCommentUiModel
|
||||||
|
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 org.koin.androidx.viewmodel.ext.android.viewModel
|
||||||
|
|
||||||
|
class CreatorChannelCommunityReplyActivity : BaseActivity<ActivityCreatorChannelCommunityReplyBinding>(
|
||||||
|
ActivityCreatorChannelCommunityReplyBinding::inflate
|
||||||
|
) {
|
||||||
|
|
||||||
|
private val viewModel: CreatorChannelCommunityReplyViewModel by viewModel()
|
||||||
|
private val replyAdapter = CreatorChannelCommunityReplyAdapter(
|
||||||
|
onModifyClick = { reply -> viewModel.startReplyEdit(reply.replyId, reply.comment) },
|
||||||
|
onDeleteClick = { reply -> showDeleteReplyDialog(reply.replyId) }
|
||||||
|
)
|
||||||
|
private var postId: Long = 0L
|
||||||
|
private var renderedEditingTarget: CreatorChannelCommunityReplyEditTarget? = null
|
||||||
|
|
||||||
|
override fun setupView() {
|
||||||
|
postId = intent.getLongExtra(EXTRA_POST_ID, 0L)
|
||||||
|
val parentComment = parentCommentFromIntent()
|
||||||
|
if (postId <= 0L || parentComment.commentId <= 0L) {
|
||||||
|
Toast.makeText(this, R.string.creator_channel_community_invalid_post, Toast.LENGTH_SHORT).show()
|
||||||
|
finish()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.btnCreatorChannelCommunityReplyBack.setOnClickListener { finish() }
|
||||||
|
binding.rvCreatorChannelCommunityReply.layoutManager = LinearLayoutManager(this)
|
||||||
|
binding.rvCreatorChannelCommunityReply.adapter = replyAdapter
|
||||||
|
binding.rvCreatorChannelCommunityReply.addOnScrollListener(
|
||||||
|
object : RecyclerView.OnScrollListener() {
|
||||||
|
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||||
|
if (dy > 0 && !recyclerView.canScrollVertically(1)) {
|
||||||
|
viewModel.loadMoreReplies()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
binding.etCreatorChannelCommunityReply.movementMethod = ScrollingMovementMethod()
|
||||||
|
binding.etCreatorChannelCommunityReply.doAfterTextChanged { editable ->
|
||||||
|
viewModel.updateReplyInput(editable?.toString().orEmpty())
|
||||||
|
}
|
||||||
|
binding.btnCreatorChannelCommunityReplySend.setOnClickListener { viewModel.submitReply() }
|
||||||
|
binding.btnCreatorChannelCommunityReplyEditCancel.setOnClickListener { viewModel.cancelReplyEdit() }
|
||||||
|
|
||||||
|
viewModel.replyStateLiveData.observe(this) { state -> render(state) }
|
||||||
|
viewModel.replyWrittenEventLiveData.observe(this) { written ->
|
||||||
|
if (written == true) {
|
||||||
|
hideReplyKeyboard()
|
||||||
|
viewModel.consumeReplyWrittenEvent()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
viewModel.replyChangedEventLiveData.observe(this) { changed ->
|
||||||
|
if (changed == true) {
|
||||||
|
setResult(Activity.RESULT_OK)
|
||||||
|
hideReplyKeyboard()
|
||||||
|
viewModel.consumeReplyChangedEvent()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
viewModel.parentCommentChangedEventLiveData.observe(this) { changed ->
|
||||||
|
if (changed == true) {
|
||||||
|
setResult(Activity.RESULT_OK)
|
||||||
|
hideReplyKeyboard()
|
||||||
|
viewModel.consumeParentCommentChangedEvent()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
viewModel.parentCommentDeletedEventLiveData.observe(this) { deleted ->
|
||||||
|
if (deleted == true) {
|
||||||
|
setResult(Activity.RESULT_OK)
|
||||||
|
viewModel.consumeParentCommentDeletedEvent()
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
viewModel.toastLiveData.observe(this) { event ->
|
||||||
|
event.consume()?.let { toast ->
|
||||||
|
val message = toast.message ?: toast.resId?.let(::getString)
|
||||||
|
message?.let(::showToast)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
viewModel.loadReplies(postId, parentComment)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun render(state: CreatorChannelCommunityReplyUiState) {
|
||||||
|
when (state) {
|
||||||
|
is CreatorChannelCommunityReplyUiState.Content -> renderContent(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun renderContent(content: CreatorChannelCommunityReplyUiState.Content) = with(binding) {
|
||||||
|
CreatorChannelCommunityCommentAdapter.bindComment(
|
||||||
|
layoutCreatorChannelCommunityReplyParent,
|
||||||
|
content.parentComment,
|
||||||
|
onReplyClick = {},
|
||||||
|
onModifyClick = { comment -> viewModel.startParentCommentEdit(comment.comment) },
|
||||||
|
onDeleteClick = { showDeleteParentCommentDialog() }
|
||||||
|
)
|
||||||
|
layoutCreatorChannelCommunityReplyParent.layoutCreatorChannelCommunityCommentLatestReply.isVisible = false
|
||||||
|
replyAdapter.submitItems(content.replies)
|
||||||
|
if (etCreatorChannelCommunityReply.text.toString() != content.replyInput) {
|
||||||
|
etCreatorChannelCommunityReply.setText(content.replyInput)
|
||||||
|
etCreatorChannelCommunityReply.setSelection(content.replyInput.length)
|
||||||
|
}
|
||||||
|
btnCreatorChannelCommunityReplyEditCancel.isVisible = content.editingTarget != null
|
||||||
|
if (content.editingTarget != null && content.editingTarget != renderedEditingTarget) {
|
||||||
|
showReplyKeyboard()
|
||||||
|
}
|
||||||
|
renderedEditingTarget = content.editingTarget
|
||||||
|
updateSendButton(content.replyInput.trim().isNotEmpty() && !content.isSendingReply)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showDeleteParentCommentDialog() {
|
||||||
|
V2ModalDialog(
|
||||||
|
activity = this,
|
||||||
|
layoutInflater = layoutInflater,
|
||||||
|
title = getString(R.string.confirm_delete_title),
|
||||||
|
desc = getString(R.string.confirm_delete_message),
|
||||||
|
confirmButtonTitle = getString(R.string.confirm),
|
||||||
|
confirmButtonClick = { viewModel.deleteParentComment() },
|
||||||
|
cancelButtonTitle = getString(R.string.cancel)
|
||||||
|
).show(screenWidth)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showDeleteReplyDialog(replyId: Long) {
|
||||||
|
V2ModalDialog(
|
||||||
|
activity = this,
|
||||||
|
layoutInflater = layoutInflater,
|
||||||
|
title = getString(R.string.confirm_delete_title),
|
||||||
|
desc = getString(R.string.confirm_delete_message),
|
||||||
|
confirmButtonTitle = getString(R.string.confirm),
|
||||||
|
confirmButtonClick = { viewModel.deleteReply(replyId) },
|
||||||
|
cancelButtonTitle = getString(R.string.cancel)
|
||||||
|
).show(screenWidth)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateSendButton(enabled: Boolean) = with(binding.btnCreatorChannelCommunityReplySend) {
|
||||||
|
isEnabled = enabled
|
||||||
|
setBackgroundResource(
|
||||||
|
if (enabled) {
|
||||||
|
R.drawable.bg_creator_channel_community_send_enabled
|
||||||
|
} else {
|
||||||
|
R.drawable.bg_creator_channel_community_send_disabled
|
||||||
|
}
|
||||||
|
)
|
||||||
|
setImageResource(if (enabled) R.drawable.ic_new_arrow_up_white else R.drawable.ic_new_arrow_up_gray)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showReplyKeyboard() {
|
||||||
|
val editText = binding.etCreatorChannelCommunityReply
|
||||||
|
editText.requestFocus()
|
||||||
|
editText.post {
|
||||||
|
val inputMethodManager = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||||
|
inputMethodManager.showSoftInput(editText, InputMethodManager.SHOW_IMPLICIT)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun hideReplyKeyboard() {
|
||||||
|
val editText = binding.etCreatorChannelCommunityReply
|
||||||
|
val inputMethodManager = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||||
|
inputMethodManager.hideSoftInputFromWindow(editText.windowToken, 0)
|
||||||
|
editText.clearFocus()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parentCommentFromIntent(): CreatorChannelCommunityCommentUiModel {
|
||||||
|
return CreatorChannelCommunityCommentUiModel(
|
||||||
|
commentId = intent.getLongExtra(EXTRA_COMMENT_ID, 0L),
|
||||||
|
memberId = intent.getLongExtra(EXTRA_MEMBER_ID, 0L),
|
||||||
|
memberNickname = intent.getStringExtra(EXTRA_MEMBER_NICKNAME).orEmpty(),
|
||||||
|
memberProfileUrl = intent.getStringExtra(EXTRA_MEMBER_PROFILE_URL),
|
||||||
|
createdAtUtc = intent.getStringExtra(EXTRA_CREATED_AT_UTC).orEmpty(),
|
||||||
|
createdAtText = intent.getStringExtra(EXTRA_CREATED_AT_TEXT).orEmpty(),
|
||||||
|
comment = intent.getStringExtra(EXTRA_COMMENT).orEmpty(),
|
||||||
|
likeCount = intent.getIntExtra(EXTRA_LIKE_COUNT, 0),
|
||||||
|
replyCount = intent.getIntExtra(EXTRA_REPLY_COUNT, 0),
|
||||||
|
isLiked = intent.getBooleanExtra(EXTRA_IS_LIKED, false),
|
||||||
|
isMine = intent.getBooleanExtra(EXTRA_IS_MINE, false),
|
||||||
|
isCreatorOwner = intent.getBooleanExtra(EXTRA_IS_CREATOR_OWNER, false),
|
||||||
|
latestReply = null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val EXTRA_POST_ID = "extra_post_id"
|
||||||
|
private const val EXTRA_COMMENT_ID = "extra_comment_id"
|
||||||
|
private const val EXTRA_MEMBER_ID = "extra_member_id"
|
||||||
|
private const val EXTRA_MEMBER_NICKNAME = "extra_member_nickname"
|
||||||
|
private const val EXTRA_MEMBER_PROFILE_URL = "extra_member_profile_url"
|
||||||
|
private const val EXTRA_CREATED_AT_UTC = "extra_created_at_utc"
|
||||||
|
private const val EXTRA_CREATED_AT_TEXT = "extra_created_at_text"
|
||||||
|
private const val EXTRA_COMMENT = "extra_comment"
|
||||||
|
private const val EXTRA_LIKE_COUNT = "extra_like_count"
|
||||||
|
private const val EXTRA_REPLY_COUNT = "extra_reply_count"
|
||||||
|
private const val EXTRA_IS_LIKED = "extra_is_liked"
|
||||||
|
private const val EXTRA_IS_MINE = "extra_is_mine"
|
||||||
|
private const val EXTRA_IS_CREATOR_OWNER = "extra_is_creator_owner"
|
||||||
|
|
||||||
|
fun newIntent(context: Context, postId: Long, parentComment: CreatorChannelCommunityCommentUiModel): Intent {
|
||||||
|
return Intent(context, CreatorChannelCommunityReplyActivity::class.java)
|
||||||
|
.putExtra(EXTRA_POST_ID, postId)
|
||||||
|
.putExtra(EXTRA_COMMENT_ID, parentComment.commentId)
|
||||||
|
.putExtra(EXTRA_MEMBER_ID, parentComment.memberId)
|
||||||
|
.putExtra(EXTRA_MEMBER_NICKNAME, parentComment.memberNickname)
|
||||||
|
.putExtra(EXTRA_MEMBER_PROFILE_URL, parentComment.memberProfileUrl)
|
||||||
|
.putExtra(EXTRA_CREATED_AT_UTC, parentComment.createdAtUtc)
|
||||||
|
.putExtra(EXTRA_CREATED_AT_TEXT, parentComment.createdAtText)
|
||||||
|
.putExtra(EXTRA_COMMENT, parentComment.comment)
|
||||||
|
.putExtra(EXTRA_LIKE_COUNT, parentComment.likeCount)
|
||||||
|
.putExtra(EXTRA_REPLY_COUNT, parentComment.replyCount)
|
||||||
|
.putExtra(EXTRA_IS_LIKED, parentComment.isLiked)
|
||||||
|
.putExtra(EXTRA_IS_MINE, parentComment.isMine)
|
||||||
|
.putExtra(EXTRA_IS_CREATOR_OWNER, parentComment.isCreatorOwner)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,529 @@
|
|||||||
|
package kr.co.vividnext.sodalive.v2.creator.channel.community.detail.reply
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||||
|
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||||
|
import kr.co.vividnext.sodalive.R
|
||||||
|
import kr.co.vividnext.sodalive.audio_content.comment.ModifyCommentRequest
|
||||||
|
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.explorer.profile.creator_community.CreatorCommunityRepository
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelEvent
|
||||||
|
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.data.CreatorChannelCommunityRepliesResponse
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.community.detail.data.CreatorChannelCommunityReplyResponse
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelRepository
|
||||||
|
|
||||||
|
class CreatorChannelCommunityReplyViewModel(
|
||||||
|
private val repository: CreatorChannelRepository,
|
||||||
|
private val legacyRepository: CreatorCommunityRepository,
|
||||||
|
private val relativeTimeTextFormatter: UtcRelativeTimeTextFormatter
|
||||||
|
) : BaseViewModel() {
|
||||||
|
|
||||||
|
private val _replyStateLiveData = MutableLiveData<CreatorChannelCommunityReplyUiState>()
|
||||||
|
val replyStateLiveData: LiveData<CreatorChannelCommunityReplyUiState>
|
||||||
|
get() = _replyStateLiveData
|
||||||
|
|
||||||
|
private val _replyWrittenEventLiveData = MutableLiveData<Boolean>()
|
||||||
|
val replyWrittenEventLiveData: LiveData<Boolean>
|
||||||
|
get() = _replyWrittenEventLiveData
|
||||||
|
|
||||||
|
private val _replyChangedEventLiveData = MutableLiveData<Boolean>()
|
||||||
|
val replyChangedEventLiveData: LiveData<Boolean>
|
||||||
|
get() = _replyChangedEventLiveData
|
||||||
|
|
||||||
|
private val _parentCommentChangedEventLiveData = MutableLiveData<Boolean>()
|
||||||
|
val parentCommentChangedEventLiveData: LiveData<Boolean>
|
||||||
|
get() = _parentCommentChangedEventLiveData
|
||||||
|
|
||||||
|
private val _parentCommentDeletedEventLiveData = MutableLiveData<Boolean>()
|
||||||
|
val parentCommentDeletedEventLiveData: LiveData<Boolean>
|
||||||
|
get() = _parentCommentDeletedEventLiveData
|
||||||
|
|
||||||
|
private val _toastLiveData = MutableLiveData<CreatorChannelEvent<ToastMessage>>()
|
||||||
|
val toastLiveData: LiveData<CreatorChannelEvent<ToastMessage>>
|
||||||
|
get() = _toastLiveData
|
||||||
|
|
||||||
|
private var postId: Long = 0L
|
||||||
|
private var parentComment: CreatorChannelCommunityCommentUiModel? = null
|
||||||
|
private var replyRequestGeneration = 0
|
||||||
|
private var isLoadingReplies = false
|
||||||
|
private var isSendingReply = false
|
||||||
|
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
|
||||||
|
replyRequestGeneration++
|
||||||
|
isLoadingReplies = false
|
||||||
|
_replyStateLiveData.value = CreatorChannelCommunityReplyUiState.Content(
|
||||||
|
parentComment = formattedParentComment,
|
||||||
|
replies = emptyList(),
|
||||||
|
replyPage = FIRST_PAGE,
|
||||||
|
hasNextReply = false
|
||||||
|
)
|
||||||
|
if (formattedParentComment.commentId > 0) {
|
||||||
|
loadReplyPage(page = FIRST_PAGE, append = false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadMoreReplies() {
|
||||||
|
val content = _replyStateLiveData.value as? CreatorChannelCommunityReplyUiState.Content ?: return
|
||||||
|
if (!content.hasNextReply || isLoadingReplies) return
|
||||||
|
|
||||||
|
loadReplyPage(page = content.replyPage + 1, append = true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateReplyInput(input: String) {
|
||||||
|
val content = _replyStateLiveData.value as? CreatorChannelCommunityReplyUiState.Content ?: return
|
||||||
|
_replyStateLiveData.value = content.copy(replyInput = input)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun submitReply() {
|
||||||
|
val content = _replyStateLiveData.value as? CreatorChannelCommunityReplyUiState.Content ?: return
|
||||||
|
val comment = content.replyInput.trim()
|
||||||
|
val editingTarget = content.editingTarget
|
||||||
|
if (editingTarget != null) {
|
||||||
|
submitReplyEdit(content, editingTarget, comment)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val commentId = content.parentComment.commentId
|
||||||
|
if (postId <= 0 || commentId <= 0 || comment.isBlank() || isSendingReply) return
|
||||||
|
|
||||||
|
isSendingReply = true
|
||||||
|
_replyStateLiveData.value = content.copy(isSendingReply = true)
|
||||||
|
compositeDisposable.add(
|
||||||
|
legacyRepository.registerComment(
|
||||||
|
postId = postId,
|
||||||
|
comment = comment,
|
||||||
|
parentId = commentId,
|
||||||
|
isSecret = false,
|
||||||
|
token = authToken()
|
||||||
|
)
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(
|
||||||
|
{ response -> handleWriteResponse(response) },
|
||||||
|
{
|
||||||
|
isSendingReply = false
|
||||||
|
val current = _replyStateLiveData.value as? CreatorChannelCommunityReplyUiState.Content
|
||||||
|
?: return@subscribe
|
||||||
|
showMutationFailureToast()
|
||||||
|
_replyStateLiveData.value = current.copy(isSendingReply = false)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun consumeReplyWrittenEvent() {
|
||||||
|
_replyWrittenEventLiveData.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun consumeReplyChangedEvent() {
|
||||||
|
_replyChangedEventLiveData.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun consumeParentCommentChangedEvent() {
|
||||||
|
_parentCommentChangedEventLiveData.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun consumeParentCommentDeletedEvent() {
|
||||||
|
_parentCommentDeletedEventLiveData.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun startReplyEdit(replyId: Long, comment: String) {
|
||||||
|
val content = _replyStateLiveData.value as? CreatorChannelCommunityReplyUiState.Content ?: return
|
||||||
|
if (replyId <= 0) return
|
||||||
|
|
||||||
|
_replyStateLiveData.value = content.copy(
|
||||||
|
replyInput = comment,
|
||||||
|
editingTarget = CreatorChannelCommunityReplyEditTarget.Reply(replyId, comment)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun startParentCommentEdit(comment: String) {
|
||||||
|
val content = _replyStateLiveData.value as? CreatorChannelCommunityReplyUiState.Content ?: return
|
||||||
|
if (content.parentComment.commentId <= 0) return
|
||||||
|
|
||||||
|
_replyStateLiveData.value = content.copy(
|
||||||
|
replyInput = comment,
|
||||||
|
editingTarget = CreatorChannelCommunityReplyEditTarget.ParentComment(comment)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun cancelReplyEdit() {
|
||||||
|
val content = _replyStateLiveData.value as? CreatorChannelCommunityReplyUiState.Content ?: return
|
||||||
|
_replyStateLiveData.value = content.copy(replyInput = "", editingTarget = null)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun modifyReply(replyId: Long, comment: String) {
|
||||||
|
val trimmedComment = comment.trim()
|
||||||
|
if (replyId <= 0 || isModifyingReply) return
|
||||||
|
if (trimmedComment.isBlank()) {
|
||||||
|
showBlankCommentToast()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
modifyComment(ModifyCommentRequest(commentId = replyId, comment = trimmedComment))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun deleteReply(replyId: Long) {
|
||||||
|
if (replyId <= 0 || isModifyingReply) return
|
||||||
|
|
||||||
|
modifyComment(ModifyCommentRequest(commentId = replyId, isActive = false))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun modifyParentComment(comment: String) {
|
||||||
|
val parentComment = parentComment ?: return
|
||||||
|
val trimmedComment = comment.trim()
|
||||||
|
if (parentComment.commentId <= 0 || isModifyingReply) return
|
||||||
|
if (trimmedComment.isBlank()) {
|
||||||
|
showBlankCommentToast()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
modifyParentComment(parentComment, trimmedComment)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun deleteParentComment() {
|
||||||
|
val parentComment = parentComment ?: return
|
||||||
|
if (parentComment.commentId <= 0 || isModifyingReply) return
|
||||||
|
|
||||||
|
deleteParentComment(parentComment.commentId)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun submitReplyEdit(
|
||||||
|
content: CreatorChannelCommunityReplyUiState.Content,
|
||||||
|
target: CreatorChannelCommunityReplyEditTarget,
|
||||||
|
comment: String
|
||||||
|
) {
|
||||||
|
if (isModifyingReply) return
|
||||||
|
if (comment.isBlank()) {
|
||||||
|
showBlankCommentToast()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (comment == target.originalComment.trim()) {
|
||||||
|
showNoChangeToast()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
when (target) {
|
||||||
|
is CreatorChannelCommunityReplyEditTarget.ParentComment -> modifyParentCommentFromInput(content, comment)
|
||||||
|
is CreatorChannelCommunityReplyEditTarget.Reply -> modifyReplyFromInput(content, target.replyId, comment)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun modifyReplyFromInput(
|
||||||
|
content: CreatorChannelCommunityReplyUiState.Content,
|
||||||
|
replyId: Long,
|
||||||
|
comment: String
|
||||||
|
) {
|
||||||
|
isModifyingReply = true
|
||||||
|
_replyStateLiveData.value = content.copy(isSendingReply = true)
|
||||||
|
compositeDisposable.add(
|
||||||
|
legacyRepository.modifyComment(ModifyCommentRequest(commentId = replyId, comment = comment), authToken())
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(
|
||||||
|
{ response ->
|
||||||
|
isModifyingReply = false
|
||||||
|
val current = _replyStateLiveData.value as? CreatorChannelCommunityReplyUiState.Content
|
||||||
|
?: return@subscribe
|
||||||
|
if (response.success) {
|
||||||
|
_replyChangedEventLiveData.value = true
|
||||||
|
_replyStateLiveData.value = current.copy(
|
||||||
|
replyInput = "",
|
||||||
|
isSendingReply = false,
|
||||||
|
editingTarget = null
|
||||||
|
)
|
||||||
|
loadReplyPage(page = FIRST_PAGE, append = false)
|
||||||
|
} else {
|
||||||
|
showMutationFailureToast(response.message)
|
||||||
|
_replyStateLiveData.value = current.copy(isSendingReply = false)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
isModifyingReply = false
|
||||||
|
val current = _replyStateLiveData.value as? CreatorChannelCommunityReplyUiState.Content
|
||||||
|
?: return@subscribe
|
||||||
|
showMutationFailureToast()
|
||||||
|
_replyStateLiveData.value = current.copy(isSendingReply = false)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun modifyParentCommentFromInput(
|
||||||
|
content: CreatorChannelCommunityReplyUiState.Content,
|
||||||
|
comment: String
|
||||||
|
) {
|
||||||
|
val parentComment = parentComment ?: return
|
||||||
|
isModifyingReply = true
|
||||||
|
_replyStateLiveData.value = content.copy(isSendingReply = true)
|
||||||
|
compositeDisposable.add(
|
||||||
|
legacyRepository.modifyComment(
|
||||||
|
ModifyCommentRequest(commentId = parentComment.commentId, comment = comment),
|
||||||
|
authToken()
|
||||||
|
)
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(
|
||||||
|
{ response ->
|
||||||
|
isModifyingReply = false
|
||||||
|
val current = _replyStateLiveData.value as? CreatorChannelCommunityReplyUiState.Content
|
||||||
|
?: return@subscribe
|
||||||
|
if (response.success) {
|
||||||
|
val updatedParent = parentComment.copy(comment = comment)
|
||||||
|
this.parentComment = updatedParent
|
||||||
|
_replyStateLiveData.value = current.copy(
|
||||||
|
parentComment = updatedParent,
|
||||||
|
replyInput = "",
|
||||||
|
isSendingReply = false,
|
||||||
|
editingTarget = null
|
||||||
|
)
|
||||||
|
_parentCommentChangedEventLiveData.value = true
|
||||||
|
} else {
|
||||||
|
showMutationFailureToast(response.message)
|
||||||
|
_replyStateLiveData.value = current.copy(isSendingReply = false)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
isModifyingReply = false
|
||||||
|
val current = _replyStateLiveData.value as? CreatorChannelCommunityReplyUiState.Content
|
||||||
|
?: return@subscribe
|
||||||
|
showMutationFailureToast()
|
||||||
|
_replyStateLiveData.value = current.copy(isSendingReply = false)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun modifyParentComment(parentComment: CreatorChannelCommunityCommentUiModel, comment: String) {
|
||||||
|
isModifyingReply = true
|
||||||
|
compositeDisposable.add(
|
||||||
|
legacyRepository.modifyComment(
|
||||||
|
ModifyCommentRequest(commentId = parentComment.commentId, comment = comment),
|
||||||
|
authToken()
|
||||||
|
)
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(
|
||||||
|
{ response ->
|
||||||
|
isModifyingReply = false
|
||||||
|
if (response.success) {
|
||||||
|
val updatedParent = parentComment.copy(comment = comment)
|
||||||
|
this.parentComment = updatedParent
|
||||||
|
val current = _replyStateLiveData.value as? CreatorChannelCommunityReplyUiState.Content
|
||||||
|
?: return@subscribe
|
||||||
|
_replyStateLiveData.value = current.copy(parentComment = updatedParent)
|
||||||
|
_parentCommentChangedEventLiveData.value = true
|
||||||
|
} else {
|
||||||
|
showMutationFailureToast(response.message)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
isModifyingReply = false
|
||||||
|
showMutationFailureToast()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun deleteParentComment(commentId: Long) {
|
||||||
|
isModifyingReply = true
|
||||||
|
compositeDisposable.add(
|
||||||
|
legacyRepository.modifyComment(ModifyCommentRequest(commentId = commentId, isActive = false), authToken())
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(
|
||||||
|
{ response ->
|
||||||
|
isModifyingReply = false
|
||||||
|
if (response.success) {
|
||||||
|
_parentCommentDeletedEventLiveData.value = true
|
||||||
|
} else {
|
||||||
|
showMutationFailureToast(response.message)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
isModifyingReply = false
|
||||||
|
showMutationFailureToast()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadReplyPage(page: Int, append: Boolean) {
|
||||||
|
val commentId = parentComment?.commentId ?: return
|
||||||
|
if (commentId <= 0 || isLoadingReplies && append) return
|
||||||
|
val generation = if (append) {
|
||||||
|
replyRequestGeneration
|
||||||
|
} else {
|
||||||
|
++replyRequestGeneration
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoadingReplies = true
|
||||||
|
val beforeRequest = _replyStateLiveData.value as? CreatorChannelCommunityReplyUiState.Content
|
||||||
|
if (beforeRequest != null) {
|
||||||
|
_replyStateLiveData.value = beforeRequest.copy(isLoadingReplies = true)
|
||||||
|
}
|
||||||
|
compositeDisposable.add(
|
||||||
|
repository.getCommunityCommentReplies(commentId, page, DEFAULT_PAGE_SIZE, authToken())
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(
|
||||||
|
{ response -> handleRepliesResponse(response, append, generation) },
|
||||||
|
{
|
||||||
|
if (generation != replyRequestGeneration) return@subscribe
|
||||||
|
|
||||||
|
isLoadingReplies = false
|
||||||
|
val current = _replyStateLiveData.value as? CreatorChannelCommunityReplyUiState.Content
|
||||||
|
?: return@subscribe
|
||||||
|
_replyStateLiveData.value = current.copy(isLoadingReplies = false)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleRepliesResponse(
|
||||||
|
response: ApiResponse<CreatorChannelCommunityRepliesResponse>,
|
||||||
|
append: Boolean,
|
||||||
|
generation: Int
|
||||||
|
) {
|
||||||
|
if (generation != replyRequestGeneration) return
|
||||||
|
|
||||||
|
isLoadingReplies = false
|
||||||
|
val current = _replyStateLiveData.value as? CreatorChannelCommunityReplyUiState.Content ?: return
|
||||||
|
val data = response.data
|
||||||
|
if (!response.success || data == null) {
|
||||||
|
_replyStateLiveData.value = current.copy(isLoadingReplies = false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val nextReplies = data.replies.map { it.toUiModel() }
|
||||||
|
_replyStateLiveData.value = current.copy(
|
||||||
|
replies = if (append) current.replies + nextReplies else nextReplies,
|
||||||
|
replyPage = data.page,
|
||||||
|
hasNextReply = data.hasNext,
|
||||||
|
isLoadingReplies = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleWriteResponse(response: ApiResponse<Any>) {
|
||||||
|
isSendingReply = false
|
||||||
|
val current = _replyStateLiveData.value as? CreatorChannelCommunityReplyUiState.Content ?: return
|
||||||
|
if (response.success) {
|
||||||
|
_replyWrittenEventLiveData.value = true
|
||||||
|
_replyChangedEventLiveData.value = true
|
||||||
|
_replyStateLiveData.value = current.copy(replyInput = "", isSendingReply = false)
|
||||||
|
loadReplyPage(page = FIRST_PAGE, append = false)
|
||||||
|
} else {
|
||||||
|
showMutationFailureToast(response.message)
|
||||||
|
_replyStateLiveData.value = current.copy(isSendingReply = false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun modifyComment(request: ModifyCommentRequest) {
|
||||||
|
val commentId = parentComment?.commentId ?: return
|
||||||
|
if (commentId <= 0) return
|
||||||
|
|
||||||
|
isModifyingReply = true
|
||||||
|
compositeDisposable.add(
|
||||||
|
legacyRepository.modifyComment(request, authToken())
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(
|
||||||
|
{ response ->
|
||||||
|
isModifyingReply = false
|
||||||
|
if (response.success) {
|
||||||
|
_replyChangedEventLiveData.value = true
|
||||||
|
loadReplyPage(page = FIRST_PAGE, append = false)
|
||||||
|
} else {
|
||||||
|
showMutationFailureToast(response.message)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
isModifyingReply = false
|
||||||
|
showMutationFailureToast()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showMutationFailureToast(message: String? = null) {
|
||||||
|
_toastLiveData.value = CreatorChannelEvent(
|
||||||
|
if (message.isNullOrBlank()) {
|
||||||
|
ToastMessage(resId = R.string.common_error_unknown)
|
||||||
|
} else {
|
||||||
|
ToastMessage(message = message)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showBlankCommentToast() {
|
||||||
|
_toastLiveData.value = CreatorChannelEvent(
|
||||||
|
ToastMessage(resId = R.string.screen_creator_community_write_content_hint)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showNoChangeToast() {
|
||||||
|
_toastLiveData.value = CreatorChannelEvent(
|
||||||
|
ToastMessage(resId = R.string.audio_content_comment_no_change)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun CreatorChannelCommunityReplyResponse.toUiModel() = CreatorChannelCommunityReplyUiModel(
|
||||||
|
replyId = commentId,
|
||||||
|
commentId = parentComment?.commentId ?: 0L,
|
||||||
|
memberId = writerId,
|
||||||
|
memberNickname = writerNickname,
|
||||||
|
memberProfileUrl = writerProfileImageUrl,
|
||||||
|
createdAtUtc = createdAtUtc,
|
||||||
|
createdAtText = relativeTimeTextFormatter.format(createdAtUtc),
|
||||||
|
comment = content,
|
||||||
|
likeCount = 0,
|
||||||
|
isLiked = false,
|
||||||
|
isMine = writerId == SharedPreferenceManager.userId,
|
||||||
|
isCreatorOwner = parentComment?.isCreatorOwner == true
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun authToken(): String = "Bearer ${SharedPreferenceManager.token}"
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val DEFAULT_PAGE_SIZE = 20
|
||||||
|
private const val FIRST_PAGE = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed interface CreatorChannelCommunityReplyEditTarget {
|
||||||
|
val originalComment: String
|
||||||
|
|
||||||
|
data class ParentComment(
|
||||||
|
override val originalComment: String
|
||||||
|
) : CreatorChannelCommunityReplyEditTarget
|
||||||
|
|
||||||
|
data class Reply(
|
||||||
|
val replyId: Long,
|
||||||
|
override val originalComment: String
|
||||||
|
) : CreatorChannelCommunityReplyEditTarget
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed interface CreatorChannelCommunityReplyUiState {
|
||||||
|
data class Content(
|
||||||
|
val parentComment: CreatorChannelCommunityCommentUiModel,
|
||||||
|
val replies: List<CreatorChannelCommunityReplyUiModel>,
|
||||||
|
val replyPage: Int,
|
||||||
|
val hasNextReply: Boolean,
|
||||||
|
val isLoadingReplies: Boolean = false,
|
||||||
|
val replyInput: String = "",
|
||||||
|
val isSendingReply: Boolean = false,
|
||||||
|
val editingTarget: CreatorChannelCommunityReplyEditTarget? = null
|
||||||
|
) : CreatorChannelCommunityReplyUiState
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
package kr.co.vividnext.sodalive.v2.creator.channel.community.detail.reply.ui
|
||||||
|
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
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.databinding.ItemCreatorChannelCommunityReplyBinding
|
||||||
|
import kr.co.vividnext.sodalive.extensions.loadUrl
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.community.detail.CreatorChannelCommunityReplyUiModel
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.community.detail.ui.CreatorChannelCommunityMorePopup
|
||||||
|
|
||||||
|
class CreatorChannelCommunityReplyAdapter(
|
||||||
|
private val onModifyClick: (CreatorChannelCommunityReplyUiModel) -> Unit,
|
||||||
|
private val onDeleteClick: (CreatorChannelCommunityReplyUiModel) -> Unit
|
||||||
|
) : RecyclerView.Adapter<CreatorChannelCommunityReplyAdapter.ViewHolder>() {
|
||||||
|
|
||||||
|
private var items: List<CreatorChannelCommunityReplyUiModel> = emptyList()
|
||||||
|
|
||||||
|
fun submitItems(items: List<CreatorChannelCommunityReplyUiModel>) {
|
||||||
|
this.items = items
|
||||||
|
notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||||
|
return ViewHolder(
|
||||||
|
ItemCreatorChannelCommunityReplyBinding.inflate(LayoutInflater.from(parent.context), parent, false),
|
||||||
|
onModifyClick,
|
||||||
|
onDeleteClick
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
|
holder.bind(items[position])
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount(): Int = items.size
|
||||||
|
|
||||||
|
class ViewHolder(
|
||||||
|
private val binding: ItemCreatorChannelCommunityReplyBinding,
|
||||||
|
private val onModifyClick: (CreatorChannelCommunityReplyUiModel) -> Unit,
|
||||||
|
private val onDeleteClick: (CreatorChannelCommunityReplyUiModel) -> Unit
|
||||||
|
) : RecyclerView.ViewHolder(binding.root) {
|
||||||
|
|
||||||
|
fun bind(item: CreatorChannelCommunityReplyUiModel) = with(binding) {
|
||||||
|
ivCreatorChannelCommunityReplyItemProfile.loadUrl(item.memberProfileUrl) {
|
||||||
|
crossfade(true)
|
||||||
|
placeholder(R.drawable.bg_placeholder)
|
||||||
|
transformations(CircleCropTransformation())
|
||||||
|
}
|
||||||
|
tvCreatorChannelCommunityReplyItemNickname.text = item.memberNickname
|
||||||
|
tvCreatorChannelCommunityReplyItemTime.text = item.createdAtText
|
||||||
|
tvCreatorChannelCommunityReplyItemBody.text = item.comment
|
||||||
|
ivCreatorChannelCommunityReplyItemMore.isVisible = item.canShowMore
|
||||||
|
ivCreatorChannelCommunityReplyItemMore.setOnClickListener { anchor ->
|
||||||
|
showOptionMenu(anchor, item, onModifyClick, onDeleteClick)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showOptionMenu(
|
||||||
|
anchor: View,
|
||||||
|
item: CreatorChannelCommunityReplyUiModel,
|
||||||
|
onModifyClick: (CreatorChannelCommunityReplyUiModel) -> Unit,
|
||||||
|
onDeleteClick: (CreatorChannelCommunityReplyUiModel) -> Unit
|
||||||
|
) {
|
||||||
|
if (!item.canShowMore) return
|
||||||
|
CreatorChannelCommunityMorePopup(
|
||||||
|
anchor = anchor,
|
||||||
|
showEdit = item.isMine,
|
||||||
|
showDelete = true,
|
||||||
|
onEditClick = { onModifyClick(item) },
|
||||||
|
onDeleteClick = { onDeleteClick(item) }
|
||||||
|
).show(anchor)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val CreatorChannelCommunityReplyUiModel.canShowMore: Boolean
|
||||||
|
get() = isMine || isCreatorOwner
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
package kr.co.vividnext.sodalive.v2.creator.channel.community.detail.ui
|
||||||
|
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
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.databinding.ItemCreatorChannelCommunityCommentBinding
|
||||||
|
import kr.co.vividnext.sodalive.extensions.loadUrl
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.community.detail.CreatorChannelCommunityCommentUiModel
|
||||||
|
|
||||||
|
class CreatorChannelCommunityCommentAdapter(
|
||||||
|
private val onReplyClick: (CreatorChannelCommunityCommentUiModel) -> Unit,
|
||||||
|
private val onModifyClick: (CreatorChannelCommunityCommentUiModel) -> Unit = {},
|
||||||
|
private val onDeleteClick: (CreatorChannelCommunityCommentUiModel) -> Unit = {}
|
||||||
|
) : RecyclerView.Adapter<CreatorChannelCommunityCommentAdapter.ViewHolder>() {
|
||||||
|
|
||||||
|
private var items: List<CreatorChannelCommunityCommentUiModel> = emptyList()
|
||||||
|
|
||||||
|
fun submitItems(items: List<CreatorChannelCommunityCommentUiModel>) {
|
||||||
|
this.items = items
|
||||||
|
notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||||
|
return ViewHolder(
|
||||||
|
ItemCreatorChannelCommunityCommentBinding.inflate(LayoutInflater.from(parent.context), parent, false),
|
||||||
|
onReplyClick,
|
||||||
|
onModifyClick,
|
||||||
|
onDeleteClick
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
|
holder.bind(items[position])
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount(): Int = items.size
|
||||||
|
|
||||||
|
class ViewHolder(
|
||||||
|
private val binding: ItemCreatorChannelCommunityCommentBinding,
|
||||||
|
private val onReplyClick: (CreatorChannelCommunityCommentUiModel) -> Unit,
|
||||||
|
private val onModifyClick: (CreatorChannelCommunityCommentUiModel) -> Unit,
|
||||||
|
private val onDeleteClick: (CreatorChannelCommunityCommentUiModel) -> Unit
|
||||||
|
) : RecyclerView.ViewHolder(binding.root) {
|
||||||
|
|
||||||
|
fun bind(item: CreatorChannelCommunityCommentUiModel) {
|
||||||
|
bindComment(binding, item, onReplyClick)
|
||||||
|
binding.ivCreatorChannelCommunityCommentMore.isVisible = item.canShowMore
|
||||||
|
binding.ivCreatorChannelCommunityCommentMore.setOnClickListener { anchor ->
|
||||||
|
showOptionMenu(anchor, item, onModifyClick, onDeleteClick)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun bindComment(
|
||||||
|
binding: ItemCreatorChannelCommunityCommentBinding,
|
||||||
|
item: CreatorChannelCommunityCommentUiModel,
|
||||||
|
onReplyClick: (CreatorChannelCommunityCommentUiModel) -> Unit,
|
||||||
|
onModifyClick: ((CreatorChannelCommunityCommentUiModel) -> Unit)? = null,
|
||||||
|
onDeleteClick: ((CreatorChannelCommunityCommentUiModel) -> Unit)? = null
|
||||||
|
) = with(binding) {
|
||||||
|
ivCreatorChannelCommunityCommentProfile.loadUrl(item.memberProfileUrl) {
|
||||||
|
crossfade(true)
|
||||||
|
placeholder(R.drawable.bg_placeholder)
|
||||||
|
transformations(CircleCropTransformation())
|
||||||
|
}
|
||||||
|
tvCreatorChannelCommunityCommentNickname.text = item.memberNickname
|
||||||
|
tvCreatorChannelCommunityCommentTime.text = item.createdAtText
|
||||||
|
tvCreatorChannelCommunityCommentBody.text = item.comment
|
||||||
|
ivCreatorChannelCommunityCommentMore.isVisible = item.canShowMore && onDeleteClick != null
|
||||||
|
ivCreatorChannelCommunityCommentMore.setOnClickListener { anchor ->
|
||||||
|
showOptionMenu(
|
||||||
|
anchor,
|
||||||
|
item,
|
||||||
|
onModifyClick ?: {},
|
||||||
|
onDeleteClick ?: {}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
layoutCreatorChannelCommunityCommentLatestReply.isVisible = item.latestReply != null
|
||||||
|
item.latestReply?.let { latestReply ->
|
||||||
|
tvCreatorChannelCommunityCommentLatestReplyNickname.text = latestReply.memberNickname
|
||||||
|
tvCreatorChannelCommunityCommentLatestReplyBody.text = latestReply.comment
|
||||||
|
}
|
||||||
|
root.setOnClickListener { onReplyClick(item) }
|
||||||
|
layoutCreatorChannelCommunityCommentLatestReply.setOnClickListener { onReplyClick(item) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showOptionMenu(
|
||||||
|
anchor: View,
|
||||||
|
item: CreatorChannelCommunityCommentUiModel,
|
||||||
|
onModifyClick: (CreatorChannelCommunityCommentUiModel) -> Unit,
|
||||||
|
onDeleteClick: (CreatorChannelCommunityCommentUiModel) -> Unit
|
||||||
|
) {
|
||||||
|
if (!item.canShowMore) return
|
||||||
|
CreatorChannelCommunityMorePopup(
|
||||||
|
anchor = anchor,
|
||||||
|
showEdit = item.isMine,
|
||||||
|
showDelete = true,
|
||||||
|
onEditClick = { onModifyClick(item) },
|
||||||
|
onDeleteClick = { onDeleteClick(item) }
|
||||||
|
).show(anchor)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val CreatorChannelCommunityCommentUiModel.canShowMore: Boolean
|
||||||
|
get() = isMine || isCreatorOwner
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package kr.co.vividnext.sodalive.v2.creator.channel.community.detail.ui
|
||||||
|
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.PopupWindow
|
||||||
|
import androidx.core.graphics.drawable.toDrawable
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import kr.co.vividnext.sodalive.databinding.ViewCreatorChannelCommunityMorePopupBinding
|
||||||
|
|
||||||
|
class CreatorChannelCommunityMorePopup(
|
||||||
|
anchor: View,
|
||||||
|
showEdit: Boolean,
|
||||||
|
showDelete: Boolean,
|
||||||
|
onEditClick: () -> Unit,
|
||||||
|
onDeleteClick: () -> Unit
|
||||||
|
) {
|
||||||
|
|
||||||
|
private val binding = ViewCreatorChannelCommunityMorePopupBinding.inflate(LayoutInflater.from(anchor.context))
|
||||||
|
private val popupWindow: PopupWindow = PopupWindow(anchor.context).apply {
|
||||||
|
contentView = binding.root
|
||||||
|
width = ViewGroup.LayoutParams.WRAP_CONTENT
|
||||||
|
height = ViewGroup.LayoutParams.WRAP_CONTENT
|
||||||
|
isOutsideTouchable = true
|
||||||
|
isFocusable = true
|
||||||
|
setBackgroundDrawable(Color.TRANSPARENT.toDrawable())
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
binding.tvCreatorChannelCommunityMoreEdit.isVisible = showEdit
|
||||||
|
binding.tvCreatorChannelCommunityMoreDelete.isVisible = showDelete
|
||||||
|
binding.tvCreatorChannelCommunityMoreEdit.setOnClickListener {
|
||||||
|
dismiss()
|
||||||
|
onEditClick()
|
||||||
|
}
|
||||||
|
binding.tvCreatorChannelCommunityMoreDelete.setOnClickListener {
|
||||||
|
dismiss()
|
||||||
|
onDeleteClick()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun show(anchor: View) {
|
||||||
|
popupWindow.showAsDropDown(anchor)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun dismiss() {
|
||||||
|
popupWindow.dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,7 +13,9 @@ import kr.co.vividnext.sodalive.extensions.moneyFormat
|
|||||||
import kr.co.vividnext.sodalive.v2.creator.channel.community.model.CreatorChannelCommunityImageMode
|
import kr.co.vividnext.sodalive.v2.creator.channel.community.model.CreatorChannelCommunityImageMode
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.community.model.CreatorChannelCommunityPostUiModel
|
import kr.co.vividnext.sodalive.v2.creator.channel.community.model.CreatorChannelCommunityPostUiModel
|
||||||
|
|
||||||
class CreatorChannelCommunityGridAdapter : RecyclerView.Adapter<CreatorChannelCommunityGridAdapter.ViewHolder>() {
|
class CreatorChannelCommunityGridAdapter(
|
||||||
|
private val onPostClick: (CreatorChannelCommunityPostUiModel) -> Unit = {}
|
||||||
|
) : RecyclerView.Adapter<CreatorChannelCommunityGridAdapter.ViewHolder>() {
|
||||||
|
|
||||||
private var items: List<CreatorChannelCommunityPostUiModel> = emptyList()
|
private var items: List<CreatorChannelCommunityPostUiModel> = emptyList()
|
||||||
private var itemSizePx: Int = 0
|
private var itemSizePx: Int = 0
|
||||||
@@ -31,7 +33,10 @@ class CreatorChannelCommunityGridAdapter : RecyclerView.Adapter<CreatorChannelCo
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||||
return ViewHolder(ItemCreatorChannelCommunityGridBinding.inflate(LayoutInflater.from(parent.context), parent, false))
|
return ViewHolder(
|
||||||
|
ItemCreatorChannelCommunityGridBinding.inflate(LayoutInflater.from(parent.context), parent, false),
|
||||||
|
onPostClick
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
@@ -41,10 +46,12 @@ class CreatorChannelCommunityGridAdapter : RecyclerView.Adapter<CreatorChannelCo
|
|||||||
override fun getItemCount(): Int = items.size
|
override fun getItemCount(): Int = items.size
|
||||||
|
|
||||||
class ViewHolder(
|
class ViewHolder(
|
||||||
private val binding: ItemCreatorChannelCommunityGridBinding
|
private val binding: ItemCreatorChannelCommunityGridBinding,
|
||||||
|
private val onPostClick: (CreatorChannelCommunityPostUiModel) -> Unit
|
||||||
) : RecyclerView.ViewHolder(binding.root) {
|
) : RecyclerView.ViewHolder(binding.root) {
|
||||||
|
|
||||||
fun bind(item: CreatorChannelCommunityPostUiModel, itemSizePx: Int) = with(binding) {
|
fun bind(item: CreatorChannelCommunityPostUiModel, itemSizePx: Int) = with(binding) {
|
||||||
|
root.setOnClickListener { onPostClick(item) }
|
||||||
if (itemSizePx > 0) {
|
if (itemSizePx > 0) {
|
||||||
root.layoutParams = root.layoutParams.apply {
|
root.layoutParams = root.layoutParams.apply {
|
||||||
width = itemSizePx
|
width = itemSizePx
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import kr.co.vividnext.sodalive.extensions.moneyFormat
|
|||||||
import kr.co.vividnext.sodalive.v2.creator.channel.community.model.CreatorChannelCommunityPostUiModel
|
import kr.co.vividnext.sodalive.v2.creator.channel.community.model.CreatorChannelCommunityPostUiModel
|
||||||
|
|
||||||
class CreatorChannelCommunityListAdapter(
|
class CreatorChannelCommunityListAdapter(
|
||||||
|
private val onPostClick: (CreatorChannelCommunityPostUiModel) -> Unit = {},
|
||||||
private val onPlayClick: (CreatorChannelCommunityPostUiModel) -> Unit = {},
|
private val onPlayClick: (CreatorChannelCommunityPostUiModel) -> Unit = {},
|
||||||
private val onOwnerMoreClick: (CreatorChannelCommunityPostUiModel) -> Unit = {},
|
private val onOwnerMoreClick: (CreatorChannelCommunityPostUiModel) -> Unit = {},
|
||||||
private val isPlayingContent: (Long) -> Boolean = { false }
|
private val isPlayingContent: (Long) -> Boolean = { false }
|
||||||
@@ -32,6 +33,7 @@ class CreatorChannelCommunityListAdapter(
|
|||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||||
return ViewHolder(
|
return ViewHolder(
|
||||||
ItemCreatorChannelCommunityListBinding.inflate(LayoutInflater.from(parent.context), parent, false),
|
ItemCreatorChannelCommunityListBinding.inflate(LayoutInflater.from(parent.context), parent, false),
|
||||||
|
onPostClick,
|
||||||
onPlayClick,
|
onPlayClick,
|
||||||
onOwnerMoreClick,
|
onOwnerMoreClick,
|
||||||
isPlayingContent
|
isPlayingContent
|
||||||
@@ -46,12 +48,14 @@ class CreatorChannelCommunityListAdapter(
|
|||||||
|
|
||||||
class ViewHolder(
|
class ViewHolder(
|
||||||
private val binding: ItemCreatorChannelCommunityListBinding,
|
private val binding: ItemCreatorChannelCommunityListBinding,
|
||||||
|
private val onPostClick: (CreatorChannelCommunityPostUiModel) -> Unit,
|
||||||
private val onPlayClick: (CreatorChannelCommunityPostUiModel) -> Unit,
|
private val onPlayClick: (CreatorChannelCommunityPostUiModel) -> Unit,
|
||||||
private val onOwnerMoreClick: (CreatorChannelCommunityPostUiModel) -> Unit,
|
private val onOwnerMoreClick: (CreatorChannelCommunityPostUiModel) -> Unit,
|
||||||
private val isPlayingContent: (Long) -> Boolean
|
private val isPlayingContent: (Long) -> Boolean
|
||||||
) : RecyclerView.ViewHolder(binding.root) {
|
) : RecyclerView.ViewHolder(binding.root) {
|
||||||
|
|
||||||
fun bind(item: CreatorChannelCommunityPostUiModel) = with(binding) {
|
fun bind(item: CreatorChannelCommunityPostUiModel) = with(binding) {
|
||||||
|
root.setOnClickListener { onPostClick(item) }
|
||||||
ivCreatorChannelCommunityListProfile.loadUrl(item.creatorProfileUrl) {
|
ivCreatorChannelCommunityListProfile.loadUrl(item.creatorProfileUrl) {
|
||||||
crossfade(true)
|
crossfade(true)
|
||||||
placeholder(R.drawable.bg_placeholder)
|
placeholder(R.drawable.bg_placeholder)
|
||||||
|
|||||||
@@ -2,9 +2,11 @@ package kr.co.vividnext.sodalive.v2.creator.channel.data
|
|||||||
|
|
||||||
import io.reactivex.rxjava3.core.Single
|
import io.reactivex.rxjava3.core.Single
|
||||||
import kr.co.vividnext.sodalive.common.ApiResponse
|
import kr.co.vividnext.sodalive.common.ApiResponse
|
||||||
import kr.co.vividnext.sodalive.v2.common.data.ContentSort
|
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.audio.data.CreatorChannelAudioTabResponse
|
import kr.co.vividnext.sodalive.v2.creator.channel.audio.data.CreatorChannelAudioTabResponse
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.community.data.CreatorChannelCommunityTabResponse
|
import kr.co.vividnext.sodalive.v2.creator.channel.community.data.CreatorChannelCommunityTabResponse
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.community.detail.data.CreatorChannelCommunityCommentsResponse
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.community.detail.data.CreatorChannelCommunityPostDetailResponse
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.community.detail.data.CreatorChannelCommunityRepliesResponse
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.donation.data.CreatorChannelDonationTabResponse
|
import kr.co.vividnext.sodalive.v2.creator.channel.donation.data.CreatorChannelDonationTabResponse
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.fantalk.data.CreatorChannelFanTalkTabResponse
|
import kr.co.vividnext.sodalive.v2.creator.channel.fantalk.data.CreatorChannelFanTalkTabResponse
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.live.data.CreatorChannelLiveTabResponse
|
import kr.co.vividnext.sodalive.v2.creator.channel.live.data.CreatorChannelLiveTabResponse
|
||||||
@@ -26,7 +28,7 @@ interface CreatorChannelApi {
|
|||||||
@Path("creatorId") creatorId: Long,
|
@Path("creatorId") creatorId: Long,
|
||||||
@Query("page") page: Int,
|
@Query("page") page: Int,
|
||||||
@Query("size") size: Int,
|
@Query("size") size: Int,
|
||||||
@Query("sort") sort: ContentSort,
|
@Query("sort") sort: String,
|
||||||
@Header("Authorization") authHeader: String
|
@Header("Authorization") authHeader: String
|
||||||
): Single<ApiResponse<CreatorChannelLiveTabResponse>>
|
): Single<ApiResponse<CreatorChannelLiveTabResponse>>
|
||||||
|
|
||||||
@@ -35,7 +37,7 @@ interface CreatorChannelApi {
|
|||||||
@Path("creatorId") creatorId: Long,
|
@Path("creatorId") creatorId: Long,
|
||||||
@Query("page") page: Int,
|
@Query("page") page: Int,
|
||||||
@Query("size") size: Int,
|
@Query("size") size: Int,
|
||||||
@Query("sort") sort: ContentSort,
|
@Query("sort") sort: String,
|
||||||
@Query("themeId") themeId: Long?,
|
@Query("themeId") themeId: Long?,
|
||||||
@Header("Authorization") authHeader: String
|
@Header("Authorization") authHeader: String
|
||||||
): Single<ApiResponse<CreatorChannelAudioTabResponse>>
|
): Single<ApiResponse<CreatorChannelAudioTabResponse>>
|
||||||
@@ -45,7 +47,7 @@ interface CreatorChannelApi {
|
|||||||
@Path("creatorId") creatorId: Long,
|
@Path("creatorId") creatorId: Long,
|
||||||
@Query("page") page: Int,
|
@Query("page") page: Int,
|
||||||
@Query("size") size: Int,
|
@Query("size") size: Int,
|
||||||
@Query("sort") sort: ContentSort,
|
@Query("sort") sort: String,
|
||||||
@Header("Authorization") authHeader: String
|
@Header("Authorization") authHeader: String
|
||||||
): Single<ApiResponse<CreatorChannelSeriesTabResponse>>
|
): Single<ApiResponse<CreatorChannelSeriesTabResponse>>
|
||||||
|
|
||||||
@@ -57,6 +59,28 @@ interface CreatorChannelApi {
|
|||||||
@Header("Authorization") authHeader: String
|
@Header("Authorization") authHeader: String
|
||||||
): Single<ApiResponse<CreatorChannelCommunityTabResponse>>
|
): Single<ApiResponse<CreatorChannelCommunityTabResponse>>
|
||||||
|
|
||||||
|
@GET("/api/v2/creator-channels/community-posts/{postId}")
|
||||||
|
fun getCommunityPostDetail(
|
||||||
|
@Path("postId") postId: Long,
|
||||||
|
@Header("Authorization") authHeader: String
|
||||||
|
): Single<ApiResponse<CreatorChannelCommunityPostDetailResponse>>
|
||||||
|
|
||||||
|
@GET("/api/v2/creator-channels/community-posts/{postId}/comments")
|
||||||
|
fun getCommunityPostComments(
|
||||||
|
@Path("postId") postId: Long,
|
||||||
|
@Query("page") page: Int,
|
||||||
|
@Query("size") size: Int,
|
||||||
|
@Header("Authorization") authHeader: String
|
||||||
|
): Single<ApiResponse<CreatorChannelCommunityCommentsResponse>>
|
||||||
|
|
||||||
|
@GET("/api/v2/creator-channels/community-comments/{commentId}/replies")
|
||||||
|
fun getCommunityCommentReplies(
|
||||||
|
@Path("commentId") commentId: Long,
|
||||||
|
@Query("page") page: Int,
|
||||||
|
@Query("size") size: Int,
|
||||||
|
@Header("Authorization") authHeader: String
|
||||||
|
): Single<ApiResponse<CreatorChannelCommunityRepliesResponse>>
|
||||||
|
|
||||||
@GET("/api/v2/creator-channels/{creatorId}/fan-talks")
|
@GET("/api/v2/creator-channels/{creatorId}/fan-talks")
|
||||||
fun getFanTalks(
|
fun getFanTalks(
|
||||||
@Path("creatorId") creatorId: Long,
|
@Path("creatorId") creatorId: Long,
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ class CreatorChannelRepository(
|
|||||||
creatorId = creatorId,
|
creatorId = creatorId,
|
||||||
page = page,
|
page = page,
|
||||||
size = size,
|
size = size,
|
||||||
sort = sort,
|
sort = sort.queryValue,
|
||||||
authHeader = token
|
authHeader = token
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -48,7 +48,7 @@ class CreatorChannelRepository(
|
|||||||
creatorId = creatorId,
|
creatorId = creatorId,
|
||||||
page = page,
|
page = page,
|
||||||
size = size,
|
size = size,
|
||||||
sort = sort,
|
sort = sort.queryValue,
|
||||||
themeId = themeId,
|
themeId = themeId,
|
||||||
authHeader = token
|
authHeader = token
|
||||||
)
|
)
|
||||||
@@ -63,7 +63,7 @@ class CreatorChannelRepository(
|
|||||||
creatorId = creatorId,
|
creatorId = creatorId,
|
||||||
page = page,
|
page = page,
|
||||||
size = size,
|
size = size,
|
||||||
sort = sort,
|
sort = sort.queryValue,
|
||||||
authHeader = token
|
authHeader = token
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -79,6 +79,35 @@ class CreatorChannelRepository(
|
|||||||
authHeader = token
|
authHeader = token
|
||||||
)
|
)
|
||||||
|
|
||||||
|
fun getCommunityPostDetail(postId: Long, token: String) = api.getCommunityPostDetail(
|
||||||
|
postId = postId,
|
||||||
|
authHeader = token
|
||||||
|
)
|
||||||
|
|
||||||
|
fun getCommunityPostComments(
|
||||||
|
postId: Long,
|
||||||
|
page: Int = 0,
|
||||||
|
size: Int = 20,
|
||||||
|
token: String
|
||||||
|
) = api.getCommunityPostComments(
|
||||||
|
postId = postId,
|
||||||
|
page = page,
|
||||||
|
size = size,
|
||||||
|
authHeader = token
|
||||||
|
)
|
||||||
|
|
||||||
|
fun getCommunityCommentReplies(
|
||||||
|
commentId: Long,
|
||||||
|
page: Int = 0,
|
||||||
|
size: Int = 20,
|
||||||
|
token: String
|
||||||
|
) = api.getCommunityCommentReplies(
|
||||||
|
commentId = commentId,
|
||||||
|
page = page,
|
||||||
|
size = size,
|
||||||
|
authHeader = token
|
||||||
|
)
|
||||||
|
|
||||||
fun getFanTalks(
|
fun getFanTalks(
|
||||||
creatorId: Long,
|
creatorId: Long,
|
||||||
page: Int,
|
page: Int,
|
||||||
@@ -160,4 +189,28 @@ class CreatorChannelRepository(
|
|||||||
request = PutModifyCheersRequest(cheersId = fanTalkId, isActive = false),
|
request = PutModifyCheersRequest(cheersId = fanTalkId, isActive = false),
|
||||||
token = token
|
token = token
|
||||||
)
|
)
|
||||||
|
|
||||||
|
fun writeFanTalk(creatorId: Long, content: String, token: String) = explorerRepository.writeCheers(
|
||||||
|
parentCheersId = null,
|
||||||
|
creatorId = creatorId,
|
||||||
|
content = content,
|
||||||
|
token = token
|
||||||
|
)
|
||||||
|
|
||||||
|
fun writeFanTalkReply(fanTalkId: Long, creatorId: Long, content: String, token: String) = explorerRepository.writeCheers(
|
||||||
|
parentCheersId = fanTalkId,
|
||||||
|
creatorId = creatorId,
|
||||||
|
content = content,
|
||||||
|
token = token
|
||||||
|
)
|
||||||
|
|
||||||
|
fun modifyFanTalkReply(replyFanTalkId: Long, content: String, token: String) = explorerRepository.modifyCheers(
|
||||||
|
request = PutModifyCheersRequest(cheersId = replyFanTalkId, content = content),
|
||||||
|
token = token
|
||||||
|
)
|
||||||
|
|
||||||
|
fun deleteFanTalkReply(replyFanTalkId: Long, token: String) = explorerRepository.modifyCheers(
|
||||||
|
request = PutModifyCheersRequest(cheersId = replyFanTalkId, isActive = false),
|
||||||
|
token = token
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ private fun CreatorChannelDonationResponse.toDonationUiModel(
|
|||||||
|
|
||||||
@ColorRes
|
@ColorRes
|
||||||
internal fun calculateDonationHeaderColorRes(can: Int): Int = when {
|
internal fun calculateDonationHeaderColorRes(can: Int): Int = when {
|
||||||
can >= 500 -> R.color.red_400
|
can >= 501 -> R.color.red_400
|
||||||
can >= 101 -> R.color.creator_channel_donation_cyan
|
can >= 101 -> R.color.creator_channel_donation_cyan
|
||||||
can >= 51 -> R.color.green_400
|
can >= 51 -> R.color.green_400
|
||||||
else -> R.color.gray_200
|
else -> R.color.gray_200
|
||||||
|
|||||||
@@ -8,15 +8,12 @@ import android.view.ViewGroup
|
|||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.recyclerview.widget.GridLayoutManager
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import coil.transform.CircleCropTransformation
|
|
||||||
import kr.co.vividnext.sodalive.R
|
|
||||||
import kr.co.vividnext.sodalive.databinding.ItemCreatorChannelDonationBinding
|
import kr.co.vividnext.sodalive.databinding.ItemCreatorChannelDonationBinding
|
||||||
import kr.co.vividnext.sodalive.databinding.ItemCreatorChannelDonationEmptyBinding
|
import kr.co.vividnext.sodalive.databinding.ItemCreatorChannelDonationEmptyBinding
|
||||||
import kr.co.vividnext.sodalive.databinding.ItemCreatorChannelDonationRankingBinding
|
import kr.co.vividnext.sodalive.databinding.ItemCreatorChannelDonationRankingBinding
|
||||||
import kr.co.vividnext.sodalive.extensions.loadUrl
|
|
||||||
import kr.co.vividnext.sodalive.extensions.moneyFormat
|
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.donation.model.CreatorChannelDonationRankingUiModel
|
import kr.co.vividnext.sodalive.v2.creator.channel.donation.model.CreatorChannelDonationRankingUiModel
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.donation.model.CreatorChannelDonationUiModel
|
import kr.co.vividnext.sodalive.v2.creator.channel.donation.model.CreatorChannelDonationUiModel
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.ui.bindCreatorChannelDonationCard
|
||||||
|
|
||||||
class CreatorChannelDonationAdapter(
|
class CreatorChannelDonationAdapter(
|
||||||
private val onRankingAllClick: () -> Unit = { },
|
private val onRankingAllClick: () -> Unit = { },
|
||||||
@@ -119,20 +116,20 @@ class CreatorChannelDonationAdapter(
|
|||||||
) : RecyclerView.ViewHolder(binding.root) {
|
) : RecyclerView.ViewHolder(binding.root) {
|
||||||
|
|
||||||
fun bind(item: CreatorChannelDonationUiModel) = with(binding) {
|
fun bind(item: CreatorChannelDonationUiModel) = with(binding) {
|
||||||
layoutCreatorChannelDonationHeader.setBackgroundColor(root.context.getColor(item.headerColorResId))
|
bindCreatorChannelDonationCard(
|
||||||
ivCreatorChannelDonationProfile.loadUrl(item.profileImageUrl) {
|
headerView = layoutCreatorChannelDonationHeader,
|
||||||
crossfade(true)
|
profileView = ivCreatorChannelDonationProfile,
|
||||||
placeholder(R.drawable.ic_placeholder_profile)
|
nicknameView = tvCreatorChannelDonationNickname,
|
||||||
error(R.drawable.ic_placeholder_profile)
|
createdAtView = tvCreatorChannelDonationCreatedAt,
|
||||||
transformations(CircleCropTransformation())
|
canView = tvCreatorChannelDonationCan,
|
||||||
}
|
messageView = tvCreatorChannelDonationMessage,
|
||||||
tvCreatorChannelDonationNickname.text = item.nickname
|
headerColorResId = item.headerColorResId,
|
||||||
tvCreatorChannelDonationCreatedAt.text = item.createdAtText
|
profileImageUrl = item.profileImageUrl,
|
||||||
tvCreatorChannelDonationCan.text = root.context.getString(
|
nickname = item.nickname,
|
||||||
R.string.creator_channel_donation_can_format,
|
createdAtText = item.createdAtText,
|
||||||
item.can.moneyFormat()
|
can = item.can,
|
||||||
|
message = item.message
|
||||||
)
|
)
|
||||||
tvCreatorChannelDonationMessage.text = item.message
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,8 @@ class CreatorChannelFanTalkFragment : BaseFragment<FragmentCreatorChannelFantalk
|
|||||||
private val viewModel: CreatorChannelFanTalkViewModel by viewModel()
|
private val viewModel: CreatorChannelFanTalkViewModel by viewModel()
|
||||||
private val fanTalkAdapter = CreatorChannelFanTalkAdapter(
|
private val fanTalkAdapter = CreatorChannelFanTalkAdapter(
|
||||||
onOwnerMoreClick = ::showOwnerMorePopup,
|
onOwnerMoreClick = ::showOwnerMorePopup,
|
||||||
onReportClick = ::showReportDialog
|
onReportClick = ::showReportDialog,
|
||||||
|
onItemClick = { host.onCreatorChannelFanTalkItemClicked(it) }
|
||||||
)
|
)
|
||||||
private var morePopup: CreatorChannelFanTalkMorePopup? = null
|
private var morePopup: CreatorChannelFanTalkMorePopup? = null
|
||||||
private var lastContentLayoutKey: CreatorChannelFanTalkContentLayoutKey? = null
|
private var lastContentLayoutKey: CreatorChannelFanTalkContentLayoutKey? = null
|
||||||
@@ -43,6 +44,7 @@ class CreatorChannelFanTalkFragment : BaseFragment<FragmentCreatorChannelFantalk
|
|||||||
morePopup?.dismiss()
|
morePopup?.dismiss()
|
||||||
morePopup = null
|
morePopup = null
|
||||||
lastContentLayoutKey = null
|
lastContentLayoutKey = null
|
||||||
|
host.onCreatorChannelFanTalkFloatingButtonVisibilityChanged(false)
|
||||||
binding.rvCreatorChannelFantalk.adapter = null
|
binding.rvCreatorChannelFantalk.adapter = null
|
||||||
super.onDestroyView()
|
super.onDestroyView()
|
||||||
}
|
}
|
||||||
@@ -77,8 +79,8 @@ class CreatorChannelFanTalkFragment : BaseFragment<FragmentCreatorChannelFantalk
|
|||||||
btnCreatorChannelFantalkRetry.setOnClickListener {
|
btnCreatorChannelFantalkRetry.setOnClickListener {
|
||||||
viewModel.retryFanTalks()
|
viewModel.retryFanTalks()
|
||||||
}
|
}
|
||||||
btnCreatorChannelFantalkWrite.setOnClickListener { }
|
btnCreatorChannelFantalkWrite.setOnClickListener { host.onCreatorChannelFanTalkWriteClicked() }
|
||||||
layoutCreatorChannelFantalkEmptyWriteButton.setOnClickListener { }
|
layoutCreatorChannelFantalkEmptyWriteButton.setOnClickListener { host.onCreatorChannelFanTalkWriteClicked() }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun observeViewModel() {
|
private fun observeViewModel() {
|
||||||
@@ -100,6 +102,7 @@ class CreatorChannelFanTalkFragment : BaseFragment<FragmentCreatorChannelFantalk
|
|||||||
tvCreatorChannelFantalkErrorMessage.isVisible = false
|
tvCreatorChannelFantalkErrorMessage.isVisible = false
|
||||||
btnCreatorChannelFantalkRetry.isVisible = false
|
btnCreatorChannelFantalkRetry.isVisible = false
|
||||||
btnCreatorChannelFantalkWrite.isVisible = false
|
btnCreatorChannelFantalkWrite.isVisible = false
|
||||||
|
host.onCreatorChannelFanTalkFloatingButtonVisibilityChanged(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun bindEmpty() = with(binding) {
|
private fun bindEmpty() = with(binding) {
|
||||||
@@ -110,6 +113,7 @@ class CreatorChannelFanTalkFragment : BaseFragment<FragmentCreatorChannelFantalk
|
|||||||
tvCreatorChannelFantalkErrorMessage.isVisible = false
|
tvCreatorChannelFantalkErrorMessage.isVisible = false
|
||||||
btnCreatorChannelFantalkRetry.isVisible = false
|
btnCreatorChannelFantalkRetry.isVisible = false
|
||||||
btnCreatorChannelFantalkWrite.isVisible = false
|
btnCreatorChannelFantalkWrite.isVisible = false
|
||||||
|
host.onCreatorChannelFanTalkFloatingButtonVisibilityChanged(false)
|
||||||
host.onCreatorChannelFanTalkContentChanged()
|
host.onCreatorChannelFanTalkContentChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,6 +126,7 @@ class CreatorChannelFanTalkFragment : BaseFragment<FragmentCreatorChannelFantalk
|
|||||||
tvCreatorChannelFantalkErrorMessage.text = state.message ?: getString(R.string.creator_channel_fantalk_error_message)
|
tvCreatorChannelFantalkErrorMessage.text = state.message ?: getString(R.string.creator_channel_fantalk_error_message)
|
||||||
btnCreatorChannelFantalkRetry.isVisible = true
|
btnCreatorChannelFantalkRetry.isVisible = true
|
||||||
btnCreatorChannelFantalkWrite.isVisible = false
|
btnCreatorChannelFantalkWrite.isVisible = false
|
||||||
|
host.onCreatorChannelFanTalkFloatingButtonVisibilityChanged(false)
|
||||||
host.onCreatorChannelFanTalkContentChanged()
|
host.onCreatorChannelFanTalkContentChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,7 +138,8 @@ class CreatorChannelFanTalkFragment : BaseFragment<FragmentCreatorChannelFantalk
|
|||||||
layoutCreatorChannelFantalkEmpty.isVisible = false
|
layoutCreatorChannelFantalkEmpty.isVisible = false
|
||||||
tvCreatorChannelFantalkErrorMessage.isVisible = false
|
tvCreatorChannelFantalkErrorMessage.isVisible = false
|
||||||
btnCreatorChannelFantalkRetry.isVisible = false
|
btnCreatorChannelFantalkRetry.isVisible = false
|
||||||
btnCreatorChannelFantalkWrite.isVisible = true
|
btnCreatorChannelFantalkWrite.isVisible = false
|
||||||
|
host.onCreatorChannelFanTalkFloatingButtonVisibilityChanged(true)
|
||||||
notifyContentChangedIfLayoutChanged(state)
|
notifyContentChangedIfLayoutChanged(state)
|
||||||
state.paginationErrorMessage?.let {
|
state.paginationErrorMessage?.let {
|
||||||
Toast.makeText(requireContext(), it, Toast.LENGTH_SHORT).show()
|
Toast.makeText(requireContext(), it, Toast.LENGTH_SHORT).show()
|
||||||
@@ -178,7 +184,10 @@ class CreatorChannelFanTalkFragment : BaseFragment<FragmentCreatorChannelFantalk
|
|||||||
interface Host {
|
interface Host {
|
||||||
fun isCreatorChannelOwner(): Boolean
|
fun isCreatorChannelOwner(): Boolean
|
||||||
fun onCreatorChannelFanTalkContentChanged()
|
fun onCreatorChannelFanTalkContentChanged()
|
||||||
|
fun onCreatorChannelFanTalkWriteClicked()
|
||||||
|
fun onCreatorChannelFanTalkFloatingButtonVisibilityChanged(isVisible: Boolean)
|
||||||
fun onCreatorChannelFanTalkDeleteClicked(fanTalkId: Long)
|
fun onCreatorChannelFanTalkDeleteClicked(fanTalkId: Long)
|
||||||
|
fun onCreatorChannelFanTalkItemClicked(item: CreatorChannelFanTalkUiModel) = Unit
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|||||||
@@ -0,0 +1,238 @@
|
|||||||
|
package kr.co.vividnext.sodalive.v2.creator.channel.fantalk.detail
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.text.method.ScrollingMovementMethod
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.view.inputmethod.InputMethodManager
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.PopupWindow
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.core.content.IntentCompat
|
||||||
|
import androidx.core.graphics.drawable.toDrawable
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
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.databinding.ActivityCreatorChannelFantalkDetailBinding
|
||||||
|
import kr.co.vividnext.sodalive.databinding.ViewCreatorChannelFantalkMorePopupBinding
|
||||||
|
import kr.co.vividnext.sodalive.extensions.loadUrl
|
||||||
|
import kr.co.vividnext.sodalive.v2.components.modal.V2ModalDialog
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.fantalk.model.CreatorChannelFanTalkReplyUiModel
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.fantalk.model.CreatorChannelFanTalkUiModel
|
||||||
|
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||||
|
|
||||||
|
class CreatorChannelFanTalkDetailActivity : BaseActivity<ActivityCreatorChannelFantalkDetailBinding>(
|
||||||
|
ActivityCreatorChannelFantalkDetailBinding::inflate
|
||||||
|
) {
|
||||||
|
|
||||||
|
private val viewModel: CreatorChannelFanTalkDetailViewModel by viewModel()
|
||||||
|
private var creatorId: Long = 0L
|
||||||
|
private var replyMorePopup: PopupWindow? = null
|
||||||
|
private var renderedEditingTarget: CreatorChannelFanTalkDetailEditTarget? = null
|
||||||
|
|
||||||
|
override fun setupView() {
|
||||||
|
creatorId = intent.getLongExtra(EXTRA_CREATOR_ID, 0L)
|
||||||
|
val payload = payloadFromIntent() ?: run {
|
||||||
|
Toast.makeText(this, R.string.creator_channel_community_invalid_post, Toast.LENGTH_SHORT).show()
|
||||||
|
finish()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (creatorId <= 0L || payload.fanTalkId <= 0L) {
|
||||||
|
Toast.makeText(this, R.string.creator_channel_community_invalid_post, Toast.LENGTH_SHORT).show()
|
||||||
|
finish()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.btnCreatorChannelFantalkDetailBack.setOnClickListener { finish() }
|
||||||
|
binding.etCreatorChannelFantalkDetailReply.movementMethod = ScrollingMovementMethod()
|
||||||
|
binding.etCreatorChannelFantalkDetailReply.doAfterTextChanged { editable ->
|
||||||
|
viewModel.updateReplyInput(editable?.toString().orEmpty())
|
||||||
|
}
|
||||||
|
binding.btnCreatorChannelFantalkDetailSend.setOnClickListener { viewModel.submitReply() }
|
||||||
|
binding.btnCreatorChannelFantalkDetailEditCancel.setOnClickListener { viewModel.cancelReplyEdit() }
|
||||||
|
|
||||||
|
viewModel.detailStateLiveData.observe(this) { state -> render(state) }
|
||||||
|
viewModel.replacementModalEventLiveData.observe(this) { event ->
|
||||||
|
event.consume()?.let(::showReplacementDialog)
|
||||||
|
}
|
||||||
|
viewModel.replyChangedEventLiveData.observe(this) { changed ->
|
||||||
|
if (changed == true) {
|
||||||
|
setResult(Activity.RESULT_OK)
|
||||||
|
hideReplyKeyboard()
|
||||||
|
viewModel.consumeReplyChangedEvent()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
viewModel.toastLiveData.observe(this) { event ->
|
||||||
|
event.consume()?.let { toast ->
|
||||||
|
val message = toast.message ?: toast.resId?.let(::getString)
|
||||||
|
message?.let(::showToast)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
viewModel.load(creatorId, payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
replyMorePopup?.dismiss()
|
||||||
|
replyMorePopup = null
|
||||||
|
super.onDestroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun render(state: CreatorChannelFanTalkDetailUiState) {
|
||||||
|
when (state) {
|
||||||
|
is CreatorChannelFanTalkDetailUiState.Content -> renderContent(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun renderContent(content: CreatorChannelFanTalkDetailUiState.Content) = with(binding) {
|
||||||
|
bindParentFanTalk(content.parentFanTalk)
|
||||||
|
bindReply(content.reply)
|
||||||
|
if (etCreatorChannelFantalkDetailReply.text.toString() != content.replyInput) {
|
||||||
|
etCreatorChannelFantalkDetailReply.setText(content.replyInput)
|
||||||
|
etCreatorChannelFantalkDetailReply.setSelection(content.replyInput.length)
|
||||||
|
}
|
||||||
|
btnCreatorChannelFantalkDetailEditCancel.isVisible = content.editingTarget != null
|
||||||
|
if (content.editingTarget != null && content.editingTarget != renderedEditingTarget) {
|
||||||
|
showReplyKeyboard()
|
||||||
|
}
|
||||||
|
renderedEditingTarget = content.editingTarget
|
||||||
|
updateSendButton(content.isSendEnabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun bindParentFanTalk(item: CreatorChannelFanTalkUiModel) = with(binding) {
|
||||||
|
ivCreatorChannelFantalkDetailProfile.loadProfile(item.writerProfileImageUrl)
|
||||||
|
tvCreatorChannelFantalkDetailNickname.text = item.writerNickname
|
||||||
|
tvCreatorChannelFantalkDetailTime.text = item.createdAtText
|
||||||
|
tvCreatorChannelFantalkDetailContent.text = item.content
|
||||||
|
ivCreatorChannelFantalkDetailMore.isVisible = false
|
||||||
|
ivCreatorChannelFantalkDetailMore.setOnClickListener(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun bindReply(reply: CreatorChannelFanTalkReplyUiModel?) = with(binding) {
|
||||||
|
viewCreatorChannelFantalkDetailReplyConnector.isVisible = reply != null
|
||||||
|
layoutCreatorChannelFantalkDetailReply.isVisible = reply != null
|
||||||
|
if (reply == null) {
|
||||||
|
replyMorePopup?.dismiss()
|
||||||
|
return@with
|
||||||
|
}
|
||||||
|
|
||||||
|
ivCreatorChannelFantalkDetailReplyProfile.loadProfile(reply.writerProfileImageUrl)
|
||||||
|
tvCreatorChannelFantalkDetailReplyNickname.text = reply.writerNickname
|
||||||
|
tvCreatorChannelFantalkDetailReplyTime.text = reply.createdAtText
|
||||||
|
tvCreatorChannelFantalkDetailReplyContent.text = reply.content
|
||||||
|
ivCreatorChannelFantalkDetailReplyMore.isVisible = reply.fanTalkId > 0L
|
||||||
|
ivCreatorChannelFantalkDetailReplyMore.setOnClickListener(
|
||||||
|
if (reply.fanTalkId > 0L) {
|
||||||
|
View.OnClickListener { anchor -> showReplyMorePopup(anchor) }
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showReplyMorePopup(anchor: View) {
|
||||||
|
replyMorePopup?.dismiss()
|
||||||
|
val popupBinding = ViewCreatorChannelFantalkMorePopupBinding.inflate(LayoutInflater.from(anchor.context))
|
||||||
|
replyMorePopup = PopupWindow(anchor.context).apply {
|
||||||
|
contentView = popupBinding.root
|
||||||
|
width = ViewGroup.LayoutParams.WRAP_CONTENT
|
||||||
|
height = ViewGroup.LayoutParams.WRAP_CONTENT
|
||||||
|
isOutsideTouchable = true
|
||||||
|
isFocusable = true
|
||||||
|
setBackgroundDrawable(Color.TRANSPARENT.toDrawable())
|
||||||
|
}
|
||||||
|
popupBinding.tvCreatorChannelFantalkMoreEdit.isVisible = true
|
||||||
|
popupBinding.tvCreatorChannelFantalkMoreDelete.isVisible = true
|
||||||
|
popupBinding.tvCreatorChannelFantalkMoreEdit.setOnClickListener {
|
||||||
|
replyMorePopup?.dismiss()
|
||||||
|
viewModel.startReplyEdit()
|
||||||
|
}
|
||||||
|
popupBinding.tvCreatorChannelFantalkMoreDelete.setOnClickListener {
|
||||||
|
replyMorePopup?.dismiss()
|
||||||
|
showDeleteReplyDialog()
|
||||||
|
}
|
||||||
|
replyMorePopup?.showAsDropDown(anchor)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showReplacementDialog(modal: CreatorChannelFanTalkReplacementModalUiModel) {
|
||||||
|
V2ModalDialog(
|
||||||
|
activity = this,
|
||||||
|
layoutInflater = layoutInflater,
|
||||||
|
title = getString(modal.titleResId),
|
||||||
|
desc = getString(modal.descriptionResId),
|
||||||
|
confirmButtonTitle = getString(modal.confirmResId),
|
||||||
|
confirmButtonClick = { viewModel.confirmReplacement() },
|
||||||
|
cancelButtonTitle = getString(modal.cancelResId),
|
||||||
|
cancelButtonClick = { viewModel.cancelReplacement() }
|
||||||
|
).show(screenWidth)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showDeleteReplyDialog() {
|
||||||
|
V2ModalDialog(
|
||||||
|
activity = this,
|
||||||
|
layoutInflater = layoutInflater,
|
||||||
|
title = getString(R.string.confirm_delete_title),
|
||||||
|
desc = getString(R.string.confirm_delete_message),
|
||||||
|
confirmButtonTitle = getString(R.string.confirm),
|
||||||
|
confirmButtonClick = { viewModel.deleteReply() },
|
||||||
|
cancelButtonTitle = getString(R.string.cancel)
|
||||||
|
).show(screenWidth)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateSendButton(enabled: Boolean) = with(binding.btnCreatorChannelFantalkDetailSend) {
|
||||||
|
isEnabled = enabled
|
||||||
|
setBackgroundResource(
|
||||||
|
if (enabled) {
|
||||||
|
R.drawable.bg_creator_channel_community_send_enabled
|
||||||
|
} else {
|
||||||
|
R.drawable.bg_creator_channel_community_send_disabled
|
||||||
|
}
|
||||||
|
)
|
||||||
|
setImageResource(if (enabled) R.drawable.ic_new_arrow_up_white else R.drawable.ic_new_arrow_up_gray)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showReplyKeyboard() {
|
||||||
|
val editText = binding.etCreatorChannelFantalkDetailReply
|
||||||
|
editText.requestFocus()
|
||||||
|
editText.post {
|
||||||
|
val inputMethodManager = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||||
|
inputMethodManager.showSoftInput(editText, InputMethodManager.SHOW_IMPLICIT)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun hideReplyKeyboard() {
|
||||||
|
val editText = binding.etCreatorChannelFantalkDetailReply
|
||||||
|
val inputMethodManager = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||||
|
inputMethodManager.hideSoftInputFromWindow(editText.windowToken, 0)
|
||||||
|
editText.clearFocus()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun payloadFromIntent(): CreatorChannelFanTalkDetailPayload? {
|
||||||
|
return IntentCompat.getParcelableExtra(intent, EXTRA_PAYLOAD, CreatorChannelFanTalkDetailPayload::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ImageView.loadProfile(url: String) {
|
||||||
|
loadUrl(url) {
|
||||||
|
crossfade(true)
|
||||||
|
placeholder(R.drawable.ic_placeholder_profile)
|
||||||
|
error(R.drawable.ic_placeholder_profile)
|
||||||
|
transformations(CircleCropTransformation())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val EXTRA_CREATOR_ID = "extra_creator_id"
|
||||||
|
private const val EXTRA_PAYLOAD = "extra_payload"
|
||||||
|
|
||||||
|
fun newIntent(context: Context, creatorId: Long, payload: CreatorChannelFanTalkDetailPayload): Intent {
|
||||||
|
return Intent(context, CreatorChannelFanTalkDetailActivity::class.java)
|
||||||
|
.putExtra(EXTRA_CREATOR_ID, creatorId)
|
||||||
|
.putExtra(EXTRA_PAYLOAD, payload)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
package kr.co.vividnext.sodalive.v2.creator.channel.fantalk.detail
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import androidx.annotation.StringRes
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
|
import kr.co.vividnext.sodalive.R
|
||||||
|
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
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data class CreatorChannelFanTalkDetailPayload(
|
||||||
|
val fanTalkId: Long,
|
||||||
|
val writerId: Long,
|
||||||
|
val writerNickname: String,
|
||||||
|
val writerProfileImageUrl: String,
|
||||||
|
val content: String,
|
||||||
|
val createdAtText: String,
|
||||||
|
val reply: CreatorChannelFanTalkDetailReplyPayload?,
|
||||||
|
val showEdit: Boolean,
|
||||||
|
val showDelete: Boolean
|
||||||
|
) : Parcelable {
|
||||||
|
fun toUiModel(): CreatorChannelFanTalkUiModel = CreatorChannelFanTalkUiModel(
|
||||||
|
fanTalkId = fanTalkId,
|
||||||
|
writerId = writerId,
|
||||||
|
writerNickname = writerNickname,
|
||||||
|
writerProfileImageUrl = writerProfileImageUrl,
|
||||||
|
content = content,
|
||||||
|
createdAtText = createdAtText,
|
||||||
|
reply = reply?.toUiModel(),
|
||||||
|
rightAction = CreatorChannelFanTalkRightAction.OwnerMore(showEdit = showEdit, showDelete = showDelete)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data class CreatorChannelFanTalkDetailReplyPayload(
|
||||||
|
val fanTalkId: Long,
|
||||||
|
val writerId: Long,
|
||||||
|
val writerNickname: String,
|
||||||
|
val writerProfileImageUrl: String,
|
||||||
|
val content: String,
|
||||||
|
val createdAtText: String
|
||||||
|
) : Parcelable {
|
||||||
|
fun toUiModel(): CreatorChannelFanTalkReplyUiModel = CreatorChannelFanTalkReplyUiModel(
|
||||||
|
fanTalkId = fanTalkId,
|
||||||
|
writerId = writerId,
|
||||||
|
writerNickname = writerNickname,
|
||||||
|
writerProfileImageUrl = writerProfileImageUrl,
|
||||||
|
content = content,
|
||||||
|
createdAtText = createdAtText
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun CreatorChannelFanTalkUiModel.toFanTalkDetailPayload(): CreatorChannelFanTalkDetailPayload {
|
||||||
|
val ownerMore = rightAction as? CreatorChannelFanTalkRightAction.OwnerMore
|
||||||
|
return CreatorChannelFanTalkDetailPayload(
|
||||||
|
fanTalkId = fanTalkId,
|
||||||
|
writerId = writerId,
|
||||||
|
writerNickname = writerNickname,
|
||||||
|
writerProfileImageUrl = writerProfileImageUrl,
|
||||||
|
content = content,
|
||||||
|
createdAtText = createdAtText,
|
||||||
|
reply = reply?.toFanTalkDetailReplyPayload(),
|
||||||
|
showEdit = ownerMore?.showEdit == true,
|
||||||
|
showDelete = ownerMore?.showDelete == true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun CreatorChannelFanTalkReplyUiModel.toFanTalkDetailReplyPayload(): CreatorChannelFanTalkDetailReplyPayload {
|
||||||
|
return CreatorChannelFanTalkDetailReplyPayload(
|
||||||
|
fanTalkId = fanTalkId,
|
||||||
|
writerId = writerId,
|
||||||
|
writerNickname = writerNickname,
|
||||||
|
writerProfileImageUrl = writerProfileImageUrl,
|
||||||
|
content = content,
|
||||||
|
createdAtText = createdAtText
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
data class CreatorChannelFanTalkReplacementModalUiModel(
|
||||||
|
@StringRes val titleResId: Int = R.string.screen_user_profile_reply_edit,
|
||||||
|
@StringRes val descriptionResId: Int = R.string.creator_channel_fantalk_reply_replace_description,
|
||||||
|
@StringRes val cancelResId: Int = R.string.cancel,
|
||||||
|
@StringRes val confirmResId: Int = R.string.confirm
|
||||||
|
)
|
||||||
|
|
||||||
|
sealed interface CreatorChannelFanTalkDetailEditTarget {
|
||||||
|
data class Reply(val replyFanTalkId: Long) : CreatorChannelFanTalkDetailEditTarget
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed interface CreatorChannelFanTalkDetailUiState {
|
||||||
|
data class Content(
|
||||||
|
val parentFanTalk: CreatorChannelFanTalkUiModel,
|
||||||
|
val reply: CreatorChannelFanTalkReplyUiModel?,
|
||||||
|
val replyInput: String = "",
|
||||||
|
val isSubmitting: Boolean = false,
|
||||||
|
val editingTarget: CreatorChannelFanTalkDetailEditTarget? = null
|
||||||
|
) : CreatorChannelFanTalkDetailUiState {
|
||||||
|
val isSendEnabled: Boolean = replyInput.trim().isNotEmpty() && !isSubmitting
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,244 @@
|
|||||||
|
package kr.co.vividnext.sodalive.v2.creator.channel.fantalk.detail
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import com.orhanobut.logger.Logger
|
||||||
|
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||||
|
import io.reactivex.rxjava3.core.Single
|
||||||
|
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||||
|
import kr.co.vividnext.sodalive.R
|
||||||
|
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
|
||||||
|
) : BaseViewModel() {
|
||||||
|
|
||||||
|
private val _detailStateLiveData = MutableLiveData<CreatorChannelFanTalkDetailUiState>()
|
||||||
|
val detailStateLiveData: LiveData<CreatorChannelFanTalkDetailUiState>
|
||||||
|
get() = _detailStateLiveData
|
||||||
|
|
||||||
|
private val _replacementModalEventLiveData =
|
||||||
|
MutableLiveData<CreatorChannelEvent<CreatorChannelFanTalkReplacementModalUiModel>>()
|
||||||
|
val replacementModalEventLiveData: LiveData<CreatorChannelEvent<CreatorChannelFanTalkReplacementModalUiModel>>
|
||||||
|
get() = _replacementModalEventLiveData
|
||||||
|
|
||||||
|
private val _replyChangedEventLiveData = MutableLiveData(false)
|
||||||
|
val replyChangedEventLiveData: LiveData<Boolean>
|
||||||
|
get() = _replyChangedEventLiveData
|
||||||
|
|
||||||
|
private val _toastLiveData = MutableLiveData<CreatorChannelEvent<ToastMessage>>()
|
||||||
|
val toastLiveData: LiveData<CreatorChannelEvent<ToastMessage>>
|
||||||
|
get() = _toastLiveData
|
||||||
|
|
||||||
|
private var creatorId: Long = 0L
|
||||||
|
private var isSubmitting: Boolean = false
|
||||||
|
private var pendingReplacementContent: String? = null
|
||||||
|
|
||||||
|
fun load(creatorId: Long, payload: CreatorChannelFanTalkDetailPayload) {
|
||||||
|
this.creatorId = creatorId
|
||||||
|
val parentFanTalk = payload.toUiModel()
|
||||||
|
_detailStateLiveData.value = CreatorChannelFanTalkDetailUiState.Content(
|
||||||
|
parentFanTalk = parentFanTalk,
|
||||||
|
reply = parentFanTalk.reply
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateReplyInput(input: String) {
|
||||||
|
val content = currentContent() ?: return
|
||||||
|
_detailStateLiveData.value = content.copy(replyInput = input)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun submitReply() {
|
||||||
|
val content = currentContent() ?: return
|
||||||
|
val trimmedContent = content.replyInput.trim()
|
||||||
|
if (trimmedContent.isEmpty() || isSubmitting) return
|
||||||
|
|
||||||
|
val editingTarget = content.editingTarget
|
||||||
|
if (editingTarget is CreatorChannelFanTalkDetailEditTarget.Reply) {
|
||||||
|
modifyReply(content, editingTarget.replyFanTalkId, trimmedContent)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val reply = content.reply
|
||||||
|
if (reply == null) {
|
||||||
|
writeReply(content, trimmedContent)
|
||||||
|
} else {
|
||||||
|
if (reply.fanTalkId <= 0L) {
|
||||||
|
showFailureToast()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pendingReplacementContent = trimmedContent
|
||||||
|
_replacementModalEventLiveData.value = CreatorChannelEvent(CreatorChannelFanTalkReplacementModalUiModel())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun confirmReplacement() {
|
||||||
|
val content = currentContent() ?: return
|
||||||
|
val reply = content.reply ?: return
|
||||||
|
val replacementContent = pendingReplacementContent ?: content.replyInput.trim()
|
||||||
|
if (replacementContent.isBlank() || isSubmitting) return
|
||||||
|
if (reply.fanTalkId <= 0L) {
|
||||||
|
pendingReplacementContent = null
|
||||||
|
showFailureToast()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
modifyReply(content, reply.fanTalkId, replacementContent)
|
||||||
|
pendingReplacementContent = null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun cancelReplacement() {
|
||||||
|
pendingReplacementContent = null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun consumeReplyChangedEvent() {
|
||||||
|
_replyChangedEventLiveData.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun cancelReplyEdit() {
|
||||||
|
val content = currentContent() ?: return
|
||||||
|
_detailStateLiveData.value = content.copy(replyInput = "", editingTarget = null)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun startReplyEdit() {
|
||||||
|
val content = currentContent() ?: return
|
||||||
|
val reply = content.reply ?: return
|
||||||
|
_detailStateLiveData.value = content.copy(
|
||||||
|
replyInput = reply.content,
|
||||||
|
editingTarget = CreatorChannelFanTalkDetailEditTarget.Reply(reply.fanTalkId)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun deleteReply() {
|
||||||
|
val content = currentContent() ?: return
|
||||||
|
val reply = content.reply ?: return
|
||||||
|
if (isSubmitting) return
|
||||||
|
|
||||||
|
submitMutation(content, repository.deleteFanTalkReply(reply.fanTalkId, authToken())) { current ->
|
||||||
|
current.copy(reply = null, replyInput = "", editingTarget = null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun writeReply(content: CreatorChannelFanTalkDetailUiState.Content, replyContent: String) {
|
||||||
|
if (creatorId <= 0L || content.parentFanTalk.fanTalkId <= 0L) return
|
||||||
|
|
||||||
|
isSubmitting = true
|
||||||
|
_detailStateLiveData.value = content.copy(isSubmitting = true)
|
||||||
|
compositeDisposable.add(
|
||||||
|
repository.writeFanTalkReply(
|
||||||
|
fanTalkId = content.parentFanTalk.fanTalkId,
|
||||||
|
creatorId = creatorId,
|
||||||
|
content = replyContent,
|
||||||
|
token = authToken()
|
||||||
|
)
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(
|
||||||
|
{ response -> handleWriteReplyResponse(response) },
|
||||||
|
{ error -> handleMutationError(error) }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun modifyReply(
|
||||||
|
content: CreatorChannelFanTalkDetailUiState.Content,
|
||||||
|
replyFanTalkId: Long,
|
||||||
|
replyContent: String
|
||||||
|
) {
|
||||||
|
if (replyFanTalkId <= 0L) return
|
||||||
|
|
||||||
|
submitMutation(
|
||||||
|
content = content,
|
||||||
|
request = repository.modifyFanTalkReply(replyFanTalkId, replyContent, authToken())
|
||||||
|
) { current ->
|
||||||
|
current.copy(
|
||||||
|
reply = current.reply?.copy(content = replyContent),
|
||||||
|
replyInput = "",
|
||||||
|
editingTarget = null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun submitMutation(
|
||||||
|
content: CreatorChannelFanTalkDetailUiState.Content,
|
||||||
|
request: Single<ApiResponse<CreatorChannelFanTalkReplyResponse>>,
|
||||||
|
onSuccess: (CreatorChannelFanTalkDetailUiState.Content) -> CreatorChannelFanTalkDetailUiState.Content
|
||||||
|
) {
|
||||||
|
isSubmitting = true
|
||||||
|
_detailStateLiveData.value = content.copy(isSubmitting = true)
|
||||||
|
compositeDisposable.add(
|
||||||
|
request
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(
|
||||||
|
{ response -> handleMutationResponse(response, onSuccess) },
|
||||||
|
{ error -> handleMutationError(error) }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleWriteReplyResponse(response: ApiResponse<CreatorChannelFanTalkReplyResponse>) {
|
||||||
|
isSubmitting = false
|
||||||
|
val current = currentContent() ?: return
|
||||||
|
if (response.success) {
|
||||||
|
_detailStateLiveData.value = current.copy(
|
||||||
|
reply = response.data?.toFanTalkReplyUiModel(relativeTimeTextFormatter) ?: current.reply,
|
||||||
|
replyInput = "",
|
||||||
|
isSubmitting = false,
|
||||||
|
editingTarget = null
|
||||||
|
)
|
||||||
|
_replyChangedEventLiveData.value = true
|
||||||
|
} else {
|
||||||
|
showFailureToast(response.message)
|
||||||
|
_detailStateLiveData.value = current.copy(isSubmitting = false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleMutationError(error: Throwable) {
|
||||||
|
isSubmitting = false
|
||||||
|
error.message?.let { Logger.e(it) }
|
||||||
|
val current = currentContent() ?: return
|
||||||
|
showFailureToast()
|
||||||
|
_detailStateLiveData.value = current.copy(isSubmitting = false)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleMutationResponse(
|
||||||
|
response: ApiResponse<CreatorChannelFanTalkReplyResponse>,
|
||||||
|
onSuccess: (CreatorChannelFanTalkDetailUiState.Content) -> CreatorChannelFanTalkDetailUiState.Content
|
||||||
|
) {
|
||||||
|
isSubmitting = false
|
||||||
|
val current = currentContent() ?: return
|
||||||
|
if (response.success) {
|
||||||
|
_detailStateLiveData.value = onSuccess(current).copy(isSubmitting = false)
|
||||||
|
_replyChangedEventLiveData.value = true
|
||||||
|
} else {
|
||||||
|
showFailureToast(response.message)
|
||||||
|
_detailStateLiveData.value = current.copy(isSubmitting = false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showFailureToast(message: String? = null) {
|
||||||
|
_toastLiveData.value = CreatorChannelEvent(
|
||||||
|
if (message.isNullOrBlank()) {
|
||||||
|
ToastMessage(resId = R.string.common_error_unknown)
|
||||||
|
} else {
|
||||||
|
ToastMessage(message = message)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun currentContent(): CreatorChannelFanTalkDetailUiState.Content? {
|
||||||
|
return _detailStateLiveData.value as? CreatorChannelFanTalkDetailUiState.Content
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun authToken(): String = "Bearer ${SharedPreferenceManager.token}"
|
||||||
|
}
|
||||||
@@ -23,11 +23,22 @@ private fun CreatorChannelFanTalkResponse.toFanTalkUiModel(
|
|||||||
writerProfileImageUrl = writerProfileImageUrl,
|
writerProfileImageUrl = writerProfileImageUrl,
|
||||||
content = content,
|
content = content,
|
||||||
createdAtText = relativeTimeTextFormatter.format(createdAtUtc),
|
createdAtText = relativeTimeTextFormatter.format(createdAtUtc),
|
||||||
reply = creatorReplies.firstOrNull()?.toReplyUiModel(relativeTimeTextFormatter),
|
reply = creatorReplies.orEmpty().firstOrNull()?.toFanTalkReplyUiModel(relativeTimeTextFormatter),
|
||||||
rightAction = toRightAction(isOwner = isOwner, currentUserId = currentUserId)
|
rightAction = toRightAction(isOwner = isOwner, currentUserId = currentUserId)
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun CreatorChannelFanTalkReplyResponse.toReplyUiModel(
|
fun CreatorChannelFanTalkReplyResponse.toFanTalkReplyUiModel(
|
||||||
|
relativeTimeTextFormatter: UtcRelativeTimeTextFormatter
|
||||||
|
) = CreatorChannelFanTalkReplyUiModel(
|
||||||
|
fanTalkId = fanTalkId,
|
||||||
|
writerId = writerId,
|
||||||
|
writerNickname = writerNickname,
|
||||||
|
writerProfileImageUrl = writerProfileImageUrl,
|
||||||
|
content = content,
|
||||||
|
createdAtText = relativeTimeTextFormatter.format(createdAtUtc)
|
||||||
|
)
|
||||||
|
|
||||||
|
fun CreatorChannelFanTalkResponse.toFanTalkReplyUiModel(
|
||||||
relativeTimeTextFormatter: UtcRelativeTimeTextFormatter
|
relativeTimeTextFormatter: UtcRelativeTimeTextFormatter
|
||||||
) = CreatorChannelFanTalkReplyUiModel(
|
) = CreatorChannelFanTalkReplyUiModel(
|
||||||
fanTalkId = fanTalkId,
|
fanTalkId = fanTalkId,
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ import kr.co.vividnext.sodalive.v2.creator.channel.fantalk.model.CreatorChannelF
|
|||||||
|
|
||||||
class CreatorChannelFanTalkAdapter(
|
class CreatorChannelFanTalkAdapter(
|
||||||
private val onOwnerMoreClick: (View, CreatorChannelFanTalkUiModel) -> Unit = { _, _ -> },
|
private val onOwnerMoreClick: (View, CreatorChannelFanTalkUiModel) -> Unit = { _, _ -> },
|
||||||
private val onReportClick: (CreatorChannelFanTalkUiModel) -> Unit = { }
|
private val onReportClick: (CreatorChannelFanTalkUiModel) -> Unit = { },
|
||||||
|
private val onItemClick: (CreatorChannelFanTalkUiModel) -> Unit = { }
|
||||||
) : RecyclerView.Adapter<CreatorChannelFanTalkAdapter.ViewHolder>() {
|
) : RecyclerView.Adapter<CreatorChannelFanTalkAdapter.ViewHolder>() {
|
||||||
|
|
||||||
private var items: List<CreatorChannelFanTalkUiModel> = emptyList()
|
private var items: List<CreatorChannelFanTalkUiModel> = emptyList()
|
||||||
@@ -29,7 +30,8 @@ class CreatorChannelFanTalkAdapter(
|
|||||||
return ViewHolder(
|
return ViewHolder(
|
||||||
ItemCreatorChannelFantalkBinding.inflate(LayoutInflater.from(parent.context), parent, false),
|
ItemCreatorChannelFantalkBinding.inflate(LayoutInflater.from(parent.context), parent, false),
|
||||||
onOwnerMoreClick,
|
onOwnerMoreClick,
|
||||||
onReportClick
|
onReportClick,
|
||||||
|
onItemClick
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,7 +44,8 @@ class CreatorChannelFanTalkAdapter(
|
|||||||
class ViewHolder(
|
class ViewHolder(
|
||||||
private val binding: ItemCreatorChannelFantalkBinding,
|
private val binding: ItemCreatorChannelFantalkBinding,
|
||||||
private val onOwnerMoreClick: (View, CreatorChannelFanTalkUiModel) -> Unit,
|
private val onOwnerMoreClick: (View, CreatorChannelFanTalkUiModel) -> Unit,
|
||||||
private val onReportClick: (CreatorChannelFanTalkUiModel) -> Unit
|
private val onReportClick: (CreatorChannelFanTalkUiModel) -> Unit,
|
||||||
|
private val onItemClick: (CreatorChannelFanTalkUiModel) -> Unit
|
||||||
) : RecyclerView.ViewHolder(binding.root) {
|
) : RecyclerView.ViewHolder(binding.root) {
|
||||||
|
|
||||||
fun bind(item: CreatorChannelFanTalkUiModel) = with(binding) {
|
fun bind(item: CreatorChannelFanTalkUiModel) = with(binding) {
|
||||||
@@ -50,6 +53,8 @@ class CreatorChannelFanTalkAdapter(
|
|||||||
tvCreatorChannelFantalkNickname.text = item.writerNickname
|
tvCreatorChannelFantalkNickname.text = item.writerNickname
|
||||||
tvCreatorChannelFantalkTime.text = item.createdAtText
|
tvCreatorChannelFantalkTime.text = item.createdAtText
|
||||||
tvCreatorChannelFantalkContent.text = item.content
|
tvCreatorChannelFantalkContent.text = item.content
|
||||||
|
root.setOnClickListener { onItemClick(item) }
|
||||||
|
tvCreatorChannelFantalkContent.setOnClickListener { onItemClick(item) }
|
||||||
bindRightAction(item)
|
bindRightAction(item)
|
||||||
bindReply(item.reply)
|
bindReply(item.reply)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,144 @@
|
|||||||
|
package kr.co.vividnext.sodalive.v2.creator.channel.fantalk.write
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.text.InputFilter
|
||||||
|
import android.view.inputmethod.InputMethodManager
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.activity.addCallback
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.core.view.WindowCompat
|
||||||
|
import androidx.core.view.WindowInsetsCompat
|
||||||
|
import androidx.core.widget.addTextChangedListener
|
||||||
|
import kr.co.vividnext.sodalive.R
|
||||||
|
import kr.co.vividnext.sodalive.base.BaseActivity
|
||||||
|
import kr.co.vividnext.sodalive.databinding.ActivityCreatorChannelFantalkWriteBinding
|
||||||
|
import kr.co.vividnext.sodalive.v2.components.modal.V2ModalDialog
|
||||||
|
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||||
|
|
||||||
|
class CreatorChannelFanTalkWriteActivity : BaseActivity<ActivityCreatorChannelFantalkWriteBinding>(
|
||||||
|
ActivityCreatorChannelFantalkWriteBinding::inflate
|
||||||
|
) {
|
||||||
|
|
||||||
|
private val viewModel: CreatorChannelFanTalkWriteViewModel by viewModel()
|
||||||
|
private var creatorId: Long = 0L
|
||||||
|
|
||||||
|
override fun setupView() {
|
||||||
|
creatorId = intent.getLongExtra(EXTRA_CREATOR_ID, 0L)
|
||||||
|
if (creatorId <= 0L) {
|
||||||
|
finish()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setupContentInput()
|
||||||
|
setupClickListeners()
|
||||||
|
setupBackHandler()
|
||||||
|
observeViewModel()
|
||||||
|
focusContentInput()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupContentInput() = with(binding.etCreatorChannelFantalkWriteContent) {
|
||||||
|
filters = arrayOf(InputFilter.LengthFilter(CreatorChannelFanTalkWriteViewModel.MAX_CONTENT_LENGTH))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupClickListeners() = with(binding) {
|
||||||
|
tvCreatorChannelFantalkWriteCancel.setOnClickListener { handleCancel() }
|
||||||
|
tvCreatorChannelFantalkWriteSend.setOnClickListener { viewModel.submit(creatorId) }
|
||||||
|
etCreatorChannelFantalkWriteContent.addTextChangedListener { text ->
|
||||||
|
viewModel.onContentChanged(text?.toString().orEmpty())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupBackHandler() {
|
||||||
|
onBackPressedDispatcher.addCallback(this) {
|
||||||
|
handleCancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun observeViewModel() {
|
||||||
|
viewModel.isSendEnabledLiveData.observe(this) { isEnabled ->
|
||||||
|
bindSendEnabled(isEnabled && viewModel.isLoadingLiveData.value != true)
|
||||||
|
}
|
||||||
|
viewModel.contentLengthLiveData.observe(this) { length ->
|
||||||
|
binding.tvCreatorChannelFantalkWriteCount.text = length.toString()
|
||||||
|
}
|
||||||
|
viewModel.isLoadingLiveData.observe(this) { isLoading ->
|
||||||
|
bindLoading(isLoading)
|
||||||
|
}
|
||||||
|
viewModel.finishEventLiveData.observe(this) { shouldFinish ->
|
||||||
|
if (shouldFinish == true) {
|
||||||
|
setResult(RESULT_OK)
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
viewModel.toastEventLiveData.observe(this) { message ->
|
||||||
|
message?.let { Toast.makeText(this, it, Toast.LENGTH_SHORT).show() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun bindSendEnabled(isEnabled: Boolean) = with(binding.tvCreatorChannelFantalkWriteSend) {
|
||||||
|
this.isEnabled = isEnabled
|
||||||
|
setTextColor(
|
||||||
|
ContextCompat.getColor(
|
||||||
|
context,
|
||||||
|
if (isEnabled) R.color.white else R.color.gray_500
|
||||||
|
)
|
||||||
|
)
|
||||||
|
setBackgroundResource(
|
||||||
|
if (isEnabled) {
|
||||||
|
R.drawable.bg_creator_channel_fantalk_write_send_enabled
|
||||||
|
} else {
|
||||||
|
R.drawable.bg_creator_channel_fantalk_write_send_disabled
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun bindLoading(isLoading: Boolean) = with(binding) {
|
||||||
|
etCreatorChannelFantalkWriteContent.isEnabled = !isLoading
|
||||||
|
tvCreatorChannelFantalkWriteSend.text = getString(
|
||||||
|
if (isLoading) R.string.creator_channel_fantalk_write_sending else R.string.creator_channel_fantalk_write_send
|
||||||
|
)
|
||||||
|
bindSendEnabled(!isLoading && viewModel.isSendEnabledLiveData.value == true)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun focusContentInput() {
|
||||||
|
binding.etCreatorChannelFantalkWriteContent.post {
|
||||||
|
binding.etCreatorChannelFantalkWriteContent.requestFocus()
|
||||||
|
WindowCompat.getInsetsController(window, binding.etCreatorChannelFantalkWriteContent)
|
||||||
|
.show(WindowInsetsCompat.Type.ime())
|
||||||
|
val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||||
|
imm.showSoftInput(binding.etCreatorChannelFantalkWriteContent, InputMethodManager.SHOW_IMPLICIT)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleCancel() {
|
||||||
|
if (binding.etCreatorChannelFantalkWriteContent.text?.toString().orEmpty().trim().isEmpty()) {
|
||||||
|
finish()
|
||||||
|
} else {
|
||||||
|
showCancelConfirmDialog()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showCancelConfirmDialog() {
|
||||||
|
V2ModalDialog(
|
||||||
|
activity = this,
|
||||||
|
layoutInflater = layoutInflater,
|
||||||
|
title = getString(R.string.creator_channel_fantalk_write_exit_title),
|
||||||
|
desc = getString(R.string.creator_channel_fantalk_write_exit_desc),
|
||||||
|
confirmButtonTitle = getString(R.string.creator_channel_fantalk_write_exit_confirm),
|
||||||
|
confirmButtonClick = { finish() },
|
||||||
|
cancelButtonTitle = getString(R.string.creator_channel_fantalk_write_exit_cancel),
|
||||||
|
cancelButtonClick = {}
|
||||||
|
).show(screenWidth)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val EXTRA_CREATOR_ID: String = "extra_creator_id"
|
||||||
|
|
||||||
|
fun newIntent(context: Context, creatorId: Long): Intent {
|
||||||
|
return Intent(context, CreatorChannelFanTalkWriteActivity::class.java).apply {
|
||||||
|
putExtra(EXTRA_CREATOR_ID, creatorId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
package kr.co.vividnext.sodalive.v2.creator.channel.fantalk.write
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import com.orhanobut.logger.Logger
|
||||||
|
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||||
|
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||||
|
import kr.co.vividnext.sodalive.R
|
||||||
|
import kr.co.vividnext.sodalive.base.BaseViewModel
|
||||||
|
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
|
||||||
|
import kr.co.vividnext.sodalive.common.SodaLiveApplicationHolder
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelRepository
|
||||||
|
|
||||||
|
class CreatorChannelFanTalkWriteViewModel(
|
||||||
|
private val repository: CreatorChannelRepository
|
||||||
|
) : BaseViewModel() {
|
||||||
|
|
||||||
|
private val _contentLiveData = MutableLiveData("")
|
||||||
|
val contentLiveData: LiveData<String>
|
||||||
|
get() = _contentLiveData
|
||||||
|
|
||||||
|
private val _isSendEnabledLiveData = MutableLiveData(false)
|
||||||
|
val isSendEnabledLiveData: LiveData<Boolean>
|
||||||
|
get() = _isSendEnabledLiveData
|
||||||
|
|
||||||
|
private val _contentLengthLiveData = MutableLiveData(0)
|
||||||
|
val contentLengthLiveData: LiveData<Int>
|
||||||
|
get() = _contentLengthLiveData
|
||||||
|
|
||||||
|
private val _isLoadingLiveData = MutableLiveData(false)
|
||||||
|
val isLoadingLiveData: LiveData<Boolean>
|
||||||
|
get() = _isLoadingLiveData
|
||||||
|
|
||||||
|
private val _finishEventLiveData = MutableLiveData(false)
|
||||||
|
val finishEventLiveData: LiveData<Boolean>
|
||||||
|
get() = _finishEventLiveData
|
||||||
|
|
||||||
|
private val _toastEventLiveData = MutableLiveData<String?>()
|
||||||
|
val toastEventLiveData: LiveData<String?>
|
||||||
|
get() = _toastEventLiveData
|
||||||
|
|
||||||
|
fun onContentChanged(content: String) {
|
||||||
|
val limitedContent = content.take(MAX_CONTENT_LENGTH)
|
||||||
|
_contentLiveData.value = limitedContent
|
||||||
|
_contentLengthLiveData.value = limitedContent.length
|
||||||
|
_isSendEnabledLiveData.value = limitedContent.trim().isNotEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun submit(creatorId: Long) {
|
||||||
|
val trimmedContent = _contentLiveData.value.orEmpty().trim()
|
||||||
|
if (creatorId <= 0L) return
|
||||||
|
if (_isLoadingLiveData.value == true) return
|
||||||
|
if (trimmedContent.isEmpty()) return
|
||||||
|
|
||||||
|
_isLoadingLiveData.value = true
|
||||||
|
compositeDisposable.add(
|
||||||
|
repository.writeFanTalk(
|
||||||
|
creatorId = creatorId,
|
||||||
|
content = trimmedContent,
|
||||||
|
token = "Bearer ${SharedPreferenceManager.token}"
|
||||||
|
)
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(
|
||||||
|
{ response ->
|
||||||
|
_isLoadingLiveData.value = false
|
||||||
|
if (response.success) {
|
||||||
|
_finishEventLiveData.value = true
|
||||||
|
} else {
|
||||||
|
_toastEventLiveData.value = response.message ?: unknownErrorMessage()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ error ->
|
||||||
|
_isLoadingLiveData.value = false
|
||||||
|
error.message?.let { Logger.e(it) }
|
||||||
|
_toastEventLiveData.value = unknownErrorMessage()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun unknownErrorMessage(): String {
|
||||||
|
return SodaLiveApplicationHolder.get().getString(R.string.common_error_unknown)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val MAX_CONTENT_LENGTH: Int = 500
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package kr.co.vividnext.sodalive.v2.creator.channel.ui
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.annotation.ColorRes
|
||||||
|
import coil.transform.CircleCropTransformation
|
||||||
|
import kr.co.vividnext.sodalive.R
|
||||||
|
import kr.co.vividnext.sodalive.extensions.loadUrl
|
||||||
|
import kr.co.vividnext.sodalive.extensions.moneyFormat
|
||||||
|
|
||||||
|
internal fun bindCreatorChannelDonationCard(
|
||||||
|
headerView: View,
|
||||||
|
profileView: ImageView,
|
||||||
|
nicknameView: TextView,
|
||||||
|
createdAtView: TextView,
|
||||||
|
canView: TextView,
|
||||||
|
messageView: TextView,
|
||||||
|
@ColorRes headerColorResId: Int,
|
||||||
|
profileImageUrl: String,
|
||||||
|
nickname: String,
|
||||||
|
createdAtText: String,
|
||||||
|
can: Int,
|
||||||
|
message: String
|
||||||
|
) {
|
||||||
|
val context = headerView.context
|
||||||
|
headerView.setBackgroundColor(context.getColor(headerColorResId))
|
||||||
|
profileView.loadUrl(profileImageUrl) {
|
||||||
|
crossfade(true)
|
||||||
|
placeholder(R.drawable.ic_placeholder_profile)
|
||||||
|
error(R.drawable.ic_placeholder_profile)
|
||||||
|
transformations(CircleCropTransformation())
|
||||||
|
}
|
||||||
|
nicknameView.text = nickname
|
||||||
|
createdAtView.text = createdAtText
|
||||||
|
canView.text = context.getString(R.string.creator_channel_donation_can_format, can.moneyFormat())
|
||||||
|
messageView.text = message
|
||||||
|
if (headerColorResId == R.color.red_400) {
|
||||||
|
nicknameView.setTextColor(context.getColor(R.color.white))
|
||||||
|
createdAtView.setTextColor(context.getColor(R.color.gray_100))
|
||||||
|
} else {
|
||||||
|
nicknameView.setTextColor(context.getColor(R.color.black))
|
||||||
|
createdAtView.setTextColor(context.getColor(R.color.gray_700))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,18 +15,18 @@ import androidx.core.net.toUri
|
|||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.recyclerview.widget.GridLayoutManager
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import kr.co.vividnext.sodalive.R
|
|
||||||
import coil.transform.CircleCropTransformation
|
import coil.transform.CircleCropTransformation
|
||||||
|
import kr.co.vividnext.sodalive.R
|
||||||
import kr.co.vividnext.sodalive.common.formatUtcRelativeTimeText
|
import kr.co.vividnext.sodalive.common.formatUtcRelativeTimeText
|
||||||
import kr.co.vividnext.sodalive.common.image.BlurTransformation
|
import kr.co.vividnext.sodalive.common.image.BlurTransformation
|
||||||
import kr.co.vividnext.sodalive.extensions.loadUrl
|
import kr.co.vividnext.sodalive.extensions.loadUrl
|
||||||
import kr.co.vividnext.sodalive.extensions.moneyFormat
|
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelAudioContentResponse
|
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelAudioContentResponse
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelCommunityPostResponse
|
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelCommunityPostResponse
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelLiveResponse
|
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelLiveResponse
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelScheduleResponse
|
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelScheduleResponse
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelSeriesResponse
|
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelSeriesResponse
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.model.CreatorChannelHomeSection
|
import kr.co.vividnext.sodalive.v2.creator.channel.model.CreatorChannelHomeSection
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.model.CreatorChannelTab
|
||||||
import kr.co.vividnext.sodalive.v2.widget.feed.FeedCommunityView
|
import kr.co.vividnext.sodalive.v2.widget.feed.FeedCommunityView
|
||||||
import kr.co.vividnext.sodalive.v2.widget.feed.FeedItem
|
import kr.co.vividnext.sodalive.v2.widget.feed.FeedItem
|
||||||
import kr.co.vividnext.sodalive.v2.widget.feed.FeedSize
|
import kr.co.vividnext.sodalive.v2.widget.feed.FeedSize
|
||||||
@@ -40,7 +40,9 @@ class CreatorChannelHomeSectionAdapter(
|
|||||||
private val onScheduleClick: (CreatorChannelScheduleResponse) -> Unit = {},
|
private val onScheduleClick: (CreatorChannelScheduleResponse) -> Unit = {},
|
||||||
private val onAudioContentClick: (CreatorChannelAudioContentResponse) -> Unit = {},
|
private val onAudioContentClick: (CreatorChannelAudioContentResponse) -> Unit = {},
|
||||||
private val onSeriesClick: (CreatorChannelSeriesResponse) -> Unit = {},
|
private val onSeriesClick: (CreatorChannelSeriesResponse) -> Unit = {},
|
||||||
private val onDonationClick: () -> Unit = {}
|
private val onDonationClick: () -> Unit = {},
|
||||||
|
private val onSectionChevronClick: (CreatorChannelTab) -> Unit = {},
|
||||||
|
private val onCommunityClick: (Long) -> Unit = {}
|
||||||
) : RecyclerView.Adapter<CreatorChannelHomeSectionAdapter.SectionViewHolder>() {
|
) : RecyclerView.Adapter<CreatorChannelHomeSectionAdapter.SectionViewHolder>() {
|
||||||
|
|
||||||
private var items: List<CreatorChannelHomeSection> = emptyList()
|
private var items: List<CreatorChannelHomeSection> = emptyList()
|
||||||
@@ -54,7 +56,16 @@ class CreatorChannelHomeSectionAdapter(
|
|||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SectionViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SectionViewHolder {
|
||||||
val view = LayoutInflater.from(parent.context).inflate(viewType, parent, false)
|
val view = LayoutInflater.from(parent.context).inflate(viewType, parent, false)
|
||||||
return SectionViewHolder(view, onLiveClick, onScheduleClick, onAudioContentClick, onSeriesClick, onDonationClick)
|
return SectionViewHolder(
|
||||||
|
view,
|
||||||
|
onLiveClick,
|
||||||
|
onScheduleClick,
|
||||||
|
onAudioContentClick,
|
||||||
|
onSeriesClick,
|
||||||
|
onDonationClick,
|
||||||
|
onSectionChevronClick,
|
||||||
|
onCommunityClick
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: SectionViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: SectionViewHolder, position: Int) {
|
||||||
@@ -69,9 +80,12 @@ class CreatorChannelHomeSectionAdapter(
|
|||||||
private val onScheduleClick: (CreatorChannelScheduleResponse) -> Unit,
|
private val onScheduleClick: (CreatorChannelScheduleResponse) -> Unit,
|
||||||
private val onAudioContentClick: (CreatorChannelAudioContentResponse) -> Unit,
|
private val onAudioContentClick: (CreatorChannelAudioContentResponse) -> Unit,
|
||||||
private val onSeriesClick: (CreatorChannelSeriesResponse) -> Unit,
|
private val onSeriesClick: (CreatorChannelSeriesResponse) -> Unit,
|
||||||
private val onDonationClick: () -> Unit
|
private val onDonationClick: () -> Unit,
|
||||||
|
private val onSectionChevronClick: (CreatorChannelTab) -> Unit,
|
||||||
|
private val onCommunityClick: (Long) -> Unit
|
||||||
) : RecyclerView.ViewHolder(view) {
|
) : RecyclerView.ViewHolder(view) {
|
||||||
private val title: TextView? = view.findViewById(R.id.tv_section_title)
|
private val title: TextView? = view.findViewById(R.id.tv_section_title)
|
||||||
|
private val sectionTitleChevron: ImageView? = view.findViewById(R.id.iv_section_title_chevron)
|
||||||
private val currentLiveTitle: TextView? = view.findViewById(R.id.tv_current_live_title)
|
private val currentLiveTitle: TextView? = view.findViewById(R.id.tv_current_live_title)
|
||||||
private val currentLiveStartTime: TextView? = view.findViewById(R.id.tv_current_live_start_time)
|
private val currentLiveStartTime: TextView? = view.findViewById(R.id.tv_current_live_start_time)
|
||||||
private val currentLivePrice: TextView? = view.findViewById(R.id.tv_current_live_price)
|
private val currentLivePrice: TextView? = view.findViewById(R.id.tv_current_live_price)
|
||||||
@@ -94,6 +108,7 @@ class CreatorChannelHomeSectionAdapter(
|
|||||||
private val audioContentsRecyclerView: RecyclerView? = view.findViewById(R.id.rv_audio_contents)
|
private val audioContentsRecyclerView: RecyclerView? = view.findViewById(R.id.rv_audio_contents)
|
||||||
private val seriesItems: LinearLayout? = view.findViewById(R.id.ll_series_items)
|
private val seriesItems: LinearLayout? = view.findViewById(R.id.ll_series_items)
|
||||||
private val communityItems: LinearLayout? = view.findViewById(R.id.ll_community_items)
|
private val communityItems: LinearLayout? = view.findViewById(R.id.ll_community_items)
|
||||||
|
private val communityMoreButton: View? = view.findViewById(R.id.layout_community_more_button)
|
||||||
private val fanTalkCard: View? = view.findViewById(R.id.layout_fantalk_card)
|
private val fanTalkCard: View? = view.findViewById(R.id.layout_fantalk_card)
|
||||||
private val fanTalkTotalRow: View? = view.findViewById(R.id.layout_fantalk_total_row)
|
private val fanTalkTotalRow: View? = view.findViewById(R.id.layout_fantalk_total_row)
|
||||||
private val fanTalkLatestRow: View? = view.findViewById(R.id.layout_fantalk_latest_row)
|
private val fanTalkLatestRow: View? = view.findViewById(R.id.layout_fantalk_latest_row)
|
||||||
@@ -121,6 +136,11 @@ class CreatorChannelHomeSectionAdapter(
|
|||||||
fun bind(item: CreatorChannelHomeSection) {
|
fun bind(item: CreatorChannelHomeSection) {
|
||||||
itemView.setOnClickListener(null)
|
itemView.setOnClickListener(null)
|
||||||
title?.setText(item.titleResId)
|
title?.setText(item.titleResId)
|
||||||
|
sectionTitleChevron?.isVisible = item.chevronTab != null
|
||||||
|
sectionTitleChevron?.setOnClickListener {
|
||||||
|
item.chevronTab?.let(onSectionChevronClick)
|
||||||
|
}
|
||||||
|
communityMoreButton?.setOnClickListener(null)
|
||||||
donationItems?.removeAllViews()
|
donationItems?.removeAllViews()
|
||||||
noticeItems?.removeAllViews()
|
noticeItems?.removeAllViews()
|
||||||
scheduleTimeline?.removeAllViews()
|
scheduleTimeline?.removeAllViews()
|
||||||
@@ -195,22 +215,24 @@ class CreatorChannelHomeSectionAdapter(
|
|||||||
).apply {
|
).apply {
|
||||||
marginEnd = if (index == visibleDonations.lastIndex) 0 else 4.dp()
|
marginEnd = if (index == visibleDonations.lastIndex) 0 else 4.dp()
|
||||||
}
|
}
|
||||||
row.findViewById<View>(R.id.layout_donation_header)
|
val headerColorResId = calculateCreatorChannelDonationHeaderColorRes(donation.can)
|
||||||
.setBackgroundColor(itemView.context.getColor(calculateCreatorChannelDonationHeaderColorRes(donation.can)))
|
val donationMessage = donation.message.ifBlank {
|
||||||
row.findViewById<ImageView>(R.id.iv_donation_profile).loadUrl(donation.profileImageUrl) {
|
|
||||||
placeholder(R.drawable.ic_placeholder_profile)
|
|
||||||
transformations(CircleCropTransformation())
|
|
||||||
}
|
|
||||||
row.findViewById<TextView>(R.id.tv_donation_nickname).text = donation.nickname
|
|
||||||
row.findViewById<TextView>(R.id.tv_donation_created_at).text =
|
|
||||||
formatUtcRelativeTimeText(itemView.context, donation.createdAtUtc)
|
|
||||||
row.findViewById<TextView>(R.id.tv_donation_can).text = itemView.context.getString(
|
|
||||||
R.string.creator_channel_donation_can_format,
|
|
||||||
donation.can.moneyFormat()
|
|
||||||
)
|
|
||||||
row.findViewById<TextView>(R.id.tv_donation_message).text = donation.message.ifBlank {
|
|
||||||
itemView.context.getString(R.string.creator_channel_donation_fallback_message, donation.can)
|
itemView.context.getString(R.string.creator_channel_donation_fallback_message, donation.can)
|
||||||
}
|
}
|
||||||
|
bindCreatorChannelDonationCard(
|
||||||
|
headerView = row.findViewById(R.id.layout_donation_header),
|
||||||
|
profileView = row.findViewById(R.id.iv_donation_profile),
|
||||||
|
nicknameView = row.findViewById(R.id.tv_donation_nickname),
|
||||||
|
createdAtView = row.findViewById(R.id.tv_donation_created_at),
|
||||||
|
canView = row.findViewById(R.id.tv_donation_can),
|
||||||
|
messageView = row.findViewById(R.id.tv_donation_message),
|
||||||
|
headerColorResId = headerColorResId,
|
||||||
|
profileImageUrl = donation.profileImageUrl,
|
||||||
|
nickname = donation.nickname,
|
||||||
|
createdAtText = formatUtcRelativeTimeText(itemView.context, donation.createdAtUtc),
|
||||||
|
can = donation.can,
|
||||||
|
message = donationMessage
|
||||||
|
)
|
||||||
donationItems?.addView(row)
|
donationItems?.addView(row)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -350,6 +372,7 @@ class CreatorChannelHomeSectionAdapter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun bindCommunities(item: CreatorChannelHomeSection.Communities) {
|
private fun bindCommunities(item: CreatorChannelHomeSection.Communities) {
|
||||||
|
communityMoreButton?.setOnClickListener { onSectionChevronClick(CreatorChannelTab.Community) }
|
||||||
val visibleCommunities = item.communities.take(MAX_COMMUNITY_ITEM_COUNT)
|
val visibleCommunities = item.communities.take(MAX_COMMUNITY_ITEM_COUNT)
|
||||||
visibleCommunities.forEachIndexed { index, community ->
|
visibleCommunities.forEachIndexed { index, community ->
|
||||||
val communityWidthDp = calculateCreatorChannelCommunityCardWidthDp(
|
val communityWidthDp = calculateCreatorChannelCommunityCardWidthDp(
|
||||||
@@ -376,6 +399,7 @@ class CreatorChannelHomeSectionAdapter(
|
|||||||
).apply {
|
).apply {
|
||||||
bottomMargin = if (index == visibleCommunities.lastIndex) 0 else 8.dp()
|
bottomMargin = if (index == visibleCommunities.lastIndex) 0 else 8.dp()
|
||||||
}
|
}
|
||||||
|
row.setOnClickListener { onCommunityClick(community.postId) }
|
||||||
communityItems?.addView(row)
|
communityItems?.addView(row)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -589,6 +613,15 @@ class CreatorChannelHomeSectionAdapter(
|
|||||||
is CreatorChannelHomeSection.Sns -> R.string.creator_channel_section_sns
|
is CreatorChannelHomeSection.Sns -> R.string.creator_channel_section_sns
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val CreatorChannelHomeSection.chevronTab: CreatorChannelTab?
|
||||||
|
get() = when (this) {
|
||||||
|
is CreatorChannelHomeSection.Donations -> CreatorChannelTab.Donation
|
||||||
|
is CreatorChannelHomeSection.AudioContents -> CreatorChannelTab.Audio
|
||||||
|
is CreatorChannelHomeSection.Series -> CreatorChannelTab.Series
|
||||||
|
is CreatorChannelHomeSection.FanTalk -> CreatorChannelTab.FanTalk
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
|
||||||
private const val MAX_DONATION_ITEM_COUNT = 8
|
private const val MAX_DONATION_ITEM_COUNT = 8
|
||||||
private const val MAX_NOTICE_ITEM_COUNT = 3
|
private const val MAX_NOTICE_ITEM_COUNT = 3
|
||||||
private const val MAX_SCHEDULE_ITEM_COUNT = 3
|
private const val MAX_SCHEDULE_ITEM_COUNT = 3
|
||||||
@@ -678,7 +711,7 @@ private const val SNS_TOTAL_GAP_DP = 64
|
|||||||
|
|
||||||
@ColorRes
|
@ColorRes
|
||||||
internal fun calculateCreatorChannelDonationHeaderColorRes(can: Int): Int = when {
|
internal fun calculateCreatorChannelDonationHeaderColorRes(can: Int): Int = when {
|
||||||
can >= 500 -> R.color.red_400
|
can >= 501 -> R.color.red_400
|
||||||
can >= 101 -> R.color.creator_channel_donation_cyan
|
can >= 101 -> R.color.creator_channel_donation_cyan
|
||||||
can >= 51 -> R.color.green_400
|
can >= 51 -> R.color.green_400
|
||||||
else -> R.color.gray_200
|
else -> R.color.gray_200
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package kr.co.vividnext.sodalive.v2.live.onair
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.Gravity
|
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.media3.common.util.UnstableApi
|
import androidx.media3.common.util.UnstableApi
|
||||||
@@ -14,7 +13,6 @@ import kr.co.vividnext.sodalive.R
|
|||||||
import kr.co.vividnext.sodalive.audio_content.AudioContentPlayService
|
import kr.co.vividnext.sodalive.audio_content.AudioContentPlayService
|
||||||
import kr.co.vividnext.sodalive.audio_content.player.AudioContentPlayerService
|
import kr.co.vividnext.sodalive.audio_content.player.AudioContentPlayerService
|
||||||
import kr.co.vividnext.sodalive.base.BaseActivity
|
import kr.co.vividnext.sodalive.base.BaseActivity
|
||||||
import kr.co.vividnext.sodalive.base.SodaDialog
|
|
||||||
import kr.co.vividnext.sodalive.common.Constants
|
import kr.co.vividnext.sodalive.common.Constants
|
||||||
import kr.co.vividnext.sodalive.common.LoadingDialog
|
import kr.co.vividnext.sodalive.common.LoadingDialog
|
||||||
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
|
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
|
||||||
@@ -34,6 +32,7 @@ import kr.co.vividnext.sodalive.settings.language.LanguageManager
|
|||||||
import kr.co.vividnext.sodalive.settings.language.LocaleHelper
|
import kr.co.vividnext.sodalive.settings.language.LocaleHelper
|
||||||
import kr.co.vividnext.sodalive.splash.SplashActivity
|
import kr.co.vividnext.sodalive.splash.SplashActivity
|
||||||
import kr.co.vividnext.sodalive.user.login.LoginActivity
|
import kr.co.vividnext.sodalive.user.login.LoginActivity
|
||||||
|
import kr.co.vividnext.sodalive.v2.components.modal.V2ModalDialog
|
||||||
import kr.co.vividnext.sodalive.v2.live.onair.model.HomeOnAirLivePageUiState
|
import kr.co.vividnext.sodalive.v2.live.onair.model.HomeOnAirLivePageUiState
|
||||||
import kr.co.vividnext.sodalive.v2.live.onair.model.canEnterHomeOnAirLiveRoom
|
import kr.co.vividnext.sodalive.v2.live.onair.model.canEnterHomeOnAirLiveRoom
|
||||||
import kr.co.vividnext.sodalive.v2.live.onair.ui.HomeOnAirLiveAdapter
|
import kr.co.vividnext.sodalive.v2.live.onair.ui.HomeOnAirLiveAdapter
|
||||||
@@ -183,7 +182,7 @@ class HomeOnAirLiveActivity : BaseActivity<ActivityHomeOnAirLiveBinding>(
|
|||||||
if (isAdult) {
|
if (isAdult) {
|
||||||
val isKoreanCountry = SharedPreferenceManager.countryCode.ifBlank { "KR" } == "KR"
|
val isKoreanCountry = SharedPreferenceManager.countryCode.ifBlank { "KR" } == "KR"
|
||||||
if (isKoreanCountry && !SharedPreferenceManager.isAuth) {
|
if (isKoreanCountry && !SharedPreferenceManager.isAuth) {
|
||||||
SodaDialog(
|
V2ModalDialog(
|
||||||
activity = this,
|
activity = this,
|
||||||
layoutInflater = layoutInflater,
|
layoutInflater = layoutInflater,
|
||||||
title = getString(R.string.auth_title),
|
title = getString(R.string.auth_title),
|
||||||
@@ -191,8 +190,7 @@ class HomeOnAirLiveActivity : BaseActivity<ActivityHomeOnAirLiveBinding>(
|
|||||||
confirmButtonTitle = getString(R.string.auth_go),
|
confirmButtonTitle = getString(R.string.auth_go),
|
||||||
confirmButtonClick = { startAuthFlow() },
|
confirmButtonClick = { startAuthFlow() },
|
||||||
cancelButtonTitle = getString(R.string.cancel),
|
cancelButtonTitle = getString(R.string.cancel),
|
||||||
cancelButtonClick = {},
|
cancelButtonClick = {}
|
||||||
descGravity = Gravity.CENTER
|
|
||||||
).show(screenWidth)
|
).show(screenWidth)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
package kr.co.vividnext.sodalive.v2.main
|
package kr.co.vividnext.sodalive.v2.main
|
||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.ComponentName
|
import android.content.ComponentName
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.content.IntentFilter
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
@@ -39,6 +42,7 @@ import kr.co.vividnext.sodalive.audition.AuditionActivity
|
|||||||
import kr.co.vividnext.sodalive.base.BaseActivity
|
import kr.co.vividnext.sodalive.base.BaseActivity
|
||||||
import kr.co.vividnext.sodalive.common.Constants
|
import kr.co.vividnext.sodalive.common.Constants
|
||||||
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
|
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
|
||||||
|
import kr.co.vividnext.sodalive.extensions.dpToPx
|
||||||
import kr.co.vividnext.sodalive.databinding.ActivityMainV2Binding
|
import kr.co.vividnext.sodalive.databinding.ActivityMainV2Binding
|
||||||
import kr.co.vividnext.sodalive.explorer.profile.creator_community.all.CreatorCommunityAllActivity
|
import kr.co.vividnext.sodalive.explorer.profile.creator_community.all.CreatorCommunityAllActivity
|
||||||
import kr.co.vividnext.sodalive.main.EventPopupDialogFragment
|
import kr.co.vividnext.sodalive.main.EventPopupDialogFragment
|
||||||
@@ -72,6 +76,7 @@ class MainV2Activity : BaseActivity<ActivityMainV2Binding>(ActivityMainV2Binding
|
|||||||
private var mediaControllerFuture: ListenableFuture<MediaController>? = null
|
private var mediaControllerFuture: ListenableFuture<MediaController>? = null
|
||||||
private val handler = Handler(Looper.getMainLooper())
|
private val handler = Handler(Looper.getMainLooper())
|
||||||
private val showMiniPlayerRunnable = Runnable { initAndVisibleMiniPlayer() }
|
private val showMiniPlayerRunnable = Runnable { initAndVisibleMiniPlayer() }
|
||||||
|
private val audioContentReceiver = AudioContentReceiver()
|
||||||
private var playerStateJob: Job? = null
|
private var playerStateJob: Job? = null
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
@@ -82,11 +87,16 @@ class MainV2Activity : BaseActivity<ActivityMainV2Binding>(ActivityMainV2Binding
|
|||||||
trackAppLaunchIfNeeded()
|
trackAppLaunchIfNeeded()
|
||||||
pushTokenUpdate()
|
pushTokenUpdate()
|
||||||
|
|
||||||
|
handler.postDelayed({
|
||||||
|
if (!handleAudioNotificationRoute(intent) && isLoggedIn()) {
|
||||||
|
executeDeeplink(intent)
|
||||||
|
}
|
||||||
|
}, 1000)
|
||||||
|
|
||||||
if (isLoggedIn()) {
|
if (isLoggedIn()) {
|
||||||
updatePidAndGaid()
|
updatePidAndGaid()
|
||||||
getEventPopup()
|
getEventPopup()
|
||||||
observePlayerState()
|
observePlayerState()
|
||||||
handler.postDelayed({ executeDeeplink(intent) }, 1000)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,14 +124,22 @@ class MainV2Activity : BaseActivity<ActivityMainV2Binding>(ActivityMainV2Binding
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isLoggedIn()) {
|
if (!handleAudioNotificationRoute(intent) && isLoggedIn()) {
|
||||||
executeDeeplink(intent)
|
executeDeeplink(intent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("UnspecifiedRegisterReceiverFlag")
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
getMemberInfo()
|
getMemberInfo()
|
||||||
|
val intentFilter = IntentFilter(Constants.ACTION_MAIN_AUDIO_CONTENT_RECEIVER)
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
registerReceiver(audioContentReceiver, intentFilter, RECEIVER_NOT_EXPORTED)
|
||||||
|
} else {
|
||||||
|
registerReceiver(audioContentReceiver, intentFilter)
|
||||||
|
}
|
||||||
|
|
||||||
startService(
|
startService(
|
||||||
Intent(this, AudioContentPlayService::class.java).apply {
|
Intent(this, AudioContentPlayService::class.java).apply {
|
||||||
action = AudioContentPlayService.MusicAction.INIT.name
|
action = AudioContentPlayService.MusicAction.INIT.name
|
||||||
@@ -129,6 +147,11 @@ class MainV2Activity : BaseActivity<ActivityMainV2Binding>(ActivityMainV2Binding
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onPause() {
|
||||||
|
unregisterReceiver(audioContentReceiver)
|
||||||
|
super.onPause()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
deInitMiniPlayer()
|
deInitMiniPlayer()
|
||||||
playerStateJob?.cancel()
|
playerStateJob?.cancel()
|
||||||
@@ -405,6 +428,39 @@ class MainV2Activity : BaseActivity<ActivityMainV2Binding>(ActivityMainV2Binding
|
|||||||
playerFragment.show(supportFragmentManager, playerFragment.tag)
|
playerFragment.show(supportFragmentManager, playerFragment.tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleAudioNotificationRoute(intent: Intent): Boolean {
|
||||||
|
val route = intent.getStringExtra(EXTRA_AUDIO_NOTIFICATION_ROUTE) ?: return false
|
||||||
|
intent.removeExtra(EXTRA_AUDIO_NOTIFICATION_ROUTE)
|
||||||
|
|
||||||
|
return when (route) {
|
||||||
|
ROUTE_AUDIO_PLAYER -> {
|
||||||
|
if (isLoggedIn()) {
|
||||||
|
showPlayerFragment()
|
||||||
|
} else {
|
||||||
|
showLoginActivity()
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
ROUTE_AUDIO_DETAIL -> {
|
||||||
|
val contentId = intent.getLongExtra(Constants.EXTRA_AUDIO_CONTENT_ID, 0)
|
||||||
|
intent.removeExtra(Constants.EXTRA_AUDIO_CONTENT_ID)
|
||||||
|
if (contentId > 0 && isLoggedIn()) {
|
||||||
|
startActivity(
|
||||||
|
Intent(applicationContext, AudioContentDetailActivity::class.java).apply {
|
||||||
|
putExtra(Constants.EXTRA_AUDIO_CONTENT_ID, contentId)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} else if (!isLoggedIn()) {
|
||||||
|
showLoginActivity()
|
||||||
|
}
|
||||||
|
contentId > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun checkPermissions() {
|
private fun checkPermissions() {
|
||||||
val permissions = mutableListOf(Manifest.permission.RECORD_AUDIO)
|
val permissions = mutableListOf(Manifest.permission.RECORD_AUDIO)
|
||||||
|
|
||||||
@@ -734,8 +790,90 @@ class MainV2Activity : BaseActivity<ActivityMainV2Binding>(ActivityMainV2Binding
|
|||||||
return SharedPreferenceManager.token.isNotBlank() && SharedPreferenceManager.token.length > 10
|
return SharedPreferenceManager.token.isNotBlank() && SharedPreferenceManager.token.length > 10
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inner class AudioContentReceiver : BroadcastReceiver() {
|
||||||
|
override fun onReceive(context: Context?, intent: Intent?) {
|
||||||
|
val contentId = intent?.getLongExtra(Constants.EXTRA_AUDIO_CONTENT_ID, 0)
|
||||||
|
val title = intent?.getStringExtra(Constants.EXTRA_AUDIO_CONTENT_TITLE)
|
||||||
|
val nickname = intent?.getStringExtra(Constants.EXTRA_NICKNAME)
|
||||||
|
val coverImageUrl = intent?.getStringExtra(
|
||||||
|
Constants.EXTRA_AUDIO_CONTENT_COVER_IMAGE_URL
|
||||||
|
)
|
||||||
|
val isPlaying = intent?.getBooleanExtra(Constants.EXTRA_AUDIO_CONTENT_PLAYING, false)
|
||||||
|
val isShowing = intent?.getBooleanExtra(Constants.EXTRA_AUDIO_CONTENT_SHOWING, false)
|
||||||
|
|
||||||
|
if (isShowing == true) {
|
||||||
|
binding.clMiniPlayer.visibility = View.VISIBLE
|
||||||
|
if (contentId != null && contentId > 0) {
|
||||||
|
binding.clMiniPlayer.setOnClickListener {
|
||||||
|
startActivity(
|
||||||
|
Intent(applicationContext, AudioContentDetailActivity::class.java).apply {
|
||||||
|
putExtra(Constants.EXTRA_AUDIO_CONTENT_ID, contentId)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isPlaying == true) {
|
||||||
|
binding.ivPlayerPlayOrPause.setImageResource(R.drawable.ic_noti_pause)
|
||||||
|
binding.ivPlayerPlayOrPause.setOnClickListener {
|
||||||
|
startService(
|
||||||
|
Intent(this@MainV2Activity, AudioContentPlayService::class.java).apply {
|
||||||
|
action = AudioContentPlayService.MusicAction.PAUSE.name
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
binding.ivPlayerPlayOrPause.setImageResource(R.drawable.ic_noti_play)
|
||||||
|
binding.ivPlayerPlayOrPause.setOnClickListener {
|
||||||
|
startService(
|
||||||
|
Intent(this@MainV2Activity, AudioContentPlayService::class.java).apply {
|
||||||
|
action = AudioContentPlayService.MusicAction.PLAY.name
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.ivPlayerStop.setOnClickListener {
|
||||||
|
startService(
|
||||||
|
Intent(this@MainV2Activity, AudioContentPlayService::class.java).apply {
|
||||||
|
action = AudioContentPlayService.MusicAction.STOP.name
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!title.isNullOrBlank()) {
|
||||||
|
binding.tvPlayerTitle.text = title
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!nickname.isNullOrBlank()) {
|
||||||
|
binding.tvPlayerNickname.text = nickname
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!coverImageUrl.isNullOrBlank()) {
|
||||||
|
binding.ivPlayerCover.load(coverImageUrl) {
|
||||||
|
crossfade(true)
|
||||||
|
placeholder(R.drawable.bg_placeholder)
|
||||||
|
transformations(RoundedCornersTransformation(5.3f.dpToPx()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
handler.post {
|
||||||
|
binding.ivPlayerPlayOrPause.setImageResource(0)
|
||||||
|
binding.ivPlayerCover.setImageResource(0)
|
||||||
|
binding.tvPlayerTitle.text = ""
|
||||||
|
binding.tvPlayerNickname.text = ""
|
||||||
|
binding.clMiniPlayer.visibility = View.GONE
|
||||||
|
binding.ivPlayerPlayOrPause.setOnClickListener {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val EXTRA_CHAT_FILTER: String = "extra_chat_filter"
|
private const val EXTRA_CHAT_FILTER: String = "extra_chat_filter"
|
||||||
|
const val EXTRA_AUDIO_NOTIFICATION_ROUTE: String = "extra_audio_notification_route"
|
||||||
|
const val ROUTE_AUDIO_PLAYER: String = "audio_player"
|
||||||
|
const val ROUTE_AUDIO_DETAIL: String = "audio_detail"
|
||||||
|
|
||||||
fun newChatDmIntent(context: Context): Intent {
|
fun newChatDmIntent(context: Context): Intent {
|
||||||
return Intent(context, MainV2Activity::class.java).apply {
|
return Intent(context, MainV2Activity::class.java).apply {
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
package kr.co.vividnext.sodalive.v2.main
|
package kr.co.vividnext.sodalive.v2.main
|
||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.view.Gravity
|
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import kr.co.vividnext.sodalive.R
|
import kr.co.vividnext.sodalive.R
|
||||||
import kr.co.vividnext.sodalive.base.SodaDialog
|
|
||||||
import kr.co.vividnext.sodalive.common.Constants
|
import kr.co.vividnext.sodalive.common.Constants
|
||||||
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
|
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
|
||||||
import kr.co.vividnext.sodalive.mypage.MyPageViewModel
|
import kr.co.vividnext.sodalive.mypage.MyPageViewModel
|
||||||
@@ -14,6 +12,7 @@ import kr.co.vividnext.sodalive.mypage.auth.AuthVerifyRequest
|
|||||||
import kr.co.vividnext.sodalive.mypage.auth.BootpayResponse
|
import kr.co.vividnext.sodalive.mypage.auth.BootpayResponse
|
||||||
import kr.co.vividnext.sodalive.settings.ContentSettingsActivity
|
import kr.co.vividnext.sodalive.settings.ContentSettingsActivity
|
||||||
import kr.co.vividnext.sodalive.splash.SplashActivity
|
import kr.co.vividnext.sodalive.splash.SplashActivity
|
||||||
|
import kr.co.vividnext.sodalive.v2.components.modal.V2ModalDialog
|
||||||
import org.koin.android.ext.android.get
|
import org.koin.android.ext.android.get
|
||||||
|
|
||||||
fun Fragment.ensureMainV2NavigationAllowed(
|
fun Fragment.ensureMainV2NavigationAllowed(
|
||||||
@@ -46,7 +45,7 @@ fun Fragment.ensureMainV2NavigationAllowed(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun Fragment.showMainV2AuthDialog() {
|
private fun Fragment.showMainV2AuthDialog() {
|
||||||
SodaDialog(
|
V2ModalDialog(
|
||||||
activity = requireActivity(),
|
activity = requireActivity(),
|
||||||
layoutInflater = layoutInflater,
|
layoutInflater = layoutInflater,
|
||||||
title = getString(R.string.auth_title),
|
title = getString(R.string.auth_title),
|
||||||
@@ -54,8 +53,7 @@ private fun Fragment.showMainV2AuthDialog() {
|
|||||||
confirmButtonTitle = getString(R.string.auth_go),
|
confirmButtonTitle = getString(R.string.auth_go),
|
||||||
confirmButtonClick = { startMainV2AuthFlow() },
|
confirmButtonClick = { startMainV2AuthFlow() },
|
||||||
cancelButtonTitle = getString(R.string.cancel),
|
cancelButtonTitle = getString(R.string.cancel),
|
||||||
cancelButtonClick = {},
|
cancelButtonClick = {}
|
||||||
descGravity = Gravity.CENTER
|
|
||||||
).show(resources.displayMetrics.widthPixels)
|
).show(resources.displayMetrics.widthPixels)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package kr.co.vividnext.sodalive.v2.main.chat
|
package kr.co.vividnext.sodalive.v2.main.chat
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
@@ -12,6 +13,9 @@ import kr.co.vividnext.sodalive.base.BaseFragment
|
|||||||
import kr.co.vividnext.sodalive.chat.talk.room.ChatRoomActivity
|
import kr.co.vividnext.sodalive.chat.talk.room.ChatRoomActivity
|
||||||
import kr.co.vividnext.sodalive.common.LoadingDialog
|
import kr.co.vividnext.sodalive.common.LoadingDialog
|
||||||
import kr.co.vividnext.sodalive.databinding.FragmentV2MainChatBinding
|
import kr.co.vividnext.sodalive.databinding.FragmentV2MainChatBinding
|
||||||
|
import kr.co.vividnext.sodalive.mypage.can.charge.CanChargeActivity
|
||||||
|
import kr.co.vividnext.sodalive.search.SearchActivity
|
||||||
|
import kr.co.vividnext.sodalive.v2.main.ensureMainV2NavigationAllowed
|
||||||
import kr.co.vividnext.sodalive.v2.main.chat.dm.DmChatRoomActivity
|
import kr.co.vividnext.sodalive.v2.main.chat.dm.DmChatRoomActivity
|
||||||
import kr.co.vividnext.sodalive.v2.main.chat.model.ChatRoomFilter
|
import kr.co.vividnext.sodalive.v2.main.chat.model.ChatRoomFilter
|
||||||
import kr.co.vividnext.sodalive.v2.main.chat.model.ChatRoomListUiItem
|
import kr.co.vividnext.sodalive.v2.main.chat.model.ChatRoomListUiItem
|
||||||
@@ -63,17 +67,29 @@ class ChatMainFragment : BaseFragment<FragmentV2MainChatBinding>(
|
|||||||
private fun setupTitleBar() {
|
private fun setupTitleBar() {
|
||||||
binding.viewChatTitleBar.tvTitleBarTitle.setText(R.string.tab_chat)
|
binding.viewChatTitleBar.tvTitleBarTitle.setText(R.string.tab_chat)
|
||||||
binding.viewChatTitleBar.ivTitleBarMenu.setImageResource(R.drawable.ic_bar_cash)
|
binding.viewChatTitleBar.ivTitleBarMenu.setImageResource(R.drawable.ic_bar_cash)
|
||||||
binding.viewChatTitleBar.llTitleBarActions.addView(
|
binding.viewChatTitleBar.ivTitleBarMenu.setOnClickListener {
|
||||||
ImageView(requireContext()).apply {
|
openWithLoginGuard(CanChargeActivity::class.java)
|
||||||
setImageResource(R.drawable.ic_bar_search)
|
}
|
||||||
layoutParams = LinearLayout.LayoutParams(
|
|
||||||
LinearLayout.LayoutParams.WRAP_CONTENT,
|
val searchAction = ImageView(requireContext()).apply {
|
||||||
LinearLayout.LayoutParams.WRAP_CONTENT
|
setImageResource(R.drawable.ic_bar_search)
|
||||||
).apply {
|
layoutParams = LinearLayout.LayoutParams(
|
||||||
marginStart = resources.getDimensionPixelSize(R.dimen.spacing_14)
|
LinearLayout.LayoutParams.WRAP_CONTENT,
|
||||||
}
|
LinearLayout.LayoutParams.WRAP_CONTENT
|
||||||
|
).apply {
|
||||||
|
marginStart = resources.getDimensionPixelSize(R.dimen.spacing_14)
|
||||||
}
|
}
|
||||||
)
|
}
|
||||||
|
searchAction.setOnClickListener {
|
||||||
|
openWithLoginGuard(SearchActivity::class.java)
|
||||||
|
}
|
||||||
|
binding.viewChatTitleBar.llTitleBarActions.addView(searchAction)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun openWithLoginGuard(activityClass: Class<*>) {
|
||||||
|
ensureMainV2NavigationAllowed {
|
||||||
|
startActivity(Intent(requireContext(), activityClass))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupFilterTabs(initialFilter: ChatRoomFilter) {
|
private fun setupFilterTabs(initialFilter: ChatRoomFilter) {
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import androidx.recyclerview.widget.GridLayoutManager
|
|||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import kr.co.vividnext.sodalive.R
|
import kr.co.vividnext.sodalive.R
|
||||||
|
import kr.co.vividnext.sodalive.audio_content.box.AudioContentBoxActivity
|
||||||
import kr.co.vividnext.sodalive.audio_content.detail.AudioContentDetailActivity
|
import kr.co.vividnext.sodalive.audio_content.detail.AudioContentDetailActivity
|
||||||
import kr.co.vividnext.sodalive.audio_content.series.detail.SeriesDetailActivity
|
import kr.co.vividnext.sodalive.audio_content.series.detail.SeriesDetailActivity
|
||||||
import kr.co.vividnext.sodalive.base.BaseFragment
|
import kr.co.vividnext.sodalive.base.BaseFragment
|
||||||
@@ -23,6 +24,8 @@ import kr.co.vividnext.sodalive.databinding.FragmentV2MainContentBinding
|
|||||||
import kr.co.vividnext.sodalive.databinding.ViewSectionTitleBinding
|
import kr.co.vividnext.sodalive.databinding.ViewSectionTitleBinding
|
||||||
import kr.co.vividnext.sodalive.extensions.moneyFormat
|
import kr.co.vividnext.sodalive.extensions.moneyFormat
|
||||||
import kr.co.vividnext.sodalive.home.SeriesPublishedDaysOfWeek
|
import kr.co.vividnext.sodalive.home.SeriesPublishedDaysOfWeek
|
||||||
|
import kr.co.vividnext.sodalive.mypage.can.charge.CanChargeActivity
|
||||||
|
import kr.co.vividnext.sodalive.search.SearchActivity
|
||||||
import kr.co.vividnext.sodalive.v2.common.data.ContentSort
|
import kr.co.vividnext.sodalive.v2.common.data.ContentSort
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.model.toLabelResId
|
import kr.co.vividnext.sodalive.v2.creator.channel.model.toLabelResId
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.ui.CreatorChannelSortPopup
|
import kr.co.vividnext.sodalive.v2.creator.channel.ui.CreatorChannelSortPopup
|
||||||
@@ -51,6 +54,7 @@ import kr.co.vividnext.sodalive.v2.main.content.model.usesSeriesItems
|
|||||||
import kr.co.vividnext.sodalive.v2.main.content.overview.ContentOverviewActivity
|
import kr.co.vividnext.sodalive.v2.main.content.overview.ContentOverviewActivity
|
||||||
import kr.co.vividnext.sodalive.v2.main.content.overview.data.ContentOverviewType
|
import kr.co.vividnext.sodalive.v2.main.content.overview.data.ContentOverviewType
|
||||||
import kr.co.vividnext.sodalive.v2.main.content.ui.CONTENT_ALL_GRID_SPAN_COUNT
|
import kr.co.vividnext.sodalive.v2.main.content.ui.CONTENT_ALL_GRID_SPAN_COUNT
|
||||||
|
import kr.co.vividnext.sodalive.v2.main.content.ui.CONTENT_RECOMMENDED_GRID_GAP_DP
|
||||||
import kr.co.vividnext.sodalive.v2.main.content.ui.CONTENT_RECOMMENDED_GRID_SPAN_COUNT
|
import kr.co.vividnext.sodalive.v2.main.content.ui.CONTENT_RECOMMENDED_GRID_SPAN_COUNT
|
||||||
import kr.co.vividnext.sodalive.v2.main.content.ui.ContentAllAudioCardAdapter
|
import kr.co.vividnext.sodalive.v2.main.content.ui.ContentAllAudioCardAdapter
|
||||||
import kr.co.vividnext.sodalive.v2.main.content.ui.ContentAllSeriesCardAdapter
|
import kr.co.vividnext.sodalive.v2.main.content.ui.ContentAllSeriesCardAdapter
|
||||||
@@ -113,6 +117,7 @@ class ContentMainFragment : BaseFragment<FragmentV2MainContentBinding>(
|
|||||||
setUpAllTypeTabs()
|
setUpAllTypeTabs()
|
||||||
setUpAllDayFilter()
|
setUpAllDayFilter()
|
||||||
setUpAllSortButton()
|
setUpAllSortButton()
|
||||||
|
setupTitleBarActions()
|
||||||
showContentTab(CONTENT_TAB_RECOMMENDATION)
|
showContentTab(CONTENT_TAB_RECOMMENDATION)
|
||||||
setUpSectionTitles()
|
setUpSectionTitles()
|
||||||
setUpAdapters()
|
setUpAdapters()
|
||||||
@@ -129,6 +134,24 @@ class ContentMainFragment : BaseFragment<FragmentV2MainContentBinding>(
|
|||||||
super.onDestroyView()
|
super.onDestroyView()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun setupTitleBarActions() {
|
||||||
|
binding.viewContentTitleBar.ivTitleBarCash.setOnClickListener {
|
||||||
|
openWithLoginGuard(CanChargeActivity::class.java)
|
||||||
|
}
|
||||||
|
binding.viewContentTitleBar.ivTitleBarSearch.setOnClickListener {
|
||||||
|
openWithLoginGuard(SearchActivity::class.java)
|
||||||
|
}
|
||||||
|
binding.viewContentTitleBar.ivTitleBarStorage.setOnClickListener {
|
||||||
|
openWithLoginGuard(AudioContentBoxActivity::class.java)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun openWithLoginGuard(activityClass: Class<*>) {
|
||||||
|
ensureMainV2NavigationAllowed {
|
||||||
|
startActivity(Intent(requireContext(), activityClass))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun selectAllTab(type: MainContentAllType, sort: ContentSort = ContentSort.LATEST) {
|
fun selectAllTab(type: MainContentAllType, sort: ContentSort = ContentSort.LATEST) {
|
||||||
pendingAllTabSelection = ContentAllTabSelection(type, sort)
|
pendingAllTabSelection = ContentAllTabSelection(type, sort)
|
||||||
if (view != null) {
|
if (view != null) {
|
||||||
@@ -296,7 +319,8 @@ class ContentMainFragment : BaseFragment<FragmentV2MainContentBinding>(
|
|||||||
binding.rvContentRecommendedAudios.apply {
|
binding.rvContentRecommendedAudios.apply {
|
||||||
layoutManager = GridLayoutManager(requireContext(), CONTENT_RECOMMENDED_GRID_SPAN_COUNT)
|
layoutManager = GridLayoutManager(requireContext(), CONTENT_RECOMMENDED_GRID_SPAN_COUNT)
|
||||||
adapter = recommendedAudioAdapter
|
adapter = recommendedAudioAdapter
|
||||||
addContentGridItemSpacing()
|
addContentGridItemSpacing(gapDp = CONTENT_RECOMMENDED_GRID_GAP_DP)
|
||||||
|
doOnLayout { updateRecommendedAudioGridItemWidth() }
|
||||||
}
|
}
|
||||||
binding.rvContentRankings.apply {
|
binding.rvContentRankings.apply {
|
||||||
layoutManager = ContentRankingAdapter.createGridLayoutManager(requireContext())
|
layoutManager = ContentRankingAdapter.createGridLayoutManager(requireContext())
|
||||||
@@ -401,6 +425,14 @@ class ContentMainFragment : BaseFragment<FragmentV2MainContentBinding>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun updateRecommendedAudioGridItemWidth() {
|
||||||
|
val widthPx = binding.rvContentRecommendedAudios.calculateContentGridItemWidthPx(
|
||||||
|
CONTENT_RECOMMENDED_GRID_SPAN_COUNT,
|
||||||
|
gapDp = CONTENT_RECOMMENDED_GRID_GAP_DP
|
||||||
|
)
|
||||||
|
recommendedAudioAdapter.setGridItemWidthPx(widthPx)
|
||||||
|
}
|
||||||
|
|
||||||
private fun updateAllTabGridItemWidth() {
|
private fun updateAllTabGridItemWidth() {
|
||||||
val widthPx = binding.rvContentAllItems.calculateContentGridItemWidthPx(CONTENT_ALL_GRID_SPAN_COUNT)
|
val widthPx = binding.rvContentAllItems.calculateContentGridItemWidthPx(CONTENT_ALL_GRID_SPAN_COUNT)
|
||||||
contentAllAudioCardAdapter.setGridItemWidthPx(widthPx)
|
contentAllAudioCardAdapter.setGridItemWidthPx(widthPx)
|
||||||
@@ -524,6 +556,7 @@ class ContentMainFragment : BaseFragment<FragmentV2MainContentBinding>(
|
|||||||
|
|
||||||
private fun bindRecommendedAudioSection(section: ContentAudioCardSection) {
|
private fun bindRecommendedAudioSection(section: ContentAudioCardSection) {
|
||||||
binding.llContentRecommendedAudioSection.visibility = section.items.toSectionVisibility()
|
binding.llContentRecommendedAudioSection.visibility = section.items.toSectionVisibility()
|
||||||
|
updateRecommendedAudioGridItemWidth()
|
||||||
recommendedAudioAdapter.submitItems(section.items)
|
recommendedAudioAdapter.submitItems(section.items)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,6 @@ interface AudioRankingsApi {
|
|||||||
@GET("/api/v2/audio/rankings")
|
@GET("/api/v2/audio/rankings")
|
||||||
fun getRankings(
|
fun getRankings(
|
||||||
@Header("Authorization") authHeader: String,
|
@Header("Authorization") authHeader: String,
|
||||||
@Query("type") type: AudioRankingType
|
@Query("type") type: String
|
||||||
): Single<ApiResponse<AudioRankingResponse>>
|
): Single<ApiResponse<AudioRankingResponse>>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,11 +14,22 @@ enum class AudioRankingType(
|
|||||||
val queryValue: String,
|
val queryValue: String,
|
||||||
val label: String
|
val label: String
|
||||||
) {
|
) {
|
||||||
|
@SerializedName("WEEKLY_POPULAR")
|
||||||
WEEKLY_POPULAR("WEEKLY_POPULAR", "주간 인기"),
|
WEEKLY_POPULAR("WEEKLY_POPULAR", "주간 인기"),
|
||||||
|
|
||||||
|
@SerializedName("RISING")
|
||||||
RISING("RISING", "지금 뜨는 중"),
|
RISING("RISING", "지금 뜨는 중"),
|
||||||
|
|
||||||
|
@SerializedName("REVENUE")
|
||||||
REVENUE("REVENUE", "매출"),
|
REVENUE("REVENUE", "매출"),
|
||||||
|
|
||||||
|
@SerializedName("SALES_COUNT")
|
||||||
SALES_COUNT("SALES_COUNT", "판매량"),
|
SALES_COUNT("SALES_COUNT", "판매량"),
|
||||||
|
|
||||||
|
@SerializedName("COMMENT_COUNT")
|
||||||
COMMENT_COUNT("COMMENT_COUNT", "댓글수"),
|
COMMENT_COUNT("COMMENT_COUNT", "댓글수"),
|
||||||
|
|
||||||
|
@SerializedName("LIKE_COUNT")
|
||||||
LIKE_COUNT("LIKE_COUNT", "좋아요")
|
LIKE_COUNT("LIKE_COUNT", "좋아요")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,6 @@ package kr.co.vividnext.sodalive.v2.main.content.data
|
|||||||
class AudioRankingsRepository(private val api: AudioRankingsApi) {
|
class AudioRankingsRepository(private val api: AudioRankingsApi) {
|
||||||
fun getRankings(token: String, type: AudioRankingType) = api.getRankings(
|
fun getRankings(token: String, type: AudioRankingType) = api.getRankings(
|
||||||
authHeader = token,
|
authHeader = token,
|
||||||
type = type
|
type = type.queryValue
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ package kr.co.vividnext.sodalive.v2.main.content.data
|
|||||||
|
|
||||||
import io.reactivex.rxjava3.core.Single
|
import io.reactivex.rxjava3.core.Single
|
||||||
import kr.co.vividnext.sodalive.common.ApiResponse
|
import kr.co.vividnext.sodalive.common.ApiResponse
|
||||||
import kr.co.vividnext.sodalive.home.SeriesPublishedDaysOfWeek
|
|
||||||
import kr.co.vividnext.sodalive.v2.common.data.ContentSort
|
|
||||||
import retrofit2.http.GET
|
import retrofit2.http.GET
|
||||||
import retrofit2.http.Header
|
import retrofit2.http.Header
|
||||||
import retrofit2.http.Query
|
import retrofit2.http.Query
|
||||||
@@ -12,10 +10,10 @@ interface MainContentAllTabApi {
|
|||||||
@GET("/api/v2/audio/contents")
|
@GET("/api/v2/audio/contents")
|
||||||
fun getContents(
|
fun getContents(
|
||||||
@Header("Authorization") authHeader: String,
|
@Header("Authorization") authHeader: String,
|
||||||
@Query("type") type: MainContentAllType,
|
@Query("type") type: String,
|
||||||
@Query("sort") sort: ContentSort,
|
@Query("sort") sort: String,
|
||||||
@Query("page") page: Int,
|
@Query("page") page: Int,
|
||||||
@Query("size") size: Int,
|
@Query("size") size: Int,
|
||||||
@Query("dayOfWeek") dayOfWeek: SeriesPublishedDaysOfWeek?
|
@Query("dayOfWeek") dayOfWeek: String?
|
||||||
): Single<ApiResponse<MainContentAllTabResponse>>
|
): Single<ApiResponse<MainContentAllTabResponse>>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,12 +18,22 @@ data class MainContentAllTabResponse(
|
|||||||
@SerializedName("hasNext") val hasNext: Boolean
|
@SerializedName("hasNext") val hasNext: Boolean
|
||||||
)
|
)
|
||||||
|
|
||||||
enum class MainContentAllType {
|
@Keep
|
||||||
AUDIO,
|
enum class MainContentAllType(val queryValue: String) {
|
||||||
SERIES,
|
@SerializedName("AUDIO")
|
||||||
ORIGINAL,
|
AUDIO("AUDIO"),
|
||||||
FREE,
|
|
||||||
POINT
|
@SerializedName("SERIES")
|
||||||
|
SERIES("SERIES"),
|
||||||
|
|
||||||
|
@SerializedName("ORIGINAL")
|
||||||
|
ORIGINAL("ORIGINAL"),
|
||||||
|
|
||||||
|
@SerializedName("FREE")
|
||||||
|
FREE("FREE"),
|
||||||
|
|
||||||
|
@SerializedName("POINT")
|
||||||
|
POINT("POINT")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Keep
|
@Keep
|
||||||
|
|||||||
@@ -13,10 +13,10 @@ class MainContentAllTabRepository(private val api: MainContentAllTabApi) {
|
|||||||
dayOfWeek: SeriesPublishedDaysOfWeek?
|
dayOfWeek: SeriesPublishedDaysOfWeek?
|
||||||
) = api.getContents(
|
) = api.getContents(
|
||||||
authHeader = token,
|
authHeader = token,
|
||||||
type = type,
|
type = type.queryValue,
|
||||||
sort = sort,
|
sort = sort.queryValue,
|
||||||
page = page,
|
page = page,
|
||||||
size = size,
|
size = size,
|
||||||
dayOfWeek = dayOfWeek
|
dayOfWeek = dayOfWeek?.queryValue
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,6 @@ interface ContentOverviewApi {
|
|||||||
@Header("Authorization") authHeader: String,
|
@Header("Authorization") authHeader: String,
|
||||||
@Query("page") page: Int,
|
@Query("page") page: Int,
|
||||||
@Query("size") size: Int,
|
@Query("size") size: Int,
|
||||||
@Query("type") type: ContentOverviewType
|
@Query("type") type: String
|
||||||
): Single<ApiResponse<ContentOverviewPageResponse>>
|
): Single<ApiResponse<ContentOverviewPageResponse>>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,8 +12,10 @@ data class ContentOverviewPageResponse(
|
|||||||
@SerializedName("hasNext") val hasNext: Boolean
|
@SerializedName("hasNext") val hasNext: Boolean
|
||||||
)
|
)
|
||||||
|
|
||||||
enum class ContentOverviewType {
|
@Keep
|
||||||
NEW_AND_HOT_AUDIO
|
enum class ContentOverviewType(val queryValue: String) {
|
||||||
|
@SerializedName("NEW_AND_HOT_AUDIO")
|
||||||
|
NEW_AND_HOT_AUDIO("NEW_AND_HOT_AUDIO")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Keep
|
@Keep
|
||||||
|
|||||||
@@ -10,6 +10,6 @@ class ContentOverviewRepository(private val api: ContentOverviewApi) {
|
|||||||
authHeader = token,
|
authHeader = token,
|
||||||
page = page,
|
page = page,
|
||||||
size = size,
|
size = size,
|
||||||
type = type
|
type = type.queryValue
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,10 +14,35 @@ class ContentAudioCardAdapter(
|
|||||||
) : RecyclerView.Adapter<ContentAudioCardAdapter.ViewHolder>() {
|
) : RecyclerView.Adapter<ContentAudioCardAdapter.ViewHolder>() {
|
||||||
|
|
||||||
private var items: List<ContentAudioCardUiModel> = emptyList()
|
private var items: List<ContentAudioCardUiModel> = emptyList()
|
||||||
|
private var gridItemWidthPx: Int = 0
|
||||||
|
|
||||||
|
fun setGridItemWidthPx(widthPx: Int) {
|
||||||
|
if (widthPx <= 0 || gridItemWidthPx == widthPx) return
|
||||||
|
gridItemWidthPx = widthPx
|
||||||
|
if (items.isNotEmpty()) notifyItemRangeChanged(0, items.size)
|
||||||
|
}
|
||||||
|
|
||||||
fun submitItems(items: List<ContentAudioCardUiModel>) {
|
fun submitItems(items: List<ContentAudioCardUiModel>) {
|
||||||
|
val previousItemCount = this.items.size
|
||||||
this.items = items
|
this.items = items
|
||||||
notifyDataSetChanged()
|
notifyItemChanges(previousItemCount, items.size)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun notifyItemChanges(previousItemCount: Int, currentItemCount: Int) {
|
||||||
|
when {
|
||||||
|
previousItemCount == 0 && currentItemCount > 0 -> notifyItemRangeInserted(0, currentItemCount)
|
||||||
|
previousItemCount > 0 && currentItemCount == 0 -> notifyItemRangeRemoved(0, previousItemCount)
|
||||||
|
previousItemCount == currentItemCount -> notifyItemRangeChanged(0, currentItemCount)
|
||||||
|
else -> {
|
||||||
|
val changedItemCount = minOf(previousItemCount, currentItemCount)
|
||||||
|
if (changedItemCount > 0) notifyItemRangeChanged(0, changedItemCount)
|
||||||
|
if (currentItemCount > previousItemCount) {
|
||||||
|
notifyItemRangeInserted(previousItemCount, currentItemCount - previousItemCount)
|
||||||
|
} else {
|
||||||
|
notifyItemRangeRemoved(currentItemCount, previousItemCount - currentItemCount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||||
@@ -29,7 +54,10 @@ class ContentAudioCardAdapter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
holder.bind(items[position])
|
holder.bind(
|
||||||
|
item = items[position],
|
||||||
|
gridItemWidthPx = gridItemWidthPx
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getItemCount(): Int = items.size
|
override fun getItemCount(): Int = items.size
|
||||||
@@ -39,8 +67,12 @@ class ContentAudioCardAdapter(
|
|||||||
private val cardSize: AudioContentCardSize,
|
private val cardSize: AudioContentCardSize,
|
||||||
private val onAudioClick: (ContentAudioCardUiModel) -> Unit
|
private val onAudioClick: (ContentAudioCardUiModel) -> Unit
|
||||||
) : RecyclerView.ViewHolder(binding.root) {
|
) : RecyclerView.ViewHolder(binding.root) {
|
||||||
fun bind(item: ContentAudioCardUiModel) = with(binding.audioContentCard.root) {
|
fun bind(item: ContentAudioCardUiModel, gridItemWidthPx: Int) = with(binding.audioContentCard.root) {
|
||||||
setSize(cardSize)
|
if (gridItemWidthPx > 0) {
|
||||||
|
setGridItemWidthPx(gridItemWidthPx)
|
||||||
|
} else {
|
||||||
|
setSize(cardSize)
|
||||||
|
}
|
||||||
setContent(item.title, item.creatorNickname)
|
setContent(item.title, item.creatorNickname)
|
||||||
setTags(item.tags)
|
setTags(item.tags)
|
||||||
setAdultVisible(item.showAdultBadge)
|
setAdultVisible(item.showAdultBadge)
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import androidx.core.view.isVisible
|
|||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import kr.co.vividnext.sodalive.R
|
import kr.co.vividnext.sodalive.R
|
||||||
import kr.co.vividnext.sodalive.databinding.ItemContentCommentedAudioBinding
|
import kr.co.vividnext.sodalive.databinding.ItemContentCommentedAudioBinding
|
||||||
|
import kr.co.vividnext.sodalive.extensions.dpToPx
|
||||||
import kr.co.vividnext.sodalive.extensions.loadUrl
|
import kr.co.vividnext.sodalive.extensions.loadUrl
|
||||||
import kr.co.vividnext.sodalive.v2.main.content.model.ContentCommentedAudioUiModel
|
import kr.co.vividnext.sodalive.v2.main.content.model.ContentCommentedAudioUiModel
|
||||||
|
|
||||||
@@ -24,8 +25,11 @@ class ContentCommentedAudioAdapter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||||
|
val binding = ItemContentCommentedAudioBinding.inflate(LayoutInflater.from(parent.context), parent, false).apply {
|
||||||
|
root.layoutParams.width = parent.resources.displayMetrics.widthPixels - (CONTENT_HORIZONTAL_MARGIN_DP * 2).dpToPx().toInt()
|
||||||
|
}
|
||||||
return ViewHolder(
|
return ViewHolder(
|
||||||
ItemContentCommentedAudioBinding.inflate(LayoutInflater.from(parent.context), parent, false),
|
binding,
|
||||||
onAudioClick
|
onAudioClick
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -47,6 +51,12 @@ class ContentCommentedAudioAdapter(
|
|||||||
outline.setRoundRect(0, 0, view.width, view.height, view.resources.getDimension(R.dimen.radius_14))
|
outline.setRoundRect(0, 0, view.width, view.height, view.resources.getDimension(R.dimen.radius_14))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
binding.ivContentCommentProfile.clipToOutline = true
|
||||||
|
binding.ivContentCommentProfile.outlineProvider = object : ViewOutlineProvider() {
|
||||||
|
override fun getOutline(view: View, outline: Outline) {
|
||||||
|
outline.setOval(0, 0, view.width, view.height)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun bind(item: ContentCommentedAudioUiModel) = with(binding) {
|
fun bind(item: ContentCommentedAudioUiModel) = with(binding) {
|
||||||
@@ -58,4 +68,8 @@ class ContentCommentedAudioAdapter(
|
|||||||
root.setOnClickListener { onAudioClick(item) }
|
root.setOnClickListener { onAudioClick(item) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private companion object {
|
||||||
|
const val CONTENT_HORIZONTAL_MARGIN_DP = 14
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,14 +10,20 @@ fun RecyclerView.addContentHorizontalItemSpacing() {
|
|||||||
if (itemDecorationCount == 0) addItemDecoration(ContentHorizontalItemDecoration())
|
if (itemDecorationCount == 0) addItemDecoration(ContentHorizontalItemDecoration())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun RecyclerView.addContentGridItemSpacing(spanCount: Int = CONTENT_RECOMMENDED_GRID_SPAN_COUNT) {
|
fun RecyclerView.addContentGridItemSpacing(
|
||||||
if (itemDecorationCount == 0) addItemDecoration(ContentGridItemDecoration(spanCount))
|
spanCount: Int = CONTENT_RECOMMENDED_GRID_SPAN_COUNT,
|
||||||
|
gapDp: Int = GRID_ITEM_GAP_DP
|
||||||
|
) {
|
||||||
|
if (itemDecorationCount == 0) addItemDecoration(ContentGridItemDecoration(spanCount, gapDp))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun RecyclerView.calculateContentGridItemWidthPx(spanCount: Int): Int {
|
fun RecyclerView.calculateContentGridItemWidthPx(
|
||||||
|
spanCount: Int,
|
||||||
|
gapDp: Int = GRID_ITEM_GAP_DP
|
||||||
|
): Int {
|
||||||
val availableWidth = measuredWidth - paddingLeft - paddingRight
|
val availableWidth = measuredWidth - paddingLeft - paddingRight
|
||||||
if (availableWidth <= 0 || spanCount <= 0) return 0
|
if (availableWidth <= 0 || spanCount <= 0) return 0
|
||||||
val totalGap = GRID_ITEM_GAP_DP.dpToPx() * (spanCount - 1)
|
val totalGap = gapDp.dpToPx() * (spanCount - 1)
|
||||||
return ((availableWidth - totalGap) / spanCount).roundToInt()
|
return ((availableWidth - totalGap) / spanCount).roundToInt()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,13 +37,14 @@ private class ContentHorizontalItemDecoration : RecyclerView.ItemDecoration() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private class ContentGridItemDecoration(
|
private class ContentGridItemDecoration(
|
||||||
private val spanCount: Int
|
private val spanCount: Int,
|
||||||
|
private val gapDp: Int
|
||||||
) : RecyclerView.ItemDecoration() {
|
) : RecyclerView.ItemDecoration() {
|
||||||
override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
|
override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
|
||||||
val position = parent.getChildAdapterPosition(view)
|
val position = parent.getChildAdapterPosition(view)
|
||||||
if (position == RecyclerView.NO_POSITION) return
|
if (position == RecyclerView.NO_POSITION) return
|
||||||
val columnIndex = position % spanCount
|
val columnIndex = position % spanCount
|
||||||
val gap = GRID_ITEM_GAP_DP.dpToPx()
|
val gap = gapDp.dpToPx()
|
||||||
outRect.left = (columnIndex * gap / spanCount).roundToInt()
|
outRect.left = (columnIndex * gap / spanCount).roundToInt()
|
||||||
outRect.right = (gap - (columnIndex + 1) * gap / spanCount).roundToInt()
|
outRect.right = (gap - (columnIndex + 1) * gap / spanCount).roundToInt()
|
||||||
if (position >= spanCount) outRect.top = GRID_ITEM_VERTICAL_GAP_DP.dpToPx().toInt()
|
if (position >= spanCount) outRect.top = GRID_ITEM_VERTICAL_GAP_DP.dpToPx().toInt()
|
||||||
@@ -45,6 +52,7 @@ private class ContentGridItemDecoration(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private const val HORIZONTAL_ITEM_GAP_DP = 8
|
private const val HORIZONTAL_ITEM_GAP_DP = 8
|
||||||
|
const val CONTENT_RECOMMENDED_GRID_GAP_DP = 4
|
||||||
private const val GRID_ITEM_GAP_DP = 8
|
private const val GRID_ITEM_GAP_DP = 8
|
||||||
private const val GRID_ITEM_VERTICAL_GAP_DP = 28
|
private const val GRID_ITEM_VERTICAL_GAP_DP = 28
|
||||||
const val CONTENT_RECOMMENDED_GRID_SPAN_COUNT = 2
|
const val CONTENT_RECOMMENDED_GRID_SPAN_COUNT = 2
|
||||||
|
|||||||
@@ -14,8 +14,14 @@ import kr.co.vividnext.sodalive.common.formatUtcRelativeTimeText
|
|||||||
import kr.co.vividnext.sodalive.databinding.FragmentV2MainHomeBinding
|
import kr.co.vividnext.sodalive.databinding.FragmentV2MainHomeBinding
|
||||||
import kr.co.vividnext.sodalive.databinding.ViewSectionTitleBinding
|
import kr.co.vividnext.sodalive.databinding.ViewSectionTitleBinding
|
||||||
import kr.co.vividnext.sodalive.explorer.profile.creator_community.all.CreatorCommunityAllActivity
|
import kr.co.vividnext.sodalive.explorer.profile.creator_community.all.CreatorCommunityAllActivity
|
||||||
|
import kr.co.vividnext.sodalive.following.FollowingCreatorActivity
|
||||||
import kr.co.vividnext.sodalive.chat.talk.room.ChatRoomActivity
|
import kr.co.vividnext.sodalive.chat.talk.room.ChatRoomActivity
|
||||||
|
import kr.co.vividnext.sodalive.home.pushnotification.PushNotificationListActivity
|
||||||
|
import kr.co.vividnext.sodalive.live.LiveViewModel
|
||||||
|
import kr.co.vividnext.sodalive.mypage.can.charge.CanChargeActivity
|
||||||
|
import kr.co.vividnext.sodalive.search.SearchActivity
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivity
|
import kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivity
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelLiveCoordinator
|
||||||
import kr.co.vividnext.sodalive.v2.live.onair.HomeOnAirLiveActivity
|
import kr.co.vividnext.sodalive.v2.live.onair.HomeOnAirLiveActivity
|
||||||
import kr.co.vividnext.sodalive.v2.main.chat.dm.DmChatRoomActivity
|
import kr.co.vividnext.sodalive.v2.main.chat.dm.DmChatRoomActivity
|
||||||
import kr.co.vividnext.sodalive.v2.main.chat.model.ChatRoomListUiItem
|
import kr.co.vividnext.sodalive.v2.main.chat.model.ChatRoomListUiItem
|
||||||
@@ -70,6 +76,7 @@ import kr.co.vividnext.sodalive.v2.main.home.ui.HomeRecentDebutCreatorAdapter
|
|||||||
import kr.co.vividnext.sodalive.v2.widget.creatorranking.CreatorRankingAdapter
|
import kr.co.vividnext.sodalive.v2.widget.creatorranking.CreatorRankingAdapter
|
||||||
import kr.co.vividnext.sodalive.v2.widget.creatorranking.CreatorRankingItem
|
import kr.co.vividnext.sodalive.v2.widget.creatorranking.CreatorRankingItem
|
||||||
import kr.co.vividnext.sodalive.v2.widget.feed.FeedItem
|
import kr.co.vividnext.sodalive.v2.widget.feed.FeedItem
|
||||||
|
import org.koin.android.ext.android.inject
|
||||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||||
|
|
||||||
class HomeMainFragment : BaseFragment<FragmentV2MainHomeBinding>(
|
class HomeMainFragment : BaseFragment<FragmentV2MainHomeBinding>(
|
||||||
@@ -78,7 +85,18 @@ class HomeMainFragment : BaseFragment<FragmentV2MainHomeBinding>(
|
|||||||
private val homeRecommendationViewModel: HomeRecommendationViewModel by viewModel()
|
private val homeRecommendationViewModel: HomeRecommendationViewModel by viewModel()
|
||||||
private val homeCreatorRankingViewModel: HomeCreatorRankingViewModel by viewModel()
|
private val homeCreatorRankingViewModel: HomeCreatorRankingViewModel by viewModel()
|
||||||
private val homeFollowingViewModel: HomeFollowingViewModel by viewModel()
|
private val homeFollowingViewModel: HomeFollowingViewModel by viewModel()
|
||||||
|
private val liveViewModel: LiveViewModel by inject()
|
||||||
private val loadingDialog: LoadingDialog by lazy { LoadingDialog(requireActivity(), layoutInflater) }
|
private val loadingDialog: LoadingDialog by lazy { LoadingDialog(requireActivity(), layoutInflater) }
|
||||||
|
private val liveCoordinator: CreatorChannelLiveCoordinator by lazy {
|
||||||
|
CreatorChannelLiveCoordinator(
|
||||||
|
activity = requireActivity(),
|
||||||
|
layoutInflater = layoutInflater,
|
||||||
|
fragmentManager = requireActivity().supportFragmentManager,
|
||||||
|
liveViewModel = liveViewModel,
|
||||||
|
screenWidthProvider = { screenWidth },
|
||||||
|
refreshHome = { homeRecommendationViewModel.loadRecommendations() }
|
||||||
|
)
|
||||||
|
}
|
||||||
private val liveAdapter = HomeLiveAdapter()
|
private val liveAdapter = HomeLiveAdapter()
|
||||||
private val recentActivityCreatorAdapter = HomeRecentActivityCreatorAdapter { onRecentActivityClick(it) }
|
private val recentActivityCreatorAdapter = HomeRecentActivityCreatorAdapter { onRecentActivityClick(it) }
|
||||||
private val recentDebutCreatorAdapter = HomeRecentDebutCreatorAdapter { openCreatorProfile(it.creatorId) }
|
private val recentDebutCreatorAdapter = HomeRecentDebutCreatorAdapter { openCreatorProfile(it.creatorId) }
|
||||||
@@ -93,7 +111,10 @@ class HomeMainFragment : BaseFragment<FragmentV2MainHomeBinding>(
|
|||||||
)
|
)
|
||||||
private val popularCommunityAdapter = HomePopularCommunityAdapter { openPopularCommunityPost(it) }
|
private val popularCommunityAdapter = HomePopularCommunityAdapter { openPopularCommunityPost(it) }
|
||||||
private val creatorRankingAdapter = CreatorRankingAdapter { openCreatorRankingProfile(it) }
|
private val creatorRankingAdapter = CreatorRankingAdapter { openCreatorRankingProfile(it) }
|
||||||
private val followingCreatorAdapter = HomeFollowingCreatorAdapter { openCreatorProfile(it.creatorId) }
|
private val followingCreatorAdapter = HomeFollowingCreatorAdapter(
|
||||||
|
onClickItem = { openCreatorProfile(it.creatorId) },
|
||||||
|
onClickAll = { openFollowingCreatorAll() }
|
||||||
|
)
|
||||||
private val followingLiveAdapter = HomeFollowingLiveAdapter { onFollowingLiveClick(it) }
|
private val followingLiveAdapter = HomeFollowingLiveAdapter { onFollowingLiveClick(it) }
|
||||||
private val followingChatAdapter = HomeFollowingChatAdapter { openFollowingChat(it) }
|
private val followingChatAdapter = HomeFollowingChatAdapter { openFollowingChat(it) }
|
||||||
private val followingScheduleAdapter = HomeFollowingScheduleAdapter { onFollowingScheduleClick(it) }
|
private val followingScheduleAdapter = HomeFollowingScheduleAdapter { onFollowingScheduleClick(it) }
|
||||||
@@ -104,6 +125,10 @@ class HomeMainFragment : BaseFragment<FragmentV2MainHomeBinding>(
|
|||||||
private var hasLoadedCreatorRankings = false
|
private var hasLoadedCreatorRankings = false
|
||||||
private var hasLoadedFollowing = false
|
private var hasLoadedFollowing = false
|
||||||
private var currentHomeTabIndex = HOME_TAB_RECOMMENDATION
|
private var currentHomeTabIndex = HOME_TAB_RECOMMENDATION
|
||||||
|
private var isRecommendationLoading = false
|
||||||
|
private var isCreatorRankingLoading = false
|
||||||
|
private var isFollowingLoading = false
|
||||||
|
private var isLiveEntryLoading = false
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
@@ -127,12 +152,32 @@ class HomeMainFragment : BaseFragment<FragmentV2MainHomeBinding>(
|
|||||||
setUpCreatorRankingAdapter()
|
setUpCreatorRankingAdapter()
|
||||||
setUpFollowingAdapters()
|
setUpFollowingAdapters()
|
||||||
setUpBusinessInfo()
|
setUpBusinessInfo()
|
||||||
|
setupTitleBarActions()
|
||||||
bindHomeRecommendationObservers()
|
bindHomeRecommendationObservers()
|
||||||
bindHomeCreatorRankingObservers()
|
bindHomeCreatorRankingObservers()
|
||||||
bindHomeFollowingObservers()
|
bindHomeFollowingObservers()
|
||||||
|
bindLiveEntryObservers()
|
||||||
homeRecommendationViewModel.loadRecommendations()
|
homeRecommendationViewModel.loadRecommendations()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun setupTitleBarActions() {
|
||||||
|
binding.viewHomeTitleBar.ivTitleBarCash.setOnClickListener {
|
||||||
|
openWithLoginGuard(CanChargeActivity::class.java)
|
||||||
|
}
|
||||||
|
binding.viewHomeTitleBar.ivTitleBarSearch.setOnClickListener {
|
||||||
|
openWithLoginGuard(SearchActivity::class.java)
|
||||||
|
}
|
||||||
|
binding.viewHomeTitleBar.ivTitleBarBell.setOnClickListener {
|
||||||
|
openWithLoginGuard(PushNotificationListActivity::class.java)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun openWithLoginGuard(activityClass: Class<*>) {
|
||||||
|
ensureMainV2NavigationAllowed {
|
||||||
|
startActivity(Intent(requireContext(), activityClass))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun setUpBusinessInfo() {
|
private fun setUpBusinessInfo() {
|
||||||
val businessInfoText = binding.tvHomeBusinessInfo.text
|
val businessInfoText = binding.tvHomeBusinessInfo.text
|
||||||
HomeBusinessInfoBinder.bind(binding.tvHomeBusinessInfo)
|
HomeBusinessInfoBinder.bind(binding.tvHomeBusinessInfo)
|
||||||
@@ -226,11 +271,8 @@ class HomeMainFragment : BaseFragment<FragmentV2MainHomeBinding>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
homeCreatorRankingViewModel.isLoading.observe(viewLifecycleOwner) { isLoading ->
|
homeCreatorRankingViewModel.isLoading.observe(viewLifecycleOwner) { isLoading ->
|
||||||
if (isLoading) {
|
isCreatorRankingLoading = isLoading
|
||||||
loadingDialog.show(screenWidth)
|
updateLoadingDialog()
|
||||||
} else {
|
|
||||||
loadingDialog.dismiss()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
homeCreatorRankingViewModel.toastLiveData.observe(viewLifecycleOwner) { toastMessage ->
|
homeCreatorRankingViewModel.toastLiveData.observe(viewLifecycleOwner) { toastMessage ->
|
||||||
toastMessage?.let(::showToast)
|
toastMessage?.let(::showToast)
|
||||||
@@ -289,11 +331,8 @@ class HomeMainFragment : BaseFragment<FragmentV2MainHomeBinding>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
homeFollowingViewModel.isLoading.observe(viewLifecycleOwner) { isLoading ->
|
homeFollowingViewModel.isLoading.observe(viewLifecycleOwner) { isLoading ->
|
||||||
if (isLoading) {
|
isFollowingLoading = isLoading
|
||||||
loadingDialog.show(screenWidth)
|
updateLoadingDialog()
|
||||||
} else {
|
|
||||||
loadingDialog.dismiss()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
homeFollowingViewModel.toastLiveData.observe(viewLifecycleOwner) { toastMessage ->
|
homeFollowingViewModel.toastLiveData.observe(viewLifecycleOwner) { toastMessage ->
|
||||||
toastMessage?.let(::showToast)
|
toastMessage?.let(::showToast)
|
||||||
@@ -311,17 +350,32 @@ class HomeMainFragment : BaseFragment<FragmentV2MainHomeBinding>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
homeRecommendationViewModel.isLoading.observe(viewLifecycleOwner) { isLoading ->
|
homeRecommendationViewModel.isLoading.observe(viewLifecycleOwner) { isLoading ->
|
||||||
if (isLoading) {
|
isRecommendationLoading = isLoading
|
||||||
loadingDialog.show(screenWidth)
|
updateLoadingDialog()
|
||||||
} else {
|
|
||||||
loadingDialog.dismiss()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
homeRecommendationViewModel.toastLiveData.observe(viewLifecycleOwner) { toastMessage ->
|
homeRecommendationViewModel.toastLiveData.observe(viewLifecycleOwner) { toastMessage ->
|
||||||
toastMessage?.let(::showToast)
|
toastMessage?.let(::showToast)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun bindLiveEntryObservers() {
|
||||||
|
liveViewModel.isLoading.observe(viewLifecycleOwner) { isLoading ->
|
||||||
|
isLiveEntryLoading = isLoading
|
||||||
|
updateLoadingDialog()
|
||||||
|
}
|
||||||
|
liveViewModel.toastLiveData.observe(viewLifecycleOwner) { message ->
|
||||||
|
message?.let { showToast(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateLoadingDialog() {
|
||||||
|
if (isRecommendationLoading || isCreatorRankingLoading || isFollowingLoading || isLiveEntryLoading) {
|
||||||
|
loadingDialog.show(screenWidth)
|
||||||
|
} else {
|
||||||
|
loadingDialog.dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun bindHomeRecommendationContent(content: HomeRecommendationUiState.Content) {
|
private fun bindHomeRecommendationContent(content: HomeRecommendationUiState.Content) {
|
||||||
bindLiveSection(content.lives)
|
bindLiveSection(content.lives)
|
||||||
bindBannerSection(content.banners)
|
bindBannerSection(content.banners)
|
||||||
@@ -488,7 +542,16 @@ class HomeMainFragment : BaseFragment<FragmentV2MainHomeBinding>(
|
|||||||
ivSectionTitleChevron.visibility = if (showMore) View.VISIBLE else View.GONE
|
ivSectionTitleChevron.visibility = if (showMore) View.VISIBLE else View.GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onLiveClick(item: HomeRecommendationLiveUiModel) = Unit
|
private fun onLiveClick(item: HomeRecommendationLiveUiModel) {
|
||||||
|
ensureMainV2NavigationAllowed {
|
||||||
|
liveViewModel.getRoomDetail(item.roomId) { roomDetail ->
|
||||||
|
if (!isAdded || view == null) return@getRoomDetail
|
||||||
|
ensureMainV2NavigationAllowed(requiresAdultContentAccess = roomDetail.isAdult) {
|
||||||
|
liveCoordinator.enterLiveRoom(item.roomId, roomDetail)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun openHomeOnAirLive() {
|
private fun openHomeOnAirLive() {
|
||||||
ensureMainV2NavigationAllowed {
|
ensureMainV2NavigationAllowed {
|
||||||
@@ -504,6 +567,12 @@ class HomeMainFragment : BaseFragment<FragmentV2MainHomeBinding>(
|
|||||||
|
|
||||||
private fun onFollowingNewsClick(item: HomeFollowingNewsUiItem) = Unit
|
private fun onFollowingNewsClick(item: HomeFollowingNewsUiItem) = Unit
|
||||||
|
|
||||||
|
private fun openFollowingCreatorAll() {
|
||||||
|
ensureMainV2NavigationAllowed {
|
||||||
|
startActivity(Intent(requireContext(), FollowingCreatorActivity::class.java))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun openFollowingChat(item: ChatRoomListUiItem) {
|
private fun openFollowingChat(item: ChatRoomListUiItem) {
|
||||||
ensureMainV2NavigationAllowed {
|
ensureMainV2NavigationAllowed {
|
||||||
when (item.chatType) {
|
when (item.chatType) {
|
||||||
|
|||||||
@@ -48,15 +48,49 @@ data class FollowingScheduleResponse(
|
|||||||
data class FollowingNewsResponse(
|
data class FollowingNewsResponse(
|
||||||
@SerializedName("newsId") val newsId: String,
|
@SerializedName("newsId") val newsId: String,
|
||||||
@SerializedName("type") val type: FollowingNewsType,
|
@SerializedName("type") val type: FollowingNewsType,
|
||||||
@SerializedName("creatorProfileImageUrl") val creatorProfileImageUrl: String,
|
|
||||||
@SerializedName("creatorNickname") val creatorNickname: String,
|
|
||||||
@SerializedName("title") val title: String,
|
|
||||||
@SerializedName("body") val body: String,
|
|
||||||
@SerializedName("thumbnailImageUrl") val thumbnailImageUrl: String?,
|
|
||||||
@SerializedName("targetId") val targetId: Long,
|
|
||||||
@SerializedName("occurredAtUtc") val occurredAtUtc: String,
|
|
||||||
@SerializedName("visibleFromAtUtc") val visibleFromAtUtc: String,
|
@SerializedName("visibleFromAtUtc") val visibleFromAtUtc: String,
|
||||||
@SerializedName("rank") val rank: Int?
|
@SerializedName("creatorRanking") val creatorRanking: FollowingCreatorRankingNewsResponse? = null,
|
||||||
|
@SerializedName("audioContent") val audioContent: FollowingContentNewsResponse? = null,
|
||||||
|
@SerializedName("photoContent") val photoContent: FollowingContentNewsResponse? = null,
|
||||||
|
@SerializedName("contentRanking") val contentRanking: FollowingContentRankingNewsResponse? = null,
|
||||||
|
@SerializedName("communityPost") val communityPost: FollowingCommunityPostNewsResponse? = null
|
||||||
|
)
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
data class FollowingCreatorRankingNewsResponse(
|
||||||
|
@SerializedName("rank") val rank: Int,
|
||||||
|
@SerializedName("creatorId") val creatorId: Long,
|
||||||
|
@SerializedName("nickname") val nickname: String,
|
||||||
|
@SerializedName("profileImageUrl") val profileImageUrl: String
|
||||||
|
)
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
data class FollowingContentNewsResponse(
|
||||||
|
@SerializedName("contentId") val contentId: Long,
|
||||||
|
@SerializedName("contentImageUrl") val contentImageUrl: String?,
|
||||||
|
@SerializedName("title") val title: String,
|
||||||
|
@SerializedName("creatorProfileImageUrl") val creatorProfileImageUrl: String,
|
||||||
|
@SerializedName("creatorNickname") val creatorNickname: String
|
||||||
|
)
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
data class FollowingContentRankingNewsResponse(
|
||||||
|
@SerializedName("rank") val rank: Int,
|
||||||
|
@SerializedName("contentId") val contentId: Long,
|
||||||
|
@SerializedName("contentImageUrl") val contentImageUrl: String?,
|
||||||
|
@SerializedName("title") val title: String
|
||||||
|
)
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
data class FollowingCommunityPostNewsResponse(
|
||||||
|
@SerializedName("postId") val postId: Long,
|
||||||
|
@SerializedName("creatorProfileImage") val creatorProfileImage: String,
|
||||||
|
@SerializedName("creatorNickname") val creatorNickname: String,
|
||||||
|
@SerializedName("imageUrl") val imageUrl: String?,
|
||||||
|
@SerializedName("content") val content: String,
|
||||||
|
@SerializedName("createdAt") val createdAt: String,
|
||||||
|
@SerializedName("likeCount") val likeCount: Int,
|
||||||
|
@SerializedName("commentCount") val commentCount: Int
|
||||||
)
|
)
|
||||||
|
|
||||||
enum class FollowingNewsType {
|
enum class FollowingNewsType {
|
||||||
|
|||||||
@@ -4,7 +4,11 @@ import androidx.annotation.StringRes
|
|||||||
import kr.co.vividnext.sodalive.R
|
import kr.co.vividnext.sodalive.R
|
||||||
import kr.co.vividnext.sodalive.common.UtcRelativeTimeTextFormatter
|
import kr.co.vividnext.sodalive.common.UtcRelativeTimeTextFormatter
|
||||||
import kr.co.vividnext.sodalive.v2.main.chat.model.toUiItems
|
import kr.co.vividnext.sodalive.v2.main.chat.model.toUiItems
|
||||||
|
import kr.co.vividnext.sodalive.v2.main.home.data.FollowingCommunityPostNewsResponse
|
||||||
|
import kr.co.vividnext.sodalive.v2.main.home.data.FollowingContentNewsResponse
|
||||||
|
import kr.co.vividnext.sodalive.v2.main.home.data.FollowingContentRankingNewsResponse
|
||||||
import kr.co.vividnext.sodalive.v2.main.home.data.FollowingCreatorResponse
|
import kr.co.vividnext.sodalive.v2.main.home.data.FollowingCreatorResponse
|
||||||
|
import kr.co.vividnext.sodalive.v2.main.home.data.FollowingCreatorRankingNewsResponse
|
||||||
import kr.co.vividnext.sodalive.v2.main.home.data.FollowingLiveResponse
|
import kr.co.vividnext.sodalive.v2.main.home.data.FollowingLiveResponse
|
||||||
import kr.co.vividnext.sodalive.v2.main.home.data.FollowingNewsResponse
|
import kr.co.vividnext.sodalive.v2.main.home.data.FollowingNewsResponse
|
||||||
import kr.co.vividnext.sodalive.v2.main.home.data.FollowingNewsType
|
import kr.co.vividnext.sodalive.v2.main.home.data.FollowingNewsType
|
||||||
@@ -62,42 +66,88 @@ private fun FollowingNewsResponse.toUiItem(
|
|||||||
): HomeFollowingNewsUiItem? {
|
): HomeFollowingNewsUiItem? {
|
||||||
val visibleFromText = relativeTimeTextFormatter.format(visibleFromAtUtc)
|
val visibleFromText = relativeTimeTextFormatter.format(visibleFromAtUtc)
|
||||||
return when (type) {
|
return when (type) {
|
||||||
FollowingNewsType.CREATOR_RANKING,
|
FollowingNewsType.CREATOR_RANKING -> creatorRanking?.toUiItem(newsId, type, visibleFromAtUtc, visibleFromText)
|
||||||
FollowingNewsType.CONTENT_RANKING -> rank?.let {
|
FollowingNewsType.CONTENT_RANKING -> contentRanking?.toUiItem(newsId, type, visibleFromAtUtc, visibleFromText)
|
||||||
HomeFollowingNewsUiItem.Ranking(
|
FollowingNewsType.COMMUNITY_POST -> communityPost?.toUiItem(newsId, type, visibleFromAtUtc, visibleFromText)
|
||||||
newsId = newsId,
|
FollowingNewsType.AUDIO_CONTENT -> audioContent?.toUiItem(newsId, type, visibleFromAtUtc, visibleFromText)
|
||||||
type = type,
|
FollowingNewsType.PHOTO_CONTENT -> photoContent?.toUiItem(newsId, type, visibleFromAtUtc, visibleFromText)
|
||||||
creatorProfileImageUrl = creatorProfileImageUrl,
|
|
||||||
creatorNickname = creatorNickname,
|
|
||||||
title = title,
|
|
||||||
body = body,
|
|
||||||
thumbnailImageUrl = thumbnailImageUrl,
|
|
||||||
targetId = targetId,
|
|
||||||
occurredAtUtc = occurredAtUtc,
|
|
||||||
visibleFromAtUtc = visibleFromAtUtc,
|
|
||||||
visibleFromText = visibleFromText,
|
|
||||||
rank = it
|
|
||||||
)
|
|
||||||
}
|
|
||||||
FollowingNewsType.COMMUNITY_POST,
|
|
||||||
FollowingNewsType.AUDIO_CONTENT,
|
|
||||||
FollowingNewsType.PHOTO_CONTENT -> HomeFollowingNewsUiItem.Content(
|
|
||||||
newsId = newsId,
|
|
||||||
type = type,
|
|
||||||
creatorProfileImageUrl = creatorProfileImageUrl,
|
|
||||||
creatorNickname = creatorNickname,
|
|
||||||
title = title,
|
|
||||||
body = body,
|
|
||||||
thumbnailImageUrl = thumbnailImageUrl,
|
|
||||||
targetId = targetId,
|
|
||||||
occurredAtUtc = occurredAtUtc,
|
|
||||||
visibleFromAtUtc = visibleFromAtUtc,
|
|
||||||
visibleFromText = visibleFromText,
|
|
||||||
labelResId = type.toLabelResId()
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun FollowingCreatorRankingNewsResponse.toUiItem(
|
||||||
|
newsId: String,
|
||||||
|
type: FollowingNewsType,
|
||||||
|
visibleFromAtUtc: String,
|
||||||
|
visibleFromText: String
|
||||||
|
) = HomeFollowingNewsUiItem.Ranking(
|
||||||
|
newsId = newsId,
|
||||||
|
type = type,
|
||||||
|
targetId = creatorId,
|
||||||
|
visibleFromAtUtc = visibleFromAtUtc,
|
||||||
|
visibleFromText = visibleFromText,
|
||||||
|
rank = rank,
|
||||||
|
imageUrl = profileImageUrl,
|
||||||
|
title = nickname,
|
||||||
|
creatorNickname = nickname
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun FollowingContentRankingNewsResponse.toUiItem(
|
||||||
|
newsId: String,
|
||||||
|
type: FollowingNewsType,
|
||||||
|
visibleFromAtUtc: String,
|
||||||
|
visibleFromText: String
|
||||||
|
) = HomeFollowingNewsUiItem.Ranking(
|
||||||
|
newsId = newsId,
|
||||||
|
type = type,
|
||||||
|
targetId = contentId,
|
||||||
|
visibleFromAtUtc = visibleFromAtUtc,
|
||||||
|
visibleFromText = visibleFromText,
|
||||||
|
rank = rank,
|
||||||
|
imageUrl = contentImageUrl,
|
||||||
|
title = title,
|
||||||
|
creatorNickname = ""
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun FollowingContentNewsResponse.toUiItem(
|
||||||
|
newsId: String,
|
||||||
|
type: FollowingNewsType,
|
||||||
|
visibleFromAtUtc: String,
|
||||||
|
visibleFromText: String
|
||||||
|
) = HomeFollowingNewsUiItem.Content(
|
||||||
|
newsId = newsId,
|
||||||
|
type = type,
|
||||||
|
targetId = contentId,
|
||||||
|
visibleFromAtUtc = visibleFromAtUtc,
|
||||||
|
visibleFromText = visibleFromText,
|
||||||
|
contentId = contentId,
|
||||||
|
contentImageUrl = contentImageUrl,
|
||||||
|
title = title,
|
||||||
|
creatorProfileImageUrl = creatorProfileImageUrl,
|
||||||
|
creatorNickname = creatorNickname,
|
||||||
|
labelResId = type.toLabelResId()
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun FollowingCommunityPostNewsResponse.toUiItem(
|
||||||
|
newsId: String,
|
||||||
|
type: FollowingNewsType,
|
||||||
|
visibleFromAtUtc: String,
|
||||||
|
visibleFromText: String
|
||||||
|
) = HomeFollowingNewsUiItem.Community(
|
||||||
|
newsId = newsId,
|
||||||
|
type = type,
|
||||||
|
targetId = postId,
|
||||||
|
visibleFromAtUtc = visibleFromAtUtc,
|
||||||
|
visibleFromText = visibleFromText,
|
||||||
|
postId = postId,
|
||||||
|
creatorProfileImageUrl = creatorProfileImage,
|
||||||
|
creatorNickname = creatorNickname,
|
||||||
|
imageUrl = imageUrl,
|
||||||
|
content = content,
|
||||||
|
createdAt = createdAt,
|
||||||
|
likeCount = likeCount,
|
||||||
|
commentCount = commentCount
|
||||||
|
)
|
||||||
|
|
||||||
@StringRes
|
@StringRes
|
||||||
private fun FollowingNewsType.toLabelResId(): Int = when (this) {
|
private fun FollowingNewsType.toLabelResId(): Int = when (this) {
|
||||||
FollowingNewsType.PHOTO_CONTENT -> R.string.screen_home_following_photo_content
|
FollowingNewsType.PHOTO_CONTENT -> R.string.screen_home_following_photo_content
|
||||||
|
|||||||
@@ -55,43 +55,49 @@ data class HomeFollowingScheduleUiItem(
|
|||||||
sealed interface HomeFollowingNewsUiItem {
|
sealed interface HomeFollowingNewsUiItem {
|
||||||
val newsId: String
|
val newsId: String
|
||||||
val type: FollowingNewsType
|
val type: FollowingNewsType
|
||||||
val creatorProfileImageUrl: String
|
|
||||||
val creatorNickname: String
|
|
||||||
val title: String
|
|
||||||
val body: String
|
|
||||||
val thumbnailImageUrl: String?
|
|
||||||
val targetId: Long
|
val targetId: Long
|
||||||
val occurredAtUtc: String
|
|
||||||
val visibleFromAtUtc: String
|
val visibleFromAtUtc: String
|
||||||
val visibleFromText: String
|
val visibleFromText: String
|
||||||
|
|
||||||
data class Ranking(
|
data class Ranking(
|
||||||
override val newsId: String,
|
override val newsId: String,
|
||||||
override val type: FollowingNewsType,
|
override val type: FollowingNewsType,
|
||||||
override val creatorProfileImageUrl: String,
|
|
||||||
override val creatorNickname: String,
|
|
||||||
override val title: String,
|
|
||||||
override val body: String,
|
|
||||||
override val thumbnailImageUrl: String?,
|
|
||||||
override val targetId: Long,
|
override val targetId: Long,
|
||||||
override val occurredAtUtc: String,
|
|
||||||
override val visibleFromAtUtc: String,
|
override val visibleFromAtUtc: String,
|
||||||
override val visibleFromText: String,
|
override val visibleFromText: String,
|
||||||
val rank: Int
|
val rank: Int,
|
||||||
|
val imageUrl: String?,
|
||||||
|
val title: String,
|
||||||
|
val creatorNickname: String
|
||||||
) : HomeFollowingNewsUiItem
|
) : HomeFollowingNewsUiItem
|
||||||
|
|
||||||
data class Content(
|
data class Content(
|
||||||
override val newsId: String,
|
override val newsId: String,
|
||||||
override val type: FollowingNewsType,
|
override val type: FollowingNewsType,
|
||||||
override val creatorProfileImageUrl: String,
|
|
||||||
override val creatorNickname: String,
|
|
||||||
override val title: String,
|
|
||||||
override val body: String,
|
|
||||||
override val thumbnailImageUrl: String?,
|
|
||||||
override val targetId: Long,
|
override val targetId: Long,
|
||||||
override val occurredAtUtc: String,
|
|
||||||
override val visibleFromAtUtc: String,
|
override val visibleFromAtUtc: String,
|
||||||
override val visibleFromText: String,
|
override val visibleFromText: String,
|
||||||
|
val contentId: Long,
|
||||||
|
val contentImageUrl: String?,
|
||||||
|
val title: String,
|
||||||
|
val creatorProfileImageUrl: String,
|
||||||
|
val creatorNickname: String,
|
||||||
@param:StringRes val labelResId: Int
|
@param:StringRes val labelResId: Int
|
||||||
) : HomeFollowingNewsUiItem
|
) : HomeFollowingNewsUiItem
|
||||||
|
|
||||||
|
data class Community(
|
||||||
|
override val newsId: String,
|
||||||
|
override val type: FollowingNewsType,
|
||||||
|
override val targetId: Long,
|
||||||
|
override val visibleFromAtUtc: String,
|
||||||
|
override val visibleFromText: String,
|
||||||
|
val postId: Long,
|
||||||
|
val creatorProfileImageUrl: String,
|
||||||
|
val creatorNickname: String,
|
||||||
|
val imageUrl: String?,
|
||||||
|
val content: String,
|
||||||
|
val createdAt: String,
|
||||||
|
val likeCount: Int,
|
||||||
|
val commentCount: Int
|
||||||
|
) : HomeFollowingNewsUiItem
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,8 +10,9 @@ import kr.co.vividnext.sodalive.R
|
|||||||
import kr.co.vividnext.sodalive.v2.main.home.model.HomeFollowingCreatorUiItem
|
import kr.co.vividnext.sodalive.v2.main.home.model.HomeFollowingCreatorUiItem
|
||||||
|
|
||||||
class HomeFollowingCreatorAdapter(
|
class HomeFollowingCreatorAdapter(
|
||||||
private val onClickItem: (HomeFollowingCreatorUiItem) -> Unit = {}
|
private val onClickItem: (HomeFollowingCreatorUiItem) -> Unit = {},
|
||||||
) : RecyclerView.Adapter<HomeFollowingCreatorAdapter.CreatorViewHolder>() {
|
private val onClickAll: () -> Unit = {}
|
||||||
|
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||||
private var items: List<HomeFollowingCreatorUiItem> = emptyList()
|
private var items: List<HomeFollowingCreatorUiItem> = emptyList()
|
||||||
|
|
||||||
fun submitItems(items: List<HomeFollowingCreatorUiItem>) {
|
fun submitItems(items: List<HomeFollowingCreatorUiItem>) {
|
||||||
@@ -19,17 +20,29 @@ class HomeFollowingCreatorAdapter(
|
|||||||
notifyDataSetChanged()
|
notifyDataSetChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CreatorViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||||
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_home_following_creator, parent, false)
|
val layoutResId = when (viewType) {
|
||||||
|
VIEW_TYPE_ALL -> R.layout.item_home_following_creator_all
|
||||||
|
else -> R.layout.item_home_following_creator
|
||||||
|
}
|
||||||
|
val view = LayoutInflater.from(parent.context).inflate(layoutResId, parent, false)
|
||||||
view.layoutParams = recyclerItemLayoutParams(parent)
|
view.layoutParams = recyclerItemLayoutParams(parent)
|
||||||
return CreatorViewHolder(view, onClickItem)
|
return when (viewType) {
|
||||||
|
VIEW_TYPE_ALL -> AllViewHolder(view, onClickAll)
|
||||||
|
else -> CreatorViewHolder(view, onClickItem)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: CreatorViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||||
holder.bind(items[position])
|
when (holder) {
|
||||||
|
is AllViewHolder -> holder.bind()
|
||||||
|
is CreatorViewHolder -> holder.bind(items[position])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getItemCount(): Int = items.size
|
override fun getItemCount(): Int = items.size + if (items.isNotEmpty()) 1 else 0
|
||||||
|
|
||||||
|
override fun getItemViewType(position: Int): Int = if (position < items.size) VIEW_TYPE_CREATOR else VIEW_TYPE_ALL
|
||||||
|
|
||||||
class CreatorViewHolder(
|
class CreatorViewHolder(
|
||||||
itemView: View,
|
itemView: View,
|
||||||
@@ -44,4 +57,18 @@ class HomeFollowingCreatorAdapter(
|
|||||||
itemView.setOnClickListener { onClickItem(item) }
|
itemView.setOnClickListener { onClickItem(item) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class AllViewHolder(
|
||||||
|
itemView: View,
|
||||||
|
private val onClickAll: () -> Unit
|
||||||
|
) : RecyclerView.ViewHolder(itemView) {
|
||||||
|
fun bind() {
|
||||||
|
itemView.setOnClickListener { onClickAll() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private companion object {
|
||||||
|
private const val VIEW_TYPE_CREATOR = 0
|
||||||
|
private const val VIEW_TYPE_ALL = 1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import android.widget.TextView
|
|||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import kr.co.vividnext.sodalive.R
|
import kr.co.vividnext.sodalive.R
|
||||||
import kr.co.vividnext.sodalive.common.formatUtcRelativeTimeText
|
import kr.co.vividnext.sodalive.common.formatUtcRelativeTimeText
|
||||||
import kr.co.vividnext.sodalive.extensions.loadUrl
|
|
||||||
import kr.co.vividnext.sodalive.v2.main.home.model.HomeFollowingLiveUiItem
|
import kr.co.vividnext.sodalive.v2.main.home.model.HomeFollowingLiveUiItem
|
||||||
|
|
||||||
class HomeFollowingLiveAdapter(
|
class HomeFollowingLiveAdapter(
|
||||||
@@ -37,14 +36,12 @@ class HomeFollowingLiveAdapter(
|
|||||||
itemView: View,
|
itemView: View,
|
||||||
private val onClickItem: (HomeFollowingLiveUiItem) -> Unit
|
private val onClickItem: (HomeFollowingLiveUiItem) -> Unit
|
||||||
) : RecyclerView.ViewHolder(itemView) {
|
) : RecyclerView.ViewHolder(itemView) {
|
||||||
private val thumbnailImage = itemView.findViewById<ImageView>(R.id.iv_home_following_live_thumbnail)
|
|
||||||
private val profileImage = itemView.findViewById<ImageView>(R.id.iv_home_following_live_creator_profile)
|
private val profileImage = itemView.findViewById<ImageView>(R.id.iv_home_following_live_creator_profile)
|
||||||
private val titleText = itemView.findViewById<TextView>(R.id.tv_home_following_live_title)
|
private val titleText = itemView.findViewById<TextView>(R.id.tv_home_following_live_title)
|
||||||
private val nicknameText = itemView.findViewById<TextView>(R.id.tv_home_following_live_creator_nickname)
|
private val nicknameText = itemView.findViewById<TextView>(R.id.tv_home_following_live_creator_nickname)
|
||||||
private val startedAtText = itemView.findViewById<TextView>(R.id.tv_home_following_live_started_at)
|
private val startedAtText = itemView.findViewById<TextView>(R.id.tv_home_following_live_started_at)
|
||||||
|
|
||||||
fun bind(item: HomeFollowingLiveUiItem) {
|
fun bind(item: HomeFollowingLiveUiItem) {
|
||||||
thumbnailImage.loadUrl(item.creatorProfileImageUrl)
|
|
||||||
profileImage.loadHomeCreatorProfileImage(item.creatorProfileImageUrl)
|
profileImage.loadHomeCreatorProfileImage(item.creatorProfileImageUrl)
|
||||||
titleText.text = item.title
|
titleText.text = item.title
|
||||||
nicknameText.text = item.creatorNickname
|
nicknameText.text = item.creatorNickname
|
||||||
|
|||||||
@@ -8,7 +8,9 @@ import kr.co.vividnext.sodalive.R
|
|||||||
import kr.co.vividnext.sodalive.extensions.loadUrl
|
import kr.co.vividnext.sodalive.extensions.loadUrl
|
||||||
import kr.co.vividnext.sodalive.v2.main.home.data.FollowingNewsType
|
import kr.co.vividnext.sodalive.v2.main.home.data.FollowingNewsType
|
||||||
import kr.co.vividnext.sodalive.v2.main.home.model.HomeFollowingNewsUiItem
|
import kr.co.vividnext.sodalive.v2.main.home.model.HomeFollowingNewsUiItem
|
||||||
|
import kr.co.vividnext.sodalive.v2.widget.feed.FeedContentCategory
|
||||||
import kr.co.vividnext.sodalive.v2.widget.feed.FeedContentView
|
import kr.co.vividnext.sodalive.v2.widget.feed.FeedContentView
|
||||||
|
import kr.co.vividnext.sodalive.v2.widget.feed.FeedRankHighlight
|
||||||
import kr.co.vividnext.sodalive.v2.widget.feed.FeedItem
|
import kr.co.vividnext.sodalive.v2.widget.feed.FeedItem
|
||||||
import kr.co.vividnext.sodalive.v2.widget.feed.FeedRankView
|
import kr.co.vividnext.sodalive.v2.widget.feed.FeedRankView
|
||||||
import kr.co.vividnext.sodalive.v2.widget.feed.FeedSize
|
import kr.co.vividnext.sodalive.v2.widget.feed.FeedSize
|
||||||
@@ -29,13 +31,8 @@ class HomeFollowingNewsAdapter(
|
|||||||
|
|
||||||
override fun getItemViewType(position: Int): Int = when (val item = items[position]) {
|
override fun getItemViewType(position: Int): Int = when (val item = items[position]) {
|
||||||
is HomeFollowingNewsUiItem.Ranking -> VIEW_TYPE_RANKING
|
is HomeFollowingNewsUiItem.Ranking -> VIEW_TYPE_RANKING
|
||||||
is HomeFollowingNewsUiItem.Content -> when (item.type) {
|
is HomeFollowingNewsUiItem.Content -> VIEW_TYPE_CONTENT
|
||||||
FollowingNewsType.COMMUNITY_POST -> VIEW_TYPE_COMMUNITY
|
is HomeFollowingNewsUiItem.Community -> VIEW_TYPE_COMMUNITY
|
||||||
FollowingNewsType.AUDIO_CONTENT,
|
|
||||||
FollowingNewsType.PHOTO_CONTENT -> VIEW_TYPE_CONTENT
|
|
||||||
FollowingNewsType.CREATOR_RANKING,
|
|
||||||
FollowingNewsType.CONTENT_RANKING -> VIEW_TYPE_CONTENT
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NewsViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NewsViewHolder {
|
||||||
@@ -76,18 +73,39 @@ class HomeFollowingNewsAdapter(
|
|||||||
|
|
||||||
override fun bind(item: HomeFollowingNewsUiItem) {
|
override fun bind(item: HomeFollowingNewsUiItem) {
|
||||||
val ranking = item as HomeFollowingNewsUiItem.Ranking
|
val ranking = item as HomeFollowingNewsUiItem.Ranking
|
||||||
|
val context = view.context
|
||||||
|
val rankText = "${ranking.rank}위"
|
||||||
|
val message = ranking.toRankingMessage(rankText)
|
||||||
val feedItem = FeedItem.Rank(
|
val feedItem = FeedItem.Rank(
|
||||||
feedId = ranking.newsId,
|
feedId = ranking.newsId,
|
||||||
imageUrl = ranking.thumbnailImageUrl.orEmpty(),
|
imageUrl = ranking.imageUrl.orEmpty(),
|
||||||
rankText = ranking.rank.toString(),
|
rankText = ranking.rank.toString(),
|
||||||
message = ranking.body.ifBlank { ranking.title },
|
message = message,
|
||||||
highlightRanges = emptyList()
|
highlightRanges = listOf(
|
||||||
|
FeedRankHighlight(
|
||||||
|
start = message.indexOf(rankText),
|
||||||
|
endExclusive = message.indexOf(rankText) + rankText.length
|
||||||
|
)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
view.setFeedSize(feedSize(FeedVariant.Rank, parent))
|
view.setFeedSize(feedSize(FeedVariant.Rank, parent))
|
||||||
view.bind(feedItem)
|
view.bind(feedItem)
|
||||||
bindImage(view.imageView(), feedItem.imageUrl)
|
bindImage(view.imageView(), feedItem.imageUrl)
|
||||||
view.setOnFeedClick { onClickItem(ranking) }
|
view.setOnFeedClick { onClickItem(ranking) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun HomeFollowingNewsUiItem.Ranking.toRankingMessage(rankText: String): String {
|
||||||
|
val messageResId = when (type) {
|
||||||
|
FollowingNewsType.CREATOR_RANKING -> R.string.screen_home_following_creator_ranking_news_message
|
||||||
|
FollowingNewsType.CONTENT_RANKING -> R.string.screen_home_following_content_ranking_news_message
|
||||||
|
FollowingNewsType.COMMUNITY_POST,
|
||||||
|
FollowingNewsType.AUDIO_CONTENT,
|
||||||
|
FollowingNewsType.PHOTO_CONTENT -> R.string.screen_home_following_content_ranking_news_message
|
||||||
|
}
|
||||||
|
val subject = if (type == FollowingNewsType.CREATOR_RANKING) creatorNickname else title
|
||||||
|
val context = view.context
|
||||||
|
return context.getString(messageResId, subject, rankText)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class CommunityViewHolder(
|
class CommunityViewHolder(
|
||||||
@@ -97,19 +115,19 @@ class HomeFollowingNewsAdapter(
|
|||||||
) : NewsViewHolder(view) {
|
) : NewsViewHolder(view) {
|
||||||
|
|
||||||
override fun bind(item: HomeFollowingNewsUiItem) {
|
override fun bind(item: HomeFollowingNewsUiItem) {
|
||||||
val community = item as HomeFollowingNewsUiItem.Content
|
val community = item as HomeFollowingNewsUiItem.Community
|
||||||
val feedItem = FeedItem.Community(
|
val feedItem = FeedItem.Community(
|
||||||
feedId = community.newsId,
|
feedId = community.newsId,
|
||||||
creatorId = community.targetId.toString(),
|
creatorId = community.targetId.toString(),
|
||||||
creatorName = community.creatorNickname,
|
creatorName = community.creatorNickname,
|
||||||
creatorImageUrl = community.creatorProfileImageUrl,
|
creatorImageUrl = community.creatorProfileImageUrl,
|
||||||
postId = community.targetId.toString(),
|
postId = community.postId.toString(),
|
||||||
bodyText = community.body.ifBlank { community.title },
|
bodyText = community.content,
|
||||||
keywordText = "",
|
keywordText = "",
|
||||||
createdAtText = community.visibleFromText,
|
createdAtText = community.visibleFromText,
|
||||||
commentCount = 0,
|
commentCount = community.commentCount,
|
||||||
likeCount = 0,
|
likeCount = community.likeCount,
|
||||||
imageUrl = community.thumbnailImageUrl
|
imageUrl = community.imageUrl
|
||||||
)
|
)
|
||||||
view.setFeedSize(feedSize(FeedVariant.Community, parent))
|
view.setFeedSize(feedSize(FeedVariant.Community, parent))
|
||||||
view.bind(feedItem)
|
view.bind(feedItem)
|
||||||
@@ -132,10 +150,11 @@ class HomeFollowingNewsAdapter(
|
|||||||
creatorId = content.targetId.toString(),
|
creatorId = content.targetId.toString(),
|
||||||
creatorName = content.creatorNickname,
|
creatorName = content.creatorNickname,
|
||||||
creatorImageUrl = content.creatorProfileImageUrl,
|
creatorImageUrl = content.creatorProfileImageUrl,
|
||||||
contentId = content.targetId.toString(),
|
contentId = content.contentId.toString(),
|
||||||
contentTitle = content.title,
|
contentTitle = content.title,
|
||||||
contentImageUrl = content.thumbnailImageUrl.orEmpty(),
|
contentImageUrl = content.contentImageUrl.orEmpty(),
|
||||||
createdAtText = content.visibleFromText
|
createdAtText = content.visibleFromText,
|
||||||
|
category = content.type.toFeedContentCategory()
|
||||||
)
|
)
|
||||||
view.setFeedSize(feedSize(FeedVariant.Content, parent))
|
view.setFeedSize(feedSize(FeedVariant.Content, parent))
|
||||||
view.bind(feedItem)
|
view.bind(feedItem)
|
||||||
@@ -164,5 +183,13 @@ class HomeFollowingNewsAdapter(
|
|||||||
imageView.loadUrl(url)
|
imageView.loadUrl(url)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun FollowingNewsType.toFeedContentCategory(): FeedContentCategory = when (this) {
|
||||||
|
FollowingNewsType.AUDIO_CONTENT -> FeedContentCategory.Audio
|
||||||
|
FollowingNewsType.PHOTO_CONTENT -> FeedContentCategory.Photo
|
||||||
|
FollowingNewsType.COMMUNITY_POST,
|
||||||
|
FollowingNewsType.CREATOR_RANKING,
|
||||||
|
FollowingNewsType.CONTENT_RANKING -> FeedContentCategory.Content
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,11 +4,13 @@ import androidx.annotation.StringRes
|
|||||||
import kr.co.vividnext.sodalive.R
|
import kr.co.vividnext.sodalive.R
|
||||||
|
|
||||||
enum class FeedContentCategory(
|
enum class FeedContentCategory(
|
||||||
@StringRes val labelResId: Int,
|
@param:StringRes val labelResId: Int,
|
||||||
val ratioWidth: Int,
|
val ratioWidth: Int,
|
||||||
val ratioHeight: Int
|
val ratioHeight: Int
|
||||||
) {
|
) {
|
||||||
Content(labelResId = R.string.feed_content_category_content, ratioWidth = 1, ratioHeight = 1),
|
Content(labelResId = R.string.feed_content_category_content, ratioWidth = 1, ratioHeight = 1),
|
||||||
|
Audio(labelResId = R.string.home_recommendation_activity_audio, ratioWidth = 1, ratioHeight = 1),
|
||||||
|
Photo(labelResId = R.string.screen_home_following_photo_content, ratioWidth = 1, ratioHeight = 1),
|
||||||
Series(labelResId = R.string.feed_content_category_series, ratioWidth = 163, ratioHeight = 230),
|
Series(labelResId = R.string.feed_content_category_series, ratioWidth = 163, ratioHeight = 230),
|
||||||
Magazine(labelResId = R.string.feed_content_category_magazine, ratioWidth = 163, ratioHeight = 218)
|
Magazine(labelResId = R.string.feed_content_category_magazine, ratioWidth = 163, ratioHeight = 218)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ class FeedContentView @JvmOverloads constructor(
|
|||||||
titleText = findViewById(R.id.tv_feed_content_title)
|
titleText = findViewById(R.id.tv_feed_content_title)
|
||||||
categoryText = findViewById(R.id.tv_feed_content_category)
|
categoryText = findViewById(R.id.tv_feed_content_category)
|
||||||
createdAtText = findViewById(R.id.tv_feed_content_created_at)
|
createdAtText = findViewById(R.id.tv_feed_content_created_at)
|
||||||
|
clipToOutline = true
|
||||||
|
outlineProvider = roundedOutlineProvider()
|
||||||
contentImageView().clipToOutline = true
|
contentImageView().clipToOutline = true
|
||||||
contentImageView().outlineProvider = roundedOutlineProvider()
|
contentImageView().outlineProvider = roundedOutlineProvider()
|
||||||
profileImageView().clipToOutline = true
|
profileImageView().clipToOutline = true
|
||||||
@@ -105,6 +107,6 @@ class FeedContentView @JvmOverloads constructor(
|
|||||||
private fun Int.dpToPx(): Int = (this * resources.displayMetrics.density).roundToInt()
|
private fun Int.dpToPx(): Int = (this * resources.displayMetrics.density).roundToInt()
|
||||||
|
|
||||||
private companion object {
|
private companion object {
|
||||||
const val CONTENT_IMAGE_WIDTH_DP = 163
|
const val CONTENT_IMAGE_WIDTH_DP = 88
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<solid android:color="@color/gray_800" />
|
||||||
|
<corners android:radius="@dimen/radius_14" />
|
||||||
|
</shape>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<solid android:color="@color/gray_900" />
|
||||||
|
<corners android:radius="20dp" />
|
||||||
|
</shape>
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
|
||||||
|
<solid android:color="@color/gray_800" />
|
||||||
|
</shape>
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
|
||||||
|
<solid android:color="@color/soda_400" />
|
||||||
|
</shape>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<solid android:color="@color/gray_800" />
|
||||||
|
<corners android:radius="18dp" />
|
||||||
|
</shape>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<solid android:color="@color/soda_400" />
|
||||||
|
<corners android:radius="18dp" />
|
||||||
|
</shape>
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<solid android:color="@color/gray_900" />
|
||||||
|
<stroke
|
||||||
|
android:width="2dp"
|
||||||
|
android:color="@color/color_3bb9f1" />
|
||||||
|
<corners android:radius="90dp" />
|
||||||
|
</shape>
|
||||||
5
app/src/main/res/drawable/bg_v2_modal.xml
Normal file
5
app/src/main/res/drawable/bg_v2_modal.xml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<solid android:color="@color/gray_900" />
|
||||||
|
<corners android:radius="@dimen/radius_14" />
|
||||||
|
</shape>
|
||||||
10
app/src/main/res/drawable/ic_feed_community_heart_fill.xml
Normal file
10
app/src/main/res/drawable/ic_feed_community_heart_fill.xml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="18dp"
|
||||||
|
android:height="18dp"
|
||||||
|
android:viewportWidth="18"
|
||||||
|
android:viewportHeight="18">
|
||||||
|
<path
|
||||||
|
android:fillColor="@color/soda_400"
|
||||||
|
android:pathData="M9,15.8L7.9,14.8C4,11.3 1.5,9 1.5,6.2C1.5,3.9 3.3,2.2 5.5,2.2C6.8,2.2 8,2.8 9,3.8C10,2.8 11.2,2.2 12.5,2.2C14.7,2.2 16.5,3.9 16.5,6.2C16.5,9 14,11.3 10.1,14.8L9,15.8Z" />
|
||||||
|
</vector>
|
||||||
10
app/src/main/res/drawable/ic_new_arrow_up_gray.xml
Normal file
10
app/src/main/res/drawable/ic_new_arrow_up_gray.xml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="@color/gray_500"
|
||||||
|
android:pathData="M12,5L5,12L6.4,13.4L11,8.8V19H13V8.8L17.6,13.4L19,12L12,5Z" />
|
||||||
|
</vector>
|
||||||
10
app/src/main/res/drawable/ic_new_arrow_up_white.xml
Normal file
10
app/src/main/res/drawable/ic_new_arrow_up_white.xml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="@color/white"
|
||||||
|
android:pathData="M12,5L5,12L6.4,13.4L11,8.8V19H13V8.8L17.6,13.4L19,12L12,5Z" />
|
||||||
|
</vector>
|
||||||
@@ -407,6 +407,31 @@
|
|||||||
app:tint="@color/white" />
|
app:tint="@color/white" />
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/btn_creator_channel_fantalk_fixed_plus"
|
||||||
|
android:layout_width="66dp"
|
||||||
|
android:layout_height="66dp"
|
||||||
|
android:layout_marginEnd="@dimen/spacing_14"
|
||||||
|
android:layout_marginBottom="@dimen/spacing_14"
|
||||||
|
android:background="@drawable/bg_creator_channel_owner_fab"
|
||||||
|
android:clickable="true"
|
||||||
|
android:contentDescription="@string/creator_channel_fantalk_support_action"
|
||||||
|
android:elevation="8dp"
|
||||||
|
android:focusable="true"
|
||||||
|
android:padding="@dimen/spacing_14"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
tools:visibility="visible">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="38dp"
|
||||||
|
android:layout_height="38dp"
|
||||||
|
android:contentDescription="@null"
|
||||||
|
android:src="@drawable/ic_plus_no_bg"
|
||||||
|
app:tint="@color/white" />
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:id="@+id/owner_fab_button"
|
android:id="@+id/owner_fab_button"
|
||||||
android:layout_width="66dp"
|
android:layout_width="66dp"
|
||||||
|
|||||||
@@ -0,0 +1,338 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="@color/black">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:id="@+id/layout_creator_channel_community_detail_title_bar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="60dp"
|
||||||
|
android:background="@color/black"
|
||||||
|
android:paddingHorizontal="@dimen/spacing_14"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/btn_creator_channel_community_detail_back"
|
||||||
|
android:layout_width="42dp"
|
||||||
|
android:layout_height="42dp"
|
||||||
|
android:background="@android:color/transparent"
|
||||||
|
android:contentDescription="@string/a11y_back"
|
||||||
|
android:src="@drawable/ic_new_bar_back"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
<androidx.core.widget.NestedScrollView
|
||||||
|
android:id="@+id/scroll_creator_channel_community_detail"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:paddingHorizontal="@dimen/spacing_14"
|
||||||
|
android:paddingBottom="@dimen/spacing_16"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/layout_creator_channel_community_detail_input_bar"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/layout_creator_channel_community_detail_title_bar">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/layout_creator_channel_community_detail_post"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@drawable/bg_feed_card"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="@dimen/spacing_14">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/iv_creator_channel_community_detail_profile"
|
||||||
|
android:layout_width="42dp"
|
||||||
|
android:layout_height="42dp"
|
||||||
|
android:contentDescription="@string/a11y_feed_profile_image"
|
||||||
|
android:scaleType="centerCrop"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:src="@drawable/bg_placeholder" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_creator_channel_community_detail_nickname"
|
||||||
|
style="@style/Typography.Body5"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="@dimen/spacing_8"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
app:layout_constraintEnd_toStartOf="@id/layout_creator_channel_community_detail_price"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/iv_creator_channel_community_detail_profile"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/iv_creator_channel_community_detail_profile"
|
||||||
|
tools:text="크리에이터" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_creator_channel_community_detail_time"
|
||||||
|
style="@style/Typography.Body6"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="2dp"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:textColor="@color/gray_500"
|
||||||
|
app:layout_constraintEnd_toEndOf="@id/tv_creator_channel_community_detail_nickname"
|
||||||
|
app:layout_constraintStart_toStartOf="@id/tv_creator_channel_community_detail_nickname"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/tv_creator_channel_community_detail_nickname"
|
||||||
|
tools:text="2분 전" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/layout_creator_channel_community_detail_price"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@drawable/bg_creator_channel_community_price"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingHorizontal="@dimen/spacing_4"
|
||||||
|
android:paddingVertical="2dp"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/iv_creator_channel_community_detail_profile"
|
||||||
|
tools:visibility="visible">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="18dp"
|
||||||
|
android:layout_height="18dp"
|
||||||
|
android:contentDescription="@null"
|
||||||
|
android:src="@drawable/ic_bar_cash" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_creator_channel_community_detail_price"
|
||||||
|
style="@style/Typography.Caption3"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="2dp"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:textColor="@color/black"
|
||||||
|
tools:text="300" />
|
||||||
|
</LinearLayout>
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_creator_channel_community_detail_body"
|
||||||
|
style="@style/Typography.Body3"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/spacing_12"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
tools:text="게시글 본문" />
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/layout_creator_channel_community_detail_image_container"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="236dp"
|
||||||
|
android:layout_marginTop="@dimen/spacing_14"
|
||||||
|
android:background="@drawable/bg_feed_community_image"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/iv_creator_channel_community_detail_image"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:contentDescription="@string/a11y_feed_content_image"
|
||||||
|
android:scaleType="centerCrop" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/layout_creator_channel_community_detail_paywall"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="@color/gray_800"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:contentDescription="@null"
|
||||||
|
android:src="@drawable/ic_new_community_lock" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_creator_channel_community_detail_paywall_price"
|
||||||
|
style="@style/Typography.Body3"
|
||||||
|
android:layout_width="70dp"
|
||||||
|
android:layout_height="36dp"
|
||||||
|
android:layout_marginTop="@dimen/spacing_4"
|
||||||
|
android:background="@drawable/bg_creator_channel_community_price"
|
||||||
|
android:drawableStart="@drawable/ic_bar_cash"
|
||||||
|
android:drawablePadding="@dimen/spacing_6"
|
||||||
|
android:gravity="center"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:textColor="@color/black"
|
||||||
|
tools:text="30" />
|
||||||
|
</LinearLayout>
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_creator_channel_community_detail_audio"
|
||||||
|
style="@style/Typography.Body6"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/spacing_12"
|
||||||
|
android:background="@drawable/bg_creator_channel_fantalk_reply"
|
||||||
|
android:drawableStart="@drawable/ic_new_player_play"
|
||||||
|
android:drawablePadding="@dimen/spacing_8"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:padding="@dimen/spacing_12"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:text="Audio"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_creator_channel_community_detail_purchased"
|
||||||
|
style="@style/Typography.Caption2"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="end"
|
||||||
|
android:layout_marginTop="@dimen/spacing_8"
|
||||||
|
android:background="@drawable/bg_creator_channel_community_price"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:paddingHorizontal="@dimen/spacing_8"
|
||||||
|
android:paddingVertical="@dimen/spacing_4"
|
||||||
|
android:text="@string/creator_channel_community_purchased_badge"
|
||||||
|
android:textColor="@color/black"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:layout_marginTop="@dimen/spacing_16"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/iv_creator_channel_community_detail_comment"
|
||||||
|
android:layout_width="18dp"
|
||||||
|
android:layout_height="18dp"
|
||||||
|
android:contentDescription="@null"
|
||||||
|
android:src="@drawable/ic_feed_community_reply" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_creator_channel_community_detail_comment_count"
|
||||||
|
style="@style/Typography.Body3"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="@dimen/spacing_4"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:textColor="@color/gray_400"
|
||||||
|
tools:text="5" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/iv_creator_channel_community_detail_heart"
|
||||||
|
android:layout_width="18dp"
|
||||||
|
android:layout_height="18dp"
|
||||||
|
android:layout_marginStart="15dp"
|
||||||
|
android:contentDescription="@null"
|
||||||
|
android:src="@drawable/ic_feed_community_heart" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_creator_channel_community_detail_like_count"
|
||||||
|
style="@style/Typography.Body3"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="@dimen/spacing_4"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:textColor="@color/gray_400"
|
||||||
|
tools:text="6" />
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_creator_channel_community_detail_comment_header"
|
||||||
|
style="@style/Typography.Body3"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/spacing_14"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:text="@string/creator_channel_community_comment_header"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:text="댓글 2"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/rv_creator_channel_community_detail_comments"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/spacing_12"
|
||||||
|
android:nestedScrollingEnabled="false"
|
||||||
|
tools:itemCount="2"
|
||||||
|
tools:listitem="@layout/item_creator_channel_community_comment" />
|
||||||
|
</LinearLayout>
|
||||||
|
</androidx.core.widget.NestedScrollView>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/layout_creator_channel_community_detail_input_bar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@color/black"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingHorizontal="@dimen/spacing_14"
|
||||||
|
android:paddingVertical="10dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent">
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/et_creator_channel_community_detail_comment"
|
||||||
|
style="@style/Typography.Body6"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:background="@drawable/bg_creator_channel_community_input_field"
|
||||||
|
android:hint="@string/creator_channel_community_comment_placeholder"
|
||||||
|
android:importantForAutofill="no"
|
||||||
|
android:inputType="textMultiLine|textCapSentences"
|
||||||
|
android:maxLines="3"
|
||||||
|
android:minHeight="40dp"
|
||||||
|
android:minLines="1"
|
||||||
|
android:paddingHorizontal="@dimen/spacing_14"
|
||||||
|
android:scrollbars="vertical"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textColorHint="@color/gray_500"
|
||||||
|
tools:ignore="LabelFor" />
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/btn_creator_channel_community_detail_send"
|
||||||
|
android:layout_width="40dp"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:layout_marginStart="@dimen/spacing_8"
|
||||||
|
android:background="@drawable/bg_creator_channel_community_send_disabled"
|
||||||
|
android:contentDescription="@string/action_send"
|
||||||
|
android:enabled="false"
|
||||||
|
android:src="@drawable/ic_new_arrow_up_gray"
|
||||||
|
tools:background="@drawable/bg_creator_channel_community_send_enabled" />
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/btn_creator_channel_community_detail_edit_cancel"
|
||||||
|
android:layout_width="40dp"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:layout_marginStart="@dimen/spacing_8"
|
||||||
|
android:background="@android:color/transparent"
|
||||||
|
android:contentDescription="@string/cancel"
|
||||||
|
android:src="@drawable/ic_x_white"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
</LinearLayout>
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="@color/black">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:id="@+id/layout_creator_channel_community_reply_title_bar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="60dp"
|
||||||
|
android:background="@color/black"
|
||||||
|
android:paddingHorizontal="@dimen/spacing_14"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/btn_creator_channel_community_reply_back"
|
||||||
|
android:layout_width="42dp"
|
||||||
|
android:layout_height="42dp"
|
||||||
|
android:background="@android:color/transparent"
|
||||||
|
android:contentDescription="@string/a11y_back"
|
||||||
|
android:src="@drawable/ic_new_bar_back"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingHorizontal="@dimen/spacing_14"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/layout_creator_channel_community_reply_input_bar"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/layout_creator_channel_community_reply_title_bar">
|
||||||
|
|
||||||
|
<include
|
||||||
|
android:id="@+id/layout_creator_channel_community_reply_parent"
|
||||||
|
layout="@layout/item_creator_channel_community_comment"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/rv_creator_channel_community_reply"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:paddingTop="@dimen/spacing_14"
|
||||||
|
tools:itemCount="2"
|
||||||
|
tools:listitem="@layout/item_creator_channel_community_reply" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/layout_creator_channel_community_reply_input_bar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@color/black"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingHorizontal="@dimen/spacing_14"
|
||||||
|
android:paddingVertical="10dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent">
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/et_creator_channel_community_reply"
|
||||||
|
style="@style/Typography.Body6"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:background="@drawable/bg_creator_channel_community_input_field"
|
||||||
|
android:hint="@string/creator_channel_community_reply_placeholder"
|
||||||
|
android:importantForAutofill="no"
|
||||||
|
android:inputType="textMultiLine|textCapSentences"
|
||||||
|
android:maxLines="3"
|
||||||
|
android:minHeight="40dp"
|
||||||
|
android:minLines="1"
|
||||||
|
android:paddingHorizontal="@dimen/spacing_14"
|
||||||
|
android:scrollbars="vertical"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textColorHint="@color/gray_500"
|
||||||
|
tools:ignore="LabelFor" />
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/btn_creator_channel_community_reply_send"
|
||||||
|
android:layout_width="40dp"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:layout_marginStart="@dimen/spacing_8"
|
||||||
|
android:background="@drawable/bg_creator_channel_community_send_disabled"
|
||||||
|
android:contentDescription="@string/action_send"
|
||||||
|
android:enabled="false"
|
||||||
|
android:src="@drawable/ic_new_arrow_up_gray" />
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/btn_creator_channel_community_reply_edit_cancel"
|
||||||
|
android:layout_width="40dp"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:layout_marginStart="@dimen/spacing_8"
|
||||||
|
android:background="@android:color/transparent"
|
||||||
|
android:contentDescription="@string/cancel"
|
||||||
|
android:src="@drawable/ic_x_white"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
</LinearLayout>
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
@@ -0,0 +1,249 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="@color/black">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:id="@+id/layout_creator_channel_fantalk_detail_title_bar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="60dp"
|
||||||
|
android:background="@color/black"
|
||||||
|
android:paddingHorizontal="@dimen/spacing_14"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/btn_creator_channel_fantalk_detail_back"
|
||||||
|
android:layout_width="42dp"
|
||||||
|
android:layout_height="42dp"
|
||||||
|
android:background="@android:color/transparent"
|
||||||
|
android:contentDescription="@string/a11y_back"
|
||||||
|
android:src="@drawable/ic_new_bar_back"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
<androidx.core.widget.NestedScrollView
|
||||||
|
android:id="@+id/scroll_creator_channel_fantalk_detail"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:fillViewport="true"
|
||||||
|
android:paddingHorizontal="@dimen/spacing_14"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/layout_creator_channel_fantalk_detail_input_bar"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/layout_creator_channel_fantalk_detail_title_bar">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingTop="@dimen/spacing_14"
|
||||||
|
android:paddingBottom="@dimen/spacing_14">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/iv_creator_channel_fantalk_detail_profile"
|
||||||
|
android:layout_width="42dp"
|
||||||
|
android:layout_height="42dp"
|
||||||
|
android:contentDescription="@string/a11y_feed_profile_image"
|
||||||
|
android:scaleType="centerCrop"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:src="@drawable/ic_placeholder_profile" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_creator_channel_fantalk_detail_nickname"
|
||||||
|
style="@style/Typography.Body5"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="@dimen/spacing_14"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
app:layout_constraintEnd_toStartOf="@id/iv_creator_channel_fantalk_detail_more"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/iv_creator_channel_fantalk_detail_profile"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/iv_creator_channel_fantalk_detail_profile"
|
||||||
|
tools:text="닉네임" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_creator_channel_fantalk_detail_time"
|
||||||
|
style="@style/Typography.Body6"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/spacing_4"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:textColor="@color/gray_500"
|
||||||
|
app:layout_constraintEnd_toEndOf="@id/tv_creator_channel_fantalk_detail_nickname"
|
||||||
|
app:layout_constraintStart_toStartOf="@id/tv_creator_channel_fantalk_detail_nickname"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/tv_creator_channel_fantalk_detail_nickname"
|
||||||
|
tools:text="2분 전" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/iv_creator_channel_fantalk_detail_more"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:contentDescription="@null"
|
||||||
|
android:src="@drawable/ic_new_more"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/tv_creator_channel_fantalk_detail_nickname"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_creator_channel_fantalk_detail_content"
|
||||||
|
style="@style/Typography.Body3"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/spacing_12"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="@id/tv_creator_channel_fantalk_detail_nickname"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/iv_creator_channel_fantalk_detail_profile"
|
||||||
|
tools:text="팬톡 응원 메시지 내용입니다." />
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/view_creator_channel_fantalk_detail_reply_connector"
|
||||||
|
android:layout_width="1dp"
|
||||||
|
android:layout_height="@dimen/spacing_14"
|
||||||
|
android:layout_marginTop="@dimen/spacing_14"
|
||||||
|
android:background="@color/gray_800"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintStart_toStartOf="@id/tv_creator_channel_fantalk_detail_content"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/tv_creator_channel_fantalk_detail_content"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/layout_creator_channel_fantalk_detail_reply"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/spacing_8"
|
||||||
|
android:background="@drawable/bg_creator_channel_fantalk_reply"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="@dimen/spacing_14"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="@id/tv_creator_channel_fantalk_detail_content"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/view_creator_channel_fantalk_detail_reply_connector"
|
||||||
|
tools:visibility="visible">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/iv_creator_channel_fantalk_detail_reply_profile"
|
||||||
|
android:layout_width="20dp"
|
||||||
|
android:layout_height="20dp"
|
||||||
|
android:contentDescription="@string/a11y_feed_profile_image"
|
||||||
|
android:scaleType="centerCrop"
|
||||||
|
tools:src="@drawable/ic_placeholder_profile" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_creator_channel_fantalk_detail_reply_nickname"
|
||||||
|
style="@style/Typography.Caption3"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="@dimen/spacing_8"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
tools:text="크리에이터" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_creator_channel_fantalk_detail_reply_time"
|
||||||
|
style="@style/Typography.Caption3"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="@dimen/spacing_4"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:textColor="@color/gray_400"
|
||||||
|
tools:text="방금 전" />
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/view_creator_channel_fantalk_detail_reply_header_spacer"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:layout_weight="1" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/iv_creator_channel_fantalk_detail_reply_more"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:layout_marginStart="@dimen/spacing_8"
|
||||||
|
android:contentDescription="@null"
|
||||||
|
android:src="@drawable/ic_new_more" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_creator_channel_fantalk_detail_reply_content"
|
||||||
|
style="@style/Typography.Body6"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/spacing_8"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
tools:text="응원 고마워요!" />
|
||||||
|
</LinearLayout>
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
</androidx.core.widget.NestedScrollView>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/layout_creator_channel_fantalk_detail_input_bar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@color/black"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingHorizontal="@dimen/spacing_14"
|
||||||
|
android:paddingVertical="10dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent">
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/et_creator_channel_fantalk_detail_reply"
|
||||||
|
style="@style/Typography.Body6"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:background="@drawable/bg_creator_channel_community_input_field"
|
||||||
|
android:hint="@string/creator_channel_community_reply_placeholder"
|
||||||
|
android:importantForAutofill="no"
|
||||||
|
android:inputType="textMultiLine|textCapSentences"
|
||||||
|
android:maxLines="3"
|
||||||
|
android:minHeight="40dp"
|
||||||
|
android:minLines="1"
|
||||||
|
android:paddingHorizontal="@dimen/spacing_14"
|
||||||
|
android:scrollbars="vertical"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textColorHint="@color/gray_500"
|
||||||
|
tools:ignore="LabelFor" />
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/btn_creator_channel_fantalk_detail_send"
|
||||||
|
android:layout_width="40dp"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:layout_marginStart="@dimen/spacing_8"
|
||||||
|
android:background="@drawable/bg_creator_channel_community_send_disabled"
|
||||||
|
android:contentDescription="@string/action_send"
|
||||||
|
android:enabled="false"
|
||||||
|
android:src="@drawable/ic_new_arrow_up_gray" />
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/btn_creator_channel_fantalk_detail_edit_cancel"
|
||||||
|
android:layout_width="40dp"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:layout_marginStart="@dimen/spacing_8"
|
||||||
|
android:background="@android:color/transparent"
|
||||||
|
android:contentDescription="@string/cancel"
|
||||||
|
android:src="@drawable/ic_x_white"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
</LinearLayout>
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="@color/black">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_creator_channel_fantalk_write_cancel"
|
||||||
|
style="@style/Typography.Body2"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="60dp"
|
||||||
|
android:layout_marginStart="@dimen/spacing_14"
|
||||||
|
android:gravity="center"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:text="@string/creator_channel_fantalk_write_cancel"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_creator_channel_fantalk_write_send"
|
||||||
|
style="@style/Typography.Body2"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="44dp"
|
||||||
|
android:layout_marginEnd="@dimen/spacing_14"
|
||||||
|
android:background="@drawable/bg_creator_channel_fantalk_write_send_disabled"
|
||||||
|
android:enabled="false"
|
||||||
|
android:gravity="center"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:minWidth="68dp"
|
||||||
|
android:paddingHorizontal="@dimen/spacing_14"
|
||||||
|
android:text="@string/creator_channel_fantalk_write_send"
|
||||||
|
android:textColor="@color/gray_500"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/tv_creator_channel_fantalk_write_cancel"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/tv_creator_channel_fantalk_write_cancel" />
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/et_creator_channel_fantalk_write_content"
|
||||||
|
style="@style/Typography.Body2"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_marginHorizontal="@dimen/spacing_14"
|
||||||
|
android:layout_marginTop="@dimen/spacing_20"
|
||||||
|
android:background="@android:color/transparent"
|
||||||
|
android:gravity="top|start"
|
||||||
|
android:hint="@string/creator_channel_fantalk_write_hint"
|
||||||
|
android:imeOptions="actionDone"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:inputType="textMultiLine|textCapSentences"
|
||||||
|
android:maxLength="500"
|
||||||
|
android:minHeight="180dp"
|
||||||
|
android:padding="0dp"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textColorHint="@color/gray_500"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/layout_creator_channel_fantalk_write_info_bar"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/tv_creator_channel_fantalk_write_cancel"
|
||||||
|
tools:text="크리에이터를 향한 응원 메시지를 입력합니다." />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/layout_creator_channel_fantalk_write_info_bar"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="52dp"
|
||||||
|
android:background="@color/black"
|
||||||
|
android:gravity="center_vertical|end"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingHorizontal="@dimen/spacing_14"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_creator_channel_fantalk_write_count"
|
||||||
|
style="@style/Typography.Body2"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
tools:text="23" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_creator_channel_fantalk_write_count_limit"
|
||||||
|
style="@style/Typography.Body2"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:text="/500자"
|
||||||
|
android:textColor="@color/gray_500" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:background="@color/gray_700"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/layout_creator_channel_fantalk_write_info_bar"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent" />
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
71
app/src/main/res/layout/dialog_v2_modal.xml
Normal file
71
app/src/main/res/layout/dialog_v2_modal.xml
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:background="@drawable/bg_v2_modal"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_title"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:fontFamily="@font/bold"
|
||||||
|
android:gravity="center"
|
||||||
|
android:minHeight="64dp"
|
||||||
|
android:paddingHorizontal="@dimen/spacing_20"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="20sp"
|
||||||
|
tools:text="모달" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_desc"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:fontFamily="@font/regular"
|
||||||
|
android:gravity="center"
|
||||||
|
android:lineSpacingMultiplier="1.45"
|
||||||
|
android:padding="@dimen/spacing_20"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="16sp"
|
||||||
|
tools:text="모달 안내 문구가\n들어가는 부분입니다." />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/layout_button"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingHorizontal="@dimen/spacing_20"
|
||||||
|
android:paddingVertical="@dimen/spacing_14"
|
||||||
|
tools:ignore="UseCompoundDrawables">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_cancel"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="@dimen/spacing_8"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:fontFamily="@font/medium"
|
||||||
|
android:gravity="center"
|
||||||
|
android:padding="@dimen/spacing_14"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="18sp"
|
||||||
|
tools:text="취소" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_confirm"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:fontFamily="@font/medium"
|
||||||
|
android:gravity="center"
|
||||||
|
android:padding="@dimen/spacing_14"
|
||||||
|
android:textColor="@color/soda_400"
|
||||||
|
android:textSize="18sp"
|
||||||
|
tools:text="확인" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
@@ -151,7 +151,9 @@
|
|||||||
android:background="@drawable/bg_creator_channel_fantalk_write_button"
|
android:background="@drawable/bg_creator_channel_fantalk_write_button"
|
||||||
android:contentDescription="@string/creator_channel_fantalk_support_action"
|
android:contentDescription="@string/creator_channel_fantalk_support_action"
|
||||||
android:padding="@dimen/spacing_16"
|
android:padding="@dimen/spacing_16"
|
||||||
android:src="@drawable/ic_new_fantalk_plus"
|
android:src="@drawable/ic_plus_no_bg"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:tint="@color/white"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent" />
|
app:layout_constraintEnd_toEndOf="parent" />
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|||||||
@@ -226,28 +226,6 @@
|
|||||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/ll_content_latest_audio_section"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="@dimen/spacing_48"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<include
|
|
||||||
android:id="@+id/view_content_latest_audio_title"
|
|
||||||
layout="@layout/view_section_title" />
|
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
|
||||||
android:id="@+id/rv_content_latest_audios"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="@dimen/spacing_14"
|
|
||||||
android:clipToPadding="false"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:paddingHorizontal="@dimen/spacing_14"
|
|
||||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/ll_content_new_and_hot_section"
|
android:id="@+id/ll_content_new_and_hot_section"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@@ -270,6 +248,28 @@
|
|||||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/ll_content_latest_audio_section"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/spacing_48"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<include
|
||||||
|
android:id="@+id/view_content_latest_audio_title"
|
||||||
|
layout="@layout/view_section_title" />
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/rv_content_latest_audios"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/spacing_14"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingHorizontal="@dimen/spacing_14"
|
||||||
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/ll_content_free_audio_section"
|
android:id="@+id/ll_content_free_audio_section"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="310dp"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical">
|
android:background="@drawable/bg_feed_card"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="@dimen/spacing_14">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@@ -46,8 +48,10 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="@dimen/spacing_12"
|
android:layout_marginTop="@dimen/spacing_12"
|
||||||
|
android:background="@drawable/bg_content_commented_audio_comment"
|
||||||
android:gravity="center_vertical"
|
android:gravity="center_vertical"
|
||||||
android:orientation="horizontal">
|
android:orientation="horizontal"
|
||||||
|
android:padding="@dimen/spacing_12">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/iv_content_comment_profile"
|
android:id="@+id/iv_content_comment_profile"
|
||||||
@@ -66,8 +70,9 @@
|
|||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:ellipsize="end"
|
android:ellipsize="end"
|
||||||
android:includeFontPadding="false"
|
android:includeFontPadding="false"
|
||||||
|
android:lineSpacingMultiplier="1.45"
|
||||||
android:maxLines="2"
|
android:maxLines="2"
|
||||||
android:textColor="@color/gray_500"
|
android:textColor="@color/white"
|
||||||
tools:text="최근 댓글 내용" />
|
tools:text="최근 댓글 내용" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|||||||
@@ -0,0 +1,110 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingVertical="@dimen/spacing_14">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/iv_creator_channel_community_comment_profile"
|
||||||
|
android:layout_width="42dp"
|
||||||
|
android:layout_height="42dp"
|
||||||
|
android:contentDescription="@string/a11y_profile_image"
|
||||||
|
android:scaleType="centerCrop"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:src="@drawable/bg_placeholder" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_creator_channel_community_comment_nickname"
|
||||||
|
style="@style/Typography.Body5"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="@dimen/spacing_8"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
app:layout_constraintEnd_toStartOf="@id/iv_creator_channel_community_comment_more"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/iv_creator_channel_community_comment_profile"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/iv_creator_channel_community_comment_profile"
|
||||||
|
tools:text="닉네임" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_creator_channel_community_comment_time"
|
||||||
|
style="@style/Typography.Body6"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="2dp"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:textColor="@color/gray_500"
|
||||||
|
app:layout_constraintEnd_toEndOf="@id/tv_creator_channel_community_comment_nickname"
|
||||||
|
app:layout_constraintStart_toStartOf="@id/tv_creator_channel_community_comment_nickname"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/tv_creator_channel_community_comment_nickname"
|
||||||
|
tools:text="2분 전" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/iv_creator_channel_community_comment_more"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:contentDescription="@null"
|
||||||
|
android:src="@drawable/ic_new_more"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_creator_channel_community_comment_body"
|
||||||
|
style="@style/Typography.Body3"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="50dp"
|
||||||
|
android:layout_marginTop="@dimen/spacing_8"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
tools:text="댓글 내용" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/layout_creator_channel_community_comment_latest_reply"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="50dp"
|
||||||
|
android:layout_marginTop="@dimen/spacing_12"
|
||||||
|
android:background="@drawable/bg_creator_channel_fantalk_reply"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="@dimen/spacing_14"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_creator_channel_community_comment_latest_reply_nickname"
|
||||||
|
style="@style/Typography.Body5"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
tools:text="답글 닉네임" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_creator_channel_community_comment_latest_reply_body"
|
||||||
|
style="@style/Typography.Body6"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/spacing_6"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
tools:text="최신 답글" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:layout_marginTop="@dimen/spacing_14"
|
||||||
|
android:background="@color/gray_800" />
|
||||||
|
</LinearLayout>
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="32dp"
|
||||||
|
android:layout_marginBottom="@dimen/spacing_12"
|
||||||
|
android:background="@drawable/bg_creator_channel_fantalk_reply"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="@dimen/spacing_14">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/iv_creator_channel_community_reply_item_profile"
|
||||||
|
android:layout_width="42dp"
|
||||||
|
android:layout_height="42dp"
|
||||||
|
android:contentDescription="@string/a11y_profile_image"
|
||||||
|
android:scaleType="centerCrop"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:src="@drawable/bg_placeholder" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_creator_channel_community_reply_item_nickname"
|
||||||
|
style="@style/Typography.Body5"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="@dimen/spacing_8"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
app:layout_constraintEnd_toStartOf="@id/iv_creator_channel_community_reply_item_more"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/iv_creator_channel_community_reply_item_profile"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/iv_creator_channel_community_reply_item_profile"
|
||||||
|
tools:text="닉네임" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_creator_channel_community_reply_item_time"
|
||||||
|
style="@style/Typography.Body6"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="2dp"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:textColor="@color/gray_500"
|
||||||
|
app:layout_constraintEnd_toEndOf="@id/tv_creator_channel_community_reply_item_nickname"
|
||||||
|
app:layout_constraintStart_toStartOf="@id/tv_creator_channel_community_reply_item_nickname"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/tv_creator_channel_community_reply_item_nickname"
|
||||||
|
tools:text="2분 전" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/iv_creator_channel_community_reply_item_more"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:contentDescription="@null"
|
||||||
|
android:src="@drawable/ic_new_more"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_creator_channel_community_reply_item_body"
|
||||||
|
style="@style/Typography.Body3"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/spacing_8"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
tools:text="답글 내용" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
@@ -67,21 +67,14 @@
|
|||||||
android:id="@+id/tv_fantalk_content"
|
android:id="@+id/tv_fantalk_content"
|
||||||
style="@style/Typography.Body6"
|
style="@style/Typography.Body6"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="40dp"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="@dimen/spacing_14"
|
android:layout_marginStart="@dimen/spacing_14"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:ellipsize="end"
|
android:ellipsize="end"
|
||||||
android:includeFontPadding="false"
|
android:includeFontPadding="false"
|
||||||
android:maxLines="2"
|
android:maxLines="1"
|
||||||
android:textColor="@color/white"
|
android:textColor="@color/white"
|
||||||
tools:text="팬이 쓴 응원이 보이는 부분입니다. 두 줄까지만 보여주고 초과 시 말줄임 처리." />
|
tools:text="팬이 쓴 응원이 보이는 부분입니다. 두 줄까지만 보여주고 초과 시 말줄임 처리." />
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:layout_width="18dp"
|
|
||||||
android:layout_height="18dp"
|
|
||||||
android:layout_marginStart="@dimen/spacing_14"
|
|
||||||
android:contentDescription="@null"
|
|
||||||
android:src="@drawable/ic_chevron_down_white" />
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
|
|||||||
28
app/src/main/res/layout/item_home_following_creator_all.xml
Normal file
28
app/src/main/res/layout/item_home_following_creator_all.xml
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center_horizontal"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingHorizontal="16dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_home_following_creator_all"
|
||||||
|
style="@style/Typography.Body5"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="75dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:text="@string/screen_home_theme_all"
|
||||||
|
android:textColor="@color/soda_400" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/Typography.Body5"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/spacing_6"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:text="@string/screen_home_theme_all"
|
||||||
|
android:visibility="invisible" />
|
||||||
|
</LinearLayout>
|
||||||
@@ -1,38 +1,48 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="244dp"
|
android:layout_width="263dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="100dp"
|
||||||
android:background="@drawable/bg_feed_card"
|
android:background="@drawable/bg_home_following_live_capsule"
|
||||||
android:orientation="vertical"
|
android:paddingStart="12dp"
|
||||||
android:padding="@dimen/spacing_14">
|
android:paddingEnd="22dp">
|
||||||
|
|
||||||
<FrameLayout
|
<ImageView
|
||||||
android:layout_width="match_parent"
|
android:id="@+id/iv_home_following_live_creator_profile"
|
||||||
android:layout_height="120dp"
|
android:layout_width="75dp"
|
||||||
android:background="@drawable/bg_audio_content_card_thumbnail">
|
android:layout_height="75dp"
|
||||||
|
android:contentDescription="@null"
|
||||||
|
android:scaleType="centerCrop"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:src="@drawable/ic_placeholder_profile" />
|
||||||
|
|
||||||
<ImageView
|
<LinearLayout
|
||||||
android:id="@+id/iv_home_following_live_thumbnail"
|
android:id="@+id/layout_home_following_live_status"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="0dp"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="wrap_content"
|
||||||
android:contentDescription="@null"
|
android:layout_marginStart="@dimen/spacing_8"
|
||||||
android:scaleType="centerCrop"
|
android:gravity="center_vertical"
|
||||||
tools:src="@drawable/ic_launcher_background" />
|
android:orientation="horizontal"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/iv_home_following_live_creator_profile"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/iv_home_following_live_creator_profile"
|
||||||
|
tools:ignore="UseCompoundDrawables">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="18dp"
|
android:layout_height="18dp"
|
||||||
android:layout_gravity="top|start"
|
|
||||||
android:layout_margin="@dimen/spacing_8"
|
|
||||||
android:background="@drawable/bg_live_thumbnail_badge_capsule"
|
android:background="@drawable/bg_live_thumbnail_badge_capsule"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:paddingHorizontal="@dimen/spacing_4">
|
android:paddingHorizontal="@dimen/spacing_4"
|
||||||
|
android:paddingVertical="2dp">
|
||||||
|
|
||||||
<View
|
<View
|
||||||
android:layout_width="@dimen/spacing_8"
|
android:layout_width="8dp"
|
||||||
android:layout_height="@dimen/spacing_8"
|
android:layout_height="8dp"
|
||||||
android:background="@drawable/bg_live_thumbnail_dot" />
|
android:background="@drawable/bg_live_thumbnail_dot" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
@@ -40,53 +50,11 @@
|
|||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="@dimen/spacing_4"
|
android:layout_marginStart="@dimen/spacing_4"
|
||||||
android:includeFontPadding="true"
|
android:includeFontPadding="false"
|
||||||
android:text="LIVE"
|
android:text="LIVE"
|
||||||
android:textColor="@color/white"
|
android:textColor="@color/white"
|
||||||
tools:ignore="HardcodedText,SmallSp" />
|
tools:ignore="HardcodedText" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</FrameLayout>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tv_home_following_live_title"
|
|
||||||
style="@style/Typography.Heading4"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="@dimen/spacing_12"
|
|
||||||
android:ellipsize="end"
|
|
||||||
android:includeFontPadding="false"
|
|
||||||
android:maxLines="1"
|
|
||||||
android:textColor="@color/white"
|
|
||||||
tools:text="라이브 제목" />
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="@dimen/spacing_8"
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:orientation="horizontal">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/iv_home_following_live_creator_profile"
|
|
||||||
android:layout_width="28dp"
|
|
||||||
android:layout_height="28dp"
|
|
||||||
android:background="@drawable/bg_round_corner_999_263238"
|
|
||||||
android:contentDescription="@null"
|
|
||||||
android:scaleType="centerCrop"
|
|
||||||
tools:src="@drawable/ic_placeholder_profile" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tv_home_following_live_creator_nickname"
|
|
||||||
style="@style/Typography.Body6"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="@dimen/spacing_8"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:ellipsize="end"
|
|
||||||
android:includeFontPadding="false"
|
|
||||||
android:maxLines="1"
|
|
||||||
android:textColor="@color/gray_300"
|
|
||||||
tools:text="크리에이터 이름" />
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/tv_home_following_live_started_at"
|
android:id="@+id/tv_home_following_live_started_at"
|
||||||
@@ -96,6 +64,36 @@
|
|||||||
android:layout_marginStart="@dimen/spacing_8"
|
android:layout_marginStart="@dimen/spacing_8"
|
||||||
android:includeFontPadding="false"
|
android:includeFontPadding="false"
|
||||||
android:textColor="@color/gray_500"
|
android:textColor="@color/gray_500"
|
||||||
tools:text="2분 전" />
|
tools:text="00:00" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</LinearLayout>
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_home_following_live_title"
|
||||||
|
style="@style/Typography.Heading4"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/spacing_4"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
app:layout_constraintEnd_toEndOf="@id/layout_home_following_live_status"
|
||||||
|
app:layout_constraintStart_toStartOf="@id/layout_home_following_live_status"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/layout_home_following_live_status"
|
||||||
|
tools:text="라이브 제목" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_home_following_live_creator_nickname"
|
||||||
|
style="@style/Typography.Body6"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/spacing_4"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:textColor="@color/gray_500"
|
||||||
|
app:layout_constraintEnd_toEndOf="@id/tv_home_following_live_title"
|
||||||
|
app:layout_constraintStart_toStartOf="@id/tv_home_following_live_title"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/tv_home_following_live_title"
|
||||||
|
tools:text="크리에이터이름" />
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@drawable/bg_creator_channel_sort_popup"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingHorizontal="@dimen/spacing_12"
|
||||||
|
android:paddingVertical="@dimen/spacing_8">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_creator_channel_community_more_edit"
|
||||||
|
style="@style/Typography.Body3"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:fontFamily="@font/regular"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:paddingHorizontal="@dimen/spacing_12"
|
||||||
|
android:paddingVertical="@dimen/spacing_8"
|
||||||
|
android:text="@string/creator_channel_fantalk_edit"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
tools:ignore="UnusedAttribute" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_creator_channel_community_more_delete"
|
||||||
|
style="@style/Typography.Body3"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:fontFamily="@font/regular"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:paddingHorizontal="@dimen/spacing_12"
|
||||||
|
android:paddingVertical="@dimen/spacing_8"
|
||||||
|
android:text="@string/creator_channel_fantalk_delete"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
tools:ignore="UnusedAttribute" />
|
||||||
|
</LinearLayout>
|
||||||
@@ -5,20 +5,19 @@
|
|||||||
android:layout_width="374dp"
|
android:layout_width="374dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="@drawable/bg_feed_card"
|
android:background="@drawable/bg_feed_card"
|
||||||
android:clipToOutline="true"
|
|
||||||
android:padding="@dimen/spacing_14">
|
android:padding="@dimen/spacing_14">
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:id="@+id/fl_feed_content_image_container"
|
android:id="@+id/fl_feed_content_image_container"
|
||||||
android:layout_width="163dp"
|
android:layout_width="88dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="88dp"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent">
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/iv_feed_content_image"
|
android:id="@+id/iv_feed_content_image"
|
||||||
android:layout_width="163dp"
|
android:layout_width="88dp"
|
||||||
android:layout_height="163dp"
|
android:layout_height="88dp"
|
||||||
android:contentDescription="@string/a11y_feed_content_image"
|
android:contentDescription="@string/a11y_feed_content_image"
|
||||||
android:scaleType="centerCrop"
|
android:scaleType="centerCrop"
|
||||||
tools:src="@drawable/ic_launcher_background" />
|
tools:src="@drawable/ic_launcher_background" />
|
||||||
|
|||||||
@@ -4,11 +4,11 @@
|
|||||||
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/menu_review_modify"
|
android:id="@+id/menu_review_modify"
|
||||||
android:title="@string/menu_modify_action"
|
android:title="@string/creator_channel_fantalk_edit"
|
||||||
app:showAsAction="ifRoom" />
|
app:showAsAction="ifRoom" />
|
||||||
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/menu_review_delete"
|
android:id="@+id/menu_review_delete"
|
||||||
android:title="@string/menu_delete_action"
|
android:title="@string/creator_channel_fantalk_delete"
|
||||||
app:showAsAction="ifRoom" />
|
app:showAsAction="ifRoom" />
|
||||||
</menu>
|
</menu>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user