From d0cfa8e1a8ef0791f3f44205c811d3a66a617e97 Mon Sep 17 00:00:00 2001 From: JSPark <48265129+pknujsp@users.noreply.github.com> Date: Mon, 29 May 2023 22:10:49 +0900 Subject: [PATCH] =?UTF-8?q?#91=20BaseNavArgs=EA=B0=9C=EC=84=A0(NavArgs?= =?UTF-8?q?=EB=A1=9C=20=EA=B0=80=EB=8A=A5=ED=95=9C=20=EA=B1=B0=EC=9D=98=20?= =?UTF-8?q?=EB=8C=80=EB=B6=80=EB=B6=84=EC=9D=98=20=ED=83=80=EC=9E=85=20?= =?UTF-8?q?=ED=98=B8=ED=99=98)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 1 + app/src/main/res/navigation/main_nav.xml | 1 + build.gradle.kts | 2 +- .../core/common/util/UriBuilder.kt | 8 ++- .../core/data/remote/di/RepositoryModule.kt | 4 +- .../MedicineApprovalRepositoryImpl.kt | 6 +- core/database/build.gradle.kts | 3 +- .../core/database/{RoomDb.kt => RoomDB.kt} | 4 +- .../mediproject/core/database/RoomModule.kt | 9 ++- .../searchhistory/SearchHistoryDao.kt | 12 ++-- .../searchhistory/SearchHistoryMapper.kt | 9 +++ core/domain/build.gradle.kts | 1 + .../core/domain/SearchHistoryUseCase.kt | 24 +++++++ .../core/model/local/navargs/BaseNavArgs.kt | 31 +++++++--- .../search/local/SearchHistoryItemDto.kt | 9 +++ .../searchmedicines/local/SearchQueryArgs.kt | 12 ++++ .../core/ui/base/view/MediSearchbar.kt | 8 +++ .../mediproject/feature/home/HomeFragment.kt | 18 +++--- .../src/main/res/layout/fragment_home.xml | 2 +- .../home/src/main/res/navigation/home_nav.xml | 17 ++--- .../feature/search/SearchMedicinesFragment.kt | 59 ++++++++---------- .../search/SearchMedicinesViewModel.kt | 13 ++-- .../RecentSearchListFragment.kt | 62 ++++++++++++------- .../RecentSearchListViewModel.kt | 16 ++++- .../manual/ManualSearchResultFragment.kt | 19 +++--- .../res/layout/fragment_search_medicines.xml | 2 +- .../res/navigation/search_medicines_nav.xml | 10 ++- 27 files changed, 232 insertions(+), 130 deletions(-) rename core/database/src/main/java/com/android/mediproject/core/database/{RoomDb.kt => RoomDB.kt} (72%) create mode 100644 core/database/src/main/java/com/android/mediproject/core/database/searchhistory/SearchHistoryMapper.kt create mode 100644 core/domain/src/main/java/com/android/mediproject/core/domain/SearchHistoryUseCase.kt create mode 100644 core/model/src/main/java/com/android/mediproject/core/model/search/local/SearchHistoryItemDto.kt create mode 100644 core/model/src/main/java/com/android/mediproject/core/model/searchmedicines/local/SearchQueryArgs.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index daaa5240b..e354ce243 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -42,6 +42,7 @@ dependencies { implementation(project(":core:data")) implementation(project(":core:ui")) implementation(project(":core:model")) + implementation(project(":core:database")) implementation(project(":feature:interestedmedicine")) diff --git a/app/src/main/res/navigation/main_nav.xml b/app/src/main/res/navigation/main_nav.xml index df0250b5e..00d63db4f 100644 --- a/app/src/main/res/navigation/main_nav.xml +++ b/app/src/main/res/navigation/main_nav.xml @@ -14,5 +14,6 @@ + \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index a762e3440..7dba145b2 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -19,7 +19,7 @@ plugins { alias(libs.plugins.kotlin.android) apply false alias(libs.plugins.nav.safeargs.kotlin) apply false alias(libs.plugins.kapt) apply false - alias(libs.plugins.ksp) apply false + // alias(libs.plugins.ksp) apply false alias(libs.plugins.kotlin.parcelize) apply false } diff --git a/core/common/src/main/java/com/android/mediproject/core/common/util/UriBuilder.kt b/core/common/src/main/java/com/android/mediproject/core/common/util/UriBuilder.kt index 6f4ef3d42..5952e218d 100644 --- a/core/common/src/main/java/com/android/mediproject/core/common/util/UriBuilder.kt +++ b/core/common/src/main/java/com/android/mediproject/core/common/util/UriBuilder.kt @@ -16,14 +16,16 @@ import com.android.mediproject.core.model.local.navargs.BaseNavArgs * @param parameter Uri에 들어갈 파라미터 * @return Uri */ -private fun toDeepUrl(deepLinkUrl: String, parameter: Map): Uri = StringBuilder(deepLinkUrl).let { uri -> +private fun toDeepUrl(deepLinkUrl: String, parameter: Map): Uri = StringBuilder(deepLinkUrl).let { uri -> parameter.takeIf { it.isNotEmpty() }?.also { map -> uri.append("?") map.onEachIndexed { index, entry -> - uri.append("${entry.key}=${entry.value}") - if (index != map.size - 1) uri.append("&") + if (entry.value != null) { + uri.append("${entry.key}=${entry.value}") + if (index != map.size - 1) uri.append("&") + } } } uri.toString().toUri() diff --git a/core/data/src/main/java/com/android/mediproject/core/data/remote/di/RepositoryModule.kt b/core/data/src/main/java/com/android/mediproject/core/data/remote/di/RepositoryModule.kt index b43de3355..ca6c9ec03 100644 --- a/core/data/src/main/java/com/android/mediproject/core/data/remote/di/RepositoryModule.kt +++ b/core/data/src/main/java/com/android/mediproject/core/data/remote/di/RepositoryModule.kt @@ -46,9 +46,9 @@ object RepositoryModule { @Provides @Singleton fun provideMedicineApprovalRepository( - medicineApprovalDataSource: MedicineApprovalDataSource, searchHistoryDao: SearchHistoryDao + medicineApprovalDataSource: MedicineApprovalDataSource, searchHistoryRepository: SearchHistoryRepository ): MedicineApprovalRepository = MedicineApprovalRepositoryImpl( - medicineApprovalDataSource, searchHistoryDao + medicineApprovalDataSource, searchHistoryRepository ) @Provides diff --git a/core/data/src/main/java/com/android/mediproject/core/data/remote/medicineapproval/MedicineApprovalRepositoryImpl.kt b/core/data/src/main/java/com/android/mediproject/core/data/remote/medicineapproval/MedicineApprovalRepositoryImpl.kt index 42293cf1f..548b69133 100644 --- a/core/data/src/main/java/com/android/mediproject/core/data/remote/medicineapproval/MedicineApprovalRepositoryImpl.kt +++ b/core/data/src/main/java/com/android/mediproject/core/data/remote/medicineapproval/MedicineApprovalRepositoryImpl.kt @@ -4,7 +4,7 @@ import androidx.paging.Pager import androidx.paging.PagingConfig import androidx.paging.PagingData import com.android.mediproject.core.common.DATA_GO_KR_PAGE_SIZE -import com.android.mediproject.core.database.searchhistory.SearchHistoryDao +import com.android.mediproject.core.data.search.SearchHistoryRepository import com.android.mediproject.core.model.medicine.medicineapproval.Item import com.android.mediproject.core.model.medicine.medicinedetailinfo.MedicineDetailInfoResponse import com.android.mediproject.core.network.datasource.medicineapproval.MedicineApprovalDataSource @@ -17,7 +17,7 @@ import kotlinx.coroutines.flow.map import javax.inject.Inject class MedicineApprovalRepositoryImpl @Inject constructor( - private val medicineApprovalDataSource: MedicineApprovalDataSource, private val searchHistoryDao: SearchHistoryDao + private val medicineApprovalDataSource: MedicineApprovalDataSource, private val searchHistoryRepository: SearchHistoryRepository ) : MedicineApprovalRepository { /** @@ -35,7 +35,7 @@ class MedicineApprovalRepositoryImpl @Inject constructor( if (itemName == null && entpName == null) { emptyFlow() } else { - + searchHistoryRepository.insertSearchHistory(itemName ?: entpName!!) Pager(config = PagingConfig(pageSize = DATA_GO_KR_PAGE_SIZE, prefetchDistance = 5), pagingSourceFactory = { MedicineApprovalListDataSourceImpl( medicineApprovalDataSource, itemName, entpName, medicationType diff --git a/core/database/build.gradle.kts b/core/database/build.gradle.kts index 2ffb38115..a2afa5298 100644 --- a/core/database/build.gradle.kts +++ b/core/database/build.gradle.kts @@ -1,7 +1,6 @@ plugins { id("mediproject.android.library") id("mediproject.android.hilt") - alias(libs.plugins.ksp) } android { @@ -15,7 +14,7 @@ android { dependencies { implementation(project(":core:common")) implementation(project(":core:model")) - implementation(libs.bundles.rooms) implementation(libs.kotlinx.coroutines.android) + kapt(libs.androidx.room.compileKsp) } \ No newline at end of file diff --git a/core/database/src/main/java/com/android/mediproject/core/database/RoomDb.kt b/core/database/src/main/java/com/android/mediproject/core/database/RoomDB.kt similarity index 72% rename from core/database/src/main/java/com/android/mediproject/core/database/RoomDb.kt rename to core/database/src/main/java/com/android/mediproject/core/database/RoomDB.kt index 84818125c..98542af44 100644 --- a/core/database/src/main/java/com/android/mediproject/core/database/RoomDb.kt +++ b/core/database/src/main/java/com/android/mediproject/core/database/RoomDB.kt @@ -6,7 +6,7 @@ import com.android.mediproject.core.database.searchhistory.SearchHistoryDao import com.android.mediproject.core.database.searchhistory.SearchHistoryDto -@Database(entities = [SearchHistoryDto::class], version = 1) -abstract class RoomDb : RoomDatabase() { +@Database(entities = [SearchHistoryDto::class], version = 1, exportSchema = true) +abstract class RoomDB : RoomDatabase() { abstract fun searchHistoryDao(): SearchHistoryDao } \ No newline at end of file diff --git a/core/database/src/main/java/com/android/mediproject/core/database/RoomModule.kt b/core/database/src/main/java/com/android/mediproject/core/database/RoomModule.kt index 88e1abe42..f732e19cc 100644 --- a/core/database/src/main/java/com/android/mediproject/core/database/RoomModule.kt +++ b/core/database/src/main/java/com/android/mediproject/core/database/RoomModule.kt @@ -10,21 +10,20 @@ import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent import javax.inject.Singleton -@Module(includes = [DaoModule::class]) +@Module @InstallIn(SingletonComponent::class) object RoomModule { @Provides @Singleton - fun provideRoomDb(@ApplicationContext context: Context): RoomDb = Room.databaseBuilder( - context, RoomDb::class.java, "medi_database" + fun provideRoomDb(@ApplicationContext context: Context): RoomDB = Room.databaseBuilder( + context, RoomDB::class.java, "medi_database" ).build() } - @Module @InstallIn(SingletonComponent::class) object DaoModule { @Provides - fun provideSearchHistoryDao(roomDb: RoomDb): SearchHistoryDao = roomDb.searchHistoryDao() + fun provideSearchHistoryDao(roomDB: RoomDB): SearchHistoryDao = roomDB.searchHistoryDao() } \ No newline at end of file diff --git a/core/database/src/main/java/com/android/mediproject/core/database/searchhistory/SearchHistoryDao.kt b/core/database/src/main/java/com/android/mediproject/core/database/searchhistory/SearchHistoryDao.kt index e6f14ee66..c595def4a 100644 --- a/core/database/src/main/java/com/android/mediproject/core/database/searchhistory/SearchHistoryDao.kt +++ b/core/database/src/main/java/com/android/mediproject/core/database/searchhistory/SearchHistoryDao.kt @@ -1,7 +1,6 @@ package com.android.mediproject.core.database.searchhistory import androidx.room.Dao -import androidx.room.Delete import androidx.room.Query import androidx.room.Upsert import kotlinx.coroutines.flow.Flow @@ -14,26 +13,25 @@ interface SearchHistoryDao { * @param searchHistoryDto */ @Upsert - suspend fun insert(searchHistoryDto: SearchHistoryDto) + fun insert(searchHistoryDto: SearchHistoryDto) /** * id에 해당하는 검색 기록을 삭제한다. * @param id */ - @Delete - suspend fun delete(id: Long) + @Query("DELETE FROM search_history_table WHERE id = :id") + fun delete(id: Long) /** * 개수 만큼 검색 목록을 가져온다. * @param count */ @Query("SELECT * FROM search_history_table ORDER BY id DESC LIMIT :count") - suspend fun select(count: Int): Flow> + fun select(count: Int): Flow> /** * 모든 검색 기록을 삭제한다. */ @Query("DELETE FROM search_history_table") - @Delete - suspend fun deleteAll() + fun deleteAll() } \ No newline at end of file diff --git a/core/database/src/main/java/com/android/mediproject/core/database/searchhistory/SearchHistoryMapper.kt b/core/database/src/main/java/com/android/mediproject/core/database/searchhistory/SearchHistoryMapper.kt new file mode 100644 index 000000000..fa2ef8e01 --- /dev/null +++ b/core/database/src/main/java/com/android/mediproject/core/database/searchhistory/SearchHistoryMapper.kt @@ -0,0 +1,9 @@ +package com.android.mediproject.core.database.searchhistory + +import java.time.LocalDateTime + +fun SearchHistoryDto.toSearchHistoryItemDto(): com.android.mediproject.core.model.search.local.SearchHistoryItemDto { + return com.android.mediproject.core.model.search.local.SearchHistoryItemDto( + id = id, query = query, searchedAt = LocalDateTime.parse(this.searchDateTime) + ) +} \ No newline at end of file diff --git a/core/domain/build.gradle.kts b/core/domain/build.gradle.kts index 4ef9e7a45..c9bbe35ea 100644 --- a/core/domain/build.gradle.kts +++ b/core/domain/build.gradle.kts @@ -22,6 +22,7 @@ dependencies { implementation(project(":core:model")) implementation(project(":core:network")) implementation(project(":core:data")) + implementation(project(":core:database")) implementation(libs.androidx.core.ktx) implementation(libs.kotlinx.serialization.json) diff --git a/core/domain/src/main/java/com/android/mediproject/core/domain/SearchHistoryUseCase.kt b/core/domain/src/main/java/com/android/mediproject/core/domain/SearchHistoryUseCase.kt new file mode 100644 index 000000000..a047cc9d8 --- /dev/null +++ b/core/domain/src/main/java/com/android/mediproject/core/domain/SearchHistoryUseCase.kt @@ -0,0 +1,24 @@ +package com.android.mediproject.core.domain + +import com.android.mediproject.core.data.search.SearchHistoryRepository +import com.android.mediproject.core.database.searchhistory.toSearchHistoryItemDto +import kotlinx.coroutines.flow.map +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class SearchHistoryUseCase @Inject constructor( + private val searchHistoryRepository: SearchHistoryRepository +) { + suspend fun insertSearchHistory(query: String) = searchHistoryRepository.insertSearchHistory(query) + + suspend fun getSearchHistoryList(count: Int) = searchHistoryRepository.getSearchHistoryList(count).map { + it.map { + it.toSearchHistoryItemDto() + } + } + + suspend fun deleteSearchHistory(id: Long) = searchHistoryRepository.deleteSearchHistory(id) + + suspend fun deleteAllSearchHistory() = searchHistoryRepository.deleteAllSearchHistory() +} \ No newline at end of file diff --git a/core/model/src/main/java/com/android/mediproject/core/model/local/navargs/BaseNavArgs.kt b/core/model/src/main/java/com/android/mediproject/core/model/local/navargs/BaseNavArgs.kt index 1f0a6177f..8105bc8e6 100644 --- a/core/model/src/main/java/com/android/mediproject/core/model/local/navargs/BaseNavArgs.kt +++ b/core/model/src/main/java/com/android/mediproject/core/model/local/navargs/BaseNavArgs.kt @@ -5,38 +5,53 @@ import androidx.navigation.NavArgs import kotlin.reflect.KClass import kotlin.reflect.full.memberProperties import kotlin.reflect.full.primaryConstructor +import kotlin.reflect.javaType abstract class BaseNavArgs( val className: String ) : NavArgs { - @Suppress("CAST_NEVER_SUCCEEDS") fun toBundle(): Bundle { val result = Bundle() toMap().forEach { (key, value) -> - result.putString(key, value) + when (value) { + null -> return@forEach + is String -> result.putString(key, value) + is Int -> result.putInt(key, value) + is Long -> result.putLong(key, value) + is Float -> result.putFloat(key, value) + is Boolean -> result.putBoolean(key, value) + } } return result } companion object { + @OptIn(ExperimentalStdlibApi::class) @JvmStatic fun fromBundle(bundle: Bundle): BaseNavArgs { - // kotlin reflection Class.forName(bundle.getString("className")!!) val kClass: KClass = Class.forName(bundle.getString("className")!!).kotlin as KClass bundle.classLoader = kClass.java.classLoader val constructor = kClass.primaryConstructor!! val args = constructor.parameters.map { parameter -> - bundle.getString(parameter.name, "") + val type = parameter.type.javaType + with(type) { + when (type) { + String::class.java -> bundle.getString(parameter.name) + Int::class.java -> bundle.getInt(parameter.name) + Long::class.java -> bundle.getLong(parameter.name) + Float::class.java -> bundle.getFloat(parameter.name) + Boolean::class.java -> bundle.getBoolean(parameter.name) + else -> throw IllegalArgumentException("Argument \"${parameter.name}\" is marked as non-null but was passed a null value.") + } + } } return constructor.call(*args.toTypedArray()) } } - fun toMap(): Map = this::class.memberProperties.let { properties -> - properties.associate { property -> - property.name to property.getter.call(this).toString() - }.toMap() + fun toMap(): Map = this::class.memberProperties.associate { property -> + property.name to property.getter.call(this) } } \ No newline at end of file diff --git a/core/model/src/main/java/com/android/mediproject/core/model/search/local/SearchHistoryItemDto.kt b/core/model/src/main/java/com/android/mediproject/core/model/search/local/SearchHistoryItemDto.kt new file mode 100644 index 000000000..2caaee1ff --- /dev/null +++ b/core/model/src/main/java/com/android/mediproject/core/model/search/local/SearchHistoryItemDto.kt @@ -0,0 +1,9 @@ +package com.android.mediproject.core.model.search.local + +import java.time.LocalDateTime + +data class SearchHistoryItemDto( + val id: Long, + val query: String, + val searchedAt: LocalDateTime +) \ No newline at end of file diff --git a/core/model/src/main/java/com/android/mediproject/core/model/searchmedicines/local/SearchQueryArgs.kt b/core/model/src/main/java/com/android/mediproject/core/model/searchmedicines/local/SearchQueryArgs.kt new file mode 100644 index 000000000..643c8568f --- /dev/null +++ b/core/model/src/main/java/com/android/mediproject/core/model/searchmedicines/local/SearchQueryArgs.kt @@ -0,0 +1,12 @@ +package com.android.mediproject.core.model.searchmedicines.local + +import com.android.mediproject.core.model.local.navargs.BaseNavArgs + +/** + * 검색어를 전달할 인자 + * + * @property query 검색어 + */ +data class SearchQueryArgs( + val query: String = "" +) : BaseNavArgs(SearchQueryArgs::class.java.name) \ No newline at end of file diff --git a/core/ui/src/main/java/com/android/mediproject/core/ui/base/view/MediSearchbar.kt b/core/ui/src/main/java/com/android/mediproject/core/ui/base/view/MediSearchbar.kt index 02853b3dc..4fa8e2c51 100644 --- a/core/ui/src/main/java/com/android/mediproject/core/ui/base/view/MediSearchbar.kt +++ b/core/ui/src/main/java/com/android/mediproject/core/ui/base/view/MediSearchbar.kt @@ -4,6 +4,7 @@ import android.annotation.SuppressLint import android.content.Context import android.content.res.ColorStateList import android.graphics.Color +import android.text.Editable import android.util.AttributeSet import android.util.TypedValue import android.view.MotionEvent @@ -230,4 +231,11 @@ class MediSearchbar constructor( super.onInterceptTouchEvent(ev) } } + + + fun getText(): Editable = searchEditView.text + + fun setText(text: String) { + searchEditView.setText(text) + } } \ No newline at end of file diff --git a/feature/home/src/main/java/com/android/mediproject/feature/home/HomeFragment.kt b/feature/home/src/main/java/com/android/mediproject/feature/home/HomeFragment.kt index 21fc00258..8dcf2bedd 100644 --- a/feature/home/src/main/java/com/android/mediproject/feature/home/HomeFragment.kt +++ b/feature/home/src/main/java/com/android/mediproject/feature/home/HomeFragment.kt @@ -4,6 +4,8 @@ import android.os.Bundle import android.view.View import androidx.fragment.app.viewModels import androidx.navigation.fragment.findNavController +import com.android.mediproject.core.common.util.navigateByDeepLink +import com.android.mediproject.core.model.searchmedicines.local.SearchQueryArgs import com.android.mediproject.core.ui.base.BaseFragment import com.android.mediproject.feature.comments.recentcommentlist.RecentCommentListFragment import com.android.mediproject.feature.home.databinding.FragmentHomeBinding @@ -52,10 +54,7 @@ class HomeFragment : BaseFragment(FragmentHo */ private fun initSearchBar() { binding.searchView.setOnBarClickListener { - Bundle().apply { - putString("searchQuery", null) - findNavController().navigate(R.id.action_homeFragment_to_searchMedicinesFragment, this) - } + findNavController().navigate(HomeFragmentDirections.actionHomeFragmentToSearchMedicinesFragment()) } } @@ -74,13 +73,10 @@ class HomeFragment : BaseFragment(FragmentHo } setFragmentResultListener(RecentSearchListFragment.ResultKey.RESULT_KEY.name, viewLifecycleOwner) { _, bundle -> bundle.apply { - getString(RecentSearchListFragment.ResultKey.WORD.name).also { - Bundle().apply { - putString("searchQuery", it) - findNavController().navigate(R.id.action_homeFragment_to_searchMedicinesFragment, this) - } - } - + findNavController().navigateByDeepLink( + "medilens://main/search/recentSearchListFragment", + SearchQueryArgs(getString(RecentSearchListFragment.ResultKey.WORD.name) ?: "") + ) } } } diff --git a/feature/home/src/main/res/layout/fragment_home.xml b/feature/home/src/main/res/layout/fragment_home.xml index 76744e66d..e6d4dee5c 100644 --- a/feature/home/src/main/res/layout/fragment_home.xml +++ b/feature/home/src/main/res/layout/fragment_home.xml @@ -68,7 +68,7 @@ android:layout_marginTop="69dp" android:layout_marginBottom="58dp" android:bufferType="spannable" - android:lineSpacingMultiplier="1.2" + android:lineSpacingMultiplier="1.1" android:text="@{viewModel.headerText}" android:textColor="@color/white" android:textSize="23sp" diff --git a/feature/home/src/main/res/navigation/home_nav.xml b/feature/home/src/main/res/navigation/home_nav.xml index aeef86341..3383d2cfe 100644 --- a/feature/home/src/main/res/navigation/home_nav.xml +++ b/feature/home/src/main/res/navigation/home_nav.xml @@ -10,25 +10,16 @@ android:name="com.android.mediproject.feature.home.HomeFragment" android:label="HomeFragment" tools:layout="@layout/fragment_home"> - - - + + - - - - - - + tools:layout="@layout/fragment_search_medicines" + android:label="SearchMedicinesFragment" /> \ No newline at end of file diff --git a/feature/search/src/main/java/com/android/mediproject/feature/search/SearchMedicinesFragment.kt b/feature/search/src/main/java/com/android/mediproject/feature/search/SearchMedicinesFragment.kt index db8a87807..b2491c568 100644 --- a/feature/search/src/main/java/com/android/mediproject/feature/search/SearchMedicinesFragment.kt +++ b/feature/search/src/main/java/com/android/mediproject/feature/search/SearchMedicinesFragment.kt @@ -28,46 +28,40 @@ class SearchMedicinesFragment : // contents_fragment_container_view 에 최근 검색 목록과 검색 결과 목록 화면 두 개를 띄운다. initSearchBar() - arguments?.apply { - getString("searchQuery")?.also { - binding.searchView.searchWithQuery(query = it) - } - } - viewLifecycleOwner.repeatOnStarted { fragmentViewModel.searchQuery.collect { query -> - if (query.isNotBlank()) { - binding.contentsFragmentContainerView.findNavController().also { navController -> - navController.currentDestination?.also { currentDestination -> - if (currentDestination.id == R.id.manualSearchResultFragment) navController.navigate(R.id.action_manualSearchResultFragment_self) - else navController.navigate( - RecentSearchListFragmentDirections.actionRecentSearchListFragmentToManualSearchResultFragment() - ) - - } - } - } + // Flow로 받은 문자열이 일치하는 경우에만 searchView에 표시한다. + if (!binding.searchView.getText().contentEquals(query)) binding.searchView.setText(query) } } + } - override fun onStart() { - super.onStart() + private fun moveToManualSearchResultFragment() { + val navController = binding.contentsFragmentContainerView.findNavController() + navController.currentDestination?.also { currentDestination -> + // 현재 프래그먼트가 검색 결과 프래그먼트인 경우에는 백스택에서 검색 결과 프래그먼트를 제거하고 검색 결과 프래그먼트로 + // 다시 이동한다. + // 검색 결과 프래그먼트가 아닌 경우에는 검색 결과 프래그먼트로 이동한다. + if (currentDestination.id == R.id.manualSearchResultFragment) navController.navigate(R.id.action_manualSearchResultFragment_self) + else navController.navigate( + RecentSearchListFragmentDirections.actionRecentSearchListFragmentToManualSearchResultFragment() + ) + } + } - binding.apply { - childFragmentManager.findFragmentById(R.id.contentsFragmentContainerView)?.also { navHostFragment -> - navHostFragment.childFragmentManager.apply { - setFragmentResultListener( - RecentSearchListFragment.ResultKey.RESULT_KEY.name, navHostFragment.viewLifecycleOwner - ) { _, bundle -> + override fun onViewStateRestored(savedInstanceState: Bundle?) { + super.onViewStateRestored(savedInstanceState) - bundle.apply { - getString(RecentSearchListFragment.ResultKey.WORD.name)?.also { - binding.searchView.searchWithQuery(query = it) - } - } - } + childFragmentManager.findFragmentById(R.id.contentsFragmentContainerView)?.also { navHostFragment -> + val childFragmentManager = navHostFragment.childFragmentManager + childFragmentManager.setFragmentResultListener( + RecentSearchListFragment.ResultKey.RESULT_KEY.name, navHostFragment.viewLifecycleOwner + ) { _, bundle -> + bundle.getString(RecentSearchListFragment.ResultKey.WORD.name)?.also { + binding.searchView.searchWithQuery(it) } + } } } @@ -83,7 +77,8 @@ class SearchMedicinesFragment : binding.searchView.setOnSearchBtnClickListener(object : MediSearchbar.SearchQueryCallback { override fun onSearchQuery(query: String) { hideKeyboard() - fragmentViewModel.searchMedicines(query) + fragmentViewModel.setQuery(query) + moveToManualSearchResultFragment() } override fun onEmptyQuery() { diff --git a/feature/search/src/main/java/com/android/mediproject/feature/search/SearchMedicinesViewModel.kt b/feature/search/src/main/java/com/android/mediproject/feature/search/SearchMedicinesViewModel.kt index 0536b46a2..aa875f6a5 100644 --- a/feature/search/src/main/java/com/android/mediproject/feature/search/SearchMedicinesViewModel.kt +++ b/feature/search/src/main/java/com/android/mediproject/feature/search/SearchMedicinesViewModel.kt @@ -11,12 +11,15 @@ import javax.inject.Inject @HiltViewModel class SearchMedicinesViewModel @Inject constructor() : BaseViewModel() { - private val _searchQuery = MutableStateFlow("") + private val _searchQuery = MutableStateFlow("") val searchQuery = _searchQuery.asStateFlow() - fun searchMedicines(query: String) { - viewModelScope.launch { - _searchQuery.emit(query) - } + + /** + * 검색어 저장 + */ + fun setQuery(query: String) = viewModelScope.launch { + _searchQuery.value = query } + } \ No newline at end of file diff --git a/feature/search/src/main/java/com/android/mediproject/feature/search/recentsearchlist/RecentSearchListFragment.kt b/feature/search/src/main/java/com/android/mediproject/feature/search/recentsearchlist/RecentSearchListFragment.kt index 1ead8c9ae..a98e9e98c 100644 --- a/feature/search/src/main/java/com/android/mediproject/feature/search/recentsearchlist/RecentSearchListFragment.kt +++ b/feature/search/src/main/java/com/android/mediproject/feature/search/recentsearchlist/RecentSearchListFragment.kt @@ -4,12 +4,15 @@ import android.os.Bundle import android.view.View import android.view.ViewGroup.LayoutParams import androidx.core.os.bundleOf -import androidx.fragment.app.viewModels +import androidx.fragment.app.activityViewModels +import com.android.mediproject.core.model.search.local.SearchHistoryItemDto import com.android.mediproject.core.ui.base.BaseFragment import com.android.mediproject.core.ui.base.view.ButtonChip import com.android.mediproject.feature.search.databinding.FragmentRecentSearchListBinding import com.google.android.flexbox.FlexboxLayout import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.flow.collectLatest +import repeatOnStarted /** @@ -25,12 +28,20 @@ class RecentSearchListFragment : RESULT_KEY, WORD } - override val fragmentViewModel: RecentSearchListViewModel by viewModels() + override val fragmentViewModel: RecentSearchListViewModel by activityViewModels() override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - addHistoryItemChips() initHeader() + + viewLifecycleOwner.repeatOnStarted { + fragmentViewModel.searchHistoryList().collectLatest { + // 최근 검색 목록을 가져옵니다. + it.forEach { searchHistoryItemDto -> + addHistoryItemChips(searchHistoryItemDto) + } + } + } } /** @@ -38,31 +49,34 @@ class RecentSearchListFragment : * * 클릭 시 관련 로직을 수행하도록 합니다. */ - private fun addHistoryItemChips() { + private fun addHistoryItemChips(searchHistoryItemDto: SearchHistoryItemDto) { binding.apply { val horizontalSpace = resources.getDimension(com.android.mediproject.core.ui.R.dimen.dp_4).toInt() + this.searchHistoryList.addView(ButtonChip(requireContext()).apply { + layoutParams = FlexboxLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT).apply { + setMargins(horizontalSpace, 0, horizontalSpace, 0) + } + data = searchHistoryItemDto.query + setChipText(data.toString()) + setOnChipClickListener { + onClicked(searchHistoryItemDto.query) + } + }) + } + } - repeat(5) { - this.searchHistoryList.addView(ButtonChip(requireContext()).apply { - layoutParams = FlexboxLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT).apply { - setMargins(horizontalSpace, 0, horizontalSpace, 0) - } - data = it.toString() - setChipText("검색어 $it") - setOnChipClickListener { - it?.also { - parentFragmentManager.apply { - setFragmentResult( - ResultKey.RESULT_KEY.name, bundleOf( - ResultKey.WORD.name to it - ) - ) - } + override fun onDestroyView() { + binding.searchHistoryList.removeAllViews() + super.onDestroyView() + } - } - } - }) - } + private fun onClicked(query: String) { + parentFragmentManager.apply { + setFragmentResult( + ResultKey.RESULT_KEY.name, bundleOf( + ResultKey.WORD.name to query + ) + ) } } diff --git a/feature/search/src/main/java/com/android/mediproject/feature/search/recentsearchlist/RecentSearchListViewModel.kt b/feature/search/src/main/java/com/android/mediproject/feature/search/recentsearchlist/RecentSearchListViewModel.kt index 9f2c88833..49bfd9c0c 100644 --- a/feature/search/src/main/java/com/android/mediproject/feature/search/recentsearchlist/RecentSearchListViewModel.kt +++ b/feature/search/src/main/java/com/android/mediproject/feature/search/recentsearchlist/RecentSearchListViewModel.kt @@ -1,10 +1,22 @@ package com.android.mediproject.feature.search.recentsearchlist +import androidx.lifecycle.viewModelScope +import com.android.mediproject.core.domain.SearchHistoryUseCase import com.android.mediproject.core.ui.base.BaseViewModel import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.shareIn import javax.inject.Inject @HiltViewModel -class RecentSearchListViewModel @Inject constructor() : BaseViewModel() { - +class RecentSearchListViewModel @Inject constructor( + private val searchHistoryUseCase: SearchHistoryUseCase +) : BaseViewModel() { + val searchHistoryList by lazy { + suspend { + searchHistoryUseCase.getSearchHistoryList(6).shareIn( + viewModelScope, started = SharingStarted.Lazily, replay = 1 + ) + } + } } \ No newline at end of file diff --git a/feature/search/src/main/java/com/android/mediproject/feature/search/result/manual/ManualSearchResultFragment.kt b/feature/search/src/main/java/com/android/mediproject/feature/search/result/manual/ManualSearchResultFragment.kt index 0b2967949..25e2f62bd 100644 --- a/feature/search/src/main/java/com/android/mediproject/feature/search/result/manual/ManualSearchResultFragment.kt +++ b/feature/search/src/main/java/com/android/mediproject/feature/search/result/manual/ManualSearchResultFragment.kt @@ -5,6 +5,7 @@ import android.view.View import androidx.core.content.ContextCompat import androidx.fragment.app.viewModels import androidx.navigation.findNavController +import androidx.navigation.fragment.navArgs import androidx.paging.map import androidx.recyclerview.widget.DividerItemDecoration import com.android.mediproject.core.common.paging.setOnStateChangedListener @@ -31,6 +32,8 @@ class ManualSearchResultFragment : override val fragmentViewModel: ManualSearchResultViewModel by viewModels() + private val searchQueryArgs: ManualSearchResultFragmentArgs by navArgs() + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -38,6 +41,10 @@ class ManualSearchResultFragment : binding.apply { viewModel = fragmentViewModel + if (searchQueryArgs.query.isNotEmpty()) { + searchMedicinesViewModel.setQuery(searchQueryArgs.query) + } + val searchResultListAdapter = ApprovedMedicinesAdapter().also { it.withLoadStateHeaderAndFooter(header = PagingLoadStateAdapter { it.retry() }, footer = PagingLoadStateAdapter { it.retry() }) @@ -79,13 +86,11 @@ class ManualSearchResultFragment : // 검색 결과를 수신하면 리스트에 표시한다. launch { fragmentViewModel.searchResultFlow.collectLatest { - it.let { pager -> - pager.map { item -> - item.onClick = this@ManualSearchResultFragment::openMedicineInfo - item - } - }.also { pagingData -> - searchResultListAdapter.submitData(pagingData) + it.map { item -> + item.onClick = this@ManualSearchResultFragment::openMedicineInfo + item + }.apply { + searchResultListAdapter.submitData(this) } } } diff --git a/feature/search/src/main/res/layout/fragment_search_medicines.xml b/feature/search/src/main/res/layout/fragment_search_medicines.xml index ee9d4093e..85d7f86a0 100644 --- a/feature/search/src/main/res/layout/fragment_search_medicines.xml +++ b/feature/search/src/main/res/layout/fragment_search_medicines.xml @@ -34,6 +34,7 @@ android:layout_marginTop="27dp" app:ai_text="@string/search_with_ai" app:camera_icon="@drawable/baseline_camera_24" + app:ime_options="actionSearch" app:is_all_btn="false" app:layout_constraintBottom_toTopOf="@id/contentsFragmentContainerView" app:layout_constraintLeft_toLeftOf="parent" @@ -42,7 +43,6 @@ app:layout_goneMarginTop="30dp" app:search_hint="@string/search_hint" app:search_icon="@drawable/baseline_search_24" - app:ime_options="actionSearch" /> + + + + - + \ No newline at end of file