feat(series-main): 시리즈 전체보기 페이지 추가
- 홈, 요일별, 장르별 탭 추가 - 홈 리스트 UI 및 데이터 - 요일별 UI 및 데이터
This commit is contained in:
@@ -86,13 +86,15 @@
|
|||||||
<data android:scheme="${URISCHEME}" />
|
<data android:scheme="${URISCHEME}" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.VIEW"/>
|
<action android:name="android.intent.action.VIEW" />
|
||||||
<category android:name="android.intent.category.DEFAULT"/>
|
|
||||||
<category android:name="android.intent.category.BROWSABLE"/>
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
<!-- PayVerse 리다이렉트에 등록할 커스텀 스킴/호스트 -->
|
<!-- PayVerse 리다이렉트에 등록할 커스텀 스킴/호스트 -->
|
||||||
<data android:scheme="${URISCHEME}"
|
<data
|
||||||
android:host="payverse"
|
android:host="payverse"
|
||||||
android:path="/result"/>
|
android:path="/result"
|
||||||
|
android:scheme="${URISCHEME}" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
@@ -196,6 +198,7 @@
|
|||||||
</activity>
|
</activity>
|
||||||
<activity android:name=".chat.character.newcharacters.NewCharactersAllActivity" />
|
<activity android:name=".chat.character.newcharacters.NewCharactersAllActivity" />
|
||||||
<activity android:name=".chat.original.detail.OriginalWorkDetailActivity" />
|
<activity android:name=".chat.original.detail.OriginalWorkDetailActivity" />
|
||||||
|
<activity android:name=".audio_content.series.main.SeriesMainActivity" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name="com.google.android.gms.oss.licenses.OssLicensesMenuActivity"
|
android:name="com.google.android.gms.oss.licenses.OssLicensesMenuActivity"
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
package kr.co.vividnext.sodalive.audio_content.series.main
|
||||||
|
|
||||||
|
import com.google.android.material.tabs.TabLayout
|
||||||
|
import kr.co.vividnext.sodalive.R
|
||||||
|
import kr.co.vividnext.sodalive.audio_content.series.main.by_genre.SeriesMainByGenreFragment
|
||||||
|
import kr.co.vividnext.sodalive.audio_content.series.main.day_of_week.SeriesMainDayOfWeekFragment
|
||||||
|
import kr.co.vividnext.sodalive.audio_content.series.main.home.SeriesMainHomeFragment
|
||||||
|
import kr.co.vividnext.sodalive.base.BaseActivity
|
||||||
|
import kr.co.vividnext.sodalive.databinding.ActivitySeriesMainBinding
|
||||||
|
|
||||||
|
class SeriesMainActivity : BaseActivity<ActivitySeriesMainBinding>(
|
||||||
|
ActivitySeriesMainBinding::inflate
|
||||||
|
) {
|
||||||
|
private var currentTab = 0
|
||||||
|
|
||||||
|
override fun setupView() {
|
||||||
|
binding.toolbar.tvBack.text = "시리즈 전체보기"
|
||||||
|
binding.toolbar.tvBack.setOnClickListener { finish() }
|
||||||
|
|
||||||
|
setupTabs()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupTabs() {
|
||||||
|
binding.tabLayout.addTab(binding.tabLayout.newTab().setText("홈"))
|
||||||
|
binding.tabLayout.addTab(binding.tabLayout.newTab().setText("요일별"))
|
||||||
|
binding.tabLayout.addTab(binding.tabLayout.newTab().setText("장르별"))
|
||||||
|
|
||||||
|
// 탭 선택 리스너 설정
|
||||||
|
binding.tabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
|
||||||
|
override fun onTabSelected(tab: TabLayout.Tab) {
|
||||||
|
currentTab = tab.position
|
||||||
|
showTabContent(currentTab)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onTabUnselected(tab: TabLayout.Tab) {
|
||||||
|
// 필요한 경우 구현
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onTabReselected(tab: TabLayout.Tab) {
|
||||||
|
// 필요한 경우 구현
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 초기 탭 선택
|
||||||
|
showTabContent(currentTab)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showTabContent(position: Int) {
|
||||||
|
val fragmentTransaction = supportFragmentManager.beginTransaction()
|
||||||
|
|
||||||
|
// 기존 프래그먼트 제거
|
||||||
|
supportFragmentManager.fragments.forEach {
|
||||||
|
fragmentTransaction.remove(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 선택된 탭에 따라 프래그먼트 표시
|
||||||
|
val fragment = when (position) {
|
||||||
|
1 -> SeriesMainDayOfWeekFragment()
|
||||||
|
2 -> SeriesMainByGenreFragment()
|
||||||
|
else -> SeriesMainHomeFragment()
|
||||||
|
}
|
||||||
|
|
||||||
|
fragmentTransaction.add(R.id.fl_container, fragment)
|
||||||
|
fragmentTransaction.commit()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
package kr.co.vividnext.sodalive.audio_content.series.main
|
||||||
|
|
||||||
|
import io.reactivex.rxjava3.core.Single
|
||||||
|
import kr.co.vividnext.sodalive.audio_content.series.GetSeriesListResponse
|
||||||
|
import kr.co.vividnext.sodalive.audio_content.series.main.by_genre.GetSeriesGenreListResponse
|
||||||
|
import kr.co.vividnext.sodalive.audio_content.series.main.home.SeriesHomeResponse
|
||||||
|
import kr.co.vividnext.sodalive.common.ApiResponse
|
||||||
|
import kr.co.vividnext.sodalive.home.SeriesPublishedDaysOfWeek
|
||||||
|
import kr.co.vividnext.sodalive.settings.ContentType
|
||||||
|
import retrofit2.http.GET
|
||||||
|
import retrofit2.http.Header
|
||||||
|
import retrofit2.http.Query
|
||||||
|
|
||||||
|
interface SeriesMainApi {
|
||||||
|
@GET("/audio-content/series/main")
|
||||||
|
fun fetchHome(
|
||||||
|
@Query("isAdultContentVisible") isAdultContentVisible: Boolean,
|
||||||
|
@Query("contentType") contentType: ContentType,
|
||||||
|
@Header("Authorization") authHeader: String
|
||||||
|
): Single<ApiResponse<SeriesHomeResponse>>
|
||||||
|
|
||||||
|
@GET("/audio-content/series/main/recommend")
|
||||||
|
fun getRecommendSeriesList(
|
||||||
|
@Query("isAdultContentVisible") isAdultContentVisible: Boolean,
|
||||||
|
@Query("contentType") contentType: ContentType,
|
||||||
|
@Header("Authorization") authHeader: String
|
||||||
|
): Single<ApiResponse<List<GetSeriesListResponse.SeriesListItem>>>
|
||||||
|
|
||||||
|
@GET("/audio-content/series/main/day-of-week")
|
||||||
|
fun getDayOfWeekSeriesList(
|
||||||
|
@Query("dayOfWeek") dayOfWeek: SeriesPublishedDaysOfWeek,
|
||||||
|
@Query("isAdultContentVisible") isAdultContentVisible: Boolean,
|
||||||
|
@Query("contentType") contentType: ContentType,
|
||||||
|
@Query("page") page: Int,
|
||||||
|
@Query("size") size: Int,
|
||||||
|
@Header("Authorization") authHeader: String
|
||||||
|
): Single<ApiResponse<List<GetSeriesListResponse.SeriesListItem>>>
|
||||||
|
|
||||||
|
@GET("/audio-content/series/main/genre-list")
|
||||||
|
fun getGenreList(
|
||||||
|
@Query("isAdultContentVisible") isAdultContentVisible: Boolean,
|
||||||
|
@Query("contentType") contentType: ContentType,
|
||||||
|
@Header("Authorization") authHeader: String
|
||||||
|
): Single<ApiResponse<List<GetSeriesGenreListResponse>>>
|
||||||
|
|
||||||
|
@GET("/audio-content/series/main/list-by-genre")
|
||||||
|
fun getSeriesListByGenre(
|
||||||
|
@Query("genreId") genreId: Long,
|
||||||
|
@Query("isAdultContentVisible") isAdultContentVisible: Boolean,
|
||||||
|
@Query("contentType") contentType: ContentType,
|
||||||
|
@Query("page") page: Int,
|
||||||
|
@Query("size") size: Int,
|
||||||
|
@Header("Authorization") authHeader: String
|
||||||
|
): Single<ApiResponse<GetSeriesListResponse>>
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
package kr.co.vividnext.sodalive.audio_content.series.main
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
|
||||||
|
import kr.co.vividnext.sodalive.home.SeriesPublishedDaysOfWeek
|
||||||
|
import kr.co.vividnext.sodalive.settings.ContentType
|
||||||
|
|
||||||
|
class SeriesMainRepository(
|
||||||
|
private val api: SeriesMainApi
|
||||||
|
) {
|
||||||
|
fun fetchData(token: String) = api.fetchHome(
|
||||||
|
isAdultContentVisible = SharedPreferenceManager.isAdultContentVisible,
|
||||||
|
contentType = ContentType.entries[SharedPreferenceManager.contentPreference],
|
||||||
|
authHeader = token
|
||||||
|
)
|
||||||
|
|
||||||
|
fun getRecommendSeriesList(token: String) = api.getRecommendSeriesList(
|
||||||
|
isAdultContentVisible = SharedPreferenceManager.isAdultContentVisible,
|
||||||
|
contentType = ContentType.entries[SharedPreferenceManager.contentPreference],
|
||||||
|
authHeader = token
|
||||||
|
)
|
||||||
|
|
||||||
|
fun getDayOfWeekSeriesList(
|
||||||
|
dayOfWeek: SeriesPublishedDaysOfWeek,
|
||||||
|
page: Int,
|
||||||
|
size: Int,
|
||||||
|
token: String
|
||||||
|
) = api.getDayOfWeekSeriesList(
|
||||||
|
dayOfWeek = dayOfWeek,
|
||||||
|
isAdultContentVisible = SharedPreferenceManager.isAdultContentVisible,
|
||||||
|
contentType = ContentType.entries[SharedPreferenceManager.contentPreference],
|
||||||
|
page = page - 1,
|
||||||
|
size = size,
|
||||||
|
authHeader = token
|
||||||
|
)
|
||||||
|
|
||||||
|
fun getGenreList(token: String) = api.getGenreList(
|
||||||
|
isAdultContentVisible = SharedPreferenceManager.isAdultContentVisible,
|
||||||
|
contentType = ContentType.entries[SharedPreferenceManager.contentPreference],
|
||||||
|
authHeader = token
|
||||||
|
)
|
||||||
|
|
||||||
|
fun getSeriesListByGenre(
|
||||||
|
genreId: Long,
|
||||||
|
page: Int,
|
||||||
|
size: Int,
|
||||||
|
token: String
|
||||||
|
) = api.getSeriesListByGenre(
|
||||||
|
genreId = genreId,
|
||||||
|
isAdultContentVisible = SharedPreferenceManager.isAdultContentVisible,
|
||||||
|
contentType = ContentType.entries[SharedPreferenceManager.contentPreference],
|
||||||
|
page = page - 1,
|
||||||
|
size = size,
|
||||||
|
authHeader = token
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package kr.co.vividnext.sodalive.audio_content.series.main.by_genre
|
||||||
|
|
||||||
|
import androidx.annotation.Keep
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
data class GetSeriesGenreListResponse(
|
||||||
|
@SerializedName("id") val id: Long,
|
||||||
|
@SerializedName("genre") val genre: String
|
||||||
|
)
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package kr.co.vividnext.sodalive.audio_content.series.main.by_genre
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.base.BaseFragment
|
||||||
|
import kr.co.vividnext.sodalive.databinding.FragmentSeriesMainByGenreBinding
|
||||||
|
|
||||||
|
class SeriesMainByGenreFragment : BaseFragment<FragmentSeriesMainByGenreBinding>(
|
||||||
|
FragmentSeriesMainByGenreBinding::inflate
|
||||||
|
) {
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package kr.co.vividnext.sodalive.audio_content.series.main.by_genre
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import kr.co.vividnext.sodalive.audio_content.series.main.SeriesMainRepository
|
||||||
|
import kr.co.vividnext.sodalive.base.BaseViewModel
|
||||||
|
|
||||||
|
class SeriesMainByGenreViewModel(
|
||||||
|
private val repository: SeriesMainRepository
|
||||||
|
) : BaseViewModel() {
|
||||||
|
private var _isLoading = MutableLiveData(false)
|
||||||
|
val isLoading: LiveData<Boolean>
|
||||||
|
get() = _isLoading
|
||||||
|
|
||||||
|
private val _toastLiveData = MutableLiveData<String?>()
|
||||||
|
val toastLiveData: LiveData<String?>
|
||||||
|
get() = _toastLiveData
|
||||||
|
}
|
||||||
@@ -0,0 +1,172 @@
|
|||||||
|
package kr.co.vividnext.sodalive.audio_content.series.main.day_of_week
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.graphics.Rect
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import kr.co.vividnext.sodalive.audio_content.series.SeriesListAdapter
|
||||||
|
import kr.co.vividnext.sodalive.audio_content.series.detail.SeriesDetailActivity
|
||||||
|
import kr.co.vividnext.sodalive.base.BaseFragment
|
||||||
|
import kr.co.vividnext.sodalive.common.Constants
|
||||||
|
import kr.co.vividnext.sodalive.common.GridSpacingItemDecoration
|
||||||
|
import kr.co.vividnext.sodalive.common.LoadingDialog
|
||||||
|
import kr.co.vividnext.sodalive.databinding.FragmentSeriesMainDayOfWeekBinding
|
||||||
|
import kr.co.vividnext.sodalive.extensions.dpToPx
|
||||||
|
import kr.co.vividnext.sodalive.home.DayOfWeekAdapter
|
||||||
|
import kr.co.vividnext.sodalive.home.SeriesPublishedDaysOfWeek
|
||||||
|
import org.koin.android.ext.android.inject
|
||||||
|
import java.util.Calendar
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
class SeriesMainDayOfWeekFragment : BaseFragment<FragmentSeriesMainDayOfWeekBinding>(
|
||||||
|
FragmentSeriesMainDayOfWeekBinding::inflate
|
||||||
|
) {
|
||||||
|
private val viewModel: SeriesMainDayOfWeekViewModel by inject()
|
||||||
|
|
||||||
|
private lateinit var loadingDialog: LoadingDialog
|
||||||
|
private lateinit var adapter: SeriesListAdapter
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
setupView()
|
||||||
|
observeViewModel()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupView() {
|
||||||
|
loadingDialog = LoadingDialog(requireActivity(), layoutInflater)
|
||||||
|
|
||||||
|
setupDayOfWeekDay()
|
||||||
|
setupSeriesView()
|
||||||
|
|
||||||
|
val dayOfWeeks = listOf(
|
||||||
|
SeriesPublishedDaysOfWeek.RANDOM,
|
||||||
|
SeriesPublishedDaysOfWeek.SUN,
|
||||||
|
SeriesPublishedDaysOfWeek.MON,
|
||||||
|
SeriesPublishedDaysOfWeek.TUE,
|
||||||
|
SeriesPublishedDaysOfWeek.WED,
|
||||||
|
SeriesPublishedDaysOfWeek.THU,
|
||||||
|
SeriesPublishedDaysOfWeek.FRI,
|
||||||
|
SeriesPublishedDaysOfWeek.SAT
|
||||||
|
)
|
||||||
|
|
||||||
|
val calendar = Calendar.getInstance()
|
||||||
|
val dayIndex = calendar.get(Calendar.DAY_OF_WEEK)
|
||||||
|
|
||||||
|
viewModel.dayOfWeek = dayOfWeeks[dayIndex]
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupDayOfWeekDay() {
|
||||||
|
val dayOfWeekAdapter = DayOfWeekAdapter(screenWidth = screenWidth) {
|
||||||
|
adapter.clear()
|
||||||
|
viewModel.dayOfWeek = it
|
||||||
|
}
|
||||||
|
|
||||||
|
val rvDayOfWeek = binding.rvSeriesDayOfWeekDay
|
||||||
|
val layoutManager = object : LinearLayoutManager(
|
||||||
|
context,
|
||||||
|
HORIZONTAL,
|
||||||
|
false
|
||||||
|
) {
|
||||||
|
override fun canScrollVertically() = false
|
||||||
|
override fun canScrollHorizontally() = false
|
||||||
|
}
|
||||||
|
rvDayOfWeek.layoutManager = layoutManager
|
||||||
|
|
||||||
|
rvDayOfWeek.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 = 2.5f.dpToPx().toInt()
|
||||||
|
}
|
||||||
|
|
||||||
|
dayOfWeekAdapter.itemCount - 1 -> {
|
||||||
|
outRect.left = 2.5f.dpToPx().toInt()
|
||||||
|
outRect.right = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
outRect.left = 2.5f.dpToPx().toInt()
|
||||||
|
outRect.right = 2.5f.dpToPx().toInt()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
rvDayOfWeek.adapter = dayOfWeekAdapter
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupSeriesView() {
|
||||||
|
adapter = SeriesListAdapter(
|
||||||
|
itemWidth = ((screenWidth - 24f.dpToPx() * 2 - 16f.dpToPx()) / 2f).roundToInt(),
|
||||||
|
onClickItem = {
|
||||||
|
startActivity(
|
||||||
|
Intent(
|
||||||
|
requireContext(),
|
||||||
|
SeriesDetailActivity::class.java
|
||||||
|
).apply {
|
||||||
|
putExtra(Constants.EXTRA_SERIES_ID, it)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onClickCreator = {},
|
||||||
|
isVisibleCreator = false
|
||||||
|
)
|
||||||
|
|
||||||
|
val spanCount = 2
|
||||||
|
val spacingPx = 16f.dpToPx().toInt()
|
||||||
|
val recyclerView = binding.rvSeriesDayOfWeek
|
||||||
|
recyclerView.layoutManager = GridLayoutManager(requireContext(), spanCount)
|
||||||
|
|
||||||
|
recyclerView.addItemDecoration(
|
||||||
|
GridSpacingItemDecoration(spanCount, spacingPx, false)
|
||||||
|
)
|
||||||
|
|
||||||
|
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
||||||
|
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||||
|
super.onScrolled(recyclerView, dx, dy)
|
||||||
|
|
||||||
|
val lastVisibleItemPosition = (recyclerView.layoutManager as LinearLayoutManager?)!!
|
||||||
|
.findLastCompletelyVisibleItemPosition()
|
||||||
|
val itemTotalCount = recyclerView.adapter!!.itemCount - 1
|
||||||
|
|
||||||
|
// 스크롤이 끝에 도달했는지 확인
|
||||||
|
if (!recyclerView.canScrollVertically(1) &&
|
||||||
|
lastVisibleItemPosition == itemTotalCount
|
||||||
|
) {
|
||||||
|
viewModel.getSeriesList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
recyclerView.adapter = adapter
|
||||||
|
|
||||||
|
viewModel.seriesListLiveData.observe(viewLifecycleOwner) {
|
||||||
|
adapter.addItems(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun observeViewModel() {
|
||||||
|
viewModel.isLoading.observe(viewLifecycleOwner) {
|
||||||
|
if (it) {
|
||||||
|
loadingDialog.show(screenWidth)
|
||||||
|
} else {
|
||||||
|
loadingDialog.dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
viewModel.toastLiveData.observe(viewLifecycleOwner) {
|
||||||
|
it?.let { Toast.makeText(requireActivity(), it, Toast.LENGTH_LONG).show() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
package kr.co.vividnext.sodalive.audio_content.series.main.day_of_week
|
||||||
|
|
||||||
|
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.series.GetSeriesListResponse
|
||||||
|
import kr.co.vividnext.sodalive.audio_content.series.main.SeriesMainRepository
|
||||||
|
import kr.co.vividnext.sodalive.base.BaseViewModel
|
||||||
|
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
|
||||||
|
import kr.co.vividnext.sodalive.home.SeriesPublishedDaysOfWeek
|
||||||
|
|
||||||
|
class SeriesMainDayOfWeekViewModel(
|
||||||
|
private val repository: SeriesMainRepository
|
||||||
|
) : BaseViewModel() {
|
||||||
|
private var _isLoading = MutableLiveData(false)
|
||||||
|
val isLoading: LiveData<Boolean>
|
||||||
|
get() = _isLoading
|
||||||
|
|
||||||
|
private val _toastLiveData = MutableLiveData<String?>()
|
||||||
|
val toastLiveData: LiveData<String?>
|
||||||
|
get() = _toastLiveData
|
||||||
|
|
||||||
|
private val _seriesListLiveData = MutableLiveData<List<GetSeriesListResponse.SeriesListItem>>()
|
||||||
|
val seriesListLiveData: LiveData<List<GetSeriesListResponse.SeriesListItem>>
|
||||||
|
get() = _seriesListLiveData
|
||||||
|
|
||||||
|
private var page = 1
|
||||||
|
private var isLast = false
|
||||||
|
private val pageSize = 20
|
||||||
|
|
||||||
|
var dayOfWeek = SeriesPublishedDaysOfWeek.RANDOM
|
||||||
|
set(newValue) {
|
||||||
|
if (field != newValue) {
|
||||||
|
page = 1
|
||||||
|
isLast = false
|
||||||
|
field = newValue
|
||||||
|
getSeriesList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getSeriesList() {
|
||||||
|
if (isLast) return
|
||||||
|
_isLoading.value = true
|
||||||
|
|
||||||
|
compositeDisposable.add(
|
||||||
|
repository.getDayOfWeekSeriesList(
|
||||||
|
dayOfWeek = dayOfWeek,
|
||||||
|
page = page,
|
||||||
|
size = pageSize,
|
||||||
|
token = "Bearer ${SharedPreferenceManager.token}"
|
||||||
|
)
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(
|
||||||
|
{
|
||||||
|
_isLoading.value = false
|
||||||
|
|
||||||
|
val data = it.data
|
||||||
|
if (it.success && data != null) {
|
||||||
|
page += 1
|
||||||
|
|
||||||
|
if (data.isNotEmpty()) {
|
||||||
|
_seriesListLiveData.value = data
|
||||||
|
} else {
|
||||||
|
isLast = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (it.message != null) {
|
||||||
|
_toastLiveData.value = it.message
|
||||||
|
} else {
|
||||||
|
_toastLiveData.value = "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_isLoading.value = false
|
||||||
|
it.message?.let { message -> Logger.e(message) }
|
||||||
|
_toastLiveData.value = "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
package kr.co.vividnext.sodalive.audio_content.series.main.home
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
|
import android.widget.FrameLayout
|
||||||
|
import android.widget.ImageView
|
||||||
|
import com.bumptech.glide.Glide
|
||||||
|
import com.bumptech.glide.request.target.CustomTarget
|
||||||
|
import com.bumptech.glide.request.transition.Transition
|
||||||
|
import com.zhpan.bannerview.BaseBannerAdapter
|
||||||
|
import com.zhpan.bannerview.BaseViewHolder
|
||||||
|
import kr.co.vividnext.sodalive.R
|
||||||
|
|
||||||
|
class SeriesBannerAdapter(
|
||||||
|
private val context: Context,
|
||||||
|
private val itemWidth: Int,
|
||||||
|
private val itemHeight: Int,
|
||||||
|
private val onClick: (SeriesBannerResponse) -> Unit
|
||||||
|
) : BaseBannerAdapter<SeriesBannerResponse>() {
|
||||||
|
override fun bindData(
|
||||||
|
holder: BaseViewHolder<SeriesBannerResponse?>,
|
||||||
|
data: SeriesBannerResponse,
|
||||||
|
position: Int,
|
||||||
|
pageSize: Int
|
||||||
|
) {
|
||||||
|
val ivBanner = holder.findViewById<ImageView>(R.id.iv_recommend_live)
|
||||||
|
val layoutParams = ivBanner.layoutParams as FrameLayout.LayoutParams
|
||||||
|
|
||||||
|
layoutParams.width = itemWidth
|
||||||
|
layoutParams.height = itemHeight
|
||||||
|
|
||||||
|
Glide
|
||||||
|
.with(context)
|
||||||
|
.asBitmap()
|
||||||
|
.load(data.imagePath)
|
||||||
|
.into(object : CustomTarget<Bitmap>() {
|
||||||
|
override fun onResourceReady(resource: Bitmap, transition: Transition<in Bitmap>?) {
|
||||||
|
ivBanner.setImageBitmap(resource)
|
||||||
|
ivBanner.layoutParams = layoutParams
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLoadCleared(placeholder: Drawable?) {
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
ivBanner.setOnClickListener { onClick(data) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getLayoutId(viewType: Int): Int {
|
||||||
|
return R.layout.item_recommend_live
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package kr.co.vividnext.sodalive.audio_content.series.main.home
|
||||||
|
|
||||||
|
import androidx.annotation.Keep
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
import kr.co.vividnext.sodalive.audio_content.series.GetSeriesListResponse
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
data class SeriesHomeResponse(
|
||||||
|
@SerializedName("banners")
|
||||||
|
val banners: List<SeriesBannerResponse>,
|
||||||
|
|
||||||
|
@SerializedName("completedSeriesList")
|
||||||
|
val completedSeriesList: List<GetSeriesListResponse.SeriesListItem>,
|
||||||
|
|
||||||
|
@SerializedName("recommendSeriesList")
|
||||||
|
val recommendSeriesList: List<GetSeriesListResponse.SeriesListItem>
|
||||||
|
)
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
data class SeriesBannerResponse(
|
||||||
|
@SerializedName("seriesId") val seriesId: Long,
|
||||||
|
@SerializedName("imagePath") val imagePath: String
|
||||||
|
)
|
||||||
@@ -0,0 +1,233 @@
|
|||||||
|
package kr.co.vividnext.sodalive.audio_content.series.main.home
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.graphics.Rect
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.LinearLayout
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
|
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.audio_content.series.SeriesListAdapter
|
||||||
|
import kr.co.vividnext.sodalive.audio_content.series.detail.SeriesDetailActivity
|
||||||
|
import kr.co.vividnext.sodalive.base.BaseFragment
|
||||||
|
import kr.co.vividnext.sodalive.common.Constants
|
||||||
|
import kr.co.vividnext.sodalive.common.GridSpacingItemDecoration
|
||||||
|
import kr.co.vividnext.sodalive.common.LoadingDialog
|
||||||
|
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
|
||||||
|
import kr.co.vividnext.sodalive.databinding.FragmentSeriesMainHomeBinding
|
||||||
|
import kr.co.vividnext.sodalive.extensions.dpToPx
|
||||||
|
import kr.co.vividnext.sodalive.home.HomeSeriesAdapter
|
||||||
|
import kr.co.vividnext.sodalive.main.MainActivity
|
||||||
|
import org.koin.android.ext.android.inject
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
class SeriesMainHomeFragment : BaseFragment<FragmentSeriesMainHomeBinding>(
|
||||||
|
FragmentSeriesMainHomeBinding::inflate
|
||||||
|
) {
|
||||||
|
private val viewModel: SeriesMainHomeViewModel by inject()
|
||||||
|
|
||||||
|
private lateinit var loadingDialog: LoadingDialog
|
||||||
|
|
||||||
|
private lateinit var bannerAdapter: SeriesBannerAdapter
|
||||||
|
private lateinit var completedAdapter: HomeSeriesAdapter
|
||||||
|
private lateinit var recommendAdapter: SeriesListAdapter
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
setupView()
|
||||||
|
observeViewModel()
|
||||||
|
|
||||||
|
viewModel.fetchData()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupView() {
|
||||||
|
loadingDialog = LoadingDialog(requireActivity(), layoutInflater)
|
||||||
|
|
||||||
|
setupBanner()
|
||||||
|
setupCompletedSeriesView()
|
||||||
|
setupRecommendSeriesView()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupBanner() {
|
||||||
|
val layoutParams = binding
|
||||||
|
.bannerSlider
|
||||||
|
.layoutParams as LinearLayout.LayoutParams
|
||||||
|
|
||||||
|
val pagerWidth = screenWidth
|
||||||
|
val pagerHeight = pagerWidth * 198 / 352
|
||||||
|
layoutParams.width = pagerWidth
|
||||||
|
layoutParams.height = pagerHeight
|
||||||
|
|
||||||
|
bannerAdapter = SeriesBannerAdapter(
|
||||||
|
requireContext(),
|
||||||
|
pagerWidth,
|
||||||
|
pagerHeight
|
||||||
|
) {
|
||||||
|
startActivity(
|
||||||
|
Intent(
|
||||||
|
requireContext(),
|
||||||
|
SeriesDetailActivity::class.java
|
||||||
|
).apply {
|
||||||
|
putExtra(Constants.EXTRA_SERIES_ID, it.seriesId)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
binding
|
||||||
|
.bannerSlider
|
||||||
|
.layoutParams = layoutParams
|
||||||
|
|
||||||
|
binding.bannerSlider.apply {
|
||||||
|
adapter = bannerAdapter as BaseBannerAdapter<Any>
|
||||||
|
|
||||||
|
setLifecycleRegistry(lifecycle)
|
||||||
|
setScrollDuration(1000)
|
||||||
|
setInterval(4 * 1000)
|
||||||
|
}.create()
|
||||||
|
|
||||||
|
binding
|
||||||
|
.bannerSlider
|
||||||
|
.setIndicatorView(binding.indicatorBanner)
|
||||||
|
.setIndicatorStyle(IndicatorStyle.ROUND_RECT)
|
||||||
|
.setIndicatorSlideMode(IndicatorSlideMode.SMOOTH)
|
||||||
|
.setIndicatorVisibility(View.GONE)
|
||||||
|
.setIndicatorSliderColor(
|
||||||
|
ContextCompat.getColor(requireContext(), R.color.color_909090),
|
||||||
|
ContextCompat.getColor(requireContext(), R.color.color_3bb9f1)
|
||||||
|
)
|
||||||
|
.setIndicatorSliderWidth(10f.dpToPx().toInt(), 10f.dpToPx().toInt())
|
||||||
|
.setIndicatorHeight(10f.dpToPx().toInt())
|
||||||
|
|
||||||
|
viewModel.bannerListLiveData.observe(viewLifecycleOwner) {
|
||||||
|
if (it.isNotEmpty()) {
|
||||||
|
binding.llBanner.visibility = View.VISIBLE
|
||||||
|
binding.bannerSlider.refreshData(it)
|
||||||
|
} else {
|
||||||
|
binding.llBanner.visibility = View.GONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupCompletedSeriesView() {
|
||||||
|
completedAdapter = HomeSeriesAdapter {
|
||||||
|
if (SharedPreferenceManager.token.isNotBlank()) {
|
||||||
|
startActivity(
|
||||||
|
Intent(requireContext(), SeriesDetailActivity::class.java).apply {
|
||||||
|
putExtra(Constants.EXTRA_SERIES_ID, it)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
(requireActivity() as MainActivity).showLoginActivity()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val recyclerView = binding.rvCompletedSeries
|
||||||
|
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 = 8f.dpToPx().toInt()
|
||||||
|
}
|
||||||
|
|
||||||
|
completedAdapter.itemCount - 1 -> {
|
||||||
|
outRect.left = 8f.dpToPx().toInt()
|
||||||
|
outRect.right = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
outRect.left = 8f.dpToPx().toInt()
|
||||||
|
outRect.right = 8f.dpToPx().toInt()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
recyclerView.adapter = completedAdapter
|
||||||
|
|
||||||
|
viewModel.completedSeriesLiveData.observe(viewLifecycleOwner) {
|
||||||
|
if (it.isNotEmpty()) {
|
||||||
|
binding.llCompletedSeries.visibility = View.VISIBLE
|
||||||
|
completedAdapter.addItems(it)
|
||||||
|
} else {
|
||||||
|
binding.llCompletedSeries.visibility = View.GONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupRecommendSeriesView() {
|
||||||
|
recommendAdapter = SeriesListAdapter(
|
||||||
|
itemWidth = ((screenWidth - 24f.dpToPx() * 2 - 16f.dpToPx()) / 2f).roundToInt(),
|
||||||
|
onClickItem = {
|
||||||
|
startActivity(
|
||||||
|
Intent(
|
||||||
|
requireContext(),
|
||||||
|
SeriesDetailActivity::class.java
|
||||||
|
).apply {
|
||||||
|
putExtra(Constants.EXTRA_SERIES_ID, it)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onClickCreator = {},
|
||||||
|
isVisibleCreator = false
|
||||||
|
)
|
||||||
|
|
||||||
|
val spanCount = 2
|
||||||
|
val spacingPx = 16f.dpToPx().toInt()
|
||||||
|
val recyclerView = binding.rvRecommendSeries
|
||||||
|
recyclerView.layoutManager = GridLayoutManager(requireContext(), spanCount)
|
||||||
|
|
||||||
|
recyclerView.addItemDecoration(
|
||||||
|
GridSpacingItemDecoration(spanCount, spacingPx, false)
|
||||||
|
)
|
||||||
|
|
||||||
|
recyclerView.adapter = recommendAdapter
|
||||||
|
|
||||||
|
binding.ivRecommendRefresh.setOnClickListener {
|
||||||
|
viewModel.getRecommendSeriesList()
|
||||||
|
}
|
||||||
|
|
||||||
|
viewModel.recommendSeriesLiveData.observe(viewLifecycleOwner) {
|
||||||
|
if (it.isNotEmpty()) {
|
||||||
|
binding.llRecommendSeries.visibility = View.VISIBLE
|
||||||
|
recommendAdapter.clear()
|
||||||
|
recommendAdapter.addItems(it)
|
||||||
|
} else {
|
||||||
|
binding.llRecommendSeries.visibility = View.GONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun observeViewModel() {
|
||||||
|
viewModel.isLoading.observe(viewLifecycleOwner) {
|
||||||
|
if (it) {
|
||||||
|
loadingDialog.show(screenWidth)
|
||||||
|
} else {
|
||||||
|
loadingDialog.dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
viewModel.toastLiveData.observe(viewLifecycleOwner) {
|
||||||
|
it?.let { Toast.makeText(requireActivity(), it, Toast.LENGTH_LONG).show() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
package kr.co.vividnext.sodalive.audio_content.series.main.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.series.GetSeriesListResponse
|
||||||
|
import kr.co.vividnext.sodalive.audio_content.series.main.SeriesMainRepository
|
||||||
|
import kr.co.vividnext.sodalive.base.BaseViewModel
|
||||||
|
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
|
||||||
|
|
||||||
|
class SeriesMainHomeViewModel(
|
||||||
|
private val repository: SeriesMainRepository
|
||||||
|
) : BaseViewModel() {
|
||||||
|
private var _isLoading = MutableLiveData(false)
|
||||||
|
val isLoading: LiveData<Boolean>
|
||||||
|
get() = _isLoading
|
||||||
|
|
||||||
|
private val _toastLiveData = MutableLiveData<String?>()
|
||||||
|
val toastLiveData: LiveData<String?>
|
||||||
|
get() = _toastLiveData
|
||||||
|
|
||||||
|
private var _bannerListLiveData = MutableLiveData<List<SeriesBannerResponse>>()
|
||||||
|
val bannerListLiveData: LiveData<List<SeriesBannerResponse>>
|
||||||
|
get() = _bannerListLiveData
|
||||||
|
|
||||||
|
private var _completedSeriesLiveData =
|
||||||
|
MutableLiveData<List<GetSeriesListResponse.SeriesListItem>>()
|
||||||
|
val completedSeriesLiveData: LiveData<List<GetSeriesListResponse.SeriesListItem>>
|
||||||
|
get() = _completedSeriesLiveData
|
||||||
|
|
||||||
|
private var _recommendSeriesLiveData =
|
||||||
|
MutableLiveData<List<GetSeriesListResponse.SeriesListItem>>()
|
||||||
|
val recommendSeriesLiveData: LiveData<List<GetSeriesListResponse.SeriesListItem>>
|
||||||
|
get() = _recommendSeriesLiveData
|
||||||
|
|
||||||
|
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) {
|
||||||
|
_bannerListLiveData.value = data.banners
|
||||||
|
_completedSeriesLiveData.value = data.completedSeriesList
|
||||||
|
_recommendSeriesLiveData.value = data.recommendSeriesList
|
||||||
|
} 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 getRecommendSeriesList() {
|
||||||
|
_isLoading.value = true
|
||||||
|
|
||||||
|
compositeDisposable.add(
|
||||||
|
repository.getRecommendSeriesList(token = "Bearer ${SharedPreferenceManager.token}")
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(
|
||||||
|
{
|
||||||
|
_isLoading.value = false
|
||||||
|
if (it.success && it.data != null) {
|
||||||
|
_recommendSeriesLiveData.value = it.data
|
||||||
|
} else {
|
||||||
|
if (it.message != null) {
|
||||||
|
_toastLiveData.value = it.message
|
||||||
|
} else {
|
||||||
|
_toastLiveData.value = "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_isLoading.value = false
|
||||||
|
it.message?.let { message -> Logger.e(message) }
|
||||||
|
_toastLiveData.value = "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -32,6 +32,11 @@ import kr.co.vividnext.sodalive.audio_content.series.SeriesListAllViewModel
|
|||||||
import kr.co.vividnext.sodalive.audio_content.series.SeriesRepository
|
import kr.co.vividnext.sodalive.audio_content.series.SeriesRepository
|
||||||
import kr.co.vividnext.sodalive.audio_content.series.content.SeriesContentAllViewModel
|
import kr.co.vividnext.sodalive.audio_content.series.content.SeriesContentAllViewModel
|
||||||
import kr.co.vividnext.sodalive.audio_content.series.detail.SeriesDetailViewModel
|
import kr.co.vividnext.sodalive.audio_content.series.detail.SeriesDetailViewModel
|
||||||
|
import kr.co.vividnext.sodalive.audio_content.series.main.SeriesMainApi
|
||||||
|
import kr.co.vividnext.sodalive.audio_content.series.main.SeriesMainRepository
|
||||||
|
import kr.co.vividnext.sodalive.audio_content.series.main.by_genre.SeriesMainByGenreViewModel
|
||||||
|
import kr.co.vividnext.sodalive.audio_content.series.main.day_of_week.SeriesMainDayOfWeekViewModel
|
||||||
|
import kr.co.vividnext.sodalive.audio_content.series.main.home.SeriesMainHomeViewModel
|
||||||
import kr.co.vividnext.sodalive.audio_content.upload.AudioContentUploadViewModel
|
import kr.co.vividnext.sodalive.audio_content.upload.AudioContentUploadViewModel
|
||||||
import kr.co.vividnext.sodalive.audio_content.upload.theme.AudioContentThemeViewModel
|
import kr.co.vividnext.sodalive.audio_content.upload.theme.AudioContentThemeViewModel
|
||||||
import kr.co.vividnext.sodalive.audition.AuditionApi
|
import kr.co.vividnext.sodalive.audition.AuditionApi
|
||||||
@@ -224,6 +229,7 @@ class AppDI(private val context: Context, isDebugMode: Boolean) {
|
|||||||
single { ApiBuilder().build(get(), TermsApi::class.java) }
|
single { ApiBuilder().build(get(), TermsApi::class.java) }
|
||||||
single { ApiBuilder().build(get(), EventApi::class.java) }
|
single { ApiBuilder().build(get(), EventApi::class.java) }
|
||||||
single { ApiBuilder().build(get(), SeriesApi::class.java) }
|
single { ApiBuilder().build(get(), SeriesApi::class.java) }
|
||||||
|
single { ApiBuilder().build(get(), SeriesMainApi::class.java) }
|
||||||
single { ApiBuilder().build(get(), ReportApi::class.java) }
|
single { ApiBuilder().build(get(), ReportApi::class.java) }
|
||||||
single { ApiBuilder().build(get(), LiveRecommendApi::class.java) }
|
single { ApiBuilder().build(get(), LiveRecommendApi::class.java) }
|
||||||
single { ApiBuilder().build(get(), ExplorerApi::class.java) }
|
single { ApiBuilder().build(get(), ExplorerApi::class.java) }
|
||||||
@@ -333,6 +339,9 @@ class AppDI(private val context: Context, isDebugMode: Boolean) {
|
|||||||
viewModel { NewCharactersAllViewModel(get()) }
|
viewModel { NewCharactersAllViewModel(get()) }
|
||||||
viewModel { OriginalWorkViewModel(get()) }
|
viewModel { OriginalWorkViewModel(get()) }
|
||||||
viewModel { OriginalWorkDetailViewModel(get()) }
|
viewModel { OriginalWorkDetailViewModel(get()) }
|
||||||
|
viewModel { SeriesMainHomeViewModel(get()) }
|
||||||
|
viewModel { SeriesMainByGenreViewModel(get()) }
|
||||||
|
viewModel { SeriesMainDayOfWeekViewModel(get()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private val repositoryModule = module {
|
private val repositoryModule = module {
|
||||||
@@ -376,6 +385,7 @@ class AppDI(private val context: Context, isDebugMode: Boolean) {
|
|||||||
factory { CharacterCommentRepository(get()) }
|
factory { CharacterCommentRepository(get()) }
|
||||||
factory { NewCharactersRepository(get()) }
|
factory { NewCharactersRepository(get()) }
|
||||||
factory { OriginalWorkRepository(get()) }
|
factory { OriginalWorkRepository(get()) }
|
||||||
|
factory { SeriesMainRepository(get()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -26,13 +26,16 @@ import com.zhpan.indicator.enums.IndicatorSlideMode
|
|||||||
import com.zhpan.indicator.enums.IndicatorStyle
|
import com.zhpan.indicator.enums.IndicatorStyle
|
||||||
import kr.co.vividnext.sodalive.R
|
import kr.co.vividnext.sodalive.R
|
||||||
import kr.co.vividnext.sodalive.audio_content.AudioContentPlayService
|
import kr.co.vividnext.sodalive.audio_content.AudioContentPlayService
|
||||||
|
import kr.co.vividnext.sodalive.audio_content.all.AudioContentAllActivity
|
||||||
import kr.co.vividnext.sodalive.audio_content.all.AudioContentNewAllActivity
|
import kr.co.vividnext.sodalive.audio_content.all.AudioContentNewAllActivity
|
||||||
import kr.co.vividnext.sodalive.audio_content.box.AudioContentBoxActivity
|
import kr.co.vividnext.sodalive.audio_content.box.AudioContentBoxActivity
|
||||||
import kr.co.vividnext.sodalive.audio_content.detail.AudioContentDetailActivity
|
import kr.co.vividnext.sodalive.audio_content.detail.AudioContentDetailActivity
|
||||||
import kr.co.vividnext.sodalive.audio_content.main.AudioContentBannerType
|
import kr.co.vividnext.sodalive.audio_content.main.AudioContentBannerType
|
||||||
import kr.co.vividnext.sodalive.audio_content.main.banner.AudioContentMainBannerAdapter
|
import kr.co.vividnext.sodalive.audio_content.main.banner.AudioContentMainBannerAdapter
|
||||||
import kr.co.vividnext.sodalive.audio_content.player.AudioContentPlayerService
|
import kr.co.vividnext.sodalive.audio_content.player.AudioContentPlayerService
|
||||||
|
import kr.co.vividnext.sodalive.audio_content.series.SeriesListAllActivity
|
||||||
import kr.co.vividnext.sodalive.audio_content.series.detail.SeriesDetailActivity
|
import kr.co.vividnext.sodalive.audio_content.series.detail.SeriesDetailActivity
|
||||||
|
import kr.co.vividnext.sodalive.audio_content.series.main.SeriesMainActivity
|
||||||
import kr.co.vividnext.sodalive.audio_content.upload.AudioContentUploadActivity
|
import kr.co.vividnext.sodalive.audio_content.upload.AudioContentUploadActivity
|
||||||
import kr.co.vividnext.sodalive.audition.AuditionActivity
|
import kr.co.vividnext.sodalive.audition.AuditionActivity
|
||||||
import kr.co.vividnext.sodalive.base.BaseFragment
|
import kr.co.vividnext.sodalive.base.BaseFragment
|
||||||
@@ -646,7 +649,10 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>(FragmentHomeBinding::infl
|
|||||||
// ‘오직 보이스온에서만’ 전체보기: isOriginal=true로 시리즈 전체보기 화면 진입
|
// ‘오직 보이스온에서만’ 전체보기: isOriginal=true로 시리즈 전체보기 화면 진입
|
||||||
if (SharedPreferenceManager.token.isNotBlank()) {
|
if (SharedPreferenceManager.token.isNotBlank()) {
|
||||||
startActivity(
|
startActivity(
|
||||||
Intent(requireContext(), kr.co.vividnext.sodalive.audio_content.series.SeriesListAllActivity::class.java).apply {
|
Intent(
|
||||||
|
requireContext(),
|
||||||
|
SeriesListAllActivity::class.java
|
||||||
|
).apply {
|
||||||
putExtra(kr.co.vividnext.sodalive.common.Constants.EXTRA_IS_ORIGINAL, true)
|
putExtra(kr.co.vividnext.sodalive.common.Constants.EXTRA_IS_ORIGINAL, true)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -791,7 +797,7 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>(FragmentHomeBinding::infl
|
|||||||
outRect.right = 2.5f.dpToPx().toInt()
|
outRect.right = 2.5f.dpToPx().toInt()
|
||||||
}
|
}
|
||||||
|
|
||||||
seriesDayOfWeekAdapter.itemCount - 1 -> {
|
dayOfWeekAdapter.itemCount - 1 -> {
|
||||||
outRect.left = 2.5f.dpToPx().toInt()
|
outRect.left = 2.5f.dpToPx().toInt()
|
||||||
outRect.right = 0
|
outRect.right = 0
|
||||||
}
|
}
|
||||||
@@ -804,6 +810,21 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>(FragmentHomeBinding::infl
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
rvDayOfWeek.adapter = dayOfWeekAdapter
|
rvDayOfWeek.adapter = dayOfWeekAdapter
|
||||||
|
|
||||||
|
binding.tvSeriesDayOfWeekAll.setOnClickListener {
|
||||||
|
if (SharedPreferenceManager.token.isNotBlank()) {
|
||||||
|
startActivity(
|
||||||
|
Intent(
|
||||||
|
requireContext(),
|
||||||
|
SeriesMainActivity::class.java
|
||||||
|
).apply {
|
||||||
|
putExtra(Constants.EXTRA_AUDIO_CONTENT_FREE, true)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
(requireActivity() as MainActivity).showLoginActivity()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupPopularCharacters() {
|
private fun setupPopularCharacters() {
|
||||||
@@ -1127,7 +1148,7 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>(FragmentHomeBinding::infl
|
|||||||
binding.tvFreeContentAll.setOnClickListener {
|
binding.tvFreeContentAll.setOnClickListener {
|
||||||
if (SharedPreferenceManager.token.isNotBlank()) {
|
if (SharedPreferenceManager.token.isNotBlank()) {
|
||||||
startActivity(
|
startActivity(
|
||||||
Intent(requireContext(), kr.co.vividnext.sodalive.audio_content.all.AudioContentAllActivity::class.java).apply {
|
Intent(requireContext(), AudioContentAllActivity::class.java).apply {
|
||||||
putExtra(Constants.EXTRA_AUDIO_CONTENT_FREE, true)
|
putExtra(Constants.EXTRA_AUDIO_CONTENT_FREE, true)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -1211,7 +1232,10 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>(FragmentHomeBinding::infl
|
|||||||
binding.tvPointContentAll.setOnClickListener {
|
binding.tvPointContentAll.setOnClickListener {
|
||||||
if (SharedPreferenceManager.token.isNotBlank()) {
|
if (SharedPreferenceManager.token.isNotBlank()) {
|
||||||
startActivity(
|
startActivity(
|
||||||
Intent(requireContext(), kr.co.vividnext.sodalive.audio_content.all.AudioContentAllActivity::class.java).apply {
|
Intent(
|
||||||
|
requireContext(),
|
||||||
|
AudioContentAllActivity::class.java
|
||||||
|
).apply {
|
||||||
putExtra(Constants.EXTRA_AUDIO_CONTENT_POINT_ONLY, true)
|
putExtra(Constants.EXTRA_AUDIO_CONTENT_POINT_ONLY, true)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -1229,7 +1253,10 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>(FragmentHomeBinding::infl
|
|||||||
recommendContentAdapter = HomeContentAdapter(onClickItem = {
|
recommendContentAdapter = HomeContentAdapter(onClickItem = {
|
||||||
if (SharedPreferenceManager.token.isNotBlank()) {
|
if (SharedPreferenceManager.token.isNotBlank()) {
|
||||||
startActivity(
|
startActivity(
|
||||||
Intent(requireContext(), AudioContentDetailActivity::class.java).apply {
|
Intent(
|
||||||
|
requireContext(),
|
||||||
|
AudioContentDetailActivity::class.java
|
||||||
|
).apply {
|
||||||
putExtra(Constants.EXTRA_AUDIO_CONTENT_ID, it)
|
putExtra(Constants.EXTRA_AUDIO_CONTENT_ID, it)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
30
app/src/main/res/layout/activity_series_main.xml
Normal file
30
app/src/main/res/layout/activity_series_main.xml
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="@color/color_131313"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<include
|
||||||
|
android:id="@+id/toolbar"
|
||||||
|
layout="@layout/detail_toolbar" />
|
||||||
|
|
||||||
|
<com.google.android.material.tabs.TabLayout
|
||||||
|
android:id="@+id/tab_layout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@color/color_131313"
|
||||||
|
app:tabIndicatorColor="@color/color_3bb9f1"
|
||||||
|
app:tabIndicatorFullWidth="true"
|
||||||
|
app:tabIndicatorHeight="4dp"
|
||||||
|
app:tabSelectedTextColor="@color/color_3bb9f1"
|
||||||
|
app:tabTextAppearance="@style/tabText"
|
||||||
|
app:tabTextColor="@color/color_b0bec5" />
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/fl_container"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
@@ -253,16 +253,33 @@
|
|||||||
android:layout_marginBottom="48dp"
|
android:layout_marginBottom="48dp"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<TextView
|
<LinearLayout
|
||||||
android:id="@+id/tv_series_day_of_week"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginHorizontal="24dp"
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingHorizontal="24dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_series_day_of_week"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
android:fontFamily="@font/pretendard_bold"
|
android:fontFamily="@font/pretendard_bold"
|
||||||
android:text="요일별 시리즈"
|
android:text="요일별 시리즈"
|
||||||
android:textColor="@color/white"
|
android:textColor="@color/white"
|
||||||
android:textSize="24sp" />
|
android:textSize="24sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_series_day_of_week_all"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:fontFamily="@font/pretendard_regular"
|
||||||
|
android:text="전체보기"
|
||||||
|
android:textColor="#90A4AE"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/rv_series_day_of_week_day"
|
android:id="@+id/rv_series_day_of_week_day"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|||||||
23
app/src/main/res/layout/fragment_series_main_by_genre.xml
Normal file
23
app/src/main/res/layout/fragment_series_main_by_genre.xml
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/rv_genre"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="24dp"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:nestedScrollingEnabled="false"
|
||||||
|
android:paddingHorizontal="24dp" />
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/rv_series_by_genre"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginTop="24dp"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:paddingHorizontal="24dp" />
|
||||||
|
</LinearLayout>
|
||||||
23
app/src/main/res/layout/fragment_series_main_day_of_week.xml
Normal file
23
app/src/main/res/layout/fragment_series_main_day_of_week.xml
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/rv_series_day_of_week_day"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="24dp"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:nestedScrollingEnabled="false"
|
||||||
|
android:paddingHorizontal="24dp" />
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/rv_series_day_of_week"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginTop="24dp"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:paddingHorizontal="24dp" />
|
||||||
|
</LinearLayout>
|
||||||
111
app/src/main/res/layout/fragment_series_main_home.xml
Normal file
111
app/src/main/res/layout/fragment_series_main_home.xml
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="@color/color_131313"
|
||||||
|
android:fillViewport="true">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingVertical="24dp">
|
||||||
|
|
||||||
|
<!-- 배너 섹션 -->
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/ll_banner"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<com.zhpan.bannerview.BannerViewPager
|
||||||
|
android:id="@+id/banner_slider"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:clipChildren="false"
|
||||||
|
android:clipToPadding="false" />
|
||||||
|
|
||||||
|
<com.zhpan.indicator.IndicatorView
|
||||||
|
android:id="@+id/indicator_banner"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_marginTop="16dp" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- 완결 시리즈 섹션 -->
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/ll_completed_series"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="48dp"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:visibility="gone">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_completed_series"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginHorizontal="24dp"
|
||||||
|
android:fontFamily="@font/pretendard_bold"
|
||||||
|
android:text="완결 시리즈"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="24sp" />
|
||||||
|
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/rv_completed_series"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:paddingHorizontal="24dp" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- 추천 시리즈 섹션 -->
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/ll_recommend_series"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="48dp"
|
||||||
|
android:layout_marginBottom="24dp"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:visibility="gone">
|
||||||
|
|
||||||
|
<!-- 제목과 새로고침 -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingHorizontal="24dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:fontFamily="@font/pretendard_bold"
|
||||||
|
android:text="추천 시리즈"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="24sp" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/iv_recommend_refresh"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:contentDescription="@null"
|
||||||
|
android:src="@drawable/ic_refresh" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- 2단 Grid 리스트 -->
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/rv_recommend_series"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:paddingHorizontal="24dp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
</androidx.core.widget.NestedScrollView>
|
||||||
Reference in New Issue
Block a user