From b574697805bb0b53d1e16ed26ecd65202628d410 Mon Sep 17 00:00:00 2001 From: hj1115hj Date: Tue, 24 Sep 2024 00:03:56 +0900 Subject: [PATCH 1/8] =?UTF-8?q?[feature]=20kk=20count=20=EB=AA=A8=EC=95=84?= =?UTF-8?q?=EC=84=9C=20=EB=B3=B4=EB=82=B4=EA=B8=B0=20=EC=8A=A4=EC=BC=88?= =?UTF-8?q?=EB=A0=88=ED=86=A4=20=EC=BD=94=EB=93=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/team/ppac/detail/DetailViewModel.kt | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/feature/detail/src/main/java/team/ppac/detail/DetailViewModel.kt b/feature/detail/src/main/java/team/ppac/detail/DetailViewModel.kt index f23a9215..9cf570a6 100644 --- a/feature/detail/src/main/java/team/ppac/detail/DetailViewModel.kt +++ b/feature/detail/src/main/java/team/ppac/detail/DetailViewModel.kt @@ -29,6 +29,8 @@ class DetailViewModel @Inject constructor( private val emitRefreshEventUseCase: EmitRefreshEventUseCase, ) : BaseViewModel(savedStateHandle) { + var isFirstClickEvent : Boolean = false + var reactionCount = 0 init { launch { getMeme(currentState.memeId) @@ -51,8 +53,13 @@ class DetailViewModel @Inject constructor( override suspend fun handleIntent(intent: DetailIntent) { when (intent) { is DetailIntent.ClickFunnyButton -> { - incrementReactionCount() - postSideEffect(DetailSideEffect.RunRisingEffect) + if(reactionCount == 0 && "연타"){ + incrementReactionCount() + postSideEffect(DetailSideEffect.RunRisingEffect) + reactionCount++ + } else { + updateReactionCount() + } } is DetailIntent.ClickBackButton -> { @@ -154,17 +161,22 @@ class DetailViewModel @Inject constructor( ) ) } + } + + private suspend fun updateReactionCount(reactionCount :Int){ runCatching { - reactMemeUseCase(currentState.memeId) + reactMemeUseCase(currentState.memeId,reactionCount) }.onFailure { reduce { copy( detailMemeUiModel = detailMemeUiModel.copy( - reactionCount = detailMemeUiModel.reactionCount - 1, + reactionCount = detailMemeUiModel.reactionCount - reactionCount, isReaction = false ) ) } + }.onSuccess { + reactionCount = 0 } } From 569c214d7dc61a2fe861f5fedcefa242a9b64a4e Mon Sep 17 00:00:00 2001 From: hyejin12-ju Date: Wed, 25 Sep 2024 01:16:09 +0900 Subject: [PATCH 2/8] =?UTF-8?q?[feat]=20=EB=A6=AC=EC=95=A1=EC=85=98=20?= =?UTF-8?q?=EB=AA=A8=EC=95=84=EC=84=9C=20=EB=B3=B4=EB=82=B4=EA=B8=B0=20?= =?UTF-8?q?=EB=94=94=ED=85=8C=EC=9D=BC=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ppac/common/kotlin/model/ReactionState.kt | 56 +++++++++++++++++++ .../util/extension/NoRippleClickable.kt | 2 +- .../java/team/ppac/detail/DetailViewModel.kt | 37 ++++++++---- 3 files changed, 84 insertions(+), 11 deletions(-) create mode 100644 core/common/kotlin/src/main/java/team/ppac/common/kotlin/model/ReactionState.kt diff --git a/core/common/kotlin/src/main/java/team/ppac/common/kotlin/model/ReactionState.kt b/core/common/kotlin/src/main/java/team/ppac/common/kotlin/model/ReactionState.kt new file mode 100644 index 00000000..3ab9e6b2 --- /dev/null +++ b/core/common/kotlin/src/main/java/team/ppac/common/kotlin/model/ReactionState.kt @@ -0,0 +1,56 @@ +package team.ppac.common.kotlin.model + +import java.util.concurrent.atomic.AtomicBoolean +import java.util.concurrent.atomic.AtomicInteger + +class ReactionState { + private val _isFirstClickEvent = AtomicBoolean(true) + private val _reactionCount = AtomicInteger(0) + private val _isUpdating = AtomicBoolean(false) + private var lastClickTime: Long = 0 + + val reactionCount: Int + get() = _reactionCount.get() + + val isUpdating: Boolean + get() = _isUpdating.get() + + val isFirstClickEvent: Boolean + get() = _isFirstClickEvent.get() + + fun setIsFirstClickEvent(value: Boolean) { + _isFirstClickEvent.set(value) + } + + fun addReactionCount(count: Int) { + _reactionCount.addAndGet(count) + } + + fun startUpdate() { + _isUpdating.compareAndSet(false, true) + .also { print("isRelease Setting = ${_isUpdating.get()}") } + } + + fun endUpdate() { + _isUpdating.set(false) + } + + fun releaseState() { + _reactionCount.set(0) + _isFirstClickEvent.set(true) + } + + fun isDoubleClickEvent(): Boolean { + val currentClickTime: Long = System.currentTimeMillis() + return if (currentClickTime - lastClickTime <= DOUBLE_CLICK_INTERVAL) { + true + } else { + lastClickTime = System.currentTimeMillis() + false + } + } + + companion object { + private const val DOUBLE_CLICK_INTERVAL = 400 + } +} \ No newline at end of file diff --git a/core/designsystem/src/main/kotlin/team/ppac/designsystem/util/extension/NoRippleClickable.kt b/core/designsystem/src/main/kotlin/team/ppac/designsystem/util/extension/NoRippleClickable.kt index bcbf2549..0d4dfe4a 100644 --- a/core/designsystem/src/main/kotlin/team/ppac/designsystem/util/extension/NoRippleClickable.kt +++ b/core/designsystem/src/main/kotlin/team/ppac/designsystem/util/extension/NoRippleClickable.kt @@ -62,6 +62,6 @@ private fun Modifier.singleClickable( enabled = enabled, onClickLabel = onClickLabel, role = role, - onClick = { multipleEventsCutter.processEvent(onClick) }, + onClick = { onClick() }, // Todo(hyejin.ju) 함수 분리해야함 ) } diff --git a/feature/detail/src/main/java/team/ppac/detail/DetailViewModel.kt b/feature/detail/src/main/java/team/ppac/detail/DetailViewModel.kt index 9cf570a6..bb165f44 100644 --- a/feature/detail/src/main/java/team/ppac/detail/DetailViewModel.kt +++ b/feature/detail/src/main/java/team/ppac/detail/DetailViewModel.kt @@ -5,7 +5,9 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.lifecycle.SavedStateHandle import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.delay import team.ppac.common.android.base.BaseViewModel +import team.ppac.common.kotlin.model.ReactionState import team.ppac.designsystem.foundation.FarmemeIcon import team.ppac.detail.mapper.toDetailMemeUiModel import team.ppac.detail.mvi.DetailIntent @@ -29,8 +31,8 @@ class DetailViewModel @Inject constructor( private val emitRefreshEventUseCase: EmitRefreshEventUseCase, ) : BaseViewModel(savedStateHandle) { - var isFirstClickEvent : Boolean = false - var reactionCount = 0 + private val reactionState = ReactionState() + init { launch { getMeme(currentState.memeId) @@ -50,15 +52,24 @@ class DetailViewModel @Inject constructor( } } + override suspend fun handleIntent(intent: DetailIntent) { when (intent) { is DetailIntent.ClickFunnyButton -> { - if(reactionCount == 0 && "연타"){ + if (!reactionState.isUpdating && reactionState.isDoubleClickEvent()) { incrementReactionCount() postSideEffect(DetailSideEffect.RunRisingEffect) - reactionCount++ + reactionState.addReactionCount(1) + if (reactionState.isFirstClickEvent) { + reactionState.setIsFirstClickEvent(false) + launch { + updateReactionCountWithDelay() + } + } } else { - updateReactionCount() + incrementReactionCount() + postSideEffect(DetailSideEffect.RunRisingEffect) + updateReactionCount(1) } } @@ -163,20 +174,26 @@ class DetailViewModel @Inject constructor( } } - private suspend fun updateReactionCount(reactionCount :Int){ + private suspend fun updateReactionCountWithDelay() { + delay(1000) + reactionState.startUpdate() + updateReactionCount(reactionState.reactionCount) // Todo(hyejin.ju) API 연결 해야함 + reactionState.releaseState() + reactionState.endUpdate() + } + + private suspend fun updateReactionCount(reactionCount: Int) { runCatching { - reactMemeUseCase(currentState.memeId,reactionCount) + reactMemeUseCase(currentState.memeId) }.onFailure { reduce { copy( detailMemeUiModel = detailMemeUiModel.copy( - reactionCount = detailMemeUiModel.reactionCount - reactionCount, + reactionCount = detailMemeUiModel.reactionCount - 1, isReaction = false ) ) } - }.onSuccess { - reactionCount = 0 } } From 5bb70d127cc5d3a559a09a0b122c8cac39a19d26 Mon Sep 17 00:00:00 2001 From: hyejin12-ju Date: Wed, 25 Sep 2024 22:55:34 +0900 Subject: [PATCH 3/8] =?UTF-8?q?[feat]=20=EC=B6=94=EC=B2=9C=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EB=A6=AC=EC=95=A1=EC=85=98=20=EB=AA=A8?= =?UTF-8?q?=EC=95=84=EC=84=9C=20=EB=B3=B4=EB=82=B4=EA=B8=B0=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../recommendation/RecommendationViewModel.kt | 62 +++++++++++++++---- 1 file changed, 49 insertions(+), 13 deletions(-) diff --git a/feature/recommendation/src/main/java/team/ppac/recommendation/RecommendationViewModel.kt b/feature/recommendation/src/main/java/team/ppac/recommendation/RecommendationViewModel.kt index c7856496..6af09273 100644 --- a/feature/recommendation/src/main/java/team/ppac/recommendation/RecommendationViewModel.kt +++ b/feature/recommendation/src/main/java/team/ppac/recommendation/RecommendationViewModel.kt @@ -10,7 +10,9 @@ import kotlinx.coroutines.async import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.delay import team.ppac.common.android.base.BaseViewModel +import team.ppac.common.kotlin.model.ReactionState import team.ppac.designsystem.foundation.FarmemeIcon +import team.ppac.domain.model.Meme import team.ppac.domain.model.MemeWatchType import team.ppac.domain.usecase.DeleteSavedMemeUseCase import team.ppac.domain.usecase.GetThisWeekRecommendMemesUseCase @@ -36,6 +38,8 @@ class RecommendationViewModel @Inject constructor( ) : BaseViewModel( savedStateHandle ) { + private val reactionState = ReactionState() + init { launch { initialAction() @@ -57,26 +61,34 @@ class RecommendationViewModel @Inject constructor( override suspend fun handleIntent(intent: RecommendationIntent) { when (intent) { is RecommendationIntent.ClickButton.LoL -> { - postSideEffect(RecommendationSideEffect.RunRisingEffect(intent.meme)) - reduce { - updateReaction(intent.meme) { - it.copy( - reactionCount = it.reactionCount + 1, - isReaction = true, - ) + if (!reactionState.isUpdating && reactionState.isDoubleClickEvent()) { + postSideEffect(RecommendationSideEffect.RunRisingEffect(intent.meme)) + reduce { + updateReaction(intent.meme) { + it.copy( + reactionCount = it.reactionCount + 1, + isReaction = true, + ) + } } - } - runCatching { - reactMemeUseCase(intent.meme.id) - }.onFailure { + reactionState.addReactionCount(1) + if (reactionState.isFirstClickEvent) { + reactionState.setIsFirstClickEvent(false) + launch { + updateReactionCountWithDelay(intent.meme) + } + } + } else { + postSideEffect(RecommendationSideEffect.RunRisingEffect(intent.meme)) reduce { updateReaction(intent.meme) { it.copy( - reactionCount = it.reactionCount - 1, - isReaction = false + reactionCount = it.reactionCount + 1, + isReaction = true, ) } } + updateReactionCount(intent.meme, 1) } } @@ -159,4 +171,28 @@ class RecommendationViewModel @Inject constructor( } } } + + private suspend fun updateReactionCountWithDelay(meme: Meme) { + delay(1000) + reactionState.startUpdate() + updateReactionCount(meme, reactionState.reactionCount) // Todo(hyejin.ju) API 연결 해야함 + reactionState.releaseState() + reactionState.endUpdate() + } + + private suspend fun updateReactionCount(meme: Meme, reactionCount: Int) { + runCatching { + reactMemeUseCase(meme.id) + }.onFailure { + reduce { + updateReaction(meme) { + it.copy( + reactionCount = it.reactionCount - 1, + isReaction = false + ) + } + } + } + } + } \ No newline at end of file From b5519f6509e49e93eab8618bb1ebe53d14740de6 Mon Sep 17 00:00:00 2001 From: hyejin12-ju Date: Thu, 26 Sep 2024 01:16:24 +0900 Subject: [PATCH 4/8] =?UTF-8?q?[feat]=20=EB=B0=88=20reactionCount=20api=20?= =?UTF-8?q?=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../team/ppac/common/kotlin/model/ReactionState.kt | 1 - .../team/ppac/data/repository/MemeRepositoryImpl.kt | 4 ++-- .../java/team/ppac/domain/repository/MemeRepository.kt | 3 +-- .../java/team/ppac/domain/usecase/ReactMemeUseCase.kt | 6 +++--- .../src/main/kotlin/team/ppac/remote/api/MemeApi.kt | 7 ++++++- .../team/ppac/remote/datasource/MemeDataSource.kt | 2 +- .../ppac/remote/datasource/impl/MemeDataSourceImpl.kt | 5 +++-- .../ppac/remote/model/request/meme/ReactMemeRequest.kt | 10 ++++++++++ .../src/main/java/team/ppac/detail/DetailViewModel.kt | 6 +++--- .../ppac/recommendation/RecommendationViewModel.kt | 6 +++--- 10 files changed, 32 insertions(+), 18 deletions(-) create mode 100644 core/remote/src/main/kotlin/team/ppac/remote/model/request/meme/ReactMemeRequest.kt diff --git a/core/common/kotlin/src/main/java/team/ppac/common/kotlin/model/ReactionState.kt b/core/common/kotlin/src/main/java/team/ppac/common/kotlin/model/ReactionState.kt index 3ab9e6b2..1842040d 100644 --- a/core/common/kotlin/src/main/java/team/ppac/common/kotlin/model/ReactionState.kt +++ b/core/common/kotlin/src/main/java/team/ppac/common/kotlin/model/ReactionState.kt @@ -28,7 +28,6 @@ class ReactionState { fun startUpdate() { _isUpdating.compareAndSet(false, true) - .also { print("isRelease Setting = ${_isUpdating.get()}") } } fun endUpdate() { diff --git a/core/data/src/main/java/team/ppac/data/repository/MemeRepositoryImpl.kt b/core/data/src/main/java/team/ppac/data/repository/MemeRepositoryImpl.kt index 149ece18..f51c814c 100644 --- a/core/data/src/main/java/team/ppac/data/repository/MemeRepositoryImpl.kt +++ b/core/data/src/main/java/team/ppac/data/repository/MemeRepositoryImpl.kt @@ -58,8 +58,8 @@ internal class MemeRepositoryImpl @Inject constructor( ) } - override suspend fun reactMeme(memeId: String): Boolean { - return memeDataSource.reactMeme(memeId) + override suspend fun reactMeme(memeId: String, count: Int): Boolean { + return memeDataSource.reactMeme(memeId, count) } override suspend fun watchMeme(memeId: String, watchType: MemeWatchType): Boolean { diff --git a/core/domain/src/main/java/team/ppac/domain/repository/MemeRepository.kt b/core/domain/src/main/java/team/ppac/domain/repository/MemeRepository.kt index 5f5821c2..768a032c 100644 --- a/core/domain/src/main/java/team/ppac/domain/repository/MemeRepository.kt +++ b/core/domain/src/main/java/team/ppac/domain/repository/MemeRepository.kt @@ -1,6 +1,5 @@ package team.ppac.domain.repository -import androidx.paging.PagingData import kotlinx.coroutines.flow.Flow import team.ppac.domain.model.Meme import team.ppac.domain.model.MemeWatchType @@ -15,7 +14,7 @@ interface MemeRepository { keyword: String, getCurrentPage: (Int) -> Unit ): MemeWithPagination - suspend fun reactMeme(memeId: String): Boolean + suspend fun reactMeme(memeId: String, count: Int): Boolean suspend fun watchMeme( memeId: String, watchType: MemeWatchType, diff --git a/core/domain/src/main/java/team/ppac/domain/usecase/ReactMemeUseCase.kt b/core/domain/src/main/java/team/ppac/domain/usecase/ReactMemeUseCase.kt index 2a188e46..f913e9c2 100644 --- a/core/domain/src/main/java/team/ppac/domain/usecase/ReactMemeUseCase.kt +++ b/core/domain/src/main/java/team/ppac/domain/usecase/ReactMemeUseCase.kt @@ -4,13 +4,13 @@ import team.ppac.domain.repository.MemeRepository import javax.inject.Inject interface ReactMemeUseCase { - suspend operator fun invoke(memeId: String): Boolean + suspend operator fun invoke(memeId: String, count: Int): Boolean } internal class ReactMemeUseCaseImpl @Inject constructor( private val memeRepository: MemeRepository, ) : ReactMemeUseCase { - override suspend fun invoke(memeId: String): Boolean { - return memeRepository.reactMeme(memeId) + override suspend fun invoke(memeId: String, count: Int): Boolean { + return memeRepository.reactMeme(memeId, count) } } \ No newline at end of file diff --git a/core/remote/src/main/kotlin/team/ppac/remote/api/MemeApi.kt b/core/remote/src/main/kotlin/team/ppac/remote/api/MemeApi.kt index 10960dbf..459eed30 100644 --- a/core/remote/src/main/kotlin/team/ppac/remote/api/MemeApi.kt +++ b/core/remote/src/main/kotlin/team/ppac/remote/api/MemeApi.kt @@ -1,10 +1,12 @@ package team.ppac.remote.api +import retrofit2.http.Body import retrofit2.http.DELETE import retrofit2.http.GET import retrofit2.http.POST import retrofit2.http.Path import retrofit2.http.Query +import team.ppac.remote.model.request.meme.ReactMemeRequest import team.ppac.remote.model.response.meme.MemeResponse import team.ppac.remote.model.response.user.SavedMemesResponse @@ -32,7 +34,10 @@ internal interface MemeApi { ): SavedMemesResponse @POST("/api/meme/{memeId}/reaction") - suspend fun reactMeme(@Path("memeId") memeId: String): Boolean + suspend fun reactMeme( + @Path("memeId") memeId: String, + @Body reactMemeRequest: ReactMemeRequest + ): Boolean @POST("/api/meme/{memeId}/watch/{type}") suspend fun watchMeme( diff --git a/core/remote/src/main/kotlin/team/ppac/remote/datasource/MemeDataSource.kt b/core/remote/src/main/kotlin/team/ppac/remote/datasource/MemeDataSource.kt index d4d4c9de..8493d8cb 100644 --- a/core/remote/src/main/kotlin/team/ppac/remote/datasource/MemeDataSource.kt +++ b/core/remote/src/main/kotlin/team/ppac/remote/datasource/MemeDataSource.kt @@ -13,7 +13,7 @@ interface MemeDataSource { page: Int, size: Int, ): SavedMemesResponse - suspend fun reactMeme(memeId: String): Boolean + suspend fun reactMeme(memeId: String, count: Int): Boolean suspend fun watchMeme( memeId: String, type: String, diff --git a/core/remote/src/main/kotlin/team/ppac/remote/datasource/impl/MemeDataSourceImpl.kt b/core/remote/src/main/kotlin/team/ppac/remote/datasource/impl/MemeDataSourceImpl.kt index ec17967c..78df66bd 100644 --- a/core/remote/src/main/kotlin/team/ppac/remote/datasource/impl/MemeDataSourceImpl.kt +++ b/core/remote/src/main/kotlin/team/ppac/remote/datasource/impl/MemeDataSourceImpl.kt @@ -2,6 +2,7 @@ package team.ppac.remote.datasource.impl import team.ppac.remote.api.MemeApi import team.ppac.remote.datasource.MemeDataSource +import team.ppac.remote.model.request.meme.ReactMemeRequest import team.ppac.remote.model.response.meme.MemeResponse import team.ppac.remote.model.response.user.SavedMemesResponse import javax.inject.Inject @@ -29,8 +30,8 @@ internal class MemeDataSourceImpl @Inject constructor( return memeApi.getSearchMemes(keyword, page, size) } - override suspend fun reactMeme(memeId: String): Boolean { - return memeApi.reactMeme(memeId) + override suspend fun reactMeme(memeId: String, count: Int): Boolean { + return memeApi.reactMeme(memeId, reactMemeRequest = ReactMemeRequest(count)) } override suspend fun watchMeme(memeId: String, type: String): Boolean { diff --git a/core/remote/src/main/kotlin/team/ppac/remote/model/request/meme/ReactMemeRequest.kt b/core/remote/src/main/kotlin/team/ppac/remote/model/request/meme/ReactMemeRequest.kt new file mode 100644 index 00000000..b152dc9c --- /dev/null +++ b/core/remote/src/main/kotlin/team/ppac/remote/model/request/meme/ReactMemeRequest.kt @@ -0,0 +1,10 @@ +package team.ppac.remote.model.request.meme + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class ReactMemeRequest ( + @field:Json(name = "count") + val count: Int, +) \ No newline at end of file diff --git a/feature/detail/src/main/java/team/ppac/detail/DetailViewModel.kt b/feature/detail/src/main/java/team/ppac/detail/DetailViewModel.kt index bb165f44..c9cffdde 100644 --- a/feature/detail/src/main/java/team/ppac/detail/DetailViewModel.kt +++ b/feature/detail/src/main/java/team/ppac/detail/DetailViewModel.kt @@ -177,19 +177,19 @@ class DetailViewModel @Inject constructor( private suspend fun updateReactionCountWithDelay() { delay(1000) reactionState.startUpdate() - updateReactionCount(reactionState.reactionCount) // Todo(hyejin.ju) API 연결 해야함 + updateReactionCount(reactionState.reactionCount) reactionState.releaseState() reactionState.endUpdate() } private suspend fun updateReactionCount(reactionCount: Int) { runCatching { - reactMemeUseCase(currentState.memeId) + reactMemeUseCase(currentState.memeId, reactionCount) }.onFailure { reduce { copy( detailMemeUiModel = detailMemeUiModel.copy( - reactionCount = detailMemeUiModel.reactionCount - 1, + reactionCount = detailMemeUiModel.reactionCount - reactionCount, isReaction = false ) ) diff --git a/feature/recommendation/src/main/java/team/ppac/recommendation/RecommendationViewModel.kt b/feature/recommendation/src/main/java/team/ppac/recommendation/RecommendationViewModel.kt index 6af09273..1e6dc148 100644 --- a/feature/recommendation/src/main/java/team/ppac/recommendation/RecommendationViewModel.kt +++ b/feature/recommendation/src/main/java/team/ppac/recommendation/RecommendationViewModel.kt @@ -175,19 +175,19 @@ class RecommendationViewModel @Inject constructor( private suspend fun updateReactionCountWithDelay(meme: Meme) { delay(1000) reactionState.startUpdate() - updateReactionCount(meme, reactionState.reactionCount) // Todo(hyejin.ju) API 연결 해야함 + updateReactionCount(meme, reactionState.reactionCount) reactionState.releaseState() reactionState.endUpdate() } private suspend fun updateReactionCount(meme: Meme, reactionCount: Int) { runCatching { - reactMemeUseCase(meme.id) + reactMemeUseCase(meme.id, reactionCount) }.onFailure { reduce { updateReaction(meme) { it.copy( - reactionCount = it.reactionCount - 1, + reactionCount = it.reactionCount - reactionCount, isReaction = false ) } From 9941d5a90c3eaa5b9d09a52487401c41c4597715 Mon Sep 17 00:00:00 2001 From: hyejin12-ju Date: Thu, 26 Sep 2024 01:47:54 +0900 Subject: [PATCH 5/8] =?UTF-8?q?[feat]=20isDebounceClick=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9=20flag=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../designsystem/component/button/Button.kt | 3 ++- .../util/extension/NoRippleClickable.kt | 20 +++++++++++++------ .../ppac/detail/component/DetailContent.kt | 1 + .../recommendation/component/ActionButtons.kt | 1 + 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/core/designsystem/src/main/kotlin/team/ppac/designsystem/component/button/Button.kt b/core/designsystem/src/main/kotlin/team/ppac/designsystem/component/button/Button.kt index 98ab9b08..ec7ae060 100644 --- a/core/designsystem/src/main/kotlin/team/ppac/designsystem/component/button/Button.kt +++ b/core/designsystem/src/main/kotlin/team/ppac/designsystem/component/button/Button.kt @@ -45,6 +45,7 @@ fun FarmemeCircleButton( @Composable fun FarmemeWeakButton( modifier: Modifier = Modifier, + isDebounceClick: Boolean = true, backgroundColor: Color, text: String, textColor: Color, @@ -55,7 +56,7 @@ fun FarmemeWeakButton( modifier = modifier .clip(FarmemeRadius.Radius25.shape) .background(color = backgroundColor) - .noRippleClickable(onClick = onClick) + .noRippleClickable(onClick = onClick, isDebounceClick = isDebounceClick) .padding(15.dp), horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically, diff --git a/core/designsystem/src/main/kotlin/team/ppac/designsystem/util/extension/NoRippleClickable.kt b/core/designsystem/src/main/kotlin/team/ppac/designsystem/util/extension/NoRippleClickable.kt index 0d4dfe4a..426b86b6 100644 --- a/core/designsystem/src/main/kotlin/team/ppac/designsystem/util/extension/NoRippleClickable.kt +++ b/core/designsystem/src/main/kotlin/team/ppac/designsystem/util/extension/NoRippleClickable.kt @@ -15,13 +15,15 @@ import team.ppac.common.kotlin.model.MultipleEventsCutter @SuppressLint("UnnecessaryComposedModifier") fun Modifier.noRippleClickable( enabled: Boolean = true, + isDebounceClick: Boolean = true, onClickLabel: String? = null, role: Role? = null, - onClick: () -> Unit, + onClick: () -> Unit ): Modifier = composed { this then singleClickable( indication = null, enabled = enabled, + isDebounceClick = isDebounceClick, onClickLabel = onClickLabel, role = role, onClick = onClick @@ -31,17 +33,19 @@ fun Modifier.noRippleClickable( @SuppressLint("UnnecessaryComposedModifier") fun Modifier.rippleClickable( rippleColor: Color = Color.Unspecified, + isDebounceClick: Boolean = true, enabled: Boolean = true, onClickLabel: String? = null, role: Role? = null, - onClick: () -> Unit, + onClick: () -> Unit ): Modifier = composed { this then singleClickable( indication = rememberRipple(color = rippleColor), enabled = enabled, + isDebounceClick = isDebounceClick, onClickLabel = onClickLabel, role = role, - onClick = onClick + onClick = onClick, ) } @@ -49,19 +53,23 @@ fun Modifier.rippleClickable( private fun Modifier.singleClickable( indication: Indication?, enabled: Boolean = true, + debounceMillis: Long = 300L, + isDebounceClick: Boolean = true, onClickLabel: String? = null, role: Role? = null, - debounceMillis: Long = 300L, onClick: () -> Unit, ): Modifier = composed { val multipleEventsCutter = remember { MultipleEventsCutter(debounceMillis) } - clickable( interactionSource = remember { MutableInteractionSource() }, indication = indication, enabled = enabled, onClickLabel = onClickLabel, role = role, - onClick = { onClick() }, // Todo(hyejin.ju) 함수 분리해야함 + onClick = if (isDebounceClick) { + { multipleEventsCutter.processEvent(onClick) } + } else { + { onClick() } + }, ) } diff --git a/feature/detail/src/main/java/team/ppac/detail/component/DetailContent.kt b/feature/detail/src/main/java/team/ppac/detail/component/DetailContent.kt index 03cc660d..7187e04b 100644 --- a/feature/detail/src/main/java/team/ppac/detail/component/DetailContent.kt +++ b/feature/detail/src/main/java/team/ppac/detail/component/DetailContent.kt @@ -254,6 +254,7 @@ fun DetailFunnyButton( .background(color = FarmemeTheme.skeletonColor.primary) .rippleClickable( rippleColor = FarmemeTheme.skeletonColor.secondary, + isDebounceClick = false, onClick = { if (!isLoading) { coroutineScope.launch { diff --git a/feature/recommendation/src/main/java/team/ppac/recommendation/component/ActionButtons.kt b/feature/recommendation/src/main/java/team/ppac/recommendation/component/ActionButtons.kt index 05531021..c42cc868 100644 --- a/feature/recommendation/src/main/java/team/ppac/recommendation/component/ActionButtons.kt +++ b/feature/recommendation/src/main/java/team/ppac/recommendation/component/ActionButtons.kt @@ -56,6 +56,7 @@ internal fun ActionButtons( val bound = it.boundsInWindow() onReactionButtonPositioned(bound.topLeft) }, + isDebounceClick = false, backgroundColor = FarmemeTheme.backgroundColor.white, text = "", textColor = Color.Unspecified, From d679cfda5938d3305681a4c49b0116119e74308b Mon Sep 17 00:00:00 2001 From: hyejin12-ju Date: Mon, 30 Sep 2024 23:35:04 +0900 Subject: [PATCH 6/8] =?UTF-8?q?[Refactor]=20=EC=B6=A9=EB=8F=8C=20=ED=95=B4?= =?UTF-8?q?=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/team/ppac/designsystem/component/button/Button.kt | 1 + .../java/team/ppac/recommendation/component/ActionButtons.kt | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/core/designsystem/src/main/kotlin/team/ppac/designsystem/component/button/Button.kt b/core/designsystem/src/main/kotlin/team/ppac/designsystem/component/button/Button.kt index fa7ff7c1..a43a0943 100644 --- a/core/designsystem/src/main/kotlin/team/ppac/designsystem/component/button/Button.kt +++ b/core/designsystem/src/main/kotlin/team/ppac/designsystem/component/button/Button.kt @@ -3,6 +3,7 @@ package team.ppac.designsystem.component.button import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.padding diff --git a/feature/recommendation/src/main/java/team/ppac/recommendation/component/ActionButtons.kt b/feature/recommendation/src/main/java/team/ppac/recommendation/component/ActionButtons.kt index 8578e717..ebc7ebe8 100644 --- a/feature/recommendation/src/main/java/team/ppac/recommendation/component/ActionButtons.kt +++ b/feature/recommendation/src/main/java/team/ppac/recommendation/component/ActionButtons.kt @@ -57,8 +57,6 @@ internal fun ActionButtons( }, isDebounceClick = false, backgroundColor = FarmemeTheme.backgroundColor.white, - text = "", - textColor = Color.Unspecified, icon = { if (meme.reactionCount == 0) { Row( From c9e699a489cda4056d88978154595635b4958861 Mon Sep 17 00:00:00 2001 From: hyejin12-ju Date: Thu, 3 Oct 2024 21:01:36 +0900 Subject: [PATCH 7/8] =?UTF-8?q?[Refactor]=20=EC=B6=A9=EB=8F=8C=20=ED=95=B4?= =?UTF-8?q?=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/kotlin/team/ppac/remote/datasource/MemeDataSource.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/remote/src/main/kotlin/team/ppac/remote/datasource/MemeDataSource.kt b/core/remote/src/main/kotlin/team/ppac/remote/datasource/MemeDataSource.kt index eb81f321..9a431957 100644 --- a/core/remote/src/main/kotlin/team/ppac/remote/datasource/MemeDataSource.kt +++ b/core/remote/src/main/kotlin/team/ppac/remote/datasource/MemeDataSource.kt @@ -13,7 +13,7 @@ interface MemeDataSource { page: Int, size: Int, ): MemesResponse - suspend fun reactMeme(memeId: String): Boolean + suspend fun reactMeme(memeId: String, count: Int): Boolean suspend fun watchMeme( memeId: String, type: String, From 5832ecee3082e0c80271926060e7492194e4bc42 Mon Sep 17 00:00:00 2001 From: hyejin12-ju Date: Thu, 3 Oct 2024 21:48:06 +0900 Subject: [PATCH 8/8] =?UTF-8?q?[Fix]=20=E3=85=8B=E3=85=8B=E3=85=8B=20?= =?UTF-8?q?=EB=A6=AC=EC=95=A1=EC=85=98=20count=20=EC=A6=9D=EA=B0=80=20?= =?UTF-8?q?=EC=84=9C=EB=B2=84=20=EC=9D=91=EB=8B=B5=20=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=84=B0=20=EB=B3=80=EA=B2=BD=20=EC=97=90=EB=9F=AC=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 --- .../java/team/ppac/data/mapper/ReactionMemeMapper.kt | 8 ++++++++ .../team/ppac/data/repository/MemeRepositoryImpl.kt | 6 ++++-- .../main/java/team/ppac/domain/model/ReactionMeme.kt | 5 +++++ .../java/team/ppac/domain/repository/MemeRepository.kt | 3 ++- .../java/team/ppac/domain/usecase/ReactMemeUseCase.kt | 5 +++-- .../src/main/kotlin/team/ppac/remote/api/MemeApi.kt | 3 ++- .../team/ppac/remote/datasource/MemeDataSource.kt | 3 ++- .../ppac/remote/datasource/impl/MemeDataSourceImpl.kt | 3 ++- .../remote/model/response/meme/ReactionMemeResponse.kt | 10 ++++++++++ .../src/main/java/team/ppac/detail/DetailViewModel.kt | 4 +++- 10 files changed, 41 insertions(+), 9 deletions(-) create mode 100644 core/data/src/main/java/team/ppac/data/mapper/ReactionMemeMapper.kt create mode 100644 core/domain/src/main/java/team/ppac/domain/model/ReactionMeme.kt create mode 100644 core/remote/src/main/kotlin/team/ppac/remote/model/response/meme/ReactionMemeResponse.kt diff --git a/core/data/src/main/java/team/ppac/data/mapper/ReactionMemeMapper.kt b/core/data/src/main/java/team/ppac/data/mapper/ReactionMemeMapper.kt new file mode 100644 index 00000000..df175907 --- /dev/null +++ b/core/data/src/main/java/team/ppac/data/mapper/ReactionMemeMapper.kt @@ -0,0 +1,8 @@ +package team.ppac.data.mapper + +import team.ppac.domain.model.ReactionMeme +import team.ppac.remote.model.response.meme.ReactionMemeResponse + +internal fun ReactionMemeResponse.toReactionMeme(): ReactionMeme = ReactionMeme( + count = count, +) \ No newline at end of file diff --git a/core/data/src/main/java/team/ppac/data/repository/MemeRepositoryImpl.kt b/core/data/src/main/java/team/ppac/data/repository/MemeRepositoryImpl.kt index df71cbaf..b91c4388 100644 --- a/core/data/src/main/java/team/ppac/data/repository/MemeRepositoryImpl.kt +++ b/core/data/src/main/java/team/ppac/data/repository/MemeRepositoryImpl.kt @@ -3,11 +3,13 @@ package team.ppac.data.repository import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import team.ppac.data.mapper.toMeme +import team.ppac.data.mapper.toReactionMeme import team.ppac.data.paging.ITEMS_PER_PAGE import team.ppac.data.paging.createPager import team.ppac.domain.model.Meme import team.ppac.domain.model.MemeWatchType import team.ppac.domain.model.MemeWithPagination +import team.ppac.domain.model.ReactionMeme import team.ppac.domain.repository.MemeRepository import team.ppac.domain.repository.SavedMemeEvent import team.ppac.remote.datasource.MemeDataSource @@ -58,8 +60,8 @@ internal class MemeRepositoryImpl @Inject constructor( ) } - override suspend fun reactMeme(memeId: String, count: Int): Boolean { - return memeDataSource.reactMeme(memeId, count) + override suspend fun reactMeme(memeId: String, count: Int): ReactionMeme { + return memeDataSource.reactMeme(memeId, count).toReactionMeme() } override suspend fun watchMeme(memeId: String, watchType: MemeWatchType): Boolean { diff --git a/core/domain/src/main/java/team/ppac/domain/model/ReactionMeme.kt b/core/domain/src/main/java/team/ppac/domain/model/ReactionMeme.kt new file mode 100644 index 00000000..057eac21 --- /dev/null +++ b/core/domain/src/main/java/team/ppac/domain/model/ReactionMeme.kt @@ -0,0 +1,5 @@ +package team.ppac.domain.model + +data class ReactionMeme ( + val count: Int +) \ No newline at end of file diff --git a/core/domain/src/main/java/team/ppac/domain/repository/MemeRepository.kt b/core/domain/src/main/java/team/ppac/domain/repository/MemeRepository.kt index 115bec2d..2fc0e700 100644 --- a/core/domain/src/main/java/team/ppac/domain/repository/MemeRepository.kt +++ b/core/domain/src/main/java/team/ppac/domain/repository/MemeRepository.kt @@ -4,6 +4,7 @@ import kotlinx.coroutines.flow.Flow import team.ppac.domain.model.Meme import team.ppac.domain.model.MemeWatchType import team.ppac.domain.model.MemeWithPagination +import team.ppac.domain.model.ReactionMeme interface MemeRepository { suspend fun getMeme(memeId: String): Meme @@ -14,7 +15,7 @@ interface MemeRepository { keyword: String, getCurrentPage: (Int) -> Unit ): MemeWithPagination - suspend fun reactMeme(memeId: String, count: Int): Boolean + suspend fun reactMeme(memeId: String, count: Int): ReactionMeme suspend fun watchMeme( memeId: String, watchType: MemeWatchType, diff --git a/core/domain/src/main/java/team/ppac/domain/usecase/ReactMemeUseCase.kt b/core/domain/src/main/java/team/ppac/domain/usecase/ReactMemeUseCase.kt index f913e9c2..99a8d14f 100644 --- a/core/domain/src/main/java/team/ppac/domain/usecase/ReactMemeUseCase.kt +++ b/core/domain/src/main/java/team/ppac/domain/usecase/ReactMemeUseCase.kt @@ -1,16 +1,17 @@ package team.ppac.domain.usecase +import team.ppac.domain.model.ReactionMeme import team.ppac.domain.repository.MemeRepository import javax.inject.Inject interface ReactMemeUseCase { - suspend operator fun invoke(memeId: String, count: Int): Boolean + suspend operator fun invoke(memeId: String, count: Int): ReactionMeme } internal class ReactMemeUseCaseImpl @Inject constructor( private val memeRepository: MemeRepository, ) : ReactMemeUseCase { - override suspend fun invoke(memeId: String, count: Int): Boolean { + override suspend fun invoke(memeId: String, count: Int): ReactionMeme { return memeRepository.reactMeme(memeId, count) } } \ No newline at end of file diff --git a/core/remote/src/main/kotlin/team/ppac/remote/api/MemeApi.kt b/core/remote/src/main/kotlin/team/ppac/remote/api/MemeApi.kt index fbb24414..b1ebdb7c 100644 --- a/core/remote/src/main/kotlin/team/ppac/remote/api/MemeApi.kt +++ b/core/remote/src/main/kotlin/team/ppac/remote/api/MemeApi.kt @@ -12,6 +12,7 @@ import retrofit2.http.Path import retrofit2.http.Query import team.ppac.remote.model.request.meme.ReactMemeRequest import team.ppac.remote.model.response.meme.MemeResponse +import team.ppac.remote.model.response.meme.ReactionMemeResponse import team.ppac.remote.model.response.user.MemesResponse import team.ppac.remote.model.response.meme.UploadMemeResponse @@ -42,7 +43,7 @@ internal interface MemeApi { suspend fun reactMeme( @Path("memeId") memeId: String, @Body reactMemeRequest: ReactMemeRequest - ): Boolean + ): ReactionMemeResponse @POST("/api/meme/{memeId}/watch/{type}") suspend fun watchMeme( diff --git a/core/remote/src/main/kotlin/team/ppac/remote/datasource/MemeDataSource.kt b/core/remote/src/main/kotlin/team/ppac/remote/datasource/MemeDataSource.kt index 9a431957..6cdcfc69 100644 --- a/core/remote/src/main/kotlin/team/ppac/remote/datasource/MemeDataSource.kt +++ b/core/remote/src/main/kotlin/team/ppac/remote/datasource/MemeDataSource.kt @@ -1,6 +1,7 @@ package team.ppac.remote.datasource import team.ppac.remote.model.response.meme.MemeResponse +import team.ppac.remote.model.response.meme.ReactionMemeResponse import team.ppac.remote.model.response.user.MemesResponse interface MemeDataSource { @@ -13,7 +14,7 @@ interface MemeDataSource { page: Int, size: Int, ): MemesResponse - suspend fun reactMeme(memeId: String, count: Int): Boolean + suspend fun reactMeme(memeId: String, count: Int): ReactionMemeResponse suspend fun watchMeme( memeId: String, type: String, diff --git a/core/remote/src/main/kotlin/team/ppac/remote/datasource/impl/MemeDataSourceImpl.kt b/core/remote/src/main/kotlin/team/ppac/remote/datasource/impl/MemeDataSourceImpl.kt index 58b10ff2..574a8e3f 100644 --- a/core/remote/src/main/kotlin/team/ppac/remote/datasource/impl/MemeDataSourceImpl.kt +++ b/core/remote/src/main/kotlin/team/ppac/remote/datasource/impl/MemeDataSourceImpl.kt @@ -13,6 +13,7 @@ import team.ppac.remote.api.MemeApi import team.ppac.remote.datasource.MemeDataSource import team.ppac.remote.model.request.meme.ReactMemeRequest import team.ppac.remote.model.response.meme.MemeResponse +import team.ppac.remote.model.response.meme.ReactionMemeResponse import team.ppac.remote.model.response.user.MemesResponse import java.io.File import java.io.FileOutputStream @@ -44,7 +45,7 @@ internal class MemeDataSourceImpl @Inject constructor( return memeApi.getSearchMemes(keyword, page, size) } - override suspend fun reactMeme(memeId: String, count: Int): Boolean { + override suspend fun reactMeme(memeId: String, count: Int): ReactionMemeResponse { return memeApi.reactMeme(memeId, reactMemeRequest = ReactMemeRequest(count)) } diff --git a/core/remote/src/main/kotlin/team/ppac/remote/model/response/meme/ReactionMemeResponse.kt b/core/remote/src/main/kotlin/team/ppac/remote/model/response/meme/ReactionMemeResponse.kt new file mode 100644 index 00000000..0839cb38 --- /dev/null +++ b/core/remote/src/main/kotlin/team/ppac/remote/model/response/meme/ReactionMemeResponse.kt @@ -0,0 +1,10 @@ +package team.ppac.remote.model.response.meme + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class ReactionMemeResponse ( + @field:Json(name = "count") + val count: Int, +) \ No newline at end of file diff --git a/feature/detail/src/main/java/team/ppac/detail/DetailViewModel.kt b/feature/detail/src/main/java/team/ppac/detail/DetailViewModel.kt index f3fbc82a..45585159 100644 --- a/feature/detail/src/main/java/team/ppac/detail/DetailViewModel.kt +++ b/feature/detail/src/main/java/team/ppac/detail/DetailViewModel.kt @@ -20,6 +20,7 @@ import team.ppac.domain.usecase.ReactMemeUseCase import team.ppac.domain.usecase.SaveMemeUseCase import team.ppac.domain.usecase.ShareMemeUseCase import team.ppac.errorhandling.FarmemeNetworkException +import timber.log.Timber import javax.inject.Inject @HiltViewModel @@ -166,7 +167,7 @@ class DetailViewModel @Inject constructor( } } - private suspend fun incrementReactionCount() { + private fun incrementReactionCount() { reduce { copy( detailMemeUiModel = detailMemeUiModel.copy( @@ -189,6 +190,7 @@ class DetailViewModel @Inject constructor( runCatching { reactMemeUseCase(currentState.memeId, reactionCount) }.onFailure { + Timber.tag(TAG).i("updateReactionCount failMessage= $it") reduce { copy( detailMemeUiModel = detailMemeUiModel.copy(