From 4e14765e941c4abcff008d4eff6d81a24e53f65b Mon Sep 17 00:00:00 2001 From: klaus Date: Fri, 3 Jan 2025 07:48:34 +0900 Subject: [PATCH] =?UTF-8?q?=EC=98=A4=EB=94=94=EC=85=98=20=EC=A7=80?= =?UTF-8?q?=EC=9B=90=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sodalive/audition/AuditionApi.kt | 13 +- .../sodalive/audition/AuditionRepository.kt | 12 ++ .../applicant/ApplicationMethodDialog.kt | 55 +++++++ .../applicant/ApplyAuditionRoleRequest.kt | 10 ++ .../applicant/AuditionApplyDialogFragment.kt | 83 +++++++++++ .../role/AuditionRoleDetailActivity.kt | 139 +++++++++++++++++- .../role/AuditionRoleDetailViewModel.kt | 83 +++++++++++ .../drawable-xxhdpi/ic_mic_color_button.png | Bin 0 -> 1129 bytes .../res/drawable-xxhdpi/ic_note_square.png | Bin 0 -> 1167 bytes .../main/res/drawable-xxhdpi/ic_upload.png | Bin 0 -> 861 bytes .../drawable/bg_round_corner_5_3_13181b.xml | 8 + .../res/layout/dialog_application_method.xml | 105 +++++++++++++ .../layout/fragment_audition_apply_dialog.xml | 126 ++++++++++++++++ 13 files changed, 632 insertions(+), 2 deletions(-) create mode 100644 app/src/main/java/kr/co/vividnext/sodalive/audition/applicant/ApplicationMethodDialog.kt create mode 100644 app/src/main/java/kr/co/vividnext/sodalive/audition/applicant/ApplyAuditionRoleRequest.kt create mode 100644 app/src/main/java/kr/co/vividnext/sodalive/audition/applicant/AuditionApplyDialogFragment.kt create mode 100644 app/src/main/res/drawable-xxhdpi/ic_mic_color_button.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_note_square.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_upload.png create mode 100644 app/src/main/res/drawable/bg_round_corner_5_3_13181b.xml create mode 100644 app/src/main/res/layout/dialog_application_method.xml create mode 100644 app/src/main/res/layout/fragment_audition_apply_dialog.xml diff --git a/app/src/main/java/kr/co/vividnext/sodalive/audition/AuditionApi.kt b/app/src/main/java/kr/co/vividnext/sodalive/audition/AuditionApi.kt index 7a0601e..88a2be0 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/audition/AuditionApi.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/audition/AuditionApi.kt @@ -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> + @POST("/audition/applicant") + @Multipart + fun applyAudition( + @Part contentFile: MultipartBody.Part, + @Part("request") request: RequestBody, + @Header("Authorization") authHeader: String + ): Single> + @POST("/audition/vote") fun voteApplicant( @Body request: VoteAuditionApplicantRequest, diff --git a/app/src/main/java/kr/co/vividnext/sodalive/audition/AuditionRepository.kt b/app/src/main/java/kr/co/vividnext/sodalive/audition/AuditionRepository.kt index 3422ec9..7b810b1 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/audition/AuditionRepository.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/audition/AuditionRepository.kt @@ -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 + ) } diff --git a/app/src/main/java/kr/co/vividnext/sodalive/audition/applicant/ApplicationMethodDialog.kt b/app/src/main/java/kr/co/vividnext/sodalive/audition/applicant/ApplicationMethodDialog.kt new file mode 100644 index 0000000..007908b --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/audition/applicant/ApplicationMethodDialog.kt @@ -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 + } +} diff --git a/app/src/main/java/kr/co/vividnext/sodalive/audition/applicant/ApplyAuditionRoleRequest.kt b/app/src/main/java/kr/co/vividnext/sodalive/audition/applicant/ApplyAuditionRoleRequest.kt new file mode 100644 index 0000000..8bd4c4e --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/audition/applicant/ApplyAuditionRoleRequest.kt @@ -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 +) diff --git a/app/src/main/java/kr/co/vividnext/sodalive/audition/applicant/AuditionApplyDialogFragment.kt b/app/src/main/java/kr/co/vividnext/sodalive/audition/applicant/AuditionApplyDialogFragment.kt new file mode 100644 index 0000000..5adf8e5 --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/audition/applicant/AuditionApplyDialogFragment.kt @@ -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( + 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 + } +} diff --git a/app/src/main/java/kr/co/vividnext/sodalive/audition/role/AuditionRoleDetailActivity.kt b/app/src/main/java/kr/co/vividnext/sodalive/audition/role/AuditionRoleDetailActivity.kt index c346791..ce7e614 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/audition/role/AuditionRoleDetailActivity.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/audition/role/AuditionRoleDetailActivity.kt @@ -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::inflate -) { +), RecordingVoiceFragment.OnAudioRecordedListener { private val viewModel: AuditionRoleDetailViewModel by inject() private lateinit var loadingDialog: LoadingDialog @@ -30,6 +38,38 @@ class AuditionRoleDetailActivity : BaseActivity + 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 + 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() + } } diff --git a/app/src/main/java/kr/co/vividnext/sodalive/audition/role/AuditionRoleDetailViewModel.kt b/app/src/main/java/kr/co/vividnext/sodalive/audition/role/AuditionRoleDetailViewModel.kt index 46dac28..458e770 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/audition/role/AuditionRoleDetailViewModel.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/audition/role/AuditionRoleDetailViewModel.kt @@ -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!! diff --git a/app/src/main/res/drawable-xxhdpi/ic_mic_color_button.png b/app/src/main/res/drawable-xxhdpi/ic_mic_color_button.png new file mode 100644 index 0000000000000000000000000000000000000000..08c059cb45e3842e92bc881b9f27228ba0fdc264 GIT binary patch literal 1129 zcmV-v1eW`WP)Y=dQz@@UCX(U0s>N?SpQu? z5T}L&74nV{KZ^BV6-a@^mVAEF^e2RLXc5Yl^EbK6Bo8IFq!8j1(j$xPB9IV?Ncx+^ ze2JrwPrB@hNFq9^d@4K;Y$WS$h|*~@d)pRGu!QM9GG}^@8e6h+1502L*K_7>lj;bv zfhTu2lL;P-WD-nB+QNjSElfz-!i1zPOi0?oqaZn@{&kZor>b5{>FuOVm~24DMW{Hj zoAuGU`s4iNLgmo#0EpruRGb({39_Ax-Pq9-QCub)#}+0eZQ=2flvB<^s7-9N%DAj5Gv`e#`Cq@0m0bI?&_3ZB`LCn zOGAxvfTSQe3O>}Hg>BFQWtqQW>P@5+UtK#BDFxfe!K1s+*oiaDI506Qxm#LmvX77_ zcOWTve3EiAGQtv~8fbCPU(X-kll1-4lkWnZvQgGl%gz>gmSE{!0#k=@v;>9kf_1CaAfr%O5RpR=vf^D6V^~; zc9Y71a*bYyC;4ofxdt~*e^dmOydAV6g53SGnydBS4^G6^Y`4XQMsgmj#F2!cnu`V2 z<-so)QhHVR&f@!pW?XATN7aA^m$`?~-RuwS^?NgB!C?WY`afKO2GKhvqgoVsBI*RX-$2 z-`NV6;_gU7CgLHJL9v7a7qxZAL9hkS-sbirr+^rYaBv%N`G_mWRmft4UbUPvGjbWF z+yz_U+eeM3cCkqbFHvpLKX?I@uf9rUSw)U-HjX9tlhndfmy%!Bo9i5{uY>YAz{otE zfL!qCSRfpwE!2-)l<%Qf|EbQ_K3NWIr?07O(^vrH!Ao}8Rag4#3w3m{j+fD0V?*Vk7^ zwgY(_rEE6KbNlG%=vr@_BRL!nuRyE>f{o+jV@I|OH#avHqM z$cv6Nhd*?`GcgbB zco-?%eJjbY$gmyfqL&J%99J%vT?vVekc>%YWmp@@>2!L5OsbZI3_}14m&+)WrxFuu zAxQ^;iM3;pnc#ea*!_U|#8XM73F#s!N^*WIBeO1S%JH1Pyu3K;CbI||iId++kl1BB zreH_X6zoWvf*naykh=9G^Ik1aogtB!NJ6BBq)NiM>uu@Rag9Y^5+D^ML9hfeBUzBx zOM+zTDjEm@D3z_%EnUHTrNlZ zu_PgsiWj^(2ww;FgNN-%0z@WB73F4>SI4=FqmGGGP(5c|V&eJv*}ZUBg%G!!n>@x*CENZ^-0Ayj+%iOxByCZbB4FzT3D!aIGR*zd zSjVelT+{ERRlTWQa{Rx!3{fRH=jI{BX;3F_cc>t~6zF@2zLs}?oR7In&dn~|{|Cx5 z{de(@yAlzJ;v^k`OPJ^GZm#6)D0_`xT32X<)d?U|rKy z0mAc`39;m)9>?|1B(mn)I5U1@`?KfAGcka{U@#c;fj0?$GVmLOR-2F-qF@b{!w3*Z z7zD=`Eyqn;D3$2LfnT#M>kx<D5_b+yw9xRb*2jmK2j~LsJ?5h2_ z2YX`Miz~;}hvc1TY9hJ4T*)XxxQE1FNAnQaZsUy~TfJjNW%Zb=vfe65hS+z+(-Zwf zBy0WeUD$|=jTH@WE||cD6~t{iV;=C+Xx_DJpAsvIbG;i8{|YOLsCnK@TG2Bnw9y$n zCEmd@pWQY#g(nQlrhLkN^if9^1;`}4ckAWFvY4*)XiAvO)N!gYUDN#sz zLZTndKx9{(^JZRIi(qDIhRnK(i;EQD#<_y@tBtmxiXMf zR?cQb?dVRXkK$%tZm%83#ocJlD2XIVDO4Z$9b{sqB81@Ra_2!;{#4C<#fwF6xOKk| z1yD#ET_m}vd=XD}5xGV4N`{mzn@hAP$&fN-Gm9228ImOX=$*d@8OiZPXO~)_0Lc)Q zEJL;n8Obb)gkB_Bm}E$-ERqKqu^}S?1zpA`t8Ty%4vLfvk;fE6Pm|IA^W6R&zrvS> z%%W4V%xy;Zd9K0Frrn%h*k|FS~ZMl)i9z}!-$q}A}^XI;NL(a+C=4F n2a)fBJWg-`27|$1s3`vc<5aPjAFjaP00000NkvXXu0mjfh%j{0 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/bg_round_corner_5_3_13181b.xml b/app/src/main/res/drawable/bg_round_corner_5_3_13181b.xml new file mode 100644 index 0000000..2ea3cec --- /dev/null +++ b/app/src/main/res/drawable/bg_round_corner_5_3_13181b.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/app/src/main/res/layout/dialog_application_method.xml b/app/src/main/res/layout/dialog_application_method.xml new file mode 100644 index 0000000..5213024 --- /dev/null +++ b/app/src/main/res/layout/dialog_application_method.xml @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_audition_apply_dialog.xml b/app/src/main/res/layout/fragment_audition_apply_dialog.xml new file mode 100644 index 0000000..a0f3021 --- /dev/null +++ b/app/src/main/res/layout/fragment_audition_apply_dialog.xml @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +