[GSoC] Image Selection (#4457)

* Localisation updates from https://translatewiki.net.

* Fixes #4357  After switching to different account, contributions screen shows pictures of previous account (#4421)

* Update UploadMediaDetailFragment.java

* Update LoginActivity.java

Clear CompositeDisposable after logging in successfully. It may help solve the problem of saving the contribution to the previous account

* Revert "Update UploadMediaDetailFragment.java"

This reverts commit b1b4257f20.

Co-authored-by: Obsidian_zero <1198474846@qq.com>

* Remove unnecessary whitespace from a message (#4439)

* Merge v3.0.1 into master (#4446)

* Versioning and changelog for v3.0.0 (#4152)

* Versioning for v3.0.0

* Update changelog.md

* Handled migration 8-9-10 in BookmarksLocationDao (#4154)

* #Fixes #4141
- Handled migrations for BookmarkLocationsDao from 8-9-10

* #Fixes #4141
- Handled migrations for BookmarkLocationsDao from 8-9-10

* Fixes #4179 (#4180)

* Handled null pointer exception in MainActivity->ContributionsFragment#backButtonClicked()
* Updated >ContributionsFragment#backButtonClicked() to handle back press properly

* Fixes #4179 (#4181)

* Handled possible null check on MediaDetails in BookmarkListRootFragment#backPressed()

* Cherrypick for hotfix3.1 (#4205)

* Fixes #4159 On Explore Tab, All Available Options on toolbar in media detail view are only targeting the first media in the list.

Fixes #4159 On Explore Tab, All Available Options on toolbar in media detail view are only targeting the first media in the list.

* fixed bug: App crashes on viewing review in Review Fragment #4132 (#4146)

* fixed bug:app crashes on viewing review in Review Fragment #4135

* Fixed the issue with back button in contribution tab. (#4177)

Co-authored-by: Pratham2305 <Pratham2305@users.noreply.github.com>

* Fixed the issue with back navigation button on toolbar in explore tab. (#4175)

* Fix (#4148) Issues on theme change

* fixed themeChange crashes

* fixed comments

* Overlooked the title bar

Co-authored-by: Pratham Pahariya <54663429+Pratham2305@users.noreply.github.com>
Co-authored-by: Shabir Ahmad <56585337+shabar-shab@users.noreply.github.com>
Co-authored-by: Pratham2305 <Pratham2305@users.noreply.github.com>
Co-authored-by: Aditya-Srivastav <54016427+4D17Y4@users.noreply.github.com>

* Fixes #4173 (#4396)

* Fix #4147  Pre-fill desc in Nearby uploads with Wikidata item's label + description (#4390)

* Update query to fetch descriptions

* Make description added to NearbyResultItem

* Make string operations to display description and label in a combined way

* Fix reviews, remove long description from list and swap label and description texts

* Fix repeated information issue

* Fix double information issue

* fix style issues

* Remove douplicated information

* Changes made (#4354)

* Remove nonexistent method

* Fix #4283 IllegalStateException (#4440)

* Fix #4283 IllegalStateException

* Fix flickering issue

* Versioning for v3.0.1

* Update changelog.md

Co-authored-by: Ashish <ashishkumar468@gmail.com>
Co-authored-by: neslihanturan <tur.neslihan@gmail.com>
Co-authored-by: Pratham Pahariya <54663429+Pratham2305@users.noreply.github.com>
Co-authored-by: Shabir Ahmad <56585337+shabar-shab@users.noreply.github.com>
Co-authored-by: Pratham2305 <Pratham2305@users.noreply.github.com>
Co-authored-by: Aditya-Srivastav <54016427+4D17Y4@users.noreply.github.com>
Co-authored-by: Madhur Gupta <30932899+madhurgupta10@users.noreply.github.com>
Co-authored-by: Vinayak Aggarwal <56196007+vinayak0505@users.noreply.github.com>

* Localisation updates from https://translatewiki.net.

* Added basic Fetch

* added permission request

* Folder count rectified

* Loaded thumbnail

* disabled overlay

* Added sha1 function

* Documented the code

* Added a feature for editing coordinates (#4418)

* not

* Place Picker added

* Pick location and API call linked

* minor warnings resolved

* Code conventions followed

* issue fixed

* Wikitext edited properly

* minor modification

* Location Picker added

* Bottom sheet removed

* Location picker fully implemented

* credit added

* credit added

* issues fixed

* issues fixed

* minor issue fixed

* Some build issues occured merging release v3.0 are fixed. One paranthesis issue is solved, a method about UploadService is removed, since we don't use it anymore. (#4451)

* Localisation updates from https://translatewiki.net.

* Fixes 4344 - Duplicate Uploads (#4442)

* Fixes 4344
- Update the retention policy of the Work Manager to ExistingWorkPolicy.APPEND_OR_REPLACE- which would append the new work to the end of existing one. This helps remove the while loop in UploadWorker which was meant to handle the cases where a new worker would be created for retries. The while loop seemed to have race conditions uploading duplicate entries.

* Update states to IN_PROGRESS before uploads are processed

* Image selection added

* Forwarded activity result to upload wizard

* Initialised xmls, made folder and image item.

* xmls done

* xmls completed

* removed unwanted attribute

* Created models, adapters and view models (#4441)

* created models, adapters and view models

* Added Image Fragment

* back button linked

* Documentation and refractor

* spaces

* Butterknife annotation

* DiffUtil

* Added Examples

* Extended Custom selector From Base Activity

* made view model injectable

* Added basic Fetch

* added permission request

* Folder count rectified

* Loaded thumbnail

* disabled overlay

* Added sha1 function

* Documented the code

* Image selection added

* Forwarded activity result to upload wizard

* [GSOC] Added Image Fetch (#4449)

* Added basic Fetch

* added permission request

* Folder count rectified

* Loaded thumbnail

* disabled overlay

* Added sha1 function

* Documented the code

* fixed merge errors

* Documented the remaining function

Co-authored-by: translatewiki.net <l10n-bot@translatewiki.net>
Co-authored-by: obsidian-zero <63155026+obsidian-zero@users.noreply.github.com>
Co-authored-by: Obsidian_zero <1198474846@qq.com>
Co-authored-by: Amir E. Aharoni <amir.aharoni@mail.huji.ac.il>
Co-authored-by: Josephine Lim <josephinelim86@gmail.com>
Co-authored-by: Ashish <ashishkumar468@gmail.com>
Co-authored-by: neslihanturan <tur.neslihan@gmail.com>
Co-authored-by: Pratham Pahariya <54663429+Pratham2305@users.noreply.github.com>
Co-authored-by: Shabir Ahmad <56585337+shabar-shab@users.noreply.github.com>
Co-authored-by: Pratham2305 <Pratham2305@users.noreply.github.com>
Co-authored-by: Madhur Gupta <30932899+madhurgupta10@users.noreply.github.com>
Co-authored-by: Vinayak Aggarwal <56196007+vinayak0505@users.noreply.github.com>
Co-authored-by: Ayan Sarkar <71203077+Ayan-10@users.noreply.github.com>
This commit is contained in:
Aditya-Srivastav 2021-06-17 14:59:27 +05:30 committed by Aditya Srivastava
parent 133c51ebd5
commit 6686ad505a
9 changed files with 226 additions and 61 deletions

View file

@ -8,7 +8,6 @@ import android.content.Context;
import android.content.Intent;
import androidx.annotation.NonNull;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.customselector.ui.selector.CustomSelectorActivity;
import fr.free.nrw.commons.filepicker.DefaultCallback;
import fr.free.nrw.commons.filepicker.FilePicker;
import fr.free.nrw.commons.filepicker.FilePicker.ImageSource;
@ -63,16 +62,11 @@ public class ContributionController {
* Initiate gallery picker with permission
*/
public void initiateCustomGalleryPickWithPermission(final Activity activity) {
boolean useExtStorage = defaultKvStore.getBoolean("useExternalStorage", true);
Intent intent = new Intent(activity,CustomSelectorActivity.class);
if (!useExtStorage) {
activity.startActivity(intent);
return;
}
setPickerConfiguration(activity,true);
PermissionUtils.checkPermissionsAndPerformAction(activity,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
() -> activity.startActivity(intent),
() -> FilePicker.openCustomSelector(activity, 0),
R.string.storage_permission_title,
R.string.write_storage_permission_rationale);
}

View file

@ -265,34 +265,34 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment impl
});
}
@OnClick(R.id.fab_custom_gallery)
void launchCustomSelector(){
controller.initiateCustomGalleryPickWithPermission(getActivity());
}
private void animateFAB(final boolean isFabOpen) {
this.isFabOpen = !isFabOpen;
if (fabPlus.isShown()) {
if (isFabOpen) {
fabPlus.startAnimation(rotate_backward);
fabCamera.startAnimation(fab_close);
fabGallery.startAnimation(fab_close);
fabCustomGallery.startAnimation(fab_close);
fabCamera.hide();
fabGallery.hide();
fabCustomGallery.hide();
} else {
fabPlus.startAnimation(rotate_forward);
fabCamera.startAnimation(fab_open);
fabGallery.startAnimation(fab_open);
fabCustomGallery.startAnimation(fab_open);
fabCamera.show();
fabGallery.show();
fabCustomGallery.show();
}
this.isFabOpen = !isFabOpen;
@OnClick(R.id.fab_custom_gallery)
void launchCustomSelector(){
controller.initiateCustomGalleryPickWithPermission(getActivity());
}
private void animateFAB(final boolean isFabOpen) {
this.isFabOpen = !isFabOpen;
if (fabPlus.isShown()) {
if (isFabOpen) {
fabPlus.startAnimation(rotate_backward);
fabCamera.startAnimation(fab_close);
fabGallery.startAnimation(fab_close);
fabCustomGallery.startAnimation(fab_close);
fabCamera.hide();
fabGallery.hide();
fabCustomGallery.hide();
} else {
fabPlus.startAnimation(rotate_forward);
fabCamera.startAnimation(fab_open);
fabGallery.startAnimation(fab_open);
fabCustomGallery.startAnimation(fab_open);
fabCamera.show();
fabGallery.show();
fabCustomGallery.show();
}
this.isFabOpen = !isFabOpen;
}
}
}
/**
* Shows welcome message if user has no contributions yet i.e. new user.

View file

@ -11,7 +11,7 @@ import kotlin.collections.ArrayList
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.
*/
object ImageHelper {
@ -49,6 +49,34 @@ object ImageHelper {
return filteredImages
}
/**
* getIndex: Returns the index of image in given list.
*/
fun getIndex(list: ArrayList<Image>, image: Image): Int {
return list.indexOf(image)
}
/**
* Gets the list of indices from the master list.
*/
fun getIndexList(list: ArrayList<Image>, masterList: ArrayList<Image>): ArrayList<Int> {
/**
* TODO
* Can be optimised as masterList is sorted by time.
*/
val indexes = arrayListOf<Int>()
for(image in list) {
val index = getIndex(masterList,image)
if (index == -1) {
continue
}
indexes.add(index)
}
return indexes
}
/**
* Generates the file sha1 from file input stream.
*/

View file

@ -10,6 +10,7 @@ import androidx.constraintlayout.widget.Group
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import fr.free.nrw.commons.customselector.helper.ImageHelper
import fr.free.nrw.commons.customselector.listeners.ImageSelectListener
import fr.free.nrw.commons.customselector.model.Image
@ -26,6 +27,16 @@ class ImageAdapter(
RecyclerViewAdapter<ImageAdapter.ImageViewHolder>(context) {
/**
* ImageSelectedOrUpdated payload class.
*/
class ImageSelectedOrUpdated
/**
* ImageUnselected payload class.
*/
class ImageUnselected
/**
* Currently selected images.
*/
@ -49,18 +60,41 @@ class ImageAdapter(
*/
override fun onBindViewHolder(holder: ImageViewHolder, position: Int) {
val image=images[position]
// todo load image thumbnail, set selected view.
val selectedIndex = ImageHelper.getIndex(selectedImages,image)
val isSelected = selectedIndex != -1
if(isSelected){
holder.itemSelected(selectedIndex+1)
}
else {
holder.itemUnselected();
}
Glide.with(context).load(image.uri).into(holder.image)
holder.itemView.setOnClickListener {
selectOrRemoveImage(image, position)
selectOrRemoveImage(holder, position)
}
}
/**
* Handle click event on an image, update counter on images.
*/
private fun selectOrRemoveImage(image:Image, position:Int){
// todo select the image if not selected and remove it if already selected
private fun selectOrRemoveImage(holder:ImageViewHolder, position:Int){
val clickedIndex = ImageHelper.getIndex(selectedImages,images[position])
if (clickedIndex != -1) {
selectedImages.removeAt(clickedIndex)
notifyItemChanged(position,ImageUnselected())
val indexes = ImageHelper.getIndexList(selectedImages, images)
for (index in indexes) {
notifyItemChanged(index, ImageSelectedOrUpdated())
}
} else {
/**
* TODO
* Show toast on tapping an uploaded item.
*/
selectedImages.add(images[position])
notifyItemChanged(position, ImageSelectedOrUpdated())
}
imageSelectListener.onSelectedImagesChanged(selectedImages)
}
/**
@ -90,9 +124,39 @@ class ImageAdapter(
*/
class ImageViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) {
val image: ImageView = itemView.findViewById(R.id.image_thumbnail)
val selectedNumber: TextView = itemView.findViewById(R.id.selected_count)
val uploadedGroup: Group = itemView.findViewById(R.id.uploaded_group)
val selectedGroup: Group = itemView.findViewById(R.id.selected_group)
private val selectedNumber: TextView = itemView.findViewById(R.id.selected_count)
private val uploadedGroup: Group = itemView.findViewById(R.id.uploaded_group)
private val selectedGroup: Group = itemView.findViewById(R.id.selected_group)
/**
* Item selected view.
*/
fun itemSelected(index: Int) {
selectedGroup.visibility = View.VISIBLE
selectedNumber.text = index.toString()
}
/**
* Item Unselected view.
*/
fun itemUnselected() {
selectedGroup.visibility = View.GONE
}
/**
* Item Uploaded view.
*/
fun itemUploaded() {
uploadedGroup.visibility = View.VISIBLE
}
/**
* Item Not Uploaded view.
*/
fun itemNotUploaded() {
uploadedGroup.visibility = View.GONE
}
}
/**

View file

@ -1,5 +1,7 @@
package fr.free.nrw.commons.customselector.ui.selector
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.widget.ImageButton
import android.widget.TextView
@ -10,6 +12,7 @@ import fr.free.nrw.commons.customselector.listeners.ImageSelectListener
import fr.free.nrw.commons.customselector.model.Folder
import fr.free.nrw.commons.customselector.model.Image
import fr.free.nrw.commons.theme.BaseActivity
import java.io.File
import javax.inject.Inject
class CustomSelectorActivity : BaseActivity(), FolderClickListener, ImageSelectListener {
@ -73,7 +76,8 @@ class CustomSelectorActivity : BaseActivity(), FolderClickListener, ImageSelectL
val back : ImageButton = findViewById(R.id.back)
back.setOnClickListener { onBackPressed() }
// todo done listener.
val done : ImageButton = findViewById(R.id.done)
done.setOnClickListener { onDone() }
}
/**
@ -91,9 +95,44 @@ class CustomSelectorActivity : BaseActivity(), FolderClickListener, ImageSelectL
* override Selected Images Change, update view model selected images.
*/
override fun onSelectedImagesChanged(selectedImages: ArrayList<Image>) {
viewModel.selectedImages.value = selectedImages
// todo update selected images in view model.
}
/**
* OnDone clicked.
* Get the selected images. Remove any non existent file, forward the data to finish selector.
*/
fun onDone() {
val selectedImages = viewModel.selectedImages.value
if(selectedImages.isNullOrEmpty()) {
finishPickImages(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++
}
finishPickImages(selectedImages)
}
/**
* finishPickImages, Load the data to the intent and set result.
* Finish the activity.
*/
private fun finishPickImages(images: ArrayList<Image>) {
val data = Intent()
data.putParcelableArrayListExtra("Images", images)
setResult(Activity.RESULT_OK, data)
finish()
}
/**
* Back pressed.
* Change toolbar title.
@ -106,16 +145,4 @@ class CustomSelectorActivity : BaseActivity(), FolderClickListener, ImageSelectL
}
}
/**
*
* TODO
* Permission check.
* OnDone
* Activity Result.
*
*
*/
}

View file

@ -1,7 +1,6 @@
package fr.free.nrw.commons.customselector.ui.selector
import android.content.Context
import android.util.Log
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import fr.free.nrw.commons.customselector.listeners.ImageLoaderListener
@ -14,10 +13,18 @@ import kotlinx.coroutines.cancel
class CustomSelectorViewModel(var context: Context,var imageFileLoader: ImageFileLoader) : ViewModel() {
/**
* Scope for coroutine task (image fetch).
*/
private val scope = CoroutineScope(Dispatchers.Main)
/**
* Result Live Data
* Stores selected images.
*/
var selectedImages: MutableLiveData<ArrayList<Image>> = MutableLiveData()
/**
* Result Live Data.
*/
val result = MutableLiveData(Result(CallbackStatus.IDLE, arrayListOf()))

View file

@ -13,7 +13,7 @@ import fr.free.nrw.commons.explore.recentsearches.RecentSearchesDao;
public class DBOpenHelper extends SQLiteOpenHelper {
private static final String DATABASE_NAME = "commons.db";
private static final int DATABASE_VERSION = 15;
private static final int DATABASE_VERSION = 14;
public static final String CONTRIBUTIONS_TABLE = "contributions";
private final String DROP_TABLE_STATEMENT="DROP TABLE IF EXISTS %s";

View file

@ -10,6 +10,7 @@ public interface Constants {
int FILE_PICKER_IMAGE_IDENTIFICATOR = 0b1101101100; //876
int SOURCE_CHOOSER = 1 << 15;
int PICK_PICTURE_FROM_CUSTOM_SELECTOR = FILE_PICKER_IMAGE_IDENTIFICATOR + (1 << 10);
int PICK_PICTURE_FROM_DOCUMENTS = FILE_PICKER_IMAGE_IDENTIFICATOR + (1 << 11);
int PICK_PICTURE_FROM_GALLERY = FILE_PICKER_IMAGE_IDENTIFICATOR + (1 << 12);
int TAKE_PICTURE = FILE_PICKER_IMAGE_IDENTIFICATOR + (1 << 13);

View file

@ -15,6 +15,8 @@ import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import fr.free.nrw.commons.customselector.model.Image;
import fr.free.nrw.commons.customselector.ui.selector.CustomSelectorActivity;
import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
@ -51,6 +53,11 @@ public class FilePicker implements Constants {
.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, configuration(context).allowsMultiplePickingInGallery());
}
private static Intent createCustomSelectorIntent(@NonNull Context context, int type) {
storeType(context, type);
return new Intent(context, CustomSelectorActivity.class);
}
private static Intent createCameraForImageIntent(@NonNull Context context, int type) {
storeType(context, type);
@ -97,6 +104,14 @@ public class FilePicker implements Constants {
activity.startActivityForResult(intent, RequestCodes.PICK_PICTURE_FROM_GALLERY);
}
/**
* Opens Custom Selector
*/
public static void openCustomSelector(Activity activity, int type) {
Intent intent = createCustomSelectorIntent(activity, type);
activity.startActivityForResult(intent, RequestCodes.PICK_PICTURE_FROM_CUSTOM_SELECTOR);
}
/**
* Opens the camera app to pick image clicked by user
*/
@ -135,12 +150,15 @@ public class FilePicker implements Constants {
if (requestCode == RequestCodes.PICK_PICTURE_FROM_GALLERY ||
requestCode == RequestCodes.TAKE_PICTURE ||
requestCode == RequestCodes.CAPTURE_VIDEO ||
requestCode == RequestCodes.PICK_PICTURE_FROM_DOCUMENTS) {
requestCode == RequestCodes.PICK_PICTURE_FROM_DOCUMENTS ||
requestCode == RequestCodes.PICK_PICTURE_FROM_CUSTOM_SELECTOR) {
if (resultCode == Activity.RESULT_OK) {
if (requestCode == RequestCodes.PICK_PICTURE_FROM_DOCUMENTS && !isPhoto(data)) {
onPictureReturnedFromDocuments(data, activity, callbacks);
} else if (requestCode == RequestCodes.PICK_PICTURE_FROM_GALLERY && !isPhoto(data)) {
onPictureReturnedFromGallery(data, activity, callbacks);
} else if (requestCode == RequestCodes.PICK_PICTURE_FROM_CUSTOM_SELECTOR) {
onPictureReturnedFromCustomSelector(data, activity, callbacks);
} else if (requestCode == RequestCodes.TAKE_PICTURE) {
onPictureReturnedFromCamera(activity, callbacks);
} else if (requestCode == RequestCodes.CAPTURE_VIDEO) {
@ -197,6 +215,32 @@ public class FilePicker implements Constants {
}
}
private static void onPictureReturnedFromCustomSelector(Intent data, Activity activity, @NonNull FilePicker.Callbacks callbacks) {
try {
List<UploadableFile> files = getFilesFromCustomSelector(data, activity);
callbacks.onImagesPicked(files, ImageSource.CUSTOM_SELECTOR, restoreType(activity));
} catch (Exception e) {
e.printStackTrace();
callbacks.onImagePickerError(e, ImageSource.CUSTOM_SELECTOR, restoreType(activity));
}
}
private static List<UploadableFile> getFilesFromCustomSelector(Intent data, Activity activity) throws IOException, SecurityException {
List<UploadableFile> files = new ArrayList<>();
ArrayList<Image> images = data.getParcelableArrayListExtra("Images");
for(Image image : images) {
Uri uri = image.getUri();
UploadableFile file = PickedFiles.pickedExistingPicture(activity, uri);
files.add(file);
}
if (configuration(activity).shouldCopyPickedImagesToPublicGalleryAppFolder()) {
PickedFiles.copyFilesInSeparateThread(activity, files);
}
return files;
}
private static void onPictureReturnedFromGallery(Intent data, Activity activity, @NonNull FilePicker.Callbacks callbacks) {
try {
List<UploadableFile> files = getFilesFromGalleryPictures(data, activity);
@ -301,7 +345,7 @@ public class FilePicker implements Constants {
public enum ImageSource {
GALLERY, DOCUMENTS, CAMERA_IMAGE, CAMERA_VIDEO
GALLERY, DOCUMENTS, CAMERA_IMAGE, CAMERA_VIDEO, CUSTOM_SELECTOR
}
public interface Callbacks {