diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index c54482e..f602a9b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -150,6 +150,7 @@ <activity android:name=".audio_content.playlist.modify.AudioContentPlaylistModifyActivity" /> <activity android:name=".audio_content.box.AudioContentBoxActivity" /> <activity android:name=".audition.detail.AuditionDetailActivity" /> + <activity android:name=".audition.role.AuditionRoleDetailActivity" /> <activity android:name=".mypage.alarm.AlarmListActivity" /> <activity android:name=".mypage.alarm.AddAlarmActivity" /> 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 4f13957..66ca14c 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 @@ -1,7 +1,10 @@ package kr.co.vividnext.sodalive.audition import io.reactivex.rxjava3.core.Single +import kr.co.vividnext.sodalive.audition.applicant.GetAuditionApplicantListResponse import kr.co.vividnext.sodalive.audition.detail.GetAuditionDetailResponse +import kr.co.vividnext.sodalive.audition.role.AuditionRoleDetailViewModel +import kr.co.vividnext.sodalive.audition.role.GetAuditionRoleDetailResponse import kr.co.vividnext.sodalive.common.ApiResponse import retrofit2.http.GET import retrofit2.http.Header @@ -21,4 +24,19 @@ interface AuditionApi { @Path("id") id: Long, @Header("Authorization") authHeader: String ): Single<ApiResponse<GetAuditionDetailResponse>> + + @GET("/audition/role/{id}") + fun getAuditionRoleDetail( + @Path("id") id: Long, + @Header("Authorization") authHeader: String + ): Single<ApiResponse<GetAuditionRoleDetailResponse>> + + @GET("/audition/applicant") + fun getAuditionApplicantList( + @Query("auditionRoleId") auditionRoleId: Long, + @Query("sortType") sortType: AuditionRoleDetailViewModel.AuditionApplicantSortType, + @Query("page") page: Int, + @Query("size") size: Int, + @Header("Authorization") authHeader: String + ): Single<ApiResponse<GetAuditionApplicantListResponse>> } 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 11ce2c9..3934cea 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 @@ -1,5 +1,7 @@ package kr.co.vividnext.sodalive.audition +import kr.co.vividnext.sodalive.audition.role.AuditionRoleDetailViewModel + class AuditionRepository( private val api: AuditionApi ) { @@ -20,4 +22,26 @@ class AuditionRepository( id = auditionId, authHeader = token ) + + fun getAuditionRoleDetail( + auditionRoleId: Long, + token: String + ) = api.getAuditionRoleDetail( + id = auditionRoleId, + authHeader = token + ) + + fun getAuditionApplicantList( + auditionRoleId: Long, + sortType: AuditionRoleDetailViewModel.AuditionApplicantSortType, + page: Int, + size: Int, + token: String + ) = api.getAuditionApplicantList( + auditionRoleId = auditionRoleId, + sortType = sortType, + page = page - 1, + size = size, + authHeader = token + ) } diff --git a/app/src/main/java/kr/co/vividnext/sodalive/audition/applicant/AuditionApplicantListAdapter.kt b/app/src/main/java/kr/co/vividnext/sodalive/audition/applicant/AuditionApplicantListAdapter.kt new file mode 100644 index 0000000..263d5bc --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/audition/applicant/AuditionApplicantListAdapter.kt @@ -0,0 +1,89 @@ +package kr.co.vividnext.sodalive.audition.applicant + +import android.annotation.SuppressLint +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.RecyclerView +import coil.load +import coil.transform.CircleCropTransformation +import kr.co.vividnext.sodalive.R +import kr.co.vividnext.sodalive.databinding.ItemAuditionApplicantBinding + +class AuditionApplicantListAdapter( + private var itemList: List<GetAuditionRoleApplicantItem>, + private val onClickVote: (Int) -> Unit, + private val onClickPlayOrPause: (Int) -> Unit +) : RecyclerView.Adapter<AuditionApplicantListAdapter.ViewHolder>() { + + private var currentPlayingIndex: Int = -1 + + inner class DiffCallback( + private val oldList: List<GetAuditionRoleApplicantItem>, + private val newList: List<GetAuditionRoleApplicantItem> + ) : DiffUtil.Callback() { + override fun getOldListSize() = oldList.size + override fun getNewListSize() = newList.size + + override fun areItemsTheSame( + oldItemPosition: Int, + newItemPosition: Int + ) = oldList[oldItemPosition].applicantId == newList[newItemPosition].applicantId + + override fun areContentsTheSame( + oldItemPosition: Int, + newItemPosition: Int + ) = oldList[oldItemPosition] == newList[newItemPosition] + } + + inner class ViewHolder( + private val binding: ItemAuditionApplicantBinding + ) : RecyclerView.ViewHolder(binding.root) { + fun bind(item: GetAuditionRoleApplicantItem, position: Int) { + binding.ivVote.setOnClickListener { onClickVote(position) } + binding.ivPlayOrPause.setOnClickListener { onClickPlayOrPause(position) } + + binding.tvNickname.text = item.nickname + binding.ivProfile.load(item.profileImageUrl) { + crossfade(true) + placeholder(R.drawable.bg_placeholder) + transformations(CircleCropTransformation()) + } + + binding.ivPlayOrPause.setImageResource( + if (position == currentPlayingIndex) { + R.drawable.ic_audition_play + } else { + R.drawable.ic_audition_pause + } + ) + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder( + ItemAuditionApplicantBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ) + ) + + override fun getItemCount() = itemList.size + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + holder.bind(itemList[position], position) + } + + @SuppressLint("NotifyDataSetChanged") + fun updateData(newData: List<GetAuditionRoleApplicantItem>) { + itemList = newData + notifyDataSetChanged() + } + + fun updatePlayingIndex(newIndex: Int) { + val previousIndex = currentPlayingIndex + currentPlayingIndex = newIndex + notifyItemChanged(previousIndex) + notifyItemChanged(currentPlayingIndex) + } +} diff --git a/app/src/main/java/kr/co/vividnext/sodalive/audition/applicant/GetAuditionApplicantListResponse.kt b/app/src/main/java/kr/co/vividnext/sodalive/audition/applicant/GetAuditionApplicantListResponse.kt new file mode 100644 index 0000000..4ecedf3 --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/audition/applicant/GetAuditionApplicantListResponse.kt @@ -0,0 +1,19 @@ +package kr.co.vividnext.sodalive.audition.applicant + +import androidx.annotation.Keep +import com.google.gson.annotations.SerializedName + +@Keep +data class GetAuditionApplicantListResponse( + @SerializedName("totalCount") val totalCount: Int, + @SerializedName("items") val items: List<GetAuditionRoleApplicantItem> +) + +@Keep +data class GetAuditionRoleApplicantItem( + @SerializedName("applicantId") val applicantId: Long, + @SerializedName("nickname") val nickname: String, + @SerializedName("profileImageUrl") val profileImageUrl: String, + @SerializedName("voiceUrl") val voiceUrl: String, + @SerializedName("voteCount") var voteCount: Long +) diff --git a/app/src/main/java/kr/co/vividnext/sodalive/audition/detail/AuditionDetailActivity.kt b/app/src/main/java/kr/co/vividnext/sodalive/audition/detail/AuditionDetailActivity.kt index f15f360..37e0edb 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/audition/detail/AuditionDetailActivity.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/audition/detail/AuditionDetailActivity.kt @@ -1,5 +1,6 @@ package kr.co.vividnext.sodalive.audition.detail +import android.content.Intent import android.graphics.Rect import android.os.Bundle import android.view.View @@ -9,6 +10,7 @@ import androidx.recyclerview.widget.RecyclerView import coil.load import coil.transform.RoundedCornersTransformation import kr.co.vividnext.sodalive.R +import kr.co.vividnext.sodalive.audition.role.AuditionRoleDetailActivity import kr.co.vividnext.sodalive.base.BaseActivity import kr.co.vividnext.sodalive.common.Constants import kr.co.vividnext.sodalive.common.LoadingDialog @@ -72,7 +74,13 @@ class AuditionDetailActivity : BaseActivity<ActivityAuditionDetailBinding>( } } - adapter = AuditionDetailRoleAdapter { } + adapter = AuditionDetailRoleAdapter { + startActivity( + Intent(applicationContext, AuditionRoleDetailActivity::class.java).apply { + putExtra(Constants.EXTRA_AUDITION_ROLE_ID, it) + } + ) + } binding.rvRole.layoutManager = LinearLayoutManager( applicationContext, 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 new file mode 100644 index 0000000..09e8c88 --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/audition/role/AuditionRoleDetailActivity.kt @@ -0,0 +1,132 @@ +package kr.co.vividnext.sodalive.audition.role + +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import android.view.View +import android.widget.Toast +import coil.load +import coil.transform.RoundedCornersTransformation +import kr.co.vividnext.sodalive.R +import kr.co.vividnext.sodalive.audition.applicant.AuditionApplicantListAdapter +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.ActivityAuditionRoleDetailBinding +import kr.co.vividnext.sodalive.extensions.dpToPx +import org.koin.android.ext.android.inject + +class AuditionRoleDetailActivity : BaseActivity<ActivityAuditionRoleDetailBinding>( + ActivityAuditionRoleDetailBinding::inflate +) { + private val viewModel: AuditionRoleDetailViewModel by inject() + + private lateinit var loadingDialog: LoadingDialog + private lateinit var adapter: AuditionApplicantListAdapter + + private var auditionRoleId: Long = 0 + private var isOpenInformation = false + + override fun onCreate(savedInstanceState: Bundle?) { + auditionRoleId = intent.getLongExtra(Constants.EXTRA_AUDITION_ROLE_ID, 0) + + if (auditionRoleId <= 0) { + Toast.makeText( + applicationContext, + "잘못된 요청입니다.\n다시 시도해 주세요.", + Toast.LENGTH_LONG + ).show() + + finish() + } + + super.onCreate(savedInstanceState) + + bindData() + viewModel.getAuditionRoleDetail(auditionRoleId = auditionRoleId) + } + + override fun setupView() { + loadingDialog = LoadingDialog(this, layoutInflater) + binding.toolbar.tvBack.setOnClickListener { finish() } + binding.tvOpen.setOnClickListener { + isOpenInformation = !isOpenInformation + if (isOpenInformation) { + binding.tvInformation.maxLines = Int.MAX_VALUE + binding.tvOpen.text = "접기" + binding.tvOpen.setCompoundDrawablesWithIntrinsicBounds( + R.drawable.ic_live_detail_top, + 0, + 0, + 0 + ) + } else { + binding.tvInformation.maxLines = 3 + binding.tvOpen.text = "펼치기" + binding.tvOpen.setCompoundDrawablesWithIntrinsicBounds( + R.drawable.ic_live_detail_bottom, + 0, + 0, + 0 + ) + } + } + + + } + + 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.auditionRoleDetailLiveData.observe(this) { roleDetail -> + binding.toolbar.tvBack.text = roleDetail.name + + binding.tvAuditionScript.setOnClickListener { + startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(roleDetail.auditionScriptUrl))) + } + + binding.tvOriginalWork.setOnClickListener { + startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(roleDetail.originalWorkUrl))) + } + + binding.tvInformation.text = roleDetail.information + + binding.ivCover.load(roleDetail.imageUrl) { + crossfade(true) + placeholder(R.drawable.bg_placeholder) + transformations(RoundedCornersTransformation(5f.dpToPx())) + } + + if (roleDetail.isAlreadyApplicant) { + binding.tvApplicant.text = "오디션 재지원" + } else { + binding.tvApplicant.text = "오디션 지원하기" + } + } + + viewModel.totalCountLiveData.observe(this) { + binding.groupApplicant.visibility = View.GONE + binding.groupNoApplicant.visibility = View.GONE + + if (it > 0) { + binding.groupApplicant.visibility = View.VISIBLE + binding.tvApplicantCount.text = "$it" + } else { + binding.groupNoApplicant.visibility = View.VISIBLE + } + } + + viewModel.applicantListLiveData.observe(this) { + } + } +} 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 new file mode 100644 index 0000000..6df5b9f --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/audition/role/AuditionRoleDetailViewModel.kt @@ -0,0 +1,180 @@ +package kr.co.vividnext.sodalive.audition.role + +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.core.Observable +import io.reactivex.rxjava3.schedulers.Schedulers +import kr.co.vividnext.sodalive.audition.AuditionRepository +import kr.co.vividnext.sodalive.audition.applicant.GetAuditionRoleApplicantItem +import kr.co.vividnext.sodalive.base.BaseViewModel +import kr.co.vividnext.sodalive.common.SharedPreferenceManager + +class AuditionRoleDetailViewModel(private val repository: AuditionRepository) : BaseViewModel() { + private val _toastLiveData = MutableLiveData<String?>() + val toastLiveData: LiveData<String?> + get() = _toastLiveData + + private val _isLoading = MutableLiveData(false) + val isLoading: LiveData<Boolean> + get() = _isLoading + + private val _auditionRoleDetailLiveData = MutableLiveData<GetAuditionRoleDetailResponse>() + val auditionRoleDetailLiveData: LiveData<GetAuditionRoleDetailResponse> + get() = _auditionRoleDetailLiveData + + private val _totalCountLiveData = MutableLiveData(0) + val totalCountLiveData: LiveData<Int> + get() = _totalCountLiveData + + private val _applicantListLiveData = MutableLiveData<List<GetAuditionRoleApplicantItem>>() + val applicantListLiveData: LiveData<List<GetAuditionRoleApplicantItem>> + get() = _applicantListLiveData + + private val _sortTypeLiveData = MutableLiveData(AuditionApplicantSortType.NEWEST) + val sortTypeLiveData: LiveData<AuditionApplicantSortType> + get() = _sortTypeLiveData + + var page = 1 + var isLast = false + private var auditionRoleId = -1L + private val pageSize = 10 + + fun getAuditionRoleDetail(auditionRoleId: Long, onFailure: (() -> Unit)? = null) { + this.auditionRoleId = auditionRoleId + _isLoading.value = true + + val auditionRoleDetail = repository.getAuditionRoleDetail( + auditionRoleId = auditionRoleId, + token = "Bearer ${SharedPreferenceManager.token}" + ) + + val getApplicantList = repository.getAuditionApplicantList( + auditionRoleId = auditionRoleId, + sortType = _sortTypeLiveData.value!!, + page = page, + size = pageSize, + token = "Bearer ${SharedPreferenceManager.token}" + ) + + compositeDisposable.add( + Observable.combineLatest( + auditionRoleDetail.toObservable(), + getApplicantList.toObservable() + ) { response1, response2 -> + Pair(response1, response2) + } + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + { pair -> + val roleDetailResponse = pair.first + val applicantListResponse = pair.second + + if (roleDetailResponse.success && roleDetailResponse.data != null) { + _auditionRoleDetailLiveData.value = roleDetailResponse.data!! + } else { + if (roleDetailResponse.message != null) { + _toastLiveData.value = roleDetailResponse.message + } else { + _toastLiveData.value = "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요." + } + + if (onFailure != null) { + onFailure() + } + } + + if (applicantListResponse.success && applicantListResponse.data != null) { + val data = applicantListResponse.data + + _totalCountLiveData.value = data.totalCount + _applicantListLiveData.value = data.items + page += 1 + } else { + if (applicantListResponse.message != null) { + _toastLiveData.value = applicantListResponse.message + } else { + _toastLiveData.value = "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요." + } + + if (onFailure != null) { + onFailure() + } + } + + _isLoading.value = false + }, + { + _isLoading.value = false + it.message?.let { message -> Logger.e(message) } + _toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.") + if (onFailure != null) { + onFailure() + } + } + ) + ) + } + + fun getAuditionApplicantList() { + _isLoading.value = true + + compositeDisposable.add( + repository.getAuditionApplicantList( + auditionRoleId = auditionRoleId, + sortType = _sortTypeLiveData.value!!, + page = page, + size = pageSize, + token = "Bearer ${SharedPreferenceManager.token}" + ) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + { + if (it.success && it.data != null) { + val data = it.data + + _totalCountLiveData.value = data.totalCount + _applicantListLiveData.value = data.items + page += 1 + } else { + if (it.message != null) { + _toastLiveData.value = it.message + } else { + _toastLiveData.value = "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요." + } + } + + _isLoading.value = false + }, + { + _isLoading.value = false + it.message?.let { message -> Logger.e(message) } + _toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.") + } + ) + ) + } + + fun setSortType(sortType: AuditionApplicantSortType) { + val prevSortType = _sortTypeLiveData.value!! + + if (sortType != prevSortType) { + _sortTypeLiveData.value = sortType + isLast = false + page = 1 + getAuditionApplicantList() + } + } + + enum class AuditionApplicantSortType { + @SerializedName("NEWEST") + NEWEST, + + @SerializedName("LIKES") + LIKES + } +} diff --git a/app/src/main/java/kr/co/vividnext/sodalive/audition/role/GetAuditionRoleDetailResponse.kt b/app/src/main/java/kr/co/vividnext/sodalive/audition/role/GetAuditionRoleDetailResponse.kt new file mode 100644 index 0000000..eaa8471 --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/audition/role/GetAuditionRoleDetailResponse.kt @@ -0,0 +1,15 @@ +package kr.co.vividnext.sodalive.audition.role + +import androidx.annotation.Keep +import com.google.gson.annotations.SerializedName + +@Keep +data class GetAuditionRoleDetailResponse( + @SerializedName("auditionRoleId") val auditionRoleId: Long, + @SerializedName("name") val name: String, + @SerializedName("imageUrl") val imageUrl: String, + @SerializedName("information") val information: String, + @SerializedName("originalWorkUrl") val originalWorkUrl: String, + @SerializedName("auditionScriptUrl") val auditionScriptUrl: String, + @SerializedName("isAlreadyApplicant") val isAlreadyApplicant: Boolean +) diff --git a/app/src/main/java/kr/co/vividnext/sodalive/common/Constants.kt b/app/src/main/java/kr/co/vividnext/sodalive/common/Constants.kt index 16aac00..92800c8 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/common/Constants.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/common/Constants.kt @@ -40,6 +40,7 @@ object Constants { const val EXTRA_LIVE_TIME_NOW = "extra_live_time_now" const val EXTRA_RESULT_ROULETTE = "extra_result_roulette" const val EXTRA_GO_TO_PREV_PAGE = "extra_go_to_prev_page" + const val EXTRA_AUDITION_ROLE_ID = "extra_audition_role_id" const val EXTRA_SELECT_RECIPIENT = "extra_select_recipient" const val EXTRA_ROOM_CHANNEL_NAME = "extra_room_channel_name" const val EXTRA_LIVE_RESERVATION_RESPONSE = "extra_live_reservation_response" diff --git a/app/src/main/java/kr/co/vividnext/sodalive/di/AppDI.kt b/app/src/main/java/kr/co/vividnext/sodalive/di/AppDI.kt index 2f6388f..6d2dc12 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/di/AppDI.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/di/AppDI.kt @@ -43,6 +43,7 @@ import kr.co.vividnext.sodalive.audition.AuditionApi import kr.co.vividnext.sodalive.audition.AuditionRepository import kr.co.vividnext.sodalive.audition.AuditionViewModel import kr.co.vividnext.sodalive.audition.detail.AuditionDetailViewModel +import kr.co.vividnext.sodalive.audition.role.AuditionRoleDetailViewModel import kr.co.vividnext.sodalive.common.ApiBuilder import kr.co.vividnext.sodalive.common.ObjectBox import kr.co.vividnext.sodalive.explorer.ExplorerApi @@ -286,6 +287,7 @@ class AppDI(private val context: Context, isDebugMode: Boolean) { viewModel { AudioContentPlayerViewModel() } viewModel { AuditionViewModel(get()) } viewModel { AuditionDetailViewModel(get()) } + viewModel { AuditionRoleDetailViewModel(get()) } } private val repositoryModule = module { diff --git a/app/src/main/res/drawable-xxhdpi/ic_audition_pause.png b/app/src/main/res/drawable-xxhdpi/ic_audition_pause.png new file mode 100644 index 0000000..7c0758c Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_audition_pause.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_audition_play.png b/app/src/main/res/drawable-xxhdpi/ic_audition_play.png new file mode 100644 index 0000000..542d947 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_audition_play.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_heart_vote.png b/app/src/main/res/drawable-xxhdpi/ic_heart_vote.png new file mode 100644 index 0000000..339bf6c Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_heart_vote.png differ diff --git a/app/src/main/res/layout/activity_audition_detail.xml b/app/src/main/res/layout/activity_audition_detail.xml index f3570db..f3bc83b 100644 --- a/app/src/main/res/layout/activity_audition_detail.xml +++ b/app/src/main/res/layout/activity_audition_detail.xml @@ -75,6 +75,7 @@ android:text="펼치기" android:textColor="@color/color_bbbbbb" android:textSize="12sp" + app:drawableStartCompat="@drawable/ic_live_detail_bottom" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/tv_information" /> diff --git a/app/src/main/res/layout/activity_audition_role_detail.xml b/app/src/main/res/layout/activity_audition_role_detail.xml new file mode 100644 index 0000000..c621903 --- /dev/null +++ b/app/src/main/res/layout/activity_audition_role_detail.xml @@ -0,0 +1,242 @@ +<?xml version="1.0" encoding="utf-8"?> +<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <include + android:id="@+id/toolbar" + layout="@layout/detail_toolbar" /> + + <androidx.core.widget.NestedScrollView + android:layout_width="0dp" + android:layout_height="0dp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/toolbar"> + + <androidx.constraintlayout.widget.ConstraintLayout + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + <ImageView + android:id="@+id/iv_cover" + android:layout_width="0dp" + android:layout_height="0dp" + android:layout_marginHorizontal="13.3dp" + android:layout_marginTop="2dp" + android:contentDescription="@null" + android:scaleType="centerCrop" + app:layout_constraintDimensionRatio="1000:350" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + tools:src="@drawable/ic_launcher_background" /> + + <LinearLayout + android:id="@+id/ll_audition_script" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginHorizontal="13.3dp" + android:layout_marginTop="15dp" + android:orientation="horizontal" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/iv_cover"> + + <TextView + android:id="@+id/tv_original_work" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:background="@drawable/bg_round_corner_10_transparent_3bb9f1" + android:fontFamily="@font/gmarket_sans_bold" + android:gravity="center" + android:paddingVertical="12dp" + android:text="원작 보러가기" + android:textColor="@color/color_3bb9f1" + android:textSize="16sp" /> + + <TextView + android:id="@+id/tv_audition_script" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginStart="14dp" + android:layout_weight="1" + android:background="@drawable/bg_round_corner_10_transparent_3bb9f1" + android:fontFamily="@font/gmarket_sans_bold" + android:gravity="center" + android:paddingVertical="12dp" + android:text="오디션 대본 확인" + android:textColor="@color/color_3bb9f1" + android:textSize="16sp" /> + </LinearLayout> + + <TextView + android:id="@+id/tv_information_title" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginHorizontal="13.3dp" + android:layout_marginTop="15dp" + android:fontFamily="@font/gmarket_sans_bold" + android:text="오디션 캐릭터 정보" + android:textColor="@color/color_eeeeee" + android:textSize="14.7sp" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/ll_audition_script" /> + + <TextView + android:id="@+id/tv_information" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginHorizontal="13.3dp" + android:layout_marginTop="15dp" + android:ellipsize="end" + android:fontFamily="@font/gmarket_sans_medium" + android:maxLines="3" + android:textColor="@color/color_777777" + android:textSize="13.3sp" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/tv_information_title" /> + + <TextView + android:id="@+id/tv_open" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:layout_marginTop="7dp" + android:drawableStart="@drawable/ic_live_detail_bottom" + android:drawablePadding="6.7dp" + android:fontFamily="@font/gmarket_sans_medium" + android:gravity="center" + android:text="펼치기" + android:textColor="@color/color_bbbbbb" + android:textSize="12sp" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/tv_information" /> + + <LinearLayout + android:id="@+id/ll_applicant_count" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="13.3dp" + android:layout_marginTop="15dp" + android:orientation="horizontal" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/tv_open"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:fontFamily="@font/gmarket_sans_medium" + android:text="참여자" + android:textColor="@color/color_bbbbbb" + android:textSize="10.7sp" /> + + <TextView + android:id="@+id/tv_applicant_count" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:fontFamily="@font/gmarket_sans_medium" + android:textColor="@color/color_3bb9f1" + android:textSize="10.7sp" + tools:text=" 24" /> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:fontFamily="@font/gmarket_sans_medium" + android:text="명" + android:textColor="@color/color_bbbbbb" + android:textSize="10.7sp" /> + </LinearLayout> + + <TextView + android:id="@+id/tv_sort_likes" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginEnd="13.3dp" + android:fontFamily="@font/gmarket_sans_medium" + android:text="좋아요순" + android:textColor="@color/color_bbbbbb" + android:textSize="10.7sp" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toTopOf="@+id/ll_applicant_count" /> + + <TextView + android:id="@+id/tv_sort_newest" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginEnd="13.3dp" + android:fontFamily="@font/gmarket_sans_medium" + android:text="최신순" + android:textColor="@color/color_3bb9f1" + android:textSize="10.7sp" + app:layout_constraintEnd_toStartOf="@+id/tv_sort_likes" + app:layout_constraintTop_toTopOf="@+id/tv_sort_likes" + tools:ignore="SmallSp" /> + + <androidx.recyclerview.widget.RecyclerView + android:id="@+id/rv_applicant" + android:layout_width="0dp" + android:layout_height="0dp" + android:layout_marginTop="12dp" + android:clipToPadding="false" + android:paddingHorizontal="13.3dp" + android:paddingBottom="12dp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/ll_applicant_count" /> + + <TextView + android:id="@+id/tv_no_applicant" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginHorizontal="13.3dp" + android:layout_marginTop="30dp" + android:fontFamily="@font/gmarket_sans_medium" + android:gravity="center" + android:text="지원자가 없습니다." + android:textColor="@color/color_bbbbbb" + android:textSize="13sp" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/tv_open" /> + + <androidx.constraintlayout.widget.Group + android:id="@+id/group_applicant" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:visibility="gone" + app:constraint_referenced_ids="ll_applicant_count, tv_sort_likes, tv_sort_newest, rv_applicant" /> + + <androidx.constraintlayout.widget.Group + android:id="@+id/group_no_applicant" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:visibility="gone" + app:constraint_referenced_ids="tv_no_applicant" /> + </androidx.constraintlayout.widget.ConstraintLayout> + </androidx.core.widget.NestedScrollView> + + <TextView + android:id="@+id/tv_applicant" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginEnd="19dp" + android:layout_marginBottom="19dp" + android:background="@drawable/bg_round_corner_44_3bb9f1" + android:fontFamily="@font/gmarket_sans_bold" + android:padding="14dp" + android:text="오디션 지원하기" + android:textColor="@color/white" + android:textSize="15.3sp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" /> +</androidx.constraintlayout.widget.ConstraintLayout> diff --git a/app/src/main/res/layout/item_audition_applicant.xml b/app/src/main/res/layout/item_audition_applicant.xml new file mode 100644 index 0000000..ba010a5 --- /dev/null +++ b/app/src/main/res/layout/item_audition_applicant.xml @@ -0,0 +1,70 @@ +<?xml version="1.0" encoding="utf-8"?> +<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingHorizontal="13.3dp" + android:paddingVertical="18.7dp" + tools:background="@color/black"> + + <ImageView + android:id="@+id/iv_profile" + android:layout_width="40dp" + android:layout_height="40dp" + android:contentDescription="@null" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + + <ImageView + android:id="@+id/iv_play_or_pause" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:contentDescription="@null" + android:src="@drawable/ic_audition_play" + app:layout_constraintBottom_toBottomOf="@+id/iv_profile" + app:layout_constraintEnd_toEndOf="@+id/iv_profile" + app:layout_constraintStart_toStartOf="@+id/iv_profile" + app:layout_constraintTop_toTopOf="@+id/iv_profile" /> + + <TextView + android:id="@+id/tv_nickname" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="13.3dp" + android:fontFamily="@font/gmarket_sans_medium" + android:textColor="@color/white" + android:textSize="12sp" + app:layout_constraintBottom_toBottomOf="@+id/iv_profile" + app:layout_constraintStart_toEndOf="@+id/iv_profile" + app:layout_constraintTop_toTopOf="@+id/iv_profile" + tools:text="닉네임" /> + + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:gravity="center" + android:orientation="vertical" + app:layout_constraintBottom_toBottomOf="@+id/iv_profile" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toTopOf="@+id/iv_profile" + tools:ignore="UseCompoundDrawables"> + + <ImageView + android:id="@+id/iv_vote" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:contentDescription="@null" + android:src="@drawable/ic_heart_vote" /> + + <TextView + android:id="@+id/tv_count_vote" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="2.3dp" + android:fontFamily="@font/gmarket_sans_medium" + android:textColor="@color/color_777777" + android:textSize="12sp" + tools:text="777" /> + </LinearLayout> +</androidx.constraintlayout.widget.ConstraintLayout> diff --git a/app/src/main/res/layout/item_audition_role.xml b/app/src/main/res/layout/item_audition_role.xml index 4d5ceea..ea7bc34 100644 --- a/app/src/main/res/layout/item_audition_role.xml +++ b/app/src/main/res/layout/item_audition_role.xml @@ -3,8 +3,8 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" - tools:background="@color/black" - android:layout_height="wrap_content"> + android:layout_height="wrap_content" + tools:background="@color/black"> <ImageView android:id="@+id/iv_cover"