diff --git a/app/build.gradle b/app/build.gradle index 4999b0a9..22346c8c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -207,4 +207,6 @@ dependencies { implementation "com.kakao.sdk:v2-common:2.21.0" implementation "com.kakao.sdk:v2-auth:2.21.0" implementation "com.kakao.sdk:v2-user:2.21.0" + + implementation 'io.github.glailton.expandabletextview:expandabletextview:1.0.4' } 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 42c6f294..0e9eca4a 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 @@ -81,6 +81,9 @@ import kr.co.vividnext.sodalive.explorer.profile.fantalk.UserProfileFantalkAllVi import kr.co.vividnext.sodalive.explorer.profile.follow.UserFollowerListViewModel import kr.co.vividnext.sodalive.following.FollowingCreatorRepository import kr.co.vividnext.sodalive.following.FollowingCreatorViewModel +import kr.co.vividnext.sodalive.home.HomeApi +import kr.co.vividnext.sodalive.home.HomeRepository +import kr.co.vividnext.sodalive.home.HomeViewModel import kr.co.vividnext.sodalive.live.LiveApi import kr.co.vividnext.sodalive.live.LiveRepository import kr.co.vividnext.sodalive.live.LiveViewModel @@ -240,6 +243,7 @@ class AppDI(private val context: Context, isDebugMode: Boolean) { single { ApiBuilder().build(get(), AdTrackingApi::class.java) } single { ApiBuilder().build(get(), SearchApi::class.java) } single { ApiBuilder().build(get(), PointStatusApi::class.java) } + single { ApiBuilder().build(get(), HomeApi::class.java) } } private val viewModelModule = module { @@ -336,6 +340,7 @@ class AppDI(private val context: Context, isDebugMode: Boolean) { viewModel { AlarmContentAllViewModel(get()) } viewModel { SearchViewModel(get()) } viewModel { PointStatusViewModel(get()) } + viewModel { HomeViewModel(get()) } } private val repositoryModule = module { @@ -379,6 +384,7 @@ class AppDI(private val context: Context, isDebugMode: Boolean) { factory { SearchRepository(get()) } factory { UserEventRepository(get()) } factory { PointStatusRepository(get()) } + factory { HomeRepository(get()) } } private val moduleList = listOf( diff --git a/app/src/main/java/kr/co/vividnext/sodalive/home/AudioContentMainItem.kt b/app/src/main/java/kr/co/vividnext/sodalive/home/AudioContentMainItem.kt new file mode 100644 index 00000000..04cb8e62 --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/home/AudioContentMainItem.kt @@ -0,0 +1,14 @@ +package kr.co.vividnext.sodalive.home + +import androidx.annotation.Keep +import com.google.gson.annotations.SerializedName + +@Keep +data class AudioContentMainItem( + @SerializedName("contentId") val contentId: Long, + @SerializedName("creatorId") val creatorId: Long, + @SerializedName("title") val title: String, + @SerializedName("coverImageUrl") val coverImageUrl: String, + @SerializedName("creatorNickname") val creatorNickname: String, + @SerializedName("isPointAvailable") val isPointAvailable: Boolean +) diff --git a/app/src/main/java/kr/co/vividnext/sodalive/home/GetHomeResponse.kt b/app/src/main/java/kr/co/vividnext/sodalive/home/GetHomeResponse.kt new file mode 100644 index 00000000..a6eecde2 --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/home/GetHomeResponse.kt @@ -0,0 +1,29 @@ +package kr.co.vividnext.sodalive.home + +import androidx.annotation.Keep +import com.google.gson.annotations.SerializedName +import kr.co.vividnext.sodalive.audio_content.main.GetAudioContentBannerResponse +import kr.co.vividnext.sodalive.audio_content.main.GetAudioContentRankingItem +import kr.co.vividnext.sodalive.audio_content.main.v2.GetContentCurationResponse +import kr.co.vividnext.sodalive.audio_content.series.GetSeriesListResponse +import kr.co.vividnext.sodalive.audition.GetAuditionListItem +import kr.co.vividnext.sodalive.explorer.GetExplorerSectionCreatorResponse +import kr.co.vividnext.sodalive.live.GetRoomListResponse +import kr.co.vividnext.sodalive.settings.event.GetEventResponse + +@Keep +data class GetHomeResponse( + @SerializedName("liveList") val liveList: List, + @SerializedName("creatorRanking") val creatorRanking: List, + @SerializedName("latestContentThemeList") val latestContentThemeList: List, + @SerializedName("latestContentList") val latestContentList: List, + @SerializedName("bannerList") val bannerList: List, + @SerializedName("eventBannerList") val eventBannerList: GetEventResponse, + @SerializedName("originalAudioDramaList") val originalAudioDramaList: List, + @SerializedName("auditionList") val auditionList: List, + @SerializedName("dayOfWeekSeriesList") val dayOfWeekSeriesList: List, + @SerializedName("contentRanking") val contentRanking: List, + @SerializedName("recommendChannelList") val recommendChannelList: List, + @SerializedName("freeContentList") val freeContentList: List, + @SerializedName("curationList") val curationList: List +) diff --git a/app/src/main/java/kr/co/vividnext/sodalive/home/HomeApi.kt b/app/src/main/java/kr/co/vividnext/sodalive/home/HomeApi.kt new file mode 100644 index 00000000..e3317452 --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/home/HomeApi.kt @@ -0,0 +1,35 @@ +package kr.co.vividnext.sodalive.home + +import io.reactivex.rxjava3.core.Single +import kr.co.vividnext.sodalive.audio_content.series.GetSeriesListResponse +import kr.co.vividnext.sodalive.common.ApiResponse +import kr.co.vividnext.sodalive.settings.ContentType +import retrofit2.http.GET +import retrofit2.http.Header +import retrofit2.http.Query + +interface HomeApi { + @GET("/api/home") + fun getHomeData( + @Query("timezone") timezone: String, + @Query("isAdultContentVisible") isAdultContentVisible: Boolean, + @Query("contentType") contentType: ContentType, + @Header("Authorization") authHeader: String + ): Single> + + @GET("/api/home/latest-content") + fun getLatestContentByTheme( + @Query("theme") theme: String, + @Query("isAdultContentVisible") isAdultContentVisible: Boolean, + @Query("contentType") contentType: ContentType, + @Header("Authorization") authHeader: String + ): Single>> + + @GET("/api/home/day-of-week-series") + fun getDayOfWeekSeriesList( + @Query("dayOfWeek") dayOfWeek: SeriesPublishedDaysOfWeek, + @Query("isAdultContentVisible") isAdultContentVisible: Boolean, + @Query("contentType") contentType: ContentType, + @Header("Authorization") authHeader: String + ): Single>> +} diff --git a/app/src/main/java/kr/co/vividnext/sodalive/home/HomeFragment.kt b/app/src/main/java/kr/co/vividnext/sodalive/home/HomeFragment.kt new file mode 100644 index 00000000..e8204967 --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/home/HomeFragment.kt @@ -0,0 +1,343 @@ +package kr.co.vividnext.sodalive.home + +import android.content.Intent +import android.content.SharedPreferences +import android.graphics.Rect +import android.os.Bundle +import android.os.Handler +import android.os.Looper +import android.text.SpannableString +import android.text.Spanned +import android.text.style.ForegroundColorSpan +import android.view.View +import android.widget.Toast +import androidx.annotation.OptIn +import androidx.core.content.ContextCompat +import androidx.media3.common.util.UnstableApi +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import kr.co.vividnext.sodalive.R +import kr.co.vividnext.sodalive.audio_content.AudioContentPlayService +import kr.co.vividnext.sodalive.audio_content.box.AudioContentBoxActivity +import kr.co.vividnext.sodalive.audio_content.player.AudioContentPlayerService +import kr.co.vividnext.sodalive.audio_content.upload.AudioContentUploadActivity +import kr.co.vividnext.sodalive.base.BaseFragment +import kr.co.vividnext.sodalive.common.Constants +import kr.co.vividnext.sodalive.common.LoadingDialog +import kr.co.vividnext.sodalive.common.SharedPreferenceManager +import kr.co.vividnext.sodalive.databinding.FragmentHomeBinding +import kr.co.vividnext.sodalive.extensions.dpToPx +import kr.co.vividnext.sodalive.live.LiveViewModel +import kr.co.vividnext.sodalive.live.room.LiveRoomActivity +import kr.co.vividnext.sodalive.live.room.detail.LiveRoomDetailFragment +import kr.co.vividnext.sodalive.live.room.dialog.LivePaymentDialog +import kr.co.vividnext.sodalive.live.room.dialog.LiveRoomPasswordDialog +import kr.co.vividnext.sodalive.main.MainActivity +import kr.co.vividnext.sodalive.mypage.can.charge.CanChargeActivity +import kr.co.vividnext.sodalive.settings.notification.MemberRole +import org.koin.android.ext.android.inject +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale + +class HomeFragment : BaseFragment(FragmentHomeBinding::inflate) { + private val viewModel: HomeViewModel by inject() + private val liveViewModel: LiveViewModel by inject() + + private lateinit var loadingDialog: LoadingDialog + + private lateinit var liveAdapter: HomeLiveAdapter + + private val handler = Handler(Looper.getMainLooper()) + + private val preferenceChangeListener = + SharedPreferences.OnSharedPreferenceChangeListener { sharedPreferences, key -> + // 특정 키에 대한 값이 변경될 때 UI 업데이트 + if (key == Constants.PREF_USER_ROLE) { + if ( + sharedPreferences.getString( + key, + MemberRole.USER.name + ) == MemberRole.CREATOR.name + ) { + binding.llUploadContent.visibility = View.VISIBLE + binding.llUploadContent.setOnClickListener { + startActivity( + Intent( + requireActivity(), + AudioContentUploadActivity::class.java + ) + ) + } + } else { + binding.llUploadContent.visibility = View.GONE + } + } + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + SharedPreferenceManager.registerOnSharedPreferenceChangeListener(preferenceChangeListener) + setupView() + bindData() + + viewModel.fetchData() + } + + override fun onDestroyView() { + SharedPreferenceManager.unregisterOnSharedPreferenceChangeListener(preferenceChangeListener) + super.onDestroyView() + } + + private fun setupView() { + loadingDialog = LoadingDialog(requireActivity(), layoutInflater) + + if (SharedPreferenceManager.role == MemberRole.CREATOR.name) { + binding.llUploadContent.visibility = View.VISIBLE + binding.llUploadContent.setOnClickListener { + startActivity( + Intent( + requireActivity(), + AudioContentUploadActivity::class.java + ) + ) + } + } else { + binding.llUploadContent.visibility = View.GONE + } + + if (SharedPreferenceManager.token.isNotBlank()) { + binding.llShortIcon.visibility = View.VISIBLE + + binding.ivSearch.setOnClickListener {} + + binding.ivCharge.setOnClickListener { + startActivity( + Intent( + requireContext(), + CanChargeActivity::class.java + ) + ) + } + + binding.ivStorage.setOnClickListener { + startActivity( + Intent( + requireContext(), + AudioContentBoxActivity::class.java + ) + ) + } + } else { + binding.llShortIcon.visibility = View.GONE + } + + setupLiveView() + } + + @OptIn(UnstableApi::class) + private fun setupLiveView() { + val spSectionTitle = SpannableString(binding.tvLiveTitle.text) + spSectionTitle.setSpan( + ForegroundColorSpan( + ContextCompat.getColor( + requireContext(), + R.color.color_3bb9f1 + ) + ), + 0, + 2, + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE + ) + binding.tvLiveTitle.text = spSectionTitle + + liveAdapter = HomeLiveAdapter { + if (SharedPreferenceManager.token.isNotBlank()) { + val detailFragment = LiveRoomDetailFragment( + it.roomId, + onClickParticipant = { enterLiveRoom(it.roomId) }, + onClickReservation = {}, + onClickModify = {}, + onClickStart = {}, + onClickCancel = {} + ) + if (detailFragment.isAdded) return@HomeLiveAdapter + + detailFragment.show( + requireActivity().supportFragmentManager, + detailFragment.tag + ) + } else { + (requireActivity() as MainActivity).showLoginActivity() + } + } + + val recyclerView = binding.rvLive + recyclerView.layoutManager = LinearLayoutManager( + context, + 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 = 0 + outRect.right = 16f.dpToPx().toInt() + } + + liveAdapter.itemCount - 1 -> { + outRect.left = 16f.dpToPx().toInt() + outRect.right = 0 + } + + else -> { + outRect.left = 16f.dpToPx().toInt() + outRect.right = 16f.dpToPx().toInt() + } + } + } + }) + + recyclerView.adapter = liveAdapter + + viewModel.liveListLiveData.observe(viewLifecycleOwner) { + if (it.isNotEmpty()) { + binding.llLive.visibility = View.VISIBLE + liveAdapter.addItems(it) + } else { + binding.llLive.visibility = View.GONE + } + } + } + + private fun bindData() { + viewModel.isLoading.observe(viewLifecycleOwner) { + if (it) { + loadingDialog.show(screenWidth) + } else { + loadingDialog.dismiss() + } + } + + viewModel.toastLiveData.observe(viewLifecycleOwner) { + it?.let { Toast.makeText(requireContext(), it, Toast.LENGTH_LONG).show() } + } + } + + @UnstableApi + fun enterLiveRoom(roomId: Long) { + requireContext().startService( + Intent(requireContext(), AudioContentPlayService::class.java).apply { + action = AudioContentPlayService.MusicAction.STOP.name + } + ) + + requireContext().startService( + Intent(requireContext(), AudioContentPlayerService::class.java).apply { + action = "STOP_SERVICE" + } + ) + + val onEnterRoomSuccess = { + requireActivity().runOnUiThread { + val intent = Intent(requireContext(), LiveRoomActivity::class.java) + intent.putExtra(Constants.EXTRA_ROOM_ID, roomId) + startActivity(intent) + } + } + + liveViewModel.getRoomDetail(roomId) { + if (it.channelName != null) { + if (it.manager.id == SharedPreferenceManager.userId) { + handler.postDelayed({ + liveViewModel.enterRoom(roomId, onEnterRoomSuccess) + }, 300) + } else if (it.price == 0 || it.isPaid) { + if (it.isPrivateRoom) { + LiveRoomPasswordDialog( + activity = requireActivity(), + layoutInflater = layoutInflater, + can = 0, + confirmButtonClick = { password -> + liveViewModel.enterRoom( + roomId = roomId, + onSuccess = onEnterRoomSuccess, + password = password + ) + } + ).show(screenWidth) + } else { + handler.postDelayed({ + liveViewModel.enterRoom(roomId, onEnterRoomSuccess) + }, 300) + } + } else { + val beginDateFormat = SimpleDateFormat("yyyy.MM.dd EEE hh:mm a", Locale.ENGLISH) + val beginDate = beginDateFormat.parse(it.beginDateTime)!! + val now = Date() + + val dateFormat = SimpleDateFormat("yyyy-MM-dd, HH:mm", Locale.getDefault()) + val diffTime: Long = now.time - beginDate.time + val hours = (diffTime / (1000 * 60 * 60)).toInt() + val mins = (diffTime / (1000 * 60)).toInt() % 60 + + if (it.isPrivateRoom) { + LiveRoomPasswordDialog( + activity = requireActivity(), + layoutInflater = layoutInflater, + can = it.price, + confirmButtonClick = { password -> + handler.postDelayed({ + liveViewModel.enterRoom( + roomId = roomId, + onSuccess = onEnterRoomSuccess, + password = password + ) + }, 300) + } + ).show(screenWidth) + } else { + LivePaymentDialog( + activity = requireActivity(), + layoutInflater = layoutInflater, + title = "유료 라이브 입장", + startDateTime = if (hours >= 1) { + dateFormat.format(beginDate) + } else { + null + }, + nowDateTime = if (hours >= 1) { + dateFormat.format(now) + } else { + null + }, + desc = "${it.price}캔을 차감하고\n라이브에 입장 하시겠습니까?", + desc2 = if (hours >= 1) { + "라이브를 시작한 지 ${hours}시간 ${mins}분이 지났습니다. 라이브에 입장 후 30분 이내에 라이브가 종료될 수도 있습니다." + } else { + null + }, + confirmButtonTitle = "결제 후 입장", + confirmButtonClick = { + handler.postDelayed({ + liveViewModel.enterRoom(roomId, onEnterRoomSuccess) + }, 300) + }, + cancelButtonTitle = "취소", + cancelButtonClick = {} + ).show(screenWidth) + } + } + } + } + } +} diff --git a/app/src/main/java/kr/co/vividnext/sodalive/home/HomeLiveAdapter.kt b/app/src/main/java/kr/co/vividnext/sodalive/home/HomeLiveAdapter.kt new file mode 100644 index 00000000..809cadcc --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/home/HomeLiveAdapter.kt @@ -0,0 +1,62 @@ +package kr.co.vividnext.sodalive.home + +import android.annotation.SuppressLint +import android.content.Context +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import com.bumptech.glide.Glide +import com.bumptech.glide.load.resource.bitmap.CircleCrop +import com.bumptech.glide.request.RequestOptions +import kr.co.vividnext.sodalive.databinding.ItemHomeLiveBinding +import kr.co.vividnext.sodalive.live.GetRoomListResponse + +class HomeLiveAdapter( + private val onClick: (GetRoomListResponse) -> Unit +) : RecyclerView.Adapter() { + + var items = mutableListOf() + + inner class ViewHolder( + private val context: Context, + private val binding: ItemHomeLiveBinding + ) : RecyclerView.ViewHolder(binding.root) { + fun bind(item: GetRoomListResponse) { + Glide + .with(context) + .load(item.coverImageUrl) + .apply( + RequestOptions().transform( + CircleCrop() + ) + ) + .into(binding.ivProfile) + + binding.tvTitle.text = item.title + binding.tvNickname.text = item.creatorNickname + + binding.root.setOnClickListener { onClick(item) } + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder( + parent.context, + ItemHomeLiveBinding.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 addItems(items: List) { + this.items.addAll(items) + notifyDataSetChanged() + } +} diff --git a/app/src/main/java/kr/co/vividnext/sodalive/home/HomeRepository.kt b/app/src/main/java/kr/co/vividnext/sodalive/home/HomeRepository.kt new file mode 100644 index 00000000..7a381b08 --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/home/HomeRepository.kt @@ -0,0 +1,30 @@ +package kr.co.vividnext.sodalive.home + +import kr.co.vividnext.sodalive.common.SharedPreferenceManager +import kr.co.vividnext.sodalive.settings.ContentType +import java.util.TimeZone + +class HomeRepository(private val api: HomeApi) { + fun fetchData(token: String) = api.getHomeData( + timezone = TimeZone.getDefault().id, + isAdultContentVisible = SharedPreferenceManager.isAdultContentVisible, + contentType = ContentType.values()[SharedPreferenceManager.contentPreference], + authHeader = token + ) + + fun getLatestContentByTheme(theme: String, token: String) = api.getLatestContentByTheme( + theme = theme, + isAdultContentVisible = SharedPreferenceManager.isAdultContentVisible, + contentType = ContentType.values()[SharedPreferenceManager.contentPreference], + authHeader = token + ) + + fun getDayOfWeekSeriesList( + dayOfWeek: SeriesPublishedDaysOfWeek, token: String + ) = api.getDayOfWeekSeriesList( + dayOfWeek = dayOfWeek, + isAdultContentVisible = SharedPreferenceManager.isAdultContentVisible, + contentType = ContentType.values()[SharedPreferenceManager.contentPreference], + authHeader = token + ) +} diff --git a/app/src/main/java/kr/co/vividnext/sodalive/home/HomeViewModel.kt b/app/src/main/java/kr/co/vividnext/sodalive/home/HomeViewModel.kt new file mode 100644 index 00000000..e30a0a35 --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/home/HomeViewModel.kt @@ -0,0 +1,164 @@ +package kr.co.vividnext.sodalive.home + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import com.orhanobut.logger.Logger +import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers +import io.reactivex.rxjava3.schedulers.Schedulers +import kr.co.vividnext.sodalive.audio_content.main.GetAudioContentBannerResponse +import kr.co.vividnext.sodalive.audio_content.main.GetAudioContentRankingItem +import kr.co.vividnext.sodalive.audio_content.main.v2.GetContentCurationResponse +import kr.co.vividnext.sodalive.audio_content.series.GetSeriesListResponse +import kr.co.vividnext.sodalive.audition.GetAuditionListItem +import kr.co.vividnext.sodalive.base.BaseViewModel +import kr.co.vividnext.sodalive.common.SharedPreferenceManager +import kr.co.vividnext.sodalive.explorer.GetExplorerSectionCreatorResponse +import kr.co.vividnext.sodalive.live.GetRoomListResponse + +class HomeViewModel(private val repository: HomeRepository) : BaseViewModel() { + + private var _isLoading = MutableLiveData(false) + val isLoading: LiveData + get() = _isLoading + + private val _toastLiveData = MutableLiveData() + val toastLiveData: LiveData + get() = _toastLiveData + + private var _liveListLiveData = MutableLiveData>() + val liveListLiveData: LiveData> + get() = _liveListLiveData + + private var _creatorRankingLiveData = MutableLiveData>() + val creatorRankingLiveData: LiveData> + get() = _creatorRankingLiveData + + private var _latestContentThemeListLiveData = MutableLiveData>() + val latestContentThemeListLiveData: LiveData> + get() = _latestContentThemeListLiveData + + private var _latestContentListLiveData = MutableLiveData>() + val latestContentListLiveData: LiveData> + get() = _latestContentListLiveData + + private var _eventBannerListLiveData = MutableLiveData>() + val eventBannerListLiveData: LiveData> + get() = _eventBannerListLiveData + + private var _originalAudioDramaListLiveData = + MutableLiveData>() + val originalAudioDramaListLiveData: LiveData> + get() = _originalAudioDramaListLiveData + + private var _auditionListLiveData = MutableLiveData>() + val auditionListLiveData: LiveData> + get() = _auditionListLiveData + + private var _dayOfWeekSeriesListLiveData = + MutableLiveData>() + val dayOfWeekSeriesListLiveData: LiveData> + get() = _dayOfWeekSeriesListLiveData + + private var _contentRankingLiveData = MutableLiveData>() + val contentRankingLiveData: LiveData> + get() = _contentRankingLiveData + + private var _recommendChannelListLiveData = MutableLiveData>() + val recommendChannelListLiveData: LiveData> + get() = _recommendChannelListLiveData + + private var _freeContentListLiveData = MutableLiveData>() + val freeContentListLiveData: LiveData> + get() = _freeContentListLiveData + + private var _curationListLiveData = MutableLiveData>() + val curationListLiveData: LiveData> + get() = _curationListLiveData + + fun fetchData() { + _isLoading.value = true + + compositeDisposable.add( + repository.fetchData(token = "Bearer ${SharedPreferenceManager.token}") + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + { + _isLoading.value = false + + val data = it.data + if (it.success && data != null) { + _liveListLiveData.value = data.liveList + _creatorRankingLiveData.value = data.creatorRanking + _latestContentThemeListLiveData.value = data.latestContentThemeList + _latestContentListLiveData.value = data.latestContentList + _eventBannerListLiveData.value = data.bannerList + _originalAudioDramaListLiveData.value = data.originalAudioDramaList + _auditionListLiveData.value = data.auditionList + _dayOfWeekSeriesListLiveData.value = data.dayOfWeekSeriesList + _contentRankingLiveData.value = data.contentRanking + } 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 getLatestContentByTheme(theme: String) { + _isLoading.value = true + + compositeDisposable.add( + repository.getLatestContentByTheme( + theme, + token = "Bearer ${SharedPreferenceManager.token}" + ) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + { + _isLoading.value = false + }, + { + _isLoading.value = false + it.message?.let { message -> Logger.e(message) } + _toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.") + } + ) + ) + } + + fun getDayOfWeekSeriesList(dayOfWeek: SeriesPublishedDaysOfWeek) { + _isLoading.value = true + + compositeDisposable.add( + repository.getDayOfWeekSeriesList( + dayOfWeek, + token = "Bearer ${SharedPreferenceManager.token}" + ) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + { + _isLoading.value = false + }, + { + _isLoading.value = false + it.message?.let { message -> Logger.e(message) } + _toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.") + } + ) + ) + } +} diff --git a/app/src/main/java/kr/co/vividnext/sodalive/home/RecommendChannelResponse.kt b/app/src/main/java/kr/co/vividnext/sodalive/home/RecommendChannelResponse.kt new file mode 100644 index 00000000..6c2338f0 --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/home/RecommendChannelResponse.kt @@ -0,0 +1,22 @@ +package kr.co.vividnext.sodalive.home + +import androidx.annotation.Keep +import com.google.gson.annotations.SerializedName + +@Keep +data class RecommendChannelResponse( + @SerializedName("channelId") val channelId: Long, + @SerializedName("creatorNickname") val creatorNickname: String, + @SerializedName("creatorProfileImageUrl") val creatorProfileImageUrl: String, + @SerializedName("contentCount") val contentCount: Long, + @SerializedName("contentList") var contentList: List +) + +@Keep +data class RecommendChannelContentItem( + @SerializedName("contentId") val contentId: Long, + @SerializedName("title") val title: String, + @SerializedName("thumbnailImageUrl") val thumbnailImageUrl: String, + @SerializedName("likeCount") val likeCount: Long, + @SerializedName("commentCount") val commentCount: Long +) diff --git a/app/src/main/java/kr/co/vividnext/sodalive/home/SeriesPublishedDaysOfWeek.kt b/app/src/main/java/kr/co/vividnext/sodalive/home/SeriesPublishedDaysOfWeek.kt new file mode 100644 index 00000000..dd429f53 --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/home/SeriesPublishedDaysOfWeek.kt @@ -0,0 +1,5 @@ +package kr.co.vividnext.sodalive.home + +enum class SeriesPublishedDaysOfWeek { + SUN, MON, TUE, WED, THU, FRI, SAT, RANDOM +} diff --git a/app/src/main/java/kr/co/vividnext/sodalive/main/MainActivity.kt b/app/src/main/java/kr/co/vividnext/sodalive/main/MainActivity.kt index 9efe2865..7eae3469 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/main/MainActivity.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/main/MainActivity.kt @@ -31,7 +31,6 @@ import com.orhanobut.logger.Logger import kr.co.vividnext.sodalive.R import kr.co.vividnext.sodalive.audio_content.AudioContentPlayService import kr.co.vividnext.sodalive.audio_content.detail.AudioContentDetailActivity -import kr.co.vividnext.sodalive.audio_content.main.v2.home.AudioContentMainTabHomeFragment import kr.co.vividnext.sodalive.audio_content.player.AudioContentPlayerFragment import kr.co.vividnext.sodalive.audio_content.player.AudioContentPlayerService import kr.co.vividnext.sodalive.audio_content.series.detail.SeriesDetailActivity @@ -43,6 +42,7 @@ import kr.co.vividnext.sodalive.databinding.ActivityMainBinding import kr.co.vividnext.sodalive.databinding.ItemMainTabBinding import kr.co.vividnext.sodalive.explorer.profile.UserProfileActivity import kr.co.vividnext.sodalive.extensions.dpToPx +import kr.co.vividnext.sodalive.home.HomeFragment import kr.co.vividnext.sodalive.live.LiveFragment import kr.co.vividnext.sodalive.message.MessageActivity import kr.co.vividnext.sodalive.mypage.MyPageFragment @@ -472,7 +472,7 @@ class MainActivity : BaseActivity(ActivityMainBinding::infl if (fragment == null) { fragment = when (currentTab) { MainViewModel.CurrentTab.LIVE -> liveFragment - MainViewModel.CurrentTab.HOME -> AudioContentMainTabHomeFragment() + MainViewModel.CurrentTab.HOME -> HomeFragment() MainViewModel.CurrentTab.MY -> MyPageFragment() } diff --git a/app/src/main/res/drawable-mdpi/ic_can_circle.png b/app/src/main/res/drawable-mdpi/ic_can_circle.png new file mode 100644 index 00000000..0609dcae Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_can_circle.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_comment_dark_green.png b/app/src/main/res/drawable-mdpi/ic_comment_dark_green.png new file mode 100644 index 00000000..b365835e Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_comment_dark_green.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_heart_dark_green.png b/app/src/main/res/drawable-mdpi/ic_heart_dark_green.png new file mode 100644 index 00000000..d45b6914 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_heart_dark_green.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_search_white.png b/app/src/main/res/drawable-mdpi/ic_search_white.png new file mode 100644 index 00000000..ac538222 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_search_white.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_storage.png b/app/src/main/res/drawable-mdpi/ic_storage.png new file mode 100644 index 00000000..6ee52aa8 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_storage.png differ diff --git a/app/src/main/res/drawable-mdpi/img_live.png b/app/src/main/res/drawable-mdpi/img_live.png new file mode 100644 index 00000000..0b4409d7 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/img_live.png differ diff --git a/app/src/main/res/drawable-mdpi/img_text_logo.png b/app/src/main/res/drawable-mdpi/img_text_logo.png new file mode 100644 index 00000000..f5e6fce4 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/img_text_logo.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_point.png b/app/src/main/res/drawable-xxhdpi/ic_point.png deleted file mode 100644 index d41e9a9f..00000000 Binary files a/app/src/main/res/drawable-xxhdpi/ic_point.png and /dev/null differ diff --git a/app/src/main/res/drawable/circle_background.xml b/app/src/main/res/drawable/circle_background.xml new file mode 100644 index 00000000..d79e1a92 --- /dev/null +++ b/app/src/main/res/drawable/circle_background.xml @@ -0,0 +1,5 @@ + + + + diff --git a/app/src/main/res/drawable/live_button_background.xml b/app/src/main/res/drawable/live_button_background.xml new file mode 100644 index 00000000..6fbb5ea2 --- /dev/null +++ b/app/src/main/res/drawable/live_button_background.xml @@ -0,0 +1,9 @@ + + + + + + diff --git a/app/src/main/res/font/font.xml b/app/src/main/res/font/font.xml index 720cf18d..10c0f321 100644 --- a/app/src/main/res/font/font.xml +++ b/app/src/main/res/font/font.xml @@ -14,4 +14,20 @@ android:font="@font/gmarket_sans_medium" app:font="@font/gmarket_sans_medium" /> + + + + + + + + diff --git a/app/src/main/res/font/pretendard_bold.otf b/app/src/main/res/font/pretendard_bold.otf new file mode 100644 index 00000000..8e5e30a2 Binary files /dev/null and b/app/src/main/res/font/pretendard_bold.otf differ diff --git a/app/src/main/res/font/pretendard_light.otf b/app/src/main/res/font/pretendard_light.otf new file mode 100644 index 00000000..228679e9 Binary files /dev/null and b/app/src/main/res/font/pretendard_light.otf differ diff --git a/app/src/main/res/font/pretendard_medium.otf b/app/src/main/res/font/pretendard_medium.otf new file mode 100644 index 00000000..05750698 Binary files /dev/null and b/app/src/main/res/font/pretendard_medium.otf differ diff --git a/app/src/main/res/font/pretendard_regular.otf b/app/src/main/res/font/pretendard_regular.otf new file mode 100644 index 00000000..08bf4cfc Binary files /dev/null and b/app/src/main/res/font/pretendard_regular.otf differ diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml new file mode 100644 index 00000000..afae985c --- /dev/null +++ b/app/src/main/res/layout/fragment_home.xml @@ -0,0 +1,415 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/item_home_live.xml b/app/src/main/res/layout/item_home_live.xml new file mode 100644 index 00000000..54a55d4b --- /dev/null +++ b/app/src/main/res/layout/item_home_live.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 8ffaa80d..14817e39 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -5,6 +5,7 @@ #80D8FF #1313BC + #131313 #9970FF #EEEEEE #777777 @@ -130,4 +131,5 @@ #CC777777 #EC3AA6 #7849BC + #607D8B