오디션 지원 기능 추가

This commit is contained in:
klaus 2025-01-03 07:48:34 +09:00
parent c6ef5970a5
commit 4e14765e94
13 changed files with 632 additions and 2 deletions

View File

@ -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,

View File

@ -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
)
}

View File

@ -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
}
}

View File

@ -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
)

View File

@ -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
}
}

View File

@ -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()
}
}

View File

@ -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!!

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 861 B

View File

@ -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_13181b" />
<corners android:radius="5.3dp" />
<stroke
android:width="1dp"
android:color="@color/color_13181b" />
</shape>

View File

@ -0,0 +1,105 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="@drawable/bg_round_corner_10_3_222222"
android:paddingVertical="13.3dp"
android:paddingHorizontal="16.7dp">
<ImageView
android:id="@+id/iv_close"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_noti_stop"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="33.3dp"
android:fontFamily="@font/gmarket_sans_bold"
android:gravity="center"
android:text="오디션 지원방식"
android:textColor="@color/color_bbbbbb"
android:textSize="18.3sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<LinearLayout
android:id="@+id/ll_method_buttons"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="21.3dp"
android:orientation="horizontal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tv_title">
<LinearLayout
android:id="@+id/ll_upload_file"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@drawable/bg_round_corner_5_3_13181b_3bb9f1"
android:gravity="center"
android:orientation="horizontal"
android:paddingVertical="8dp">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="3dp"
android:src="@drawable/ic_upload" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/gmarket_sans_medium"
android:text="파일 업로드"
android:textColor="@color/color_3bb9f1"
android:textSize="14.7sp" />
</LinearLayout>
<LinearLayout
android:id="@+id/ll_record"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="13.3dp"
android:layout_weight="1"
android:background="@drawable/bg_round_corner_5_3_13181b_3bb9f1"
android:gravity="center"
android:orientation="horizontal"
android:paddingVertical="8dp">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="3dp"
android:src="@drawable/ic_mic_color_button" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/gmarket_sans_medium"
android:text="바로 녹음"
android:textColor="@color/color_3bb9f1"
android:textSize="14.7sp" />
</LinearLayout>
</LinearLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="13.3dp"
android:fontFamily="@font/gmarket_sans_medium"
android:text="※ 파일은 mp3, aac만 업로드 가능"
android:textColor="@color/color_777777"
android:textSize="12sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/ll_method_buttons" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,126 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/color_222222">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingHorizontal="13.3dp"
android:paddingVertical="16.7dp">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:fontFamily="@font/gmarket_sans_medium"
android:text="오디션 지원"
android:textColor="@color/white"
android:textSize="18.3sp" />
<ImageView
android:id="@+id/iv_close"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:src="@drawable/ic_noti_stop" />
</RelativeLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:fontFamily="@font/gmarket_sans_bold"
android:text="녹음파일"
android:textColor="@color/color_eeeeee"
android:textSize="16.7sp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:background="@drawable/bg_round_corner_5_3_13181b"
android:orientation="horizontal"
android:paddingHorizontal="13.3dp"
android:paddingVertical="8dp">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_note_square" />
<TextView
android:id="@+id/tv_audio_file_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:fontFamily="@font/gmarket_sans_medium"
android:textColor="@color/color_d2d2d2"
android:textSize="13.3sp" />
</LinearLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="15dp"
android:fontFamily="@font/gmarket_sans_bold"
android:text="연락처"
android:textColor="@color/color_eeeeee"
android:textSize="16.7sp" />
<EditText
android:id="@+id/et_phone_number"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:background="@drawable/bg_round_corner_5_3_13181b"
android:fontFamily="@font/gmarket_sans_medium"
android:hint="합격시 연락받을 연락처를 남겨주세요"
android:importantForAutofill="no"
android:inputType="numberSigned"
android:maxEms="12"
android:paddingHorizontal="13.3dp"
android:paddingVertical="17dp"
android:textColor="@color/color_eeeeee"
android:textColorHint="@color/color_777777"
android:textCursorDrawable="@drawable/edit_text_cursor"
android:textSize="13.3sp"
android:theme="@style/EditTextStyle"
tools:ignore="LabelFor,TextFields" />
<TextView
android:id="@+id/tv_agree"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:drawablePadding="13.3dp"
android:fontFamily="@font/gmarket_sans_medium"
android:gravity="center_vertical"
android:text="보이스온 오디오 드라마 오디션 합격시 개인 연락을 위한 개인 정보(연락처) 수집 및 활용에 동의합니다.\n오디션 지원자는 개인정보 수집 및 활용 동의에 거부할 권리가 있으며 비동의시 오디션 지원은 취소 됩니다."
android:textColor="@color/color_eeeeee"
android:textSize="13.3sp"
app:drawableStartCompat="@drawable/ic_select" />
<TextView
android:id="@+id/tv_audition_apply"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="35dp"
android:background="@drawable/bg_round_corner_8_3bb9f1"
android:fontFamily="@font/gmarket_sans_bold"
android:gravity="center"
android:paddingVertical="13.3dp"
android:text="오디션 지원하기"
android:textColor="@color/white"
android:textSize="13.3sp" />
</LinearLayout>
</ScrollView>