From 79808d50ce56502dc56d92c76a42b7b1539bac28 Mon Sep 17 00:00:00 2001 From: jung0115 Date: Wed, 3 Jul 2024 21:15:56 +0900 Subject: [PATCH 1/4] =?UTF-8?q?KKUMI-30=20[FEAT]=20#34=20-=20=ED=8F=AC?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EB=AC=B4=ED=95=9C=20=EC=8A=A4=ED=81=AC?= =?UTF-8?q?=EB=A1=A4=20>=20=EC=8A=A4=ED=81=AC=EB=A1=A4=EC=9D=B4=20?= =?UTF-8?q?=EC=B5=9C=ED=95=98=EB=8B=A8=EC=9C=BC=EB=A1=9C=20=EB=82=B4?= =?UTF-8?q?=EB=A0=A4=EA=B0=80=EB=A9=B4=20=EB=8B=A4=EC=9D=8C=20=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=84=B0=20=EB=B0=9B=EC=95=84=EC=98=A4=EA=B8=B0=20+?= =?UTF-8?q?=20=EB=B0=9B=EC=95=84=EC=98=AC=20=EB=8D=B0=EC=9D=B4=ED=84=B0?= =?UTF-8?q?=EA=B0=80=20=EC=97=86=EB=8B=A4=EB=A9=B4=20=EA=B7=B8=20=EB=8B=A4?= =?UTF-8?q?=EC=9D=8C=EB=B6=80=ED=84=B0=EB=8A=94=20=EC=8A=A4=ED=81=AC?= =?UTF-8?q?=EB=A1=A4=EC=9D=B4=20=EB=82=B4=EB=A0=A4=EA=B0=80=EB=8F=84=20?= =?UTF-8?q?=EC=9A=94=EC=B2=AD=20X?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mykkumi/feature/home/HomeFragment.kt | 41 ++++++++++++++++--- .../feature/home/post/PostListAdapter.kt | 12 +++--- .../feature/home/post/PostViewModel.kt | 34 +++++++++++---- .../src/main/res/layout/fragment_home.xml | 1 + 4 files changed, 69 insertions(+), 19 deletions(-) diff --git a/feature/home/src/main/java/com/swmarastro/mykkumi/feature/home/HomeFragment.kt b/feature/home/src/main/java/com/swmarastro/mykkumi/feature/home/HomeFragment.kt index 5401b009..17b24fe8 100644 --- a/feature/home/src/main/java/com/swmarastro/mykkumi/feature/home/HomeFragment.kt +++ b/feature/home/src/main/java/com/swmarastro/mykkumi/feature/home/HomeFragment.kt @@ -1,14 +1,18 @@ package com.swmarastro.mykkumi.feature.home import android.os.Bundle +import android.util.Log import android.view.View +import android.widget.ScrollView import androidx.fragment.app.viewModels import androidx.lifecycle.lifecycleScope import androidx.navigation.findNavController import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView import androidx.viewpager2.widget.ViewPager2 import com.swmarastro.mykkumi.common_ui.base.BaseFragment import com.swmarastro.mykkumi.domain.entity.BannerListVO +import com.swmarastro.mykkumi.domain.entity.HomePostItemVO import com.swmarastro.mykkumi.domain.entity.HomePostListVO import com.swmarastro.mykkumi.feature.home.banner.HomeBannerViewPagerAdapter import com.swmarastro.mykkumi.feature.home.databinding.FragmentHomeBinding @@ -18,6 +22,7 @@ import com.swmarastro.mykkumi.feature.home.post.PostViewModel import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach +import okhttp3.internal.notifyAll import java.util.Timer import java.util.TimerTask @@ -29,6 +34,7 @@ class HomeFragment : BaseFragment(R.layout.fragment_home) { private lateinit var bannerAdapter: HomeBannerViewPagerAdapter private lateinit var postListAdapter: PostListAdapter + private lateinit var timer: Timer override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -116,21 +122,36 @@ class HomeFragment : BaseFragment(R.layout.fragment_home) { } // 포스트 리스트 recyclerview - private fun initPostRecyclerView(posts: HomePostListVO) { - postListAdapter = PostListAdapter( - posts.posts.toMutableList() - ) + private fun initPostRecyclerView(posts: MutableList) { + //postList = posts + postListAdapter = PostListAdapter() binding.recyclerviewPostList.layoutManager = LinearLayoutManager( context, LinearLayoutManager.VERTICAL, false ) binding.recyclerviewPostList.adapter = postListAdapter + postListAdapter.postList = posts + + // 스크롤 다 내려가면 다음 데이터 받아오기 + binding.scrollHomeBannerNPost.setOnScrollChangeListener { v, scrollX, scrollY, oldScrollX, oldScrollY -> + if (v is ScrollView) { + // 스크롤이 최하단까지 내려갔는지 확인 + val view = v.getChildAt(v.childCount - 1) + val diff = view.bottom - (v.height + v.scrollY) + if (diff == 0) { + if(!postViewModel.getIsPostEnd()) { + postListAdapter.postList.add(HomePostItemVO()) + setNextPostList() + } + } + } + } } // 포스트 내용 세팅 private fun setPostList() { - postViewModel.setPostList(null, null) + postViewModel.setPostList(false) postViewModel.postListUiState .onEach { initPostRecyclerView(it) @@ -138,6 +159,16 @@ class HomeFragment : BaseFragment(R.layout.fragment_home) { .launchIn(lifecycleScope) } + // 포스트 무한 스크롤 -> 스크롤 최하단 도달 시 다음 데이터 요청 + private fun setNextPostList() { + postViewModel.setPostList(true) + postViewModel.postListUiState + .onEach { + postListAdapter.postList = it + } + .launchIn(lifecycleScope) + } + override fun onDestroyView() { _binding = null timer.cancel() diff --git a/feature/home/src/main/java/com/swmarastro/mykkumi/feature/home/post/PostListAdapter.kt b/feature/home/src/main/java/com/swmarastro/mykkumi/feature/home/post/PostListAdapter.kt index efd62fe2..20732a89 100644 --- a/feature/home/src/main/java/com/swmarastro/mykkumi/feature/home/post/PostListAdapter.kt +++ b/feature/home/src/main/java/com/swmarastro/mykkumi/feature/home/post/PostListAdapter.kt @@ -22,9 +22,10 @@ import com.swmarastro.mykkumi.common_ui.R import com.swmarastro.mykkumi.feature.home.databinding.ItemPostRecyclerviewBinding class PostListAdapter ( - private var postList: MutableList ) : RecyclerView.Adapter(){ + var postList = mutableListOf() + private val postContentMaxLine = 2 private val postContentShowMoreText = "...더보기" @@ -93,7 +94,7 @@ class PostListAdapter ( // 글 내용 -------------------------------------------------------------------------------------- - val testContent = "#템빨 즉 아이템의 비중이 크거나 #다양한 취미를 즐기는 사람들이 모인 커뮤니티를 제공한다. #취미 생활에 관련된 팁을 주고 받거나, #새로운 제품과 스타일을 시도해볼 수 있다." + // val testContent = "#템빨 즉 아이템의 비중이 크거나 #다양한 취미를 즐기는 사람들이 모인 커뮤니티를 제공한다. #취미 생활에 관련된 팁을 주고 받거나, #새로운 제품과 스타일을 시도해볼 수 있다." // 닉네임 클릭 이벤트 val clickableNicknameSpan = object : ClickableSpan() { @@ -124,7 +125,7 @@ class PostListAdapter ( append(" ") // 글 내용 중 해시태그 색상 강조 - val words = testContent.split(" ") + val words = item.content.split(" ") var startIndex = item.writer.nickname.length + 2 // 닉네임 + 공백 다음에 시작 for (word in words) { if (word.startsWith("#")) { @@ -145,9 +146,6 @@ class PostListAdapter ( append(" ") startIndex += word.length + 1 } - - // 글 내용 - //append(" " + testContent) // item.content } binding.textPostNicknameContent.text = originalSpannableStringBuilder @@ -173,7 +171,7 @@ class PostListAdapter ( // 숨기기 val availableTextLength = binding.textPostNicknameContent.layout.getLineEnd(postContentMaxLine - 1) - item.writer.nickname.length - postContentShowMoreText.length - 2 - val hideContent = testContent.substring(0, availableTextLength) + val hideContent = item.content.substring(0, availableTextLength) // 글 내용 중 해시태그 색상 강조 val words = hideContent.split(" ") diff --git a/feature/home/src/main/java/com/swmarastro/mykkumi/feature/home/post/PostViewModel.kt b/feature/home/src/main/java/com/swmarastro/mykkumi/feature/home/post/PostViewModel.kt index 22c1dac8..c0a38ede 100644 --- a/feature/home/src/main/java/com/swmarastro/mykkumi/feature/home/post/PostViewModel.kt +++ b/feature/home/src/main/java/com/swmarastro/mykkumi/feature/home/post/PostViewModel.kt @@ -1,8 +1,10 @@ package com.swmarastro.mykkumi.feature.home.post +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.swmarastro.mykkumi.domain.entity.HomePostListVO +import com.swmarastro.mykkumi.domain.entity.HomePostItemVO import com.swmarastro.mykkumi.domain.usecase.GetHomePostListUseCase import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Dispatchers @@ -19,20 +21,38 @@ class PostViewModel @Inject constructor( ) : ViewModel() { // 포스트 리스트 - private val _postListUiState = MutableStateFlow(HomePostListVO()) - val postListUiState: StateFlow get() = _postListUiState + private val _postListUiState = MutableStateFlow>(mutableListOf()) + val postListUiState: StateFlow> get() = _postListUiState + + // 포스트 리스트 cursor, limit + private val postLimit: Int? = null + private var postCursor: String? = null + + // 포스트 끝 도달 + private var isPostEnd = false // 포스트 리스트 - fun setPostList(cursor: String?, limit: Int?) { + fun setPostList(isCursor: Boolean) { viewModelScope.launch { try { val homePostList = withContext(Dispatchers.IO) { - getHomePostListUseCase(cursor, limit) + if(isCursor) getHomePostListUseCase(postCursor, postLimit) + else getHomePostListUseCase(null, postLimit) + } + if(homePostList.posts.size == 0) isPostEnd = true + else { + if (isCursor) _postListUiState.value.addAll(homePostList.posts) + else _postListUiState.value = homePostList.posts.toMutableList() + postCursor = homePostList.cursor } - _postListUiState.value = homePostList } catch (e: Exception) { - _postListUiState.value = HomePostListVO() + _postListUiState.value = mutableListOf() } } } + + // 더 조회할 포스트가 있는지 + fun getIsPostEnd() : Boolean { + return isPostEnd + } } \ No newline at end of file diff --git a/feature/home/src/main/res/layout/fragment_home.xml b/feature/home/src/main/res/layout/fragment_home.xml index b43fea9d..e2e94e09 100644 --- a/feature/home/src/main/res/layout/fragment_home.xml +++ b/feature/home/src/main/res/layout/fragment_home.xml @@ -101,6 +101,7 @@ From cef7f38686e4e4b5800e7b21258dd996434018e4 Mon Sep 17 00:00:00 2001 From: jung0115 Date: Wed, 3 Jul 2024 21:50:06 +0900 Subject: [PATCH 2/4] =?UTF-8?q?[REFACTOR]=20-=20=EB=B0=B0=EB=84=88=20?= =?UTF-8?q?=EB=A6=AC=EC=8A=A4=ED=8A=B8=EC=9D=98=20=EC=95=84=EC=9D=B4?= =?UTF-8?q?=ED=85=9C=20DTO,=20VO=EC=99=80=20=EB=B0=B0=EB=84=88=20=EC=84=B8?= =?UTF-8?q?=EB=B6=80=20DTO,=20VO=EB=A5=BC=20=EB=94=B0=EB=A1=9C=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC=20/=20response=20=ED=98=95=ED=83=9C=EB=8A=94=20?= =?UTF-8?q?=EB=98=91=EA=B0=99=EC=A7=80=EB=A7=8C=20=EB=82=B4=EC=9A=A9?= =?UTF-8?q?=EC=9D=80=20=EB=8B=A4=EB=A5=B4=EA=B8=B0=20=EB=95=8C=EB=AC=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/res/layout/item_loading.xml | 22 +++++++++++++++++++ .../data/datasource/BannerDataSource.kt | 3 ++- .../{BannerListDTO.kt => BannerDTO.kt} | 14 ++++++++++++ .../data/repository/BannerRepositoryImpl.kt | 4 ++-- .../mykkumi/domain/entity/BannerVO.kt | 5 +++++ .../domain/repository/BannerRepository.kt | 4 ++-- .../domain/usecase/GetBannerDetailUseCase.kt | 4 ++-- .../home/banner/HomeBannerViewModel.kt | 7 +++--- .../feature/home/post/PostViewModel.kt | 2 -- 9 files changed, 53 insertions(+), 12 deletions(-) create mode 100644 core/common-ui/src/main/res/layout/item_loading.xml rename core/data/src/main/java/com/swmarastro/mykkumi/data/dto/response/{BannerListDTO.kt => BannerDTO.kt} (69%) diff --git a/core/common-ui/src/main/res/layout/item_loading.xml b/core/common-ui/src/main/res/layout/item_loading.xml new file mode 100644 index 00000000..22aeab24 --- /dev/null +++ b/core/common-ui/src/main/res/layout/item_loading.xml @@ -0,0 +1,22 @@ + + + + + + + + + + \ No newline at end of file diff --git a/core/data/src/main/java/com/swmarastro/mykkumi/data/datasource/BannerDataSource.kt b/core/data/src/main/java/com/swmarastro/mykkumi/data/datasource/BannerDataSource.kt index ba1eab3a..610fcd3d 100644 --- a/core/data/src/main/java/com/swmarastro/mykkumi/data/datasource/BannerDataSource.kt +++ b/core/data/src/main/java/com/swmarastro/mykkumi/data/datasource/BannerDataSource.kt @@ -1,5 +1,6 @@ package com.swmarastro.mykkumi.data.datasource +import com.swmarastro.mykkumi.data.dto.response.BannerDetailDTO import com.swmarastro.mykkumi.data.dto.response.BannerListDTO import retrofit2.http.GET import retrofit2.http.Path @@ -12,5 +13,5 @@ interface BannerDataSource { @GET("api/v1/banners/{id}") suspend fun getBannerDetail( @Path("id") id: Int - ) : BannerListDTO.BannerItemDTO// Response + ) : BannerDetailDTO// Response } \ No newline at end of file diff --git a/core/data/src/main/java/com/swmarastro/mykkumi/data/dto/response/BannerListDTO.kt b/core/data/src/main/java/com/swmarastro/mykkumi/data/dto/response/BannerDTO.kt similarity index 69% rename from core/data/src/main/java/com/swmarastro/mykkumi/data/dto/response/BannerListDTO.kt rename to core/data/src/main/java/com/swmarastro/mykkumi/data/dto/response/BannerDTO.kt index e8f8391c..362dea8f 100644 --- a/core/data/src/main/java/com/swmarastro/mykkumi/data/dto/response/BannerListDTO.kt +++ b/core/data/src/main/java/com/swmarastro/mykkumi/data/dto/response/BannerDTO.kt @@ -1,6 +1,7 @@ package com.swmarastro.mykkumi.data.dto.response import com.google.gson.annotations.SerializedName +import com.swmarastro.mykkumi.domain.entity.BannerDetailVO import com.swmarastro.mykkumi.domain.entity.BannerItemVO import com.swmarastro.mykkumi.domain.entity.BannerListVO @@ -24,5 +25,18 @@ data class BannerListDTO( imageUrl = imageUrl ) } +} + +data class BannerDetailDTO( + @SerializedName("id") + val id: Int, + @SerializedName("imageUrl") + val imageUrl: String +) { + fun toEntity(): BannerDetailVO = BannerDetailVO( + id = id, + imageUrl = imageUrl + ) } + diff --git a/core/data/src/main/java/com/swmarastro/mykkumi/data/repository/BannerRepositoryImpl.kt b/core/data/src/main/java/com/swmarastro/mykkumi/data/repository/BannerRepositoryImpl.kt index 4e51f210..0a63324a 100644 --- a/core/data/src/main/java/com/swmarastro/mykkumi/data/repository/BannerRepositoryImpl.kt +++ b/core/data/src/main/java/com/swmarastro/mykkumi/data/repository/BannerRepositoryImpl.kt @@ -1,7 +1,7 @@ package com.swmarastro.mykkumi.data.repository import com.swmarastro.mykkumi.data.datasource.BannerDataSource -import com.swmarastro.mykkumi.domain.entity.BannerItemVO +import com.swmarastro.mykkumi.domain.entity.BannerDetailVO import com.swmarastro.mykkumi.domain.entity.BannerListVO import com.swmarastro.mykkumi.domain.repository.BannerRepository import javax.inject.Inject @@ -25,7 +25,7 @@ class BannerRepositoryImpl @Inject constructor( } */ - override suspend fun getBannerDetail(bannerId: Int): BannerItemVO { + override suspend fun getBannerDetail(bannerId: Int): BannerDetailVO { return bannerDataSource.getBannerDetail(bannerId).toEntity() } } \ No newline at end of file diff --git a/core/domain/src/main/java/com/swmarastro/mykkumi/domain/entity/BannerVO.kt b/core/domain/src/main/java/com/swmarastro/mykkumi/domain/entity/BannerVO.kt index 19b674ba..36ef5810 100644 --- a/core/domain/src/main/java/com/swmarastro/mykkumi/domain/entity/BannerVO.kt +++ b/core/domain/src/main/java/com/swmarastro/mykkumi/domain/entity/BannerVO.kt @@ -9,3 +9,8 @@ data class BannerItemVO( val id: Int = -1, val imageUrl: String = "" ) + +data class BannerDetailVO( + val id: Int = -1, + val imageUrl: String = "" +) \ No newline at end of file diff --git a/core/domain/src/main/java/com/swmarastro/mykkumi/domain/repository/BannerRepository.kt b/core/domain/src/main/java/com/swmarastro/mykkumi/domain/repository/BannerRepository.kt index 38e854bc..0092dd35 100644 --- a/core/domain/src/main/java/com/swmarastro/mykkumi/domain/repository/BannerRepository.kt +++ b/core/domain/src/main/java/com/swmarastro/mykkumi/domain/repository/BannerRepository.kt @@ -1,10 +1,10 @@ package com.swmarastro.mykkumi.domain.repository +import com.swmarastro.mykkumi.domain.entity.BannerDetailVO import com.swmarastro.mykkumi.domain.entity.BannerListVO -import com.swmarastro.mykkumi.domain.entity.BannerItemVO interface BannerRepository { suspend fun getBannerList(): BannerListVO // HomeBannerListResponse - suspend fun getBannerDetail(bannerId: Int): BannerItemVO + suspend fun getBannerDetail(bannerId: Int): BannerDetailVO } \ No newline at end of file diff --git a/core/domain/src/main/java/com/swmarastro/mykkumi/domain/usecase/GetBannerDetailUseCase.kt b/core/domain/src/main/java/com/swmarastro/mykkumi/domain/usecase/GetBannerDetailUseCase.kt index 30e27139..7a92de27 100644 --- a/core/domain/src/main/java/com/swmarastro/mykkumi/domain/usecase/GetBannerDetailUseCase.kt +++ b/core/domain/src/main/java/com/swmarastro/mykkumi/domain/usecase/GetBannerDetailUseCase.kt @@ -1,6 +1,6 @@ package com.swmarastro.mykkumi.domain.usecase -import com.swmarastro.mykkumi.domain.entity.BannerItemVO +import com.swmarastro.mykkumi.domain.entity.BannerDetailVO import com.swmarastro.mykkumi.domain.repository.BannerRepository import javax.inject.Inject @@ -8,7 +8,7 @@ class GetBannerDetailUseCase @Inject constructor( private val repository: BannerRepository ){ - suspend operator fun invoke(bannerId: Int): BannerItemVO { + suspend operator fun invoke(bannerId: Int): BannerDetailVO { return repository.getBannerDetail(bannerId) } } \ No newline at end of file diff --git a/feature/home/src/main/java/com/swmarastro/mykkumi/feature/home/banner/HomeBannerViewModel.kt b/feature/home/src/main/java/com/swmarastro/mykkumi/feature/home/banner/HomeBannerViewModel.kt index 4bc08918..5685a2e7 100644 --- a/feature/home/src/main/java/com/swmarastro/mykkumi/feature/home/banner/HomeBannerViewModel.kt +++ b/feature/home/src/main/java/com/swmarastro/mykkumi/feature/home/banner/HomeBannerViewModel.kt @@ -4,6 +4,7 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.swmarastro.mykkumi.domain.entity.BannerDetailVO import com.swmarastro.mykkumi.domain.entity.BannerItemVO import com.swmarastro.mykkumi.domain.entity.BannerListVO import com.swmarastro.mykkumi.domain.usecase.GetBannerDetailUseCase @@ -27,8 +28,8 @@ class HomeBannerViewModel @Inject constructor( val bannerListUiState: StateFlow get() = _bannerListUiState // 배너 상세 - private val _bannerDetailUiState = MutableStateFlow(BannerItemVO()) - val bannerDetailUiState: StateFlow get() = _bannerDetailUiState + private val _bannerDetailUiState = MutableStateFlow(BannerDetailVO()) + val bannerDetailUiState: StateFlow get() = _bannerDetailUiState // 선택된 배너 private val _selectBannerId = MutableLiveData() @@ -62,7 +63,7 @@ class HomeBannerViewModel @Inject constructor( } _bannerDetailUiState.value = homeBannerDetail } catch (e: Exception) { - _bannerDetailUiState.value = BannerItemVO() + _bannerDetailUiState.value = BannerDetailVO() } } } diff --git a/feature/home/src/main/java/com/swmarastro/mykkumi/feature/home/post/PostViewModel.kt b/feature/home/src/main/java/com/swmarastro/mykkumi/feature/home/post/PostViewModel.kt index c0a38ede..83c7cb6e 100644 --- a/feature/home/src/main/java/com/swmarastro/mykkumi/feature/home/post/PostViewModel.kt +++ b/feature/home/src/main/java/com/swmarastro/mykkumi/feature/home/post/PostViewModel.kt @@ -1,7 +1,5 @@ package com.swmarastro.mykkumi.feature.home.post -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.swmarastro.mykkumi.domain.entity.HomePostItemVO From bda0dc8a7d8a838255565683bb281d65c0ae471d Mon Sep 17 00:00:00 2001 From: jung0115 Date: Thu, 4 Jul 2024 20:05:37 +0900 Subject: [PATCH 3/4] =?UTF-8?q?KKUMI-30=20[FEAT]=20#34=20-=20=EB=AC=B4?= =?UTF-8?q?=ED=95=9C=20=EC=8A=A4=ED=81=AC=EB=A1=A4=20=EA=B5=AC=ED=98=84=20?= =?UTF-8?q?/=20=EC=8A=A4=ED=81=AC=EB=A1=A4=EC=9D=84=20=EB=B9=A0=EB=A5=B4?= =?UTF-8?q?=EA=B2=8C=20=EB=82=B4=EB=A6=B4=20=EB=95=8C=20=EA=B0=99=EC=9D=80?= =?UTF-8?q?=20=EB=8D=B0=EC=9D=B4=ED=84=B0=EB=A5=BC=20=EC=97=AC=EB=9F=AC=20?= =?UTF-8?q?=EB=B2=88=20=EC=9A=94=EC=B2=AD=ED=95=98=EA=B2=8C=20=EB=90=98?= =?UTF-8?q?=EB=8A=94=20=EB=AC=B8=EC=A0=9C=20=EB=B0=9C=EC=83=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/res/layout/item_list_loading.xml | 14 ++++++++++++ .../src/main/res/layout/item_loading.xml | 22 ------------------- .../mykkumi/feature/home/HomeFragment.kt | 20 ++++++++--------- .../feature/home/post/PostViewModel.kt | 4 ++-- .../src/main/res/layout/fragment_home.xml | 16 ++++++++++++++ 5 files changed, 42 insertions(+), 34 deletions(-) create mode 100644 core/common-ui/src/main/res/layout/item_list_loading.xml delete mode 100644 core/common-ui/src/main/res/layout/item_loading.xml diff --git a/core/common-ui/src/main/res/layout/item_list_loading.xml b/core/common-ui/src/main/res/layout/item_list_loading.xml new file mode 100644 index 00000000..54dff6fe --- /dev/null +++ b/core/common-ui/src/main/res/layout/item_list_loading.xml @@ -0,0 +1,14 @@ + + + + + + \ No newline at end of file diff --git a/core/common-ui/src/main/res/layout/item_loading.xml b/core/common-ui/src/main/res/layout/item_loading.xml deleted file mode 100644 index 22aeab24..00000000 --- a/core/common-ui/src/main/res/layout/item_loading.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/feature/home/src/main/java/com/swmarastro/mykkumi/feature/home/HomeFragment.kt b/feature/home/src/main/java/com/swmarastro/mykkumi/feature/home/HomeFragment.kt index 17b24fe8..7448551d 100644 --- a/feature/home/src/main/java/com/swmarastro/mykkumi/feature/home/HomeFragment.kt +++ b/feature/home/src/main/java/com/swmarastro/mykkumi/feature/home/HomeFragment.kt @@ -8,12 +8,10 @@ import androidx.fragment.app.viewModels import androidx.lifecycle.lifecycleScope import androidx.navigation.findNavController import androidx.recyclerview.widget.LinearLayoutManager -import androidx.recyclerview.widget.RecyclerView import androidx.viewpager2.widget.ViewPager2 import com.swmarastro.mykkumi.common_ui.base.BaseFragment import com.swmarastro.mykkumi.domain.entity.BannerListVO import com.swmarastro.mykkumi.domain.entity.HomePostItemVO -import com.swmarastro.mykkumi.domain.entity.HomePostListVO import com.swmarastro.mykkumi.feature.home.banner.HomeBannerViewPagerAdapter import com.swmarastro.mykkumi.feature.home.databinding.FragmentHomeBinding import com.swmarastro.mykkumi.feature.home.banner.HomeBannerViewModel @@ -22,7 +20,6 @@ import com.swmarastro.mykkumi.feature.home.post.PostViewModel import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach -import okhttp3.internal.notifyAll import java.util.Timer import java.util.TimerTask @@ -36,6 +33,7 @@ class HomeFragment : BaseFragment(R.layout.fragment_home) { private lateinit var postListAdapter: PostListAdapter private lateinit var timer: Timer + private var isPostListLoading = false override fun onViewCreated(view: View, savedInstanceState: Bundle?) { binding.lifecycleOwner = this @@ -137,13 +135,14 @@ class HomeFragment : BaseFragment(R.layout.fragment_home) { binding.scrollHomeBannerNPost.setOnScrollChangeListener { v, scrollX, scrollY, oldScrollX, oldScrollY -> if (v is ScrollView) { // 스크롤이 최하단까지 내려갔는지 확인 - val view = v.getChildAt(v.childCount - 1) - val diff = view.bottom - (v.height + v.scrollY) - if (diff == 0) { - if(!postViewModel.getIsPostEnd()) { - postListAdapter.postList.add(HomePostItemVO()) - setNextPostList() - } + val scroll = v.getChildAt(v.childCount - 1) + val diff = scroll.bottom - (v.height + v.scrollY) + if(postViewModel.getIsPostEnd()) { + binding.includeListLoading.visibility = View.GONE + } + else if (diff == 0 && !postViewModel.getIsPostEnd() && !isPostListLoading) { + isPostListLoading = true // 스크롤 이벤트가 연속적으로 호출되는 것을 방지 + setNextPostList() } } } @@ -165,6 +164,7 @@ class HomeFragment : BaseFragment(R.layout.fragment_home) { postViewModel.postListUiState .onEach { postListAdapter.postList = it + isPostListLoading = false } .launchIn(lifecycleScope) } diff --git a/feature/home/src/main/java/com/swmarastro/mykkumi/feature/home/post/PostViewModel.kt b/feature/home/src/main/java/com/swmarastro/mykkumi/feature/home/post/PostViewModel.kt index 83c7cb6e..80580223 100644 --- a/feature/home/src/main/java/com/swmarastro/mykkumi/feature/home/post/PostViewModel.kt +++ b/feature/home/src/main/java/com/swmarastro/mykkumi/feature/home/post/PostViewModel.kt @@ -30,12 +30,12 @@ class PostViewModel @Inject constructor( private var isPostEnd = false // 포스트 리스트 + // isCursor의 역할: 처음으로 데이터를 조회해오는 것인지, cursor가 있는 상태로 다음 데이터를 불러오는 것인지 fun setPostList(isCursor: Boolean) { viewModelScope.launch { try { val homePostList = withContext(Dispatchers.IO) { - if(isCursor) getHomePostListUseCase(postCursor, postLimit) - else getHomePostListUseCase(null, postLimit) + getHomePostListUseCase(postCursor, postLimit) } if(homePostList.posts.size == 0) isPostEnd = true else { diff --git a/feature/home/src/main/res/layout/fragment_home.xml b/feature/home/src/main/res/layout/fragment_home.xml index e2e94e09..45a6ec6b 100644 --- a/feature/home/src/main/res/layout/fragment_home.xml +++ b/feature/home/src/main/res/layout/fragment_home.xml @@ -174,6 +174,22 @@ android:layout_below="@id/relative_banner" android:nestedScrollingEnabled="false"/> + + + + + + + From 82294999bcd547231c06cfc8d0d9fc64fd44fa33 Mon Sep 17 00:00:00 2001 From: jung0115 Date: Thu, 4 Jul 2024 21:43:40 +0900 Subject: [PATCH 4/4] =?UTF-8?q?KKUMI-30=20[FIX]=20#34=20-=20=EC=8A=A4?= =?UTF-8?q?=ED=81=AC=EB=A1=A4=EC=9D=B4=20=EC=B5=9C=ED=95=98=EB=8B=A8?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EC=9D=B4=EB=8F=99=ED=96=88=EC=9D=84=20?= =?UTF-8?q?=EB=95=8C=20=EB=8B=A4=EC=9D=8C=20=EB=8D=B0=EC=9D=B4=ED=84=B0?= =?UTF-8?q?=EB=A5=BC=20=ED=95=9C=20=EB=B2=88=EB=A7=8C=20=EC=9A=94=EC=B2=AD?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95=20-=20isPostLi?= =?UTF-8?q?stLoading=EB=A1=9C=20=EC=A7=80=EA=B8=88=20=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=84=B0=EB=A5=BC=20=EC=9A=94=EC=B2=AD=20=EC=A4=91=EC=9D=B8?= =?UTF-8?q?=EC=A7=80=20=EC=83=81=ED=83=9C=20=ED=99=95=EC=9D=B8=20-=20ViewM?= =?UTF-8?q?odel=EC=9D=98=20setPostList=EB=A5=BC=20suspend=20=ED=95=A8?= =?UTF-8?q?=EC=88=98=EB=A1=9C=20=EB=B3=80=EA=B2=BD=ED=95=98=EA=B3=A0,=20Vi?= =?UTF-8?q?ew=EC=97=90=EC=84=9C=20launch=20=EB=B8=94=EB=A1=9D=20=EC=95=88?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=ED=98=B8=EC=B6=9C=ED=95=98=EA=B8=B0=20-?= =?UTF-8?q?=20setPostList=EC=9D=98=20=EC=9E=91=EC=97=85=EC=9D=B4=20?= =?UTF-8?q?=EB=81=9D=EB=82=9C=20=EC=9D=B4=ED=9B=84=EC=97=90=20isPostListLo?= =?UTF-8?q?ading=20=3D=20false?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mykkumi/feature/home/HomeFragment.kt | 25 ++++++++++++------ .../mykkumi/feature/home/UiState.kt | 2 ++ .../feature/home/post/PostViewModel.kt | 26 +++++++++---------- 3 files changed, 31 insertions(+), 22 deletions(-) diff --git a/feature/home/src/main/java/com/swmarastro/mykkumi/feature/home/HomeFragment.kt b/feature/home/src/main/java/com/swmarastro/mykkumi/feature/home/HomeFragment.kt index 7448551d..04f0a5b1 100644 --- a/feature/home/src/main/java/com/swmarastro/mykkumi/feature/home/HomeFragment.kt +++ b/feature/home/src/main/java/com/swmarastro/mykkumi/feature/home/HomeFragment.kt @@ -20,6 +20,7 @@ import com.swmarastro.mykkumi.feature.home.post.PostViewModel import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch import java.util.Timer import java.util.TimerTask @@ -150,23 +151,31 @@ class HomeFragment : BaseFragment(R.layout.fragment_home) { // 포스트 내용 세팅 private fun setPostList() { + lifecycleScope.launch { postViewModel.setPostList(false) postViewModel.postListUiState .onEach { initPostRecyclerView(it) } - .launchIn(lifecycleScope) + .launchIn(viewLifecycleOwner.lifecycleScope) + } } // 포스트 무한 스크롤 -> 스크롤 최하단 도달 시 다음 데이터 요청 private fun setNextPostList() { - postViewModel.setPostList(true) - postViewModel.postListUiState - .onEach { - postListAdapter.postList = it - isPostListLoading = false - } - .launchIn(lifecycleScope) + lifecycleScope.launch { + postViewModel.setPostList(true) + postViewModel.postListUiState + .onEach { + postListAdapter.postList = it + isPostListLoading = false + + if (postViewModel.getIsPostEnd()) { + binding.includeListLoading.visibility = View.GONE + } + } + .launchIn(viewLifecycleOwner.lifecycleScope) + } } override fun onDestroyView() { diff --git a/feature/home/src/main/java/com/swmarastro/mykkumi/feature/home/UiState.kt b/feature/home/src/main/java/com/swmarastro/mykkumi/feature/home/UiState.kt index 9891b115..48f81a57 100644 --- a/feature/home/src/main/java/com/swmarastro/mykkumi/feature/home/UiState.kt +++ b/feature/home/src/main/java/com/swmarastro/mykkumi/feature/home/UiState.kt @@ -1,6 +1,8 @@ package com.swmarastro.mykkumi.feature.home sealed class UiState { + data object Loading : UiState() + data class Success( val data: T, val isFirstMeet: Boolean = false diff --git a/feature/home/src/main/java/com/swmarastro/mykkumi/feature/home/post/PostViewModel.kt b/feature/home/src/main/java/com/swmarastro/mykkumi/feature/home/post/PostViewModel.kt index 80580223..763d3349 100644 --- a/feature/home/src/main/java/com/swmarastro/mykkumi/feature/home/post/PostViewModel.kt +++ b/feature/home/src/main/java/com/swmarastro/mykkumi/feature/home/post/PostViewModel.kt @@ -31,21 +31,19 @@ class PostViewModel @Inject constructor( // 포스트 리스트 // isCursor의 역할: 처음으로 데이터를 조회해오는 것인지, cursor가 있는 상태로 다음 데이터를 불러오는 것인지 - fun setPostList(isCursor: Boolean) { - viewModelScope.launch { - try { - val homePostList = withContext(Dispatchers.IO) { - getHomePostListUseCase(postCursor, postLimit) - } - if(homePostList.posts.size == 0) isPostEnd = true - else { - if (isCursor) _postListUiState.value.addAll(homePostList.posts) - else _postListUiState.value = homePostList.posts.toMutableList() - postCursor = homePostList.cursor - } - } catch (e: Exception) { - _postListUiState.value = mutableListOf() + suspend fun setPostList(isCursor: Boolean) { + try { + val homePostList = withContext(Dispatchers.IO) { + getHomePostListUseCase(postCursor, postLimit) } + if(homePostList.posts.size == 0) isPostEnd = true + else { + if (isCursor) _postListUiState.value.addAll(homePostList.posts) + else _postListUiState.value = homePostList.posts.toMutableList() + postCursor = homePostList.cursor + } + } catch (e: Exception) { + _postListUiState.value = mutableListOf() } }