From 4e058418bd4124c46a774c442341c3a2beb4d0d9 Mon Sep 17 00:00:00 2001 From: Sehwan Yun <39579912+l5x5l@users.noreply.github.com> Date: Sun, 25 Aug 2024 01:44:35 +0900 Subject: [PATCH] [Fix] hackerton ysh (#45) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [FIX] #41 링크 추가/삭제 UseCase, Repository의 Result 타입을 int에서 Link로 수정 (링크 업데이트 내용 반영 구현 관련) * [BASE] #41 core:feature 모듈 추가 * [FIX] #41 RemindResult에 id값 추가 * [FEATURE] #41 링크 추가/수정, 포킷 추가/수정시 다른 화면에 반영되도록 구현 * [FIX] #41 포킷 수정화면에서 기존 포킷의 정보를 UI에 반영하지 못하는 문제 수정, 포킷 수정 호출시 memo가 emptyString일시 body request에서 제외되어 전송되는 문제 수정 * [FIX] #41 리마인드 화면 내 화면 이동 로직 누락 수정 * [FIX] #41 링크 리스트 조회의 isRead, favorites 변수값을 Nullable하게 수정 * [FEATURE] #41 포킷, 링크 삭제 발생시 관련 화면에서 이벤트 수신 가능하도록 구현 * [CHORE] #41 ktlint 적용 * [FIX] #41 포킷 상세 조회시 기본 필터값 중 북마크여부와 안읽음 여부를 false로 변경 --- app/build.gradle.kts | 2 + .../pokitmons/pokit/navigation/RootNavHost.kt | 17 +- build.gradle.kts | 1 + core/feature/.gitignore | 1 + core/feature/build.gradle.kts | 59 +++++++ core/feature/consumer-rules.pro | 0 core/feature/proguard-rules.pro | 21 +++ .../core/feature/ExampleInstrumentedTest.kt | 22 +++ core/feature/src/main/AndroidManifest.xml | 4 + .../core/feature/navigation/args/LinkArg.kt | 13 ++ .../navigation/args/LinkUpdateEvent.kt | 27 ++++ .../core/feature/navigation/args/PokitArg.kt | 12 ++ .../navigation/args/PokitUpdateEvent.kt | 27 ++++ .../pokit/core/feature/ExampleUnitTest.kt | 16 ++ .../java/pokitmons/pokit/data/api/LinkApi.kt | 4 +- .../datasource/remote/link/LinkDataSource.kt | 4 +- .../remote/link/RemoteLinkDataSource.kt | 4 +- .../data/mapper/home/home/RemindMapper.kt | 2 + .../pokit/data/mapper/link/LinkMapper.kt | 20 ++- .../model/link/request/ModifyLinkRequest.kt | 14 +- .../model/link/response/GetLinkResponse.kt | 10 +- .../repository/link/LinkRepositoryImpl.kt | 14 +- .../domain/model/home/remind/RemindResult.kt | 1 + .../domain/repository/link/LinkRepository.kt | 8 +- .../domain/usecase/link/CreateLinkUseCase.kt | 3 +- .../domain/usecase/link/GetLinksUseCase.kt | 4 +- .../domain/usecase/link/ModifyLinkUseCase.kt | 3 +- feature/addlink/build.gradle.kts | 1 + .../strayalpaca/addlink/AddLinkViewModel.kt | 16 +- feature/addpokit/build.gradle.kts | 1 + .../strayalpaca/addpokit/AddPokitScreen.kt | 10 -- .../strayalpaca/addpokit/AddPokitViewModel.kt | 13 +- .../addpokit/model/AddPokitScreenState.kt | 2 - feature/home/build.gradle.kts | 1 + .../java/pokitmons/pokit/home/HomeScreen.kt | 7 +- .../pokit/home/pokit/PokitViewModel.kt | 45 ++++++ .../pokit/home/remind/RemindScreen.kt | 94 +++++++++-- .../pokit/home/remind/RemindViewModel.kt | 153 ++++++++++++++++++ .../pokit/home/remind/TodayLinkCard.kt | 26 ++- feature/pokitdetail/build.gradle.kts | 13 ++ .../pokitdetail/PokitDetailViewModel.kt | 41 ++++- .../strayalpaca/pokitdetail/model/Filter.kt | 4 +- feature/search/build.gradle.kts | 1 + .../pokitmons/pokit/search/SearchViewModel.kt | 27 ++++ settings.gradle.kts | 1 + 45 files changed, 677 insertions(+), 92 deletions(-) create mode 100644 core/feature/.gitignore create mode 100644 core/feature/build.gradle.kts create mode 100644 core/feature/consumer-rules.pro create mode 100644 core/feature/proguard-rules.pro create mode 100644 core/feature/src/androidTest/java/pokitmons/pokit/core/feature/ExampleInstrumentedTest.kt create mode 100644 core/feature/src/main/AndroidManifest.xml create mode 100644 core/feature/src/main/java/pokitmons/pokit/core/feature/navigation/args/LinkArg.kt create mode 100644 core/feature/src/main/java/pokitmons/pokit/core/feature/navigation/args/LinkUpdateEvent.kt create mode 100644 core/feature/src/main/java/pokitmons/pokit/core/feature/navigation/args/PokitArg.kt create mode 100644 core/feature/src/main/java/pokitmons/pokit/core/feature/navigation/args/PokitUpdateEvent.kt create mode 100644 core/feature/src/test/java/pokitmons/pokit/core/feature/ExampleUnitTest.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index a150919e..dbb030cc 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -3,6 +3,7 @@ plugins { alias(libs.plugins.org.jetbrains.kotlin.android) alias(libs.plugins.hilt) alias(libs.plugins.googleServices) + alias(libs.plugins.kotlin.parcelize) id("kotlin-kapt") } @@ -70,6 +71,7 @@ dependencies { debugImplementation(libs.androidx.ui.test.manifest) implementation(project(":core:ui")) + implementation(project(":core:feature")) implementation(project(":data")) implementation(project(":domain")) implementation(project(":feature:addlink")) diff --git a/app/src/main/java/pokitmons/pokit/navigation/RootNavHost.kt b/app/src/main/java/pokitmons/pokit/navigation/RootNavHost.kt index 13ce39b9..366b2f38 100644 --- a/app/src/main/java/pokitmons/pokit/navigation/RootNavHost.kt +++ b/app/src/main/java/pokitmons/pokit/navigation/RootNavHost.kt @@ -3,7 +3,6 @@ package pokitmons.pokit.navigation import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Modifier import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavHostController @@ -65,13 +64,7 @@ fun RootNavHost( val viewModel: AddPokitViewModel = hiltViewModel() AddPokitScreenContainer( viewModel = viewModel, - onBackPressed = navHostController::popBackStack, - onBackWithModifySuccess = { modifiedPokitId -> - navHostController.popBackStack() - navHostController.currentBackStackEntry - ?.savedStateHandle - ?.set("modified_pokit_id", modifiedPokitId) - } + onBackPressed = navHostController::popBackStack ) } @@ -80,11 +73,6 @@ fun RootNavHost( arguments = PokitDetail.arguments ) { val viewModel: PokitDetailViewModel = hiltViewModel() - LaunchedEffect(it) { - val pokitId = navHostController.currentBackStackEntry?.savedStateHandle?.get("modified_pokit_id") ?: return@LaunchedEffect - viewModel.getPokit(pokitId) - } - PokitDetailScreenContainer( viewModel = viewModel, onBackPressed = navHostController::popBackStack, @@ -134,7 +122,8 @@ fun RootNavHost( onNavigateToSetting = { navHostController.navigate(Setting.route) }, onNavigateToPokitDetail = { navHostController.navigate("${PokitDetail.route}/$it") }, onNavigateAddLink = { navHostController.navigate(AddLink.route) }, - onNavigateAddPokit = { navHostController.navigate(AddPokit.route) } + onNavigateAddPokit = { navHostController.navigate(AddPokit.route) }, + onNavigateToLinkModify = { navHostController.navigate("${AddLink.route}?${AddLink.linkIdArg}=$it") } ) } diff --git a/build.gradle.kts b/build.gradle.kts index 8a983d73..c6357ec3 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -7,6 +7,7 @@ plugins { alias(libs.plugins.hilt) apply false alias(libs.plugins.com.android.library) apply false alias(libs.plugins.googleServices) apply false + alias(libs.plugins.kotlin.parcelize) apply false } subprojects { diff --git a/core/feature/.gitignore b/core/feature/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/core/feature/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/core/feature/build.gradle.kts b/core/feature/build.gradle.kts new file mode 100644 index 00000000..26159256 --- /dev/null +++ b/core/feature/build.gradle.kts @@ -0,0 +1,59 @@ +plugins { + alias(libs.plugins.com.android.library) + alias(libs.plugins.org.jetbrains.kotlin.android) + alias(libs.plugins.kotlin.parcelize) +} + +android { + namespace = "pokitmons.pokit.core.feature" + compileSdk = 34 + + defaultConfig { + minSdk = 24 + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles("consumer-rules.pro") + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + buildFeatures { + compose = true + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } + composeOptions { + kotlinCompilerExtensionVersion = "1.5.1" + } +} + +dependencies { + + implementation(libs.androidx.core.ktx) + implementation(libs.androidx.lifecycle.runtime.ktx) + implementation(libs.androidx.activity.compose) + implementation(platform(libs.androidx.compose.bom)) + implementation(libs.androidx.ui) + implementation(libs.androidx.ui.graphics) + implementation(libs.androidx.ui.tooling.preview) + implementation(libs.androidx.material3) + testImplementation(libs.junit) + androidTestImplementation(libs.androidx.junit) + androidTestImplementation(libs.androidx.espresso.core) + androidTestImplementation(platform(libs.androidx.compose.bom)) + androidTestImplementation(libs.androidx.ui.test.junit4) + debugImplementation(libs.androidx.ui.tooling) + debugImplementation(libs.androidx.ui.test.manifest) +} diff --git a/core/feature/consumer-rules.pro b/core/feature/consumer-rules.pro new file mode 100644 index 00000000..e69de29b diff --git a/core/feature/proguard-rules.pro b/core/feature/proguard-rules.pro new file mode 100644 index 00000000..481bb434 --- /dev/null +++ b/core/feature/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/core/feature/src/androidTest/java/pokitmons/pokit/core/feature/ExampleInstrumentedTest.kt b/core/feature/src/androidTest/java/pokitmons/pokit/core/feature/ExampleInstrumentedTest.kt new file mode 100644 index 00000000..b32e09bf --- /dev/null +++ b/core/feature/src/androidTest/java/pokitmons/pokit/core/feature/ExampleInstrumentedTest.kt @@ -0,0 +1,22 @@ +package pokitmons.pokit.core.feature + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import org.junit.Assert.assertEquals +import org.junit.Test +import org.junit.runner.RunWith + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("pokitmons.pokit.core.feature.test", appContext.packageName) + } +} diff --git a/core/feature/src/main/AndroidManifest.xml b/core/feature/src/main/AndroidManifest.xml new file mode 100644 index 00000000..a5918e68 --- /dev/null +++ b/core/feature/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/core/feature/src/main/java/pokitmons/pokit/core/feature/navigation/args/LinkArg.kt b/core/feature/src/main/java/pokitmons/pokit/core/feature/navigation/args/LinkArg.kt new file mode 100644 index 00000000..12e8e623 --- /dev/null +++ b/core/feature/src/main/java/pokitmons/pokit/core/feature/navigation/args/LinkArg.kt @@ -0,0 +1,13 @@ +package pokitmons.pokit.core.feature.navigation.args + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + +@Parcelize +data class LinkArg( + val id: Int, + val title: String, + val thumbnail: String, + val createdAt: String, + val domain: String, +) : Parcelable diff --git a/core/feature/src/main/java/pokitmons/pokit/core/feature/navigation/args/LinkUpdateEvent.kt b/core/feature/src/main/java/pokitmons/pokit/core/feature/navigation/args/LinkUpdateEvent.kt new file mode 100644 index 00000000..acbee359 --- /dev/null +++ b/core/feature/src/main/java/pokitmons/pokit/core/feature/navigation/args/LinkUpdateEvent.kt @@ -0,0 +1,27 @@ +package pokitmons.pokit.core.feature.navigation.args + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.launch + +object LinkUpdateEvent { + private val _updatedLink = MutableSharedFlow() + val updatedLink = _updatedLink.asSharedFlow() + + private val _removedLink = MutableSharedFlow() + val removedLink = _removedLink.asSharedFlow() + + fun modifySuccess(link: LinkArg) { + CoroutineScope(Dispatchers.Default).launch { + _updatedLink.emit(link) + } + } + + fun removeSuccess(linkId: Int) { + CoroutineScope(Dispatchers.Default).launch { + _removedLink.emit(linkId) + } + } +} 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 new file mode 100644 index 00000000..6a67a471 --- /dev/null +++ b/core/feature/src/main/java/pokitmons/pokit/core/feature/navigation/args/PokitArg.kt @@ -0,0 +1,12 @@ +package pokitmons.pokit.core.feature.navigation.args + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + +@Parcelize +data class PokitArg( + val id: Int, + val imageId: Int, + val imageUrl: String, + val title: String, +) : Parcelable 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 new file mode 100644 index 00000000..37a28625 --- /dev/null +++ b/core/feature/src/main/java/pokitmons/pokit/core/feature/navigation/args/PokitUpdateEvent.kt @@ -0,0 +1,27 @@ +package pokitmons.pokit.core.feature.navigation.args + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.launch + +object PokitUpdateEvent { + private val _updatedPokit = MutableSharedFlow() + val updatedPokit = _updatedPokit.asSharedFlow() + + private val _removedPokitId = MutableSharedFlow() + val removedPokitId = _removedPokitId.asSharedFlow() + + fun updatePokit(pokitArg: PokitArg) { + CoroutineScope(Dispatchers.Default).launch { + _updatedPokit.emit(pokitArg) + } + } + + fun removePokit(pokitId: Int) { + CoroutineScope(Dispatchers.Default).launch { + _removedPokitId.emit(pokitId) + } + } +} 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 new file mode 100644 index 00000000..00cadeb1 --- /dev/null +++ b/core/feature/src/test/java/pokitmons/pokit/core/feature/ExampleUnitTest.kt @@ -0,0 +1,16 @@ +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/data/src/main/java/pokitmons/pokit/data/api/LinkApi.kt b/data/src/main/java/pokitmons/pokit/data/api/LinkApi.kt index a7ac524f..812ee227 100644 --- a/data/src/main/java/pokitmons/pokit/data/api/LinkApi.kt +++ b/data/src/main/java/pokitmons/pokit/data/api/LinkApi.kt @@ -21,8 +21,8 @@ interface LinkApi { @Query("page") page: Int = 0, @Query("size") size: Int = 10, @Query("sort") sort: List = listOf(LinksSort.RECENT.value), - @Query("isRead") isRead: Boolean = false, - @Query("favorites") favorites: Boolean = false, + @Query("isRead") isRead: Boolean? = null, + @Query("favorites") favorites: Boolean? = null, @Query("startDate") startDate: String? = null, @Query("endDate") endDate: String? = null, @Query("categoryIds") categoryIds: List? = null, diff --git a/data/src/main/java/pokitmons/pokit/data/datasource/remote/link/LinkDataSource.kt b/data/src/main/java/pokitmons/pokit/data/datasource/remote/link/LinkDataSource.kt index 3bca87ee..412d70f3 100644 --- a/data/src/main/java/pokitmons/pokit/data/datasource/remote/link/LinkDataSource.kt +++ b/data/src/main/java/pokitmons/pokit/data/datasource/remote/link/LinkDataSource.kt @@ -13,8 +13,8 @@ interface LinkDataSource { page: Int = 0, size: Int = 10, sort: List = listOf(LinksSort.RECENT.value), - isRead: Boolean = false, - favorites: Boolean = false, + isRead: Boolean? = null, + favorites: Boolean? = null, startDate: String? = null, endDate: String? = null, categoryIds: List? = null, diff --git a/data/src/main/java/pokitmons/pokit/data/datasource/remote/link/RemoteLinkDataSource.kt b/data/src/main/java/pokitmons/pokit/data/datasource/remote/link/RemoteLinkDataSource.kt index a0b965ef..4d746ffa 100644 --- a/data/src/main/java/pokitmons/pokit/data/datasource/remote/link/RemoteLinkDataSource.kt +++ b/data/src/main/java/pokitmons/pokit/data/datasource/remote/link/RemoteLinkDataSource.kt @@ -17,8 +17,8 @@ class RemoteLinkDataSource @Inject constructor( page: Int, size: Int, sort: List, - isRead: Boolean, - favorites: Boolean, + isRead: Boolean?, + favorites: Boolean?, startDate: String?, endDate: String?, categoryIds: List?, diff --git a/data/src/main/java/pokitmons/pokit/data/mapper/home/home/RemindMapper.kt b/data/src/main/java/pokitmons/pokit/data/mapper/home/home/RemindMapper.kt index f95a100d..f798c2db 100644 --- a/data/src/main/java/pokitmons/pokit/data/mapper/home/home/RemindMapper.kt +++ b/data/src/main/java/pokitmons/pokit/data/mapper/home/home/RemindMapper.kt @@ -8,6 +8,7 @@ object RemindMapper { fun mapperToRemind(remindResponse: RemindResponse): List { return remindResponse.data.map { remind -> RemindResult( + id = remind.contentId, title = remind.title, domain = remind.domain, createdAt = remind.createdAt, @@ -21,6 +22,7 @@ object RemindMapper { fun mapperToTodayContents(remindResponse: List): List { return remindResponse.map { remind -> RemindResult( + id = remind.contentId, title = remind.title, domain = remind.domain, createdAt = remind.createdAt, diff --git a/data/src/main/java/pokitmons/pokit/data/mapper/link/LinkMapper.kt b/data/src/main/java/pokitmons/pokit/data/mapper/link/LinkMapper.kt index 74fef49a..cb23f1c5 100644 --- a/data/src/main/java/pokitmons/pokit/data/mapper/link/LinkMapper.kt +++ b/data/src/main/java/pokitmons/pokit/data/mapper/link/LinkMapper.kt @@ -2,6 +2,7 @@ package pokitmons.pokit.data.mapper.link import pokitmons.pokit.data.model.link.response.GetLinkResponse import pokitmons.pokit.data.model.link.response.GetLinksResponse +import pokitmons.pokit.data.model.link.response.ModifyLinkResponse import pokitmons.pokit.domain.model.link.Link object LinkMapper { @@ -26,8 +27,8 @@ object LinkMapper { fun mapperToLink(linkResponse: GetLinkResponse): Link { return Link( id = linkResponse.contentId, - categoryId = linkResponse.categoryId, - categoryName = "", + categoryId = linkResponse.category.categoryId, + categoryName = linkResponse.category.categoryName, data = linkResponse.data, domain = "", title = linkResponse.title, @@ -37,4 +38,19 @@ object LinkMapper { favorites = linkResponse.favorites ) } + + fun mapperToLink(modifyLinkResponse: ModifyLinkResponse): Link { + return Link( + id = modifyLinkResponse.contentId, + categoryId = modifyLinkResponse.categoryId, + categoryName = "", + data = modifyLinkResponse.data, + domain = "", + title = modifyLinkResponse.title, + memo = modifyLinkResponse.memo, + alertYn = modifyLinkResponse.alertYn, + createdAt = modifyLinkResponse.createdAt, + favorites = modifyLinkResponse.favorites + ) + } } diff --git a/data/src/main/java/pokitmons/pokit/data/model/link/request/ModifyLinkRequest.kt b/data/src/main/java/pokitmons/pokit/data/model/link/request/ModifyLinkRequest.kt index 7144bae4..3ac57dda 100644 --- a/data/src/main/java/pokitmons/pokit/data/model/link/request/ModifyLinkRequest.kt +++ b/data/src/main/java/pokitmons/pokit/data/model/link/request/ModifyLinkRequest.kt @@ -3,11 +3,11 @@ package pokitmons.pokit.data.model.link.request import kotlinx.serialization.Serializable @Serializable -class ModifyLinkRequest( - val data: String = "", - val title: String = "", - val categoryId: Int = 0, - val memo: String = "", - val alertYn: String = "", - val thumbNail: String = "", +data class ModifyLinkRequest( + val data: String, + val title: String, + val categoryId: Int, + val memo: String, + val alertYn: String, + val thumbNail: String, ) diff --git a/data/src/main/java/pokitmons/pokit/data/model/link/response/GetLinkResponse.kt b/data/src/main/java/pokitmons/pokit/data/model/link/response/GetLinkResponse.kt index b484c9bf..514e3e76 100644 --- a/data/src/main/java/pokitmons/pokit/data/model/link/response/GetLinkResponse.kt +++ b/data/src/main/java/pokitmons/pokit/data/model/link/response/GetLinkResponse.kt @@ -5,11 +5,17 @@ import kotlinx.serialization.Serializable @Serializable data class GetLinkResponse( val contentId: Int = 0, - val categoryId: Int = 0, + val category: Category = Category(), val data: String = "", val title: String = "", val memo: String = "", val alertYn: String = "", val createdAt: String = "", val favorites: Boolean = true, -) +) { + @Serializable + data class Category( + val categoryId: Int = 0, + val categoryName: String = "", + ) +} diff --git a/data/src/main/java/pokitmons/pokit/data/repository/link/LinkRepositoryImpl.kt b/data/src/main/java/pokitmons/pokit/data/repository/link/LinkRepositoryImpl.kt index abff80f5..e877a52e 100644 --- a/data/src/main/java/pokitmons/pokit/data/repository/link/LinkRepositoryImpl.kt +++ b/data/src/main/java/pokitmons/pokit/data/repository/link/LinkRepositoryImpl.kt @@ -19,8 +19,8 @@ class LinkRepositoryImpl @Inject constructor( size: Int, page: Int, sort: LinksSort, - isRead: Boolean, - favorite: Boolean, + isRead: Boolean?, + favorite: Boolean?, startDate: String?, endDate: String?, categoryIds: List?, @@ -101,7 +101,7 @@ class LinkRepositoryImpl @Inject constructor( memo: String, alertYn: String, thumbNail: String, - ): PokitResult { + ): PokitResult { return runCatching { val modifyLinkRequest = ModifyLinkRequest( data = data, @@ -112,13 +112,14 @@ class LinkRepositoryImpl @Inject constructor( thumbNail = thumbNail ) val response = dataSource.modifyLink(contentId = linkId, modifyLinkRequest = modifyLinkRequest) - PokitResult.Success(response.contentId) + val mappedResponse = LinkMapper.mapperToLink(response) + PokitResult.Success(mappedResponse) }.getOrElse { throwable -> parseErrorResult(throwable) } } - override suspend fun createLink(data: String, title: String, categoryId: Int, memo: String, alertYn: String, thumbNail: String): PokitResult { + override suspend fun createLink(data: String, title: String, categoryId: Int, memo: String, alertYn: String, thumbNail: String): PokitResult { return runCatching { val createLinkRequest = ModifyLinkRequest( data = data, @@ -129,7 +130,8 @@ class LinkRepositoryImpl @Inject constructor( thumbNail = thumbNail ) val response = dataSource.createLink(createLinkRequest = createLinkRequest) - PokitResult.Success(response.contentId) + val mappedResponse = LinkMapper.mapperToLink(response) + PokitResult.Success(mappedResponse) }.getOrElse { throwable -> parseErrorResult(throwable) } diff --git a/domain/src/main/java/pokitmons/pokit/domain/model/home/remind/RemindResult.kt b/domain/src/main/java/pokitmons/pokit/domain/model/home/remind/RemindResult.kt index 6518a021..1e22c69d 100644 --- a/domain/src/main/java/pokitmons/pokit/domain/model/home/remind/RemindResult.kt +++ b/domain/src/main/java/pokitmons/pokit/domain/model/home/remind/RemindResult.kt @@ -1,6 +1,7 @@ package pokitmons.pokit.domain.model.home.remind data class RemindResult( + val id: Int, val title: String, val domain: String, val createdAt: String, diff --git a/domain/src/main/java/pokitmons/pokit/domain/repository/link/LinkRepository.kt b/domain/src/main/java/pokitmons/pokit/domain/repository/link/LinkRepository.kt index 7bc829df..65a3883a 100644 --- a/domain/src/main/java/pokitmons/pokit/domain/repository/link/LinkRepository.kt +++ b/domain/src/main/java/pokitmons/pokit/domain/repository/link/LinkRepository.kt @@ -11,8 +11,8 @@ interface LinkRepository { size: Int = 10, page: Int = 0, sort: LinksSort = LinksSort.RECENT, - isRead: Boolean = false, - favorite: Boolean = false, + isRead: Boolean? = null, + favorite: Boolean? = null, startDate: String? = null, endDate: String? = null, categoryIds: List? = null, @@ -42,7 +42,7 @@ interface LinkRepository { memo: String, alertYn: String, thumbNail: String, - ): PokitResult + ): PokitResult suspend fun createLink( data: String, @@ -51,7 +51,7 @@ interface LinkRepository { memo: String, alertYn: String, thumbNail: String, - ): PokitResult + ): PokitResult suspend fun setBookmark(linkId: Int, bookmarked: Boolean): PokitResult diff --git a/domain/src/main/java/pokitmons/pokit/domain/usecase/link/CreateLinkUseCase.kt b/domain/src/main/java/pokitmons/pokit/domain/usecase/link/CreateLinkUseCase.kt index 9c934e1a..adfc5a23 100644 --- a/domain/src/main/java/pokitmons/pokit/domain/usecase/link/CreateLinkUseCase.kt +++ b/domain/src/main/java/pokitmons/pokit/domain/usecase/link/CreateLinkUseCase.kt @@ -1,6 +1,7 @@ package pokitmons.pokit.domain.usecase.link import pokitmons.pokit.domain.commom.PokitResult +import pokitmons.pokit.domain.model.link.Link import pokitmons.pokit.domain.repository.link.LinkRepository import javax.inject.Inject @@ -14,7 +15,7 @@ class CreateLinkUseCase @Inject constructor( memo: String, alertYn: String, thumbNail: String, - ): PokitResult { + ): PokitResult { return repository.createLink( data = data, title = title, diff --git a/domain/src/main/java/pokitmons/pokit/domain/usecase/link/GetLinksUseCase.kt b/domain/src/main/java/pokitmons/pokit/domain/usecase/link/GetLinksUseCase.kt index 04bfb2ad..77aec8e5 100644 --- a/domain/src/main/java/pokitmons/pokit/domain/usecase/link/GetLinksUseCase.kt +++ b/domain/src/main/java/pokitmons/pokit/domain/usecase/link/GetLinksUseCase.kt @@ -14,8 +14,8 @@ class GetLinksUseCase @Inject constructor( size: Int = 10, page: Int = 0, sort: LinksSort = LinksSort.RECENT, - isRead: Boolean = false, - favorite: Boolean = false, + isRead: Boolean? = null, + favorite: Boolean? = null, startDate: String? = null, endDate: String? = null, categoryIds: List? = null, diff --git a/domain/src/main/java/pokitmons/pokit/domain/usecase/link/ModifyLinkUseCase.kt b/domain/src/main/java/pokitmons/pokit/domain/usecase/link/ModifyLinkUseCase.kt index cbe1ba72..a9760256 100644 --- a/domain/src/main/java/pokitmons/pokit/domain/usecase/link/ModifyLinkUseCase.kt +++ b/domain/src/main/java/pokitmons/pokit/domain/usecase/link/ModifyLinkUseCase.kt @@ -1,6 +1,7 @@ package pokitmons.pokit.domain.usecase.link import pokitmons.pokit.domain.commom.PokitResult +import pokitmons.pokit.domain.model.link.Link import pokitmons.pokit.domain.repository.link.LinkRepository import javax.inject.Inject @@ -15,7 +16,7 @@ class ModifyLinkUseCase @Inject constructor( memo: String, alertYn: String, thumbNail: String, - ): PokitResult { + ): PokitResult { return repository.modifyLink( linkId = linkId, data = data, diff --git a/feature/addlink/build.gradle.kts b/feature/addlink/build.gradle.kts index ff240027..59c2c114 100644 --- a/feature/addlink/build.gradle.kts +++ b/feature/addlink/build.gradle.kts @@ -70,5 +70,6 @@ dependencies { kapt(libs.hilt.compiler) implementation(project(":core:ui")) + implementation(project(":core:feature")) implementation(project(":domain")) } 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 74d41475..71f40ec8 100644 --- a/feature/addlink/src/main/java/com/strayalpaca/addlink/AddLinkViewModel.kt +++ b/feature/addlink/src/main/java/com/strayalpaca/addlink/AddLinkViewModel.kt @@ -26,6 +26,8 @@ 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.navigation.args.LinkArg +import pokitmons.pokit.core.feature.navigation.args.LinkUpdateEvent import pokitmons.pokit.domain.commom.PokitResult import pokitmons.pokit.domain.usecase.link.CreateLinkUseCase import pokitmons.pokit.domain.usecase.link.GetLinkCardUseCase @@ -92,6 +94,9 @@ class AddLinkViewModel @Inject constructor( step = ScreenStep.IDLE ) } + _title.update { response.result.title } + _linkUrl.update { response.result.data } + _memo.update { response.result.memo } } else { postSideEffect(AddLinkScreenSideEffect.OnNavigationBack) } @@ -164,7 +169,7 @@ class AddLinkViewModel @Inject constructor( modifyLinkUseCase.modifyLink( linkId = currentLinkId!!, data = currentState.link!!.url, - title = currentState.link.title, + title = title.value, categoryId = currentState.currentPokit!!.id.toInt(), memo = memo.value, alertYn = if (currentState.useRemind) "Y" else "n", @@ -182,6 +187,15 @@ class AddLinkViewModel @Inject constructor( } if (response is PokitResult.Success) { reduce { state.copy(step = ScreenStep.IDLE) } + val responseLink = response.result + val linkArg = LinkArg( + id = responseLink.id, + title = responseLink.title, + thumbnail = responseLink.thumbnail, + domain = responseLink.domain, + createdAt = responseLink.createdAt + ) + LinkUpdateEvent.modifySuccess(linkArg) postSideEffect(AddLinkScreenSideEffect.AddLinkSuccess) } else { reduce { state.copy(step = ScreenStep.IDLE) } diff --git a/feature/addpokit/build.gradle.kts b/feature/addpokit/build.gradle.kts index de2cc726..9f9f9b69 100644 --- a/feature/addpokit/build.gradle.kts +++ b/feature/addpokit/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/addpokit/src/main/java/com/strayalpaca/addpokit/AddPokitScreen.kt b/feature/addpokit/src/main/java/com/strayalpaca/addpokit/AddPokitScreen.kt index e9d95822..aa5a5434 100644 --- a/feature/addpokit/src/main/java/com/strayalpaca/addpokit/AddPokitScreen.kt +++ b/feature/addpokit/src/main/java/com/strayalpaca/addpokit/AddPokitScreen.kt @@ -64,8 +64,6 @@ import pokitmons.pokit.core.ui.R.string as coreString fun AddPokitScreenContainer( viewModel: AddPokitViewModel, onBackPressed: () -> Unit, - onBackWithModifySuccess: (Int) -> Unit = {}, - onBackWithCreateSuccess: () -> Unit = {}, ) { val state by viewModel.container.stateFlow.collectAsState() val pokitName by viewModel.pokitName.collectAsState() @@ -83,17 +81,9 @@ fun AddPokitScreenContainer( viewModel.collectSideEffect { sideEffect -> when (sideEffect) { - AddPokitSideEffect.AddPokitSuccess -> { - onBackWithCreateSuccess() - } - AddPokitSideEffect.OnNavigationBack -> { onBackPressed() } - - is AddPokitSideEffect.ModifyPokitSuccess -> { - onBackWithModifySuccess(sideEffect.id) - } } } 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 985fa6f6..29cc0f33 100644 --- a/feature/addpokit/src/main/java/com/strayalpaca/addpokit/AddPokitViewModel.kt +++ b/feature/addpokit/src/main/java/com/strayalpaca/addpokit/AddPokitViewModel.kt @@ -24,6 +24,8 @@ 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.navigation.args.PokitArg +import pokitmons.pokit.core.feature.navigation.args.PokitUpdateEvent import pokitmons.pokit.domain.commom.PokitResult import pokitmons.pokit.domain.usecase.pokit.CreatePokitUseCase import pokitmons.pokit.domain.usecase.pokit.GetPokitImagesUseCase @@ -120,6 +122,8 @@ class AddPokitViewModel @Inject constructor( if (isInAvailableLength) { val errorMessage = errorMessageProvider.getTextLengthErrorMessage() reduce { state.copy(pokitInputErrorMessage = errorMessage) } + } else { + reduce { state.copy(pokitInputErrorMessage = null) } } } } @@ -140,13 +144,8 @@ class AddPokitViewModel @Inject constructor( if (response is PokitResult.Success) { reduce { state.copy(step = AddPokitScreenStep.IDLE) } - val sideEffect = if (pokitId == null) { - AddPokitSideEffect.AddPokitSuccess - } else { - AddPokitSideEffect.ModifyPokitSuccess(pokitId) - } - - postSideEffect(sideEffect) + PokitUpdateEvent.updatePokit(PokitArg(id = pokitId ?: 0, title = currentPokitName, imageUrl = state.pokitImage?.url ?: "", imageId = pokitImageId)) + postSideEffect(AddPokitSideEffect.OnNavigationBack) } else { response as PokitResult.Error val errorMessage = errorMessageProvider.errorCodeToMessage(response.error.code) diff --git a/feature/addpokit/src/main/java/com/strayalpaca/addpokit/model/AddPokitScreenState.kt b/feature/addpokit/src/main/java/com/strayalpaca/addpokit/model/AddPokitScreenState.kt index 5bbcbf05..78ae278f 100644 --- a/feature/addpokit/src/main/java/com/strayalpaca/addpokit/model/AddPokitScreenState.kt +++ b/feature/addpokit/src/main/java/com/strayalpaca/addpokit/model/AddPokitScreenState.kt @@ -17,7 +17,5 @@ sealed class AddPokitScreenStep { } sealed class AddPokitSideEffect { - data object AddPokitSuccess : AddPokitSideEffect() - data class ModifyPokitSuccess(val id: Int) : AddPokitSideEffect() data object OnNavigationBack : AddPokitSideEffect() } diff --git a/feature/home/build.gradle.kts b/feature/home/build.gradle.kts index 4e02a173..202d76ba 100644 --- a/feature/home/build.gradle.kts +++ b/feature/home/build.gradle.kts @@ -72,6 +72,7 @@ dependencies { // module implementation(project(":core:ui")) + implementation(project(":core:feature")) implementation(project(":domain")) implementation(project(":feature:pokitdetail")) } diff --git a/feature/home/src/main/java/pokitmons/pokit/home/HomeScreen.kt b/feature/home/src/main/java/pokitmons/pokit/home/HomeScreen.kt index 2e247642..8f14fda2 100644 --- a/feature/home/src/main/java/pokitmons/pokit/home/HomeScreen.kt +++ b/feature/home/src/main/java/pokitmons/pokit/home/HomeScreen.kt @@ -49,7 +49,7 @@ fun HomeScreen( onNavigateToSetting: () -> Unit, onNavigateAddLink: () -> Unit, onNavigateAddPokit: () -> Unit, - + onNavigateToLinkModify: (String) -> Unit, ) { val sheetState = rememberModalBottomSheetState() val scope = rememberCoroutineScope() @@ -164,7 +164,10 @@ fun HomeScreen( } is ScreenType.Remind -> { - RemindScreen(Modifier.padding(padding)) + RemindScreen( + modifier = Modifier.padding(padding), + onNavigateToLinkModify = onNavigateToLinkModify + ) } } } 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 dd191d15..83a043e6 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 @@ -10,8 +10,11 @@ import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch +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 @@ -19,6 +22,7 @@ import pokitmons.pokit.domain.usecase.link.GetLinksUseCase import pokitmons.pokit.domain.usecase.pokit.GetPokitsUseCase import javax.inject.Inject import com.strayalpaca.pokitdetail.model.Link as DetailLink +import pokitmons.pokit.domain.model.pokit.Pokit as DomainPokit @HiltViewModel class PokitViewModel @Inject constructor( @@ -26,6 +30,47 @@ class PokitViewModel @Inject constructor( private val getLinksUseCase: GetLinksUseCase, ) : ViewModel() { + init { + initLinkUpdateEventDetector() + initPokitUpdateEventDetector() + initPokitRemoveEventDetector() + } + + private fun initLinkUpdateEventDetector() { + 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) + } + } + } + + private fun initPokitUpdateEventDetector() { + viewModelScope.launch { + PokitUpdateEvent.updatedPokit.collectLatest { updatedPokit -> + val targetPokit = pokitPaging.pagingData.value.find { it.id == updatedPokit.id.toString() } ?: return@collectLatest + val pokitImage = DomainPokit.Image(id = updatedPokit.imageId, url = updatedPokit.imageUrl) + val modifiedPokit = targetPokit.copy(title = updatedPokit.title, image = pokitImage) + pokitPaging.modifyItem(modifiedPokit) + } + } + } + + private fun initPokitRemoveEventDetector() { + viewModelScope.launch { + PokitUpdateEvent.removedPokitId.collectLatest { removedPokitId -> + val targetPokit = pokitPaging.pagingData.value.find { it.id == removedPokitId.toString() } ?: return@collectLatest + pokitPaging.deleteItem(targetPokit) + } + } + } + var selectedCategory = mutableStateOf(Category.Pokit) private set diff --git a/feature/home/src/main/java/pokitmons/pokit/home/remind/RemindScreen.kt b/feature/home/src/main/java/pokitmons/pokit/home/remind/RemindScreen.kt index e9c6e4c1..73ecfb11 100644 --- a/feature/home/src/main/java/pokitmons/pokit/home/remind/RemindScreen.kt +++ b/feature/home/src/main/java/pokitmons/pokit/home/remind/RemindScreen.kt @@ -12,21 +12,77 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import coil.compose.rememberAsyncImagePainter +import com.strayalpaca.pokitdetail.R +import com.strayalpaca.pokitdetail.components.template.linkdetailbottomsheet.LinkDetailBottomSheet +import com.strayalpaca.pokitdetail.model.BottomSheetType +import com.strayalpaca.pokitdetail.model.Link import pokitmons.pokit.core.ui.components.block.linkcard.LinkCard +import pokitmons.pokit.core.ui.components.template.bottomsheet.PokitBottomSheet +import pokitmons.pokit.core.ui.components.template.modifybottomsheet.ModifyBottomSheetContent +import pokitmons.pokit.core.ui.components.template.removeItemBottomSheet.TwoButtonBottomSheetContent @Composable fun RemindScreen( modifier: Modifier = Modifier, viewModel: RemindViewModel = hiltViewModel(), + onNavigateToLinkModify: (String) -> Unit, ) { val unreadContents = viewModel.unReadContents.collectAsState() val todayContents = viewModel.todayContents.collectAsState() val bookmarkContents = viewModel.bookmarkContents.collectAsState() + val currentDetailShowLink by viewModel.currentShowingLink.collectAsState() + + val pokitOptionBottomSheetType by viewModel.pokitOptionBottomSheetType.collectAsState() + val currentSelectedLink by viewModel.currentSelectedLink.collectAsState() + + PokitBottomSheet( + onHideBottomSheet = viewModel::hideLinkOptionBottomSheet, + show = pokitOptionBottomSheetType != null + ) { + when (pokitOptionBottomSheetType) { + BottomSheetType.MODIFY -> { + ModifyBottomSheetContent( + onClickShare = {}, + onClickModify = remember { + { + viewModel.hideLinkOptionBottomSheet() + onNavigateToLinkModify(currentSelectedLink!!.id) + } + }, + onClickRemove = viewModel::showLinkRemoveBottomSheet + ) + } + BottomSheetType.REMOVE -> { + TwoButtonBottomSheetContent( + title = stringResource(id = R.string.title_remove_pokit), + subText = stringResource(id = R.string.sub_remove_pokit), + onClickLeftButton = viewModel::hideLinkOptionBottomSheet, + onClickRightButton = remember { + { + viewModel.hideLinkOptionBottomSheet() + viewModel.removeCurrentSelectedLink() + } + } + ) + } + else -> {} + } + } + + LinkDetailBottomSheet( + show = currentDetailShowLink != null, + link = currentDetailShowLink ?: Link(), + onHideBottomSheet = viewModel::hideDetailLinkBottomSheet + ) + Column( modifier = modifier .padding(20.dp) @@ -47,7 +103,13 @@ fun RemindScreen( sub = todayContent.createdAt, painter = rememberAsyncImagePainter(todayContent.thumbNail), badgeText = todayContent.data, - domain = todayContent.domain + domain = todayContent.domain, + onClick = { + viewModel.showDetailLinkBottomSheet(remindResult = todayContent) + }, + onClickKebab = { + viewModel.showLinkOptionBottomSheet(remindResult = todayContent) + } ) } } @@ -69,8 +131,12 @@ fun RemindScreen( painter = rememberAsyncImagePainter(unReadContent.thumbNail), notRead = unReadContent.isRead, badgeText = unReadContent.data, - onClickKebab = { }, - onClickItem = { } + onClickKebab = { + viewModel.showLinkOptionBottomSheet(remindResult = unReadContent) + }, + onClickItem = { + viewModel.showDetailLinkBottomSheet(remindResult = unReadContent) + } ) } } @@ -84,16 +150,20 @@ fun RemindScreen( modifier = Modifier, verticalArrangement = Arrangement.spacedBy(20.dp) ) { - bookmarkContents.value.forEach { unReadContent -> + bookmarkContents.value.forEach { favoriteContent -> LinkCard( - item = unReadContent.title, - title = unReadContent.title, - sub = "${unReadContent.createdAt} • ${unReadContent.domain}", - painter = rememberAsyncImagePainter(unReadContent.thumbNail), - notRead = unReadContent.isRead, - badgeText = unReadContent.data, - onClickKebab = { }, - onClickItem = { } + item = favoriteContent.title, + title = favoriteContent.title, + sub = "${favoriteContent.createdAt} • ${favoriteContent.domain}", + painter = rememberAsyncImagePainter(favoriteContent.thumbNail), + notRead = favoriteContent.isRead, + badgeText = favoriteContent.data, + onClickKebab = { + viewModel.showLinkOptionBottomSheet(remindResult = favoriteContent) + }, + onClickItem = { + viewModel.showDetailLinkBottomSheet(remindResult = favoriteContent) + } ) } } diff --git a/feature/home/src/main/java/pokitmons/pokit/home/remind/RemindViewModel.kt b/feature/home/src/main/java/pokitmons/pokit/home/remind/RemindViewModel.kt index bf3deefb..a8bf75aa 100644 --- a/feature/home/src/main/java/pokitmons/pokit/home/remind/RemindViewModel.kt +++ b/feature/home/src/main/java/pokitmons/pokit/home/remind/RemindViewModel.kt @@ -2,16 +2,22 @@ package pokitmons.pokit.home.remind import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.strayalpaca.pokitdetail.model.BottomSheetType +import com.strayalpaca.pokitdetail.model.Link import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch +import pokitmons.pokit.core.feature.navigation.args.LinkUpdateEvent import pokitmons.pokit.domain.commom.PokitResult import pokitmons.pokit.domain.model.home.remind.RemindResult import pokitmons.pokit.domain.usecase.home.remind.BookMarkContentsUseCase import pokitmons.pokit.domain.usecase.home.remind.TodayContentsUseCase import pokitmons.pokit.domain.usecase.home.remind.UnReadContentsUseCase +import pokitmons.pokit.domain.usecase.link.DeleteLinkUseCase import javax.inject.Inject @HiltViewModel @@ -19,10 +25,13 @@ class RemindViewModel @Inject constructor( private val unReadContentsUseCase: UnReadContentsUseCase, private val todayContentsUseCase: TodayContentsUseCase, private val bookMarkContentsUseCase: BookMarkContentsUseCase, + private val deleteLinkUseCase: DeleteLinkUseCase, ) : ViewModel() { init { loadContents() + initLinkUpdateEventDetector() + initLinkRemoveEventDetector() } private var _unReadContents: MutableStateFlow> = MutableStateFlow(emptyList()) @@ -37,6 +46,96 @@ class RemindViewModel @Inject constructor( val bookmarkContents: StateFlow> get() = _bookmarkContents.asStateFlow() + private val _currentSelectedLink = MutableStateFlow(null) + val currentSelectedLink = _currentSelectedLink.asStateFlow() + + private val _pokitOptionBottomSheetType = MutableStateFlow(null) + val pokitOptionBottomSheetType = _pokitOptionBottomSheetType.asStateFlow() + + private val _currentShowingLink = MutableStateFlow(null) + val currentShowingLink = _currentShowingLink.asStateFlow() + + private fun initLinkUpdateEventDetector() { + viewModelScope.launch { + LinkUpdateEvent.updatedLink.collectLatest { updatedLink -> + unReadContents.value + .find { it.id == updatedLink.id } + ?.let { targetLink -> + val modifiedLink = targetLink + .copy( + title = updatedLink.title, + domain = updatedLink.domain, + createdAt = updatedLink.createdAt, + thumbNail = updatedLink.thumbnail + ) + _unReadContents.update { contents -> + contents.map { content -> + if (content.id == targetLink.id) { + modifiedLink + } else { + content + } + } + } + } + + todayContents.value + .find { it.id == updatedLink.id } + ?.let { targetLink -> + val modifiedLink = targetLink + .copy( + title = updatedLink.title, + domain = updatedLink.domain, + createdAt = updatedLink.createdAt, + thumbNail = updatedLink.thumbnail + ) + _todayContents.update { contents -> + contents.map { content -> + if (content.id == targetLink.id) { + modifiedLink + } else { + content + } + } + } + } + + bookmarkContents.value + .find { it.id == updatedLink.id } + ?.let { targetLink -> + val modifiedLink = targetLink + .copy( + title = updatedLink.title, + domain = updatedLink.domain, + createdAt = updatedLink.createdAt, + thumbNail = updatedLink.thumbnail + ) + _bookmarkContents.update { contents -> + contents.map { content -> + if (content.id == targetLink.id) { + modifiedLink + } else { + content + } + } + } + } + } + } + } + + private fun initLinkRemoveEventDetector() { + viewModelScope.launch { + LinkUpdateEvent.removedLink.collectLatest { removedLinkId -> + unReadContents.value.filter { it.id != removedLinkId } + + todayContents.value.filter { it.id != removedLinkId } + + bookmarkContents.value.filter { it.id != removedLinkId } + } + } + } + fun loadContents() { viewModelScope.launch { when (val response = unReadContentsUseCase.getUnreadContents()) { @@ -58,4 +157,58 @@ class RemindViewModel @Inject constructor( } } } + + fun showDetailLinkBottomSheet(remindResult: RemindResult) { + _currentShowingLink.update { + Link( + id = remindResult.id.toString(), + title = remindResult.title, + dateString = remindResult.createdAt, + url = remindResult.data, + isRead = remindResult.isRead, + domainUrl = remindResult.domain + ) + } + } + + fun hideDetailLinkBottomSheet() { + _currentShowingLink.update { null } + } + + fun showLinkOptionBottomSheet(remindResult: RemindResult) { + _currentSelectedLink.update { + Link( + id = remindResult.id.toString(), + title = remindResult.title, + dateString = remindResult.createdAt, + url = remindResult.data, + isRead = remindResult.isRead, + domainUrl = remindResult.domain + ) + } + _pokitOptionBottomSheetType.update { + BottomSheetType.MODIFY + } + } + + fun showLinkRemoveBottomSheet() { + _pokitOptionBottomSheetType.update { + BottomSheetType.REMOVE + } + } + + fun hideLinkOptionBottomSheet() { + _currentSelectedLink.update { null } + _pokitOptionBottomSheetType.update { null } + } + + fun removeCurrentSelectedLink() { + val currentSelectedLinkId = currentSelectedLink.value?.id?.toIntOrNull() ?: return + viewModelScope.launch { + val response = deleteLinkUseCase.deleteLink(currentSelectedLinkId) + if (response is PokitResult.Success) { + LinkUpdateEvent.removeSuccess(response.result) + } + } + } } diff --git a/feature/home/src/main/java/pokitmons/pokit/home/remind/TodayLinkCard.kt b/feature/home/src/main/java/pokitmons/pokit/home/remind/TodayLinkCard.kt index 3c01a522..c828e659 100644 --- a/feature/home/src/main/java/pokitmons/pokit/home/remind/TodayLinkCard.kt +++ b/feature/home/src/main/java/pokitmons/pokit/home/remind/TodayLinkCard.kt @@ -2,6 +2,7 @@ package pokitmons.pokit.home.remind import androidx.compose.foundation.Image import androidx.compose.foundation.background +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -11,9 +12,11 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment @@ -25,24 +28,26 @@ import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp -import androidx.hilt.navigation.compose.hiltViewModel import pokitmons.pokit.core.ui.theme.PokitTheme -import pokitmons.pokit.home.R @Composable fun ToadyLinkCard( - viewModel: RemindViewModel = hiltViewModel(), title: String, sub: String, painter: Painter, badgeText: String, domain: String, + onClick: () -> Unit, + onClickKebab: () -> Unit, ) { Box( modifier = Modifier .width(216.dp) .height(194.dp) .clip(RoundedCornerShape(8.dp)) + .clickable { + onClick() + } ) { Image( painter = painter, @@ -93,11 +98,16 @@ fun ToadyLinkCard( Spacer(modifier = Modifier.padding(4.dp)) - Icon( - painter = painterResource(id = pokitmons.pokit.core.ui.R.drawable.icon_24_kebab), - contentDescription = null, - tint = Color.White - ) + IconButton( + onClick = { onClickKebab() }, + modifier = Modifier.size(24.dp) + ) { + Icon( + painter = painterResource(id = pokitmons.pokit.core.ui.R.drawable.icon_24_kebab), + contentDescription = null, + tint = Color.White + ) + } } Spacer(modifier = Modifier.height(8.dp)) diff --git a/feature/pokitdetail/build.gradle.kts b/feature/pokitdetail/build.gradle.kts index 76cf12f3..b350afe6 100644 --- a/feature/pokitdetail/build.gradle.kts +++ b/feature/pokitdetail/build.gradle.kts @@ -6,6 +6,10 @@ plugins { } android { + tasks.withType().configureEach { + useJUnitPlatform() + } + namespace = "com.strayalpaca.pokitdetail" compileSdk = 34 @@ -62,10 +66,19 @@ dependencies { implementation(libs.orbit.core) implementation(libs.orbit.viewmodel) + // kotest + testImplementation(libs.kotest.runner.junit5) + testImplementation(libs.kotlin.reflect) + + // mockk + testImplementation(libs.mockk) + androidTestImplementation(libs.mockk.android) + // hilt implementation(libs.hilt) kapt(libs.hilt.compiler) implementation(project(":core:ui")) + implementation(project(":core:feature")) implementation(project(":domain")) } 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 f241aa06..aedb0445 100644 --- a/feature/pokitdetail/src/main/java/com/strayalpaca/pokitdetail/PokitDetailViewModel.kt +++ b/feature/pokitdetail/src/main/java/com/strayalpaca/pokitdetail/PokitDetailViewModel.kt @@ -15,8 +15,11 @@ import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch +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.LinksSort import pokitmons.pokit.domain.usecase.link.GetLinksUseCase @@ -66,6 +69,37 @@ class PokitDetailViewModel @Inject constructor( } getPokit(pokitId) } + + initLinkUpdateEventDetector() + initPokitUpdateEventDetector() + } + + private fun initLinkUpdateEventDetector() { + 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) + } + } + } + + private fun initPokitUpdateEventDetector() { + viewModelScope.launch { + PokitUpdateEvent.updatedPokit.collectLatest { updatedPokit -> + if (state.value.currentPokit?.id != updatedPokit.id.toString()) return@collectLatest + + val pokit = state.value.currentPokit?.copy( + title = updatedPokit.title + ) ?: return@collectLatest + _state.update { it.copy(currentPokit = pokit) } + } + } } private suspend fun getLinks(categoryId: Int, size: Int, page: Int, sort: LinksSort): PokitResult> { @@ -75,12 +109,12 @@ class PokitDetailViewModel @Inject constructor( size = size, page = page, sort = sort, - isRead = !currentFilter.notReadChecked, - favorite = currentFilter.bookmarkChecked + isRead = if (currentFilter.notReadChecked) false else null, + favorite = if (currentFilter.bookmarkChecked) true else null ) } - fun getPokit(pokitId: Int) { + private fun getPokit(pokitId: Int) { viewModelScope.launch { val response = getPokitUseCase.getPokit(pokitId) if (response is PokitResult.Success) { @@ -183,6 +217,7 @@ class PokitDetailViewModel @Inject constructor( viewModelScope.launch { val response = deletePokitUseCase.deletePokit(pokitId) if (response is PokitResult.Success) { + PokitUpdateEvent.removePokit(pokitId) // 뒤로가기? } } diff --git a/feature/pokitdetail/src/main/java/com/strayalpaca/pokitdetail/model/Filter.kt b/feature/pokitdetail/src/main/java/com/strayalpaca/pokitdetail/model/Filter.kt index afeec77d..f1def5f9 100644 --- a/feature/pokitdetail/src/main/java/com/strayalpaca/pokitdetail/model/Filter.kt +++ b/feature/pokitdetail/src/main/java/com/strayalpaca/pokitdetail/model/Filter.kt @@ -2,6 +2,6 @@ package com.strayalpaca.pokitdetail.model data class Filter( val recentSortUsed: Boolean = true, - val bookmarkChecked: Boolean = true, - val notReadChecked: Boolean = true, + val bookmarkChecked: Boolean = false, + val notReadChecked: Boolean = false, ) diff --git a/feature/search/build.gradle.kts b/feature/search/build.gradle.kts index e18eb426..04c4de52 100644 --- a/feature/search/build.gradle.kts +++ b/feature/search/build.gradle.kts @@ -67,5 +67,6 @@ dependencies { kapt(libs.hilt.compiler) implementation(project(":core:ui")) + implementation(project(":core:feature")) implementation(project(":domain")) } 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 7bd984fd..66a5c1ad 100644 --- a/feature/search/src/main/java/pokitmons/pokit/search/SearchViewModel.kt +++ b/feature/search/src/main/java/pokitmons/pokit/search/SearchViewModel.kt @@ -7,10 +7,12 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch +import pokitmons.pokit.core.feature.navigation.args.LinkUpdateEvent import pokitmons.pokit.domain.commom.PokitResult import pokitmons.pokit.domain.usecase.link.SearchLinksUseCase import pokitmons.pokit.domain.usecase.link.SetBookmarkUseCase @@ -43,6 +45,12 @@ class SearchViewModel @Inject constructor( private val removeRecentSearchWordUseCase: RemoveRecentSearchWordUseCase, private val setBookmarkUseCase: SetBookmarkUseCase, ) : ViewModel() { + + init { + initLinkUpdateEventDetector() + initLinkRemoveEventDetector() + } + private val linkPaging = LinkPaging( searchLinksUseCase = searchLinksUseCase, filter = Filter(), @@ -82,6 +90,25 @@ class SearchViewModel @Inject constructor( private var appliedSearchWord = "" + private fun initLinkUpdateEventDetector() { + 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) + linkPaging.modifyItem(modifiedLink) + } + } + } + + private fun initLinkRemoveEventDetector() { + viewModelScope.launch { + LinkUpdateEvent.removedLink.collectLatest { removedLinkId -> + val targetItem = linkPaging.pagingData.value.find { it.id == removedLinkId.toString() } ?: return@collectLatest + linkPaging.deleteItem(targetItem) + } + } + } + fun inputSearchWord(newSearchWord: String) { _searchWord.update { newSearchWord } val currentState = state.value diff --git a/settings.gradle.kts b/settings.gradle.kts index f0c4e192..39e6aa36 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -33,3 +33,4 @@ include(":feature:search") include(":feature:settings") include(":feature:alarm") include(":feature:home") +include(":core:feature")