diff --git a/imagepicker/build.gradle b/imagepicker/build.gradle index 1921b3c9..e261a21a 100644 --- a/imagepicker/build.gradle +++ b/imagepicker/build.gradle @@ -56,8 +56,9 @@ artifacts { dependencies { implementation "com.github.bumptech.glide:glide:4.14.2" implementation 'androidx.recyclerview:recyclerview:1.2.1' + implementation 'androidx.constraintlayout:constraintlayout:2.1.4' - implementation 'androidx.appcompat:appcompat:1.5.1' + implementation 'androidx.appcompat:appcompat:1.6.1' implementation "androidx.core:core-ktx:$core_ktx_version" diff --git a/imagepicker/src/main/java/com/esafirm/imagepicker/adapter/FolderPickerAdapter.kt b/imagepicker/src/main/java/com/esafirm/imagepicker/adapter/FolderPickerAdapter.kt index faef0af2..6541adb9 100644 --- a/imagepicker/src/main/java/com/esafirm/imagepicker/adapter/FolderPickerAdapter.kt +++ b/imagepicker/src/main/java/com/esafirm/imagepicker/adapter/FolderPickerAdapter.kt @@ -52,7 +52,7 @@ class FolderPickerAdapter( override fun getItemCount() = folders.size class FolderViewHolder(binding: EfImagepickerItemFolderBinding) : ViewHolder(binding.root) { - val image = binding.image + val image = binding.imageView val name = binding.tvName val number = binding.tvNumber } diff --git a/imagepicker/src/main/java/com/esafirm/imagepicker/adapter/ImagePickerAdapter.kt b/imagepicker/src/main/java/com/esafirm/imagepicker/adapter/ImagePickerAdapter.kt index 60585429..e0a8d477 100644 --- a/imagepicker/src/main/java/com/esafirm/imagepicker/adapter/ImagePickerAdapter.kt +++ b/imagepicker/src/main/java/com/esafirm/imagepicker/adapter/ImagePickerAdapter.kt @@ -6,8 +6,6 @@ import android.provider.MediaStore import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.widget.FrameLayout -import androidx.core.content.ContextCompat import androidx.recyclerview.widget.AsyncListDiffer import androidx.recyclerview.widget.RecyclerView.ViewHolder import com.esafirm.imagepicker.R @@ -25,6 +23,7 @@ class ImagePickerAdapter( context: Context, imageLoader: ImageLoader, selectedImages: List, + private val isShowImageNames: Boolean, private val itemClickListener: OnImageClickListener ) : BaseListAdapter(context, imageLoader) { @@ -68,10 +67,9 @@ class ImagePickerAdapter( if (ImagePickerUtils.isVideoFormat(image)) { if (!videoDurationHolder.containsKey(image.id)) { - val uri = - Uri.withAppendedPath(MediaStore.Files.getContentUri("external"), "" + image.id) videoDurationHolder[image.id] = ImagePickerUtils.getVideoDurationLabel( - context, uri + context = context, + uri = Uri.withAppendedPath(MediaStore.Files.getContentUri("external"), "" + image.id) ) } @@ -80,9 +78,15 @@ class ImagePickerAdapter( } viewHolder.apply { + if (isShowImageNames) { + nameView.text = image.name + bottomView.visibility = View.VISIBLE + } else { + bottomView.visibility = View.GONE + } fileTypeIndicator.text = fileTypeLabel fileTypeIndicator.visibility = if (showFileTypeIndicator) View.VISIBLE else View.GONE - alphaView.alpha = if (isSelected) 0.5f else 0f + selectedView.visibility = if (isSelected) View.VISIBLE else View.GONE itemView.setOnClickListener { val shouldSelect = itemClickListener(isSelected) @@ -92,21 +96,27 @@ class ImagePickerAdapter( addSelected(image, position) } } - container.foreground = if (isSelected) ContextCompat.getDrawable( - context, - R.drawable.ef_ic_done_white - ) else null } } - private fun isSelected(image: Image): Boolean { - return selectedImages.any { it.path == image.path } + override fun getItemCount() = listDiffer.currentList.size + + override fun getItemViewType(position: Int): Int { + return position } - override fun getItemCount() = listDiffer.currentList.size + override fun getItemId(position: Int): Long { + return position.toLong() + } + + fun setData(images: List, commitCallback: (() -> Unit)? = null) { + listDiffer.submitList(images) { + commitCallback?.invoke() + } + } - fun setData(images: List) { - listDiffer.submitList(images) + private fun isSelected(image: Image): Boolean { + return selectedImages.any { it.path == image.path } } private fun addSelected(image: Image, position: Int) { @@ -143,8 +153,9 @@ class ImagePickerAdapter( class ImageViewHolder(binding: EfImagepickerItemImageBinding) : ViewHolder(binding.root) { val imageView = binding.imageView - val alphaView = binding.viewAlpha + val nameView = binding.tvImageName val fileTypeIndicator = binding.efItemFileTypeIndicator - val container = binding.root as FrameLayout + val bottomView = binding.efBottomView + val selectedView = binding.viewSelected } } \ No newline at end of file diff --git a/imagepicker/src/main/java/com/esafirm/imagepicker/features/ImagePickerAction.kt b/imagepicker/src/main/java/com/esafirm/imagepicker/features/ImagePickerAction.kt index 074a9b40..c5161795 100644 --- a/imagepicker/src/main/java/com/esafirm/imagepicker/features/ImagePickerAction.kt +++ b/imagepicker/src/main/java/com/esafirm/imagepicker/features/ImagePickerAction.kt @@ -2,15 +2,14 @@ package com.esafirm.imagepicker.features import com.esafirm.imagepicker.helper.state.ObservableState import com.esafirm.imagepicker.helper.state.SingleEvent -import com.esafirm.imagepicker.helper.state.asSingleEvent import com.esafirm.imagepicker.model.Folder import com.esafirm.imagepicker.model.Image data class ImagePickerState( val images: List = emptyList(), val folders: List = emptyList(), - // TODO: handle the transitions between folder and images in the view state as well - val isFolder: SingleEvent? = null, + val isFoldersMode: SingleEvent? = null, + val currentFolder: Folder? = null, val isLoading: Boolean = false, val error: SingleEvent? = null, val finishPickImage: SingleEvent>? = null, diff --git a/imagepicker/src/main/java/com/esafirm/imagepicker/features/ImagePickerActivity.kt b/imagepicker/src/main/java/com/esafirm/imagepicker/features/ImagePickerActivity.kt index 8ece339f..536bb88d 100644 --- a/imagepicker/src/main/java/com/esafirm/imagepicker/features/ImagePickerActivity.kt +++ b/imagepicker/src/main/java/com/esafirm/imagepicker/features/ImagePickerActivity.kt @@ -9,8 +9,10 @@ import android.view.Menu import android.view.MenuItem import androidx.activity.result.ActivityResult import androidx.activity.result.contract.ActivityResultContracts +import android.view.View import androidx.appcompat.app.ActionBar import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.Toolbar import com.esafirm.imagepicker.R import com.esafirm.imagepicker.features.cameraonly.CameraOnlyConfig @@ -26,6 +28,9 @@ class ImagePickerActivity : AppCompatActivity(), ImagePickerInteractionListener private val cameraModule = ImagePickerComponentsHolder.cameraModule private var actionBar: ActionBar? = null + private var optionsMenu: Menu? = null + private var searchView: SearchView? = null + private var isNeedSearch = true private lateinit var imagePickerFragment: ImagePickerFragment private val config: ImagePickerConfig? by lazy { @@ -98,6 +103,8 @@ class ImagePickerActivity : AppCompatActivity(), ImagePickerInteractionListener */ override fun onCreateOptionsMenu(menu: Menu): Boolean { menuInflater.inflate(R.menu.ef_image_picker_menu_main, menu) + this.optionsMenu = menu + initSearchView(menu) return true } @@ -129,6 +136,10 @@ class ImagePickerActivity : AppCompatActivity(), ImagePickerInteractionListener imagePickerFragment.captureImage() return true } + if (id == R.id.menu_sort) { + imagePickerFragment.showSortPopupMenu(item) + return true + } return super.onOptionsItemSelected(item) } @@ -158,13 +169,57 @@ class ImagePickerActivity : AppCompatActivity(), ImagePickerInteractionListener } } + //region Search + + private fun initSearchView(menu: Menu) { + searchView = menu.findItem(R.id.menu_search).actionView as? SearchView + if (config?.isShowSearch == true) { + searchView?.setIconifiedByDefault(true) + searchView?.setOnQueryTextListener(object : SearchView.OnQueryTextListener { + override fun onQueryTextSubmit(query: String): Boolean { + search(query) + return true + } + + override fun onQueryTextChange(newText: String): Boolean { + search(newText) + return true + } + }) + searchView?.setOnCloseListener { + search(null) + false + } + } else { + searchView?.visibility = View.GONE + } + } + + private fun search(query: String?) { + if (isNeedSearch) { + imagePickerFragment.search(query) + } + } + + private fun closeSearchView() { + // at the same time the onClose event is triggered, so isNeedSearch must be avoided reloading + isNeedSearch = false + searchView?.setQuery(null, false) + searchView?.isIconified = true + isNeedSearch = true + } + + //endregion Search + /* --------------------------------------------------- */ /* > ImagePickerInteractionListener Methods */ /* --------------------------------------------------- */ override fun setTitle(title: String?) { actionBar?.title = title - invalidateOptionsMenu() + if (optionsMenu != null) { + onPrepareOptionsMenu(optionsMenu!!) + } } override fun cancel() { @@ -179,4 +234,8 @@ class ImagePickerActivity : AppCompatActivity(), ImagePickerInteractionListener setResult(RESULT_OK, result) finish() } -} + + override fun isFolderModeChanged() { + closeSearchView() + } +} \ No newline at end of file diff --git a/imagepicker/src/main/java/com/esafirm/imagepicker/features/ImagePickerConfig.kt b/imagepicker/src/main/java/com/esafirm/imagepicker/features/ImagePickerConfig.kt index 2e266a41..1b4d4f0a 100644 --- a/imagepicker/src/main/java/com/esafirm/imagepicker/features/ImagePickerConfig.kt +++ b/imagepicker/src/main/java/com/esafirm/imagepicker/features/ImagePickerConfig.kt @@ -28,7 +28,12 @@ class ImagePickerConfig( override var savePath: ImagePickerSavePath = ImagePickerSavePath.DEFAULT, override var returnMode: ReturnMode = ReturnMode.NONE, override var isSaveImage: Boolean = true, - var showDoneButtonAlways: Boolean = false + var showDoneButtonAlways: Boolean = false, + var isShowSearch: Boolean = false, + var searchQuery: String? = null, + var isShowImageNames: Boolean = false, + var foldersSortMode: FolderSortMode = FolderSortMode.NONE, + var imagesSortMode: ImageSortMode = ImageSortMode.NONE ) : BaseConfig(), Parcelable { @IgnoredOnParcel diff --git a/imagepicker/src/main/java/com/esafirm/imagepicker/features/ImagePickerFragment.kt b/imagepicker/src/main/java/com/esafirm/imagepicker/features/ImagePickerFragment.kt index b6eda312..37dbe835 100644 --- a/imagepicker/src/main/java/com/esafirm/imagepicker/features/ImagePickerFragment.kt +++ b/imagepicker/src/main/java/com/esafirm/imagepicker/features/ImagePickerFragment.kt @@ -1,6 +1,7 @@ package com.esafirm.imagepicker.features import android.Manifest +import android.annotation.SuppressLint import android.app.Activity import android.content.Context import android.content.Intent @@ -13,11 +14,15 @@ import android.os.Environment import android.os.Parcelable import android.provider.Settings import android.view.LayoutInflater +import android.view.MenuItem import android.view.View import android.view.ViewGroup import android.widget.Toast import androidx.activity.result.contract.ActivityResultContracts.RequestMultiplePermissions import androidx.appcompat.view.ContextThemeWrapper +import androidx.appcompat.view.menu.MenuBuilder +import androidx.appcompat.view.menu.MenuPopupHelper +import androidx.appcompat.widget.PopupMenu import androidx.core.app.ActivityCompat import androidx.fragment.app.Fragment import androidx.recyclerview.widget.RecyclerView @@ -31,8 +36,11 @@ import com.esafirm.imagepicker.helper.ImagePickerPreferences import com.esafirm.imagepicker.helper.ImagePickerUtils import com.esafirm.imagepicker.helper.IpLogger import com.esafirm.imagepicker.helper.state.fetch +import com.esafirm.imagepicker.model.BaseItem import com.esafirm.imagepicker.model.Folder import com.esafirm.imagepicker.model.Image +import java.util.ArrayList +import java.util.regex.Pattern class ImagePickerFragment : Fragment() { @@ -141,7 +149,7 @@ class ImagePickerFragment : Fragment() { } private fun subscribeToUiState() = presenter.getUiState().observe(this) { state -> - showLoading(state.isLoading) + showLoading(state.isLoading, hideRecyclerView = true) state.error.fetch { showError(this) @@ -153,13 +161,15 @@ class ImagePickerFragment : Fragment() { return@observe } - state.isFolder.fetch { + state.isFoldersMode.fetch { + config.searchQuery = null val isFolderMode = this if (isFolderMode) { setFolderAdapter(state.folders) } else { - setImageAdapter(state.images) + setImageAdapter(state.currentFolder?.images ?: emptyList()) } + interactionListener.isFolderModeChanged() } state.finishPickImage.fetch { @@ -174,6 +184,41 @@ class ImagePickerFragment : Fragment() { } } + private fun filter(list: List): List { + val regex = config.searchQuery?.let { + if (it.isNotBlank()) buildRegex(it) else null + } + return if (regex != null) { + list.filter { it.getItemName().matches(regex) } + } else { + list + } + } + + private fun sortFolders(list: List): List { + return when (config.foldersSortMode) { + FolderSortMode.NAME_ASC -> list.sortedBy { it.folderName } + FolderSortMode.NAME_DESC -> list.sortedByDescending { it.folderName } + FolderSortMode.NUMBER_ASC -> list.sortedBy { it.images.size } + FolderSortMode.NUMBER_DESC -> list.sortedByDescending { it.images.size } + else -> list + } + } + + private fun sortImages(list: List): List { + return when (config.imagesSortMode) { + ImageSortMode.NAME_ASC -> list.sortedBy { it.name } + ImageSortMode.NAME_DESC -> list.sortedByDescending { it.name } + ImageSortMode.TYPE_ASC -> list.sortedBy { it.type } + ImageSortMode.TYPE_DESC -> list.sortedByDescending { it.type } + ImageSortMode.DATE_MODIFIED_ASC -> list.sortedBy { it.dateModified } + ImageSortMode.DATE_MODIFIED_DESC -> list.sortedByDescending { it.dateModified } + ImageSortMode.SIZE_ASC -> list.sortedBy { it.size } + ImageSortMode.SIZE_DESC -> list.sortedByDescending { it.size } + else -> list + } + } + override fun onDestroyView() { super.onDestroyView() binding = null @@ -191,7 +236,7 @@ class ImagePickerFragment : Fragment() { ).apply { val selectListener = { isSelected: Boolean -> selectImage(isSelected) } val folderClick = { bucket: Folder -> - setImageAdapter(bucket.images) + presenter.setFolder(bucket) updateTitle() } @@ -226,17 +271,38 @@ class ImagePickerFragment : Fragment() { * 3. Update title */ private fun setImageAdapter(images: List) { - recyclerViewManager.setImageAdapter(images) - updateTitle() + filter(images) + .let { sortImages(it) } + .let { + showLoading(true) + recyclerViewManager.setImageAdapter(it) { + showLoading(false) + } + checkDataIsEmpty(it) + updateTitle() + } } private fun setFolderAdapter(folders: List) { - recyclerViewManager.setFolderAdapter(folders) - updateTitle() + filter(folders) + .let { sortFolders(it) } + .let { + showLoading(true) + recyclerViewManager.setFolderAdapter(it) { + showLoading(false) + } + checkDataIsEmpty(it) + updateTitle() + } + } + + private fun checkDataIsEmpty(data: List<*>) { + binding?.tvEmptyImages?.visibility = if (data.isEmpty()) View.VISIBLE else View.GONE } private fun updateTitle() { - interactionListener.setTitle(recyclerViewManager.title) + val state = presenter.getUiState().get() + interactionListener.setTitle(recyclerViewManager.getTitle(state.currentFolder?.folderName)) } /** @@ -273,6 +339,138 @@ class ImagePickerFragment : Fragment() { private fun loadData() = presenter.loadData(config) + fun search(searchQuery: String?) { + config.searchQuery = searchQuery + val state = presenter.getUiState().get() + if (state.currentFolder == null) { + setFolderAdapter(state.folders) + } else { + setImageAdapter(state.currentFolder.images) + } + } + + fun sortFolders(sortMode: FolderSortMode) { + config.foldersSortMode = sortMode + val state = presenter.getUiState().get() + setFolderAdapter(state.folders) + } + + fun sortImages(sortMode: ImageSortMode) { + config.imagesSortMode = sortMode + val state = presenter.getUiState().get() + setImageAdapter(state.currentFolder?.images ?: emptyList()) + } + + //region Sort + + @SuppressLint("NonConstantResourceId") + fun showSortPopupMenu(menuItem: MenuItem) { + val view: View = requireActivity().findViewById(menuItem.itemId) + val popupMenu = PopupMenu(requireContext(), view) + val isFoldersMode = isFoldersMode + popupMenu.inflate(if (isFoldersMode) R.menu.ef_sort_folders else R.menu.ef_sort_images) + + // select the current sort mode + val selectedMenuItemId = if (isFoldersMode) { + when (config.foldersSortMode) { + FolderSortMode.NAME_ASC -> R.id.action_sort_folder_name_asc + FolderSortMode.NAME_DESC -> R.id.action_sort_folder_name_desc + FolderSortMode.NUMBER_ASC -> R.id.action_sort_num_asc + FolderSortMode.NUMBER_DESC -> R.id.action_sort_num_desc + else -> R.id.action_sort_num_desc + } + } else { + when (config.imagesSortMode) { + ImageSortMode.NAME_ASC -> R.id.action_sort_name_asc + ImageSortMode.NAME_DESC -> R.id.action_sort_name_desc + ImageSortMode.TYPE_ASC -> R.id.action_sort_type_asc + ImageSortMode.TYPE_DESC -> R.id.action_sort_type_desc + ImageSortMode.DATE_MODIFIED_ASC -> R.id.action_sort_date_asc + ImageSortMode.DATE_MODIFIED_DESC -> R.id.action_sort_date_desc + ImageSortMode.SIZE_ASC -> R.id.action_sort_size_asc + ImageSortMode.SIZE_DESC -> R.id.action_sort_size_desc + else -> R.id.action_sort_date_desc + } + } + + val selectedItem = popupMenu.menu.findItem(selectedMenuItemId) + selectedItem.isChecked = true + + popupMenu.setOnMenuItemClickListener { item: MenuItem -> + val result = when (item.itemId) { + // folders + R.id.action_sort_folder_name_asc -> { + sortFolders(FolderSortMode.NAME_ASC) + true + } + R.id.action_sort_folder_name_desc -> { + sortFolders(FolderSortMode.NAME_DESC) + true + } + R.id.action_sort_num_asc -> { + sortFolders(FolderSortMode.NUMBER_ASC) + true + } + R.id.action_sort_num_desc -> { + sortFolders(FolderSortMode.NUMBER_DESC) + true + } + // images + R.id.action_sort_name_asc -> { + sortImages(ImageSortMode.NAME_ASC) + true + } + R.id.action_sort_name_desc -> { + sortImages(ImageSortMode.NAME_DESC) + true + } + R.id.action_sort_type_asc -> { + sortImages(ImageSortMode.TYPE_ASC) + true + } + R.id.action_sort_type_desc -> { + sortImages(ImageSortMode.TYPE_DESC) + true + } + R.id.action_sort_date_asc -> { + sortImages(ImageSortMode.DATE_MODIFIED_ASC) + true + } + R.id.action_sort_date_desc -> { + sortImages(ImageSortMode.DATE_MODIFIED_DESC) + true + } + R.id.action_sort_size_asc -> { + sortImages(ImageSortMode.SIZE_ASC) + true + } + R.id.action_sort_size_desc -> { + sortImages(ImageSortMode.SIZE_DESC) + true + } + else -> false + } + if (result) { + selectedItem.isChecked = false + item.isChecked = true + } + return@setOnMenuItemClickListener result + } + + (popupMenu.menu as? MenuBuilder)?.let { + setForceShowMenuIcons(view, it) + } + } + + @SuppressLint("RestrictedApi") + private fun setForceShowMenuIcons(v: View, menu: MenuBuilder) { + val menuHelper = MenuPopupHelper(requireContext(), menu, v) + menuHelper.setForceShowIcon(true) + menuHelper.show() + } + + //endregion Sort + /** * Request for permission * If permission denied or app is first launched, request for permission @@ -345,8 +543,8 @@ class ImagePickerFragment : Fragment() { */ fun handleBack(): Boolean { if (recyclerViewManager.handleBack()) { + presenter.setFolder(null) // Handled. - updateTitle() return true } return false @@ -355,6 +553,9 @@ class ImagePickerFragment : Fragment() { val isShowDoneButton: Boolean get() = recyclerViewManager.isShowDoneButton + val isFoldersMode: Boolean + get() = presenter.getUiState().get().currentFolder == null + override fun onAttach(context: Context) { super.onAttach(context) if (context is ImagePickerInteractionListener) { @@ -374,10 +575,12 @@ class ImagePickerFragment : Fragment() { Toast.makeText(activity, message, Toast.LENGTH_SHORT).show() } - private fun showLoading(isLoading: Boolean) { + private fun showLoading(isLoading: Boolean, hideRecyclerView: Boolean = false) { binding?.run { progressBar.visibility = if (isLoading) View.VISIBLE else View.GONE - recyclerView.visibility = if (isLoading) View.GONE else View.VISIBLE + if (hideRecyclerView) { + recyclerView.visibility = if (isLoading) View.GONE else View.VISIBLE + } tvEmptyImages.visibility = View.GONE } } @@ -404,5 +607,9 @@ class ImagePickerFragment : Fragment() { arguments = args } } + + private fun buildRegex(query: String): Regex { + return Regex("(?is)" + ".*" + Pattern.quote(query) + ".*") + } } -} +} \ No newline at end of file diff --git a/imagepicker/src/main/java/com/esafirm/imagepicker/features/ImagePickerInteractionListener.kt b/imagepicker/src/main/java/com/esafirm/imagepicker/features/ImagePickerInteractionListener.kt index a3e427ac..ba9214e5 100644 --- a/imagepicker/src/main/java/com/esafirm/imagepicker/features/ImagePickerInteractionListener.kt +++ b/imagepicker/src/main/java/com/esafirm/imagepicker/features/ImagePickerInteractionListener.kt @@ -16,4 +16,9 @@ interface ImagePickerInteractionListener { * May include Images whose files no longer exist. */ fun selectionChanged(imageList: List?) + + /** + * Called when the user select the bucket or move back to list of buckets. + */ + fun isFolderModeChanged() } \ No newline at end of file diff --git a/imagepicker/src/main/java/com/esafirm/imagepicker/features/ImagePickerPresenter.kt b/imagepicker/src/main/java/com/esafirm/imagepicker/features/ImagePickerPresenter.kt index f62e005e..cb394002 100644 --- a/imagepicker/src/main/java/com/esafirm/imagepicker/features/ImagePickerPresenter.kt +++ b/imagepicker/src/main/java/com/esafirm/imagepicker/features/ImagePickerPresenter.kt @@ -32,6 +32,13 @@ internal class ImagePickerPresenter( stateObs.set(newState(stateObs.get())) } + fun setFolder(folder: Folder?) { + setState { copy( + isFoldersMode = (folder == null).asSingleEvent(), + currentFolder = folder + ) } + } + fun abortLoad() { imageLoader.abortLoadImages() } @@ -47,7 +54,7 @@ internal class ImagePickerPresenter( images = images, folders = folders, isLoading = false, - isFolder = config.isFolderMode.asSingleEvent() + isFoldersMode = config.isFolderMode.asSingleEvent() ) } } diff --git a/imagepicker/src/main/java/com/esafirm/imagepicker/features/SortMode.kt b/imagepicker/src/main/java/com/esafirm/imagepicker/features/SortMode.kt new file mode 100644 index 00000000..78a81bf3 --- /dev/null +++ b/imagepicker/src/main/java/com/esafirm/imagepicker/features/SortMode.kt @@ -0,0 +1,21 @@ +package com.esafirm.imagepicker.features + +enum class FolderSortMode { + NONE, + NAME_ASC, + NAME_DESC, + NUMBER_ASC, + NUMBER_DESC +} + +enum class ImageSortMode { + NONE, + NAME_ASC, + NAME_DESC, + TYPE_ASC, + TYPE_DESC, + DATE_MODIFIED_ASC, + DATE_MODIFIED_DESC, + SIZE_ASC, + SIZE_DESC +} \ No newline at end of file diff --git a/imagepicker/src/main/java/com/esafirm/imagepicker/features/fileloader/DefaultImageFileLoader.kt b/imagepicker/src/main/java/com/esafirm/imagepicker/features/fileloader/DefaultImageFileLoader.kt index bd5ea4d0..1866442a 100644 --- a/imagepicker/src/main/java/com/esafirm/imagepicker/features/fileloader/DefaultImageFileLoader.kt +++ b/imagepicker/src/main/java/com/esafirm/imagepicker/features/fileloader/DefaultImageFileLoader.kt @@ -9,12 +9,13 @@ import android.os.Build import android.os.Bundle import android.provider.MediaStore import com.esafirm.imagepicker.features.ImagePickerConfig +import com.esafirm.imagepicker.features.ImageSortMode import com.esafirm.imagepicker.features.common.ImageLoaderListener import com.esafirm.imagepicker.helper.ImagePickerUtils import com.esafirm.imagepicker.model.Folder import com.esafirm.imagepicker.model.Image import java.io.File -import java.util.ArrayList +import java.util.* import java.util.concurrent.ExecutorService import java.util.concurrent.Executors @@ -27,21 +28,17 @@ class DefaultImageFileLoader(private val context: Context) : ImageFileLoader { config: ImagePickerConfig, listener: ImageLoaderListener ) { - val isFolderMode = config.isFolderMode - val includeVideo = config.isIncludeVideo - val onlyVideo = config.isOnlyVideo - val includeAnimation = config.isIncludeAnimation - val excludedImages = config.excludedImages getExecutorService().execute( ImageLoadRunnable( - context.applicationContext, - isFolderMode, - onlyVideo, - includeVideo, - includeAnimation, - excludedImages, - listener + context = context.applicationContext, + isFolderMode = config.isFolderMode, + onlyVideo = config.isOnlyVideo, + includeVideo = config.isIncludeVideo, + includeAnimation = config.isIncludeAnimation, + excludedImages = config.excludedImages, + sortMode = config.imagesSortMode, + listener = listener ) ) } @@ -65,6 +62,7 @@ class DefaultImageFileLoader(private val context: Context) : ImageFileLoader { private val includeVideo: Boolean, private val includeAnimation: Boolean, private val excludedImages: List?, + private val sortMode: ImageSortMode, private val listener: ImageLoaderListener ) : Runnable { @@ -75,12 +73,19 @@ class DefaultImageFileLoader(private val context: Context) : ImageFileLoader { private const val QUERY_LIMIT = "limit" } - private val projection = arrayOf( - MediaStore.Images.Media._ID, - MediaStore.Images.Media.DISPLAY_NAME, - MediaStore.Images.Media.DATA, - MediaStore.Images.Media.BUCKET_DISPLAY_NAME - ) + enum class Projection(val column: String) { + ID(MediaStore.Images.Media._ID), + DISPLAY_NAME(MediaStore.Images.Media.DISPLAY_NAME), + DATA(MediaStore.Images.Media.DATA), + BUCKET_DISPLAY_NAME(MediaStore.Images.Media.BUCKET_DISPLAY_NAME), + MIME_TYPE(MediaStore.Images.Media.MIME_TYPE), + DATE_MODIFIED(MediaStore.Images.Media.DATE_MODIFIED), + SIZE(MediaStore.Images.Media.SIZE); + + companion object { + fun columns() = values().map { it.column }.toTypedArray() + } + } @SuppressLint("InlinedApi") private fun queryData(limit: Int? = null): Cursor? { @@ -102,39 +107,55 @@ class DefaultImageFileLoader(private val context: Context) : ImageFileLoader { else -> "" } - if (useNewApi) { + val sortColumn = when (sortMode) { + ImageSortMode.NAME_ASC, + ImageSortMode.NAME_DESC -> MediaStore.Files.FileColumns.DISPLAY_NAME + ImageSortMode.TYPE_ASC, + ImageSortMode.TYPE_DESC -> MediaStore.Files.FileColumns.MIME_TYPE + ImageSortMode.DATE_MODIFIED_ASC, + ImageSortMode.DATE_MODIFIED_DESC -> MediaStore.Files.FileColumns.DATE_MODIFIED + ImageSortMode.SIZE_ASC, + ImageSortMode.SIZE_DESC -> MediaStore.Files.FileColumns.SIZE + else -> MediaStore.Files.FileColumns.DATE_MODIFIED + } + val sortDirection = when (sortMode) { + ImageSortMode.NAME_ASC, + ImageSortMode.TYPE_ASC, + ImageSortMode.DATE_MODIFIED_ASC, + ImageSortMode.SIZE_ASC -> ContentResolver.QUERY_SORT_DIRECTION_ASCENDING + ImageSortMode.NAME_DESC, + ImageSortMode.TYPE_DESC, + ImageSortMode.DATE_MODIFIED_DESC, + ImageSortMode.SIZE_DESC -> ContentResolver.QUERY_SORT_DIRECTION_DESCENDING + else -> ContentResolver.QUERY_SORT_DIRECTION_DESCENDING + } + + return if (useNewApi) { val args = Bundle().apply { // Sort function - putStringArray( - ContentResolver.QUERY_ARG_SORT_COLUMNS, - arrayOf(MediaStore.Files.FileColumns.DATE_MODIFIED) - ) - putInt( - ContentResolver.QUERY_ARG_SORT_DIRECTION, - ContentResolver.QUERY_SORT_DIRECTION_DESCENDING - ) + putStringArray(ContentResolver.QUERY_ARG_SORT_COLUMNS, arrayOf(sortColumn)) + putInt(ContentResolver.QUERY_ARG_SORT_DIRECTION, sortDirection) // Selection - putString( - ContentResolver.QUERY_ARG_SQL_SELECTION, - selection - ) + putString(ContentResolver.QUERY_ARG_SQL_SELECTION, selection) // Limit if (limit != null) { putInt(ContentResolver.QUERY_ARG_LIMIT, limit) } } - return context.contentResolver.query(sourceUri, projection, args, null) - } + context.contentResolver.query(sourceUri, Projection.columns(), args, null) + } else { + val oldSortDirection = if (sortDirection == ContentResolver.QUERY_SORT_DIRECTION_ASCENDING) "ASC" else "DESC" - val sortOrder = "${MediaStore.Images.Media.DATE_MODIFIED} DESC".let { - if (limit != null) "$it LIMIT $limit" else it - } + val sortOrder = "$sortColumn $oldSortDirection".let { + if (limit != null) "$it LIMIT $limit" else it + } - return context.contentResolver.query( - sourceUri, projection, - selection, null, sortOrder - ) + context.contentResolver.query( + sourceUri, Projection.columns(), + selection, null, sortOrder + ) + } } private fun getSourceUri(): Uri { @@ -144,7 +165,7 @@ class DefaultImageFileLoader(private val context: Context) : ImageFileLoader { } private fun cursorToImage(cursor: Cursor): Image? { - val path = cursor.getString(cursor.getColumnIndex(projection[2])) + val path = cursor.getString(cursor.getColumnIndex(Projection.DATA.column)) val file = makeSafeFile(path) ?: return null if (excludedImages != null && excludedImages.contains(file)) return null @@ -153,13 +174,16 @@ class DefaultImageFileLoader(private val context: Context) : ImageFileLoader { if (ImagePickerUtils.isGifFormat(path)) return null } - val id = cursor.getLong(cursor.getColumnIndex(projection[0])) - val name = cursor.getString(cursor.getColumnIndex(projection[1])) + val id = cursor.getLong(cursor.getColumnIndex(Projection.ID.column)) + val name = cursor.getString(cursor.getColumnIndex(Projection.DISPLAY_NAME.column)) + val type = cursor.getString(cursor.getColumnIndex(Projection.MIME_TYPE.column)) + val dateString = cursor.getString(cursor.getColumnIndex(Projection.DATE_MODIFIED.column)) + val date = Date(dateString.toLong()) + val size = cursor.getLong(cursor.getColumnIndex(Projection.SIZE.column)) - if (name != null) { - return Image(id, name, path) - } - return null + return if (name != null) { + return Image(id, name, path, type, date, size) + } else null } private fun processData(cursor: Cursor?) { @@ -180,7 +204,7 @@ class DefaultImageFileLoader(private val context: Context) : ImageFileLoader { // Load folders if (!isFolderMode) continue - var bucket = cursor.getString(cursor.getColumnIndex(projection[3])) + var bucket = cursor.getString(cursor.getColumnIndex(Projection.BUCKET_DISPLAY_NAME.column)) if (bucket == null) { val parent = File(image.path).parentFile bucket = if (parent != null) parent.name else DEFAULT_FOLDER_NAME diff --git a/imagepicker/src/main/java/com/esafirm/imagepicker/features/recyclers/RecyclerViewManager.kt b/imagepicker/src/main/java/com/esafirm/imagepicker/features/recyclers/RecyclerViewManager.kt index 94122a74..35bfead8 100644 --- a/imagepicker/src/main/java/com/esafirm/imagepicker/features/recyclers/RecyclerViewManager.kt +++ b/imagepicker/src/main/java/com/esafirm/imagepicker/features/recyclers/RecyclerViewManager.kt @@ -37,6 +37,7 @@ class RecyclerViewManager( private lateinit var folderAdapter: FolderPickerAdapter private var foldersState: Parcelable? = null + private var imagesState: Parcelable? = null private var imageColumns = 0 private var folderColumns = 0 @@ -82,8 +83,11 @@ class RecyclerViewManager( /* Init folder and image adapter */ val imageLoader = ImagePickerComponentsHolder.imageLoader imageAdapter = ImagePickerAdapter( - context, imageLoader, selectedImages - ?: emptyList(), onImageClick + context = context, + imageLoader = imageLoader, + selectedImages = selectedImages ?: emptyList(), + isShowImageNames = config.isShowImageNames, + itemClickListener = onImageClick ) folderAdapter = FolderPickerAdapter(context, imageLoader) { foldersState = recyclerView.layoutManager?.onSaveInstanceState() @@ -111,7 +115,6 @@ class RecyclerViewManager( // Returns true if a back action was handled by going back a folder; false otherwise. fun handleBack(): Boolean { if (config.isFolderMode && !isDisplayingFolderView) { - setFolderAdapter(null) imageAdapter.setData(emptyList()) return true } @@ -121,44 +124,51 @@ class RecyclerViewManager( private val isDisplayingFolderView: Boolean get() = recyclerView.adapter == null || recyclerView.adapter is FolderPickerAdapter - val title: String - get() { - if (isDisplayingFolderView) { - return ConfigUtils.getFolderTitle(context, config) + fun getTitle(currentFolderName: String?): String { + val selectedNum = imageAdapter.selectedImages.size + return when { + isDisplayingFolderView -> { + ConfigUtils.getFolderTitle(context, config) + } + config.mode == ImagePickerMode.SINGLE -> { + ConfigUtils.getImageTitle(context, config) + } + selectedNum == 0 && config.imageTitle?.isNotBlank() == true -> { + ConfigUtils.getImageTitle(context, config) } - if (config.mode == ImagePickerMode.SINGLE) { - return ConfigUtils.getImageTitle(context, config) + selectedNum == 0 && currentFolderName != null -> { + currentFolderName } - val imageSize = imageAdapter.selectedImages.size - val useDefaultTitle = config.imageTitle.isNullOrBlank().not() && imageSize == 0 - if (useDefaultTitle) { - return ConfigUtils.getImageTitle(context, config) + config.limit == IpCons.MAX_LIMIT -> { + context.getString(R.string.ef_selected, selectedNum) } - return if (config.limit == IpCons.MAX_LIMIT) { - String.format(context.getString(R.string.ef_selected), imageSize) - } else { - String.format( - context.getString(R.string.ef_selected_with_limit), - imageSize, - config.limit - ) + else -> { + context.getString(R.string.ef_selected_with_limit, selectedNum, config.limit) } } + } - fun setImageAdapter(images: List = emptyList()) { - imageAdapter.setData(images) + fun setImageAdapter(images: List = emptyList(), commitCallback: (() -> Unit)? = null) { + imagesState = recyclerView.layoutManager?.onSaveInstanceState() + imageAdapter.setData(images) { + imagesState?.let { + recyclerView.layoutManager!!.onRestoreInstanceState(it) + } + commitCallback?.invoke() + } setItemDecoration(imageColumns) recyclerView.adapter = imageAdapter } - fun setFolderAdapter(folders: List?) { + fun setFolderAdapter(folders: List?, commitCallback: (() -> Unit)? = null) { folderAdapter.setData(folders) setItemDecoration(folderColumns) recyclerView.adapter = folderAdapter - if (foldersState != null) { + foldersState?.let { layoutManager!!.spanCount = folderColumns - recyclerView.layoutManager!!.onRestoreInstanceState(foldersState) + recyclerView.layoutManager!!.onRestoreInstanceState(it) } + commitCallback?.invoke() } /* --------------------------------------------------- */ @@ -197,8 +207,7 @@ class RecyclerViewManager( } val isShowDoneButton: Boolean - get() = (!isDisplayingFolderView - && (imageAdapter.selectedImages.isNotEmpty() || config.showDoneButtonAlways) + get() = ((imageAdapter.selectedImages.isNotEmpty() || config.showDoneButtonAlways) && config.returnMode !== ReturnMode.ALL && config.returnMode !== ReturnMode.GALLERY_ONLY) } \ No newline at end of file diff --git a/imagepicker/src/main/java/com/esafirm/imagepicker/model/BaseItem.kt b/imagepicker/src/main/java/com/esafirm/imagepicker/model/BaseItem.kt new file mode 100644 index 00000000..a420642f --- /dev/null +++ b/imagepicker/src/main/java/com/esafirm/imagepicker/model/BaseItem.kt @@ -0,0 +1,5 @@ +package com.esafirm.imagepicker.model + +interface BaseItem { + fun getItemName(): String +} \ No newline at end of file diff --git a/imagepicker/src/main/java/com/esafirm/imagepicker/model/Folder.kt b/imagepicker/src/main/java/com/esafirm/imagepicker/model/Folder.kt index 640e8067..0c146d0c 100644 --- a/imagepicker/src/main/java/com/esafirm/imagepicker/model/Folder.kt +++ b/imagepicker/src/main/java/com/esafirm/imagepicker/model/Folder.kt @@ -1,5 +1,11 @@ package com.esafirm.imagepicker.model -class Folder(var folderName: String) { +class Folder( + var folderName: String +) : BaseItem { + var images: MutableList = mutableListOf() + + override fun getItemName() = folderName + } \ No newline at end of file diff --git a/imagepicker/src/main/java/com/esafirm/imagepicker/model/Image.kt b/imagepicker/src/main/java/com/esafirm/imagepicker/model/Image.kt index 44631615..a0d7ce66 100644 --- a/imagepicker/src/main/java/com/esafirm/imagepicker/model/Image.kt +++ b/imagepicker/src/main/java/com/esafirm/imagepicker/model/Image.kt @@ -7,13 +7,17 @@ import android.provider.MediaStore import com.esafirm.imagepicker.helper.ImagePickerUtils import kotlinx.parcelize.IgnoredOnParcel import kotlinx.parcelize.Parcelize +import java.util.* @Parcelize class Image( val id: Long, val name: String, val path: String, -) : Parcelable { + val type: String? = null, + val dateModified: Date? = null, + val size: Long? = null +) : Parcelable, BaseItem { @IgnoredOnParcel private var uriHolder: Uri? = null @@ -33,6 +37,8 @@ class Image( } } + override fun getItemName() = name + override fun equals(other: Any?): Boolean { return when { this === other -> true @@ -49,6 +55,9 @@ class Image( result = 31 * result + name.hashCode() result = 31 * result + path.hashCode() result = 31 * result + (uriHolder?.hashCode() ?: 0) + result = 31 * result + (type?.hashCode() ?: 0) + result = 31 * result + (dateModified?.hashCode() ?: 0) + result = 31 * result + (size?.hashCode() ?: 0) return result } } \ No newline at end of file diff --git a/imagepicker/src/main/res/drawable-anydpi/ef_ic_arrow_down_gray.xml b/imagepicker/src/main/res/drawable-anydpi/ef_ic_arrow_down_gray.xml new file mode 100644 index 00000000..856e1d1e --- /dev/null +++ b/imagepicker/src/main/res/drawable-anydpi/ef_ic_arrow_down_gray.xml @@ -0,0 +1,11 @@ + + + diff --git a/imagepicker/src/main/res/drawable-anydpi/ef_ic_arrow_up_gray.xml b/imagepicker/src/main/res/drawable-anydpi/ef_ic_arrow_up_gray.xml new file mode 100644 index 00000000..5703c0c9 --- /dev/null +++ b/imagepicker/src/main/res/drawable-anydpi/ef_ic_arrow_up_gray.xml @@ -0,0 +1,11 @@ + + + diff --git a/imagepicker/src/main/res/drawable-anydpi/ef_ic_search_white.xml b/imagepicker/src/main/res/drawable-anydpi/ef_ic_search_white.xml new file mode 100644 index 00000000..44dcb4af --- /dev/null +++ b/imagepicker/src/main/res/drawable-anydpi/ef_ic_search_white.xml @@ -0,0 +1,11 @@ + + + diff --git a/imagepicker/src/main/res/drawable-anydpi/ef_ic_sort_white.xml b/imagepicker/src/main/res/drawable-anydpi/ef_ic_sort_white.xml new file mode 100644 index 00000000..76f52184 --- /dev/null +++ b/imagepicker/src/main/res/drawable-anydpi/ef_ic_sort_white.xml @@ -0,0 +1,11 @@ + + + diff --git a/imagepicker/src/main/res/drawable-hdpi/ef_ic_arrow_down_gray.png b/imagepicker/src/main/res/drawable-hdpi/ef_ic_arrow_down_gray.png new file mode 100644 index 00000000..3eee8170 Binary files /dev/null and b/imagepicker/src/main/res/drawable-hdpi/ef_ic_arrow_down_gray.png differ diff --git a/imagepicker/src/main/res/drawable-hdpi/ef_ic_arrow_up_gray.png b/imagepicker/src/main/res/drawable-hdpi/ef_ic_arrow_up_gray.png new file mode 100644 index 00000000..a7f677ab Binary files /dev/null and b/imagepicker/src/main/res/drawable-hdpi/ef_ic_arrow_up_gray.png differ diff --git a/imagepicker/src/main/res/drawable-hdpi/ef_ic_search_white.png b/imagepicker/src/main/res/drawable-hdpi/ef_ic_search_white.png new file mode 100644 index 00000000..98c47378 Binary files /dev/null and b/imagepicker/src/main/res/drawable-hdpi/ef_ic_search_white.png differ diff --git a/imagepicker/src/main/res/drawable-hdpi/ef_ic_sort_white.png b/imagepicker/src/main/res/drawable-hdpi/ef_ic_sort_white.png new file mode 100644 index 00000000..2b60141d Binary files /dev/null and b/imagepicker/src/main/res/drawable-hdpi/ef_ic_sort_white.png differ diff --git a/imagepicker/src/main/res/drawable-mdpi/ef_ic_arrow_down_gray.png b/imagepicker/src/main/res/drawable-mdpi/ef_ic_arrow_down_gray.png new file mode 100644 index 00000000..16535149 Binary files /dev/null and b/imagepicker/src/main/res/drawable-mdpi/ef_ic_arrow_down_gray.png differ diff --git a/imagepicker/src/main/res/drawable-mdpi/ef_ic_arrow_up_gray.png b/imagepicker/src/main/res/drawable-mdpi/ef_ic_arrow_up_gray.png new file mode 100644 index 00000000..9b95c85b Binary files /dev/null and b/imagepicker/src/main/res/drawable-mdpi/ef_ic_arrow_up_gray.png differ diff --git a/imagepicker/src/main/res/drawable-mdpi/ef_ic_search_white.png b/imagepicker/src/main/res/drawable-mdpi/ef_ic_search_white.png new file mode 100644 index 00000000..25e1bf8c Binary files /dev/null and b/imagepicker/src/main/res/drawable-mdpi/ef_ic_search_white.png differ diff --git a/imagepicker/src/main/res/drawable-mdpi/ef_ic_sort_white.png b/imagepicker/src/main/res/drawable-mdpi/ef_ic_sort_white.png new file mode 100644 index 00000000..1c8dfe00 Binary files /dev/null and b/imagepicker/src/main/res/drawable-mdpi/ef_ic_sort_white.png differ diff --git a/imagepicker/src/main/res/drawable-xhdpi/ef_ic_arrow_down_gray.png b/imagepicker/src/main/res/drawable-xhdpi/ef_ic_arrow_down_gray.png new file mode 100644 index 00000000..e9253415 Binary files /dev/null and b/imagepicker/src/main/res/drawable-xhdpi/ef_ic_arrow_down_gray.png differ diff --git a/imagepicker/src/main/res/drawable-xhdpi/ef_ic_arrow_up_gray.png b/imagepicker/src/main/res/drawable-xhdpi/ef_ic_arrow_up_gray.png new file mode 100644 index 00000000..277b1e6f Binary files /dev/null and b/imagepicker/src/main/res/drawable-xhdpi/ef_ic_arrow_up_gray.png differ diff --git a/imagepicker/src/main/res/drawable-xhdpi/ef_ic_search_white.png b/imagepicker/src/main/res/drawable-xhdpi/ef_ic_search_white.png new file mode 100644 index 00000000..8b62340f Binary files /dev/null and b/imagepicker/src/main/res/drawable-xhdpi/ef_ic_search_white.png differ diff --git a/imagepicker/src/main/res/drawable-xhdpi/ef_ic_sort_white.png b/imagepicker/src/main/res/drawable-xhdpi/ef_ic_sort_white.png new file mode 100644 index 00000000..0df02af8 Binary files /dev/null and b/imagepicker/src/main/res/drawable-xhdpi/ef_ic_sort_white.png differ diff --git a/imagepicker/src/main/res/drawable-xxhdpi/ef_ic_arrow_down_gray.png b/imagepicker/src/main/res/drawable-xxhdpi/ef_ic_arrow_down_gray.png new file mode 100644 index 00000000..6bf11714 Binary files /dev/null and b/imagepicker/src/main/res/drawable-xxhdpi/ef_ic_arrow_down_gray.png differ diff --git a/imagepicker/src/main/res/drawable-xxhdpi/ef_ic_arrow_up_gray.png b/imagepicker/src/main/res/drawable-xxhdpi/ef_ic_arrow_up_gray.png new file mode 100644 index 00000000..dd4a46c6 Binary files /dev/null and b/imagepicker/src/main/res/drawable-xxhdpi/ef_ic_arrow_up_gray.png differ diff --git a/imagepicker/src/main/res/drawable-xxhdpi/ef_ic_search_white.png b/imagepicker/src/main/res/drawable-xxhdpi/ef_ic_search_white.png new file mode 100644 index 00000000..f8a2af50 Binary files /dev/null and b/imagepicker/src/main/res/drawable-xxhdpi/ef_ic_search_white.png differ diff --git a/imagepicker/src/main/res/drawable-xxhdpi/ef_ic_sort_white.png b/imagepicker/src/main/res/drawable-xxhdpi/ef_ic_sort_white.png new file mode 100644 index 00000000..4e944a9d Binary files /dev/null and b/imagepicker/src/main/res/drawable-xxhdpi/ef_ic_sort_white.png differ diff --git a/imagepicker/src/main/res/layout/ef_fragment_image_picker.xml b/imagepicker/src/main/res/layout/ef_fragment_image_picker.xml index 980b26d7..f8edc1bd 100644 --- a/imagepicker/src/main/res/layout/ef_fragment_image_picker.xml +++ b/imagepicker/src/main/res/layout/ef_fragment_image_picker.xml @@ -1,5 +1,7 @@ - @@ -11,19 +13,21 @@ android:padding="@dimen/ef_spacing_double" android:text="@string/ef_msg_empty_images" android:textSize="@dimen/ef_font_medium" - android:visibility="gone" /> + android:visibility="gone" + tools:visibility="visible" /> + + - - + android:visibility="gone" + tools:visibility="visible" /> - + android:scaleType="centerCrop" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + tools:src="@tools:sample/avatars" /> + android:padding="@dimen/ef_padding_small" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent"> + android:textSize="@dimen/ef_font_small" + tools:text="@tools:sample/full_names" /> + android:textSize="@dimen/ef_font_small" + tools:text="@tools:sample/us_zipcodes" /> - + diff --git a/imagepicker/src/main/res/layout/ef_imagepicker_item_image.xml b/imagepicker/src/main/res/layout/ef_imagepicker_item_image.xml index 73458b80..1b51128a 100644 --- a/imagepicker/src/main/res/layout/ef_imagepicker_item_image.xml +++ b/imagepicker/src/main/res/layout/ef_imagepicker_item_image.xml @@ -1,38 +1,83 @@ - + android:layout_height="wrap_content"> + android:scaleType="centerCrop" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + tools:src="@tools:sample/avatars" /> - + + + + + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toEndOf="@id/ef_bottom_view" + tools:visibility="visible" /> + + - + diff --git a/imagepicker/src/main/res/menu/ef_image_picker_menu_main.xml b/imagepicker/src/main/res/menu/ef_image_picker_menu_main.xml index a0535829..d1f4d7b6 100644 --- a/imagepicker/src/main/res/menu/ef_image_picker_menu_main.xml +++ b/imagepicker/src/main/res/menu/ef_image_picker_menu_main.xml @@ -2,6 +2,19 @@ + + + + + + + + + + diff --git a/imagepicker/src/main/res/menu/ef_sort_images.xml b/imagepicker/src/main/res/menu/ef_sort_images.xml new file mode 100644 index 00000000..d2c68fb9 --- /dev/null +++ b/imagepicker/src/main/res/menu/ef_sort_images.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + diff --git a/imagepicker/src/main/res/values-ar/strings.xml b/imagepicker/src/main/res/values-ar/strings.xml index f22bb5fb..a58cd873 100644 --- a/imagepicker/src/main/res/values-ar/strings.xml +++ b/imagepicker/src/main/res/values-ar/strings.xml @@ -9,6 +9,15 @@ دليل أنقر لإختيار الصور طلب الاذن مرفوض + بحث + لم يتم العثور على شيء + + نوع + مقاس + تاريخ + نوع + عدد + اسم إخترت %d إخترت %1$d/%2$d diff --git a/imagepicker/src/main/res/values-da/strings.xml b/imagepicker/src/main/res/values-da/strings.xml index 1955e0fd..51d83ab5 100644 --- a/imagepicker/src/main/res/values-da/strings.xml +++ b/imagepicker/src/main/res/values-da/strings.xml @@ -9,6 +9,15 @@ Mappe Tryk for at vælge billeder Adgang nægtet + Søg + Intet fundet + + Sortere + Navn + Nummer + Type + Dato + Størrelse %d valgt %1$d/%2$d valgt diff --git a/imagepicker/src/main/res/values-de/strings.xml b/imagepicker/src/main/res/values-de/strings.xml index f3d51ffc..aa27d184 100644 --- a/imagepicker/src/main/res/values-de/strings.xml +++ b/imagepicker/src/main/res/values-de/strings.xml @@ -9,6 +9,15 @@ Verzeichnis Tippen um Bilder auszuwählen Erlaubnis verweigert + Suche + Nichts gefunden + + Sortieren + Name + Nummer + Typ + Datum + Größe %d ausgewählt %1$d/%2$d ausgewählt diff --git a/imagepicker/src/main/res/values-fr/strings.xml b/imagepicker/src/main/res/values-fr/strings.xml index 90a7407a..94b73400 100644 --- a/imagepicker/src/main/res/values-fr/strings.xml +++ b/imagepicker/src/main/res/values-fr/strings.xml @@ -9,6 +9,15 @@ Dossier Toucher pour sélectionner des images Permission refusée + Chercher + Rien n\'a été trouvé + + Sorte + Taille + Date + Taper + Nombre + Nom %d sélectionnées %1$d/%2$d sélectionnées diff --git a/imagepicker/src/main/res/values-in/strings.xml b/imagepicker/src/main/res/values-in/strings.xml index 5a4806fe..49bfba91 100644 --- a/imagepicker/src/main/res/values-in/strings.xml +++ b/imagepicker/src/main/res/values-in/strings.xml @@ -9,6 +9,15 @@ Folder Ketuk untuk memilih gambar Izin ditolak + Cari + Tidak ada yang ditemukan + + Menyortir + Ukuran + Tanggal + Jenis + Nomor + Nama %d terpilih %1$d/%2$d terpilih diff --git a/imagepicker/src/main/res/values-it/strings.xml b/imagepicker/src/main/res/values-it/strings.xml index 6db39f2a..ca1daae9 100644 --- a/imagepicker/src/main/res/values-it/strings.xml +++ b/imagepicker/src/main/res/values-it/strings.xml @@ -8,6 +8,15 @@ Cartella Premi per selezionare l\'immagine + Ricerca + Non abbiamo trovato nulla + + Ordinare + Nome + Numero + Tipo + Data + Taglia %d selezionate %1$d/%2$d selezionate diff --git a/imagepicker/src/main/res/values-ja/strings.xml b/imagepicker/src/main/res/values-ja/strings.xml index c4a31679..c8431ee8 100644 --- a/imagepicker/src/main/res/values-ja/strings.xml +++ b/imagepicker/src/main/res/values-ja/strings.xml @@ -7,6 +7,15 @@ フォルダ 画像を選択 + 検索 + 何も見つかりません + + 選別 + サイズ + 日にち + タイプ + 番号 + 名前 %d 枚を選択済 %1$d/%2$d 枚を選択済 diff --git a/imagepicker/src/main/res/values-ko/strings.xml b/imagepicker/src/main/res/values-ko/strings.xml index db37f48b..fe254401 100644 --- a/imagepicker/src/main/res/values-ko/strings.xml +++ b/imagepicker/src/main/res/values-ko/strings.xml @@ -9,6 +9,15 @@ 사진첩 이미지를 선택하세요 권한이 없음 + 찾다 + 아무것도 찾을 수 없음何も見つかりません + + 종류 + 이름 + 숫자 + 유형 + 날짜 + 크기 %d 선택됨 %1$d/%2$d 선택됨 diff --git a/imagepicker/src/main/res/values-pt-rBR/strings.xml b/imagepicker/src/main/res/values-pt-rBR/strings.xml index 02860b5c..12c78924 100644 --- a/imagepicker/src/main/res/values-pt-rBR/strings.xml +++ b/imagepicker/src/main/res/values-pt-rBR/strings.xml @@ -9,6 +9,15 @@ Pasta Toque para selecionar as imagens Permissão negada + Procurar + Nada encontrado + + Ordenação + O tamanho + Data + Tipo de + Cantitar + Nume %d selecionadas %1$d/%2$d selecionadas diff --git a/imagepicker/src/main/res/values-ro/strings.xml b/imagepicker/src/main/res/values-ro/strings.xml index 65a4e439..ac13a4db 100644 --- a/imagepicker/src/main/res/values-ro/strings.xml +++ b/imagepicker/src/main/res/values-ro/strings.xml @@ -8,6 +8,15 @@ Fișier Atingeți pentru a selecta imagini + Căutare + Nimic gasit + + Triere + Nume + Cantitate + Tip de + Data + Marimea %d selectate %1$d/%2$d selectate diff --git a/imagepicker/src/main/res/values-ru/strings.xml b/imagepicker/src/main/res/values-ru/strings.xml index c8039f46..7ecdfc39 100644 --- a/imagepicker/src/main/res/values-ru/strings.xml +++ b/imagepicker/src/main/res/values-ru/strings.xml @@ -1,17 +1,31 @@ Разрешите доступ к хранилищу, чтобы выбрать изображения + ОК ГОТОВО КАМЕРА + Папка Коснитесь, чтобы выбрать Доступ запрещён + Поиск + Ничего не найдено + + Сортировка + Имя + Количество + Тип + Дата + Размер + %d выбрано %1$d/%2$d выбрано + Не удалось создать файл Камера недоступна Ой! Что-то пошло не так! + Изображений не найдено Пожалуйста, разрешите доступ к хранилищу, чтобы выбрать изображения Пожалуйста, разрешите доступ к камере, чтобы сделать снимок diff --git a/imagepicker/src/main/res/values-tr/strings.xml b/imagepicker/src/main/res/values-tr/strings.xml index ae9aa89c..bffdd684 100644 --- a/imagepicker/src/main/res/values-tr/strings.xml +++ b/imagepicker/src/main/res/values-tr/strings.xml @@ -8,6 +8,15 @@ Klasör Resimleri seçmek için hafifçe dokunun İzin reddedildi + Arama + Hiçbirşey Bulunamadı + + Sıralama + Boyut + Tarih + Bir çeşit + Miktar + İsim %d seçili %1$d/%2$d seçili diff --git a/imagepicker/src/main/res/values-uk/strings.xml b/imagepicker/src/main/res/values-uk/strings.xml index a2a84e6c..be7647cc 100644 --- a/imagepicker/src/main/res/values-uk/strings.xml +++ b/imagepicker/src/main/res/values-uk/strings.xml @@ -4,14 +4,27 @@ ОК ГОТОВО КАМЕРА + Папка Торкніться, щоб обрати Доступ заборонено + Пошук + Нічого не знайдено + + Сортування + Ім\'я + Кількість + Тип + Дата + Розмір + %d обрано %1$d/%2$d обрано + Не вдалося створити файл Камера недоступна Ой! Щось пішло не так! + Зображень не знайдено Будь ласка, дозвольте доступ до сховища, щоб обрати зображення Будь ласка, дозвольте доступ до камери, щоб зробити знімок diff --git a/imagepicker/src/main/res/values-zh-rCN/strings.xml b/imagepicker/src/main/res/values-zh-rCN/strings.xml index 950a7999..679f2c1b 100644 --- a/imagepicker/src/main/res/values-zh-rCN/strings.xml +++ b/imagepicker/src/main/res/values-zh-rCN/strings.xml @@ -7,6 +7,15 @@ 文件夹 选择照片 + 搜索 + 沒有發現 + + 種類 + 姓名 + 數字 + 類型 + 日期 + 尺寸 已选择 %d 已选择 %1$d/%2$d diff --git a/imagepicker/src/main/res/values-zh-rTW/strings.xml b/imagepicker/src/main/res/values-zh-rTW/strings.xml index 2392a5ef..94c8a042 100644 --- a/imagepicker/src/main/res/values-zh-rTW/strings.xml +++ b/imagepicker/src/main/res/values-zh-rTW/strings.xml @@ -7,6 +7,15 @@ 檔案夹 選擇相片 + 搜索 + 没有发现 + + 种类 + 尺寸 + 日期 + 类型 + 数字 + 姓名 已選擇 %d 已選擇 %1$d/%2$d diff --git a/imagepicker/src/main/res/values/colors.xml b/imagepicker/src/main/res/values/colors.xml index e8919c4f..a3c0c962 100644 --- a/imagepicker/src/main/res/values/colors.xml +++ b/imagepicker/src/main/res/values/colors.xml @@ -6,6 +6,7 @@ #FFFFFF #FFFFFF + #80FFFFFF #9E9E9E #80000000 #AA000000 diff --git a/imagepicker/src/main/res/values/strings.xml b/imagepicker/src/main/res/values/strings.xml index 4f33fbad..76c054d1 100644 --- a/imagepicker/src/main/res/values/strings.xml +++ b/imagepicker/src/main/res/values/strings.xml @@ -9,6 +9,15 @@ Folder Tap to select images Permission denied + Search + Nothing found + + Sort + Name + Number + Type + Date + Size %d selected %1$d/%2$d selected