mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-26 20:33:53 +01:00
[GSoC] Hide/Unhide actioned pictures and change numbering (#5012)
* Changed numbering of marked images * Hide Unhide implemented * Test fixed * Improved speed for database operation * Improved speed for database operation * Changed progress dialog * Improved hiding speed * Test fixed * Fixed bug * Fixed bug and improved performance * Fixed bug and improved performance * Test fixed * Bug fixed * Bug fixed * Bug fixed * Bug fixed * Bug fixed * Code clean up * Test hiding images * Test hiding images * Test hiding images * Code clean up and test fixed * Fixed layout * Fixed bug * Bug fixed * Renamed method * Documentation added explaining logic * Documentation added explaining logic
This commit is contained in:
parent
5559282c1a
commit
a6c51a75a8
10 changed files with 638 additions and 163 deletions
|
|
@ -47,6 +47,19 @@ abstract class UploadedStatusDao {
|
|||
insert(uploadedStatus)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the imageSHA1 is present in database
|
||||
*/
|
||||
@Query("SELECT COUNT() FROM uploaded_table WHERE imageSHA1 = (:imageSHA1) AND imageResult = (:imageResult) ")
|
||||
abstract suspend fun findByImageSHA1(imageSHA1 : String, imageResult: Boolean): Int
|
||||
|
||||
/**
|
||||
* Check whether the modifiedImageSHA1 is present in database
|
||||
*/
|
||||
@Query("SELECT COUNT() FROM uploaded_table WHERE modifiedImageSHA1 = (:modifiedImageSHA1) AND modifiedImageResult = (:modifiedImageResult) ")
|
||||
abstract suspend fun findByModifiedImageSHA1(modifiedImageSHA1 : String,
|
||||
modifiedImageResult: Boolean): Int
|
||||
|
||||
/**
|
||||
* Asynchronous image sha1 query.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -4,10 +4,20 @@ import fr.free.nrw.commons.customselector.model.Folder
|
|||
import fr.free.nrw.commons.customselector.model.Image
|
||||
|
||||
/**
|
||||
* Image Helper object, includes all the static functions required by custom selector.
|
||||
* Image Helper object, includes all the static functions and variables required by custom selector.
|
||||
*/
|
||||
object ImageHelper {
|
||||
|
||||
/**
|
||||
* Custom selector preference key
|
||||
*/
|
||||
const val CUSTOM_SELECTOR_PREFERENCE_KEY: String = "custom_selector"
|
||||
|
||||
/**
|
||||
* Switch state preference key
|
||||
*/
|
||||
const val SWITCH_STATE_PREFERENCE_KEY: String = "switch_state"
|
||||
|
||||
/**
|
||||
* Returns the list of folders from given image list.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package fr.free.nrw.commons.customselector.ui.adapter
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
|
|
@ -13,9 +14,14 @@ import com.bumptech.glide.Glide
|
|||
import com.simplecityapps.recyclerview_fastscroll.views.FastScrollRecyclerView
|
||||
import fr.free.nrw.commons.R
|
||||
import fr.free.nrw.commons.customselector.helper.ImageHelper
|
||||
import fr.free.nrw.commons.customselector.helper.ImageHelper.CUSTOM_SELECTOR_PREFERENCE_KEY
|
||||
import fr.free.nrw.commons.customselector.helper.ImageHelper.SWITCH_STATE_PREFERENCE_KEY
|
||||
import fr.free.nrw.commons.customselector.listeners.ImageSelectListener
|
||||
import fr.free.nrw.commons.customselector.model.Image
|
||||
import fr.free.nrw.commons.customselector.ui.selector.ImageLoader
|
||||
import kotlinx.coroutines.*
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
/**
|
||||
* Custom selector ImageAdapter.
|
||||
|
|
@ -49,6 +55,8 @@ class ImageAdapter(
|
|||
*/
|
||||
class ImageUnselected
|
||||
|
||||
private var stopAddition: Boolean = false
|
||||
|
||||
/**
|
||||
* Currently selected images.
|
||||
*/
|
||||
|
|
@ -64,6 +72,35 @@ class ImageAdapter(
|
|||
*/
|
||||
private var images: ArrayList<Image> = ArrayList()
|
||||
|
||||
/**
|
||||
* Stores all images
|
||||
*/
|
||||
private var allImages: List<Image> = ArrayList()
|
||||
|
||||
/**
|
||||
* Map to store actionable images
|
||||
*/
|
||||
private var mapActionableImages: TreeMap<Int, Image> = TreeMap()
|
||||
|
||||
/**
|
||||
* Stores already added positions of actionable images
|
||||
*/
|
||||
private var alreadyAddedPositions: ArrayList<Int> = ArrayList()
|
||||
|
||||
/**
|
||||
* Next starting index to initiate query to find next actionable image
|
||||
*/
|
||||
private var nextImage = 0
|
||||
|
||||
private var count = 0
|
||||
|
||||
/**
|
||||
* Coroutine Dispatchers and Scope.
|
||||
*/
|
||||
private var defaultDispatcher : CoroutineDispatcher = Dispatchers.Default
|
||||
private var ioDispatcher : CoroutineDispatcher = Dispatchers.IO
|
||||
private val scope : CoroutineScope = MainScope()
|
||||
|
||||
/**
|
||||
* Create View holder.
|
||||
*/
|
||||
|
|
@ -76,7 +113,7 @@ class ImageAdapter(
|
|||
* Bind View holder, load image, selected view, click listeners.
|
||||
*/
|
||||
override fun onBindViewHolder(holder: ImageViewHolder, position: Int) {
|
||||
val image=images[position]
|
||||
var image=images[position]
|
||||
holder.image.setImageDrawable (null)
|
||||
if (context.contentResolver.getType(image.uri) == null) {
|
||||
// Image does not exist anymore, update adapter.
|
||||
|
|
@ -87,18 +124,107 @@ class ImageAdapter(
|
|||
notifyItemRangeChanged(updatedPosition, images.size)
|
||||
}
|
||||
} else {
|
||||
val selectedIndex = ImageHelper.getIndex(selectedImages, image)
|
||||
val sharedPreferences: SharedPreferences =
|
||||
context.getSharedPreferences(CUSTOM_SELECTOR_PREFERENCE_KEY, 0)
|
||||
val switchState =
|
||||
sharedPreferences.getBoolean(SWITCH_STATE_PREFERENCE_KEY, true)
|
||||
|
||||
// Getting selected index when switch is on
|
||||
val selectedIndex: Int = if (switchState) {
|
||||
ImageHelper.getIndex(selectedImages, image)
|
||||
|
||||
// Getting selected index when switch is off
|
||||
} else if (mapActionableImages.size > position) {
|
||||
ImageHelper
|
||||
.getIndex(selectedImages, ArrayList(mapActionableImages.values)[position])
|
||||
|
||||
// For any other case return -1
|
||||
} else {
|
||||
-1
|
||||
}
|
||||
|
||||
val isSelected = selectedIndex != -1
|
||||
if (isSelected) {
|
||||
holder.itemSelected(selectedIndex + 1)
|
||||
holder.itemSelected(selectedImages.size)
|
||||
} else {
|
||||
holder.itemUnselected();
|
||||
holder.itemUnselected()
|
||||
}
|
||||
Glide.with(holder.image).load(image.uri).thumbnail(0.3f).into(holder.image)
|
||||
imageLoader.queryAndSetView(holder, image)
|
||||
|
||||
scope.launch {
|
||||
imageLoader.queryAndSetView(
|
||||
holder, image, ioDispatcher, defaultDispatcher
|
||||
)
|
||||
val sharedPreferences: SharedPreferences =
|
||||
context.getSharedPreferences(CUSTOM_SELECTOR_PREFERENCE_KEY, 0)
|
||||
val switchState =
|
||||
sharedPreferences.getBoolean(SWITCH_STATE_PREFERENCE_KEY, true)
|
||||
if (!switchState) {
|
||||
// If the position is not already visited, that means the position is new then
|
||||
// finds the next actionable image position from all images
|
||||
if(!alreadyAddedPositions.contains(position)) {
|
||||
val next = imageLoader.nextActionableImage(
|
||||
allImages, ioDispatcher, defaultDispatcher,
|
||||
nextImage
|
||||
)
|
||||
|
||||
// If next actionable image is found, saves it, as the the search for
|
||||
// finding next actionable image will start from this position
|
||||
if (next > -1) {
|
||||
nextImage = next+1
|
||||
|
||||
// If map doesn't contains the next actionable image, that means it's a
|
||||
// new actionable image, if will put it to the map as actionable images
|
||||
// and it will load the new image in the view holder
|
||||
if (!mapActionableImages.containsKey(next)) {
|
||||
mapActionableImages[next] = allImages[next]
|
||||
alreadyAddedPositions.add(count)
|
||||
count++
|
||||
Glide.with(holder.image).load(allImages[next].uri)
|
||||
.thumbnail(0.3f).into(holder.image)
|
||||
notifyItemInserted(position)
|
||||
notifyItemRangeChanged(position, itemCount+1)
|
||||
}
|
||||
|
||||
// If next actionable image is not found, that means searching is
|
||||
// complete till end, and it will stop searching.
|
||||
} else {
|
||||
stopAddition = true
|
||||
notifyItemRemoved(position)
|
||||
}
|
||||
|
||||
// If the position is already visited, that means the image is already present
|
||||
// inside map, so it will fetch the image from the map and load in the holder
|
||||
} else {
|
||||
val actionableImages: List<Image> = ArrayList(mapActionableImages.values)
|
||||
image = actionableImages[position]
|
||||
Glide.with(holder.image).load(image.uri)
|
||||
.thumbnail(0.3f).into(holder.image)
|
||||
}
|
||||
|
||||
// If switch is turned off, it just fetches the image from all images without any
|
||||
// further operations
|
||||
} else {
|
||||
Glide.with(holder.image).load(image.uri)
|
||||
.thumbnail(0.3f).into(holder.image)
|
||||
}
|
||||
}
|
||||
|
||||
holder.itemView.setOnClickListener {
|
||||
val sharedPreferences: SharedPreferences =
|
||||
context.getSharedPreferences(CUSTOM_SELECTOR_PREFERENCE_KEY, 0)
|
||||
val switchState =
|
||||
sharedPreferences.getBoolean(SWITCH_STATE_PREFERENCE_KEY, true)
|
||||
|
||||
// While switch is turned off, lets user click on image only if the position is
|
||||
// added inside map
|
||||
if (!switchState) {
|
||||
if (mapActionableImages.size > position) {
|
||||
selectOrRemoveImage(holder, position)
|
||||
}
|
||||
} else {
|
||||
selectOrRemoveImage(holder, position)
|
||||
}
|
||||
}
|
||||
|
||||
// launch media preview on long click.
|
||||
holder.itemView.setOnLongClickListener {
|
||||
|
|
@ -112,14 +238,35 @@ class ImageAdapter(
|
|||
* Handle click event on an image, update counter on images.
|
||||
*/
|
||||
private fun selectOrRemoveImage(holder: ImageViewHolder, position: Int){
|
||||
val clickedIndex = ImageHelper.getIndex(selectedImages, images[position])
|
||||
val sharedPreferences: SharedPreferences =
|
||||
context.getSharedPreferences(CUSTOM_SELECTOR_PREFERENCE_KEY, 0)
|
||||
val switchState =
|
||||
sharedPreferences.getBoolean(SWITCH_STATE_PREFERENCE_KEY, true)
|
||||
|
||||
// Getting clicked index from all images index when switch is on
|
||||
val clickedIndex: Int = if(switchState) {
|
||||
ImageHelper.getIndex(selectedImages, images[position])
|
||||
|
||||
// Getting clicked index from actionable images when switch is off
|
||||
} else {
|
||||
ImageHelper.getIndex(selectedImages, ArrayList(mapActionableImages.values)[position])
|
||||
}
|
||||
|
||||
if (clickedIndex != -1) {
|
||||
selectedImages.removeAt(clickedIndex)
|
||||
if (holder.isItemNotForUpload()) {
|
||||
selectedNotForUploadImages--
|
||||
}
|
||||
notifyItemChanged(position, ImageUnselected())
|
||||
val indexes = ImageHelper.getIndexList(selectedImages, images)
|
||||
|
||||
// Getting index from all images index when switch is on
|
||||
val indexes = if (switchState) {
|
||||
ImageHelper.getIndexList(selectedImages, images)
|
||||
|
||||
// Getting index from actionable images when switch is off
|
||||
} else {
|
||||
ImageHelper.getIndexList(selectedImages, ArrayList(mapActionableImages.values))
|
||||
}
|
||||
for (index in indexes) {
|
||||
notifyItemChanged(index, ImageSelectedOrUpdated())
|
||||
}
|
||||
|
|
@ -130,8 +277,21 @@ class ImageAdapter(
|
|||
if (holder.isItemNotForUpload()) {
|
||||
selectedNotForUploadImages++
|
||||
}
|
||||
|
||||
// Getting index from all images index when switch is on
|
||||
val indexes: ArrayList<Int> = if (switchState) {
|
||||
selectedImages.add(images[position])
|
||||
notifyItemChanged(position, ImageSelectedOrUpdated())
|
||||
ImageHelper.getIndexList(selectedImages, images)
|
||||
|
||||
// Getting index from actionable images when switch is off
|
||||
} else {
|
||||
selectedImages.add(ArrayList(mapActionableImages.values)[position])
|
||||
ImageHelper.getIndexList(selectedImages, ArrayList(mapActionableImages.values))
|
||||
}
|
||||
|
||||
for (index in indexes) {
|
||||
notifyItemChanged(index, ImageSelectedOrUpdated())
|
||||
}
|
||||
}
|
||||
}
|
||||
imageSelectListener.onSelectedImagesChanged(selectedImages, selectedNotForUploadImages)
|
||||
|
|
@ -140,9 +300,16 @@ class ImageAdapter(
|
|||
/**
|
||||
* Initialize the data set.
|
||||
*/
|
||||
fun init(newImages: List<Image>) {
|
||||
fun init(newImages: List<Image>, fixedImages: List<Image>, emptyMap: TreeMap<Int, Image>) {
|
||||
allImages = fixedImages
|
||||
val oldImageList:ArrayList<Image> = images
|
||||
val newImageList:ArrayList<Image> = ArrayList(newImages)
|
||||
mapActionableImages = emptyMap
|
||||
alreadyAddedPositions = ArrayList()
|
||||
nextImage = 0
|
||||
stopAddition = false
|
||||
selectedImages = ArrayList()
|
||||
count = 0
|
||||
val diffResult = DiffUtil.calculateDiff(
|
||||
ImagesDiffCallback(oldImageList, newImageList)
|
||||
)
|
||||
|
|
@ -153,12 +320,12 @@ class ImageAdapter(
|
|||
/**
|
||||
* Refresh the data in the adapter
|
||||
*/
|
||||
fun refresh(newImages: List<Image>) {
|
||||
fun refresh(newImages: List<Image>, fixedImages: List<Image>) {
|
||||
selectedNotForUploadImages = 0
|
||||
selectedImages.clear()
|
||||
images.clear()
|
||||
selectedImages = arrayListOf()
|
||||
init(newImages)
|
||||
init(newImages, fixedImages, TreeMap())
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
|
|
@ -168,13 +335,38 @@ class ImageAdapter(
|
|||
* @return The total number of items in this adapter.
|
||||
*/
|
||||
override fun getItemCount(): Int {
|
||||
return images.size
|
||||
val sharedPreferences: SharedPreferences =
|
||||
context.getSharedPreferences(CUSTOM_SELECTOR_PREFERENCE_KEY, 0)
|
||||
val switchState =
|
||||
sharedPreferences.getBoolean(SWITCH_STATE_PREFERENCE_KEY, true)
|
||||
|
||||
// While switch is on initializes the holder with all images size
|
||||
return if(switchState) {
|
||||
allImages.size
|
||||
|
||||
// While switch is off and searching for next actionable has ended, initializes the holder
|
||||
// with size of all actionable images
|
||||
} else if (mapActionableImages.size == allImages.size || stopAddition) {
|
||||
mapActionableImages.size
|
||||
|
||||
// While switch is off, initializes the holder with and extra view holder so that finding
|
||||
// and addition of the next actionable image in the adapter can be continued
|
||||
} else {
|
||||
mapActionableImages.size + 1
|
||||
}
|
||||
}
|
||||
|
||||
fun getImageIdAt(position: Int): Long {
|
||||
return images.get(position).id
|
||||
}
|
||||
|
||||
/**
|
||||
* CleanUp function.
|
||||
*/
|
||||
fun cleanUp() {
|
||||
scope.cancel()
|
||||
}
|
||||
|
||||
/**
|
||||
* Image view holder.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1,19 +1,26 @@
|
|||
package fr.free.nrw.commons.customselector.ui.selector
|
||||
|
||||
import android.app.Activity
|
||||
import android.net.Uri
|
||||
import android.content.Context.MODE_PRIVATE
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ProgressBar
|
||||
import android.widget.Switch
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import fr.free.nrw.commons.R
|
||||
import fr.free.nrw.commons.customselector.database.NotForUploadStatusDao
|
||||
import fr.free.nrw.commons.customselector.database.UploadedStatusDao
|
||||
import fr.free.nrw.commons.customselector.helper.ImageHelper
|
||||
import fr.free.nrw.commons.customselector.helper.ImageHelper.CUSTOM_SELECTOR_PREFERENCE_KEY
|
||||
import fr.free.nrw.commons.customselector.helper.ImageHelper.SWITCH_STATE_PREFERENCE_KEY
|
||||
import fr.free.nrw.commons.customselector.listeners.ImageSelectListener
|
||||
import fr.free.nrw.commons.customselector.listeners.RefreshUIListener
|
||||
import fr.free.nrw.commons.customselector.model.CallbackStatus
|
||||
|
|
@ -21,13 +28,16 @@ import fr.free.nrw.commons.customselector.model.Image
|
|||
import fr.free.nrw.commons.customselector.model.Result
|
||||
import fr.free.nrw.commons.customselector.ui.adapter.ImageAdapter
|
||||
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment
|
||||
import fr.free.nrw.commons.media.MediaClient
|
||||
import fr.free.nrw.commons.theme.BaseActivity
|
||||
import fr.free.nrw.commons.upload.FileProcessor
|
||||
import fr.free.nrw.commons.upload.FileUtilsWrapper
|
||||
import kotlinx.android.synthetic.main.fragment_custom_selector.*
|
||||
import kotlinx.android.synthetic.main.fragment_custom_selector.view.*
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.net.URI
|
||||
import kotlinx.coroutines.*
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
/**
|
||||
* Custom Selector Image Fragment.
|
||||
|
|
@ -54,8 +64,14 @@ class ImageFragment: CommonsDaggerSupportFragment(), RefreshUIListener {
|
|||
*/
|
||||
private var selectorRV: RecyclerView? = null
|
||||
private var loader: ProgressBar? = null
|
||||
private var switch: Switch? = null
|
||||
lateinit var filteredImages: ArrayList<Image>;
|
||||
|
||||
/**
|
||||
* Stores all images
|
||||
*/
|
||||
var allImages: ArrayList<Image> = ArrayList()
|
||||
|
||||
/**
|
||||
* View model Factory.
|
||||
*/
|
||||
|
|
@ -78,9 +94,48 @@ class ImageFragment: CommonsDaggerSupportFragment(), RefreshUIListener {
|
|||
*/
|
||||
private lateinit var gridLayoutManager: GridLayoutManager
|
||||
|
||||
/**
|
||||
* For showing progress
|
||||
*/
|
||||
private var progressLayout: ConstraintLayout? = null
|
||||
|
||||
/**
|
||||
* NotForUploadStatus Dao class for database operations
|
||||
*/
|
||||
@Inject
|
||||
lateinit var notForUploadStatusDao: NotForUploadStatusDao
|
||||
|
||||
/**
|
||||
* UploadedStatus Dao class for database operations
|
||||
*/
|
||||
@Inject
|
||||
lateinit var uploadedStatusDao: UploadedStatusDao
|
||||
|
||||
/**
|
||||
* FileUtilsWrapper class to get imageSHA1 from uri
|
||||
*/
|
||||
@Inject
|
||||
lateinit var fileUtilsWrapper: FileUtilsWrapper
|
||||
|
||||
/**
|
||||
* FileProcessor to pre-process the file.
|
||||
*/
|
||||
@Inject
|
||||
lateinit var fileProcessor: FileProcessor
|
||||
|
||||
/**
|
||||
* MediaClient for SHA1 query.
|
||||
*/
|
||||
@Inject
|
||||
lateinit var mediaClient: MediaClient
|
||||
|
||||
companion object {
|
||||
|
||||
/**
|
||||
* Switch state
|
||||
*/
|
||||
var switchState: Boolean = true
|
||||
|
||||
/**
|
||||
* BucketId args name
|
||||
*/
|
||||
|
|
@ -131,12 +186,50 @@ class ImageFragment: CommonsDaggerSupportFragment(), RefreshUIListener {
|
|||
handleResult(it)
|
||||
})
|
||||
|
||||
switch = root.switchWidget
|
||||
switch?.visibility = View.VISIBLE
|
||||
switch?.setOnCheckedChangeListener { _, isChecked -> onChangeSwitchState(isChecked) }
|
||||
selectorRV = root.selector_rv
|
||||
loader = root.loader
|
||||
progressLayout = root.progressLayout
|
||||
|
||||
val sharedPreferences: SharedPreferences =
|
||||
requireContext().getSharedPreferences(CUSTOM_SELECTOR_PREFERENCE_KEY, MODE_PRIVATE)
|
||||
switchState = sharedPreferences.getBoolean(SWITCH_STATE_PREFERENCE_KEY, true)
|
||||
switch?.isChecked = switchState
|
||||
switch?.text =
|
||||
if (switchState) getString(R.string.hide_already_actioned_pictures)
|
||||
else getString(R.string.show_already_actioned_pictures)
|
||||
|
||||
return root
|
||||
}
|
||||
|
||||
private fun onChangeSwitchState(checked: Boolean) {
|
||||
if (checked) {
|
||||
switchState = true
|
||||
val sharedPreferences: SharedPreferences =
|
||||
requireContext().getSharedPreferences(CUSTOM_SELECTOR_PREFERENCE_KEY, MODE_PRIVATE)
|
||||
val editor = sharedPreferences.edit()
|
||||
editor.putBoolean(SWITCH_STATE_PREFERENCE_KEY, true)
|
||||
editor.apply()
|
||||
switch?.text = getString(R.string.hide_already_actioned_pictures)
|
||||
|
||||
imageAdapter.init(allImages, allImages, TreeMap())
|
||||
imageAdapter.notifyDataSetChanged()
|
||||
} else {
|
||||
switchState = false
|
||||
val sharedPreferences: SharedPreferences =
|
||||
requireContext().getSharedPreferences(CUSTOM_SELECTOR_PREFERENCE_KEY, MODE_PRIVATE)
|
||||
val editor = sharedPreferences.edit()
|
||||
editor.putBoolean(SWITCH_STATE_PREFERENCE_KEY, false)
|
||||
editor.apply()
|
||||
switch?.text = getString(R.string.show_already_actioned_pictures)
|
||||
|
||||
imageAdapter.init(allImages, allImages, TreeMap())
|
||||
imageAdapter.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaching data listener
|
||||
*/
|
||||
|
|
@ -157,7 +250,12 @@ class ImageFragment: CommonsDaggerSupportFragment(), RefreshUIListener {
|
|||
val images = result.images
|
||||
if(images.isNotEmpty()) {
|
||||
filteredImages = ImageHelper.filterImages(images, bucketId)
|
||||
imageAdapter.init(filteredImages)
|
||||
allImages = ArrayList(filteredImages)
|
||||
if(switchState) {
|
||||
imageAdapter.init(filteredImages, allImages, TreeMap())
|
||||
} else {
|
||||
imageAdapter.init(filteredImages, allImages, TreeMap())
|
||||
}
|
||||
selectorRV?.let {
|
||||
it.visibility = View.VISIBLE
|
||||
lastItemId?.let { pos ->
|
||||
|
|
@ -205,7 +303,7 @@ class ImageFragment: CommonsDaggerSupportFragment(), RefreshUIListener {
|
|||
* Save the Image Fragment state.
|
||||
*/
|
||||
override fun onDestroy() {
|
||||
imageLoader?.cleanUP()
|
||||
imageAdapter.cleanUp()
|
||||
|
||||
val position = (selectorRV?.layoutManager as GridLayoutManager)
|
||||
.findFirstVisibleItemPosition()
|
||||
|
|
@ -227,6 +325,6 @@ class ImageFragment: CommonsDaggerSupportFragment(), RefreshUIListener {
|
|||
}
|
||||
|
||||
override fun refresh() {
|
||||
imageAdapter.refresh(filteredImages)
|
||||
imageAdapter.refresh(filteredImages, allImages)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,27 +1,23 @@
|
|||
package fr.free.nrw.commons.customselector.ui.selector
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.net.Uri
|
||||
import androidx.exifinterface.media.ExifInterface
|
||||
import fr.free.nrw.commons.customselector.database.NotForUploadStatusDao
|
||||
import fr.free.nrw.commons.customselector.database.UploadedStatus
|
||||
import fr.free.nrw.commons.customselector.database.UploadedStatusDao
|
||||
import fr.free.nrw.commons.customselector.helper.ImageHelper
|
||||
import fr.free.nrw.commons.customselector.model.Image
|
||||
import fr.free.nrw.commons.customselector.ui.adapter.ImageAdapter.ImageViewHolder
|
||||
import fr.free.nrw.commons.filepicker.PickedFiles
|
||||
import fr.free.nrw.commons.media.MediaClient
|
||||
import fr.free.nrw.commons.upload.FileProcessor
|
||||
import fr.free.nrw.commons.upload.FileUtilsWrapper
|
||||
import fr.free.nrw.commons.utils.CustomSelectorUtils
|
||||
import fr.free.nrw.commons.utils.CustomSelectorUtils.Companion.querySHA1
|
||||
import kotlinx.coroutines.*
|
||||
import timber.log.Timber
|
||||
import java.io.FileNotFoundException
|
||||
import java.io.IOException
|
||||
import java.net.UnknownHostException
|
||||
import java.util.*
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Inject
|
||||
import kotlin.collections.HashMap
|
||||
|
||||
/**
|
||||
* Image Loader class, loads images, depending on API results.
|
||||
|
|
@ -67,39 +63,43 @@ class ImageLoader @Inject constructor(
|
|||
private var mapResult: HashMap<String, Result> = HashMap()
|
||||
private var mapImageSHA1: HashMap<Uri, String> = HashMap()
|
||||
|
||||
/**
|
||||
* Coroutine Dispatchers and Scope.
|
||||
*/
|
||||
private var defaultDispatcher : CoroutineDispatcher = Dispatchers.Default
|
||||
private var ioDispatcher : CoroutineDispatcher = Dispatchers.IO
|
||||
private val scope : CoroutineScope = MainScope()
|
||||
|
||||
/**
|
||||
* Query image and setUp the view.
|
||||
*/
|
||||
fun queryAndSetView(holder: ImageViewHolder, image: Image) {
|
||||
suspend fun queryAndSetView(
|
||||
holder: ImageViewHolder,
|
||||
image: Image,
|
||||
ioDispatcher: CoroutineDispatcher,
|
||||
defaultDispatcher: CoroutineDispatcher
|
||||
) {
|
||||
|
||||
/**
|
||||
* Recycler view uses same view holder, so we can identify the latest query image from holder.
|
||||
*/
|
||||
mapHolderImage[holder] = image
|
||||
holder.itemNotUploaded()
|
||||
|
||||
scope.launch {
|
||||
holder.itemForUpload()
|
||||
|
||||
var result: Result = Result.NOTFOUND
|
||||
|
||||
if (mapHolderImage[holder] != image) {
|
||||
return@launch
|
||||
return
|
||||
}
|
||||
|
||||
val imageSHA1: String = when(mapImageSHA1[image.uri] != null) {
|
||||
true -> mapImageSHA1[image.uri]!!
|
||||
else -> CustomSelectorUtils.getImageSHA1(image.uri, ioDispatcher, fileUtilsWrapper, context.contentResolver)
|
||||
else -> CustomSelectorUtils.getImageSHA1(
|
||||
image.uri,
|
||||
ioDispatcher,
|
||||
fileUtilsWrapper,
|
||||
context.contentResolver
|
||||
)
|
||||
}
|
||||
mapImageSHA1[image.uri] = imageSHA1
|
||||
|
||||
if(imageSHA1.isEmpty())
|
||||
return@launch
|
||||
if(imageSHA1.isEmpty()) {
|
||||
return
|
||||
}
|
||||
val uploadedStatus = getFromUploaded(imageSHA1)
|
||||
|
||||
val sha1 = uploadedStatus?.let {
|
||||
|
|
@ -107,66 +107,135 @@ class ImageLoader @Inject constructor(
|
|||
uploadedStatus.modifiedImageSHA1
|
||||
} ?: run {
|
||||
if (mapHolderImage[holder] == image) {
|
||||
getSHA1(image)
|
||||
getSHA1(image, defaultDispatcher)
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}
|
||||
|
||||
if (mapHolderImage[holder] != image) {
|
||||
return@launch
|
||||
return
|
||||
}
|
||||
|
||||
val exists = notForUploadStatusDao.find(imageSHA1)
|
||||
|
||||
if (result in arrayOf(Result.NOTFOUND, Result.INVALID) && sha1.isNotEmpty()) {
|
||||
when {
|
||||
mapResult[imageSHA1] == null -> {
|
||||
// Query original image.
|
||||
result = querySHA1(imageSHA1)
|
||||
result = querySHA1(imageSHA1, ioDispatcher, mediaClient)
|
||||
when (result) {
|
||||
is Result.TRUE -> {
|
||||
mapResult[imageSHA1] = Result.TRUE
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
result = mapResult[imageSHA1]!!
|
||||
}
|
||||
}
|
||||
if (result is Result.TRUE) {
|
||||
// Original image found.
|
||||
insertIntoUploaded(imageSHA1, sha1, result is Result.TRUE, false)
|
||||
} else {
|
||||
when {
|
||||
mapResult[sha1] == null -> {
|
||||
// Original image not found, query modified image.
|
||||
result = querySHA1(sha1)
|
||||
result = querySHA1(sha1, ioDispatcher, mediaClient)
|
||||
when (result) {
|
||||
is Result.TRUE -> {
|
||||
mapResult[sha1] = Result.TRUE
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
result = mapResult[sha1]!!
|
||||
}
|
||||
}
|
||||
if (result != Result.ERROR) {
|
||||
insertIntoUploaded(imageSHA1, sha1, false, result is Result.TRUE)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val sharedPreferences: SharedPreferences =
|
||||
context
|
||||
.getSharedPreferences(ImageHelper.CUSTOM_SELECTOR_PREFERENCE_KEY, 0)
|
||||
val switchState =
|
||||
sharedPreferences.getBoolean(ImageHelper.SWITCH_STATE_PREFERENCE_KEY, true)
|
||||
|
||||
if(mapHolderImage[holder] == image) {
|
||||
if (result is Result.TRUE) holder.itemUploaded() else holder.itemNotUploaded()
|
||||
if (exists > 0) holder.itemNotForUpload() else holder.itemForUpload()
|
||||
}
|
||||
if (result is Result.TRUE) {
|
||||
if (switchState) holder.itemUploaded() else holder.itemNotUploaded()
|
||||
} else holder.itemNotUploaded()
|
||||
|
||||
if (exists > 0) {
|
||||
if (switchState) holder.itemNotForUpload() else holder.itemForUpload()
|
||||
} else holder.itemForUpload()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Query SHA1, return result if previously queried, otherwise start a new query.
|
||||
*
|
||||
* @return Query result.
|
||||
* Finds out the next actionable image position
|
||||
*/
|
||||
suspend fun nextActionableImage(
|
||||
allImages: List<Image>, ioDispatcher: CoroutineDispatcher,
|
||||
defaultDispatcher: CoroutineDispatcher,
|
||||
nextImagePosition: Int
|
||||
): Int {
|
||||
var next = -1
|
||||
|
||||
suspend fun querySHA1(SHA1: String): Result {
|
||||
return withContext(ioDispatcher) {
|
||||
mapResult[SHA1]?.let {
|
||||
return@withContext it
|
||||
// Traversing from given position to the end
|
||||
for (i in nextImagePosition until allImages.size){
|
||||
val it = allImages[i]
|
||||
val imageSHA1: String = when (mapImageSHA1[it.uri] != null) {
|
||||
true -> mapImageSHA1[it.uri]!!
|
||||
else -> CustomSelectorUtils.getImageSHA1(
|
||||
it.uri,
|
||||
ioDispatcher,
|
||||
fileUtilsWrapper,
|
||||
context.contentResolver
|
||||
)
|
||||
}
|
||||
var result: Result = Result.FALSE
|
||||
try {
|
||||
if (mediaClient.checkFileExistsUsingSha(SHA1).blockingGet()) {
|
||||
mapResult[SHA1] = Result.TRUE
|
||||
result = Result.TRUE
|
||||
next = notForUploadStatusDao.find(imageSHA1)
|
||||
|
||||
// After checking the image in the not for upload database, if the image is present then
|
||||
// skips the image and moves to next image for checking
|
||||
if(next > 0){
|
||||
continue
|
||||
|
||||
// Otherwise checks in already uploaded database
|
||||
} else {
|
||||
next = uploadedStatusDao.findByImageSHA1(imageSHA1, true)
|
||||
|
||||
// If the image is not present in the already uploaded database, checks for it's
|
||||
// modified SHA1 in already uploaded database
|
||||
if (next <= 0) {
|
||||
val modifiedImageSha1 = getSHA1(it, defaultDispatcher)
|
||||
next = uploadedStatusDao.findByModifiedImageSHA1(
|
||||
modifiedImageSha1,
|
||||
true
|
||||
)
|
||||
|
||||
// If the modified image SHA1 is not present in the already uploaded database,
|
||||
// returns the position as next actionable image position
|
||||
if (next <= 0) {
|
||||
return i
|
||||
|
||||
// If present in tha db then skips iteration for the image and moves to the next
|
||||
// for checking
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
if (e is UnknownHostException) {
|
||||
// Handle no network connection.
|
||||
Timber.e(e, "Network Connection Error")
|
||||
|
||||
// If present in tha db then skips iteration for the image and moves to the next
|
||||
// for checking
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
result = Result.ERROR
|
||||
e.printStackTrace()
|
||||
}
|
||||
result
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -174,11 +243,17 @@ class ImageLoader @Inject constructor(
|
|||
*
|
||||
* @return sha1 of the image
|
||||
*/
|
||||
suspend fun getSHA1(image: Image): String {
|
||||
suspend fun getSHA1(image: Image, defaultDispatcher: CoroutineDispatcher): String {
|
||||
mapModifiedImageSHA1[image]?.let{
|
||||
return it
|
||||
}
|
||||
val sha1 = generateModifiedSHA1(image);
|
||||
val sha1 = CustomSelectorUtils
|
||||
.generateModifiedSHA1(image,
|
||||
defaultDispatcher,
|
||||
context,
|
||||
fileProcessor,
|
||||
fileUtilsWrapper
|
||||
)
|
||||
mapModifiedImageSHA1[image] = sha1;
|
||||
return sha1;
|
||||
}
|
||||
|
|
@ -221,35 +296,6 @@ class ImageLoader @Inject constructor(
|
|||
return Result.INVALID
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate Modified SHA1 using present Exif settings.
|
||||
*
|
||||
* @return modified sha1
|
||||
*/
|
||||
private suspend fun generateModifiedSHA1(image: Image) : String {
|
||||
return withContext(defaultDispatcher) {
|
||||
val uploadableFile = PickedFiles.pickedExistingPicture(context, image.uri)
|
||||
val exifInterface: ExifInterface? = try {
|
||||
ExifInterface(uploadableFile.file!!)
|
||||
} catch (e: IOException) {
|
||||
Timber.e(e)
|
||||
null
|
||||
}
|
||||
fileProcessor.redactExifTags(exifInterface, fileProcessor.getExifTagsToRedact())
|
||||
val sha1 =
|
||||
fileUtilsWrapper.getSHA1(fileUtilsWrapper.getFileInputStream(uploadableFile.filePath))
|
||||
uploadableFile.file.delete()
|
||||
sha1
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* CleanUp function.
|
||||
*/
|
||||
fun cleanUP() {
|
||||
scope.cancel()
|
||||
}
|
||||
|
||||
/**
|
||||
* Sealed Result class.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1,17 +1,28 @@
|
|||
package fr.free.nrw.commons.utils
|
||||
|
||||
import android.content.ContentResolver
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import androidx.exifinterface.media.ExifInterface
|
||||
import fr.free.nrw.commons.customselector.model.Image
|
||||
import fr.free.nrw.commons.customselector.ui.selector.ImageLoader
|
||||
import fr.free.nrw.commons.filepicker.PickedFiles
|
||||
import fr.free.nrw.commons.media.MediaClient
|
||||
import fr.free.nrw.commons.upload.FileProcessor
|
||||
import fr.free.nrw.commons.upload.FileUtilsWrapper
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
import java.io.FileNotFoundException
|
||||
import java.io.IOException
|
||||
import java.net.UnknownHostException
|
||||
|
||||
/**
|
||||
* Util Class for Custom Selector
|
||||
*/
|
||||
class CustomSelectorUtils {
|
||||
companion object {
|
||||
|
||||
/**
|
||||
* Get image sha1 from uri, used to retrieve the original image sha1.
|
||||
*/
|
||||
|
|
@ -31,5 +42,60 @@ class CustomSelectorUtils {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate Modified SHA1 using present Exif settings.
|
||||
*
|
||||
* @return modified sha1
|
||||
*/
|
||||
suspend fun generateModifiedSHA1(image: Image,
|
||||
defaultDispatcher : CoroutineDispatcher,
|
||||
context: Context,
|
||||
fileProcessor: FileProcessor,
|
||||
fileUtilsWrapper: FileUtilsWrapper
|
||||
) : String {
|
||||
return withContext(defaultDispatcher) {
|
||||
val uploadableFile = PickedFiles.pickedExistingPicture(context, image.uri)
|
||||
val exifInterface: ExifInterface? = try {
|
||||
ExifInterface(uploadableFile.file!!)
|
||||
} catch (e: IOException) {
|
||||
Timber.e(e)
|
||||
null
|
||||
}
|
||||
fileProcessor.redactExifTags(exifInterface, fileProcessor.getExifTagsToRedact())
|
||||
val sha1 =
|
||||
fileUtilsWrapper.getSHA1(fileUtilsWrapper.getFileInputStream(uploadableFile.filePath))
|
||||
uploadableFile.file.delete()
|
||||
sha1
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Query SHA1, return result if previously queried, otherwise start a new query.
|
||||
*
|
||||
* @return Query result.
|
||||
*/
|
||||
suspend fun querySHA1(SHA1: String,
|
||||
ioDispatcher : CoroutineDispatcher,
|
||||
mediaClient: MediaClient
|
||||
): ImageLoader.Result {
|
||||
return withContext(ioDispatcher) {
|
||||
|
||||
var result: ImageLoader.Result = ImageLoader.Result.FALSE
|
||||
try {
|
||||
if (mediaClient.checkFileExistsUsingSha(SHA1).blockingGet()) {
|
||||
result = ImageLoader.Result.TRUE
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
if (e is UnknownHostException) {
|
||||
// Handle no network connection.
|
||||
Timber.e(e, "Network Connection Error")
|
||||
}
|
||||
result = ImageLoader.Result.ERROR
|
||||
e.printStackTrace()
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,13 +3,26 @@
|
|||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:background="?attr/mainBackground">
|
||||
|
||||
<Switch
|
||||
android:id="@+id/switchWidget"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
android:text="@string/hide_already_actioned_pictures"
|
||||
android:padding="@dimen/dimen_10"
|
||||
android:checked="true" />
|
||||
|
||||
<com.simplecityapps.recyclerview_fastscroll.views.FastScrollRecyclerView
|
||||
android:id="@+id/selector_rv"
|
||||
android:background="?attr/mainBackground"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?attr/mainBackground"
|
||||
android:layout_height="@dimen/dimen_0"
|
||||
app:fastScrollPopupBgColor="@color/primaryColor"
|
||||
app:fastScrollPopupTextColor="@android:color/primary_text_dark"
|
||||
app:fastScrollPopupTextSize="@dimen/subheading_text_size"
|
||||
|
|
@ -17,6 +30,10 @@
|
|||
app:fastScrollThumbColor="@color/primaryColor"
|
||||
app:fastScrollTrackColor="@color/upload_overlay_background_light"
|
||||
app:fastScrollPopupPosition="adjacent"
|
||||
app:layout_constraintBottom_toTopOf="@id/progressLayout"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/switchWidget"
|
||||
/>
|
||||
|
||||
<TextView
|
||||
|
|
@ -45,4 +62,41 @@
|
|||
app:layout_constraintStart_toStartOf="parent"
|
||||
/>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/progressLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="@dimen/dimen_5"
|
||||
android:background="@color/drawerHeader_background_light"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/selector_rv">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/hiding_already_actioned_pictures"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:gravity="center"
|
||||
android:padding="@dimen/dimen_5"/>
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progressBar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:visibility="visible"
|
||||
app:layout_constraintTop_toBottomOf="@id/text"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
/>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
|
@ -736,4 +736,7 @@ Upload your first media by tapping on the add button.</string>
|
|||
<string name="your_feedback">Your feedback</string>
|
||||
<string name="mark_as_not_for_upload">Mark as not for upload</string>
|
||||
<string name="unmark_as_not_for_upload">Unmark as not for upload</string>
|
||||
<string name="show_already_actioned_pictures">Show already actioned pictures</string>
|
||||
<string name="hide_already_actioned_pictures">Hide already actioned pictures</string>
|
||||
<string name="hiding_already_actioned_pictures">Hiding already actioned pictures</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package fr.free.nrw.commons.customselector.ui.adapter
|
|||
|
||||
import android.content.ContentResolver
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.net.Uri
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
|
|
@ -23,6 +24,8 @@ import org.robolectric.Robolectric
|
|||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.annotation.Config
|
||||
import java.lang.reflect.Field
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
/**
|
||||
* Custom Selector image adapter test.
|
||||
|
|
@ -88,8 +91,10 @@ class ImageAdapterTest {
|
|||
|
||||
// Parameters.
|
||||
images.add(image)
|
||||
imageAdapter.init(images)
|
||||
imageAdapter.init(images, images, TreeMap())
|
||||
|
||||
whenever(context.getSharedPreferences("custom_selector", 0))
|
||||
.thenReturn(Mockito.mock(SharedPreferences::class.java))
|
||||
// Test conditions.
|
||||
imageAdapter.onBindViewHolder(holder, 0)
|
||||
selectedImageField.set(imageAdapter, images)
|
||||
|
|
@ -101,7 +106,7 @@ class ImageAdapterTest {
|
|||
*/
|
||||
@Test
|
||||
fun init() {
|
||||
imageAdapter.init(images)
|
||||
imageAdapter.init(images, images, TreeMap())
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -115,7 +120,7 @@ class ImageAdapterTest {
|
|||
|
||||
// Parameters
|
||||
images.addAll(listOf(image, image))
|
||||
imageAdapter.init(images)
|
||||
imageAdapter.init(images, images, TreeMap())
|
||||
|
||||
// Test conditions
|
||||
holder.itemUploaded()
|
||||
|
|
@ -142,7 +147,7 @@ class ImageAdapterTest {
|
|||
*/
|
||||
@Test
|
||||
fun getImageIdAt() {
|
||||
imageAdapter.init(listOf(image))
|
||||
imageAdapter.init(listOf(image), listOf(image), TreeMap())
|
||||
Assertions.assertEquals(1, imageAdapter.getImageIdAt(0))
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ package fr.free.nrw.commons.customselector.ui.selector
|
|||
|
||||
import android.content.ContentResolver
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.net.Uri
|
||||
import com.nhaarman.mockitokotlin2.*
|
||||
import fr.free.nrw.commons.TestCommonsApplication
|
||||
|
|
@ -117,8 +118,6 @@ class ImageLoaderTest {
|
|||
Whitebox.setInternalState(imageLoader, "mapModifiedImageSHA1", mapModifiedImageSHA1);
|
||||
Whitebox.setInternalState(imageLoader, "mapResult", mapResult);
|
||||
Whitebox.setInternalState(imageLoader, "context", context)
|
||||
Whitebox.setInternalState(imageLoader, "ioDispatcher", testDispacher)
|
||||
Whitebox.setInternalState(imageLoader, "defaultDispatcher", testDispacher)
|
||||
|
||||
whenever(contentResolver.openInputStream(uri)).thenReturn(inputStream)
|
||||
whenever(context.contentResolver).thenReturn(contentResolver)
|
||||
|
|
@ -141,14 +140,17 @@ class ImageLoaderTest {
|
|||
@Test
|
||||
fun testQueryAndSetViewUploadedStatusNull() = testDispacher.runBlockingTest {
|
||||
whenever(uploadedStatusDao.getUploadedFromImageSHA1(any())).thenReturn(null)
|
||||
whenever(notForUploadStatusDao.find(any())).thenReturn(0)
|
||||
mapModifiedImageSHA1[image] = "testSha1"
|
||||
mapImageSHA1[uri] = "testSha1"
|
||||
whenever(context.getSharedPreferences("custom_selector", 0))
|
||||
.thenReturn(Mockito.mock(SharedPreferences::class.java))
|
||||
|
||||
mapResult["testSha1"] = ImageLoader.Result.TRUE
|
||||
imageLoader.queryAndSetView(holder, image)
|
||||
imageLoader.queryAndSetView(holder, image, testDispacher, testDispacher)
|
||||
|
||||
mapResult["testSha1"] = ImageLoader.Result.FALSE
|
||||
imageLoader.queryAndSetView(holder, image)
|
||||
imageLoader.queryAndSetView(holder, image, testDispacher, testDispacher)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -157,20 +159,10 @@ class ImageLoaderTest {
|
|||
@Test
|
||||
fun testQueryAndSetViewUploadedStatusNotNull() = testDispacher.runBlockingTest {
|
||||
whenever(uploadedStatusDao.getUploadedFromImageSHA1(any())).thenReturn(uploadedStatus)
|
||||
imageLoader.queryAndSetView(holder, image)
|
||||
}
|
||||
|
||||
/**
|
||||
* Test querySha1
|
||||
*/
|
||||
@Test
|
||||
fun testQuerySha1() = testDispacher.runBlockingTest {
|
||||
|
||||
whenever(single.blockingGet()).thenReturn(true)
|
||||
whenever(mediaClient.checkFileExistsUsingSha("testSha1")).thenReturn(single)
|
||||
whenever(fileUtilsWrapper.getSHA1(any())).thenReturn("testSha1")
|
||||
|
||||
imageLoader.querySHA1("testSha1")
|
||||
whenever(notForUploadStatusDao.find(any())).thenReturn(0)
|
||||
whenever(context.getSharedPreferences("custom_selector", 0))
|
||||
.thenReturn(Mockito.mock(SharedPreferences::class.java))
|
||||
imageLoader.queryAndSetView(holder, image, testDispacher, testDispacher)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -188,13 +180,13 @@ class ImageLoaderTest {
|
|||
whenever(fileUtilsWrapper.getFileInputStream("ABC")).thenReturn(inputStream)
|
||||
whenever(fileUtilsWrapper.getSHA1(inputStream)).thenReturn("testSha1")
|
||||
|
||||
Assert.assertEquals("testSha1", imageLoader.getSHA1(image));
|
||||
Assert.assertEquals("testSha1", imageLoader.getSHA1(image, testDispacher));
|
||||
whenever(PickedFiles.pickedExistingPicture(context, Uri.parse("test"))).thenReturn(
|
||||
uploadableFile
|
||||
)
|
||||
|
||||
mapModifiedImageSHA1[image] = "testSha2"
|
||||
Assert.assertEquals("testSha2", imageLoader.getSHA1(image));
|
||||
Assert.assertEquals("testSha2", imageLoader.getSHA1(image, testDispacher));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -218,8 +210,4 @@ class ImageLoaderTest {
|
|||
imageLoader.getResultFromUploadedStatus(uploadedStatus))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCleanUP() {
|
||||
imageLoader.cleanUP()
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue