From d8517f029b71a7f17d533ac270e33190c43c0072 Mon Sep 17 00:00:00 2001 From: JSPark <48265129+pknujsp@users.noreply.github.com> Date: Wed, 31 May 2023 23:09:27 +0900 Subject: [PATCH] =?UTF-8?q?#91=20=EB=8C=93=EA=B8=80=20=EC=A0=84=EC=86=A1?= =?UTF-8?q?=20=EC=8B=9C=20=EB=A1=9C=EC=A7=81=EC=9D=84=20BindingAdapter?= =?UTF-8?q?=EB=A1=9C=20=ED=86=B5=ED=95=A9=ED=95=B4=20=EC=A0=9C=EC=9E=91,?= =?UTF-8?q?=20=EB=8C=93=EA=B8=80=20=EB=93=B1=EB=A1=9D/=EC=88=98=EC=A0=95/?= =?UTF-8?q?=EC=A2=8B=EC=95=84=EC=9A=94/=EC=82=AD=EC=A0=9C=20=EB=B9=84?= =?UTF-8?q?=EC=A6=88=EB=8B=88=EC=8A=A4=20=EB=A1=9C=EC=A7=81=20=EA=B5=90?= =?UTF-8?q?=EC=B2=B4,?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/android/mediproject/MainActivity.kt | 21 +- app/src/main/res/layout/activity_main.xml | 1 + .../common/bindingadapter/BindingAdapter.kt | 31 ++- .../core/common/bindingadapter/ISendText.kt | 5 + .../core/common/paging/PagingUtil.kt | 12 +- .../remote/comments/CommentsRepository.kt | 31 ++- .../remote/comments/CommentsRepositoryImpl.kt | 22 +- .../RecallSuspensionRepository.kt | 4 +- .../RecallSuspensionRepositoryImpl.kt | 36 +-- .../core/domain/CommentsUseCase.kt | 56 +++++ .../core/domain/GetCommentsUseCase.kt | 52 ----- .../domain/GetMedicineApprovalListUseCase.kt | 11 +- .../domain/GetRecallSuspensionInfoUseCase.kt | 39 ++-- .../core/domain/SearchHistoryUseCase.kt | 12 +- .../core/model/comments/CommentDto.kt | 26 ++- .../MedicineApprovalListResponse.kt | 2 +- .../datasource/comments/CommentsDataSource.kt | 16 +- .../comments/CommentsDataSourceImpl.kt | 40 +++- .../comments/CommentsListDataSourceImpl.kt | 4 +- .../MedicineApprovalListDataSourceImpl.kt | 2 +- .../RecallSuspensionDataSource.kt | 2 +- .../RecallSuspensionDataSourceImpl.kt | 47 ++-- .../mediproject/core/ui/WindowViewModel.kt | 22 ++ .../core/ui/base/view/SimpleListItemView.kt | 31 ++- .../main/res/layout/viewgroup_paging_list.xml | 15 +- feature/comments/src/main/AndroidManifest.xml | 3 +- .../commentsofamedicine/CommentsAdapter.kt | 173 +++++++------- .../MedicineCommentsFragment.kt | 71 +++--- .../MedicineCommentsViewModel.kt | 213 ++++++++++++++---- .../RecentCommentsAdapter.kt | 6 +- .../mediproject/feature/comments/values.kt | 7 - .../feature/comments/view/CommentItemView.kt | 42 ++-- .../res/layout/fragment_medicine_comments.xml | 23 +- .../mediproject/feature/home/HomeFragment.kt | 20 +- .../medicine/main/MedicineInfoFragment.kt | 61 +++-- .../res/layout/fragment_medicine_info.xml | 51 ++--- .../medicine/src/main/res/values/styles.xml | 5 - .../recentpenaltylist/PenaltyListAdapter.kt | 23 +- .../RecentPenaltyListFragment.kt | 6 +- .../feature/search/SearchMedicinesFragment.kt | 41 ++-- .../RecentSearchListFragment.kt | 34 ++- .../RecentSearchListViewModel.kt | 10 +- .../manual/ManualSearchResultFragment.kt | 48 ++-- .../manual/ManualSearchResultViewModel.kt | 6 +- 44 files changed, 870 insertions(+), 513 deletions(-) create mode 100644 core/common/src/main/java/com/android/mediproject/core/common/bindingadapter/ISendText.kt create mode 100644 core/domain/src/main/java/com/android/mediproject/core/domain/CommentsUseCase.kt delete mode 100644 core/domain/src/main/java/com/android/mediproject/core/domain/GetCommentsUseCase.kt create mode 100644 core/ui/src/main/java/com/android/mediproject/core/ui/WindowViewModel.kt delete mode 100644 feature/comments/src/main/java/com/android/mediproject/feature/comments/values.kt diff --git a/app/src/main/java/com/android/mediproject/MainActivity.kt b/app/src/main/java/com/android/mediproject/MainActivity.kt index 1085203c0..d360d4e2e 100644 --- a/app/src/main/java/com/android/mediproject/MainActivity.kt +++ b/app/src/main/java/com/android/mediproject/MainActivity.kt @@ -3,15 +3,18 @@ package com.android.mediproject import android.animation.ObjectAnimator import android.os.Build import android.view.View +import android.view.ViewGroup import android.view.ViewTreeObserver import android.view.animation.AnticipateInterpolator import androidx.activity.viewModels +import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.core.animation.doOnEnd import androidx.core.net.toUri import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.navigation.NavController import androidx.navigation.fragment.NavHostFragment import androidx.navigation.ui.setupWithNavController +import com.android.mediproject.core.ui.WindowViewModel import com.android.mediproject.core.ui.base.BaseActivity import com.android.mediproject.databinding.ActivityMainBinding import dagger.hilt.android.AndroidEntryPoint @@ -21,6 +24,8 @@ import repeatOnStarted class MainActivity : BaseActivity(ActivityMainBinding::inflate) { + private val windowViewModel: WindowViewModel by viewModels() + companion object { const val VISIBLE = 0 const val INVISIBLE = 1 @@ -60,10 +65,24 @@ class MainActivity : viewModel = activityViewModel.apply { repeatOnStarted { eventFlow.collect { handleEvent(it) } } } + + root.viewTreeObserver.addOnPreDrawListener(object : ViewTreeObserver.OnPreDrawListener { + override fun onPreDraw(): Boolean { + if (bottomAppBar.height > 0) { + root.viewTreeObserver.removeOnPreDrawListener(this) + val containerHeight = root.height - bottomAppBar.height + windowViewModel.setBottomNavHeight(containerHeight) + fragmentContainerView.layoutParams = CoordinatorLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, containerHeight + ) + } + return true + } + }) } } - override fun setSplash(){ + override fun setSplash() { installSplashScreen() } diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 78682545e..8068b1494 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -11,6 +11,7 @@ diff --git a/core/common/src/main/java/com/android/mediproject/core/common/bindingadapter/BindingAdapter.kt b/core/common/src/main/java/com/android/mediproject/core/common/bindingadapter/BindingAdapter.kt index 8bd63c6de..669c6fcc7 100644 --- a/core/common/src/main/java/com/android/mediproject/core/common/bindingadapter/BindingAdapter.kt +++ b/core/common/src/main/java/com/android/mediproject/core/common/bindingadapter/BindingAdapter.kt @@ -2,6 +2,8 @@ package com.android.mediproject.core.common.bindingadapter import android.graphics.Bitmap import android.text.Spanned +import android.view.View +import android.widget.EditText import android.widget.ImageView import android.widget.TextView import androidx.core.text.PrecomputedTextCompat @@ -26,22 +28,45 @@ object BindingAdapter { @BindingAdapter("img") @JvmStatic fun loadImage(imageView: ImageView, img: String) { - GlideApp.with(imageView.context).load(img).centerInside().skipMemoryCache(false).into(imageView) + GlideApp.with(imageView.context).load(img).centerInside().skipMemoryCache(false) + .into(imageView) } @BindingAdapter("img") @JvmStatic fun loadImage(imageView: ImageView, img: Bitmap) { - GlideApp.with(imageView.context).load(img).centerInside().skipMemoryCache(false).into(imageView) + GlideApp.with(imageView.context).load(img).centerInside().skipMemoryCache(false) + .into(imageView) } + /** + * TextView에 비동기적으로 텍스트를 설정합니다. + */ @BindingAdapter("asyncText") @JvmStatic fun setAsyncText(textView: TextView, text: Spanned?) { if (text != null) { - val precomputedText = PrecomputedTextCompat.create(text, TextViewCompat.getTextMetricsParams(textView)) + val precomputedText = + PrecomputedTextCompat.create(text, TextViewCompat.getTextMetricsParams(textView)) TextViewCompat.setPrecomputedText(textView, precomputedText) } } + + /** + * EditText의 텍스트를 전달받은 ViewModel의 onClickedSendButton 함수에 전달하고, EditText의 텍스트를 지웁니다. + * + * @param view 클릭 이벤트를 받을 View + * @param editText 텍스트를 전달받을 EditText + * @param iSendText 텍스트를 전달받을 ViewModel + */ + @BindingAdapter("onClickSend", "inputText") + @JvmStatic + fun setOnClick(view: View, iSendText: ISendText, editText: EditText) { + view.setOnClickListener { + iSendText.onClickedSendButton(editText.text) + editText.text.clear() + } + } + } \ No newline at end of file diff --git a/core/common/src/main/java/com/android/mediproject/core/common/bindingadapter/ISendText.kt b/core/common/src/main/java/com/android/mediproject/core/common/bindingadapter/ISendText.kt new file mode 100644 index 000000000..62af5b7b8 --- /dev/null +++ b/core/common/src/main/java/com/android/mediproject/core/common/bindingadapter/ISendText.kt @@ -0,0 +1,5 @@ +package com.android.mediproject.core.common.bindingadapter + +interface ISendText { + fun onClickedSendButton(text: CharSequence) +} \ No newline at end of file diff --git a/core/common/src/main/java/com/android/mediproject/core/common/paging/PagingUtil.kt b/core/common/src/main/java/com/android/mediproject/core/common/paging/PagingUtil.kt index 8aaac4c41..13443de22 100644 --- a/core/common/src/main/java/com/android/mediproject/core/common/paging/PagingUtil.kt +++ b/core/common/src/main/java/com/android/mediproject/core/common/paging/PagingUtil.kt @@ -16,15 +16,21 @@ import com.google.android.material.progressindicator.CircularProgressIndicator * @param emptyMsg 로딩 상태가 아닐 때 보여줄 메시지 */ fun PagingDataAdapter<*, *>.setOnStateChangedListener( - msgTextView: TextView, listView: RecyclerView, progressBar: CircularProgressIndicator, emptyMsg: String + msgTextView: TextView, + listView: RecyclerView, + progressBar: CircularProgressIndicator, + emptyMsg: String ) { var isFirstLoad = true addLoadStateListener { loadState -> isFirstLoad = loadState.refresh is LoadState.Loading if (isFirstLoad) listView.scrollToPosition(0) + msgTextView.text = emptyMsg + progressBar.isVisible = isFirstLoad - listView.isVisible = (!isFirstLoad && loadState.source.refresh !is LoadState.Loading) - msgTextView.isVisible = !isFirstLoad && loadState.source.refresh !is LoadState.Loading && itemCount == 0 + listView.isVisible = (!isFirstLoad && (loadState.source.refresh !is LoadState.Loading)) + msgTextView.isVisible = + ((!isFirstLoad && loadState.source.refresh !is LoadState.Loading) && itemCount == 0) } } \ No newline at end of file diff --git a/core/data/src/main/java/com/android/mediproject/core/data/remote/comments/CommentsRepository.kt b/core/data/src/main/java/com/android/mediproject/core/data/remote/comments/CommentsRepository.kt index f4771991d..5669a7c5b 100644 --- a/core/data/src/main/java/com/android/mediproject/core/data/remote/comments/CommentsRepository.kt +++ b/core/data/src/main/java/com/android/mediproject/core/data/remote/comments/CommentsRepository.kt @@ -1,11 +1,40 @@ package com.android.mediproject.core.data.remote.comments import androidx.paging.PagingData +import com.android.mediproject.core.model.comments.EditedCommentDto +import com.android.mediproject.core.model.comments.MyCommentDto +import com.android.mediproject.core.model.comments.NewCommentDto import com.android.mediproject.core.model.remote.comments.MedicineCommentsResponse import kotlinx.coroutines.flow.Flow interface CommentsRepository { - suspend fun getCommentsForAMedicine( + fun getCommentsForAMedicine( itemSeq: String, ): Flow> + + /** + * 내가 작성한 댓글을 가져오는 메서드입니다. + */ + fun getMyComments(userId: Int): Flow> + + /** + * 댓글을 수정합니다. + */ + fun applyEditedComment(editedCommentDto: EditedCommentDto): Flow> + + + /** + * 댓글을 등록합니다. + */ + fun applyNewComment(newCommentDto: NewCommentDto): Flow> + + /** + * 댓글 삭제 클릭 + */ + fun deleteComment(commentId: Int): Flow> + + /** + * 댓글 좋아요 클릭 + */ + fun likeComment(commentId: Int): Flow> } \ No newline at end of file diff --git a/core/data/src/main/java/com/android/mediproject/core/data/remote/comments/CommentsRepositoryImpl.kt b/core/data/src/main/java/com/android/mediproject/core/data/remote/comments/CommentsRepositoryImpl.kt index c597b8d7e..63c102e69 100644 --- a/core/data/src/main/java/com/android/mediproject/core/data/remote/comments/CommentsRepositoryImpl.kt +++ b/core/data/src/main/java/com/android/mediproject/core/data/remote/comments/CommentsRepositoryImpl.kt @@ -4,6 +4,9 @@ import androidx.paging.Pager import androidx.paging.PagingConfig import androidx.paging.PagingData import com.android.mediproject.core.common.AWS_LOAD_PAGE_SIZE +import com.android.mediproject.core.model.comments.EditedCommentDto +import com.android.mediproject.core.model.comments.MyCommentDto +import com.android.mediproject.core.model.comments.NewCommentDto import com.android.mediproject.core.model.remote.comments.MedicineCommentsResponse import com.android.mediproject.core.network.datasource.comments.CommentsDataSource import com.android.mediproject.core.network.datasource.comments.CommentsListDataSourceImpl @@ -13,12 +16,23 @@ import javax.inject.Inject class CommentsRepositoryImpl @Inject constructor( private val commentsDataSource: CommentsDataSource ) : CommentsRepository { - override suspend fun getCommentsForAMedicine(itemSeq: String): Flow> = + override fun getCommentsForAMedicine(itemSeq: String): Flow> = Pager(config = PagingConfig(pageSize = AWS_LOAD_PAGE_SIZE, prefetchDistance = 5), pagingSourceFactory = { - CommentsListDataSourceImpl( - commentsDataSource, itemSeq - ) + CommentsListDataSourceImpl(commentsDataSource, itemSeq) }).flow + override fun getMyComments(userId: Int): Flow> { + TODO("Not yet implemented") + } + + override fun applyEditedComment(editedCommentDto: EditedCommentDto): Flow> = + commentsDataSource.applyEditedComment(editedCommentDto) + + override fun applyNewComment(newCommentDto: NewCommentDto): Flow> = commentsDataSource.applyNewComment(newCommentDto) + + override fun deleteComment(commentId: Int): Flow> = commentsDataSource.deleteComment(commentId) + + override fun likeComment(commentId: Int): Flow> = commentsDataSource.likeComment(commentId) + } \ No newline at end of file diff --git a/core/data/src/main/java/com/android/mediproject/core/data/remote/recallsuspension/RecallSuspensionRepository.kt b/core/data/src/main/java/com/android/mediproject/core/data/remote/recallsuspension/RecallSuspensionRepository.kt index d28e4af65..ab3846d6f 100644 --- a/core/data/src/main/java/com/android/mediproject/core/data/remote/recallsuspension/RecallSuspensionRepository.kt +++ b/core/data/src/main/java/com/android/mediproject/core/data/remote/recallsuspension/RecallSuspensionRepository.kt @@ -6,14 +6,14 @@ import com.android.mediproject.core.model.remote.recall.RecallSuspensionListResp import kotlinx.coroutines.flow.Flow interface RecallSuspensionRepository { - suspend fun getRecallDisposalList( + fun getRecallDisposalList( ): Flow> suspend fun getRecentRecallDisposalList( pageNo: Int = 1, numOfRows: Int = 15 ): Result> - suspend fun getDetailRecallSuspension( + fun getDetailRecallSuspension( company: String?, product: String? ): Flow> } \ No newline at end of file diff --git a/core/data/src/main/java/com/android/mediproject/core/data/remote/recallsuspension/RecallSuspensionRepositoryImpl.kt b/core/data/src/main/java/com/android/mediproject/core/data/remote/recallsuspension/RecallSuspensionRepositoryImpl.kt index 21b0096ce..9ce5abc4a 100644 --- a/core/data/src/main/java/com/android/mediproject/core/data/remote/recallsuspension/RecallSuspensionRepositoryImpl.kt +++ b/core/data/src/main/java/com/android/mediproject/core/data/remote/recallsuspension/RecallSuspensionRepositoryImpl.kt @@ -17,31 +17,33 @@ class RecallSuspensionRepositoryImpl @Inject constructor( private val recallSuspensionListDataSource: RecallSuspensionListDataSourceImpl, ) : RecallSuspensionRepository { - override suspend fun getRecallDisposalList(): Flow> = + override fun getRecallDisposalList(): Flow> = Pager(config = PagingConfig(pageSize = DATA_GO_KR_PAGE_SIZE), pagingSourceFactory = { recallSuspensionListDataSource }).flow override suspend fun getRecentRecallDisposalList( pageNo: Int, numOfRows: Int - ): Result> = recallSuspensionDataSource.getRecallSuspensionList( - pageNo, numOfRows - ).map { - it.body.items.map { item -> - item.item + ): Result> = + recallSuspensionDataSource.getRecallSuspensionList( + pageNo, numOfRows + ).map { + it.body.items.map { item -> + item.item + } } - } - override suspend fun getDetailRecallSuspension( + override fun getDetailRecallSuspension( company: String?, product: String? - ): Flow> = recallSuspensionDataSource.getDetailRecallSuspensionInfo( - company = company, product = product - ).map { result -> - result.fold(onSuccess = { - Result.success(it.body.items.first().item) - }, onFailure = { - Result.failure(it) - }) - } + ): Flow> = + recallSuspensionDataSource.getDetailRecallSuspensionInfo( + company = company, product = product + ).map { result -> + result.fold(onSuccess = { + Result.success(it.body.items.first().item) + }, onFailure = { + Result.failure(it) + }) + } } \ No newline at end of file diff --git a/core/domain/src/main/java/com/android/mediproject/core/domain/CommentsUseCase.kt b/core/domain/src/main/java/com/android/mediproject/core/domain/CommentsUseCase.kt new file mode 100644 index 000000000..cef31cad2 --- /dev/null +++ b/core/domain/src/main/java/com/android/mediproject/core/domain/CommentsUseCase.kt @@ -0,0 +1,56 @@ +package com.android.mediproject.core.domain + +import androidx.paging.PagingData +import androidx.paging.map +import com.android.mediproject.core.data.remote.comments.CommentsRepository +import com.android.mediproject.core.model.comments.CommentDto +import com.android.mediproject.core.model.comments.EditedCommentDto +import com.android.mediproject.core.model.comments.MyCommentDto +import com.android.mediproject.core.model.comments.NewCommentDto +import com.android.mediproject.core.model.comments.toDto +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +import javax.inject.Inject + +class CommentsUseCase @Inject constructor( + private val commentsRepository: CommentsRepository +) { + + /** + * 약에 대한 댓글을 가져오는 메서드입니다. + */ + fun getCommentsForAMedicine(itemSeq: String): Flow> = commentsRepository.getCommentsForAMedicine(itemSeq).map { + it.map { response -> + response.toDto() + } + } + + /** + * 내가 작성한 댓글을 가져오는 메서드입니다. + */ + fun getMyComments(userId: Int): Flow> { + TODO() + } + + + /** + * 댓글을 수정합니다. + */ + fun applyEditedComment(editedCommentDto: EditedCommentDto): Flow> = commentsRepository.applyEditedComment(editedCommentDto) + + + /** + * 댓글을 등록합니다. + */ + fun applyNewComment(newCommentDto: NewCommentDto): Flow> = commentsRepository.applyNewComment(newCommentDto) + + /** + * 댓글 삭제 클릭 + */ + fun deleteComment(commentId: Int): Flow> = commentsRepository.deleteComment(commentId) + + /** + * 댓글 좋아요 클릭 + */ + fun likeComment(commentId: Int): Flow> = commentsRepository.likeComment(commentId) +} \ No newline at end of file diff --git a/core/domain/src/main/java/com/android/mediproject/core/domain/GetCommentsUseCase.kt b/core/domain/src/main/java/com/android/mediproject/core/domain/GetCommentsUseCase.kt deleted file mode 100644 index e2c05f43d..000000000 --- a/core/domain/src/main/java/com/android/mediproject/core/domain/GetCommentsUseCase.kt +++ /dev/null @@ -1,52 +0,0 @@ -package com.android.mediproject.core.domain - -import androidx.paging.PagingData -import androidx.paging.map -import com.android.mediproject.core.data.remote.comments.CommentsRepository -import com.android.mediproject.core.model.comments.CommentDto -import com.android.mediproject.core.model.comments.MyCommentDto -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.map -import kotlinx.datetime.toLocalDateTime -import javax.inject.Inject - -class GetCommentsUseCase @Inject constructor( - private val commentsRepository: CommentsRepository -) { - - - /** - * 약에 대한 댓글을 가져오는 메서드입니다. - */ - suspend fun getCommentsForAMedicine(itemSeq: String): Flow> = commentsRepository.getCommentsForAMedicine(itemSeq) - .let { - it.map { - it.map { response -> - CommentDto( - commentId = response.commentId, - userId = response.userId, - userName = response.userName, - isReply = response.isReply, - subordinationId = response.subordinationId, - content = response.content, - createdAt = response.createdAt.toLocalDateTime(), - updatedAt = response.updatedAt.toLocalDateTime(), - onClickReply = null, - onClickLike = null, - onClickDelete = null, - onClickEdit = null, - onClickApplyEdited = null, - onClickEditCancel = null - ) - } - } - } - - /** - * 내가 작성한 댓글을 가져오는 메서드입니다. - */ - fun getMyComments(userId: Int): Flow> { - TODO() - } - -} \ No newline at end of file diff --git a/core/domain/src/main/java/com/android/mediproject/core/domain/GetMedicineApprovalListUseCase.kt b/core/domain/src/main/java/com/android/mediproject/core/domain/GetMedicineApprovalListUseCase.kt index d80f4e76b..1bbad66bd 100644 --- a/core/domain/src/main/java/com/android/mediproject/core/domain/GetMedicineApprovalListUseCase.kt +++ b/core/domain/src/main/java/com/android/mediproject/core/domain/GetMedicineApprovalListUseCase.kt @@ -1,20 +1,15 @@ package com.android.mediproject.core.domain import androidx.paging.map -import com.android.mediproject.core.common.network.Dispatcher -import com.android.mediproject.core.common.network.MediDispatchers import com.android.mediproject.core.data.remote.medicineapproval.MedicineApprovalRepository import com.android.mediproject.core.model.constants.MedicationType import com.android.mediproject.core.model.medicine.medicineapproval.toDto import com.android.mediproject.core.model.parameters.ApprovalListSearchParameter -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import javax.inject.Inject class GetMedicineApprovalListUseCase @Inject constructor( private val medicineApprovalRepository: MedicineApprovalRepository, - @Dispatcher(MediDispatchers.Default) private val defaultDispatcher: CoroutineDispatcher, ) { /** @@ -23,7 +18,9 @@ class GetMedicineApprovalListUseCase @Inject constructor( operator fun invoke( parameter: ApprovalListSearchParameter ) = medicineApprovalRepository.getMedicineApprovalList( - itemName = parameter.itemName, entpName = parameter.entpName, medicationType = when (parameter.medicationType) { + itemName = parameter.itemName, + entpName = parameter.entpName, + medicationType = when (parameter.medicationType) { MedicationType.ALL -> null else -> parameter.medicationType.description } @@ -32,5 +29,5 @@ class GetMedicineApprovalListUseCase @Inject constructor( // 데이터 변환 item.toDto() } - }.flowOn(defaultDispatcher) + } } \ No newline at end of file diff --git a/core/domain/src/main/java/com/android/mediproject/core/domain/GetRecallSuspensionInfoUseCase.kt b/core/domain/src/main/java/com/android/mediproject/core/domain/GetRecallSuspensionInfoUseCase.kt index 039330b33..df8fa9378 100644 --- a/core/domain/src/main/java/com/android/mediproject/core/domain/GetRecallSuspensionInfoUseCase.kt +++ b/core/domain/src/main/java/com/android/mediproject/core/domain/GetRecallSuspensionInfoUseCase.kt @@ -14,30 +14,33 @@ class GetRecallSuspensionInfoUseCase @Inject constructor( private val recallSuspensionRepository: RecallSuspensionRepository ) { - suspend fun getRecallDisposalList( - ): Flow> = recallSuspensionRepository.getRecallDisposalList().let { pager -> - pager.map { pagingData -> - pagingData.map { - it.toDto() + fun getRecallDisposalList( + ): Flow> = + recallSuspensionRepository.getRecallDisposalList().let { pager -> + pager.map { pagingData -> + pagingData.map { + it.toDto() + } } } - } suspend fun getRecentRecallDisposalList( pageNo: Int = 1, numOfRows: Int = 15 - ): Result> = recallSuspensionRepository.getRecentRecallDisposalList(pageNo, numOfRows).map { - it.map { item -> - item.toDto() + ): Result> = + recallSuspensionRepository.getRecentRecallDisposalList(pageNo, numOfRows).map { + it.map { item -> + item.toDto() + } } - } - suspend fun getDetailRecallSuspension( + fun getDetailRecallSuspension( company: String?, product: String? - ): Flow> = recallSuspensionRepository.getDetailRecallSuspension(company, product).map { - it.fold(onSuccess = { item -> - Result.success(item.toDto()) - }, onFailure = { throwable -> - Result.failure(throwable) - }) - } + ): Flow> = + recallSuspensionRepository.getDetailRecallSuspension(company, product).map { + it.fold(onSuccess = { item -> + Result.success(item.toDto()) + }, onFailure = { throwable -> + Result.failure(throwable) + }) + } } \ No newline at end of file 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 index 2bbd94a10..ecde3650d 100644 --- 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 @@ -11,13 +11,15 @@ import javax.inject.Singleton class SearchHistoryUseCase @Inject constructor( private val searchHistoryRepository: SearchHistoryRepository ) { - suspend fun insertSearchHistory(query: String) = searchHistoryRepository.insertSearchHistory(SearchHistoryDto(query)) + suspend fun insertSearchHistory(query: String) = + searchHistoryRepository.insertSearchHistory(SearchHistoryDto(query)) - fun getSearchHistoryList(count: Int) = searchHistoryRepository.getSearchHistoryList(count).mapLatest { - it.map { dto -> - dto.toSearchHistoryItemDto() + fun getSearchHistoryList(count: Int) = + searchHistoryRepository.getSearchHistoryList(count).mapLatest { + it.map { dto -> + dto.toSearchHistoryItemDto() + } } - } suspend fun deleteSearchHistory(id: Long) = searchHistoryRepository.deleteSearchHistory(id) diff --git a/core/model/src/main/java/com/android/mediproject/core/model/comments/CommentDto.kt b/core/model/src/main/java/com/android/mediproject/core/model/comments/CommentDto.kt index 668baa450..3707fcfa9 100644 --- a/core/model/src/main/java/com/android/mediproject/core/model/comments/CommentDto.kt +++ b/core/model/src/main/java/com/android/mediproject/core/model/comments/CommentDto.kt @@ -1,6 +1,8 @@ package com.android.mediproject.core.model.comments +import com.android.mediproject.core.model.remote.comments.MedicineCommentsResponse import kotlinx.datetime.LocalDateTime +import kotlinx.datetime.toLocalDateTime /** * 댓글 정보를 담는 데이터 클래스입니다. @@ -17,7 +19,6 @@ import kotlinx.datetime.LocalDateTime * @property onClickReply 답글 달기 버튼 클릭 시 실행되는 람다 함수 * @property onClickLike 댓글 좋아요 버튼 클릭 시 실행되는 람다 함수 * @property onClickApplyEdited 답글 수정 버튼 클릭 시 실행되는 람다 함수 - * @property onClickEditCancel 답글 수정 취소 버튼 클릭 시 실행되는 람다 함수 * @property onClickDelete 답글 삭제 버튼 클릭 시 실행되는 람다 함수 * @property onClickEdit 답글 수정 버튼 클릭 시 실행되는 람다 함수 * @property isMine 내가 쓴 댓글인지 여부 @@ -34,9 +35,8 @@ data class CommentDto( var onClickReply: ((Int) -> Unit)?, var onClickLike: ((Int) -> Unit)?, var onClickDelete: ((Int) -> Unit)?, - var onClickEdit: ((Int) -> Unit)?, - var onClickApplyEdited: ((CommentDto, Int) -> Unit)?, - var onClickEditCancel: ((Int) -> Unit)?, + var onClickEdit: ((CommentDto, Int) -> Unit)?, + var onClickApplyEdited: ((CommentDto) -> Unit)?, var isMine: Boolean = false ) { var isEditing: Boolean = false @@ -50,4 +50,20 @@ SUBORDINATION int 댓글 종속성 CONTENT varchar2 댓글 내용 CREATED_AT DATETIME 작성 시각 UPDATED_AT DATETIME 수정 시각 - */ \ No newline at end of file + */ + +fun MedicineCommentsResponse.toDto() = CommentDto( + commentId = commentId, + userId = userId, + userName = userName, + isReply = isReply, + subordinationId = subordinationId, + content = content, + createdAt = createdAt.toLocalDateTime(), + updatedAt = updatedAt.toLocalDateTime(), + onClickReply = null, + onClickLike = null, + onClickDelete = null, + onClickEdit = null, + onClickApplyEdited = null, +) \ No newline at end of file diff --git a/core/model/src/main/java/com/android/mediproject/core/model/medicine/medicineapproval/MedicineApprovalListResponse.kt b/core/model/src/main/java/com/android/mediproject/core/model/medicine/medicineapproval/MedicineApprovalListResponse.kt index 37d002c13..e664424cc 100644 --- a/core/model/src/main/java/com/android/mediproject/core/model/medicine/medicineapproval/MedicineApprovalListResponse.kt +++ b/core/model/src/main/java/com/android/mediproject/core/model/medicine/medicineapproval/MedicineApprovalListResponse.kt @@ -18,7 +18,7 @@ data class MedicineApprovalListResponse( @Serializable data class Body( - @SerialName("items") val items: List, @SerialName("numOfRows") val numOfRows: Int, // 15 + val items: List = emptyList(), @SerialName("numOfRows") val numOfRows: Int, // 15 @SerialName("pageNo") val pageNo: Int, // 1 @SerialName("totalCount") val totalCount: Int // 245 ) diff --git a/core/network/src/main/java/com/android/mediproject/core/network/datasource/comments/CommentsDataSource.kt b/core/network/src/main/java/com/android/mediproject/core/network/datasource/comments/CommentsDataSource.kt index 90f01e602..9c69b9036 100644 --- a/core/network/src/main/java/com/android/mediproject/core/network/datasource/comments/CommentsDataSource.kt +++ b/core/network/src/main/java/com/android/mediproject/core/network/datasource/comments/CommentsDataSource.kt @@ -1,8 +1,22 @@ package com.android.mediproject.core.network.datasource.comments +import androidx.paging.PagingData +import com.android.mediproject.core.model.comments.EditedCommentDto +import com.android.mediproject.core.model.comments.MyCommentDto +import com.android.mediproject.core.model.comments.NewCommentDto import com.android.mediproject.core.model.remote.comments.MedicineCommentsResponse +import kotlinx.coroutines.flow.Flow interface CommentsDataSource { - suspend fun getCommentsForAMedicine(itemSeq: String): Result> + suspend fun getCommentsForAMedicineCatching(itemSeq: String): Result> + + fun getMyComments(userId: Int): Flow> + + fun applyEditedComment(editedCommentDto: EditedCommentDto): Flow> + + fun applyNewComment(newCommentDto: NewCommentDto): Flow> + fun deleteComment(commentId: Int): Flow> + + fun likeComment(commentId: Int): Flow> } \ No newline at end of file diff --git a/core/network/src/main/java/com/android/mediproject/core/network/datasource/comments/CommentsDataSourceImpl.kt b/core/network/src/main/java/com/android/mediproject/core/network/datasource/comments/CommentsDataSourceImpl.kt index ba2e85d48..e3c9c0e44 100644 --- a/core/network/src/main/java/com/android/mediproject/core/network/datasource/comments/CommentsDataSourceImpl.kt +++ b/core/network/src/main/java/com/android/mediproject/core/network/datasource/comments/CommentsDataSourceImpl.kt @@ -1,7 +1,12 @@ package com.android.mediproject.core.network.datasource.comments +import androidx.paging.PagingData +import com.android.mediproject.core.model.comments.EditedCommentDto +import com.android.mediproject.core.model.comments.MyCommentDto +import com.android.mediproject.core.model.comments.NewCommentDto import com.android.mediproject.core.model.remote.comments.MedicineCommentsResponse import com.android.mediproject.core.network.module.AwsNetworkApi +import kotlinx.coroutines.flow.Flow import javax.inject.Inject import kotlin.random.Random @@ -9,10 +14,9 @@ class CommentsDataSourceImpl @Inject constructor( private val awsNetworkApi: AwsNetworkApi ) : CommentsDataSource { - override suspend fun getCommentsForAMedicine(itemSeq: String): Result> { + override suspend fun getCommentsForAMedicineCatching(itemSeq: String): Result> { val userNames = listOf("김철수", "박영희", "이민수", "김영미", "최준호", "정혜진", "유지현", "박지훈", "김민서", "이지아") - val comments = listOf( - "오늘 머리가 너무 아팠어요. 그래서 타이레놀을 먹었는데 금방 나아졌어요.", + val comments = listOf("오늘 머리가 너무 아팠어요. 그래서 타이레놀을 먹었는데 금방 나아졌어요.", "타이레놀을 복용하니까 두통이 확실히 가셨어요.", "아이가 열이 나서 걱정했는데, 타이레놀을 줘보니 열도 잘 내려가고 좋네요.", "타이레놀 복용하면 빠르게 나아지지만, 긴 기간 복용하면 안 좋을 수 있으니 조심하세요.", @@ -26,9 +30,7 @@ class CommentsDataSourceImpl @Inject constructor( "빈속에 약을 먹으면 위에 좋지 않을 수 있어요. 가급적이면 식사 후에 복용하세요.", "타이레놀은 먹고 나서 운전하는 것은 피하는게 좋아요.", "타이레놀 복용 후에는 알코올을 피해야 해요.", - "제가 느낀 바로는, 타이레놀은 가벼운 두통에 특히 효과적인 것 같아요." - ) - + "제가 느낀 바로는, 타이레놀은 가벼운 두통에 특히 효과적인 것 같아요.") val rand = Random(System.currentTimeMillis()) @@ -41,18 +43,36 @@ class CommentsDataSourceImpl @Inject constructor( val isReply = rand.nextInt(0, userNames.size) > userNames.size - 3 - MedicineCommentsResponse( - commentId = index, + MedicineCommentsResponse(commentId = index, userId = userId, userName = userNames[userId], isReply = isReply, subordinationId = if (isReply) rand.nextInt(0, userNames.size) else -1, content = comments[index], createdAt = time, - updatedAt = time - ) + updatedAt = time) } return Result.success(commentList) } + + override fun getMyComments(userId: Int): Flow> { + TODO("Not yet implemented") + } + + override fun applyEditedComment(editedCommentDto: EditedCommentDto): Flow> { + TODO("Not yet implemented") + } + + override fun applyNewComment(newCommentDto: NewCommentDto): Flow> { + TODO("Not yet implemented") + } + + override fun deleteComment(commentId: Int): Flow> { + TODO("Not yet implemented") + } + + override fun likeComment(commentId: Int): Flow> { + TODO("Not yet implemented") + } } \ No newline at end of file diff --git a/core/network/src/main/java/com/android/mediproject/core/network/datasource/comments/CommentsListDataSourceImpl.kt b/core/network/src/main/java/com/android/mediproject/core/network/datasource/comments/CommentsListDataSourceImpl.kt index 1422c1041..68c073f8f 100644 --- a/core/network/src/main/java/com/android/mediproject/core/network/datasource/comments/CommentsListDataSourceImpl.kt +++ b/core/network/src/main/java/com/android/mediproject/core/network/datasource/comments/CommentsListDataSourceImpl.kt @@ -21,11 +21,11 @@ class CommentsListDataSourceImpl( val currentPage = params.key ?: 1 return try { - commentsDataSource.getCommentsForAMedicine( + commentsDataSource.getCommentsForAMedicineCatching( itemSeq ).fold(onSuccess = { val nextKey = it.let { body -> - if (body.count() <= AWS_LOAD_PAGE_SIZE) null + if (body.size <= AWS_LOAD_PAGE_SIZE) null else currentPage + 1 } diff --git a/core/network/src/main/java/com/android/mediproject/core/network/datasource/medicineapproval/MedicineApprovalListDataSourceImpl.kt b/core/network/src/main/java/com/android/mediproject/core/network/datasource/medicineapproval/MedicineApprovalListDataSourceImpl.kt index 50f5a07e6..218a63eb1 100644 --- a/core/network/src/main/java/com/android/mediproject/core/network/datasource/medicineapproval/MedicineApprovalListDataSourceImpl.kt +++ b/core/network/src/main/java/com/android/mediproject/core/network/datasource/medicineapproval/MedicineApprovalListDataSourceImpl.kt @@ -30,7 +30,7 @@ class MedicineApprovalListDataSourceImpl( pageNo = currentPage, ).fold(onSuccess = { val nextKey = it.body.let { body -> - if (body.items.count() < DATA_GO_KR_PAGE_SIZE) null + if (body.items.size < DATA_GO_KR_PAGE_SIZE) null else currentPage + 1 } diff --git a/core/network/src/main/java/com/android/mediproject/core/network/datasource/penalties/recallsuspension/RecallSuspensionDataSource.kt b/core/network/src/main/java/com/android/mediproject/core/network/datasource/penalties/recallsuspension/RecallSuspensionDataSource.kt index 7375c206c..e09ad3ce1 100644 --- a/core/network/src/main/java/com/android/mediproject/core/network/datasource/penalties/recallsuspension/RecallSuspensionDataSource.kt +++ b/core/network/src/main/java/com/android/mediproject/core/network/datasource/penalties/recallsuspension/RecallSuspensionDataSource.kt @@ -19,7 +19,7 @@ interface RecallSuspensionDataSource { * @param company 제조사 * @param product 제품명 */ - suspend fun getDetailRecallSuspensionInfo( + fun getDetailRecallSuspensionInfo( company: String?, product: String?, ): Flow> diff --git a/core/network/src/main/java/com/android/mediproject/core/network/datasource/penalties/recallsuspension/RecallSuspensionDataSourceImpl.kt b/core/network/src/main/java/com/android/mediproject/core/network/datasource/penalties/recallsuspension/RecallSuspensionDataSourceImpl.kt index dbe626ba1..f356a4bb2 100644 --- a/core/network/src/main/java/com/android/mediproject/core/network/datasource/penalties/recallsuspension/RecallSuspensionDataSourceImpl.kt +++ b/core/network/src/main/java/com/android/mediproject/core/network/datasource/penalties/recallsuspension/RecallSuspensionDataSourceImpl.kt @@ -3,39 +3,46 @@ package com.android.mediproject.core.network.datasource.penalties.recallsuspensi import com.android.mediproject.core.common.network.Dispatcher import com.android.mediproject.core.common.network.MediDispatchers import com.android.mediproject.core.model.DataGoKrResult +import com.android.mediproject.core.model.remote.recall.DetailRecallSuspensionResponse import com.android.mediproject.core.network.module.DataGoKrNetworkApi import com.android.mediproject.core.network.onResponse import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOf import javax.inject.Inject class RecallSuspensionDataSourceImpl @Inject constructor( - @Dispatcher(MediDispatchers.IO) private val ioDispatcher: CoroutineDispatcher, private val dataGoKrNetworkApi: DataGoKrNetworkApi + @Dispatcher(MediDispatchers.IO) private val ioDispatcher: CoroutineDispatcher, + private val dataGoKrNetworkApi: DataGoKrNetworkApi ) : RecallSuspensionDataSource { - override suspend fun getDetailRecallSuspensionInfo( + override fun getDetailRecallSuspensionInfo( company: String?, product: String? - ) = dataGoKrNetworkApi.getDetailRecallSuspensionInfo(company = company, product = product).onResponse().fold(onSuccess = { response -> - response.isSuccess().let { - if (it == DataGoKrResult.isSuccess) Result.success(response) - else Result.failure(Throwable(it.failedMessage)) - } - }, onFailure = { - Result.failure(it) - }).let { - flowOf(it) + ): Flow> = flow { + dataGoKrNetworkApi.getDetailRecallSuspensionInfo(company = company, product = product) + .onResponse().fold(onSuccess = { response -> + response.isSuccess().let { + if (it == DataGoKrResult.isSuccess) Result.success(response) + else Result.failure(Throwable(it.failedMessage)) + } + }, onFailure = { + Result.failure(it) + }).also { + flowOf(it) + } } - override suspend fun getRecallSuspensionList(pageNo: Int, numOfRows: Int) = - dataGoKrNetworkApi.getRecallSuspensionList(pageNo = pageNo, numOfRows = numOfRows).onResponse().fold(onSuccess = { response -> - response.isSuccess().let { - if (it == DataGoKrResult.isSuccess) Result.success(response) - else Result.failure(Throwable(it.failedMessage)) - } - }, onFailure = { - Result.failure(it) - }) + dataGoKrNetworkApi.getRecallSuspensionList(pageNo = pageNo, numOfRows = numOfRows) + .onResponse().fold(onSuccess = { response -> + response.isSuccess().let { + if (it == DataGoKrResult.isSuccess) Result.success(response) + else Result.failure(Throwable(it.failedMessage)) + } + }, onFailure = { + Result.failure(it) + }) } \ No newline at end of file diff --git a/core/ui/src/main/java/com/android/mediproject/core/ui/WindowViewModel.kt b/core/ui/src/main/java/com/android/mediproject/core/ui/WindowViewModel.kt new file mode 100644 index 000000000..ad465d6c8 --- /dev/null +++ b/core/ui/src/main/java/com/android/mediproject/core/ui/WindowViewModel.kt @@ -0,0 +1,22 @@ +package com.android.mediproject.core.ui + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class WindowViewModel @Inject constructor() : ViewModel() { + + private val _bottomNavHeight = MutableStateFlow(-1) + val bottomNavHeight get() = _bottomNavHeight.asStateFlow() + + fun setBottomNavHeight(height: Int) { + viewModelScope.launch { + _bottomNavHeight.value = height + } + } +} \ No newline at end of file diff --git a/core/ui/src/main/java/com/android/mediproject/core/ui/base/view/SimpleListItemView.kt b/core/ui/src/main/java/com/android/mediproject/core/ui/base/view/SimpleListItemView.kt index a6b752db7..d35000d8f 100644 --- a/core/ui/src/main/java/com/android/mediproject/core/ui/base/view/SimpleListItemView.kt +++ b/core/ui/src/main/java/com/android/mediproject/core/ui/base/view/SimpleListItemView.kt @@ -26,7 +26,9 @@ class SimpleListItemView : ConstraintLayout { init(context, attrs) } - constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs) { + constructor( + context: Context, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int + ) : super(context, attrs) { init(context, attrs) } @@ -41,13 +43,15 @@ class SimpleListItemView : ConstraintLayout { } get() = _data - @SuppressLint("ResourceType") private val chip = ButtonChip(context).apply { + @SuppressLint("ResourceType") + private val chip = ButtonChip(context).apply { textSize = 13f isClickable = false id = 10 } - @SuppressLint("ResourceType") private val textView = TextView(context).apply { + @SuppressLint("ResourceType") + private val textView = TextView(context).apply { ellipsize = TextUtils.TruncateAt.END isClickable = false maxLines = 1 @@ -63,26 +67,35 @@ class SimpleListItemView : ConstraintLayout { try { // 투명 배경에 ripple 효과를 주기 위함 val outValue = TypedValue() - context.theme.resolveAttribute(android.R.attr.selectableItemBackground, outValue, true) + context.theme.resolveAttribute( + android.R.attr.selectableItemBackground, outValue, true + ) setBackgroundResource(outValue.resourceId) // 수평 마진 - TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 3f, resources.displayMetrics).toInt() - .apply { setPadding(0, this, 0, this) } + TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 3f, resources.displayMetrics) + .toInt().apply { setPadding(0, this, 0, this) } - addView(chip, LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT).apply { + addView(chip, LayoutParams(0, LayoutParams.WRAP_CONTENT).apply { topToTop = LayoutParams.PARENT_ID bottomToBottom = LayoutParams.PARENT_ID leftToLeft = LayoutParams.PARENT_ID + rightToLeft = textView.id + horizontalBias = midRatio + horizontalChainStyle = LayoutParams.CHAIN_PACKED }) addView(textView, LayoutParams(0, LayoutParams.WRAP_CONTENT).apply { topToTop = LayoutParams.PARENT_ID bottomToBottom = LayoutParams.PARENT_ID leftToRight = chip.id rightToRight = LayoutParams.PARENT_ID - leftMargin = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10f, resources.displayMetrics).toInt() + leftMargin = TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, 10f, resources.displayMetrics + ).toInt() }) isClickable = true + clipToPadding = false + clipChildren = false layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT) } finally { @@ -90,8 +103,6 @@ class SimpleListItemView : ConstraintLayout { } } - - } fun setTitle(text: String): SimpleListItemView { diff --git a/core/ui/src/main/res/layout/viewgroup_paging_list.xml b/core/ui/src/main/res/layout/viewgroup_paging_list.xml index ade576dcd..efbf60d2d 100644 --- a/core/ui/src/main/res/layout/viewgroup_paging_list.xml +++ b/core/ui/src/main/res/layout/viewgroup_paging_list.xml @@ -3,40 +3,33 @@ xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" - android:clipChildren="false" - > + android:clipChildren="false"> + app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" /> + app:layout_constraintVertical_chainStyle="packed" /> + app:layout_constraintTop_toBottomOf="@id/progressIndicator" /> \ No newline at end of file diff --git a/feature/comments/src/main/AndroidManifest.xml b/feature/comments/src/main/AndroidManifest.xml index 145151899..b39209408 100644 --- a/feature/comments/src/main/AndroidManifest.xml +++ b/feature/comments/src/main/AndroidManifest.xml @@ -1,6 +1,5 @@ - + diff --git a/feature/comments/src/main/java/com/android/mediproject/feature/comments/commentsofamedicine/CommentsAdapter.kt b/feature/comments/src/main/java/com/android/mediproject/feature/comments/commentsofamedicine/CommentsAdapter.kt index a897bcadd..8d8b4e130 100644 --- a/feature/comments/src/main/java/com/android/mediproject/feature/comments/commentsofamedicine/CommentsAdapter.kt +++ b/feature/comments/src/main/java/com/android/mediproject/feature/comments/commentsofamedicine/CommentsAdapter.kt @@ -13,15 +13,11 @@ import com.android.mediproject.feature.comments.databinding.ItemViewCommentEditB import com.android.mediproject.feature.comments.view.CommentItemView -class CommentsAdapter : PagingDataAdapter(Diff) { +class CommentsAdapter : PagingDataAdapter(Diff) { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = viewType.let { when (it) { CommentType.COMMENT.ordinal -> CommentsViewHolder(CommentItemView(parent.context)) - CommentType.EDITING.ordinal -> CommentEditViewHolder( - ItemViewCommentEditBinding.inflate( - LayoutInflater.from(parent.context), parent, false - ) - ) + CommentType.EDITING.ordinal -> CommentEditViewHolder(ItemViewCommentEditBinding.inflate(LayoutInflater.from(parent.context), parent, false)) else -> throw IllegalArgumentException("Invalid view type") } @@ -34,115 +30,124 @@ class CommentsAdapter : PagingDataAdapter(Dif } /** - * 현재 댓글 수정 상태이면 1, 아니면 0 반환 + * 현재 댓글이 수정 중인 상태이면 1, 아니면 0 반환 */ - override fun getItemViewType(position: Int): Int { - return getItem(position)?.let { - if (it.isEditing) CommentType.EDITING.ordinal else CommentType.COMMENT.ordinal - } ?: CommentType.COMMENT.ordinal - } -} + override fun getItemViewType(position: Int): Int = getItem(position)?.run { + if (isEditing) CommentType.EDITING.ordinal else CommentType.COMMENT.ordinal + } ?: CommentType.COMMENT.ordinal -/** - * 댓글 아이템 뷰 홀더 - * - * type : 0 - */ -class CommentsViewHolder(private val view: CommentItemView) : BaseCommentViewHolder(view.rootView) { - private var item: CommentDto? = null + /** + * 댓글 아이템 뷰 홀더 + * + * type : 0 + * 댓글 아이템 뷰 + */ + class CommentsViewHolder(private val view: CommentItemView) : BaseCommentViewHolder(view.rootView) { + private var item: CommentDto? = null - init { - view.setOnReplyClickListener { - item?.also { safeItem -> - safeItem.onClickReply?.invoke(safeItem.commentId) + init { + view.setOnReplyClickListener { + item?.apply { + onClickReply?.invoke(commentId) + } } - } - view.setOnLikeClickListener { - item?.also { safeItem -> - safeItem.onClickLike?.invoke(safeItem.commentId) + view.setOnLikeClickListener { + item?.apply { + onClickLike?.invoke(commentId) + } } - } - view.setOnMoreClickListener { - item?.takeIf { it.isMine }?.also { safeItem -> - MediPopupMenu.showMenu(it, R.menu.comment_action_menu) { menuItem -> - when (menuItem.itemId) { - R.id.deleteMyComment -> safeItem.onClickDelete?.invoke(absoluteAdapterPosition) - R.id.editMyComment -> { - safeItem.isEditing = true - safeItem.onClickEdit?.invoke(absoluteAdapterPosition) + view.setOnMoreClickListener { + item?.apply { + if (isMine) { + // 내 댓글이면 삭제, 수정 메뉴를 보여준다. + MediPopupMenu.showMenu(it, R.menu.comment_action_menu) { menuItem -> + when (menuItem.itemId) { + R.id.deleteMyComment -> onClickDelete?.invoke(absoluteAdapterPosition) + R.id.editMyComment -> { + onClickEdit?.invoke(this, absoluteAdapterPosition) + } + } + true } } - true } } + + } - } + override fun bind(commentDto: CommentDto) { + item = commentDto + view.setComment(commentDto) - override fun bind(commentDto: CommentDto) { - item = commentDto - view.setComment(commentDto) - } + if (commentDto.isMine) view.moreButtonVisible(true) + } - override fun fixBind() { - item?.also { - view.setComment(it) + override fun fixBind() { } } -} - - -/** - * 댓글 수정 뷰홀더 - * - * type : 1 - */ -class CommentEditViewHolder(private val binding: ItemViewCommentEditBinding) : BaseCommentViewHolder(binding.root) { - init { - binding.apply { - commentEditButton.setOnClickListener { - takeIf { !binding.commentInput.text.isNullOrBlank() && commentDto != null }?.apply { - commentDto?.apply { - onClickApplyEdited?.invoke(copy(content = binding.commentInput.text.toString()), absoluteAdapterPosition) + + + /** + * 댓글 수정 뷰홀더 + * + * type : 1 + * 나의 댓글을 수정 중일때 보여주는 아이템 뷰 + */ + class CommentEditViewHolder(private val binding: ItemViewCommentEditBinding) : BaseCommentViewHolder(binding.root) { + init { + binding.apply { + commentEditButton.setOnClickListener { + takeIf { !binding.commentInput.text.isNullOrBlank() && commentDto != null }?.apply { + commentDto?.apply { + onClickApplyEdited?.invoke(copy(content = binding.commentInput.text.toString())) + } } } - } - cancelButton.setOnClickListener { - commentDto?.apply { - isEditing = false - onClickEditCancel?.invoke(absoluteAdapterPosition) + cancelButton.setOnClickListener { + commentDto?.apply { + onClickEdit?.invoke(this, absoluteAdapterPosition) + } } } - } - } + } - override fun bind(commentDto: CommentDto) { - binding.commentDto = commentDto - binding.executePendingBindings() - } + override fun bind(commentDto: CommentDto) { + binding.commentDto = commentDto + binding.executePendingBindings() + } - override fun fixBind() { + override fun fixBind() { + } } -} -abstract class BaseCommentViewHolder(view: View) : RecyclerView.ViewHolder(view) { - open fun bind(commentDto: CommentDto) {} + abstract class BaseCommentViewHolder(view: View) : RecyclerView.ViewHolder(view) { + open fun bind(commentDto: CommentDto) {} - open fun fixBind() {} -} + /** + * 뷰홀더가 재활용 될 때 호출하는 메소드 + */ + open fun fixBind() {} + } -object Diff : DiffUtil.ItemCallback() { - override fun areItemsTheSame(oldItem: CommentDto, newItem: CommentDto): Boolean = oldItem.commentId == newItem.commentId + object Diff : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: CommentDto, newItem: CommentDto): Boolean = oldItem.commentId == newItem.commentId - override fun areContentsTheSame(oldItem: CommentDto, newItem: CommentDto): Boolean = oldItem == newItem + override fun areContentsTheSame(oldItem: CommentDto, newItem: CommentDto): Boolean = oldItem == newItem -} + } -enum class CommentType { - COMMENT, EDITING + /** + * 아이템 뷰 타입 + * 0 : 댓글 + * 1 : 내 댓글 수정 중 + */ + enum class CommentType { + COMMENT, EDITING + } } \ No newline at end of file diff --git a/feature/comments/src/main/java/com/android/mediproject/feature/comments/commentsofamedicine/MedicineCommentsFragment.kt b/feature/comments/src/main/java/com/android/mediproject/feature/comments/commentsofamedicine/MedicineCommentsFragment.kt index 0dc4b616e..193ada1e4 100644 --- a/feature/comments/src/main/java/com/android/mediproject/feature/comments/commentsofamedicine/MedicineCommentsFragment.kt +++ b/feature/comments/src/main/java/com/android/mediproject/feature/comments/commentsofamedicine/MedicineCommentsFragment.kt @@ -3,22 +3,24 @@ package com.android.mediproject.feature.comments.commentsofamedicine import android.os.Bundle import android.view.View import androidx.fragment.app.viewModels +import androidx.recyclerview.widget.RecyclerView import com.android.mediproject.core.common.paging.setOnStateChangedListener import com.android.mediproject.core.ui.base.BaseFragment -import com.android.mediproject.feature.comments.CommentActionType import com.android.mediproject.feature.comments.R import com.android.mediproject.feature.comments.databinding.FragmentMedicineCommentsBinding import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch import repeatOnStarted @AndroidEntryPoint class MedicineCommentsFragment : - BaseFragment(FragmentMedicineCommentsBinding::inflate) { + BaseFragment( + FragmentMedicineCommentsBinding::inflate + ) { override val fragmentViewModel: MedicineCommentsViewModel by viewModels() - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -33,7 +35,18 @@ class MedicineCommentsFragment : getString(R.string.emptyComments) ) } + pagingListView.pagingList.apply { + /* RecyclerView 최적화 + 뷰 홀더 캐시를 사용하지 않도록 설정 + 캐시에 있는 뷰 홀더를 사용하면 onBindViewHolder()를 호출하지 않기 때문에 답글이나 수정 중인 댓글 아이템의 상태와 + 뷰 홀더의 상태가 일치하지 않는 문제가 발생함 + */ + setItemViewCacheSize(0) + setRecycledViewPool(RecyclerView.RecycledViewPool().apply { + setMaxRecycledViews(CommentsAdapter.CommentType.COMMENT.ordinal, 6) + setMaxRecycledViews(CommentsAdapter.CommentType.EDITING.ordinal, 1) + }) this.adapter = adapter } @@ -41,57 +54,55 @@ class MedicineCommentsFragment : launch { fragmentViewModel.action.collect { action -> - when (action.commentActionType) { - CommentActionType.EDIT -> { - // 댓글 수정 + when (action) { + is CommentActionState.CLICKED_LIKE -> { + } + + is CommentActionState.CLICKED_REPLY -> { adapter.notifyItemChanged(action.position) } - CommentActionType.DELETE -> { - // 댓글 삭제 - adapter.notifyItemRemoved(action.position) + is CommentActionState.CLICKED_EDIT_COMMENT -> { + adapter.notifyItemChanged(action.position) } - CommentActionType.REPLY -> { - // 댓글 답글 + is CommentActionState.CLICKED_DELETE_MY_COMMENT -> { + } - CommentActionType.LIKE -> { - // 댓글 좋아요 + is CommentActionState.COMPLETED_LIKE -> { + adapter.refresh() } - CommentActionType.CANCEL_EDIT -> { - // 댓글 수정 취소 - adapter.notifyItemChanged(action.position) + is CommentActionState.COMPLETED_APPLY_COMMENT_REPLY -> { + adapter.refresh() } - CommentActionType.APPLYED_EDITED_COMMENT -> { - // 댓글 수정 적용 - toast(getString(R.string.successAddComment)) + is CommentActionState.COMPLETED_APPLY_EDITED_COMMENT -> { + adapter.refresh() } - CommentActionType.APPLYED_NEW_COMMENT -> { - // 댓글 추가 - toast(getString(R.string.successAddComment)) + is CommentActionState.COMPLETED_DELETE_COMMENT -> { + adapter.refresh() + } + + is CommentActionState.ERROR -> { + toast(action.errorMessage) + } + + is CommentActionState.NONE -> { } } } } - launch { - fragmentViewModel.comments.collect { + fragmentViewModel.comments.collectLatest { adapter.submitData(it) } } } - sendBtn.setOnClickListener { - commentInput.takeIf { !it.text.isNullOrBlank() }?.also { safeCommentInput -> - fragmentViewModel.applyNewComment(safeCommentInput.text.toString()) - safeCommentInput.setText("") - } ?: toast(getString(R.string.commentInputHint)) - } } } diff --git a/feature/comments/src/main/java/com/android/mediproject/feature/comments/commentsofamedicine/MedicineCommentsViewModel.kt b/feature/comments/src/main/java/com/android/mediproject/feature/comments/commentsofamedicine/MedicineCommentsViewModel.kt index 9096f66fc..82cca4ba4 100644 --- a/feature/comments/src/main/java/com/android/mediproject/feature/comments/commentsofamedicine/MedicineCommentsViewModel.kt +++ b/feature/comments/src/main/java/com/android/mediproject/feature/comments/commentsofamedicine/MedicineCommentsViewModel.kt @@ -5,129 +5,256 @@ import androidx.lifecycle.viewModelScope import androidx.paging.PagingData import androidx.paging.cachedIn import androidx.paging.map +import com.android.mediproject.core.common.bindingadapter.ISendText import com.android.mediproject.core.common.network.Dispatcher import com.android.mediproject.core.common.network.MediDispatchers -import com.android.mediproject.core.domain.GetCommentsUseCase +import com.android.mediproject.core.domain.CommentsUseCase import com.android.mediproject.core.model.comments.CommentDto import com.android.mediproject.core.model.comments.EditedCommentDto import com.android.mediproject.core.model.comments.NewCommentDto import com.android.mediproject.core.ui.base.BaseViewModel -import com.android.mediproject.feature.comments.CommentAction -import com.android.mediproject.feature.comments.CommentActionType +import com.android.mediproject.feature.comments.commentsofamedicine.CommentActionState.CLICKED_DELETE_MY_COMMENT +import com.android.mediproject.feature.comments.commentsofamedicine.CommentActionState.CLICKED_EDIT_COMMENT +import com.android.mediproject.feature.comments.commentsofamedicine.CommentActionState.CLICKED_LIKE +import com.android.mediproject.feature.comments.commentsofamedicine.CommentActionState.CLICKED_REPLY +import com.android.mediproject.feature.comments.commentsofamedicine.CommentActionState.COMPLETED_APPLY_COMMENT_REPLY +import com.android.mediproject.feature.comments.commentsofamedicine.CommentActionState.COMPLETED_APPLY_EDITED_COMMENT +import com.android.mediproject.feature.comments.commentsofamedicine.CommentActionState.COMPLETED_DELETE_COMMENT +import com.android.mediproject.feature.comments.commentsofamedicine.CommentActionState.COMPLETED_LIKE +import com.android.mediproject.feature.comments.commentsofamedicine.CommentActionState.ERROR +import com.android.mediproject.feature.comments.commentsofamedicine.CommentActionState.NONE import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.channels.BufferOverflow -import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOn -import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import javax.inject.Inject @HiltViewModel class MedicineCommentsViewModel @Inject constructor( - private val getCommentsUseCase: GetCommentsUseCase, + private val commentsUseCase: CommentsUseCase, private val savedStateHandle: SavedStateHandle, - @Dispatcher(MediDispatchers.IO) private val ioDispatcher: CoroutineDispatcher -) : BaseViewModel() { + @Dispatcher(MediDispatchers.IO) private val ioDispatcher: CoroutineDispatcher, +) : BaseViewModel(), ISendText { - private val myUserId = savedStateHandle.getStateFlow("myUserId", -1) + private val myUserId = savedStateHandle.getStateFlow("myUserId", 3) - private val _action = MutableSharedFlow( - replay = 0, onBufferOverflow = BufferOverflow.DROP_OLDEST, - extraBufferCapacity = 1 + private val _action = MutableSharedFlow( + replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST, extraBufferCapacity = 1 ) private val medicindItemSeq = savedStateHandle.getStateFlow("medicineItemSeq", "") val action get() = _action.asSharedFlow() - val comments: Flow> = medicindItemSeq.flatMapLatest { - getCommentsUseCase.getCommentsForAMedicine(it).map { + val comments: StateFlow> = medicindItemSeq.flatMapLatest { itemSeq -> + commentsUseCase.getCommentsForAMedicine(itemSeq).flatMapLatest { val id = myUserId.value it.map { commentDto -> commentDto.apply { - onClickReply = ::onClickReply - onClickLike = ::onClickLike + onClickReply = ::onClickedReply + onClickLike = ::onClickedLike - // 내가 쓴 댓글이면 수정, 삭제 가능 + // 내가 쓴 댓글이면 수정, 삭제 가능하도록 메서드 참조 설정 if (commentDto.userId == id) { - onClickEditCancel = ::onClickEditCancel - onClickEdit = ::onClickEdit - onClickDelete = ::onClickDelete + onClickEdit = ::onClickedEdit + onClickDelete = ::onClickedDelete onClickApplyEdited = ::applyEditedComment isMine = true } } } - }.flowOn(ioDispatcher).cachedIn(viewModelScope) - } + flowOf(it) + } + }.flowOn(ioDispatcher).cachedIn(viewModelScope) + .stateIn(viewModelScope, SharingStarted.Lazily, PagingData.empty()) + /** - * 새로운 댓글(답글) 등록 + * 답글 등록 + * + * @param comment 답글 내용 + * @param subordinationId 부모 댓글의 id */ - fun applyNewComment(comment: String) { + private fun applyReplyComment(comment: String, subordinationId: Int) { viewModelScope.launch { - NewCommentDto(medicineId = "placerat", userId = myUserId.value, content = "sds", subordinationId = -1).apply { - + commentsUseCase.applyNewComment( + NewCommentDto( + medicineId = medicindItemSeq.value, + userId = myUserId.value, + content = comment, + subordinationId = subordinationId + ) + ).collectLatest { result -> + result.onSuccess { + // 댓글 등록 성공 + _action.emit(COMPLETED_APPLY_COMMENT_REPLY) + }.onFailure { + _action.emit(ERROR(it.message ?: "Failed")) + } } } } /** * 수정한 댓글(답글) 등록 + * + * @param commentDto 수정한 댓글(답글) 정보 */ - private fun applyEditedComment(commentDto: CommentDto, position: Int) { + private fun applyEditedComment(commentDto: CommentDto) { viewModelScope.launch { - EditedCommentDto( - commentId = commentDto.commentId, content = commentDto.content - ).apply { - + commentsUseCase.applyEditedComment( + EditedCommentDto( + commentId = commentDto.commentId, content = commentDto.content + ) + ).collectLatest { result -> + result.onSuccess { + // 댓글 수정 성공 + _action.emit(COMPLETED_APPLY_EDITED_COMMENT) + }.onFailure { + _action.emit(ERROR(it.message ?: "Failed")) + } } } } /** - * 답글 작성 클릭 + * 답글 작성하기 버튼 클릭 + * - 답글 등록하기 버튼 클릭이 아님 + * + * @param position 답글 작성 아이템 뷰가 표시될 리스트내 절대 위치 */ - private fun onClickReply(commentId: Int) { - + private fun onClickedReply(position: Int) { + viewModelScope.launch { + _action.tryEmit(CLICKED_REPLY(position)) + } } + /** * 댓글 삭제 클릭 + * + * @param commentId 삭제할 댓글의 id */ - private fun onClickDelete(commentId: Int) { + private fun onClickedDelete(commentId: Int) { viewModelScope.launch { - _action.emit(CommentAction(commentId, CommentActionType.DELETE)) + _action.tryEmit(CLICKED_DELETE_MY_COMMENT(commentId)) } } /** * 댓글 좋아요 클릭 + * - 좋아요 등록 또는 해제를 처리함 + * + * @param commentId LIKE를 등록또는 해제 할 댓글의 id */ - private fun onClickLike(commentId: Int) { - + private fun onClickedLike(commentId: Int) { + viewModelScope.launch { + commentsUseCase.likeComment(commentId).collectLatest { result -> + result.onSuccess { + // like 처리 완료 + _action.emit(COMPLETED_LIKE) + }.onFailure { + _action.emit(ERROR(it.message ?: "Failed")) + } + } + } } /** * 댓글 수정하기 클릭 + * - 수정할 내용을 입력 한 후 서버에 업데이트하는 로직이 아님 + * + * @param item 수정할 댓글의 정보 + * @param position 수정할 댓글의 리스트 내 절대 위치 */ - private fun onClickEdit(position: Int) { + private fun onClickedEdit(item: CommentDto, position: Int) { viewModelScope.launch { - _action.emit(CommentAction(position, CommentActionType.EDIT)) + // 수정 상태 변경 + item.isEditing = !item.isEditing + _action.tryEmit(CLICKED_EDIT_COMMENT(position)) } } + /** - * 댓글 수정 취소 클릭 + * 댓글 등록하기 버튼 클릭 + * + * @param text 댓글 내용 + * */ - private fun onClickEditCancel(position: Int) { + override fun onClickedSendButton(text: CharSequence) { viewModelScope.launch { - _action.emit(CommentAction(position, CommentActionType.CANCEL_EDIT)) + if (text.isEmpty()) _action.tryEmit(ERROR("댓글 내용을 입력해주세요.")) + else commentsUseCase.applyNewComment( + NewCommentDto( + medicineId = medicindItemSeq.value, + userId = myUserId.value, + content = text.toString(), + subordinationId = -1 + ) + ).collectLatest { result -> + result.onSuccess { + // 댓글 등록 성공 + _action.emit(COMPLETED_APPLY_COMMENT_REPLY) + }.onFailure { + _action.emit(ERROR(it.message ?: "Failed")) + } + } } } +} + +/** + * 댓글 액션 상태 + * + * @property CLICKED_DELETE_MY_COMMENT 내가 쓴 댓글 삭제 클릭 + * @property CLICKED_REPLY 답글 입력하기 클릭 + * @property CLICKED_LIKE 댓글 좋아요 클릭 + * @property CLICKED_EDIT_COMMENT 댓글 수정 클릭 + * @property COMPLETED_APPLY_COMMENT_REPLY 댓글/답글 등록 완료 + * @property COMPLETED_APPLY_EDITED_COMMENT 댓글 수정 완료 + * @property COMPLETED_LIKE 댓글 좋아요 완료 + * @property COMPLETED_DELETE_COMMENT 댓글 삭제 완료 + * @property ERROR 댓글 등록, 수정, 삭제, 좋아요 에러 + * @property NONE 초기 상태 + */ +@Suppress("ClassName") +sealed class CommentActionState { + + /** + * @property commentId 삭제할 댓글의 id + */ + data class CLICKED_DELETE_MY_COMMENT(val commentId: Int) : CommentActionState() + + /** + * @property position 답글 입력하기 클릭한 댓글의 리스트 내 절대 위치 + */ + data class CLICKED_REPLY(val position: Int) : CommentActionState() + object CLICKED_LIKE : CommentActionState() + + /** + * @property position 수정할 댓글의 리스트 내 절대 위치 + */ + data class CLICKED_EDIT_COMMENT(val position: Int) : CommentActionState() + + object COMPLETED_APPLY_COMMENT_REPLY : CommentActionState() + object COMPLETED_APPLY_EDITED_COMMENT : CommentActionState() + object COMPLETED_LIKE : CommentActionState() + object COMPLETED_DELETE_COMMENT : CommentActionState() + + /** + * @property errorMessage 댓글 등록, 수정, 삭제, 좋아요 에러 메시지 + */ + data class ERROR(val errorMessage: String) : CommentActionState() + object NONE : CommentActionState() } \ No newline at end of file diff --git a/feature/comments/src/main/java/com/android/mediproject/feature/comments/recentcommentlist/RecentCommentsAdapter.kt b/feature/comments/src/main/java/com/android/mediproject/feature/comments/recentcommentlist/RecentCommentsAdapter.kt index 273958cb7..b6541e222 100644 --- a/feature/comments/src/main/java/com/android/mediproject/feature/comments/recentcommentlist/RecentCommentsAdapter.kt +++ b/feature/comments/src/main/java/com/android/mediproject/feature/comments/recentcommentlist/RecentCommentsAdapter.kt @@ -7,10 +7,12 @@ import androidx.recyclerview.widget.RecyclerView import com.android.mediproject.core.model.comments.CommentDto import com.android.mediproject.core.ui.base.view.SimpleListItemView -class RecentCommentsAdapter : ListAdapter(AsyncDiffer) { +class RecentCommentsAdapter : + ListAdapter(AsyncDiffer) { - class RecentCommentListViewHolder(private val view: SimpleListItemView) : RecyclerView.ViewHolder(view) { + class RecentCommentListViewHolder(private val view: SimpleListItemView) : + RecyclerView.ViewHolder(view) { private var item: CommentDto? = null diff --git a/feature/comments/src/main/java/com/android/mediproject/feature/comments/values.kt b/feature/comments/src/main/java/com/android/mediproject/feature/comments/values.kt deleted file mode 100644 index 06cb09dad..000000000 --- a/feature/comments/src/main/java/com/android/mediproject/feature/comments/values.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.android.mediproject.feature.comments - -enum class CommentActionType { - EDIT, DELETE, REPLY, LIKE, CANCEL_EDIT, APPLYED_EDITED_COMMENT, APPLYED_NEW_COMMENT; -} - -data class CommentAction(val position: Int, val commentActionType: CommentActionType) \ No newline at end of file diff --git a/feature/comments/src/main/java/com/android/mediproject/feature/comments/view/CommentItemView.kt b/feature/comments/src/main/java/com/android/mediproject/feature/comments/view/CommentItemView.kt index 7959c0142..eb2069d5c 100644 --- a/feature/comments/src/main/java/com/android/mediproject/feature/comments/view/CommentItemView.kt +++ b/feature/comments/src/main/java/com/android/mediproject/feature/comments/view/CommentItemView.kt @@ -9,6 +9,7 @@ import android.widget.ImageView import android.widget.TextView import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.content.ContextCompat +import androidx.core.view.isVisible import com.android.mediproject.core.common.uiutil.dpToPx import com.android.mediproject.core.model.comments.CommentDto import com.android.mediproject.feature.comments.R @@ -16,7 +17,7 @@ import kotlinx.datetime.toJavaLocalDateTime import java.time.format.DateTimeFormatter class CommentItemView( - context: Context + context: Context, ) : ConstraintLayout(context) { companion object { @@ -110,12 +111,10 @@ class CommentItemView( commentTextView = TextView(context).apply { id = R.id.commentTextView - layoutParams = - ConstraintLayout.LayoutParams(ConstraintLayout.LayoutParams.MATCH_PARENT, ConstraintLayout.LayoutParams.WRAP_CONTENT) - .apply { - topToBottom = userProfileImageView.id - topMargin = 6.dpToPx(context) - } + layoutParams = ConstraintLayout.LayoutParams(ConstraintLayout.LayoutParams.MATCH_PARENT, ConstraintLayout.LayoutParams.WRAP_CONTENT).apply { + topToBottom = userProfileImageView.id + topMargin = 6.dpToPx(context) + } setPadding(0, 9.dpToPx(context), 0, 0) text = "comment" setTextColor(Color.BLACK) @@ -124,12 +123,10 @@ class CommentItemView( dateTimeTextView = TextView(context).apply { id = R.id.dateTimeTextView - layoutParams = - ConstraintLayout.LayoutParams(ConstraintLayout.LayoutParams.MATCH_PARENT, ConstraintLayout.LayoutParams.WRAP_CONTENT) - .apply { - topToBottom = commentTextView.id - topMargin = 4.dpToPx(context) - } + layoutParams = ConstraintLayout.LayoutParams(ConstraintLayout.LayoutParams.MATCH_PARENT, ConstraintLayout.LayoutParams.WRAP_CONTENT).apply { + topToBottom = commentTextView.id + topMargin = 4.dpToPx(context) + } setPadding(0, 4.dpToPx(context), 0, 0) text = "dateTime" setTextColor(Color.GRAY) @@ -162,10 +159,8 @@ class CommentItemView( * 뷰 배경을 댓글 종속성에 따라 지정합니다. */ private fun setCommentBackground(isReply: Boolean) { - setBackgroundResource( - if (isReply) R.drawable.reply_background - else R.drawable.comment_background - ) + setBackgroundResource(if (isReply) R.drawable.reply_background + else R.drawable.comment_background) } @@ -202,14 +197,7 @@ class CommentItemView( onLikeClickListener.onClick(it) } } - - /** - * 내 댓글 삭제 버튼 클릭 콜백 추가 - */ - fun setOnDeleteClickListener(onDeleteClickListener: OnClickListener) { - TODO() - } - + /** * 더 보기 버튼 */ @@ -219,4 +207,8 @@ class CommentItemView( } } + fun moreButtonVisible(visible: Boolean) { + moreButton.isVisible = visible + } + } \ No newline at end of file diff --git a/feature/comments/src/main/res/layout/fragment_medicine_comments.xml b/feature/comments/src/main/res/layout/fragment_medicine_comments.xml index 282415733..1d1dbee32 100644 --- a/feature/comments/src/main/res/layout/fragment_medicine_comments.xml +++ b/feature/comments/src/main/res/layout/fragment_medicine_comments.xml @@ -5,16 +5,14 @@ + type="com.android.mediproject.feature.comments.commentsofamedicine.MedicineCommentsViewModel" /> + android:paddingHorizontal="24dp"> + app:layout_constraintTop_toTopOf="parent" /> + app:layout_constraintBottom_toBottomOf="parent"> + app:layout_constraintTop_toTopOf="parent"> + android:textSize="14sp" /> @@ -73,11 +67,12 @@ android:contentDescription="@string/sendButtonContentDescription" android:padding="4dp" android:src="@drawable/outline_send_24" + app:inputText="@{commentInput}" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" - app:tint="@color/sendButtonBackground" - /> + app:onClickSend="@{viewModel}" + app:tint="@color/sendButtonBackground" /> 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 0248e408a..a0c824b51 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 @@ -12,10 +12,12 @@ import com.android.mediproject.feature.search.recentsearchlist.RecentSearchListF import dagger.hilt.android.AndroidEntryPoint @AndroidEntryPoint -class HomeFragment : BaseFragment(FragmentHomeBinding::inflate) { +class HomeFragment : + BaseFragment(FragmentHomeBinding::inflate) { override val fragmentViewModel by viewModels() + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) initSearchBar() @@ -59,21 +61,27 @@ class HomeFragment : BaseFragment(FragmentHo private fun initChildFragments() { // 아이템 클릭 시 처리 로직 childFragmentManager.apply { - setFragmentResultListener(RecentCommentListFragment.ResultKey.RESULT_KEY.name, viewLifecycleOwner) { _, bundle -> + setFragmentResultListener( + RecentCommentListFragment.ResultKey.RESULT_KEY.name, viewLifecycleOwner + ) { _, bundle -> bundle.apply { val result = getInt(RecentCommentListFragment.ResultKey.WORD.name) } } - setFragmentResultListener(RecentPenaltyListFragment.ResultKey.RESULT_KEY.name, viewLifecycleOwner) { _, bundle -> + setFragmentResultListener( + RecentPenaltyListFragment.ResultKey.RESULT_KEY.name, viewLifecycleOwner + ) { _, bundle -> bundle.apply { val result = getInt(RecentPenaltyListFragment.ResultKey.PENALTY_ID.name) } } - setFragmentResultListener(RecentSearchListFragment.ResultKey.RESULT_KEY.name, viewLifecycleOwner) { _, bundle -> + setFragmentResultListener( + RecentSearchListFragment.ResultKey.RESULT_KEY.name, viewLifecycleOwner + ) { _, bundle -> findNavController().navigate( HomeFragmentDirections.actionHomeFragmentToSearchMedicinesFragment( - bundle.getString(RecentSearchListFragment.ResultKey.WORD.name) ?: "" - ) + bundle.getString(RecentSearchListFragment.ResultKey.WORD.name) ?: "" + ) ) } } diff --git a/feature/medicine/src/main/java/com/android/mediproject/feature/medicine/main/MedicineInfoFragment.kt b/feature/medicine/src/main/java/com/android/mediproject/feature/medicine/main/MedicineInfoFragment.kt index 592b0f851..9cd9e51de 100644 --- a/feature/medicine/src/main/java/com/android/mediproject/feature/medicine/main/MedicineInfoFragment.kt +++ b/feature/medicine/src/main/java/com/android/mediproject/feature/medicine/main/MedicineInfoFragment.kt @@ -2,9 +2,9 @@ package com.android.mediproject.feature.medicine.main import android.os.Bundle import android.view.View -import android.view.ViewGroup.LayoutParams -import android.view.ViewTreeObserver +import android.view.ViewGroup import androidx.coordinatorlayout.widget.CoordinatorLayout +import androidx.core.view.doOnPreDraw import androidx.core.view.isVisible import androidx.fragment.app.viewModels import androidx.navigation.fragment.navArgs @@ -24,7 +24,8 @@ import repeatOnStarted * */ @AndroidEntryPoint -class MedicineInfoFragment : BaseFragment(FragmentMedicineInfoBinding::inflate) { +class MedicineInfoFragment : + BaseFragment(FragmentMedicineInfoBinding::inflate) { override val fragmentViewModel: MedicineInfoViewModel by viewModels() @@ -36,40 +37,35 @@ class MedicineInfoFragment : BaseFragment 0 && rootLayout.measuredWidth > 0) { - rootLayout.viewTreeObserver.removeOnPreDrawListener(this) - - // coordinatorlayout으로 인해 viewpager의 높이가 휴대폰 화면 하단을 벗어나 버리는 현상을 방지하기 위해 사용 - val viewPagerHeight = rootLayout.height - topAppBar.height - contentViewPager.layoutParams = CoordinatorLayout.LayoutParams(LayoutParams.MATCH_PARENT, viewPagerHeight).apply { - behavior = AppBarLayout.ScrollingViewBehavior() - } + root.doOnPreDraw { + // coordinatorlayout으로 인해 viewpager의 높이가 휴대폰 화면 하단을 벗어나 버리는 현상을 방지하기 위해 사용 + val viewPagerHeight = root.height - topAppBar.height + contentViewPager.layoutParams = CoordinatorLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, viewPagerHeight + ).apply { + behavior = AppBarLayout.ScrollingViewBehavior() + } - topAppBar.removeOnOffsetChangedListener(null) - // smoothly hide medicinePrimaryInfoViewgroup when collapsing toolbar - topAppBar.addOnOffsetChangedListener { appBarLayout, verticalOffset -> - // 스크롤 할 때 마다 medicinePrimaryInfoViewgroup의 투명도 조정 - medicinePrimaryInfoViewgroup.alpha = - 1.0f + (verticalOffset.toFloat() / appBarLayout.totalScrollRange.toFloat()).apply { - if (this == -1.0f) medicinePrimaryInfoViewgroup.visibility = View.INVISIBLE - else if (this > -0.8f) medicinePrimaryInfoViewgroup.isVisible = true - } - - // 스크롤 할 때 마다 viewpager의 높이를 조정 - contentViewPager.layoutParams = - CoordinatorLayout.LayoutParams(LayoutParams.MATCH_PARENT, (viewPagerHeight - verticalOffset)).apply { - behavior = AppBarLayout.ScrollingViewBehavior() - } + topAppBar.removeOnOffsetChangedListener(null) + // smoothly hide medicinePrimaryInfoViewgroup when collapsing toolbar + topAppBar.addOnOffsetChangedListener { appBarLayout, verticalOffset -> + // 스크롤 할 때 마다 medicinePrimaryInfoViewgroup의 투명도 조정 + medicinePrimaryInfoViewgroup.alpha = + 1.0f + (verticalOffset.toFloat() / appBarLayout.totalScrollRange.toFloat()).apply { + if (this == -1.0f) medicinePrimaryInfoViewgroup.visibility = + View.INVISIBLE + else if (this > -0.8f) medicinePrimaryInfoViewgroup.isVisible = true } + // 스크롤 할 때 마다 viewpager의 높이를 조정 + contentViewPager.layoutParams = CoordinatorLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, (viewPagerHeight - verticalOffset) + ).apply { + behavior = AppBarLayout.ScrollingViewBehavior() } - - return true } - }) + } } viewLifecycleOwner.repeatOnStarted { @@ -108,7 +104,8 @@ class MedicineInfoFragment : BaseFragment diff --git a/feature/medicine/src/main/res/layout/fragment_medicine_info.xml b/feature/medicine/src/main/res/layout/fragment_medicine_info.xml index 574fe1776..9cc0ad30a 100644 --- a/feature/medicine/src/main/res/layout/fragment_medicine_info.xml +++ b/feature/medicine/src/main/res/layout/fragment_medicine_info.xml @@ -1,14 +1,12 @@ + xmlns:app="http://schemas.android.com/apk/res-auto"> + type="com.android.mediproject.feature.medicine.main.MedicineInfoViewModel" /> @@ -16,30 +14,27 @@ android:id="@+id/rootLayout" android:layout_width="match_parent" android:layout_height="match_parent" - android:background="@color/white" - > + android:background="@color/white"> + android:backgroundTint="@color/white"> + app:expandedTitleTextColor="@android:color/transparent" + app:layout_scrollFlags="scroll|exitUntilCollapsed" + app:title="@{viewModel.medicinePrimaryInfo.medicineName}" + app:titleCollapseMode="scale"> + app:layout_collapseMode="parallax"> + app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.App.CornerSize.20DP" /> + app:layout_constraintVertical_chainStyle="packed" /> + app:layout_constraintTop_toBottomOf="@id/medicineKorName" /> + app:tint="@color/gray" /> @@ -118,8 +108,7 @@ android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="wrap_content" - app:layout_collapseMode="pin" - /> + app:layout_collapseMode="pin" /> @@ -130,8 +119,7 @@ android:layout_marginTop="15dp" android:background="@color/white" app:tabIndicatorColor="@color/design_default_color_primary" - app:tabTextColor="@color/black" - /> + app:tabTextColor="@color/black" /> @@ -140,8 +128,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/white" - app:layout_behavior="@string/appbar_scrolling_view_behavior" - /> + app:layout_behavior="@string/appbar_scrolling_view_behavior" /> diff --git a/feature/medicine/src/main/res/values/styles.xml b/feature/medicine/src/main/res/values/styles.xml index 2c0012b96..b80a2410c 100644 --- a/feature/medicine/src/main/res/values/styles.xml +++ b/feature/medicine/src/main/res/values/styles.xml @@ -6,9 +6,4 @@ 0dp 0dp - - \ No newline at end of file diff --git a/feature/penalties/src/main/java/com/android/mediproject/feature/penalties/recentpenaltylist/PenaltyListAdapter.kt b/feature/penalties/src/main/java/com/android/mediproject/feature/penalties/recentpenaltylist/PenaltyListAdapter.kt index 87123aea9..279435dab 100644 --- a/feature/penalties/src/main/java/com/android/mediproject/feature/penalties/recentpenaltylist/PenaltyListAdapter.kt +++ b/feature/penalties/src/main/java/com/android/mediproject/feature/penalties/recentpenaltylist/PenaltyListAdapter.kt @@ -8,15 +8,20 @@ import com.android.mediproject.core.model.remote.recall.RecallSuspensionListItem import com.android.mediproject.core.ui.base.view.SimpleListItemView import com.android.mediproject.feature.penalties.R -class PenaltyListAdapter : ListAdapter(object : - DiffUtil.ItemCallback() { - override fun areItemsTheSame(oldItem: RecallSuspensionListItemDto, newItem: RecallSuspensionListItemDto): Boolean = oldItem == newItem - - override fun areContentsTheSame(oldItem: RecallSuspensionListItemDto, newItem: RecallSuspensionListItemDto): Boolean = - oldItem == newItem -}) { - - class PenaltyViewHolder(private val view: SimpleListItemView) : RecyclerView.ViewHolder(view) { +class PenaltyListAdapter : + ListAdapter(object : + DiffUtil.ItemCallback() { + override fun areItemsTheSame( + oldItem: RecallSuspensionListItemDto, newItem: RecallSuspensionListItemDto + ): Boolean = oldItem == newItem + + override fun areContentsTheSame( + oldItem: RecallSuspensionListItemDto, newItem: RecallSuspensionListItemDto + ): Boolean = oldItem == newItem + }) { + + class PenaltyViewHolder(private val view: SimpleListItemView) : + RecyclerView.ViewHolder(view) { private var item: RecallSuspensionListItemDto? = null diff --git a/feature/penalties/src/main/java/com/android/mediproject/feature/penalties/recentpenaltylist/RecentPenaltyListFragment.kt b/feature/penalties/src/main/java/com/android/mediproject/feature/penalties/recentpenaltylist/RecentPenaltyListFragment.kt index 6ed24177c..f6b124e0a 100644 --- a/feature/penalties/src/main/java/com/android/mediproject/feature/penalties/recentpenaltylist/RecentPenaltyListFragment.kt +++ b/feature/penalties/src/main/java/com/android/mediproject/feature/penalties/recentpenaltylist/RecentPenaltyListFragment.kt @@ -20,7 +20,9 @@ import repeatOnStarted */ @AndroidEntryPoint class RecentPenaltyListFragment : - BaseFragment(FragmentRecentPenaltyListBinding::inflate) { + BaseFragment( + FragmentRecentPenaltyListBinding::inflate + ) { enum class ResultKey { RESULT_KEY, PENALTY_ID @@ -42,7 +44,7 @@ class RecentPenaltyListFragment : fragmentViewModel.recallDisposalList.stateAsCollect(headerView).collect { uiState -> when (uiState) { is UiState.Error -> { - + } is UiState.Initial -> {} 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 ed5c05954..13f360a96 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 @@ -18,7 +18,9 @@ import repeatOnStarted @AndroidEntryPoint class SearchMedicinesFragment : - BaseFragment(FragmentSearchMedicinesBinding::inflate) { + BaseFragment( + FragmentSearchMedicinesBinding::inflate + ) { override val fragmentViewModel by viewModels() @@ -31,7 +33,9 @@ class SearchMedicinesFragment : viewLifecycleOwner.repeatOnStarted { fragmentViewModel.searchQuery.collect { query -> // Flow로 받은 문자열이 일치하는 경우에만 searchView에 표시한다. - if (!binding.searchView.getText().contentEquals(query)) binding.searchView.setText(query) + if (!binding.searchView.getText().contentEquals(query)) binding.searchView.setText( + query + ) } } @@ -53,26 +57,33 @@ class SearchMedicinesFragment : override fun onViewStateRestored(savedInstanceState: Bundle?) { super.onViewStateRestored(savedInstanceState) - 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) + 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) + } } } - } // 검색어가 전달된 경우에는 검색어를 넣은 후 검색 arguments?.getString("query")?.takeIf { it.isNotEmpty() }?.also { - fragmentViewModel.setQuery(it) - binding.contentsFragmentContainerView.findNavController().navigate( - RecentSearchListFragmentDirections.actionRecentSearchListFragmentToManualSearchResultFragment(), - NavOptions.Builder().setPopUpTo(R.id.manualSearchResultFragment, true).build() - ) + binding.contentsFragmentContainerView.findNavController().apply { + if (currentDestination?.id != R.id.manualSearchResultFragment) { + fragmentViewModel.setQuery(it) + navigate( + RecentSearchListFragmentDirections.actionRecentSearchListFragmentToManualSearchResultFragment(), + NavOptions.Builder().setPopUpTo(R.id.manualSearchResultFragment, true) + .build() + ) + } + } } } 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 955748df2..223191420 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 @@ -6,9 +6,11 @@ import android.view.ViewGroup.LayoutParams import androidx.core.os.bundleOf import androidx.core.view.size import androidx.fragment.app.activityViewModels +import com.android.mediproject.core.common.viewmodel.UiState 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.core.ui.base.view.stateAsCollect import com.android.mediproject.feature.search.databinding.FragmentRecentSearchListBinding import com.google.android.flexbox.FlexboxLayout import dagger.hilt.android.AndroidEntryPoint @@ -23,7 +25,9 @@ import repeatOnStarted */ @AndroidEntryPoint class RecentSearchListFragment : - BaseFragment(FragmentRecentSearchListBinding::inflate) { + BaseFragment( + FragmentRecentSearchListBinding::inflate + ) { enum class ResultKey { RESULT_KEY, WORD @@ -36,13 +40,18 @@ class RecentSearchListFragment : initHeader() viewLifecycleOwner.repeatOnStarted { - fragmentViewModel.searchHistoryList().collectLatest { - // 최근 검색 목록을 가져옵니다. + // 최근 검색 목록을 가져옵니다. + fragmentViewModel.searchHistoryList().stateAsCollect(binding.headerView).collectLatest { + when (it) { + is UiState.Success -> { + // 리스트를 비웁니다. + if (binding.searchHistoryList.size > 0) binding.searchHistoryList.removeAllViews() + it.data.forEach { searchHistoryItemDto -> + addHistoryItemChips(searchHistoryItemDto) + } + } - // 리스트를 비웁니다. - if (binding.searchHistoryList.size > 0) binding.searchHistoryList.removeAllViews() - it.forEach { searchHistoryItemDto -> - addHistoryItemChips(searchHistoryItemDto) + else -> {} } } } @@ -55,11 +64,14 @@ class RecentSearchListFragment : */ private fun addHistoryItemChips(searchHistoryItemDto: SearchHistoryItemDto) { binding.apply { - val horizontalSpace = resources.getDimension(com.android.mediproject.core.ui.R.dimen.dp_4).toInt() + 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) - } + layoutParams = + FlexboxLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT) + .apply { + setMargins(horizontalSpace, 0, horizontalSpace, 0) + } data = searchHistoryItemDto.query setChipText(data.toString()) setOnChipClickListener { 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 2784a9f89..cecf69bdf 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,9 +1,13 @@ package com.android.mediproject.feature.search.recentsearchlist import androidx.lifecycle.viewModelScope +import com.android.mediproject.core.common.viewmodel.UiState 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.catch +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.stateIn import javax.inject.Inject @@ -13,7 +17,11 @@ class RecentSearchListViewModel @Inject constructor( ) : BaseViewModel() { val searchHistoryList by lazy { suspend { - searchHistoryUseCase.getSearchHistoryList(6).stateIn(viewModelScope) + searchHistoryUseCase.getSearchHistoryList(6).flatMapLatest { + flowOf(UiState.Success(it)) + }.catch { + flowOf(UiState.Error(it.message ?: "Error")) + }.stateIn(viewModelScope) } } } \ 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 ba16b0202..54f5b48ff 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 @@ -1,8 +1,8 @@ package com.android.mediproject.feature.search.result.manual +import android.annotation.SuppressLint import android.os.Bundle import android.view.View -import androidx.core.content.ContextCompat import androidx.fragment.app.viewModels import androidx.navigation.findNavController import androidx.paging.map @@ -24,13 +24,16 @@ import repeatOnStarted @AndroidEntryPoint class ManualSearchResultFragment : - BaseFragment(FragmentManualSearchResultBinding::inflate) { + BaseFragment( + FragmentManualSearchResultBinding::inflate + ) { private val searchMedicinesViewModel: SearchMedicinesViewModel by viewModels({ requireParentFragment().requireParentFragment() }) override val fragmentViewModel: ManualSearchResultViewModel by viewModels() + @SuppressLint("UseCompatLoadingForDrawables") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -50,9 +53,15 @@ class ManualSearchResultFragment : pagingListViewGroup.pagingList.apply { setHasFixedSize(true) - setItemViewCacheSize(8) - addItemDecoration(DividerItemDecoration(requireContext(), DividerItemDecoration.VERTICAL).apply { - setDrawable(ContextCompat.getDrawable(requireContext(), com.android.mediproject.core.ui.R.drawable.divider)!!) + setItemViewCacheSize(10) + addItemDecoration(DividerItemDecoration( + requireContext(), DividerItemDecoration.VERTICAL + ).apply { + setDrawable( + resources.getDrawable( + com.android.mediproject.core.ui.R.drawable.divider, null + ) + ) }) adapter = searchResultListAdapter } @@ -63,11 +72,17 @@ class ManualSearchResultFragment : ) { menuItem -> when (menuItem.itemId) { - R.id.listOnlySpecialtyMedicines -> fragmentViewModel.searchMedicinesByMedicationType(MedicationType.SPECIALTY) + R.id.listOnlySpecialtyMedicines -> fragmentViewModel.searchMedicinesByMedicationType( + MedicationType.SPECIALTY + ) - R.id.listOnlyGenericMedicines -> fragmentViewModel.searchMedicinesByMedicationType(MedicationType.GENERAL) + R.id.listOnlyGenericMedicines -> fragmentViewModel.searchMedicinesByMedicationType( + MedicationType.GENERAL + ) - R.id.listAllMedicines -> fragmentViewModel.searchMedicinesByMedicationType(MedicationType.ALL) + R.id.listAllMedicines -> fragmentViewModel.searchMedicinesByMedicationType( + MedicationType.ALL + ) } true } @@ -107,15 +122,16 @@ class ManualSearchResultFragment : } private fun openMedicineInfo(approvedMedicineItemDto: ApprovedMedicineItemDto) { - activity?.findNavController(com.android.mediproject.core.common.R.id.fragmentContainerView)?.navigateByDeepLink( - "medilens://search/medicine/medicine_detail_nav", MedicineInfoArgs( - medicineName = approvedMedicineItemDto.itemName, - imgUrl = approvedMedicineItemDto.bigPrdtImgUrl ?: "", - entpName = approvedMedicineItemDto.entpName ?: "", - itemSequence = approvedMedicineItemDto.itemSeq ?: "", - medicineEngName = approvedMedicineItemDto.itemEngName ?: "" + activity?.findNavController(com.android.mediproject.core.common.R.id.fragmentContainerView) + ?.navigateByDeepLink( + "medilens://search/medicine/medicine_detail_nav", MedicineInfoArgs( + medicineName = approvedMedicineItemDto.itemName, + imgUrl = approvedMedicineItemDto.bigPrdtImgUrl ?: "", + entpName = approvedMedicineItemDto.entpName ?: "", + itemSequence = approvedMedicineItemDto.itemSeq ?: "", + medicineEngName = approvedMedicineItemDto.itemEngName ?: "" + ) ) - ) } } \ No newline at end of file diff --git a/feature/search/src/main/java/com/android/mediproject/feature/search/result/manual/ManualSearchResultViewModel.kt b/feature/search/src/main/java/com/android/mediproject/feature/search/result/manual/ManualSearchResultViewModel.kt index dc17fde9c..ce73f9f49 100644 --- a/feature/search/src/main/java/com/android/mediproject/feature/search/result/manual/ManualSearchResultViewModel.kt +++ b/feature/search/src/main/java/com/android/mediproject/feature/search/result/manual/ManualSearchResultViewModel.kt @@ -2,6 +2,7 @@ package com.android.mediproject.feature.search.result.manual import androidx.lifecycle.viewModelScope import androidx.paging.PagingData +import androidx.paging.cachedIn import com.android.mediproject.core.common.network.Dispatcher import com.android.mediproject.core.common.network.MediDispatchers import com.android.mediproject.core.common.viewmodel.UiState @@ -19,6 +20,7 @@ import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch @@ -49,11 +51,11 @@ class ManualSearchResultViewModel @Inject constructor( if (parameter.entpName.isNullOrEmpty() && parameter.itemName.isNullOrEmpty()) { flowOf(UiState.Error("검색어를 입력해주세요.")) } else { - getMedicineApprovalListUseCase(parameter).flatMapLatest { + getMedicineApprovalListUseCase(parameter).cachedIn(viewModelScope).flatMapLatest { flowOf(UiState.Success(it)) } } - }.catch { + }.flowOn(ioDispatcher).catch { flowOf(UiState.Error(it.message ?: "알 수 없는 오류가 발생했습니다.")) }.stateIn(viewModelScope, started = SharingStarted.Lazily, initialValue = UiState.Loading) }