Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/develop' into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
jung0115 committed Jul 8, 2024
2 parents ef507b4 + b9aabbf commit 4946fa2
Show file tree
Hide file tree
Showing 13 changed files with 144 additions and 36 deletions.
14 changes: 14 additions & 0 deletions core/common-ui/src/main/res/layout/item_list_loading.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<layout
xmlns:android="http://schemas.android.com/apk/res/android">

<ProgressBar
android:id="@+id/progressbar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:indeterminate="true"
android:paddingLeft="8dp"
android:paddingRight="8dp" />

</layout>
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -12,5 +13,5 @@ interface BannerDataSource {
@GET("api/v1/banners/{id}")
suspend fun getBannerDetail(
@Path("id") id: Int
) : BannerListDTO.BannerItemDTO// Response<HomeBannerItemDTO>
) : BannerDetailDTO// Response<HomeBannerItemDTO>
}
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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
)
}

Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,8 @@ data class BannerItemVO(
val id: Int = -1,
val imageUrl: String = ""
)

data class BannerDetailVO(
val id: Int = -1,
val imageUrl: String = ""
)
Original file line number Diff line number Diff line change
@@ -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
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
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

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)
}
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
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.viewpager2.widget.ViewPager2
import com.swmarastro.mykkumi.common_ui.base.BaseFragment
import com.swmarastro.mykkumi.domain.entity.BannerListVO
import com.swmarastro.mykkumi.domain.entity.HomePostListVO
import com.swmarastro.mykkumi.domain.entity.HomePostItemVO
import com.swmarastro.mykkumi.feature.home.banner.HomeBannerViewPagerAdapter
import com.swmarastro.mykkumi.feature.home.databinding.FragmentHomeBinding
import com.swmarastro.mykkumi.feature.home.banner.HomeBannerViewModel
Expand All @@ -18,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

Expand All @@ -29,7 +32,9 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>(R.layout.fragment_home) {

private lateinit var bannerAdapter: HomeBannerViewPagerAdapter
private lateinit var postListAdapter: PostListAdapter

private lateinit var timer: Timer
private var isPostListLoading = false

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
binding.lifecycleOwner = this
Expand Down Expand Up @@ -116,26 +121,61 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>(R.layout.fragment_home) {
}

// 포스트 리스트 recyclerview
private fun initPostRecyclerView(posts: HomePostListVO) {
postListAdapter = PostListAdapter(
posts.posts.toMutableList()
)
private fun initPostRecyclerView(posts: MutableList<HomePostItemVO>) {
//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 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()
}
}
}
}

// 포스트 내용 세팅
private fun setPostList() {
postViewModel.setPostList(null, null)
lifecycleScope.launch {
postViewModel.setPostList(false)
postViewModel.postListUiState
.onEach {
initPostRecyclerView(it)
}
.launchIn(lifecycleScope)
.launchIn(viewLifecycleOwner.lifecycleScope)
}
}

// 포스트 무한 스크롤 -> 스크롤 최하단 도달 시 다음 데이터 요청
private fun setNextPostList() {
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() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.swmarastro.mykkumi.feature.home

sealed class UiState<out T : Any> {
data object Loading : UiState<Nothing>()

data class Success<out T : Any>(
val data: T,
val isFirstMeet: Boolean = false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -27,8 +28,8 @@ class HomeBannerViewModel @Inject constructor(
val bannerListUiState: StateFlow<BannerListVO> get() = _bannerListUiState

// 배너 상세
private val _bannerDetailUiState = MutableStateFlow<BannerItemVO>(BannerItemVO())
val bannerDetailUiState: StateFlow<BannerItemVO> get() = _bannerDetailUiState
private val _bannerDetailUiState = MutableStateFlow<BannerDetailVO>(BannerDetailVO())
val bannerDetailUiState: StateFlow<BannerDetailVO> get() = _bannerDetailUiState

// 선택된 배너
private val _selectBannerId = MutableLiveData<Int>()
Expand Down Expand Up @@ -62,7 +63,7 @@ class HomeBannerViewModel @Inject constructor(
}
_bannerDetailUiState.value = homeBannerDetail
} catch (e: Exception) {
_bannerDetailUiState.value = BannerItemVO()
_bannerDetailUiState.value = BannerDetailVO()
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<HomePostItemVO>
) : RecyclerView.Adapter<PostListAdapter.PostListViewHolder>(){

var postList = mutableListOf<HomePostItemVO>()

private val postContentMaxLine = 2
private val postContentShowMoreText = "...더보기"

Expand Down Expand Up @@ -93,7 +94,7 @@ class PostListAdapter (

// 글 내용 --------------------------------------------------------------------------------------

val testContent = "#템빨 즉 아이템의 비중이 크거나 #다양한 취미를 즐기는 사람들이 모인 커뮤니티를 제공한다. #취미 생활에 관련된 팁을 주고 받거나, #새로운 제품과 스타일을 시도해볼 수 있다."
// val testContent = "#템빨 즉 아이템의 비중이 크거나 #다양한 취미를 즐기는 사람들이 모인 커뮤니티를 제공한다. #취미 생활에 관련된 팁을 주고 받거나, #새로운 제품과 스타일을 시도해볼 수 있다."

// 닉네임 클릭 이벤트
val clickableNicknameSpan = object : ClickableSpan() {
Expand Down Expand Up @@ -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("#")) {
Expand All @@ -145,9 +146,6 @@ class PostListAdapter (
append(" ")
startIndex += word.length + 1
}

// 글 내용
//append(" " + testContent) // item.content
}

binding.textPostNicknameContent.text = originalSpannableStringBuilder
Expand All @@ -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(" ")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package com.swmarastro.mykkumi.feature.home.post

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
Expand All @@ -19,20 +19,36 @@ class PostViewModel @Inject constructor(
) : ViewModel() {

// 포스트 리스트
private val _postListUiState = MutableStateFlow<HomePostListVO>(HomePostListVO())
val postListUiState: StateFlow<HomePostListVO> get() = _postListUiState
private val _postListUiState = MutableStateFlow<MutableList<HomePostItemVO>>(mutableListOf())
val postListUiState: StateFlow<MutableList<HomePostItemVO>> get() = _postListUiState

// 포스트 리스트 cursor, limit
private val postLimit: Int? = null
private var postCursor: String? = null

// 포스트 끝 도달
private var isPostEnd = false

// 포스트 리스트
fun setPostList(cursor: String?, limit: Int?) {
viewModelScope.launch {
try {
val homePostList = withContext(Dispatchers.IO) {
getHomePostListUseCase(cursor, limit)
}
_postListUiState.value = homePostList
} catch (e: Exception) {
_postListUiState.value = HomePostListVO()
// isCursor의 역할: 처음으로 데이터를 조회해오는 것인지, cursor가 있는 상태로 다음 데이터를 불러오는 것인지
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()
}
}

// 더 조회할 포스트가 있는지
fun getIsPostEnd() : Boolean {
return isPostEnd
}
}
17 changes: 17 additions & 0 deletions feature/home/src/main/res/layout/fragment_home.xml
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@


<ScrollView
android:id="@+id/scroll_home_banner_n_post"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/toolbar">
Expand Down Expand Up @@ -173,6 +174,22 @@
android:layout_below="@id/relative_banner"
android:nestedScrollingEnabled="false"/>

<!-- 로딩 -->
<LinearLayout
android:id="@+id/include_list_loading"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:orientation="vertical"
android:layout_below="@id/recyclerview_post_list">

<include
layout="@layout/item_list_loading"
android:layout_width="match_parent"
android:layout_height="wrap_content" />

</LinearLayout>

</RelativeLayout>

</ScrollView>
Expand Down

0 comments on commit 4946fa2

Please sign in to comment.