mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-27 04:43:54 +01:00
[GSoC] Insert and Remove Images from not for upload (#4999)
* Inserted and marked images as not for upload * Documentation added * Test delete * Implemented remove from not for upload * Test fixed * Requested changes done * Added tests for new lines in existing classes
This commit is contained in:
parent
3bb3908eeb
commit
40323be3a0
16 changed files with 359 additions and 29 deletions
|
|
@ -46,6 +46,12 @@ abstract class NotForUploadStatusDao {
|
||||||
suspend fun deleteNotForUploadWithImageSHA1(imageSHA1: String) {
|
suspend fun deleteNotForUploadWithImageSHA1(imageSHA1: String) {
|
||||||
return deleteWithImageSHA1(imageSHA1)
|
return deleteWithImageSHA1(imageSHA1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether the imageSHA1 is present in database
|
||||||
|
*/
|
||||||
|
@Query("SELECT COUNT() FROM not_for_upload_table WHERE imageSHA1 = (:imageSHA1) ")
|
||||||
|
abstract suspend fun find(imageSHA1 : String): Int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,9 @@ interface ImageSelectListener {
|
||||||
/**
|
/**
|
||||||
* onSelectedImagesChanged
|
* onSelectedImagesChanged
|
||||||
* @param selectedImages : new selected images.
|
* @param selectedImages : new selected images.
|
||||||
|
* @param selectedNotForUploadImages : number of selected not for upload images
|
||||||
*/
|
*/
|
||||||
fun onSelectedImagesChanged(selectedImages: ArrayList<Image>)
|
fun onSelectedImagesChanged(selectedImages: ArrayList<Image>, selectedNotForUploadImages: Int)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* onLongPress
|
* onLongPress
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
package fr.free.nrw.commons.customselector.listeners
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refresh UI Listener
|
||||||
|
*/
|
||||||
|
interface RefreshUIListener {
|
||||||
|
/**
|
||||||
|
* Refreshes the data in adapter
|
||||||
|
*/
|
||||||
|
fun refresh()
|
||||||
|
}
|
||||||
|
|
@ -53,6 +53,11 @@ class ImageAdapter(
|
||||||
*/
|
*/
|
||||||
private var selectedImages = arrayListOf<Image>()
|
private var selectedImages = arrayListOf<Image>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of selected not for upload images
|
||||||
|
*/
|
||||||
|
private var selectedNotForUploadImages = 0
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List of all images in adapter.
|
* List of all images in adapter.
|
||||||
*/
|
*/
|
||||||
|
|
@ -109,6 +114,9 @@ class ImageAdapter(
|
||||||
val clickedIndex = ImageHelper.getIndex(selectedImages, images[position])
|
val clickedIndex = ImageHelper.getIndex(selectedImages, images[position])
|
||||||
if (clickedIndex != -1) {
|
if (clickedIndex != -1) {
|
||||||
selectedImages.removeAt(clickedIndex)
|
selectedImages.removeAt(clickedIndex)
|
||||||
|
if (holder.isItemNotForUpload()) {
|
||||||
|
selectedNotForUploadImages--
|
||||||
|
}
|
||||||
notifyItemChanged(position, ImageUnselected())
|
notifyItemChanged(position, ImageUnselected())
|
||||||
val indexes = ImageHelper.getIndexList(selectedImages, images)
|
val indexes = ImageHelper.getIndexList(selectedImages, images)
|
||||||
for (index in indexes) {
|
for (index in indexes) {
|
||||||
|
|
@ -118,11 +126,14 @@ class ImageAdapter(
|
||||||
if(holder.isItemUploaded()){
|
if(holder.isItemUploaded()){
|
||||||
Toast.makeText(context, R.string.custom_selector_already_uploaded_image_text, Toast.LENGTH_SHORT).show()
|
Toast.makeText(context, R.string.custom_selector_already_uploaded_image_text, Toast.LENGTH_SHORT).show()
|
||||||
} else {
|
} else {
|
||||||
|
if (holder.isItemNotForUpload()) {
|
||||||
|
selectedNotForUploadImages++
|
||||||
|
}
|
||||||
selectedImages.add(images[position])
|
selectedImages.add(images[position])
|
||||||
notifyItemChanged(position, ImageSelectedOrUpdated())
|
notifyItemChanged(position, ImageSelectedOrUpdated())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
imageSelectListener.onSelectedImagesChanged(selectedImages)
|
imageSelectListener.onSelectedImagesChanged(selectedImages, selectedNotForUploadImages)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -138,6 +149,18 @@ class ImageAdapter(
|
||||||
diffResult.dispatchUpdatesTo(this)
|
diffResult.dispatchUpdatesTo(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refresh the data in the adapter
|
||||||
|
*/
|
||||||
|
fun refresh(newImages: List<Image>) {
|
||||||
|
selectedNotForUploadImages = 0
|
||||||
|
selectedImages.clear()
|
||||||
|
images.clear()
|
||||||
|
selectedImages = arrayListOf()
|
||||||
|
init(newImages)
|
||||||
|
notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the total number of items in the data set held by the adapter.
|
* Returns the total number of items in the data set held by the adapter.
|
||||||
*
|
*
|
||||||
|
|
@ -158,6 +181,7 @@ class ImageAdapter(
|
||||||
val image: ImageView = itemView.findViewById(R.id.image_thumbnail)
|
val image: ImageView = itemView.findViewById(R.id.image_thumbnail)
|
||||||
private val selectedNumber: TextView = itemView.findViewById(R.id.selected_count)
|
private val selectedNumber: TextView = itemView.findViewById(R.id.selected_count)
|
||||||
private val uploadedGroup: Group = itemView.findViewById(R.id.uploaded_group)
|
private val uploadedGroup: Group = itemView.findViewById(R.id.uploaded_group)
|
||||||
|
private val notForUploadGroup: Group = itemView.findViewById(R.id.not_for_upload_group)
|
||||||
private val selectedGroup: Group = itemView.findViewById(R.id.selected_group)
|
private val selectedGroup: Group = itemView.findViewById(R.id.selected_group)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -182,9 +206,24 @@ class ImageAdapter(
|
||||||
uploadedGroup.visibility = View.VISIBLE
|
uploadedGroup.visibility = View.VISIBLE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Item is not for upload view
|
||||||
|
*/
|
||||||
|
fun itemNotForUpload() {
|
||||||
|
notForUploadGroup.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
|
||||||
fun isItemUploaded():Boolean {
|
fun isItemUploaded():Boolean {
|
||||||
return uploadedGroup.visibility == View.VISIBLE
|
return uploadedGroup.visibility == View.VISIBLE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Item is not for upload
|
||||||
|
*/
|
||||||
|
fun isItemNotForUpload():Boolean {
|
||||||
|
return notForUploadGroup.visibility == View.VISIBLE
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Item Not Uploaded view.
|
* Item Not Uploaded view.
|
||||||
*/
|
*/
|
||||||
|
|
@ -192,6 +231,12 @@ class ImageAdapter(
|
||||||
uploadedGroup.visibility = View.GONE
|
uploadedGroup.visibility = View.GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Item can be uploaded view
|
||||||
|
*/
|
||||||
|
fun itemForUpload() {
|
||||||
|
notForUploadGroup.visibility = View.GONE
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -14,11 +14,17 @@ import android.widget.TextView
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import fr.free.nrw.commons.R
|
import fr.free.nrw.commons.R
|
||||||
|
import fr.free.nrw.commons.customselector.database.NotForUploadStatus
|
||||||
|
import fr.free.nrw.commons.customselector.database.NotForUploadStatusDao
|
||||||
import fr.free.nrw.commons.customselector.listeners.FolderClickListener
|
import fr.free.nrw.commons.customselector.listeners.FolderClickListener
|
||||||
import fr.free.nrw.commons.customselector.listeners.ImageSelectListener
|
import fr.free.nrw.commons.customselector.listeners.ImageSelectListener
|
||||||
import fr.free.nrw.commons.customselector.model.Image
|
import fr.free.nrw.commons.customselector.model.Image
|
||||||
import fr.free.nrw.commons.media.ZoomableActivity
|
import fr.free.nrw.commons.media.ZoomableActivity
|
||||||
import fr.free.nrw.commons.theme.BaseActivity
|
import fr.free.nrw.commons.theme.BaseActivity
|
||||||
|
import fr.free.nrw.commons.upload.FileUtilsWrapper
|
||||||
|
import fr.free.nrw.commons.utils.CustomSelectorUtils
|
||||||
|
import kotlinx.android.synthetic.main.custom_selector_bottom_layout.*
|
||||||
|
import kotlinx.coroutines.*
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
|
@ -54,6 +60,29 @@ class CustomSelectorActivity: BaseActivity(), FolderClickListener, ImageSelectLi
|
||||||
*/
|
*/
|
||||||
@Inject lateinit var customSelectorViewModelFactory: CustomSelectorViewModelFactory
|
@Inject lateinit var customSelectorViewModelFactory: CustomSelectorViewModelFactory
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NotForUploadStatus Dao class for database operations
|
||||||
|
*/
|
||||||
|
@Inject
|
||||||
|
lateinit var notForUploadStatusDao: NotForUploadStatusDao
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FileUtilsWrapper class to get imageSHA1 from uri
|
||||||
|
*/
|
||||||
|
@Inject
|
||||||
|
lateinit var fileUtilsWrapper: FileUtilsWrapper
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Coroutine Dispatchers and Scope.
|
||||||
|
*/
|
||||||
|
private val scope : CoroutineScope = MainScope()
|
||||||
|
private var ioDispatcher : CoroutineDispatcher = Dispatchers.IO
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Image Fragment instance
|
||||||
|
*/
|
||||||
|
var imageFragment: ImageFragment? = null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* onCreate Activity, sets theme, initialises the view model, setup view.
|
* onCreate Activity, sets theme, initialises the view model, setup view.
|
||||||
*/
|
*/
|
||||||
|
|
@ -112,6 +141,99 @@ class CustomSelectorActivity: BaseActivity(), FolderClickListener, ImageSelectLi
|
||||||
private fun setUpBottomLayout() {
|
private fun setUpBottomLayout() {
|
||||||
val done : Button = findViewById(R.id.upload)
|
val done : Button = findViewById(R.id.upload)
|
||||||
done.setOnClickListener { onDone() }
|
done.setOnClickListener { onDone() }
|
||||||
|
|
||||||
|
val notForUpload : Button = findViewById(R.id.not_for_upload)
|
||||||
|
notForUpload.setOnClickListener{ onClickNotForUpload() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets selected images and proceed for database operations
|
||||||
|
*/
|
||||||
|
private fun onClickNotForUpload() {
|
||||||
|
val selectedImages = viewModel.selectedImages.value
|
||||||
|
if(selectedImages.isNullOrEmpty()) {
|
||||||
|
markAsNotForUpload(arrayListOf())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var i = 0
|
||||||
|
while (i < selectedImages.size) {
|
||||||
|
val path = selectedImages[i].path
|
||||||
|
val file = File(path)
|
||||||
|
if (!file.exists()) {
|
||||||
|
selectedImages.removeAt(i)
|
||||||
|
i--
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
markAsNotForUpload(selectedImages)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Insert selected images in the database
|
||||||
|
*/
|
||||||
|
private fun markAsNotForUpload(images: ArrayList<Image>) {
|
||||||
|
insertIntoNotForUpload(images)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializing ImageFragment
|
||||||
|
*/
|
||||||
|
fun setOnDataListener(imageFragment: ImageFragment?) {
|
||||||
|
this.imageFragment = imageFragment
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Insert images into not for upload
|
||||||
|
* Remove images from not for upload
|
||||||
|
* Refresh the UI
|
||||||
|
*/
|
||||||
|
private fun insertIntoNotForUpload(images: ArrayList<Image>) {
|
||||||
|
scope.launch {
|
||||||
|
var allImagesAlreadyNotForUpload = true
|
||||||
|
images.forEach{
|
||||||
|
val imageSHA1 = CustomSelectorUtils.getImageSHA1(
|
||||||
|
it.uri,
|
||||||
|
ioDispatcher,
|
||||||
|
fileUtilsWrapper,
|
||||||
|
contentResolver
|
||||||
|
)
|
||||||
|
val exists = notForUploadStatusDao.find(imageSHA1)
|
||||||
|
if (exists < 1) {
|
||||||
|
allImagesAlreadyNotForUpload = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!allImagesAlreadyNotForUpload) {
|
||||||
|
images.forEach {
|
||||||
|
val imageSHA1 = CustomSelectorUtils.getImageSHA1(
|
||||||
|
it.uri,
|
||||||
|
ioDispatcher,
|
||||||
|
fileUtilsWrapper,
|
||||||
|
contentResolver
|
||||||
|
)
|
||||||
|
notForUploadStatusDao.insert(
|
||||||
|
NotForUploadStatus(
|
||||||
|
imageSHA1,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
images.forEach {
|
||||||
|
val imageSHA1 = CustomSelectorUtils.getImageSHA1(
|
||||||
|
it.uri,
|
||||||
|
ioDispatcher,
|
||||||
|
fileUtilsWrapper,
|
||||||
|
contentResolver
|
||||||
|
)
|
||||||
|
notForUploadStatusDao.deleteNotForUploadWithImageSHA1(imageSHA1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
imageFragment!!.refresh()
|
||||||
|
val bottomLayout : ConstraintLayout = findViewById(R.id.bottom_layout)
|
||||||
|
bottomLayout.visibility = View.GONE
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -156,11 +278,25 @@ class CustomSelectorActivity: BaseActivity(), FolderClickListener, ImageSelectLi
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* override Selected Images Change, update view model selected images.
|
* override Selected Images Change, update view model selected images and change UI.
|
||||||
*/
|
*/
|
||||||
override fun onSelectedImagesChanged(selectedImages: ArrayList<Image>) {
|
override fun onSelectedImagesChanged(selectedImages: ArrayList<Image>,
|
||||||
|
selectedNotForUploadImages: Int) {
|
||||||
viewModel.selectedImages.value = selectedImages
|
viewModel.selectedImages.value = selectedImages
|
||||||
|
|
||||||
|
if (selectedNotForUploadImages > 0) {
|
||||||
|
upload.isEnabled = false
|
||||||
|
upload.alpha = 0.5f
|
||||||
|
} else {
|
||||||
|
upload.isEnabled = true
|
||||||
|
upload.alpha = 1f
|
||||||
|
}
|
||||||
|
|
||||||
|
not_for_upload.text = when (selectedImages.size == selectedNotForUploadImages) {
|
||||||
|
true -> getString(R.string.unmark_as_not_for_upload)
|
||||||
|
else -> getString(R.string.mark_as_not_for_upload)
|
||||||
|
}
|
||||||
|
|
||||||
val bottomLayout : ConstraintLayout = findViewById(R.id.bottom_layout)
|
val bottomLayout : ConstraintLayout = findViewById(R.id.bottom_layout)
|
||||||
bottomLayout.visibility = if (selectedImages.isEmpty()) View.GONE else View.VISIBLE
|
bottomLayout.visibility = if (selectedImages.isEmpty()) View.GONE else View.VISIBLE
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package fr.free.nrw.commons.customselector.ui.selector
|
package fr.free.nrw.commons.customselector.ui.selector
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
|
@ -14,6 +15,7 @@ import androidx.recyclerview.widget.RecyclerView
|
||||||
import fr.free.nrw.commons.R
|
import fr.free.nrw.commons.R
|
||||||
import fr.free.nrw.commons.customselector.helper.ImageHelper
|
import fr.free.nrw.commons.customselector.helper.ImageHelper
|
||||||
import fr.free.nrw.commons.customselector.listeners.ImageSelectListener
|
import fr.free.nrw.commons.customselector.listeners.ImageSelectListener
|
||||||
|
import fr.free.nrw.commons.customselector.listeners.RefreshUIListener
|
||||||
import fr.free.nrw.commons.customselector.model.CallbackStatus
|
import fr.free.nrw.commons.customselector.model.CallbackStatus
|
||||||
import fr.free.nrw.commons.customselector.model.Image
|
import fr.free.nrw.commons.customselector.model.Image
|
||||||
import fr.free.nrw.commons.customselector.model.Result
|
import fr.free.nrw.commons.customselector.model.Result
|
||||||
|
|
@ -30,7 +32,7 @@ import javax.inject.Inject
|
||||||
/**
|
/**
|
||||||
* Custom Selector Image Fragment.
|
* Custom Selector Image Fragment.
|
||||||
*/
|
*/
|
||||||
class ImageFragment: CommonsDaggerSupportFragment() {
|
class ImageFragment: CommonsDaggerSupportFragment(), RefreshUIListener {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Current bucketId.
|
* Current bucketId.
|
||||||
|
|
@ -135,6 +137,18 @@ class ImageFragment: CommonsDaggerSupportFragment() {
|
||||||
return root
|
return root
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attaching data listener
|
||||||
|
*/
|
||||||
|
override fun onAttach(activity: Activity) {
|
||||||
|
super.onAttach(activity)
|
||||||
|
try {
|
||||||
|
(getActivity() as CustomSelectorActivity).setOnDataListener(this)
|
||||||
|
} catch (ex: Exception) {
|
||||||
|
ex.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle view model result.
|
* Handle view model result.
|
||||||
*/
|
*/
|
||||||
|
|
@ -211,4 +225,8 @@ class ImageFragment: CommonsDaggerSupportFragment() {
|
||||||
}
|
}
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun refresh() {
|
||||||
|
imageAdapter.refresh(filteredImages)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3,6 +3,7 @@ package fr.free.nrw.commons.customselector.ui.selector
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import androidx.exifinterface.media.ExifInterface
|
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.UploadedStatus
|
||||||
import fr.free.nrw.commons.customselector.database.UploadedStatusDao
|
import fr.free.nrw.commons.customselector.database.UploadedStatusDao
|
||||||
import fr.free.nrw.commons.customselector.model.Image
|
import fr.free.nrw.commons.customselector.model.Image
|
||||||
|
|
@ -11,6 +12,7 @@ import fr.free.nrw.commons.filepicker.PickedFiles
|
||||||
import fr.free.nrw.commons.media.MediaClient
|
import fr.free.nrw.commons.media.MediaClient
|
||||||
import fr.free.nrw.commons.upload.FileProcessor
|
import fr.free.nrw.commons.upload.FileProcessor
|
||||||
import fr.free.nrw.commons.upload.FileUtilsWrapper
|
import fr.free.nrw.commons.upload.FileUtilsWrapper
|
||||||
|
import fr.free.nrw.commons.utils.CustomSelectorUtils
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.io.FileNotFoundException
|
import java.io.FileNotFoundException
|
||||||
|
|
@ -46,6 +48,11 @@ class ImageLoader @Inject constructor(
|
||||||
*/
|
*/
|
||||||
var uploadedStatusDao: UploadedStatusDao,
|
var uploadedStatusDao: UploadedStatusDao,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NotForUploadDao for database operations
|
||||||
|
*/
|
||||||
|
var notForUploadStatusDao: NotForUploadStatusDao,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Context for coroutine.
|
* Context for coroutine.
|
||||||
*/
|
*/
|
||||||
|
|
@ -86,7 +93,11 @@ class ImageLoader @Inject constructor(
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
|
|
||||||
val imageSHA1 = getImageSHA1(image.uri)
|
val imageSHA1: String = when(mapImageSHA1[image.uri] != null) {
|
||||||
|
true -> mapImageSHA1[image.uri]!!
|
||||||
|
else -> CustomSelectorUtils.getImageSHA1(image.uri, ioDispatcher, fileUtilsWrapper, context.contentResolver)
|
||||||
|
}
|
||||||
|
|
||||||
if(imageSHA1.isEmpty())
|
if(imageSHA1.isEmpty())
|
||||||
return@launch
|
return@launch
|
||||||
val uploadedStatus = getFromUploaded(imageSHA1)
|
val uploadedStatus = getFromUploaded(imageSHA1)
|
||||||
|
|
@ -106,6 +117,8 @@ class ImageLoader @Inject constructor(
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val exists = notForUploadStatusDao.find(imageSHA1)
|
||||||
|
|
||||||
if (result in arrayOf(Result.NOTFOUND, Result.INVALID) && sha1.isNotEmpty()) {
|
if (result in arrayOf(Result.NOTFOUND, Result.INVALID) && sha1.isNotEmpty()) {
|
||||||
// Query original image.
|
// Query original image.
|
||||||
result = querySHA1(imageSHA1)
|
result = querySHA1(imageSHA1)
|
||||||
|
|
@ -122,6 +135,7 @@ class ImageLoader @Inject constructor(
|
||||||
}
|
}
|
||||||
if(mapHolderImage[holder] == image) {
|
if(mapHolderImage[holder] == image) {
|
||||||
if (result is Result.TRUE) holder.itemUploaded() else holder.itemNotUploaded()
|
if (result is Result.TRUE) holder.itemUploaded() else holder.itemNotUploaded()
|
||||||
|
if (exists > 0) holder.itemNotForUpload() else holder.itemForUpload()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -190,25 +204,6 @@ class ImageLoader @Inject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get image sha1 from uri, used to retrieve the original image sha1.
|
|
||||||
*/
|
|
||||||
suspend fun getImageSHA1(uri: Uri): String {
|
|
||||||
return withContext(ioDispatcher) {
|
|
||||||
mapImageSHA1[uri]?.let{
|
|
||||||
return@withContext it
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
val result = fileUtilsWrapper.getSHA1(context.contentResolver.openInputStream(uri))
|
|
||||||
mapImageSHA1[uri] = result
|
|
||||||
result
|
|
||||||
} catch (e: FileNotFoundException){
|
|
||||||
e.printStackTrace()
|
|
||||||
""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get result data from database.
|
* Get result data from database.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
package fr.free.nrw.commons.utils
|
||||||
|
|
||||||
|
import android.content.ContentResolver
|
||||||
|
import android.net.Uri
|
||||||
|
import fr.free.nrw.commons.upload.FileUtilsWrapper
|
||||||
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import java.io.FileNotFoundException
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Util Class for Custom Selector
|
||||||
|
*/
|
||||||
|
class CustomSelectorUtils {
|
||||||
|
companion object {
|
||||||
|
/**
|
||||||
|
* Get image sha1 from uri, used to retrieve the original image sha1.
|
||||||
|
*/
|
||||||
|
suspend fun getImageSHA1(uri: Uri,
|
||||||
|
ioDispatcher : CoroutineDispatcher,
|
||||||
|
fileUtilsWrapper: FileUtilsWrapper,
|
||||||
|
contentResolver: ContentResolver
|
||||||
|
): String {
|
||||||
|
return withContext(ioDispatcher) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
val result = fileUtilsWrapper.getSHA1(contentResolver.openInputStream(uri))
|
||||||
|
result
|
||||||
|
} catch (e: FileNotFoundException){
|
||||||
|
e.printStackTrace()
|
||||||
|
""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
6
app/src/main/res/drawable/not_for_upload.xml
Normal file
6
app/src/main/res/drawable/not_for_upload.xml
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#D50000" android:viewportHeight="24.0" android:viewportWidth="24.0" android:width="24dp">
|
||||||
|
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM4,12c0,-4.42 3.58,-8 8,-8 1.85,0 3.55,0.63 4.9,1.69L5.69,16.9C4.63,15.55 4,13.85 4,12zM12,20c-1.85,0 -3.55,-0.63 -4.9,-1.69L18.31,7.1C19.37,8.45 20,10.15 20,12c0,4.42 -3.58,8 -8,8z"/>
|
||||||
|
|
||||||
|
</vector>
|
||||||
|
|
@ -82,6 +82,23 @@
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
app:constraint_referenced_ids="uploaded_overlay,uploaded_overlay_icon"/>
|
app:constraint_referenced_ids="uploaded_overlay,uploaded_overlay_icon"/>
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/not_for_upload_overlay_icon"
|
||||||
|
android:layout_width="@dimen/dimen_50"
|
||||||
|
android:layout_height="@dimen/dimen_50"
|
||||||
|
android:paddingBottom="@dimen/dimen_20"
|
||||||
|
android:paddingEnd="@dimen/dimen_20"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:srcCompat="@drawable/not_for_upload"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.Group
|
||||||
|
android:id="@+id/not_for_upload_group"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:constraint_referenced_ids="selected_overlay,not_for_upload_overlay_icon"/>
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
</androidx.cardview.widget.CardView>
|
</androidx.cardview.widget.CardView>
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,7 @@
|
||||||
<dimen name="dimen_20">20dp</dimen>
|
<dimen name="dimen_20">20dp</dimen>
|
||||||
<dimen name="dimen_40">40dp</dimen>
|
<dimen name="dimen_40">40dp</dimen>
|
||||||
<dimen name="dimen_42">42dp</dimen>
|
<dimen name="dimen_42">42dp</dimen>
|
||||||
|
<dimen name="dimen_50">50dp</dimen>
|
||||||
<dimen name="dimen_250">250dp</dimen>
|
<dimen name="dimen_250">250dp</dimen>
|
||||||
<dimen name="dimen_150">150dp</dimen>
|
<dimen name="dimen_150">150dp</dimen>
|
||||||
<dimen name="dimen_72">72dp</dimen>
|
<dimen name="dimen_72">72dp</dimen>
|
||||||
|
|
|
||||||
|
|
@ -735,4 +735,5 @@ Upload your first media by tapping on the add button.</string>
|
||||||
<string name="enter_description">What is your feedback?</string>
|
<string name="enter_description">What is your feedback?</string>
|
||||||
<string name="your_feedback">Your feedback</string>
|
<string name="your_feedback">Your feedback</string>
|
||||||
<string name="mark_as_not_for_upload">Mark as not for upload</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>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -121,6 +121,9 @@ class ImageAdapterTest {
|
||||||
holder.itemUploaded()
|
holder.itemUploaded()
|
||||||
func.invoke(imageAdapter, holder, 0)
|
func.invoke(imageAdapter, holder, 0)
|
||||||
holder.itemNotUploaded()
|
holder.itemNotUploaded()
|
||||||
|
holder.itemNotForUpload()
|
||||||
|
func.invoke(imageAdapter, holder, 0)
|
||||||
|
holder.itemNotForUpload()
|
||||||
func.invoke(imageAdapter, holder, 0)
|
func.invoke(imageAdapter, holder, 0)
|
||||||
selectedImageField.set(imageAdapter, images)
|
selectedImageField.set(imageAdapter, images)
|
||||||
func.invoke(imageAdapter, holder, 1)
|
func.invoke(imageAdapter, holder, 1)
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,8 @@ class CustomSelectorActivityTest {
|
||||||
|
|
||||||
private lateinit var activity: CustomSelectorActivity
|
private lateinit var activity: CustomSelectorActivity
|
||||||
|
|
||||||
|
private lateinit var imageFragment: ImageFragment
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set up the tests.
|
* Set up the tests.
|
||||||
*/
|
*/
|
||||||
|
|
@ -44,6 +46,7 @@ class CustomSelectorActivityTest {
|
||||||
val onCreate = activity.javaClass.getDeclaredMethod("onCreate", Bundle::class.java)
|
val onCreate = activity.javaClass.getDeclaredMethod("onCreate", Bundle::class.java)
|
||||||
onCreate.isAccessible = true
|
onCreate.isAccessible = true
|
||||||
onCreate.invoke(activity, null)
|
onCreate.invoke(activity, null)
|
||||||
|
imageFragment = ImageFragment.newInstance(1,0)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -81,7 +84,7 @@ class CustomSelectorActivityTest {
|
||||||
@Test
|
@Test
|
||||||
@Throws(Exception::class)
|
@Throws(Exception::class)
|
||||||
fun testOnSelectedImagesChanged() {
|
fun testOnSelectedImagesChanged() {
|
||||||
activity.onSelectedImagesChanged(ArrayList())
|
activity.onSelectedImagesChanged(ArrayList(), 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -91,10 +94,40 @@ class CustomSelectorActivityTest {
|
||||||
@Throws(Exception::class)
|
@Throws(Exception::class)
|
||||||
fun testOnDone() {
|
fun testOnDone() {
|
||||||
activity.onDone()
|
activity.onDone()
|
||||||
activity.onSelectedImagesChanged(ArrayList(arrayListOf(Image(1, "test", Uri.parse("test"), "test", 1))));
|
activity.onSelectedImagesChanged(
|
||||||
|
ArrayList(arrayListOf(Image(1, "test", Uri.parse("test"), "test", 1))),
|
||||||
|
1
|
||||||
|
)
|
||||||
activity.onDone()
|
activity.onDone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test onClickNotForUpload function.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
@Throws(Exception::class)
|
||||||
|
fun testOnClickNotForUpload() {
|
||||||
|
val method: Method = CustomSelectorActivity::class.java.getDeclaredMethod(
|
||||||
|
"onClickNotForUpload"
|
||||||
|
)
|
||||||
|
method.isAccessible = true
|
||||||
|
method.invoke(activity)
|
||||||
|
activity.onSelectedImagesChanged(
|
||||||
|
ArrayList(arrayListOf(Image(1, "test", Uri.parse("test"), "test", 1))),
|
||||||
|
0
|
||||||
|
)
|
||||||
|
method.invoke(activity)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test setOnDataListener Function.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
@Throws(Exception::class)
|
||||||
|
fun testSetOnDataListener() {
|
||||||
|
activity.setOnDataListener(imageFragment)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test onBackPressed Function.
|
* Test onBackPressed Function.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,7 @@ import java.lang.reflect.Field
|
||||||
class ImageFragmentTest {
|
class ImageFragmentTest {
|
||||||
|
|
||||||
private lateinit var fragment: ImageFragment
|
private lateinit var fragment: ImageFragment
|
||||||
|
private lateinit var activity: CustomSelectorActivity
|
||||||
private lateinit var view: View
|
private lateinit var view: View
|
||||||
private lateinit var selectorRV : RecyclerView
|
private lateinit var selectorRV : RecyclerView
|
||||||
private lateinit var loader : ProgressBar
|
private lateinit var loader : ProgressBar
|
||||||
|
|
@ -76,7 +77,7 @@ class ImageFragmentTest {
|
||||||
AppAdapter.set(TestAppAdapter())
|
AppAdapter.set(TestAppAdapter())
|
||||||
SoLoader.setInTestMode()
|
SoLoader.setInTestMode()
|
||||||
Fresco.initialize(context)
|
Fresco.initialize(context)
|
||||||
val activity = Robolectric.buildActivity(CustomSelectorActivity::class.java).create().get()
|
activity = Robolectric.buildActivity(CustomSelectorActivity::class.java).create().get()
|
||||||
|
|
||||||
fragment = ImageFragment.newInstance(1,0)
|
fragment = ImageFragment.newInstance(1,0)
|
||||||
val fragmentManager: FragmentManager = activity.supportFragmentManager
|
val fragmentManager: FragmentManager = activity.supportFragmentManager
|
||||||
|
|
@ -92,6 +93,7 @@ class ImageFragmentTest {
|
||||||
Whitebox.setInternalState(fragment, "imageAdapter", adapter)
|
Whitebox.setInternalState(fragment, "imageAdapter", adapter)
|
||||||
Whitebox.setInternalState(fragment, "selectorRV", selectorRV )
|
Whitebox.setInternalState(fragment, "selectorRV", selectorRV )
|
||||||
Whitebox.setInternalState(fragment, "loader", loader)
|
Whitebox.setInternalState(fragment, "loader", loader)
|
||||||
|
Whitebox.setInternalState(fragment, "filteredImages", arrayListOf(image,image))
|
||||||
|
|
||||||
viewModelField = fragment.javaClass.getDeclaredField("viewModel")
|
viewModelField = fragment.javaClass.getDeclaredField("viewModel")
|
||||||
viewModelField.isAccessible = true
|
viewModelField.isAccessible = true
|
||||||
|
|
@ -139,6 +141,21 @@ class ImageFragmentTest {
|
||||||
assertEquals(3, func.invoke(fragment))
|
assertEquals(3, func.invoke(fragment))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test onAttach function.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
fun testOnAttach() {
|
||||||
|
fragment.onAttach(activity)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test refresh function.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
fun testRefresh() {
|
||||||
|
fragment.refresh()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test onResume.
|
* Test onResume.
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import android.content.Context
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import com.nhaarman.mockitokotlin2.*
|
import com.nhaarman.mockitokotlin2.*
|
||||||
import fr.free.nrw.commons.TestCommonsApplication
|
import fr.free.nrw.commons.TestCommonsApplication
|
||||||
|
import fr.free.nrw.commons.customselector.database.NotForUploadStatusDao
|
||||||
import fr.free.nrw.commons.customselector.database.UploadedStatus
|
import fr.free.nrw.commons.customselector.database.UploadedStatus
|
||||||
import fr.free.nrw.commons.customselector.database.UploadedStatusDao
|
import fr.free.nrw.commons.customselector.database.UploadedStatusDao
|
||||||
import fr.free.nrw.commons.customselector.model.Image
|
import fr.free.nrw.commons.customselector.model.Image
|
||||||
|
|
@ -61,6 +62,9 @@ class ImageLoaderTest {
|
||||||
@Mock
|
@Mock
|
||||||
private lateinit var uploadedStatusDao: UploadedStatusDao
|
private lateinit var uploadedStatusDao: UploadedStatusDao
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private lateinit var notForUploadStatusDao: NotForUploadStatusDao
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private lateinit var holder: ImageAdapter.ImageViewHolder
|
private lateinit var holder: ImageAdapter.ImageViewHolder
|
||||||
|
|
||||||
|
|
@ -97,7 +101,8 @@ class ImageLoaderTest {
|
||||||
MockitoAnnotations.initMocks(this)
|
MockitoAnnotations.initMocks(this)
|
||||||
|
|
||||||
imageLoader =
|
imageLoader =
|
||||||
ImageLoader(mediaClient, fileProcessor, fileUtilsWrapper, uploadedStatusDao, context)
|
ImageLoader(mediaClient, fileProcessor, fileUtilsWrapper, uploadedStatusDao,
|
||||||
|
notForUploadStatusDao, context)
|
||||||
uploadedStatus= UploadedStatus(
|
uploadedStatus= UploadedStatus(
|
||||||
"testSha1",
|
"testSha1",
|
||||||
"testSha1",
|
"testSha1",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue