diff --git a/app/src/main/java/com/runnect/runnect/data/repository/CourseRepositoryImpl.kt b/app/src/main/java/com/runnect/runnect/data/repository/CourseRepositoryImpl.kt index 4fb1cfdad..6dadbcad5 100644 --- a/app/src/main/java/com/runnect/runnect/data/repository/CourseRepositoryImpl.kt +++ b/app/src/main/java/com/runnect/runnect/data/repository/CourseRepositoryImpl.kt @@ -33,11 +33,11 @@ class CourseRepositoryImpl @Inject constructor(private val remoteCourseDataSourc override suspend fun getRecommendCourse( pageNo: String, - ordering: String + sort: String ): Result = runCatching { val response = remoteCourseDataSource.getRecommendCourse( pageNo = pageNo, - ordering = ordering + sort = sort ).data response?.let { diff --git a/app/src/main/java/com/runnect/runnect/data/service/CourseService.kt b/app/src/main/java/com/runnect/runnect/data/service/CourseService.kt index e61137072..10b17da70 100644 --- a/app/src/main/java/com/runnect/runnect/data/service/CourseService.kt +++ b/app/src/main/java/com/runnect/runnect/data/service/CourseService.kt @@ -19,7 +19,7 @@ interface CourseService { @GET("/api/public-course") suspend fun getRecommendCourse( @Query("pageNo") pageNo: String, - @Query("ordering") ordering: String + @Query("sort") sort: String ): BaseResponse @POST("/api/scrap") diff --git a/app/src/main/java/com/runnect/runnect/data/source/remote/RemoteCourseDataSource.kt b/app/src/main/java/com/runnect/runnect/data/source/remote/RemoteCourseDataSource.kt index 093876699..2f1b6112a 100644 --- a/app/src/main/java/com/runnect/runnect/data/source/remote/RemoteCourseDataSource.kt +++ b/app/src/main/java/com/runnect/runnect/data/source/remote/RemoteCourseDataSource.kt @@ -24,9 +24,9 @@ class RemoteCourseDataSource @Inject constructor( suspend fun getRecommendCourse( pageNo: String, - ordering: String + sort: String ): BaseResponse = - courseService.getRecommendCourse(pageNo = pageNo, ordering = ordering) + courseService.getRecommendCourse(pageNo = pageNo, sort = sort) suspend fun postCourseScrap(requestPostCourseScrap: RequestPostCourseScrap): BaseResponse = courseService.postCourseScrap(requestPostCourseScrap) diff --git a/app/src/main/java/com/runnect/runnect/domain/repository/CourseRepository.kt b/app/src/main/java/com/runnect/runnect/domain/repository/CourseRepository.kt index ec62303f2..9c7857eb6 100644 --- a/app/src/main/java/com/runnect/runnect/domain/repository/CourseRepository.kt +++ b/app/src/main/java/com/runnect/runnect/domain/repository/CourseRepository.kt @@ -26,7 +26,7 @@ interface CourseRepository { suspend fun getRecommendCourse( pageNo: String, - ordering: String + sort: String ): Result suspend fun getCourseSearch(keyword: String): Result?> diff --git a/app/src/main/java/com/runnect/runnect/presentation/MainActivity.kt b/app/src/main/java/com/runnect/runnect/presentation/MainActivity.kt index 161bee765..7bef940ae 100644 --- a/app/src/main/java/com/runnect/runnect/presentation/MainActivity.kt +++ b/app/src/main/java/com/runnect/runnect/presentation/MainActivity.kt @@ -149,7 +149,7 @@ class MainActivity : BindingActivity(R.layout.activity_main var storageScrapFragment: StorageScrapFragment? = null fun updateCourseDiscoverScreen() { - discoverFragment?.getRecommendCourses() + discoverFragment?.refreshDiscoverCourses() } fun updateStorageScrapScreen() { diff --git a/app/src/main/java/com/runnect/runnect/presentation/discover/DiscoverFragment.kt b/app/src/main/java/com/runnect/runnect/presentation/discover/DiscoverFragment.kt index dd34a979f..7141c91eb 100644 --- a/app/src/main/java/com/runnect/runnect/presentation/discover/DiscoverFragment.kt +++ b/app/src/main/java/com/runnect/runnect/presentation/discover/DiscoverFragment.kt @@ -17,7 +17,6 @@ import com.runnect.runnect.R import com.runnect.runnect.binding.BindingFragment import com.runnect.runnect.databinding.FragmentDiscoverBinding import com.runnect.runnect.domain.entity.DiscoverBanner -import com.runnect.runnect.domain.entity.DiscoverMultiViewItem import com.runnect.runnect.presentation.discover.model.EditableDiscoverCourse import com.runnect.runnect.presentation.MainActivity import com.runnect.runnect.presentation.MainActivity.Companion.isVisitorMode @@ -41,8 +40,6 @@ import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking -import timber.log.Timber @AndroidEntryPoint class DiscoverFragment : BindingFragment(R.layout.fragment_discover) { @@ -98,10 +95,12 @@ class DiscoverFragment : BindingFragment(R.layout.fragm }, handleVisitorMode = { context?.let { showCourseScrapWarningToast(it) } + }, + onSortButtonClick = { criteria -> + viewModel.sortRecommendCourses(criteria) } ).apply { binding.rvDiscoverMultiView.adapter = this - binding.rvDiscoverMultiView.setHasFixedSize(true) } } @@ -192,19 +191,15 @@ class DiscoverFragment : BindingFragment(R.layout.fragm val isScrollDown = dy > 0 if (isScrollDown) showCircleUploadButton() - checkNextPageLoadingCondition(recyclerView) + if (checkNextPageLoadingCondition(recyclerView)) { + viewModel.getRecommendCourseNextPage() + } } }) } - private fun checkNextPageLoadingCondition(recyclerView: RecyclerView) { - if (isCourseLoadingCompleted() && !recyclerView.canScrollVertically(SCROLL_DIRECTION)) { - Timber.d("스크롤이 끝에 도달했어요!") - if (!viewModel.isNextPageLoading()) { - viewModel.getRecommendCourseNextPage() - } - } - } + private fun checkNextPageLoadingCondition(recyclerView: RecyclerView) = + isCourseLoadingCompleted() && !recyclerView.canScrollVertically(SCROLL_DIRECTION) && !viewModel.isNextPageLoading() private fun isCourseLoadingCompleted() = ::multiViewAdapter.isInitialized && multiViewAdapter.itemCount >= DiscoverMultiViewType.values().size @@ -228,6 +223,11 @@ class DiscoverFragment : BindingFragment(R.layout.fragm private fun initRefreshLayoutListener() { binding.refreshLayout.setOnRefreshListener { + // 리프레시 직후에 비어있는 리스트로 리사이클러뷰 초기화 + multiViewAdapter.initMarathonCourses(emptyList()) + multiViewAdapter.initRecommendCourses(emptyList()) + + // 서버통신 직후에 첫 페이지 데이터로 리사이클러뷰 초기화 viewModel.refreshDiscoverCourses() binding.refreshLayout.isRefreshing = false } @@ -273,6 +273,7 @@ class DiscoverFragment : BindingFragment(R.layout.fragm setupMarathonCourseGetStateObserver() setupRecommendCourseGetStateObserver() setupRecommendCourseNextPageStateObserver() + setupRecommendCourseSortStateObserver() setupCourseScrapStateObserver() } @@ -333,7 +334,7 @@ class DiscoverFragment : BindingFragment(R.layout.fragm } private fun setupMarathonCourseGetStateObserver() { - viewModel.marathonCourseState.observe(viewLifecycleOwner) { state -> + viewModel.marathonCourseGetState.observe(viewLifecycleOwner) { state -> when (state) { is UiStateV2.Success -> { multiViewAdapter.initMarathonCourses(state.data) @@ -353,13 +354,16 @@ class DiscoverFragment : BindingFragment(R.layout.fragm } private fun setupRecommendCourseGetStateObserver() { - viewModel.recommendCourseState.observe(viewLifecycleOwner) { state -> + viewModel.recommendCourseGetState.observe(viewLifecycleOwner) { state -> when (state) { is UiStateV2.Loading -> showLoadingProgressBar() is UiStateV2.Success -> { - dismissLoadingProgressBar() + // todo: 리프레시에 의한 추천코스 어댑터의 submitList 동작이 완료되고 나서 multiViewAdapter.initRecommendCourses(state.data) + + // todo: 로딩 프로그레스바를 삭제해야 한다. + dismissLoadingProgressBar() } is UiStateV2.Failure -> { @@ -387,7 +391,7 @@ class DiscoverFragment : BindingFragment(R.layout.fragm } private fun setupRecommendCourseNextPageStateObserver() { - viewModel.nextPageState.observe(viewLifecycleOwner) { state -> + viewModel.recommendCourseNextPageState.observe(viewLifecycleOwner) { state -> when (state) { is UiStateV2.Success -> { multiViewAdapter.addRecommendCourseNextPage(state.data) @@ -406,6 +410,26 @@ class DiscoverFragment : BindingFragment(R.layout.fragm } } + private fun setupRecommendCourseSortStateObserver() { + viewModel.recommendCourseSortState.observe(viewLifecycleOwner) { state -> + when (state) { + is UiStateV2.Success -> { + multiViewAdapter.sortRecommendCourseFirstPage(state.data) + } + + is UiStateV2.Failure -> { + context?.showSnackbar( + anchorView = binding.root, + message = state.msg, + gravity = Gravity.TOP + ) + } + + else -> {} + } + } + } + private fun setupCourseScrapStateObserver() { viewModel.courseScrapState.observe(viewLifecycleOwner) { state -> if (state is UiStateV2.Failure) { @@ -447,8 +471,8 @@ class DiscoverFragment : BindingFragment(R.layout.fragm return layoutManager.findFirstCompletelyVisibleItemPosition() > 0 } - fun getRecommendCourses() { - viewModel.getRecommendCourses() + fun refreshDiscoverCourses() { + viewModel.refreshDiscoverCourses() } override fun onAttach(context: Context) { diff --git a/app/src/main/java/com/runnect/runnect/presentation/discover/DiscoverViewModel.kt b/app/src/main/java/com/runnect/runnect/presentation/discover/DiscoverViewModel.kt index 548ce07f5..f74624510 100644 --- a/app/src/main/java/com/runnect/runnect/presentation/discover/DiscoverViewModel.kt +++ b/app/src/main/java/com/runnect/runnect/presentation/discover/DiscoverViewModel.kt @@ -26,17 +26,21 @@ class DiscoverViewModel @Inject constructor( val bannerGetState: LiveData>> get() = _bannerGetState - private val _marathonCourseState = MutableLiveData>>() - val marathonCourseState: LiveData>> - get() = _marathonCourseState + private val _marathonCourseGetState = MutableLiveData>>() + val marathonCourseGetState: LiveData>> + get() = _marathonCourseGetState - private val _recommendCourseState = MutableLiveData>>() - val recommendCourseState: LiveData>> - get() = _recommendCourseState + private val _recommendCourseGetState = MutableLiveData>>() + val recommendCourseGetState: LiveData>> + get() = _recommendCourseGetState - private val _nextPageState = MutableLiveData>>() - val nextPageState: LiveData>> - get() = _nextPageState + private val _recommendCourseNextPageState = MutableLiveData>>() + val recommendCourseNextPageState: LiveData>> + get() = _recommendCourseNextPageState + + private val _recommendCourseSortState = MutableLiveData>>() + val recommendCourseSortState: LiveData>> + get() = _recommendCourseSortState private val _courseScrapState = MutableLiveData>() val courseScrapState: LiveData> @@ -47,6 +51,7 @@ class DiscoverViewModel @Inject constructor( private var isRecommendCoursePageEnd = false private var currentPageNumber = FIRST_PAGE_NUM + private var currentSortCriteria = DEFAULT_SORT_CRITERIA init { getDiscoverBanners() @@ -60,13 +65,21 @@ class DiscoverViewModel @Inject constructor( fun refreshDiscoverCourses() { getMarathonCourses() - initRecommendCoursePagingData() + updateRecommendCourseSortCriteria( + criteria = DEFAULT_SORT_CRITERIA + ) getRecommendCourses() } - private fun initRecommendCoursePagingData() { - isRecommendCoursePageEnd = false - currentPageNumber = FIRST_PAGE_NUM + private fun updateRecommendCoursePagingData(isEnd: Boolean, pageNo: Int) { + isRecommendCoursePageEnd = isEnd + currentPageNumber = pageNo + Timber.d("isEnd: ${isRecommendCoursePageEnd}, page: ${currentPageNumber}") + } + + private fun updateRecommendCourseSortCriteria(criteria: String) { + currentSortCriteria = criteria + Timber.d("sort: ${currentSortCriteria}") } private fun getDiscoverBanners() { @@ -85,82 +98,121 @@ class DiscoverViewModel @Inject constructor( private fun getMarathonCourses() { viewModelScope.launch { - _marathonCourseState.value = UiStateV2.Loading + _marathonCourseGetState.value = UiStateV2.Loading courseRepository.getMarathonCourse() .onSuccess { courses -> if (courses == null) { - _marathonCourseState.value = + _marathonCourseGetState.value = UiStateV2.Failure("MARATHON COURSE DATA IS NULL") return@launch } - _marathonCourseState.value = UiStateV2.Success(courses) + _marathonCourseGetState.value = UiStateV2.Success(courses) Timber.d("MARATHON COURSE GET SUCCESS") } .onFailure { exception -> - _marathonCourseState.value = UiStateV2.Failure(exception.message.toString()) + _marathonCourseGetState.value = UiStateV2.Failure(exception.message.toString()) Timber.e("MARATHON COURSE GET FAIL") } } } - fun getRecommendCourses() { + private fun getRecommendCourses() { viewModelScope.launch { - _recommendCourseState.value = UiStateV2.Loading + _recommendCourseGetState.value = UiStateV2.Loading courseRepository.getRecommendCourse( pageNo = FIRST_PAGE_NUM.toString(), - ordering = DEFAULT_SORT_CRITERIA + sort = currentSortCriteria ).onSuccess { pagingData -> if (pagingData == null) { - _recommendCourseState.value = + _recommendCourseGetState.value = UiStateV2.Failure("RECOMMEND COURSE DATA IS NULL") return@onSuccess } - isRecommendCoursePageEnd = pagingData.isEnd - _recommendCourseState.value = UiStateV2.Success(pagingData.recommendCourses) + updateRecommendCoursePagingData( + isEnd = pagingData.isEnd, + pageNo = FIRST_PAGE_NUM + ) + + _recommendCourseGetState.value = UiStateV2.Success(pagingData.recommendCourses) Timber.d("RECOMMEND COURSE GET SUCCESS") + }.onFailure { exception -> - _recommendCourseState.value = UiStateV2.Failure(exception.message.toString()) + _recommendCourseGetState.value = UiStateV2.Failure(exception.message.toString()) Timber.e("RECOMMEND COURSE GET FAIL") } } } - fun isNextPageLoading() = nextPageState.value is UiStateV2.Loading + fun isNextPageLoading() = recommendCourseNextPageState.value is UiStateV2.Loading fun getRecommendCourseNextPage() { viewModelScope.launch { + // 다음 페이지가 없으면 요청하지 않는다. if (isRecommendCoursePageEnd) return@launch - Timber.d("다음 페이지를 요청했어요!") - _nextPageState.value = UiStateV2.Loading - currentPageNumber++ + _recommendCourseNextPageState.value = UiStateV2.Loading courseRepository.getRecommendCourse( - pageNo = currentPageNumber.toString(), - ordering = DEFAULT_SORT_CRITERIA + pageNo = (currentPageNumber + 1).toString(), + sort = currentSortCriteria ) .onSuccess { pagingData -> if (pagingData == null) { - _nextPageState.value = + _recommendCourseNextPageState.value = UiStateV2.Failure("RECOMMEND COURSE NEXT PAGE DATA IS NULL") return@onSuccess } - isRecommendCoursePageEnd = pagingData.isEnd - _nextPageState.value = UiStateV2.Success(pagingData.recommendCourses) + updateRecommendCoursePagingData( + isEnd = pagingData.isEnd, + pageNo = currentPageNumber + 1 + ) + + _recommendCourseNextPageState.value = UiStateV2.Success(pagingData.recommendCourses) Timber.d("RECOMMEND COURSE NEXT PAGE GET SUCCESS") } .onFailure { exception -> - _nextPageState.value = UiStateV2.Failure(exception.message.toString()) + _recommendCourseNextPageState.value = + UiStateV2.Failure(exception.message.toString()) Timber.e("RECOMMEND COURSE NEXT PAGE GET FAIL") } } } + fun sortRecommendCourses(criteria: String) { + updateRecommendCourseSortCriteria(criteria) + + viewModelScope.launch { + _recommendCourseSortState.value = UiStateV2.Loading + + courseRepository.getRecommendCourse( + pageNo = FIRST_PAGE_NUM.toString(), + sort = currentSortCriteria + ).onSuccess { pagingData -> + if (pagingData == null) { + _recommendCourseSortState.value = + UiStateV2.Failure("RECOMMEND COURSE DATA IS NULL") + return@onSuccess + } + + updateRecommendCoursePagingData( + isEnd = pagingData.isEnd, + pageNo = FIRST_PAGE_NUM + ) + + _recommendCourseSortState.value = UiStateV2.Success(pagingData.recommendCourses) + Timber.d("RECOMMEND COURSE SORT SUCCESS") + }.onFailure { exception -> + _recommendCourseSortState.value = UiStateV2.Failure(exception.message.toString()) + Timber.e("RECOMMEND COURSE SORT FAIL") + } + } + } + fun postCourseScrap(id: Int, scrapTF: Boolean) { viewModelScope.launch { _courseScrapState.value = UiStateV2.Loading diff --git a/app/src/main/java/com/runnect/runnect/presentation/discover/adapter/DiscoverMarathonAdapter.kt b/app/src/main/java/com/runnect/runnect/presentation/discover/adapter/DiscoverMarathonAdapter.kt index 1e3f57f7e..c07b49b5d 100644 --- a/app/src/main/java/com/runnect/runnect/presentation/discover/adapter/DiscoverMarathonAdapter.kt +++ b/app/src/main/java/com/runnect/runnect/presentation/discover/adapter/DiscoverMarathonAdapter.kt @@ -78,14 +78,17 @@ class DiscoverMarathonAdapter( } fun updateMarathonCourseItem( - targetIndex: Int, + publicCourseId: Int, updatedCourse: EditableDiscoverCourse ) { - currentList[targetIndex].apply { - title = updatedCourse.title - scrap = updatedCourse.scrap + currentList.forEachIndexed { index, course -> + if (course.id == publicCourseId) { + course.title = updatedCourse.title + course.scrap = updatedCourse.scrap + notifyItemChanged(index) + return + } } - notifyItemChanged(targetIndex) } companion object { diff --git a/app/src/main/java/com/runnect/runnect/presentation/discover/adapter/DiscoverRecommendAdapter.kt b/app/src/main/java/com/runnect/runnect/presentation/discover/adapter/DiscoverRecommendAdapter.kt index f991cc4d4..f12119923 100644 --- a/app/src/main/java/com/runnect/runnect/presentation/discover/adapter/DiscoverRecommendAdapter.kt +++ b/app/src/main/java/com/runnect/runnect/presentation/discover/adapter/DiscoverRecommendAdapter.kt @@ -41,7 +41,7 @@ class DiscoverRecommendAdapter( private val binding: ItemDiscoverRecommendBinding, private val onHeartButtonClick: (Int, Boolean) -> Unit, private val onCourseItemClick: (Int) -> Unit, - private val handleVisitorMode: () -> Unit, + private val handleVisitorMode: () -> Unit ) : RecyclerView.ViewHolder(binding.root) { fun bind(course: DiscoverMultiViewItem.RecommendCourse) { with(binding) { @@ -79,25 +79,47 @@ class DiscoverRecommendAdapter( } fun updateRecommendCourseItem( - targetIndex: Int, + publicCourseId: Int, updatedCourse: EditableDiscoverCourse ) { - currentList[targetIndex].apply { - title = updatedCourse.title - scrap = updatedCourse.scrap + currentList.forEachIndexed { index, course -> + if (course.id == publicCourseId) { + course.title = updatedCourse.title + course.scrap = updatedCourse.scrap + notifyItemChanged(index) + return + } } - notifyItemChanged(targetIndex) } - fun addRecommendCourseNextPage(items: List) { + fun addRecommendCourseNextPage(nextPageItems: List) { + Timber.d("before item count : $itemCount") + val newList = currentList.toMutableList() - newList.addAll(items) - submitList(newList) + newList.addAll(nextPageItems) + + submitList(newList) { // 비동기 작업이 끝나고 나서 호출되는 콜백 함수 + Timber.d("after item count : $itemCount") + } + } + + fun sortRecommendCourseFirstPage(firstPageItems: List) { + Timber.d("before item count : $itemCount") + + val newList = currentList.toMutableList() + newList.apply { + clear() + addAll(firstPageItems) + } + + submitList(newList) { + Timber.d("after item count : $itemCount") + } } companion object { private val diffUtil = ItemDiffCallback( - onItemsTheSame = { old, new -> old.id == new.id }, + onItemsTheSame = { old, new -> old === new }, onContentsTheSame = { old, new -> old == new } ) } diff --git a/app/src/main/java/com/runnect/runnect/presentation/discover/adapter/multiview/DiscoverMultiViewAdapter.kt b/app/src/main/java/com/runnect/runnect/presentation/discover/adapter/multiview/DiscoverMultiViewAdapter.kt index b4c3465b6..5a0c60911 100644 --- a/app/src/main/java/com/runnect/runnect/presentation/discover/adapter/multiview/DiscoverMultiViewAdapter.kt +++ b/app/src/main/java/com/runnect/runnect/presentation/discover/adapter/multiview/DiscoverMultiViewAdapter.kt @@ -5,12 +5,12 @@ import androidx.recyclerview.widget.RecyclerView import com.runnect.runnect.domain.entity.DiscoverMultiViewItem.MarathonCourse import com.runnect.runnect.domain.entity.DiscoverMultiViewItem.RecommendCourse import com.runnect.runnect.presentation.discover.model.EditableDiscoverCourse -import timber.log.Timber class DiscoverMultiViewAdapter( private val onHeartButtonClick: (Int, Boolean) -> Unit, private val onCourseItemClick: (Int) -> Unit, private val handleVisitorMode: () -> Unit, + private val onSortButtonClick: (String) -> Unit ) : RecyclerView.Adapter() { private val multiViewHolderFactory by lazy { DiscoverMultiViewHolderFactory() } private val marathonCourses = arrayListOf() @@ -23,7 +23,8 @@ class DiscoverMultiViewAdapter( viewType = viewType, onHeartButtonClick = onHeartButtonClick, onCourseItemClick = onCourseItemClick, - handleVisitorMode = handleVisitorMode + handleVisitorMode = handleVisitorMode, + onSortButtonClick = onSortButtonClick ) } @@ -34,7 +35,9 @@ class DiscoverMultiViewAdapter( } is DiscoverMultiViewHolder.RecommendCourseViewHolder -> { - holder.bind(recommendCourses) + // 내부 리사이클러뷰에서는 완전히 새로운 리스트를 참조하도록 깊은 복사 수행 + val newList = recommendCourses.map { it.copy() }.toList() + holder.bind(newList) } } } @@ -88,36 +91,21 @@ class DiscoverMultiViewAdapter( return viewTypes.indexOfFirst { viewType == it } } - fun addRecommendCourseNextPage(items: List) { - recommendCourses.addAll(items) - multiViewHolderFactory.recommendCourseAdapter.addRecommendCourseNextPage(items) + fun addRecommendCourseNextPage(nextPageItems: List) { + multiViewHolderFactory.recommendCourseAdapter.addRecommendCourseNextPage(nextPageItems) + } + + fun sortRecommendCourseFirstPage(firstPageItems: List) { + multiViewHolderFactory.recommendCourseAdapter.sortRecommendCourseFirstPage(firstPageItems) } fun updateCourseItem( publicCourseId: Int, updatedCourse: EditableDiscoverCourse ) { - val multiViewItems = marathonCourses + recommendCourses - val targetItem = multiViewItems.find { item -> - item.id == publicCourseId - } ?: return - - when (targetItem) { - is MarathonCourse -> { - val targetIndex = marathonCourses.indexOf(targetItem) - multiViewHolderFactory.marathonCourseAdapter.updateMarathonCourseItem( - targetIndex = targetIndex, - updatedCourse = updatedCourse - ) - } - - is RecommendCourse -> { - val targetIndex = recommendCourses.indexOf(targetItem) - multiViewHolderFactory.recommendCourseAdapter.updateRecommendCourseItem( - targetIndex = targetIndex, - updatedCourse = updatedCourse - ) - } + multiViewHolderFactory.apply { + marathonCourseAdapter.updateMarathonCourseItem(publicCourseId, updatedCourse) + recommendCourseAdapter.updateRecommendCourseItem(publicCourseId, updatedCourse) } } } \ No newline at end of file diff --git a/app/src/main/java/com/runnect/runnect/presentation/discover/adapter/multiview/DiscoverMultiViewHolder.kt b/app/src/main/java/com/runnect/runnect/presentation/discover/adapter/multiview/DiscoverMultiViewHolder.kt index 0364492ca..5d480ce10 100644 --- a/app/src/main/java/com/runnect/runnect/presentation/discover/adapter/multiview/DiscoverMultiViewHolder.kt +++ b/app/src/main/java/com/runnect/runnect/presentation/discover/adapter/multiview/DiscoverMultiViewHolder.kt @@ -1,18 +1,22 @@ package com.runnect.runnect.presentation.discover.adapter.multiview +import android.content.Context +import android.graphics.Typeface +import android.widget.TextView import androidx.databinding.ViewDataBinding import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.RecyclerView +import com.runnect.runnect.R import com.runnect.runnect.databinding.ItemDiscoverMultiviewMarathonBinding import com.runnect.runnect.databinding.ItemDiscoverMultiviewRecommendBinding import com.runnect.runnect.domain.entity.DiscoverMultiViewItem.MarathonCourse import com.runnect.runnect.domain.entity.DiscoverMultiViewItem.RecommendCourse import com.runnect.runnect.presentation.discover.adapter.DiscoverMarathonAdapter import com.runnect.runnect.presentation.discover.adapter.DiscoverRecommendAdapter -import com.runnect.runnect.presentation.discover.model.EditableDiscoverCourse import com.runnect.runnect.util.custom.deco.DiscoverMarathonItemDecoration import com.runnect.runnect.util.custom.deco.DiscoverRecommendItemDecoration -import com.runnect.runnect.util.custom.deco.GridSpacingItemDecoration +import com.runnect.runnect.util.extension.colorOf +import com.runnect.runnect.util.extension.fontOf import timber.log.Timber sealed class DiscoverMultiViewHolder(binding: ViewDataBinding) : @@ -65,6 +69,7 @@ sealed class DiscoverMultiViewHolder(binding: ViewDataBinding) : onHeartButtonClick: (Int, Boolean) -> Unit, onCourseItemClick: (Int) -> Unit, handleVisitorMode: () -> Unit, + private val onSortButtonClick: (String) -> Unit ) : DiscoverMultiViewHolder(binding) { val recommendAdapter by lazy { DiscoverRecommendAdapter( @@ -75,15 +80,19 @@ sealed class DiscoverMultiViewHolder(binding: ViewDataBinding) : } fun bind(courses: List) { - Timber.d("추천 코스 리스트 크기: ${courses.size}") initRecommendRecyclerView(courses) + initSortButtonTextStyle() + initSortButtonClickListener() } private fun initRecommendRecyclerView(courses: List) { binding.rvDiscoverRecommend.apply { layoutManager = GridLayoutManager(context, 2) adapter = recommendAdapter.apply { - submitList(courses) + Timber.e("refresh before item count: ${itemCount}") + submitList(courses) { + Timber.e("refresh after item count: ${itemCount}") + } } addItemDecorationOnlyOnce(this) } @@ -103,5 +112,59 @@ sealed class DiscoverMultiViewHolder(binding: ViewDataBinding) : ) } } + + private fun initSortButtonTextStyle() { + binding.tvDiscoverRecommendSortByDate.apply { + activateTextStyle(view = this, context = this.context) + } + + binding.tvDiscoverRecommendSortByScrap.apply { + deactivateTextStyle(view = this, context = this.context) + } + } + + private fun initSortButtonClickListener() { + initSortByDateClickListener() + initSortByScrapClickListener() + } + + private fun initSortByDateClickListener() { + binding.tvDiscoverRecommendSortByDate.setOnClickListener { + val context = it.context ?: return@setOnClickListener + activateTextStyle(view = it as TextView, context = context) + deactivateTextStyle( + view = binding.tvDiscoverRecommendSortByScrap, + context = context + ) + onSortButtonClick.invoke(SORT_BY_DATE) + } + } + + private fun initSortByScrapClickListener() { + binding.tvDiscoverRecommendSortByScrap.setOnClickListener { + val context = it.context ?: return@setOnClickListener + activateTextStyle(view = it as TextView, context = context) + deactivateTextStyle( + view = binding.tvDiscoverRecommendSortByDate, + context = context + ) + onSortButtonClick.invoke(SORT_BY_SCRAP) + } + } + + private fun activateTextStyle(view: TextView, context: Context) { + view.setTextColor(context.colorOf(R.color.M1)) + view.typeface = context.fontOf(R.font.pretendard_semibold, Typeface.NORMAL) + } + + private fun deactivateTextStyle(view: TextView, context: Context) { + view.setTextColor(context.colorOf(R.color.G2)) + view.typeface = context.fontOf(R.font.pretendard_regular, Typeface.NORMAL) + } + + companion object { + private const val SORT_BY_DATE = "date" + private const val SORT_BY_SCRAP = "scrap" + } } } diff --git a/app/src/main/java/com/runnect/runnect/presentation/discover/adapter/multiview/DiscoverMultiViewHolderFactory.kt b/app/src/main/java/com/runnect/runnect/presentation/discover/adapter/multiview/DiscoverMultiViewHolderFactory.kt index 1eb5667aa..60fb681bf 100644 --- a/app/src/main/java/com/runnect/runnect/presentation/discover/adapter/multiview/DiscoverMultiViewHolderFactory.kt +++ b/app/src/main/java/com/runnect/runnect/presentation/discover/adapter/multiview/DiscoverMultiViewHolderFactory.kt @@ -16,6 +16,7 @@ class DiscoverMultiViewHolderFactory { onHeartButtonClick: (Int, Boolean) -> Unit, onCourseItemClick: (Int) -> Unit, handleVisitorMode: () -> Unit, + onSortButtonClick: (String) -> Unit ): DiscoverMultiViewHolder { when (viewType) { DiscoverMultiViewType.MARATHON.ordinal -> { @@ -34,7 +35,8 @@ class DiscoverMultiViewHolderFactory { binding = parent.getViewDataBinding(layoutRes = R.layout.item_discover_multiview_recommend), onHeartButtonClick = onHeartButtonClick, onCourseItemClick = onCourseItemClick, - handleVisitorMode = handleVisitorMode + handleVisitorMode = handleVisitorMode, + onSortButtonClick = onSortButtonClick ) recommendCourseAdapter = viewHolder.recommendAdapter return viewHolder diff --git a/app/src/main/java/com/runnect/runnect/util/extension/ContextExt.kt b/app/src/main/java/com/runnect/runnect/util/extension/ContextExt.kt index e7f491a14..cafff350e 100644 --- a/app/src/main/java/com/runnect/runnect/util/extension/ContextExt.kt +++ b/app/src/main/java/com/runnect/runnect/util/extension/ContextExt.kt @@ -4,6 +4,7 @@ import android.app.AlertDialog import android.content.Context import android.content.Intent import android.graphics.Color +import android.graphics.Typeface import android.graphics.drawable.ColorDrawable import android.net.Uri import android.view.Gravity @@ -13,10 +14,13 @@ import android.view.inputmethod.InputMethodManager import android.widget.Toast import androidx.annotation.ColorRes import androidx.annotation.DrawableRes +import androidx.annotation.FontRes +import androidx.annotation.StyleRes import androidx.appcompat.widget.AppCompatButton import androidx.appcompat.widget.LinearLayoutCompat import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.core.content.ContextCompat +import androidx.core.content.res.ResourcesCompat import androidx.fragment.app.Fragment import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.snackbar.Snackbar @@ -182,3 +186,6 @@ fun Context.showSnackbar(anchorView: View, message: String, @GravityFlag gravity fun Context.colorOf(@ColorRes resId: Int) = ContextCompat.getColor(this, resId) fun Context.drawableOf(@DrawableRes resId: Int) = ContextCompat.getDrawable(this, resId) + +fun Context.fontOf(@FontRes resId: Int, @StyleRes style: Int): Typeface = + Typeface.create(ResourcesCompat.getFont(this, resId), style) \ No newline at end of file