Skip to content

Commit d382205

Browse files
author
Majid Arabi
committed
Close #3
Paginate Files (faster load files)
1 parent 968e3f3 commit d382205

File tree

15 files changed

+194
-72
lines changed

15 files changed

+194
-72
lines changed

app/src/main/java/ir/one_developer/filepickerlibrary/FileAdapter.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import androidx.core.net.toUri
66
import androidx.recyclerview.widget.DiffUtil
77
import androidx.recyclerview.widget.ListAdapter
88
import androidx.recyclerview.widget.RecyclerView
9-
import com.github.file_picker.model.Media
9+
import com.github.file_picker.data.model.Media
1010
import ir.one_developer.filepickerlibrary.databinding.FileLayoutBinding
1111
import java.io.File
1212

app/src/main/java/ir/one_developer/filepickerlibrary/MainActivity.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import com.github.file_picker.adapter.FilePickerAdapter
88
import com.github.file_picker.extension.showFilePicker
99
import com.github.file_picker.listener.OnItemClickListener
1010
import com.github.file_picker.listener.OnSubmitClickListener
11-
import com.github.file_picker.model.Media
11+
import com.github.file_picker.data.model.Media
1212
import ir.one_developer.filepickerlibrary.databinding.ActivityMainBinding
1313

1414
class MainActivity : AppCompatActivity() {

file-picker/build.gradle

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,20 +35,32 @@ android {
3535
}
3636

3737
dependencies {
38-
implementation 'androidx.core:core-ktx:1.7.0'
38+
implementation 'androidx.core:core-ktx:1.8.0'
3939
implementation 'androidx.appcompat:appcompat:1.4.1'
4040
implementation 'androidx.recyclerview:recyclerview:1.2.1'
41-
implementation 'com.google.android.material:material:1.5.0'
41+
implementation 'com.google.android.material:material:1.6.1'
4242

43-
// Coroutines
44-
def coroutine_version = "1.6.0"
43+
def activity_version = "1.5.1"
44+
implementation "androidx.activity:activity-ktx:$activity_version"
45+
46+
def fragment_version = "1.5.2"
47+
implementation "androidx.fragment:fragment-ktx:$fragment_version"
48+
49+
def coroutine_version = "1.6.1"
4550
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutine_version"
4651
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutine_version"
4752

48-
// Glide
4953
def glide_version = "4.13.0"
5054
implementation "com.github.bumptech.glide:glide:$glide_version"
5155
annotationProcessor "com.github.bumptech.glide:compiler:$glide_version"
56+
57+
def paging_version = "3.1.1"
58+
implementation "androidx.paging:paging-runtime:$paging_version"
59+
60+
def lifecycle_version = "2.5.1"
61+
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
62+
implementation "androidx.lifecycle:lifecycle-runtime:$lifecycle_version"
63+
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
5264
}
5365

5466
afterEvaluate {
@@ -58,7 +70,7 @@ afterEvaluate {
5870
from components.release
5971
groupId = 'com.github.majidarabi'
6072
artifactId = 'file-picker'
61-
version = '0.0.9'
73+
version = '0.2.0'
6274
}
6375
}
6476
}

file-picker/src/main/java/com/github/file_picker/Const.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,6 @@ enum class FileType : Parcelable {
1414
VIDEO,
1515
IMAGE,
1616
AUDIO,
17-
}
17+
}
18+
19+
const val PAGE_SIZE = 10

file-picker/src/main/java/com/github/file_picker/FilePicker.kt

Lines changed: 20 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,15 @@ import androidx.annotation.FloatRange
1414
import androidx.appcompat.app.AppCompatActivity
1515
import androidx.core.view.isVisible
1616
import androidx.fragment.app.FragmentManager
17+
import androidx.paging.LoadState
1718
import androidx.recyclerview.widget.GridLayoutManager
1819
import androidx.recyclerview.widget.RecyclerView
1920
import com.github.file_picker.adapter.ItemAdapter
20-
import com.github.file_picker.extension.getStorageFiles
21+
import com.github.file_picker.data.model.Media
22+
import com.github.file_picker.data.repository.FilesRepository
2123
import com.github.file_picker.extension.hasPermission
2224
import com.github.file_picker.listener.OnItemClickListener
2325
import com.github.file_picker.listener.OnSubmitClickListener
24-
import com.github.file_picker.model.Media
2526
import com.google.android.material.bottomsheet.BottomSheetBehavior
2627
import com.google.android.material.bottomsheet.BottomSheetDialog
2728
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
@@ -50,6 +51,7 @@ class FilePicker private constructor(
5051
private var _binding: FilePickerBinding? = null
5152

5253
private var itemsAdapter: ItemAdapter? = null
54+
private lateinit var repository: FilesRepository
5355

5456
private var title: String
5557
private var titleTextColor by Delegates.notNull<Int>()
@@ -269,6 +271,7 @@ class FilePicker private constructor(
269271
}
270272

271273
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
274+
repository = FilesRepository(requireActivity().application)
272275
setCancellableDialog(cancellable)
273276
setupViews()
274277
}
@@ -403,7 +406,7 @@ class FilePicker private constructor(
403406
private fun setupOnItemClickListener(position: Int) {
404407
if (onItemClickListener == null) return
405408
if (itemsAdapter == null) return
406-
val media = itemsAdapter?.currentList?.get(position) ?: return
409+
val media = itemsAdapter?.snapshot()?.items?.get(position) ?: return
407410
onItemClickListener?.onClick(media, position, itemsAdapter!!)
408411
}
409412

@@ -424,34 +427,29 @@ class FilePicker private constructor(
424427

425428
/**
426429
* Load files
427-
*
428430
*/
429431
private fun loadFiles() = CoroutineScope(Dispatchers.IO).launch {
430-
val files = getStorageFiles(fileType = fileType)
431-
.map { Media(file = it, type = fileType) }
432-
433-
if (selectedFiles.isNotEmpty()) {
434-
selectedFiles.forEach { media ->
435-
val selectedMedia = files.find { it.id == media.id }
436-
if (selectedMedia != null) {
437-
selectedMedia.isSelected = media.isSelected
438-
selectedMedia.order = media.order
432+
itemsAdapter?.addLoadStateListener { state ->
433+
binding.progress.isVisible = state.source.refresh is LoadState.Loading
434+
if (state.source.refresh is LoadState.NotLoading) {
435+
selectedFiles.forEach { media ->
436+
itemsAdapter?.snapshot()?.items?.find { it.id == media.id }?.let {
437+
it.isSelected = media.isSelected
438+
it.order = media.order
439+
}
439440
}
441+
updateSelectedCount()
442+
setFixedSubmitButton()
443+
changeSubmitButtonState()
440444
}
441445
}
442-
443-
requireActivity().runOnUiThread {
444-
itemsAdapter?.submitList(files)
445-
updateSelectedCount()
446-
setFixedSubmitButton()
447-
changeSubmitButtonState()
448-
binding.progress.isVisible = false
446+
repository.getFiles(fileType = fileType).collect { pagingData ->
447+
itemsAdapter?.submitData(pagingData)
449448
}
450449
}
451450

452451
/**
453452
* Submit list
454-
*
455453
*/
456454
private fun submitList() = getSelectedItems()?.let {
457455
onSubmitClickListener?.onClick(it)
@@ -463,7 +461,7 @@ class FilePicker private constructor(
463461
* @return
464462
*/
465463
private fun getSelectedItems(): List<Media>? =
466-
itemsAdapter?.currentList?.filter { it.isSelected }?.sortedBy { it.order }
464+
itemsAdapter?.snapshot()?.items?.filter { it.isSelected }?.sortedBy { it.order }
467465

468466
/**
469467
* Has selected item

file-picker/src/main/java/com/github/file_picker/adapter/ItemAdapter.kt

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,21 @@ package com.github.file_picker.adapter
22

33
import android.view.LayoutInflater
44
import android.view.ViewGroup
5+
import androidx.paging.PagingDataAdapter
56
import androidx.recyclerview.widget.DiffUtil
67
import androidx.recyclerview.widget.ListAdapter
78
import com.github.file_picker.FilePicker
89
import com.github.file_picker.extension.isValidPosition
9-
import com.github.file_picker.model.Media
10+
import com.github.file_picker.data.model.Media
1011
import ir.one_developer.file_picker.databinding.ItemLayoutBinding
12+
import java.util.Locale.filter
1113

1214
internal class ItemAdapter(
1315
private var accentColor: Int = FilePicker.DEFAULT_ACCENT_COLOR,
1416
private var overlayAlpha: Float = FilePicker.DEFAULT_OVERLAY_ALPHA,
1517
private var limitSelectionCount: Int = FilePicker.DEFAULT_LIMIT_COUNT,
1618
private var listener: ((Int) -> Unit)? = null
17-
) : ListAdapter<Media, ItemVH>(COMPARATOR), FilePickerAdapter {
19+
) : PagingDataAdapter<Media, ItemVH>(COMPARATOR), FilePickerAdapter {
1820

1921
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemVH(
2022
listener = listener,
@@ -29,7 +31,7 @@ internal class ItemAdapter(
2931
)
3032

3133
override fun onBindViewHolder(holder: ItemVH, position: Int) {
32-
holder.bind(getItem(position))
34+
getItem(position)?.let { holder.bind(it) }
3335
}
3436

3537
/**
@@ -39,8 +41,8 @@ internal class ItemAdapter(
3941
*/
4042
override fun setSelected(position: Int) {
4143
if (limitSelectionCount > 1) {
42-
val item = getItem(position)
43-
val selectedItems = currentList.filter { it.isSelected && it.id != item.id }
44+
val item = getItem(position) ?: return
45+
val selectedItems = snapshot().items.filter { it.isSelected && it.id != item.id }
4446
val selectedItemCount = selectedItems.size
4547

4648
if (item.isSelected) {
@@ -49,7 +51,7 @@ internal class ItemAdapter(
4951
selectedItems.forEach { media ->
5052
if (media.order > item.order) {
5153
media.order--
52-
notifyItemChanged(currentList.indexOf(media))
54+
notifyItemChanged(snapshot().items.indexOf(media))
5355
}
5456
}
5557
return
@@ -63,22 +65,16 @@ internal class ItemAdapter(
6365
return
6466
}
6567

66-
if (!currentList.isValidPosition(lastSelectedPosition)) {
68+
if (!snapshot().items.isValidPosition(lastSelectedPosition)) {
6769
lastSelectedPosition = position
6870
}
69-
getItem(lastSelectedPosition).isSelected = false
71+
getItem(lastSelectedPosition)?.isSelected = false
7072
notifyItemChanged(lastSelectedPosition)
7173
lastSelectedPosition = position
72-
getItem(lastSelectedPosition).isSelected = true
74+
getItem(lastSelectedPosition)?.isSelected = true
7375
notifyItemChanged(lastSelectedPosition)
7476
}
7577

76-
override fun setHasStableIds(hasStableIds: Boolean) {
77-
super.setHasStableIds(true)
78-
}
79-
80-
override fun getItemId(position: Int): Long = getItem(position).id.toLong()
81-
8278
companion object {
8379
private var lastSelectedPosition = -1
8480

file-picker/src/main/java/com/github/file_picker/adapter/ItemVH.kt

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@ import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
1111
import com.bumptech.glide.request.RequestListener
1212
import com.bumptech.glide.request.target.Target
1313
import com.github.file_picker.FileType
14+
import com.github.file_picker.data.model.Media
1415
import com.github.file_picker.extension.getMusicCoverArt
1516
import com.github.file_picker.extension.lastPathTitle
1617
import com.github.file_picker.extension.size
17-
import com.github.file_picker.model.Media
1818
import ir.one_developer.file_picker.R
1919
import ir.one_developer.file_picker.databinding.ItemLayoutBinding
2020

@@ -65,7 +65,7 @@ internal class ItemVH(
6565

6666
Glide.with(ivImage)
6767
.load(previewImage)
68-
.diskCacheStrategy(DiskCacheStrategy.NONE)
68+
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
6969
.transition(DrawableTransitionOptions.withCrossFade())
7070
.listener(object : RequestListener<Drawable> {
7171
override fun onLoadFailed(
@@ -84,20 +84,18 @@ internal class ItemVH(
8484
target: Target<Drawable>?,
8585
dataSource: DataSource?,
8686
isFirstResource: Boolean
87-
): Boolean {
88-
return false
89-
}
90-
87+
): Boolean = false
9188
})
9289
.into(ivImage)
9390
}
9491

9592
private fun setErrorState(type: FileType) = binding.apply {
9693
cardErrorState.isVisible = true
9794
when (type) {
98-
FileType.AUDIO -> ivErrorIcon.setImageResource(R.drawable.ic_audiotrack)
99-
FileType.IMAGE -> ivErrorIcon.setImageResource(R.drawable.ic_image)
100-
FileType.VIDEO -> ivErrorIcon.setImageResource(R.drawable.ic_play)
95+
FileType.VIDEO -> Glide.with(ivErrorIcon).load(R.drawable.ic_play).into(ivErrorIcon)
96+
FileType.IMAGE -> Glide.with(ivErrorIcon).load(R.drawable.ic_image).into(ivErrorIcon)
97+
FileType.AUDIO -> Glide.with(ivErrorIcon).load(R.drawable.ic_audiotrack)
98+
.into(ivErrorIcon)
10199
}
102100
}
103101

file-picker/src/main/java/com/github/file_picker/model/Media.kt renamed to file-picker/src/main/java/com/github/file_picker/data/model/Media.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.github.file_picker.model
1+
package com.github.file_picker.data.model
22

33
import android.os.Parcelable
44
import com.github.file_picker.FileType
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package com.github.file_picker.data.repository
2+
3+
import android.app.Application
4+
import androidx.paging.PagingSource
5+
import androidx.paging.PagingState
6+
import com.github.file_picker.FileType
7+
import com.github.file_picker.data.model.Media
8+
import com.github.file_picker.extension.getStorageFiles
9+
10+
internal class FilesPagingSource(
11+
private val application: Application,
12+
private val fileType: FileType,
13+
) : PagingSource<Int, Media>() {
14+
15+
// the initial load size for the first page may be different from the requested size
16+
var initialLoadSize: Int = 0
17+
18+
override fun getRefreshKey(state: PagingState<Int, Media>): Int? =
19+
state.anchorPosition?.let { anchorPosition ->
20+
val anchorPage = state.closestPageToPosition(anchorPosition)
21+
anchorPage?.prevKey?.plus(1) ?: anchorPage?.nextKey?.minus(1)
22+
}
23+
24+
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Media> {
25+
return try {
26+
// Start refresh at page 1 if undefined.
27+
val nextPageNumber = params.key ?: 1
28+
29+
if (params.key == null) initialLoadSize = params.loadSize
30+
31+
// work out the offset into the database to retrieve records from the page number,
32+
// allow for a different load size for the first page
33+
val offsetCalc = {
34+
if (nextPageNumber == 2)
35+
initialLoadSize
36+
else
37+
((nextPageNumber - 1) * params.loadSize) + (initialLoadSize - params.loadSize)
38+
}
39+
40+
val offset = offsetCalc.invoke()
41+
42+
val files = application.getStorageFiles(
43+
fileType = fileType,
44+
limit = params.loadSize,
45+
offset = offset
46+
)
47+
val count = files.size
48+
49+
LoadResult.Page(
50+
data = files,
51+
prevKey = null,
52+
nextKey = if (count < params.loadSize) null else nextPageNumber + 1
53+
)
54+
} catch (e: Exception) {
55+
LoadResult.Error(e)
56+
}
57+
}
58+
59+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package com.github.file_picker.data.repository
2+
3+
import android.app.Application
4+
import androidx.paging.Pager
5+
import androidx.paging.PagingConfig
6+
import com.github.file_picker.FileType
7+
import com.github.file_picker.PAGE_SIZE
8+
9+
class FilesRepository(
10+
private val application: Application
11+
) {
12+
13+
fun getFiles(
14+
fileType: FileType = FileType.IMAGE,
15+
) = Pager(
16+
PagingConfig(
17+
pageSize = PAGE_SIZE,
18+
enablePlaceholders = true,
19+
initialLoadSize = PAGE_SIZE,
20+
)
21+
) {
22+
FilesPagingSource(
23+
application = application,
24+
fileType = fileType
25+
)
26+
}.flow
27+
28+
}

0 commit comments

Comments
 (0)