From 0ac9b1b4690fb58429b9ee42f2caf5b9a51905d9 Mon Sep 17 00:00:00 2001 From: WorldOneTop Date: Sat, 10 Jun 2023 16:54:34 +0900 Subject: [PATCH 1/5] =?UTF-8?q?[2023.06.10.=EC=9D=B4=EC=A0=9C=EC=9D=BC]=20?= =?UTF-8?q?1.=20gradle=20implement=20to=20paging=20source=202.=20gallery?= =?UTF-8?q?=20data=20source=20di=20injection=203.=20create=20gallery=20ima?= =?UTF-8?q?ge=20model=204.=20create=20gallery=20repository=20and=20data=20?= =?UTF-8?q?source?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- data/build.gradle | 4 + .../repository/LocalDataSourceModule.kt | 6 ++ .../di/module/repository/RepositoryModule.kt | 6 ++ .../diningcoach/data/extensions/Extensions.kt | 17 +++- .../diningcoach/data/model/GalleryModel.kt | 15 +++ .../gallery/GalleryRepositoryImpl.kt | 38 ++++++++ .../gallery/local/GalleryLocalDataSource.kt | 7 ++ .../local/GalleryLocalDataSourceImpl.kt | 91 +++++++++++++++++++ 8 files changed, 183 insertions(+), 1 deletion(-) create mode 100644 data/src/main/java/com/diningcoach/data/model/GalleryModel.kt create mode 100644 data/src/main/java/com/diningcoach/data/repository/gallery/GalleryRepositoryImpl.kt create mode 100644 data/src/main/java/com/diningcoach/data/repository/gallery/local/GalleryLocalDataSource.kt create mode 100644 data/src/main/java/com/diningcoach/data/repository/gallery/local/GalleryLocalDataSourceImpl.kt diff --git a/data/build.gradle b/data/build.gradle index 23b7817..b95cb59 100644 --- a/data/build.gradle +++ b/data/build.gradle @@ -38,6 +38,7 @@ dependencies { def retrofitVersion = "2.9.0" def hiltVersion = "2.45" + def pagingVersion = "3.1.1" // Retrofit2 implementation "com.squareup.retrofit2:retrofit:$retrofitVersion" @@ -51,4 +52,7 @@ dependencies { implementation "com.google.dagger:hilt-android:$hiltVersion" kapt "com.google.dagger:hilt-compiler:$hiltVersion" kapt "androidx.hilt:hilt-compiler:1.0.0" + + // paging + implementation "androidx.paging:paging-runtime-ktx:$pagingVersion" } \ No newline at end of file diff --git a/data/src/main/java/com/diningcoach/data/di/module/repository/LocalDataSourceModule.kt b/data/src/main/java/com/diningcoach/data/di/module/repository/LocalDataSourceModule.kt index 9f4be9b..1050811 100644 --- a/data/src/main/java/com/diningcoach/data/di/module/repository/LocalDataSourceModule.kt +++ b/data/src/main/java/com/diningcoach/data/di/module/repository/LocalDataSourceModule.kt @@ -1,5 +1,7 @@ package com.diningcoach.data.di.module.repository +import com.diningcoach.data.repository.gallery.local.GalleryLocalDataSource +import com.diningcoach.data.repository.gallery.local.GalleryLocalDataSourceImpl import com.diningcoach.data.repository.user.local.UserLocalDataSource import com.diningcoach.data.repository.user.local.UserLocalDataSourceImpl import dagger.Binds @@ -15,4 +17,8 @@ interface LocalDataSourceModule { @Singleton @Binds fun bindsUserLocalDataSource(implements: UserLocalDataSourceImpl): UserLocalDataSource + + @Binds + fun provideGalleryLocalDataSource(implements: GalleryLocalDataSourceImpl): GalleryLocalDataSource + } \ No newline at end of file diff --git a/data/src/main/java/com/diningcoach/data/di/module/repository/RepositoryModule.kt b/data/src/main/java/com/diningcoach/data/di/module/repository/RepositoryModule.kt index cb17815..9810caa 100644 --- a/data/src/main/java/com/diningcoach/data/di/module/repository/RepositoryModule.kt +++ b/data/src/main/java/com/diningcoach/data/di/module/repository/RepositoryModule.kt @@ -1,6 +1,8 @@ package com.diningcoach.data.di.module.repository +import com.diningcoach.data.repository.gallery.GalleryRepositoryImpl import com.diningcoach.data.repository.user.UserRepositoryImpl +import com.diningcoach.domain.repository.GalleryRepository import com.diningcoach.domain.repository.UserRepository import dagger.Binds import dagger.Module @@ -15,4 +17,8 @@ interface RepositoryModule { @Singleton @Binds fun bindsUserRepository(implements: UserRepositoryImpl): UserRepository + + @Singleton + @Binds + fun bindsGalleryRepository(implements: GalleryRepositoryImpl): GalleryRepository } \ No newline at end of file diff --git a/data/src/main/java/com/diningcoach/data/extensions/Extensions.kt b/data/src/main/java/com/diningcoach/data/extensions/Extensions.kt index 5d3c76b..68dd031 100644 --- a/data/src/main/java/com/diningcoach/data/extensions/Extensions.kt +++ b/data/src/main/java/com/diningcoach/data/extensions/Extensions.kt @@ -1,10 +1,25 @@ package com.diningcoach.data.extensions +import com.diningcoach.data.model.GalleryModel import com.diningcoach.data.model.UserModel +import com.diningcoach.domain.model.Gallery import com.diningcoach.domain.model.User +import java.util.* fun UserModel.toUser(): User { return User( - id, accessToken, refreshToken + id, accessToken, refreshToken ) } + +fun GalleryModel.toGallery() = Gallery( + uri, + name, + fullName, + mimeType, + Date(addedDate), + folder, + size, + width, + height, +) diff --git a/data/src/main/java/com/diningcoach/data/model/GalleryModel.kt b/data/src/main/java/com/diningcoach/data/model/GalleryModel.kt new file mode 100644 index 0000000..130c5d6 --- /dev/null +++ b/data/src/main/java/com/diningcoach/data/model/GalleryModel.kt @@ -0,0 +1,15 @@ +package com.diningcoach.data.model + +import android.net.Uri + +data class GalleryModel( + val uri: Uri, + val name: String, + val fullName: String, + val mimeType: String, + val addedDate: Long, + val folder: String, + val size: Long, + val width: Int, + val height: Int, +) diff --git a/data/src/main/java/com/diningcoach/data/repository/gallery/GalleryRepositoryImpl.kt b/data/src/main/java/com/diningcoach/data/repository/gallery/GalleryRepositoryImpl.kt new file mode 100644 index 0000000..9a36e14 --- /dev/null +++ b/data/src/main/java/com/diningcoach/data/repository/gallery/GalleryRepositoryImpl.kt @@ -0,0 +1,38 @@ +package com.diningcoach.data.repository.gallery + +import androidx.paging.PagingSource +import androidx.paging.PagingState +import com.diningcoach.data.extensions.toGallery +import com.diningcoach.data.repository.gallery.local.GalleryLocalDataSource +import com.diningcoach.domain.model.Gallery +import com.diningcoach.domain.repository.GalleryRepository +import javax.inject.Inject + +class GalleryRepositoryImpl @Inject constructor( + private val galleryLocalDataSource: GalleryLocalDataSource, +): GalleryRepository { + override fun getGalleryImagePagingSource():PagingSource { + return object: PagingSource() { + override fun getRefreshKey(state: PagingState): Int? { + return state.anchorPosition?.let { anchorPosition -> + val anchorPage = state.closestPageToPosition(anchorPosition) + anchorPage?.prevKey?.plus(1) ?: anchorPage?.nextKey?.minus(1) + } + } + override suspend fun load(params: LoadParams): LoadResult { + try { + val pageNumber = params.key ?: 0 + val response = galleryLocalDataSource.fetchGalleryImages(params.loadSize, pageNumber*params.loadSize) + return LoadResult.Page( + data = response.map { it.toGallery() }, + prevKey = if(pageNumber==0) null else pageNumber-1, + nextKey = if(response.isEmpty()) null else pageNumber+1 + ) + } catch (e: Exception) { + // TODO("error process") + throw e + } + } + } + } +} \ No newline at end of file diff --git a/data/src/main/java/com/diningcoach/data/repository/gallery/local/GalleryLocalDataSource.kt b/data/src/main/java/com/diningcoach/data/repository/gallery/local/GalleryLocalDataSource.kt new file mode 100644 index 0000000..ba33a20 --- /dev/null +++ b/data/src/main/java/com/diningcoach/data/repository/gallery/local/GalleryLocalDataSource.kt @@ -0,0 +1,7 @@ +package com.diningcoach.data.repository.gallery.local + +import com.diningcoach.data.model.GalleryModel + +interface GalleryLocalDataSource { + fun fetchGalleryImages(limit: Int, offset: Int): List +} \ No newline at end of file diff --git a/data/src/main/java/com/diningcoach/data/repository/gallery/local/GalleryLocalDataSourceImpl.kt b/data/src/main/java/com/diningcoach/data/repository/gallery/local/GalleryLocalDataSourceImpl.kt new file mode 100644 index 0000000..1f36422 --- /dev/null +++ b/data/src/main/java/com/diningcoach/data/repository/gallery/local/GalleryLocalDataSourceImpl.kt @@ -0,0 +1,91 @@ +package com.diningcoach.data.repository.gallery.local + +import android.content.ContentResolver +import android.content.Context +import android.net.Uri +import android.os.Build +import android.os.Bundle +import android.provider.MediaStore.Images.Media +import com.diningcoach.data.model.GalleryModel +import dagger.hilt.android.qualifiers.ApplicationContext +import javax.inject.Inject + +class GalleryLocalDataSourceImpl @Inject constructor( + @ApplicationContext context: Context +) : GalleryLocalDataSource { + private val contentResolver = context.contentResolver + + override fun fetchGalleryImages(limit: Int, offset: Int): List { + val contentUri = Media.EXTERNAL_CONTENT_URI + val projection = arrayOf( + Media._ID, + Media.TITLE, + Media.DISPLAY_NAME, + Media.MIME_TYPE, + Media.DATE_TAKEN, + Media.BUCKET_DISPLAY_NAME, + Media.SIZE, + Media.WIDTH, + Media.HEIGHT, + ) + val galleryImage = mutableListOf() + val selection = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) Media.SIZE + " > 0" + else null + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + contentResolver.query( + contentUri, + projection, + Bundle().apply { + // Limit & Offset + putInt(ContentResolver.QUERY_ARG_LIMIT, limit) + putInt(ContentResolver.QUERY_ARG_OFFSET, offset) + + // Sort function + putStringArray( + ContentResolver.QUERY_ARG_SORT_COLUMNS, + arrayOf(Media.DATE_TAKEN) + ) + putInt( + ContentResolver.QUERY_ARG_SORT_DIRECTION, + ContentResolver.QUERY_SORT_DIRECTION_DESCENDING + ) + + // Selection + putString(ContentResolver.QUERY_ARG_SQL_SELECTION, selection) + }, null + ) + } else { + val sortOrder = + "${Media.DATE_TAKEN} DESC, ${Media._ID} DESC LIMIT $limit OFFSET $offset" + contentResolver.query( + contentUri, + projection, + selection, + null, + sortOrder + ) + }?.use { cursor -> + while (cursor.moveToNext()) { + galleryImage.add( + GalleryModel( + uri = Uri.withAppendedPath( + contentUri, + cursor.getLong(cursor.getColumnIndexOrThrow(Media._ID)).toString() + ), + name = cursor.getString(cursor.getColumnIndexOrThrow(Media.TITLE)), + fullName = cursor.getString(cursor.getColumnIndexOrThrow(Media.DISPLAY_NAME)), + mimeType = cursor.getString(cursor.getColumnIndexOrThrow(Media.MIME_TYPE)), + addedDate = cursor.getLong(cursor.getColumnIndexOrThrow(Media.DATE_TAKEN)), + folder = cursor.getString(cursor.getColumnIndexOrThrow(Media.BUCKET_DISPLAY_NAME)), + size = cursor.getLong(cursor.getColumnIndexOrThrow(Media.SIZE)), + width = cursor.getInt(cursor.getColumnIndexOrThrow(Media.WIDTH)), + height = cursor.getInt(cursor.getColumnIndexOrThrow(Media.HEIGHT)), + ) + ) + } + } + return galleryImage + } +} \ No newline at end of file From bba5eae27c54450a970e1317239df68ed96a74dd Mon Sep 17 00:00:00 2001 From: WorldOneTop Date: Sat, 10 Jun 2023 16:55:25 +0900 Subject: [PATCH 2/5] =?UTF-8?q?[2023.06.10.=EC=9D=B4=EC=A0=9C=EC=9D=BC]=20?= =?UTF-8?q?1.=20=EA=B0=A4=EB=9F=AC=EB=A6=AC=20=EB=8F=84=EB=A9=94=EC=9D=B8?= =?UTF-8?q?=20=EB=A0=88=EC=9D=B4=EC=96=B4=20=EC=99=84=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- domain/build.gradle | 5 +++++ .../java/com/diningcoach/domain/model/Gallery.kt | 16 ++++++++++++++++ .../domain/repository/GalleryRepository.kt | 8 ++++++++ .../usecase/gallery/GalleryImageFetchUseCase.kt | 12 ++++++++++++ 4 files changed, 41 insertions(+) create mode 100644 domain/src/main/java/com/diningcoach/domain/model/Gallery.kt create mode 100644 domain/src/main/java/com/diningcoach/domain/repository/GalleryRepository.kt create mode 100644 domain/src/main/java/com/diningcoach/domain/usecase/gallery/GalleryImageFetchUseCase.kt diff --git a/domain/build.gradle b/domain/build.gradle index 4acb4ca..e3b9830 100644 --- a/domain/build.gradle +++ b/domain/build.gradle @@ -37,6 +37,7 @@ dependencies { def lifecycleVersion = "2.6.1" def retrofitVersion = "2.9.0" def hiltVersion = "2.45" + def pagingVersion = "3.1.1" // Coroutine implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutineVersion" @@ -61,4 +62,8 @@ dependencies { implementation "com.squareup.retrofit2:converter-gson:$retrofitVersion" // Retrofit2 - log implementation "com.squareup.okhttp3:logging-interceptor:4.9.0" + + // paging + implementation "androidx.paging:paging-common:$pagingVersion" + } \ No newline at end of file diff --git a/domain/src/main/java/com/diningcoach/domain/model/Gallery.kt b/domain/src/main/java/com/diningcoach/domain/model/Gallery.kt new file mode 100644 index 0000000..3320749 --- /dev/null +++ b/domain/src/main/java/com/diningcoach/domain/model/Gallery.kt @@ -0,0 +1,16 @@ +package com.diningcoach.domain.model + +import android.net.Uri +import java.util.Date + +data class Gallery( + val uri: Uri, + val name: String, + val fullName: String, + val mimeType: String, + val addedDate: Date, + val folder: String, + val size: Long, + val width: Int, + val height: Int, +) diff --git a/domain/src/main/java/com/diningcoach/domain/repository/GalleryRepository.kt b/domain/src/main/java/com/diningcoach/domain/repository/GalleryRepository.kt new file mode 100644 index 0000000..d823a1e --- /dev/null +++ b/domain/src/main/java/com/diningcoach/domain/repository/GalleryRepository.kt @@ -0,0 +1,8 @@ +package com.diningcoach.domain.repository + +import androidx.paging.PagingSource +import com.diningcoach.domain.model.Gallery + +interface GalleryRepository { + fun getGalleryImagePagingSource(): PagingSource +} \ No newline at end of file diff --git a/domain/src/main/java/com/diningcoach/domain/usecase/gallery/GalleryImageFetchUseCase.kt b/domain/src/main/java/com/diningcoach/domain/usecase/gallery/GalleryImageFetchUseCase.kt new file mode 100644 index 0000000..0e57bc5 --- /dev/null +++ b/domain/src/main/java/com/diningcoach/domain/usecase/gallery/GalleryImageFetchUseCase.kt @@ -0,0 +1,12 @@ +package com.diningcoach.domain.usecase.gallery + +import androidx.paging.PagingSource +import com.diningcoach.domain.model.Gallery +import com.diningcoach.domain.repository.GalleryRepository +import javax.inject.Inject + +class GalleryImageFetchUseCase @Inject constructor( + private val repository: GalleryRepository +) { + operator fun invoke(): PagingSource =repository.getGalleryImagePagingSource() +} \ No newline at end of file From a320de3cdc4e52ca8dc085a3aae985c864482707 Mon Sep 17 00:00:00 2001 From: WorldOneTop Date: Sat, 10 Jun 2023 16:58:53 +0900 Subject: [PATCH 3/5] =?UTF-8?q?[2023.06.10.=EC=9D=B4=EC=A0=9C=EC=9D=BC]=20?= =?UTF-8?q?1.=20gradle=20implement=20to=20Paging=20source=202.=20=EA=B8=B0?= =?UTF-8?q?=EB=B3=B8=EC=A0=81=EC=9D=B8=20=EA=B0=A4=EB=9F=AC=EB=A6=AC=20?= =?UTF-8?q?=ED=99=94=EB=A9=B4=20=EA=B5=AC=EC=84=B1=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle | 5 ++ app/src/main/AndroidManifest.xml | 4 ++ .../coach/ui/gallery/GalleryActivity.kt | 66 +++++++++++++++++++ .../ui/gallery/GalleryRecyclerViewAdapter.kt | 54 +++++++++++++++ .../coach/ui/gallery/GalleryViewModel.kt | 23 +++++++ app/src/main/res/layout/activity_gallery.xml | 16 +++++ app/src/main/res/layout/row_gallery_image.xml | 15 +++++ 7 files changed, 183 insertions(+) create mode 100644 app/src/main/java/com/dining/coach/ui/gallery/GalleryActivity.kt create mode 100644 app/src/main/java/com/dining/coach/ui/gallery/GalleryRecyclerViewAdapter.kt create mode 100644 app/src/main/java/com/dining/coach/ui/gallery/GalleryViewModel.kt create mode 100644 app/src/main/res/layout/activity_gallery.xml create mode 100644 app/src/main/res/layout/row_gallery_image.xml diff --git a/app/build.gradle b/app/build.gradle index 0ab674e..b757107 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -52,6 +52,7 @@ dependencies { def pagingVersion = "3.1.1" def navigationVersion= "2.5.3" def retrofitVersion = "2.9.0" + def glideVersion = "4.13.2" implementation 'androidx.core:core-ktx:1.10.1' implementation 'androidx.appcompat:appcompat:1.6.1' @@ -96,4 +97,8 @@ dependencies { // logging implementation "com.squareup.okhttp3:logging-interceptor:4.9.0" + // glide + implementation "com.github.bumptech.glide:glide:$glideVersion" + annotationProcessor "com.github.bumptech.glide:compiler:$glideVersion" + } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 6e421d9..25bfe0a 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -52,6 +52,10 @@ + + \ No newline at end of file diff --git a/app/src/main/java/com/dining/coach/ui/gallery/GalleryActivity.kt b/app/src/main/java/com/dining/coach/ui/gallery/GalleryActivity.kt new file mode 100644 index 0000000..f48b033 --- /dev/null +++ b/app/src/main/java/com/dining/coach/ui/gallery/GalleryActivity.kt @@ -0,0 +1,66 @@ +package com.dining.coach.ui.gallery + +import android.Manifest +import android.content.pm.PackageManager +import android.os.Bundle +import android.widget.Toast +import androidx.activity.result.contract.ActivityResultContracts +import androidx.activity.viewModels +import androidx.core.content.ContextCompat +import androidx.lifecycle.lifecycleScope +import com.dining.coach.R +import com.dining.coach.base.BaseActivity +import com.dining.coach.databinding.ActivityGalleryBinding +import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.launch + +@AndroidEntryPoint +class GalleryActivity : BaseActivity(R.layout.activity_gallery) { + private val viewModel: GalleryViewModel by viewModels() + private val requiredPermissions = arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE) + private lateinit var galleryRecyclerViewAdapter:GalleryRecyclerViewAdapter + + override fun createActivity() = viewModel + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + setRecyclerView() + } + private fun setRecyclerView(){ + wrapGridRecyclerView(bind.galleryRv, 4) + galleryRecyclerViewAdapter = GalleryRecyclerViewAdapter() + bind.galleryRv.adapter = galleryRecyclerViewAdapter + + fetchGalleryAdapter() + } + + private fun fetchGalleryAdapter(){ + if (hasAllPermissions()) { + lifecycleScope.launch { + viewModel.galleryPager.collectLatest { pagingData -> + galleryRecyclerViewAdapter.submitData(pagingData) + } + } + } else { + registerForActivityResult( + ActivityResultContracts.RequestMultiplePermissions() + ) { isGranted -> + if (isGranted.all { it.value }) { + fetchGalleryAdapter() + } else { + // TODO("replace permission denied process") + Toast.makeText(this, "permission error", Toast.LENGTH_SHORT).show() + finish() + } + }.launch(requiredPermissions) + } + } + + + private fun hasAllPermissions() = requiredPermissions.all { + ContextCompat.checkSelfPermission(this, it) == PackageManager.PERMISSION_GRANTED + } + +} diff --git a/app/src/main/java/com/dining/coach/ui/gallery/GalleryRecyclerViewAdapter.kt b/app/src/main/java/com/dining/coach/ui/gallery/GalleryRecyclerViewAdapter.kt new file mode 100644 index 0000000..8b7362e --- /dev/null +++ b/app/src/main/java/com/dining/coach/ui/gallery/GalleryRecyclerViewAdapter.kt @@ -0,0 +1,54 @@ +package com.dining.coach.ui.gallery + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.paging.PagingDataAdapter +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.RecyclerView +import com.bumptech.glide.Glide +import com.dining.coach.databinding.RowGalleryImageBinding +import com.diningcoach.domain.model.Gallery + +class GalleryRecyclerViewAdapter : + PagingDataAdapter(diffCallback) { + + companion object { + val diffCallback = object : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: Gallery, newItem: Gallery) = + oldItem.uri == newItem.uri + + override fun areContentsTheSame(oldItem: Gallery, newItem: Gallery) = + oldItem == newItem + } + } + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ): ImageViewHolder = ImageViewHolder( + RowGalleryImageBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ) + ) + + override fun onBindViewHolder(holder: ImageViewHolder, position: Int) { + val item = getItem(position) + item?.let { + holder.bind(it) + } + } + + inner class ImageViewHolder( + private val binding: RowGalleryImageBinding + ) : RecyclerView.ViewHolder(binding.root) { + fun bind(item: Gallery) { + binding.run { + Glide.with(binding.root.context) + .load(item.uri) + .into(binding.rowGalleryImageView) + } + } + } +} diff --git a/app/src/main/java/com/dining/coach/ui/gallery/GalleryViewModel.kt b/app/src/main/java/com/dining/coach/ui/gallery/GalleryViewModel.kt new file mode 100644 index 0000000..69b5fd9 --- /dev/null +++ b/app/src/main/java/com/dining/coach/ui/gallery/GalleryViewModel.kt @@ -0,0 +1,23 @@ +package com.dining.coach.ui.gallery + +import androidx.lifecycle.viewModelScope +import androidx.paging.Pager +import androidx.paging.PagingConfig +import androidx.paging.cachedIn +import com.dining.coach.base.BaseViewModel +import com.diningcoach.domain.usecase.gallery.GalleryImageFetchUseCase +import dagger.hilt.android.lifecycle.HiltViewModel +import javax.inject.Inject + +@HiltViewModel +class GalleryViewModel @Inject constructor( + private val galleryImageFetch: GalleryImageFetchUseCase +): BaseViewModel() { + val galleryPager = Pager( + config = PagingConfig(pageSize = 50) + ) { + galleryImageFetch() + }.flow.cachedIn(viewModelScope) + + +} \ No newline at end of file diff --git a/app/src/main/res/layout/activity_gallery.xml b/app/src/main/res/layout/activity_gallery.xml new file mode 100644 index 0000000..853885c --- /dev/null +++ b/app/src/main/res/layout/activity_gallery.xml @@ -0,0 +1,16 @@ + + + + + + + + diff --git a/app/src/main/res/layout/row_gallery_image.xml b/app/src/main/res/layout/row_gallery_image.xml new file mode 100644 index 0000000..bc38bb4 --- /dev/null +++ b/app/src/main/res/layout/row_gallery_image.xml @@ -0,0 +1,15 @@ + + + + + + + \ No newline at end of file From de6fab67a29d90d6990ae11d9e1e6eb6a3f27a57 Mon Sep 17 00:00:00 2001 From: WorldOneTop Date: Wed, 21 Jun 2023 04:50:17 +0900 Subject: [PATCH 4/5] =?UTF-8?q?[2023.06.21.=EC=9D=B4=EC=A0=9C=EC=9D=BC]=20?= =?UTF-8?q?1.=20=EB=AA=A8=EB=8D=B8=EB=AA=85=20=EB=B3=80=EA=B2=BD=20gallery?= =?UTF-8?q?=20->=20photo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../coach/ui/gallery/GalleryRecyclerViewAdapter.kt | 12 ++++++------ .../com/diningcoach/data/extensions/Extensions.kt | 6 +++--- .../data/model/{GalleryModel.kt => PhotoModel.kt} | 2 +- .../repository/gallery/GalleryRepositoryImpl.kt | 14 +++++++------- .../gallery/local/GalleryLocalDataSource.kt | 4 ++-- .../gallery/local/GalleryLocalDataSourceImpl.kt | 8 ++++---- .../domain/model/{Gallery.kt => Photo.kt} | 2 +- .../domain/repository/GalleryRepository.kt | 4 ++-- .../usecase/gallery/GalleryImageFetchUseCase.kt | 4 ++-- 9 files changed, 28 insertions(+), 28 deletions(-) rename data/src/main/java/com/diningcoach/data/model/{GalleryModel.kt => PhotoModel.kt} (91%) rename domain/src/main/java/com/diningcoach/domain/model/{Gallery.kt => Photo.kt} (93%) diff --git a/app/src/main/java/com/dining/coach/ui/gallery/GalleryRecyclerViewAdapter.kt b/app/src/main/java/com/dining/coach/ui/gallery/GalleryRecyclerViewAdapter.kt index 8b7362e..86f48da 100644 --- a/app/src/main/java/com/dining/coach/ui/gallery/GalleryRecyclerViewAdapter.kt +++ b/app/src/main/java/com/dining/coach/ui/gallery/GalleryRecyclerViewAdapter.kt @@ -7,17 +7,17 @@ import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.RecyclerView import com.bumptech.glide.Glide import com.dining.coach.databinding.RowGalleryImageBinding -import com.diningcoach.domain.model.Gallery +import com.diningcoach.domain.model.Photo class GalleryRecyclerViewAdapter : - PagingDataAdapter(diffCallback) { + PagingDataAdapter(diffCallback) { companion object { - val diffCallback = object : DiffUtil.ItemCallback() { - override fun areItemsTheSame(oldItem: Gallery, newItem: Gallery) = + val diffCallback = object : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: Photo, newItem: Photo) = oldItem.uri == newItem.uri - override fun areContentsTheSame(oldItem: Gallery, newItem: Gallery) = + override fun areContentsTheSame(oldItem: Photo, newItem: Photo) = oldItem == newItem } } @@ -43,7 +43,7 @@ class GalleryRecyclerViewAdapter : inner class ImageViewHolder( private val binding: RowGalleryImageBinding ) : RecyclerView.ViewHolder(binding.root) { - fun bind(item: Gallery) { + fun bind(item: Photo) { binding.run { Glide.with(binding.root.context) .load(item.uri) diff --git a/data/src/main/java/com/diningcoach/data/extensions/Extensions.kt b/data/src/main/java/com/diningcoach/data/extensions/Extensions.kt index 68dd031..5fa1606 100644 --- a/data/src/main/java/com/diningcoach/data/extensions/Extensions.kt +++ b/data/src/main/java/com/diningcoach/data/extensions/Extensions.kt @@ -1,8 +1,8 @@ package com.diningcoach.data.extensions -import com.diningcoach.data.model.GalleryModel +import com.diningcoach.data.model.PhotoModel import com.diningcoach.data.model.UserModel -import com.diningcoach.domain.model.Gallery +import com.diningcoach.domain.model.Photo import com.diningcoach.domain.model.User import java.util.* @@ -12,7 +12,7 @@ fun UserModel.toUser(): User { ) } -fun GalleryModel.toGallery() = Gallery( +fun PhotoModel.toPhoto() = Photo( uri, name, fullName, diff --git a/data/src/main/java/com/diningcoach/data/model/GalleryModel.kt b/data/src/main/java/com/diningcoach/data/model/PhotoModel.kt similarity index 91% rename from data/src/main/java/com/diningcoach/data/model/GalleryModel.kt rename to data/src/main/java/com/diningcoach/data/model/PhotoModel.kt index 130c5d6..4ff5506 100644 --- a/data/src/main/java/com/diningcoach/data/model/GalleryModel.kt +++ b/data/src/main/java/com/diningcoach/data/model/PhotoModel.kt @@ -2,7 +2,7 @@ package com.diningcoach.data.model import android.net.Uri -data class GalleryModel( +data class PhotoModel( val uri: Uri, val name: String, val fullName: String, diff --git a/data/src/main/java/com/diningcoach/data/repository/gallery/GalleryRepositoryImpl.kt b/data/src/main/java/com/diningcoach/data/repository/gallery/GalleryRepositoryImpl.kt index 9a36e14..0b1de67 100644 --- a/data/src/main/java/com/diningcoach/data/repository/gallery/GalleryRepositoryImpl.kt +++ b/data/src/main/java/com/diningcoach/data/repository/gallery/GalleryRepositoryImpl.kt @@ -2,29 +2,29 @@ package com.diningcoach.data.repository.gallery import androidx.paging.PagingSource import androidx.paging.PagingState -import com.diningcoach.data.extensions.toGallery +import com.diningcoach.data.extensions.toPhoto import com.diningcoach.data.repository.gallery.local.GalleryLocalDataSource -import com.diningcoach.domain.model.Gallery +import com.diningcoach.domain.model.Photo import com.diningcoach.domain.repository.GalleryRepository import javax.inject.Inject class GalleryRepositoryImpl @Inject constructor( private val galleryLocalDataSource: GalleryLocalDataSource, ): GalleryRepository { - override fun getGalleryImagePagingSource():PagingSource { - return object: PagingSource() { - override fun getRefreshKey(state: PagingState): Int? { + override fun getGalleryImagePagingSource():PagingSource { + return object: PagingSource() { + override fun getRefreshKey(state: PagingState): Int? { return state.anchorPosition?.let { anchorPosition -> val anchorPage = state.closestPageToPosition(anchorPosition) anchorPage?.prevKey?.plus(1) ?: anchorPage?.nextKey?.minus(1) } } - override suspend fun load(params: LoadParams): LoadResult { + override suspend fun load(params: LoadParams): LoadResult { try { val pageNumber = params.key ?: 0 val response = galleryLocalDataSource.fetchGalleryImages(params.loadSize, pageNumber*params.loadSize) return LoadResult.Page( - data = response.map { it.toGallery() }, + data = response.map { it.toPhoto() }, prevKey = if(pageNumber==0) null else pageNumber-1, nextKey = if(response.isEmpty()) null else pageNumber+1 ) diff --git a/data/src/main/java/com/diningcoach/data/repository/gallery/local/GalleryLocalDataSource.kt b/data/src/main/java/com/diningcoach/data/repository/gallery/local/GalleryLocalDataSource.kt index ba33a20..e8158f8 100644 --- a/data/src/main/java/com/diningcoach/data/repository/gallery/local/GalleryLocalDataSource.kt +++ b/data/src/main/java/com/diningcoach/data/repository/gallery/local/GalleryLocalDataSource.kt @@ -1,7 +1,7 @@ package com.diningcoach.data.repository.gallery.local -import com.diningcoach.data.model.GalleryModel +import com.diningcoach.data.model.PhotoModel interface GalleryLocalDataSource { - fun fetchGalleryImages(limit: Int, offset: Int): List + fun fetchGalleryImages(limit: Int, offset: Int): List } \ No newline at end of file diff --git a/data/src/main/java/com/diningcoach/data/repository/gallery/local/GalleryLocalDataSourceImpl.kt b/data/src/main/java/com/diningcoach/data/repository/gallery/local/GalleryLocalDataSourceImpl.kt index 1f36422..9aee800 100644 --- a/data/src/main/java/com/diningcoach/data/repository/gallery/local/GalleryLocalDataSourceImpl.kt +++ b/data/src/main/java/com/diningcoach/data/repository/gallery/local/GalleryLocalDataSourceImpl.kt @@ -6,7 +6,7 @@ import android.net.Uri import android.os.Build import android.os.Bundle import android.provider.MediaStore.Images.Media -import com.diningcoach.data.model.GalleryModel +import com.diningcoach.data.model.PhotoModel import dagger.hilt.android.qualifiers.ApplicationContext import javax.inject.Inject @@ -15,7 +15,7 @@ class GalleryLocalDataSourceImpl @Inject constructor( ) : GalleryLocalDataSource { private val contentResolver = context.contentResolver - override fun fetchGalleryImages(limit: Int, offset: Int): List { + override fun fetchGalleryImages(limit: Int, offset: Int): List { val contentUri = Media.EXTERNAL_CONTENT_URI val projection = arrayOf( Media._ID, @@ -28,7 +28,7 @@ class GalleryLocalDataSourceImpl @Inject constructor( Media.WIDTH, Media.HEIGHT, ) - val galleryImage = mutableListOf() + val galleryImage = mutableListOf() val selection = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) Media.SIZE + " > 0" else null @@ -69,7 +69,7 @@ class GalleryLocalDataSourceImpl @Inject constructor( }?.use { cursor -> while (cursor.moveToNext()) { galleryImage.add( - GalleryModel( + PhotoModel( uri = Uri.withAppendedPath( contentUri, cursor.getLong(cursor.getColumnIndexOrThrow(Media._ID)).toString() diff --git a/domain/src/main/java/com/diningcoach/domain/model/Gallery.kt b/domain/src/main/java/com/diningcoach/domain/model/Photo.kt similarity index 93% rename from domain/src/main/java/com/diningcoach/domain/model/Gallery.kt rename to domain/src/main/java/com/diningcoach/domain/model/Photo.kt index 3320749..b5fcb84 100644 --- a/domain/src/main/java/com/diningcoach/domain/model/Gallery.kt +++ b/domain/src/main/java/com/diningcoach/domain/model/Photo.kt @@ -3,7 +3,7 @@ package com.diningcoach.domain.model import android.net.Uri import java.util.Date -data class Gallery( +data class Photo( val uri: Uri, val name: String, val fullName: String, diff --git a/domain/src/main/java/com/diningcoach/domain/repository/GalleryRepository.kt b/domain/src/main/java/com/diningcoach/domain/repository/GalleryRepository.kt index d823a1e..c1b5440 100644 --- a/domain/src/main/java/com/diningcoach/domain/repository/GalleryRepository.kt +++ b/domain/src/main/java/com/diningcoach/domain/repository/GalleryRepository.kt @@ -1,8 +1,8 @@ package com.diningcoach.domain.repository import androidx.paging.PagingSource -import com.diningcoach.domain.model.Gallery +import com.diningcoach.domain.model.Photo interface GalleryRepository { - fun getGalleryImagePagingSource(): PagingSource + fun getGalleryImagePagingSource(): PagingSource } \ No newline at end of file diff --git a/domain/src/main/java/com/diningcoach/domain/usecase/gallery/GalleryImageFetchUseCase.kt b/domain/src/main/java/com/diningcoach/domain/usecase/gallery/GalleryImageFetchUseCase.kt index 0e57bc5..60dd804 100644 --- a/domain/src/main/java/com/diningcoach/domain/usecase/gallery/GalleryImageFetchUseCase.kt +++ b/domain/src/main/java/com/diningcoach/domain/usecase/gallery/GalleryImageFetchUseCase.kt @@ -1,12 +1,12 @@ package com.diningcoach.domain.usecase.gallery import androidx.paging.PagingSource -import com.diningcoach.domain.model.Gallery +import com.diningcoach.domain.model.Photo import com.diningcoach.domain.repository.GalleryRepository import javax.inject.Inject class GalleryImageFetchUseCase @Inject constructor( private val repository: GalleryRepository ) { - operator fun invoke(): PagingSource =repository.getGalleryImagePagingSource() + operator fun invoke(): PagingSource =repository.getGalleryImagePagingSource() } \ No newline at end of file From 5133a91a72ec32e018e590bae5c02e0525e80b6c Mon Sep 17 00:00:00 2001 From: WorldOneTop Date: Wed, 21 Jun 2023 07:07:03 +0900 Subject: [PATCH 5/5] =?UTF-8?q?[2023.06.21.=EC=9D=B4=EC=A0=9C=EC=9D=BC]=20?= =?UTF-8?q?1.=20=EA=B0=A4=EB=9F=AC=EB=A6=AC=20=EC=9D=B4=EB=AF=B8=EC=A7=80?= =?UTF-8?q?=20=EC=A0=91=EA=B7=BC=20=EB=AA=A8=EB=93=88=ED=99=94=20=EB=B0=8F?= =?UTF-8?q?=20use=20case=20module=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../di/manager/local/MediaStoreManager.kt | 58 +++++++++++++++++++ .../data/di/module/UseCaseModule.kt | 8 +++ .../repository/LocalDataSourceModule.kt | 3 +- .../local/GalleryLocalDataSourceImpl.kt | 48 +-------------- 4 files changed, 71 insertions(+), 46 deletions(-) create mode 100644 data/src/main/java/com/diningcoach/data/di/manager/local/MediaStoreManager.kt diff --git a/data/src/main/java/com/diningcoach/data/di/manager/local/MediaStoreManager.kt b/data/src/main/java/com/diningcoach/data/di/manager/local/MediaStoreManager.kt new file mode 100644 index 0000000..bb8e979 --- /dev/null +++ b/data/src/main/java/com/diningcoach/data/di/manager/local/MediaStoreManager.kt @@ -0,0 +1,58 @@ +package com.diningcoach.data.di.manager.local + +import android.content.ContentResolver +import android.content.Context +import android.database.Cursor +import android.os.Build +import android.os.Bundle +import android.provider.MediaStore +import dagger.hilt.android.qualifiers.ApplicationContext +import javax.inject.Inject + +class MediaStoreManager @Inject constructor( + @ApplicationContext context: Context +) { + private val contentResolver = context.contentResolver + + fun fetchImages(projection:Array, limit: Int, offset: Int): Cursor?{ + val contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI + val selection = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) MediaStore.Images.Media.SIZE + " > 0" + else null + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + return contentResolver.query( + contentUri, + projection, + Bundle().apply { + // Limit & Offset + putInt(ContentResolver.QUERY_ARG_LIMIT, limit) + putInt(ContentResolver.QUERY_ARG_OFFSET, offset) + + // Sort function + putStringArray( + ContentResolver.QUERY_ARG_SORT_COLUMNS, + arrayOf(MediaStore.Images.Media.DATE_TAKEN) + ) + putInt( + ContentResolver.QUERY_ARG_SORT_DIRECTION, + ContentResolver.QUERY_SORT_DIRECTION_DESCENDING + ) + + // Selection + putString(ContentResolver.QUERY_ARG_SQL_SELECTION, selection) + }, null + ) + } else { + val sortOrder = + "${MediaStore.Images.Media.DATE_TAKEN} DESC, ${MediaStore.Images.Media._ID} DESC LIMIT $limit OFFSET $offset" + return contentResolver.query( + contentUri, + projection, + selection, + null, + sortOrder + ) + } + } +} \ No newline at end of file diff --git a/data/src/main/java/com/diningcoach/data/di/module/UseCaseModule.kt b/data/src/main/java/com/diningcoach/data/di/module/UseCaseModule.kt index 855f0e6..507a458 100644 --- a/data/src/main/java/com/diningcoach/data/di/module/UseCaseModule.kt +++ b/data/src/main/java/com/diningcoach/data/di/module/UseCaseModule.kt @@ -1,6 +1,8 @@ package com.diningcoach.data.di.module +import com.diningcoach.domain.repository.GalleryRepository import com.diningcoach.domain.repository.UserRepository +import com.diningcoach.domain.usecase.gallery.GalleryImageFetchUseCase import com.diningcoach.domain.usecase.user.CheckIsLoginUseCase import dagger.Module import dagger.Provides @@ -16,4 +18,10 @@ object UseCaseModule { @Singleton fun provideCheckIsLoginUseCase(repository: UserRepository): CheckIsLoginUseCase = CheckIsLoginUseCase(repository) + + @Provides + @Singleton + fun provideGalleryImageFetchUseCase(repository: GalleryRepository): GalleryImageFetchUseCase = + GalleryImageFetchUseCase(repository) + } \ No newline at end of file diff --git a/data/src/main/java/com/diningcoach/data/di/module/repository/LocalDataSourceModule.kt b/data/src/main/java/com/diningcoach/data/di/module/repository/LocalDataSourceModule.kt index 1050811..c4a45e6 100644 --- a/data/src/main/java/com/diningcoach/data/di/module/repository/LocalDataSourceModule.kt +++ b/data/src/main/java/com/diningcoach/data/di/module/repository/LocalDataSourceModule.kt @@ -18,7 +18,8 @@ interface LocalDataSourceModule { @Binds fun bindsUserLocalDataSource(implements: UserLocalDataSourceImpl): UserLocalDataSource + @Singleton @Binds - fun provideGalleryLocalDataSource(implements: GalleryLocalDataSourceImpl): GalleryLocalDataSource + fun bindsGalleryLocalDataSource(implements: GalleryLocalDataSourceImpl): GalleryLocalDataSource } \ No newline at end of file diff --git a/data/src/main/java/com/diningcoach/data/repository/gallery/local/GalleryLocalDataSourceImpl.kt b/data/src/main/java/com/diningcoach/data/repository/gallery/local/GalleryLocalDataSourceImpl.kt index 9aee800..8e6a789 100644 --- a/data/src/main/java/com/diningcoach/data/repository/gallery/local/GalleryLocalDataSourceImpl.kt +++ b/data/src/main/java/com/diningcoach/data/repository/gallery/local/GalleryLocalDataSourceImpl.kt @@ -1,19 +1,14 @@ package com.diningcoach.data.repository.gallery.local -import android.content.ContentResolver -import android.content.Context import android.net.Uri -import android.os.Build -import android.os.Bundle import android.provider.MediaStore.Images.Media +import com.diningcoach.data.di.manager.local.MediaStoreManager import com.diningcoach.data.model.PhotoModel -import dagger.hilt.android.qualifiers.ApplicationContext import javax.inject.Inject class GalleryLocalDataSourceImpl @Inject constructor( - @ApplicationContext context: Context + private val mediaStoreManager: MediaStoreManager ) : GalleryLocalDataSource { - private val contentResolver = context.contentResolver override fun fetchGalleryImages(limit: Int, offset: Int): List { val contentUri = Media.EXTERNAL_CONTENT_URI @@ -29,44 +24,7 @@ class GalleryLocalDataSourceImpl @Inject constructor( Media.HEIGHT, ) val galleryImage = mutableListOf() - val selection = - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) Media.SIZE + " > 0" - else null - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - contentResolver.query( - contentUri, - projection, - Bundle().apply { - // Limit & Offset - putInt(ContentResolver.QUERY_ARG_LIMIT, limit) - putInt(ContentResolver.QUERY_ARG_OFFSET, offset) - - // Sort function - putStringArray( - ContentResolver.QUERY_ARG_SORT_COLUMNS, - arrayOf(Media.DATE_TAKEN) - ) - putInt( - ContentResolver.QUERY_ARG_SORT_DIRECTION, - ContentResolver.QUERY_SORT_DIRECTION_DESCENDING - ) - - // Selection - putString(ContentResolver.QUERY_ARG_SQL_SELECTION, selection) - }, null - ) - } else { - val sortOrder = - "${Media.DATE_TAKEN} DESC, ${Media._ID} DESC LIMIT $limit OFFSET $offset" - contentResolver.query( - contentUri, - projection, - selection, - null, - sortOrder - ) - }?.use { cursor -> + mediaStoreManager.fetchImages(projection, limit, offset)?.use{ cursor -> while (cursor.moveToNext()) { galleryImage.add( PhotoModel(