오디션 지원 기능 추가
This commit is contained in:
@@ -7,11 +7,14 @@ import kr.co.vividnext.sodalive.audition.role.AuditionRoleDetailViewModel
|
||||
import kr.co.vividnext.sodalive.audition.role.GetAuditionRoleDetailResponse
|
||||
import kr.co.vividnext.sodalive.audition.vote.VoteAuditionApplicantRequest
|
||||
import kr.co.vividnext.sodalive.common.ApiResponse
|
||||
import kr.co.vividnext.sodalive.live.room.menu.UpdateLiveMenuRequest
|
||||
import okhttp3.MultipartBody
|
||||
import okhttp3.RequestBody
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Header
|
||||
import retrofit2.http.Multipart
|
||||
import retrofit2.http.POST
|
||||
import retrofit2.http.Part
|
||||
import retrofit2.http.Path
|
||||
import retrofit2.http.Query
|
||||
|
||||
@@ -44,6 +47,14 @@ interface AuditionApi {
|
||||
@Header("Authorization") authHeader: String
|
||||
): Single<ApiResponse<GetAuditionApplicantListResponse>>
|
||||
|
||||
@POST("/audition/applicant")
|
||||
@Multipart
|
||||
fun applyAudition(
|
||||
@Part contentFile: MultipartBody.Part,
|
||||
@Part("request") request: RequestBody,
|
||||
@Header("Authorization") authHeader: String
|
||||
): Single<ApiResponse<Any>>
|
||||
|
||||
@POST("/audition/vote")
|
||||
fun voteApplicant(
|
||||
@Body request: VoteAuditionApplicantRequest,
|
||||
|
||||
@@ -2,6 +2,8 @@ package kr.co.vividnext.sodalive.audition
|
||||
|
||||
import kr.co.vividnext.sodalive.audition.role.AuditionRoleDetailViewModel
|
||||
import kr.co.vividnext.sodalive.audition.vote.VoteAuditionApplicantRequest
|
||||
import okhttp3.MultipartBody
|
||||
import okhttp3.RequestBody
|
||||
|
||||
class AuditionRepository(
|
||||
private val api: AuditionApi
|
||||
@@ -53,4 +55,14 @@ class AuditionRepository(
|
||||
request = request,
|
||||
authHeader = token
|
||||
)
|
||||
|
||||
fun applyAudition(
|
||||
contentFile: MultipartBody.Part,
|
||||
request: RequestBody,
|
||||
token: String
|
||||
) = api.applyAudition(
|
||||
contentFile = contentFile,
|
||||
request = request,
|
||||
authHeader = token
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
package kr.co.vividnext.sodalive.audition.applicant
|
||||
|
||||
import android.app.Activity
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.view.LayoutInflater
|
||||
import android.view.WindowManager
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import kr.co.vividnext.sodalive.databinding.DialogApplicationMethodBinding
|
||||
import kr.co.vividnext.sodalive.extensions.dpToPx
|
||||
|
||||
class ApplicationMethodDialog(
|
||||
activity: Activity,
|
||||
layoutInflater: LayoutInflater,
|
||||
onClickFileUpload: () -> Unit,
|
||||
onClickRecord: () -> Unit
|
||||
) {
|
||||
private val alertDialog: AlertDialog
|
||||
|
||||
val dialogView = DialogApplicationMethodBinding.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.ivClose.setOnClickListener {
|
||||
alertDialog.dismiss()
|
||||
}
|
||||
|
||||
dialogView.llUploadFile.setOnClickListener {
|
||||
alertDialog.dismiss()
|
||||
onClickFileUpload()
|
||||
}
|
||||
|
||||
dialogView.llRecord.setOnClickListener {
|
||||
alertDialog.dismiss()
|
||||
onClickRecord()
|
||||
}
|
||||
}
|
||||
|
||||
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,10 @@
|
||||
package kr.co.vividnext.sodalive.audition.applicant
|
||||
|
||||
import androidx.annotation.Keep
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
@Keep
|
||||
data class ApplyAuditionRoleRequest(
|
||||
@SerializedName("roleId") val roleId: Long,
|
||||
@SerializedName("phoneNumber") val phoneNumber: String
|
||||
)
|
||||
@@ -0,0 +1,83 @@
|
||||
package kr.co.vividnext.sodalive.audition.applicant
|
||||
|
||||
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 android.widget.Toast
|
||||
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.databinding.FragmentAuditionApplyDialogBinding
|
||||
|
||||
class AuditionApplyDialogFragment(
|
||||
private val fileName: String,
|
||||
private val onClickApply: (String) -> Unit,
|
||||
private val onClickClose: () -> Unit
|
||||
) : BottomSheetDialogFragment() {
|
||||
private lateinit var binding: FragmentAuditionApplyDialogBinding
|
||||
|
||||
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) {
|
||||
val bottomSheetBehavior = BottomSheetBehavior.from(bottomSheet)
|
||||
bottomSheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED
|
||||
bottomSheetBehavior.isDraggable = false
|
||||
}
|
||||
}
|
||||
|
||||
return dialog
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
binding = FragmentAuditionApplyDialogBinding.inflate(inflater, container, false)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
binding.ivClose.setOnClickListener {
|
||||
onClickClose()
|
||||
dismiss()
|
||||
}
|
||||
|
||||
binding.tvAuditionApply.setOnClickListener {
|
||||
val phoneNumber = binding.etPhoneNumber.text.toString()
|
||||
if (phoneNumber.isBlank() || phoneNumber.length != 11) {
|
||||
Toast.makeText(
|
||||
activity,
|
||||
"잘못된 연락처 입니다.\n다시 입력해 주세요.",
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
} else if (!binding.tvAgree.isSelected) {
|
||||
Toast.makeText(
|
||||
activity,
|
||||
"연락처 수집 및 활용에 동의하셔야 오디션 지원이 가능합니다.",
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
} else {
|
||||
dismiss()
|
||||
onClickApply(binding.etPhoneNumber.text.toString())
|
||||
}
|
||||
}
|
||||
|
||||
binding.tvAgree.setOnClickListener {
|
||||
it.isSelected = !it.isSelected
|
||||
}
|
||||
|
||||
binding.tvAudioFileName.text = fileName
|
||||
}
|
||||
}
|
||||
@@ -4,25 +4,33 @@ import android.content.Intent
|
||||
import android.graphics.Rect
|
||||
import android.net.Uri
|
||||
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 androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import coil.load
|
||||
import coil.transform.RoundedCornersTransformation
|
||||
import kr.co.vividnext.sodalive.R
|
||||
import kr.co.vividnext.sodalive.audition.applicant.ApplicationMethodDialog
|
||||
import kr.co.vividnext.sodalive.audition.applicant.AuditionApplicantListAdapter
|
||||
import kr.co.vividnext.sodalive.audition.applicant.AuditionApplyDialogFragment
|
||||
import kr.co.vividnext.sodalive.base.BaseActivity
|
||||
import kr.co.vividnext.sodalive.base.SodaDialog
|
||||
import kr.co.vividnext.sodalive.common.Constants
|
||||
import kr.co.vividnext.sodalive.common.LoadingDialog
|
||||
import kr.co.vividnext.sodalive.common.RealPathUtil
|
||||
import kr.co.vividnext.sodalive.databinding.ActivityAuditionRoleDetailBinding
|
||||
import kr.co.vividnext.sodalive.explorer.profile.creator_community.write.RecordingVoiceFragment
|
||||
import kr.co.vividnext.sodalive.extensions.dpToPx
|
||||
import org.koin.android.ext.android.inject
|
||||
import java.io.File
|
||||
|
||||
class AuditionRoleDetailActivity : BaseActivity<ActivityAuditionRoleDetailBinding>(
|
||||
ActivityAuditionRoleDetailBinding::inflate
|
||||
) {
|
||||
), RecordingVoiceFragment.OnAudioRecordedListener {
|
||||
private val viewModel: AuditionRoleDetailViewModel by inject()
|
||||
|
||||
private lateinit var loadingDialog: LoadingDialog
|
||||
@@ -30,6 +38,38 @@ class AuditionRoleDetailActivity : BaseActivity<ActivityAuditionRoleDetailBindin
|
||||
|
||||
private var auditionRoleId: Long = 0
|
||||
private var isOpenInformation = false
|
||||
private var reApplication = false
|
||||
private var isDeleteAudioFile = false
|
||||
|
||||
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) {
|
||||
viewModel.audioFileName = getFileName(fileUri).toString()
|
||||
viewModel.audioFile = File(
|
||||
RealPathUtil.getRealPath(applicationContext, fileUri!!)
|
||||
)
|
||||
showAuditionApplyDialog()
|
||||
} else {
|
||||
Toast.makeText(
|
||||
this,
|
||||
"잘못된 녹음 파일 입니다.\n다시 선택해 주세요.",
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
} else {
|
||||
Toast.makeText(
|
||||
this,
|
||||
"잘못된 녹음 파일 입니다.\n다시 선택해 주세요.",
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
auditionRoleId = intent.getLongExtra(Constants.EXTRA_AUDITION_ROLE_ID, 0)
|
||||
@@ -76,6 +116,21 @@ class AuditionRoleDetailActivity : BaseActivity<ActivityAuditionRoleDetailBindin
|
||||
}
|
||||
}
|
||||
|
||||
binding.tvApplicant.setOnClickListener {
|
||||
if (reApplication) {
|
||||
SodaDialog(
|
||||
activity = this@AuditionRoleDetailActivity,
|
||||
layoutInflater = layoutInflater,
|
||||
title = "재지원 안내",
|
||||
desc = "재지원 시 이전 지원 내역은 삭제되며 받은 투표수는 무효 처리됩니다.",
|
||||
confirmButtonTitle = "확인",
|
||||
confirmButtonClick = { showApplicationMethodDialog() }
|
||||
).show(screenWidth)
|
||||
} else {
|
||||
showApplicationMethodDialog()
|
||||
}
|
||||
}
|
||||
|
||||
binding.tvSortNewest.setOnClickListener {
|
||||
viewModel.setSortType(AuditionRoleDetailViewModel.AuditionApplicantSortType.NEWEST)
|
||||
}
|
||||
@@ -144,6 +199,53 @@ class AuditionRoleDetailActivity : BaseActivity<ActivityAuditionRoleDetailBindin
|
||||
recyclerView.adapter = adapter
|
||||
}
|
||||
|
||||
private fun showApplicationMethodDialog() {
|
||||
isDeleteAudioFile = false
|
||||
ApplicationMethodDialog(
|
||||
activity = this@AuditionRoleDetailActivity,
|
||||
layoutInflater = layoutInflater,
|
||||
onClickFileUpload = {
|
||||
isDeleteAudioFile = false
|
||||
val intent = Intent().apply {
|
||||
type = "audio/*"
|
||||
action = Intent.ACTION_GET_CONTENT
|
||||
addCategory(Intent.CATEGORY_OPENABLE)
|
||||
}
|
||||
selectAudioActivityResultLauncher.launch(
|
||||
Intent.createChooser(
|
||||
intent,
|
||||
"Select Audio"
|
||||
)
|
||||
)
|
||||
},
|
||||
onClickRecord = {
|
||||
isDeleteAudioFile = true
|
||||
val fragment = RecordingVoiceFragment()
|
||||
fragment.isCancelable = false
|
||||
fragment.show(supportFragmentManager, fragment.tag)
|
||||
}
|
||||
).show(screenWidth)
|
||||
}
|
||||
|
||||
private fun showAuditionApplyDialog() {
|
||||
val auditionApplyDialogFragment = AuditionApplyDialogFragment(
|
||||
fileName = viewModel.audioFileName,
|
||||
onClickApply = {
|
||||
viewModel.applyAudition(auditionRoleId = auditionRoleId, phoneNumber = it) {
|
||||
if (isDeleteAudioFile) {
|
||||
deleteAudioFile()
|
||||
}
|
||||
}
|
||||
},
|
||||
onClickClose = {
|
||||
if (isDeleteAudioFile) {
|
||||
deleteAudioFile()
|
||||
}
|
||||
}
|
||||
)
|
||||
auditionApplyDialogFragment.show(supportFragmentManager, auditionApplyDialogFragment.tag)
|
||||
}
|
||||
|
||||
private fun bindData() {
|
||||
viewModel.toastLiveData.observe(this) {
|
||||
it?.let { Toast.makeText(applicationContext, it, Toast.LENGTH_LONG).show() }
|
||||
@@ -177,8 +279,10 @@ class AuditionRoleDetailActivity : BaseActivity<ActivityAuditionRoleDetailBindin
|
||||
}
|
||||
|
||||
if (roleDetail.isAlreadyApplicant) {
|
||||
reApplication = true
|
||||
binding.tvApplicant.text = "오디션 재지원"
|
||||
} else {
|
||||
reApplication = false
|
||||
binding.tvApplicant.text = "오디션 지원하기"
|
||||
}
|
||||
}
|
||||
@@ -218,4 +322,37 @@ class AuditionRoleDetailActivity : BaseActivity<ActivityAuditionRoleDetailBindin
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun deleteAudioFile() {
|
||||
if (viewModel.audioFile != null && viewModel.audioFile!!.exists()) {
|
||||
viewModel.audioFile?.delete()
|
||||
viewModel.audioFileName = ""
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
override fun onAudioRecorded(file: File) {
|
||||
deleteAudioFile()
|
||||
viewModel.audioFile = file
|
||||
viewModel.audioFileName = file.name
|
||||
showAuditionApplyDialog()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,16 +2,25 @@ package kr.co.vividnext.sodalive.audition.role
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import com.orhanobut.logger.Logger
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.core.Observable
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import kr.co.vividnext.sodalive.audition.AuditionRepository
|
||||
import kr.co.vividnext.sodalive.audition.applicant.ApplyAuditionRoleRequest
|
||||
import kr.co.vividnext.sodalive.audition.applicant.GetAuditionRoleApplicantItem
|
||||
import kr.co.vividnext.sodalive.audition.vote.VoteAuditionApplicantRequest
|
||||
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
|
||||
import java.util.TimeZone
|
||||
|
||||
class AuditionRoleDetailViewModel(private val repository: AuditionRepository) : BaseViewModel() {
|
||||
@@ -41,6 +50,8 @@ class AuditionRoleDetailViewModel(private val repository: AuditionRepository) :
|
||||
|
||||
var page = 1
|
||||
var isLast = false
|
||||
var audioFile: File? = null
|
||||
var audioFileName: String = ""
|
||||
private var auditionRoleId = -1L
|
||||
private val pageSize = 10
|
||||
|
||||
@@ -171,6 +182,78 @@ class AuditionRoleDetailViewModel(private val repository: AuditionRepository) :
|
||||
)
|
||||
}
|
||||
|
||||
fun applyAudition(auditionRoleId: Long, phoneNumber: String, onSuccess: () -> Unit) {
|
||||
if (audioFile == null) {
|
||||
_toastLiveData.value = "잘못된 녹음 파일 입니다.\n다시 선택해 주세요."
|
||||
return
|
||||
}
|
||||
|
||||
_isLoading.value = true
|
||||
|
||||
val request = ApplyAuditionRoleRequest(roleId = auditionRoleId, phoneNumber = phoneNumber)
|
||||
val requestJson = Gson().toJson(request)
|
||||
|
||||
val contentFile = MultipartBody.Part.createFormData(
|
||||
"contentFile",
|
||||
audioFile!!.name,
|
||||
body = object : RequestBody() {
|
||||
override fun contentType(): MediaType {
|
||||
return "audio/*".toMediaType()
|
||||
}
|
||||
|
||||
override fun writeTo(sink: BufferedSink) {
|
||||
audioFile!!.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 audioFile!!.length()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
compositeDisposable.add(
|
||||
repository.applyAudition(
|
||||
contentFile = contentFile,
|
||||
request = requestJson.toRequestBody("text/plain".toMediaType()),
|
||||
token = "Bearer ${SharedPreferenceManager.token}"
|
||||
).subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{
|
||||
_isLoading.value = false
|
||||
if (it.success) {
|
||||
_applicantListLiveData.value = emptyList()
|
||||
page = 1
|
||||
isLast = false
|
||||
getAuditionRoleDetail(auditionRoleId = auditionRoleId)
|
||||
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 setSortType(sortType: AuditionApplicantSortType) {
|
||||
val prevSortType = _sortTypeLiveData.value!!
|
||||
|
||||
|
||||
Reference in New Issue
Block a user