mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-27 04:43:54 +01:00
[GSoC] Show uploaded images differently. (#4464)
* uploaded images shown differently * Loaded images before query * Handled exceptions, Made ImageLoader injectable, Document and clean code
This commit is contained in:
parent
746a660028
commit
7a6b24470e
8 changed files with 174 additions and 63 deletions
|
|
@ -1,15 +1,20 @@
|
||||||
package fr.free.nrw.commons.customselector.helper
|
package fr.free.nrw.commons.customselector.helper
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import com.mapbox.android.core.FileUtils
|
||||||
import fr.free.nrw.commons.customselector.model.Folder
|
import fr.free.nrw.commons.customselector.model.Folder
|
||||||
import fr.free.nrw.commons.customselector.model.Image
|
import fr.free.nrw.commons.customselector.model.Image
|
||||||
|
import fr.free.nrw.commons.filepicker.Constants
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.io.*
|
import java.io.*
|
||||||
import java.math.BigInteger
|
import java.math.BigInteger
|
||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
import java.security.NoSuchAlgorithmException
|
import java.security.NoSuchAlgorithmException
|
||||||
import kotlin.collections.ArrayList
|
import kotlin.collections.ArrayList
|
||||||
|
import kotlin.collections.HashMap
|
||||||
import kotlin.collections.LinkedHashMap
|
import kotlin.collections.LinkedHashMap
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Image Helper object, includes all the static functions required by custom selector.
|
* Image Helper object, includes all the static functions required by custom selector.
|
||||||
*/
|
*/
|
||||||
|
|
@ -68,7 +73,7 @@ object ImageHelper {
|
||||||
|
|
||||||
val indexes = arrayListOf<Int>()
|
val indexes = arrayListOf<Int>()
|
||||||
for(image in list) {
|
for(image in list) {
|
||||||
val index = getIndex(masterList,image)
|
val index = getIndex(masterList, image)
|
||||||
if (index == -1) {
|
if (index == -1) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
@ -76,47 +81,4 @@ object ImageHelper {
|
||||||
}
|
}
|
||||||
return indexes
|
return indexes
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates the file sha1 from file input stream.
|
|
||||||
*/
|
|
||||||
fun generateSHA1(`is`: InputStream): String {
|
|
||||||
val digest: MessageDigest = try {
|
|
||||||
MessageDigest.getInstance("SHA1")
|
|
||||||
} catch (e: NoSuchAlgorithmException) {
|
|
||||||
Timber.e(e, "Exception while getting Digest")
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
val buffer = ByteArray(8192)
|
|
||||||
var read: Int
|
|
||||||
return try {
|
|
||||||
while (`is`.read(buffer).also { read = it } > 0) {
|
|
||||||
digest.update(buffer, 0, read)
|
|
||||||
}
|
|
||||||
val md5sum = digest.digest()
|
|
||||||
val bigInt = BigInteger(1, md5sum)
|
|
||||||
var output = bigInt.toString(16)
|
|
||||||
output = String.format("%40s", output).replace(' ', '0')
|
|
||||||
Timber.i("File SHA1: %s", output)
|
|
||||||
output
|
|
||||||
} catch (e: IOException) {
|
|
||||||
Timber.e(e, "IO Exception")
|
|
||||||
""
|
|
||||||
} finally {
|
|
||||||
try {
|
|
||||||
`is`.close()
|
|
||||||
} catch (e: IOException) {
|
|
||||||
Timber.e(e, "Exception on closing input stream")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the file input stream from the file path.
|
|
||||||
*/
|
|
||||||
@Throws(FileNotFoundException::class)
|
|
||||||
fun getFileInputStream(filePath: String?): InputStream {
|
|
||||||
return FileInputStream(filePath)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -11,7 +11,6 @@ import com.bumptech.glide.Glide
|
||||||
import fr.free.nrw.commons.R
|
import fr.free.nrw.commons.R
|
||||||
import fr.free.nrw.commons.customselector.listeners.FolderClickListener
|
import fr.free.nrw.commons.customselector.listeners.FolderClickListener
|
||||||
import fr.free.nrw.commons.customselector.model.Folder
|
import fr.free.nrw.commons.customselector.model.Folder
|
||||||
import fr.free.nrw.commons.customselector.ui.selector.ImageLoader
|
|
||||||
|
|
||||||
class FolderAdapter(
|
class FolderAdapter(
|
||||||
/**
|
/**
|
||||||
|
|
@ -23,12 +22,8 @@ class FolderAdapter(
|
||||||
* Folder Click listener for click events.
|
* Folder Click listener for click events.
|
||||||
*/
|
*/
|
||||||
private val itemClickListener: FolderClickListener
|
private val itemClickListener: FolderClickListener
|
||||||
) : RecyclerViewAdapter<FolderAdapter.FolderViewHolder?>(context) {
|
|
||||||
|
|
||||||
/**
|
) : RecyclerViewAdapter<FolderAdapter.FolderViewHolder?>(context) {
|
||||||
* Image Loader for loading images.
|
|
||||||
*/
|
|
||||||
private val imageLoader = ImageLoader()
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List of folders.
|
* List of folders.
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import fr.free.nrw.commons.R
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
|
import android.widget.Toast
|
||||||
import androidx.constraintlayout.widget.Group
|
import androidx.constraintlayout.widget.Group
|
||||||
import androidx.recyclerview.widget.DiffUtil
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
|
@ -13,6 +14,7 @@ import com.bumptech.glide.Glide
|
||||||
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.model.Image
|
import fr.free.nrw.commons.customselector.model.Image
|
||||||
|
import fr.free.nrw.commons.customselector.ui.selector.ImageLoader
|
||||||
|
|
||||||
class ImageAdapter(
|
class ImageAdapter(
|
||||||
/**
|
/**
|
||||||
|
|
@ -23,7 +25,13 @@ class ImageAdapter(
|
||||||
/**
|
/**
|
||||||
* Image select listener for click events on image.
|
* Image select listener for click events on image.
|
||||||
*/
|
*/
|
||||||
private var imageSelectListener: ImageSelectListener ):
|
private var imageSelectListener: ImageSelectListener,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ImageLoader queries images.
|
||||||
|
*/
|
||||||
|
private var imageLoader: ImageLoader
|
||||||
|
):
|
||||||
|
|
||||||
RecyclerViewAdapter<ImageAdapter.ImageViewHolder>(context) {
|
RecyclerViewAdapter<ImageAdapter.ImageViewHolder>(context) {
|
||||||
|
|
||||||
|
|
@ -48,7 +56,7 @@ class ImageAdapter(
|
||||||
private var images: ArrayList<Image> = ArrayList()
|
private var images: ArrayList<Image> = ArrayList()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* create View holder.
|
* Create View holder.
|
||||||
*/
|
*/
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ImageViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ImageViewHolder {
|
||||||
val itemView = inflater.inflate(R.layout.item_custom_selector_image,parent, false)
|
val itemView = inflater.inflate(R.layout.item_custom_selector_image,parent, false)
|
||||||
|
|
@ -69,6 +77,7 @@ class ImageAdapter(
|
||||||
holder.itemUnselected();
|
holder.itemUnselected();
|
||||||
}
|
}
|
||||||
Glide.with(context).load(image.uri).into(holder.image)
|
Glide.with(context).load(image.uri).into(holder.image)
|
||||||
|
imageLoader.queryAndSetView(holder,image)
|
||||||
holder.itemView.setOnClickListener {
|
holder.itemView.setOnClickListener {
|
||||||
selectOrRemoveImage(holder, position)
|
selectOrRemoveImage(holder, position)
|
||||||
}
|
}
|
||||||
|
|
@ -87,12 +96,12 @@ class ImageAdapter(
|
||||||
notifyItemChanged(index, ImageSelectedOrUpdated())
|
notifyItemChanged(index, ImageSelectedOrUpdated())
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
/**
|
if(holder.isItemUploaded()){
|
||||||
* TODO
|
Toast.makeText(context,"Already Uploaded image", Toast.LENGTH_SHORT).show()
|
||||||
* Show toast on tapping an uploaded item.
|
} else {
|
||||||
*/
|
|
||||||
selectedImages.add(images[position])
|
selectedImages.add(images[position])
|
||||||
notifyItemChanged(position, ImageSelectedOrUpdated())
|
notifyItemChanged(position, ImageSelectedOrUpdated())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
imageSelectListener.onSelectedImagesChanged(selectedImages)
|
imageSelectListener.onSelectedImagesChanged(selectedImages)
|
||||||
}
|
}
|
||||||
|
|
@ -150,6 +159,9 @@ class ImageAdapter(
|
||||||
uploadedGroup.visibility = View.VISIBLE
|
uploadedGroup.visibility = View.VISIBLE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun isItemUploaded():Boolean {
|
||||||
|
return uploadedGroup.visibility == View.VISIBLE
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Item Not Uploaded view.
|
* Item Not Uploaded view.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -12,9 +12,10 @@ import fr.free.nrw.commons.customselector.helper.ImageHelper
|
||||||
import fr.free.nrw.commons.customselector.model.Result
|
import fr.free.nrw.commons.customselector.model.Result
|
||||||
import fr.free.nrw.commons.customselector.listeners.FolderClickListener
|
import fr.free.nrw.commons.customselector.listeners.FolderClickListener
|
||||||
import fr.free.nrw.commons.customselector.model.CallbackStatus
|
import fr.free.nrw.commons.customselector.model.CallbackStatus
|
||||||
import fr.free.nrw.commons.customselector.model.Folder
|
|
||||||
import fr.free.nrw.commons.customselector.ui.adapter.FolderAdapter
|
import fr.free.nrw.commons.customselector.ui.adapter.FolderAdapter
|
||||||
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment
|
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment
|
||||||
|
import fr.free.nrw.commons.media.MediaClient
|
||||||
|
import fr.free.nrw.commons.upload.FileProcessor
|
||||||
import kotlinx.android.synthetic.main.fragment_custom_selector.*
|
import kotlinx.android.synthetic.main.fragment_custom_selector.*
|
||||||
import kotlinx.android.synthetic.main.fragment_custom_selector.view.*
|
import kotlinx.android.synthetic.main.fragment_custom_selector.view.*
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
@ -32,7 +33,11 @@ class FolderFragment : CommonsDaggerSupportFragment() {
|
||||||
var customSelectorViewModelFactory: CustomSelectorViewModelFactory? = null
|
var customSelectorViewModelFactory: CustomSelectorViewModelFactory? = null
|
||||||
@Inject set
|
@Inject set
|
||||||
|
|
||||||
|
var fileProcessor: FileProcessor? = null
|
||||||
|
@Inject set
|
||||||
|
|
||||||
|
var mediaClient: MediaClient? = null
|
||||||
|
@Inject set
|
||||||
/**
|
/**
|
||||||
* Folder Adapter.
|
* Folder Adapter.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,12 @@ class ImageFragment: CommonsDaggerSupportFragment() {
|
||||||
lateinit var customSelectorViewModelFactory: CustomSelectorViewModelFactory
|
lateinit var customSelectorViewModelFactory: CustomSelectorViewModelFactory
|
||||||
@Inject set
|
@Inject set
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Image loader for adapter.
|
||||||
|
*/
|
||||||
|
var imageLoader: ImageLoader? = null
|
||||||
|
@Inject set
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Image Adapter for recycle view.
|
* Image Adapter for recycle view.
|
||||||
*/
|
*/
|
||||||
|
|
@ -84,7 +90,7 @@ class ImageFragment: CommonsDaggerSupportFragment() {
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||||
|
|
||||||
val root = inflater.inflate(R.layout.fragment_custom_selector, container, false)
|
val root = inflater.inflate(R.layout.fragment_custom_selector, container, false)
|
||||||
imageAdapter = ImageAdapter(requireActivity(), activity as ImageSelectListener)
|
imageAdapter = ImageAdapter(requireActivity(), activity as ImageSelectListener, imageLoader!!)
|
||||||
gridLayoutManager = GridLayoutManager(context,getSpanCount())
|
gridLayoutManager = GridLayoutManager(context,getSpanCount())
|
||||||
with(root.selector_rv){
|
with(root.selector_rv){
|
||||||
this.layoutManager = gridLayoutManager
|
this.layoutManager = gridLayoutManager
|
||||||
|
|
@ -118,6 +124,8 @@ class ImageFragment: CommonsDaggerSupportFragment() {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* getSpanCount for GridViewManager.
|
* getSpanCount for GridViewManager.
|
||||||
|
*
|
||||||
|
* @return spanCount.
|
||||||
*/
|
*/
|
||||||
private fun getSpanCount(): Int {
|
private fun getSpanCount(): Int {
|
||||||
return 3
|
return 3
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,136 @@
|
||||||
package fr.free.nrw.commons.customselector.ui.selector
|
package fr.free.nrw.commons.customselector.ui.selector
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.exifinterface.media.ExifInterface
|
||||||
|
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 kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import timber.log.Timber
|
||||||
|
import java.io.IOException
|
||||||
|
import java.util.*
|
||||||
|
import javax.inject.Inject
|
||||||
|
import kotlin.collections.HashMap
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Image Loader class, loads images, depending on API results.
|
* Image Loader class, loads images, depending on API results.
|
||||||
*/
|
*/
|
||||||
class ImageLoader {
|
class ImageLoader @Inject constructor(
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MediaClient for SHA1 query.
|
||||||
|
*/
|
||||||
|
var mediaClient: MediaClient,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FileProcessor to pre-process the file.
|
||||||
|
*/
|
||||||
|
var fileProcessor: FileProcessor,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* File Utils Wrapper for SHA1
|
||||||
|
*/
|
||||||
|
var fileUtilsWrapper: FileUtilsWrapper,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Context for coroutine.
|
||||||
|
*/
|
||||||
|
val context: Context) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps to facilitate image query.
|
||||||
|
*/
|
||||||
|
private var mapImageSHA1: HashMap<Image,String> = HashMap()
|
||||||
|
private var mapHolderImage : HashMap<ImageViewHolder,Image> = HashMap()
|
||||||
|
private var mapResult: HashMap<String,Boolean> = HashMap()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Query image and setUp the view.
|
||||||
|
*/
|
||||||
|
fun queryAndSetView(holder: ImageViewHolder, image: Image){
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recycler view uses same view holder, so we can identify the latest query image from holder.
|
||||||
|
*/
|
||||||
|
mapHolderImage[holder] = image
|
||||||
|
holder.itemNotUploaded()
|
||||||
|
|
||||||
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
|
var value = false
|
||||||
|
withContext(Dispatchers.Default) {
|
||||||
|
if(mapHolderImage[holder] != image) {
|
||||||
|
// View holder has a new query image, terminate this query.
|
||||||
|
return@withContext
|
||||||
|
}
|
||||||
|
val sha1 = getSHA1(image)
|
||||||
|
if(mapHolderImage[holder] != image) {
|
||||||
|
// View holder has a new query image, terminate this query.
|
||||||
|
return@withContext
|
||||||
|
}
|
||||||
|
value = querySHA1(sha1)
|
||||||
|
}
|
||||||
|
if(mapHolderImage[holder] == image) {
|
||||||
|
// View holder and latest query image match, setup the view.
|
||||||
|
if (value) {
|
||||||
|
holder.itemUploaded()
|
||||||
|
} else {
|
||||||
|
holder.itemNotUploaded()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Query SHA1, return result if previously queried, otherwise start a new query.
|
||||||
|
*
|
||||||
|
* @return Query result.
|
||||||
|
*/
|
||||||
|
private fun querySHA1(SHA1: String): Boolean {
|
||||||
|
if(mapResult[SHA1] != null) {
|
||||||
|
return mapResult[SHA1]!!
|
||||||
|
}
|
||||||
|
val isUploaded = mediaClient.checkFileExistsUsingSha(SHA1).blockingGet()
|
||||||
|
mapResult[SHA1] = isUploaded
|
||||||
|
return isUploaded
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get SHA1, return SHA1 if available, otherwise generate and store the SHA1.
|
||||||
|
*
|
||||||
|
* @return sha1 of the image
|
||||||
|
*/
|
||||||
|
private fun getSHA1(image: Image): String{
|
||||||
|
if(mapImageSHA1[image] != null) {
|
||||||
|
return mapImageSHA1[image]!!
|
||||||
|
}
|
||||||
|
val sha1 = generateModifiedSHA1(image);
|
||||||
|
mapImageSHA1[image] = sha1;
|
||||||
|
return sha1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate Modified SHA1 using present Exif settings.
|
||||||
|
*
|
||||||
|
* @return modified sha1
|
||||||
|
*/
|
||||||
|
private fun generateModifiedSHA1(image: Image) : String {
|
||||||
|
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()
|
||||||
|
return sha1
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -25,7 +25,7 @@ import java.util.UUID;
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
|
|
||||||
class PickedFiles implements Constants {
|
public class PickedFiles implements Constants {
|
||||||
|
|
||||||
private static String getFolderName(@NonNull Context context) {
|
private static String getFolderName(@NonNull Context context) {
|
||||||
return FilePicker.configuration(context).getFolderName();
|
return FilePicker.configuration(context).getFolderName();
|
||||||
|
|
@ -104,7 +104,7 @@ class PickedFiles implements Constants {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static UploadableFile pickedExistingPicture(@NonNull Context context, Uri photoUri) throws IOException, SecurityException {// SecurityException for those file providers who share URI but forget to grant necessary permissions
|
public static UploadableFile pickedExistingPicture(@NonNull Context context, Uri photoUri) throws IOException, SecurityException {// SecurityException for those file providers who share URI but forget to grant necessary permissions
|
||||||
InputStream pictureInputStream = context.getContentResolver().openInputStream(photoUri);
|
InputStream pictureInputStream = context.getContentResolver().openInputStream(photoUri);
|
||||||
File directory = tempImageDirectory(context);
|
File directory = tempImageDirectory(context);
|
||||||
File photoFile = new File(directory, UUID.randomUUID().toString() + "." + getMimeType(context, photoUri));
|
File photoFile = new File(directory, UUID.randomUUID().toString() + "." + getMimeType(context, photoUri));
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,7 @@ class FileProcessor @Inject constructor(
|
||||||
*
|
*
|
||||||
* @return tags to be redacted
|
* @return tags to be redacted
|
||||||
*/
|
*/
|
||||||
private fun getExifTagsToRedact(): Set<String> {
|
fun getExifTagsToRedact(): Set<String> {
|
||||||
val prefManageEXIFTags =
|
val prefManageEXIFTags =
|
||||||
defaultKvStore.getStringSet(Prefs.MANAGED_EXIF_TAGS) ?: emptySet()
|
defaultKvStore.getStringSet(Prefs.MANAGED_EXIF_TAGS) ?: emptySet()
|
||||||
val redactTags: Set<String> =
|
val redactTags: Set<String> =
|
||||||
|
|
@ -91,7 +91,7 @@ class FileProcessor @Inject constructor(
|
||||||
* @param exifInterface ExifInterface object
|
* @param exifInterface ExifInterface object
|
||||||
* @param redactTags tags to be redacted
|
* @param redactTags tags to be redacted
|
||||||
*/
|
*/
|
||||||
private fun redactExifTags(exifInterface: ExifInterface?, redactTags: Set<String>) {
|
fun redactExifTags(exifInterface: ExifInterface?, redactTags: Set<String>) {
|
||||||
compositeDisposable.add(
|
compositeDisposable.add(
|
||||||
Observable.fromIterable(redactTags)
|
Observable.fromIterable(redactTags)
|
||||||
.flatMap { Observable.fromArray(*FileMetadataUtils.getTagsFromPref(it)) }
|
.flatMap { Observable.fromArray(*FileMetadataUtils.getTagsFromPref(it)) }
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue