From 382560c6c36904198273afb7d33a446da7f68f09 Mon Sep 17 00:00:00 2001 From: boiledegg Date: Sun, 22 Dec 2024 16:39:52 +0900 Subject: [PATCH 01/11] =?UTF-8?q?[ADD/#315]=20Paging=20=EB=9D=BC=EC=9D=B4?= =?UTF-8?q?=EB=B8=8C=EB=9F=AC=EB=A6=AC=20=EC=B6=94=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- data/home/build.gradle.kts | 3 +++ gradle/libs.versions.toml | 7 +++++++ 2 files changed, 10 insertions(+) diff --git a/data/home/build.gradle.kts b/data/home/build.gradle.kts index b4047c106..7c8a3ad9f 100644 --- a/data/home/build.gradle.kts +++ b/data/home/build.gradle.kts @@ -11,4 +11,7 @@ android { dependencies { // domain implementation(project(":domain:home")) + + //paging + implementation(libs.paging.runtime) } \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 78642c01a..7a765ed09 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -95,6 +95,9 @@ dokka = "1.9.0" ## amplitude amplitude = "1.17.3" +## Paging +paging = "3.3.2" + [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "androidxCore" } @@ -177,6 +180,10 @@ process-phoenix = { group = "com.jakewharton", name = "process-phoenix", version accompanist-systemuicontroller = { module = "com.google.accompanist:accompanist-systemuicontroller", version.ref = "accompanistSystemuicontroller" } amplitude = { group = "com.amplitude", name = "analytics-android", version.ref="amplitude" } +paging-runtime = {group = "androidx.paging", name = "paging-runtime", version.ref = "paging"} +paging-compose = {group = "androidx.paging", name = "paging-compose", version.ref = "paging"} + + [plugins] android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" } kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } From 864ca66e36961d65da6bbdf65e0ab24bcb981586 Mon Sep 17 00:00:00 2001 From: boiledegg Date: Wed, 25 Dec 2024 00:09:10 +0900 Subject: [PATCH 02/11] =?UTF-8?q?[FEAT/#315]=20HomePagingSource=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../datasourceimpl/HomePagingSourceImpl.kt | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 data/home/src/main/java/com/terning/data/home/datasourceimpl/HomePagingSourceImpl.kt diff --git a/data/home/src/main/java/com/terning/data/home/datasourceimpl/HomePagingSourceImpl.kt b/data/home/src/main/java/com/terning/data/home/datasourceimpl/HomePagingSourceImpl.kt new file mode 100644 index 000000000..3845b6b3d --- /dev/null +++ b/data/home/src/main/java/com/terning/data/home/datasourceimpl/HomePagingSourceImpl.kt @@ -0,0 +1,41 @@ +package com.terning.data.home.datasourceimpl + +import androidx.paging.PagingSource +import androidx.paging.PagingState +import com.terning.data.home.dto.response.HomeRecommendInternResponseDto +import com.terning.data.home.service.HomeService + +// TODO: HomeDataSource의 getRecommendIntern()를 대체하는 PagingSource 구현체 +class HomePagingSourceImpl( + private val service: HomeService, + private val sortBy: String, + private val startYear: Int, + private val startMonth: Int, +) : PagingSource() { + + override suspend fun load(params: LoadParams): LoadResult { + try { + val nextPageNumber = params.key ?: 1 + val response = service.getRecommendIntern( + sortBy = sortBy, + startYear = startYear, + startMonth = startMonth, + // TODO: nextPageNumber = nextPageNumber, + ) + return LoadResult.Page( + data = response.result.result, + prevKey = null, + nextKey = nextPageNumber + ) + } catch (e: Exception) { + return LoadResult.Error(e) + } + } + + override fun getRefreshKey(state: PagingState): Int? { + return state.anchorPosition?.let { anchorPosition -> + val anchorPage = state.closestPageToPosition(anchorPosition) + anchorPage?.prevKey?.plus(1) ?: anchorPage?.nextKey?.minus(1) + } + } +} \ No newline at end of file From f6f6ea775d45c638020ba612e3d4982c95d62378 Mon Sep 17 00:00:00 2001 From: boiledegg Date: Wed, 25 Dec 2024 01:46:49 +0900 Subject: [PATCH 03/11] =?UTF-8?q?[ADD/#315]=20Kotlin=20=EB=9D=BC=EC=9D=B4?= =?UTF-8?q?=EB=B8=8C=EB=9F=AC=EB=A6=AC=EC=97=90=20=EC=BD=94=EB=A3=A8?= =?UTF-8?q?=ED=8B=B4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build-logic/src/main/java/terning.kotlin.library.gradle.kts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/build-logic/src/main/java/terning.kotlin.library.gradle.kts b/build-logic/src/main/java/terning.kotlin.library.gradle.kts index adb1b0651..425cabd99 100644 --- a/build-logic/src/main/java/terning.kotlin.library.gradle.kts +++ b/build-logic/src/main/java/terning.kotlin.library.gradle.kts @@ -1,3 +1,4 @@ +import com.terning.build_logic.convention.configureCoroutineKotlin import com.terning.build_logic.convention.configureKotlin plugins { @@ -8,4 +9,5 @@ kotlin { jvmToolchain(8) } -configureKotlin() \ No newline at end of file +configureKotlin() +configureCoroutineKotlin() \ No newline at end of file From 8a07483ca7732b50ff12ec08b96db8662ce67672 Mon Sep 17 00:00:00 2001 From: boiledegg Date: Wed, 25 Dec 2024 01:47:24 +0900 Subject: [PATCH 04/11] =?UTF-8?q?[ADD/#315]=20:home:domain=20paging=20?= =?UTF-8?q?=EB=9D=BC=EC=9D=B4=EB=B8=8C=EB=9F=AC=EB=A6=AC=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- domain/home/build.gradle.kts | 4 ++++ gradle/libs.versions.toml | 7 ++++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/domain/home/build.gradle.kts b/domain/home/build.gradle.kts index 0fde5b106..bb5609971 100644 --- a/domain/home/build.gradle.kts +++ b/domain/home/build.gradle.kts @@ -1,3 +1,7 @@ plugins { alias(libs.plugins.terning.kotlin) +} + +dependencies { + implementation(libs.paging.common) } \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7a765ed09..5d6847819 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -178,10 +178,11 @@ kakao-user = { group = "com.kakao.sdk", name = "v2-user", version.ref = "kakaoVe process-phoenix = { group = "com.jakewharton", name = "process-phoenix", version.ref = "processPhoenix" } accompanist-systemuicontroller = { module = "com.google.accompanist:accompanist-systemuicontroller", version.ref = "accompanistSystemuicontroller" } -amplitude = { group = "com.amplitude", name = "analytics-android", version.ref="amplitude" } +amplitude = { group = "com.amplitude", name = "analytics-android", version.ref = "amplitude" } -paging-runtime = {group = "androidx.paging", name = "paging-runtime", version.ref = "paging"} -paging-compose = {group = "androidx.paging", name = "paging-compose", version.ref = "paging"} +paging-runtime = { group = "androidx.paging", name = "paging-runtime", version.ref = "paging" } +paging-common = { group = "androidx.paging", name = "paging-common", version.ref = "paging" } +paging-compose = { group = "androidx.paging", name = "paging-compose", version.ref = "paging" } [plugins] From 026aff734c1366105e3b6024d66ae499764caa00 Mon Sep 17 00:00:00 2001 From: boiledegg Date: Fri, 27 Dec 2024 22:04:57 +0900 Subject: [PATCH 05/11] =?UTF-8?q?[FEAT/#315]=20PagingSource=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20=EB=B0=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/home/datasource/HomeDataSource.kt | 1 + .../home/datasourceimpl/HomeDataSourceImpl.kt | 2 + .../datasourceimpl/HomePagingSourceImpl.kt | 41 ------------------- .../HomeRecommendInternResponseDto.kt | 2 + .../home/pagingsource/HomePagingSource.kt | 41 +++++++++++++++++++ .../terning/data/home/service/HomeService.kt | 1 + 6 files changed, 47 insertions(+), 41 deletions(-) delete mode 100644 data/home/src/main/java/com/terning/data/home/datasourceimpl/HomePagingSourceImpl.kt create mode 100644 data/home/src/main/java/com/terning/data/home/pagingsource/HomePagingSource.kt diff --git a/data/home/src/main/java/com/terning/data/home/datasource/HomeDataSource.kt b/data/home/src/main/java/com/terning/data/home/datasource/HomeDataSource.kt index 0c255517c..fbc6e6125 100644 --- a/data/home/src/main/java/com/terning/data/home/datasource/HomeDataSource.kt +++ b/data/home/src/main/java/com/terning/data/home/datasource/HomeDataSource.kt @@ -12,6 +12,7 @@ interface HomeDataSource { suspend fun getRecommendIntern( sortBy: String, + page: Int, ): BaseResponse suspend fun getFilteringInfo(): BaseResponse diff --git a/data/home/src/main/java/com/terning/data/home/datasourceimpl/HomeDataSourceImpl.kt b/data/home/src/main/java/com/terning/data/home/datasourceimpl/HomeDataSourceImpl.kt index 973151a64..e90b1803c 100644 --- a/data/home/src/main/java/com/terning/data/home/datasourceimpl/HomeDataSourceImpl.kt +++ b/data/home/src/main/java/com/terning/data/home/datasourceimpl/HomeDataSourceImpl.kt @@ -18,9 +18,11 @@ class HomeDataSourceImpl @Inject constructor( override suspend fun getRecommendIntern( sortBy: String, + page: Int, ): BaseResponse = homeService.getRecommendIntern( sortBy = sortBy, + page = page ) override suspend fun getFilteringInfo(): BaseResponse = diff --git a/data/home/src/main/java/com/terning/data/home/datasourceimpl/HomePagingSourceImpl.kt b/data/home/src/main/java/com/terning/data/home/datasourceimpl/HomePagingSourceImpl.kt deleted file mode 100644 index 3845b6b3d..000000000 --- a/data/home/src/main/java/com/terning/data/home/datasourceimpl/HomePagingSourceImpl.kt +++ /dev/null @@ -1,41 +0,0 @@ -package com.terning.data.home.datasourceimpl - -import androidx.paging.PagingSource -import androidx.paging.PagingState -import com.terning.data.home.dto.response.HomeRecommendInternResponseDto -import com.terning.data.home.service.HomeService - -// TODO: HomeDataSource의 getRecommendIntern()를 대체하는 PagingSource 구현체 -class HomePagingSourceImpl( - private val service: HomeService, - private val sortBy: String, - private val startYear: Int, - private val startMonth: Int, -) : PagingSource() { - - override suspend fun load(params: LoadParams): LoadResult { - try { - val nextPageNumber = params.key ?: 1 - val response = service.getRecommendIntern( - sortBy = sortBy, - startYear = startYear, - startMonth = startMonth, - // TODO: nextPageNumber = nextPageNumber, - ) - return LoadResult.Page( - data = response.result.result, - prevKey = null, - nextKey = nextPageNumber - ) - } catch (e: Exception) { - return LoadResult.Error(e) - } - } - - override fun getRefreshKey(state: PagingState): Int? { - return state.anchorPosition?.let { anchorPosition -> - val anchorPage = state.closestPageToPosition(anchorPosition) - anchorPage?.prevKey?.plus(1) ?: anchorPage?.nextKey?.minus(1) - } - } -} \ No newline at end of file diff --git a/data/home/src/main/java/com/terning/data/home/dto/response/HomeRecommendInternResponseDto.kt b/data/home/src/main/java/com/terning/data/home/dto/response/HomeRecommendInternResponseDto.kt index 1cf736f04..8039cc406 100644 --- a/data/home/src/main/java/com/terning/data/home/dto/response/HomeRecommendInternResponseDto.kt +++ b/data/home/src/main/java/com/terning/data/home/dto/response/HomeRecommendInternResponseDto.kt @@ -7,6 +7,8 @@ import kotlinx.serialization.Serializable data class HomeRecommendInternResponseDto( @SerialName("totalCount") val totalCount: Int, + @SerialName("hasNext") + val hasNextPage: Boolean, @SerialName("result") val result: List ) { diff --git a/data/home/src/main/java/com/terning/data/home/pagingsource/HomePagingSource.kt b/data/home/src/main/java/com/terning/data/home/pagingsource/HomePagingSource.kt new file mode 100644 index 000000000..50b1c1660 --- /dev/null +++ b/data/home/src/main/java/com/terning/data/home/pagingsource/HomePagingSource.kt @@ -0,0 +1,41 @@ +package com.terning.data.home.pagingsource + +import androidx.paging.PagingSource +import androidx.paging.PagingState +import com.terning.data.home.datasource.HomeDataSource +import com.terning.data.home.dto.response.HomeRecommendInternResponseDto + +class HomePagingSource( + private val sortBy: String, + private val dataSource: HomeDataSource +) : PagingSource>() { + + private var hasNextPage = false + + override suspend fun load(params: LoadParams): LoadResult> { + return try { + val nextParamKey = params.key ?: 0 + + val response = dataSource.getRecommendIntern(sortBy = sortBy, page = nextParamKey) + val totalCount = response.result.totalCount + hasNextPage = response.result.hasNextPage + + LoadResult.Page( + data = response.result.result.map { + Pair(totalCount, it) + }, + prevKey = null, // 다음 페이지 로딩만 가능하도록 설정 + nextKey = if (hasNextPage) nextParamKey + 1 else null + ) + } catch (e: Exception) { + LoadResult.Error(e) + } + } + + override fun getRefreshKey(state: PagingState>): Int? { + return state.anchorPosition?.let { anchorPosition -> + val anchorPage = state.closestPageToPosition(anchorPosition) + anchorPage?.prevKey?.plus(1) ?: anchorPage?.nextKey?.minus(1) + } + } +} \ No newline at end of file diff --git a/data/home/src/main/java/com/terning/data/home/service/HomeService.kt b/data/home/src/main/java/com/terning/data/home/service/HomeService.kt index cc54844be..cb395d3f5 100644 --- a/data/home/src/main/java/com/terning/data/home/service/HomeService.kt +++ b/data/home/src/main/java/com/terning/data/home/service/HomeService.kt @@ -18,6 +18,7 @@ interface HomeService { @GET("api/v1/home") suspend fun getRecommendIntern( @Query("sortBy") sortBy: String, + @Query("page") page: Int, ): BaseResponse @GET("api/v1/filters") From c6dff2d640f03b62fc13ed197eb21246911c47d1 Mon Sep 17 00:00:00 2001 From: boiledegg Date: Fri, 27 Dec 2024 22:05:30 +0900 Subject: [PATCH 06/11] =?UTF-8?q?[FEAT/#315]=20=EB=A0=88=ED=8F=AC=EC=A7=80?= =?UTF-8?q?=ED=86=A0=EB=A6=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../home/mapper/HomeRecommendInternMapper.kt | 22 ++++++--------- .../home/repositoryimpl/HomeRepositoryImpl.kt | 28 +++++++++++++------ .../home/entity/HomeRecommendedIntern.kt | 14 ++++++++++ .../domain/home/repository/HomeRepository.kt | 10 ++++--- 4 files changed, 48 insertions(+), 26 deletions(-) create mode 100644 domain/home/src/main/java/com/terning/domain/home/entity/HomeRecommendedIntern.kt diff --git a/data/home/src/main/java/com/terning/data/home/mapper/HomeRecommendInternMapper.kt b/data/home/src/main/java/com/terning/data/home/mapper/HomeRecommendInternMapper.kt index 00f4d2b93..ba6e16732 100644 --- a/data/home/src/main/java/com/terning/data/home/mapper/HomeRecommendInternMapper.kt +++ b/data/home/src/main/java/com/terning/data/home/mapper/HomeRecommendInternMapper.kt @@ -1,25 +1,19 @@ package com.terning.data.home.mapper import com.terning.data.home.dto.response.HomeRecommendInternResponseDto -import com.terning.domain.home.entity.HomeRecommendIntern +import com.terning.domain.home.entity.HomeRecommendedIntern -fun HomeRecommendInternResponseDto.toHomeRecommendInternList(): HomeRecommendIntern = - HomeRecommendIntern( - totalCount = this.totalCount, - homeRecommendInternDetail = this.result.map { - it.toHomeRecommendInternDetail() - } - ) -fun HomeRecommendInternResponseDto.Result.toHomeRecommendInternDetail(): HomeRecommendIntern.HomeRecommendInternDetail = - HomeRecommendIntern.HomeRecommendInternDetail( +fun HomeRecommendInternResponseDto.Result.toHomeRecommendedIntern(totalCount: Int): HomeRecommendedIntern = + HomeRecommendedIntern( + totalCount = totalCount, internshipAnnouncementId = this.internshipAnnouncementId, - title = this.title, + companyImage = this.companyImage, dDay = this.dDay, - deadline = deadline, + title = this.title, workingPeriod = this.workingPeriod, - startYearMonth = this.startYearMonth, - companyImage = this.companyImage, isScrapped = this.isScrapped, color = this.color, + deadline = this.deadline, + startYearMonth = this.startYearMonth, ) \ No newline at end of file diff --git a/data/home/src/main/java/com/terning/data/home/repositoryimpl/HomeRepositoryImpl.kt b/data/home/src/main/java/com/terning/data/home/repositoryimpl/HomeRepositoryImpl.kt index d06771591..d70fabb7d 100644 --- a/data/home/src/main/java/com/terning/data/home/repositoryimpl/HomeRepositoryImpl.kt +++ b/data/home/src/main/java/com/terning/data/home/repositoryimpl/HomeRepositoryImpl.kt @@ -1,33 +1,45 @@ package com.terning.data.home.repositoryimpl +import androidx.paging.Pager +import androidx.paging.PagingConfig +import androidx.paging.PagingData +import androidx.paging.map import com.terning.data.home.datasource.HomeDataSource import com.terning.data.home.dto.request.toChangeFilterRequestDto import com.terning.data.home.mapper.toHomeFilteringInfo -import com.terning.data.home.mapper.toHomeRecommendInternList +import com.terning.data.home.mapper.toHomeRecommendedIntern import com.terning.data.home.mapper.toHomeUpcomingInternList +import com.terning.data.home.pagingsource.HomePagingSource import com.terning.domain.home.entity.ChangeFilteringRequestModel import com.terning.domain.home.entity.HomeFilteringInfo -import com.terning.domain.home.entity.HomeRecommendIntern +import com.terning.domain.home.entity.HomeRecommendedIntern import com.terning.domain.home.entity.HomeUpcomingIntern import com.terning.domain.home.repository.HomeRepository +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map import javax.inject.Inject class HomeRepositoryImpl @Inject constructor( private val homeDataSource: HomeDataSource, ) : HomeRepository { + override suspend fun getHomeUpcomingInternList(): Result = runCatching { homeDataSource.getUpcomingIntern().result.toHomeUpcomingInternList() } - override suspend fun getRecommendIntern( - sortBy: String, - ): Result = - runCatching { - homeDataSource.getRecommendIntern( + override fun getRecommendIntern(sortBy: String): Flow> { + return Pager( + PagingConfig(pageSize = 10) + ) { + HomePagingSource( sortBy = sortBy, - ).result.toHomeRecommendInternList() + dataSource = homeDataSource + ) + }.flow.map { pagedData -> + pagedData.map { it.second.toHomeRecommendedIntern(it.first) } } + } override suspend fun getFilteringInfo(): Result = runCatching { diff --git a/domain/home/src/main/java/com/terning/domain/home/entity/HomeRecommendedIntern.kt b/domain/home/src/main/java/com/terning/domain/home/entity/HomeRecommendedIntern.kt new file mode 100644 index 000000000..57d077beb --- /dev/null +++ b/domain/home/src/main/java/com/terning/domain/home/entity/HomeRecommendedIntern.kt @@ -0,0 +1,14 @@ +package com.terning.domain.home.entity + +data class HomeRecommendedIntern( + val totalCount: Int, + val internshipAnnouncementId: Long, + val companyImage: String, + val dDay: String, + val title: String, + val workingPeriod: String, + val isScrapped: Boolean, + val color: String?, + val deadline: String, + val startYearMonth: String, +) \ No newline at end of file diff --git a/domain/home/src/main/java/com/terning/domain/home/repository/HomeRepository.kt b/domain/home/src/main/java/com/terning/domain/home/repository/HomeRepository.kt index 609212b3c..dccccf145 100644 --- a/domain/home/src/main/java/com/terning/domain/home/repository/HomeRepository.kt +++ b/domain/home/src/main/java/com/terning/domain/home/repository/HomeRepository.kt @@ -1,16 +1,18 @@ package com.terning.domain.home.repository +import androidx.paging.PagingData import com.terning.domain.home.entity.ChangeFilteringRequestModel import com.terning.domain.home.entity.HomeFilteringInfo -import com.terning.domain.home.entity.HomeRecommendIntern +import com.terning.domain.home.entity.HomeRecommendedIntern import com.terning.domain.home.entity.HomeUpcomingIntern +import kotlinx.coroutines.flow.Flow interface HomeRepository { suspend fun getHomeUpcomingInternList(): Result - suspend fun getRecommendIntern( - sortBy: String, - ): Result + fun getRecommendIntern( + sortBy: String + ): Flow> suspend fun getFilteringInfo(): Result From 4dc525db23a041f0e923e6d86173f7ebc92dc994 Mon Sep 17 00:00:00 2001 From: boiledegg Date: Mon, 30 Dec 2024 01:34:42 +0900 Subject: [PATCH 07/11] =?UTF-8?q?[FEAT/#315]=20UI=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=95=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- feature/home/build.gradle.kts | 4 + .../com/terning/feature/home/HomeRoute.kt | 91 +++++++++---------- .../com/terning/feature/home/HomeState.kt | 1 + .../com/terning/feature/home/HomeViewModel.kt | 71 +++++++++++---- .../com/terning/feature/main/MainNavigator.kt | 2 +- 5 files changed, 105 insertions(+), 64 deletions(-) diff --git a/feature/home/build.gradle.kts b/feature/home/build.gradle.kts index cce28b828..8d4b7d8b5 100644 --- a/feature/home/build.gradle.kts +++ b/feature/home/build.gradle.kts @@ -15,4 +15,8 @@ dependencies { // feature implementation(project(":feature:dialog")) + + // paging + implementation(libs.paging.runtime) + implementation(libs.paging.compose) } \ No newline at end of file diff --git a/feature/home/src/main/java/com/terning/feature/home/HomeRoute.kt b/feature/home/src/main/java/com/terning/feature/home/HomeRoute.kt index eba443d2a..dfcd79695 100644 --- a/feature/home/src/main/java/com/terning/feature/home/HomeRoute.kt +++ b/feature/home/src/main/java/com/terning/feature/home/HomeRoute.kt @@ -27,6 +27,7 @@ import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.flowWithLifecycle +import androidx.paging.compose.collectAsLazyPagingItems import com.google.accompanist.systemuicontroller.rememberSystemUiController import com.terning.core.analytics.EventType import com.terning.core.analytics.LocalTracker @@ -46,7 +47,7 @@ import com.terning.core.designsystem.theme.White import com.terning.core.designsystem.type.Grade import com.terning.core.designsystem.type.WorkingPeriod import com.terning.domain.home.entity.HomeFilteringInfo -import com.terning.domain.home.entity.HomeRecommendIntern +import com.terning.domain.home.entity.HomeRecommendedIntern import com.terning.domain.home.entity.HomeUpcomingIntern import com.terning.feature.dialog.cancel.ScrapCancelDialog import com.terning.feature.dialog.detail.ScrapDialog @@ -59,6 +60,7 @@ import com.terning.feature.home.component.HomeUpcomingInternScreen import okhttp3.internal.toImmutableList const val NAME_MAX_LENGTH = 5 +private const val ZERO_TOTAL_COUNT = 0 @Composable fun HomeRoute( @@ -85,7 +87,6 @@ fun HomeRoute( LaunchedEffect(key1 = true) { viewModel.getProfile() viewModel.getFilteringInfo() - viewModel.getRecommendInternsData(0) viewModel.getHomeUpcomingInternList() } @@ -120,7 +121,7 @@ fun HomeRoute( updateSortingSheetVisibility = viewModel::updateSortingSheetVisibility, updateSortBy = viewModel::updateSortBy, getHomeUpcomingInternList = viewModel::getHomeUpcomingInternList, - getRecommendInternsData = viewModel::getRecommendInternsData, + updateInternModelScrapState = viewModel::updateInternScrapState, viewModel = viewModel, ) } @@ -135,10 +136,11 @@ fun HomeScreen( updateSortingSheetVisibility: (Boolean) -> Unit, updateSortBy: (Int) -> Unit, getHomeUpcomingInternList: () -> Unit, - getRecommendInternsData: (Int) -> Unit, + updateInternModelScrapState: () -> Unit, viewModel: HomeViewModel, ) { val homeState by viewModel.homeState.collectAsStateWithLifecycle() + val recommendedInternList = viewModel.recommendInternFlow.collectAsLazyPagingItems() val homeUserName = when (homeState.homeUserNameState) { is UiState.Success -> (homeState.homeUserNameState as UiState.Success).data @@ -150,14 +152,10 @@ fun HomeScreen( else -> HomeFilteringInfo(null, null, null, null) } - val homeRecommendInternList = when (homeState.homeRecommendInternState) { - is UiState.Success -> (homeState.homeRecommendInternState as UiState.Success).data.homeRecommendInternDetail.toImmutableList() - else -> listOf() - } - - val homeRecommendInternTotal = when (homeState.homeRecommendInternState) { - is UiState.Success -> (homeState.homeRecommendInternState as UiState.Success).data.totalCount - else -> 0 + val homeRecommendInternTotal = remember(recommendedInternList.loadState.refresh) { + if(recommendedInternList.itemCount > 0) { + recommendedInternList[0]?.totalCount ?: ZERO_TOTAL_COUNT + } else { ZERO_TOTAL_COUNT } } var changeFilteringSheetState by remember { mutableStateOf(false) } @@ -216,9 +214,7 @@ fun HomeScreen( updateRecommendDialogVisibility(false) if (isScrapCancelled) { getHomeUpcomingInternList() - getRecommendInternsData( - homeState.sortBy.ordinal, - ) + updateInternModelScrapState() } } ) @@ -235,9 +231,7 @@ fun HomeScreen( onDismissRequest = { isScrapped -> updateRecommendDialogVisibility(false) if (isScrapped) { - getRecommendInternsData( - homeState.sortBy.ordinal, - ) + updateInternModelScrapState() getHomeUpcomingInternList() } }, @@ -324,34 +318,8 @@ fun HomeScreen( } } - if (homeRecommendInternList.isNotEmpty()) { - items(homeRecommendInternList.size) { index -> - RecommendInternItem( - navigateToIntern = navigateToIntern, - intern = homeRecommendInternList[index], - onScrapButtonClicked = { - amplitudeTracker.track( - type = EventType.CLICK, - name = "home_scrap" - ) - updateRecommendDialogVisibility(true) - with(homeRecommendInternList[index]) { - viewModel.updateHomeInternModel( - internshipAnnouncementId = internshipAnnouncementId, - companyImage = companyImage, - title = title, - dDay = dDay, - deadline = deadline, - workingPeriod = workingPeriod, - isScrapped = isScrapped, - color = color, - startYearMonth = startYearMonth, - ) - } - } - ) - } - } else { + + if (recommendedInternList.itemCount == 0) { item { HomeRecommendEmptyIntern( text = @@ -359,6 +327,35 @@ fun HomeScreen( else R.string.home_recommend_no_intern ) } + } else { + items(recommendedInternList.itemCount, key = { it }) { index -> + recommendedInternList[index]?.run { + RecommendInternItem( + navigateToIntern = navigateToIntern, + intern = this, + onScrapButtonClicked = { + amplitudeTracker.track( + type = EventType.CLICK, + name = "home_scrap" + ) + updateRecommendDialogVisibility(true) + with(this) { + viewModel.updateHomeInternModel( + internshipAnnouncementId = internshipAnnouncementId, + companyImage = companyImage, + title = title, + dDay = dDay, + deadline = deadline, + workingPeriod = workingPeriod, + isScrapped = isScrapped, + color = color, + startYearMonth = startYearMonth, + ) + } + } + ) + } + } } } } @@ -367,7 +364,7 @@ fun HomeScreen( @Composable private fun RecommendInternItem( - intern: HomeRecommendIntern.HomeRecommendInternDetail, + intern: HomeRecommendedIntern, navigateToIntern: (Long) -> Unit, onScrapButtonClicked: (Long) -> Unit, ) { diff --git a/feature/home/src/main/java/com/terning/feature/home/HomeState.kt b/feature/home/src/main/java/com/terning/feature/home/HomeState.kt index 1f0ca01c8..f21fd5430 100644 --- a/feature/home/src/main/java/com/terning/feature/home/HomeState.kt +++ b/feature/home/src/main/java/com/terning/feature/home/HomeState.kt @@ -7,6 +7,7 @@ import com.terning.domain.home.entity.HomeRecommendIntern import com.terning.domain.home.entity.HomeUpcomingIntern data class HomeState( + val totalCount: Int = 0, val sortBy: SortBy = SortBy.EARLIEST, val sortingSheetVisibility: Boolean = false, val homeUserNameState: UiState = UiState.Loading, diff --git a/feature/home/src/main/java/com/terning/feature/home/HomeViewModel.kt b/feature/home/src/main/java/com/terning/feature/home/HomeViewModel.kt index 06636e318..e9c885a94 100644 --- a/feature/home/src/main/java/com/terning/feature/home/HomeViewModel.kt +++ b/feature/home/src/main/java/com/terning/feature/home/HomeViewModel.kt @@ -2,17 +2,25 @@ package com.terning.feature.home import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import androidx.paging.PagingData +import androidx.paging.cachedIn +import androidx.paging.map import com.terning.core.designsystem.state.UiState import com.terning.core.designsystem.type.SortBy import com.terning.domain.home.entity.ChangeFilteringRequestModel import com.terning.domain.home.entity.HomeRecommendIntern +import com.terning.domain.home.entity.HomeRecommendedIntern import com.terning.domain.home.repository.HomeRepository import com.terning.domain.mypage.repository.MyPageRepository import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import javax.inject.Inject @@ -28,19 +36,42 @@ class HomeViewModel @Inject constructor( private val _homeSideEffect = MutableSharedFlow() val homeSideEffect get() = _homeSideEffect.asSharedFlow() - fun getRecommendInternsData(sortBy: Int) { - viewModelScope.launch { - homeRepository.getRecommendIntern( - sortBy = SortBy.entries[sortBy].type, - ).onSuccess { internList -> - _homeState.value = _homeState.value.copy( - homeRecommendInternState = UiState.Success(internList) - ) - }.onFailure { exception: Throwable -> - _homeState.value = _homeState.value.copy( - homeRecommendInternState = UiState.Failure(exception.toString()) + private val scrapStateFlow = MutableStateFlow(mapOf()) + + private val _recommendInternFlow = + MutableStateFlow(homeRepository.getRecommendIntern(_homeState.value.sortBy.type)) + + @OptIn(ExperimentalCoroutinesApi::class) + val recommendInternFlow: Flow> = combine( + _recommendInternFlow.flatMapLatest { + it.cachedIn(viewModelScope) + }, scrapStateFlow + ) { paging, scrapState -> + paging.map { intern -> + val isScrapped = scrapState[intern.internshipAnnouncementId] ?: intern.isScrapped + intern.copy( + isScrapped = isScrapped + ) + } + } + + private fun refreshRecommendInternFlow() { + _recommendInternFlow.value = getRecommendInternData() + } + + private fun getRecommendInternData(): Flow> { + val pagingFlow = homeRepository.getRecommendIntern( + sortBy = _homeState.value.sortBy.type + ).cachedIn(viewModelScope) + + return combine( + pagingFlow, scrapStateFlow + ) { paging, scrapState -> + paging.map { intern -> + val isScrapped = scrapState[intern.internshipAnnouncementId] ?: intern.isScrapped + intern.copy( + isScrapped = isScrapped ) - _homeSideEffect.emit(HomeSideEffect.ShowToast(R.string.server_failure)) } } } @@ -86,7 +117,7 @@ class HomeViewModel @Inject constructor( ) ).onSuccess { getFilteringInfo() - getRecommendInternsData(_homeState.value.sortBy.ordinal) + refreshRecommendInternFlow() } } } @@ -146,9 +177,7 @@ class HomeViewModel @Inject constructor( sortBy = SortBy.entries[sortBy] ) } - getRecommendInternsData( - _homeState.value.sortBy.ordinal, - ) + refreshRecommendInternFlow() } fun updateSortingSheetVisibility(visibility: Boolean) { @@ -170,4 +199,14 @@ class HomeViewModel @Inject constructor( _homeSideEffect.emit(HomeSideEffect.NavigateToIntern(announcementId)) } } + + fun updateInternScrapState() { + _homeState.value.homeInternModel?.run { + val isScrapped = scrapStateFlow.value[this.internshipAnnouncementId] ?: this.isScrapped + + scrapStateFlow.update { currentMap -> + currentMap + (this.internshipAnnouncementId to !isScrapped) + } + } + } } \ No newline at end of file diff --git a/feature/main/src/main/java/com/terning/feature/main/MainNavigator.kt b/feature/main/src/main/java/com/terning/feature/main/MainNavigator.kt index fff645486..3977a0c0a 100644 --- a/feature/main/src/main/java/com/terning/feature/main/MainNavigator.kt +++ b/feature/main/src/main/java/com/terning/feature/main/MainNavigator.kt @@ -37,7 +37,7 @@ class MainNavigator( } } launchSingleTop = true - restoreState = true + restoreState = false } when (tab) { From c4f2e14acdbdf829af71f9214706838c954c2096 Mon Sep 17 00:00:00 2001 From: boiledegg Date: Thu, 2 Jan 2025 04:15:01 +0900 Subject: [PATCH 08/11] =?UTF-8?q?[FEAT/#315]=20scrapStateFlow=20=EA=B4=80?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/terning/feature/home/HomeRoute.kt | 2 ++ .../com/terning/feature/home/HomeViewModel.kt | 32 ++++++++----------- .../com/terning/feature/main/MainNavigator.kt | 2 +- 3 files changed, 17 insertions(+), 19 deletions(-) diff --git a/feature/home/src/main/java/com/terning/feature/home/HomeRoute.kt b/feature/home/src/main/java/com/terning/feature/home/HomeRoute.kt index dfcd79695..1d45324fe 100644 --- a/feature/home/src/main/java/com/terning/feature/home/HomeRoute.kt +++ b/feature/home/src/main/java/com/terning/feature/home/HomeRoute.kt @@ -58,6 +58,7 @@ import com.terning.feature.home.component.HomeUpcomingEmptyFilter import com.terning.feature.home.component.HomeUpcomingEmptyIntern import com.terning.feature.home.component.HomeUpcomingInternScreen import okhttp3.internal.toImmutableList +import timber.log.Timber const val NAME_MAX_LENGTH = 5 private const val ZERO_TOTAL_COUNT = 0 @@ -88,6 +89,7 @@ fun HomeRoute( viewModel.getProfile() viewModel.getFilteringInfo() viewModel.getHomeUpcomingInternList() + viewModel.getRecommendInternFlow() } LaunchedEffect(viewModel.homeSideEffect, lifecycleOwner) { diff --git a/feature/home/src/main/java/com/terning/feature/home/HomeViewModel.kt b/feature/home/src/main/java/com/terning/feature/home/HomeViewModel.kt index e9c885a94..23e64d57c 100644 --- a/feature/home/src/main/java/com/terning/feature/home/HomeViewModel.kt +++ b/feature/home/src/main/java/com/terning/feature/home/HomeViewModel.kt @@ -21,6 +21,7 @@ import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import javax.inject.Inject @@ -36,10 +37,11 @@ class HomeViewModel @Inject constructor( private val _homeSideEffect = MutableSharedFlow() val homeSideEffect get() = _homeSideEffect.asSharedFlow() - private val scrapStateFlow = MutableStateFlow(mapOf()) + private val scrapStateFlow: MutableStateFlow> = + MutableStateFlow(emptyMap()) - private val _recommendInternFlow = - MutableStateFlow(homeRepository.getRecommendIntern(_homeState.value.sortBy.type)) + private val _recommendInternFlow: MutableStateFlow>> = + MutableStateFlow(flow { }) @OptIn(ExperimentalCoroutinesApi::class) val recommendInternFlow: Flow> = combine( @@ -55,25 +57,19 @@ class HomeViewModel @Inject constructor( } } - private fun refreshRecommendInternFlow() { - _recommendInternFlow.value = getRecommendInternData() + fun getRecommendInternFlow() { + refreshScrapStateFlow() + refreshRecommendInternFlow() } - private fun getRecommendInternData(): Flow> { - val pagingFlow = homeRepository.getRecommendIntern( + private fun refreshScrapStateFlow() { + scrapStateFlow.value = emptyMap() + } + + private fun refreshRecommendInternFlow() { + _recommendInternFlow.value = homeRepository.getRecommendIntern( sortBy = _homeState.value.sortBy.type ).cachedIn(viewModelScope) - - return combine( - pagingFlow, scrapStateFlow - ) { paging, scrapState -> - paging.map { intern -> - val isScrapped = scrapState[intern.internshipAnnouncementId] ?: intern.isScrapped - intern.copy( - isScrapped = isScrapped - ) - } - } } fun getHomeUpcomingInternList() { diff --git a/feature/main/src/main/java/com/terning/feature/main/MainNavigator.kt b/feature/main/src/main/java/com/terning/feature/main/MainNavigator.kt index 3977a0c0a..fff645486 100644 --- a/feature/main/src/main/java/com/terning/feature/main/MainNavigator.kt +++ b/feature/main/src/main/java/com/terning/feature/main/MainNavigator.kt @@ -37,7 +37,7 @@ class MainNavigator( } } launchSingleTop = true - restoreState = false + restoreState = true } when (tab) { From 6d2dbc4aa8acf57721c71a32283583c6f2bbb211 Mon Sep 17 00:00:00 2001 From: boiledegg Date: Thu, 2 Jan 2025 04:24:20 +0900 Subject: [PATCH 09/11] =?UTF-8?q?[FIX/#315]=20hasNext=20=EC=A7=80=EC=97=AD?= =?UTF-8?q?=EB=B3=80=EC=88=98=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/terning/data/home/pagingsource/HomePagingSource.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/data/home/src/main/java/com/terning/data/home/pagingsource/HomePagingSource.kt b/data/home/src/main/java/com/terning/data/home/pagingsource/HomePagingSource.kt index 50b1c1660..421236626 100644 --- a/data/home/src/main/java/com/terning/data/home/pagingsource/HomePagingSource.kt +++ b/data/home/src/main/java/com/terning/data/home/pagingsource/HomePagingSource.kt @@ -10,7 +10,6 @@ class HomePagingSource( private val dataSource: HomeDataSource ) : PagingSource>() { - private var hasNextPage = false override suspend fun load(params: LoadParams): LoadResult> { return try { @@ -18,7 +17,7 @@ class HomePagingSource( val response = dataSource.getRecommendIntern(sortBy = sortBy, page = nextParamKey) val totalCount = response.result.totalCount - hasNextPage = response.result.hasNextPage + val hasNextPage = response.result.hasNextPage LoadResult.Page( data = response.result.result.map { From d7cd07f34b176d40c777dce3a9a2fe464b5bf6ec Mon Sep 17 00:00:00 2001 From: boiledegg Date: Thu, 2 Jan 2025 04:26:16 +0900 Subject: [PATCH 10/11] =?UTF-8?q?[DEL/#315]=20=EB=AF=B8=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=20=EB=9D=BC=EC=9D=B4=EB=B8=8C=EB=9F=AC=EB=A6=AC=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- feature/home/src/main/java/com/terning/feature/home/HomeRoute.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/feature/home/src/main/java/com/terning/feature/home/HomeRoute.kt b/feature/home/src/main/java/com/terning/feature/home/HomeRoute.kt index 1d45324fe..ba0a25c78 100644 --- a/feature/home/src/main/java/com/terning/feature/home/HomeRoute.kt +++ b/feature/home/src/main/java/com/terning/feature/home/HomeRoute.kt @@ -58,7 +58,6 @@ import com.terning.feature.home.component.HomeUpcomingEmptyFilter import com.terning.feature.home.component.HomeUpcomingEmptyIntern import com.terning.feature.home.component.HomeUpcomingInternScreen import okhttp3.internal.toImmutableList -import timber.log.Timber const val NAME_MAX_LENGTH = 5 private const val ZERO_TOTAL_COUNT = 0 From 444f402dac5fe7b6c93d49554d6765222eda6f7a Mon Sep 17 00:00:00 2001 From: boiledegg Date: Sat, 4 Jan 2025 23:40:32 +0900 Subject: [PATCH 11/11] =?UTF-8?q?[MOD/#315]=20=EC=BD=94=EB=A3=A8=ED=8B=B4?= =?UTF-8?q?=20=EC=9D=98=EC=A1=B4=EC=84=B1=20=EC=9C=84=EC=B9=98=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build-logic/src/main/java/terning.kotlin.library.gradle.kts | 3 +-- domain/home/build.gradle.kts | 2 ++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/build-logic/src/main/java/terning.kotlin.library.gradle.kts b/build-logic/src/main/java/terning.kotlin.library.gradle.kts index 425cabd99..a02ef1c4e 100644 --- a/build-logic/src/main/java/terning.kotlin.library.gradle.kts +++ b/build-logic/src/main/java/terning.kotlin.library.gradle.kts @@ -9,5 +9,4 @@ kotlin { jvmToolchain(8) } -configureKotlin() -configureCoroutineKotlin() \ No newline at end of file +configureKotlin() \ No newline at end of file diff --git a/domain/home/build.gradle.kts b/domain/home/build.gradle.kts index bb5609971..8c7175e69 100644 --- a/domain/home/build.gradle.kts +++ b/domain/home/build.gradle.kts @@ -4,4 +4,6 @@ plugins { dependencies { implementation(libs.paging.common) + implementation(libs.coroutines.core) + implementation(libs.coroutines.test) } \ No newline at end of file