diff --git a/app/build.gradle b/app/build.gradle index cb006b1..dda3c75 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -98,7 +98,7 @@ dependencies { } // Gson - implementation "com.google.code.gson:gson:2.9.0" + implementation "com.google.code.gson:gson:2.9.1" // Network implementation "com.squareup.retrofit2:retrofit:2.9.0" @@ -115,6 +115,7 @@ dependencies { implementation "io.github.ParkSangGwon:tedpermission-normal:3.3.0" implementation 'com.github.dhaval2404:imagepicker:2.1' + implementation 'com.github.zhpanvip:bannerviewpager:3.5.7' implementation 'com.google.android.gms:play-services-oss-licenses:17.0.1' 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 b700cbf..5eff772 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 @@ -10,11 +10,12 @@ object Constants { const val PREF_USER_ROLE = "pref_user_role" const val PREF_PUSH_TOKEN = "pref_push_token" const val PREF_PROFILE_IMAGE = "pref_profile_image" + const val PREF_IS_FOLLOWED_CREATOR_LIVE = "pref_is_followed_creator_live" const val EXTRA_DATA = "extra_data" const val EXTRA_TERMS = "extra_terms" - const val EXTRA_USER_ID = "extra_user_id" const val EXTRA_ROOM_ID = "extra_room_id" + const val EXTRA_USER_ID = "extra_user_id" const val EXTRA_MESSAGE_ID = "extra_message_id" const val EXTRA_CONTENT_ID = "extra_content_id" diff --git a/app/src/main/java/kr/co/vividnext/sodalive/common/SharedPreferenceManager.kt b/app/src/main/java/kr/co/vividnext/sodalive/common/SharedPreferenceManager.kt index 60bbe3f..1e77cd3 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/common/SharedPreferenceManager.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/common/SharedPreferenceManager.kt @@ -98,4 +98,10 @@ object SharedPreferenceManager { set(value) { sharedPreferences[Constants.PREF_PUSH_TOKEN] = value } + + var isFollowedCreatorLive: Boolean + get() = sharedPreferences[Constants.PREF_IS_FOLLOWED_CREATOR_LIVE, false] + set(value) { + sharedPreferences[Constants.PREF_IS_FOLLOWED_CREATOR_LIVE] = value + } } 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 8e37ae9..7b70ef8 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 @@ -4,8 +4,15 @@ import android.content.Context import com.google.gson.GsonBuilder import kr.co.vividnext.sodalive.BuildConfig import kr.co.vividnext.sodalive.common.ApiBuilder +import kr.co.vividnext.sodalive.live.LiveApi +import kr.co.vividnext.sodalive.live.LiveRepository +import kr.co.vividnext.sodalive.live.LiveViewModel +import kr.co.vividnext.sodalive.live.recommend.LiveRecommendApi +import kr.co.vividnext.sodalive.live.recommend.LiveRecommendRepository import kr.co.vividnext.sodalive.main.MainViewModel import kr.co.vividnext.sodalive.network.TokenAuthenticator +import kr.co.vividnext.sodalive.settings.event.EventApi +import kr.co.vividnext.sodalive.settings.event.EventRepository import kr.co.vividnext.sodalive.settings.terms.TermsApi import kr.co.vividnext.sodalive.settings.terms.TermsRepository import kr.co.vividnext.sodalive.settings.terms.TermsViewModel @@ -58,6 +65,9 @@ class AppDI(private val context: Context, isDebugMode: Boolean) { single { ApiBuilder().build(get(), UserApi::class.java) } single { ApiBuilder().build(get(), TermsApi::class.java) } + single { ApiBuilder().build(get(), LiveApi::class.java) } + single { ApiBuilder().build(get(), EventApi::class.java) } + single { ApiBuilder().build(get(), LiveRecommendApi::class.java) } } private val viewModelModule = module { @@ -66,11 +76,15 @@ class AppDI(private val context: Context, isDebugMode: Boolean) { viewModel { TermsViewModel(get()) } viewModel { FindPasswordViewModel(get()) } viewModel { MainViewModel(get()) } + viewModel { LiveViewModel(get(), get(), get()) } } private val repositoryModule = module { factory { UserRepository(get()) } factory { TermsRepository(get()) } + factory { LiveRepository(get()) } + factory { EventRepository(get()) } + factory { LiveRecommendRepository(get()) } } private val moduleList = listOf( diff --git a/app/src/main/java/kr/co/vividnext/sodalive/live/GetRoomListResponse.kt b/app/src/main/java/kr/co/vividnext/sodalive/live/GetRoomListResponse.kt new file mode 100644 index 0000000..a0b0860 --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/live/GetRoomListResponse.kt @@ -0,0 +1,21 @@ +package kr.co.vividnext.sodalive.live + +import com.google.gson.annotations.SerializedName + +data class GetRoomListResponse( + @SerializedName("roomId") val roomId: Long, + @SerializedName("title") val title: String, + @SerializedName("content") val content: String, + @SerializedName("beginDateTime") val beginDateTime: String, + @SerializedName("numberOfParticipate") val numberOfParticipate: Int, + @SerializedName("numberOfPeople") val numberOfPeople: Int, + @SerializedName("coverImageUrl") val coverImageUrl: String, + @SerializedName("isAdult") val isAdult: Boolean, + @SerializedName("price") val price: Int, + @SerializedName("tags") val tags: List, + @SerializedName("channelName") val channelName: String?, + @SerializedName("managerNickname") val managerNickname: String, + @SerializedName("managerId") val managerId: Long, + @SerializedName("isReservation") val isReservation: Boolean, + @SerializedName("isPrivateRoom") val isPrivateRoom: Boolean +) diff --git a/app/src/main/java/kr/co/vividnext/sodalive/live/LiveApi.kt b/app/src/main/java/kr/co/vividnext/sodalive/live/LiveApi.kt new file mode 100644 index 0000000..fc0d70d --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/live/LiveApi.kt @@ -0,0 +1,20 @@ +package kr.co.vividnext.sodalive.live + +import io.reactivex.rxjava3.core.Flowable +import kr.co.vividnext.sodalive.common.ApiResponse +import kr.co.vividnext.sodalive.live.room.LiveRoomStatus +import retrofit2.http.GET +import retrofit2.http.Header +import retrofit2.http.Query + +interface LiveApi { + @GET("/live/room") + fun roomList( + @Query("timezone") timezone: String, + @Query("dateString") dateString: String?, + @Query("status") status: LiveRoomStatus, + @Query("page") page: Int, + @Query("size") size: Int, + @Header("Authorization") authHeader: String + ): Flowable>> +} diff --git a/app/src/main/java/kr/co/vividnext/sodalive/live/LiveFragment.kt b/app/src/main/java/kr/co/vividnext/sodalive/live/LiveFragment.kt index c46c85e..7cf3dfe 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/live/LiveFragment.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/live/LiveFragment.kt @@ -1,7 +1,403 @@ package kr.co.vividnext.sodalive.live +import android.annotation.SuppressLint +import android.content.Intent +import android.graphics.Rect +import android.net.Uri +import android.os.Bundle +import android.view.View +import android.webkit.URLUtil +import android.widget.LinearLayout +import android.widget.Toast +import androidx.core.content.ContextCompat +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.zhpan.bannerview.BaseBannerAdapter +import com.zhpan.indicator.enums.IndicatorSlideMode +import com.zhpan.indicator.enums.IndicatorStyle +import kr.co.vividnext.sodalive.R import kr.co.vividnext.sodalive.base.BaseFragment +import kr.co.vividnext.sodalive.common.LoadingDialog +import kr.co.vividnext.sodalive.common.SharedPreferenceManager import kr.co.vividnext.sodalive.databinding.FragmentLiveBinding +import kr.co.vividnext.sodalive.extensions.dpToPx +import kr.co.vividnext.sodalive.live.event_banner.EventBannerAdapter +import kr.co.vividnext.sodalive.live.now.LiveNowAdapter +import kr.co.vividnext.sodalive.live.recommend.RecommendLiveAdapter +import kr.co.vividnext.sodalive.live.recommend_channel.LiveRecommendChannelAdapter +import kr.co.vividnext.sodalive.live.reservation.LiveReservationAdapter +import kr.co.vividnext.sodalive.settings.notification.MemberRole +import org.koin.android.ext.android.inject +import kotlin.math.roundToInt class LiveFragment : BaseFragment(FragmentLiveBinding::inflate) { + private val viewModel: LiveViewModel by inject() + + private lateinit var liveNowAdapter: LiveNowAdapter + private lateinit var liveReservationAdapter: LiveReservationAdapter + private lateinit var liveRecommendChannelAdapter: LiveRecommendChannelAdapter + + private lateinit var loadingDialog: LoadingDialog + + private var message = "" + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + setupView() + setupRecommendLive() + setupRecommendChannel() + setupLiveNow() + setupLiveReservation() + setupEvent() + + message = "라이브를 불러오고 있습니다." + viewModel.getSummary() + } + + private fun setupView() { + loadingDialog = LoadingDialog(requireActivity(), layoutInflater) + + viewModel.toastLiveData.observe(viewLifecycleOwner) { + it?.let { Toast.makeText(requireActivity(), it, Toast.LENGTH_LONG).show() } + } + + viewModel.isLoading.observe(viewLifecycleOwner) { + if (it) { + loadingDialog.show(screenWidth, message) + } else { + loadingDialog.dismiss() + } + } + + binding.ivMakeRoom.visibility = + if (SharedPreferenceManager.role == MemberRole.CREATOR.name) { + View.VISIBLE + } else { + View.GONE + } + binding.ivMakeRoom.setOnClickListener {} + + binding.swipeRefreshLayout.setOnRefreshListener { refreshSummary() } + + val ivHowToUseLp = binding.ivHowToUse.layoutParams as LinearLayout.LayoutParams + ivHowToUseLp.width = screenWidth + ivHowToUseLp.height = (200 * screenWidth) / 1080 + binding.ivHowToUse.layoutParams = ivHowToUseLp + binding.ivHowToUse.setOnClickListener { + val url = "https://blog.naver.com/yozmlive" + if (URLUtil.isValidUrl(url)) { + startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url))) + } + } + } + + private fun refreshSummary() { + liveNowAdapter.clear() + liveRecommendChannelAdapter.clear() + liveReservationAdapter.clear() + + message = "라이브를 불러오고 있습니다." + viewModel.getSummary() + + binding.swipeRefreshLayout.isRefreshing = false + } + + @SuppressLint("NotifyDataSetChanged") + private fun setupRecommendLive() { + val layoutParams = binding + .layoutRecommendLive + .pager + .layoutParams as LinearLayout.LayoutParams + + val pagerWidth = screenWidth.toDouble() - 26.7f.dpToPx() + val pagerHeight = (pagerWidth * 0.53).roundToInt() + layoutParams.width = pagerWidth.roundToInt() + layoutParams.height = pagerHeight + + binding + .layoutRecommendLive + .pager + .layoutParams = layoutParams + + binding.layoutRecommendLive.pager.apply { + adapter = RecommendLiveAdapter(pagerWidth.roundToInt(), pagerHeight) { + } as BaseBannerAdapter + + setLifecycleRegistry(lifecycle) + setScrollDuration(1000) + setInterval(4 * 1000) + }.create() + + binding + .layoutRecommendLive + .pager + .setIndicatorView(binding.layoutRecommendLive.indicator2) + .setIndicatorStyle(IndicatorStyle.ROUND_RECT) + .setIndicatorSlideMode(IndicatorSlideMode.SMOOTH) + .setIndicatorVisibility(View.GONE) + .setIndicatorSliderColor( + ContextCompat.getColor(requireContext(), R.color.color_909090), + ContextCompat.getColor(requireContext(), R.color.color_9970ff) + ) + .setIndicatorSliderWidth(4f.dpToPx().toInt(), 10f.dpToPx().toInt()) + .setIndicatorHeight(4f.dpToPx().toInt()) + + viewModel.recommendLiveData.observe(viewLifecycleOwner) { + if (it.isNotEmpty()) { + binding.layoutRecommendLive.root.visibility = View.VISIBLE + binding.layoutRecommendLive.pager.refreshData(it) + } else { + binding.layoutRecommendLive.root.visibility = View.GONE + } + } + } + + private fun setupRecommendChannel() { + liveRecommendChannelAdapter = LiveRecommendChannelAdapter( + onClick = {}, + onClickMore = {} + ) + + binding.layoutRecommendChannel.rvRecommendChannel.layoutManager = LinearLayoutManager( + requireContext(), + LinearLayoutManager.HORIZONTAL, + false + ) + + binding + .layoutRecommendChannel + .rvRecommendChannel + .addItemDecoration(object : RecyclerView.ItemDecoration() { + override fun getItemOffsets( + outRect: Rect, + view: View, + parent: RecyclerView, + state: RecyclerView.State + ) { + super.getItemOffsets(outRect, view, parent, state) + + when (parent.getChildAdapterPosition(view)) { + 0 -> { + outRect.left = 0.dpToPx().toInt() + outRect.right = 8.3f.dpToPx().toInt() + } + + liveRecommendChannelAdapter.itemCount - 1 -> { + outRect.left = 8.3f.dpToPx().toInt() + outRect.right = 0.dpToPx().toInt() + } + + else -> { + outRect.left = 8.3f.dpToPx().toInt() + outRect.right = 8.3f.dpToPx().toInt() + } + } + } + }) + + binding.layoutRecommendChannel.rvRecommendChannel.adapter = liveRecommendChannelAdapter + binding.layoutRecommendChannel.ivSwitch.setOnClickListener { + viewModel.toggleIsFollowedCreatorLive() + } + + viewModel.recommendChannelLiveData.observe(viewLifecycleOwner) { + binding.layoutRecommendChannel.root.visibility = View.VISIBLE + liveRecommendChannelAdapter.addItems(it) + binding.layoutRecommendChannel.rvRecommendChannel.requestLayout() + binding.layoutRecommendChannel.root.requestLayout() + } + + viewModel.isFollowedCreatorLive.observe(viewLifecycleOwner) { + liveRecommendChannelAdapter.isFollowedCreatorLive = it + liveRecommendChannelAdapter.clear() + + if (it) { + binding.layoutRecommendChannel.ivSwitch.setImageResource(R.drawable.btn_toggle_on_big) + binding.layoutRecommendChannel.llTitle2.visibility = View.VISIBLE + binding.layoutRecommendChannel.llTitle1.visibility = View.GONE + } else { + binding.layoutRecommendChannel.ivSwitch.setImageResource(R.drawable.btn_toggle_off_big) + binding.layoutRecommendChannel.llTitle1.visibility = View.VISIBLE + binding.layoutRecommendChannel.llTitle2.visibility = View.GONE + } + } + } + + @SuppressLint("NotifyDataSetChanged") + private fun setupLiveNow() { + binding + .layoutLiveNow + .tvAllView + .setOnClickListener {} + + val recyclerView = binding + .layoutLiveNow + .rvSudaNow + + liveNowAdapter = LiveNowAdapter {} + + recyclerView.layoutManager = LinearLayoutManager( + requireContext(), + LinearLayoutManager.HORIZONTAL, + false + ) + + recyclerView.addItemDecoration(object : RecyclerView.ItemDecoration() { + override fun getItemOffsets( + outRect: Rect, + view: View, + parent: RecyclerView, + state: RecyclerView.State + ) { + super.getItemOffsets(outRect, view, parent, state) + + when (parent.getChildAdapterPosition(view)) { + 0 -> { + outRect.left = 13.3f.dpToPx().toInt() + outRect.right = 5.dpToPx().toInt() + } + + liveNowAdapter.itemCount - 1 -> { + outRect.left = 5.dpToPx().toInt() + outRect.right = 13.3f.dpToPx().toInt() + } + + else -> { + outRect.left = 5.dpToPx().toInt() + outRect.right = 5.dpToPx().toInt() + } + } + } + }) + + recyclerView.adapter = liveNowAdapter + + viewModel.liveNowLiveData.observe(viewLifecycleOwner) { + if (liveNowAdapter.items.isEmpty() && it.isEmpty()) { + recyclerView.visibility = View.GONE + binding.layoutLiveNow.tvAllView.visibility = View.GONE + binding.layoutLiveNow.llNoItems.visibility = View.VISIBLE + binding.layoutLiveNow.tvMakeRoom.setOnClickListener {} + + recyclerView.requestLayout() + binding.layoutLiveNow.llNoItems.requestLayout() + } else { + binding.layoutLiveNow.tvAllView.visibility = View.VISIBLE + binding.layoutLiveNow.llNoItems.visibility = View.GONE + liveNowAdapter.items.addAll(it) + liveNowAdapter.notifyDataSetChanged() + recyclerView.visibility = View.VISIBLE + recyclerView.requestLayout() + } + + binding.layoutLiveNow.root.requestLayout() + } + } + + @SuppressLint("NotifyDataSetChanged") + private fun setupLiveReservation() { + val recyclerView = binding + .layoutLiveReservation + .rvSudaReservation + + liveReservationAdapter = LiveReservationAdapter(isMain = true) {} + + recyclerView.layoutManager = LinearLayoutManager( + requireContext(), + LinearLayoutManager.VERTICAL, + false + ) + + recyclerView.addItemDecoration(object : RecyclerView.ItemDecoration() { + override fun getItemOffsets( + outRect: Rect, + view: View, + parent: RecyclerView, + state: RecyclerView.State + ) { + super.getItemOffsets(outRect, view, parent, state) + + outRect.left = 13.3f.dpToPx().toInt() + outRect.right = 13.3f.dpToPx().toInt() + + when (parent.getChildAdapterPosition(view)) { + 0 -> { + outRect.top = 0f.dpToPx().toInt() + outRect.bottom = 6.7f.dpToPx().toInt() + } + + liveReservationAdapter.itemCount - 1 -> { + outRect.top = 6.7f.dpToPx().toInt() + outRect.bottom = 13.3f.dpToPx().toInt() + } + + else -> { + outRect.top = 6.7f.dpToPx().toInt() + outRect.bottom = 6.7f.dpToPx().toInt() + } + } + } + }) + + recyclerView.adapter = liveReservationAdapter + + viewModel.liveReservationLiveData.observe(viewLifecycleOwner) { + if (liveReservationAdapter.items.isEmpty() && it.isEmpty()) { + recyclerView.visibility = View.GONE + binding.layoutLiveReservation.tvAllView.visibility = View.GONE + binding.layoutLiveReservation.llNoItems.visibility = View.VISIBLE + binding.layoutLiveReservation.tvMakeRoom.setOnClickListener {} + + recyclerView.requestLayout() + binding.layoutLiveReservation.llNoItems.requestLayout() + } else { + binding.layoutLiveReservation.tvAllView.visibility = View.VISIBLE + binding.layoutLiveReservation.llNoItems.visibility = View.GONE + liveReservationAdapter.items.addAll(it) + liveReservationAdapter.notifyDataSetChanged() + + recyclerView.visibility = View.VISIBLE + recyclerView.requestLayout() + } + + binding.layoutLiveReservation.root.requestLayout() + } + + binding.layoutLiveReservation.tvAllView.setOnClickListener {} + } + + @SuppressLint("NotifyDataSetChanged") + private fun setupEvent() { + val imageSliderLp = binding.eventBannerSlider.layoutParams + imageSliderLp.width = screenWidth + imageSliderLp.height = (screenWidth * 300) / 1000 + binding.eventBannerSlider.layoutParams = imageSliderLp + + binding.eventBannerSlider.apply { + adapter = EventBannerAdapter(requireContext()) {} as BaseBannerAdapter + setLifecycleRegistry(lifecycle) + setScrollDuration(800) + }.create() + + binding.eventBannerSlider + .setIndicatorView(binding.indicator) + .setIndicatorStyle(IndicatorStyle.ROUND_RECT) + .setIndicatorSlideMode(IndicatorSlideMode.SMOOTH) + .setIndicatorVisibility(View.GONE) + .setIndicatorSliderColor( + ContextCompat.getColor(requireContext(), R.color.color_909090), + ContextCompat.getColor(requireContext(), R.color.color_9970ff) + ) + .setIndicatorSliderWidth(4f.dpToPx().toInt(), 10f.dpToPx().toInt()) + .setIndicatorHeight(4f.dpToPx().toInt()) + + viewModel.eventLiveData.observe(viewLifecycleOwner) { + if (it.isNotEmpty()) { + binding.eventBannerSlider.visibility = View.VISIBLE + binding.eventBannerSlider.refreshData(it) + } else { + binding.eventBannerSlider.visibility = View.GONE + } + } + } } diff --git a/app/src/main/java/kr/co/vividnext/sodalive/live/LiveRepository.kt b/app/src/main/java/kr/co/vividnext/sodalive/live/LiveRepository.kt new file mode 100644 index 0000000..e0ca9e0 --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/live/LiveRepository.kt @@ -0,0 +1,25 @@ +package kr.co.vividnext.sodalive.live + +import io.reactivex.rxjava3.core.Flowable +import kr.co.vividnext.sodalive.common.ApiResponse +import kr.co.vividnext.sodalive.live.room.LiveRoomStatus +import java.util.TimeZone + +class LiveRepository(private val api: LiveApi) { + fun roomList( + dateString: String? = null, + status: LiveRoomStatus, + page: Int, + size: Int, + token: String + ): Flowable>> { + return api.roomList( + timezone = TimeZone.getDefault().id, + dateString = dateString, + status = status, + page = page - 1, + size = size, + authHeader = token + ) + } +} diff --git a/app/src/main/java/kr/co/vividnext/sodalive/live/LiveSummary.kt b/app/src/main/java/kr/co/vividnext/sodalive/live/LiveSummary.kt new file mode 100644 index 0000000..206ba1a --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/live/LiveSummary.kt @@ -0,0 +1,13 @@ +package kr.co.vividnext.sodalive.live + +import com.google.gson.annotations.SerializedName +import kr.co.vividnext.sodalive.common.ApiResponse +import kr.co.vividnext.sodalive.live.recommend.GetRecommendLiveResponse +import kr.co.vividnext.sodalive.settings.event.GetEventResponse + +data class LiveSummary( + @SerializedName("liveNow") val liveNow: ApiResponse>, + @SerializedName("liveReservation") val liveReservation: ApiResponse>, + @SerializedName("event") val event: ApiResponse, + @SerializedName("recommendLive") val recommendLive: ApiResponse>, +) diff --git a/app/src/main/java/kr/co/vividnext/sodalive/live/LiveViewModel.kt b/app/src/main/java/kr/co/vividnext/sodalive/live/LiveViewModel.kt new file mode 100644 index 0000000..bba57cb --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/live/LiveViewModel.kt @@ -0,0 +1,241 @@ +package kr.co.vividnext.sodalive.live + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import com.orhanobut.logger.Logger +import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers +import io.reactivex.rxjava3.core.Flowable +import io.reactivex.rxjava3.schedulers.Schedulers +import kr.co.vividnext.sodalive.base.BaseViewModel +import kr.co.vividnext.sodalive.common.SharedPreferenceManager +import kr.co.vividnext.sodalive.live.recommend.GetRecommendLiveResponse +import kr.co.vividnext.sodalive.live.recommend.LiveRecommendRepository +import kr.co.vividnext.sodalive.live.recommend_channel.GetRecommendChannelResponse +import kr.co.vividnext.sodalive.live.room.LiveRoomStatus +import kr.co.vividnext.sodalive.settings.event.EventItem +import kr.co.vividnext.sodalive.settings.event.EventRepository + +class LiveViewModel( + private val repository: LiveRepository, + private val eventRepository: EventRepository, + private val liveRecommendRepository: LiveRecommendRepository +) : BaseViewModel() { + private var _isLoading = MutableLiveData(false) + val isLoading: LiveData + get() = _isLoading + + private val _toastLiveData = MutableLiveData() + val toastLiveData: LiveData + get() = _toastLiveData + + private val _recommendLiveData = MutableLiveData>() + val recommendLiveData: LiveData> + get() = _recommendLiveData + + private val _isFollowedCreatorLive = MutableLiveData( + SharedPreferenceManager.isFollowedCreatorLive + ) + val isFollowedCreatorLive: LiveData + get() = _isFollowedCreatorLive + + private val _recommendChannelLiveData = MutableLiveData>() + val recommendChannelLiveData: LiveData> + get() = _recommendChannelLiveData + + private val _liveNowLiveData = MutableLiveData>() + val liveNowLiveData: LiveData> + get() = _liveNowLiveData + + private val _liveReservationLiveData = MutableLiveData>() + val liveReservationLiveData: LiveData> + get() = _liveReservationLiveData + + private val _eventLiveData = MutableLiveData>() + val eventLiveData: LiveData> + get() = _eventLiveData + + var page = 1 + var isLast = false + private val pageSize = 10 + + fun toggleIsFollowedCreatorLive() { + val isOn = !_isFollowedCreatorLive.value!! + SharedPreferenceManager.isFollowedCreatorLive = isOn + _isFollowedCreatorLive.value = isOn + if (_isFollowedCreatorLive.value!!) { + getFollowedChannelList() + } else { + getRecommendChannelList() + } + } + + private fun getFollowedChannelList() { + compositeDisposable.add( + liveRecommendRepository.getFollowingChannelList( + token = "Bearer ${SharedPreferenceManager.token}" + ) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + { + if (it.success && it.data != null) { + _recommendChannelLiveData.postValue(it.data!!) + } else { + if (it.message != null) { + _toastLiveData.postValue(it.message) + } else { + _toastLiveData.postValue( + "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요." + ) + } + } + }, + { + _isLoading.value = false + it.message?.let { message -> Logger.e(message) } + _toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.") + } + ) + ) + } + + private fun getRecommendChannelList() { + compositeDisposable.add( + liveRecommendRepository.getRecommendChannelList( + token = "Bearer ${SharedPreferenceManager.token}" + ) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + { + if (it.success && it.data != null) { + _recommendChannelLiveData.postValue(it.data!!) + } 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 getSummary() { + if (!_isLoading.value!!) { + if (_isFollowedCreatorLive.value!!) { + getFollowedChannelList() + } else { + getRecommendChannelList() + } + + val liveNow = repository.roomList( + status = LiveRoomStatus.NOW, + page = 1, + size = pageSize, + token = "Bearer ${SharedPreferenceManager.token}" + ) + + val liveReservation = repository.roomList( + status = LiveRoomStatus.RESERVATION, + page = 1, + size = pageSize, + token = "Bearer ${SharedPreferenceManager.token}" + ) + + val event = eventRepository.getEvents( + 0, + 5, + token = "Bearer ${SharedPreferenceManager.token}" + ) + + val recommendLive = liveRecommendRepository.getRecommendLive( + token = "Bearer ${SharedPreferenceManager.token}" + ) + + _isLoading.postValue(true) + + compositeDisposable.add( + Flowable.combineLatest( + liveNow, + liveReservation, + event, + recommendLive, + ) { t1, t2, t3, t4 -> LiveSummary(t1, t2, t3, t4) } + .subscribe( + { + val now = it.liveNow + if (now.success && now.data != null) { + _liveNowLiveData.postValue(now.data!!) + if (now.data.isNotEmpty()) { + page += 1 + } else { + isLast = true + } + } else { + if (now.message != null) { + _toastLiveData.postValue(now.message) + } else { + _toastLiveData.postValue( + "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요." + ) + } + } + + val reservation = it.liveReservation + if (reservation.success && reservation.data != null) { + _liveReservationLiveData.postValue(reservation.data!!) + if (reservation.data.isNotEmpty()) { + page += 1 + } else { + isLast = true + } + } else { + if (reservation.message != null) { + _toastLiveData.postValue(reservation.message) + } else { + _toastLiveData.postValue( + "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요." + ) + } + } + + val eventResponse = it.event + if (eventResponse.success && eventResponse.data != null) { + val data = eventResponse.data + _eventLiveData.postValue(data.eventList) + } else { + _eventLiveData.postValue(emptyList()) + } + + val recommendLiveResponse = it.recommendLive + if ( + recommendLiveResponse.success && + recommendLiveResponse.data != null + ) { + val data = recommendLiveResponse.data + _recommendLiveData.postValue(data!!) + } else { + _recommendLiveData.postValue(emptyList()) + } + + _isLoading.postValue(false) + }, + { + _isLoading.postValue(false) + it.message?.let { message -> Logger.e(message) } + _toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.") + } + ) + ) + } + } +} diff --git a/app/src/main/java/kr/co/vividnext/sodalive/live/event_banner/EventBannerAdapter.kt b/app/src/main/java/kr/co/vividnext/sodalive/live/event_banner/EventBannerAdapter.kt new file mode 100644 index 0000000..cdaed10 --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/live/event_banner/EventBannerAdapter.kt @@ -0,0 +1,38 @@ +package kr.co.vividnext.sodalive.live.event_banner + +import android.content.Context +import android.widget.ImageView +import coil.load +import com.zhpan.bannerview.BaseBannerAdapter +import com.zhpan.bannerview.BaseViewHolder +import kr.co.vividnext.sodalive.R +import kr.co.vividnext.sodalive.settings.event.EventItem + +class EventBannerAdapter( + private val context: Context, + private val itemClick: (EventItem) -> Unit +) : BaseBannerAdapter() { + + override fun bindData( + holder: BaseViewHolder, + data: EventItem, + position: Int, + pageSize: Int + ) { + val ivThumbnail = holder.findViewById(R.id.iv_thumbnail) + ivThumbnail.load(data.thumbnailImageUrl) { + crossfade(true) + + val layoutParams = ivThumbnail.layoutParams + val screenWidth = context.resources.displayMetrics.widthPixels + layoutParams.width = screenWidth + layoutParams.height = (screenWidth * 300) / 1000 + ivThumbnail.layoutParams = layoutParams + } + ivThumbnail.setOnClickListener { itemClick(data) } + } + + override fun getLayoutId(viewType: Int): Int { + return R.layout.item_event_slider + } +} diff --git a/app/src/main/java/kr/co/vividnext/sodalive/live/now/LiveNowAdapter.kt b/app/src/main/java/kr/co/vividnext/sodalive/live/now/LiveNowAdapter.kt new file mode 100644 index 0000000..52a73a2 --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/live/now/LiveNowAdapter.kt @@ -0,0 +1,76 @@ +package kr.co.vividnext.sodalive.live.now + +import android.annotation.SuppressLint +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import coil.load +import coil.transform.RoundedCornersTransformation +import kr.co.vividnext.sodalive.R +import kr.co.vividnext.sodalive.databinding.ItemLiveNowBinding +import kr.co.vividnext.sodalive.extensions.dpToPx +import kr.co.vividnext.sodalive.live.GetRoomListResponse + +class LiveNowAdapter( + private val onClick: (GetRoomListResponse) -> Unit +) : RecyclerView.Adapter() { + + var items = mutableListOf() + + inner class ViewHolder( + private val binding: ItemLiveNowBinding + ) : RecyclerView.ViewHolder(binding.root) { + + fun bind(item: GetRoomListResponse) { + binding.ivCover.load(item.coverImageUrl) { + crossfade(true) + placeholder(R.drawable.bg_placeholder) + transformations(RoundedCornersTransformation(4.7f.dpToPx())) + } + binding.tvManager.text = item.managerNickname + binding.tvNumberOfMembers.text = "${item.numberOfParticipate}" + binding.ivLock.visibility = if (item.isPrivateRoom) { + View.VISIBLE + } else { + View.GONE + } + + if (item.price > 0) { + binding.tvPrice.text = "유료" + binding.tvPrice.setBackgroundResource(R.drawable.bg_round_corner_10_881609) + } else { + binding.tvPrice.text = "무료" + binding.tvPrice.setBackgroundResource(R.drawable.bg_round_corner_10_643bc8) + } + + binding.iv19.visibility = if (item.isAdult) { + View.VISIBLE + } else { + View.GONE + } + + binding.root.setOnClickListener { onClick(item) } + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder( + ItemLiveNowBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ) + ) + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + holder.bind(items[position]) + } + + override fun getItemCount() = items.count() + + @SuppressLint("NotifyDataSetChanged") + fun clear() { + items.clear() + notifyDataSetChanged() + } +} diff --git a/app/src/main/java/kr/co/vividnext/sodalive/live/recommend/GetRecommendLiveResponse.kt b/app/src/main/java/kr/co/vividnext/sodalive/live/recommend/GetRecommendLiveResponse.kt new file mode 100644 index 0000000..891e9e4 --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/live/recommend/GetRecommendLiveResponse.kt @@ -0,0 +1,10 @@ +package kr.co.vividnext.sodalive.live.recommend + +import com.google.gson.annotations.SerializedName + +data class GetRecommendLiveResponse( + @SerializedName("imageUrl") + val imageUrl: String, + @SerializedName("creatorId") + val creatorId: Long +) diff --git a/app/src/main/java/kr/co/vividnext/sodalive/live/recommend/LiveRecommendApi.kt b/app/src/main/java/kr/co/vividnext/sodalive/live/recommend/LiveRecommendApi.kt new file mode 100644 index 0000000..9f7f9dc --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/live/recommend/LiveRecommendApi.kt @@ -0,0 +1,25 @@ +package kr.co.vividnext.sodalive.live.recommend + +import io.reactivex.rxjava3.core.Flowable +import io.reactivex.rxjava3.core.Single +import kr.co.vividnext.sodalive.common.ApiResponse +import kr.co.vividnext.sodalive.live.recommend_channel.GetRecommendChannelResponse +import retrofit2.http.GET +import retrofit2.http.Header + +interface LiveRecommendApi { + @GET("/live/recommend") + fun getRecommendLive( + @Header("Authorization") authHeader: String + ): Flowable>> + + @GET("/live/recommend/channel") + fun getRecommendChannelList( + @Header("Authorization") authHeader: String + ): Flowable>> + + @GET("/live/following/channel") + fun getFollowingChannelList( + @Header("Authorization") authHeader: String + ): Single>> +} diff --git a/app/src/main/java/kr/co/vividnext/sodalive/live/recommend/LiveRecommendRepository.kt b/app/src/main/java/kr/co/vividnext/sodalive/live/recommend/LiveRecommendRepository.kt new file mode 100644 index 0000000..989d6c4 --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/live/recommend/LiveRecommendRepository.kt @@ -0,0 +1,7 @@ +package kr.co.vividnext.sodalive.live.recommend + +class LiveRecommendRepository(private val api: LiveRecommendApi) { + fun getRecommendLive(token: String) = api.getRecommendLive(authHeader = token) + fun getRecommendChannelList(token: String) = api.getRecommendChannelList(authHeader = token) + fun getFollowingChannelList(token: String) = api.getFollowingChannelList(authHeader = token) +} diff --git a/app/src/main/java/kr/co/vividnext/sodalive/live/recommend/RecommendLiveAdapter.kt b/app/src/main/java/kr/co/vividnext/sodalive/live/recommend/RecommendLiveAdapter.kt new file mode 100644 index 0000000..4906110 --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/live/recommend/RecommendLiveAdapter.kt @@ -0,0 +1,41 @@ +package kr.co.vividnext.sodalive.live.recommend + +import android.widget.FrameLayout +import android.widget.ImageView +import coil.load +import coil.transform.RoundedCornersTransformation +import com.zhpan.bannerview.BaseBannerAdapter +import com.zhpan.bannerview.BaseViewHolder +import kr.co.vividnext.sodalive.R +import kr.co.vividnext.sodalive.extensions.dpToPx + +class RecommendLiveAdapter( + private val itemWidth: Int, + private val itemHeight: Int, + private val onClick: (Long) -> Unit +) : BaseBannerAdapter() { + override fun bindData( + holder: BaseViewHolder, + data: GetRecommendLiveResponse, + position: Int, + pageSize: Int + ) { + val ivRecommendLive = holder.findViewById(R.id.iv_recommend_live) + val layoutParams = ivRecommendLive.layoutParams as FrameLayout.LayoutParams + + layoutParams.width = itemWidth + layoutParams.height = itemHeight + + ivRecommendLive.load(data.imageUrl) { + crossfade(true) + placeholder(R.drawable.bg_placeholder) + transformations(RoundedCornersTransformation(5.3f.dpToPx())) + } + ivRecommendLive.layoutParams = layoutParams + ivRecommendLive.setOnClickListener { onClick(data.creatorId) } + } + + override fun getLayoutId(viewType: Int): Int { + return R.layout.item_recommend_live + } +} diff --git a/app/src/main/java/kr/co/vividnext/sodalive/live/recommend_channel/GetRecommendChannelResponse.kt b/app/src/main/java/kr/co/vividnext/sodalive/live/recommend_channel/GetRecommendChannelResponse.kt new file mode 100644 index 0000000..d42057b --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/live/recommend_channel/GetRecommendChannelResponse.kt @@ -0,0 +1,14 @@ +package kr.co.vividnext.sodalive.live.recommend_channel + +import com.google.gson.annotations.SerializedName + +data class GetRecommendChannelResponse( + @SerializedName("creatorId") + val creatorId: Long, + @SerializedName("nickname") + val nickname: String, + @SerializedName("profileImageUrl") + val profileImageUrl: String, + @SerializedName("isOnAir") + val isOnAir: Boolean +) diff --git a/app/src/main/java/kr/co/vividnext/sodalive/live/recommend_channel/LiveRecommendChannelAdapter.kt b/app/src/main/java/kr/co/vividnext/sodalive/live/recommend_channel/LiveRecommendChannelAdapter.kt new file mode 100644 index 0000000..75727ee --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/live/recommend_channel/LiveRecommendChannelAdapter.kt @@ -0,0 +1,142 @@ +package kr.co.vividnext.sodalive.live.recommend_channel + +import android.annotation.SuppressLint +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.FrameLayout +import androidx.recyclerview.widget.RecyclerView +import coil.load +import coil.transform.RoundedCornersTransformation +import kr.co.vividnext.sodalive.R +import kr.co.vividnext.sodalive.databinding.ItemRecommendChannelBinding +import kr.co.vividnext.sodalive.extensions.dpToPx + +class LiveRecommendChannelAdapter( + private val onClick: (Long) -> Unit, + private val onClickMore: () -> Unit, +) : RecyclerView.Adapter() { + + var isFollowedCreatorLive = false + + private val items = mutableListOf() + + class FooterViewHolder( + private val binding: ItemRecommendChannelBinding, + private val onClickItem: () -> Unit + ) : RecyclerView.ViewHolder(binding.root) { + fun bind() { + binding.ivRecommendChannel.setImageResource(R.drawable.btn_item_more) + binding.tvRecommendChannelNickname.text = "더보기" + + val layoutParams = binding.ivRecommendChannel.layoutParams as FrameLayout.LayoutParams + layoutParams.width = 60f.dpToPx().toInt() + layoutParams.height = 60f.dpToPx().toInt() + + binding.ivRecommendChannel.layoutParams = layoutParams + binding.ivRecommendChannelBg.layoutParams = layoutParams + + binding.tvRecommendChannel.visibility = View.GONE + binding.ivRecommendChannelBg.visibility = View.GONE + binding.root.setOnClickListener { onClickItem() } + } + + companion object { + const val VIEW_TYPE = 1 + } + } + + class ViewHolder( + private val binding: ItemRecommendChannelBinding, + private val onClickItem: (Long) -> Unit + ) : RecyclerView.ViewHolder(binding.root) { + + fun bind(item: GetRecommendChannelResponse) { + binding.ivRecommendChannel.load(item.profileImageUrl) { + crossfade(true) + placeholder(R.drawable.bg_placeholder) + transformations(RoundedCornersTransformation(30f.dpToPx())) + } + binding.tvRecommendChannelNickname.text = item.nickname + + val layoutParams = binding.ivRecommendChannel.layoutParams as FrameLayout.LayoutParams + layoutParams.width = 60f.dpToPx().toInt() + layoutParams.height = 60f.dpToPx().toInt() + + binding.ivRecommendChannel.layoutParams = layoutParams + binding.ivRecommendChannelBg.layoutParams = layoutParams + + if (item.isOnAir) { + binding.tvRecommendChannel.visibility = View.VISIBLE + binding.ivRecommendChannelBg.visibility = View.VISIBLE + } else { + binding.tvRecommendChannel.visibility = View.GONE + binding.ivRecommendChannelBg.visibility = View.GONE + } + + binding.root.setOnClickListener { onClickItem(item.creatorId) } + } + + companion object { + const val VIEW_TYPE = 0 + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + return if (viewType == FooterViewHolder.VIEW_TYPE) { + FooterViewHolder( + ItemRecommendChannelBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ), + onClickItem = onClickMore + ) + } else { + ViewHolder( + ItemRecommendChannelBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ), + onClickItem = onClick + ) + } + } + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + if (holder is FooterViewHolder) { + holder.bind() + } else { + (holder as ViewHolder).bind(items[position]) + } + } + + override fun getItemCount(): Int { + return if (isFollowedCreatorLive) { + items.size + 1 + } else { + items.size + } + } + + override fun getItemViewType(position: Int): Int { + return if (position == items.size) { + FooterViewHolder.VIEW_TYPE + } else { + ViewHolder.VIEW_TYPE + } + } + + @SuppressLint("NotifyDataSetChanged") + fun clear() { + items.clear() + notifyDataSetChanged() + } + + @SuppressLint("NotifyDataSetChanged") + fun addItems(items: List) { + this.items.addAll(items) + notifyDataSetChanged() + } +} diff --git a/app/src/main/java/kr/co/vividnext/sodalive/live/reservation/LiveReservationAdapter.kt b/app/src/main/java/kr/co/vividnext/sodalive/live/reservation/LiveReservationAdapter.kt new file mode 100644 index 0000000..a9e8c58 --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/live/reservation/LiveReservationAdapter.kt @@ -0,0 +1,146 @@ +package kr.co.vividnext.sodalive.live.reservation + +import android.annotation.SuppressLint +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import coil.load +import coil.transform.RoundedCornersTransformation +import kr.co.vividnext.sodalive.R +import kr.co.vividnext.sodalive.common.SharedPreferenceManager +import kr.co.vividnext.sodalive.databinding.ItemLiveReservationBinding +import kr.co.vividnext.sodalive.databinding.ItemMyLiveReservationBinding +import kr.co.vividnext.sodalive.extensions.dpToPx +import kr.co.vividnext.sodalive.extensions.moneyFormat +import kr.co.vividnext.sodalive.live.GetRoomListResponse + +class LiveReservationAdapter( + private val isMain: Boolean = false, + private val onClick: (GetRoomListResponse) -> Unit +) : RecyclerView.Adapter() { + var items = mutableListOf() + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + return if (viewType == 1) { + MyLiveViewHolder( + ItemMyLiveReservationBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ) + ) + } else { + ViewHolder( + ItemLiveReservationBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ) + ) + } + } + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + val item = items[position] + if (isMyLive(item)) { + (holder as MyLiveViewHolder).bind(item, position) + } else { + (holder as ViewHolder).bind(item) + } + } + + override fun getItemCount() = items.count() + + override fun getItemViewType(position: Int): Int { + return if (isMyLive(items[position])) { + 1 + } else { + 0 + } + } + + private fun isMyLive(item: GetRoomListResponse) = + item.managerId == SharedPreferenceManager.userId && isMain + + @SuppressLint("NotifyDataSetChanged") + fun clear() { + items.clear() + notifyDataSetChanged() + } + + inner class ViewHolder( + private val binding: ItemLiveReservationBinding + ) : RecyclerView.ViewHolder(binding.root) { + + fun bind(item: GetRoomListResponse) { + binding.ivCover.load(item.coverImageUrl) { + crossfade(true) + placeholder(R.drawable.bg_placeholder) + transformations(RoundedCornersTransformation(4.7f.dpToPx())) + } + binding.tvDate.text = item.beginDateTime + binding.tvNickname.text = item.managerNickname + binding.tvTitle.text = item.title + binding.root.setOnClickListener { onClick(item) } + binding.ivLock.visibility = if (item.isPrivateRoom) { + View.VISIBLE + } else { + View.GONE + } + + if (item.isReservation) { + binding.tvPrice.visibility = View.GONE + binding.tvCompleteReservation.visibility = View.VISIBLE + } else { + binding.tvPrice.visibility = View.VISIBLE + binding.tvCompleteReservation.visibility = View.GONE + + binding.tvPrice.text = if (item.price <= 0) { + "무료" + } else { + "${item.price.moneyFormat()}코인" + } + } + + binding.iv19.visibility = if (item.isAdult) { + View.VISIBLE + } else { + View.GONE + } + } + } + + inner class MyLiveViewHolder( + private val binding: ItemMyLiveReservationBinding + ) : RecyclerView.ViewHolder(binding.root) { + fun bind(item: GetRoomListResponse, position: Int) { + binding.tvMyLive.visibility = if (position == 0) { + View.VISIBLE + } else { + View.GONE + } + binding.ivCover.load(item.coverImageUrl) { + crossfade(true) + placeholder(R.drawable.bg_placeholder) + transformations(RoundedCornersTransformation(4f.dpToPx())) + } + binding.tvDate.text = item.beginDateTime + binding.tvNickname.text = item.managerNickname + binding.tvTitle.text = item.title + binding.root.setOnClickListener { onClick(item) } + + binding.ivLock.visibility = if (item.isPrivateRoom) { + View.VISIBLE + } else { + View.GONE + } + + binding.iv19.visibility = if (item.isAdult) { + View.VISIBLE + } else { + View.GONE + } + } + } +} diff --git a/app/src/main/java/kr/co/vividnext/sodalive/live/room/LiveRoomStatus.kt b/app/src/main/java/kr/co/vividnext/sodalive/live/room/LiveRoomStatus.kt new file mode 100644 index 0000000..0acf81d --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/live/room/LiveRoomStatus.kt @@ -0,0 +1,8 @@ +package kr.co.vividnext.sodalive.live.room + +import com.google.gson.annotations.SerializedName + +enum class LiveRoomStatus { + @SerializedName("NOW") NOW, + @SerializedName("RESERVATION") RESERVATION +} diff --git a/app/src/main/java/kr/co/vividnext/sodalive/settings/event/EventApi.kt b/app/src/main/java/kr/co/vividnext/sodalive/settings/event/EventApi.kt new file mode 100644 index 0000000..9e8a22b --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/settings/event/EventApi.kt @@ -0,0 +1,20 @@ +package kr.co.vividnext.sodalive.settings.event + +import io.reactivex.rxjava3.core.Flowable +import io.reactivex.rxjava3.core.Single +import kr.co.vividnext.sodalive.common.ApiResponse +import retrofit2.http.GET +import retrofit2.http.Header +import retrofit2.http.Query + +interface EventApi { + @GET("/event") + fun getEvents( + @Query("page") page: Int, + @Query("size") size: Int, + @Header("Authorization") authHeader: String + ): Flowable> + + @GET("/event/popup") + fun getEventPopup(@Header("Authorization") authHeader: String): Single> +} diff --git a/app/src/main/java/kr/co/vividnext/sodalive/settings/event/EventRepository.kt b/app/src/main/java/kr/co/vividnext/sodalive/settings/event/EventRepository.kt new file mode 100644 index 0000000..c426fd1 --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/settings/event/EventRepository.kt @@ -0,0 +1,11 @@ +package kr.co.vividnext.sodalive.settings.event + +class EventRepository(private val api: EventApi) { + fun getEvents( + page: Int, + size: Int, + token: String + ) = api.getEvents(page, size, authHeader = token) + + fun getEventPopup(token: String) = api.getEventPopup(authHeader = token) +} diff --git a/app/src/main/java/kr/co/vividnext/sodalive/settings/event/GetEventResponse.kt b/app/src/main/java/kr/co/vividnext/sodalive/settings/event/GetEventResponse.kt new file mode 100644 index 0000000..bddec51 --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/settings/event/GetEventResponse.kt @@ -0,0 +1,19 @@ +package kr.co.vividnext.sodalive.settings.event + +import android.os.Parcelable +import com.google.gson.annotations.SerializedName +import kotlinx.parcelize.Parcelize + +data class GetEventResponse( + @SerializedName("totalCount") val totalCount: Int, + @SerializedName("eventList") val eventList: List +) + +@Parcelize +data class EventItem( + @SerializedName("id") val id: Long, + @SerializedName("thumbnailImageUrl") val thumbnailImageUrl: String, + @SerializedName("detailImageUrl") val detailImageUrl: String? = null, + @SerializedName("popupImageUrl") val popupImageUrl: String? = null, + @SerializedName("link") val link: String? = null +) : Parcelable diff --git a/app/src/main/res/drawable-xxhdpi/btn_item_more.png b/app/src/main/res/drawable-xxhdpi/btn_item_more.png new file mode 100644 index 0000000..f219986 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/btn_item_more.png differ diff --git a/app/src/main/res/drawable-xxhdpi/btn_make_live.png b/app/src/main/res/drawable-xxhdpi/btn_make_live.png new file mode 100644 index 0000000..8c1d45f Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/btn_make_live.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_19.png b/app/src/main/res/drawable-xxhdpi/ic_19.png new file mode 100644 index 0000000..df995e9 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_19.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_avatar.png b/app/src/main/res/drawable-xxhdpi/ic_avatar.png new file mode 100644 index 0000000..a69e271 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_avatar.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_lock.png b/app/src/main/res/drawable-xxhdpi/ic_lock.png new file mode 100644 index 0000000..39e287d Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_lock.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_mic_colored.png b/app/src/main/res/drawable-xxhdpi/ic_mic_colored.png new file mode 100644 index 0000000..c97a2b2 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_mic_colored.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_no_item.png b/app/src/main/res/drawable-xxhdpi/ic_no_item.png new file mode 100644 index 0000000..498f40c Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_no_item.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_plus_no_bg.png b/app/src/main/res/drawable-xxhdpi/ic_plus_no_bg.png new file mode 100644 index 0000000..acb5e05 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_plus_no_bg.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/img_how_to_use.png b/app/src/main/res/drawable-xxxhdpi/img_how_to_use.png new file mode 100644 index 0000000..1f8cfa8 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/img_how_to_use.png differ diff --git a/app/src/main/res/drawable/bg_placeholder.xml b/app/src/main/res/drawable/bg_placeholder.xml new file mode 100644 index 0000000..1cd81f8 --- /dev/null +++ b/app/src/main/res/drawable/bg_placeholder.xml @@ -0,0 +1,1353 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/bg_round_corner_10_533d89.xml b/app/src/main/res/drawable/bg_round_corner_10_533d89.xml new file mode 100644 index 0000000..021bea2 --- /dev/null +++ b/app/src/main/res/drawable/bg_round_corner_10_533d89.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/app/src/main/res/drawable/bg_round_corner_10_643bc8.xml b/app/src/main/res/drawable/bg_round_corner_10_643bc8.xml new file mode 100644 index 0000000..955baae --- /dev/null +++ b/app/src/main/res/drawable/bg_round_corner_10_643bc8.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/app/src/main/res/drawable/bg_round_corner_10_881609.xml b/app/src/main/res/drawable/bg_round_corner_10_881609.xml new file mode 100644 index 0000000..cae42d1 --- /dev/null +++ b/app/src/main/res/drawable/bg_round_corner_10_881609.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/app/src/main/res/drawable/bg_round_corner_33_3_transparent_9970ff.xml b/app/src/main/res/drawable/bg_round_corner_33_3_transparent_9970ff.xml new file mode 100644 index 0000000..3ae5d7f --- /dev/null +++ b/app/src/main/res/drawable/bg_round_corner_33_3_transparent_9970ff.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/app/src/main/res/drawable/bg_round_corner_4_7_2b2635.xml b/app/src/main/res/drawable/bg_round_corner_4_7_2b2635.xml new file mode 100644 index 0000000..f7705de --- /dev/null +++ b/app/src/main/res/drawable/bg_round_corner_4_7_2b2635.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/app/src/main/res/drawable/bg_round_corner_4_7_9970ff.xml b/app/src/main/res/drawable/bg_round_corner_4_7_9970ff.xml new file mode 100644 index 0000000..24973ed --- /dev/null +++ b/app/src/main/res/drawable/bg_round_corner_4_7_9970ff.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/app/src/main/res/drawable/gradient_live_room_item.xml b/app/src/main/res/drawable/gradient_live_room_item.xml new file mode 100644 index 0000000..882480a --- /dev/null +++ b/app/src/main/res/drawable/gradient_live_room_item.xml @@ -0,0 +1,10 @@ + + + + diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_live.xml b/app/src/main/res/layout/fragment_live.xml index d7a8f06..8a2b316 100644 --- a/app/src/main/res/layout/fragment_live.xml +++ b/app/src/main/res/layout/fragment_live.xml @@ -4,13 +4,83 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - + + + + + + + + + + + + + + + + + + + + + + + - + app:layout_constraintEnd_toEndOf="parent" /> diff --git a/app/src/main/res/layout/item_event_slider.xml b/app/src/main/res/layout/item_event_slider.xml new file mode 100644 index 0000000..5287bf8 --- /dev/null +++ b/app/src/main/res/layout/item_event_slider.xml @@ -0,0 +1,13 @@ + + + + + + diff --git a/app/src/main/res/layout/item_live_now.xml b/app/src/main/res/layout/item_live_now.xml new file mode 100644 index 0000000..9c9382f --- /dev/null +++ b/app/src/main/res/layout/item_live_now.xml @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/item_live_reservation.xml b/app/src/main/res/layout/item_live_reservation.xml new file mode 100644 index 0000000..5510b7e --- /dev/null +++ b/app/src/main/res/layout/item_live_reservation.xml @@ -0,0 +1,127 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/item_my_live_reservation.xml b/app/src/main/res/layout/item_my_live_reservation.xml new file mode 100644 index 0000000..2af237f --- /dev/null +++ b/app/src/main/res/layout/item_my_live_reservation.xml @@ -0,0 +1,113 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/item_recommend_channel.xml b/app/src/main/res/layout/item_recommend_channel.xml new file mode 100644 index 0000000..87b92cc --- /dev/null +++ b/app/src/main/res/layout/item_recommend_channel.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/item_recommend_live.xml b/app/src/main/res/layout/item_recommend_live.xml new file mode 100644 index 0000000..afd32a8 --- /dev/null +++ b/app/src/main/res/layout/item_recommend_live.xml @@ -0,0 +1,13 @@ + + + + + + diff --git a/app/src/main/res/layout/layout_live_now.xml b/app/src/main/res/layout/layout_live_now.xml new file mode 100644 index 0000000..4d3f260 --- /dev/null +++ b/app/src/main/res/layout/layout_live_now.xml @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/layout_live_recommend_channel.xml b/app/src/main/res/layout/layout_live_recommend_channel.xml new file mode 100644 index 0000000..574bd52 --- /dev/null +++ b/app/src/main/res/layout/layout_live_recommend_channel.xml @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/layout_live_reservation.xml b/app/src/main/res/layout/layout_live_reservation.xml new file mode 100644 index 0000000..88ab43d --- /dev/null +++ b/app/src/main/res/layout/layout_live_reservation.xml @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/layout_recommend_live.xml b/app/src/main/res/layout/layout_recommend_live.xml new file mode 100644 index 0000000..750559c --- /dev/null +++ b/app/src/main/res/layout/layout_recommend_live.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index f729221..3ae1c1e 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -13,8 +13,17 @@ #A0E2FF #ECFAFF #111111 + #FF5C49 + #2b2635 + #FFD300 + #E2E2E2 + #D2D2D2 + #533D89 + #643BC8 + #881609 #B3909090 #88909090 #339970FF + #7FE2E2E2