콘텐츠 기능 추가
|
@ -61,8 +61,8 @@ android {
|
|||
|
||||
buildConfigField 'String', 'BASE_URL', '"https://test-api.sodalive.net"'
|
||||
buildConfigField 'String', 'BOOTPAY_APP_ID', '"6242a7772701800023f68b2e"'
|
||||
buildConfigField 'String', 'AGORA_APP_ID', '"d28c80855d314a599cd7c15280920699"'
|
||||
buildConfigField 'String', 'AGORA_APP_CERTIFICATE', '"29ef33b7c37e4b80b74af9a6e9b2af5e"'
|
||||
buildConfigField 'String', 'AGORA_APP_ID', '"b96574e191a9430fa54c605528aa3ef7"'
|
||||
buildConfigField 'String', 'AGORA_APP_CERTIFICATE', '"ae18ade3afcf4086bd4397726eb0654c"'
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
|
@ -142,4 +142,8 @@ dependencies {
|
|||
|
||||
// sound visualizer
|
||||
implementation "com.gauravk.audiovisualizer:audiovisualizer:0.9.2"
|
||||
|
||||
// Glide
|
||||
implementation 'com.github.bumptech.glide:glide:4.12.0'
|
||||
annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0'
|
||||
}
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
{
|
||||
"_note1": "KEEP THIS FILE! Check it into a version control system (VCS) like git.",
|
||||
"_note2": "ObjectBox manages crucial IDs for your object model. See docs for details.",
|
||||
"_note3": "If you have VCS merge conflicts, you must resolve them according to ObjectBox docs.",
|
||||
"entities": [
|
||||
{
|
||||
"id": "1:2209417227252155460",
|
||||
"lastPropertyId": "8:7803281435927194929",
|
||||
"name": "PlaybackTracking",
|
||||
"properties": [
|
||||
{
|
||||
"id": "1:3889922602505997244",
|
||||
"name": "id",
|
||||
"type": 6,
|
||||
"flags": 1
|
||||
},
|
||||
{
|
||||
"id": "2:874896374244616380",
|
||||
"name": "contentId",
|
||||
"type": 6
|
||||
},
|
||||
{
|
||||
"id": "3:305496269372931228",
|
||||
"name": "totalDuration",
|
||||
"type": 5
|
||||
},
|
||||
{
|
||||
"id": "4:1202262957765031780",
|
||||
"name": "startPosition",
|
||||
"type": 5
|
||||
},
|
||||
{
|
||||
"id": "5:1595250877919247629",
|
||||
"name": "isFree",
|
||||
"type": 1
|
||||
},
|
||||
{
|
||||
"id": "6:4066577743967565922",
|
||||
"name": "isPreview",
|
||||
"type": 1
|
||||
},
|
||||
{
|
||||
"id": "7:7482414752180672089",
|
||||
"name": "endPosition",
|
||||
"type": 5
|
||||
},
|
||||
{
|
||||
"id": "8:7803281435927194929",
|
||||
"name": "playDateTime",
|
||||
"type": 9
|
||||
}
|
||||
],
|
||||
"relations": []
|
||||
}
|
||||
],
|
||||
"lastEntityId": "1:2209417227252155460",
|
||||
"lastIndexId": "0:0",
|
||||
"lastRelationId": "0:0",
|
||||
"lastSequenceId": "0:0",
|
||||
"modelVersion": 5,
|
||||
"modelVersionParserMinimum": 5,
|
||||
"retiredEntityUids": [],
|
||||
"retiredIndexUids": [],
|
||||
"retiredPropertyUids": [],
|
||||
"retiredRelationUids": [],
|
||||
"version": 1
|
||||
}
|
|
@ -2,9 +2,31 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission
|
||||
android:name="android.permission.READ_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="32" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
|
||||
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
<uses-permission
|
||||
android:name="android.permission.BLUETOOTH"
|
||||
android:maxSdkVersion="30" />
|
||||
<uses-permission
|
||||
android:name="android.permission.BLUETOOTH_ADMIN"
|
||||
android:maxSdkVersion="30" />
|
||||
<uses-permission
|
||||
android:name="android.permission.BLUETOOTH_SCAN"
|
||||
android:usesPermissionFlags="neverForLocation"
|
||||
tools:targetApi="s" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
|
||||
<uses-permission android:name="com.google.android.gms.permission.AD_ID" />
|
||||
|
||||
<application
|
||||
android:name=".app.SodaLiveApp"
|
||||
|
@ -15,6 +37,7 @@
|
|||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:largeHeap="true"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.SodaLive"
|
||||
android:usesCleartextTraffic="true"
|
||||
|
@ -57,6 +80,11 @@
|
|||
<activity android:name=".settings.notification.NotificationSettingsActivity" />
|
||||
<activity android:name=".live.reservation_status.LiveReservationStatusActivity" />
|
||||
<activity android:name=".live.reservation_status.LiveReservationCancelActivity" />
|
||||
<activity android:name=".audio_content.AudioContentActivity" />
|
||||
<activity android:name=".audio_content.detail.AudioContentDetailActivity" />
|
||||
<activity android:name=".audio_content.modify.AudioContentModifyActivity" />
|
||||
<activity android:name=".audio_content.order.AudioContentOrderListActivity" />
|
||||
<activity android:name=".audio_content.upload.AudioContentUploadActivity" />
|
||||
|
||||
<activity
|
||||
android:name="com.google.android.gms.oss.licenses.OssLicensesMenuActivity"
|
||||
|
@ -65,6 +93,12 @@
|
|||
android:name="com.google.android.gms.oss.licenses.OssLicensesActivity"
|
||||
android:theme="@style/Theme.AppCompat.DayNight" />
|
||||
|
||||
<service
|
||||
android:name=".common.SodaLiveService"
|
||||
android:stopWithTask="false" />
|
||||
|
||||
<service android:name=".audio_content.AudioContentPlayService" />
|
||||
|
||||
<!-- [START firebase_service] -->
|
||||
<service
|
||||
android:name=".fcm.SodaFirebaseMessagingService"
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
package kr.co.vividnext.sodalive.audio_content
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import java.util.TimeZone
|
||||
|
||||
data class AddAllPlaybackTrackingRequest(
|
||||
@SerializedName("timezone") val timezone: String = TimeZone.getDefault().id,
|
||||
@SerializedName("trackingDataList") val trackingDataList: List<PlaybackTrackingData>
|
||||
)
|
||||
|
||||
data class PlaybackTrackingData(
|
||||
@SerializedName("contentId") val contentId: Long,
|
||||
@SerializedName("playDateTime") val playDateTime: String,
|
||||
@SerializedName("isPreview") val isPreview: Boolean,
|
||||
)
|
|
@ -0,0 +1,212 @@
|
|||
package kr.co.vividnext.sodalive.audio_content
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.graphics.Rect
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import kr.co.vividnext.sodalive.R
|
||||
import kr.co.vividnext.sodalive.audio_content.detail.AudioContentDetailActivity
|
||||
import kr.co.vividnext.sodalive.audio_content.upload.AudioContentUploadActivity
|
||||
import kr.co.vividnext.sodalive.base.BaseActivity
|
||||
import kr.co.vividnext.sodalive.common.Constants
|
||||
import kr.co.vividnext.sodalive.common.LoadingDialog
|
||||
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
|
||||
import kr.co.vividnext.sodalive.databinding.ActivityAudioContentBinding
|
||||
import kr.co.vividnext.sodalive.extensions.dpToPx
|
||||
import org.koin.android.ext.android.inject
|
||||
|
||||
class AudioContentActivity : BaseActivity<ActivityAudioContentBinding>(
|
||||
ActivityAudioContentBinding::inflate
|
||||
) {
|
||||
|
||||
private val viewModel: AudioContentViewModel by inject()
|
||||
|
||||
private lateinit var loadingDialog: LoadingDialog
|
||||
private lateinit var audioContentAdapter: AudioContentAdapter
|
||||
|
||||
private var userId: Long = 0
|
||||
private lateinit var activityResultLauncher: ActivityResultLauncher<Intent>
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
userId = intent.getLongExtra(Constants.EXTRA_USER_ID, 0)
|
||||
activityResultLauncher = registerForActivityResult(
|
||||
ActivityResultContracts.StartActivityForResult()
|
||||
) {
|
||||
if (it.resultCode == Activity.RESULT_OK) {
|
||||
viewModel.page = 1
|
||||
viewModel.getAudioContentList(userId = userId) { finish() }
|
||||
}
|
||||
}
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
if (userId <= 0) {
|
||||
Toast.makeText(applicationContext, "잘못된 요청입니다.", Toast.LENGTH_LONG).show()
|
||||
finish()
|
||||
}
|
||||
|
||||
bindData()
|
||||
viewModel.getAudioContentList(userId = userId) { finish() }
|
||||
}
|
||||
|
||||
override fun setupView() {
|
||||
loadingDialog = LoadingDialog(this, layoutInflater)
|
||||
binding.toolbar.tvBack.text = "콘텐츠 전체보기"
|
||||
binding.toolbar.tvBack.setOnClickListener { finish() }
|
||||
|
||||
audioContentAdapter = AudioContentAdapter {
|
||||
val intent = Intent(applicationContext, AudioContentDetailActivity::class.java)
|
||||
.apply {
|
||||
putExtra(Constants.EXTRA_AUDIO_CONTENT_ID, it)
|
||||
}
|
||||
activityResultLauncher.launch(intent)
|
||||
}
|
||||
|
||||
binding.rvAudioContent.layoutManager = LinearLayoutManager(
|
||||
applicationContext,
|
||||
LinearLayoutManager.VERTICAL,
|
||||
false
|
||||
)
|
||||
|
||||
binding.rvAudioContent.addItemDecoration(object : RecyclerView.ItemDecoration() {
|
||||
override fun getItemOffsets(
|
||||
outRect: Rect,
|
||||
view: View,
|
||||
parent: RecyclerView,
|
||||
state: RecyclerView.State
|
||||
) {
|
||||
super.getItemOffsets(outRect, view, parent, state)
|
||||
|
||||
when (parent.getChildAdapterPosition(view)) {
|
||||
audioContentAdapter.itemCount - 1 -> {
|
||||
outRect.bottom = 0
|
||||
}
|
||||
|
||||
else -> {
|
||||
outRect.bottom = 13.3f.dpToPx().toInt()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
binding.rvAudioContent.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
||||
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||
super.onScrolled(recyclerView, dx, dy)
|
||||
|
||||
val lastVisibleItemPosition = (recyclerView.layoutManager as LinearLayoutManager?)!!
|
||||
.findLastCompletelyVisibleItemPosition()
|
||||
val itemTotalCount = recyclerView.adapter!!.itemCount - 1
|
||||
|
||||
// 스크롤이 끝에 도달했는지 확인
|
||||
if (!recyclerView.canScrollVertically(1) &&
|
||||
lastVisibleItemPosition == itemTotalCount
|
||||
) {
|
||||
viewModel.getAudioContentList(userId = userId) { }
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
binding.rvAudioContent.adapter = audioContentAdapter
|
||||
|
||||
binding.tvSortNewest.setOnClickListener {
|
||||
viewModel.changeSort(AudioContentViewModel.Sort.NEWEST)
|
||||
}
|
||||
|
||||
binding.tvSortPriceLow.setOnClickListener {
|
||||
viewModel.changeSort(AudioContentViewModel.Sort.PRICE_LOW)
|
||||
}
|
||||
|
||||
binding.tvSortPriceHigh.setOnClickListener {
|
||||
viewModel.changeSort(AudioContentViewModel.Sort.PRICE_HIGH)
|
||||
}
|
||||
|
||||
if (userId == SharedPreferenceManager.userId) {
|
||||
binding.tvNewContent.visibility = View.VISIBLE
|
||||
binding.tvNewContent.setOnClickListener {
|
||||
startActivity(
|
||||
Intent(
|
||||
applicationContext,
|
||||
AudioContentUploadActivity::class.java
|
||||
)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
binding.tvNewContent.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
private fun bindData() {
|
||||
viewModel.toastLiveData.observe(this) {
|
||||
it?.let { Toast.makeText(applicationContext, it, Toast.LENGTH_LONG).show() }
|
||||
}
|
||||
|
||||
viewModel.isLoading.observe(this) {
|
||||
if (it) {
|
||||
loadingDialog.show(screenWidth, "")
|
||||
} else {
|
||||
loadingDialog.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.audioContentListLiveData.observe(this) {
|
||||
if (viewModel.page - 1 == 1) {
|
||||
audioContentAdapter.items.clear()
|
||||
binding.rvAudioContent.scrollToPosition(0)
|
||||
}
|
||||
|
||||
binding.tvTotalCount.text = "${it.totalCount}"
|
||||
|
||||
audioContentAdapter.items.addAll(it.items)
|
||||
audioContentAdapter.notifyDataSetChanged()
|
||||
}
|
||||
|
||||
viewModel.sort.observe(this) {
|
||||
deselectSort()
|
||||
selectSort(
|
||||
when (it) {
|
||||
AudioContentViewModel.Sort.PRICE_HIGH -> {
|
||||
binding.tvSortPriceHigh
|
||||
}
|
||||
|
||||
AudioContentViewModel.Sort.PRICE_LOW -> {
|
||||
binding.tvSortPriceLow
|
||||
}
|
||||
|
||||
else -> {
|
||||
binding.tvSortNewest
|
||||
}
|
||||
}
|
||||
)
|
||||
viewModel.getAudioContentList(userId = userId) { finish() }
|
||||
}
|
||||
}
|
||||
|
||||
private fun deselectSort() {
|
||||
val color = ContextCompat.getColor(
|
||||
applicationContext,
|
||||
R.color.color_88e2e2e2
|
||||
)
|
||||
|
||||
binding.tvSortNewest.setTextColor(color)
|
||||
binding.tvSortPriceLow.setTextColor(color)
|
||||
binding.tvSortPriceHigh.setTextColor(color)
|
||||
}
|
||||
|
||||
private fun selectSort(view: TextView) {
|
||||
view.setTextColor(
|
||||
ContextCompat.getColor(
|
||||
applicationContext,
|
||||
R.color.color_e2e2e2
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
package kr.co.vividnext.sodalive.audio_content
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import coil.load
|
||||
import coil.transform.RoundedCornersTransformation
|
||||
import kr.co.vividnext.sodalive.R
|
||||
import kr.co.vividnext.sodalive.databinding.ItemAudioContentBinding
|
||||
import kr.co.vividnext.sodalive.explorer.profile.GetAudioContentListItem
|
||||
import kr.co.vividnext.sodalive.extensions.dpToPx
|
||||
import kr.co.vividnext.sodalive.extensions.moneyFormat
|
||||
|
||||
class AudioContentAdapter(
|
||||
private val onClickItem: (Long) -> Unit
|
||||
) : RecyclerView.Adapter<AudioContentAdapter.ViewHolder>() {
|
||||
|
||||
val items = mutableListOf<GetAudioContentListItem>()
|
||||
|
||||
inner class ViewHolder(
|
||||
private val binding: ItemAudioContentBinding
|
||||
) : RecyclerView.ViewHolder(binding.root) {
|
||||
fun bind(item: GetAudioContentListItem) {
|
||||
binding.ivCover.load(item.coverImageUrl) {
|
||||
crossfade(true)
|
||||
placeholder(R.drawable.bg_placeholder)
|
||||
transformations(RoundedCornersTransformation(5.3f.dpToPx()))
|
||||
}
|
||||
|
||||
binding.tvTitle.text = item.title
|
||||
binding.tvTheme.text = item.themeStr
|
||||
binding.tvDuration.text = item.duration
|
||||
binding.tvLikeCount.text = item.likeCount.moneyFormat()
|
||||
binding.tvCommentCount.text = item.commentCount.moneyFormat()
|
||||
|
||||
if (item.price < 1) {
|
||||
binding.tvPrice.text = "무료"
|
||||
binding.tvPrice.setCompoundDrawables(null, null, null, null)
|
||||
} else {
|
||||
binding.tvPrice.text = item.price.moneyFormat()
|
||||
binding.tvPrice.setCompoundDrawablesWithIntrinsicBounds(
|
||||
R.drawable.ic_coin_w,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
)
|
||||
}
|
||||
|
||||
binding.iv19.visibility = if (item.isAdult) {
|
||||
View.VISIBLE
|
||||
} else {
|
||||
View.GONE
|
||||
}
|
||||
|
||||
binding.root.setOnClickListener { onClickItem(item.contentId) }
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder(
|
||||
ItemAudioContentBinding.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
parent,
|
||||
false
|
||||
)
|
||||
)
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
holder.bind(items[position])
|
||||
}
|
||||
|
||||
override fun getItemCount() = items.count()
|
||||
}
|
|
@ -0,0 +1,140 @@
|
|||
package kr.co.vividnext.sodalive.audio_content
|
||||
|
||||
import io.reactivex.rxjava3.core.Single
|
||||
import kr.co.vividnext.sodalive.audio_content.comment.GetAudioContentCommentListResponse
|
||||
import kr.co.vividnext.sodalive.audio_content.comment.RegisterAudioContentCommentRequest
|
||||
import kr.co.vividnext.sodalive.audio_content.detail.GetAudioContentDetailResponse
|
||||
import kr.co.vividnext.sodalive.audio_content.detail.PutAudioContentLikeRequest
|
||||
import kr.co.vividnext.sodalive.audio_content.detail.PutAudioContentLikeResponse
|
||||
import kr.co.vividnext.sodalive.audio_content.donation.AudioContentDonationRequest
|
||||
import kr.co.vividnext.sodalive.audio_content.main.GetAudioContentMainItem
|
||||
import kr.co.vividnext.sodalive.audio_content.main.GetAudioContentMainResponse
|
||||
import kr.co.vividnext.sodalive.audio_content.order.GetAudioContentOrderListResponse
|
||||
import kr.co.vividnext.sodalive.audio_content.order.OrderRequest
|
||||
import kr.co.vividnext.sodalive.audio_content.upload.theme.GetAudioContentThemeResponse
|
||||
import kr.co.vividnext.sodalive.common.ApiResponse
|
||||
import kr.co.vividnext.sodalive.explorer.profile.GetAudioContentListResponse
|
||||
import okhttp3.MultipartBody
|
||||
import okhttp3.RequestBody
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.DELETE
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Header
|
||||
import retrofit2.http.Multipart
|
||||
import retrofit2.http.POST
|
||||
import retrofit2.http.PUT
|
||||
import retrofit2.http.Part
|
||||
import retrofit2.http.Path
|
||||
import retrofit2.http.Query
|
||||
|
||||
interface AudioContentApi {
|
||||
@GET("/audio-content")
|
||||
fun getAudioContentList(
|
||||
@Query("creator-id") id: Long,
|
||||
@Query("page") page: Int,
|
||||
@Query("size") size: Int,
|
||||
@Query("sort-type") sort: AudioContentViewModel.Sort,
|
||||
@Header("Authorization") authHeader: String
|
||||
): Single<ApiResponse<GetAudioContentListResponse>>
|
||||
|
||||
@GET("/audio-content/theme")
|
||||
fun getAudioContentThemeList(
|
||||
@Header("Authorization") authHeader: String
|
||||
): Single<ApiResponse<List<GetAudioContentThemeResponse>>>
|
||||
|
||||
@POST("/audio-content")
|
||||
@Multipart
|
||||
fun uploadAudioContent(
|
||||
@Part coverImage: MultipartBody.Part,
|
||||
@Part contentFile: MultipartBody.Part,
|
||||
@Part("request") request: RequestBody,
|
||||
@Header("Authorization") authHeader: String
|
||||
): Single<ApiResponse<Any>>
|
||||
|
||||
@GET("/audio-content/{id}")
|
||||
fun getAudioContentDetail(
|
||||
@Path("id") id: Long,
|
||||
@Query("timezone") timezone: String,
|
||||
@Header("Authorization") authHeader: String
|
||||
): Single<ApiResponse<GetAudioContentDetailResponse>>
|
||||
|
||||
@POST("/order/audio-content")
|
||||
fun orderAudioContent(
|
||||
@Body request: OrderRequest,
|
||||
@Header("Authorization") authHeader: String
|
||||
): Single<ApiResponse<Any>>
|
||||
|
||||
@GET("/order/audio-content")
|
||||
fun getAudioContentOrderList(
|
||||
@Query("page") page: Int,
|
||||
@Query("size") size: Int,
|
||||
@Header("Authorization") authHeader: String
|
||||
): Single<ApiResponse<GetAudioContentOrderListResponse>>
|
||||
|
||||
@POST("/audio-content/playback-tracking")
|
||||
fun addAllPlaybackTracking(
|
||||
@Body request: AddAllPlaybackTrackingRequest,
|
||||
@Header("Authorization") authHeader: String
|
||||
): Single<ApiResponse<Any>>
|
||||
|
||||
@POST("/audio-content/comment")
|
||||
fun registerComment(
|
||||
@Body request: RegisterAudioContentCommentRequest,
|
||||
@Header("Authorization") authHeader: String
|
||||
): Single<ApiResponse<Any>>
|
||||
|
||||
@GET("/audio-content/{id}/comment")
|
||||
fun getAudioContentCommentList(
|
||||
@Path("id") id: Long,
|
||||
@Query("page") page: Int,
|
||||
@Query("size") size: Int,
|
||||
@Query("timezone") timezone: String,
|
||||
@Header("Authorization") authHeader: String
|
||||
): Single<ApiResponse<GetAudioContentCommentListResponse>>
|
||||
|
||||
@GET("/audio-content/comment/{id}")
|
||||
fun getAudioContentCommentCommentList(
|
||||
@Path("id") id: Long,
|
||||
@Query("page") page: Int,
|
||||
@Query("size") size: Int,
|
||||
@Query("timezone") timezone: String,
|
||||
@Header("Authorization") authHeader: String
|
||||
): Single<ApiResponse<GetAudioContentCommentListResponse>>
|
||||
|
||||
@PUT("/audio-content/like")
|
||||
fun likeContent(
|
||||
@Body request: PutAudioContentLikeRequest,
|
||||
@Header("Authorization") authHeader: String
|
||||
): Single<ApiResponse<PutAudioContentLikeResponse>>
|
||||
|
||||
@PUT("/audio-content")
|
||||
@Multipart
|
||||
fun modifyAudioContent(
|
||||
@Part coverImage: MultipartBody.Part?,
|
||||
@Part("request") request: RequestBody,
|
||||
@Header("Authorization") authHeader: String
|
||||
): Single<ApiResponse<Any>>
|
||||
|
||||
@DELETE("/audio-content/{id}")
|
||||
fun deleteAudioContent(
|
||||
@Path("id") id: Long,
|
||||
@Header("Authorization") authHeader: String
|
||||
): Single<ApiResponse<Any>>
|
||||
|
||||
@GET("/audio-content/main")
|
||||
fun getMain(
|
||||
@Header("Authorization") authHeader: String
|
||||
): Single<ApiResponse<GetAudioContentMainResponse>>
|
||||
|
||||
@GET("/audio-content/main/new")
|
||||
fun getNewContentOfTheme(
|
||||
@Query("theme") theme: String,
|
||||
@Header("Authorization") authHeader: String
|
||||
): Single<ApiResponse<List<GetAudioContentMainItem>>>
|
||||
|
||||
@POST("/audio-content/donation")
|
||||
fun donation(
|
||||
@Body request: AudioContentDonationRequest,
|
||||
@Header("Authorization") authHeader: String
|
||||
): Single<ApiResponse<Any>>
|
||||
}
|
|
@ -0,0 +1,556 @@
|
|||
package kr.co.vividnext.sodalive.audio_content
|
||||
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.app.PendingIntent
|
||||
import android.app.Service
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.media.AudioAttributes
|
||||
import android.media.MediaPlayer
|
||||
import android.os.Build
|
||||
import android.os.Handler
|
||||
import android.os.IBinder
|
||||
import android.os.Looper
|
||||
import androidx.core.app.NotificationCompat
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.request.target.CustomTarget
|
||||
import com.bumptech.glide.request.transition.Transition
|
||||
import kr.co.vividnext.sodalive.R
|
||||
import kr.co.vividnext.sodalive.common.Constants
|
||||
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
|
||||
import kr.co.vividnext.sodalive.main.MainActivity
|
||||
import org.koin.android.ext.android.inject
|
||||
|
||||
class AudioContentPlayService :
|
||||
Service(),
|
||||
MediaPlayer.OnPreparedListener,
|
||||
MediaPlayer.OnCompletionListener {
|
||||
|
||||
private var playbackTrackingId: Long = 0
|
||||
private val playbackTrackingRepository: PlaybackTrackingRepository by inject()
|
||||
|
||||
private lateinit var mediaPlayer: MediaPlayer
|
||||
private var url: String? = null
|
||||
private var title: String? = null
|
||||
private var isFree: Boolean? = null
|
||||
private var isPreview: Boolean? = null
|
||||
private var nickname: String? = null
|
||||
private var contentId: Long? = null
|
||||
private var creatorId: Long? = null
|
||||
private var coverImageUrl: String? = null
|
||||
|
||||
private var isPlaying = false
|
||||
|
||||
private val handler = Handler(Looper.getMainLooper())
|
||||
private var changeMediaPlayerPositionRunnable = object : Runnable {
|
||||
override fun run() {
|
||||
val intent = Intent(Constants.ACTION_AUDIO_CONTENT_RECEIVER).apply {
|
||||
putExtra(Constants.EXTRA_AUDIO_CONTENT_PROGRESS, mediaPlayer.currentPosition)
|
||||
putExtra(Constants.EXTRA_AUDIO_CONTENT_ID, contentId)
|
||||
}
|
||||
sendBroadcast(intent)
|
||||
handler.postDelayed(this, 1000)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
when (intent?.action) {
|
||||
MusicAction.INIT.name -> {
|
||||
val contentId = intent.getLongExtra(Constants.EXTRA_AUDIO_CONTENT_ID, 0)
|
||||
if (this.contentId != null && this.contentId == contentId) {
|
||||
sendBroadcast(
|
||||
Intent(Constants.ACTION_AUDIO_CONTENT_RECEIVER)
|
||||
.apply {
|
||||
putExtra(
|
||||
Constants.EXTRA_AUDIO_CONTENT_CHANGE_UI,
|
||||
true
|
||||
)
|
||||
|
||||
putExtra(
|
||||
Constants.EXTRA_AUDIO_CONTENT_PLAYING,
|
||||
isPlaying
|
||||
)
|
||||
|
||||
putExtra(
|
||||
Constants.EXTRA_AUDIO_CONTENT_DURATION,
|
||||
mediaPlayer.duration
|
||||
)
|
||||
|
||||
putExtra(
|
||||
Constants.EXTRA_AUDIO_CONTENT_PROGRESS,
|
||||
mediaPlayer.currentPosition
|
||||
)
|
||||
|
||||
putExtra(
|
||||
Constants.EXTRA_AUDIO_CONTENT_ID,
|
||||
contentId
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if (
|
||||
this.contentId != null &&
|
||||
title != null &&
|
||||
nickname != null &&
|
||||
coverImageUrl != null
|
||||
) {
|
||||
sendBroadcast(
|
||||
Intent(Constants.ACTION_MAIN_AUDIO_CONTENT_RECEIVER)
|
||||
.apply {
|
||||
putExtra(
|
||||
Constants.EXTRA_AUDIO_CONTENT_SHOWING,
|
||||
true
|
||||
)
|
||||
|
||||
putExtra(
|
||||
Constants.EXTRA_AUDIO_CONTENT_PLAYING,
|
||||
isPlaying
|
||||
)
|
||||
|
||||
putExtra(
|
||||
Constants.EXTRA_AUDIO_CONTENT_TITLE,
|
||||
title
|
||||
)
|
||||
|
||||
putExtra(
|
||||
Constants.EXTRA_NICKNAME,
|
||||
nickname
|
||||
)
|
||||
|
||||
putExtra(
|
||||
Constants.EXTRA_AUDIO_CONTENT_COVER_IMAGE_URL,
|
||||
coverImageUrl
|
||||
)
|
||||
|
||||
putExtra(
|
||||
Constants.EXTRA_AUDIO_CONTENT_ID,
|
||||
this@AudioContentPlayService.contentId
|
||||
)
|
||||
}
|
||||
)
|
||||
} else {
|
||||
sendBroadcast(
|
||||
Intent(Constants.ACTION_MAIN_AUDIO_CONTENT_RECEIVER)
|
||||
.apply {
|
||||
putExtra(
|
||||
Constants.EXTRA_AUDIO_CONTENT_SHOWING,
|
||||
false
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
MusicAction.PLAY.name -> {
|
||||
if (!isPlaying) {
|
||||
mediaPlayer.start()
|
||||
toggleIsPlaying()
|
||||
updateNotification()
|
||||
}
|
||||
}
|
||||
|
||||
MusicAction.PAUSE.name -> {
|
||||
if (isPlaying) {
|
||||
mediaPlayer.pause()
|
||||
toggleIsPlaying()
|
||||
updateNotification()
|
||||
}
|
||||
}
|
||||
|
||||
MusicAction.STOP.name -> {
|
||||
if (this::mediaPlayer.isInitialized) {
|
||||
mediaPlayer.stop()
|
||||
setEndPositionPlaybackTracking(mediaPlayer.currentPosition)
|
||||
toggleIsPlaying(false)
|
||||
onStopService()
|
||||
}
|
||||
}
|
||||
|
||||
MusicAction.CONDITIONAL_STOP.name -> {
|
||||
val contentId = intent.getLongExtra(Constants.EXTRA_AUDIO_CONTENT_ID, 0)
|
||||
if (
|
||||
this.contentId != null &&
|
||||
this.contentId == contentId &&
|
||||
this::mediaPlayer.isInitialized
|
||||
) {
|
||||
mediaPlayer.stop()
|
||||
setEndPositionPlaybackTracking(mediaPlayer.currentPosition)
|
||||
toggleIsPlaying(false)
|
||||
onStopService()
|
||||
}
|
||||
}
|
||||
|
||||
MusicAction.PROGRESS.name -> {
|
||||
val progress = intent.getIntExtra(Constants.EXTRA_AUDIO_CONTENT_PROGRESS, 0)
|
||||
if (progress > 0) {
|
||||
if (contentId != null) saveNewPlaybackTracking(
|
||||
totalDuration = mediaPlayer.duration,
|
||||
progress = progress
|
||||
)
|
||||
mediaPlayer.seekTo(progress)
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
val contentId = intent?.getLongExtra(Constants.EXTRA_AUDIO_CONTENT_ID, 0)
|
||||
if (contentId != null && this.contentId == contentId) {
|
||||
if (isPlaying) {
|
||||
sendBroadcast(
|
||||
Intent(Constants.ACTION_AUDIO_CONTENT_RECEIVER)
|
||||
.apply {
|
||||
putExtra(
|
||||
Constants.EXTRA_AUDIO_CONTENT_NEXT_ACTION,
|
||||
MusicAction.PAUSE
|
||||
)
|
||||
|
||||
putExtra(
|
||||
Constants.EXTRA_AUDIO_CONTENT_ID,
|
||||
contentId
|
||||
)
|
||||
}
|
||||
)
|
||||
} else {
|
||||
sendBroadcast(
|
||||
Intent(Constants.ACTION_AUDIO_CONTENT_RECEIVER)
|
||||
.apply {
|
||||
putExtra(
|
||||
Constants.EXTRA_AUDIO_CONTENT_NEXT_ACTION,
|
||||
MusicAction.PLAY
|
||||
)
|
||||
|
||||
putExtra(
|
||||
Constants.EXTRA_AUDIO_CONTENT_ID,
|
||||
contentId
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
} else {
|
||||
url = intent?.getStringExtra(Constants.EXTRA_AUDIO_CONTENT_URL)
|
||||
title = intent?.getStringExtra(Constants.EXTRA_AUDIO_CONTENT_TITLE)
|
||||
isFree = intent?.getBooleanExtra(
|
||||
Constants.EXTRA_AUDIO_CONTENT_FREE,
|
||||
true
|
||||
)
|
||||
isPreview = intent?.getBooleanExtra(
|
||||
Constants.EXTRA_AUDIO_CONTENT_PREVIEW,
|
||||
true
|
||||
)
|
||||
nickname = intent?.getStringExtra(Constants.EXTRA_NICKNAME)
|
||||
coverImageUrl = intent?.getStringExtra(
|
||||
Constants.EXTRA_AUDIO_CONTENT_COVER_IMAGE_URL
|
||||
)
|
||||
creatorId = intent?.getLongExtra(Constants.EXTRA_AUDIO_CONTENT_CREATOR_ID, 0)
|
||||
this.contentId = contentId
|
||||
|
||||
if (url != null) {
|
||||
sendBroadcast(
|
||||
Intent(Constants.ACTION_AUDIO_CONTENT_RECEIVER)
|
||||
.apply {
|
||||
putExtra(
|
||||
Constants.EXTRA_AUDIO_CONTENT_LOADING,
|
||||
true
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
if (isPlaying) {
|
||||
mediaPlayer.stop()
|
||||
setEndPositionPlaybackTracking(mediaPlayer.currentPosition)
|
||||
|
||||
mediaPlayer.release()
|
||||
toggleIsPlaying()
|
||||
}
|
||||
|
||||
initMediaPlayer()
|
||||
mediaPlayer.setDataSource(url)
|
||||
mediaPlayer.prepareAsync()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return START_NOT_STICKY
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
if (this::mediaPlayer.isInitialized) {
|
||||
mediaPlayer.release()
|
||||
}
|
||||
|
||||
onStopService()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
override fun onBind(p0: Intent?): IBinder? {
|
||||
return null
|
||||
}
|
||||
|
||||
override fun onCompletion(mp: MediaPlayer?) {
|
||||
setEndPositionPlaybackTracking(mediaPlayer.currentPosition)
|
||||
|
||||
if (SharedPreferenceManager.isContentPlayLoop) {
|
||||
saveNewPlaybackTracking(totalDuration = mediaPlayer.duration, progress = 0)
|
||||
mediaPlayer.start()
|
||||
} else {
|
||||
toggleIsPlaying(false)
|
||||
mediaPlayer.release()
|
||||
onStopService()
|
||||
}
|
||||
}
|
||||
|
||||
private fun toggleIsPlaying(isPlaying: Boolean? = null) {
|
||||
this.isPlaying = isPlaying ?: !this.isPlaying
|
||||
if (this.isPlaying) {
|
||||
handler.postDelayed(changeMediaPlayerPositionRunnable, 1000)
|
||||
} else {
|
||||
handler.removeCallbacks(changeMediaPlayerPositionRunnable)
|
||||
}
|
||||
|
||||
sendBroadcast(
|
||||
Intent(Constants.ACTION_AUDIO_CONTENT_RECEIVER)
|
||||
.apply {
|
||||
putExtra(
|
||||
Constants.EXTRA_AUDIO_CONTENT_CHANGE_UI,
|
||||
true
|
||||
)
|
||||
|
||||
putExtra(
|
||||
Constants.EXTRA_AUDIO_CONTENT_PLAYING,
|
||||
this@AudioContentPlayService.isPlaying
|
||||
)
|
||||
|
||||
putExtra(
|
||||
Constants.EXTRA_AUDIO_CONTENT_ID,
|
||||
contentId
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
if (isPlaying != null && !isPlaying) {
|
||||
resetAudioData()
|
||||
}
|
||||
|
||||
sendBroadcast(
|
||||
Intent(Constants.ACTION_MAIN_AUDIO_CONTENT_RECEIVER)
|
||||
.apply {
|
||||
putExtra(
|
||||
Constants.EXTRA_AUDIO_CONTENT_PLAYING,
|
||||
this@AudioContentPlayService.isPlaying
|
||||
)
|
||||
|
||||
putExtra(
|
||||
Constants.EXTRA_AUDIO_CONTENT_SHOWING,
|
||||
contentId != null
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun resetAudioData() {
|
||||
url = null
|
||||
title = null
|
||||
nickname = null
|
||||
contentId = null
|
||||
}
|
||||
|
||||
private fun initMediaPlayer() {
|
||||
mediaPlayer = MediaPlayer()
|
||||
mediaPlayer.setOnPreparedListener(this)
|
||||
mediaPlayer.setOnCompletionListener(this)
|
||||
mediaPlayer.setAudioAttributes(
|
||||
AudioAttributes.Builder()
|
||||
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
|
||||
.build()
|
||||
)
|
||||
}
|
||||
|
||||
override fun onPrepared(mp: MediaPlayer?) {
|
||||
saveNewPlaybackTracking(totalDuration = mediaPlayer.duration, progress = 0)
|
||||
sendBroadcast(
|
||||
Intent(Constants.ACTION_AUDIO_CONTENT_RECEIVER)
|
||||
.apply {
|
||||
putExtra(
|
||||
Constants.EXTRA_AUDIO_CONTENT_NEXT_ACTION,
|
||||
MusicAction.PLAY
|
||||
)
|
||||
|
||||
putExtra(
|
||||
Constants.EXTRA_AUDIO_CONTENT_DURATION,
|
||||
mediaPlayer.duration
|
||||
)
|
||||
|
||||
putExtra(
|
||||
Constants.EXTRA_AUDIO_CONTENT_ID,
|
||||
contentId
|
||||
)
|
||||
|
||||
putExtra(
|
||||
Constants.EXTRA_AUDIO_CONTENT_ALERT_PREVIEW,
|
||||
true
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
sendBroadcast(
|
||||
Intent(Constants.ACTION_MAIN_AUDIO_CONTENT_RECEIVER)
|
||||
.apply {
|
||||
putExtra(
|
||||
Constants.EXTRA_AUDIO_CONTENT_PLAYING,
|
||||
false
|
||||
)
|
||||
|
||||
putExtra(
|
||||
Constants.EXTRA_AUDIO_CONTENT_SHOWING,
|
||||
true
|
||||
)
|
||||
|
||||
putExtra(
|
||||
Constants.EXTRA_AUDIO_CONTENT_TITLE,
|
||||
title
|
||||
)
|
||||
|
||||
putExtra(
|
||||
Constants.EXTRA_NICKNAME,
|
||||
nickname
|
||||
)
|
||||
|
||||
putExtra(
|
||||
Constants.EXTRA_AUDIO_CONTENT_COVER_IMAGE_URL,
|
||||
coverImageUrl
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun updateNotification() {
|
||||
val intent = Intent(this, MainActivity::class.java)
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
|
||||
val pendingIntent = PendingIntent.getActivity(
|
||||
this,
|
||||
0,
|
||||
intent,
|
||||
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_CANCEL_CURRENT
|
||||
)
|
||||
|
||||
val channelId = "audio_content_play_channel"
|
||||
val notificationManager =
|
||||
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val channel = NotificationChannel(
|
||||
channelId,
|
||||
"콘텐츠 알림 채널",
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
)
|
||||
notificationManager.createNotificationChannel(channel)
|
||||
}
|
||||
|
||||
val playPauseIcon =
|
||||
if (isPlaying) R.drawable.ic_noti_pause else R.drawable.ic_noti_play
|
||||
val playPauseAction =
|
||||
if (isPlaying) MusicAction.PAUSE.name else MusicAction.PLAY.name
|
||||
|
||||
Glide
|
||||
.with(this)
|
||||
.asBitmap()
|
||||
.load(coverImageUrl)
|
||||
.into(object : CustomTarget<Bitmap>() {
|
||||
override fun onResourceReady(resource: Bitmap, transition: Transition<in Bitmap>?) {
|
||||
val notificationBuilder = NotificationCompat
|
||||
.Builder(this@AudioContentPlayService, channelId)
|
||||
.setSmallIcon(R.drawable.ic_noti)
|
||||
.setLargeIcon(resource)
|
||||
.setContentTitle(title ?: "오디오 콘텐츠")
|
||||
.setContentText(nickname ?: "")
|
||||
.setContentIntent(pendingIntent)
|
||||
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||
.addAction(
|
||||
NotificationCompat
|
||||
.Action
|
||||
.Builder(
|
||||
playPauseIcon,
|
||||
"Play_or_Pause",
|
||||
getServiceIntent(playPauseAction)
|
||||
).build()
|
||||
)
|
||||
.addAction(
|
||||
NotificationCompat
|
||||
.Action
|
||||
.Builder(
|
||||
R.drawable.ic_noti_stop,
|
||||
"Stop",
|
||||
getServiceIntent(MusicAction.STOP.name)
|
||||
).build()
|
||||
)
|
||||
|
||||
notificationBuilder.setStyle(
|
||||
androidx.media.app.NotificationCompat.MediaStyle()
|
||||
.setShowActionsInCompactView(0, 1)
|
||||
)
|
||||
|
||||
startForeground(1, notificationBuilder.build())
|
||||
}
|
||||
|
||||
override fun onLoadCleared(placeholder: Drawable?) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun getServiceIntent(action: String): PendingIntent {
|
||||
val intent = Intent(this, AudioContentPlayService::class.java)
|
||||
intent.action = action
|
||||
return PendingIntent.getService(
|
||||
this,
|
||||
0,
|
||||
intent,
|
||||
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_CANCEL_CURRENT
|
||||
)
|
||||
}
|
||||
|
||||
private fun saveNewPlaybackTracking(totalDuration: Int, progress: Int) {
|
||||
if (creatorId != SharedPreferenceManager.userId) {
|
||||
playbackTrackingId = playbackTrackingRepository.savePlaybackTracking(
|
||||
PlaybackTracking(
|
||||
contentId = contentId!!,
|
||||
totalDuration = totalDuration,
|
||||
startPosition = progress,
|
||||
isFree = isFree!!,
|
||||
isPreview = isPreview!!
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setEndPositionPlaybackTracking(progress: Int) {
|
||||
if (creatorId != SharedPreferenceManager.userId && playbackTrackingId > 0) {
|
||||
val playbackTracking = playbackTrackingRepository
|
||||
.getPlaybackTracking(playbackTrackingId)
|
||||
|
||||
if (playbackTracking != null) {
|
||||
playbackTracking.endPosition = progress
|
||||
playbackTrackingRepository.savePlaybackTracking(playbackTracking)
|
||||
}
|
||||
|
||||
playbackTrackingId = 0
|
||||
}
|
||||
}
|
||||
|
||||
private fun onStopService() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
stopForeground(STOP_FOREGROUND_REMOVE)
|
||||
} else {
|
||||
stopForeground(true)
|
||||
}
|
||||
|
||||
stopSelf()
|
||||
}
|
||||
|
||||
enum class MusicAction {
|
||||
PLAY, PAUSE, STOP, PROGRESS, INIT, CONDITIONAL_STOP
|
||||
}
|
||||
}
|
|
@ -0,0 +1,138 @@
|
|||
package kr.co.vividnext.sodalive.audio_content
|
||||
|
||||
import kr.co.vividnext.sodalive.audio_content.detail.PutAudioContentLikeRequest
|
||||
import kr.co.vividnext.sodalive.audio_content.donation.AudioContentDonationRequest
|
||||
import kr.co.vividnext.sodalive.audio_content.order.OrderRequest
|
||||
import kr.co.vividnext.sodalive.audio_content.order.OrderType
|
||||
import kr.co.vividnext.sodalive.user.CreatorFollowRequestRequest
|
||||
import kr.co.vividnext.sodalive.user.UserApi
|
||||
import okhttp3.MultipartBody
|
||||
import okhttp3.RequestBody
|
||||
import java.util.TimeZone
|
||||
|
||||
class AudioContentRepository(
|
||||
private val api: AudioContentApi,
|
||||
private val userApi: UserApi
|
||||
) {
|
||||
fun getAudioContentList(
|
||||
id: Long,
|
||||
page: Int,
|
||||
size: Int,
|
||||
sort: AudioContentViewModel.Sort,
|
||||
token: String
|
||||
) = api.getAudioContentList(
|
||||
id = id,
|
||||
page = page - 1,
|
||||
size = size,
|
||||
sort = sort,
|
||||
authHeader = token
|
||||
)
|
||||
|
||||
fun getAudioContentThemeList(token: String) = api.getAudioContentThemeList(token)
|
||||
|
||||
fun uploadAudioContent(
|
||||
coverImage: MultipartBody.Part,
|
||||
contentFile: MultipartBody.Part,
|
||||
request: RequestBody,
|
||||
token: String
|
||||
) = api.uploadAudioContent(
|
||||
coverImage = coverImage,
|
||||
contentFile = contentFile,
|
||||
request = request,
|
||||
authHeader = token
|
||||
)
|
||||
|
||||
fun modifyAudioContent(
|
||||
coverImage: MultipartBody.Part? = null,
|
||||
request: RequestBody,
|
||||
token: String
|
||||
) = api.modifyAudioContent(
|
||||
coverImage = coverImage,
|
||||
request = request,
|
||||
authHeader = token
|
||||
)
|
||||
|
||||
fun deleteAudioContent(
|
||||
id: Long,
|
||||
token: String
|
||||
) = api.deleteAudioContent(
|
||||
id = id,
|
||||
authHeader = token
|
||||
)
|
||||
|
||||
fun getAudioContentDetail(audioContentId: Long, token: String) = api.getAudioContentDetail(
|
||||
id = audioContentId,
|
||||
timezone = TimeZone.getDefault().id,
|
||||
authHeader = token
|
||||
)
|
||||
|
||||
fun registerNotification(
|
||||
creatorId: Long,
|
||||
token: String
|
||||
) = userApi.creatorFollow(
|
||||
request = CreatorFollowRequestRequest(creatorId = creatorId),
|
||||
authHeader = token
|
||||
)
|
||||
|
||||
fun unRegisterNotification(
|
||||
creatorId: Long,
|
||||
token: String
|
||||
) = userApi.creatorUnFollow(
|
||||
request = CreatorFollowRequestRequest(creatorId = creatorId),
|
||||
authHeader = token
|
||||
)
|
||||
|
||||
fun orderContent(
|
||||
contentId: Long,
|
||||
orderType: OrderType,
|
||||
token: String
|
||||
) = api.orderAudioContent(
|
||||
request = OrderRequest(
|
||||
contentId = contentId,
|
||||
orderType = orderType,
|
||||
container = "aos"
|
||||
),
|
||||
authHeader = token
|
||||
)
|
||||
|
||||
fun getAudioContentOrderList(
|
||||
page: Int,
|
||||
size: Int,
|
||||
token: String
|
||||
) = api.getAudioContentOrderList(
|
||||
page = page - 1,
|
||||
size = size,
|
||||
authHeader = token
|
||||
)
|
||||
|
||||
fun addAllPlaybackTracking(
|
||||
request: AddAllPlaybackTrackingRequest,
|
||||
token: String
|
||||
) = api.addAllPlaybackTracking(request, authHeader = token)
|
||||
|
||||
fun likeContent(
|
||||
request: PutAudioContentLikeRequest,
|
||||
token: String
|
||||
) = api.likeContent(request, authHeader = token)
|
||||
|
||||
fun getMain(token: String) = api.getMain(authHeader = token)
|
||||
|
||||
fun getNewContentOfTheme(theme: String, token: String) = api.getNewContentOfTheme(
|
||||
theme = theme,
|
||||
authHeader = token
|
||||
)
|
||||
|
||||
fun donation(
|
||||
contentId: Long,
|
||||
can: Int,
|
||||
comment: String,
|
||||
token: String
|
||||
) = api.donation(
|
||||
request = AudioContentDonationRequest(
|
||||
contentId = contentId,
|
||||
donationCan = can,
|
||||
comment = comment
|
||||
),
|
||||
authHeader = token
|
||||
)
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
package kr.co.vividnext.sodalive.audio_content
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import com.orhanobut.logger.Logger
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import kr.co.vividnext.sodalive.base.BaseViewModel
|
||||
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
|
||||
import kr.co.vividnext.sodalive.explorer.profile.GetAudioContentListResponse
|
||||
|
||||
class AudioContentViewModel(private val repository: AudioContentRepository) : BaseViewModel() {
|
||||
|
||||
private val _toastLiveData = MutableLiveData<String?>()
|
||||
val toastLiveData: LiveData<String?>
|
||||
get() = _toastLiveData
|
||||
|
||||
private var _isLoading = MutableLiveData(false)
|
||||
val isLoading: LiveData<Boolean>
|
||||
get() = _isLoading
|
||||
|
||||
private var _audioContentListLiveData = MutableLiveData<GetAudioContentListResponse>()
|
||||
val audioContentListLiveData: LiveData<GetAudioContentListResponse>
|
||||
get() = _audioContentListLiveData
|
||||
|
||||
private val _sort = MutableLiveData(Sort.NEWEST)
|
||||
val sort: LiveData<Sort>
|
||||
get() = _sort
|
||||
|
||||
enum class Sort {
|
||||
@SerializedName("NEWEST")
|
||||
NEWEST,
|
||||
|
||||
@SerializedName("PRICE_HIGH")
|
||||
PRICE_HIGH,
|
||||
|
||||
@SerializedName("PRICE_LOW")
|
||||
PRICE_LOW
|
||||
}
|
||||
|
||||
private var isLast = false
|
||||
var page = 1
|
||||
private val size = 10
|
||||
|
||||
fun getAudioContentList(userId: Long, onFailure: (() -> Unit)? = null) {
|
||||
if (!_isLoading.value!! && !isLast) {
|
||||
_isLoading.value = true
|
||||
compositeDisposable.add(
|
||||
repository.getAudioContentList(
|
||||
id = userId,
|
||||
page = page,
|
||||
size = size,
|
||||
token = "Bearer ${SharedPreferenceManager.token}",
|
||||
sort = _sort.value!!
|
||||
)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{
|
||||
if (it.success && it.data != null) {
|
||||
if (it.data.items.isNotEmpty()) {
|
||||
page += 1
|
||||
_audioContentListLiveData.postValue(it.data!!)
|
||||
} else {
|
||||
isLast = true
|
||||
}
|
||||
} else {
|
||||
if (it.message != null) {
|
||||
_toastLiveData.postValue(it.message)
|
||||
} else {
|
||||
_toastLiveData.postValue(
|
||||
"알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||
)
|
||||
}
|
||||
|
||||
if (onFailure != null) {
|
||||
onFailure()
|
||||
}
|
||||
}
|
||||
|
||||
_isLoading.value = false
|
||||
},
|
||||
{
|
||||
_isLoading.value = false
|
||||
it.message?.let { message -> Logger.e(message) }
|
||||
_toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
|
||||
if (onFailure != null) {
|
||||
onFailure()
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun changeSort(sort: Sort) {
|
||||
page = 1
|
||||
isLast = false
|
||||
_sort.postValue(sort)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package kr.co.vividnext.sodalive.audio_content
|
||||
|
||||
import io.objectbox.annotation.Entity
|
||||
import io.objectbox.annotation.Id
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
|
||||
@Entity
|
||||
data class PlaybackTracking(
|
||||
@Id
|
||||
var id: Long = 0,
|
||||
var contentId: Long,
|
||||
var totalDuration: Int,
|
||||
var startPosition: Int,
|
||||
var isFree: Boolean,
|
||||
var isPreview: Boolean,
|
||||
var endPosition: Int? = null,
|
||||
var playDateTime: String = SimpleDateFormat(
|
||||
"yyyy-MM-dd HH:mm:ss",
|
||||
Locale.getDefault()
|
||||
).format(Date())
|
||||
)
|
|
@ -0,0 +1,29 @@
|
|||
package kr.co.vividnext.sodalive.audio_content
|
||||
|
||||
import kr.co.vividnext.sodalive.common.ObjectBox
|
||||
|
||||
class PlaybackTrackingRepository(private val objectBox: ObjectBox) {
|
||||
fun savePlaybackTracking(data: PlaybackTracking): Long {
|
||||
return objectBox.playbackTrackingBox.put(data)
|
||||
}
|
||||
|
||||
fun getPlaybackTracking(id: Long): PlaybackTracking? {
|
||||
val query = objectBox.playbackTrackingBox
|
||||
.query(PlaybackTracking_.id.equal(id))
|
||||
.build()
|
||||
|
||||
val playbackTracking = query.findFirst()
|
||||
query.close()
|
||||
return playbackTracking
|
||||
}
|
||||
|
||||
fun getAllPlaybackTracking(): List<PlaybackTracking> {
|
||||
return objectBox
|
||||
.playbackTrackingBox
|
||||
.all
|
||||
}
|
||||
|
||||
fun removeAllPlaybackTracking() {
|
||||
objectBox.playbackTrackingBox.removeAll()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.comment
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.LinearLayout
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import coil.load
|
||||
import coil.transform.CircleCropTransformation
|
||||
import kr.co.vividnext.sodalive.R
|
||||
import kr.co.vividnext.sodalive.databinding.ItemAudioContentCommentBinding
|
||||
import kr.co.vividnext.sodalive.extensions.dpToPx
|
||||
import kr.co.vividnext.sodalive.extensions.moneyFormat
|
||||
|
||||
class AudioContentCommentAdapter(
|
||||
private val onItemClick: (GetAudioContentCommentListItem) -> Unit
|
||||
) : RecyclerView.Adapter<AudioContentCommentAdapter.ViewHolder>() {
|
||||
|
||||
var items = mutableSetOf<GetAudioContentCommentListItem>()
|
||||
|
||||
inner class ViewHolder(
|
||||
private val binding: ItemAudioContentCommentBinding
|
||||
) : RecyclerView.ViewHolder(binding.root) {
|
||||
|
||||
fun bind(item: GetAudioContentCommentListItem) {
|
||||
binding.ivCommentProfile.load(item.profileUrl) {
|
||||
crossfade(true)
|
||||
placeholder(R.drawable.bg_placeholder)
|
||||
transformations(CircleCropTransformation())
|
||||
}
|
||||
|
||||
val tvCommentLayoutParams = binding.tvComment.layoutParams as LinearLayout.LayoutParams
|
||||
val coin = item.donationCoin
|
||||
if (coin > 0) {
|
||||
tvCommentLayoutParams.topMargin = 0
|
||||
binding.llDonationCoin.visibility = View.VISIBLE
|
||||
binding.tvDonationCoin.text = coin.moneyFormat()
|
||||
binding.llDonationCoin.setBackgroundResource(
|
||||
when {
|
||||
coin >= 100000 -> {
|
||||
R.drawable.bg_round_corner_10_7_973a3a
|
||||
}
|
||||
|
||||
coin >= 50000 -> {
|
||||
R.drawable.bg_round_corner_10_7_d85e37
|
||||
}
|
||||
|
||||
coin >= 10000 -> {
|
||||
R.drawable.bg_round_corner_10_7_d38c38
|
||||
}
|
||||
|
||||
coin >= 5000 -> {
|
||||
R.drawable.bg_round_corner_10_7_59548f
|
||||
}
|
||||
|
||||
coin >= 1000 -> {
|
||||
R.drawable.bg_round_corner_10_7_4d6aa4
|
||||
}
|
||||
|
||||
coin >= 500 -> {
|
||||
R.drawable.bg_round_corner_10_7_2d7390
|
||||
}
|
||||
|
||||
else -> {
|
||||
R.drawable.bg_round_corner_10_7_548f7d
|
||||
}
|
||||
}
|
||||
)
|
||||
} else {
|
||||
tvCommentLayoutParams.topMargin = 13.3f.dpToPx().toInt()
|
||||
binding.llDonationCoin.visibility = View.GONE
|
||||
}
|
||||
binding.tvComment.layoutParams = tvCommentLayoutParams
|
||||
|
||||
binding.tvComment.text = item.comment
|
||||
binding.tvCommentDate.text = item.date
|
||||
binding.tvCommentNickname.text = item.nickname
|
||||
|
||||
binding.tvWriteReply.text = if (item.replyCount > 0) {
|
||||
"답글 ${item.replyCount}개"
|
||||
} else {
|
||||
"답글 쓰기"
|
||||
}
|
||||
|
||||
binding.tvWriteReply.setOnClickListener { onItemClick(item) }
|
||||
binding.root.setOnClickListener { onItemClick(item) }
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder(
|
||||
ItemAudioContentCommentBinding.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
parent,
|
||||
false
|
||||
)
|
||||
)
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
holder.bind(items.toList()[position])
|
||||
}
|
||||
|
||||
override fun getItemCount() = items.size
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.comment
|
||||
|
||||
import android.app.Dialog
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.FrameLayout
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
import kr.co.vividnext.sodalive.R
|
||||
import kr.co.vividnext.sodalive.databinding.DialogAudioContentCommentBinding
|
||||
|
||||
class AudioContentCommentFragment(private val audioContentId: Long) : BottomSheetDialogFragment() {
|
||||
|
||||
private lateinit var binding: DialogAudioContentCommentBinding
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
val dialog = super.onCreateDialog(savedInstanceState)
|
||||
|
||||
dialog.setOnShowListener {
|
||||
val d = it as BottomSheetDialog
|
||||
val bottomSheet = d.findViewById<FrameLayout>(
|
||||
com.google.android.material.R.id.design_bottom_sheet
|
||||
)
|
||||
if (bottomSheet != null) {
|
||||
BottomSheetBehavior.from(bottomSheet).state = BottomSheetBehavior.STATE_EXPANDED
|
||||
}
|
||||
}
|
||||
|
||||
return dialog
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
binding = DialogAudioContentCommentBinding.inflate(inflater, container, false)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
val commentListFragmentTag = "COMMENT_LIST_FRAGMENT"
|
||||
val commentListFragment = AudioContentCommentListFragment.newInstance(
|
||||
audioContentId = audioContentId
|
||||
)
|
||||
val fragmentTransaction = childFragmentManager.beginTransaction()
|
||||
fragmentTransaction.add(R.id.fl_container, commentListFragment, commentListFragmentTag)
|
||||
fragmentTransaction.addToBackStack(commentListFragmentTag)
|
||||
fragmentTransaction.commit()
|
||||
}
|
||||
|
||||
fun hideCommentDialog() {
|
||||
dialog?.dismiss()
|
||||
}
|
||||
|
||||
fun onClickComment(comment: GetAudioContentCommentListItem) {
|
||||
val commentReplyFragmentTag = "COMMENT_REPLY_FRAGMENT"
|
||||
val commentReplyFragment = AudioContentCommentReplyFragment.newInstance(
|
||||
audioContentId = audioContentId,
|
||||
comment = comment
|
||||
)
|
||||
val fragmentTransaction = childFragmentManager.beginTransaction()
|
||||
fragmentTransaction.add(R.id.fl_container, commentReplyFragment, commentReplyFragmentTag)
|
||||
fragmentTransaction.addToBackStack(commentReplyFragmentTag)
|
||||
fragmentTransaction.commit()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,182 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.comment
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Service
|
||||
import android.graphics.Rect
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.Toast
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import coil.load
|
||||
import coil.transform.CircleCropTransformation
|
||||
import kr.co.vividnext.sodalive.R
|
||||
import kr.co.vividnext.sodalive.base.BaseFragment
|
||||
import kr.co.vividnext.sodalive.common.Constants
|
||||
import kr.co.vividnext.sodalive.common.LoadingDialog
|
||||
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
|
||||
import kr.co.vividnext.sodalive.databinding.FragmentAudioContentCommentListBinding
|
||||
import kr.co.vividnext.sodalive.extensions.dpToPx
|
||||
import org.koin.android.ext.android.inject
|
||||
|
||||
class AudioContentCommentListFragment : BaseFragment<FragmentAudioContentCommentListBinding>(
|
||||
FragmentAudioContentCommentListBinding::inflate
|
||||
) {
|
||||
|
||||
private val viewModel: AudioContentCommentListViewModel by inject()
|
||||
|
||||
private lateinit var imm: InputMethodManager
|
||||
private lateinit var loadingDialog: LoadingDialog
|
||||
private lateinit var adapter: AudioContentCommentAdapter
|
||||
|
||||
private var audioContentId: Long = 0
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
audioContentId = arguments?.getLong(Constants.EXTRA_AUDIO_CONTENT_ID) ?: 0
|
||||
return super.onCreateView(inflater, container, savedInstanceState)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
loadingDialog = LoadingDialog(requireActivity(), layoutInflater)
|
||||
imm = requireContext().getSystemService(
|
||||
Service.INPUT_METHOD_SERVICE
|
||||
) as InputMethodManager
|
||||
|
||||
setupView()
|
||||
bindData()
|
||||
viewModel.getCommentList(audioContentId = audioContentId) { hideDialog() }
|
||||
}
|
||||
|
||||
private fun hideDialog() {
|
||||
(parentFragment as AudioContentCommentFragment).hideCommentDialog()
|
||||
}
|
||||
|
||||
private fun setupView() {
|
||||
binding.ivClose.setOnClickListener { hideDialog() }
|
||||
|
||||
binding.ivCommentProfile.load(SharedPreferenceManager.profileImage) {
|
||||
crossfade(true)
|
||||
placeholder(R.drawable.ic_logo)
|
||||
transformations(CircleCropTransformation())
|
||||
}
|
||||
|
||||
binding.ivCommentSend.setOnClickListener {
|
||||
hideKeyboard()
|
||||
val comment = binding.etComment.text.toString()
|
||||
binding.etComment.setText("")
|
||||
viewModel.registerComment(audioContentId, comment)
|
||||
}
|
||||
|
||||
adapter = AudioContentCommentAdapter {
|
||||
(parentFragment as AudioContentCommentFragment).onClickComment(it)
|
||||
}
|
||||
|
||||
val recyclerView = binding.rvComment
|
||||
recyclerView.setHasFixedSize(true)
|
||||
recyclerView.layoutManager = LinearLayoutManager(
|
||||
activity,
|
||||
LinearLayoutManager.VERTICAL,
|
||||
false
|
||||
)
|
||||
recyclerView.addItemDecoration(object : RecyclerView.ItemDecoration() {
|
||||
override fun getItemOffsets(
|
||||
outRect: Rect,
|
||||
view: View,
|
||||
parent: RecyclerView,
|
||||
state: RecyclerView.State
|
||||
) {
|
||||
super.getItemOffsets(outRect, view, parent, state)
|
||||
|
||||
outRect.left = 13.3f.dpToPx().toInt()
|
||||
outRect.right = 13.3f.dpToPx().toInt()
|
||||
|
||||
when (parent.getChildAdapterPosition(view)) {
|
||||
0 -> {
|
||||
outRect.top = 13.3f.dpToPx().toInt()
|
||||
outRect.bottom = 6.7f.dpToPx().toInt()
|
||||
}
|
||||
|
||||
adapter.itemCount - 1 -> {
|
||||
outRect.top = 6.7f.dpToPx().toInt()
|
||||
outRect.bottom = 13.3f.dpToPx().toInt()
|
||||
}
|
||||
|
||||
else -> {
|
||||
outRect.top = 6.7f.dpToPx().toInt()
|
||||
outRect.bottom = 6.7f.dpToPx().toInt()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
||||
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||
super.onScrolled(recyclerView, dx, dy)
|
||||
|
||||
val lastVisibleItemPosition = (recyclerView.layoutManager as LinearLayoutManager?)!!
|
||||
.findLastCompletelyVisibleItemPosition()
|
||||
val itemTotalCount = recyclerView.adapter!!.itemCount - 1
|
||||
|
||||
// 스크롤이 끝에 도달했는지 확인
|
||||
if (!recyclerView.canScrollVertically(1) &&
|
||||
lastVisibleItemPosition == itemTotalCount
|
||||
) {
|
||||
viewModel.getCommentList(audioContentId = audioContentId)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
recyclerView.adapter = adapter
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
private fun bindData() {
|
||||
viewModel.isLoading.observe(viewLifecycleOwner) {
|
||||
if (it) {
|
||||
loadingDialog.show(screenWidth)
|
||||
} else {
|
||||
loadingDialog.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.toastLiveData.observe(viewLifecycleOwner) {
|
||||
it?.let { Toast.makeText(requireContext(), it, Toast.LENGTH_LONG).show() }
|
||||
}
|
||||
|
||||
viewModel.totalCommentCount.observe(viewLifecycleOwner) {
|
||||
binding.tvCommentCount.text = "$it"
|
||||
}
|
||||
|
||||
viewModel.commentList.observe(viewLifecycleOwner) {
|
||||
if (viewModel.page - 1 == 1) {
|
||||
adapter.items.clear()
|
||||
binding.rvComment.scrollToPosition(0)
|
||||
}
|
||||
|
||||
adapter.items.addAll(it)
|
||||
adapter.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
|
||||
private fun hideKeyboard() {
|
||||
imm.hideSoftInputFromWindow(view?.windowToken, 0)
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun newInstance(audioContentId: Long): AudioContentCommentListFragment {
|
||||
val args = Bundle()
|
||||
args.putLong(Constants.EXTRA_AUDIO_CONTENT_ID, audioContentId)
|
||||
|
||||
val fragment = AudioContentCommentListFragment()
|
||||
fragment.arguments = args
|
||||
return fragment
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,125 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.comment
|
||||
|
||||
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.base.BaseViewModel
|
||||
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
|
||||
|
||||
class AudioContentCommentListViewModel(
|
||||
private val repository: AudioContentCommentRepository
|
||||
) : BaseViewModel() {
|
||||
private val _toastLiveData = MutableLiveData<String?>()
|
||||
val toastLiveData: LiveData<String?>
|
||||
get() = _toastLiveData
|
||||
|
||||
private var _isLoading = MutableLiveData(false)
|
||||
val isLoading: LiveData<Boolean>
|
||||
get() = _isLoading
|
||||
|
||||
private var _commentList = MutableLiveData<List<GetAudioContentCommentListItem>>()
|
||||
val commentList: LiveData<List<GetAudioContentCommentListItem>>
|
||||
get() = _commentList
|
||||
|
||||
private var _totalCommentCount = MutableLiveData(0)
|
||||
val totalCommentCount: LiveData<Int>
|
||||
get() = _totalCommentCount
|
||||
|
||||
var page = 1
|
||||
private var isLast = false
|
||||
private val size = 10
|
||||
|
||||
fun getCommentList(audioContentId: Long, onFailure: (() -> Unit)? = null) {
|
||||
if (!_isLoading.value!! && !isLast) {
|
||||
_isLoading.value = true
|
||||
compositeDisposable.add(
|
||||
repository.getAudioContentCommentList(
|
||||
audioContentId = audioContentId,
|
||||
page = page,
|
||||
size = size,
|
||||
token = "Bearer ${SharedPreferenceManager.token}"
|
||||
)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{
|
||||
if (it.success && it.data != null) {
|
||||
_totalCommentCount.postValue(it.data.totalCount)
|
||||
|
||||
if (it.data.items.isNotEmpty()) {
|
||||
page += 1
|
||||
_commentList.postValue(it.data.items)
|
||||
} else {
|
||||
isLast = true
|
||||
}
|
||||
} else {
|
||||
if (it.message != null) {
|
||||
_toastLiveData.postValue(it.message)
|
||||
} else {
|
||||
_toastLiveData.postValue(
|
||||
"알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||
)
|
||||
}
|
||||
|
||||
if (onFailure != null) {
|
||||
onFailure()
|
||||
}
|
||||
}
|
||||
|
||||
_isLoading.value = false
|
||||
},
|
||||
{
|
||||
_isLoading.value = false
|
||||
it.message?.let { message -> Logger.e(message) }
|
||||
_toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
|
||||
if (onFailure != null) {
|
||||
onFailure()
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun registerComment(contentId: Long, comment: String) {
|
||||
if (!_isLoading.value!!) {
|
||||
_isLoading.value = true
|
||||
}
|
||||
|
||||
compositeDisposable.add(
|
||||
repository.registerComment(
|
||||
contentId = contentId,
|
||||
comment = comment,
|
||||
token = "Bearer ${SharedPreferenceManager.token}"
|
||||
)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{
|
||||
_isLoading.value = false
|
||||
|
||||
if (it.success) {
|
||||
page = 1
|
||||
isLast = false
|
||||
getCommentList(contentId)
|
||||
} else {
|
||||
if (it.message != null) {
|
||||
_toastLiveData.postValue(it.message)
|
||||
} else {
|
||||
_toastLiveData.postValue(
|
||||
"알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
_isLoading.value = false
|
||||
it.message?.let { message -> Logger.e(message) }
|
||||
_toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.comment
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.viewbinding.ViewBinding
|
||||
import coil.load
|
||||
import coil.transform.CircleCropTransformation
|
||||
import kr.co.vividnext.sodalive.R
|
||||
import kr.co.vividnext.sodalive.databinding.ItemAudioContentCommentBinding
|
||||
import kr.co.vividnext.sodalive.databinding.ItemAudioContentCommentReplyBinding
|
||||
|
||||
class AudioContentCommentReplyAdapter :
|
||||
RecyclerView.Adapter<AudioContentCommentReplyViewHolder>() {
|
||||
|
||||
var items = mutableSetOf<GetAudioContentCommentListItem>()
|
||||
|
||||
override fun onCreateViewHolder(
|
||||
parent: ViewGroup,
|
||||
viewType: Int
|
||||
): AudioContentCommentReplyViewHolder {
|
||||
return if (viewType == 0) {
|
||||
AudioContentCommentReplyHeaderViewHolder(
|
||||
ItemAudioContentCommentBinding.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
parent,
|
||||
false
|
||||
)
|
||||
)
|
||||
} else {
|
||||
AudioContentCommentReplyItemViewHolder(
|
||||
ItemAudioContentCommentReplyBinding.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
parent,
|
||||
false
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: AudioContentCommentReplyViewHolder, position: Int) {
|
||||
holder.bind(items.toList()[position])
|
||||
}
|
||||
|
||||
override fun getItemCount() = items.size
|
||||
|
||||
override fun getItemViewType(position: Int): Int {
|
||||
return position
|
||||
}
|
||||
}
|
||||
|
||||
abstract class AudioContentCommentReplyViewHolder(
|
||||
binding: ViewBinding
|
||||
) : RecyclerView.ViewHolder(binding.root) {
|
||||
abstract fun bind(item: GetAudioContentCommentListItem)
|
||||
}
|
||||
|
||||
class AudioContentCommentReplyHeaderViewHolder(
|
||||
private val binding: ItemAudioContentCommentBinding
|
||||
) : AudioContentCommentReplyViewHolder(binding) {
|
||||
|
||||
override fun bind(item: GetAudioContentCommentListItem) {
|
||||
binding.ivCommentProfile.load(item.profileUrl) {
|
||||
crossfade(true)
|
||||
placeholder(R.drawable.ic_logo)
|
||||
transformations(CircleCropTransformation())
|
||||
}
|
||||
|
||||
binding.tvComment.text = item.comment
|
||||
binding.tvCommentDate.text = item.date
|
||||
binding.tvCommentNickname.text = item.nickname
|
||||
|
||||
binding.tvWriteReply.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
class AudioContentCommentReplyItemViewHolder(
|
||||
private val binding: ItemAudioContentCommentReplyBinding
|
||||
) : AudioContentCommentReplyViewHolder(binding) {
|
||||
|
||||
override fun bind(item: GetAudioContentCommentListItem) {
|
||||
binding.ivCommentProfile.load(item.profileUrl) {
|
||||
crossfade(true)
|
||||
placeholder(R.drawable.ic_logo)
|
||||
transformations(CircleCropTransformation())
|
||||
}
|
||||
|
||||
binding.tvComment.text = item.comment
|
||||
binding.tvCommentDate.text = item.date
|
||||
binding.tvCommentNickname.text = item.nickname
|
||||
}
|
||||
}
|
|
@ -0,0 +1,204 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.comment
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Service
|
||||
import android.graphics.Rect
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.Toast
|
||||
import androidx.core.os.BundleCompat
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import coil.load
|
||||
import coil.transform.CircleCropTransformation
|
||||
import kr.co.vividnext.sodalive.R
|
||||
import kr.co.vividnext.sodalive.base.BaseFragment
|
||||
import kr.co.vividnext.sodalive.common.Constants
|
||||
import kr.co.vividnext.sodalive.common.LoadingDialog
|
||||
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
|
||||
import kr.co.vividnext.sodalive.databinding.FragmentAudioContentCommentReplyBinding
|
||||
import kr.co.vividnext.sodalive.extensions.dpToPx
|
||||
import org.koin.android.ext.android.inject
|
||||
|
||||
class AudioContentCommentReplyFragment : BaseFragment<FragmentAudioContentCommentReplyBinding>(
|
||||
FragmentAudioContentCommentReplyBinding::inflate
|
||||
) {
|
||||
|
||||
private val viewModel: AudioContentCommentReplyViewModel by inject()
|
||||
|
||||
private lateinit var imm: InputMethodManager
|
||||
private lateinit var loadingDialog: LoadingDialog
|
||||
private lateinit var adapter: AudioContentCommentReplyAdapter
|
||||
|
||||
private var originalComment: GetAudioContentCommentListItem? = null
|
||||
private var audioContentId: Long = 0
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
audioContentId = arguments?.getLong(Constants.EXTRA_AUDIO_CONTENT_ID) ?: 0
|
||||
originalComment = BundleCompat.getParcelable(
|
||||
requireArguments(),
|
||||
Constants.EXTRA_AUDIO_CONTENT_COMMENT,
|
||||
GetAudioContentCommentListItem::class.java
|
||||
)
|
||||
return super.onCreateView(inflater, container, savedInstanceState)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
if (originalComment == null) {
|
||||
parentFragmentManager.popBackStack()
|
||||
}
|
||||
|
||||
loadingDialog = LoadingDialog(requireActivity(), layoutInflater)
|
||||
imm = requireContext().getSystemService(
|
||||
Service.INPUT_METHOD_SERVICE
|
||||
) as InputMethodManager
|
||||
|
||||
setupView()
|
||||
bindData()
|
||||
viewModel.getCommentReplyList(commentId = originalComment!!.id) {
|
||||
parentFragmentManager.popBackStack()
|
||||
}
|
||||
}
|
||||
|
||||
private fun hideDialog() {
|
||||
(parentFragment as AudioContentCommentFragment).hideCommentDialog()
|
||||
}
|
||||
|
||||
private fun setupView() {
|
||||
binding.root.setOnClickListener { }
|
||||
|
||||
binding.tvBack.setOnClickListener {
|
||||
parentFragmentManager.popBackStack()
|
||||
}
|
||||
|
||||
binding.ivClose.setOnClickListener { hideDialog() }
|
||||
|
||||
binding.ivCommentProfile.load(SharedPreferenceManager.profileImage) {
|
||||
crossfade(true)
|
||||
placeholder(R.drawable.ic_logo)
|
||||
transformations(CircleCropTransformation())
|
||||
}
|
||||
|
||||
binding.ivCommentSend.setOnClickListener {
|
||||
hideKeyboard()
|
||||
val comment = binding.etComment.text.toString()
|
||||
binding.etComment.setText("")
|
||||
viewModel.registerComment(audioContentId, originalComment!!.id, comment)
|
||||
}
|
||||
|
||||
adapter = AudioContentCommentReplyAdapter().apply {
|
||||
items.add(originalComment!!)
|
||||
}
|
||||
|
||||
val recyclerView = binding.rvCommentReply
|
||||
recyclerView.setHasFixedSize(true)
|
||||
recyclerView.layoutManager = LinearLayoutManager(
|
||||
activity,
|
||||
LinearLayoutManager.VERTICAL,
|
||||
false
|
||||
)
|
||||
|
||||
recyclerView.addItemDecoration(object : RecyclerView.ItemDecoration() {
|
||||
override fun getItemOffsets(
|
||||
outRect: Rect,
|
||||
view: View,
|
||||
parent: RecyclerView,
|
||||
state: RecyclerView.State
|
||||
) {
|
||||
super.getItemOffsets(outRect, view, parent, state)
|
||||
|
||||
outRect.left = 13.3f.dpToPx().toInt()
|
||||
outRect.right = 13.3f.dpToPx().toInt()
|
||||
|
||||
when (parent.getChildAdapterPosition(view)) {
|
||||
0 -> {
|
||||
outRect.top = 13.3f.dpToPx().toInt()
|
||||
outRect.bottom = 12f.dpToPx().toInt()
|
||||
}
|
||||
|
||||
adapter.itemCount - 1 -> {
|
||||
outRect.top = 12f.dpToPx().toInt()
|
||||
outRect.bottom = 13.3f.dpToPx().toInt()
|
||||
}
|
||||
|
||||
else -> {
|
||||
outRect.top = 12f.dpToPx().toInt()
|
||||
outRect.bottom = 12f.dpToPx().toInt()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
||||
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||
super.onScrolled(recyclerView, dx, dy)
|
||||
|
||||
val lastVisibleItemPosition = (recyclerView.layoutManager as LinearLayoutManager?)!!
|
||||
.findLastCompletelyVisibleItemPosition()
|
||||
val itemTotalCount = recyclerView.adapter!!.itemCount - 1
|
||||
|
||||
// 스크롤이 끝에 도달했는지 확인
|
||||
if (!recyclerView.canScrollVertically(1) &&
|
||||
lastVisibleItemPosition == itemTotalCount
|
||||
) {
|
||||
viewModel.getCommentReplyList(originalComment!!.id)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
recyclerView.adapter = adapter
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
private fun bindData() {
|
||||
viewModel.isLoading.observe(viewLifecycleOwner) {
|
||||
if (it) {
|
||||
loadingDialog.show(screenWidth)
|
||||
} else {
|
||||
loadingDialog.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.toastLiveData.observe(viewLifecycleOwner) {
|
||||
it?.let { Toast.makeText(requireContext(), it, Toast.LENGTH_LONG).show() }
|
||||
}
|
||||
|
||||
viewModel.commentList.observe(viewLifecycleOwner) {
|
||||
if (viewModel.page - 1 == 1) {
|
||||
adapter.items.clear()
|
||||
binding.rvCommentReply.scrollToPosition(0)
|
||||
adapter.items.add(originalComment!!)
|
||||
}
|
||||
|
||||
adapter.items.addAll(it)
|
||||
adapter.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
|
||||
private fun hideKeyboard() {
|
||||
imm.hideSoftInputFromWindow(view?.windowToken, 0)
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun newInstance(
|
||||
audioContentId: Long,
|
||||
comment: GetAudioContentCommentListItem
|
||||
): AudioContentCommentReplyFragment {
|
||||
val args = Bundle()
|
||||
args.putLong(Constants.EXTRA_AUDIO_CONTENT_ID, audioContentId)
|
||||
args.putParcelable(Constants.EXTRA_AUDIO_CONTENT_COMMENT, comment)
|
||||
|
||||
val fragment = AudioContentCommentReplyFragment()
|
||||
fragment.arguments = args
|
||||
return fragment
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,120 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.comment
|
||||
|
||||
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.base.BaseViewModel
|
||||
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
|
||||
|
||||
class AudioContentCommentReplyViewModel(
|
||||
private val repository: AudioContentCommentRepository
|
||||
) : BaseViewModel() {
|
||||
private val _toastLiveData = MutableLiveData<String?>()
|
||||
val toastLiveData: LiveData<String?>
|
||||
get() = _toastLiveData
|
||||
|
||||
private var _isLoading = MutableLiveData(false)
|
||||
val isLoading: LiveData<Boolean>
|
||||
get() = _isLoading
|
||||
|
||||
private var _commentList = MutableLiveData<List<GetAudioContentCommentListItem>>()
|
||||
val commentList: LiveData<List<GetAudioContentCommentListItem>>
|
||||
get() = _commentList
|
||||
|
||||
var page = 1
|
||||
private var isLast = false
|
||||
private val size = 10
|
||||
|
||||
fun getCommentReplyList(commentId: Long, onFailure: (() -> Unit)? = null) {
|
||||
if (!_isLoading.value!! && !isLast) {
|
||||
_isLoading.value = true
|
||||
compositeDisposable.add(
|
||||
repository.getAudioContentCommentReplyList(
|
||||
commentId = commentId,
|
||||
page = page,
|
||||
size = size,
|
||||
token = "Bearer ${SharedPreferenceManager.token}"
|
||||
)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{
|
||||
if (it.success && it.data != null) {
|
||||
if (it.data.items.isNotEmpty()) {
|
||||
page += 1
|
||||
_commentList.postValue(it.data.items)
|
||||
} else {
|
||||
isLast = true
|
||||
}
|
||||
} else {
|
||||
if (it.message != null) {
|
||||
_toastLiveData.postValue(it.message)
|
||||
} else {
|
||||
_toastLiveData.postValue(
|
||||
"알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||
)
|
||||
}
|
||||
|
||||
if (onFailure != null) {
|
||||
onFailure()
|
||||
}
|
||||
}
|
||||
|
||||
_isLoading.value = false
|
||||
},
|
||||
{
|
||||
_isLoading.value = false
|
||||
it.message?.let { message -> Logger.e(message) }
|
||||
_toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
|
||||
if (onFailure != null) {
|
||||
onFailure()
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun registerComment(contentId: Long, commentId: Long, comment: String) {
|
||||
if (!_isLoading.value!!) {
|
||||
_isLoading.value = true
|
||||
}
|
||||
|
||||
compositeDisposable.add(
|
||||
repository.registerComment(
|
||||
contentId = contentId,
|
||||
comment = comment,
|
||||
parentId = commentId,
|
||||
token = "Bearer ${SharedPreferenceManager.token}"
|
||||
)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{
|
||||
_isLoading.value = false
|
||||
|
||||
if (it.success) {
|
||||
page = 1
|
||||
isLast = false
|
||||
getCommentReplyList(commentId)
|
||||
} else {
|
||||
if (it.message != null) {
|
||||
_toastLiveData.postValue(it.message)
|
||||
} else {
|
||||
_toastLiveData.postValue(
|
||||
"알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
_isLoading.value = false
|
||||
it.message?.let { message -> Logger.e(message) }
|
||||
_toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.comment
|
||||
|
||||
import kr.co.vividnext.sodalive.audio_content.AudioContentApi
|
||||
import java.util.TimeZone
|
||||
|
||||
class AudioContentCommentRepository(private val api: AudioContentApi) {
|
||||
fun registerComment(
|
||||
contentId: Long,
|
||||
comment: String,
|
||||
parentId: Long? = null,
|
||||
token: String
|
||||
) = api.registerComment(
|
||||
request = RegisterAudioContentCommentRequest(
|
||||
comment = comment,
|
||||
contentId = contentId,
|
||||
parentId = parentId
|
||||
),
|
||||
authHeader = token
|
||||
)
|
||||
|
||||
fun getAudioContentCommentList(
|
||||
audioContentId: Long,
|
||||
page: Int,
|
||||
size: Int,
|
||||
token: String
|
||||
) = api.getAudioContentCommentList(
|
||||
id = audioContentId,
|
||||
page = page - 1,
|
||||
size = size,
|
||||
timezone = TimeZone.getDefault().id,
|
||||
authHeader = token
|
||||
)
|
||||
|
||||
fun getAudioContentCommentReplyList(
|
||||
commentId: Long,
|
||||
page: Int,
|
||||
size: Int,
|
||||
token: String
|
||||
) = api.getAudioContentCommentCommentList(
|
||||
id = commentId,
|
||||
page = page - 1,
|
||||
size = size,
|
||||
timezone = TimeZone.getDefault().id,
|
||||
authHeader = token
|
||||
)
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.comment
|
||||
|
||||
import android.os.Parcelable
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
data class GetAudioContentCommentListResponse(
|
||||
@SerializedName("totalCount") val totalCount: Int,
|
||||
@SerializedName("items") val items: List<GetAudioContentCommentListItem>
|
||||
)
|
||||
|
||||
@Parcelize
|
||||
data class GetAudioContentCommentListItem(
|
||||
@SerializedName("id") val id: Long,
|
||||
@SerializedName("nickname") val nickname: String,
|
||||
@SerializedName("profileUrl") val profileUrl: String,
|
||||
@SerializedName("comment") val comment: String,
|
||||
@SerializedName("donationCoin") val donationCoin: Int,
|
||||
@SerializedName("date") val date: String,
|
||||
@SerializedName("replyCount") val replyCount: Int
|
||||
) : Parcelable
|
|
@ -0,0 +1,9 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.comment
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class RegisterAudioContentCommentRequest(
|
||||
@SerializedName("comment") val comment: String,
|
||||
@SerializedName("contentId") val contentId: Long,
|
||||
@SerializedName("parentId") val parentId: Long?
|
||||
)
|
|
@ -0,0 +1,67 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.detail
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.view.LayoutInflater
|
||||
import android.view.WindowManager
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import kr.co.vividnext.sodalive.databinding.DialogAudioContentDeleteBinding
|
||||
import kr.co.vividnext.sodalive.extensions.dpToPx
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
class AudioContentDeleteDialog(
|
||||
activity: Activity,
|
||||
layoutInflater: LayoutInflater,
|
||||
title: String,
|
||||
confirmButtonClick: () -> Unit
|
||||
) {
|
||||
|
||||
private val alertDialog: AlertDialog
|
||||
|
||||
val dialogView = DialogAudioContentDeleteBinding.inflate(layoutInflater)
|
||||
|
||||
init {
|
||||
val dialogBuilder = AlertDialog.Builder(activity)
|
||||
dialogBuilder.setView(dialogView.root)
|
||||
|
||||
alertDialog = dialogBuilder.create()
|
||||
alertDialog.setCancelable(false)
|
||||
alertDialog.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
|
||||
|
||||
dialogView.tvTitle.text = "[$title]을 삭제하시겠습니까?"
|
||||
dialogView.tvCancel.setOnClickListener {
|
||||
alertDialog.dismiss()
|
||||
}
|
||||
|
||||
dialogView.tvConfirm.setOnClickListener {
|
||||
if (dialogView.tvNotice.isSelected) {
|
||||
alertDialog.dismiss()
|
||||
confirmButtonClick()
|
||||
} else {
|
||||
Toast.makeText(
|
||||
activity,
|
||||
"동의하셔야 삭제할 수 있습니다.",
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
}
|
||||
}
|
||||
|
||||
dialogView.tvNotice.setOnClickListener {
|
||||
it.isSelected = !it.isSelected
|
||||
}
|
||||
}
|
||||
|
||||
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?.attributes = lp
|
||||
}
|
||||
}
|
|
@ -0,0 +1,788 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.detail
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.graphics.Rect
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.widget.RelativeLayout
|
||||
import android.widget.SeekBar
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.widget.PopupMenu
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import coil.load
|
||||
import coil.transform.CircleCropTransformation
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.request.RequestOptions
|
||||
import com.google.gson.Gson
|
||||
import kr.co.vividnext.sodalive.R
|
||||
import kr.co.vividnext.sodalive.audio_content.AudioContentPlayService
|
||||
import kr.co.vividnext.sodalive.audio_content.comment.AudioContentCommentFragment
|
||||
import kr.co.vividnext.sodalive.audio_content.modify.AudioContentModifyActivity
|
||||
import kr.co.vividnext.sodalive.audio_content.order.AudioContentOrderConfirmDialog
|
||||
import kr.co.vividnext.sodalive.audio_content.order.AudioContentOrderFragment
|
||||
import kr.co.vividnext.sodalive.audio_content.order.OrderType
|
||||
import kr.co.vividnext.sodalive.base.BaseActivity
|
||||
import kr.co.vividnext.sodalive.common.Constants
|
||||
import kr.co.vividnext.sodalive.common.LoadingDialog
|
||||
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
|
||||
import kr.co.vividnext.sodalive.common.Utils
|
||||
import kr.co.vividnext.sodalive.databinding.ActivityAudioContentDetailBinding
|
||||
import kr.co.vividnext.sodalive.explorer.profile.UserProfileActivity
|
||||
import kr.co.vividnext.sodalive.extensions.dpToPx
|
||||
import kr.co.vividnext.sodalive.live.room.donation.LiveRoomDonationDialog
|
||||
import kr.co.vividnext.sodalive.mypage.auth.Auth
|
||||
import kr.co.vividnext.sodalive.mypage.auth.AuthVerifyRequest
|
||||
import kr.co.vividnext.sodalive.mypage.auth.BootpayResponse
|
||||
import kr.co.vividnext.sodalive.report.ReportType
|
||||
import org.koin.android.ext.android.inject
|
||||
|
||||
class AudioContentDetailActivity : BaseActivity<ActivityAudioContentDetailBinding>(
|
||||
ActivityAudioContentDetailBinding::inflate
|
||||
) {
|
||||
private val viewModel: AudioContentDetailViewModel by inject()
|
||||
|
||||
private lateinit var loadingDialog: LoadingDialog
|
||||
private lateinit var creatorOtherContentAdapter: OtherContentAdapter
|
||||
private lateinit var sameThemeOtherContentAdapter: OtherContentAdapter
|
||||
|
||||
private var audioContentId: Long = 0
|
||||
private var isAlertPreview = false
|
||||
private val audioContentReceiver = AudioContentReceiver()
|
||||
|
||||
private var refresh = false
|
||||
set(value) {
|
||||
field = value
|
||||
setResult(RESULT_OK)
|
||||
}
|
||||
private var title = ""
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun onNewIntent(intent: Intent) {
|
||||
super.onNewIntent(intent)
|
||||
|
||||
binding.scrollView.scrollTo(0, 0)
|
||||
binding.sbProgress.progress = 0
|
||||
binding.ivPlayOrPause.setImageResource(R.drawable.btn_audio_content_play)
|
||||
binding.tvTotalDuration.text = " / 00:00:00"
|
||||
binding.tvCurrentDuration.text = "00:00:00"
|
||||
binding.rlPreviewAlert.visibility = View.GONE
|
||||
|
||||
audioContentId = intent.getLongExtra(Constants.EXTRA_AUDIO_CONTENT_ID, 0)
|
||||
viewModel.getAudioContentDetail(audioContentId = audioContentId) { finish() }
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
audioContentId = intent.getLongExtra(Constants.EXTRA_AUDIO_CONTENT_ID, 0)
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
if (audioContentId <= 0) {
|
||||
Toast.makeText(applicationContext, "잘못된 요청입니다.", Toast.LENGTH_LONG).show()
|
||||
finish()
|
||||
}
|
||||
|
||||
bindData()
|
||||
viewModel.getAudioContentDetail(audioContentId = audioContentId) { finish() }
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
val intentFilter = IntentFilter(Constants.ACTION_AUDIO_CONTENT_RECEIVER)
|
||||
registerReceiver(audioContentReceiver, intentFilter)
|
||||
|
||||
if (refresh) {
|
||||
viewModel.getAudioContentDetail(audioContentId = audioContentId) { finish() }
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
unregisterReceiver(audioContentReceiver)
|
||||
}
|
||||
|
||||
override fun setupView() {
|
||||
loadingDialog = LoadingDialog(this, layoutInflater)
|
||||
binding.tvBack.text = "콘텐츠 상세"
|
||||
binding.tvBack.setOnClickListener { finish() }
|
||||
binding.ivClosePreviewAlert.setOnClickListener { viewModel.toggleShowPreviewAlert() }
|
||||
binding.ivMenu.setOnClickListener {
|
||||
showOptionMenu(
|
||||
this,
|
||||
binding.ivMenu,
|
||||
)
|
||||
}
|
||||
|
||||
creatorOtherContentAdapter = OtherContentAdapter {
|
||||
val intent = Intent(applicationContext, AudioContentDetailActivity::class.java)
|
||||
.apply {
|
||||
putExtra(Constants.EXTRA_AUDIO_CONTENT_ID, it)
|
||||
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||
addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
|
||||
}
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
binding.rvCreatorOtherContent.layoutManager = LinearLayoutManager(
|
||||
applicationContext,
|
||||
LinearLayoutManager.HORIZONTAL,
|
||||
false
|
||||
)
|
||||
|
||||
binding.swipeRefreshLayout.setOnRefreshListener {
|
||||
viewModel.getAudioContentDetail(
|
||||
audioContentId = audioContentId
|
||||
) { finish() }
|
||||
binding.swipeRefreshLayout.isRefreshing = false
|
||||
}
|
||||
|
||||
binding.rvCreatorOtherContent.addItemDecoration(object : RecyclerView.ItemDecoration() {
|
||||
override fun getItemOffsets(
|
||||
outRect: Rect,
|
||||
view: View,
|
||||
parent: RecyclerView,
|
||||
state: RecyclerView.State
|
||||
) {
|
||||
super.getItemOffsets(outRect, view, parent, state)
|
||||
|
||||
when (parent.getChildAdapterPosition(view)) {
|
||||
0 -> {
|
||||
outRect.left = 0
|
||||
outRect.right = 6.7f.dpToPx().toInt()
|
||||
}
|
||||
|
||||
creatorOtherContentAdapter.itemCount - 1 -> {
|
||||
outRect.left = 6.7f.dpToPx().toInt()
|
||||
outRect.right = 0
|
||||
}
|
||||
|
||||
else -> {
|
||||
outRect.left = 6.7f.dpToPx().toInt()
|
||||
outRect.right = 6.7f.dpToPx().toInt()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
binding.rvCreatorOtherContent.adapter = creatorOtherContentAdapter
|
||||
|
||||
sameThemeOtherContentAdapter = OtherContentAdapter {
|
||||
val intent = Intent(applicationContext, AudioContentDetailActivity::class.java)
|
||||
.apply {
|
||||
putExtra(Constants.EXTRA_AUDIO_CONTENT_ID, it)
|
||||
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||
addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
|
||||
}
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
binding.rvThemeOtherContent.layoutManager = LinearLayoutManager(
|
||||
applicationContext,
|
||||
LinearLayoutManager.HORIZONTAL,
|
||||
false
|
||||
)
|
||||
|
||||
binding.rvThemeOtherContent.addItemDecoration(object : RecyclerView.ItemDecoration() {
|
||||
override fun getItemOffsets(
|
||||
outRect: Rect,
|
||||
view: View,
|
||||
parent: RecyclerView,
|
||||
state: RecyclerView.State
|
||||
) {
|
||||
super.getItemOffsets(outRect, view, parent, state)
|
||||
|
||||
when (parent.getChildAdapterPosition(view)) {
|
||||
0 -> {
|
||||
outRect.left = 0
|
||||
outRect.right = 6.7f.dpToPx().toInt()
|
||||
}
|
||||
|
||||
creatorOtherContentAdapter.itemCount - 1 -> {
|
||||
outRect.left = 6.7f.dpToPx().toInt()
|
||||
outRect.right = 0
|
||||
}
|
||||
|
||||
else -> {
|
||||
outRect.left = 6.7f.dpToPx().toInt()
|
||||
outRect.right = 6.7f.dpToPx().toInt()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
binding.rvThemeOtherContent.adapter = sameThemeOtherContentAdapter
|
||||
|
||||
binding.sbProgress.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
|
||||
override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
|
||||
}
|
||||
|
||||
override fun onStartTrackingTouch(p0: SeekBar?) {
|
||||
}
|
||||
|
||||
override fun onStopTrackingTouch(seekBar: SeekBar?) {
|
||||
if (seekBar != null) {
|
||||
val intent = Intent(
|
||||
this@AudioContentDetailActivity,
|
||||
AudioContentPlayService::class.java
|
||||
)
|
||||
intent.action = AudioContentPlayService.MusicAction.PROGRESS.name
|
||||
intent.putExtra(Constants.EXTRA_AUDIO_CONTENT_PROGRESS, seekBar.progress)
|
||||
startService(intent)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
val layoutParams = binding.ivCover.layoutParams as RelativeLayout.LayoutParams
|
||||
layoutParams.width = (screenWidth - 13.3f.dpToPx()).toInt()
|
||||
layoutParams.height = (screenWidth - 13.3f.dpToPx()).toInt()
|
||||
binding.ivCover.layoutParams = layoutParams
|
||||
binding.ivPlayLoop.setOnClickListener { viewModel.togglePlayLoop() }
|
||||
binding.llDonation.setOnClickListener {
|
||||
val dialog = LiveRoomDonationDialog(
|
||||
this,
|
||||
LayoutInflater.from(this)
|
||||
) { can, message ->
|
||||
if (can <= 0) {
|
||||
showToast("1코인 이상 후원하실 수 있습니다.")
|
||||
} else if (message.isBlank()) {
|
||||
showToast("함께 보낼 메시지를 입력하세요.")
|
||||
} else {
|
||||
donation(can, message)
|
||||
}
|
||||
}
|
||||
|
||||
dialog.show(screenWidth)
|
||||
}
|
||||
}
|
||||
|
||||
private fun donation(coin: Int, message: String) {
|
||||
viewModel.donation(audioContentId, coin, message) {
|
||||
viewModel.getAudioContentDetail(audioContentId = audioContentId) { finish() }
|
||||
}
|
||||
}
|
||||
|
||||
private fun showOptionMenu(context: Context, v: View) {
|
||||
val popup = PopupMenu(context, v)
|
||||
val inflater = popup.menuInflater
|
||||
|
||||
if (
|
||||
viewModel.audioContentLiveData.value!!.creator.creatorId ==
|
||||
SharedPreferenceManager.userId
|
||||
) {
|
||||
inflater.inflate(R.menu.audio_content_detail_creator_menu, popup.menu)
|
||||
|
||||
popup.setOnMenuItemClickListener {
|
||||
when (it.itemId) {
|
||||
R.id.menu_modify -> {
|
||||
refresh = true
|
||||
startActivity(
|
||||
Intent(applicationContext, AudioContentModifyActivity::class.java)
|
||||
.apply {
|
||||
putExtra(Constants.EXTRA_AUDIO_CONTENT_ID, audioContentId)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
R.id.menu_delete -> {
|
||||
showDeleteDialog()
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
} else {
|
||||
inflater.inflate(R.menu.audio_content_detail_user_menu, popup.menu)
|
||||
|
||||
popup.setOnMenuItemClickListener {
|
||||
when (it.itemId) {
|
||||
R.id.menu_report -> {
|
||||
showReportDialog()
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
popup.show()
|
||||
}
|
||||
|
||||
private fun showDeleteDialog() {
|
||||
AudioContentDeleteDialog(
|
||||
this,
|
||||
layoutInflater,
|
||||
this.title,
|
||||
confirmButtonClick = {
|
||||
viewModel.deleteAudioContent(audioContentId) {
|
||||
setResult(RESULT_OK)
|
||||
finish()
|
||||
}
|
||||
}
|
||||
).show(screenWidth)
|
||||
}
|
||||
|
||||
private fun showReportDialog() {
|
||||
AudioContentReportDialog(this, layoutInflater) {
|
||||
viewModel.report(
|
||||
type = ReportType.AUDIO_CONTENT,
|
||||
contentId = audioContentId,
|
||||
reason = it
|
||||
)
|
||||
}.show(screenWidth)
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged", "SetTextI18n")
|
||||
private fun bindData() {
|
||||
viewModel.toastLiveData.observe(this) {
|
||||
it?.let { Toast.makeText(applicationContext, it, Toast.LENGTH_LONG).show() }
|
||||
}
|
||||
|
||||
viewModel.isLoading.observe(this) {
|
||||
if (it) {
|
||||
loadingDialog.show(screenWidth, "")
|
||||
} else {
|
||||
loadingDialog.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.isExpandDetail.observe(this) {
|
||||
binding.tvDetail.maxLines = if (it) {
|
||||
Int.MAX_VALUE
|
||||
} else {
|
||||
2
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.isShowPreviewAlert.observe(this) {
|
||||
binding.rlPreviewAlert.visibility = if (it) {
|
||||
View.VISIBLE
|
||||
} else {
|
||||
View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.audioContentLiveData.observe(this) {
|
||||
refresh = false
|
||||
startService(
|
||||
Intent(this, AudioContentPlayService::class.java).apply {
|
||||
action = AudioContentPlayService.MusicAction.INIT.name
|
||||
putExtra(Constants.EXTRA_AUDIO_CONTENT_ID, it.contentId)
|
||||
}
|
||||
)
|
||||
|
||||
title = it.title
|
||||
setupCreatorArea(it.creator)
|
||||
setupMosaicArea(it.isMosaic)
|
||||
setupPlayArea(it)
|
||||
setupInfoArea(it)
|
||||
setupPurchaseButton(it)
|
||||
setupCommentArea(it)
|
||||
setupCreatorOtherContentListArea(it.creatorOtherContentList)
|
||||
setupSameThemeOtherContentList(it.sameThemeOtherContentList)
|
||||
|
||||
isAlertPreview = it.creator.creatorId != SharedPreferenceManager.userId &&
|
||||
!it.existOrdered &&
|
||||
it.price > 0
|
||||
}
|
||||
|
||||
viewModel.isContentPlayLoopLiveData.observe(this) {
|
||||
if (it) {
|
||||
binding.ivPlayLoop.setImageResource(R.drawable.btn_player_repeat)
|
||||
} else {
|
||||
binding.ivPlayLoop.setImageResource(R.drawable.btn_player_repeat_done)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
private fun setupSameThemeOtherContentList(
|
||||
sameThemeOtherContentList: List<OtherContentResponse>
|
||||
) {
|
||||
if (sameThemeOtherContentList.isEmpty()) {
|
||||
binding.rvThemeOtherContent.visibility = View.GONE
|
||||
binding.llThemeOtherContentPreparing.visibility = View.VISIBLE
|
||||
} else {
|
||||
binding.rvThemeOtherContent.visibility = View.VISIBLE
|
||||
binding.llThemeOtherContentPreparing.visibility = View.GONE
|
||||
|
||||
sameThemeOtherContentAdapter.items.clear()
|
||||
sameThemeOtherContentAdapter.items.addAll(sameThemeOtherContentList)
|
||||
sameThemeOtherContentAdapter.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
private fun setupCreatorOtherContentListArea(
|
||||
creatorOtherContentList: List<OtherContentResponse>
|
||||
) {
|
||||
if (creatorOtherContentList.isEmpty()) {
|
||||
binding.rvCreatorOtherContent.visibility = View.GONE
|
||||
binding.llCreatorOtherContentPreparing.visibility = View.VISIBLE
|
||||
} else {
|
||||
binding.rvCreatorOtherContent.visibility = View.VISIBLE
|
||||
binding.llCreatorOtherContentPreparing.visibility = View.GONE
|
||||
|
||||
creatorOtherContentAdapter.items.clear()
|
||||
creatorOtherContentAdapter.items.addAll(creatorOtherContentList)
|
||||
creatorOtherContentAdapter.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupCommentArea(response: GetAudioContentDetailResponse) {
|
||||
if (response.isCommentAvailable) {
|
||||
binding.llDonation.visibility = View.VISIBLE
|
||||
binding.llComment.visibility = View.VISIBLE
|
||||
binding.tvCommentCount.text = "${response.commentCount}"
|
||||
|
||||
if (response.commentCount > 0) {
|
||||
binding.ivCommentProfile.load(response.commentList[0].profileUrl) {
|
||||
crossfade(true)
|
||||
placeholder(R.drawable.bg_placeholder)
|
||||
transformations(CircleCropTransformation())
|
||||
}
|
||||
binding.tvCommentText.text = response.commentList[0].comment
|
||||
binding.tvCommentText.visibility = View.VISIBLE
|
||||
binding.rlInputComment.visibility = View.GONE
|
||||
|
||||
binding.llComment.setOnClickListener { showCommentBottomSheetDialog() }
|
||||
} else {
|
||||
binding.tvCommentText.visibility = View.GONE
|
||||
binding.rlInputComment.visibility = View.VISIBLE
|
||||
binding.ivCommentProfile.load(SharedPreferenceManager.profileImage) {
|
||||
crossfade(true)
|
||||
placeholder(R.drawable.bg_placeholder)
|
||||
transformations(CircleCropTransformation())
|
||||
}
|
||||
|
||||
binding.ivCommentSend.setOnClickListener {
|
||||
val comment = binding.etComment.text.toString()
|
||||
binding.etComment.setText("")
|
||||
viewModel.registerComment(audioContentId, comment)
|
||||
}
|
||||
|
||||
binding.llComment.setOnClickListener {}
|
||||
}
|
||||
} else {
|
||||
binding.llComment.visibility = View.GONE
|
||||
binding.llDonation.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
private fun showCommentBottomSheetDialog() {
|
||||
val dialog = AudioContentCommentFragment(audioContentId = audioContentId)
|
||||
dialog.show(
|
||||
supportFragmentManager,
|
||||
dialog.tag
|
||||
)
|
||||
}
|
||||
|
||||
private fun setupPurchaseButton(response: GetAudioContentDetailResponse) {
|
||||
if (
|
||||
response.price > 0 &&
|
||||
!response.existOrdered &&
|
||||
response.orderType == null &&
|
||||
response.creator.creatorId != SharedPreferenceManager.userId
|
||||
) {
|
||||
binding.llPurchase.visibility = View.VISIBLE
|
||||
binding.tvPrice.text = response.price.toString()
|
||||
|
||||
binding.llPurchase.setOnClickListener {
|
||||
showOrderDialog(audioContent = response)
|
||||
}
|
||||
} else {
|
||||
binding.llPurchase.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
private fun setupPlayArea(response: GetAudioContentDetailResponse) {
|
||||
Glide
|
||||
.with(this)
|
||||
.load(response.coverImageUrl)
|
||||
.centerCrop()
|
||||
.placeholder(R.drawable.bg_black)
|
||||
.apply(RequestOptions().override((screenWidth - 13.3f.dpToPx()).toInt()))
|
||||
.into(binding.ivCover)
|
||||
|
||||
binding.ivPlayOrPause.setOnClickListener {
|
||||
startService(
|
||||
Intent(this, AudioContentPlayService::class.java).apply {
|
||||
putExtra(Constants.EXTRA_AUDIO_CONTENT_COVER_IMAGE_URL, response.coverImageUrl)
|
||||
putExtra(Constants.EXTRA_AUDIO_CONTENT_URL, response.contentUrl)
|
||||
putExtra(Constants.EXTRA_NICKNAME, response.creator.nickname)
|
||||
putExtra(Constants.EXTRA_AUDIO_CONTENT_TITLE, response.title)
|
||||
putExtra(Constants.EXTRA_AUDIO_CONTENT_ID, response.contentId)
|
||||
putExtra(Constants.EXTRA_AUDIO_CONTENT_CREATOR_ID, response.creator.creatorId)
|
||||
putExtra(Constants.EXTRA_AUDIO_CONTENT_FREE, response.price <= 0)
|
||||
putExtra(
|
||||
Constants.EXTRA_AUDIO_CONTENT_PREVIEW,
|
||||
!response.existOrdered && response.price > 0
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
binding.tvTotalDuration.text = " / ${response.duration}"
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
private fun setupInfoArea(response: GetAudioContentDetailResponse) {
|
||||
binding.tvTheme.text = response.themeStr
|
||||
binding.tv19.visibility = if (response.isAdult) {
|
||||
View.VISIBLE
|
||||
} else {
|
||||
View.GONE
|
||||
}
|
||||
|
||||
if (response.orderType != null && response.orderType == OrderType.KEEP) {
|
||||
binding.tvPurchased.visibility = View.VISIBLE
|
||||
binding.tvRental.visibility = View.GONE
|
||||
binding.tvRemainingTime.visibility = View.GONE
|
||||
} else if (response.orderType != null && response.orderType == OrderType.RENTAL) {
|
||||
binding.tvPurchased.visibility = View.GONE
|
||||
binding.tvRental.visibility = View.VISIBLE
|
||||
binding.tvRemainingTime.visibility = View.VISIBLE
|
||||
binding.tvRemainingTime.text = response.remainingTime
|
||||
} else {
|
||||
binding.tvPurchased.visibility = View.GONE
|
||||
binding.tvRental.visibility = View.GONE
|
||||
binding.tvRemainingTime.visibility = View.GONE
|
||||
}
|
||||
|
||||
binding.tvTitle.text = response.title
|
||||
binding.tvDetail.text = response.detail
|
||||
binding.tvDetail.setOnClickListener { viewModel.toggleExpandDetail() }
|
||||
|
||||
if (response.tag.isNotBlank()) {
|
||||
binding.tvTag.visibility = View.VISIBLE
|
||||
binding.tvTag.text = response.tag
|
||||
} else {
|
||||
binding.tvTag.visibility = View.GONE
|
||||
}
|
||||
|
||||
binding.ivLike.setImageResource(
|
||||
if (response.isLike) {
|
||||
R.drawable.ic_audio_content_heart_pressed
|
||||
} else {
|
||||
R.drawable.ic_audio_content_heart_normal
|
||||
}
|
||||
)
|
||||
|
||||
binding.tvLike.text = "${response.likeCount}"
|
||||
binding.llLike.setOnClickListener {
|
||||
viewModel.likeContent(contentId = audioContentId) {
|
||||
val likeCount = binding.tvLike.text.toString().toInt()
|
||||
if (it) {
|
||||
binding.tvLike.text = "${likeCount + 1}"
|
||||
binding.ivLike.setImageResource(R.drawable.ic_audio_content_heart_pressed)
|
||||
} else {
|
||||
binding.tvLike.text = if (likeCount - 1 < 0) {
|
||||
"0"
|
||||
} else {
|
||||
"${likeCount - 1}"
|
||||
}
|
||||
binding.ivLike.setImageResource(R.drawable.ic_audio_content_heart_normal)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
binding.tvShare.setOnClickListener {
|
||||
viewModel.shareAudioContent(
|
||||
audioContentId = audioContentId,
|
||||
contentImage = response.coverImageUrl,
|
||||
contentTitle = "${response.title} - ${response.creator.nickname}"
|
||||
) {
|
||||
val intent = Intent(Intent.ACTION_SEND)
|
||||
intent.type = "text/plain"
|
||||
intent.putExtra(Intent.EXTRA_TEXT, it)
|
||||
|
||||
val shareIntent = Intent.createChooser(intent, "오디오콘텐츠 공유")
|
||||
startActivity(shareIntent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupMosaicArea(isMosaic: Boolean) {
|
||||
if (isMosaic) {
|
||||
binding.alert19Bg.visibility = View.VISIBLE
|
||||
binding.llAlert19.visibility = View.VISIBLE
|
||||
|
||||
binding.tvAuth.setOnClickListener {
|
||||
Auth.auth(this, applicationContext) { data ->
|
||||
val bootpayResponse = Gson().fromJson(data, BootpayResponse::class.java)
|
||||
val request = AuthVerifyRequest(receiptId = bootpayResponse.data.receiptId)
|
||||
runOnUiThread {
|
||||
viewModel.authVerify(audioContentId = audioContentId, request = request)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
binding.alert19Bg.visibility = View.GONE
|
||||
binding.llAlert19.visibility = View.GONE
|
||||
binding.tvAuth.setOnClickListener {}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupCreatorArea(creator: AudioContentCreator) {
|
||||
binding.rlProfile.setOnClickListener {
|
||||
startActivity(
|
||||
Intent(applicationContext, UserProfileActivity::class.java).apply {
|
||||
putExtra(Constants.EXTRA_USER_ID, creator.creatorId)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
binding.ivProfile.load(creator.profileImageUrl) {
|
||||
crossfade(true)
|
||||
placeholder(R.drawable.bg_placeholder)
|
||||
transformations(CircleCropTransformation())
|
||||
}
|
||||
|
||||
binding.tvProfileNickname.text = creator.nickname
|
||||
|
||||
if (creator.creatorId != SharedPreferenceManager.userId) {
|
||||
binding.ivFollow.visibility = View.VISIBLE
|
||||
|
||||
if (creator.isFollowing) {
|
||||
binding.ivFollow.setImageResource(R.drawable.btn_notification_selected)
|
||||
binding.ivFollow.setOnClickListener {
|
||||
viewModel.unRegisterNotification(
|
||||
contentId = audioContentId,
|
||||
creatorId = creator.creatorId
|
||||
)
|
||||
}
|
||||
} else {
|
||||
binding.ivFollow.setImageResource(R.drawable.btn_notification)
|
||||
binding.ivFollow.setOnClickListener {
|
||||
viewModel.registerNotification(
|
||||
contentId = audioContentId,
|
||||
creatorId = creator.creatorId
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
binding.ivFollow.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
private fun showOrderDialog(audioContent: GetAudioContentDetailResponse) {
|
||||
val dialog = AudioContentOrderFragment(
|
||||
price = audioContent.price,
|
||||
onClickKeep = { showOrderConfirmDialog(audioContent, OrderType.KEEP) },
|
||||
onClickRental = { showOrderConfirmDialog(audioContent, OrderType.RENTAL) }
|
||||
)
|
||||
|
||||
dialog.show(
|
||||
supportFragmentManager,
|
||||
dialog.tag
|
||||
)
|
||||
}
|
||||
|
||||
private fun showOrderConfirmDialog(
|
||||
audioContent: GetAudioContentDetailResponse,
|
||||
orderType: OrderType
|
||||
) {
|
||||
AudioContentOrderConfirmDialog(
|
||||
activity = this,
|
||||
layoutInflater = layoutInflater,
|
||||
title = audioContent.title,
|
||||
theme = audioContent.themeStr,
|
||||
coverImageUrl = audioContent.coverImageUrl,
|
||||
isAdult = audioContent.isAdult,
|
||||
profileImageUrl = audioContent.creator.profileImageUrl,
|
||||
nickname = audioContent.creator.nickname,
|
||||
duration = audioContent.duration,
|
||||
orderType = orderType,
|
||||
price = audioContent.price,
|
||||
confirmButtonClick = {
|
||||
startService(
|
||||
Intent(this, AudioContentPlayService::class.java).apply {
|
||||
action = AudioContentPlayService.MusicAction.CONDITIONAL_STOP.name
|
||||
putExtra(Constants.EXTRA_AUDIO_CONTENT_ID, audioContent.contentId)
|
||||
}
|
||||
)
|
||||
|
||||
binding.rlPreviewAlert.visibility = View.GONE
|
||||
|
||||
viewModel.order(
|
||||
contentId = audioContent.contentId,
|
||||
orderType = orderType
|
||||
)
|
||||
},
|
||||
).show(screenWidth)
|
||||
}
|
||||
|
||||
inner class AudioContentReceiver : BroadcastReceiver() {
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
val nextAction = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
intent?.getSerializableExtra(
|
||||
Constants.EXTRA_AUDIO_CONTENT_NEXT_ACTION,
|
||||
AudioContentPlayService.MusicAction::class.java
|
||||
)
|
||||
} else {
|
||||
intent?.getSerializableExtra(Constants.EXTRA_AUDIO_CONTENT_NEXT_ACTION)
|
||||
}
|
||||
|
||||
val duration = intent?.getIntExtra(Constants.EXTRA_AUDIO_CONTENT_DURATION, 0)
|
||||
val progress = intent?.getIntExtra(Constants.EXTRA_AUDIO_CONTENT_PROGRESS, 0)
|
||||
val contentId = intent?.getLongExtra(Constants.EXTRA_AUDIO_CONTENT_ID, 0)
|
||||
val isPlaying = intent?.getBooleanExtra(Constants.EXTRA_AUDIO_CONTENT_PLAYING, false)
|
||||
val changeUi = intent?.getBooleanExtra(Constants.EXTRA_AUDIO_CONTENT_CHANGE_UI, false)
|
||||
val alertPreview = intent?.getBooleanExtra(
|
||||
Constants.EXTRA_AUDIO_CONTENT_ALERT_PREVIEW,
|
||||
false
|
||||
)
|
||||
val isLoading = intent?.getBooleanExtra(
|
||||
Constants.EXTRA_AUDIO_CONTENT_LOADING,
|
||||
false
|
||||
)
|
||||
|
||||
viewModel.isLoading.value = isLoading ?: false
|
||||
|
||||
if (this@AudioContentDetailActivity.audioContentId == contentId) {
|
||||
runOnUiThread {
|
||||
if (changeUi != null && changeUi) {
|
||||
binding.ivPlayOrPause.setImageResource(
|
||||
if (isPlaying != null && isPlaying) {
|
||||
R.drawable.btn_audio_content_pause
|
||||
} else {
|
||||
R.drawable.btn_audio_content_play
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (duration != null && duration > 0) {
|
||||
binding.sbProgress.max = duration
|
||||
binding.tvTotalDuration.text = " / ${Utils.convertDurationToString(duration)}"
|
||||
}
|
||||
|
||||
if (progress != null && progress > 0) {
|
||||
binding.sbProgress.progress = progress
|
||||
binding.tvCurrentDuration.text = Utils.convertDurationToString(progress)
|
||||
}
|
||||
|
||||
if (alertPreview != null && alertPreview && isAlertPreview) {
|
||||
viewModel.toggleShowPreviewAlert()
|
||||
}
|
||||
|
||||
if (nextAction != null) {
|
||||
startService(
|
||||
Intent(
|
||||
this@AudioContentDetailActivity,
|
||||
AudioContentPlayService::class.java
|
||||
).apply {
|
||||
action = (nextAction as AudioContentPlayService.MusicAction).name
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,475 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.detail
|
||||
|
||||
import android.net.Uri
|
||||
import androidx.core.net.toUri
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import com.google.firebase.dynamiclinks.ShortDynamicLink
|
||||
import com.google.firebase.dynamiclinks.ktx.androidParameters
|
||||
import com.google.firebase.dynamiclinks.ktx.dynamicLinks
|
||||
import com.google.firebase.dynamiclinks.ktx.iosParameters
|
||||
import com.google.firebase.dynamiclinks.ktx.shortLinkAsync
|
||||
import com.google.firebase.dynamiclinks.ktx.socialMetaTagParameters
|
||||
import com.google.firebase.ktx.Firebase
|
||||
import com.orhanobut.logger.Logger
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import kr.co.vividnext.sodalive.audio_content.AudioContentRepository
|
||||
import kr.co.vividnext.sodalive.audio_content.comment.AudioContentCommentRepository
|
||||
import kr.co.vividnext.sodalive.audio_content.order.OrderType
|
||||
import kr.co.vividnext.sodalive.base.BaseViewModel
|
||||
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
|
||||
import kr.co.vividnext.sodalive.extensions.moneyFormat
|
||||
import kr.co.vividnext.sodalive.mypage.auth.AuthRepository
|
||||
import kr.co.vividnext.sodalive.mypage.auth.AuthVerifyRequest
|
||||
import kr.co.vividnext.sodalive.report.ReportRepository
|
||||
import kr.co.vividnext.sodalive.report.ReportRequest
|
||||
import kr.co.vividnext.sodalive.report.ReportType
|
||||
|
||||
class AudioContentDetailViewModel(
|
||||
private val repository: AudioContentRepository,
|
||||
private val authRepository: AuthRepository,
|
||||
private val reportRepository: ReportRepository,
|
||||
private val commentRepository: AudioContentCommentRepository
|
||||
) : BaseViewModel() {
|
||||
|
||||
private val _toastLiveData = MutableLiveData<String?>()
|
||||
val toastLiveData: LiveData<String?>
|
||||
get() = _toastLiveData
|
||||
|
||||
var isLoading = MutableLiveData(false)
|
||||
private set
|
||||
|
||||
private var _audioContentLiveData = MutableLiveData<GetAudioContentDetailResponse>()
|
||||
val audioContentLiveData: LiveData<GetAudioContentDetailResponse>
|
||||
get() = _audioContentLiveData
|
||||
|
||||
private val _isExpandDetail = MutableLiveData(false)
|
||||
val isExpandDetail: LiveData<Boolean>
|
||||
get() = _isExpandDetail
|
||||
|
||||
private val _isShowPreviewAlert = MutableLiveData(false)
|
||||
val isShowPreviewAlert: LiveData<Boolean>
|
||||
get() = _isShowPreviewAlert
|
||||
|
||||
private val _isContentPlayLoopLiveData = MutableLiveData(
|
||||
SharedPreferenceManager.isContentPlayLoop
|
||||
)
|
||||
val isContentPlayLoopLiveData: LiveData<Boolean>
|
||||
get() = _isContentPlayLoopLiveData
|
||||
|
||||
fun getAudioContentDetail(audioContentId: Long, onFailure: (() -> Unit)? = null) {
|
||||
if (!isLoading.value!!) {
|
||||
isLoading.value = true
|
||||
}
|
||||
|
||||
compositeDisposable.add(
|
||||
repository.getAudioContentDetail(
|
||||
audioContentId = audioContentId,
|
||||
token = "Bearer ${SharedPreferenceManager.token}"
|
||||
)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{
|
||||
if (it.success && it.data != null) {
|
||||
_audioContentLiveData.postValue(it.data!!)
|
||||
} else {
|
||||
if (it.message != null) {
|
||||
_toastLiveData.postValue(it.message)
|
||||
} else {
|
||||
_toastLiveData.postValue(
|
||||
"알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||
)
|
||||
}
|
||||
|
||||
if (onFailure != null) {
|
||||
onFailure()
|
||||
}
|
||||
}
|
||||
|
||||
isLoading.value = false
|
||||
},
|
||||
{
|
||||
isLoading.value = false
|
||||
it.message?.let { message -> Logger.e(message) }
|
||||
_toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
|
||||
if (onFailure != null) {
|
||||
onFailure()
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun registerNotification(contentId: Long, creatorId: Long) {
|
||||
isLoading.value = true
|
||||
compositeDisposable.add(
|
||||
repository.registerNotification(
|
||||
creatorId,
|
||||
"Bearer ${SharedPreferenceManager.token}"
|
||||
)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{
|
||||
if (it.success && it.data != null) {
|
||||
getAudioContentDetail(contentId)
|
||||
} else {
|
||||
if (it.message != null) {
|
||||
_toastLiveData.postValue(it.message)
|
||||
} else {
|
||||
_toastLiveData.postValue(
|
||||
"알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||
)
|
||||
}
|
||||
}
|
||||
isLoading.value = false
|
||||
},
|
||||
{
|
||||
isLoading.value = false
|
||||
it.message?.let { message -> Logger.e(message) }
|
||||
_toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun unRegisterNotification(contentId: Long, creatorId: Long) {
|
||||
isLoading.value = true
|
||||
compositeDisposable.add(
|
||||
repository.unRegisterNotification(
|
||||
creatorId,
|
||||
"Bearer ${SharedPreferenceManager.token}"
|
||||
)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{
|
||||
if (it.success && it.data != null) {
|
||||
getAudioContentDetail(contentId)
|
||||
} else {
|
||||
if (it.message != null) {
|
||||
_toastLiveData.postValue(it.message)
|
||||
} else {
|
||||
_toastLiveData.postValue(
|
||||
"알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||
)
|
||||
}
|
||||
}
|
||||
isLoading.value = false
|
||||
},
|
||||
{
|
||||
isLoading.value = false
|
||||
it.message?.let { message -> Logger.e(message) }
|
||||
_toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun toggleExpandDetail() {
|
||||
_isExpandDetail.value = !_isExpandDetail.value!!
|
||||
}
|
||||
|
||||
fun toggleShowPreviewAlert() {
|
||||
_isShowPreviewAlert.value = !_isShowPreviewAlert.value!!
|
||||
}
|
||||
|
||||
fun order(contentId: Long, orderType: OrderType) {
|
||||
isLoading.value = true
|
||||
compositeDisposable.add(
|
||||
repository.orderContent(
|
||||
contentId = contentId,
|
||||
orderType = orderType,
|
||||
token = "Bearer ${SharedPreferenceManager.token}"
|
||||
)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{
|
||||
if (it.success && it.data != null) {
|
||||
getAudioContentDetail(audioContentId = contentId)
|
||||
_toastLiveData.postValue("구매가 완료되었습니다.")
|
||||
} else {
|
||||
if (it.message != null) {
|
||||
_toastLiveData.postValue(it.message)
|
||||
} else {
|
||||
_toastLiveData.postValue(
|
||||
"알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||
)
|
||||
}
|
||||
}
|
||||
isLoading.value = false
|
||||
},
|
||||
{
|
||||
isLoading.value = false
|
||||
it.message?.let { message -> Logger.e(message) }
|
||||
_toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun authVerify(audioContentId: Long, request: AuthVerifyRequest) {
|
||||
if (!isLoading.value!!) {
|
||||
isLoading.value = true
|
||||
}
|
||||
|
||||
compositeDisposable.add(
|
||||
authRepository.verify(request, "Bearer ${SharedPreferenceManager.token}")
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{
|
||||
if (it.success) {
|
||||
getAudioContentDetail(audioContentId)
|
||||
} else {
|
||||
if (it.message != null) {
|
||||
_toastLiveData.postValue(it.message)
|
||||
} else {
|
||||
_toastLiveData.postValue(
|
||||
"알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||
)
|
||||
}
|
||||
|
||||
isLoading.value = false
|
||||
}
|
||||
},
|
||||
{
|
||||
isLoading.value = false
|
||||
it.message?.let { message -> Logger.e(message) }
|
||||
_toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun registerComment(audioContentId: Long, comment: String) {
|
||||
if (!isLoading.value!!) {
|
||||
isLoading.value = true
|
||||
}
|
||||
|
||||
compositeDisposable.add(
|
||||
commentRepository.registerComment(
|
||||
contentId = audioContentId,
|
||||
comment = comment,
|
||||
token = "Bearer ${SharedPreferenceManager.token}"
|
||||
)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{
|
||||
if (it.success) {
|
||||
getAudioContentDetail(audioContentId)
|
||||
} else {
|
||||
if (it.message != null) {
|
||||
_toastLiveData.postValue(it.message)
|
||||
} else {
|
||||
_toastLiveData.postValue(
|
||||
"알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||
)
|
||||
}
|
||||
|
||||
isLoading.value = false
|
||||
}
|
||||
},
|
||||
{
|
||||
isLoading.value = false
|
||||
it.message?.let { message -> Logger.e(message) }
|
||||
_toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun likeContent(contentId: Long, onSuccess: (Boolean) -> Unit) {
|
||||
if (!isLoading.value!!) {
|
||||
isLoading.value = true
|
||||
}
|
||||
|
||||
compositeDisposable.add(
|
||||
repository.likeContent(
|
||||
request = PutAudioContentLikeRequest(contentId = contentId),
|
||||
token = "Bearer ${SharedPreferenceManager.token}"
|
||||
)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{
|
||||
isLoading.value = false
|
||||
|
||||
if (it.success) {
|
||||
if (it.data != null) {
|
||||
onSuccess(it.data.like)
|
||||
} else {
|
||||
getAudioContentDetail(contentId)
|
||||
}
|
||||
} else {
|
||||
if (it.message != null) {
|
||||
_toastLiveData.postValue(it.message)
|
||||
} else {
|
||||
_toastLiveData.postValue(
|
||||
"알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
isLoading.value = false
|
||||
it.message?.let { message -> Logger.e(message) }
|
||||
_toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun shareAudioContent(
|
||||
audioContentId: Long,
|
||||
contentImage: String,
|
||||
contentTitle: String,
|
||||
onSuccess: (String) -> Unit
|
||||
) {
|
||||
isLoading.value = true
|
||||
Firebase.dynamicLinks.shortLinkAsync(ShortDynamicLink.Suffix.SHORT) {
|
||||
link = Uri.parse("https://yozm.day/?audio_content_id=$audioContentId")
|
||||
domainUriPrefix = "https://yozm.page.link"
|
||||
androidParameters { }
|
||||
iosParameters("kr.co.vividnext.yozm") {
|
||||
appStoreId = "1630284226"
|
||||
}
|
||||
socialMetaTagParameters {
|
||||
title = contentTitle
|
||||
description = "지금 요즘라이브에서 이 콘텐츠 감상하기"
|
||||
imageUrl = contentImage.toUri()
|
||||
}
|
||||
}.addOnSuccessListener {
|
||||
val uri = it.shortLink
|
||||
if (uri != null) {
|
||||
val message = uri.toString()
|
||||
onSuccess(message)
|
||||
} else {
|
||||
_toastLiveData.postValue("공유링크를 생성하지 못했습니다.\n다시 시도해 주세요.")
|
||||
}
|
||||
}.addOnFailureListener {
|
||||
_toastLiveData.postValue("공유링크를 생성하지 못했습니다.\n다시 시도해 주세요.")
|
||||
}.addOnCompleteListener {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
fun deleteAudioContent(audioContentId: Long, onSuccess: () -> Unit) {
|
||||
isLoading.value = true
|
||||
|
||||
compositeDisposable.add(
|
||||
repository.deleteAudioContent(
|
||||
audioContentId,
|
||||
token = "Bearer ${SharedPreferenceManager.token}"
|
||||
)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{
|
||||
isLoading.value = false
|
||||
|
||||
if (it.success) {
|
||||
_toastLiveData.postValue(
|
||||
"삭제되었습니다."
|
||||
)
|
||||
onSuccess()
|
||||
} else {
|
||||
if (it.message != null) {
|
||||
_toastLiveData.postValue(it.message)
|
||||
} else {
|
||||
_toastLiveData.postValue(
|
||||
"알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
isLoading.value = false
|
||||
it.message?.let { message -> Logger.e(message) }
|
||||
_toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun report(type: ReportType, contentId: Long, reason: String) {
|
||||
isLoading.value = true
|
||||
val request = ReportRequest(type = type, reason = reason, contentId = contentId)
|
||||
compositeDisposable.add(
|
||||
reportRepository.report(
|
||||
request = request,
|
||||
token = "Bearer ${SharedPreferenceManager.token}"
|
||||
)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{
|
||||
if (it.message != null) {
|
||||
_toastLiveData.postValue(it.message)
|
||||
} else {
|
||||
_toastLiveData.postValue(
|
||||
"신고가 접수되었습니다."
|
||||
)
|
||||
}
|
||||
|
||||
isLoading.value = false
|
||||
},
|
||||
{
|
||||
isLoading.value = false
|
||||
it.message?.let { message -> Logger.e(message) }
|
||||
_toastLiveData.postValue("신고가 접수되었습니다.")
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun togglePlayLoop() {
|
||||
val isPlayLoop = !SharedPreferenceManager.isContentPlayLoop
|
||||
SharedPreferenceManager.isContentPlayLoop = isPlayLoop
|
||||
_isContentPlayLoopLiveData.value = isPlayLoop
|
||||
}
|
||||
|
||||
fun donation(contentId: Long, can: Int, comment: String, onSuccess: () -> Unit) {
|
||||
isLoading.value = true
|
||||
compositeDisposable.add(
|
||||
repository.donation(
|
||||
contentId = contentId,
|
||||
can = can,
|
||||
comment = comment,
|
||||
token = "Bearer ${SharedPreferenceManager.token}"
|
||||
)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{
|
||||
isLoading.value = false
|
||||
|
||||
if (it.success) {
|
||||
SharedPreferenceManager.can -= can
|
||||
_toastLiveData.postValue(
|
||||
"${can.moneyFormat()}캔을 후원하였습니다."
|
||||
)
|
||||
onSuccess()
|
||||
} else {
|
||||
if (it.message != null) {
|
||||
_toastLiveData.postValue(it.message)
|
||||
} else {
|
||||
_toastLiveData.postValue(
|
||||
"알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
isLoading.value = false
|
||||
it.message?.let { message -> Logger.e(message) }
|
||||
_toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.detail
|
||||
|
||||
import android.app.Activity
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.view.LayoutInflater
|
||||
import android.view.WindowManager
|
||||
import android.widget.RadioButton
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import kr.co.vividnext.sodalive.databinding.DialogAudioContentReportBinding
|
||||
import kr.co.vividnext.sodalive.extensions.dpToPx
|
||||
|
||||
class AudioContentReportDialog(
|
||||
activity: Activity,
|
||||
layoutInflater: LayoutInflater,
|
||||
confirmButtonClick: (String) -> Unit
|
||||
) {
|
||||
private val alertDialog: AlertDialog
|
||||
val dialogView = DialogAudioContentReportBinding.inflate(layoutInflater)
|
||||
var reason = ""
|
||||
|
||||
init {
|
||||
val dialogBuilder = AlertDialog.Builder(activity)
|
||||
dialogBuilder.setView(dialogView.root)
|
||||
|
||||
alertDialog = dialogBuilder.create()
|
||||
alertDialog.setCancelable(false)
|
||||
alertDialog.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
|
||||
|
||||
dialogView.tvCancel.setOnClickListener {
|
||||
alertDialog.dismiss()
|
||||
}
|
||||
|
||||
dialogView.tvReport.setOnClickListener {
|
||||
if (reason.isNotBlank()) {
|
||||
alertDialog.dismiss()
|
||||
confirmButtonClick(reason)
|
||||
} else {
|
||||
Toast.makeText(activity, "신고 이유를 선택하세요.", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
|
||||
dialogView.radioGroup.setOnCheckedChangeListener { radioGroup, checkedId ->
|
||||
val radioButton = radioGroup.findViewById<RadioButton>(checkedId)
|
||||
reason = radioButton.text.toString()
|
||||
}
|
||||
}
|
||||
|
||||
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?.attributes = lp
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.detail
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import kr.co.vividnext.sodalive.audio_content.comment.GetAudioContentCommentListItem
|
||||
import kr.co.vividnext.sodalive.audio_content.order.OrderType
|
||||
|
||||
data class GetAudioContentDetailResponse(
|
||||
@SerializedName("contentId") val contentId: Long,
|
||||
@SerializedName("title") val title: String,
|
||||
@SerializedName("detail") val detail: String,
|
||||
@SerializedName("coverImageUrl") val coverImageUrl: String,
|
||||
@SerializedName("contentUrl") val contentUrl: String,
|
||||
@SerializedName("themeStr") val themeStr: String,
|
||||
@SerializedName("tag") val tag: String,
|
||||
@SerializedName("price") val price: Int,
|
||||
@SerializedName("duration") val duration: String,
|
||||
@SerializedName("isAdult") val isAdult: Boolean,
|
||||
@SerializedName("isMosaic") val isMosaic: Boolean,
|
||||
@SerializedName("existOrdered") val existOrdered: Boolean,
|
||||
@SerializedName("orderType") val orderType: OrderType?,
|
||||
@SerializedName("remainingTime") val remainingTime: String?,
|
||||
@SerializedName("creatorOtherContentList")
|
||||
val creatorOtherContentList: List<OtherContentResponse>,
|
||||
@SerializedName("sameThemeOtherContentList")
|
||||
val sameThemeOtherContentList: List<OtherContentResponse>,
|
||||
@SerializedName("isCommentAvailable") val isCommentAvailable: Boolean,
|
||||
@SerializedName("isLike") val isLike: Boolean,
|
||||
@SerializedName("likeCount") val likeCount: Int,
|
||||
@SerializedName("commentList") val commentList: List<GetAudioContentCommentListItem>,
|
||||
@SerializedName("commentCount") val commentCount: Int,
|
||||
@SerializedName("creator") val creator: AudioContentCreator
|
||||
)
|
||||
|
||||
data class OtherContentResponse(
|
||||
@SerializedName("contentId") val contentId: Long,
|
||||
@SerializedName("title") val title: String,
|
||||
@SerializedName("coverUrl") val coverUrl: String,
|
||||
)
|
||||
|
||||
data class AudioContentCreator(
|
||||
@SerializedName("creatorId") val creatorId: Long,
|
||||
@SerializedName("nickname") val nickname: String,
|
||||
@SerializedName("profileImageUrl") val profileImageUrl: String,
|
||||
@SerializedName("isFollowing") val isFollowing: Boolean
|
||||
)
|
|
@ -0,0 +1,46 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.detail
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import coil.load
|
||||
import coil.transform.RoundedCornersTransformation
|
||||
import kr.co.vividnext.sodalive.R
|
||||
import kr.co.vividnext.sodalive.databinding.ItemAudioOtherContentBinding
|
||||
import kr.co.vividnext.sodalive.extensions.dpToPx
|
||||
|
||||
class OtherContentAdapter(
|
||||
private val onClickItem: (Long) -> Unit
|
||||
) : RecyclerView.Adapter<OtherContentAdapter.ViewHolder>() {
|
||||
|
||||
val items = mutableListOf<OtherContentResponse>()
|
||||
|
||||
inner class ViewHolder(
|
||||
private val binding: ItemAudioOtherContentBinding
|
||||
) : RecyclerView.ViewHolder(binding.root) {
|
||||
fun bind(item: OtherContentResponse) {
|
||||
binding.ivCover.load(item.coverUrl) {
|
||||
crossfade(true)
|
||||
placeholder(R.drawable.bg_placeholder)
|
||||
transformations(RoundedCornersTransformation(2.7f.dpToPx()))
|
||||
}
|
||||
|
||||
binding.tvTitle.text = item.title
|
||||
binding.root.setOnClickListener { onClickItem(item.contentId) }
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder(
|
||||
ItemAudioOtherContentBinding.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
parent,
|
||||
false
|
||||
)
|
||||
)
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
holder.bind(items[position])
|
||||
}
|
||||
|
||||
override fun getItemCount() = items.count()
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.detail
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class PutAudioContentLikeRequest(
|
||||
@SerializedName("contentId") val contentId: Long
|
||||
)
|
||||
|
||||
data class PutAudioContentLikeResponse(
|
||||
@SerializedName("like") val like: Boolean
|
||||
)
|
|
@ -0,0 +1,10 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.donation
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class AudioContentDonationRequest(
|
||||
@SerializedName("contentId") val contentId: Long,
|
||||
@SerializedName("donationCan") val donationCan: Int,
|
||||
@SerializedName("comment") val comment: String,
|
||||
@SerializedName("container") val container: String = "aos"
|
||||
)
|
|
@ -0,0 +1,41 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.main
|
||||
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.ImageView
|
||||
import coil.load
|
||||
import coil.transform.RoundedCornersTransformation
|
||||
import com.zhpan.bannerview.BaseBannerAdapter
|
||||
import com.zhpan.bannerview.BaseViewHolder
|
||||
import kr.co.vividnext.sodalive.R
|
||||
import kr.co.vividnext.sodalive.extensions.dpToPx
|
||||
|
||||
class AudioContentMainBannerAdapter(
|
||||
private val itemWidth: Int,
|
||||
private val itemHeight: Int,
|
||||
private val onClick: (GetAudioContentBannerResponse) -> Unit
|
||||
) : BaseBannerAdapter<GetAudioContentBannerResponse>() {
|
||||
override fun bindData(
|
||||
holder: BaseViewHolder<GetAudioContentBannerResponse>,
|
||||
data: GetAudioContentBannerResponse,
|
||||
position: Int,
|
||||
pageSize: Int
|
||||
) {
|
||||
val ivBanner = holder.findViewById<ImageView>(R.id.iv_recommend_live)
|
||||
val layoutParams = ivBanner.layoutParams as FrameLayout.LayoutParams
|
||||
|
||||
layoutParams.width = itemWidth
|
||||
layoutParams.height = itemHeight
|
||||
|
||||
ivBanner.load(data.thumbnailImageUrl) {
|
||||
crossfade(true)
|
||||
placeholder(R.drawable.ic_logo)
|
||||
transformations(RoundedCornersTransformation(5.3f.dpToPx()))
|
||||
}
|
||||
ivBanner.layoutParams = layoutParams
|
||||
ivBanner.setOnClickListener { onClick(data) }
|
||||
}
|
||||
|
||||
override fun getLayoutId(viewType: Int): Int {
|
||||
return R.layout.item_recommend_live
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.main
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import kr.co.vividnext.sodalive.databinding.ItemAudioContentMainBinding
|
||||
|
||||
class AudioContentMainContentAdapter(
|
||||
private val onClickItem: (Long) -> Unit,
|
||||
private val onClickCreator: (Long) -> Unit,
|
||||
) : RecyclerView.Adapter<AudioContentMainItemViewHolder>() {
|
||||
|
||||
private val items = mutableListOf<GetAudioContentMainItem>()
|
||||
|
||||
override fun onCreateViewHolder(
|
||||
parent: ViewGroup,
|
||||
viewType: Int
|
||||
) = AudioContentMainItemViewHolder(
|
||||
ItemAudioContentMainBinding.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
parent,
|
||||
false
|
||||
),
|
||||
onClickItem = onClickItem,
|
||||
onClickCreator = onClickCreator
|
||||
)
|
||||
|
||||
override fun getItemCount() = items.size
|
||||
|
||||
override fun onBindViewHolder(holder: AudioContentMainItemViewHolder, position: Int) {
|
||||
holder.bind(items[position])
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
fun addItems(items: List<GetAudioContentMainItem>) {
|
||||
this.items.clear()
|
||||
this.items.addAll(items)
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.main
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.graphics.Rect
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import kr.co.vividnext.sodalive.databinding.ItemAudioContentMainCurationBinding
|
||||
import kr.co.vividnext.sodalive.extensions.dpToPx
|
||||
|
||||
class AudioContentMainCurationAdapter(
|
||||
private val onClickItem: (Long) -> Unit,
|
||||
private val onClickCreator: (Long) -> Unit,
|
||||
) : RecyclerView.Adapter<AudioContentMainCurationAdapter.ViewHolder>() {
|
||||
|
||||
private val items = mutableListOf<GetAudioContentCurationResponse>()
|
||||
|
||||
inner class ViewHolder(
|
||||
private val context: Context,
|
||||
private val binding: ItemAudioContentMainCurationBinding
|
||||
) : RecyclerView.ViewHolder(binding.root) {
|
||||
fun bind(item: GetAudioContentCurationResponse) {
|
||||
binding.tvTitle.text = item.title
|
||||
binding.tvDesc.text = item.description
|
||||
setAudioContentList(item.audioContents)
|
||||
}
|
||||
|
||||
private fun setAudioContentList(audioContents: List<GetAudioContentMainItem>) {
|
||||
val adapter = AudioContentMainContentAdapter(onClickItem, onClickCreator)
|
||||
|
||||
binding.rvCuration.layoutManager = LinearLayoutManager(
|
||||
context,
|
||||
LinearLayoutManager.HORIZONTAL,
|
||||
false
|
||||
)
|
||||
if (binding.rvCuration.itemDecorationCount == 0) {
|
||||
binding.rvCuration.addItemDecoration(object : RecyclerView.ItemDecoration() {
|
||||
override fun getItemOffsets(
|
||||
outRect: Rect,
|
||||
view: View,
|
||||
parent: RecyclerView,
|
||||
state: RecyclerView.State
|
||||
) {
|
||||
super.getItemOffsets(outRect, view, parent, state)
|
||||
|
||||
when (parent.getChildAdapterPosition(view)) {
|
||||
0 -> {
|
||||
outRect.left = 0
|
||||
outRect.right = 6.7f.dpToPx().toInt()
|
||||
}
|
||||
|
||||
adapter.itemCount - 1 -> {
|
||||
outRect.left = 6.7f.dpToPx().toInt()
|
||||
outRect.right = 0
|
||||
}
|
||||
|
||||
else -> {
|
||||
outRect.left = 6.7f.dpToPx().toInt()
|
||||
outRect.right = 6.7f.dpToPx().toInt()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
binding.rvCuration.adapter = adapter
|
||||
adapter.addItems(audioContents)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder(
|
||||
parent.context,
|
||||
ItemAudioContentMainCurationBinding.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
parent,
|
||||
false
|
||||
)
|
||||
)
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
holder.bind(items[position])
|
||||
}
|
||||
|
||||
override fun getItemCount() = items.size
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
fun addItems(items: List<GetAudioContentCurationResponse>) {
|
||||
this.items.clear()
|
||||
this.items.addAll(items)
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,470 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.main
|
||||
|
||||
import android.app.Service
|
||||
import android.content.Intent
|
||||
import android.graphics.Rect
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.Toast
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.zhpan.bannerview.BaseBannerAdapter
|
||||
import com.zhpan.indicator.enums.IndicatorSlideMode
|
||||
import com.zhpan.indicator.enums.IndicatorStyle
|
||||
import kr.co.vividnext.sodalive.R
|
||||
import kr.co.vividnext.sodalive.audio_content.detail.AudioContentDetailActivity
|
||||
import kr.co.vividnext.sodalive.audio_content.order.AudioContentOrderListActivity
|
||||
import kr.co.vividnext.sodalive.audio_content.upload.AudioContentUploadActivity
|
||||
import kr.co.vividnext.sodalive.base.BaseFragment
|
||||
import kr.co.vividnext.sodalive.common.Constants
|
||||
import kr.co.vividnext.sodalive.common.LoadingDialog
|
||||
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
|
||||
import kr.co.vividnext.sodalive.databinding.FragmentAudioContentMainBinding
|
||||
import kr.co.vividnext.sodalive.explorer.profile.UserProfileActivity
|
||||
import kr.co.vividnext.sodalive.extensions.dpToPx
|
||||
import kr.co.vividnext.sodalive.settings.event.EventDetailActivity
|
||||
import kr.co.vividnext.sodalive.settings.notification.MemberRole
|
||||
import org.koin.android.ext.android.inject
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class AudioContentMainFragment : BaseFragment<FragmentAudioContentMainBinding>(
|
||||
FragmentAudioContentMainBinding::inflate
|
||||
) {
|
||||
private val viewModel: AudioContentMainViewModel by inject()
|
||||
|
||||
private lateinit var loadingDialog: LoadingDialog
|
||||
private lateinit var imm: InputMethodManager
|
||||
|
||||
private lateinit var newContentCreatorAdapter: AudioContentMainNewContentCreatorAdapter
|
||||
private lateinit var bannerAdapter: AudioContentMainBannerAdapter
|
||||
private lateinit var orderListAdapter: AudioContentMainContentAdapter
|
||||
private lateinit var newContentThemeAdapter: AudioContentMainNewContentThemeAdapter
|
||||
private lateinit var newContentAdapter: AudioContentMainContentAdapter
|
||||
private lateinit var curationAdapter: AudioContentMainCurationAdapter
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
loadingDialog = LoadingDialog(requireActivity(), layoutInflater)
|
||||
imm = requireContext().getSystemService(
|
||||
Service.INPUT_METHOD_SERVICE
|
||||
) as InputMethodManager
|
||||
|
||||
setupView()
|
||||
bindData()
|
||||
|
||||
viewModel.getMain()
|
||||
}
|
||||
|
||||
private fun setupView() {
|
||||
if (SharedPreferenceManager.role == MemberRole.CREATOR.name) {
|
||||
binding.llUploadContent.visibility = View.VISIBLE
|
||||
binding.llUploadContent.setOnClickListener {
|
||||
startActivity(
|
||||
Intent(
|
||||
requireActivity(),
|
||||
AudioContentUploadActivity::class.java
|
||||
)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
binding.llUploadContent.visibility = View.GONE
|
||||
}
|
||||
|
||||
setupNewContentCreator()
|
||||
setupBanner()
|
||||
setupOrderList()
|
||||
setupNewContentTheme()
|
||||
setupNewContent()
|
||||
setupCuration()
|
||||
|
||||
binding.swipeRefreshLayout.setOnRefreshListener {
|
||||
binding.swipeRefreshLayout.isRefreshing = false
|
||||
viewModel.getMain()
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupNewContentCreator() {
|
||||
newContentCreatorAdapter = AudioContentMainNewContentCreatorAdapter {
|
||||
val intent = Intent(requireContext(), UserProfileActivity::class.java)
|
||||
intent.putExtra(Constants.EXTRA_USER_ID, it)
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
binding.rvNewContentCreator.layoutManager = LinearLayoutManager(
|
||||
context,
|
||||
LinearLayoutManager.HORIZONTAL,
|
||||
false
|
||||
)
|
||||
|
||||
binding.rvNewContentCreator.addItemDecoration(object : RecyclerView.ItemDecoration() {
|
||||
override fun getItemOffsets(
|
||||
outRect: Rect,
|
||||
view: View,
|
||||
parent: RecyclerView,
|
||||
state: RecyclerView.State
|
||||
) {
|
||||
super.getItemOffsets(outRect, view, parent, state)
|
||||
|
||||
when (parent.getChildAdapterPosition(view)) {
|
||||
0 -> {
|
||||
outRect.left = 0
|
||||
outRect.right = 10.7f.dpToPx().toInt()
|
||||
}
|
||||
|
||||
orderListAdapter.itemCount - 1 -> {
|
||||
outRect.left = 10.7f.dpToPx().toInt()
|
||||
outRect.right = 0
|
||||
}
|
||||
|
||||
else -> {
|
||||
outRect.left = 10.7f.dpToPx().toInt()
|
||||
outRect.right = 10.7f.dpToPx().toInt()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
binding.rvNewContentCreator.adapter = newContentCreatorAdapter
|
||||
}
|
||||
|
||||
private fun setupBanner() {
|
||||
val layoutParams = binding
|
||||
.rvBanner
|
||||
.layoutParams as LinearLayout.LayoutParams
|
||||
|
||||
val pagerWidth = screenWidth.toDouble() - 26.7f.dpToPx()
|
||||
val pagerHeight = (pagerWidth * 0.53).roundToInt()
|
||||
layoutParams.width = pagerWidth.roundToInt()
|
||||
layoutParams.height = pagerHeight
|
||||
|
||||
bannerAdapter = AudioContentMainBannerAdapter(pagerWidth.roundToInt(), pagerHeight) {
|
||||
when (it.type) {
|
||||
AudioContentBannerType.EVENT -> {
|
||||
startActivity(
|
||||
Intent(requireContext(), EventDetailActivity::class.java).apply {
|
||||
putExtra(Constants.EXTRA_EVENT, it.eventItem!!)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
AudioContentBannerType.CREATOR -> {
|
||||
startActivity(
|
||||
Intent(requireContext(), UserProfileActivity::class.java).apply {
|
||||
putExtra(Constants.EXTRA_USER_ID, it.creatorId!!)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
AudioContentBannerType.LINK -> {
|
||||
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(it.link!!)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
binding
|
||||
.rvBanner
|
||||
.layoutParams = layoutParams
|
||||
|
||||
binding.rvBanner.apply {
|
||||
adapter = bannerAdapter as BaseBannerAdapter<Any>
|
||||
|
||||
setLifecycleRegistry(lifecycle)
|
||||
setScrollDuration(1000)
|
||||
setInterval(4 * 1000)
|
||||
}.create()
|
||||
|
||||
binding
|
||||
.rvBanner
|
||||
.setIndicatorView(binding.indicatorBanner)
|
||||
.setIndicatorStyle(IndicatorStyle.ROUND_RECT)
|
||||
.setIndicatorSlideMode(IndicatorSlideMode.SMOOTH)
|
||||
.setIndicatorVisibility(View.GONE)
|
||||
.setIndicatorSliderColor(
|
||||
ContextCompat.getColor(requireContext(), R.color.color_909090),
|
||||
ContextCompat.getColor(requireContext(), R.color.color_9970ff)
|
||||
)
|
||||
.setIndicatorSliderWidth(4f.dpToPx().toInt(), 10f.dpToPx().toInt())
|
||||
.setIndicatorHeight(4f.dpToPx().toInt())
|
||||
}
|
||||
|
||||
private fun setupOrderList() {
|
||||
orderListAdapter = AudioContentMainContentAdapter(
|
||||
onClickItem = {
|
||||
startActivity(
|
||||
Intent(requireContext(), AudioContentDetailActivity::class.java).apply {
|
||||
putExtra(Constants.EXTRA_AUDIO_CONTENT_ID, it)
|
||||
}
|
||||
)
|
||||
},
|
||||
onClickCreator = {
|
||||
startActivity(
|
||||
Intent(requireContext(), UserProfileActivity::class.java).apply {
|
||||
putExtra(Constants.EXTRA_USER_ID, it)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
binding.rvMyStash.layoutManager = LinearLayoutManager(
|
||||
context,
|
||||
LinearLayoutManager.HORIZONTAL,
|
||||
false
|
||||
)
|
||||
|
||||
binding.rvMyStash.addItemDecoration(object : RecyclerView.ItemDecoration() {
|
||||
override fun getItemOffsets(
|
||||
outRect: Rect,
|
||||
view: View,
|
||||
parent: RecyclerView,
|
||||
state: RecyclerView.State
|
||||
) {
|
||||
super.getItemOffsets(outRect, view, parent, state)
|
||||
|
||||
when (parent.getChildAdapterPosition(view)) {
|
||||
0 -> {
|
||||
outRect.left = 0
|
||||
outRect.right = 6.7f.dpToPx().toInt()
|
||||
}
|
||||
|
||||
orderListAdapter.itemCount - 1 -> {
|
||||
outRect.left = 6.7f.dpToPx().toInt()
|
||||
outRect.right = 0
|
||||
}
|
||||
|
||||
else -> {
|
||||
outRect.left = 6.7f.dpToPx().toInt()
|
||||
outRect.right = 6.7f.dpToPx().toInt()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
binding.rvMyStash.adapter = orderListAdapter
|
||||
binding.tvMyStashViewAll.setOnClickListener {
|
||||
startActivity(Intent(requireContext(), AudioContentOrderListActivity::class.java))
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupNewContentTheme() {
|
||||
newContentThemeAdapter = AudioContentMainNewContentThemeAdapter {
|
||||
viewModel.getNewContentOfTheme(theme = it)
|
||||
}
|
||||
|
||||
binding.rvNewContentTheme.layoutManager = LinearLayoutManager(
|
||||
context,
|
||||
LinearLayoutManager.HORIZONTAL,
|
||||
false
|
||||
)
|
||||
|
||||
binding.rvNewContentTheme.addItemDecoration(object : RecyclerView.ItemDecoration() {
|
||||
override fun getItemOffsets(
|
||||
outRect: Rect,
|
||||
view: View,
|
||||
parent: RecyclerView,
|
||||
state: RecyclerView.State
|
||||
) {
|
||||
super.getItemOffsets(outRect, view, parent, state)
|
||||
|
||||
when (parent.getChildAdapterPosition(view)) {
|
||||
0 -> {
|
||||
outRect.left = 0
|
||||
outRect.right = 4f.dpToPx().toInt()
|
||||
}
|
||||
|
||||
orderListAdapter.itemCount - 1 -> {
|
||||
outRect.left = 4f.dpToPx().toInt()
|
||||
outRect.right = 0
|
||||
}
|
||||
|
||||
else -> {
|
||||
outRect.left = 4f.dpToPx().toInt()
|
||||
outRect.right = 4f.dpToPx().toInt()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
binding.rvNewContentTheme.adapter = newContentThemeAdapter
|
||||
}
|
||||
|
||||
private fun setupNewContent() {
|
||||
newContentAdapter = AudioContentMainContentAdapter(
|
||||
onClickItem = {
|
||||
startActivity(
|
||||
Intent(requireContext(), AudioContentDetailActivity::class.java).apply {
|
||||
putExtra(Constants.EXTRA_AUDIO_CONTENT_ID, it)
|
||||
}
|
||||
)
|
||||
},
|
||||
onClickCreator = {
|
||||
startActivity(
|
||||
Intent(requireContext(), UserProfileActivity::class.java).apply {
|
||||
putExtra(Constants.EXTRA_USER_ID, it)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
binding.rvNewContent.layoutManager = LinearLayoutManager(
|
||||
context,
|
||||
LinearLayoutManager.HORIZONTAL,
|
||||
false
|
||||
)
|
||||
|
||||
binding.rvNewContent.addItemDecoration(object : RecyclerView.ItemDecoration() {
|
||||
override fun getItemOffsets(
|
||||
outRect: Rect,
|
||||
view: View,
|
||||
parent: RecyclerView,
|
||||
state: RecyclerView.State
|
||||
) {
|
||||
super.getItemOffsets(outRect, view, parent, state)
|
||||
|
||||
when (parent.getChildAdapterPosition(view)) {
|
||||
0 -> {
|
||||
outRect.left = 0
|
||||
outRect.right = 6.7f.dpToPx().toInt()
|
||||
}
|
||||
|
||||
orderListAdapter.itemCount - 1 -> {
|
||||
outRect.left = 6.7f.dpToPx().toInt()
|
||||
outRect.right = 0
|
||||
}
|
||||
|
||||
else -> {
|
||||
outRect.left = 6.7f.dpToPx().toInt()
|
||||
outRect.right = 6.7f.dpToPx().toInt()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
binding.rvNewContent.adapter = newContentAdapter
|
||||
}
|
||||
|
||||
private fun setupCuration() {
|
||||
curationAdapter = AudioContentMainCurationAdapter(
|
||||
onClickItem = {
|
||||
startActivity(
|
||||
Intent(requireContext(), AudioContentDetailActivity::class.java).apply {
|
||||
putExtra(Constants.EXTRA_AUDIO_CONTENT_ID, it)
|
||||
}
|
||||
)
|
||||
},
|
||||
onClickCreator = {
|
||||
startActivity(
|
||||
Intent(requireContext(), UserProfileActivity::class.java).apply {
|
||||
putExtra(Constants.EXTRA_USER_ID, it)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
binding.rvCuration.layoutManager = LinearLayoutManager(
|
||||
context,
|
||||
LinearLayoutManager.VERTICAL,
|
||||
false
|
||||
)
|
||||
|
||||
binding.rvCuration.addItemDecoration(object : RecyclerView.ItemDecoration() {
|
||||
override fun getItemOffsets(
|
||||
outRect: Rect,
|
||||
view: View,
|
||||
parent: RecyclerView,
|
||||
state: RecyclerView.State
|
||||
) {
|
||||
super.getItemOffsets(outRect, view, parent, state)
|
||||
|
||||
when (parent.getChildAdapterPosition(view)) {
|
||||
0 -> {
|
||||
outRect.top = 40f.dpToPx().toInt()
|
||||
outRect.bottom = 20f.dpToPx().toInt()
|
||||
}
|
||||
|
||||
curationAdapter.itemCount - 1 -> {
|
||||
outRect.top = 20f.dpToPx().toInt()
|
||||
outRect.bottom = 40f.dpToPx().toInt()
|
||||
}
|
||||
|
||||
else -> {
|
||||
outRect.top = 20f.dpToPx().toInt()
|
||||
outRect.bottom = 20f.dpToPx().toInt()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
binding.rvCuration.adapter = curationAdapter
|
||||
}
|
||||
|
||||
private fun bindData() {
|
||||
viewModel.isLoading.observe(viewLifecycleOwner) {
|
||||
if (it) {
|
||||
loadingDialog.show(screenWidth)
|
||||
} else {
|
||||
loadingDialog.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.toastLiveData.observe(viewLifecycleOwner) {
|
||||
it?.let { Toast.makeText(requireContext(), it, Toast.LENGTH_LONG).show() }
|
||||
}
|
||||
|
||||
viewModel.newContentUploadCreatorListLiveData.observe(viewLifecycleOwner) {
|
||||
newContentCreatorAdapter.addItems(it)
|
||||
binding.rvNewContentCreator.visibility = if (
|
||||
newContentCreatorAdapter.itemCount <= 0 && it.isEmpty()
|
||||
) {
|
||||
View.GONE
|
||||
} else {
|
||||
View.VISIBLE
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.bannerLiveData.observe(viewLifecycleOwner) {
|
||||
if (bannerAdapter.itemCount <= 0 && it.isEmpty()) {
|
||||
binding.rvBanner.visibility = View.GONE
|
||||
binding.indicatorBanner.visibility = View.GONE
|
||||
} else {
|
||||
binding.rvBanner.visibility = View.VISIBLE
|
||||
binding.indicatorBanner.visibility = View.VISIBLE
|
||||
binding.rvBanner.refreshData(it)
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.orderListLiveData.observe(viewLifecycleOwner) {
|
||||
orderListAdapter.addItems(it)
|
||||
binding.llMyStash.visibility = if (
|
||||
orderListAdapter.itemCount <= 0 && it.isEmpty()
|
||||
) {
|
||||
View.GONE
|
||||
} else {
|
||||
View.VISIBLE
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.newContentListLiveData.observe(viewLifecycleOwner) {
|
||||
newContentAdapter.addItems(it)
|
||||
}
|
||||
|
||||
viewModel.themeListLiveData.observe(viewLifecycleOwner) {
|
||||
binding.llNewContent.visibility = View.VISIBLE
|
||||
newContentThemeAdapter.addItems(it)
|
||||
}
|
||||
|
||||
viewModel.curationListLiveData.observe(viewLifecycleOwner) {
|
||||
curationAdapter.addItems(it)
|
||||
binding.rvCuration.visibility = if (
|
||||
curationAdapter.itemCount <= 0 && it.isEmpty()
|
||||
) {
|
||||
View.GONE
|
||||
} else {
|
||||
View.VISIBLE
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.main
|
||||
|
||||
import android.view.View
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import coil.load
|
||||
import coil.transform.CircleCropTransformation
|
||||
import coil.transform.RoundedCornersTransformation
|
||||
import kr.co.vividnext.sodalive.R
|
||||
import kr.co.vividnext.sodalive.databinding.ItemAudioContentMainBinding
|
||||
import kr.co.vividnext.sodalive.extensions.dpToPx
|
||||
|
||||
class AudioContentMainItemViewHolder(
|
||||
private val binding: ItemAudioContentMainBinding,
|
||||
private val onClickItem: (Long) -> Unit,
|
||||
private val onClickCreator: (Long) -> Unit
|
||||
) : RecyclerView.ViewHolder(binding.root) {
|
||||
fun bind(item: GetAudioContentMainItem) {
|
||||
binding.ivAudioContentCoverImage.load(item.coverImageUrl) {
|
||||
crossfade(true)
|
||||
placeholder(R.drawable.ic_logo)
|
||||
transformations(RoundedCornersTransformation(2.7f.dpToPx()))
|
||||
}
|
||||
|
||||
binding.ivAudioContentCreator.load(item.creatorProfileImageUrl) {
|
||||
crossfade(true)
|
||||
placeholder(R.drawable.ic_logo)
|
||||
transformations(CircleCropTransformation())
|
||||
}
|
||||
|
||||
binding.tvAudioContentTitle.text = item.title
|
||||
binding.tvAudioContentCreatorNickname.text = item.creatorNickname
|
||||
|
||||
binding.ivAudioContentCreator.setOnClickListener { onClickCreator(item.creatorId) }
|
||||
binding.iv19.visibility = if (item.isAdult) {
|
||||
View.VISIBLE
|
||||
} else {
|
||||
View.GONE
|
||||
}
|
||||
binding.root.setOnClickListener { onClickItem(item.contentId) }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.main
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import coil.load
|
||||
import coil.transform.CircleCropTransformation
|
||||
import kr.co.vividnext.sodalive.R
|
||||
import kr.co.vividnext.sodalive.databinding.ItemAudioContentMainNewContentCreatorBinding
|
||||
|
||||
class AudioContentMainNewContentCreatorAdapter(
|
||||
private val onClickItem: (Long) -> Unit
|
||||
) : RecyclerView.Adapter<AudioContentMainNewContentCreatorAdapter.ViewHolder>() {
|
||||
|
||||
private val items = mutableListOf<GetNewContentUploadCreator>()
|
||||
|
||||
inner class ViewHolder(
|
||||
private val binding: ItemAudioContentMainNewContentCreatorBinding
|
||||
) : RecyclerView.ViewHolder(binding.root) {
|
||||
fun bind(item: GetNewContentUploadCreator) {
|
||||
binding.tvNewContentCreator.text = item.creatorNickname
|
||||
binding.ivNewContentCreator.load(item.creatorProfileImageUrl) {
|
||||
crossfade(true)
|
||||
placeholder(R.drawable.ic_logo)
|
||||
transformations(CircleCropTransformation())
|
||||
}
|
||||
binding.root.setOnClickListener { onClickItem(item.creatorId) }
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder(
|
||||
ItemAudioContentMainNewContentCreatorBinding.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
parent,
|
||||
false
|
||||
)
|
||||
)
|
||||
|
||||
override fun getItemCount() = items.size
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
holder.bind(items[position])
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
fun addItems(items: List<GetNewContentUploadCreator>) {
|
||||
this.items.clear()
|
||||
this.items.addAll(items)
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.main
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import kr.co.vividnext.sodalive.R
|
||||
import kr.co.vividnext.sodalive.databinding.ItemAudioContentMainNewContentThemeBinding
|
||||
|
||||
class AudioContentMainNewContentThemeAdapter(
|
||||
private val onClickItem: (String) -> Unit
|
||||
) : RecyclerView.Adapter<AudioContentMainNewContentThemeAdapter.ViewHolder>() {
|
||||
|
||||
private val themeList = mutableListOf<String>()
|
||||
private var selectedTheme = ""
|
||||
|
||||
inner class ViewHolder(
|
||||
private val context: Context,
|
||||
private val binding: ItemAudioContentMainNewContentThemeBinding
|
||||
) : RecyclerView.ViewHolder(binding.root) {
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
fun bind(theme: String) {
|
||||
if (theme == selectedTheme || (selectedTheme == "" && theme == "전체")) {
|
||||
binding.tvTheme.setBackgroundResource(
|
||||
R.drawable.bg_round_corner_16_7_transparent_9970ff
|
||||
)
|
||||
binding.tvTheme.setTextColor(ContextCompat.getColor(context, R.color.color_9970ff))
|
||||
} else {
|
||||
binding.tvTheme.setBackgroundResource(
|
||||
R.drawable.bg_round_corner_16_7_transparent_777777
|
||||
)
|
||||
binding.tvTheme.setTextColor(ContextCompat.getColor(context, R.color.color_777777))
|
||||
}
|
||||
|
||||
binding.tvTheme.text = theme
|
||||
binding.root.setOnClickListener {
|
||||
onClickItem(theme)
|
||||
selectedTheme = theme
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
fun addItems(themeList: List<String>) {
|
||||
this.selectedTheme = ""
|
||||
this.themeList.clear()
|
||||
this.themeList.addAll(themeList)
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder(
|
||||
parent.context,
|
||||
ItemAudioContentMainNewContentThemeBinding.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
parent,
|
||||
false
|
||||
)
|
||||
)
|
||||
|
||||
override fun getItemCount() = themeList.size
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
holder.bind(themeList[position])
|
||||
}
|
||||
}
|
|
@ -0,0 +1,121 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.main
|
||||
|
||||
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.audio_content.AudioContentRepository
|
||||
import kr.co.vividnext.sodalive.base.BaseViewModel
|
||||
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
|
||||
|
||||
class AudioContentMainViewModel(
|
||||
private val repository: AudioContentRepository
|
||||
) : BaseViewModel() {
|
||||
private val _toastLiveData = MutableLiveData<String?>()
|
||||
val toastLiveData: LiveData<String?>
|
||||
get() = _toastLiveData
|
||||
|
||||
private var _isLoading = MutableLiveData(false)
|
||||
val isLoading: LiveData<Boolean>
|
||||
get() = _isLoading
|
||||
|
||||
private var _newContentUploadCreatorListLiveData =
|
||||
MutableLiveData<List<GetNewContentUploadCreator>>()
|
||||
val newContentUploadCreatorListLiveData: LiveData<List<GetNewContentUploadCreator>>
|
||||
get() = _newContentUploadCreatorListLiveData
|
||||
|
||||
private var _newContentListLiveData = MutableLiveData<List<GetAudioContentMainItem>>()
|
||||
val newContentListLiveData: LiveData<List<GetAudioContentMainItem>>
|
||||
get() = _newContentListLiveData
|
||||
|
||||
private var _bannerLiveData = MutableLiveData<List<GetAudioContentBannerResponse>>()
|
||||
val bannerLiveData: LiveData<List<GetAudioContentBannerResponse>>
|
||||
get() = _bannerLiveData
|
||||
|
||||
private var _orderListLiveData = MutableLiveData<List<GetAudioContentMainItem>>()
|
||||
val orderListLiveData: LiveData<List<GetAudioContentMainItem>>
|
||||
get() = _orderListLiveData
|
||||
|
||||
private var _themeListLiveData = MutableLiveData<List<String>>()
|
||||
val themeListLiveData: LiveData<List<String>>
|
||||
get() = _themeListLiveData
|
||||
|
||||
private var _curationListLiveData = MutableLiveData<List<GetAudioContentCurationResponse>>()
|
||||
val curationListLiveData: LiveData<List<GetAudioContentCurationResponse>>
|
||||
get() = _curationListLiveData
|
||||
|
||||
fun getMain() {
|
||||
_isLoading.value = true
|
||||
compositeDisposable.add(
|
||||
repository.getMain(token = "Bearer ${SharedPreferenceManager.token}")
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{
|
||||
if (it.success && it.data != null) {
|
||||
val data = it.data
|
||||
_newContentUploadCreatorListLiveData.value =
|
||||
data.newContentUploadCreatorList
|
||||
_newContentListLiveData.value = data.newContentList
|
||||
_orderListLiveData.value = data.orderList
|
||||
_bannerLiveData.value = data.bannerList
|
||||
_curationListLiveData.value = data.curationList
|
||||
|
||||
val themeList = listOf("전체").union(data.themeList).toList()
|
||||
_themeListLiveData.value = themeList
|
||||
} else {
|
||||
if (it.message != null) {
|
||||
_toastLiveData.postValue(it.message)
|
||||
} else {
|
||||
_toastLiveData.postValue(
|
||||
"알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
_isLoading.value = false
|
||||
},
|
||||
{
|
||||
_isLoading.value = false
|
||||
it.message?.let { message -> Logger.e(message) }
|
||||
_toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun getNewContentOfTheme(theme: String) {
|
||||
compositeDisposable.add(
|
||||
repository.getNewContentOfTheme(
|
||||
theme = if (theme == "전체") {
|
||||
""
|
||||
} else {
|
||||
theme
|
||||
},
|
||||
token = "Bearer ${SharedPreferenceManager.token}"
|
||||
)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{
|
||||
if (it.success && it.data != null) {
|
||||
_newContentListLiveData.value = it.data!!
|
||||
} else {
|
||||
if (it.message != null) {
|
||||
_toastLiveData.postValue(it.message)
|
||||
} else {
|
||||
_toastLiveData.postValue(
|
||||
"알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
it.message?.let { message -> Logger.e(message) }
|
||||
_toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.main
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import kr.co.vividnext.sodalive.settings.event.EventItem
|
||||
|
||||
data class GetAudioContentMainResponse(
|
||||
@SerializedName("newContentUploadCreatorList")
|
||||
val newContentUploadCreatorList: List<GetNewContentUploadCreator>,
|
||||
@SerializedName("bannerList") val bannerList: List<GetAudioContentBannerResponse>,
|
||||
@SerializedName("orderList") val orderList: List<GetAudioContentMainItem>,
|
||||
@SerializedName("themeList") val themeList: List<String>,
|
||||
@SerializedName("newContentList") val newContentList: List<GetAudioContentMainItem>,
|
||||
@SerializedName("curationList") val curationList: List<GetAudioContentCurationResponse>
|
||||
)
|
||||
|
||||
data class GetNewContentUploadCreator(
|
||||
@SerializedName("creatorId") val creatorId: Long,
|
||||
@SerializedName("creatorNickname") val creatorNickname: String,
|
||||
@SerializedName("creatorProfileImageUrl") val creatorProfileImageUrl: String
|
||||
)
|
||||
|
||||
data class GetAudioContentMainItem(
|
||||
@SerializedName("contentId") val contentId: Long,
|
||||
@SerializedName("coverImageUrl") val coverImageUrl: String,
|
||||
@SerializedName("title") val title: String,
|
||||
@SerializedName("isAdult") val isAdult: Boolean,
|
||||
@SerializedName("creatorId") val creatorId: Long,
|
||||
@SerializedName("creatorProfileImageUrl") val creatorProfileImageUrl: String,
|
||||
@SerializedName("creatorNickname") val creatorNickname: String
|
||||
)
|
||||
|
||||
data class GetAudioContentCurationResponse(
|
||||
@SerializedName("title") val title: String,
|
||||
@SerializedName("description") val description: String,
|
||||
@SerializedName("audioContents") val audioContents: List<GetAudioContentMainItem>
|
||||
)
|
||||
|
||||
data class GetAudioContentBannerResponse(
|
||||
@SerializedName("type") val type: AudioContentBannerType,
|
||||
@SerializedName("thumbnailImageUrl") val thumbnailImageUrl: String,
|
||||
@SerializedName("eventItem") val eventItem: EventItem?,
|
||||
@SerializedName("creatorId") val creatorId: Long?,
|
||||
@SerializedName("link") val link: String?
|
||||
)
|
||||
|
||||
enum class AudioContentBannerType {
|
||||
@SerializedName("EVENT")
|
||||
EVENT,
|
||||
|
||||
@SerializedName("CREATOR")
|
||||
CREATOR,
|
||||
|
||||
@SerializedName("LINK")
|
||||
LINK
|
||||
}
|
|
@ -0,0 +1,288 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.modify
|
||||
|
||||
import android.Manifest
|
||||
import android.annotation.SuppressLint
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.setPadding
|
||||
import coil.load
|
||||
import coil.transform.RoundedCornersTransformation
|
||||
import com.github.dhaval2404.imagepicker.ImagePicker
|
||||
import com.gun0912.tedpermission.PermissionListener
|
||||
import com.gun0912.tedpermission.normal.TedPermission
|
||||
import com.jakewharton.rxbinding4.widget.textChanges
|
||||
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.BaseActivity
|
||||
import kr.co.vividnext.sodalive.common.Constants
|
||||
import kr.co.vividnext.sodalive.common.LoadingDialog
|
||||
import kr.co.vividnext.sodalive.common.RealPathUtil
|
||||
import kr.co.vividnext.sodalive.databinding.ActivityAudioContentModifyBinding
|
||||
import kr.co.vividnext.sodalive.extensions.dpToPx
|
||||
import org.koin.android.ext.android.inject
|
||||
|
||||
class AudioContentModifyActivity : BaseActivity<ActivityAudioContentModifyBinding>(
|
||||
ActivityAudioContentModifyBinding::inflate
|
||||
) {
|
||||
|
||||
private val viewModel: AudioContentModifyViewModel by inject()
|
||||
|
||||
private lateinit var loadingDialog: LoadingDialog
|
||||
|
||||
private val imageResult = registerForActivityResult(
|
||||
ActivityResultContracts.StartActivityForResult()
|
||||
) { result ->
|
||||
val resultCode = result.resultCode
|
||||
val data = result.data
|
||||
|
||||
if (resultCode == RESULT_OK) {
|
||||
val fileUri = data?.data
|
||||
|
||||
if (fileUri != null) {
|
||||
binding.ivCover.setPadding(0)
|
||||
binding.ivCover.background = null
|
||||
binding.ivCover.load(fileUri) {
|
||||
crossfade(true)
|
||||
placeholder(R.drawable.bg_placeholder)
|
||||
transformations(RoundedCornersTransformation(13.3f.dpToPx()))
|
||||
}
|
||||
viewModel.coverImageUri = fileUri
|
||||
} else {
|
||||
Toast.makeText(
|
||||
this,
|
||||
"잘못된 파일입니다.\n다시 선택해 주세요.",
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
} else if (resultCode == ImagePicker.RESULT_ERROR) {
|
||||
Toast.makeText(this, ImagePicker.getError(data), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
val audioContentId = intent.getLongExtra(Constants.EXTRA_AUDIO_CONTENT_ID, 0)
|
||||
if (audioContentId <= 0) {
|
||||
Toast.makeText(applicationContext, "잘못된 요청입니다.", Toast.LENGTH_LONG).show()
|
||||
finish()
|
||||
}
|
||||
|
||||
checkPermissions()
|
||||
|
||||
viewModel.getRealPathFromURI = {
|
||||
RealPathUtil.getRealPath(applicationContext, it)
|
||||
}
|
||||
|
||||
bindData()
|
||||
viewModel.getAudioContentDetail(audioContentId = audioContentId) { finish() }
|
||||
}
|
||||
|
||||
override fun setupView() {
|
||||
loadingDialog = LoadingDialog(this, layoutInflater)
|
||||
binding.toolbar.tvBack.text = "콘텐츠 수정"
|
||||
binding.toolbar.tvBack.setOnClickListener { finish() }
|
||||
|
||||
binding.ivPhotoPicker.setOnClickListener {
|
||||
ImagePicker.with(this)
|
||||
.crop()
|
||||
.galleryOnly()
|
||||
.galleryMimeTypes( // Exclude gif images
|
||||
mimeTypes = arrayOf(
|
||||
"image/png",
|
||||
"image/jpg",
|
||||
"image/jpeg"
|
||||
)
|
||||
)
|
||||
.createIntent { imageResult.launch(it) }
|
||||
}
|
||||
|
||||
binding.llCommentNo.setOnClickListener { viewModel.setAvailableComment(false) }
|
||||
binding.llCommentYes.setOnClickListener { viewModel.setAvailableComment(true) }
|
||||
|
||||
binding.tvCancel.setOnClickListener { finish() }
|
||||
binding.tvModify.setOnClickListener {
|
||||
viewModel.modifyAudioContent { finish() }
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkPermissions() {
|
||||
val permissions = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
listOf(Manifest.permission.READ_MEDIA_AUDIO, Manifest.permission.READ_MEDIA_IMAGES)
|
||||
} else {
|
||||
listOf(Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||
}
|
||||
|
||||
TedPermission.create()
|
||||
.setPermissionListener(object : PermissionListener {
|
||||
override fun onPermissionGranted() {
|
||||
}
|
||||
|
||||
override fun onPermissionDenied(deniedPermissions: MutableList<String>?) {
|
||||
finish()
|
||||
}
|
||||
})
|
||||
.setDeniedMessage(R.string.read_storage_permission_denied_message)
|
||||
.setPermissions(*permissions.toTypedArray())
|
||||
.check()
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
private fun bindData() {
|
||||
compositeDisposable.add(
|
||||
binding.etTitle.textChanges().skip(1)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe {
|
||||
viewModel.title = it.toString()
|
||||
}
|
||||
)
|
||||
|
||||
compositeDisposable.add(
|
||||
binding.etDetail.textChanges().skip(1)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe {
|
||||
binding.tvNumberOfCharacters.text = "${it.length}자"
|
||||
viewModel.detail = it.toString()
|
||||
}
|
||||
)
|
||||
|
||||
viewModel.toastLiveData.observe(this) {
|
||||
it?.let { Toast.makeText(applicationContext, it, Toast.LENGTH_LONG).show() }
|
||||
}
|
||||
|
||||
viewModel.isLoading.observe(this) {
|
||||
if (it) {
|
||||
loadingDialog.show(screenWidth, "")
|
||||
} else {
|
||||
loadingDialog.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.isAvailableCommentLiveData.observe(this) {
|
||||
if (it) {
|
||||
binding.ivCommentYes.visibility = View.VISIBLE
|
||||
binding.tvCommentYes.setTextColor(
|
||||
ContextCompat.getColor(
|
||||
applicationContext,
|
||||
R.color.color_eeeeee
|
||||
)
|
||||
)
|
||||
binding.llCommentYes.setBackgroundResource(R.drawable.bg_round_corner_6_7_9970ff)
|
||||
|
||||
binding.ivCommentNo.visibility = View.GONE
|
||||
binding.tvCommentNo.setTextColor(
|
||||
ContextCompat.getColor(
|
||||
applicationContext,
|
||||
R.color.color_9970ff
|
||||
)
|
||||
)
|
||||
binding.llCommentNo.setBackgroundResource(
|
||||
R.drawable.bg_round_corner_6_7_1f1734_9970ff
|
||||
)
|
||||
} else {
|
||||
binding.ivCommentNo.visibility = View.VISIBLE
|
||||
binding.tvCommentNo.setTextColor(
|
||||
ContextCompat.getColor(
|
||||
applicationContext,
|
||||
R.color.color_eeeeee
|
||||
)
|
||||
)
|
||||
binding.llCommentNo.setBackgroundResource(R.drawable.bg_round_corner_6_7_9970ff)
|
||||
|
||||
binding.ivCommentYes.visibility = View.GONE
|
||||
binding.tvCommentYes.setTextColor(
|
||||
ContextCompat.getColor(
|
||||
applicationContext,
|
||||
R.color.color_9970ff
|
||||
)
|
||||
)
|
||||
binding.llCommentYes
|
||||
.setBackgroundResource(R.drawable.bg_round_corner_6_7_1f1734_9970ff)
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.isAdultShowUiLiveData.observe(this) {
|
||||
if (it) {
|
||||
binding.llSetAdult.visibility = View.VISIBLE
|
||||
|
||||
binding.llAgeAll.setOnClickListener {
|
||||
viewModel.setAdult(false)
|
||||
}
|
||||
|
||||
binding.llAge19.setOnClickListener {
|
||||
viewModel.setAdult(true)
|
||||
}
|
||||
|
||||
viewModel.isAdultLiveData.observe(this) {
|
||||
if (it) {
|
||||
binding.ivAgeAll.visibility = View.GONE
|
||||
binding.llAgeAll.setBackgroundResource(
|
||||
R.drawable.bg_round_corner_6_7_1f1734
|
||||
)
|
||||
binding.tvAgeAll.setTextColor(
|
||||
ContextCompat.getColor(
|
||||
applicationContext,
|
||||
R.color.color_9970ff
|
||||
)
|
||||
)
|
||||
|
||||
binding.ivAge19.visibility = View.VISIBLE
|
||||
binding.llAge19.setBackgroundResource(R.drawable.bg_round_corner_6_7_9970ff)
|
||||
binding.tvAge19.setTextColor(
|
||||
ContextCompat.getColor(
|
||||
applicationContext,
|
||||
R.color.color_eeeeee
|
||||
)
|
||||
)
|
||||
} else {
|
||||
binding.ivAge19.visibility = View.GONE
|
||||
binding.llAge19.setBackgroundResource(R.drawable.bg_round_corner_6_7_1f1734)
|
||||
binding.tvAge19.setTextColor(
|
||||
ContextCompat.getColor(
|
||||
applicationContext,
|
||||
R.color.color_9970ff
|
||||
)
|
||||
)
|
||||
|
||||
binding.ivAgeAll.visibility = View.VISIBLE
|
||||
binding.llAgeAll.setBackgroundResource(
|
||||
R.drawable.bg_round_corner_6_7_9970ff
|
||||
)
|
||||
binding.tvAgeAll.setTextColor(
|
||||
ContextCompat.getColor(
|
||||
applicationContext,
|
||||
R.color.color_eeeeee
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
binding.llSetAdult.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.coverImageLiveData.observe(this) {
|
||||
binding.ivCover.setPadding(0)
|
||||
binding.ivCover.background = null
|
||||
binding.ivCover.load(it) {
|
||||
crossfade(true)
|
||||
placeholder(R.drawable.bg_placeholder)
|
||||
transformations(RoundedCornersTransformation(13.3f.dpToPx()))
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.titleLiveData.observe(this) {
|
||||
binding.etTitle.setText(it)
|
||||
}
|
||||
|
||||
viewModel.detailLiveData.observe(this) {
|
||||
binding.etDetail.setText(it)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,212 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.modify
|
||||
|
||||
import android.net.Uri
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import com.google.gson.Gson
|
||||
import com.orhanobut.logger.Logger
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import kr.co.vividnext.sodalive.audio_content.AudioContentRepository
|
||||
import kr.co.vividnext.sodalive.base.BaseViewModel
|
||||
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
|
||||
import okhttp3.MediaType
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.MultipartBody
|
||||
import okhttp3.RequestBody
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import okio.BufferedSink
|
||||
import java.io.File
|
||||
|
||||
class AudioContentModifyViewModel(
|
||||
private val repository: AudioContentRepository
|
||||
) : BaseViewModel() {
|
||||
|
||||
private val _toastLiveData = MutableLiveData<String?>()
|
||||
val toastLiveData: LiveData<String?>
|
||||
get() = _toastLiveData
|
||||
|
||||
private var _isLoading = MutableLiveData(false)
|
||||
val isLoading: LiveData<Boolean>
|
||||
get() = _isLoading
|
||||
|
||||
private val _isAdultLiveData = MutableLiveData(false)
|
||||
val isAdultLiveData: LiveData<Boolean>
|
||||
get() = _isAdultLiveData
|
||||
|
||||
private val _isAvailableCommentLiveData = MutableLiveData(false)
|
||||
val isAvailableCommentLiveData: LiveData<Boolean>
|
||||
get() = _isAvailableCommentLiveData
|
||||
|
||||
private val _titleLiveData = MutableLiveData("")
|
||||
val titleLiveData: LiveData<String>
|
||||
get() = _titleLiveData
|
||||
|
||||
private val _detailLiveData = MutableLiveData("")
|
||||
val detailLiveData: LiveData<String>
|
||||
get() = _detailLiveData
|
||||
|
||||
private val _coverImageLiveData = MutableLiveData("")
|
||||
val coverImageLiveData: LiveData<String>
|
||||
get() = _coverImageLiveData
|
||||
|
||||
private val _isAdultShowUiLiveData = MutableLiveData(true)
|
||||
val isAdultShowUiLiveData: LiveData<Boolean>
|
||||
get() = _isAdultShowUiLiveData
|
||||
|
||||
lateinit var getRealPathFromURI: (Uri) -> String?
|
||||
|
||||
var contentId: Long = 0
|
||||
var title: String? = null
|
||||
var detail: String? = null
|
||||
var coverImageUri: Uri? = null
|
||||
|
||||
fun setAdult(isAdult: Boolean) {
|
||||
_isAdultLiveData.postValue(isAdult)
|
||||
}
|
||||
|
||||
fun setAvailableComment(isAvailableComment: Boolean) {
|
||||
_isAvailableCommentLiveData.postValue(isAvailableComment)
|
||||
}
|
||||
|
||||
fun getAudioContentDetail(audioContentId: Long, onFailure: (() -> Unit)? = null) {
|
||||
this.contentId = audioContentId
|
||||
_isLoading.value = true
|
||||
|
||||
compositeDisposable.add(
|
||||
repository.getAudioContentDetail(
|
||||
audioContentId = audioContentId,
|
||||
token = "Bearer ${SharedPreferenceManager.token}"
|
||||
)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{
|
||||
if (it.success && it.data != null) {
|
||||
_titleLiveData.value = it.data.title
|
||||
_detailLiveData.value = it.data.detail
|
||||
_coverImageLiveData.value = it.data.coverImageUrl
|
||||
_isAvailableCommentLiveData.value = it.data.isCommentAvailable
|
||||
_isAdultLiveData.value = it.data.isAdult
|
||||
_isAdultShowUiLiveData.value = !it.data.isAdult
|
||||
} else {
|
||||
if (it.message != null) {
|
||||
_toastLiveData.postValue(it.message)
|
||||
} else {
|
||||
_toastLiveData.postValue(
|
||||
"알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||
)
|
||||
}
|
||||
|
||||
if (onFailure != null) {
|
||||
onFailure()
|
||||
}
|
||||
}
|
||||
|
||||
_isLoading.value = false
|
||||
},
|
||||
{
|
||||
_isLoading.value = false
|
||||
it.message?.let { message -> Logger.e(message) }
|
||||
_toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
|
||||
if (onFailure != null) {
|
||||
onFailure()
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun modifyAudioContent(onSuccess: () -> Unit) {
|
||||
if (!_isLoading.value!! && contentId > 0 && validateData()) {
|
||||
_isLoading.value = true
|
||||
|
||||
val request = ModifyAudioContentRequest(
|
||||
contentId = contentId,
|
||||
title = title,
|
||||
detail = detail,
|
||||
isAdult = _isAdultLiveData.value!!,
|
||||
isCommentAvailable = _isAvailableCommentLiveData.value!!
|
||||
)
|
||||
|
||||
val requestJson = Gson().toJson(request)
|
||||
|
||||
val coverImage = if (coverImageUri != null) {
|
||||
val file = File(getRealPathFromURI(coverImageUri!!))
|
||||
MultipartBody.Part.createFormData(
|
||||
"coverImage",
|
||||
file.name,
|
||||
body = object : RequestBody() {
|
||||
override fun contentType(): MediaType {
|
||||
return "image/*".toMediaType()
|
||||
}
|
||||
|
||||
override fun writeTo(sink: BufferedSink) {
|
||||
file.inputStream().use { inputStream ->
|
||||
val buffer = ByteArray(1024)
|
||||
var bytesRead: Int
|
||||
while (inputStream.read(buffer).also { bytesRead = it } != -1) {
|
||||
sink.write(buffer, 0, bytesRead)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun contentLength(): Long {
|
||||
return file.length()
|
||||
}
|
||||
}
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
compositeDisposable.add(
|
||||
repository.modifyAudioContent(
|
||||
coverImage = coverImage,
|
||||
request = requestJson.toRequestBody("text/plain".toMediaType()),
|
||||
token = "Bearer ${SharedPreferenceManager.token}"
|
||||
)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{
|
||||
if (it.success) {
|
||||
_toastLiveData.postValue("수정되었습니다.")
|
||||
onSuccess()
|
||||
} else {
|
||||
if (it.message != null) {
|
||||
_toastLiveData.postValue(it.message)
|
||||
} else {
|
||||
_toastLiveData.postValue(
|
||||
"알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||
)
|
||||
}
|
||||
}
|
||||
_isLoading.postValue(false)
|
||||
},
|
||||
{
|
||||
_isLoading.postValue(false)
|
||||
it.message?.let { message -> Logger.e(message) }
|
||||
_toastLiveData.postValue(
|
||||
"알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||
)
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun validateData(): Boolean {
|
||||
if (title != null && title!!.isBlank()) {
|
||||
_toastLiveData.postValue("제목을 입력해 주세요.")
|
||||
return false
|
||||
}
|
||||
|
||||
if (detail != null && (detail!!.isBlank() || detail!!.length < 5)) {
|
||||
_toastLiveData.postValue("내용을 5자 이상 입력해 주세요.")
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.modify
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class ModifyAudioContentRequest(
|
||||
@SerializedName("contentId") val contentId: Long,
|
||||
@SerializedName("title") val title: String?,
|
||||
@SerializedName("detail") val detail: String?,
|
||||
@SerializedName("isAdult") val isAdult: Boolean,
|
||||
@SerializedName("isCommentAvailable") val isCommentAvailable: Boolean
|
||||
)
|
|
@ -0,0 +1,100 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.order
|
||||
|
||||
import android.app.Activity
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.WindowManager
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import coil.load
|
||||
import coil.transform.CircleCropTransformation
|
||||
import coil.transform.RoundedCornersTransformation
|
||||
import kr.co.vividnext.sodalive.R
|
||||
import kr.co.vividnext.sodalive.databinding.DialogAudioContentOrderConfirmBinding
|
||||
import kr.co.vividnext.sodalive.extensions.dpToPx
|
||||
import kotlin.math.ceil
|
||||
|
||||
class AudioContentOrderConfirmDialog(
|
||||
activity: Activity,
|
||||
layoutInflater: LayoutInflater,
|
||||
title: String,
|
||||
theme: String,
|
||||
coverImageUrl: String,
|
||||
isAdult: Boolean,
|
||||
profileImageUrl: String,
|
||||
nickname: String,
|
||||
duration: String,
|
||||
orderType: OrderType,
|
||||
price: Int,
|
||||
confirmButtonClick: () -> Unit,
|
||||
) {
|
||||
|
||||
private val alertDialog: AlertDialog
|
||||
|
||||
val dialogView = DialogAudioContentOrderConfirmBinding.inflate(layoutInflater)
|
||||
|
||||
init {
|
||||
val dialogBuilder = AlertDialog.Builder(activity)
|
||||
dialogBuilder.setView(dialogView.root)
|
||||
|
||||
alertDialog = dialogBuilder.create()
|
||||
alertDialog.setCancelable(false)
|
||||
alertDialog.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
|
||||
|
||||
dialogView.tvTitle.text = title
|
||||
dialogView.tvTheme.text = theme
|
||||
dialogView.tvProfileNickname.text = nickname
|
||||
|
||||
dialogView.ivCover.load(coverImageUrl) {
|
||||
crossfade(true)
|
||||
placeholder(R.drawable.ic_logo)
|
||||
transformations(RoundedCornersTransformation(4f))
|
||||
}
|
||||
|
||||
dialogView.ivProfile.load(profileImageUrl) {
|
||||
crossfade(true)
|
||||
placeholder(R.drawable.ic_logo)
|
||||
transformations(CircleCropTransformation())
|
||||
}
|
||||
|
||||
dialogView.tvDuration.text = duration
|
||||
dialogView.tvPrice.text = if (orderType == OrderType.RENTAL) {
|
||||
"${ceil(price * 0.7).toInt()}"
|
||||
} else {
|
||||
"$price"
|
||||
}
|
||||
|
||||
dialogView.iv19.visibility = if (isAdult) {
|
||||
View.VISIBLE
|
||||
} else {
|
||||
View.GONE
|
||||
}
|
||||
|
||||
dialogView.tvNotice.text = if (orderType == OrderType.RENTAL) {
|
||||
"콘텐츠를 대여하시겠습니까?\n아래 코인이 차감됩니다."
|
||||
} else {
|
||||
"콘텐츠를 소장하시겠습니까?\n아래 코인이 차감됩니다."
|
||||
}
|
||||
|
||||
dialogView.tvCancel.setOnClickListener {
|
||||
alertDialog.dismiss()
|
||||
}
|
||||
|
||||
dialogView.tvConfirm.setOnClickListener {
|
||||
alertDialog.dismiss()
|
||||
confirmButtonClick()
|
||||
}
|
||||
}
|
||||
|
||||
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?.attributes = lp
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.order
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
import kr.co.vividnext.sodalive.databinding.FragmentAudioContentOrderBinding
|
||||
import kotlin.math.ceil
|
||||
|
||||
class AudioContentOrderFragment(
|
||||
private val price: Int,
|
||||
private val onClickRental: () -> Unit,
|
||||
private val onClickKeep: () -> Unit
|
||||
) : BottomSheetDialogFragment() {
|
||||
|
||||
private lateinit var binding: FragmentAudioContentOrderBinding
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
binding = FragmentAudioContentOrderBinding.inflate(inflater, container, false)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
binding.tvKeep.text = "$price"
|
||||
binding.tvRental.text = "${ceil(price * 0.7).toInt()}"
|
||||
|
||||
binding.llKeep.setOnClickListener {
|
||||
onClickKeep()
|
||||
dismiss()
|
||||
}
|
||||
|
||||
binding.llRental.setOnClickListener {
|
||||
onClickRental()
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,110 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.order
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Intent
|
||||
import android.graphics.Rect
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.widget.Toast
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import kr.co.vividnext.sodalive.audio_content.detail.AudioContentDetailActivity
|
||||
import kr.co.vividnext.sodalive.base.BaseActivity
|
||||
import kr.co.vividnext.sodalive.common.Constants
|
||||
import kr.co.vividnext.sodalive.common.LoadingDialog
|
||||
import kr.co.vividnext.sodalive.databinding.ActivityAudioContentOrderListBinding
|
||||
import kr.co.vividnext.sodalive.extensions.dpToPx
|
||||
import org.koin.android.ext.android.inject
|
||||
|
||||
class AudioContentOrderListActivity : BaseActivity<ActivityAudioContentOrderListBinding>(
|
||||
ActivityAudioContentOrderListBinding::inflate
|
||||
) {
|
||||
|
||||
private val viewModel: AudioContentOrderListViewModel by inject()
|
||||
|
||||
private lateinit var loadingDialog: LoadingDialog
|
||||
private lateinit var adapter: AudioContentOrderListAdapter
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
bindData()
|
||||
viewModel.getAudioContentOrderList { finish() }
|
||||
}
|
||||
|
||||
override fun setupView() {
|
||||
loadingDialog = LoadingDialog(this, layoutInflater)
|
||||
binding.toolbar.tvBack.text = "구매목록"
|
||||
binding.toolbar.tvBack.setOnClickListener { finish() }
|
||||
|
||||
adapter = AudioContentOrderListAdapter {
|
||||
startActivity(
|
||||
Intent(applicationContext, AudioContentDetailActivity::class.java)
|
||||
.apply { putExtra(Constants.EXTRA_AUDIO_CONTENT_ID, it) }
|
||||
)
|
||||
}
|
||||
|
||||
binding.rvOrderList.layoutManager = LinearLayoutManager(
|
||||
applicationContext,
|
||||
LinearLayoutManager.VERTICAL,
|
||||
false
|
||||
)
|
||||
|
||||
binding.rvOrderList.addItemDecoration(object : RecyclerView.ItemDecoration() {
|
||||
override fun getItemOffsets(
|
||||
outRect: Rect,
|
||||
view: View,
|
||||
parent: RecyclerView,
|
||||
state: RecyclerView.State
|
||||
) {
|
||||
super.getItemOffsets(outRect, view, parent, state)
|
||||
|
||||
outRect.left = 13.3f.dpToPx().toInt()
|
||||
outRect.right = 13.3f.dpToPx().toInt()
|
||||
|
||||
when (parent.getChildAdapterPosition(view)) {
|
||||
0 -> {
|
||||
outRect.top = 13.3f.dpToPx().toInt()
|
||||
outRect.bottom = 6.7f.dpToPx().toInt()
|
||||
}
|
||||
|
||||
adapter.itemCount - 1 -> {
|
||||
outRect.top = 6.7f.dpToPx().toInt()
|
||||
outRect.bottom = 13.3f.dpToPx().toInt()
|
||||
}
|
||||
|
||||
else -> {
|
||||
outRect.top = 6.7f.dpToPx().toInt()
|
||||
outRect.bottom = 6.7f.dpToPx().toInt()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
binding.rvOrderList.adapter = adapter
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
private fun bindData() {
|
||||
viewModel.toastLiveData.observe(this) {
|
||||
it?.let { Toast.makeText(applicationContext, it, Toast.LENGTH_LONG).show() }
|
||||
}
|
||||
|
||||
viewModel.isLoading.observe(this) {
|
||||
if (it) {
|
||||
loadingDialog.show(screenWidth, "")
|
||||
} else {
|
||||
loadingDialog.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.orderList.observe(this) {
|
||||
if (viewModel.page == 2) {
|
||||
adapter.items.clear()
|
||||
}
|
||||
|
||||
adapter.items.addAll(it)
|
||||
adapter.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.order
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import coil.load
|
||||
import coil.transform.RoundedCornersTransformation
|
||||
import kr.co.vividnext.sodalive.R
|
||||
import kr.co.vividnext.sodalive.databinding.ItemAudioContentOrderListBinding
|
||||
import kr.co.vividnext.sodalive.extensions.dpToPx
|
||||
import kr.co.vividnext.sodalive.extensions.moneyFormat
|
||||
|
||||
class AudioContentOrderListAdapter(
|
||||
private val onItemClick: (Long) -> Unit
|
||||
) : RecyclerView.Adapter<AudioContentOrderListAdapter.ViewHolder>() {
|
||||
|
||||
var items = mutableSetOf<GetAudioContentOrderListItem>()
|
||||
|
||||
inner class ViewHolder(
|
||||
private val binding: ItemAudioContentOrderListBinding
|
||||
) : RecyclerView.ViewHolder(binding.root) {
|
||||
fun bind(item: GetAudioContentOrderListItem) {
|
||||
binding.ivCover.load(item.coverImageUrl) {
|
||||
crossfade(true)
|
||||
placeholder(R.drawable.ic_logo)
|
||||
transformations(RoundedCornersTransformation(5.3f.dpToPx()))
|
||||
}
|
||||
|
||||
binding.tvTitle.text = item.title
|
||||
binding.tvTheme.text = item.themeStr
|
||||
binding.tvDuration.text = item.duration
|
||||
binding.tvLikeCount.text = item.likeCount.moneyFormat()
|
||||
binding.tvCommentCount.text = item.commentCount.moneyFormat()
|
||||
|
||||
if (item.orderType == OrderType.RENTAL) {
|
||||
binding.tvRental.visibility = View.VISIBLE
|
||||
binding.tvPurchased.visibility = View.GONE
|
||||
} else {
|
||||
binding.tvPurchased.visibility = View.VISIBLE
|
||||
binding.tvRental.visibility = View.GONE
|
||||
}
|
||||
|
||||
binding.root.setOnClickListener { onItemClick(item.contentId) }
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder(
|
||||
ItemAudioContentOrderListBinding.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
parent,
|
||||
false
|
||||
)
|
||||
)
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
holder.bind(items.toList()[position])
|
||||
}
|
||||
|
||||
override fun getItemCount() = items.size
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.order
|
||||
|
||||
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.audio_content.AudioContentRepository
|
||||
import kr.co.vividnext.sodalive.base.BaseViewModel
|
||||
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
|
||||
|
||||
class AudioContentOrderListViewModel(
|
||||
private val repository: AudioContentRepository
|
||||
) : BaseViewModel() {
|
||||
|
||||
private val _toastLiveData = MutableLiveData<String?>()
|
||||
val toastLiveData: LiveData<String?>
|
||||
get() = _toastLiveData
|
||||
|
||||
private var _isLoading = MutableLiveData(false)
|
||||
val isLoading: LiveData<Boolean>
|
||||
get() = _isLoading
|
||||
|
||||
private var _orderList = MutableLiveData<List<GetAudioContentOrderListItem>>()
|
||||
val orderList: LiveData<List<GetAudioContentOrderListItem>>
|
||||
get() = _orderList
|
||||
|
||||
private var isLast = false
|
||||
var page = 1
|
||||
private val size = 10
|
||||
|
||||
fun getAudioContentOrderList(onFailure: (() -> Unit)? = null) {
|
||||
_isLoading.value = true
|
||||
compositeDisposable.add(
|
||||
repository.getAudioContentOrderList(
|
||||
page = page,
|
||||
size = size,
|
||||
token = "Bearer ${SharedPreferenceManager.token}"
|
||||
)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{
|
||||
if (it.success && it.data != null) {
|
||||
if (it.data.items.isNotEmpty()) {
|
||||
page += 1
|
||||
_orderList.postValue(it.data.items)
|
||||
} else {
|
||||
isLast = true
|
||||
}
|
||||
} else {
|
||||
if (it.message != null) {
|
||||
_toastLiveData.postValue(it.message)
|
||||
} else {
|
||||
_toastLiveData.postValue(
|
||||
"알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||
)
|
||||
}
|
||||
|
||||
if (onFailure != null) {
|
||||
onFailure()
|
||||
}
|
||||
}
|
||||
_isLoading.value = false
|
||||
},
|
||||
{
|
||||
_isLoading.value = false
|
||||
it.message?.let { message -> Logger.e(message) }
|
||||
_toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
|
||||
if (onFailure != null) {
|
||||
onFailure()
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.order
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class GetAudioContentOrderListResponse(
|
||||
@SerializedName("totalCount") val totalCount: Int,
|
||||
@SerializedName("items") val items: List<GetAudioContentOrderListItem>
|
||||
)
|
||||
|
||||
data class GetAudioContentOrderListItem(
|
||||
@SerializedName("contentId") val contentId: Long,
|
||||
@SerializedName("coverImageUrl") val coverImageUrl: String,
|
||||
@SerializedName("title") val title: String,
|
||||
@SerializedName("themeStr") val themeStr: String,
|
||||
@SerializedName("duration") val duration: String?,
|
||||
@SerializedName("isAdult") val isAdult: Boolean,
|
||||
@SerializedName("orderType") val orderType: OrderType,
|
||||
@SerializedName("likeCount") val likeCount: Int,
|
||||
@SerializedName("commentCount") val commentCount: Int,
|
||||
)
|
|
@ -0,0 +1,9 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.order
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class OrderRequest(
|
||||
@SerializedName("contentId") val contentId: Long,
|
||||
@SerializedName("orderType") val orderType: OrderType,
|
||||
@SerializedName("container") val container: String
|
||||
)
|
|
@ -0,0 +1,11 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.order
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
enum class OrderType {
|
||||
@SerializedName("RENTAL")
|
||||
RENTAL,
|
||||
|
||||
@SerializedName("KEEP")
|
||||
KEEP
|
||||
}
|
|
@ -0,0 +1,435 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.upload
|
||||
|
||||
import android.Manifest
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.provider.OpenableColumns
|
||||
import android.view.View
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.core.content.ContextCompat
|
||||
import coil.load
|
||||
import coil.transform.CircleCropTransformation
|
||||
import coil.transform.RoundedCornersTransformation
|
||||
import com.github.dhaval2404.imagepicker.ImagePicker
|
||||
import com.gun0912.tedpermission.PermissionListener
|
||||
import com.gun0912.tedpermission.normal.TedPermission
|
||||
import com.jakewharton.rxbinding4.widget.textChanges
|
||||
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.upload.theme.AudioContentThemeFragment
|
||||
import kr.co.vividnext.sodalive.base.BaseActivity
|
||||
import kr.co.vividnext.sodalive.common.LoadingDialog
|
||||
import kr.co.vividnext.sodalive.common.RealPathUtil
|
||||
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
|
||||
import kr.co.vividnext.sodalive.databinding.ActivityAudioContentUploadBinding
|
||||
import kr.co.vividnext.sodalive.dialog.LiveDialog
|
||||
import kr.co.vividnext.sodalive.extensions.dpToPx
|
||||
import org.koin.android.ext.android.inject
|
||||
import java.io.File
|
||||
|
||||
class AudioContentUploadActivity : BaseActivity<ActivityAudioContentUploadBinding>(
|
||||
ActivityAudioContentUploadBinding::inflate
|
||||
) {
|
||||
|
||||
private val viewModel: AudioContentUploadViewModel by inject()
|
||||
|
||||
private lateinit var loadingDialog: LoadingDialog
|
||||
|
||||
private val themeFragment: AudioContentThemeFragment by lazy {
|
||||
AudioContentThemeFragment(
|
||||
getSelectedTheme = { viewModel.theme },
|
||||
onItemClick = {
|
||||
binding.ivTheme.load(it.image) {
|
||||
crossfade(true)
|
||||
placeholder(R.drawable.bg_placeholder)
|
||||
transformations(CircleCropTransformation())
|
||||
|
||||
binding.ivTheme.visibility = View.VISIBLE
|
||||
}
|
||||
binding.tvTheme.text = it.theme
|
||||
viewModel.theme = it
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private val imageResult = registerForActivityResult(
|
||||
ActivityResultContracts.StartActivityForResult()
|
||||
) { result ->
|
||||
val resultCode = result.resultCode
|
||||
val data = result.data
|
||||
|
||||
if (resultCode == RESULT_OK) {
|
||||
val fileUri = data?.data
|
||||
|
||||
if (fileUri != null) {
|
||||
binding.ivCover.background = null
|
||||
binding.ivCover.load(fileUri) {
|
||||
crossfade(true)
|
||||
placeholder(R.drawable.ic_logo)
|
||||
transformations(RoundedCornersTransformation(13.3f.dpToPx()))
|
||||
}
|
||||
viewModel.coverImageUri = fileUri
|
||||
} else {
|
||||
Toast.makeText(
|
||||
this,
|
||||
"잘못된 파일입니다.\n다시 선택해 주세요.",
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
} else if (resultCode == ImagePicker.RESULT_ERROR) {
|
||||
Toast.makeText(this, ImagePicker.getError(data), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
private val selectAudioActivityResultLauncher = registerForActivityResult(
|
||||
ActivityResultContracts.StartActivityForResult()
|
||||
) { result ->
|
||||
val resultCode = result.resultCode
|
||||
val data = result.data
|
||||
|
||||
if (resultCode == RESULT_OK) {
|
||||
val fileUri = data?.data
|
||||
if (fileUri != null) {
|
||||
binding.tvSelectContent.text = getFileName(fileUri)
|
||||
viewModel.contentUri = fileUri
|
||||
} else {
|
||||
Toast.makeText(
|
||||
this,
|
||||
"잘못된 파일입니다.\n다시 선택해 주세요.",
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
} else if (resultCode == ImagePicker.RESULT_ERROR) {
|
||||
binding.tvSelectContent.text = "파일 선택"
|
||||
viewModel.contentUri = null
|
||||
Toast.makeText(this, ImagePicker.getError(data), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
checkPermissions()
|
||||
|
||||
viewModel.getRealPathFromURI = {
|
||||
RealPathUtil.getRealPath(applicationContext, it)
|
||||
}
|
||||
|
||||
bindData()
|
||||
}
|
||||
|
||||
override fun setupView() {
|
||||
loadingDialog = LoadingDialog(this, layoutInflater)
|
||||
|
||||
binding.toolbar.tvBack.text = "콘텐츠 등록"
|
||||
binding.toolbar.tvBack.setOnClickListener { finish() }
|
||||
binding.llTheme.setOnClickListener {
|
||||
if (themeFragment.isAdded) return@setOnClickListener
|
||||
|
||||
themeFragment.show(supportFragmentManager, themeFragment.tag)
|
||||
}
|
||||
|
||||
binding.ivPhotoPicker.setOnClickListener {
|
||||
ImagePicker.with(this)
|
||||
.crop()
|
||||
.galleryOnly()
|
||||
.galleryMimeTypes( // Exclude gif images
|
||||
mimeTypes = arrayOf(
|
||||
"image/png",
|
||||
"image/jpg",
|
||||
"image/jpeg"
|
||||
)
|
||||
)
|
||||
.createIntent { imageResult.launch(it) }
|
||||
}
|
||||
|
||||
binding.tvSelectContent.setOnClickListener {
|
||||
val intent = Intent().apply {
|
||||
type = "audio/*"
|
||||
action = Intent.ACTION_GET_CONTENT
|
||||
addCategory(Intent.CATEGORY_OPENABLE)
|
||||
}
|
||||
selectAudioActivityResultLauncher.launch(
|
||||
Intent.createChooser(
|
||||
intent,
|
||||
"Select Audio"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
if (SharedPreferenceManager.isAuth) {
|
||||
binding.llSetAdult.visibility = View.VISIBLE
|
||||
} else {
|
||||
binding.llSetAdult.visibility = View.GONE
|
||||
}
|
||||
|
||||
binding.llPricePaid.setOnClickListener { viewModel.setPriceFree(false) }
|
||||
binding.llPriceFree.setOnClickListener { viewModel.setPriceFree(true) }
|
||||
binding.llCommentNo.setOnClickListener { viewModel.setAvailableComment(false) }
|
||||
binding.llCommentYes.setOnClickListener { viewModel.setAvailableComment(true) }
|
||||
|
||||
binding.tvCancel.setOnClickListener { finish() }
|
||||
binding.tvUpload.setOnClickListener {
|
||||
viewModel.uploadAudioContent {
|
||||
LiveDialog(
|
||||
activity = this,
|
||||
layoutInflater = layoutInflater,
|
||||
title = "콘텐츠 업로드",
|
||||
desc = "등록한 콘텐츠가 업로드 중입니다.\n" +
|
||||
"콘텐츠 등록이 완료되면 알림을 보내드립니다.\n" +
|
||||
"이 페이지를 나가도 콘텐츠는 자동으로 등록됩니다.",
|
||||
confirmButtonTitle = "확인",
|
||||
confirmButtonClick = { finish() },
|
||||
).show(screenWidth)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkPermissions() {
|
||||
val permissions = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
listOf(Manifest.permission.READ_MEDIA_AUDIO, Manifest.permission.READ_MEDIA_IMAGES)
|
||||
} else {
|
||||
listOf(Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||
}
|
||||
|
||||
TedPermission.create()
|
||||
.setPermissionListener(object : PermissionListener {
|
||||
override fun onPermissionGranted() {
|
||||
}
|
||||
|
||||
override fun onPermissionDenied(deniedPermissions: MutableList<String>?) {
|
||||
finish()
|
||||
}
|
||||
})
|
||||
.setDeniedMessage(R.string.read_storage_permission_denied_message)
|
||||
.setPermissions(*permissions.toTypedArray())
|
||||
.check()
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
private fun bindData() {
|
||||
compositeDisposable.add(
|
||||
binding.etTitle.textChanges().skip(1)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe {
|
||||
viewModel.title = it.toString()
|
||||
}
|
||||
)
|
||||
|
||||
compositeDisposable.add(
|
||||
binding.etDetail.textChanges().skip(1)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe {
|
||||
binding.tvNumberOfCharacters.text = "${it.length}자"
|
||||
viewModel.detail = it.toString()
|
||||
}
|
||||
)
|
||||
|
||||
compositeDisposable.add(
|
||||
binding.etTag.textChanges().skip(1)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe {
|
||||
viewModel.tags = it.toString()
|
||||
}
|
||||
)
|
||||
|
||||
compositeDisposable.add(
|
||||
binding.etSetPrice.textChanges().skip(1)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe {
|
||||
val price = it.toString().toIntOrNull()
|
||||
if (price != null) {
|
||||
viewModel.price = price.toInt()
|
||||
} else {
|
||||
viewModel.price = 0
|
||||
if (it.isNotBlank()) {
|
||||
binding.etSetPrice.setText(it.substring(0, it.length - 1))
|
||||
binding.etSetPrice.setSelection(it.length - 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
viewModel.toastLiveData.observe(this) {
|
||||
it?.let { Toast.makeText(applicationContext, it, Toast.LENGTH_LONG).show() }
|
||||
}
|
||||
|
||||
viewModel.isLoading.observe(this) {
|
||||
if (it) {
|
||||
loadingDialog.show(screenWidth, "콘텐츠를 업로드 하는 중입니다.")
|
||||
} else {
|
||||
loadingDialog.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.isPriceFreeLiveData.observe(this) {
|
||||
if (it) {
|
||||
viewModel.price = 0
|
||||
binding.etSetPrice.setText("0")
|
||||
binding.llSetPrice.visibility = View.GONE
|
||||
|
||||
binding.ivPriceFree.visibility = View.VISIBLE
|
||||
binding.tvPriceFree.setTextColor(
|
||||
ContextCompat.getColor(
|
||||
applicationContext,
|
||||
R.color.color_eeeeee
|
||||
)
|
||||
)
|
||||
binding.llPriceFree.setBackgroundResource(R.drawable.bg_round_corner_6_7_9970ff)
|
||||
|
||||
binding.ivPricePaid.visibility = View.GONE
|
||||
binding.tvPricePaid.setTextColor(
|
||||
ContextCompat.getColor(
|
||||
applicationContext,
|
||||
R.color.color_9970ff
|
||||
)
|
||||
)
|
||||
binding.llPricePaid.setBackgroundResource(
|
||||
R.drawable.bg_round_corner_6_7_1f1734_9970ff
|
||||
)
|
||||
} else {
|
||||
binding.llSetPrice.visibility = View.VISIBLE
|
||||
|
||||
binding.ivPricePaid.visibility = View.VISIBLE
|
||||
binding.tvPricePaid.setTextColor(
|
||||
ContextCompat.getColor(
|
||||
applicationContext,
|
||||
R.color.color_eeeeee
|
||||
)
|
||||
)
|
||||
binding.llPricePaid.setBackgroundResource(R.drawable.bg_round_corner_6_7_9970ff)
|
||||
|
||||
binding.ivPriceFree.visibility = View.GONE
|
||||
binding.tvPriceFree.setTextColor(
|
||||
ContextCompat.getColor(
|
||||
applicationContext,
|
||||
R.color.color_9970ff
|
||||
)
|
||||
)
|
||||
binding.llPriceFree.setBackgroundResource(
|
||||
R.drawable.bg_round_corner_6_7_1f1734_9970ff
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.isAvailableCommentLiveData.observe(this) {
|
||||
if (it) {
|
||||
binding.ivCommentYes.visibility = View.VISIBLE
|
||||
binding.tvCommentYes.setTextColor(
|
||||
ContextCompat.getColor(
|
||||
applicationContext,
|
||||
R.color.color_eeeeee
|
||||
)
|
||||
)
|
||||
binding.llCommentYes.setBackgroundResource(R.drawable.bg_round_corner_6_7_9970ff)
|
||||
|
||||
binding.ivCommentNo.visibility = View.GONE
|
||||
binding.tvCommentNo.setTextColor(
|
||||
ContextCompat.getColor(
|
||||
applicationContext,
|
||||
R.color.color_9970ff
|
||||
)
|
||||
)
|
||||
binding.llCommentNo.setBackgroundResource(
|
||||
R.drawable.bg_round_corner_6_7_1f1734_9970ff
|
||||
)
|
||||
} else {
|
||||
binding.ivCommentNo.visibility = View.VISIBLE
|
||||
binding.tvCommentNo.setTextColor(
|
||||
ContextCompat.getColor(
|
||||
applicationContext,
|
||||
R.color.color_eeeeee
|
||||
)
|
||||
)
|
||||
binding.llCommentNo.setBackgroundResource(R.drawable.bg_round_corner_6_7_9970ff)
|
||||
|
||||
binding.ivCommentYes.visibility = View.GONE
|
||||
binding.tvCommentYes.setTextColor(
|
||||
ContextCompat.getColor(
|
||||
applicationContext,
|
||||
R.color.color_9970ff
|
||||
)
|
||||
)
|
||||
binding.llCommentYes
|
||||
.setBackgroundResource(R.drawable.bg_round_corner_6_7_1f1734_9970ff)
|
||||
}
|
||||
}
|
||||
|
||||
if (SharedPreferenceManager.isAuth) {
|
||||
binding.llAgeAll.setOnClickListener {
|
||||
viewModel.setAdult(false)
|
||||
}
|
||||
|
||||
binding.llAge19.setOnClickListener {
|
||||
viewModel.setAdult(true)
|
||||
}
|
||||
|
||||
viewModel.isAdultLiveData.observe(this) {
|
||||
if (it) {
|
||||
binding.ivAgeAll.visibility = View.GONE
|
||||
binding.llAgeAll.setBackgroundResource(R.drawable.bg_round_corner_6_7_1f1734)
|
||||
binding.tvAgeAll.setTextColor(
|
||||
ContextCompat.getColor(
|
||||
applicationContext,
|
||||
R.color.color_9970ff
|
||||
)
|
||||
)
|
||||
|
||||
binding.ivAge19.visibility = View.VISIBLE
|
||||
binding.llAge19.setBackgroundResource(R.drawable.bg_round_corner_6_7_9970ff)
|
||||
binding.tvAge19.setTextColor(
|
||||
ContextCompat.getColor(
|
||||
applicationContext,
|
||||
R.color.color_eeeeee
|
||||
)
|
||||
)
|
||||
} else {
|
||||
binding.ivAge19.visibility = View.GONE
|
||||
binding.llAge19.setBackgroundResource(R.drawable.bg_round_corner_6_7_1f1734)
|
||||
binding.tvAge19.setTextColor(
|
||||
ContextCompat.getColor(
|
||||
applicationContext,
|
||||
R.color.color_9970ff
|
||||
)
|
||||
)
|
||||
|
||||
binding.ivAgeAll.visibility = View.VISIBLE
|
||||
binding.llAgeAll.setBackgroundResource(R.drawable.bg_round_corner_6_7_9970ff)
|
||||
binding.tvAgeAll.setTextColor(
|
||||
ContextCompat.getColor(
|
||||
applicationContext,
|
||||
R.color.color_eeeeee
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getFileName(uri: Uri): String? {
|
||||
val scheme = uri.scheme
|
||||
var fileName: String? = null
|
||||
|
||||
if (scheme == "content") {
|
||||
contentResolver.query(uri, null, null, null, null)?.use { cursor ->
|
||||
val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
|
||||
if (nameIndex != -1 && cursor.moveToFirst()) {
|
||||
fileName = cursor.getString(nameIndex)
|
||||
}
|
||||
}
|
||||
} else if (scheme == "file") {
|
||||
val file = File(uri.path ?: "")
|
||||
fileName = file.name
|
||||
}
|
||||
|
||||
return fileName
|
||||
}
|
||||
}
|
|
@ -0,0 +1,221 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.upload
|
||||
|
||||
import android.net.Uri
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import com.google.gson.Gson
|
||||
import com.orhanobut.logger.Logger
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import kr.co.vividnext.sodalive.audio_content.AudioContentRepository
|
||||
import kr.co.vividnext.sodalive.audio_content.upload.theme.GetAudioContentThemeResponse
|
||||
import kr.co.vividnext.sodalive.base.BaseViewModel
|
||||
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
|
||||
import okhttp3.MediaType
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.MultipartBody
|
||||
import okhttp3.RequestBody
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import okio.BufferedSink
|
||||
import java.io.File
|
||||
|
||||
class AudioContentUploadViewModel(
|
||||
private val repository: AudioContentRepository
|
||||
) : BaseViewModel() {
|
||||
|
||||
private val _toastLiveData = MutableLiveData<String?>()
|
||||
val toastLiveData: LiveData<String?>
|
||||
get() = _toastLiveData
|
||||
|
||||
private var _isLoading = MutableLiveData(false)
|
||||
val isLoading: LiveData<Boolean>
|
||||
get() = _isLoading
|
||||
|
||||
private val _isAdultLiveData = MutableLiveData(false)
|
||||
val isAdultLiveData: LiveData<Boolean>
|
||||
get() = _isAdultLiveData
|
||||
|
||||
private val _isAvailableCommentLiveData = MutableLiveData(true)
|
||||
val isAvailableCommentLiveData: LiveData<Boolean>
|
||||
get() = _isAvailableCommentLiveData
|
||||
|
||||
private val _isPriceFreeLiveData = MutableLiveData(true)
|
||||
val isPriceFreeLiveData: LiveData<Boolean>
|
||||
get() = _isPriceFreeLiveData
|
||||
|
||||
lateinit var getRealPathFromURI: (Uri) -> String?
|
||||
|
||||
var title = ""
|
||||
var detail = ""
|
||||
var tags = ""
|
||||
var price = 0
|
||||
var theme: GetAudioContentThemeResponse? = null
|
||||
var coverImageUri: Uri? = null
|
||||
var contentUri: Uri? = null
|
||||
|
||||
fun setAdult(isAdult: Boolean) {
|
||||
_isAdultLiveData.postValue(isAdult)
|
||||
}
|
||||
|
||||
fun setAvailableComment(isAvailableComment: Boolean) {
|
||||
_isAvailableCommentLiveData.postValue(isAvailableComment)
|
||||
}
|
||||
|
||||
fun setPriceFree(isPriceFree: Boolean) {
|
||||
_isPriceFreeLiveData.postValue(isPriceFree)
|
||||
}
|
||||
|
||||
fun uploadAudioContent(onSuccess: () -> Unit) {
|
||||
if (!_isLoading.value!! && validateData()) {
|
||||
_isLoading.postValue(true)
|
||||
|
||||
val request = CreateAudioContentRequest(
|
||||
title = title,
|
||||
detail = detail,
|
||||
tags = tags,
|
||||
price = price,
|
||||
themeId = theme!!.id,
|
||||
isAdult = _isAdultLiveData.value!!,
|
||||
isCommentAvailable = _isAvailableCommentLiveData.value!!
|
||||
)
|
||||
|
||||
val requestJson = Gson().toJson(request)
|
||||
|
||||
val coverImage = if (coverImageUri != null) {
|
||||
val file = File(getRealPathFromURI(coverImageUri!!))
|
||||
MultipartBody.Part.createFormData(
|
||||
"coverImage",
|
||||
file.name,
|
||||
body = object : RequestBody() {
|
||||
override fun contentType(): MediaType {
|
||||
return "image/*".toMediaType()
|
||||
}
|
||||
|
||||
override fun writeTo(sink: BufferedSink) {
|
||||
file.inputStream().use { inputStream ->
|
||||
val buffer = ByteArray(1024)
|
||||
var bytesRead: Int
|
||||
while (inputStream.read(buffer).also { bytesRead = it } != -1) {
|
||||
sink.write(buffer, 0, bytesRead)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun contentLength(): Long {
|
||||
return file.length()
|
||||
}
|
||||
}
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
val contentFile = if (contentUri != null) {
|
||||
val file = File(getRealPathFromURI(contentUri!!))
|
||||
MultipartBody.Part.createFormData(
|
||||
"contentFile",
|
||||
file.name,
|
||||
body = object : RequestBody() {
|
||||
override fun contentType(): MediaType {
|
||||
return "audio/*".toMediaType()
|
||||
}
|
||||
|
||||
override fun writeTo(sink: BufferedSink) {
|
||||
file.inputStream().use { inputStream ->
|
||||
val buffer = ByteArray(512)
|
||||
var bytesRead: Int
|
||||
while (inputStream.read(buffer).also { bytesRead = it } != -1) {
|
||||
sink.write(buffer, 0, bytesRead)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun contentLength(): Long {
|
||||
return file.length()
|
||||
}
|
||||
}
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
if (coverImage == null) {
|
||||
_toastLiveData.postValue("커버이미지를 선택해 주세요.")
|
||||
return
|
||||
}
|
||||
|
||||
if (contentFile == null) {
|
||||
_toastLiveData.postValue("오디오 콘텐츠를 선택해 주세요.")
|
||||
return
|
||||
}
|
||||
|
||||
compositeDisposable.add(
|
||||
repository.uploadAudioContent(
|
||||
coverImage = coverImage,
|
||||
contentFile = contentFile,
|
||||
request = requestJson.toRequestBody("text/plain".toMediaType()),
|
||||
token = "Bearer ${SharedPreferenceManager.token}"
|
||||
)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{
|
||||
if (it.success && it.data != null) {
|
||||
onSuccess()
|
||||
} else {
|
||||
if (it.message != null) {
|
||||
_toastLiveData.postValue(it.message)
|
||||
} else {
|
||||
_toastLiveData.postValue(
|
||||
"알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||
)
|
||||
}
|
||||
}
|
||||
_isLoading.postValue(false)
|
||||
},
|
||||
{
|
||||
_isLoading.postValue(false)
|
||||
it.message?.let { message -> Logger.e(message) }
|
||||
_toastLiveData.postValue(
|
||||
"알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||
)
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun validateData(): Boolean {
|
||||
if (title.isBlank()) {
|
||||
_toastLiveData.postValue("제목을 입력해 주세요.")
|
||||
return false
|
||||
}
|
||||
|
||||
if (detail.isBlank() || detail.length < 5) {
|
||||
_toastLiveData.postValue("내용을 5자 이상 입력해 주세요.")
|
||||
return false
|
||||
}
|
||||
|
||||
if (theme == null) {
|
||||
_toastLiveData.postValue("테마를 선택해 주세요.")
|
||||
return false
|
||||
}
|
||||
|
||||
if (coverImageUri == null) {
|
||||
_toastLiveData.postValue("커버이미지를 선택해 주세요.")
|
||||
return false
|
||||
}
|
||||
|
||||
if (contentUri == null) {
|
||||
_toastLiveData.postValue("오디오 콘텐츠를 선택해 주세요.")
|
||||
return false
|
||||
}
|
||||
|
||||
if (!isPriceFreeLiveData.value!! && price < 10) {
|
||||
_toastLiveData.postValue("콘텐츠의 최소금액은 10코인 입니다.")
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.upload
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class CreateAudioContentRequest(
|
||||
@SerializedName("title") val title: String,
|
||||
@SerializedName("detail") val detail: String,
|
||||
@SerializedName("tags") val tags: String,
|
||||
@SerializedName("price") val price: Int,
|
||||
@SerializedName("themeId") val themeId: Long,
|
||||
@SerializedName("isAdult") val isAdult: Boolean,
|
||||
@SerializedName("isCommentAvailable") val isCommentAvailable: Boolean,
|
||||
)
|
|
@ -0,0 +1,64 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.upload.theme
|
||||
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import coil.load
|
||||
import coil.transform.CircleCropTransformation
|
||||
import kr.co.vividnext.sodalive.R
|
||||
import kr.co.vividnext.sodalive.databinding.ItemAudioContentThemeBinding
|
||||
|
||||
class AudioContentThemeAdapter(
|
||||
private val selectedTheme: GetAudioContentThemeResponse?,
|
||||
private val onItemClick: (GetAudioContentThemeResponse) -> Unit
|
||||
) : RecyclerView.Adapter<AudioContentThemeAdapter.ViewHolder>() {
|
||||
|
||||
inner class ViewHolder(
|
||||
private val context: Context,
|
||||
private val binding: ItemAudioContentThemeBinding
|
||||
) : RecyclerView.ViewHolder(binding.root) {
|
||||
private var isChecked = false
|
||||
|
||||
fun bind(item: GetAudioContentThemeResponse) {
|
||||
if (selectedTheme == item) {
|
||||
binding.ivThemeChecked.visibility = View.VISIBLE
|
||||
binding.tvTheme.setTextColor(ContextCompat.getColor(context, R.color.color_9970ff))
|
||||
isChecked = true
|
||||
} else {
|
||||
binding.ivThemeChecked.visibility = View.GONE
|
||||
binding.tvTheme.setTextColor(ContextCompat.getColor(context, R.color.color_bbbbbb))
|
||||
isChecked = false
|
||||
}
|
||||
|
||||
binding.ivTheme.load(item.image) {
|
||||
crossfade(true)
|
||||
placeholder(R.drawable.ic_logo)
|
||||
transformations(CircleCropTransformation())
|
||||
}
|
||||
|
||||
binding.tvTheme.text = item.theme
|
||||
|
||||
binding.root.setOnClickListener { onItemClick(item) }
|
||||
}
|
||||
}
|
||||
|
||||
val items = mutableSetOf<GetAudioContentThemeResponse>()
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder(
|
||||
parent.context,
|
||||
ItemAudioContentThemeBinding.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
parent,
|
||||
false
|
||||
)
|
||||
)
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
holder.bind(items.toList()[position])
|
||||
}
|
||||
|
||||
override fun getItemCount() = items.size
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.upload.theme
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Dialog
|
||||
import android.graphics.Rect
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.ImageView
|
||||
import android.widget.Toast
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
import kr.co.vividnext.sodalive.R
|
||||
import kr.co.vividnext.sodalive.extensions.dpToPx
|
||||
import org.koin.android.ext.android.inject
|
||||
|
||||
class AudioContentThemeFragment(
|
||||
private val getSelectedTheme: () -> GetAudioContentThemeResponse?,
|
||||
private val onItemClick: (GetAudioContentThemeResponse) -> Unit
|
||||
) : BottomSheetDialogFragment() {
|
||||
|
||||
private val viewModel: AudioContentThemeViewModel by inject()
|
||||
|
||||
private lateinit var adapter: AudioContentThemeAdapter
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
val dialog = super.onCreateDialog(savedInstanceState)
|
||||
|
||||
dialog.setOnShowListener {
|
||||
val d = it as BottomSheetDialog
|
||||
val bottomSheet = d.findViewById<FrameLayout>(
|
||||
com.google.android.material.R.id.design_bottom_sheet
|
||||
)
|
||||
if (bottomSheet != null) {
|
||||
BottomSheetBehavior.from(bottomSheet).state = BottomSheetBehavior.STATE_EXPANDED
|
||||
}
|
||||
}
|
||||
|
||||
return dialog
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View = inflater.inflate(R.layout.fragment_audio_content_theme, container, false)
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
view.findViewById<ImageView>(R.id.iv_close).setOnClickListener {
|
||||
dialog?.dismiss()
|
||||
}
|
||||
|
||||
setupAdapter(view)
|
||||
bindData()
|
||||
|
||||
viewModel.getThemes()
|
||||
}
|
||||
|
||||
private fun setupAdapter(view: View) {
|
||||
val recyclerView = view.findViewById<RecyclerView>(R.id.rv_themes)
|
||||
adapter = AudioContentThemeAdapter(getSelectedTheme()) {
|
||||
onItemClick(it)
|
||||
dialog?.dismiss()
|
||||
}
|
||||
|
||||
recyclerView.setHasFixedSize(true)
|
||||
recyclerView.layoutManager = GridLayoutManager(requireContext(), 4)
|
||||
recyclerView.addItemDecoration(object : RecyclerView.ItemDecoration() {
|
||||
override fun getItemOffsets(
|
||||
outRect: Rect,
|
||||
view: View,
|
||||
parent: RecyclerView,
|
||||
state: RecyclerView.State
|
||||
) {
|
||||
super.getItemOffsets(outRect, view, parent, state)
|
||||
|
||||
outRect.top = 13.3f.dpToPx().toInt()
|
||||
outRect.bottom = 13.3f.dpToPx().toInt()
|
||||
outRect.left = 13.3f.dpToPx().toInt()
|
||||
outRect.right = 13.3f.dpToPx().toInt()
|
||||
}
|
||||
})
|
||||
recyclerView.adapter = adapter
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
private fun bindData() {
|
||||
viewModel.toastLiveData.observe(viewLifecycleOwner) {
|
||||
it?.let { Toast.makeText(requireActivity(), it, Toast.LENGTH_LONG).show() }
|
||||
}
|
||||
|
||||
viewModel.themeLiveData.observe(viewLifecycleOwner) {
|
||||
adapter.items.addAll(it)
|
||||
adapter.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.upload.theme
|
||||
|
||||
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.audio_content.AudioContentRepository
|
||||
import kr.co.vividnext.sodalive.base.BaseViewModel
|
||||
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
|
||||
|
||||
class AudioContentThemeViewModel(private val repository: AudioContentRepository) : BaseViewModel() {
|
||||
private val _toastLiveData = MutableLiveData<String?>()
|
||||
val toastLiveData: LiveData<String?>
|
||||
get() = _toastLiveData
|
||||
|
||||
private val _themeLiveData = MutableLiveData<List<GetAudioContentThemeResponse>>()
|
||||
val themeLiveData: LiveData<List<GetAudioContentThemeResponse>>
|
||||
get() = _themeLiveData
|
||||
|
||||
fun getThemes() {
|
||||
compositeDisposable.add(
|
||||
repository.getAudioContentThemeList(token = "Bearer ${SharedPreferenceManager.token}")
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{
|
||||
if (it.success && it.data != null) {
|
||||
_themeLiveData.postValue(it.data!!)
|
||||
} else {
|
||||
if (it.message != null) {
|
||||
_toastLiveData.postValue(it.message)
|
||||
} else {
|
||||
_toastLiveData.postValue(
|
||||
"알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
it.message?.let { message -> Logger.e(message) }
|
||||
_toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package kr.co.vividnext.sodalive.audio_content.upload.theme
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class GetAudioContentThemeResponse(
|
||||
@SerializedName("id") val id: Long,
|
||||
@SerializedName("theme") val theme: String,
|
||||
@SerializedName("image") val image: String
|
||||
)
|
|
@ -10,7 +10,9 @@ object Constants {
|
|||
const val PREF_USER_ROLE = "pref_user_role"
|
||||
const val PREF_PUSH_TOKEN = "pref_push_token"
|
||||
const val PREF_PROFILE_IMAGE = "pref_profile_image"
|
||||
const val PREF_IS_CONTENT_PLAY_LOOP = "pref_is_content_play_loop"
|
||||
const val PREF_IS_FOLLOWED_CREATOR_LIVE = "pref_is_followed_creator_live"
|
||||
const val PREF_NOT_SHOWING_EVENT_POPUP_ID = "pref_not_showing_event_popup_id"
|
||||
|
||||
const val EXTRA_CAN = "extra_can"
|
||||
const val EXTRA_DATA = "extra_data"
|
||||
|
@ -30,7 +32,24 @@ object Constants {
|
|||
const val EXTRA_ROOM_CHANNEL_NAME = "extra_room_channel_name"
|
||||
const val EXTRA_LIVE_RESERVATION_RESPONSE = "extra_live_reservation_response"
|
||||
|
||||
const val EXTRA_CONTENT_ID = "extra_content_id"
|
||||
const val EXTRA_AUDIO_CONTENT_ID = "audio_content_id"
|
||||
const val EXTRA_AUDIO_CONTENT_URL = "audio_content_url"
|
||||
const val EXTRA_AUDIO_CONTENT_TITLE = "audio_content_title"
|
||||
const val EXTRA_AUDIO_CONTENT_FREE = "audio_content_is_free"
|
||||
const val EXTRA_AUDIO_CONTENT_PREVIEW = "audio_content_is_preview"
|
||||
const val EXTRA_AUDIO_CONTENT_PLAYING = "audio_content_is_playing"
|
||||
const val EXTRA_AUDIO_CONTENT_SHOWING = "audio_content_is_showing"
|
||||
const val EXTRA_AUDIO_CONTENT_CHANGE_UI = "audio_content_change_ui"
|
||||
const val EXTRA_AUDIO_CONTENT_PROGRESS = "audio_content_progress"
|
||||
const val EXTRA_AUDIO_CONTENT_DURATION = "audio_content_duration"
|
||||
const val EXTRA_AUDIO_CONTENT_COMMENT = "audio_content_comment"
|
||||
const val EXTRA_AUDIO_CONTENT_LOADING = "audio_content_loading"
|
||||
const val EXTRA_AUDIO_CONTENT_CREATOR_ID = "audio_content_creator_id"
|
||||
const val EXTRA_AUDIO_CONTENT_NEXT_ACTION = "audio_content_next_action"
|
||||
const val EXTRA_AUDIO_CONTENT_ALERT_PREVIEW = "audio_content_alert_preview"
|
||||
const val EXTRA_AUDIO_CONTENT_COVER_IMAGE_URL = "audio_content_cover_image_url"
|
||||
|
||||
const val LIVE_SERVICE_NOTIFICATION_ID: Int = 2
|
||||
const val ACTION_AUDIO_CONTENT_RECEIVER = "soda_live_action_content_receiver"
|
||||
const val ACTION_MAIN_AUDIO_CONTENT_RECEIVER = "soda_live_action_main_content_receiver"
|
||||
}
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
package kr.co.vividnext.sodalive.common
|
||||
|
||||
import android.content.Context
|
||||
import io.objectbox.BoxStore
|
||||
import kr.co.vividnext.sodalive.audio_content.MyObjectBox
|
||||
import kr.co.vividnext.sodalive.audio_content.PlaybackTracking
|
||||
|
||||
class ObjectBox(context: Context) {
|
||||
private var store: BoxStore = MyObjectBox.builder()
|
||||
.androidContext(context.applicationContext)
|
||||
.build()
|
||||
|
||||
val playbackTrackingBox = store.boxFor(PlaybackTracking::class.java)
|
||||
}
|
|
@ -104,4 +104,16 @@ object SharedPreferenceManager {
|
|||
set(value) {
|
||||
sharedPreferences[Constants.PREF_IS_FOLLOWED_CREATOR_LIVE] = value
|
||||
}
|
||||
|
||||
var isContentPlayLoop: Boolean
|
||||
get() = sharedPreferences[Constants.PREF_IS_CONTENT_PLAY_LOOP, false]
|
||||
set(value) {
|
||||
sharedPreferences[Constants.PREF_IS_CONTENT_PLAY_LOOP] = value
|
||||
}
|
||||
|
||||
var notShowingEventPopupId: Long
|
||||
get() = sharedPreferences[Constants.PREF_NOT_SHOWING_EVENT_POPUP_ID, 0]
|
||||
set(value) {
|
||||
sharedPreferences[Constants.PREF_NOT_SHOWING_EVENT_POPUP_ID] = value
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
package kr.co.vividnext.sodalive.common
|
||||
|
||||
object Utils {
|
||||
fun convertDurationToString(duration: Int): String {
|
||||
val durationSeconds = duration / 1000
|
||||
val hours = (durationSeconds / 3600)
|
||||
val minutes = ((durationSeconds % 3600) / 60)
|
||||
val seconds = (durationSeconds % 60)
|
||||
|
||||
return "%02d:%02d:%02d".format(hours, minutes, seconds)
|
||||
}
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
package kr.co.vividnext.sodalive.content.main
|
||||
|
||||
import kr.co.vividnext.sodalive.base.BaseFragment
|
||||
import kr.co.vividnext.sodalive.databinding.FragmentContentMainBinding
|
||||
|
||||
class ContentMainFragment : BaseFragment<FragmentContentMainBinding>(
|
||||
FragmentContentMainBinding::inflate
|
||||
) {
|
||||
}
|
|
@ -3,6 +3,18 @@ package kr.co.vividnext.sodalive.di
|
|||
import android.content.Context
|
||||
import com.google.gson.GsonBuilder
|
||||
import kr.co.vividnext.sodalive.BuildConfig
|
||||
import kr.co.vividnext.sodalive.audio_content.AudioContentApi
|
||||
import kr.co.vividnext.sodalive.audio_content.AudioContentRepository
|
||||
import kr.co.vividnext.sodalive.audio_content.AudioContentViewModel
|
||||
import kr.co.vividnext.sodalive.audio_content.comment.AudioContentCommentListViewModel
|
||||
import kr.co.vividnext.sodalive.audio_content.comment.AudioContentCommentReplyViewModel
|
||||
import kr.co.vividnext.sodalive.audio_content.comment.AudioContentCommentRepository
|
||||
import kr.co.vividnext.sodalive.audio_content.detail.AudioContentDetailViewModel
|
||||
import kr.co.vividnext.sodalive.audio_content.main.AudioContentMainViewModel
|
||||
import kr.co.vividnext.sodalive.audio_content.modify.AudioContentModifyViewModel
|
||||
import kr.co.vividnext.sodalive.audio_content.order.AudioContentOrderListViewModel
|
||||
import kr.co.vividnext.sodalive.audio_content.upload.AudioContentUploadViewModel
|
||||
import kr.co.vividnext.sodalive.audio_content.upload.theme.AudioContentThemeViewModel
|
||||
import kr.co.vividnext.sodalive.common.ApiBuilder
|
||||
import kr.co.vividnext.sodalive.explorer.ExplorerApi
|
||||
import kr.co.vividnext.sodalive.explorer.ExplorerRepository
|
||||
|
@ -112,6 +124,7 @@ class AppDI(private val context: Context, isDebugMode: Boolean) {
|
|||
single { ApiBuilder().build(get(), ExplorerApi::class.java) }
|
||||
single { ApiBuilder().build(get(), MessageApi::class.java) }
|
||||
single { ApiBuilder().build(get(), NoticeApi::class.java) }
|
||||
single { ApiBuilder().build(get(), AudioContentApi::class.java) }
|
||||
}
|
||||
|
||||
private val viewModelModule = module {
|
||||
|
@ -146,6 +159,15 @@ class AppDI(private val context: Context, isDebugMode: Boolean) {
|
|||
viewModel { SettingsViewModel(get()) }
|
||||
viewModel { TextMessageDetailViewModel(get()) }
|
||||
viewModel { LiveReservationStatusViewModel(get()) }
|
||||
viewModel { AudioContentMainViewModel(get()) }
|
||||
viewModel { AudioContentViewModel(get()) }
|
||||
viewModel { AudioContentOrderListViewModel(get()) }
|
||||
viewModel { AudioContentUploadViewModel(get()) }
|
||||
viewModel { AudioContentModifyViewModel(get()) }
|
||||
viewModel { AudioContentThemeViewModel(get()) }
|
||||
viewModel { AudioContentDetailViewModel(get(), get(), get(), get()) }
|
||||
viewModel { AudioContentCommentListViewModel(get()) }
|
||||
viewModel { AudioContentCommentReplyViewModel(get()) }
|
||||
}
|
||||
|
||||
private val repositoryModule = module {
|
||||
|
@ -161,6 +183,8 @@ class AppDI(private val context: Context, isDebugMode: Boolean) {
|
|||
factory { ExplorerRepository(get()) }
|
||||
factory { MessageRepository(get()) }
|
||||
factory { NoticeRepository(get()) }
|
||||
factory { AudioContentRepository(get(), get()) }
|
||||
factory { AudioContentCommentRepository(get()) }
|
||||
}
|
||||
|
||||
private val moduleList = listOf(
|
||||
|
|
|
@ -63,7 +63,7 @@ class SodaFirebaseMessagingService : FirebaseMessagingService() {
|
|||
|
||||
val audioContentId = messageData["content_id"]
|
||||
if (audioContentId != null) {
|
||||
intent.putExtra(Constants.EXTRA_CONTENT_ID, audioContentId.toLong())
|
||||
intent.putExtra(Constants.EXTRA_AUDIO_CONTENT_ID, audioContentId.toLong())
|
||||
}
|
||||
|
||||
val pendingIntent =
|
||||
|
|
|
@ -11,10 +11,10 @@ import com.gun0912.tedpermission.PermissionListener
|
|||
import com.gun0912.tedpermission.normal.TedPermission
|
||||
import com.orhanobut.logger.Logger
|
||||
import kr.co.vividnext.sodalive.R
|
||||
import kr.co.vividnext.sodalive.audio_content.main.AudioContentMainFragment
|
||||
import kr.co.vividnext.sodalive.base.BaseActivity
|
||||
import kr.co.vividnext.sodalive.common.LoadingDialog
|
||||
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
|
||||
import kr.co.vividnext.sodalive.content.main.ContentMainFragment
|
||||
import kr.co.vividnext.sodalive.databinding.ActivityMainBinding
|
||||
import kr.co.vividnext.sodalive.databinding.ItemMainTabBinding
|
||||
import kr.co.vividnext.sodalive.explorer.ExplorerFragment
|
||||
|
@ -189,7 +189,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>(ActivityMainBinding::infl
|
|||
if (fragment == null) {
|
||||
fragment = when (currentTab) {
|
||||
MainViewModel.CurrentTab.LIVE -> liveFragment
|
||||
MainViewModel.CurrentTab.CONTENT -> ContentMainFragment()
|
||||
MainViewModel.CurrentTab.CONTENT -> AudioContentMainFragment()
|
||||
MainViewModel.CurrentTab.EXPLORER -> ExplorerFragment()
|
||||
MainViewModel.CurrentTab.MESSAGE -> MessageFragment()
|
||||
MainViewModel.CurrentTab.MY -> MyPageFragment()
|
||||
|
|
|
@ -7,7 +7,7 @@ data class ReportRequest(
|
|||
@SerializedName("reason") val reason: String,
|
||||
@SerializedName("reportedAccountId") val reportedAccountId: Long? = null,
|
||||
@SerializedName("cheersId") val cheersId: Long? = null,
|
||||
@SerializedName("audioContentId") val audioContentId: Long? = null,
|
||||
@SerializedName("audioContentId") val contentId: Long? = null,
|
||||
)
|
||||
|
||||
enum class ReportType {
|
||||
|
|
|
@ -145,7 +145,7 @@ class SplashActivity : BaseActivity<ActivitySplashBinding>(ActivitySplashBinding
|
|||
)
|
||||
} else if (audioContentIdString != null) {
|
||||
bundleOf(
|
||||
Constants.EXTRA_CONTENT_ID to audioContentIdString.toLong()
|
||||
Constants.EXTRA_AUDIO_CONTENT_ID to audioContentIdString.toLong()
|
||||
)
|
||||
} else {
|
||||
null
|
||||
|
|
|
@ -74,13 +74,13 @@ interface UserApi {
|
|||
|
||||
@POST("/member/creator/follow")
|
||||
fun creatorFollow(
|
||||
request: Any,
|
||||
request: CreatorFollowRequestRequest,
|
||||
@Header("Authorization") authHeader: String
|
||||
): Single<ApiResponse<Any>>
|
||||
|
||||
@POST("/member/creator/unfollow")
|
||||
fun creatorUnFollow(
|
||||
request: Any,
|
||||
request: CreatorFollowRequestRequest,
|
||||
@Header("Authorization") authHeader: String
|
||||
): Single<ApiResponse<Any>>
|
||||
|
||||
|
|
After Width: | Height: | Size: 973 B |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 3.8 KiB |
After Width: | Height: | Size: 758 B |
After Width: | Height: | Size: 606 B |
After Width: | Height: | Size: 708 B |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 830 B |
After Width: | Height: | Size: 471 B |
After Width: | Height: | Size: 442 B |
After Width: | Height: | Size: 670 B |
After Width: | Height: | Size: 556 B |
After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 910 B |
|
@ -0,0 +1,22 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:id="@android:id/background">
|
||||
<shape>
|
||||
<solid android:color="@color/color_cc979797" />
|
||||
</shape>
|
||||
</item>
|
||||
<item android:id="@android:id/secondaryProgress">
|
||||
<clip>
|
||||
<shape>
|
||||
<solid android:color="@color/color_cc979797" />
|
||||
</shape>
|
||||
</clip>
|
||||
</item>
|
||||
<item android:id="@android:id/progress">
|
||||
<clip>
|
||||
<shape>
|
||||
<solid android:color="@color/color_9970ff" />
|
||||
</shape>
|
||||
</clip>
|
||||
</item>
|
||||
</layer-list>
|
|
@ -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/black" />
|
||||
<stroke android:width="0dp" />
|
||||
</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/color_2d7390" />
|
||||
<corners android:radius="10dp" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="@color/color_2d7390" />
|
||||
</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/color_4d6aa4" />
|
||||
<corners android:radius="10dp" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="@color/color_4d6aa4" />
|
||||
</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/color_548f7d" />
|
||||
<corners android:radius="10dp" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="@color/color_548f7d" />
|
||||
</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/color_59548f" />
|
||||
<corners android:radius="10dp" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="@color/color_59548f" />
|
||||
</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/color_973a3a" />
|
||||
<corners android:radius="10dp" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="@color/color_973a3a" />
|
||||
</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/color_d38c38" />
|
||||
<corners android:radius="10dp" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="@color/color_d38c38" />
|
||||
</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/color_d85e37" />
|
||||
<corners android:radius="10dp" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="@color/color_d85e37" />
|
||||
</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/color_303030" />
|
||||
<corners android:radius="13.3dp" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="@color/color_303030" />
|
||||
</shape>
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="@color/color_19ffffff" />
|
||||
<corners android:radius="26.7dp" />
|
||||
<stroke android:width="0dp" />
|
||||
</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/color_26ffffff" />
|
||||
<corners android:radius="26.7dp" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="@color/color_26ffffff" />
|
||||
</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/color_28312b" />
|
||||
<corners android:radius="2dp" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="@color/color_28312b" />
|
||||
</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/color_222222" />
|
||||
<corners android:radius="2.6dp" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="@color/color_222222" />
|
||||
</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/color_26310f" />
|
||||
<corners android:radius="2.6dp" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="@color/color_26310f" />
|
||||
</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/color_28312b" />
|
||||
<corners android:radius="2.6dp" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="@color/color_28312b" />
|
||||
</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/color_30176f" />
|
||||
<corners android:radius="2.6dp" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="@color/color_30176f" />
|
||||
</shape>
|