From 52912087d6f2f1d98579089ef604e7ee6a6555bd Mon Sep 17 00:00:00 2001 From: Ayan Sarkar <71203077+Ayan-10@users.noreply.github.com> Date: Sat, 20 Aug 2022 15:51:36 +0530 Subject: [PATCH] [GSoC] Full Screen Mode (#5032) * Gesture detection implemented * Left and right swipe * Selection implemented * onDown implemented * onDown implemented * FS mode implemented * OnSwipe doc * Scope cancel * Added label in Manifest --- app/src/main/AndroidManifest.xml | 5 +- .../customselector/database/UploadedDao.kt | 14 + .../helper/CustomSelectorConstants.kt | 10 + .../helper/OnSwipeTouchListener.kt | 72 ++++ .../listeners/ImageSelectListener.kt | 6 +- .../listeners/PassDataListener.kt | 7 + .../customselector/ui/adapter/ImageAdapter.kt | 9 +- .../ui/selector/CustomSelectorActivity.kt | 30 +- .../ui/selector/ImageFragment.kt | 8 +- .../nrw/commons/di/ActivityBuilderModule.java | 4 + .../nrw/commons/media/ZoomableActivity.java | 92 ----- .../nrw/commons/media/ZoomableActivity.kt | 387 ++++++++++++++++++ .../nrw/commons/utils/CustomSelectorUtils.kt | 33 +- app/src/main/res/layout/activity_zoomable.xml | 17 + 14 files changed, 579 insertions(+), 115 deletions(-) create mode 100644 app/src/main/java/fr/free/nrw/commons/customselector/helper/CustomSelectorConstants.kt create mode 100644 app/src/main/java/fr/free/nrw/commons/customselector/helper/OnSwipeTouchListener.kt create mode 100644 app/src/main/java/fr/free/nrw/commons/customselector/listeners/PassDataListener.kt delete mode 100644 app/src/main/java/fr/free/nrw/commons/media/ZoomableActivity.java create mode 100644 app/src/main/java/fr/free/nrw/commons/media/ZoomableActivity.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 8fc66230f..4fa7e4f82 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -46,7 +46,10 @@ android:finishOnTaskLaunch="true" /> + android:name=".media.ZoomableActivity" + android:label="Zoomable Activity" + android:configChanges="screenSize|keyboard|orientation" + android:parentActivityName=".customselector.ui.selector.CustomSelectorActivity" /> diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/database/UploadedDao.kt b/app/src/main/java/fr/free/nrw/commons/customselector/database/UploadedDao.kt index 619504e7d..d8c100a08 100644 --- a/app/src/main/java/fr/free/nrw/commons/customselector/database/UploadedDao.kt +++ b/app/src/main/java/fr/free/nrw/commons/customselector/database/UploadedDao.kt @@ -66,4 +66,18 @@ abstract class UploadedStatusDao { suspend fun getUploadedFromImageSHA1(imageSHA1: String):UploadedStatus? { return getFromImageSHA1(imageSHA1) } + + /** + * 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 + } \ No newline at end of file diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/helper/CustomSelectorConstants.kt b/app/src/main/java/fr/free/nrw/commons/customselector/helper/CustomSelectorConstants.kt new file mode 100644 index 000000000..3f5b26c96 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/customselector/helper/CustomSelectorConstants.kt @@ -0,0 +1,10 @@ +package fr.free.nrw.commons.customselector.helper + +object CustomSelectorConstants { + + const val TOTAL_IMAGES = "total_images" + const val TOTAL_SELECTED_IMAGES = "total_selected_images" + const val PRESENT_POSITION = "present_position" + const val NEW_SELECTED_IMAGES = "new_selected_images" + +} \ No newline at end of file diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/helper/OnSwipeTouchListener.kt b/app/src/main/java/fr/free/nrw/commons/customselector/helper/OnSwipeTouchListener.kt new file mode 100644 index 000000000..b39d976ee --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/customselector/helper/OnSwipeTouchListener.kt @@ -0,0 +1,72 @@ +package fr.free.nrw.commons.customselector.helper + +import android.content.Context +import android.view.GestureDetector +import android.view.MotionEvent +import android.view.View +import kotlin.math.abs + +/** + * Class for detecting swipe gestures + */ +open class OnSwipeTouchListener(context: Context?) : View.OnTouchListener { + + private val gestureDetector: GestureDetector + + override fun onTouch(view: View?, motionEvent: MotionEvent?): Boolean { + return gestureDetector.onTouchEvent(motionEvent) + } + + private inner class GestureListener : GestureDetector.SimpleOnGestureListener() { + + private val SWIPE_THRESHOLD = 100 + private val SWIPE_VELOCITY_THRESHOLD = 100 + + override fun onDown(e: MotionEvent?): Boolean { + return true + } + + override fun onFling( + event1: MotionEvent, + event2: MotionEvent, + velocityX: Float, + velocityY: Float + ): Boolean { + try { + val diffY: Float = event2.y - event1.y + val diffX: Float = event2.x - event1.x + if (abs(diffX) > abs(diffY)) { + if (abs(diffX) > SWIPE_THRESHOLD && abs(velocityX) > + SWIPE_VELOCITY_THRESHOLD) { + if (diffX > 0) { + onSwipeRight() + } else { + onSwipeLeft() + } + } + } else { + if (abs(diffY) > SWIPE_THRESHOLD && abs(velocityY) > + SWIPE_VELOCITY_THRESHOLD) { + if (diffY > 0) { + onSwipeDown() + } else { + onSwipeUp() + } + } + } + } catch (exception: Exception) { + exception.printStackTrace() + } + return false + } + } + + open fun onSwipeRight() {} + open fun onSwipeLeft() {} + open fun onSwipeUp() {} + open fun onSwipeDown() {} + + init { + gestureDetector = GestureDetector(context, GestureListener()) + } +} \ No newline at end of file diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/listeners/ImageSelectListener.kt b/app/src/main/java/fr/free/nrw/commons/customselector/listeners/ImageSelectListener.kt index a9cc85e19..d6349eb49 100644 --- a/app/src/main/java/fr/free/nrw/commons/customselector/listeners/ImageSelectListener.kt +++ b/app/src/main/java/fr/free/nrw/commons/customselector/listeners/ImageSelectListener.kt @@ -19,5 +19,9 @@ interface ImageSelectListener { * onLongPress * @param imageUri : uri of image */ - fun onLongPress(imageUri: Uri) + fun onLongPress( + position: Int, + images: ArrayList, + selectedImages: ArrayList + ) } \ No newline at end of file diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/listeners/PassDataListener.kt b/app/src/main/java/fr/free/nrw/commons/customselector/listeners/PassDataListener.kt new file mode 100644 index 000000000..8d32d948d --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/customselector/listeners/PassDataListener.kt @@ -0,0 +1,7 @@ +package fr.free.nrw.commons.customselector.listeners + +import fr.free.nrw.commons.customselector.model.Image + +interface PassDataListener { + fun passSelectedImages(selectedImages: ArrayList) +} \ No newline at end of file diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/ui/adapter/ImageAdapter.kt b/app/src/main/java/fr/free/nrw/commons/customselector/ui/adapter/ImageAdapter.kt index 1e81c9466..d25e0fd1c 100644 --- a/app/src/main/java/fr/free/nrw/commons/customselector/ui/adapter/ImageAdapter.kt +++ b/app/src/main/java/fr/free/nrw/commons/customselector/ui/adapter/ImageAdapter.kt @@ -228,7 +228,7 @@ class ImageAdapter( // launch media preview on long click. holder.itemView.setOnLongClickListener { - imageSelectListener.onLongPress(image.uri) + imageSelectListener.onLongPress(position, images, selectedImages) true } } @@ -317,6 +317,13 @@ class ImageAdapter( diffResult.dispatchUpdatesTo(this) } + /** + * Set new selected images + */ + fun setSelectedImages(newSelectedImages: ArrayList){ + selectedImages = ArrayList(newSelectedImages) + imageSelectListener.onSelectedImagesChanged(selectedImages, 0) + } /** * Refresh the data in the adapter */ diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/CustomSelectorActivity.kt b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/CustomSelectorActivity.kt index 5de0aeca5..3d3ff55c9 100644 --- a/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/CustomSelectorActivity.kt +++ b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/CustomSelectorActivity.kt @@ -4,7 +4,6 @@ import android.app.Activity import android.app.Dialog import android.content.Intent import android.content.SharedPreferences -import android.net.Uri import android.os.Bundle import android.view.View import android.view.Window @@ -16,6 +15,7 @@ import androidx.lifecycle.ViewModelProvider 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.helper.CustomSelectorConstants import fr.free.nrw.commons.customselector.listeners.FolderClickListener import fr.free.nrw.commons.customselector.listeners.ImageSelectListener import fr.free.nrw.commons.customselector.model.Image @@ -112,6 +112,18 @@ class CustomSelectorActivity: BaseActivity(), FolderClickListener, ImageSelectLi } } + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + if (requestCode == 101) { + if (resultCode == Activity.RESULT_OK) { + val selectedImages: ArrayList = + data!! + .getParcelableArrayListExtra(CustomSelectorConstants.NEW_SELECTED_IMAGES)!! + imageFragment!!.passSelectedImages(selectedImages) + } + } + } + /** * Show Custom Selector Welcome Dialog. */ @@ -305,9 +317,19 @@ class CustomSelectorActivity: BaseActivity(), FolderClickListener, ImageSelectLi * onLongPress * @param imageUri : uri of image */ - override fun onLongPress(imageUri: Uri) { - val intent = Intent(this, ZoomableActivity::class.java).setData(imageUri); - startActivity(intent) + override fun onLongPress( + position: Int, + images: ArrayList, + selectedImages: ArrayList + ) { + val intent = Intent(this, ZoomableActivity::class.java) + intent.putExtra(CustomSelectorConstants.PRESENT_POSITION, position); + intent.putParcelableArrayListExtra(CustomSelectorConstants.TOTAL_IMAGES, images) + intent.putParcelableArrayListExtra( + CustomSelectorConstants.TOTAL_SELECTED_IMAGES, + selectedImages + ) + startActivityForResult(intent, 101) } /** diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/ImageFragment.kt b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/ImageFragment.kt index a6025e23d..b7da43a85 100644 --- a/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/ImageFragment.kt +++ b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/ImageFragment.kt @@ -19,6 +19,7 @@ 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.listeners.PassDataListener 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 @@ -42,7 +43,7 @@ import kotlin.collections.ArrayList /** * Custom Selector Image Fragment. */ -class ImageFragment: CommonsDaggerSupportFragment(), RefreshUIListener { +class ImageFragment: CommonsDaggerSupportFragment(), RefreshUIListener, PassDataListener { /** * Current bucketId. @@ -293,6 +294,7 @@ class ImageFragment: CommonsDaggerSupportFragment(), RefreshUIListener { * notifyDataSetChanged, rebuild the holder views to account for deleted images. */ override fun onResume() { + Log.d("haha", "onResume: ") imageAdapter.notifyDataSetChanged() super.onResume() } @@ -327,4 +329,8 @@ class ImageFragment: CommonsDaggerSupportFragment(), RefreshUIListener { override fun refresh() { imageAdapter.refresh(filteredImages, allImages) } + + override fun passSelectedImages(selectedImages: ArrayList){ + imageAdapter.setSelectedImages(selectedImages) + } } \ No newline at end of file diff --git a/app/src/main/java/fr/free/nrw/commons/di/ActivityBuilderModule.java b/app/src/main/java/fr/free/nrw/commons/di/ActivityBuilderModule.java index 31412236d..6a9277906 100644 --- a/app/src/main/java/fr/free/nrw/commons/di/ActivityBuilderModule.java +++ b/app/src/main/java/fr/free/nrw/commons/di/ActivityBuilderModule.java @@ -13,6 +13,7 @@ import fr.free.nrw.commons.customselector.ui.selector.CustomSelectorActivity; import fr.free.nrw.commons.description.DescriptionEditActivity; import fr.free.nrw.commons.explore.depictions.WikidataItemDetailsActivity; import fr.free.nrw.commons.explore.SearchActivity; +import fr.free.nrw.commons.media.ZoomableActivity; import fr.free.nrw.commons.notification.NotificationActivity; import fr.free.nrw.commons.profile.ProfileActivity; import fr.free.nrw.commons.review.ReviewActivity; @@ -75,4 +76,7 @@ public abstract class ActivityBuilderModule { @ContributesAndroidInjector abstract DescriptionEditActivity bindDescriptionEditActivity(); + + @ContributesAndroidInjector + abstract ZoomableActivity bindZoomableActivity(); } diff --git a/app/src/main/java/fr/free/nrw/commons/media/ZoomableActivity.java b/app/src/main/java/fr/free/nrw/commons/media/ZoomableActivity.java deleted file mode 100644 index 286384ff1..000000000 --- a/app/src/main/java/fr/free/nrw/commons/media/ZoomableActivity.java +++ /dev/null @@ -1,92 +0,0 @@ -package fr.free.nrw.commons.media; - -import android.graphics.drawable.Animatable; -import android.net.Uri; -import android.os.Bundle; -import android.view.View; -import android.widget.ProgressBar; -import androidx.annotation.Nullable; -import androidx.appcompat.app.AppCompatActivity; -import butterknife.BindView; -import butterknife.ButterKnife; -import com.facebook.drawee.backends.pipeline.Fresco; -import com.facebook.drawee.controller.BaseControllerListener; -import com.facebook.drawee.controller.ControllerListener; -import com.facebook.drawee.drawable.ProgressBarDrawable; -import com.facebook.drawee.drawable.ScalingUtils; -import com.facebook.drawee.generic.GenericDraweeHierarchy; -import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder; -import com.facebook.drawee.interfaces.DraweeController; -import com.facebook.imagepipeline.image.ImageInfo; -import fr.free.nrw.commons.R; -import fr.free.nrw.commons.media.zoomControllers.zoomable.DoubleTapGestureListener; -import fr.free.nrw.commons.media.zoomControllers.zoomable.ZoomableDraweeView; -import timber.log.Timber; - - -public class ZoomableActivity extends AppCompatActivity { - private Uri imageUri; - - @BindView(R.id.zoomable) - ZoomableDraweeView photo; - @BindView(R.id.zoom_progress_bar) - ProgressBar spinner; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - imageUri = getIntent().getData(); - if (null == imageUri) { - throw new IllegalArgumentException("No data to display"); - } - Timber.d("URl = " + imageUri); - - setContentView(R.layout.activity_zoomable); - ButterKnife.bind(this); - init(); - } - - /** - * Two types of loading indicators have been added to the zoom activity: - * 1. An Indeterminate spinner for showing the time lapsed between dispatch of the image request - * and starting to receiving the image. - * 2. ProgressBarDrawable that reflects how much image has been downloaded - */ - private final ControllerListener loadingListener = new BaseControllerListener() { - @Override - public void onSubmit(String id, Object callerContext) { - // Sometimes the spinner doesn't appear when rapidly switching between images, this fixes that - spinner.setVisibility(View.VISIBLE); - } - - @Override - public void onIntermediateImageSet(String id, @Nullable ImageInfo imageInfo) { - spinner.setVisibility(View.GONE); - } - @Override - public void onFinalImageSet(String id, @Nullable ImageInfo imageInfo, @Nullable Animatable animatable) { - spinner.setVisibility(View.GONE); - } - }; - private void init() { - if( imageUri != null ) { - GenericDraweeHierarchy hierarchy = GenericDraweeHierarchyBuilder.newInstance(getResources()) - .setActualImageScaleType(ScalingUtils.ScaleType.FIT_CENTER) - .setProgressBarImage(new ProgressBarDrawable()) - .setProgressBarImageScaleType(ScalingUtils.ScaleType.FIT_CENTER) - .build(); - photo.setHierarchy(hierarchy); - photo.setAllowTouchInterceptionWhileZoomed(true); - photo.setIsLongpressEnabled(false); - photo.setTapListener(new DoubleTapGestureListener(photo)); - DraweeController controller = Fresco.newDraweeControllerBuilder() - .setUri(imageUri) - .setControllerListener(loadingListener) - .build(); - photo.setController(controller); - } - } - - -} diff --git a/app/src/main/java/fr/free/nrw/commons/media/ZoomableActivity.kt b/app/src/main/java/fr/free/nrw/commons/media/ZoomableActivity.kt new file mode 100644 index 000000000..8d788dcc6 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/media/ZoomableActivity.kt @@ -0,0 +1,387 @@ +package fr.free.nrw.commons.media + +import android.app.Activity +import android.content.Intent +import android.graphics.drawable.Animatable +import android.net.Uri +import android.os.Bundle +import android.view.View +import android.widget.ProgressBar +import android.widget.TextView +import android.widget.Toast +import butterknife.BindView +import butterknife.ButterKnife +import com.facebook.drawee.backends.pipeline.Fresco +import com.facebook.drawee.controller.BaseControllerListener +import com.facebook.drawee.controller.ControllerListener +import com.facebook.drawee.drawable.ProgressBarDrawable +import com.facebook.drawee.drawable.ScalingUtils +import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder +import com.facebook.drawee.interfaces.DraweeController +import com.facebook.imagepipeline.image.ImageInfo +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.database.UploadedStatusDao +import fr.free.nrw.commons.customselector.helper.CustomSelectorConstants +import fr.free.nrw.commons.customselector.helper.OnSwipeTouchListener +import fr.free.nrw.commons.customselector.model.Image +import fr.free.nrw.commons.media.zoomControllers.zoomable.DoubleTapGestureListener +import fr.free.nrw.commons.media.zoomControllers.zoomable.ZoomableDraweeView +import fr.free.nrw.commons.theme.BaseActivity +import fr.free.nrw.commons.upload.FileProcessor +import fr.free.nrw.commons.upload.FileUtilsWrapper +import fr.free.nrw.commons.utils.CustomSelectorUtils +import kotlinx.coroutines.* +import timber.log.Timber +import javax.inject.Inject + +class ZoomableActivity : BaseActivity() { + + private lateinit var imageUri: Uri + + @JvmField + @BindView(R.id.zoomable) + var photo: ZoomableDraweeView? = null + + @JvmField + @BindView(R.id.zoom_progress_bar) + var spinner: ProgressBar? = null + + @JvmField + @BindView(R.id.selection_count) + var selectedCount: TextView? = null + + /** + * Total images present in folder + */ + private var images: ArrayList? = null + + /** + * Total selected images present in folder + */ + private var selectedImages: ArrayList? = null + + /** + * Present position of the image + */ + private var position = 0 + + /** + * FileUtilsWrapper class to get imageSHA1 from uri + */ + @Inject + lateinit var fileUtilsWrapper: FileUtilsWrapper + + /** + * FileProcessor to pre-process the file. + */ + @Inject + lateinit var fileProcessor: FileProcessor + + /** + * NotForUploadStatus Dao class for database operations + */ + @Inject + lateinit var notForUploadStatusDao: NotForUploadStatusDao + + /** + * UploadedStatus Dao class for database operations + */ + @Inject + lateinit var uploadedStatusDao: UploadedStatusDao + + /** + * Coroutine Dispatchers and Scope. + */ + private var defaultDispatcher : CoroutineDispatcher = Dispatchers.Default + private var ioDispatcher : CoroutineDispatcher = Dispatchers.IO + private val scope : CoroutineScope = MainScope() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + images = intent.getParcelableArrayListExtra( + CustomSelectorConstants.TOTAL_IMAGES + ) + selectedImages = intent.getParcelableArrayListExtra( + CustomSelectorConstants.TOTAL_SELECTED_IMAGES + ) + position = intent.getIntExtra(CustomSelectorConstants.PRESENT_POSITION, 0) + imageUri = if (images.isNullOrEmpty()) { + intent.data as Uri + } else { + images!![position].uri + } + Timber.d("URl = $imageUri") + setContentView(R.layout.activity_zoomable) + ButterKnife.bind(this) + init(imageUri) + onSwap() + } + + /** + * Handle swap gestures. Ex. onSwipeLeft, onSwipeRight, onSwipeUp, onSwipeDown + */ + private fun onSwap() { + if (!images.isNullOrEmpty()) { + photo!!.setOnTouchListener(object : OnSwipeTouchListener(this) { + override fun onSwipeLeft() { + super.onSwipeLeft() + if (position < images!!.size - 1) { + position++ + init(images!![position].uri) + } else { + Toast.makeText( + this@ZoomableActivity, + "No more images found", + Toast.LENGTH_SHORT + ).show() + } + } + + override fun onSwipeRight() { + super.onSwipeRight() + if (position > 0) { + position-- + init(images!![position].uri) + } else { + Toast.makeText( + this@ZoomableActivity, + "No more images found", + Toast.LENGTH_SHORT + ).show() + } + } + + override fun onSwipeUp() { + super.onSwipeUp() + scope.launch { + val imageSHA1 = CustomSelectorUtils.getImageSHA1( + images!![position].uri, + ioDispatcher, + fileUtilsWrapper, + contentResolver + ) + var isNonActionable = notForUploadStatusDao.find(imageSHA1) + if (isNonActionable > 0) { + Toast.makeText( + this@ZoomableActivity, + "Can't select this image for upload", Toast.LENGTH_SHORT + ).show() + } else { + isNonActionable = + uploadedStatusDao.findByImageSHA1(imageSHA1, true) + if (isNonActionable > 0) { + Toast.makeText( + this@ZoomableActivity, + "Can't select this image for upload", Toast.LENGTH_SHORT + ).show() + } else { + val imageModifiedSHA1 = CustomSelectorUtils.generateModifiedSHA1( + images!![position], + defaultDispatcher, + this@ZoomableActivity, + fileProcessor, + fileUtilsWrapper + ) + isNonActionable = uploadedStatusDao.findByModifiedImageSHA1( + imageModifiedSHA1, + true + ) + if (isNonActionable > 0) { + Toast.makeText( + this@ZoomableActivity, + "Can't select this image for upload", + Toast.LENGTH_SHORT + ).show() + } else { + if (!selectedImages!!.contains(images!![position])) { + selectedImages!!.add(images!![position]) + } + position = getNextActionableImage(position + 1) + init(images!![position].uri) + } + } + } + } + } + + override fun onSwipeDown() { + super.onSwipeDown() + scope.launch { + insertInNotForUpload(images!![position]) + if (position < images!!.size - 1) { + position++ + init(images!![position].uri) + } else { + Toast.makeText( + this@ZoomableActivity, + "No more images found", + Toast.LENGTH_SHORT + ).show() + } + } + } + }) + } + } + + /** + * Gets next actionable image + */ + private suspend fun getNextActionableImage(index: Int): Int { + var nextPosition = position + for(i in index until images!!.size){ + nextPosition = i + val imageSHA1 = CustomSelectorUtils.getImageSHA1( + images!![i].uri, + ioDispatcher, + fileUtilsWrapper, + contentResolver + ) + var isNonActionable = notForUploadStatusDao.find(imageSHA1) + if (isNonActionable <= 0) { + isNonActionable = uploadedStatusDao.findByImageSHA1(imageSHA1, true) + if (isNonActionable <= 0) { + val imageModifiedSHA1 = CustomSelectorUtils.generateModifiedSHA1( + images!![i], + defaultDispatcher, + this@ZoomableActivity, + fileProcessor, + fileUtilsWrapper + ) + isNonActionable = uploadedStatusDao.findByModifiedImageSHA1( + imageModifiedSHA1, + true + ) + if (isNonActionable <= 0) { + return i + } else { + continue + } + } else { + continue + } + } else { + continue + } + } + return nextPosition + } + + /** + * Unselect item UI + */ + private fun itemUnselected() { + selectedCount!!.visibility = View.INVISIBLE + } + + /** + * Select item UI + */ + private fun itemSelected(i: Int) { + selectedCount!!.visibility = View.VISIBLE + selectedCount!!.text = i.toString() + } + + /** + * Get index from list + */ + private fun getIndex(list: ArrayList?, image: Image): Int { + return list!!.indexOf(image) + } + + /** + * Two types of loading indicators have been added to the zoom activity: + * 1. An Indeterminate spinner for showing the time lapsed between dispatch of the image request + * and starting to receiving the image. + * 2. ProgressBarDrawable that reflects how much image has been downloaded + */ + private val loadingListener: ControllerListener = + object : BaseControllerListener() { + override fun onSubmit(id: String, callerContext: Any) { + // Sometimes the spinner doesn't appear when rapidly switching between images, this fixes that + spinner!!.visibility = View.VISIBLE + } + + override fun onIntermediateImageSet(id: String, imageInfo: ImageInfo?) { + spinner!!.visibility = View.GONE + } + + override fun onFinalImageSet( + id: String, + imageInfo: ImageInfo?, + animatable: Animatable? + ) { + spinner!!.visibility = View.GONE + } + } + + private fun init(imageUri: Uri?) { + if (imageUri != null) { + val hierarchy = GenericDraweeHierarchyBuilder.newInstance(resources) + .setActualImageScaleType(ScalingUtils.ScaleType.FIT_CENTER) + .setProgressBarImage(ProgressBarDrawable()) + .setProgressBarImageScaleType(ScalingUtils.ScaleType.FIT_CENTER) + .build() + photo!!.hierarchy = hierarchy + photo!!.setAllowTouchInterceptionWhileZoomed(true) + photo!!.setIsLongpressEnabled(false) + photo!!.setTapListener(DoubleTapGestureListener(photo)) + val controller: DraweeController = Fresco.newDraweeControllerBuilder() + .setUri(imageUri) + .setControllerListener(loadingListener) + .build() + photo!!.controller = controller + + if (!images.isNullOrEmpty()) { + val selectedIndex = getIndex(selectedImages, images!![position]) + val isSelected = selectedIndex != -1 + if (isSelected) { + itemSelected(selectedIndex + 1) + } else { + itemUnselected() + } + } + } + } + + /** + * Inserts an image in Not For Upload Database + */ + private suspend fun insertInNotForUpload(it: Image) { + val imageSHA1 = CustomSelectorUtils.getImageSHA1( + it.uri, + ioDispatcher, + fileUtilsWrapper, + contentResolver + ) + notForUploadStatusDao.insert( + NotForUploadStatus( + imageSHA1, + true + ) + ) + } + + /** + * Send selected images in fragment + */ + override fun onBackPressed() { + if (!images.isNullOrEmpty()) { + val returnIntent = Intent() + returnIntent.putParcelableArrayListExtra( + CustomSelectorConstants.NEW_SELECTED_IMAGES, + selectedImages + ) + setResult(Activity.RESULT_OK, returnIntent) + finish() + } + super.onBackPressed() + } + + override fun onDestroy() { + scope.cancel() + super.onDestroy() + } +} \ No newline at end of file diff --git a/app/src/main/java/fr/free/nrw/commons/utils/CustomSelectorUtils.kt b/app/src/main/java/fr/free/nrw/commons/utils/CustomSelectorUtils.kt index 5d710cef0..fa426e626 100644 --- a/app/src/main/java/fr/free/nrw/commons/utils/CustomSelectorUtils.kt +++ b/app/src/main/java/fr/free/nrw/commons/utils/CustomSelectorUtils.kt @@ -5,6 +5,7 @@ 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.filepicker.PickedFiles import fr.free.nrw.commons.customselector.ui.selector.ImageLoader import fr.free.nrw.commons.filepicker.PickedFiles import fr.free.nrw.commons.media.MediaClient @@ -26,17 +27,18 @@ class CustomSelectorUtils { /** * Get image sha1 from uri, used to retrieve the original image sha1. */ - suspend fun getImageSHA1(uri: Uri, - ioDispatcher : CoroutineDispatcher, - fileUtilsWrapper: FileUtilsWrapper, - contentResolver: ContentResolver + 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){ + } catch (e: FileNotFoundException) { e.printStackTrace() "" } @@ -44,16 +46,15 @@ class CustomSelectorUtils { } /** - * Generate Modified SHA1 using present Exif settings. - * - * @return modified sha1 + * Generates modified SHA1 of an image */ - suspend fun generateModifiedSHA1(image: Image, - defaultDispatcher : CoroutineDispatcher, - context: Context, - fileProcessor: FileProcessor, - fileUtilsWrapper: FileUtilsWrapper - ) : String { + 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 { @@ -64,7 +65,9 @@ class CustomSelectorUtils { } fileProcessor.redactExifTags(exifInterface, fileProcessor.getExifTagsToRedact()) val sha1 = - fileUtilsWrapper.getSHA1(fileUtilsWrapper.getFileInputStream(uploadableFile.filePath)) + fileUtilsWrapper.getSHA1( + fileUtilsWrapper.getFileInputStream(uploadableFile.filePath) + ) uploadableFile.file.delete() sha1 } diff --git a/app/src/main/res/layout/activity_zoomable.xml b/app/src/main/res/layout/activity_zoomable.xml index d022199e2..83c3d13be 100644 --- a/app/src/main/res/layout/activity_zoomable.xml +++ b/app/src/main/res/layout/activity_zoomable.xml @@ -23,4 +23,21 @@ app:layout_constraintTop_toTopOf="parent" /> + +