From 8df3bd73c4c11dbc1895a7f559ec77afa4671dd2 Mon Sep 17 00:00:00 2001 From: yunsehwan Date: Mon, 16 Sep 2024 11:28:41 +0900 Subject: [PATCH 1/9] =?UTF-8?q?[FEATURE]=20=ED=8E=98=EC=9D=B4=EC=A7=95=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=EA=B3=B5=ED=86=B5=20=ED=81=B4=EB=9E=98?= =?UTF-8?q?=EC=8A=A4=20=EA=B5=AC=ED=98=84=20=EB=B0=8F=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/feature/build.gradle.kts | 14 +++ .../feature/model/paging/PagingLoadResult.kt | 18 +++ .../core/feature/model/paging/PagingSource.kt | 5 + .../core/feature/model/paging/PagingState.kt | 5 + .../core/feature/model/paging/SimplePaging.kt | 118 ++++++++++++++++++ .../pokit/core/feature/ExampleUnitTest.kt | 16 --- .../core/feature/SimplePagingUnitTest.kt | 86 +++++++++++++ .../core/feature/model/TestPagingSource.kt | 27 ++++ 8 files changed, 273 insertions(+), 16 deletions(-) create mode 100644 core/feature/src/main/java/pokitmons/pokit/core/feature/model/paging/PagingLoadResult.kt create mode 100644 core/feature/src/main/java/pokitmons/pokit/core/feature/model/paging/PagingSource.kt create mode 100644 core/feature/src/main/java/pokitmons/pokit/core/feature/model/paging/PagingState.kt create mode 100644 core/feature/src/main/java/pokitmons/pokit/core/feature/model/paging/SimplePaging.kt delete mode 100644 core/feature/src/test/java/pokitmons/pokit/core/feature/ExampleUnitTest.kt create mode 100644 core/feature/src/test/java/pokitmons/pokit/core/feature/SimplePagingUnitTest.kt create mode 100644 core/feature/src/test/java/pokitmons/pokit/core/feature/model/TestPagingSource.kt diff --git a/core/feature/build.gradle.kts b/core/feature/build.gradle.kts index 26159256..bccf77b1 100644 --- a/core/feature/build.gradle.kts +++ b/core/feature/build.gradle.kts @@ -5,6 +5,10 @@ plugins { } android { + tasks.withType().configureEach { + useJUnitPlatform() + } + namespace = "pokitmons.pokit.core.feature" compileSdk = 34 @@ -56,4 +60,14 @@ dependencies { androidTestImplementation(libs.androidx.ui.test.junit4) debugImplementation(libs.androidx.ui.tooling) debugImplementation(libs.androidx.ui.test.manifest) + + // kotest + testImplementation(libs.kotest.runner.junit5) + testImplementation(libs.kotlin.reflect) + + // mockk + testImplementation(libs.mockk) + androidTestImplementation(libs.mockk.android) + + implementation(project(":domain")) } diff --git a/core/feature/src/main/java/pokitmons/pokit/core/feature/model/paging/PagingLoadResult.kt b/core/feature/src/main/java/pokitmons/pokit/core/feature/model/paging/PagingLoadResult.kt new file mode 100644 index 00000000..eab9712c --- /dev/null +++ b/core/feature/src/main/java/pokitmons/pokit/core/feature/model/paging/PagingLoadResult.kt @@ -0,0 +1,18 @@ +package pokitmons.pokit.core.feature.model.paging + +import pokitmons.pokit.domain.commom.PokitResult + +sealed interface PagingLoadResult { + data class Success(val result: List): PagingLoadResult + data class Error(val errorCode: String) : PagingLoadResult + + companion object { + fun fromPokitResult(pokitResult: PokitResult, mapper: (K) -> List): PagingLoadResult { + return if (pokitResult is PokitResult.Success) { + Success(result = mapper(pokitResult.result)) + } else { + Error(errorCode = (pokitResult as PokitResult.Error).error.code) + } + } + } +} diff --git a/core/feature/src/main/java/pokitmons/pokit/core/feature/model/paging/PagingSource.kt b/core/feature/src/main/java/pokitmons/pokit/core/feature/model/paging/PagingSource.kt new file mode 100644 index 00000000..c9b9cd96 --- /dev/null +++ b/core/feature/src/main/java/pokitmons/pokit/core/feature/model/paging/PagingSource.kt @@ -0,0 +1,5 @@ +package pokitmons.pokit.core.feature.model.paging + +interface PagingSource { + suspend fun load(pageIndex: Int, pageSize: Int): PagingLoadResult +} diff --git a/core/feature/src/main/java/pokitmons/pokit/core/feature/model/paging/PagingState.kt b/core/feature/src/main/java/pokitmons/pokit/core/feature/model/paging/PagingState.kt new file mode 100644 index 00000000..9adf4445 --- /dev/null +++ b/core/feature/src/main/java/pokitmons/pokit/core/feature/model/paging/PagingState.kt @@ -0,0 +1,5 @@ +package pokitmons.pokit.core.feature.model.paging + +enum class PagingState { + IDLE, LOADING_NEXT, LOADING_INIT, FAILURE_NEXT, FAILURE_INIT, LAST +} diff --git a/core/feature/src/main/java/pokitmons/pokit/core/feature/model/paging/SimplePaging.kt b/core/feature/src/main/java/pokitmons/pokit/core/feature/model/paging/SimplePaging.kt new file mode 100644 index 00000000..0335b8e8 --- /dev/null +++ b/core/feature/src/main/java/pokitmons/pokit/core/feature/model/paging/SimplePaging.kt @@ -0,0 +1,118 @@ +package pokitmons.pokit.core.feature.model.paging + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import kotlin.coroutines.cancellation.CancellationException + +class SimplePaging ( + private val pagingSource: PagingSource, + private val getKeyFromItem: (ITEM) -> KEY, + private val coroutineScope: CoroutineScope = CoroutineScope(Dispatchers.IO), + private val perPage: Int = 10, + private val initPage: Int = 0, + private val firstRequestPage: Int = 3, +) { + private val _pagingData : MutableStateFlow> = MutableStateFlow(emptyList()) + val pagingData : StateFlow> = _pagingData.asStateFlow() + + private val _pagingState : MutableStateFlow = MutableStateFlow(PagingState.IDLE) + val pagingState : StateFlow = _pagingState.asStateFlow() + + private var pagingDataRequestJob: Job? = null + private var currentPageIndex = 0 + + suspend fun refresh() { + pagingDataRequestJob?.cancel() + + _pagingData.update { emptyList() } + _pagingState.update { PagingState.LOADING_INIT } + + pagingDataRequestJob = coroutineScope.launch { + try { + currentPageIndex = initPage + + val response = pagingSource.load(pageIndex = currentPageIndex, pageSize = perPage * firstRequestPage) + when (response) { + is PagingLoadResult.Success -> { + val itemList = response.result + applyResponse(itemList, firstRequestPage) + } + is PagingLoadResult.Error -> { + _pagingState.update { PagingState.FAILURE_INIT } + } + } + } catch (exception: Exception) { + if (exception !is CancellationException) { + _pagingState.update { PagingState.FAILURE_INIT } + } + } + } + } + + suspend fun load() { + if (pagingState.value != PagingState.IDLE) return + + pagingDataRequestJob?.cancel() + _pagingState.update { PagingState.LOADING_NEXT } + + pagingDataRequestJob = coroutineScope.launch { + try { + val response = pagingSource.load(pageIndex = currentPageIndex, pageSize = perPage) + when (response) { + is PagingLoadResult.Success -> { + val itemList = response.result + applyResponse(itemList) + } + is PagingLoadResult.Error -> { + _pagingState.update { PagingState.FAILURE_NEXT } + } + } + } catch (exception: Exception) { + if (exception !is CancellationException) { + _pagingState.update { PagingState.FAILURE_NEXT } + } + } + } + } + + private fun applyResponse(dataInResponse: List, multiple: Int = 1) { + if (dataInResponse.size < perPage * multiple) { + _pagingState.update { PagingState.LAST } + } else { + _pagingState.update { PagingState.IDLE } + } + _pagingData.update { _pagingData.value + dataInResponse } + currentPageIndex += multiple + } + + fun clear() { + pagingDataRequestJob?.cancel() + _pagingData.update { emptyList() } + _pagingState.update { PagingState.IDLE } + } + + fun deleteItem(targetItemKey: KEY) { + val currentDataList = _pagingData.value + _pagingData.update { currentDataList.filter { getKeyFromItem(it) != targetItemKey } } + } + + fun modifyItem(targetItem: ITEM) { + val currentDataList = _pagingData.value + + _pagingData.update { + currentDataList.map { item -> + if (getKeyFromItem(targetItem) == getKeyFromItem(item)) { + targetItem + } else { + item + } + } + } + } +} diff --git a/core/feature/src/test/java/pokitmons/pokit/core/feature/ExampleUnitTest.kt b/core/feature/src/test/java/pokitmons/pokit/core/feature/ExampleUnitTest.kt deleted file mode 100644 index 00cadeb1..00000000 --- a/core/feature/src/test/java/pokitmons/pokit/core/feature/ExampleUnitTest.kt +++ /dev/null @@ -1,16 +0,0 @@ -package pokitmons.pokit.core.feature - -import org.junit.Assert.assertEquals -import org.junit.Test - -/** - * Example local unit test, which will execute on the development machine (host). - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -class ExampleUnitTest { - @Test - fun addition_isCorrect() { - assertEquals(4, 2 + 2) - } -} diff --git a/core/feature/src/test/java/pokitmons/pokit/core/feature/SimplePagingUnitTest.kt b/core/feature/src/test/java/pokitmons/pokit/core/feature/SimplePagingUnitTest.kt new file mode 100644 index 00000000..3475d285 --- /dev/null +++ b/core/feature/src/test/java/pokitmons/pokit/core/feature/SimplePagingUnitTest.kt @@ -0,0 +1,86 @@ +package pokitmons.pokit.core.feature + +import io.kotest.common.ExperimentalKotest +import io.kotest.core.spec.style.DescribeSpec +import io.kotest.core.test.testCoroutineScheduler +import io.kotest.matchers.shouldBe +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.first +import pokitmons.pokit.core.feature.model.TestPagingSource +import pokitmons.pokit.core.feature.model.paging.PagingState +import pokitmons.pokit.core.feature.model.paging.SimplePaging + +const val FIRST_REQUEST_PAGE_SAMPLE = 3 +const val PAGE_LOAD_TIME = 1000L +const val TOTAL_ITEM_COUNT = 46 + +@OptIn(ExperimentalKotest::class, ExperimentalStdlibApi::class, ExperimentalCoroutinesApi::class) +class SimplePagingUnitTest : DescribeSpec({ + + describe("SimplePaging").config(coroutineTestScope = true) { + val coroutineScope = this + val testPaging = SimplePaging( + coroutineScope = coroutineScope, + pagingSource = TestPagingSource( + loadTime = PAGE_LOAD_TIME, + totalItemCount = TOTAL_ITEM_COUNT + ), + getKeyFromItem = {it}, + firstRequestPage = FIRST_REQUEST_PAGE_SAMPLE, + ) + + context("새로고침을 한 상황에서") { + it("새로고침 로딩 상태가 되어야 한다.") { + testPaging.refresh() + testPaging.pagingState.value shouldBe PagingState.LOADING_INIT + } + + it("다른 페이지 요청을 무시한다.") { + testPaging.refresh() + testPaging.load() + + coroutineScope.testCoroutineScheduler.advanceTimeBy(5000L) + // testCoroutineScheduler.advanceUntilIdle() + // it 내의 this(coroutineScope)와 전체 describe의 coroutineScope가 서로 다르다! + + val state = testPaging.pagingState.first() + state shouldBe PagingState.IDLE + } + + it("초기화 작업은 수행 가능하다.") { + testPaging.refresh() + testPaging.clear() + + val state = testPaging.pagingState.first() + state shouldBe PagingState.IDLE + } + } + + context("기존 페이지를 로드하던 중 새로고침 요청이 들어온 상황에서") { + it("기존 작업을 무시하고 새로고침을 수행한다.") { + testPaging.load() + testPaging.refresh() + testPaging.pagingState.value shouldBe PagingState.LOADING_INIT + } + } + + context("전체 데이터를 모두 로드한 상황에서") { + testPaging.clear() + testPaging.refresh() + coroutineScope.testCoroutineScheduler.advanceTimeBy(PAGE_LOAD_TIME + 1) + testPaging.load() + coroutineScope.testCoroutineScheduler.advanceTimeBy(PAGE_LOAD_TIME + 1) + testPaging.load() + coroutineScope.testCoroutineScheduler.advanceTimeBy(PAGE_LOAD_TIME + 1) + + it("마지막 상태를 가진다.") { + testPaging.pagingState.value shouldBe PagingState.LAST + } + + it("추가적인 데이터 로드 요청을 무시한다.") { + testPaging.load() + testPaging.pagingState.value shouldBe PagingState.LAST + } + } + } +}) diff --git a/core/feature/src/test/java/pokitmons/pokit/core/feature/model/TestPagingSource.kt b/core/feature/src/test/java/pokitmons/pokit/core/feature/model/TestPagingSource.kt new file mode 100644 index 00000000..d3144f60 --- /dev/null +++ b/core/feature/src/test/java/pokitmons/pokit/core/feature/model/TestPagingSource.kt @@ -0,0 +1,27 @@ +package pokitmons.pokit.core.feature.model + +import kotlinx.coroutines.delay +import pokitmons.pokit.core.feature.model.paging.PagingLoadResult +import pokitmons.pokit.core.feature.model.paging.PagingSource +import kotlin.math.min + +class TestPagingSource( + private val loadTime: Long = 1000L, + private val totalItemCount: Int = 30, +) : PagingSource { + override suspend fun load(pageIndex: Int, pageSize: Int): PagingLoadResult { + delay(loadTime) + + val firstItemCount = pageIndex * pageSize + 1 + + if (totalItemCount < firstItemCount) { + return PagingLoadResult.Success(emptyList()) + } + + val startIndex = pageIndex * pageSize + val lastIndex = min(((pageIndex + 1) * pageSize), totalItemCount) + + val itemList = (startIndex until lastIndex).map { "${it}번째 아이템" } + return PagingLoadResult.Success(itemList) + } +} From 6393f62ade78286d918e381b5e0665bb7249d646 Mon Sep 17 00:00:00 2001 From: yunsehwan Date: Mon, 16 Sep 2024 16:00:00 +0900 Subject: [PATCH 2/9] =?UTF-8?q?[REFACTOR]=20=ED=8F=AC=ED=82=B7=20=EC=83=81?= =?UTF-8?q?=EC=84=B8=20=ED=99=94=EB=A9=B4=EC=97=90=EC=84=9C=20core:feature?= =?UTF-8?q?=EB=AA=A8=EB=93=88=EC=97=90=20=EC=84=A0=EC=96=B8=ED=95=9C=20Sim?= =?UTF-8?q?plePaging=EC=9D=84=20=EC=82=AC=EC=9A=A9=ED=95=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EC=88=98=EC=A0=95=20=EB=B0=8F=20=EC=B5=9C=EC=8B=A0?= =?UTF-8?q?/=EC=98=A4=EB=9E=98=EB=90=9C=20=EC=88=9C=20=EC=A0=95=EB=A0=AC?= =?UTF-8?q?=EC=9D=B4=20=EC=A0=81=EC=9A=A9=EB=90=98=EC=A7=80=20=EC=95=8A?= =?UTF-8?q?=EB=8D=98=20=EB=AC=B8=EC=A0=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pokitdetail/PokitDetailScreen.kt | 14 ++-- .../pokitdetail/PokitDetailViewModel.kt | 77 +++++++++++-------- 2 files changed, 52 insertions(+), 39 deletions(-) diff --git a/feature/pokitdetail/src/main/java/com/strayalpaca/pokitdetail/PokitDetailScreen.kt b/feature/pokitdetail/src/main/java/com/strayalpaca/pokitdetail/PokitDetailScreen.kt index ef9e81dd..978f0fa0 100644 --- a/feature/pokitdetail/src/main/java/com/strayalpaca/pokitdetail/PokitDetailScreen.kt +++ b/feature/pokitdetail/src/main/java/com/strayalpaca/pokitdetail/PokitDetailScreen.kt @@ -32,8 +32,8 @@ import com.strayalpaca.pokitdetail.model.Filter import com.strayalpaca.pokitdetail.model.Link import com.strayalpaca.pokitdetail.model.Pokit import com.strayalpaca.pokitdetail.model.PokitDetailScreenState -import com.strayalpaca.pokitdetail.paging.SimplePagingState import pokitmons.pokit.core.feature.flow.collectAsEffect +import pokitmons.pokit.core.feature.model.paging.PagingState import pokitmons.pokit.core.ui.components.atom.loading.LoadingProgress import pokitmons.pokit.core.ui.components.block.linkcard.LinkCard import pokitmons.pokit.core.ui.components.block.pokitlist.PokitList @@ -121,9 +121,9 @@ fun PokitDetailScreen( hideLinkDetailBottomSheet: () -> Unit = {}, state: PokitDetailScreenState = PokitDetailScreenState(), linkList: List = emptyList(), - linkListState: SimplePagingState = SimplePagingState.IDLE, + linkListState: PagingState = PagingState.IDLE, pokitList: List = emptyList(), - pokitListState: SimplePagingState = SimplePagingState.IDLE, + pokitListState: PagingState = PagingState.IDLE, onClickLink: (Link) -> Unit = {}, onClickPokitModify: (String) -> Unit = {}, onClickPokitRemove: () -> Unit = {}, @@ -163,20 +163,20 @@ fun PokitDetailScreen( } LaunchedEffect(startLinkPaging.value) { - if (startLinkPaging.value && linkListState == SimplePagingState.IDLE) { + if (startLinkPaging.value && linkListState == PagingState.IDLE) { loadNextLinks() } } when { - (linkListState == SimplePagingState.LOADING_INIT) -> { + (linkListState == PagingState.LOADING_INIT) -> { LoadingProgress( modifier = Modifier .fillMaxWidth() .weight(1f) ) } - (linkListState == SimplePagingState.FAILURE_INIT) -> { + (linkListState == PagingState.FAILURE_INIT) -> { ErrorPooki( modifier = Modifier .fillMaxWidth() @@ -285,7 +285,7 @@ fun PokitDetailScreen( } LaunchedEffect(startPaging.value) { - if (startPaging.value && pokitListState == SimplePagingState.IDLE) { + if (startPaging.value && pokitListState == PagingState.IDLE) { loadNextPokits() } } diff --git a/feature/pokitdetail/src/main/java/com/strayalpaca/pokitdetail/PokitDetailViewModel.kt b/feature/pokitdetail/src/main/java/com/strayalpaca/pokitdetail/PokitDetailViewModel.kt index 98ed8a19..ab79a2ea 100644 --- a/feature/pokitdetail/src/main/java/com/strayalpaca/pokitdetail/PokitDetailViewModel.kt +++ b/feature/pokitdetail/src/main/java/com/strayalpaca/pokitdetail/PokitDetailViewModel.kt @@ -8,9 +8,6 @@ import com.strayalpaca.pokitdetail.model.Filter import com.strayalpaca.pokitdetail.model.Link import com.strayalpaca.pokitdetail.model.Pokit import com.strayalpaca.pokitdetail.model.PokitDetailScreenState -import com.strayalpaca.pokitdetail.paging.LinkPaging -import com.strayalpaca.pokitdetail.paging.PokitPaging -import com.strayalpaca.pokitdetail.paging.SimplePagingState import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -20,6 +17,10 @@ import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import pokitmons.pokit.core.feature.flow.MutableEventFlow import pokitmons.pokit.core.feature.flow.asEventFlow +import pokitmons.pokit.core.feature.model.paging.PagingLoadResult +import pokitmons.pokit.core.feature.model.paging.PagingSource +import pokitmons.pokit.core.feature.model.paging.PagingState +import pokitmons.pokit.core.feature.model.paging.SimplePaging import pokitmons.pokit.core.feature.navigation.args.LinkUpdateEvent import pokitmons.pokit.core.feature.navigation.args.PokitUpdateEvent import pokitmons.pokit.domain.commom.PokitResult @@ -32,7 +33,6 @@ import pokitmons.pokit.domain.usecase.pokit.DeletePokitUseCase import pokitmons.pokit.domain.usecase.pokit.GetPokitUseCase import pokitmons.pokit.domain.usecase.pokit.GetPokitsUseCase import javax.inject.Inject -import pokitmons.pokit.domain.model.link.Link as DomainLink @HiltViewModel class PokitDetailViewModel @Inject constructor( @@ -45,29 +45,56 @@ class PokitDetailViewModel @Inject constructor( private val getLinkUseCase: GetLinkUseCase, savedStateHandle: SavedStateHandle, ) : ViewModel() { - private val pokitPaging = PokitPaging( - getPokits = getPokitsUseCase, - perPage = 10, - coroutineScope = viewModelScope, - initPage = 0 + private val pokitPagingSource = object: PagingSource { + override suspend fun load(pageIndex: Int, pageSize: Int): PagingLoadResult { + val response = getPokitsUseCase.getPokits(size = pageSize, page = pageIndex) + return PagingLoadResult.fromPokitResult( + pokitResult = response, + mapper = { domainPokits -> domainPokits.map { Pokit.fromDomainPokit(it) }} + ) + } + } + private val pokitPaging = SimplePaging( + pagingSource = pokitPagingSource, + getKeyFromItem = { pokit -> pokit.id }, + coroutineScope = viewModelScope ) - private val linkPaging = LinkPaging( - getLinks = ::getLinks, - perPage = 10, - coroutineScope = viewModelScope, - initPage = 0, - initCategoryId = 1 + private val linkPagingSource = object: PagingSource { + override suspend fun load(pageIndex: Int, pageSize: Int): PagingLoadResult { + val currentPokit = state.value.currentPokit + val currentFilter = state.value.currentFilter + val categoryId = currentPokit?.id?.toIntOrNull() ?: savedStateHandle.get("pokit_id")?.toIntOrNull() ?: 0 + val sort = if (currentFilter.recentSortUsed) LinksSort.RECENT else LinksSort.OLDER + val response = getLinksUseCase.getLinks( + page = pageIndex, + size = pageSize, + categoryId = categoryId, + sort = sort, + isRead = if (currentFilter.notReadChecked) false else null, + favorite = if (currentFilter.bookmarkChecked) true else null, + ) + return PagingLoadResult.fromPokitResult( + pokitResult = response, + mapper = { domainLinks -> domainLinks.map { Link.fromDomainLink(it) } } + ) + } + } + + private val linkPaging = SimplePaging( + pagingSource = linkPagingSource, + getKeyFromItem = { link -> link.id }, + coroutineScope = viewModelScope ) private val _state = MutableStateFlow(PokitDetailScreenState()) val state: StateFlow = _state.asStateFlow() val pokitList: StateFlow> = pokitPaging.pagingData - val pokitListState: StateFlow = pokitPaging.pagingState + val pokitListState: StateFlow = pokitPaging.pagingState val linkList: StateFlow> = linkPaging.pagingData - val linkListState: StateFlow = linkPaging.pagingState + val linkListState: StateFlow = linkPaging.pagingState private val _moveToBackEvent = MutableEventFlow() val moveToBackEvent = _moveToBackEvent.asEventFlow() @@ -77,7 +104,6 @@ class PokitDetailViewModel @Inject constructor( val linkCount = savedStateHandle.get("pokit_count")?.toIntOrNull() ?: 0 pokitId?.let { id -> - linkPaging.changeOptions(categoryId = id, sort = LinksSort.RECENT) viewModelScope.launch { linkPaging.refresh() } @@ -108,7 +134,7 @@ class PokitDetailViewModel @Inject constructor( viewModelScope.launch { LinkUpdateEvent.removedLink.collectLatest { removedLinkId -> val targetLink = linkPaging.pagingData.value.find { it.id == removedLinkId.toString() } ?: return@collectLatest - linkPaging.deleteItem(targetLink) + linkPaging.deleteItem(targetLink.id) } } } @@ -126,18 +152,6 @@ class PokitDetailViewModel @Inject constructor( } } - private suspend fun getLinks(categoryId: Int, size: Int, page: Int, sort: LinksSort): PokitResult> { - val currentFilter = state.value.currentFilter - return getLinksUseCase.getLinks( - categoryId = categoryId, - size = size, - page = page, - sort = sort, - isRead = if (currentFilter.notReadChecked) false else null, - favorite = if (currentFilter.bookmarkChecked) true else null - ) - } - private fun getPokit(pokitId: Int, linkCount: Int) { viewModelScope.launch { val response = getPokitUseCase.getPokit(pokitId) @@ -149,7 +163,6 @@ class PokitDetailViewModel @Inject constructor( fun changePokit(pokit: Pokit) { _state.update { it.copy(currentPokit = pokit, pokitSelectBottomSheetVisible = false) } - linkPaging.changeOptions(categoryId = pokit.id.toInt(), sort = LinksSort.RECENT) viewModelScope.launch { linkPaging.refresh() } From 20af47843b7ebff557842be79bc5e65474694705 Mon Sep 17 00:00:00 2001 From: yunsehwan Date: Mon, 16 Sep 2024 16:22:12 +0900 Subject: [PATCH 3/9] =?UTF-8?q?[REFACTOR]=20=ED=99=88=20=ED=99=94=EB=A9=B4?= =?UTF-8?q?=EC=97=90=EC=84=9C=20core:feature=EB=AA=A8=EB=93=88=EC=97=90=20?= =?UTF-8?q?=EC=84=A0=EC=96=B8=ED=95=9C=20SimplePaging=EC=9D=84=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pokitmons/pokit/home/pokit/PokitScreen.kt | 10 +- .../pokit/home/pokit/PokitViewModel.kt | 101 ++++++++---------- 2 files changed, 50 insertions(+), 61 deletions(-) diff --git a/feature/home/src/main/java/pokitmons/pokit/home/pokit/PokitScreen.kt b/feature/home/src/main/java/pokitmons/pokit/home/pokit/PokitScreen.kt index 0716220c..a86edfdf 100644 --- a/feature/home/src/main/java/pokitmons/pokit/home/pokit/PokitScreen.kt +++ b/feature/home/src/main/java/pokitmons/pokit/home/pokit/PokitScreen.kt @@ -23,7 +23,7 @@ import androidx.compose.ui.unit.dp import coil.compose.rememberAsyncImagePainter import com.strayalpaca.pokitdetail.R import com.strayalpaca.pokitdetail.model.BottomSheetType -import com.strayalpaca.pokitdetail.paging.SimplePagingState +import pokitmons.pokit.core.feature.model.paging.PagingState import pokitmons.pokit.core.ui.components.atom.loading.LoadingProgress import pokitmons.pokit.core.ui.components.block.pokitcard.PokitCard import pokitmons.pokit.core.ui.components.template.bottomsheet.PokitBottomSheet @@ -97,10 +97,10 @@ fun PokitScreen( when (selectedCategory) { is Category.Pokit -> { when { - (pokitsState == SimplePagingState.LOADING_INIT) -> { + (pokitsState == PagingState.LOADING_INIT) -> { LoadingProgress(modifier = Modifier.fillMaxSize()) } - (pokitsState == SimplePagingState.FAILURE_INIT) -> { + (pokitsState == PagingState.FAILURE_INIT) -> { ErrorPooki( modifier = Modifier.fillMaxSize(), title = stringResource(id = coreString.title_error), @@ -144,10 +144,10 @@ fun PokitScreen( is Category.Unclassified -> { when { - (unCategoryLinksState == SimplePagingState.LOADING_INIT) -> { + (unCategoryLinksState == PagingState.LOADING_INIT) -> { LoadingProgress(modifier = Modifier.fillMaxSize()) } - (unCategoryLinksState == SimplePagingState.FAILURE_INIT) -> { + (unCategoryLinksState == PagingState.FAILURE_INIT) -> { ErrorPooki( modifier = Modifier.fillMaxSize(), title = stringResource(id = coreString.title_error), diff --git a/feature/home/src/main/java/pokitmons/pokit/home/pokit/PokitViewModel.kt b/feature/home/src/main/java/pokitmons/pokit/home/pokit/PokitViewModel.kt index edbc9059..ee98a078 100644 --- a/feature/home/src/main/java/pokitmons/pokit/home/pokit/PokitViewModel.kt +++ b/feature/home/src/main/java/pokitmons/pokit/home/pokit/PokitViewModel.kt @@ -5,8 +5,6 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.strayalpaca.pokitdetail.model.BottomSheetType import com.strayalpaca.pokitdetail.model.Pokit -import com.strayalpaca.pokitdetail.paging.LinkPaging -import com.strayalpaca.pokitdetail.paging.PokitPaging import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -16,10 +14,12 @@ import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import pokitmons.pokit.core.feature.flow.MutableEventFlow import pokitmons.pokit.core.feature.flow.asEventFlow +import pokitmons.pokit.core.feature.model.paging.PagingLoadResult +import pokitmons.pokit.core.feature.model.paging.PagingSource +import pokitmons.pokit.core.feature.model.paging.SimplePaging import pokitmons.pokit.core.feature.navigation.args.LinkUpdateEvent import pokitmons.pokit.core.feature.navigation.args.PokitUpdateEvent import pokitmons.pokit.domain.commom.PokitResult -import pokitmons.pokit.domain.model.link.Link import pokitmons.pokit.domain.model.link.LinksSort import pokitmons.pokit.domain.model.pokit.MAX_POKIT_COUNT import pokitmons.pokit.domain.model.pokit.PokitsSort @@ -60,7 +60,7 @@ class PokitViewModel @Inject constructor( val isCategoryChanged = (targetLink.pokitId != updatedLink.pokitId.toString()) if (isCategoryChanged) { - linkPaging.deleteItem(targetLink) + linkPaging.deleteItem(targetLink.id) } else { val modifiedLink = targetLink.copy( title = updatedLink.title, @@ -87,9 +87,8 @@ class PokitViewModel @Inject constructor( private fun initLinkRemoveEventDetector() { viewModelScope.launch { - LinkUpdateEvent.removedLink.collectLatest { removedLink -> - val targetLink = linkPaging.pagingData.value.find { it.id == removedLink.toString() } ?: return@collectLatest - linkPaging.deleteItem(targetLink) + LinkUpdateEvent.removedLink.collectLatest { removedLinkId -> + linkPaging.deleteItem(removedLinkId.toString()) } } } @@ -108,8 +107,7 @@ class PokitViewModel @Inject constructor( private fun initPokitRemoveEventDetector() { viewModelScope.launch { PokitUpdateEvent.removedPokitId.collectLatest { removedPokitId -> - val targetPokit = pokitPaging.pagingData.value.find { it.id == removedPokitId.toString() } ?: return@collectLatest - pokitPaging.deleteItem(targetPokit) + pokitPaging.deleteItem(removedPokitId.toString()) } } } @@ -134,28 +132,53 @@ class PokitViewModel @Inject constructor( var screenType = mutableStateOf(ScreenType.Pokit) private set - private val pokitPaging = PokitPaging( - getPokits = getPokitsUseCase, - perPage = 10, - coroutineScope = viewModelScope, - initPage = 0 + private val pokitPagingSource = object: PagingSource { + override suspend fun load(pageIndex: Int, pageSize: Int): PagingLoadResult { + val sort = when (pokitsSortOrder.value) { + PokitsSortOrder.Latest -> PokitsSort.RECENT + PokitsSortOrder.Name -> PokitsSort.ALPHABETICAL + } + val response = getPokitsUseCase.getPokits(size = pageSize, page = pageIndex, sort = sort) + return PagingLoadResult.fromPokitResult( + pokitResult = response, + mapper = { domainPokits -> domainPokits.map { Pokit.fromDomainPokit(it) }} + ) + } + } + + private val pokitPaging = SimplePaging( + pagingSource = pokitPagingSource, + getKeyFromItem = { pokit -> pokit.id }, + coroutineScope = viewModelScope ) - private val linkPaging = LinkPaging( - getLinks = ::getUncategorizedLinks, - perPage = 10, - coroutineScope = viewModelScope, - initPage = 0, - initCategoryId = 1 + private val linksPagingSource = object: PagingSource { + override suspend fun load(pageIndex: Int, pageSize: Int): PagingLoadResult { + val sort = when(linksSortOrder.value) { + UncategorizedLinksSortOrder.Latest -> LinksSort.RECENT + UncategorizedLinksSortOrder.Older -> LinksSort.OLDER + } + val response = getLinksUseCase.getUncategorizedLinks(size = pageSize, page = pageIndex, sort = sort) + return PagingLoadResult.fromPokitResult( + pokitResult = response, + mapper = { domainLinks -> domainLinks.map { DetailLink.fromDomainLink(it) } } + ) + } + } + + private val linkPaging = SimplePaging( + pagingSource = linksPagingSource, + getKeyFromItem = { link -> link.id }, + coroutineScope = viewModelScope ) val pokits: StateFlow> - get() = pokitPaging._pagingData.asStateFlow() + get() = pokitPaging.pagingData val pokitsState = pokitPaging.pagingState val unCategoryLinks: StateFlow> - get() = linkPaging._pagingData.asStateFlow() + get() = linkPaging.pagingData val linksState = linkPaging.pagingState @@ -192,19 +215,6 @@ class PokitViewModel @Inject constructor( fun updatePokitsSortOrder(order: PokitsSortOrder) { pokitsSortOrder.value = order - sortPokits() - } - - private fun sortPokits() { - when (pokitsSortOrder.value) { - is PokitsSortOrder.Name -> { - pokitPaging.changeSort(PokitsSort.ALPHABETICAL) - } - is PokitsSortOrder.Latest -> { - pokitPaging.changeSort(PokitsSort.RECENT) - } - } - viewModelScope.launch { pokitPaging.refresh() } @@ -212,32 +222,11 @@ class PokitViewModel @Inject constructor( fun updateLinksSortOrder(order: UncategorizedLinksSortOrder) { linksSortOrder.value = order - sortUncategorizedLinks() - } - - private fun sortUncategorizedLinks() { - when (linksSortOrder.value) { - is UncategorizedLinksSortOrder.Latest -> { - linkPaging.changeOptions(0, LinksSort.RECENT) - } - is UncategorizedLinksSortOrder.Older -> { - linkPaging.changeOptions(0, LinksSort.OLDER) - } - } - viewModelScope.launch { linkPaging.refresh() } } - private suspend fun getUncategorizedLinks(categoryId: Int, size: Int, page: Int, sort: LinksSort): PokitResult> { - return getLinksUseCase.getUncategorizedLinks( - size = size, - page = page, - sort = sort - ) - } - fun updateScreenType(type: ScreenType) { screenType.value = type } From b32d6fc976a4451501d008be430f5da779c00542 Mon Sep 17 00:00:00 2001 From: yunsehwan Date: Mon, 16 Sep 2024 17:02:33 +0900 Subject: [PATCH 4/9] =?UTF-8?q?[REFACTOR]=20=EC=95=8C=EB=9E=8C/=ED=8F=AC?= =?UTF-8?q?=ED=82=B7=EC=B6=94=EA=B0=80/=EB=A7=81=ED=81=AC=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=ED=99=94=EB=A9=B4=EC=97=90=EC=84=9C=20core:feature?= =?UTF-8?q?=EB=AA=A8=EB=93=88=EC=97=90=20=EC=84=A0=EC=96=B8=ED=95=9C=20Sim?= =?UTF-8?q?plePaging=EC=9D=84=20=EC=82=AC=EC=9A=A9=ED=95=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/strayalpaca/addlink/AddLinkScreen.kt | 4 +- .../strayalpaca/addlink/AddLinkViewModel.kt | 27 ++-- .../strayalpaca/addlink/paging/PokitPaging.kt | 125 ------------------ .../addlink/paging/SimplePaging.kt | 13 -- .../addlink/paging/SimplePagingState.kt | 5 - .../strayalpaca/addpokit/AddPokitScreen.kt | 11 +- .../strayalpaca/addpokit/AddPokitViewModel.kt | 27 ++-- .../addpokit/paging/PokitPaging.kt | 124 ----------------- .../addpokit/paging/SimplePaging.kt | 13 -- .../addpokit/paging/SimplePagingState.kt | 5 - feature/alarm/build.gradle.kts | 1 + .../java/pokitmons/pokit/alarm/AlarmScreen.kt | 10 +- .../pokitmons/pokit/alarm/AlarmViewModel.kt | 32 +++-- .../pokit/alarm/paging/AlarmPaging.kt | 125 ------------------ .../pokit/alarm/paging/SimplePaging.kt | 13 -- .../pokit/alarm/paging/SimplePagingState.kt | 5 - 16 files changed, 75 insertions(+), 465 deletions(-) delete mode 100644 feature/addlink/src/main/java/com/strayalpaca/addlink/paging/PokitPaging.kt delete mode 100644 feature/addlink/src/main/java/com/strayalpaca/addlink/paging/SimplePaging.kt delete mode 100644 feature/addlink/src/main/java/com/strayalpaca/addlink/paging/SimplePagingState.kt delete mode 100644 feature/addpokit/src/main/java/com/strayalpaca/addpokit/paging/PokitPaging.kt delete mode 100644 feature/addpokit/src/main/java/com/strayalpaca/addpokit/paging/SimplePaging.kt delete mode 100644 feature/addpokit/src/main/java/com/strayalpaca/addpokit/paging/SimplePagingState.kt delete mode 100644 feature/alarm/src/main/java/pokitmons/pokit/alarm/paging/AlarmPaging.kt delete mode 100644 feature/alarm/src/main/java/pokitmons/pokit/alarm/paging/SimplePaging.kt delete mode 100644 feature/alarm/src/main/java/pokitmons/pokit/alarm/paging/SimplePagingState.kt diff --git a/feature/addlink/src/main/java/com/strayalpaca/addlink/AddLinkScreen.kt b/feature/addlink/src/main/java/com/strayalpaca/addlink/AddLinkScreen.kt index f08baec8..731a551b 100644 --- a/feature/addlink/src/main/java/com/strayalpaca/addlink/AddLinkScreen.kt +++ b/feature/addlink/src/main/java/com/strayalpaca/addlink/AddLinkScreen.kt @@ -36,10 +36,10 @@ import com.strayalpaca.addlink.model.AddLinkScreenSideEffect import com.strayalpaca.addlink.model.AddLinkScreenState import com.strayalpaca.addlink.model.ScreenStep import com.strayalpaca.addlink.model.ToastMessageEvent -import com.strayalpaca.addlink.paging.SimplePagingState import com.strayalpaca.addlink.utils.BackPressHandler import org.orbitmvi.orbit.compose.collectAsState import org.orbitmvi.orbit.compose.collectSideEffect +import pokitmons.pokit.core.feature.model.paging.PagingState import pokitmons.pokit.core.ui.components.atom.button.PokitButton import pokitmons.pokit.core.ui.components.atom.button.attributes.PokitButtonIcon import pokitmons.pokit.core.ui.components.atom.button.attributes.PokitButtonIconPosition @@ -106,7 +106,7 @@ fun AddLinkScreenContainer( } LaunchedEffect(startPaging.value) { - if (startPaging.value && pokitListState == SimplePagingState.IDLE) { + if (startPaging.value && pokitListState == PagingState.IDLE) { viewModel.loadNextPokits() } } diff --git a/feature/addlink/src/main/java/com/strayalpaca/addlink/AddLinkViewModel.kt b/feature/addlink/src/main/java/com/strayalpaca/addlink/AddLinkViewModel.kt index 2c089666..4cd939b2 100644 --- a/feature/addlink/src/main/java/com/strayalpaca/addlink/AddLinkViewModel.kt +++ b/feature/addlink/src/main/java/com/strayalpaca/addlink/AddLinkViewModel.kt @@ -9,8 +9,6 @@ import com.strayalpaca.addlink.model.Link import com.strayalpaca.addlink.model.Pokit import com.strayalpaca.addlink.model.ScreenStep import com.strayalpaca.addlink.model.ToastMessageEvent -import com.strayalpaca.addlink.paging.PokitPaging -import com.strayalpaca.addlink.paging.SimplePagingState import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -27,6 +25,10 @@ import org.orbitmvi.orbit.syntax.simple.intent import org.orbitmvi.orbit.syntax.simple.postSideEffect import org.orbitmvi.orbit.syntax.simple.reduce import org.orbitmvi.orbit.viewmodel.container +import pokitmons.pokit.core.feature.model.paging.PagingLoadResult +import pokitmons.pokit.core.feature.model.paging.PagingSource +import pokitmons.pokit.core.feature.model.paging.PagingState +import pokitmons.pokit.core.feature.model.paging.SimplePaging import pokitmons.pokit.core.feature.navigation.args.LinkArg import pokitmons.pokit.core.feature.navigation.args.LinkUpdateEvent import pokitmons.pokit.core.feature.navigation.args.PokitUpdateEvent @@ -54,15 +56,24 @@ class AddLinkViewModel @Inject constructor( ) : ContainerHost, ViewModel() { override val container: Container = container(AddLinkScreenState()) - private val pokitPaging = PokitPaging( - getPokits = getPokitsUseCase, - perPage = 10, - coroutineScope = viewModelScope, - initPage = 0 + private val pokitPagingSource = object: PagingSource { + override suspend fun load(pageIndex: Int, pageSize: Int): PagingLoadResult { + val response = getPokitsUseCase.getPokits(page = pageIndex, size = pageSize) + return PagingLoadResult.fromPokitResult( + pokitResult = response, + mapper = { domainPokits -> domainPokits.map { Pokit.fromDomainPokit(it) } } + ) + } + } + + private val pokitPaging = SimplePaging( + pagingSource = pokitPagingSource, + getKeyFromItem = { pokit -> pokit.id }, + coroutineScope = viewModelScope ) val pokitList: StateFlow> = pokitPaging.pagingData - val pokitListState: StateFlow = pokitPaging.pagingState + val pokitListState: StateFlow = pokitPaging.pagingState private val _linkUrl = MutableStateFlow("") val linkUrl: StateFlow = _linkUrl.asStateFlow() diff --git a/feature/addlink/src/main/java/com/strayalpaca/addlink/paging/PokitPaging.kt b/feature/addlink/src/main/java/com/strayalpaca/addlink/paging/PokitPaging.kt deleted file mode 100644 index 581f5f91..00000000 --- a/feature/addlink/src/main/java/com/strayalpaca/addlink/paging/PokitPaging.kt +++ /dev/null @@ -1,125 +0,0 @@ -package com.strayalpaca.addlink.paging - -import com.strayalpaca.addlink.model.Pokit -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.update -import kotlinx.coroutines.launch -import pokitmons.pokit.domain.commom.PokitResult -import pokitmons.pokit.domain.usecase.pokit.GetPokitsUseCase -import kotlin.coroutines.cancellation.CancellationException - -class PokitPaging( - private val getPokits: GetPokitsUseCase, - private val perPage: Int = 10, - private val coroutineScope: CoroutineScope = CoroutineScope(Dispatchers.IO), - private val initPage: Int = 0, - private val firstRequestPage: Int = 3, - private val showUncategorizedPokit: Boolean = true, -) : SimplePaging { - private val _pagingState = MutableStateFlow(SimplePagingState.IDLE) - override val pagingState: StateFlow = _pagingState.asStateFlow() - - private val _pagingData: MutableStateFlow> = MutableStateFlow(emptyList()) - override val pagingData: StateFlow> = _pagingData.asStateFlow() - private var currentPageIndex = initPage - private var requestJob: Job? = null - - override suspend fun refresh() { - requestJob?.cancel() - - _pagingData.update { emptyList() } - _pagingState.update { SimplePagingState.LOADING_INIT } - requestJob = coroutineScope.launch { - try { - currentPageIndex = initPage - val response = getPokits.getPokits(size = perPage * firstRequestPage, page = currentPageIndex, filterUncategorized = !showUncategorizedPokit) - when (response) { - is PokitResult.Success -> { - val pokitList = response.result.map { domainPokit -> - Pokit.fromDomainPokit(domainPokit) - } - applyResponse(pokitList, firstRequestPage) - } - - is PokitResult.Error -> { - _pagingState.update { SimplePagingState.FAILURE_INIT } - } - } - } catch (exception: Exception) { - if (exception !is CancellationException) { - _pagingState.update { SimplePagingState.FAILURE_INIT } - } - } - } - } - - override suspend fun load() { - if (pagingState.value != SimplePagingState.IDLE) return - - requestJob?.cancel() - _pagingState.update { SimplePagingState.LOADING_NEXT } - - requestJob = coroutineScope.launch { - try { - val response = getPokits.getPokits(size = perPage, page = currentPageIndex, filterUncategorized = !showUncategorizedPokit) - when (response) { - is PokitResult.Success -> { - val pokitList = response.result.map { domainPokit -> - Pokit.fromDomainPokit(domainPokit) - } - applyResponse(pokitList) - } - - is PokitResult.Error -> { - _pagingState.update { SimplePagingState.FAILURE_NEXT } - } - } - } catch (exception: Exception) { - if (exception !is CancellationException) { - _pagingState.update { SimplePagingState.FAILURE_NEXT } - } - } - } - } - - private fun applyResponse(dataInResponse: List, multiple: Int = 1) { - if (dataInResponse.size < perPage * multiple) { - _pagingState.update { SimplePagingState.LAST } - } else { - _pagingState.update { SimplePagingState.IDLE } - } - _pagingData.update { _pagingData.value + dataInResponse } - currentPageIndex += multiple - } - - override fun clear() { - requestJob?.cancel() - _pagingData.update { emptyList() } - _pagingState.update { SimplePagingState.IDLE } - } - - override suspend fun deleteItem(item: Pokit) { - val capturedDataList = _pagingData.value - _pagingData.update { capturedDataList.filter { it.id != item.id } } - } - - override suspend fun modifyItem(item: Pokit) { - val capturedDataList = _pagingData.value - val targetPokit = capturedDataList.find { it.id == item.id } ?: return - - _pagingData.update { - capturedDataList.map { pokit -> - if (targetPokit.id == pokit.id) { - item - } else { - pokit - } - } - } - } -} diff --git a/feature/addlink/src/main/java/com/strayalpaca/addlink/paging/SimplePaging.kt b/feature/addlink/src/main/java/com/strayalpaca/addlink/paging/SimplePaging.kt deleted file mode 100644 index e89daa6a..00000000 --- a/feature/addlink/src/main/java/com/strayalpaca/addlink/paging/SimplePaging.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.strayalpaca.addlink.paging - -import kotlinx.coroutines.flow.Flow - -interface SimplePaging { - val pagingData: Flow> - suspend fun refresh() - suspend fun load() - val pagingState: Flow - suspend fun modifyItem(item: T) - suspend fun deleteItem(item: T) - fun clear() -} diff --git a/feature/addlink/src/main/java/com/strayalpaca/addlink/paging/SimplePagingState.kt b/feature/addlink/src/main/java/com/strayalpaca/addlink/paging/SimplePagingState.kt deleted file mode 100644 index 4c1bfc2c..00000000 --- a/feature/addlink/src/main/java/com/strayalpaca/addlink/paging/SimplePagingState.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.strayalpaca.addlink.paging - -enum class SimplePagingState { - IDLE, LOADING_NEXT, LOADING_INIT, FAILURE_NEXT, FAILURE_INIT, LAST -} diff --git a/feature/addpokit/src/main/java/com/strayalpaca/addpokit/AddPokitScreen.kt b/feature/addpokit/src/main/java/com/strayalpaca/addpokit/AddPokitScreen.kt index aa5029f7..a2c87285 100644 --- a/feature/addpokit/src/main/java/com/strayalpaca/addpokit/AddPokitScreen.kt +++ b/feature/addpokit/src/main/java/com/strayalpaca/addpokit/AddPokitScreen.kt @@ -46,9 +46,10 @@ import com.strayalpaca.addpokit.model.AddPokitScreenStep import com.strayalpaca.addpokit.model.AddPokitSideEffect import com.strayalpaca.addpokit.model.Pokit import com.strayalpaca.addpokit.model.PokitImage -import com.strayalpaca.addpokit.paging.SimplePagingState import com.strayalpaca.addpokit.utils.BackPressHandler import org.orbitmvi.orbit.compose.collectSideEffect +import pokitmons.pokit.core.feature.model.NetworkState +import pokitmons.pokit.core.feature.model.paging.PagingState import pokitmons.pokit.core.ui.components.atom.button.PokitButton import pokitmons.pokit.core.ui.components.atom.button.attributes.PokitButtonSize import pokitmons.pokit.core.ui.components.block.labeledinput.LabeledInput @@ -122,10 +123,10 @@ fun AddPokitScreen( selectPokitProfileImage: (PokitImage) -> Unit = {}, hideToastMessage: () -> Unit = {}, pokits: List = emptyList(), - pokitsState: SimplePagingState = SimplePagingState.IDLE, + pokitsState: PagingState = PagingState.IDLE, loadPokits: () -> Unit = {}, pokitImages: List = emptyList(), - pokitImagesState: SimplePagingState = SimplePagingState.IDLE, + pokitImagesState: NetworkState = NetworkState.IDLE, ) { Column( modifier = Modifier @@ -228,7 +229,7 @@ fun AddPokitScreen( } LaunchedEffect(startPokitPaging.value) { - if (startPokitPaging.value && pokitsState == SimplePagingState.IDLE) { + if (startPokitPaging.value && pokitsState == PagingState.IDLE) { loadPokits() } } @@ -248,7 +249,7 @@ fun AddPokitScreen( } } - if (pokitImagesState == SimplePagingState.LOADING_INIT) { + if (pokitImagesState == NetworkState.LOADING) { CircularProgressIndicator( modifier = Modifier.width(64.dp), color = PokitTheme.colors.brand, diff --git a/feature/addpokit/src/main/java/com/strayalpaca/addpokit/AddPokitViewModel.kt b/feature/addpokit/src/main/java/com/strayalpaca/addpokit/AddPokitViewModel.kt index d032f3bd..40c8a6ba 100644 --- a/feature/addpokit/src/main/java/com/strayalpaca/addpokit/AddPokitViewModel.kt +++ b/feature/addpokit/src/main/java/com/strayalpaca/addpokit/AddPokitViewModel.kt @@ -9,8 +9,6 @@ import com.strayalpaca.addpokit.model.AddPokitScreenStep import com.strayalpaca.addpokit.model.AddPokitSideEffect import com.strayalpaca.addpokit.model.Pokit import com.strayalpaca.addpokit.model.PokitImage -import com.strayalpaca.addpokit.paging.PokitPaging -import com.strayalpaca.addpokit.paging.SimplePagingState import com.strayalpaca.addpokit.utils.ErrorMessageProvider import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow @@ -24,6 +22,10 @@ import org.orbitmvi.orbit.syntax.simple.intent import org.orbitmvi.orbit.syntax.simple.postSideEffect import org.orbitmvi.orbit.syntax.simple.reduce import org.orbitmvi.orbit.viewmodel.container +import pokitmons.pokit.core.feature.model.paging.PagingLoadResult +import pokitmons.pokit.core.feature.model.paging.PagingSource +import pokitmons.pokit.core.feature.model.paging.PagingState +import pokitmons.pokit.core.feature.model.paging.SimplePaging import pokitmons.pokit.core.feature.navigation.args.PokitArg import pokitmons.pokit.core.feature.navigation.args.PokitUpdateEvent import pokitmons.pokit.domain.commom.PokitResult @@ -49,18 +51,27 @@ class AddPokitViewModel @Inject constructor( private val pokitId = savedStateHandle.get("pokit_id")?.toIntOrNull() - private val pokitPaging = PokitPaging( - getPokits = getPokitsUseCase, - perPage = 10, - coroutineScope = viewModelScope, - initPage = 0 + private val pokitPagingSource = object: PagingSource { + override suspend fun load(pageIndex: Int, pageSize: Int): PagingLoadResult { + val response = getPokitsUseCase.getPokits(page = pageIndex, size = pageSize) + return PagingLoadResult.fromPokitResult( + pokitResult = response, + mapper = { domainPokits -> domainPokits.map { Pokit.fromDomainPokit(it) } } + ) + } + } + + private val pokitPaging = SimplePaging( + pagingSource = pokitPagingSource, + getKeyFromItem = { pokit -> pokit.id }, + coroutineScope = viewModelScope ) private val _pokitName = MutableStateFlow("") val pokitName: StateFlow = _pokitName.asStateFlow() val pokitList: StateFlow> = pokitPaging.pagingData - val pokitListState: StateFlow = pokitPaging.pagingState + val pokitListState: StateFlow = pokitPaging.pagingState private val _pokitIamges = MutableStateFlow>(emptyList()) val pokitImages: StateFlow> = _pokitIamges.asStateFlow() diff --git a/feature/addpokit/src/main/java/com/strayalpaca/addpokit/paging/PokitPaging.kt b/feature/addpokit/src/main/java/com/strayalpaca/addpokit/paging/PokitPaging.kt deleted file mode 100644 index e2661e50..00000000 --- a/feature/addpokit/src/main/java/com/strayalpaca/addpokit/paging/PokitPaging.kt +++ /dev/null @@ -1,124 +0,0 @@ -package com.strayalpaca.addpokit.paging - -import com.strayalpaca.addpokit.model.Pokit -import kotlinx.coroutines.CancellationException -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.update -import kotlinx.coroutines.launch -import pokitmons.pokit.domain.commom.PokitResult -import pokitmons.pokit.domain.usecase.pokit.GetPokitsUseCase - -class PokitPaging( - private val getPokits: GetPokitsUseCase, - private val perPage: Int = 10, - private val coroutineScope: CoroutineScope = CoroutineScope(Dispatchers.IO), - private val initPage: Int = 0, - private val firstRequestPage: Int = 3, -) : SimplePaging { - private val _pagingState = MutableStateFlow(SimplePagingState.IDLE) - override val pagingState: StateFlow = _pagingState.asStateFlow() - - private val _pagingData: MutableStateFlow> = MutableStateFlow(emptyList()) - override val pagingData: StateFlow> = _pagingData.asStateFlow() - private var currentPageIndex = initPage - private var requestJob: Job? = null - - override suspend fun refresh() { - requestJob?.cancel() - - _pagingData.update { emptyList() } - _pagingState.update { SimplePagingState.LOADING_INIT } - requestJob = coroutineScope.launch { - try { - currentPageIndex = initPage - val response = getPokits.getPokits(size = perPage * firstRequestPage, page = currentPageIndex) - when (response) { - is PokitResult.Success -> { - val pokitList = response.result.map { domainPokit -> - Pokit.fromDomainPokit(domainPokit) - } - applyResponse(pokitList, firstRequestPage) - } - - is PokitResult.Error -> { - _pagingState.update { SimplePagingState.FAILURE_INIT } - } - } - } catch (exception: Exception) { - if (exception !is CancellationException) { - _pagingState.update { SimplePagingState.FAILURE_INIT } - } - } - } - } - - override suspend fun load() { - if (pagingState.value != SimplePagingState.IDLE) return - - requestJob?.cancel() - _pagingState.update { SimplePagingState.LOADING_NEXT } - - requestJob = coroutineScope.launch { - try { - val response = getPokits.getPokits(size = perPage, page = currentPageIndex) - when (response) { - is PokitResult.Success -> { - val pokitList = response.result.map { domainPokit -> - Pokit.fromDomainPokit(domainPokit) - } - applyResponse(pokitList) - } - - is PokitResult.Error -> { - _pagingState.update { SimplePagingState.FAILURE_NEXT } - } - } - } catch (exception: Exception) { - if (exception !is CancellationException) { - _pagingState.update { SimplePagingState.FAILURE_NEXT } - } - } - } - } - - private fun applyResponse(dataInResponse: List, multiple: Int = 1) { - if (dataInResponse.size < perPage * multiple) { - _pagingState.update { SimplePagingState.LAST } - } else { - _pagingState.update { SimplePagingState.IDLE } - } - _pagingData.update { _pagingData.value + dataInResponse } - currentPageIndex += multiple - } - - override fun clear() { - requestJob?.cancel() - _pagingData.update { emptyList() } - _pagingState.update { SimplePagingState.IDLE } - } - - override suspend fun deleteItem(item: Pokit) { - val capturedDataList = _pagingData.value - _pagingData.update { capturedDataList.filter { it.id != item.id } } - } - - override suspend fun modifyItem(item: Pokit) { - val capturedDataList = _pagingData.value - val targetPokit = capturedDataList.find { it.id == item.id } ?: return - - _pagingData.update { - capturedDataList.map { pokit -> - if (targetPokit.id == pokit.id) { - item - } else { - pokit - } - } - } - } -} diff --git a/feature/addpokit/src/main/java/com/strayalpaca/addpokit/paging/SimplePaging.kt b/feature/addpokit/src/main/java/com/strayalpaca/addpokit/paging/SimplePaging.kt deleted file mode 100644 index 510374f3..00000000 --- a/feature/addpokit/src/main/java/com/strayalpaca/addpokit/paging/SimplePaging.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.strayalpaca.addpokit.paging - -import kotlinx.coroutines.flow.Flow - -interface SimplePaging { - val pagingData: Flow> - suspend fun refresh() - suspend fun load() - val pagingState: Flow - suspend fun modifyItem(item: T) - suspend fun deleteItem(item: T) - fun clear() -} diff --git a/feature/addpokit/src/main/java/com/strayalpaca/addpokit/paging/SimplePagingState.kt b/feature/addpokit/src/main/java/com/strayalpaca/addpokit/paging/SimplePagingState.kt deleted file mode 100644 index 6ac52825..00000000 --- a/feature/addpokit/src/main/java/com/strayalpaca/addpokit/paging/SimplePagingState.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.strayalpaca.addpokit.paging - -enum class SimplePagingState { - IDLE, LOADING_NEXT, LOADING_INIT, FAILURE_NEXT, FAILURE_INIT, LAST -} diff --git a/feature/alarm/build.gradle.kts b/feature/alarm/build.gradle.kts index 71d3f2d6..e47acf4a 100644 --- a/feature/alarm/build.gradle.kts +++ b/feature/alarm/build.gradle.kts @@ -70,5 +70,6 @@ dependencies { implementation(libs.coil.compose) implementation(project(":core:ui")) + implementation(project(":core:feature")) implementation(project(":domain")) } diff --git a/feature/alarm/src/main/java/pokitmons/pokit/alarm/AlarmScreen.kt b/feature/alarm/src/main/java/pokitmons/pokit/alarm/AlarmScreen.kt index 965b327a..a8d00c6b 100644 --- a/feature/alarm/src/main/java/pokitmons/pokit/alarm/AlarmScreen.kt +++ b/feature/alarm/src/main/java/pokitmons/pokit/alarm/AlarmScreen.kt @@ -18,7 +18,7 @@ import androidx.compose.ui.res.stringResource import pokitmons.pokit.alarm.components.alarmitem.AlarmItem import pokitmons.pokit.alarm.components.toolbar.Toolbar import pokitmons.pokit.alarm.model.Alarm -import pokitmons.pokit.alarm.paging.SimplePagingState +import pokitmons.pokit.core.feature.model.paging.PagingState import pokitmons.pokit.core.ui.components.atom.loading.LoadingProgress import pokitmons.pokit.core.ui.components.template.pooki.Pooki import pokitmons.pokit.core.ui.components.template.pookierror.ErrorPooki @@ -56,7 +56,7 @@ fun AlarmScreen( onClickAlarm: (String) -> Unit = {}, onClickAlarmRemove: (String) -> Unit = {}, alarms: List = emptyList(), - alarmsState: SimplePagingState = SimplePagingState.IDLE, + alarmsState: PagingState = PagingState.IDLE, loadNextAlarms: () -> Unit = {}, refreshAlarms: () -> Unit = {}, ) { @@ -78,20 +78,20 @@ fun AlarmScreen( } LaunchedEffect(startAlarmPaging.value) { - if (startAlarmPaging.value && alarmsState == SimplePagingState.IDLE) { + if (startAlarmPaging.value && alarmsState == PagingState.IDLE) { loadNextAlarms() } } when { - alarmsState == SimplePagingState.LOADING_INIT -> { + alarmsState == PagingState.LOADING_INIT -> { LoadingProgress( modifier = Modifier .fillMaxWidth() .weight(1f) ) } - alarmsState == SimplePagingState.FAILURE_INIT -> { + alarmsState == PagingState.FAILURE_INIT -> { ErrorPooki( modifier = Modifier .fillMaxWidth() diff --git a/feature/alarm/src/main/java/pokitmons/pokit/alarm/AlarmViewModel.kt b/feature/alarm/src/main/java/pokitmons/pokit/alarm/AlarmViewModel.kt index 8da4f137..d5d0bd2e 100644 --- a/feature/alarm/src/main/java/pokitmons/pokit/alarm/AlarmViewModel.kt +++ b/feature/alarm/src/main/java/pokitmons/pokit/alarm/AlarmViewModel.kt @@ -6,8 +6,10 @@ import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch import pokitmons.pokit.alarm.model.Alarm -import pokitmons.pokit.alarm.paging.AlarmPaging -import pokitmons.pokit.alarm.paging.SimplePagingState +import pokitmons.pokit.core.feature.model.paging.PagingLoadResult +import pokitmons.pokit.core.feature.model.paging.PagingSource +import pokitmons.pokit.core.feature.model.paging.PagingState +import pokitmons.pokit.core.feature.model.paging.SimplePaging import pokitmons.pokit.domain.commom.PokitResult import pokitmons.pokit.domain.usecase.alert.DeleteAlertUseCase import pokitmons.pokit.domain.usecase.alert.GetAlertsUseCase @@ -15,14 +17,28 @@ import javax.inject.Inject @HiltViewModel class AlarmViewModel @Inject constructor( - getAlertsUseCase: GetAlertsUseCase, + private val getAlertsUseCase: GetAlertsUseCase, private val deleteAlertUseCase: DeleteAlertUseCase, ) : ViewModel() { - private val alarmPaging = AlarmPaging(getAlertsUseCase = getAlertsUseCase) + private val alarmPagingSource = object: PagingSource { + override suspend fun load(pageIndex: Int, pageSize: Int): PagingLoadResult { + val response = getAlertsUseCase.getAlerts(page = pageIndex, size = pageSize) + return PagingLoadResult.fromPokitResult( + pokitResult = response, + mapper = { alerts -> alerts.map { Alarm.fromDomainAlarm(it) } } + ) + } + } + + private val alarmPaging = SimplePaging( + pagingSource = alarmPagingSource, + getKeyFromItem = { alarm -> alarm.id }, + coroutineScope = viewModelScope + ) val alarms: StateFlow> = alarmPaging.pagingData - val alarmsState: StateFlow = alarmPaging.pagingState + val alarmsState: StateFlow = alarmPaging.pagingState init { viewModelScope.launch { @@ -36,9 +52,7 @@ class AlarmViewModel @Inject constructor( val response = deleteAlertUseCase.deleteAlert(id) if (response is PokitResult.Success) { viewModelScope.launch { - alarms.value.find { it.id == alarmId }?.let { targetItem -> - alarmPaging.deleteItem(targetItem) - } + alarmPaging.deleteItem(alarmId) } } } @@ -60,7 +74,7 @@ class AlarmViewModel @Inject constructor( val targetAlarm = alarms.value.find { it.id == alarmId } ?: return viewModelScope.launch { - alarmPaging.modifyItem(item = targetAlarm.copy(read = true)) + alarmPaging.modifyItem(targetItem = targetAlarm.copy(read = true)) } } } diff --git a/feature/alarm/src/main/java/pokitmons/pokit/alarm/paging/AlarmPaging.kt b/feature/alarm/src/main/java/pokitmons/pokit/alarm/paging/AlarmPaging.kt deleted file mode 100644 index b2d21c1a..00000000 --- a/feature/alarm/src/main/java/pokitmons/pokit/alarm/paging/AlarmPaging.kt +++ /dev/null @@ -1,125 +0,0 @@ -package pokitmons.pokit.alarm.paging - -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.update -import kotlinx.coroutines.launch -import pokitmons.pokit.alarm.model.Alarm -import pokitmons.pokit.domain.commom.PokitResult -import pokitmons.pokit.domain.usecase.alert.GetAlertsUseCase -import kotlin.coroutines.cancellation.CancellationException - -class AlarmPaging( - private val getAlertsUseCase: GetAlertsUseCase, - private val perPage: Int = 10, - private val coroutineScope: CoroutineScope = CoroutineScope(Dispatchers.IO), - private val initPage: Int = 0, - private val firstRequestPage: Int = 3, -) : SimplePaging { - private val _pagingData: MutableStateFlow> = MutableStateFlow(emptyList()) - override val pagingData: StateFlow> = _pagingData.asStateFlow() - - private val _pagingState: MutableStateFlow = MutableStateFlow(SimplePagingState.IDLE) - override val pagingState: StateFlow = _pagingState.asStateFlow() - - private var currentPageIndex = initPage - private var requestJob: Job? = null - - override suspend fun refresh() { - requestJob?.cancel() - - _pagingData.update { emptyList() } - _pagingState.update { SimplePagingState.LOADING_INIT } - requestJob = coroutineScope.launch { - try { - currentPageIndex = initPage - val response = getAlertsUseCase.getAlerts(size = perPage * firstRequestPage, page = currentPageIndex) - when (response) { - is PokitResult.Success -> { - val alarms = response.result.map { domainAlarm -> - Alarm.fromDomainAlarm(domainAlarm) - } - applyResponse(alarms, firstRequestPage) - } - - is PokitResult.Error -> { - _pagingState.update { SimplePagingState.FAILURE_INIT } - } - } - } catch (exception: Exception) { - if (exception !is CancellationException) { - _pagingState.update { SimplePagingState.FAILURE_INIT } - } - } - } - } - - override suspend fun load() { - if (pagingState.value != SimplePagingState.IDLE) return - - requestJob?.cancel() - _pagingState.update { SimplePagingState.LOADING_NEXT } - - requestJob = coroutineScope.launch { - try { - val response = getAlertsUseCase.getAlerts(size = perPage, page = currentPageIndex) - when (response) { - is PokitResult.Success -> { - val alarms = response.result.map { domainAlarm -> - Alarm.fromDomainAlarm(domainAlarm) - } - applyResponse(alarms) - } - - is PokitResult.Error -> { - _pagingState.update { SimplePagingState.FAILURE_NEXT } - } - } - } catch (exception: Exception) { - if (exception !is CancellationException) { - _pagingState.update { SimplePagingState.FAILURE_NEXT } - } - } - } - } - - private fun applyResponse(dataInResponse: List, multiple: Int = 1) { - if (dataInResponse.size < perPage * multiple) { - _pagingState.update { SimplePagingState.LAST } - } else { - _pagingState.update { SimplePagingState.IDLE } - } - _pagingData.update { _pagingData.value + dataInResponse } - currentPageIndex += multiple - } - - override fun clear() { - requestJob?.cancel() - _pagingData.update { emptyList() } - _pagingState.update { SimplePagingState.IDLE } - } - - override suspend fun deleteItem(item: Alarm) { - val capturedDataList = _pagingData.value - _pagingData.update { capturedDataList.filter { it.id != item.id } } - } - - override suspend fun modifyItem(item: Alarm) { - val capturedDataList = _pagingData.value - val targetData = capturedDataList.find { it.id == item.id } ?: return - - _pagingData.update { - capturedDataList.map { data -> - if (targetData.id == data.id) { - item - } else { - data - } - } - } - } -} diff --git a/feature/alarm/src/main/java/pokitmons/pokit/alarm/paging/SimplePaging.kt b/feature/alarm/src/main/java/pokitmons/pokit/alarm/paging/SimplePaging.kt deleted file mode 100644 index 8062d91c..00000000 --- a/feature/alarm/src/main/java/pokitmons/pokit/alarm/paging/SimplePaging.kt +++ /dev/null @@ -1,13 +0,0 @@ -package pokitmons.pokit.alarm.paging - -import kotlinx.coroutines.flow.Flow - -interface SimplePaging { - val pagingData: Flow> - suspend fun refresh() - suspend fun load() - val pagingState: Flow - suspend fun modifyItem(item: T) - suspend fun deleteItem(item: T) - fun clear() -} diff --git a/feature/alarm/src/main/java/pokitmons/pokit/alarm/paging/SimplePagingState.kt b/feature/alarm/src/main/java/pokitmons/pokit/alarm/paging/SimplePagingState.kt deleted file mode 100644 index 29aeb08e..00000000 --- a/feature/alarm/src/main/java/pokitmons/pokit/alarm/paging/SimplePagingState.kt +++ /dev/null @@ -1,5 +0,0 @@ -package pokitmons.pokit.alarm.paging - -enum class SimplePagingState { - IDLE, LOADING_NEXT, LOADING_INIT, FAILURE_NEXT, FAILURE_INIT, LAST -} From f05a35747319f73bdf6fa745cb8165629f975e4c Mon Sep 17 00:00:00 2001 From: yunsehwan Date: Mon, 16 Sep 2024 17:03:38 +0900 Subject: [PATCH 5/9] =?UTF-8?q?[REFACTOR]=20=EA=B2=80=EC=83=89=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4=EC=97=90=EC=84=9C=20core:feature=EB=AA=A8=EB=93=88?= =?UTF-8?q?=EC=97=90=20=EC=84=A0=EC=96=B8=ED=95=9C=20SimplePaging=EC=9D=84?= =?UTF-8?q?=20=EC=82=AC=EC=9A=A9=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EB=B0=8F=20=EC=B5=9C=EC=8B=A0=EC=88=9C/=EC=98=A4?= =?UTF-8?q?=EB=9E=98=EB=90=9C=EC=88=9C=20=EC=A0=95=EB=A0=AC=EC=9D=B4=20?= =?UTF-8?q?=EC=95=88=EB=90=98=EB=8D=98=20=EB=AC=B8=EC=A0=9C=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 --- .../pokitmons/pokit/search/SearchScreen.kt | 8 +- .../pokitmons/pokit/search/SearchViewModel.kt | 77 ++++++--- .../filterbottomsheet/FilterBottomSheet.kt | 4 +- .../FilterBottomSheetContent.kt | 6 +- .../searchitemlist/SearchItemList.kt | 6 +- .../pokit/search/paging/LinkPaging.kt | 160 ------------------ .../pokit/search/paging/PokitPaging.kt | 124 -------------- .../pokit/search/paging/SimplePaging.kt | 13 -- .../pokit/search/paging/SimplePagingState.kt | 5 - 9 files changed, 67 insertions(+), 336 deletions(-) delete mode 100644 feature/search/src/main/java/pokitmons/pokit/search/paging/LinkPaging.kt delete mode 100644 feature/search/src/main/java/pokitmons/pokit/search/paging/PokitPaging.kt delete mode 100644 feature/search/src/main/java/pokitmons/pokit/search/paging/SimplePaging.kt delete mode 100644 feature/search/src/main/java/pokitmons/pokit/search/paging/SimplePagingState.kt diff --git a/feature/search/src/main/java/pokitmons/pokit/search/SearchScreen.kt b/feature/search/src/main/java/pokitmons/pokit/search/SearchScreen.kt index 0f6de2c0..c7dd4e0d 100644 --- a/feature/search/src/main/java/pokitmons/pokit/search/SearchScreen.kt +++ b/feature/search/src/main/java/pokitmons/pokit/search/SearchScreen.kt @@ -16,6 +16,7 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import coil.compose.rememberAsyncImagePainter +import pokitmons.pokit.core.feature.model.paging.PagingState import pokitmons.pokit.core.ui.components.atom.loading.LoadingProgress import pokitmons.pokit.core.ui.components.template.bottomsheet.PokitBottomSheet import pokitmons.pokit.core.ui.components.template.linkdetailbottomsheet.LinkDetailBottomSheet @@ -35,7 +36,6 @@ import pokitmons.pokit.search.model.FilterType import pokitmons.pokit.search.model.Link import pokitmons.pokit.search.model.SearchScreenState import pokitmons.pokit.search.model.SearchScreenStep -import pokitmons.pokit.search.paging.SimplePagingState import pokitmons.pokit.core.ui.R.string as coreString @Composable @@ -167,7 +167,7 @@ fun SearchScreen( state: SearchScreenState = SearchScreenState(), currentSearchWord: String = "", linkList: List = emptyList(), - linkPagingState: SimplePagingState = SimplePagingState.IDLE, + linkPagingState: PagingState = PagingState.IDLE, onClickBack: () -> Unit = {}, inputSearchWord: (String) -> Unit = {}, onClickSearch: () -> Unit = {}, @@ -223,14 +223,14 @@ fun SearchScreen( if (state.step == SearchScreenStep.RESULT) { when { - (linkPagingState == SimplePagingState.LOADING_INIT) -> { + (linkPagingState == PagingState.LOADING_INIT) -> { LoadingProgress( modifier = Modifier .fillMaxWidth() .weight(1f) ) } - (linkPagingState == SimplePagingState.FAILURE_INIT) -> { + (linkPagingState == PagingState.FAILURE_INIT) -> { ErrorPooki( modifier = Modifier .fillMaxWidth() diff --git a/feature/search/src/main/java/pokitmons/pokit/search/SearchViewModel.kt b/feature/search/src/main/java/pokitmons/pokit/search/SearchViewModel.kt index fc205e8a..0281d6ee 100644 --- a/feature/search/src/main/java/pokitmons/pokit/search/SearchViewModel.kt +++ b/feature/search/src/main/java/pokitmons/pokit/search/SearchViewModel.kt @@ -12,8 +12,13 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch +import pokitmons.pokit.core.feature.model.paging.PagingLoadResult +import pokitmons.pokit.core.feature.model.paging.PagingSource +import pokitmons.pokit.core.feature.model.paging.PagingState +import pokitmons.pokit.core.feature.model.paging.SimplePaging import pokitmons.pokit.core.feature.navigation.args.LinkUpdateEvent import pokitmons.pokit.domain.commom.PokitResult +import pokitmons.pokit.domain.model.link.LinksSort import pokitmons.pokit.domain.usecase.link.DeleteLinkUseCase import pokitmons.pokit.domain.usecase.link.GetLinkUseCase import pokitmons.pokit.domain.usecase.link.SearchLinksUseCase @@ -31,9 +36,6 @@ import pokitmons.pokit.search.model.Link import pokitmons.pokit.search.model.Pokit import pokitmons.pokit.search.model.SearchScreenState import pokitmons.pokit.search.model.SearchScreenStep -import pokitmons.pokit.search.paging.LinkPaging -import pokitmons.pokit.search.paging.PokitPaging -import pokitmons.pokit.search.paging.SimplePagingState import javax.inject.Inject @HiltViewModel @@ -55,26 +57,61 @@ class SearchViewModel @Inject constructor( initLinkRemoveEventDetector() } - private val linkPaging = LinkPaging( - searchLinksUseCase = searchLinksUseCase, - filter = Filter(), - perPage = 10, - coroutineScope = viewModelScope, - initPage = 0 + private val linkPagingSource = object: PagingSource { + override suspend fun load(pageIndex: Int, pageSize: Int): PagingLoadResult { + val currentFilter = state.value.filter ?: Filter() + + val isRead = if (currentFilter.notRead) false else null + val favorites = if (currentFilter.bookmark) true else null + val sort = if (state.value.sortRecent) LinksSort.RECENT else LinksSort.OLDER + val currentAppliedSearchWord = appliedSearchWord + + val response = searchLinksUseCase.searchLinks( + page = pageIndex, + size = pageSize, + sort = listOf(sort.value), + isRead = isRead, + favorites = favorites, + startDate = currentFilter.startDate?.toDateString(), + endDate = currentFilter.endDate?.toDateString(), + categoryIds = currentFilter.selectedPokits.mapNotNull { it.id.toIntOrNull() }, + searchWord = currentAppliedSearchWord + ) + return PagingLoadResult.fromPokitResult( + pokitResult = response, + mapper = { domainLinks -> domainLinks.map { Link.fromDomainLink(it) } } + ) + } + + } + + private val linkPaging = SimplePaging( + pagingSource = linkPagingSource, + getKeyFromItem = { link -> link.id }, + coroutineScope = viewModelScope ) - private val pokitPaging = PokitPaging( - getPokits = getPokitsUseCase, - perPage = 10, - coroutineScope = viewModelScope, - initPage = 0 + private val pokitPagingSource = object: PagingSource { + override suspend fun load(pageIndex: Int, pageSize: Int): PagingLoadResult { + val response = getPokitsUseCase.getPokits(page = pageIndex, size = pageSize) + return PagingLoadResult.fromPokitResult( + pokitResult = response, + mapper = { domainPokits -> domainPokits.map { Pokit.fromDomainPokit(it) } } + ) + } + } + + private val pokitPaging = SimplePaging( + pagingSource = pokitPagingSource, + getKeyFromItem = { pokit -> pokit.id }, + coroutineScope = viewModelScope ) val linkList: StateFlow> = linkPaging.pagingData - val linkPagingState: StateFlow = linkPaging.pagingState + val linkPagingState: StateFlow = linkPaging.pagingState val pokitList: StateFlow> = pokitPaging.pagingData - val pokitPagingState: StateFlow = pokitPaging.pagingState + val pokitPagingState: StateFlow = pokitPaging.pagingState private val _searchWord = MutableStateFlow("") val searchWord = _searchWord.asStateFlow() @@ -108,7 +145,7 @@ class SearchViewModel @Inject constructor( viewModelScope.launch { LinkUpdateEvent.removedLink.collectLatest { removedLinkId -> val targetItem = linkPaging.pagingData.value.find { it.id == removedLinkId.toString() } ?: return@collectLatest - linkPaging.deleteItem(targetItem) + linkPaging.deleteItem(targetItem.id) } } } @@ -133,7 +170,6 @@ class SearchViewModel @Inject constructor( } viewModelScope.launch { addRecentSearchWordUseCase.addRecentSearchWord(appliedSearchWord) - linkPaging.changeSearchWord(appliedSearchWord) linkPaging.refresh() } } @@ -149,7 +185,6 @@ class SearchViewModel @Inject constructor( } viewModelScope.launch { addRecentSearchWordUseCase.addRecentSearchWord(appliedSearchWord) - linkPaging.changeSearchWord(appliedSearchWord) linkPaging.refresh() } } @@ -272,7 +307,6 @@ class SearchViewModel @Inject constructor( } viewModelScope.launch { - linkPaging.changeFilter(filter) linkPaging.refresh() } } @@ -283,7 +317,6 @@ class SearchViewModel @Inject constructor( } viewModelScope.launch { - linkPaging.changeRecentSort(state.value.sortRecent) linkPaging.refresh() } } @@ -332,7 +365,7 @@ class SearchViewModel @Inject constructor( if (response is PokitResult.Success) { LinkUpdateEvent.removeSuccess(currentLinkId) val targetLink = linkPaging.pagingData.value.find { it.id == currentLinkId.toString() } ?: return@launch - linkPaging.deleteItem(targetLink) + linkPaging.deleteItem(targetLink.id) } } } diff --git a/feature/search/src/main/java/pokitmons/pokit/search/components/filterbottomsheet/FilterBottomSheet.kt b/feature/search/src/main/java/pokitmons/pokit/search/components/filterbottomsheet/FilterBottomSheet.kt index 2d0540a3..b86bf690 100644 --- a/feature/search/src/main/java/pokitmons/pokit/search/components/filterbottomsheet/FilterBottomSheet.kt +++ b/feature/search/src/main/java/pokitmons/pokit/search/components/filterbottomsheet/FilterBottomSheet.kt @@ -2,12 +2,12 @@ package pokitmons.pokit.search.components.filterbottomsheet import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import pokitmons.pokit.core.feature.model.paging.PagingState import pokitmons.pokit.core.ui.components.template.bottomsheet.PokitBottomSheet import pokitmons.pokit.search.model.Filter import pokitmons.pokit.search.model.FilterType import pokitmons.pokit.search.model.Pokit import pokitmons.pokit.search.model.samplePokits -import pokitmons.pokit.search.paging.SimplePagingState @Composable fun FilterBottomSheet( @@ -15,7 +15,7 @@ fun FilterBottomSheet( firstShowType: FilterType = FilterType.Pokit, onSaveClilck: (Filter) -> Unit = {}, pokits: List = samplePokits, - pokitPagingState: SimplePagingState = SimplePagingState.IDLE, + pokitPagingState: PagingState = PagingState.IDLE, loadNextPokits: () -> Unit = {}, refreshPokits: () -> Unit = {}, show: Boolean = false, diff --git a/feature/search/src/main/java/pokitmons/pokit/search/components/filterbottomsheet/FilterBottomSheetContent.kt b/feature/search/src/main/java/pokitmons/pokit/search/components/filterbottomsheet/FilterBottomSheetContent.kt index 64e927d0..6bc89d98 100644 --- a/feature/search/src/main/java/pokitmons/pokit/search/components/filterbottomsheet/FilterBottomSheetContent.kt +++ b/feature/search/src/main/java/pokitmons/pokit/search/components/filterbottomsheet/FilterBottomSheetContent.kt @@ -32,6 +32,7 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp +import pokitmons.pokit.core.feature.model.paging.PagingState import pokitmons.pokit.core.ui.components.atom.button.PokitButton import pokitmons.pokit.core.ui.components.atom.button.attributes.PokitButtonSize import pokitmons.pokit.core.ui.components.atom.checkbox.PokitCheckbox @@ -48,7 +49,6 @@ import pokitmons.pokit.search.model.Filter import pokitmons.pokit.search.model.FilterType import pokitmons.pokit.search.model.Pokit import pokitmons.pokit.search.model.samplePokits -import pokitmons.pokit.search.paging.SimplePagingState import pokitmons.pokit.core.ui.R.string as coreString @OptIn(ExperimentalFoundationApi::class) @@ -58,7 +58,7 @@ fun FilterBottomSheetContent( firstShowType: FilterType = FilterType.Pokit, onSaveClilck: (Filter) -> Unit = {}, pokits: List = samplePokits, - pokitPagingState: SimplePagingState = SimplePagingState.IDLE, + pokitPagingState: PagingState = PagingState.IDLE, loadNextPokits: () -> Unit = {}, ) { var currentFilter by remember { mutableStateOf(filter) } @@ -80,7 +80,7 @@ fun FilterBottomSheetContent( } LaunchedEffect(startLinkPaging.value) { - if (startLinkPaging.value && pokitPagingState == SimplePagingState.IDLE) { + if (startLinkPaging.value && pokitPagingState == PagingState.IDLE) { loadNextPokits() } } diff --git a/feature/search/src/main/java/pokitmons/pokit/search/components/searchitemlist/SearchItemList.kt b/feature/search/src/main/java/pokitmons/pokit/search/components/searchitemlist/SearchItemList.kt index 75ec7004..e0e68f31 100644 --- a/feature/search/src/main/java/pokitmons/pokit/search/components/searchitemlist/SearchItemList.kt +++ b/feature/search/src/main/java/pokitmons/pokit/search/components/searchitemlist/SearchItemList.kt @@ -22,10 +22,10 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import coil.compose.rememberAsyncImagePainter +import pokitmons.pokit.core.feature.model.paging.PagingState import pokitmons.pokit.core.ui.components.block.linkcard.LinkCard import pokitmons.pokit.core.ui.theme.PokitTheme import pokitmons.pokit.search.model.Link -import pokitmons.pokit.search.paging.SimplePagingState import pokitmons.pokit.core.ui.R.drawable as coreDrawable import pokitmons.pokit.search.R.string as SearchString @@ -35,7 +35,7 @@ internal fun SearchItemList( onToggleSort: () -> Unit = {}, useRecentOrder: Boolean = true, links: List = emptyList(), - linkPagingState: SimplePagingState = SimplePagingState.IDLE, + linkPagingState: PagingState = PagingState.IDLE, onClickLinkKebab: (Link) -> Unit = {}, onClickLink: (Link) -> Unit = {}, loadNextLinks: () -> Unit = {}, @@ -50,7 +50,7 @@ internal fun SearchItemList( } LaunchedEffect(startLinkPaging.value) { - if (startLinkPaging.value && linkPagingState == SimplePagingState.IDLE) { + if (startLinkPaging.value && linkPagingState == PagingState.IDLE) { loadNextLinks() } } diff --git a/feature/search/src/main/java/pokitmons/pokit/search/paging/LinkPaging.kt b/feature/search/src/main/java/pokitmons/pokit/search/paging/LinkPaging.kt deleted file mode 100644 index 54e31ec0..00000000 --- a/feature/search/src/main/java/pokitmons/pokit/search/paging/LinkPaging.kt +++ /dev/null @@ -1,160 +0,0 @@ -package pokitmons.pokit.search.paging - -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.update -import kotlinx.coroutines.launch -import pokitmons.pokit.domain.commom.PokitResult -import pokitmons.pokit.domain.usecase.link.SearchLinksUseCase -import pokitmons.pokit.search.model.Filter -import pokitmons.pokit.search.model.Link -import kotlin.coroutines.cancellation.CancellationException - -class LinkPaging( - private val searchLinksUseCase: SearchLinksUseCase, - private val perPage: Int = 10, - private val coroutineScope: CoroutineScope = CoroutineScope(Dispatchers.IO), - private val initPage: Int = 0, - private val firstRequestPage: Int = 3, - private var filter: Filter, - private var searchWord: String = "", - private var recentSort: Boolean = true, -) : SimplePaging { - - private val _pagingState: MutableStateFlow = MutableStateFlow(SimplePagingState.IDLE) - override val pagingState: StateFlow = _pagingState.asStateFlow() - - private val _pagingData: MutableStateFlow> = MutableStateFlow(emptyList()) - override val pagingData: StateFlow> = _pagingData.asStateFlow() - - private var currentPageIndex = initPage - private var requestJob: Job? = null - - fun changeFilter(filter: Filter) { - this.filter = filter - } - - fun changeSearchWord(searchWord: String) { - this.searchWord = searchWord - } - - fun changeRecentSort(recentSort: Boolean) { - this.recentSort = recentSort - } - - override suspend fun refresh() { - requestJob?.cancel() - - _pagingData.update { emptyList() } - _pagingState.update { SimplePagingState.LOADING_INIT } - requestJob = coroutineScope.launch { - try { - currentPageIndex = initPage - val response = searchLinksUseCase.searchLinks( - page = currentPageIndex, - size = perPage * firstRequestPage, - sort = listOf(), - isRead = if (filter.notRead) false else null, - favorites = if (filter.bookmark) true else null, - startDate = filter.startDate?.toDateString(), - endDate = filter.endDate?.toDateString(), - categoryIds = filter.selectedPokits.mapNotNull { it.id.toIntOrNull() }, - searchWord = searchWord - ) - when (response) { - is PokitResult.Success -> { - val links = response.result.map { domainLink -> - Link.fromDomainLink(domainLink) - } - applyResponse(links, firstRequestPage) - } - is PokitResult.Error -> { - _pagingState.update { SimplePagingState.FAILURE_INIT } - } - } - } catch (exception: Exception) { - if (exception !is CancellationException) { - _pagingState.update { SimplePagingState.FAILURE_INIT } - } - } - } - } - - override suspend fun load() { - if (pagingState.value != SimplePagingState.IDLE) return - - requestJob?.cancel() - _pagingState.update { SimplePagingState.LOADING_NEXT } - - requestJob = coroutineScope.launch { - try { - val response = searchLinksUseCase.searchLinks( - page = currentPageIndex, - size = perPage, - sort = listOf(), - isRead = if (filter.notRead) false else null, - favorites = if (filter.bookmark) true else null, - startDate = filter.startDate?.toDateString(), - endDate = filter.endDate?.toDateString(), - categoryIds = filter.selectedPokits.mapNotNull { it.id.toIntOrNull() }, - searchWord = searchWord - ) - when (response) { - is PokitResult.Success -> { - val links = response.result.map { domainLink -> - Link.fromDomainLink(domainLink) - } - applyResponse(links) - } - is PokitResult.Error -> { - _pagingState.update { SimplePagingState.FAILURE_INIT } - } - } - } catch (exception: Exception) { - if (exception !is CancellationException) { - _pagingState.update { SimplePagingState.FAILURE_NEXT } - } - } - } - } - - private fun applyResponse(dataInResponse: List, multiple: Int = 1) { - if (dataInResponse.size < perPage * multiple) { - _pagingState.update { SimplePagingState.LAST } - } else { - _pagingState.update { SimplePagingState.IDLE } - } - _pagingData.update { _pagingData.value + dataInResponse } - currentPageIndex += multiple - } - - override fun clear() { - requestJob?.cancel() - _pagingData.update { emptyList() } - _pagingState.update { SimplePagingState.IDLE } - } - - override suspend fun deleteItem(item: Link) { - val capturedDataList = _pagingData.value - _pagingData.update { capturedDataList.filter { it.id != item.id } } - } - - override suspend fun modifyItem(item: Link) { - val capturedDataList = _pagingData.value - val targetPokit = capturedDataList.find { it.id == item.id } ?: return - - _pagingData.update { - capturedDataList.map { pokit -> - if (targetPokit.id == pokit.id) { - item - } else { - pokit - } - } - } - } -} diff --git a/feature/search/src/main/java/pokitmons/pokit/search/paging/PokitPaging.kt b/feature/search/src/main/java/pokitmons/pokit/search/paging/PokitPaging.kt deleted file mode 100644 index b3a6ae54..00000000 --- a/feature/search/src/main/java/pokitmons/pokit/search/paging/PokitPaging.kt +++ /dev/null @@ -1,124 +0,0 @@ -package pokitmons.pokit.search.paging - -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.update -import kotlinx.coroutines.launch -import pokitmons.pokit.domain.commom.PokitResult -import pokitmons.pokit.domain.usecase.pokit.GetPokitsUseCase -import pokitmons.pokit.search.model.Pokit -import kotlin.coroutines.cancellation.CancellationException - -class PokitPaging( - private val getPokits: GetPokitsUseCase, - private val perPage: Int = 10, - private val coroutineScope: CoroutineScope = CoroutineScope(Dispatchers.IO), - private val initPage: Int = 0, - private val firstRequestPage: Int = 3, -) : SimplePaging { - private val _pagingState = MutableStateFlow(SimplePagingState.IDLE) - override val pagingState: StateFlow = _pagingState.asStateFlow() - - private val _pagingData: MutableStateFlow> = MutableStateFlow(emptyList()) - override val pagingData: StateFlow> = _pagingData.asStateFlow() - private var currentPageIndex = initPage - private var requestJob: Job? = null - - override suspend fun refresh() { - requestJob?.cancel() - - _pagingData.update { emptyList() } - _pagingState.update { SimplePagingState.LOADING_INIT } - requestJob = coroutineScope.launch { - try { - currentPageIndex = initPage - val response = getPokits.getPokits(size = perPage * firstRequestPage, page = currentPageIndex) - when (response) { - is PokitResult.Success -> { - val pokitList = response.result.map { domainPokit -> - Pokit.fromDomainPokit(domainPokit) - } - applyResponse(pokitList, firstRequestPage) - } - - is PokitResult.Error -> { - _pagingState.update { SimplePagingState.FAILURE_INIT } - } - } - } catch (exception: Exception) { - if (exception !is CancellationException) { - _pagingState.update { SimplePagingState.FAILURE_INIT } - } - } - } - } - - override suspend fun load() { - if (pagingState.value != SimplePagingState.IDLE) return - - requestJob?.cancel() - _pagingState.update { SimplePagingState.LOADING_NEXT } - - requestJob = coroutineScope.launch { - try { - val response = getPokits.getPokits(size = perPage, page = currentPageIndex) - when (response) { - is PokitResult.Success -> { - val pokitList = response.result.map { domainPokit -> - Pokit.fromDomainPokit(domainPokit) - } - applyResponse(pokitList) - } - - is PokitResult.Error -> { - _pagingState.update { SimplePagingState.FAILURE_NEXT } - } - } - } catch (exception: Exception) { - if (exception !is CancellationException) { - _pagingState.update { SimplePagingState.FAILURE_NEXT } - } - } - } - } - - private fun applyResponse(dataInResponse: List, multiple: Int = 1) { - if (dataInResponse.size < perPage * multiple) { - _pagingState.update { SimplePagingState.LAST } - } else { - _pagingState.update { SimplePagingState.IDLE } - } - _pagingData.update { _pagingData.value + dataInResponse } - currentPageIndex += multiple - } - - override fun clear() { - requestJob?.cancel() - _pagingData.update { emptyList() } - _pagingState.update { SimplePagingState.IDLE } - } - - override suspend fun deleteItem(item: Pokit) { - val capturedDataList = _pagingData.value - _pagingData.update { capturedDataList.filter { it.id != item.id } } - } - - override suspend fun modifyItem(item: Pokit) { - val capturedDataList = _pagingData.value - val targetPokit = capturedDataList.find { it.id == item.id } ?: return - - _pagingData.update { - capturedDataList.map { pokit -> - if (targetPokit.id == pokit.id) { - item - } else { - pokit - } - } - } - } -} diff --git a/feature/search/src/main/java/pokitmons/pokit/search/paging/SimplePaging.kt b/feature/search/src/main/java/pokitmons/pokit/search/paging/SimplePaging.kt deleted file mode 100644 index b80d630c..00000000 --- a/feature/search/src/main/java/pokitmons/pokit/search/paging/SimplePaging.kt +++ /dev/null @@ -1,13 +0,0 @@ -package pokitmons.pokit.search.paging - -import kotlinx.coroutines.flow.Flow - -interface SimplePaging { - val pagingData: Flow> - suspend fun refresh() - suspend fun load() - val pagingState: Flow - suspend fun modifyItem(item: T) - suspend fun deleteItem(item: T) - fun clear() -} diff --git a/feature/search/src/main/java/pokitmons/pokit/search/paging/SimplePagingState.kt b/feature/search/src/main/java/pokitmons/pokit/search/paging/SimplePagingState.kt deleted file mode 100644 index 50a931f1..00000000 --- a/feature/search/src/main/java/pokitmons/pokit/search/paging/SimplePagingState.kt +++ /dev/null @@ -1,5 +0,0 @@ -package pokitmons.pokit.search.paging - -enum class SimplePagingState { - IDLE, LOADING_NEXT, LOADING_INIT, FAILURE_NEXT, FAILURE_INIT, LAST -} From 4288725e6926a94627729025d8673e9c4f69c7bf Mon Sep 17 00:00:00 2001 From: yunsehwan Date: Mon, 16 Sep 2024 17:03:52 +0900 Subject: [PATCH 6/9] =?UTF-8?q?[CHORE]=20=EB=AF=B8=EC=82=AC=EC=9A=A9=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pokitdetail/paging/LinkPaging.kt | 136 ------------------ .../pokitdetail/paging/PokitPaging.kt | 131 ----------------- .../pokitdetail/paging/SimplePaging.kt | 13 -- .../pokitdetail/paging/SimplePagingState.kt | 5 - .../pokitdetail/PokitPagingTest.kt | 72 ---------- 5 files changed, 357 deletions(-) delete mode 100644 feature/pokitdetail/src/main/java/com/strayalpaca/pokitdetail/paging/LinkPaging.kt delete mode 100644 feature/pokitdetail/src/main/java/com/strayalpaca/pokitdetail/paging/PokitPaging.kt delete mode 100644 feature/pokitdetail/src/main/java/com/strayalpaca/pokitdetail/paging/SimplePaging.kt delete mode 100644 feature/pokitdetail/src/main/java/com/strayalpaca/pokitdetail/paging/SimplePagingState.kt delete mode 100644 feature/pokitdetail/src/test/java/com/strayalpaca/pokitdetail/PokitPagingTest.kt diff --git a/feature/pokitdetail/src/main/java/com/strayalpaca/pokitdetail/paging/LinkPaging.kt b/feature/pokitdetail/src/main/java/com/strayalpaca/pokitdetail/paging/LinkPaging.kt deleted file mode 100644 index 37662455..00000000 --- a/feature/pokitdetail/src/main/java/com/strayalpaca/pokitdetail/paging/LinkPaging.kt +++ /dev/null @@ -1,136 +0,0 @@ -package com.strayalpaca.pokitdetail.paging - -import com.strayalpaca.pokitdetail.model.Link -import kotlinx.coroutines.CancellationException -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.update -import kotlinx.coroutines.launch -import pokitmons.pokit.domain.commom.PokitResult -import pokitmons.pokit.domain.model.link.LinksSort -import kotlin.reflect.KSuspendFunction4 -import pokitmons.pokit.domain.model.link.Link as DomainLink - -class LinkPaging( - private var getLinks: KSuspendFunction4>>, - private val perPage: Int = 10, - private val coroutineScope: CoroutineScope = CoroutineScope(Dispatchers.IO), - private val initPage: Int = 0, - private val firstRequestPage: Int = 3, - initCategoryId: Int = 0, -) : SimplePaging { - private val _pagingState = MutableStateFlow(SimplePagingState.IDLE) - override val pagingState: StateFlow = _pagingState.asStateFlow() - - val _pagingData: MutableStateFlow> = MutableStateFlow(emptyList()) - - override val pagingData: StateFlow> = _pagingData.asStateFlow() - private var currentPageIndex = initPage - private var requestJob: Job? = null - - private var currentCategoryId: Int = initCategoryId - private var currentSort = LinksSort.RECENT - - fun changeOptions(categoryId: Int, sort: LinksSort) { - currentCategoryId = categoryId - currentSort = sort - } - - override suspend fun refresh() { - requestJob?.cancel() - - _pagingData.update { emptyList() } - _pagingState.update { SimplePagingState.LOADING_INIT } - requestJob = coroutineScope.launch { - try { - currentPageIndex = initPage - val response = getLinks(currentCategoryId, perPage * firstRequestPage, currentPageIndex, currentSort) - when (response) { - is PokitResult.Success -> { - val links = response.result.map { domainLink -> - Link.fromDomainLink(domainLink) - } - applyResponse(links, firstRequestPage) - } - - is PokitResult.Error -> { - _pagingState.update { SimplePagingState.FAILURE_INIT } - } - } - } catch (exception: Exception) { - if (exception !is CancellationException) { - _pagingState.update { SimplePagingState.FAILURE_INIT } - } - } - } - } - - override suspend fun load() { - if (pagingState.value != SimplePagingState.IDLE) return - - requestJob?.cancel() - _pagingState.update { SimplePagingState.LOADING_NEXT } - - requestJob = coroutineScope.launch { - try { - val response = getLinks(currentCategoryId, perPage, currentPageIndex, currentSort) - when (response) { - is PokitResult.Success -> { - val links = response.result.map { domainLink -> - Link.fromDomainLink(domainLink) - } - applyResponse(links) - } - - is PokitResult.Error -> { - _pagingState.update { SimplePagingState.FAILURE_NEXT } - } - } - } catch (exception: Exception) { - if (exception !is CancellationException) { - _pagingState.update { SimplePagingState.FAILURE_NEXT } - } - } - } - } - - private fun applyResponse(dataInResponse: List, multiple: Int = 1) { - if (dataInResponse.size < perPage * multiple) { - _pagingState.update { SimplePagingState.LAST } - } else { - _pagingState.update { SimplePagingState.IDLE } - } - _pagingData.update { _pagingData.value + dataInResponse } - currentPageIndex += multiple - } - - override fun clear() { - requestJob?.cancel() - _pagingData.update { emptyList() } - _pagingState.update { SimplePagingState.IDLE } - } - - override suspend fun deleteItem(item: Link) { - val capturedDataList = _pagingData.value - _pagingData.update { capturedDataList.filter { it.id != item.id } } - } - - override suspend fun modifyItem(item: Link) { - val capturedDataList = _pagingData.value - val targetPokit = capturedDataList.find { it.id == item.id } ?: return - - _pagingData.update { - capturedDataList.map { pokit -> - if (targetPokit.id == pokit.id) { - item - } else { - pokit - } - } - } - } -} diff --git a/feature/pokitdetail/src/main/java/com/strayalpaca/pokitdetail/paging/PokitPaging.kt b/feature/pokitdetail/src/main/java/com/strayalpaca/pokitdetail/paging/PokitPaging.kt deleted file mode 100644 index ca2a8702..00000000 --- a/feature/pokitdetail/src/main/java/com/strayalpaca/pokitdetail/paging/PokitPaging.kt +++ /dev/null @@ -1,131 +0,0 @@ -package com.strayalpaca.pokitdetail.paging - -import com.strayalpaca.pokitdetail.model.Pokit -import kotlinx.coroutines.CancellationException -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.update -import kotlinx.coroutines.launch -import pokitmons.pokit.domain.commom.PokitResult -import pokitmons.pokit.domain.model.pokit.PokitsSort -import pokitmons.pokit.domain.usecase.pokit.GetPokitsUseCase - -class PokitPaging( - private val getPokits: GetPokitsUseCase, - private val perPage: Int = 10, - private val coroutineScope: CoroutineScope = CoroutineScope(Dispatchers.IO), - private val initPage: Int = 0, - private val firstRequestPage: Int = 3, -) : SimplePaging { - private val _pagingState = MutableStateFlow(SimplePagingState.IDLE) - override val pagingState: StateFlow = _pagingState.asStateFlow() - - val _pagingData: MutableStateFlow> = MutableStateFlow(emptyList()) - - override val pagingData: StateFlow> = _pagingData.asStateFlow() - private var currentPageIndex = initPage - private var requestJob: Job? = null - private var sort: PokitsSort = PokitsSort.RECENT - - override suspend fun refresh() { - requestJob?.cancel() - - _pagingData.update { emptyList() } - _pagingState.update { SimplePagingState.LOADING_INIT } - requestJob = coroutineScope.launch { - try { - currentPageIndex = initPage - val response = getPokits.getPokits(sort = sort, size = perPage * firstRequestPage, page = currentPageIndex) - when (response) { - is PokitResult.Success -> { - val pokitList = response.result.map { domainPokit -> - Pokit.fromDomainPokit(domainPokit) - } - applyResponse(pokitList, firstRequestPage) - } - - is PokitResult.Error -> { - _pagingState.update { SimplePagingState.FAILURE_INIT } - } - } - } catch (exception: Exception) { - if (exception !is CancellationException) { - _pagingState.update { SimplePagingState.FAILURE_INIT } - } - } - } - } - - override suspend fun load() { - if (pagingState.value != SimplePagingState.IDLE) return - - requestJob?.cancel() - _pagingState.update { SimplePagingState.LOADING_NEXT } - - requestJob = coroutineScope.launch { - try { - val response = getPokits.getPokits(sort = sort, size = perPage, page = currentPageIndex) - when (response) { - is PokitResult.Success -> { - val pokitList = response.result.map { domainPokit -> - Pokit.fromDomainPokit(domainPokit) - } - applyResponse(pokitList) - } - - is PokitResult.Error -> { - _pagingState.update { SimplePagingState.FAILURE_NEXT } - } - } - } catch (exception: Exception) { - if (exception !is CancellationException) { - _pagingState.update { SimplePagingState.FAILURE_NEXT } - } - } - } - } - - private fun applyResponse(dataInResponse: List, multiple: Int = 1) { - if (dataInResponse.size < perPage * multiple) { - _pagingState.update { SimplePagingState.LAST } - } else { - _pagingState.update { SimplePagingState.IDLE } - } - _pagingData.update { _pagingData.value + dataInResponse } - currentPageIndex += multiple - } - - override fun clear() { - requestJob?.cancel() - _pagingData.update { emptyList() } - _pagingState.update { SimplePagingState.IDLE } - } - - override suspend fun deleteItem(item: Pokit) { - val capturedDataList = _pagingData.value - _pagingData.update { capturedDataList.filter { it.id != item.id } } - } - - override suspend fun modifyItem(item: Pokit) { - val capturedDataList = _pagingData.value - val targetPokit = capturedDataList.find { it.id == item.id } ?: return - - _pagingData.update { - capturedDataList.map { pokit -> - if (targetPokit.id == pokit.id) { - item - } else { - pokit - } - } - } - } - - fun changeSort(sort: PokitsSort) { - this.sort = sort - } -} diff --git a/feature/pokitdetail/src/main/java/com/strayalpaca/pokitdetail/paging/SimplePaging.kt b/feature/pokitdetail/src/main/java/com/strayalpaca/pokitdetail/paging/SimplePaging.kt deleted file mode 100644 index de38d66e..00000000 --- a/feature/pokitdetail/src/main/java/com/strayalpaca/pokitdetail/paging/SimplePaging.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.strayalpaca.pokitdetail.paging - -import kotlinx.coroutines.flow.Flow - -interface SimplePaging { - val pagingData: Flow> - suspend fun refresh() - suspend fun load() - val pagingState: Flow - suspend fun modifyItem(item: T) - suspend fun deleteItem(item: T) - fun clear() -} diff --git a/feature/pokitdetail/src/main/java/com/strayalpaca/pokitdetail/paging/SimplePagingState.kt b/feature/pokitdetail/src/main/java/com/strayalpaca/pokitdetail/paging/SimplePagingState.kt deleted file mode 100644 index a0fc6519..00000000 --- a/feature/pokitdetail/src/main/java/com/strayalpaca/pokitdetail/paging/SimplePagingState.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.strayalpaca.pokitdetail.paging - -enum class SimplePagingState { - IDLE, LOADING_NEXT, LOADING_INIT, FAILURE_NEXT, FAILURE_INIT, LAST -} diff --git a/feature/pokitdetail/src/test/java/com/strayalpaca/pokitdetail/PokitPagingTest.kt b/feature/pokitdetail/src/test/java/com/strayalpaca/pokitdetail/PokitPagingTest.kt deleted file mode 100644 index 29a7c2cf..00000000 --- a/feature/pokitdetail/src/test/java/com/strayalpaca/pokitdetail/PokitPagingTest.kt +++ /dev/null @@ -1,72 +0,0 @@ -package com.strayalpaca.pokitdetail - -import com.strayalpaca.pokitdetail.paging.PokitPaging -import com.strayalpaca.pokitdetail.paging.SimplePagingState -import io.kotest.common.ExperimentalKotest -import io.kotest.core.spec.style.DescribeSpec -import io.kotest.core.test.testCoroutineScheduler -import io.kotest.matchers.shouldBe -import io.mockk.coEvery -import io.mockk.mockk -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.first -import pokitmons.pokit.domain.commom.PokitResult -import pokitmons.pokit.domain.usecase.pokit.GetPokitsUseCase -import pokitmons.pokit.domain.model.pokit.Pokit as DomainPokit - -const val PER_PAGE_SAMPLE = 3 -const val FIRST_REQUEST_PAGE_SAMPLE = 3 - -@OptIn(ExperimentalKotest::class, ExperimentalStdlibApi::class, ExperimentalCoroutinesApi::class) -class PokitPagingTest : DescribeSpec({ - val sampleGetPokitsUseCase: GetPokitsUseCase = mockk() - - describe("PokitPaging").config(coroutineTestScope = true) { - val coroutineScope = this - val pokitPaging = PokitPaging( - getPokits = sampleGetPokitsUseCase, - coroutineScope = coroutineScope, - firstRequestPage = FIRST_REQUEST_PAGE_SAMPLE, - perPage = PER_PAGE_SAMPLE - ) - coEvery { sampleGetPokitsUseCase.getPokits(size = PER_PAGE_SAMPLE * FIRST_REQUEST_PAGE_SAMPLE, page = 0) } coAnswers { - delay(1000L) - PokitResult.Success(result = listOf(DomainPokit(1, 1, "", DomainPokit.Image(1, ""), 1, ""))) - } - coEvery { sampleGetPokitsUseCase.getPokits(size = PER_PAGE_SAMPLE, page = 0) } coAnswers { - delay(1000L) - PokitResult.Success(result = listOf(DomainPokit(1, 1, "", DomainPokit.Image(1, ""), 1, ""))) - } - - context("새로고침을 하는 경우") { - it("새로고침 로딩 상태가 되어야 한다.") { - pokitPaging.refresh() - pokitPaging.pagingState.value shouldBe SimplePagingState.LOADING_INIT - } - } - - context("기존 페이지를 로드하던 중 다른 페이지 요청이 들어온 경우") { - it("해당 요청을 무시하고 기존 상태를 유지한다.") { - pokitPaging.refresh() - pokitPaging.load() - - coroutineScope.testCoroutineScheduler.advanceTimeBy(5000L) - - val state = pokitPaging.pagingState.first() - state shouldBe SimplePagingState.LAST - - // testCoroutineScheduler.advanceUntilIdle() - // it 내의 this(coroutineScope)와 전체 describe의 coroutineScope가 서로 다르다! - } - } - - context("기존 페이지를 로드하던 중 새로고침 요청이 들어온 경우") { - it("기존 작업을 무시하고 새로고침을 수행한다.") { - pokitPaging.load() - pokitPaging.refresh() - pokitPaging.pagingState.value shouldBe SimplePagingState.LOADING_INIT - } - } - } -}) From e0a066d55826ca85ff4c8687e52ba0760e40f989 Mon Sep 17 00:00:00 2001 From: yunsehwan Date: Mon, 16 Sep 2024 17:13:15 +0900 Subject: [PATCH 7/9] =?UTF-8?q?[FIX]=20=EA=B2=80=EC=83=89=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4=20=EB=82=B4=20lazyColumn=20items=EC=97=90=20key?= =?UTF-8?q?=EC=86=8D=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pokit/search/components/searchitemlist/SearchItemList.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/feature/search/src/main/java/pokitmons/pokit/search/components/searchitemlist/SearchItemList.kt b/feature/search/src/main/java/pokitmons/pokit/search/components/searchitemlist/SearchItemList.kt index e0e68f31..95fe34e3 100644 --- a/feature/search/src/main/java/pokitmons/pokit/search/components/searchitemlist/SearchItemList.kt +++ b/feature/search/src/main/java/pokitmons/pokit/search/components/searchitemlist/SearchItemList.kt @@ -85,7 +85,7 @@ internal fun SearchItemList( LazyColumn( state = linkLazyColumnListState ) { - items(links) { link -> + items(items = links, key = { link -> link.id }) { link -> LinkCard( item = link, title = link.title, From d4b418c10f0b6acd976f9257637825ffdc0d5407 Mon Sep 17 00:00:00 2001 From: yunsehwan Date: Fri, 20 Sep 2024 19:46:32 +0900 Subject: [PATCH 8/9] =?UTF-8?q?[FIX]=20=EB=A7=81=ED=81=AC=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=EC=9C=BC=EB=A1=9C=20=EC=9D=B8=ED=95=B4=20=ED=8F=AC?= =?UTF-8?q?=ED=82=B7=EC=9D=98=20=EB=A7=81=ED=81=AC=20=EA=B0=9C=EC=88=98?= =?UTF-8?q?=EA=B0=80=20=EB=B3=80=EA=B2=BD=EB=90=98=EC=97=87=EC=9D=84=20?= =?UTF-8?q?=EB=95=8C,=20=EC=9D=B4=EB=A5=BC=20=ED=99=88=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4=20=EB=B0=8F=20=ED=8F=AC=ED=82=B7=20=EC=83=81=EC=84=B8?= =?UTF-8?q?=20=ED=99=94=EB=A9=B4=EC=97=90=EC=84=9C=20=EB=B0=98=EC=98=81?= =?UTF-8?q?=ED=95=98=EC=A7=80=20=EB=AA=BB=ED=95=98=EB=8A=94=20=EB=AC=B8?= =?UTF-8?q?=EC=A0=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/feature/navigation/args/PokitArg.kt | 5 +++ .../navigation/args/PokitUpdateEvent.kt | 11 ++++++ .../strayalpaca/addlink/AddLinkViewModel.kt | 8 +++++ .../pokit/home/pokit/PokitViewModel.kt | 22 ++++++++++++ .../pokitdetail/PokitDetailViewModel.kt | 36 +++++++++++++++---- 5 files changed, 75 insertions(+), 7 deletions(-) diff --git a/core/feature/src/main/java/pokitmons/pokit/core/feature/navigation/args/PokitArg.kt b/core/feature/src/main/java/pokitmons/pokit/core/feature/navigation/args/PokitArg.kt index 6a67a471..74d3b761 100644 --- a/core/feature/src/main/java/pokitmons/pokit/core/feature/navigation/args/PokitArg.kt +++ b/core/feature/src/main/java/pokitmons/pokit/core/feature/navigation/args/PokitArg.kt @@ -10,3 +10,8 @@ data class PokitArg( val imageUrl: String, val title: String, ) : Parcelable + +data class LinkCountChangedPokitIds( + val increasedPokitId: Int?, + val decreasedPokitId: Int?, +) diff --git a/core/feature/src/main/java/pokitmons/pokit/core/feature/navigation/args/PokitUpdateEvent.kt b/core/feature/src/main/java/pokitmons/pokit/core/feature/navigation/args/PokitUpdateEvent.kt index a506e3ec..c516186d 100644 --- a/core/feature/src/main/java/pokitmons/pokit/core/feature/navigation/args/PokitUpdateEvent.kt +++ b/core/feature/src/main/java/pokitmons/pokit/core/feature/navigation/args/PokitUpdateEvent.kt @@ -16,6 +16,9 @@ object PokitUpdateEvent { private val _addedPokit = MutableSharedFlow() val addedPokit = _addedPokit.asSharedFlow() + private val _countModifiedPokitIds = MutableSharedFlow() + val countModifiedPokitIds = _countModifiedPokitIds.asSharedFlow() + fun updatePokit(pokitArg: PokitArg) { CoroutineScope(Dispatchers.Default).launch { _updatedPokit.emit(pokitArg) @@ -33,4 +36,12 @@ object PokitUpdateEvent { _addedPokit.emit(pokitArg) } } + + fun updatePokitLinkCount(linkRemovedPokitId: Int? = null, linkAddedPokitId: Int? = null) { + if (linkRemovedPokitId == linkAddedPokitId) return + + CoroutineScope(Dispatchers.Default).launch { + _countModifiedPokitIds.emit(LinkCountChangedPokitIds(increasedPokitId = linkAddedPokitId, decreasedPokitId = linkRemovedPokitId)) + } + } } diff --git a/feature/addlink/src/main/java/com/strayalpaca/addlink/AddLinkViewModel.kt b/feature/addlink/src/main/java/com/strayalpaca/addlink/AddLinkViewModel.kt index 4cd939b2..36ab79c1 100644 --- a/feature/addlink/src/main/java/com/strayalpaca/addlink/AddLinkViewModel.kt +++ b/feature/addlink/src/main/java/com/strayalpaca/addlink/AddLinkViewModel.kt @@ -86,6 +86,9 @@ class AddLinkViewModel @Inject constructor( val currentLinkId: Int? = savedStateHandle.get("link_id")?.toIntOrNull() + // 수정 이전 pokit과 수정 이후 pokit이 다른 경우를 체크하기 위해서만 사용 + private var prevPokitId: Int? = null + init { initPokitAddEventDetector() @@ -147,6 +150,7 @@ class AddLinkViewModel @Inject constructor( step = ScreenStep.IDLE ) } + prevPokitId = responseResult.categoryId _title.update { response.result.title } _memo.update { response.result.memo } _linkUrl.update { response.result.data } @@ -266,6 +270,10 @@ class AddLinkViewModel @Inject constructor( if (isCreate) { LinkUpdateEvent.createSuccess(linkArg) } else { + PokitUpdateEvent.updatePokitLinkCount( + linkAddedPokitId = currentSelectedPokit.id.toIntOrNull(), + linkRemovedPokitId = prevPokitId + ) LinkUpdateEvent.modifySuccess(linkArg) } diff --git a/feature/home/src/main/java/pokitmons/pokit/home/pokit/PokitViewModel.kt b/feature/home/src/main/java/pokitmons/pokit/home/pokit/PokitViewModel.kt index ee98a078..da537a88 100644 --- a/feature/home/src/main/java/pokitmons/pokit/home/pokit/PokitViewModel.kt +++ b/feature/home/src/main/java/pokitmons/pokit/home/pokit/PokitViewModel.kt @@ -33,6 +33,7 @@ import pokitmons.pokit.domain.usecase.pokit.GetPokitsUseCase import pokitmons.pokit.home.model.HomeSideEffect import pokitmons.pokit.home.model.HomeToastMessage import javax.inject.Inject +import kotlin.math.max import com.strayalpaca.pokitdetail.model.Link as DetailLink import pokitmons.pokit.domain.model.pokit.Pokit as DomainPokit @@ -102,6 +103,27 @@ class PokitViewModel @Inject constructor( pokitPaging.modifyItem(modifiedPokit) } } + + viewModelScope.launch { + PokitUpdateEvent.countModifiedPokitIds.collectLatest { linkCountChangedPokitIds -> + linkCountChangedPokitIds.increasedPokitId?.let { linkCountIncreasedPokitId -> + val currentPokit = pokitPaging.pagingData.value.find { it.id == linkCountIncreasedPokitId.toString() } + currentPokit?.let { linkCountIncreasedPokit -> + val increasedLinkCount = linkCountIncreasedPokit.count + 1 + pokitPaging.modifyItem(linkCountIncreasedPokit.copy(count = increasedLinkCount)) + } + } + + linkCountChangedPokitIds.decreasedPokitId?.let { linkCountDecreasedPokitId -> + val currentPokit = pokitPaging.pagingData.value.find { it.id == linkCountDecreasedPokitId.toString() } + currentPokit?.let { linkCountDecreasedPokit -> + val decreasedLinkCount = max(0, linkCountDecreasedPokit.count - 1) + pokitPaging.modifyItem(linkCountDecreasedPokit.copy(count = decreasedLinkCount)) + } + } + + } + } } private fun initPokitRemoveEventDetector() { diff --git a/feature/pokitdetail/src/main/java/com/strayalpaca/pokitdetail/PokitDetailViewModel.kt b/feature/pokitdetail/src/main/java/com/strayalpaca/pokitdetail/PokitDetailViewModel.kt index ab79a2ea..b220c328 100644 --- a/feature/pokitdetail/src/main/java/com/strayalpaca/pokitdetail/PokitDetailViewModel.kt +++ b/feature/pokitdetail/src/main/java/com/strayalpaca/pokitdetail/PokitDetailViewModel.kt @@ -33,6 +33,7 @@ import pokitmons.pokit.domain.usecase.pokit.DeletePokitUseCase import pokitmons.pokit.domain.usecase.pokit.GetPokitUseCase import pokitmons.pokit.domain.usecase.pokit.GetPokitsUseCase import javax.inject.Inject +import kotlin.math.max @HiltViewModel class PokitDetailViewModel @Inject constructor( @@ -119,13 +120,18 @@ class PokitDetailViewModel @Inject constructor( viewModelScope.launch { LinkUpdateEvent.updatedLink.collectLatest { updatedLink -> val targetLink = linkPaging.pagingData.value.find { it.id == updatedLink.id.toString() } ?: return@collectLatest - val modifiedLink = targetLink.copy( - title = updatedLink.title, - imageUrl = updatedLink.thumbnail, - domainUrl = updatedLink.domain, - createdAt = updatedLink.createdAt - ) - linkPaging.modifyItem(modifiedLink) + + if (updatedLink.pokitId.toString() != targetLink.pokitId) { + linkPaging.deleteItem(targetLink.id) + } else { + val modifiedLink = targetLink.copy( + title = updatedLink.title, + imageUrl = updatedLink.thumbnail, + domainUrl = updatedLink.domain, + createdAt = updatedLink.createdAt + ) + linkPaging.modifyItem(modifiedLink) + } } } } @@ -135,6 +141,10 @@ class PokitDetailViewModel @Inject constructor( LinkUpdateEvent.removedLink.collectLatest { removedLinkId -> val targetLink = linkPaging.pagingData.value.find { it.id == removedLinkId.toString() } ?: return@collectLatest linkPaging.deleteItem(targetLink.id) + + val currentPokit = state.value.currentPokit ?: return@collectLatest + val changedLinkCount = max(currentPokit.count - 1, 0) + _state.update { it.copy(currentPokit = currentPokit.copy(count = changedLinkCount)) } } } } @@ -150,6 +160,18 @@ class PokitDetailViewModel @Inject constructor( _state.update { it.copy(currentPokit = pokit) } } } + + viewModelScope.launch { + PokitUpdateEvent.countModifiedPokitIds.collectLatest { linkCountChangedPokitIds -> + val currentPokit = state.value.currentPokit ?: return@collectLatest + linkCountChangedPokitIds.decreasedPokitId?.let { targetId -> + if (targetId.toString() == currentPokit.id) { + val changedLinkCount = max(currentPokit.count - 1, 0) + _state.update { it.copy(currentPokit = currentPokit.copy(count = changedLinkCount)) } + } + } + } + } } private fun getPokit(pokitId: Int, linkCount: Int) { From c32350a4920220128fd6f0b3129b1836464538a0 Mon Sep 17 00:00:00 2001 From: yunsehwan Date: Fri, 20 Sep 2024 19:48:01 +0900 Subject: [PATCH 9/9] =?UTF-8?q?[CHORE]=20ktlint=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pokit/core/feature/model/paging/PagingLoadResult.kt | 2 +- .../pokit/core/feature/model/paging/SimplePaging.kt | 8 ++++---- .../pokitmons/pokit/core/feature/SimplePagingUnitTest.kt | 4 ++-- .../pokit/core/feature/model/TestPagingSource.kt | 2 +- .../java/com/strayalpaca/addlink/AddLinkViewModel.kt | 2 +- .../java/com/strayalpaca/addpokit/AddPokitViewModel.kt | 2 +- .../main/java/pokitmons/pokit/alarm/AlarmViewModel.kt | 2 +- .../java/pokitmons/pokit/home/pokit/PokitViewModel.kt | 9 ++++----- .../com/strayalpaca/pokitdetail/PokitDetailViewModel.kt | 8 ++++---- .../main/java/pokitmons/pokit/search/SearchViewModel.kt | 5 ++--- 10 files changed, 21 insertions(+), 23 deletions(-) diff --git a/core/feature/src/main/java/pokitmons/pokit/core/feature/model/paging/PagingLoadResult.kt b/core/feature/src/main/java/pokitmons/pokit/core/feature/model/paging/PagingLoadResult.kt index eab9712c..e0f57b44 100644 --- a/core/feature/src/main/java/pokitmons/pokit/core/feature/model/paging/PagingLoadResult.kt +++ b/core/feature/src/main/java/pokitmons/pokit/core/feature/model/paging/PagingLoadResult.kt @@ -3,7 +3,7 @@ package pokitmons.pokit.core.feature.model.paging import pokitmons.pokit.domain.commom.PokitResult sealed interface PagingLoadResult { - data class Success(val result: List): PagingLoadResult + data class Success(val result: List) : PagingLoadResult data class Error(val errorCode: String) : PagingLoadResult companion object { diff --git a/core/feature/src/main/java/pokitmons/pokit/core/feature/model/paging/SimplePaging.kt b/core/feature/src/main/java/pokitmons/pokit/core/feature/model/paging/SimplePaging.kt index 0335b8e8..22bb91bf 100644 --- a/core/feature/src/main/java/pokitmons/pokit/core/feature/model/paging/SimplePaging.kt +++ b/core/feature/src/main/java/pokitmons/pokit/core/feature/model/paging/SimplePaging.kt @@ -18,11 +18,11 @@ class SimplePaging ( private val initPage: Int = 0, private val firstRequestPage: Int = 3, ) { - private val _pagingData : MutableStateFlow> = MutableStateFlow(emptyList()) - val pagingData : StateFlow> = _pagingData.asStateFlow() + private val _pagingData: MutableStateFlow> = MutableStateFlow(emptyList()) + val pagingData: StateFlow> = _pagingData.asStateFlow() - private val _pagingState : MutableStateFlow = MutableStateFlow(PagingState.IDLE) - val pagingState : StateFlow = _pagingState.asStateFlow() + private val _pagingState: MutableStateFlow = MutableStateFlow(PagingState.IDLE) + val pagingState: StateFlow = _pagingState.asStateFlow() private var pagingDataRequestJob: Job? = null private var currentPageIndex = 0 diff --git a/core/feature/src/test/java/pokitmons/pokit/core/feature/SimplePagingUnitTest.kt b/core/feature/src/test/java/pokitmons/pokit/core/feature/SimplePagingUnitTest.kt index 3475d285..a6914467 100644 --- a/core/feature/src/test/java/pokitmons/pokit/core/feature/SimplePagingUnitTest.kt +++ b/core/feature/src/test/java/pokitmons/pokit/core/feature/SimplePagingUnitTest.kt @@ -25,8 +25,8 @@ class SimplePagingUnitTest : DescribeSpec({ loadTime = PAGE_LOAD_TIME, totalItemCount = TOTAL_ITEM_COUNT ), - getKeyFromItem = {it}, - firstRequestPage = FIRST_REQUEST_PAGE_SAMPLE, + getKeyFromItem = { it }, + firstRequestPage = FIRST_REQUEST_PAGE_SAMPLE ) context("새로고침을 한 상황에서") { diff --git a/core/feature/src/test/java/pokitmons/pokit/core/feature/model/TestPagingSource.kt b/core/feature/src/test/java/pokitmons/pokit/core/feature/model/TestPagingSource.kt index d3144f60..8d14f0b8 100644 --- a/core/feature/src/test/java/pokitmons/pokit/core/feature/model/TestPagingSource.kt +++ b/core/feature/src/test/java/pokitmons/pokit/core/feature/model/TestPagingSource.kt @@ -21,7 +21,7 @@ class TestPagingSource( val startIndex = pageIndex * pageSize val lastIndex = min(((pageIndex + 1) * pageSize), totalItemCount) - val itemList = (startIndex until lastIndex).map { "${it}번째 아이템" } + val itemList = (startIndex until lastIndex).map { "${it}번째 아이템" } return PagingLoadResult.Success(itemList) } } diff --git a/feature/addlink/src/main/java/com/strayalpaca/addlink/AddLinkViewModel.kt b/feature/addlink/src/main/java/com/strayalpaca/addlink/AddLinkViewModel.kt index 36ab79c1..327c3395 100644 --- a/feature/addlink/src/main/java/com/strayalpaca/addlink/AddLinkViewModel.kt +++ b/feature/addlink/src/main/java/com/strayalpaca/addlink/AddLinkViewModel.kt @@ -56,7 +56,7 @@ class AddLinkViewModel @Inject constructor( ) : ContainerHost, ViewModel() { override val container: Container = container(AddLinkScreenState()) - private val pokitPagingSource = object: PagingSource { + private val pokitPagingSource = object : PagingSource { override suspend fun load(pageIndex: Int, pageSize: Int): PagingLoadResult { val response = getPokitsUseCase.getPokits(page = pageIndex, size = pageSize) return PagingLoadResult.fromPokitResult( diff --git a/feature/addpokit/src/main/java/com/strayalpaca/addpokit/AddPokitViewModel.kt b/feature/addpokit/src/main/java/com/strayalpaca/addpokit/AddPokitViewModel.kt index 40c8a6ba..e3f5425a 100644 --- a/feature/addpokit/src/main/java/com/strayalpaca/addpokit/AddPokitViewModel.kt +++ b/feature/addpokit/src/main/java/com/strayalpaca/addpokit/AddPokitViewModel.kt @@ -51,7 +51,7 @@ class AddPokitViewModel @Inject constructor( private val pokitId = savedStateHandle.get("pokit_id")?.toIntOrNull() - private val pokitPagingSource = object: PagingSource { + private val pokitPagingSource = object : PagingSource { override suspend fun load(pageIndex: Int, pageSize: Int): PagingLoadResult { val response = getPokitsUseCase.getPokits(page = pageIndex, size = pageSize) return PagingLoadResult.fromPokitResult( diff --git a/feature/alarm/src/main/java/pokitmons/pokit/alarm/AlarmViewModel.kt b/feature/alarm/src/main/java/pokitmons/pokit/alarm/AlarmViewModel.kt index d5d0bd2e..0a375609 100644 --- a/feature/alarm/src/main/java/pokitmons/pokit/alarm/AlarmViewModel.kt +++ b/feature/alarm/src/main/java/pokitmons/pokit/alarm/AlarmViewModel.kt @@ -21,7 +21,7 @@ class AlarmViewModel @Inject constructor( private val deleteAlertUseCase: DeleteAlertUseCase, ) : ViewModel() { - private val alarmPagingSource = object: PagingSource { + private val alarmPagingSource = object : PagingSource { override suspend fun load(pageIndex: Int, pageSize: Int): PagingLoadResult { val response = getAlertsUseCase.getAlerts(page = pageIndex, size = pageSize) return PagingLoadResult.fromPokitResult( diff --git a/feature/home/src/main/java/pokitmons/pokit/home/pokit/PokitViewModel.kt b/feature/home/src/main/java/pokitmons/pokit/home/pokit/PokitViewModel.kt index da537a88..dfe94ae6 100644 --- a/feature/home/src/main/java/pokitmons/pokit/home/pokit/PokitViewModel.kt +++ b/feature/home/src/main/java/pokitmons/pokit/home/pokit/PokitViewModel.kt @@ -121,7 +121,6 @@ class PokitViewModel @Inject constructor( pokitPaging.modifyItem(linkCountDecreasedPokit.copy(count = decreasedLinkCount)) } } - } } } @@ -154,7 +153,7 @@ class PokitViewModel @Inject constructor( var screenType = mutableStateOf(ScreenType.Pokit) private set - private val pokitPagingSource = object: PagingSource { + private val pokitPagingSource = object : PagingSource { override suspend fun load(pageIndex: Int, pageSize: Int): PagingLoadResult { val sort = when (pokitsSortOrder.value) { PokitsSortOrder.Latest -> PokitsSort.RECENT @@ -163,7 +162,7 @@ class PokitViewModel @Inject constructor( val response = getPokitsUseCase.getPokits(size = pageSize, page = pageIndex, sort = sort) return PagingLoadResult.fromPokitResult( pokitResult = response, - mapper = { domainPokits -> domainPokits.map { Pokit.fromDomainPokit(it) }} + mapper = { domainPokits -> domainPokits.map { Pokit.fromDomainPokit(it) } } ) } } @@ -174,9 +173,9 @@ class PokitViewModel @Inject constructor( coroutineScope = viewModelScope ) - private val linksPagingSource = object: PagingSource { + private val linksPagingSource = object : PagingSource { override suspend fun load(pageIndex: Int, pageSize: Int): PagingLoadResult { - val sort = when(linksSortOrder.value) { + val sort = when (linksSortOrder.value) { UncategorizedLinksSortOrder.Latest -> LinksSort.RECENT UncategorizedLinksSortOrder.Older -> LinksSort.OLDER } diff --git a/feature/pokitdetail/src/main/java/com/strayalpaca/pokitdetail/PokitDetailViewModel.kt b/feature/pokitdetail/src/main/java/com/strayalpaca/pokitdetail/PokitDetailViewModel.kt index b220c328..d4705edf 100644 --- a/feature/pokitdetail/src/main/java/com/strayalpaca/pokitdetail/PokitDetailViewModel.kt +++ b/feature/pokitdetail/src/main/java/com/strayalpaca/pokitdetail/PokitDetailViewModel.kt @@ -46,12 +46,12 @@ class PokitDetailViewModel @Inject constructor( private val getLinkUseCase: GetLinkUseCase, savedStateHandle: SavedStateHandle, ) : ViewModel() { - private val pokitPagingSource = object: PagingSource { + private val pokitPagingSource = object : PagingSource { override suspend fun load(pageIndex: Int, pageSize: Int): PagingLoadResult { val response = getPokitsUseCase.getPokits(size = pageSize, page = pageIndex) return PagingLoadResult.fromPokitResult( pokitResult = response, - mapper = { domainPokits -> domainPokits.map { Pokit.fromDomainPokit(it) }} + mapper = { domainPokits -> domainPokits.map { Pokit.fromDomainPokit(it) } } ) } } @@ -61,7 +61,7 @@ class PokitDetailViewModel @Inject constructor( coroutineScope = viewModelScope ) - private val linkPagingSource = object: PagingSource { + private val linkPagingSource = object : PagingSource { override suspend fun load(pageIndex: Int, pageSize: Int): PagingLoadResult { val currentPokit = state.value.currentPokit val currentFilter = state.value.currentFilter @@ -73,7 +73,7 @@ class PokitDetailViewModel @Inject constructor( categoryId = categoryId, sort = sort, isRead = if (currentFilter.notReadChecked) false else null, - favorite = if (currentFilter.bookmarkChecked) true else null, + favorite = if (currentFilter.bookmarkChecked) true else null ) return PagingLoadResult.fromPokitResult( pokitResult = response, diff --git a/feature/search/src/main/java/pokitmons/pokit/search/SearchViewModel.kt b/feature/search/src/main/java/pokitmons/pokit/search/SearchViewModel.kt index 0281d6ee..7835fc76 100644 --- a/feature/search/src/main/java/pokitmons/pokit/search/SearchViewModel.kt +++ b/feature/search/src/main/java/pokitmons/pokit/search/SearchViewModel.kt @@ -57,7 +57,7 @@ class SearchViewModel @Inject constructor( initLinkRemoveEventDetector() } - private val linkPagingSource = object: PagingSource { + private val linkPagingSource = object : PagingSource { override suspend fun load(pageIndex: Int, pageSize: Int): PagingLoadResult { val currentFilter = state.value.filter ?: Filter() @@ -82,7 +82,6 @@ class SearchViewModel @Inject constructor( mapper = { domainLinks -> domainLinks.map { Link.fromDomainLink(it) } } ) } - } private val linkPaging = SimplePaging( @@ -91,7 +90,7 @@ class SearchViewModel @Inject constructor( coroutineScope = viewModelScope ) - private val pokitPagingSource = object: PagingSource { + private val pokitPagingSource = object : PagingSource { override suspend fun load(pageIndex: Int, pageSize: Int): PagingLoadResult { val response = getPokitsUseCase.getPokits(page = pageIndex, size = pageSize) return PagingLoadResult.fromPokitResult(