Skip to content

Commit

Permalink
Merge pull request #303 from TeamBeMyPlan/feature/302
Browse files Browse the repository at this point in the history
[FEAT] #302 스크랩 탭 무한 스크롤 구현
  • Loading branch information
hansh0101 authored Jun 20, 2022
2 parents c0cab89 + 7792094 commit 4b8307f
Show file tree
Hide file tree
Showing 9 changed files with 150 additions and 70 deletions.
21 changes: 19 additions & 2 deletions app/src/main/java/co/kr/bemyplan/data/api/ScrapListService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,33 @@ package co.kr.bemyplan.data.api
import co.kr.bemyplan.data.entity.main.scrap.ResponseEmptyScrapList
import co.kr.bemyplan.data.entity.main.scrap.ResponseScrapList
import retrofit2.http.GET
import retrofit2.http.Path
import retrofit2.http.Query

interface ScrapListService {
@GET("v1/plan/bookmark")
suspend fun getScrapList(
suspend fun fetchDefaultScrapList(
@Query("size") size: Int
): ResponseScrapList

@GET("v1/plan/bookmark")
suspend fun fetchQueryScrapList(
@Query("size") page: Int,
@Query("sort", encoded = true) sort: String
): ResponseScrapList

@GET("v1/post/random")
suspend fun getEmptyScrapList(): ResponseEmptyScrapList

@GET("v1/plan/bookmark")
suspend fun fetchDefaultMoreScrapList(
@Query("size") size: Int,
@Query("lastScrapId") lastScrapId: Int
): ResponseScrapList

@GET("v1/plan/bookmark")
suspend fun fetchQueryMoreScrapList(
@Query("size") size: Int,
@Query("lastScrapId") lastScrapId: Int,
@Query("sort", encoded = true) sort: String
): ResponseScrapList
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,39 @@ class ScrapListRepositoryImpl @Inject constructor(
private val service: ScrapListService,
private val coroutineDispatcher: CoroutineDispatcher
) : ScrapListRepository {
override suspend fun getScrapList(
override suspend fun fetchDefaultScrapList(): PlanList =
withContext(coroutineDispatcher) {
service.fetchDefaultScrapList(10).data
}

override suspend fun fetchQueryScrapList(
sort: String
): PlanList {
return withContext(coroutineDispatcher) {
service.getScrapList(
service.fetchQueryScrapList(
10,
sort
).data
}
}

override suspend fun getEmptyScrapList(): ResponseEmptyScrapList {
return withContext(Dispatchers.IO) {
override suspend fun fetchEmptyScrapList(): ResponseEmptyScrapList {
return withContext(coroutineDispatcher) {
service.getEmptyScrapList()
}
}

override suspend fun fetchDefaultMoreScrapList(size: Int, lastScrapId: Int): PlanList =
withContext(coroutineDispatcher) {
service.fetchDefaultMoreScrapList(size, lastScrapId).data
}

override suspend fun fetchQueryMoreScrapList(
size: Int,
lastScrapId: Int,
sort: String
): PlanList =
withContext(coroutineDispatcher) {
service.fetchQueryMoreScrapList(size, lastScrapId, sort).data
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,13 @@ import co.kr.bemyplan.data.entity.main.scrap.ResponseEmptyScrapList
import co.kr.bemyplan.domain.model.list.PlanList

interface ScrapListRepository {
suspend fun getScrapList(
sort: String
): PlanList
suspend fun fetchDefaultScrapList(): PlanList

suspend fun getEmptyScrapList(): ResponseEmptyScrapList
suspend fun fetchQueryScrapList(sort: String): PlanList

suspend fun fetchEmptyScrapList(): ResponseEmptyScrapList

suspend fun fetchDefaultMoreScrapList(size: Int, lastScrapId: Int): PlanList

suspend fun fetchQueryMoreScrapList(size: Int, lastScrapId: Int, sort: String): PlanList
}
36 changes: 20 additions & 16 deletions app/src/main/java/co/kr/bemyplan/ui/list/ListActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import co.kr.bemyplan.R
import co.kr.bemyplan.data.firebase.FirebaseAnalyticsProvider
Expand Down Expand Up @@ -119,22 +120,25 @@ class ListActivity : AppCompatActivity() {
rvLinearContent.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
if (!rvLinearContent.canScrollVertically(1)) {
when (from) {
"new" -> {
viewModel.fetchMoreLatestList()
}
"suggest" -> {
viewModel.fetchMoreSuggestList()
}
"location" -> {
viewModel.fetchMoreLocationList(
region,
sortViewModel.sort.value.toString()
)
}
"user" -> {
viewModel.fetchMoreUserPlanList(sortViewModel.sort.value.toString())
if(dy > 0) {
if (!rvLinearContent.canScrollVertically(1) &&
(recyclerView.layoutManager as LinearLayoutManager).findLastVisibleItemPosition() == listAdapter.itemCount - 1) {
when (from) {
"new" -> {
viewModel.fetchMoreLatestList()
}
"suggest" -> {
viewModel.fetchMoreSuggestList()
}
"location" -> {
viewModel.fetchMoreLocationList(
region,
sortViewModel.sort.value.toString()
)
}
"user" -> {
viewModel.fetchMoreUserPlanList(sortViewModel.sort.value.toString())
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,41 +15,39 @@ import co.kr.bemyplan.util.ToastMessage.shortToast
import co.kr.bemyplan.util.clipTo
import javax.inject.Inject

class LocationAdapter(val itemClick: (LocationData) -> Unit, val myContext: Context) :
class LocationAdapter(
private val itemClick: (LocationData) -> Unit,
private val myContext: Context,
private val logEvent: (Boolean, String) -> Unit
) :
ListAdapter<LocationData, LocationAdapter.LocationViewHolder>(LocationComparator()) {
@Inject
lateinit var firebaseAnalyticsProvider: FirebaseAnalyticsProvider

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): LocationViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding = ItemLocationBinding.inflate(layoutInflater, parent, false)
return LocationViewHolder(binding)
return LocationViewHolder(binding, itemClick, myContext, logEvent)
}

override fun onBindViewHolder(holder: LocationViewHolder, position: Int) {
holder.onBind(getItem(position))
}

inner class LocationViewHolder(private val binding: ItemLocationBinding) :
class LocationViewHolder(
private val binding: ItemLocationBinding,
private val itemClick: (LocationData) -> Unit,
private val myContext: Context,
private val logEvent: (Boolean, String) -> Unit
) :
RecyclerView.ViewHolder(binding.root) {
fun onBind(data: LocationData) {
binding.locationItem = data
clipTo(binding.ivLocation, data.thumbnailUrl)
binding.root.setOnClickListener {
if (binding.ivLock.visibility == View.GONE) {
firebaseAnalyticsProvider.firebaseAnalytics.logEvent(
"clickOpenedTravelSpot",
Bundle().apply {
putString("spot", data.name)
})
itemClick(data)
logEvent(true, data.name)
} else {
firebaseAnalyticsProvider.firebaseAnalytics.logEvent(
"clickClosedTravelSpot",
Bundle().apply {
putString("spot", data.name)
})
myContext.shortToast("추후에 오픈될 예정입니다")
logEvent(false, data.name)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,22 @@ import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import co.kr.bemyplan.R
import co.kr.bemyplan.data.firebase.FirebaseAnalyticsProvider
import co.kr.bemyplan.databinding.FragmentLocationBinding
import co.kr.bemyplan.ui.list.ListActivity
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject

@AndroidEntryPoint
class LocationFragment : Fragment() {

private var _binding: FragmentLocationBinding? = null
private val binding get() = _binding ?: error("Binding이 초기화 되지 않았습니다.")
private lateinit var locationAdapter: LocationAdapter
private val locationViewModel: LocationViewModel by viewModels()

@Inject
lateinit var firebaseAnalyticsProvider: FirebaseAnalyticsProvider

override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
Expand All @@ -34,13 +38,27 @@ class LocationFragment : Fragment() {
}

private fun initAdapter() {
locationAdapter = LocationAdapter(itemClick = {
locationAdapter = LocationAdapter({
val intent = Intent(requireContext(), ListActivity::class.java)
intent.putExtra("from", "location")
intent.putExtra("region", it.region)
intent.putExtra("locationName", it.name)
startActivity(intent)
}, myContext = requireContext())
}, myContext = requireContext(), {opened, name ->
if(opened) {
firebaseAnalyticsProvider.firebaseAnalytics.logEvent(
"clickOpenedTravelSpot",
Bundle().apply {
putString("spot", name)
})
} else {
firebaseAnalyticsProvider.firebaseAnalytics.logEvent(
"clickClosedTravelSpot",
Bundle().apply {
putString("spot", name)
})
}
})
binding.rvLocation.adapter = locationAdapter
binding.rvLocation.addItemDecoration(
VerticalItemDecorator(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import co.kr.bemyplan.R
import co.kr.bemyplan.data.firebase.FirebaseAnalyticsProvider
import co.kr.bemyplan.databinding.FragmentNotEmptyScrapBinding
Expand Down Expand Up @@ -102,6 +104,18 @@ class NotEmptyScrapFragment : Fragment() {
false -> viewModel.postScrap(planId)
}
})
binding.rvContent.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
if (dy > 0) {
if (!binding.rvContent.canScrollVertically(1) &&
(recyclerView.layoutManager as GridLayoutManager).findLastVisibleItemPosition() == scrapAdapter.itemCount - 1
) {
viewModel.getMoreScrapList(requireNotNull(sortViewModel.sort.value))
}
}
}
})
binding.rvContent.adapter = scrapAdapter
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,11 @@ class ScrapViewModel @Inject constructor(
fun getScrapList(sort: String) {
viewModelScope.launch {
kotlin.runCatching {
scrapListRepository.getScrapList(sort)
if (sort == "id,desc") {
scrapListRepository.fetchDefaultScrapList()
} else {
scrapListRepository.fetchQueryScrapList(sort)
}
}.onSuccess { response ->
_scrapList.value = response.contents
lastPlanId = response.nextCursor
Expand All @@ -50,7 +54,7 @@ class ScrapViewModel @Inject constructor(
fun getEmptyScrapList() {
viewModelScope.launch {
kotlin.runCatching {
scrapListRepository.getEmptyScrapList()
scrapListRepository.fetchEmptyScrapList()
}.onSuccess {
if (_emptyScrapList.value != it.data) {
_emptyScrapList.value = it.data
Expand All @@ -61,6 +65,23 @@ class ScrapViewModel @Inject constructor(
}
}

fun getMoreScrapList(sort: String) {
viewModelScope.launch {
runCatching {
if (sort == "id,desc") {
scrapListRepository.fetchDefaultMoreScrapList(size, lastPlanId)
} else {
scrapListRepository.fetchQueryMoreScrapList(size, lastPlanId, sort)
}
}.onSuccess {
_scrapList.value = scrapList.value?.toMutableList()?.apply { addAll(it.contents) }
lastPlanId = it.nextCursor
}.onFailure {
Timber.e(it)
}
}
}

fun postScrap(planId: Int) {
viewModelScope.launch {
kotlin.runCatching {
Expand Down
31 changes: 8 additions & 23 deletions app/src/main/res/layout/fragment_not_empty_scrap.xml
Original file line number Diff line number Diff line change
Expand Up @@ -33,33 +33,18 @@

</androidx.constraintlayout.widget.ConstraintLayout>

<androidx.core.widget.NestedScrollView
android:id="@+id/nsv"
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_content"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="2dp"
android:layout_marginBottom="20dp"
android:overScrollMode="never"
android:paddingHorizontal="18dp"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/layout_order">

<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">

<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:layout_marginBottom="20dp"
android:overScrollMode="never"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:spanCount="2" />

</androidx.constraintlayout.widget.ConstraintLayout>

</androidx.core.widget.NestedScrollView>
app:layout_constraintTop_toBottomOf="@id/layout_order"
app:spanCount="2" />

</androidx.constraintlayout.widget.ConstraintLayout>

Expand Down

0 comments on commit 4b8307f

Please sign in to comment.