diff --git a/build.gradle.kts b/build.gradle.kts index cbc3bec35..a762e3440 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -19,7 +19,7 @@ plugins { alias(libs.plugins.kotlin.android) apply false alias(libs.plugins.nav.safeargs.kotlin) apply false alias(libs.plugins.kapt) apply false - //alias(libs.plugins.ksp) apply false + alias(libs.plugins.ksp) apply false alias(libs.plugins.kotlin.parcelize) apply false } diff --git a/core/common/src/main/java/com/android/mediproject/core/common/adapter/GodBindListAdapter.kt b/core/common/src/main/java/com/android/mediproject/core/common/adapter/GodBindListAdapter.kt new file mode 100644 index 000000000..685bf1e5a --- /dev/null +++ b/core/common/src/main/java/com/android/mediproject/core/common/adapter/GodBindListAdapter.kt @@ -0,0 +1,76 @@ +package com.android.mediproject.core.common.adapter + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.annotation.IdRes +import androidx.databinding.DataBindingUtil +import androidx.databinding.ViewDataBinding +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import androidx.recyclerview.widget.RecyclerView.ViewHolder + +/** + * 귀찮음 줄여주는 ListAdapter + * + * @param T : ListAdapter에 들어갈 아이템 타입 + * @param V : ViewDataBinding + * @param differ : DiffUtil.ItemCallback + * - ListAdapter의 DiffUtil.ItemCallback + * - 아이템이 같은지 비교하는 메소드를 구현해야함 + * @param initViewHolder : (((CViewHolder) -> Unit) + * - ViewHolder 초기화 + * - ex) { holder -> ViewHolder class init 시 필요한 작업 처리 } + * @param onBind : (CViewHolder, T, Int) -> Unit + * - ViewHolder 바인딩 + * - ex) { holder, item, position -> 필요한 작업 처리 } + * @param itemViewId : Int + * - ViewHolder의 itemView id + * - ex) R.layout.item_view + * + */ +abstract class GodBindListAdapter( + differ: DiffUtil.ItemCallback, + private val initViewHolder: ((GodBindViewHolder) -> Unit)?, + private val onBind: (GodBindViewHolder, T, Int) -> Unit, + @IdRes private val itemViewId: Int +) : ListAdapter>(differ) { + + private var _inflater: LayoutInflater? = null + private val inflater get() = _inflater!! + + override fun onAttachedToRecyclerView(recyclerView: RecyclerView) { + super.onAttachedToRecyclerView(recyclerView) + _inflater = null + _inflater = LayoutInflater.from(recyclerView.context) + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GodBindViewHolder = + GodBindViewHolder(DataBindingUtil.inflate(inflater, itemViewId, parent, false), initViewHolder, onBind) + + + override fun onBindViewHolder(holder: GodBindViewHolder, position: Int) { + holder.bind(getItem(position)) + } + + override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) { + super.onDetachedFromRecyclerView(recyclerView) + _inflater = null + } +} + +class GodBindViewHolder( + private val binding: V, + private val initViewHolder: ((GodBindViewHolder) -> Unit)?, + private val onBind: (GodBindViewHolder, T, Int) -> Unit, +) : ViewHolder(binding.root) { + + init { + initViewHolder?.invoke(this) + } + + fun bind(item: T) { + onBind.invoke(this, item, absoluteAdapterPosition) + binding.executePendingBindings() + } +} \ No newline at end of file diff --git a/core/data/build.gradle.kts b/core/data/build.gradle.kts index c0d74dbd0..5b138523c 100644 --- a/core/data/build.gradle.kts +++ b/core/data/build.gradle.kts @@ -21,6 +21,7 @@ dependencies { implementation(project(":core:datastore")) implementation(project(":core:model")) implementation(project(":core:network")) + implementation(project(":core:database")) implementation(libs.androidx.core.ktx) implementation(libs.kotlinx.serialization.json) diff --git a/core/data/src/main/java/com/android/mediproject/core/data/remote/di/RepositoryModule.kt b/core/data/src/main/java/com/android/mediproject/core/data/remote/di/RepositoryModule.kt index 20565a8e7..b43de3355 100644 --- a/core/data/src/main/java/com/android/mediproject/core/data/remote/di/RepositoryModule.kt +++ b/core/data/src/main/java/com/android/mediproject/core/data/remote/di/RepositoryModule.kt @@ -1,5 +1,7 @@ package com.android.mediproject.core.data.remote.di +import com.android.mediproject.core.common.network.Dispatcher +import com.android.mediproject.core.common.network.MediDispatchers import com.android.mediproject.core.data.remote.adminaction.AdminActionRepository import com.android.mediproject.core.data.remote.adminaction.AdminActionRepositoryImpl import com.android.mediproject.core.data.remote.comments.CommentsRepository @@ -16,6 +18,9 @@ import com.android.mediproject.core.data.remote.recallsuspension.RecallSuspensio import com.android.mediproject.core.data.remote.recallsuspension.RecallSuspensionRepositoryImpl import com.android.mediproject.core.data.remote.sign.SignRepository import com.android.mediproject.core.data.remote.sign.SignRepositoryImpl +import com.android.mediproject.core.data.search.SearchHistoryRepository +import com.android.mediproject.core.data.search.SearchHistoryRepositoryImpl +import com.android.mediproject.core.database.searchhistory.SearchHistoryDao import com.android.mediproject.core.datastore.AppDataStore import com.android.mediproject.core.datastore.TokenDataSourceImpl import com.android.mediproject.core.network.datasource.comments.CommentsDataSource @@ -31,6 +36,7 @@ import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent +import kotlinx.coroutines.CoroutineDispatcher import javax.inject.Singleton @InstallIn(SingletonComponent::class) @@ -40,8 +46,10 @@ object RepositoryModule { @Provides @Singleton fun provideMedicineApprovalRepository( - medicineApprovalDataSource: MedicineApprovalDataSource - ): MedicineApprovalRepository = MedicineApprovalRepositoryImpl(medicineApprovalDataSource) + medicineApprovalDataSource: MedicineApprovalDataSource, searchHistoryDao: SearchHistoryDao + ): MedicineApprovalRepository = MedicineApprovalRepositoryImpl( + medicineApprovalDataSource, searchHistoryDao + ) @Provides @Singleton @@ -81,4 +89,10 @@ object RepositoryModule { fun providesSignRepository( signDataSource: SignDataSource, connectionTokenDataSourceImpl: TokenDataSourceImpl, appDataStore: AppDataStore ): SignRepository = SignRepositoryImpl(signDataSource, connectionTokenDataSourceImpl, appDataStore) + + @Provides + @Singleton + fun providesSearchHistoryRepository( + searchHistoryDao: SearchHistoryDao, @Dispatcher(MediDispatchers.IO) ioDispatcher: CoroutineDispatcher + ): SearchHistoryRepository = SearchHistoryRepositoryImpl(searchHistoryDao, ioDispatcher) } \ No newline at end of file diff --git a/core/data/src/main/java/com/android/mediproject/core/data/remote/medicineapproval/MedicineApprovalRepositoryImpl.kt b/core/data/src/main/java/com/android/mediproject/core/data/remote/medicineapproval/MedicineApprovalRepositoryImpl.kt index 76e42790b..42293cf1f 100644 --- a/core/data/src/main/java/com/android/mediproject/core/data/remote/medicineapproval/MedicineApprovalRepositoryImpl.kt +++ b/core/data/src/main/java/com/android/mediproject/core/data/remote/medicineapproval/MedicineApprovalRepositoryImpl.kt @@ -4,6 +4,7 @@ import androidx.paging.Pager import androidx.paging.PagingConfig import androidx.paging.PagingData import com.android.mediproject.core.common.DATA_GO_KR_PAGE_SIZE +import com.android.mediproject.core.database.searchhistory.SearchHistoryDao import com.android.mediproject.core.model.medicine.medicineapproval.Item import com.android.mediproject.core.model.medicine.medicinedetailinfo.MedicineDetailInfoResponse import com.android.mediproject.core.network.datasource.medicineapproval.MedicineApprovalDataSource @@ -15,8 +16,9 @@ import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.map import javax.inject.Inject -class MedicineApprovalRepositoryImpl @Inject constructor(private val medicineApprovalDataSource: MedicineApprovalDataSource) : - MedicineApprovalRepository { +class MedicineApprovalRepositoryImpl @Inject constructor( + private val medicineApprovalDataSource: MedicineApprovalDataSource, private val searchHistoryDao: SearchHistoryDao +) : MedicineApprovalRepository { /** * PagingData를 사용하여 페이징 처리를 하기 위해 Pager를 사용 @@ -33,6 +35,7 @@ class MedicineApprovalRepositoryImpl @Inject constructor(private val medicineApp if (itemName == null && entpName == null) { emptyFlow() } else { + Pager(config = PagingConfig(pageSize = DATA_GO_KR_PAGE_SIZE, prefetchDistance = 5), pagingSourceFactory = { MedicineApprovalListDataSourceImpl( medicineApprovalDataSource, itemName, entpName, medicationType @@ -43,9 +46,7 @@ class MedicineApprovalRepositoryImpl @Inject constructor(private val medicineApp override suspend fun getMedicineDetailInfo(itemName: String): Flow> = channelFlow { medicineApprovalDataSource.getMedicineDetailInfo(itemName).map { result -> - result.fold( - onSuccess = { Result.success(it.body.items.first()) }, - onFailure = { Result.failure(it) }) + result.fold(onSuccess = { Result.success(it.body.items.first()) }, onFailure = { Result.failure(it) }) }.collectLatest { send(it) } diff --git a/core/data/src/main/java/com/android/mediproject/core/data/search/SearchHistoryRepository.kt b/core/data/src/main/java/com/android/mediproject/core/data/search/SearchHistoryRepository.kt new file mode 100644 index 000000000..5ac9b1357 --- /dev/null +++ b/core/data/src/main/java/com/android/mediproject/core/data/search/SearchHistoryRepository.kt @@ -0,0 +1,14 @@ +package com.android.mediproject.core.data.search + +import com.android.mediproject.core.database.searchhistory.SearchHistoryDto +import kotlinx.coroutines.flow.Flow + +interface SearchHistoryRepository { + suspend fun insertSearchHistory(query: String) + + suspend fun getSearchHistoryList(count: Int = 5): Flow> + + suspend fun deleteSearchHistory(id: Long) + + suspend fun deleteAllSearchHistory() +} \ No newline at end of file diff --git a/core/data/src/main/java/com/android/mediproject/core/data/search/SearchHistoryRepositoryImpl.kt b/core/data/src/main/java/com/android/mediproject/core/data/search/SearchHistoryRepositoryImpl.kt new file mode 100644 index 000000000..434698abc --- /dev/null +++ b/core/data/src/main/java/com/android/mediproject/core/data/search/SearchHistoryRepositoryImpl.kt @@ -0,0 +1,44 @@ +package com.android.mediproject.core.data.search + +import com.android.mediproject.core.common.network.Dispatcher +import com.android.mediproject.core.common.network.MediDispatchers +import com.android.mediproject.core.database.searchhistory.SearchHistoryDao +import com.android.mediproject.core.database.searchhistory.SearchHistoryDto +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.withContext +import java.time.LocalDateTime +import java.time.format.DateTimeFormatter +import javax.inject.Inject + +class SearchHistoryRepositoryImpl @Inject constructor( + private val searchHistoryDao: SearchHistoryDao, @Dispatcher(MediDispatchers.IO) private val ioDispatcher: CoroutineDispatcher +) : SearchHistoryRepository { + override suspend fun insertSearchHistory(query: String) { + withContext(ioDispatcher) { + searchHistoryDao.insert( + SearchHistoryDto( + query = query, id = 0L, searchDateTime = LocalDateTime.now().format(DateTimeFormatter.ISO_DATE_TIME) + ) + ) + } + } + + override suspend fun getSearchHistoryList(count: Int): Flow> = withContext(ioDispatcher) { + searchHistoryDao.select(count) + } + + + override suspend fun deleteSearchHistory(id: Long) { + withContext(ioDispatcher) { + searchHistoryDao.delete(id) + } + } + + override suspend fun deleteAllSearchHistory() { + withContext(ioDispatcher) { + searchHistoryDao.deleteAll() + } + } + +} \ No newline at end of file diff --git a/core/database/.gitignore b/core/database/.gitignore new file mode 100644 index 000000000..42afabfd2 --- /dev/null +++ b/core/database/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/core/database/build.gradle.kts b/core/database/build.gradle.kts new file mode 100644 index 000000000..2ffb38115 --- /dev/null +++ b/core/database/build.gradle.kts @@ -0,0 +1,21 @@ +plugins { + id("mediproject.android.library") + id("mediproject.android.hilt") + alias(libs.plugins.ksp) +} + +android { + namespace = "com.android.mediproject.core.database" + + buildFeatures { + buildConfig = true + } +} + +dependencies { + implementation(project(":core:common")) + implementation(project(":core:model")) + + implementation(libs.bundles.rooms) + implementation(libs.kotlinx.coroutines.android) +} \ No newline at end of file diff --git a/core/database/consumer-rules.pro b/core/database/consumer-rules.pro new file mode 100644 index 000000000..e69de29bb diff --git a/core/database/proguard-rules.pro b/core/database/proguard-rules.pro new file mode 100644 index 000000000..481bb4348 --- /dev/null +++ b/core/database/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/core/database/src/androidTest/java/com/android/mediproject/core/database/ExampleInstrumentedTest.kt b/core/database/src/androidTest/java/com/android/mediproject/core/database/ExampleInstrumentedTest.kt new file mode 100644 index 000000000..4a92e2f7f --- /dev/null +++ b/core/database/src/androidTest/java/com/android/mediproject/core/database/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.android.mediproject.core.database + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.android.mediproject.core.database.test", appContext.packageName) + } +} \ No newline at end of file diff --git a/core/database/src/main/AndroidManifest.xml b/core/database/src/main/AndroidManifest.xml new file mode 100644 index 000000000..a5918e68a --- /dev/null +++ b/core/database/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/core/database/src/main/java/com/android/mediproject/core/database/RoomDb.kt b/core/database/src/main/java/com/android/mediproject/core/database/RoomDb.kt new file mode 100644 index 000000000..84818125c --- /dev/null +++ b/core/database/src/main/java/com/android/mediproject/core/database/RoomDb.kt @@ -0,0 +1,12 @@ +package com.android.mediproject.core.database + +import androidx.room.Database +import androidx.room.RoomDatabase +import com.android.mediproject.core.database.searchhistory.SearchHistoryDao +import com.android.mediproject.core.database.searchhistory.SearchHistoryDto + + +@Database(entities = [SearchHistoryDto::class], version = 1) +abstract class RoomDb : RoomDatabase() { + abstract fun searchHistoryDao(): SearchHistoryDao +} \ No newline at end of file diff --git a/core/database/src/main/java/com/android/mediproject/core/database/RoomModule.kt b/core/database/src/main/java/com/android/mediproject/core/database/RoomModule.kt new file mode 100644 index 000000000..88e1abe42 --- /dev/null +++ b/core/database/src/main/java/com/android/mediproject/core/database/RoomModule.kt @@ -0,0 +1,30 @@ +package com.android.mediproject.core.database + +import android.content.Context +import androidx.room.Room +import com.android.mediproject.core.database.searchhistory.SearchHistoryDao +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module(includes = [DaoModule::class]) +@InstallIn(SingletonComponent::class) +object RoomModule { + + @Provides + @Singleton + fun provideRoomDb(@ApplicationContext context: Context): RoomDb = Room.databaseBuilder( + context, RoomDb::class.java, "medi_database" + ).build() +} + + +@Module +@InstallIn(SingletonComponent::class) +object DaoModule { + @Provides + fun provideSearchHistoryDao(roomDb: RoomDb): SearchHistoryDao = roomDb.searchHistoryDao() +} \ No newline at end of file diff --git a/core/database/src/main/java/com/android/mediproject/core/database/searchhistory/SearchHistoryDao.kt b/core/database/src/main/java/com/android/mediproject/core/database/searchhistory/SearchHistoryDao.kt new file mode 100644 index 000000000..e6f14ee66 --- /dev/null +++ b/core/database/src/main/java/com/android/mediproject/core/database/searchhistory/SearchHistoryDao.kt @@ -0,0 +1,39 @@ +package com.android.mediproject.core.database.searchhistory + +import androidx.room.Dao +import androidx.room.Delete +import androidx.room.Query +import androidx.room.Upsert +import kotlinx.coroutines.flow.Flow + +@Dao +interface SearchHistoryDao { + + /** + * 검색 기록을 추가한다. + * @param searchHistoryDto + */ + @Upsert + suspend fun insert(searchHistoryDto: SearchHistoryDto) + + /** + * id에 해당하는 검색 기록을 삭제한다. + * @param id + */ + @Delete + suspend fun delete(id: Long) + + /** + * 개수 만큼 검색 목록을 가져온다. + * @param count + */ + @Query("SELECT * FROM search_history_table ORDER BY id DESC LIMIT :count") + suspend fun select(count: Int): Flow> + + /** + * 모든 검색 기록을 삭제한다. + */ + @Query("DELETE FROM search_history_table") + @Delete + suspend fun deleteAll() +} \ No newline at end of file diff --git a/core/database/src/main/java/com/android/mediproject/core/database/searchhistory/SearchHistoryDto.kt b/core/database/src/main/java/com/android/mediproject/core/database/searchhistory/SearchHistoryDto.kt new file mode 100644 index 000000000..9c490ce70 --- /dev/null +++ b/core/database/src/main/java/com/android/mediproject/core/database/searchhistory/SearchHistoryDto.kt @@ -0,0 +1,12 @@ +package com.android.mediproject.core.database.searchhistory + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey + +@Entity(tableName = "search_history_table") +data class SearchHistoryDto( + @PrimaryKey(autoGenerate = true) val id: Long, + + @ColumnInfo(name = "query") val query: String, @ColumnInfo(name = "search_date_time") val searchDateTime: String +) \ No newline at end of file diff --git a/core/database/src/test/java/com/android/mediproject/core/database/ExampleUnitTest.kt b/core/database/src/test/java/com/android/mediproject/core/database/ExampleUnitTest.kt new file mode 100644 index 000000000..6e6bf79f3 --- /dev/null +++ b/core/database/src/test/java/com/android/mediproject/core/database/ExampleUnitTest.kt @@ -0,0 +1,17 @@ +package com.android.mediproject.core.database + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} \ No newline at end of file diff --git a/core/datastore/src/main/java/com/android/mediproject/core/datastore/AppDataStoreImpl.kt b/core/datastore/src/main/java/com/android/mediproject/core/datastore/AppDataStoreImpl.kt index 27c74308d..50a89f170 100644 --- a/core/datastore/src/main/java/com/android/mediproject/core/datastore/AppDataStoreImpl.kt +++ b/core/datastore/src/main/java/com/android/mediproject/core/datastore/AppDataStoreImpl.kt @@ -20,6 +20,7 @@ class AppDataStoreImpl @Inject constructor( private val KEY_NICK_NAME = stringPreferencesKey("nick_name") private val KEY_SKIP_INTRO = booleanPreferencesKey("skip_intro") + override val userEmail: Flow = context.dataStore.data.map { it[KEY_USER_EMAIL] ?: "" } override val nickName: Flow = context.dataStore.data.map { it[KEY_NICK_NAME] ?: "" } diff --git a/core/ui/src/main/java/com/android/mediproject/core/ui/base/view/HeaderForElementsView.kt b/core/ui/src/main/java/com/android/mediproject/core/ui/base/view/HeaderForElementsView.kt index 8249bbd79..61dde0fcf 100644 --- a/core/ui/src/main/java/com/android/mediproject/core/ui/base/view/HeaderForElementsView.kt +++ b/core/ui/src/main/java/com/android/mediproject/core/ui/base/view/HeaderForElementsView.kt @@ -12,8 +12,13 @@ import android.widget.ImageView import android.widget.TextView import androidx.appcompat.content.res.AppCompatResources import androidx.constraintlayout.widget.ConstraintLayout +import androidx.core.view.isVisible +import com.android.mediproject.core.common.viewmodel.UiState import com.android.mediproject.core.ui.R import com.google.android.material.progressindicator.CircularProgressIndicator +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOf private const val DOT_CHAR = "• " @@ -31,6 +36,25 @@ class HeaderForElementsView constructor( private val moreBtnView: TextView private val titleView: TextView private var expanded = true + set(value) { + field = value + expandBtnView.setImageDrawable( + AppCompatResources.getDrawable( + context, if (value) R.drawable.baseline_expand_more_24 else R.drawable.baseline_expand_less_24 + ) + ) + + takeIf { targetViewId != -1 }?.let { + (parent.parent as View).findViewById(targetViewId)?.apply { + visibility = if (value) View.VISIBLE + else View.GONE + } + } + + onExpandClickListener?.onExpandClick(value) + } + + private var moreVisibility = View.VISIBLE private var onExpandClickListener: OnExpandClickListener? = null @@ -66,11 +90,9 @@ class HeaderForElementsView constructor( val iconColor = typedArr.getColor(R.styleable.HeaderForElementsView_expand_icon_color, Color.BLACK) val titleFontSize = typedArr.getDimension(R.styleable.HeaderForElementsView_title_text_size, 15f) val moreFontSize = typedArr.getDimension(R.styleable.HeaderForElementsView_more_text_size, 14f) - expanded = typedArr.getBoolean(R.styleable.HeaderForElementsView_is_expanded, expanded) moreVisibility = typedArr.getInt(R.styleable.HeaderForElementsView_more_visibility, moreVisibility) targetViewId = typedArr.getResourceId(R.styleable.HeaderForElementsView_visibility_target_view, -1) - // title titleView = TextView(context).apply { layoutParams = ConstraintLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT).apply { @@ -84,7 +106,7 @@ class HeaderForElementsView constructor( id = 1 setOnClickListener { - setExpand(!expanded) + this@HeaderForElementsView.expanded = !this@HeaderForElementsView.expanded } } setTitle(title) @@ -93,7 +115,7 @@ class HeaderForElementsView constructor( // 확장 버튼 expandBtnView = ImageView(context).apply { setOnClickListener { - setExpand(!expanded) + this@HeaderForElementsView.expanded = !this@HeaderForElementsView.expanded } isClickable = true @@ -196,8 +218,8 @@ class HeaderForElementsView constructor( /** * 더보기 버튼 Visible 관련 로직 */ - fun setMoreVisiblity(boolean: Boolean){ - when(boolean){ + fun setMoreVisiblity(boolean: Boolean) { + when (boolean) { false -> moreBtnView.visibility = View.GONE true -> moreBtnView.visibility = View.VISIBLE } @@ -206,8 +228,8 @@ class HeaderForElementsView constructor( /** * 확장 버튼 Visible 관련 로직 */ - fun setExpandVisiblity(boolean: Boolean){ - when(boolean){ + fun setExpandVisiblity(boolean: Boolean) { + when (boolean) { false -> expandBtnView.visibility = View.GONE true -> expandBtnView.visibility = View.VISIBLE } @@ -215,50 +237,45 @@ class HeaderForElementsView constructor( /** - * 확장 버튼 클릭 시 호출 - * - * @param expanded 확장 여부 + * 인디케이터의 Visible 여부에 따라 확장 버튼의 Visible 여부를 결정 * + * @param visibility 인디케이터의 Visible 여부 */ - fun setExpand(expanded: Boolean) { - this.expanded = expanded - expandBtnView.setImageDrawable( - AppCompatResources.getDrawable( - context, if (expanded) R.drawable.baseline_expand_more_24 else R.drawable.baseline_expand_less_24 - ) - ) - - takeIf { targetViewId != -1 }?.let { - (parent.parent as View).findViewById(targetViewId)?.apply { - visibility = if (expanded) View.VISIBLE - else View.GONE - } - } - - onExpandClickListener?.onExpandClick(expanded) - } - override fun onIndicatorVisibilityChanged(visibility: Boolean) { findViewById(R.id.indicator)?.apply { - - val visible = if (visibility) View.VISIBLE else View.GONE - setVisibility(visible) - when (visibility) { - true -> { - show() - expandBtnView.visibility = View.GONE - } - - false -> { - hide() - expandBtnView.visibility = View.VISIBLE - } - } + isVisible = visibility + expandBtnView.isVisible = !visibility + expanded = !visibility + if (visibility) show() + else hide() } } - } fun interface OnIndicatorVisibilityChangedListener { fun onIndicatorVisibilityChanged(visibility: Boolean) +} + + +suspend inline fun Flow>.stateAsCollect( + headerForElementsView: HeaderForElementsView, +): Flow> = flatMapLatest { + when (it) { + is UiState.Error -> { + headerForElementsView.onIndicatorVisibilityChanged(false) + } + + is UiState.Loading -> { + headerForElementsView.onIndicatorVisibilityChanged(true) + } + + is UiState.Success -> { + headerForElementsView.onIndicatorVisibilityChanged(false) + } + + is UiState.Initial -> { + headerForElementsView.onIndicatorVisibilityChanged(true) + } + } + flowOf(it) } \ No newline at end of file diff --git a/core/ui/src/main/res/values/attrs.xml b/core/ui/src/main/res/values/attrs.xml index 26a85d1bb..79c64ffb8 100644 --- a/core/ui/src/main/res/values/attrs.xml +++ b/core/ui/src/main/res/values/attrs.xml @@ -74,7 +74,6 @@ - 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 ce0bf69ca..39a4fed78 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,7 +8,13 @@ import androidx.recyclerview.widget.RecyclerView import com.android.mediproject.core.model.remote.recall.RecallSuspensionListItemDto import com.android.mediproject.core.ui.base.view.SimpleListItemView -class PenaltyListAdapter : ListAdapter(Diff) { +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) { @@ -39,11 +45,6 @@ class PenaltyListAdapter : ListAdapter() { - override fun areItemsTheSame(oldItem: RecallSuspensionListItemDto, newItem: RecallSuspensionListItemDto): Boolean = oldItem == newItem - override fun areContentsTheSame(oldItem: RecallSuspensionListItemDto, newItem: RecallSuspensionListItemDto): Boolean = - oldItem == newItem } \ No newline at end of file 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 533e13034..6ed24177c 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 @@ -7,6 +7,7 @@ import androidx.fragment.app.viewModels import androidx.navigation.fragment.findNavController import com.android.mediproject.core.common.viewmodel.UiState import com.android.mediproject.core.ui.base.BaseFragment +import com.android.mediproject.core.ui.base.view.stateAsCollect import com.android.mediproject.feature.penalties.databinding.FragmentRecentPenaltyListBinding import dagger.hilt.android.AndroidEntryPoint import repeatOnStarted @@ -32,17 +33,17 @@ class RecentPenaltyListFragment : initHeader() binding.apply { - headerView.onIndicatorVisibilityChanged(true) - headerView.setExpand(false) penaltyList.setHasFixedSize(true) val penaltyListAdapter = PenaltyListAdapter() penaltyList.adapter = penaltyListAdapter viewLifecycleOwner.repeatOnStarted { - fragmentViewModel.recallDisposalList.collect { uiState -> + fragmentViewModel.recallDisposalList.stateAsCollect(headerView).collect { uiState -> when (uiState) { - is UiState.Error -> {} + is UiState.Error -> { + + } is UiState.Initial -> {} @@ -50,8 +51,6 @@ class RecentPenaltyListFragment : is UiState.Success -> { penaltyListAdapter.submitList(uiState.data) - binding.headerView.onIndicatorVisibilityChanged(false) - binding.headerView.setExpand(true) } } } diff --git a/feature/penalties/src/main/res/layout/fragment_recent_penalty_list.xml b/feature/penalties/src/main/res/layout/fragment_recent_penalty_list.xml index 6baf5dfa5..d3e3edcb2 100644 --- a/feature/penalties/src/main/res/layout/fragment_recent_penalty_list.xml +++ b/feature/penalties/src/main/res/layout/fragment_recent_penalty_list.xml @@ -16,7 +16,6 @@ app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" app:more_title="@string/read_more" - app:is_expanded="false" app:visibility_target_view="@id/penaltyList" /> diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d141897bf..2a2406743 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -146,9 +146,10 @@ androidx-navigation-dynamic = { group = "androidx.navigation", name = "navigatio androidx-work-ktx = { group = "androidx.work", name = "work-runtime-ktx", version.ref = "androidxWork" } androidx-work-testingAndroidTestImplementation = { group = "androidx.work", name = "work-testing", version.ref = "androidxWork" } -androidx-room-compileKapt = { group = "androidx.room", name = "room-compiler", version.ref = "androidxRoom" } +androidx-room-compileKsp = { group = "androidx.room", name = "room-compiler", version.ref = "androidxRoom" } androidx-room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "androidxRoom" } androidx-room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "androidxRoom" } +androidx-room-paging = { module = "androidx.room:room-paging", version.ref = "androidxRoom" } androidx-recyclerview = { group = "androidx.recyclerview", name = "recyclerview", version.ref = "androidxRecyclerview" } androidx-concurrentfutures = { module = "androidx.concurrent:concurrent-futures-ktx", version.ref = "androidxConcurrentFutures" } @@ -278,7 +279,7 @@ firebases = ["firebase-bom", "firebase-crashlytics"] hilts = ["androidx-hilt-navigation-compose", "androidx-hilt-android", "androidx-hilt-navigation-fragment", "androidx-hilt-work", "androidx-hilt-navigation-compose"] -rooms = ["androidx-room-ktx", "androidx-room-runtime"] +rooms = ["androidx-room-ktx", "androidx-room-runtime", "androidx-room-paging"] dataStores = ["androidx-datastore-preferences", "androidx-datastore"] diff --git a/settings.gradle.kts b/settings.gradle.kts index 0dfb6bde6..c63ff9d05 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -39,4 +39,5 @@ include(":feature:setting") include(":feature:medicine") include(":feature:news") -include(":feature:camera") \ No newline at end of file +include(":feature:camera") +include(":core:database") \ No newline at end of file