From 560662cad4702627855e3ed16adedbdea168e7a5 Mon Sep 17 00:00:00 2001
From: Aditya Srivastava
Date: Tue, 25 May 2021 00:54:55 +0530
Subject: [PATCH 01/28] Initialised xmls, made folder and image item.
---
app/build.gradle | 2 +-
.../main/res/drawable-ldpi/circle_shape.xml | 4 +
app/src/main/res/drawable-ldpi/commons.xml | 62 +++++++++++++
.../res/layout/activity_custom_selector.xml | 7 ++
.../res/layout/fragment_custom_selector.xml | 42 +++++++++
.../layout/item_custom_selector_folder.xml | 84 ++++++++++++++++++
.../res/layout/item_custom_selector_image.xml | 87 +++++++++++++++++++
7 files changed, 287 insertions(+), 1 deletion(-)
create mode 100644 app/src/main/res/drawable-ldpi/circle_shape.xml
create mode 100644 app/src/main/res/drawable-ldpi/commons.xml
create mode 100644 app/src/main/res/layout/activity_custom_selector.xml
create mode 100644 app/src/main/res/layout/fragment_custom_selector.xml
create mode 100644 app/src/main/res/layout/item_custom_selector_folder.xml
create mode 100644 app/src/main/res/layout/item_custom_selector_image.xml
diff --git a/app/build.gradle b/app/build.gradle
index c6f34cc682..68405acce4 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -204,7 +204,7 @@ android {
}
}
debug {
- minifyEnabled true
+ minifyEnabled false
testCoverageEnabled project.hasProperty('coverage')
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
testProguardFile 'test-proguard-rules.txt'
diff --git a/app/src/main/res/drawable-ldpi/circle_shape.xml b/app/src/main/res/drawable-ldpi/circle_shape.xml
new file mode 100644
index 0000000000..d581bfb9f3
--- /dev/null
+++ b/app/src/main/res/drawable-ldpi/circle_shape.xml
@@ -0,0 +1,4 @@
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable-ldpi/commons.xml b/app/src/main/res/drawable-ldpi/commons.xml
new file mode 100644
index 0000000000..4c2e6cabff
--- /dev/null
+++ b/app/src/main/res/drawable-ldpi/commons.xml
@@ -0,0 +1,62 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_custom_selector.xml b/app/src/main/res/layout/activity_custom_selector.xml
new file mode 100644
index 0000000000..1b6d22e00b
--- /dev/null
+++ b/app/src/main/res/layout/activity_custom_selector.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_custom_selector.xml b/app/src/main/res/layout/fragment_custom_selector.xml
new file mode 100644
index 0000000000..45a174bff2
--- /dev/null
+++ b/app/src/main/res/layout/fragment_custom_selector.xml
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_custom_selector_folder.xml b/app/src/main/res/layout/item_custom_selector_folder.xml
new file mode 100644
index 0000000000..3592255ddb
--- /dev/null
+++ b/app/src/main/res/layout/item_custom_selector_folder.xml
@@ -0,0 +1,84 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_custom_selector_image.xml b/app/src/main/res/layout/item_custom_selector_image.xml
new file mode 100644
index 0000000000..7252f543ff
--- /dev/null
+++ b/app/src/main/res/layout/item_custom_selector_image.xml
@@ -0,0 +1,87 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
From 4f394ffd71f892c1e75b7c23eae3890634c4498f Mon Sep 17 00:00:00 2001
From: Aditya Srivastava
Date: Sat, 5 Jun 2021 05:25:10 +0530
Subject: [PATCH 02/28] xmls done
---
app/src/main/res/drawable/ic_arrow_back_black.xml | 10 ++++++++++
app/src/main/res/drawable/ic_done_black.xml | 10 ++++++++++
app/src/main/res/drawable/ic_done_white.xml | 5 +++++
app/src/main/res/layout/custom_selector_toolbar.xml | 6 ++++++
4 files changed, 31 insertions(+)
create mode 100644 app/src/main/res/drawable/ic_arrow_back_black.xml
create mode 100644 app/src/main/res/drawable/ic_done_black.xml
create mode 100644 app/src/main/res/drawable/ic_done_white.xml
create mode 100644 app/src/main/res/layout/custom_selector_toolbar.xml
diff --git a/app/src/main/res/drawable/ic_arrow_back_black.xml b/app/src/main/res/drawable/ic_arrow_back_black.xml
new file mode 100644
index 0000000000..b5487b3ea4
--- /dev/null
+++ b/app/src/main/res/drawable/ic_arrow_back_black.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_done_black.xml b/app/src/main/res/drawable/ic_done_black.xml
new file mode 100644
index 0000000000..899cbb6840
--- /dev/null
+++ b/app/src/main/res/drawable/ic_done_black.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_done_white.xml b/app/src/main/res/drawable/ic_done_white.xml
new file mode 100644
index 0000000000..2728880b72
--- /dev/null
+++ b/app/src/main/res/drawable/ic_done_white.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/layout/custom_selector_toolbar.xml b/app/src/main/res/layout/custom_selector_toolbar.xml
new file mode 100644
index 0000000000..28f5b725cc
--- /dev/null
+++ b/app/src/main/res/layout/custom_selector_toolbar.xml
@@ -0,0 +1,6 @@
+
+
+
+
\ No newline at end of file
From d6472a1fb123151c0b49b4b91abdee72fa3b1bff Mon Sep 17 00:00:00 2001
From: Aditya Srivastava
Date: Sat, 5 Jun 2021 05:25:47 +0530
Subject: [PATCH 03/28] xmls completed
---
app/src/main/res/drawable/ic_done_black.xml | 2 +-
.../res/layout/activity_custom_selector.xml | 23 ++++++++--
.../res/layout/custom_selector_toolbar.xml | 46 +++++++++++++++++--
.../res/layout/fragment_custom_selector.xml | 14 +++---
.../layout/item_custom_selector_folder.xml | 13 +++---
.../res/layout/item_custom_selector_image.xml | 15 +++---
app/src/main/res/values/attrs.xml | 2 +
app/src/main/res/values/dimens.xml | 1 +
app/src/main/res/values/strings.xml | 2 +
app/src/main/res/values/styles.xml | 4 ++
10 files changed, 91 insertions(+), 31 deletions(-)
diff --git a/app/src/main/res/drawable/ic_done_black.xml b/app/src/main/res/drawable/ic_done_black.xml
index 899cbb6840..2d3858a70b 100644
--- a/app/src/main/res/drawable/ic_done_black.xml
+++ b/app/src/main/res/drawable/ic_done_black.xml
@@ -5,6 +5,6 @@
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
diff --git a/app/src/main/res/layout/activity_custom_selector.xml b/app/src/main/res/layout/activity_custom_selector.xml
index 1b6d22e00b..f90ca51e24 100644
--- a/app/src/main/res/layout/activity_custom_selector.xml
+++ b/app/src/main/res/layout/activity_custom_selector.xml
@@ -1,7 +1,24 @@
-
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/custom_selector_toolbar.xml b/app/src/main/res/layout/custom_selector_toolbar.xml
index 28f5b725cc..45ebdc9234 100644
--- a/app/src/main/res/layout/custom_selector_toolbar.xml
+++ b/app/src/main/res/layout/custom_selector_toolbar.xml
@@ -1,6 +1,44 @@
-
+
-
\ No newline at end of file
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_custom_selector.xml b/app/src/main/res/layout/fragment_custom_selector.xml
index 45a174bff2..8a41cb6dd4 100644
--- a/app/src/main/res/layout/fragment_custom_selector.xml
+++ b/app/src/main/res/layout/fragment_custom_selector.xml
@@ -6,20 +6,19 @@
xmlns:app="http://schemas.android.com/apk/res-auto">
+ />
+
+
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
index ac5d31cf73..b3fb6d1a4d 100644
--- a/app/src/main/res/values/dimens.xml
+++ b/app/src/main/res/values/dimens.xml
@@ -58,6 +58,7 @@
0dp
+ 2dp
6dp
10dp
20dp
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index e0cf7d7901..4e3c8fb040 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -646,5 +646,7 @@ Upload your first media by tapping on the add button.
LEARN MORE
Wiki Loves Monuments
Wiki Loves Monuments is an international photo contest for monuments organised by Wikimedia
+ Custom Selector
+ No Images
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index 308291dea8..1ae9e0a7cb 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -55,6 +55,8 @@
- @color/white
- @color/white
- @drawable/ic_search_white_24dp
+ - @drawable/ic_done_white
+ - @drawable/ic_arrow_back_white
- false
- false
@@ -113,6 +115,8 @@
- @color/disabled_button_text_color_dark
- @color/primaryDarkColor
- @drawable/ic_search_blue_24dp
+ - @drawable/ic_done_black
+ - @drawable/ic_arrow_back_black
- false
- false
From 63798c00ca023ba642bd9f6d2369f22e471d825b Mon Sep 17 00:00:00 2001
From: Aditya Srivastava
Date: Sat, 5 Jun 2021 08:00:07 +0530
Subject: [PATCH 04/28] removed unwanted attribute
---
app/src/main/res/layout/item_custom_selector_folder.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/src/main/res/layout/item_custom_selector_folder.xml b/app/src/main/res/layout/item_custom_selector_folder.xml
index c13838e923..4dcc78c1ae 100644
--- a/app/src/main/res/layout/item_custom_selector_folder.xml
+++ b/app/src/main/res/layout/item_custom_selector_folder.xml
@@ -31,7 +31,7 @@
android:layout_height="match_parent"
android:id="@+id/album_overlay"
android:alpha="0.05"
- android:background=""/>
+ />
<54016427+4D17Y4@users.noreply.github.com>
Date: Thu, 10 Jun 2021 13:02:00 +0530
Subject: [PATCH 05/28] 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
---
app/src/main/AndroidManifest.xml | 3 +
.../ContributionsListFragment.java | 83 +++++++----
.../listeners/FolderClickListener.kt | 7 +
.../listeners/ImageLoaderListener.kt | 8 +
.../listeners/ImageSelectListener.kt | 7 +
.../customselector/model/CallbackStatus.kt | 18 +++
.../commons/customselector/model/Folder.kt | 44 ++++++
.../nrw/commons/customselector/model/Image.kt | 125 ++++++++++++++++
.../commons/customselector/model/Result.kt | 13 ++
.../ui/adapter/FolderAdapter.kt | 141 ++++++++++++++++++
.../customselector/ui/adapter/ImageAdapter.kt | 135 +++++++++++++++++
.../ui/adapter/RecyclerViewAdapter.kt | 12 ++
.../ui/selector/CustomSelectorActivity.kt | 121 +++++++++++++++
.../ui/selector/CustomSelectorViewModel.kt | 36 +++++
.../CustomSelectorViewModelFactory.kt | 17 +++
.../ui/selector/FolderFragment.kt | 108 ++++++++++++++
.../ui/selector/ImageFileLoader.kt | 38 +++++
.../ui/selector/ImageFragment.kt | 126 ++++++++++++++++
.../customselector/ui/selector/ImageLoader.kt | 7 +
.../nrw/commons/di/ActivityBuilderModule.java | 4 +
.../commons/di/CommonsApplicationModule.java | 6 +
.../nrw/commons/di/FragmentBuilderModule.java | 8 +
.../res/layout/activity_custom_selector.xml | 9 +-
.../res/layout/custom_selector_toolbar.xml | 7 +-
.../layout/fragment_contributions_list.xml | 13 ++
.../res/layout/fragment_custom_selector.xml | 1 +
.../layout/item_custom_selector_folder.xml | 45 +++---
.../res/layout/item_custom_selector_image.xml | 23 +--
28 files changed, 1092 insertions(+), 73 deletions(-)
create mode 100644 app/src/main/java/fr/free/nrw/commons/customselector/listeners/FolderClickListener.kt
create mode 100644 app/src/main/java/fr/free/nrw/commons/customselector/listeners/ImageLoaderListener.kt
create mode 100644 app/src/main/java/fr/free/nrw/commons/customselector/listeners/ImageSelectListener.kt
create mode 100644 app/src/main/java/fr/free/nrw/commons/customselector/model/CallbackStatus.kt
create mode 100644 app/src/main/java/fr/free/nrw/commons/customselector/model/Folder.kt
create mode 100644 app/src/main/java/fr/free/nrw/commons/customselector/model/Image.kt
create mode 100644 app/src/main/java/fr/free/nrw/commons/customselector/model/Result.kt
create mode 100644 app/src/main/java/fr/free/nrw/commons/customselector/ui/adapter/FolderAdapter.kt
create mode 100644 app/src/main/java/fr/free/nrw/commons/customselector/ui/adapter/ImageAdapter.kt
create mode 100644 app/src/main/java/fr/free/nrw/commons/customselector/ui/adapter/RecyclerViewAdapter.kt
create mode 100644 app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/CustomSelectorActivity.kt
create mode 100644 app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/CustomSelectorViewModel.kt
create mode 100644 app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/CustomSelectorViewModelFactory.kt
create mode 100644 app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/FolderFragment.kt
create mode 100644 app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/ImageFileLoader.kt
create mode 100644 app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/ImageFragment.kt
create mode 100644 app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/ImageLoader.kt
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index cc6576aad0..1c10683275 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -110,6 +110,9 @@
+
+
)
+ fun onFailed(throwable: Throwable)
+}
\ 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
new file mode 100644
index 0000000000..c29aa21e2e
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/customselector/listeners/ImageSelectListener.kt
@@ -0,0 +1,7 @@
+package fr.free.nrw.commons.customselector.listeners
+
+import fr.free.nrw.commons.customselector.model.Image
+
+interface ImageSelectListener {
+ fun onSelectedImagesChanged(selectedImages: ArrayList)
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/model/CallbackStatus.kt b/app/src/main/java/fr/free/nrw/commons/customselector/model/CallbackStatus.kt
new file mode 100644
index 0000000000..257b39a950
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/customselector/model/CallbackStatus.kt
@@ -0,0 +1,18 @@
+package fr.free.nrw.commons.customselector.model
+
+sealed class CallbackStatus {
+ /**
+ IDLE : The callback is idle , doing nothing.
+ */
+ object IDLE : CallbackStatus()
+
+ /**
+ FETCHING : Fetching images.
+ */
+ object FETCHING : CallbackStatus()
+
+ /**
+ SUCCESS : Success fetching images.
+ */
+ object SUCCESS : CallbackStatus()
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/model/Folder.kt b/app/src/main/java/fr/free/nrw/commons/customselector/model/Folder.kt
new file mode 100644
index 0000000000..0ce95ec22d
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/customselector/model/Folder.kt
@@ -0,0 +1,44 @@
+package fr.free.nrw.commons.customselector.model
+
+data class Folder(
+ /**
+ bucketId : Unique directory id, eg 540528482
+ */
+ var bucketId: Long,
+
+ /**
+ name : bucket/folder name, eg Camera
+ */
+ var name: String,
+
+ /**
+ images : folder images, list of all images under this folder.
+ */
+ var images: ArrayList = arrayListOf()
+
+
+) {
+ /**
+ * Indicates whether some other object is "equal to" this one.
+ */
+ override fun equals(other: Any?): Boolean {
+
+ if (javaClass != other?.javaClass) {
+ return false
+ }
+
+ other as Folder
+
+ if (bucketId != other.bucketId) {
+ return false
+ }
+ if (name != other.name) {
+ return false
+ }
+ if (images != other.images) {
+ return false
+ }
+
+ return true
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/model/Image.kt b/app/src/main/java/fr/free/nrw/commons/customselector/model/Image.kt
new file mode 100644
index 0000000000..d6d296f29d
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/customselector/model/Image.kt
@@ -0,0 +1,125 @@
+package fr.free.nrw.commons.customselector.model
+
+import android.net.Uri
+import android.os.Parcel
+import android.os.Parcelable
+
+data class Image(
+ /**
+ id : Unique image id, primary key of image in device, eg 104950
+ */
+ var id: Long,
+
+ /**
+ name : Name of the image with extension, eg CommonsLogo.jpeg
+ */
+ var name: String,
+
+ /**
+ uri : Uri of the image, points to image location or name, eg content://media/external/images/camera/10495 (Android 10)
+ */
+ var uri: Uri,
+
+ /**
+ path : System path of the image, eg storage/emulated/0/camera/CommonsLogo.jpeg
+ */
+ var path: String,
+
+ /**
+ bucketId : bucketId of folder, eg 540528482
+ */
+ var bucketId: Long = 0,
+
+ /**
+ bucketName : name of folder, eg Camera
+ */
+ var bucketName: String = "",
+
+ /**
+ sha1 : sha1 of original image.
+ */
+ var sha1: String = ""
+) : Parcelable {
+
+ /**
+ default parcelable constructor.
+ */
+ constructor(parcel: Parcel):
+ this(parcel.readLong(),
+ parcel.readString()!!,
+ parcel.readParcelable(Uri::class.java.classLoader)!!,
+ parcel.readString()!!,
+ parcel.readLong(),
+ parcel.readString()!!,
+ parcel.readString()!!
+ )
+
+ /**
+ Write to parcel method.
+ */
+ override fun writeToParcel(parcel: Parcel, flags: Int) {
+ parcel.writeLong(id)
+ parcel.writeString(name)
+ parcel.writeParcelable(uri, flags)
+ parcel.writeString(path)
+ parcel.writeLong(bucketId)
+ parcel.writeString(bucketName)
+ parcel.writeString(sha1)
+ }
+
+ /**
+ * Describe the kinds of special objects contained in this Parcelable
+ */
+ override fun describeContents(): Int {
+ return 0
+ }
+
+ /**
+ * Indicates whether some other object is "equal to" this one.
+ */
+ override fun equals(other: Any?): Boolean {
+
+ if(javaClass != other?.javaClass) {
+ return false
+ }
+
+ other as Image
+
+ if(id != other.id) {
+ return false;
+ }
+ if(name != other.name) {
+ return false;
+ }
+ if(uri != other.uri) {
+ return false;
+ }
+ if(path != other.path) {
+ return false;
+ }
+ if(bucketId != other.bucketId) {
+ return false;
+ }
+ if(bucketName != other.bucketName) {
+ return false;
+ }
+ if(sha1 != other.sha1) {
+ return false;
+ }
+
+ return true
+ }
+
+ /**
+ * Parcelable companion object
+ */
+ companion object CREATOR : Parcelable.Creator {
+ override fun createFromParcel(parcel: Parcel): Image {
+ return Image(parcel)
+ }
+
+ override fun newArray(size: Int): Array {
+ return arrayOfNulls(size)
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/model/Result.kt b/app/src/main/java/fr/free/nrw/commons/customselector/model/Result.kt
new file mode 100644
index 0000000000..0eb4decbd0
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/customselector/model/Result.kt
@@ -0,0 +1,13 @@
+package fr.free.nrw.commons.customselector.model
+
+data class Result(
+ /**
+ * CallbackStatus : stores the result status
+ */
+ val status:CallbackStatus,
+
+ /**
+ * Images : images retrieved
+ */
+ val images: ArrayList) {
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/ui/adapter/FolderAdapter.kt b/app/src/main/java/fr/free/nrw/commons/customselector/ui/adapter/FolderAdapter.kt
new file mode 100644
index 0000000000..11450549c9
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/customselector/ui/adapter/FolderAdapter.kt
@@ -0,0 +1,141 @@
+package fr.free.nrw.commons.customselector.ui.adapter
+
+import android.content.Context
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.recyclerview.widget.DiffUtil
+import androidx.recyclerview.widget.RecyclerView
+import fr.free.nrw.commons.R
+import fr.free.nrw.commons.customselector.listeners.FolderClickListener
+import fr.free.nrw.commons.customselector.model.Folder
+import fr.free.nrw.commons.customselector.ui.selector.ImageLoader
+
+class FolderAdapter(
+ /**
+ * Application context.
+ */
+ context: Context,
+
+ /**
+ * Folder Click listener for click events.
+ */
+ private val itemClickListener: FolderClickListener
+) : RecyclerViewAdapter(context) {
+
+ /**
+ * Image Loader for loading images.
+ */
+ private val imageLoader = ImageLoader()
+
+ /**
+ * List of folders.
+ */
+ private var folders: MutableList = mutableListOf()
+
+ /**
+ * Create view holder, returns View holder item.
+ */
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FolderViewHolder {
+ val itemView = inflater.inflate(R.layout.item_custom_selector_folder, parent, false)
+ return FolderViewHolder(itemView)
+ }
+
+ /**
+ * Bind view holder, setup the item view, title, count and click listener
+ */
+ override fun onBindViewHolder(holder: FolderViewHolder, position: Int) {
+ val folder = folders[position]
+ val count = folder.images.size
+ val previewImage = folder.images[0]
+ holder.name.text = folder.name
+ holder.count.text= count.toString()
+ holder.itemView.setOnClickListener{
+ itemClickListener.onFolderClick(folder)
+ }
+
+ //todo load image thumbnail.
+ }
+
+ /**
+ * Initialise the data set.
+ */
+ fun init(newFolders: List) {
+ val oldFolderList: MutableList = folders
+ val newFolderList = newFolders.toMutableList()
+ val diffResult = DiffUtil.calculateDiff(
+ FoldersDiffCallback(oldFolderList, newFolderList)
+ )
+ folders = newFolderList
+ diffResult.dispatchUpdatesTo(this)
+ }
+
+
+ /**
+ * returns item count.
+ */
+ override fun getItemCount(): Int {
+ return folders.size
+ }
+
+ /**
+ * Folder view holder.
+ */
+ class FolderViewHolder(itemView:View) : RecyclerView.ViewHolder(itemView) {
+
+ /**
+ * Folder thumbnail image view.
+ */
+ val image: ImageView = itemView.findViewById(R.id.folder_thumbnail)
+
+ /**
+ * Folder/album name
+ */
+ val name: TextView = itemView.findViewById(R.id.folder_name)
+
+ /**
+ * Item count in Folder/Item
+ */
+ val count: TextView = itemView.findViewById(R.id.folder_count)
+ }
+
+ /**
+ * DiffUtilCallback.
+ */
+ class FoldersDiffCallback(
+ var oldFolders: MutableList,
+ var newFolders: MutableList
+ ) : DiffUtil.Callback() {
+ /**
+ * Returns the size of the old list.
+ */
+ override fun getOldListSize(): Int {
+ return oldFolders.size
+ }
+
+ /**
+ * Returns the size of the new list.
+ */
+ override fun getNewListSize(): Int {
+ return newFolders.size
+ }
+
+ /**
+ * Called by the DiffUtil to decide whether two object represent the same Item.
+ */
+ override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
+ return oldFolders.get(oldItemPosition).bucketId == newFolders.get(newItemPosition).bucketId
+ }
+
+ /**
+ * Called by the DiffUtil when it wants to check whether two items have the same data.
+ * DiffUtil uses this information to detect if the contents of an item has changed.
+ */
+ override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
+ return oldFolders.get(oldItemPosition).equals(newFolders.get(newItemPosition))
+ }
+
+ }
+
+}
\ 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
new file mode 100644
index 0000000000..b29910c000
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/customselector/ui/adapter/ImageAdapter.kt
@@ -0,0 +1,135 @@
+package fr.free.nrw.commons.customselector.ui.adapter
+
+import android.content.Context
+import android.view.ViewGroup
+import fr.free.nrw.commons.R
+import android.view.View
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.constraintlayout.widget.Group
+import androidx.recyclerview.widget.DiffUtil
+import androidx.recyclerview.widget.RecyclerView
+import fr.free.nrw.commons.customselector.listeners.ImageSelectListener
+import fr.free.nrw.commons.customselector.model.Image
+
+class ImageAdapter(
+ /**
+ * Application Context.
+ */
+ context: Context,
+
+ /**
+ * Image select listener for click events on image.
+ */
+ private var imageSelectListener: ImageSelectListener ):
+
+ RecyclerViewAdapter(context) {
+
+ /**
+ * Currently selected images.
+ */
+ private var selectedImages = arrayListOf()
+
+ /**
+ * List of all images in adapter.
+ */
+ private var images: ArrayList = ArrayList()
+
+ /**
+ * create View holder.
+ */
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ImageViewHolder {
+ val itemView = inflater.inflate(R.layout.item_custom_selector_image,parent, false)
+ return ImageViewHolder(itemView)
+ }
+
+ /**
+ * Bind View holder, load image, selected view, click listeners.
+ */
+ override fun onBindViewHolder(holder: ImageViewHolder, position: Int) {
+ val image=images[position]
+ // todo load image thumbnail, set selected view.
+ holder.itemView.setOnClickListener {
+ selectOrRemoveImage(image, 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
+ }
+
+ /**
+ * Initialize the data set.
+ */
+ fun init(newImages:List) {
+ val oldImageList:ArrayList = images
+ val newImageList:ArrayList = ArrayList(newImages)
+ val diffResult = DiffUtil.calculateDiff(
+ ImagesDiffCallback(oldImageList, newImageList)
+ )
+ images = newImageList
+ diffResult.dispatchUpdatesTo(this)
+ }
+
+ /**
+ * Returns the total number of items in the data set held by the adapter.
+ *
+ * @return The total number of items in this adapter.
+ */
+ override fun getItemCount(): Int {
+ return images.size
+ }
+
+ /**
+ * Image view holder.
+ */
+ 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)
+ }
+
+ /**
+ * DiffUtilCallback.
+ */
+ class ImagesDiffCallback(
+ var oldImageList: ArrayList,
+ var newImageList: ArrayList
+ ) : DiffUtil.Callback(){
+
+ /**
+ * Returns the size of the old list.
+ */
+ override fun getOldListSize(): Int {
+ return oldImageList.size
+ }
+
+ /**
+ * Returns the size of the new list.
+ */
+ override fun getNewListSize(): Int {
+ return newImageList.size
+ }
+
+ /**
+ * Called by the DiffUtil to decide whether two object represent the same Item.
+ */
+ override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
+ return newImageList[newItemPosition].id == oldImageList[oldItemPosition].id
+ }
+
+ /**
+ * Called by the DiffUtil when it wants to check whether two items have the same data.
+ * DiffUtil uses this information to detect if the contents of an item has changed.
+ */
+ override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
+ return oldImageList[oldItemPosition].equals(newImageList[newItemPosition])
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/ui/adapter/RecyclerViewAdapter.kt b/app/src/main/java/fr/free/nrw/commons/customselector/ui/adapter/RecyclerViewAdapter.kt
new file mode 100644
index 0000000000..75f9353028
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/customselector/ui/adapter/RecyclerViewAdapter.kt
@@ -0,0 +1,12 @@
+package fr.free.nrw.commons.customselector.ui.adapter
+
+import android.content.Context
+import android.view.LayoutInflater
+import androidx.recyclerview.widget.RecyclerView
+
+/**
+ * Generic Recycler view adapter.
+ */
+abstract class RecyclerViewAdapter(val context: Context): RecyclerView.Adapter() {
+ val inflater: LayoutInflater = LayoutInflater.from(context)
+}
\ No newline at end of file
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
new file mode 100644
index 0000000000..1ab30f67ea
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/CustomSelectorActivity.kt
@@ -0,0 +1,121 @@
+package fr.free.nrw.commons.customselector.ui.selector
+
+import android.os.Bundle
+import android.widget.ImageButton
+import android.widget.TextView
+import androidx.lifecycle.ViewModelProvider
+import fr.free.nrw.commons.R
+import fr.free.nrw.commons.customselector.listeners.FolderClickListener
+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 javax.inject.Inject
+
+class CustomSelectorActivity : BaseActivity(), FolderClickListener, ImageSelectListener {
+
+ /**
+ * View model.
+ */
+ private lateinit var viewModel: CustomSelectorViewModel
+
+ /**
+ * View Model Factory.
+ */
+ @Inject lateinit var customSelectorViewModelFactory: CustomSelectorViewModelFactory
+
+ /**
+ * onCreate Activity, sets theme, initialises the view model, setup view.
+ */
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_custom_selector)
+
+ viewModel = ViewModelProvider(this,customSelectorViewModelFactory).get(CustomSelectorViewModel::class.java)
+
+ setupViews()
+ }
+
+ /**
+ * Set up view, default folder view.
+ */
+ private fun setupViews() {
+ supportFragmentManager.beginTransaction()
+ .replace(R.id.fragment_container, FolderFragment.newInstance())
+ .commit()
+ fetchData()
+ setUpToolbar()
+
+ // todo : open image fragment depending on the last user visit.
+ }
+
+ /**
+ * Start data fetch in view model.
+ */
+ private fun fetchData() {
+ viewModel.fetchImages()
+ }
+
+ /**
+ * Change the title of the toolbar.
+ */
+ private fun changeTitle(title:String) {
+ val titleText = findViewById(R.id.title)
+ if(titleText != null) {
+ titleText.text = title
+ }
+ }
+
+ /**
+ * Set up the toolbar, back listener, done listener.
+ */
+ private fun setUpToolbar() {
+ val back : ImageButton = findViewById(R.id.back)
+ back.setOnClickListener { onBackPressed() }
+
+ // todo done listener.
+ }
+
+ /**
+ * override on folder click, change the toolbar title on folder click.
+ */
+ override fun onFolderClick(folder: Folder) {
+ supportFragmentManager.beginTransaction()
+ .add(R.id.fragment_container, ImageFragment.newInstance(folder.bucketId))
+ .addToBackStack(null)
+ .commit()
+ changeTitle(folder.name)
+ }
+
+ /**
+ * override Selected Images Change, update view model selected images.
+ */
+ override fun onSelectedImagesChanged(selectedImages: ArrayList) {
+ // todo update selected images in view model.
+ }
+
+ /**
+ * Back pressed.
+ * Change toolbar title.
+ */
+ override fun onBackPressed() {
+ super.onBackPressed()
+ val fragment = supportFragmentManager.findFragmentById(R.id.fragment_container)
+ if(fragment != null && fragment is FolderFragment){
+ changeTitle(getString(R.string.custom_selector_title))
+ }
+ }
+
+
+ /**
+ *
+ * TODO
+ * Permission check.
+ * OnDone
+ * Activity Result.
+ *
+ *
+ */
+
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/CustomSelectorViewModel.kt b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/CustomSelectorViewModel.kt
new file mode 100644
index 0000000000..a5f7cf6e5a
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/CustomSelectorViewModel.kt
@@ -0,0 +1,36 @@
+package fr.free.nrw.commons.customselector.ui.selector
+
+import android.content.Context
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import fr.free.nrw.commons.customselector.listeners.ImageLoaderListener
+import fr.free.nrw.commons.customselector.model.CallbackStatus
+import fr.free.nrw.commons.customselector.model.Image
+import fr.free.nrw.commons.customselector.model.Result
+
+class CustomSelectorViewModel(val context: Context,var imageFileLoader: ImageFileLoader) : ViewModel() {
+
+ /**
+ * Result Live Data
+ */
+ val result = MutableLiveData(Result(CallbackStatus.IDLE, arrayListOf()))
+
+ /**
+ * Fetch Images and supply to result.
+ */
+ fun fetchImages() {
+ result.postValue(Result(CallbackStatus.FETCHING, arrayListOf()))
+ imageFileLoader.abortLoadImage()
+ imageFileLoader.loadDeviceImages(object: ImageLoaderListener {
+
+ override fun onImageLoaded(images: ArrayList) {
+ result.postValue(Result(CallbackStatus.SUCCESS, images))
+ }
+
+ override fun onFailed(throwable: Throwable) {
+ result.postValue(Result(CallbackStatus.SUCCESS, arrayListOf()))
+ }
+
+ })
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/CustomSelectorViewModelFactory.kt b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/CustomSelectorViewModelFactory.kt
new file mode 100644
index 0000000000..d7a7d42f40
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/CustomSelectorViewModelFactory.kt
@@ -0,0 +1,17 @@
+package fr.free.nrw.commons.customselector.ui.selector
+
+import android.content.Context
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import javax.inject.Inject
+
+/**
+ * View Model Factory.
+ */
+class CustomSelectorViewModelFactory @Inject constructor(val context: Context,val imageFileLoader: ImageFileLoader) : ViewModelProvider.Factory {
+
+ override fun create(modelClass: Class) : CustomSelectorViewModel {
+ return CustomSelectorViewModel(context,imageFileLoader) as CustomSelectorViewModel
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/FolderFragment.kt b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/FolderFragment.kt
new file mode 100644
index 0000000000..a3db475717
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/FolderFragment.kt
@@ -0,0 +1,108 @@
+package fr.free.nrw.commons.customselector.ui.selector
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.lifecycle.Observer
+import androidx.lifecycle.ViewModelProvider
+import androidx.recyclerview.widget.GridLayoutManager
+import fr.free.nrw.commons.R
+import fr.free.nrw.commons.customselector.model.Result
+import fr.free.nrw.commons.customselector.listeners.FolderClickListener
+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.di.CommonsDaggerSupportFragment
+import kotlinx.android.synthetic.main.fragment_custom_selector.*
+import kotlinx.android.synthetic.main.fragment_custom_selector.view.*
+import javax.inject.Inject
+
+class FolderFragment : CommonsDaggerSupportFragment() {
+
+ /**
+ * View Model for images.
+ */
+ private var viewModel: CustomSelectorViewModel? = null
+
+ /**
+ * View Model Factory.
+ */
+ var customSelectorViewModelFactory: CustomSelectorViewModelFactory? = null
+ @Inject set
+
+
+ /**
+ * Folder Adapter.
+ */
+ private lateinit var folderAdapter: FolderAdapter
+
+ /**
+ * Grid Layout Manager for recycler view.
+ */
+ private lateinit var gridLayoutManager: GridLayoutManager
+
+ /**
+ * Companion newInstance.
+ */
+ companion object{
+ fun newInstance(): FolderFragment {
+ return FolderFragment()
+ }
+ }
+
+ /**
+ * OnCreate Fragment, get the view model.
+ */
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ viewModel = ViewModelProvider(requireActivity(),customSelectorViewModelFactory!!).get(CustomSelectorViewModel::class.java)
+
+ }
+
+ /**
+ * OnCreateView.
+ * Inflate Layout, init adapter, init gridLayoutManager, setUp recycler view, observe the view model for result.
+ */
+ override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
+ val root = inflater.inflate(R.layout.fragment_custom_selector, container, false)
+ folderAdapter = FolderAdapter(requireActivity(), activity as FolderClickListener)
+ gridLayoutManager = GridLayoutManager(context, columnCount())
+ with(root.selector_rv){
+ this.layoutManager = gridLayoutManager
+ setHasFixedSize(true)
+ this.adapter = folderAdapter
+ }
+ viewModel?.result?.observe(viewLifecycleOwner, Observer {
+ handleResult(it)
+ })
+ return root
+ }
+
+ /**
+ * Handle view model result.
+ * Get folders from images.
+ * Load adapter.
+ */
+ private fun handleResult(result: Result) {
+ if(result.status is CallbackStatus.SUCCESS){
+ val folders = arrayListOf()
+ for( i in 1..12) {
+ folders.add(Folder(i.toLong(), "Folder$i",result.images))
+ }
+ folderAdapter.init(folders)
+ folderAdapter.notifyDataSetChanged()
+ selector_rv.visibility = View.VISIBLE
+ }
+ loader.visibility = if (result.status is CallbackStatus.FETCHING) View.VISIBLE else View.GONE
+ }
+
+ /**
+ * Return Column count ie span count for grid view adapter.
+ */
+ private fun columnCount(): Int {
+ return 2
+ // todo change column count depending on the orientation of the device.
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/ImageFileLoader.kt b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/ImageFileLoader.kt
new file mode 100644
index 0000000000..738c40e989
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/ImageFileLoader.kt
@@ -0,0 +1,38 @@
+package fr.free.nrw.commons.customselector.ui.selector
+
+import android.content.Context
+import android.net.Uri
+import fr.free.nrw.commons.customselector.listeners.ImageLoaderListener
+import fr.free.nrw.commons.customselector.model.Image
+
+class ImageFileLoader(val context: Context) {
+
+ /**
+ * Load Device Images.
+ */
+ fun loadDeviceImages(listener: ImageLoaderListener) {
+ var tempImage = Image(0, "temp", Uri.parse("http://www.google.com"), "path", 0, "bucket", "1223")
+ var array: ArrayList = ArrayList()
+ for(i in 1..100) {
+ array.add(tempImage)
+ }
+ listener.onImageLoaded(array)
+
+ // todo load images from device using cursor.
+ }
+
+ /**
+ * Abort loading images.
+ */
+ fun abortLoadImage(){
+ //todo Abort loading images.
+ }
+
+ /**
+ *
+ * TODO
+ * Runnable Thread for image loading.
+ * Sha1 for image (original image).
+ *
+ */
+}
\ No newline at end of file
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
new file mode 100644
index 0000000000..c22313d655
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/ImageFragment.kt
@@ -0,0 +1,126 @@
+package fr.free.nrw.commons.customselector.ui.selector
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.fragment.app.Fragment
+import androidx.lifecycle.Observer
+import androidx.lifecycle.ViewModelProvider
+import androidx.recyclerview.widget.GridLayoutManager
+import fr.free.nrw.commons.R
+import fr.free.nrw.commons.customselector.listeners.ImageSelectListener
+import fr.free.nrw.commons.customselector.model.CallbackStatus
+import fr.free.nrw.commons.customselector.model.Result
+import fr.free.nrw.commons.customselector.ui.adapter.ImageAdapter
+import fr.free.nrw.commons.di.CommonsDaggerSupportFragment
+import kotlinx.android.synthetic.main.fragment_custom_selector.*
+import kotlinx.android.synthetic.main.fragment_custom_selector.view.*
+import javax.inject.Inject
+
+class ImageFragment: CommonsDaggerSupportFragment() {
+
+ /**
+ * Current bucketId.
+ */
+ private var bucketId: Long? = null
+
+ /**
+ * View model for images.
+ */
+ private lateinit var viewModel: CustomSelectorViewModel
+
+ /**
+ * View model Factory.
+ */
+ lateinit var customSelectorViewModelFactory: CustomSelectorViewModelFactory
+ @Inject set
+
+ /**
+ * Image Adapter for recycle view.
+ */
+ private lateinit var imageAdapter: ImageAdapter
+
+ /**
+ * GridLayoutManager for recycler view.
+ */
+ private lateinit var gridLayoutManager: GridLayoutManager
+
+
+ companion object {
+
+ /**
+ * BucketId args name
+ */
+ const val BUCKET_ID = "BucketId"
+
+ /**
+ * newInstance from bucketId.
+ */
+ fun newInstance(bucketId: Long): ImageFragment {
+ val fragment = ImageFragment()
+ val args = Bundle()
+ args.putLong(BUCKET_ID, bucketId)
+ fragment.arguments = args
+ return fragment
+ }
+ }
+
+ /**
+ * OnCreate
+ * Get BucketId, view Model.
+ */
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ bucketId = arguments?.getLong(BUCKET_ID)
+ viewModel = ViewModelProvider(requireActivity(),customSelectorViewModelFactory).get(CustomSelectorViewModel::class.java)
+ }
+
+ /**
+ * OnCreateView
+ * Init imageAdapter, gridLayoutManger.
+ * SetUp recycler view.
+ */
+ override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
+
+ val root = inflater.inflate(R.layout.fragment_custom_selector, container, false)
+ imageAdapter = ImageAdapter(requireActivity(), activity as ImageSelectListener)
+ gridLayoutManager = GridLayoutManager(context,getSpanCount())
+ with(root.selector_rv){
+ this.layoutManager = gridLayoutManager
+ setHasFixedSize(true)
+ this.adapter = imageAdapter
+ }
+
+ viewModel.result.observe(viewLifecycleOwner, Observer{
+ handleResult(it)
+ })
+
+ return root
+ }
+
+ /**
+ * Handle view model result.
+ */
+ private fun handleResult(result:Result){
+ if(result.status is CallbackStatus.SUCCESS){
+ val images = result.images
+ if(images.isNotEmpty()) {
+ imageAdapter.init(images)
+ selector_rv.visibility = View.VISIBLE
+ }
+ else{
+ selector_rv.visibility = View.GONE
+ }
+ }
+ loader.visibility = if (result.status is CallbackStatus.FETCHING) View.VISIBLE else View.GONE
+ }
+
+ /**
+ * getSpanCount for GridViewManager.
+ */
+ private fun getSpanCount(): Int {
+ return 3
+ // todo change span count depending on the device orientation and other factos.
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/ImageLoader.kt b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/ImageLoader.kt
new file mode 100644
index 0000000000..22da8cbbb9
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/ImageLoader.kt
@@ -0,0 +1,7 @@
+package fr.free.nrw.commons.customselector.ui.selector
+
+/**
+ * Image Loader class, loads images, depending on API results.
+ */
+class ImageLoader {
+}
\ 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 28c79c612f..6381bdc8e9 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
@@ -8,6 +8,7 @@
import fr.free.nrw.commons.auth.SignupActivity;
import fr.free.nrw.commons.category.CategoryDetailsActivity;
import fr.free.nrw.commons.contributions.MainActivity;
+import fr.free.nrw.commons.customselector.ui.selector.CustomSelectorActivity;
import fr.free.nrw.commons.explore.depictions.WikidataItemDetailsActivity;
import fr.free.nrw.commons.explore.SearchActivity;
import fr.free.nrw.commons.notification.NotificationActivity;
@@ -34,6 +35,9 @@ public abstract class ActivityBuilderModule {
@ContributesAndroidInjector
abstract MainActivity bindContributionsActivity();
+ @ContributesAndroidInjector
+ abstract CustomSelectorActivity bindCustomSelectorActivity();
+
@ContributesAndroidInjector
abstract SettingsActivity bindSettingsActivity();
diff --git a/app/src/main/java/fr/free/nrw/commons/di/CommonsApplicationModule.java b/app/src/main/java/fr/free/nrw/commons/di/CommonsApplicationModule.java
index 1e19de5f4e..bca71de983 100644
--- a/app/src/main/java/fr/free/nrw/commons/di/CommonsApplicationModule.java
+++ b/app/src/main/java/fr/free/nrw/commons/di/CommonsApplicationModule.java
@@ -17,6 +17,7 @@
import fr.free.nrw.commons.auth.AccountUtil;
import fr.free.nrw.commons.auth.SessionManager;
import fr.free.nrw.commons.contributions.ContributionDao;
+import fr.free.nrw.commons.customselector.ui.selector.ImageFileLoader;
import fr.free.nrw.commons.data.DBOpenHelper;
import fr.free.nrw.commons.db.AppDatabase;
import fr.free.nrw.commons.kvstore.JsonKvStore;
@@ -66,6 +67,11 @@ public CommonsApplicationModule(Context applicationContext) {
this.applicationContext = applicationContext;
}
+ @Provides
+ public ImageFileLoader providesImageFileLoader() {
+ return new ImageFileLoader(this.applicationContext);
+ }
+
@Provides
public Context providesApplicationContext() {
return this.applicationContext;
diff --git a/app/src/main/java/fr/free/nrw/commons/di/FragmentBuilderModule.java b/app/src/main/java/fr/free/nrw/commons/di/FragmentBuilderModule.java
index 3757a21473..f255134eaf 100644
--- a/app/src/main/java/fr/free/nrw/commons/di/FragmentBuilderModule.java
+++ b/app/src/main/java/fr/free/nrw/commons/di/FragmentBuilderModule.java
@@ -8,6 +8,8 @@
import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesFragment;
import fr.free.nrw.commons.contributions.ContributionsFragment;
import fr.free.nrw.commons.contributions.ContributionsListFragment;
+import fr.free.nrw.commons.customselector.ui.selector.FolderFragment;
+import fr.free.nrw.commons.customselector.ui.selector.ImageFragment;
import fr.free.nrw.commons.explore.ExploreFragment;
import fr.free.nrw.commons.explore.ExploreListRootFragment;
import fr.free.nrw.commons.explore.categories.media.CategoriesMediaFragment;
@@ -49,6 +51,12 @@ public abstract class FragmentBuilderModule {
@ContributesAndroidInjector
abstract MediaDetailFragment bindMediaDetailFragment();
+ @ContributesAndroidInjector
+ abstract FolderFragment bindFolderFragment();
+
+ @ContributesAndroidInjector
+ abstract ImageFragment bindImageFragment();
+
@ContributesAndroidInjector
abstract MediaDetailPagerFragment bindMediaDetailPagerFragment();
diff --git a/app/src/main/res/layout/activity_custom_selector.xml b/app/src/main/res/layout/activity_custom_selector.xml
index f90ca51e24..9587e7c0a0 100644
--- a/app/src/main/res/layout/activity_custom_selector.xml
+++ b/app/src/main/res/layout/activity_custom_selector.xml
@@ -15,10 +15,11 @@
app:layout_constraintTop_toTopOf="parent"/>
-
+ app:layout_constraintTop_toBottomOf="@+id/toolbar_layout"/>
\ No newline at end of file
diff --git a/app/src/main/res/layout/custom_selector_toolbar.xml b/app/src/main/res/layout/custom_selector_toolbar.xml
index 45ebdc9234..29b9ab66b3 100644
--- a/app/src/main/res/layout/custom_selector_toolbar.xml
+++ b/app/src/main/res/layout/custom_selector_toolbar.xml
@@ -3,7 +3,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
>
-
@@ -29,7 +31,7 @@
android:text="@string/custom_selector_title"
style="@style/TextAppearance.AppCompat.Widget.ActionBar.Title" />
-
diff --git a/app/src/main/res/layout/fragment_contributions_list.xml b/app/src/main/res/layout/fragment_contributions_list.xml
index 49e4e60c74..e9852f49af 100644
--- a/app/src/main/res/layout/fragment_contributions_list.xml
+++ b/app/src/main/res/layout/fragment_contributions_list.xml
@@ -69,6 +69,19 @@
app:fabSize="mini"
app:srcCompat="@drawable/ic_photo_white_24dp" />
+
+
diff --git a/app/src/main/res/layout/item_custom_selector_folder.xml b/app/src/main/res/layout/item_custom_selector_folder.xml
index 4dcc78c1ae..077968c6a5 100644
--- a/app/src/main/res/layout/item_custom_selector_folder.xml
+++ b/app/src/main/res/layout/item_custom_selector_folder.xml
@@ -1,8 +1,8 @@
-
@@ -24,49 +23,47 @@
android:id="@+id/folder_thumbnail"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:scaleType="centerCrop"/>
+ android:background="@color/black"
+ android:alpha="0.15"
+ android:scaleType="centerCrop" />
+ android:alpha="0.05" />
+ app:layout_constraintBottom_toBottomOf="parent">
+ android:textSize="16sp"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintLeft_toLeftOf="parent" />
+ android:textStyle="bold"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintRight_toRightOf="parent" />
@@ -75,7 +72,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="visible"
- app:constraint_referenced_ids="folder_details,album_overlay"/>
+ app:constraint_referenced_ids="folder_details,album_overlay" />
diff --git a/app/src/main/res/layout/item_custom_selector_image.xml b/app/src/main/res/layout/item_custom_selector_image.xml
index e3240e90ee..eec1eb9d99 100644
--- a/app/src/main/res/layout/item_custom_selector_image.xml
+++ b/app/src/main/res/layout/item_custom_selector_image.xml
@@ -3,16 +3,16 @@
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
+ android:padding="@dimen/dimen_2"
android:layout_height="wrap_content">
@@ -38,18 +38,19 @@
android:layout_width="@dimen/dimen_20"
android:layout_height="@dimen/dimen_20"
app:layout_constraintDimensionRatio="H,1:1"
- android:layout_margin="@dimen/dimen_10"
- android:gravity="center|center_vertical"
- android:includeFontPadding="false"
+ android:textSize="11sp"
android:textStyle="bold"
- android:textColor="@color/black"
+ android:layout_margin="@dimen/dimen_6"
+ android:gravity="center|center_vertical"
+ style="@style/TextAppearance.AppCompat.Small"
+ android:text="12"
android:background="@drawable/circle_shape"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<54016427+4D17Y4@users.noreply.github.com>
Date: Sun, 13 Jun 2021 16:10:04 +0530
Subject: [PATCH 06/28] [GSOC] Added Image Fetch (#4449)
* Added basic Fetch
* added permission request
* Folder count rectified
* Loaded thumbnail
* disabled overlay
* Added sha1 function
* Documented the code
---
app/build.gradle | 4 +
.../contributions/ContributionController.java | 20 ++++
.../ContributionsListFragment.java | 3 +-
.../customselector/helper/ImageHelper.kt | 94 +++++++++++++++++++
.../ui/adapter/FolderAdapter.kt | 4 +-
.../customselector/ui/adapter/ImageAdapter.kt | 2 +
.../ui/selector/CustomSelectorViewModel.kt | 20 +++-
.../ui/selector/FolderFragment.kt | 10 +-
.../ui/selector/ImageFileLoader.kt | 92 +++++++++++++++---
.../ui/selector/ImageFragment.kt | 6 +-
.../layout/item_custom_selector_folder.xml | 17 ++--
.../res/layout/item_custom_selector_image.xml | 6 +-
12 files changed, 239 insertions(+), 39 deletions(-)
create mode 100644 app/src/main/java/fr/free/nrw/commons/customselector/helper/ImageHelper.kt
diff --git a/app/build.gradle b/app/build.gradle
index 68405acce4..501595fdaa 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -142,6 +142,10 @@ dependencies {
def work_version = "2.4.0"
// Kotlin + coroutines
implementation "androidx.work:work-runtime-ktx:$work_version"
+
+ //Glide
+ implementation 'com.github.bumptech.glide:glide:4.12.0'
+ annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0'
}
android {
diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionController.java b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionController.java
index 15c61d8367..778b1afdcc 100644
--- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionController.java
+++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionController.java
@@ -8,6 +8,7 @@
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;
@@ -58,6 +59,25 @@ public void initiateGalleryPick(final Activity activity, final boolean allowMult
initiateGalleryUpload(activity, allowMultipleUploads);
}
+ /**
+ * 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;
+ }
+
+ PermissionUtils.checkPermissionsAndPerformAction(activity,
+ Manifest.permission.WRITE_EXTERNAL_STORAGE,
+ () -> activity.startActivity(intent),
+ R.string.storage_permission_title,
+ R.string.write_storage_permission_rationale);
+ }
+
+
/**
* Open chooser for gallery uploads
*/
diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListFragment.java b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListFragment.java
index d0fd857e9b..c2c6c40866 100644
--- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListFragment.java
+++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListFragment.java
@@ -271,8 +271,7 @@ private void setListeners() {
@OnClick(R.id.fab_custom_gallery)
void launchCustomSelector(){
- Intent intent = new Intent(getActivity(), CustomSelectorActivity.class);
- startActivity(intent);
+ controller.initiateCustomGalleryPickWithPermission(getActivity());
}
private void animateFAB(final boolean isFabOpen) {
diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/helper/ImageHelper.kt b/app/src/main/java/fr/free/nrw/commons/customselector/helper/ImageHelper.kt
new file mode 100644
index 0000000000..1b676b6e2c
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/customselector/helper/ImageHelper.kt
@@ -0,0 +1,94 @@
+package fr.free.nrw.commons.customselector.helper
+
+import fr.free.nrw.commons.customselector.model.Folder
+import fr.free.nrw.commons.customselector.model.Image
+import timber.log.Timber
+import java.io.*
+import java.math.BigInteger
+import java.security.MessageDigest
+import java.security.NoSuchAlgorithmException
+import kotlin.collections.ArrayList
+import kotlin.collections.LinkedHashMap
+
+/**
+ * Image Helper object, includes all the static functions required by custom selector
+ */
+
+object ImageHelper {
+
+ /**
+ * Returns the list of folders from given image list.
+ */
+ fun folderListFromImages(images: List): List {
+ val folderMap: MutableMap = LinkedHashMap()
+ for (image in images) {
+ val bucketId = image.bucketId
+ val bucketName = image.bucketName
+ var folder = folderMap[bucketId]
+ if (folder == null) {
+ folder = Folder(bucketId, bucketName)
+ folderMap[bucketId] = folder
+ }
+ folder.images.add(image)
+ }
+ return ArrayList(folderMap.values)
+ }
+
+ /**
+ * Filters the images based on the given bucketId (folder)
+ */
+ fun filterImages(images: ArrayList, bukketId: Long?): ArrayList {
+ if (bukketId == null) return images
+
+ val filteredImages = arrayListOf()
+ for (image in images) {
+ if (image.bucketId == bukketId) {
+ filteredImages.add(image)
+ }
+ }
+ return filteredImages
+ }
+
+ /**
+ * 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)
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/ui/adapter/FolderAdapter.kt b/app/src/main/java/fr/free/nrw/commons/customselector/ui/adapter/FolderAdapter.kt
index 11450549c9..5d28a46d1b 100644
--- a/app/src/main/java/fr/free/nrw/commons/customselector/ui/adapter/FolderAdapter.kt
+++ b/app/src/main/java/fr/free/nrw/commons/customselector/ui/adapter/FolderAdapter.kt
@@ -7,6 +7,7 @@ import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
+import com.bumptech.glide.Glide
import fr.free.nrw.commons.R
import fr.free.nrw.commons.customselector.listeners.FolderClickListener
import fr.free.nrw.commons.customselector.model.Folder
@@ -49,8 +50,9 @@ class FolderAdapter(
val folder = folders[position]
val count = folder.images.size
val previewImage = folder.images[0]
+ Glide.with(context).load(previewImage.uri).into(holder.image)
holder.name.text = folder.name
- holder.count.text= count.toString()
+ holder.count.text = count.toString()
holder.itemView.setOnClickListener{
itemClickListener.onFolderClick(folder)
}
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 b29910c000..53de6de773 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
@@ -9,6 +9,7 @@ import android.widget.TextView
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.listeners.ImageSelectListener
import fr.free.nrw.commons.customselector.model.Image
@@ -49,6 +50,7 @@ class ImageAdapter(
override fun onBindViewHolder(holder: ImageViewHolder, position: Int) {
val image=images[position]
// todo load image thumbnail, set selected view.
+ Glide.with(context).load(image.uri).into(holder.image)
holder.itemView.setOnClickListener {
selectOrRemoveImage(image, position)
}
diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/CustomSelectorViewModel.kt b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/CustomSelectorViewModel.kt
index a5f7cf6e5a..26b8033bac 100644
--- a/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/CustomSelectorViewModel.kt
+++ b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/CustomSelectorViewModel.kt
@@ -1,14 +1,20 @@
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
import fr.free.nrw.commons.customselector.model.CallbackStatus
import fr.free.nrw.commons.customselector.model.Image
import fr.free.nrw.commons.customselector.model.Result
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.cancel
-class CustomSelectorViewModel(val context: Context,var imageFileLoader: ImageFileLoader) : ViewModel() {
+class CustomSelectorViewModel(var context: Context,var imageFileLoader: ImageFileLoader) : ViewModel() {
+
+ private val scope = CoroutineScope(Dispatchers.Main)
/**
* Result Live Data
@@ -20,9 +26,8 @@ class CustomSelectorViewModel(val context: Context,var imageFileLoader: ImageFil
*/
fun fetchImages() {
result.postValue(Result(CallbackStatus.FETCHING, arrayListOf()))
- imageFileLoader.abortLoadImage()
+ scope.cancel()
imageFileLoader.loadDeviceImages(object: ImageLoaderListener {
-
override fun onImageLoaded(images: ArrayList) {
result.postValue(Result(CallbackStatus.SUCCESS, images))
}
@@ -30,7 +35,14 @@ class CustomSelectorViewModel(val context: Context,var imageFileLoader: ImageFil
override fun onFailed(throwable: Throwable) {
result.postValue(Result(CallbackStatus.SUCCESS, arrayListOf()))
}
+ },scope)
+ }
- })
+ /**
+ * Clear the coroutine task linked with context.
+ */
+ override fun onCleared() {
+ scope.cancel()
+ super.onCleared()
}
}
\ No newline at end of file
diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/FolderFragment.kt b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/FolderFragment.kt
index a3db475717..ffacde0e72 100644
--- a/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/FolderFragment.kt
+++ b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/FolderFragment.kt
@@ -8,6 +8,7 @@ import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.GridLayoutManager
import fr.free.nrw.commons.R
+import fr.free.nrw.commons.customselector.helper.ImageHelper
import fr.free.nrw.commons.customselector.model.Result
import fr.free.nrw.commons.customselector.listeners.FolderClickListener
import fr.free.nrw.commons.customselector.model.CallbackStatus
@@ -29,7 +30,7 @@ class FolderFragment : CommonsDaggerSupportFragment() {
* View Model Factory.
*/
var customSelectorViewModelFactory: CustomSelectorViewModelFactory? = null
- @Inject set
+ @Inject set
/**
@@ -67,7 +68,7 @@ class FolderFragment : CommonsDaggerSupportFragment() {
*/
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val root = inflater.inflate(R.layout.fragment_custom_selector, container, false)
- folderAdapter = FolderAdapter(requireActivity(), activity as FolderClickListener)
+ folderAdapter = FolderAdapter(activity!!, activity as FolderClickListener)
gridLayoutManager = GridLayoutManager(context, columnCount())
with(root.selector_rv){
this.layoutManager = gridLayoutManager
@@ -87,10 +88,7 @@ class FolderFragment : CommonsDaggerSupportFragment() {
*/
private fun handleResult(result: Result) {
if(result.status is CallbackStatus.SUCCESS){
- val folders = arrayListOf()
- for( i in 1..12) {
- folders.add(Folder(i.toLong(), "Folder$i",result.images))
- }
+ val folders = ImageHelper.folderListFromImages(result.images)
folderAdapter.init(folders)
folderAdapter.notifyDataSetChanged()
selector_rv.visibility = View.VISIBLE
diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/ImageFileLoader.kt b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/ImageFileLoader.kt
index 738c40e989..95cb8233ff 100644
--- a/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/ImageFileLoader.kt
+++ b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/ImageFileLoader.kt
@@ -1,26 +1,97 @@
package fr.free.nrw.commons.customselector.ui.selector
+import android.content.ContentUris
import android.content.Context
-import android.net.Uri
+import android.provider.MediaStore
import fr.free.nrw.commons.customselector.listeners.ImageLoaderListener
import fr.free.nrw.commons.customselector.model.Image
+import kotlinx.coroutines.*
+import java.io.File
+import kotlin.coroutines.CoroutineContext
-class ImageFileLoader(val context: Context) {
+class ImageFileLoader(val context: Context) : CoroutineScope{
/**
- * Load Device Images.
+ * Coroutine context for fetching images.
*/
- fun loadDeviceImages(listener: ImageLoaderListener) {
- var tempImage = Image(0, "temp", Uri.parse("http://www.google.com"), "path", 0, "bucket", "1223")
- var array: ArrayList = ArrayList()
- for(i in 1..100) {
- array.add(tempImage)
+ override val coroutineContext: CoroutineContext = Dispatchers.Main
+
+ /**
+ * Media paramerters required.
+ */
+ private val projection = arrayOf(
+ MediaStore.Images.Media._ID,
+ MediaStore.Images.Media.DISPLAY_NAME,
+ MediaStore.Images.Media.DATA,
+ MediaStore.Images.Media.BUCKET_ID,
+ MediaStore.Images.Media.BUCKET_DISPLAY_NAME)
+
+ /**
+ * Load Device Images under coroutine.
+ */
+ fun loadDeviceImages(listener: ImageLoaderListener, scope: CoroutineScope) {
+ launch(Dispatchers.Main) {
+ withContext(Dispatchers.IO) {
+ getImages(listener)
+ }
}
- listener.onImageLoaded(array)
+ }
+
- // todo load images from device using cursor.
+ /**
+ * Load the device images using cursor
+ */
+ private fun getImages(listener:ImageLoaderListener) {
+ val cursor = context.contentResolver.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, projection, null, null, MediaStore.Images.Media.DATE_ADDED + " DESC")
+ if (cursor == null) {
+ listener.onFailed(NullPointerException())
+ return
+ }
+
+ val idColumn = cursor.getColumnIndex(MediaStore.Images.Media._ID)
+ val nameColumn = cursor.getColumnIndex(MediaStore.Images.Media.DISPLAY_NAME)
+ val dataColumn = cursor.getColumnIndex(MediaStore.Images.Media.DATA)
+ val bucketIdColumn = cursor.getColumnIndex(MediaStore.Images.Media.BUCKET_ID)
+ val bucketNameColumn = cursor.getColumnIndex(MediaStore.Images.Media.BUCKET_DISPLAY_NAME)
+
+ val images = arrayListOf()
+ if (cursor.moveToFirst()) {
+ do {
+ if (Thread.interrupted()) {
+ listener.onFailed(NullPointerException())
+ return
+ }
+ val id = cursor.getLong(idColumn)
+ val name = cursor.getString(nameColumn)
+ val path = cursor.getString(dataColumn)
+ val bucketId = cursor.getLong(bucketIdColumn)
+ val bucketName = cursor.getString(bucketNameColumn)
+
+ val file =
+ if (path == null || path.isEmpty()) {
+ null
+ } else try {
+ File(path)
+ } catch (ignored: Exception) {
+ null
+ }
+
+
+ if (file != null && file.exists()) {
+ if (id != null && name != null && path != null && bucketId != null && bucketName != null) {
+ val uri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id)
+ val image = Image(id, name, uri, path, bucketId, bucketName)
+ images.add(image)
+ }
+ }
+
+ } while (cursor.moveToNext())
+ }
+ cursor.close()
+ listener.onImageLoaded(images)
}
+
/**
* Abort loading images.
*/
@@ -31,7 +102,6 @@ class ImageFileLoader(val context: Context) {
/**
*
* TODO
- * Runnable Thread for image loading.
* Sha1 for image (original image).
*
*/
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 c22313d655..f4b5c99343 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
@@ -4,11 +4,11 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
-import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.GridLayoutManager
import fr.free.nrw.commons.R
+import fr.free.nrw.commons.customselector.helper.ImageHelper
import fr.free.nrw.commons.customselector.listeners.ImageSelectListener
import fr.free.nrw.commons.customselector.model.CallbackStatus
import fr.free.nrw.commons.customselector.model.Result
@@ -34,7 +34,7 @@ class ImageFragment: CommonsDaggerSupportFragment() {
* View model Factory.
*/
lateinit var customSelectorViewModelFactory: CustomSelectorViewModelFactory
- @Inject set
+ @Inject set
/**
* Image Adapter for recycle view.
@@ -106,7 +106,7 @@ class ImageFragment: CommonsDaggerSupportFragment() {
if(result.status is CallbackStatus.SUCCESS){
val images = result.images
if(images.isNotEmpty()) {
- imageAdapter.init(images)
+ imageAdapter.init(ImageHelper.filterImages(images,bucketId))
selector_rv.visibility = View.VISIBLE
}
else{
diff --git a/app/src/main/res/layout/item_custom_selector_folder.xml b/app/src/main/res/layout/item_custom_selector_folder.xml
index 077968c6a5..6d4df43075 100644
--- a/app/src/main/res/layout/item_custom_selector_folder.xml
+++ b/app/src/main/res/layout/item_custom_selector_folder.xml
@@ -19,30 +19,30 @@
android:background="@color/white"
android:layout_height="match_parent">
-
+ android:background="@color/black"
+ android:alpha="0.15" />
diff --git a/app/src/main/res/layout/item_custom_selector_image.xml b/app/src/main/res/layout/item_custom_selector_image.xml
index eec1eb9d99..021f463bc6 100644
--- a/app/src/main/res/layout/item_custom_selector_image.xml
+++ b/app/src/main/res/layout/item_custom_selector_image.xml
@@ -19,7 +19,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
-
@@ -78,7 +78,7 @@
android:id="@+id/uploaded_group"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:visibility="visible"
+ android:visibility="gone"
app:constraint_referenced_ids="uploaded_overlay,uploaded_overlay_icon"/>
From c7dae69ddf45184b6713cb8ea60a78329906bd85 Mon Sep 17 00:00:00 2001
From: Aditya-Srivastav <54016427+4D17Y4@users.noreply.github.com>
Date: Thu, 17 Jun 2021 14:59:27 +0530
Subject: [PATCH 07/28] [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 b1b4257f205b022ffaadee9f947357e5fc04c337.
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
* 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
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
Co-authored-by: neslihanturan
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
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
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
Co-authored-by: Josephine Lim
Co-authored-by: Ashish
Co-authored-by: neslihanturan
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
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>
---
.../contributions/ContributionController.java | 10 +--
.../ContributionsListFragment.java | 54 ++++++-------
.../customselector/helper/ImageHelper.kt | 30 ++++++-
.../customselector/ui/adapter/ImageAdapter.kt | 78 +++++++++++++++++--
.../ui/selector/CustomSelectorActivity.kt | 53 +++++++++----
.../ui/selector/CustomSelectorViewModel.kt | 11 ++-
.../nrw/commons/filepicker/Constants.java | 1 +
.../nrw/commons/filepicker/FilePicker.java | 48 +++++++++++-
8 files changed, 225 insertions(+), 60 deletions(-)
diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionController.java b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionController.java
index 778b1afdcc..27cef1c0f5 100644
--- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionController.java
+++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionController.java
@@ -8,7 +8,6 @@
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 void initiateGalleryPick(final Activity activity, final boolean allowMult
* 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);
}
diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListFragment.java b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListFragment.java
index c2c6c40866..d2bceae1fe 100644
--- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListFragment.java
+++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListFragment.java
@@ -269,34 +269,34 @@ private void setListeners() {
});
}
- @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.
diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/helper/ImageHelper.kt b/app/src/main/java/fr/free/nrw/commons/customselector/helper/ImageHelper.kt
index 1b676b6e2c..9228dc5acb 100644
--- a/app/src/main/java/fr/free/nrw/commons/customselector/helper/ImageHelper.kt
+++ b/app/src/main/java/fr/free/nrw/commons/customselector/helper/ImageHelper.kt
@@ -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): Int {
+ return list.indexOf(image)
+ }
+
+ /**
+ * Gets the list of indices from the master list.
+ */
+ fun getIndexList(list: ArrayList, masterList: ArrayList): ArrayList {
+
+ /**
+ * TODO
+ * Can be optimised as masterList is sorted by time.
+ */
+
+ val indexes = arrayListOf()
+ 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.
*/
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 53de6de773..a38200463d 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
@@ -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(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
+ }
+
}
/**
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 1ab30f67ea..099c89a862 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
@@ -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) {
+ 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) {
+ 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.
- *
- *
- */
-
-
}
\ No newline at end of file
diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/CustomSelectorViewModel.kt b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/CustomSelectorViewModel.kt
index 26b8033bac..4f56a808b5 100644
--- a/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/CustomSelectorViewModel.kt
+++ b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/CustomSelectorViewModel.kt
@@ -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> = MutableLiveData()
+
+ /**
+ * Result Live Data.
*/
val result = MutableLiveData(Result(CallbackStatus.IDLE, arrayListOf()))
diff --git a/app/src/main/java/fr/free/nrw/commons/filepicker/Constants.java b/app/src/main/java/fr/free/nrw/commons/filepicker/Constants.java
index 83d838bc2a..4b5b91e684 100644
--- a/app/src/main/java/fr/free/nrw/commons/filepicker/Constants.java
+++ b/app/src/main/java/fr/free/nrw/commons/filepicker/Constants.java
@@ -10,6 +10,7 @@ interface RequestCodes {
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);
diff --git a/app/src/main/java/fr/free/nrw/commons/filepicker/FilePicker.java b/app/src/main/java/fr/free/nrw/commons/filepicker/FilePicker.java
index 698e2d51f2..6d516abd96 100644
--- a/app/src/main/java/fr/free/nrw/commons/filepicker/FilePicker.java
+++ b/app/src/main/java/fr/free/nrw/commons/filepicker/FilePicker.java
@@ -15,6 +15,8 @@
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 @@ private static Intent createGalleryIntent(@NonNull Context context, int type) {
.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 static void openGallery(Activity activity, int type) {
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 static void handleActivityResult(int requestCode, int resultCode, Intent
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 @@ private static void onPictureReturnedFromDocuments(Intent data, Activity activit
}
}
+ private static void onPictureReturnedFromCustomSelector(Intent data, Activity activity, @NonNull FilePicker.Callbacks callbacks) {
+ try {
+ List 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 getFilesFromCustomSelector(Intent data, Activity activity) throws IOException, SecurityException {
+ List files = new ArrayList<>();
+ ArrayList 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 files = getFilesFromGalleryPictures(data, activity);
@@ -301,7 +345,7 @@ public static FilePickerConfiguration configuration(@NonNull Context context) {
public enum ImageSource {
- GALLERY, DOCUMENTS, CAMERA_IMAGE, CAMERA_VIDEO
+ GALLERY, DOCUMENTS, CAMERA_IMAGE, CAMERA_VIDEO, CUSTOM_SELECTOR
}
public interface Callbacks {
From 088f66ead066c0184b3325c76315d5dff32ae942 Mon Sep 17 00:00:00 2001
From: Aditya-Srivastav <54016427+4D17Y4@users.noreply.github.com>
Date: Tue, 22 Jun 2021 03:53:30 +0530
Subject: [PATCH 08/28] [GSoC] Show uploaded images differently. (#4464)
* uploaded images shown differently
* Loaded images before query
* Handled exceptions, Made ImageLoader injectable, Document and clean code
---
.../customselector/helper/ImageHelper.kt | 50 +------
.../ui/adapter/FolderAdapter.kt | 7 +-
.../customselector/ui/adapter/ImageAdapter.kt | 24 +++-
.../ui/selector/FolderFragment.kt | 7 +-
.../ui/selector/ImageFragment.kt | 10 +-
.../customselector/ui/selector/ImageLoader.kt | 131 +++++++++++++++++-
.../nrw/commons/filepicker/PickedFiles.java | 4 +-
.../free/nrw/commons/upload/FileProcessor.kt | 4 +-
8 files changed, 174 insertions(+), 63 deletions(-)
diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/helper/ImageHelper.kt b/app/src/main/java/fr/free/nrw/commons/customselector/helper/ImageHelper.kt
index 9228dc5acb..0a751d47bc 100644
--- a/app/src/main/java/fr/free/nrw/commons/customselector/helper/ImageHelper.kt
+++ b/app/src/main/java/fr/free/nrw/commons/customselector/helper/ImageHelper.kt
@@ -1,15 +1,20 @@
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.Image
+import fr.free.nrw.commons.filepicker.Constants
import timber.log.Timber
import java.io.*
import java.math.BigInteger
import java.security.MessageDigest
import java.security.NoSuchAlgorithmException
import kotlin.collections.ArrayList
+import kotlin.collections.HashMap
import kotlin.collections.LinkedHashMap
+
/**
* Image Helper object, includes all the static functions required by custom selector.
*/
@@ -68,7 +73,7 @@ object ImageHelper {
val indexes = arrayListOf()
for(image in list) {
- val index = getIndex(masterList,image)
+ val index = getIndex(masterList, image)
if (index == -1) {
continue
}
@@ -76,47 +81,4 @@ object ImageHelper {
}
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)
- }
-
}
\ No newline at end of file
diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/ui/adapter/FolderAdapter.kt b/app/src/main/java/fr/free/nrw/commons/customselector/ui/adapter/FolderAdapter.kt
index 5d28a46d1b..fb3e497940 100644
--- a/app/src/main/java/fr/free/nrw/commons/customselector/ui/adapter/FolderAdapter.kt
+++ b/app/src/main/java/fr/free/nrw/commons/customselector/ui/adapter/FolderAdapter.kt
@@ -11,7 +11,6 @@ import com.bumptech.glide.Glide
import fr.free.nrw.commons.R
import fr.free.nrw.commons.customselector.listeners.FolderClickListener
import fr.free.nrw.commons.customselector.model.Folder
-import fr.free.nrw.commons.customselector.ui.selector.ImageLoader
class FolderAdapter(
/**
@@ -23,12 +22,8 @@ class FolderAdapter(
* Folder Click listener for click events.
*/
private val itemClickListener: FolderClickListener
-) : RecyclerViewAdapter(context) {
- /**
- * Image Loader for loading images.
- */
- private val imageLoader = ImageLoader()
+) : RecyclerViewAdapter(context) {
/**
* List of folders.
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 a38200463d..9029e03bc2 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
@@ -6,6 +6,7 @@ import fr.free.nrw.commons.R
import android.view.View
import android.widget.ImageView
import android.widget.TextView
+import android.widget.Toast
import androidx.constraintlayout.widget.Group
import androidx.recyclerview.widget.DiffUtil
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.listeners.ImageSelectListener
import fr.free.nrw.commons.customselector.model.Image
+import fr.free.nrw.commons.customselector.ui.selector.ImageLoader
class ImageAdapter(
/**
@@ -23,7 +25,13 @@ class ImageAdapter(
/**
* Image select listener for click events on image.
*/
- private var imageSelectListener: ImageSelectListener ):
+ private var imageSelectListener: ImageSelectListener,
+
+ /**
+ * ImageLoader queries images.
+ */
+ private var imageLoader: ImageLoader
+):
RecyclerViewAdapter(context) {
@@ -48,7 +56,7 @@ class ImageAdapter(
private var images: ArrayList = ArrayList()
/**
- * create View holder.
+ * Create View holder.
*/
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ImageViewHolder {
val itemView = inflater.inflate(R.layout.item_custom_selector_image,parent, false)
@@ -69,6 +77,7 @@ class ImageAdapter(
holder.itemUnselected();
}
Glide.with(context).load(image.uri).into(holder.image)
+ imageLoader.queryAndSetView(holder,image)
holder.itemView.setOnClickListener {
selectOrRemoveImage(holder, position)
}
@@ -87,12 +96,12 @@ class ImageAdapter(
notifyItemChanged(index, ImageSelectedOrUpdated())
}
} else {
- /**
- * TODO
- * Show toast on tapping an uploaded item.
- */
+ if(holder.isItemUploaded()){
+ Toast.makeText(context,"Already Uploaded image", Toast.LENGTH_SHORT).show()
+ } else {
selectedImages.add(images[position])
notifyItemChanged(position, ImageSelectedOrUpdated())
+ }
}
imageSelectListener.onSelectedImagesChanged(selectedImages)
}
@@ -150,6 +159,9 @@ class ImageAdapter(
uploadedGroup.visibility = View.VISIBLE
}
+ fun isItemUploaded():Boolean {
+ return uploadedGroup.visibility == View.VISIBLE
+ }
/**
* Item Not Uploaded view.
*/
diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/FolderFragment.kt b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/FolderFragment.kt
index ffacde0e72..1d5901c9d0 100644
--- a/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/FolderFragment.kt
+++ b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/FolderFragment.kt
@@ -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.listeners.FolderClickListener
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.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.view.*
import javax.inject.Inject
@@ -32,7 +33,11 @@ class FolderFragment : CommonsDaggerSupportFragment() {
var customSelectorViewModelFactory: CustomSelectorViewModelFactory? = null
@Inject set
+ var fileProcessor: FileProcessor? = null
+ @Inject set
+ var mediaClient: MediaClient? = null
+ @Inject set
/**
* Folder Adapter.
*/
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 f4b5c99343..a2de0ed294 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
@@ -36,6 +36,12 @@ class ImageFragment: CommonsDaggerSupportFragment() {
lateinit var customSelectorViewModelFactory: CustomSelectorViewModelFactory
@Inject set
+ /**
+ * Image loader for adapter.
+ */
+ var imageLoader: ImageLoader? = null
+ @Inject set
+
/**
* Image Adapter for recycle view.
*/
@@ -84,7 +90,7 @@ class ImageFragment: CommonsDaggerSupportFragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
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())
with(root.selector_rv){
this.layoutManager = gridLayoutManager
@@ -118,6 +124,8 @@ class ImageFragment: CommonsDaggerSupportFragment() {
/**
* getSpanCount for GridViewManager.
+ *
+ * @return spanCount.
*/
private fun getSpanCount(): Int {
return 3
diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/ImageLoader.kt b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/ImageLoader.kt
index 22da8cbbb9..a3ae38e348 100644
--- a/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/ImageLoader.kt
+++ b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/ImageLoader.kt
@@ -1,7 +1,136 @@
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.
*/
-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 = HashMap()
+ private var mapHolderImage : HashMap = HashMap()
+ private var mapResult: HashMap = 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
+ }
+
}
\ No newline at end of file
diff --git a/app/src/main/java/fr/free/nrw/commons/filepicker/PickedFiles.java b/app/src/main/java/fr/free/nrw/commons/filepicker/PickedFiles.java
index 01e68c940a..c5eb101bc3 100644
--- a/app/src/main/java/fr/free/nrw/commons/filepicker/PickedFiles.java
+++ b/app/src/main/java/fr/free/nrw/commons/filepicker/PickedFiles.java
@@ -25,7 +25,7 @@
import timber.log.Timber;
-class PickedFiles implements Constants {
+public class PickedFiles implements Constants {
private static String getFolderName(@NonNull Context context) {
return FilePicker.configuration(context).getFolderName();
@@ -104,7 +104,7 @@ static void scanCopiedImages(Context context, List copiedImages) {
});
}
- 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);
File directory = tempImageDirectory(context);
File photoFile = new File(directory, UUID.randomUUID().toString() + "." + getMimeType(context, photoUri));
diff --git a/app/src/main/java/fr/free/nrw/commons/upload/FileProcessor.kt b/app/src/main/java/fr/free/nrw/commons/upload/FileProcessor.kt
index ff3f63eb86..5ad6952ee0 100644
--- a/app/src/main/java/fr/free/nrw/commons/upload/FileProcessor.kt
+++ b/app/src/main/java/fr/free/nrw/commons/upload/FileProcessor.kt
@@ -77,7 +77,7 @@ class FileProcessor @Inject constructor(
*
* @return tags to be redacted
*/
- private fun getExifTagsToRedact(): Set {
+ fun getExifTagsToRedact(): Set {
val prefManageEXIFTags =
defaultKvStore.getStringSet(Prefs.MANAGED_EXIF_TAGS) ?: emptySet()
val redactTags: Set =
@@ -91,7 +91,7 @@ class FileProcessor @Inject constructor(
* @param exifInterface ExifInterface object
* @param redactTags tags to be redacted
*/
- private fun redactExifTags(exifInterface: ExifInterface?, redactTags: Set) {
+ fun redactExifTags(exifInterface: ExifInterface?, redactTags: Set) {
compositeDisposable.add(
Observable.fromIterable(redactTags)
.flatMap { Observable.fromArray(*FileMetadataUtils.getTagsFromPref(it)) }
From 1749a7b8819aeac4e3f53dfea112fcffd90299df Mon Sep 17 00:00:00 2001
From: Aditya-Srivastav <54016427+4D17Y4@users.noreply.github.com>
Date: Tue, 29 Jun 2021 10:09:00 +0530
Subject: [PATCH 09/28] [GSoC] Added Uploaded status table in room database.
(#4476)
* added Uploaded status table in room database
* Added unique property, minor refractoring
* Database intigrated
* Database integrated
* Handled result null exception
* Exceptions handled and refractored
* Introduced constants
* moved to sealed class
* No database insert on network error
* queried original image
* documented the code
* Updated uploaded status on upload success
---
.../nrw/commons/contributions/Contribution.kt | 6 +-
.../customselector/database/UploadedDao.kt | 88 +++++++++++
.../customselector/database/UploadedStatus.kt | 39 +++++
.../customselector/ui/selector/ImageLoader.kt | 148 ++++++++++++++----
.../fr/free/nrw/commons/db/AppDatabase.kt | 5 +-
.../commons/di/CommonsApplicationModule.java | 15 +-
.../free/nrw/commons/upload/UploadItem.java | 9 +-
.../free/nrw/commons/upload/UploadModel.java | 3 +-
.../nrw/commons/upload/worker/UploadWorker.kt | 23 +++
9 files changed, 297 insertions(+), 39 deletions(-)
create mode 100644 app/src/main/java/fr/free/nrw/commons/customselector/database/UploadedDao.kt
create mode 100644 app/src/main/java/fr/free/nrw/commons/customselector/database/UploadedStatus.kt
diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/Contribution.kt b/app/src/main/java/fr/free/nrw/commons/contributions/Contribution.kt
index b46d1087eb..6b895232f9 100644
--- a/app/src/main/java/fr/free/nrw/commons/contributions/Contribution.kt
+++ b/app/src/main/java/fr/free/nrw/commons/contributions/Contribution.kt
@@ -39,7 +39,8 @@ data class Contribution constructor(
var dataLength: Long = 0,
var dateCreated: Date? = null,
var dateModified: Date? = null,
- var hasInvalidLocation : Int = 0
+ var hasInvalidLocation : Int = 0,
+ var contentUri: Uri? = null
) : Parcelable {
fun completeWith(media: Media): Contribution {
@@ -64,7 +65,8 @@ data class Contribution constructor(
decimalCoords = item.gpsCoords.decimalCoords,
dateCreatedSource = "",
depictedItems = depictedItems,
- wikidataPlace = from(item.place)
+ wikidataPlace = from(item.place),
+ contentUri = item.contentUri
)
/**
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
new file mode 100644
index 0000000000..d9f2fc55eb
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/customselector/database/UploadedDao.kt
@@ -0,0 +1,88 @@
+package fr.free.nrw.commons.customselector.database
+
+import androidx.room.*
+import kotlinx.coroutines.runBlocking
+import java.util.*
+import kotlinx.coroutines.*
+
+/**
+ * UploadedStatusDao for Custom Selector.
+ */
+@Dao
+abstract class UploadedStatusDao {
+
+ /**
+ * Insert into uploaded status.
+ */
+ @Insert( onConflict = OnConflictStrategy.REPLACE )
+ abstract suspend fun insert(uploadedStatus: UploadedStatus)
+
+ /**
+ * Update uploaded status entry.
+ */
+ @Update
+ abstract suspend fun update(uploadedStatus: UploadedStatus)
+
+ /**
+ * Delete uploaded status entry.
+ */
+ @Delete
+ abstract suspend fun delete(uploadedStatus: UploadedStatus)
+
+ /**
+ * Get All entries from the uploaded status table.
+ */
+ @Query("SELECT * FROM uploaded_table")
+ abstract suspend fun getAll() : List
+
+ /**
+ * Query uploaded status with image sha1.
+ */
+ @Query("SELECT * FROM uploaded_table WHERE imageSHA1 = (:imageSHA1) ")
+ abstract suspend fun getFromImageSHA1(imageSHA1 : String) : UploadedStatus
+
+ /**
+ * Query uploaded status with modified image sha1.
+ */
+ @Query("SELECT * FROM uploaded_table WHERE modifiedImageSHA1 = (:modifiedImageSHA1) ")
+ abstract suspend fun getFromModifiedImageSHA1(modifiedImageSHA1 : String) : UploadedStatus
+
+ /**
+ * Asynchronous insert into uploaded status table.
+ */
+ fun insertUploaded(uploadedStatus: UploadedStatus) = runBlocking {
+ async {
+ uploadedStatus.lastUpdated = Calendar.getInstance().time as Date?
+ insert(uploadedStatus)
+ }.await()
+ }
+
+ /**
+ * Asynchronous delete from uploaded status table.
+ */
+ fun deleteUploaded(uploadedStatus: UploadedStatus) = runBlocking {
+ async { delete(uploadedStatus) }
+ }
+
+ /**
+ * Asynchronous update entry in uploaded status table.
+ */
+ fun updateUploaded(uploadedStatus: UploadedStatus) = runBlocking {
+ async { update(uploadedStatus) }
+ }
+
+ /**
+ * Asynchronous image sha1 query.
+ */
+ fun getUploadedFromImageSHA1(imageSHA1: String) = runBlocking {
+ async { getFromImageSHA1(imageSHA1) }.await()
+ }
+
+ /**
+ * Asynchronous modified image sha1 query.
+ */
+ fun getUploadedFromModifiedImageSHA1(modifiedImageSHA1: String) = runBlocking {
+ async { getFromModifiedImageSHA1(modifiedImageSHA1) }.await()
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/database/UploadedStatus.kt b/app/src/main/java/fr/free/nrw/commons/customselector/database/UploadedStatus.kt
new file mode 100644
index 0000000000..93e4a82437
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/customselector/database/UploadedStatus.kt
@@ -0,0 +1,39 @@
+package fr.free.nrw.commons.customselector.database
+
+import androidx.room.Entity
+import androidx.room.Index
+import androidx.room.PrimaryKey
+import java.util.*
+
+/**
+ * Entity class for Uploaded Status.
+ */
+@Entity(tableName = "uploaded_table", indices = [Index(value = ["modifiedImageSHA1"], unique = true)])
+data class UploadedStatus(
+
+ /**
+ * Original image sha1.
+ */
+ @PrimaryKey
+ val imageSHA1 : String,
+
+ /**
+ * Modified image sha1 (after exif changes).
+ */
+ val modifiedImageSHA1 : String,
+
+ /**
+ * imageSHA1 query result from API.
+ */
+ var imageResult : Boolean,
+
+ /**
+ * modifiedImageSHA1 query result from API.
+ */
+ var modifiedImageResult : Boolean,
+
+ /**
+ * lastUpdated for data validation.
+ */
+ var lastUpdated : Date? = null
+)
diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/ImageLoader.kt b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/ImageLoader.kt
index a3ae38e348..f2d4d5709f 100644
--- a/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/ImageLoader.kt
+++ b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/ImageLoader.kt
@@ -1,7 +1,10 @@
package fr.free.nrw.commons.customselector.ui.selector
import android.content.Context
+import android.net.Uri
import androidx.exifinterface.media.ExifInterface
+import fr.free.nrw.commons.customselector.database.UploadedStatus
+import fr.free.nrw.commons.customselector.database.UploadedStatusDao
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
@@ -14,6 +17,7 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import timber.log.Timber
import java.io.IOException
+import java.net.UnknownHostException
import java.util.*
import javax.inject.Inject
import kotlin.collections.HashMap
@@ -38,22 +42,28 @@ class ImageLoader @Inject constructor(
*/
var fileUtilsWrapper: FileUtilsWrapper,
+ /**
+ * UploadedStatusDao for cache query.
+ */
+ var uploadedStatusDao: UploadedStatusDao,
+
/**
* Context for coroutine.
*/
- val context: Context) {
+ val context: Context
+) {
/**
* Maps to facilitate image query.
*/
- private var mapImageSHA1: HashMap = HashMap()
- private var mapHolderImage : HashMap = HashMap()
- private var mapResult: HashMap = HashMap()
+ private var mapImageSHA1: HashMap = HashMap()
+ private var mapHolderImage : HashMap = HashMap()
+ private var mapResult: HashMap = HashMap()
/**
* Query image and setUp the view.
*/
- fun queryAndSetView(holder: ImageViewHolder, image: Image){
+ fun queryAndSetView(holder: ImageViewHolder, image: Image) {
/**
* Recycler view uses same view holder, so we can identify the latest query image from holder.
@@ -62,26 +72,46 @@ class ImageLoader @Inject constructor(
holder.itemNotUploaded()
CoroutineScope(Dispatchers.Main).launch {
- var value = false
+
+ var result : Result = Result.NOTFOUND
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
+
+ if (mapHolderImage[holder] == image) {
+ val imageSHA1 = getImageSHA1(image.uri)
+ val uploadedStatus = uploadedStatusDao.getUploadedFromImageSHA1(imageSHA1)
+
+ val sha1 = uploadedStatus?.let {
+ result = getResultFromUploadedStatus(uploadedStatus)
+ uploadedStatus.modifiedImageSHA1
+ } ?: run {
+ if(mapHolderImage[holder] == image) {
+ getSHA1(image)
+ } else {
+ ""
+ }
+ }
+
+ if (mapHolderImage[holder] == image &&
+ result in arrayOf(Result.NOTFOUND, Result.INVALID) &&
+ sha1.isNotEmpty()) {
+ // Query original image.
+ result = querySHA1(imageSHA1)
+ if( result is Result.TRUE ) {
+ // Original image found.
+ insertIntoUploaded(imageSHA1, sha1, result is Result.TRUE, false)
+ }
+ else {
+ // Original image not found, query modified image.
+ result = querySHA1(sha1)
+ if (result != Result.ERROR) {
+ insertIntoUploaded(imageSHA1, sha1, false, result is Result.TRUE)
+ }
+ }
+ }
}
- value = querySHA1(sha1)
}
if(mapHolderImage[holder] == image) {
- // View holder and latest query image match, setup the view.
- if (value) {
- holder.itemUploaded()
- } else {
- holder.itemNotUploaded()
- }
+ if (result is Result.TRUE) holder.itemUploaded() else holder.itemNotUploaded()
}
}
}
@@ -91,13 +121,26 @@ class ImageLoader @Inject constructor(
*
* @return Query result.
*/
- private fun querySHA1(SHA1: String): Boolean {
- if(mapResult[SHA1] != null) {
- return mapResult[SHA1]!!
+ private fun querySHA1(SHA1: String): Result {
+ mapResult[SHA1]?.let{
+ return it
+ }
+ var result : Result = Result.FALSE
+ try {
+ if (mediaClient.checkFileExistsUsingSha(SHA1).blockingGet()) {
+ mapResult[SHA1] = Result.TRUE
+ result = Result.TRUE
+ }
+ } catch (e: Exception) {
+ if (e is UnknownHostException) {
+ // Handle no network connection.
+ Timber.e(e, "Network Connection Error")
+ }
+ result = Result.ERROR
+ e.printStackTrace()
+ } finally {
+ return result
}
- val isUploaded = mediaClient.checkFileExistsUsingSha(SHA1).blockingGet()
- mapResult[SHA1] = isUploaded
- return isUploaded
}
/**
@@ -105,15 +148,45 @@ class ImageLoader @Inject constructor(
*
* @return sha1 of the image
*/
- private fun getSHA1(image: Image): String{
- if(mapImageSHA1[image] != null) {
- return mapImageSHA1[image]!!
+ private fun getSHA1(image: Image): String {
+ mapImageSHA1[image]?.let{
+ return it
}
val sha1 = generateModifiedSHA1(image);
mapImageSHA1[image] = sha1;
return sha1;
}
+ /**
+ * Insert into uploaded status table.
+ */
+ private fun insertIntoUploaded(imageSha1:String, modifiedImageSha1:String, imageResult:Boolean, modifiedImageResult: Boolean){
+ uploadedStatusDao.insertUploaded(UploadedStatus(imageSha1, modifiedImageSha1, imageResult, modifiedImageResult))
+ }
+
+ /**
+ * Get image sha1 from uri, used to retrieve the original image sha1.
+ */
+ private fun getImageSHA1(uri: Uri): String {
+ return fileUtilsWrapper.getSHA1(context.contentResolver.openInputStream(uri))
+ }
+
+ /**
+ * Get result data from database.
+ */
+ private fun getResultFromUploadedStatus(uploadedStatus: UploadedStatus): Result {
+ if (uploadedStatus.imageResult || uploadedStatus.modifiedImageResult) {
+ return Result.TRUE
+ } else {
+ uploadedStatus.lastUpdated?.let {
+ if (it.date >= Calendar.getInstance().time.date - INVALIDATE_DAY_COUNT) {
+ return Result.FALSE
+ }
+ }
+ }
+ return Result.INVALID
+ }
+
/**
* Generate Modified SHA1 using present Exif settings.
*
@@ -133,4 +206,19 @@ class ImageLoader @Inject constructor(
return sha1
}
+ /**
+ * Sealed Result class.
+ */
+ sealed class Result {
+ object TRUE : Result()
+ object FALSE : Result()
+ object INVALID : Result()
+ object NOTFOUND : Result()
+ object ERROR : Result()
+ }
+
+ companion object {
+ const val INVALIDATE_DAY_COUNT: Int = 7
+ }
+
}
\ No newline at end of file
diff --git a/app/src/main/java/fr/free/nrw/commons/db/AppDatabase.kt b/app/src/main/java/fr/free/nrw/commons/db/AppDatabase.kt
index c408ea5f75..7f6ea70276 100644
--- a/app/src/main/java/fr/free/nrw/commons/db/AppDatabase.kt
+++ b/app/src/main/java/fr/free/nrw/commons/db/AppDatabase.kt
@@ -5,6 +5,8 @@ import androidx.room.RoomDatabase
import androidx.room.TypeConverters
import fr.free.nrw.commons.contributions.Contribution
import fr.free.nrw.commons.contributions.ContributionDao
+import fr.free.nrw.commons.customselector.database.UploadedStatus
+import fr.free.nrw.commons.customselector.database.UploadedStatusDao
import fr.free.nrw.commons.upload.depicts.Depicts
import fr.free.nrw.commons.upload.depicts.DepictsDao
@@ -12,9 +14,10 @@ import fr.free.nrw.commons.upload.depicts.DepictsDao
* The database for accessing the respective DAOs
*
*/
-@Database(entities = [Contribution::class, Depicts::class], version = 8, exportSchema = false)
+@Database(entities = [Contribution::class, Depicts::class, UploadedStatus::class], version = 8, exportSchema = false)
@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() {
abstract fun contributionDao(): ContributionDao
abstract fun DepictsDao(): DepictsDao;
+ abstract fun UploadedStatusDao(): UploadedStatusDao;
}
diff --git a/app/src/main/java/fr/free/nrw/commons/di/CommonsApplicationModule.java b/app/src/main/java/fr/free/nrw/commons/di/CommonsApplicationModule.java
index bca71de983..7d9c061ffc 100644
--- a/app/src/main/java/fr/free/nrw/commons/di/CommonsApplicationModule.java
+++ b/app/src/main/java/fr/free/nrw/commons/di/CommonsApplicationModule.java
@@ -17,6 +17,7 @@
import fr.free.nrw.commons.auth.AccountUtil;
import fr.free.nrw.commons.auth.SessionManager;
import fr.free.nrw.commons.contributions.ContributionDao;
+import fr.free.nrw.commons.customselector.database.UploadedStatusDao;
import fr.free.nrw.commons.customselector.ui.selector.ImageFileLoader;
import fr.free.nrw.commons.data.DBOpenHelper;
import fr.free.nrw.commons.db.AppDatabase;
@@ -68,8 +69,8 @@ public CommonsApplicationModule(Context applicationContext) {
}
@Provides
- public ImageFileLoader providesImageFileLoader() {
- return new ImageFileLoader(this.applicationContext);
+ public ImageFileLoader providesImageFileLoader(Context context) {
+ return new ImageFileLoader(context);
}
@Provides
@@ -250,13 +251,21 @@ public ContributionDao providesContributionsDao(AppDatabase appDatabase) {
}
/**
- * Get the reference of DepictsDao class
+ * Get the reference of DepictsDao class.
*/
@Provides
public DepictsDao providesDepictDao(AppDatabase appDatabase) {
return appDatabase.DepictsDao();
}
+ /**
+ * Get the reference of UploadedStatus class.
+ */
+ @Provides
+ public UploadedStatusDao providesUploadedStatusDao(AppDatabase appDatabase) {
+ return appDatabase.UploadedStatusDao();
+ }
+
@Provides
public ContentResolver providesContentResolver(Context context){
return context.getContentResolver();
diff --git a/app/src/main/java/fr/free/nrw/commons/upload/UploadItem.java b/app/src/main/java/fr/free/nrw/commons/upload/UploadItem.java
index 0487fd87fe..bed3e3454f 100644
--- a/app/src/main/java/fr/free/nrw/commons/upload/UploadItem.java
+++ b/app/src/main/java/fr/free/nrw/commons/upload/UploadItem.java
@@ -23,6 +23,7 @@ public class UploadItem {
private final String createdTimestampSource;
private final BehaviorSubject imageQuality;
private boolean hasInvalidLocation;
+ private final Uri contentUri;
@SuppressLint("CheckResult")
@@ -31,7 +32,8 @@ public class UploadItem {
final ImageCoordinates gpsCoords,
final Place place,
final long createdTimestamp,
- final String createdTimestampSource) {
+ final String createdTimestampSource,
+ final Uri contentUri) {
this.createdTimestampSource = createdTimestampSource;
uploadMediaDetails = new ArrayList<>(Collections.singletonList(new UploadMediaDetail()));
this.place = place;
@@ -39,6 +41,7 @@ public class UploadItem {
this.mimeType = mimeType;
this.gpsCoords = gpsCoords;
this.createdTimestamp = createdTimestamp;
+ this.contentUri = contentUri;
imageQuality = BehaviorSubject.createDefault(ImageUtils.IMAGE_WAIT);
}
@@ -66,8 +69,10 @@ public int getImageQuality() {
return imageQuality.getValue();
}
+ public Uri getContentUri() { return contentUri; }
+
public void setImageQuality(final int imageQuality) {
- this.imageQuality.onNext(imageQuality);
+ this.imageQuality.onNext(imageQuality);
}
/**
diff --git a/app/src/main/java/fr/free/nrw/commons/upload/UploadModel.java b/app/src/main/java/fr/free/nrw/commons/upload/UploadModel.java
index cf72fa5d63..1d1b7117fc 100644
--- a/app/src/main/java/fr/free/nrw/commons/upload/UploadModel.java
+++ b/app/src/main/java/fr/free/nrw/commons/upload/UploadModel.java
@@ -106,7 +106,8 @@ private UploadItem createAndAddUploadItem(final UploadableFile uploadableFile,
final UploadItem uploadItem = new UploadItem(
Uri.parse(uploadableFile.getFilePath()),
uploadableFile.getMimeType(context), imageCoordinates, place, fileCreatedDate,
- createdTimestampSource);
+ createdTimestampSource,
+ uploadableFile.getContentUri());
if (place != null) {
uploadItem.getUploadMediaDetails().set(0, new UploadMediaDetail(place));
}
diff --git a/app/src/main/java/fr/free/nrw/commons/upload/worker/UploadWorker.kt b/app/src/main/java/fr/free/nrw/commons/upload/worker/UploadWorker.kt
index ad0c08c4cf..8f9bc9504b 100644
--- a/app/src/main/java/fr/free/nrw/commons/upload/worker/UploadWorker.kt
+++ b/app/src/main/java/fr/free/nrw/commons/upload/worker/UploadWorker.kt
@@ -18,9 +18,12 @@ import fr.free.nrw.commons.auth.SessionManager
import fr.free.nrw.commons.contributions.ChunkInfo
import fr.free.nrw.commons.contributions.Contribution
import fr.free.nrw.commons.contributions.ContributionDao
+import fr.free.nrw.commons.customselector.database.UploadedStatus
+import fr.free.nrw.commons.customselector.database.UploadedStatusDao
import fr.free.nrw.commons.di.ApplicationlessInjection
import fr.free.nrw.commons.location.LatLng
import fr.free.nrw.commons.media.MediaClient
+import fr.free.nrw.commons.upload.FileUtilsWrapper
import fr.free.nrw.commons.upload.StashUploadState
import fr.free.nrw.commons.upload.UploadClient
import fr.free.nrw.commons.upload.UploadResult
@@ -51,12 +54,18 @@ class UploadWorker(var appContext: Context, workerParams: WorkerParameters) :
@Inject
lateinit var contributionDao: ContributionDao
+ @Inject
+ lateinit var uploadedStatusDao: UploadedStatusDao
+
@Inject
lateinit var uploadClient: UploadClient
@Inject
lateinit var mediaClient: MediaClient
+ @Inject
+ lateinit var fileUtilsWrapper: FileUtilsWrapper
+
private val PROCESSING_UPLOADS_NOTIFICATION_TAG = BuildConfig.APPLICATION_ID + " : upload_tag"
private val PROCESSING_UPLOADS_NOTIFICATION_ID = 101
@@ -417,6 +426,20 @@ class UploadWorker(var appContext: Context, workerParams: WorkerParameters) :
.blockingGet()
contributionFromUpload.dateModified=Date()
contributionDao.deleteAndSaveContribution(contribution, contributionFromUpload)
+
+ // Upload success, save to uploaded status.
+ saveIntoUploadedStatus(contribution)
+ }
+
+ /**
+ * Save to uploadedStatusDao.
+ */
+ private fun saveIntoUploadedStatus(contribution: Contribution) {
+ contribution.contentUri?.let {
+ val imageSha1 = fileUtilsWrapper.getSHA1(appContext.contentResolver.openInputStream(it))
+ val modifiedSha1 = fileUtilsWrapper.getSHA1(fileUtilsWrapper.getFileInputStream(contribution.localUri?.path))
+ uploadedStatusDao.insertUploaded(UploadedStatus(imageSha1, modifiedSha1, imageSha1 == modifiedSha1, true));
+ }
}
private fun findUniqueFileName(fileName: String): String {
From 9488737f3e2ff92289239af485751e9a2c468c9b Mon Sep 17 00:00:00 2001
From: Aditya-Srivastav <54016427+4D17Y4@users.noreply.github.com>
Date: Wed, 30 Jun 2021 11:53:04 +0530
Subject: [PATCH 10/28] Image Helper test (#4485)
---
.../customselector/helper/ImageHelperTest.kt | 55 +++++++++++++++++++
.../nrw/commons/filepicker/FilePickerTest.kt | 10 ++++
2 files changed, 65 insertions(+)
create mode 100644 app/src/test/kotlin/fr/free/nrw/commons/customselector/helper/ImageHelperTest.kt
diff --git a/app/src/test/kotlin/fr/free/nrw/commons/customselector/helper/ImageHelperTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/customselector/helper/ImageHelperTest.kt
new file mode 100644
index 0000000000..2fe382368f
--- /dev/null
+++ b/app/src/test/kotlin/fr/free/nrw/commons/customselector/helper/ImageHelperTest.kt
@@ -0,0 +1,55 @@
+package fr.free.nrw.commons.customselector.helper
+
+import android.net.Uri
+import fr.free.nrw.commons.customselector.model.Folder
+import fr.free.nrw.commons.customselector.model.Image
+import org.junit.jupiter.api.Assertions.*
+
+import org.junit.jupiter.api.Test
+import org.mockito.Mockito.mock
+
+/**
+ * Custom Selector Image Helper Test
+ */
+internal class ImageHelperTest {
+
+ var uri: Uri = mock(Uri::class.java)
+ private val folderImage1 = Image(1, "image1", uri, "abc/abc", 1, "bucket1")
+ private val folderImage2 = Image(2, "image1", uri, "xyz/xyz", 2, "bucket2")
+ private val mockImageList = ArrayList(listOf(folderImage1, folderImage2))
+ private val folderImageList1 = ArrayList(listOf(folderImage1))
+ private val folderImageList2 = ArrayList(listOf(folderImage2))
+
+ /**
+ * Test folder list from images.
+ */
+ @Test
+ fun folderListFromImages() {
+ val folderList = ArrayList(listOf(Folder(1, "bucket1", folderImageList1), Folder(2, "bucket2", folderImageList2)))
+ assertEquals(folderList, ImageHelper.folderListFromImages(mockImageList))
+ }
+
+ /**
+ * Test filter images.
+ */
+ @Test
+ fun filterImages() {
+ assertEquals(folderImageList1, ImageHelper.filterImages(mockImageList, 1))
+ }
+
+ /**
+ * Test get index from image list.
+ */
+ @Test
+ fun getIndex() {
+ assertEquals(1,ImageHelper.getIndex(mockImageList, folderImage2))
+ }
+
+ /**
+ * Test get index list.
+ */
+ @Test
+ fun getIndexList() {
+ assertEquals(ArrayList(listOf(0)), ImageHelper.getIndexList(mockImageList, folderImageList2))
+ }
+}
\ No newline at end of file
diff --git a/app/src/test/kotlin/fr/free/nrw/commons/filepicker/FilePickerTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/filepicker/FilePickerTest.kt
index b9712df04c..ae841cd1cd 100644
--- a/app/src/test/kotlin/fr/free/nrw/commons/filepicker/FilePickerTest.kt
+++ b/app/src/test/kotlin/fr/free/nrw/commons/filepicker/FilePickerTest.kt
@@ -54,4 +54,14 @@ class FilePickerTest {
verify(activity).startActivityForResult(ArgumentMatchers.anyObject(), requestCodeCaptor?.capture()?.toInt()!!)
assertEquals(requestCodeCaptor?.value, RequestCodes.TAKE_PICTURE)
}
+
+ @Test
+ fun testOpenCustomSelectorRequestCode() {
+ `when`(PreferenceManager.getDefaultSharedPreferences(activity)).thenReturn(sharedPref)
+ `when`(sharedPref.edit()).thenReturn(sharedPreferencesEditor)
+ `when`(sharedPref.edit().putInt("type", 0)).thenReturn(sharedPreferencesEditor)
+ FilePicker.openCustomSelector(activity, 0)
+ verify(activity).startActivityForResult(ArgumentMatchers.anyObject(), requestCodeCaptor?.capture()?.toInt()!!)
+ assertEquals(requestCodeCaptor?.value, RequestCodes.PICK_PICTURE_FROM_CUSTOM_SELECTOR)
+ }
}
\ No newline at end of file
From e10e6662b0bd2402d8c95d657b9d7b8807baf71c Mon Sep 17 00:00:00 2001
From: Aditya-Srivastav <54016427+4D17Y4@users.noreply.github.com>
Date: Sat, 3 Jul 2021 11:32:54 +0530
Subject: [PATCH 11/28] [GSoC] Adapter Tests (#4488)
* Added FolderAdapterTest
* Image Adapter Test
---
.../ui/adapter/FolderAdapterTest.kt | 81 ++++++++++++
.../ui/adapter/ImageAdapterTest.kt | 121 ++++++++++++++++++
2 files changed, 202 insertions(+)
create mode 100644 app/src/test/kotlin/fr/free/nrw/commons/customselector/ui/adapter/FolderAdapterTest.kt
create mode 100644 app/src/test/kotlin/fr/free/nrw/commons/customselector/ui/adapter/ImageAdapterTest.kt
diff --git a/app/src/test/kotlin/fr/free/nrw/commons/customselector/ui/adapter/FolderAdapterTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/customselector/ui/adapter/FolderAdapterTest.kt
new file mode 100644
index 0000000000..6a6271b96e
--- /dev/null
+++ b/app/src/test/kotlin/fr/free/nrw/commons/customselector/ui/adapter/FolderAdapterTest.kt
@@ -0,0 +1,81 @@
+package fr.free.nrw.commons.customselector.ui.adapter
+
+import fr.free.nrw.commons.R
+import android.content.Context
+import android.net.Uri
+import android.view.LayoutInflater
+import android.view.View
+import android.widget.GridLayout
+import fr.free.nrw.commons.TestCommonsApplication
+import fr.free.nrw.commons.customselector.listeners.FolderClickListener
+import fr.free.nrw.commons.customselector.model.Folder
+import fr.free.nrw.commons.customselector.model.Image
+import fr.free.nrw.commons.customselector.ui.selector.CustomSelectorActivity
+import org.junit.Before
+import org.junit.Test
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.runner.RunWith
+import org.mockito.Mockito
+import org.robolectric.Robolectric
+import org.robolectric.RobolectricTestRunner
+import org.robolectric.annotation.Config
+
+/**
+ * Custom Selector Folder Adapter Test.
+ */
+@RunWith(RobolectricTestRunner::class)
+@Config(sdk = [21], application = TestCommonsApplication::class)
+class FolderAdapterTest {
+
+ private var uri: Uri = Mockito.mock(Uri::class.java)
+ private lateinit var activity: CustomSelectorActivity
+ private lateinit var folderAdapter: FolderAdapter
+ private lateinit var image: Image
+ private lateinit var folder: Folder
+ private lateinit var folderList: ArrayList
+
+ @Before
+ @Throws(Exception::class)
+ fun setUp() {
+ activity = Robolectric.buildActivity(CustomSelectorActivity::class.java).get()
+ image = Image(1, "image", uri, "abc/abc", 1, "bucket1")
+ folder = Folder(1, "bucket1", ArrayList(listOf(image)))
+ folderList = ArrayList(listOf(folder))
+ folderAdapter = FolderAdapter(activity, activity as FolderClickListener)
+ }
+
+ /**
+ * Test on create view holder.
+ */
+ @Test
+ fun onCreateViewHolder() {
+ folderAdapter.createViewHolder(GridLayout(activity), 0)
+ }
+
+ /**
+ * Test on bind view holder.
+ */
+ @Test
+ fun onBindViewHolder() {
+ folderAdapter.init(folderList)
+ val inflater = activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
+ val listItemView: View = inflater.inflate(R.layout.item_custom_selector_folder, null, false)
+ folderAdapter.onBindViewHolder(FolderAdapter.FolderViewHolder(listItemView), 0)
+ }
+
+ /**
+ * Test init.
+ */
+ @Test
+ fun init() {
+ folderAdapter.init(folderList)
+ }
+
+ /**
+ * Test get item count.
+ */
+ @Test
+ fun getItemCount() {
+ assertEquals(0, folderAdapter.itemCount)
+ }
+}
\ No newline at end of file
diff --git a/app/src/test/kotlin/fr/free/nrw/commons/customselector/ui/adapter/ImageAdapterTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/customselector/ui/adapter/ImageAdapterTest.kt
new file mode 100644
index 0000000000..8de08a2d32
--- /dev/null
+++ b/app/src/test/kotlin/fr/free/nrw/commons/customselector/ui/adapter/ImageAdapterTest.kt
@@ -0,0 +1,121 @@
+package fr.free.nrw.commons.customselector.ui.adapter
+
+import android.content.Context
+import android.view.LayoutInflater
+import android.view.View
+import android.widget.GridLayout
+import fr.free.nrw.commons.R
+import fr.free.nrw.commons.TestCommonsApplication
+import fr.free.nrw.commons.customselector.listeners.ImageSelectListener
+import fr.free.nrw.commons.customselector.model.Image
+import fr.free.nrw.commons.customselector.ui.selector.CustomSelectorActivity
+import fr.free.nrw.commons.customselector.ui.selector.ImageLoader
+import org.junit.Before
+import org.junit.Test
+import org.junit.jupiter.api.Assertions
+import org.junit.runner.RunWith
+import org.mockito.*
+import org.robolectric.Robolectric
+import org.robolectric.RobolectricTestRunner
+import org.robolectric.annotation.Config
+import java.lang.reflect.Field
+
+/**
+ * Custom Selector image adapter test.
+ */
+@RunWith(RobolectricTestRunner::class)
+@Config(sdk = [21], application = TestCommonsApplication::class)
+class ImageAdapterTest {
+ @Mock
+ private lateinit var image: Image
+ @Mock
+ private lateinit var imageLoader: ImageLoader
+ @Mock
+ private lateinit var imageSelectListener: ImageSelectListener
+
+ private lateinit var activity: CustomSelectorActivity
+ private lateinit var imageAdapter: ImageAdapter
+ private lateinit var images : ArrayList
+ private lateinit var holder: ImageAdapter.ImageViewHolder
+ private lateinit var selectedImageField: Field
+
+ /**
+ * Set up variables.
+ */
+ @Before
+ @Throws(Exception::class)
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ activity = Robolectric.buildActivity(CustomSelectorActivity::class.java).get()
+ imageAdapter = ImageAdapter(activity, imageSelectListener, imageLoader)
+ images = ArrayList()
+
+ val inflater = activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
+ val listItemView: View = inflater.inflate(R.layout.item_custom_selector_image, null, false)
+ holder = ImageAdapter.ImageViewHolder(listItemView)
+
+ selectedImageField = imageAdapter.javaClass.getDeclaredField("selectedImages")
+ selectedImageField.isAccessible = true
+ }
+
+ /**
+ * Test on create view holder.
+ */
+ @Test
+ fun onCreateViewHolder() {
+ imageAdapter.createViewHolder(GridLayout(activity), 0)
+ }
+
+ /**
+ * Test on bind view holder.
+ */
+ @Test
+ fun onBindViewHolder() {
+ // Parameters.
+ images.add(image)
+ imageAdapter.init(images)
+
+ // Test conditions.
+ imageAdapter.onBindViewHolder(holder, 0)
+ selectedImageField.set(imageAdapter, images)
+ imageAdapter.onBindViewHolder(holder, 0)
+ }
+
+ /**
+ * Test init.
+ */
+ @Test
+ fun init() {
+ imageAdapter.init(images)
+ }
+
+ /**
+ * Test private function select or remove image.
+ */
+ @Test
+ fun selectOrRemoveImage() {
+ // Access function
+ val func = imageAdapter.javaClass.getDeclaredMethod("selectOrRemoveImage", ImageAdapter.ImageViewHolder::class.java, Int::class.java)
+ func.isAccessible = true
+
+ // Parameters
+ images.addAll(listOf(image, image))
+ imageAdapter.init(images)
+
+ // Test conditions
+ holder.itemUploaded()
+ func.invoke(imageAdapter, holder, 0)
+ holder.itemNotUploaded()
+ func.invoke(imageAdapter, holder, 0)
+ selectedImageField.set(imageAdapter, images)
+ func.invoke(imageAdapter, holder, 1)
+ }
+
+ /**
+ * Test get item count.
+ */
+ @Test
+ fun getItemCount() {
+ Assertions.assertEquals(0, imageAdapter.itemCount)
+ }
+}
\ No newline at end of file
From 86ee96ccdd6f1abadab4cc858797238f10637c1c Mon Sep 17 00:00:00 2001
From: Aditya-Srivastav <54016427+4D17Y4@users.noreply.github.com>
Date: Wed, 14 Jul 2021 16:35:08 +0530
Subject: [PATCH 12/28] [GSoC] Master rebase. (#4505)
* 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 b1b4257f205b022ffaadee9f947357e5fc04c337.
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
* 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
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
Co-authored-by: neslihanturan
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
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 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
* Fixes #3694 Pre-select places as depictions (#4452)
* WikidataEditService: stop automatically adding WikidataPlace as a depiction
When the user initiates the upload process from Nearby and also manually adds the place as a depiction, the depiction is added twice. Since this behavior is invisible to the user, it is being removed in preparation for auto-selecting the place as a depiction on the DepictsFragment screen.
* DepictsFragment: auto-select place as a depiction
Pass the Place reference from UploadActivity to DepictsFragment and select the corresponding DepictedItem. Using the place id, retrieve the corresponding Entity to create and select a DepictedItem.
* UploadRepository: use Place from UploadItem to obtain a DepictedItem
Instead of passing a Place object from UploadActivity to DepictsFragment and then passing the Place object up the chain to obtain and select a DepictedItem, retrieve the Place object directly within UploadRepository
* DepictsFragment: select Place depiction when fragment becomes visible
* UploadDepictsAdapter: make adapter aware of selection state
Update selection state when recycled list items are automatically selected, preventing automatically selected items from appearing as unselected until they are forced to re-bind (i.e. after scrolling)
* DepictsFragment: pre-select place depictions for all UploadItems
If several images are selected and set to different places, pre-select all place depictions to reinforce the intended upload workflow philosophy (i.e. all images in a set are intended to be from/of the same place). See discussion in commons-app/apps-android-commons#3694
* DepictsFragment: scroll to the top every time list is updated
* Typo fixes (#4461)
* Fixed typo on class documentation of TextUtils
* corrected comma placement in documentation
* Fixed typos in comments
* fix-issue-4424 (#4445)
Co-authored-by: Pratham2305
* fix edit categories ui (#4414)
Co-authored-by: Pratham2305
* Fix doom version issue (#4463)
* Update db version
* DBOpenHelper version update
* fix :Back Pressed Event not work in Explore tab when user not login (#4404)
* fix :Back Pressed Event not work in Explore tab
* minor changes
* fix :Upload count or number of contribution does not get updated when media is successful uploaded (#4399)
* * fix:Number of Contributions not updated
* Add javadocs
* minor changes
* made minor changes
* String was nonsense and untranslatible, fixed (#4466)
* Ability to show captions and descriptions in all entered languages (#4355)
* implement Ability to show captions and descriptions in all entered languages
*Add Javadoc
* handle Back event of fragment(mediaDetailFragment)
* fix minor bugs
* add internationalization
* revert previous changes
* fix visibility bug
* resolve conflict
* Fixes #4437 - Changed indentation on files with 2 spaces to 4 spaces (#4462)
* Edited Project.xml to make indent size 4
* Changed files with 2 space indentation to use 4 space indentation
* Edited Project.xml to make indent size 4
* changed files with 2 space indent to 4 space indent
* fix :Back Pressed Event not work in Explore tab when user not login (#4404)
* fix :Back Pressed Event not work in Explore tab
* minor changes
* fix :Upload count or number of contribution does not get updated when media is successful uploaded (#4399)
* * fix:Number of Contributions not updated
* Add javadocs
* minor changes
* made minor changes
* String was nonsense and untranslatible, fixed (#4466)
* Ability to show captions and descriptions in all entered languages (#4355)
* implement Ability to show captions and descriptions in all entered languages
*Add Javadoc
* handle Back event of fragment(mediaDetailFragment)
* fix minor bugs
* add internationalization
* revert previous changes
* fix visibility bug
* resolve conflict
Co-authored-by: Prince kushwaha <65972015+Prince-kushwaha@users.noreply.github.com>
Co-authored-by: neslihanturan
* Use more understandable strings (#4470)
* Fix #3792 Missing Column Issue (#4468)
* Fix Missing Column Issue
* Fix tests
* Add UploadCategoriesFragment Unit Tests (#4473)
* Panorama (#4467)
* panoramic images fixed
* made requested changes
* Minor refactoring
Co-authored-by: Aditya Srivastava
* Localisation updates from https://translatewiki.net.
* Main activity title is sometimes "Contributions", sometimes "Commons" (#4472)
Fixes #4438 Replace == with equals() in onRestoreInstanceState
* Localisation updates from https://translatewiki.net.
* caption and description copyable (#4481)
* Removed next button in quiz (#4382)
* issues resolved
* modification done
* warning fixed
* issues resolved
* Button added
* don't know function added
* Button added
* modification done
* modification done
* Localisation updates from https://translatewiki.net.
* Added option to show and modify location while uploading (#4475)
* initial commit
* Everything done
* minor modification
* minor modification
* Issues fixed
* minor modifications
* issue fixed
* Issues fixed
* Tutorial removed from log out state (#4479)
* tutorial removed from log out state
* Issue removed
* Update changelog.md
* Versioning for v3.0.2
* Fix #4482 (#4484)
* Fix crash when image resolution is very high (#4483)
* Localisation updates from https://translatewiki.net.
* Add Contributions Fragment Unit Tests (#4490)
* Fix Tests Errors (#4491)
* Add UploadMediaDetailFragment Unit Tests (#4492)
* Localisation updates from https://translatewiki.net.
* Localisation updates from https://translatewiki.net.
* Localisation updates from https://translatewiki.net.
* 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
* [GSOC] Added Image Fetch (#4449)
* Added basic Fetch
* added permission request
* Folder count rectified
* Loaded thumbnail
* disabled overlay
* Added sha1 function
* Documented the code
* [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 b1b4257f205b022ffaadee9f947357e5fc04c337.
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
* 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
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
Co-authored-by: neslihanturan
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
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
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
Co-authored-by: Josephine Lim
Co-authored-by: Ashish
Co-authored-by: neslihanturan
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
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>
* [GSoC] Show uploaded images differently. (#4464)
* uploaded images shown differently
* Loaded images before query
* Handled exceptions, Made ImageLoader injectable, Document and clean code
* [GSoC] Added Uploaded status table in room database. (#4476)
* added Uploaded status table in room database
* Added unique property, minor refractoring
* Database intigrated
* Database integrated
* Handled result null exception
* Exceptions handled and refractored
* Introduced constants
* moved to sealed class
* No database insert on network error
* queried original image
* documented the code
* Updated uploaded status on upload success
* Image Helper test (#4485)
* [GSoC] Adapter Tests (#4488)
* Added FolderAdapterTest
* Image Adapter Test
* merge fix
* rebase fix
Co-authored-by: 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
Co-authored-by: Josephine Lim
Co-authored-by: Ashish
Co-authored-by: neslihanturan
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
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>
Co-authored-by: Brigham Byerly <6891883+byerlyb20@users.noreply.github.com>
Co-authored-by: Jamie Brown
Co-authored-by: Prince kushwaha <65972015+Prince-kushwaha@users.noreply.github.com>
Co-authored-by: Nicolas Raoul
Co-authored-by: Ashar
---
app/src/main/res/values/attrs.xml | 1 +
1 file changed, 1 insertion(+)
diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml
index f43772fb55..fb61e8d18a 100644
--- a/app/src/main/res/values/attrs.xml
+++ b/app/src/main/res/values/attrs.xml
@@ -51,6 +51,7 @@
+
From 732b343b89c29e7e47b5d3b0821fce40289d1b05 Mon Sep 17 00:00:00 2001
From: Aditya-Srivastav <54016427+4D17Y4@users.noreply.github.com>
Date: Sun, 18 Jul 2021 05:47:01 +0530
Subject: [PATCH 13/28] [GSoC] Custom Selector Tests (#4494)
* 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 b1b4257f205b022ffaadee9f947357e5fc04c337.
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
* 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
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
Co-authored-by: neslihanturan
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
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 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
* Fixes #3694 Pre-select places as depictions (#4452)
* WikidataEditService: stop automatically adding WikidataPlace as a depiction
When the user initiates the upload process from Nearby and also manually adds the place as a depiction, the depiction is added twice. Since this behavior is invisible to the user, it is being removed in preparation for auto-selecting the place as a depiction on the DepictsFragment screen.
* DepictsFragment: auto-select place as a depiction
Pass the Place reference from UploadActivity to DepictsFragment and select the corresponding DepictedItem. Using the place id, retrieve the corresponding Entity to create and select a DepictedItem.
* UploadRepository: use Place from UploadItem to obtain a DepictedItem
Instead of passing a Place object from UploadActivity to DepictsFragment and then passing the Place object up the chain to obtain and select a DepictedItem, retrieve the Place object directly within UploadRepository
* DepictsFragment: select Place depiction when fragment becomes visible
* UploadDepictsAdapter: make adapter aware of selection state
Update selection state when recycled list items are automatically selected, preventing automatically selected items from appearing as unselected until they are forced to re-bind (i.e. after scrolling)
* DepictsFragment: pre-select place depictions for all UploadItems
If several images are selected and set to different places, pre-select all place depictions to reinforce the intended upload workflow philosophy (i.e. all images in a set are intended to be from/of the same place). See discussion in commons-app/apps-android-commons#3694
* DepictsFragment: scroll to the top every time list is updated
* Typo fixes (#4461)
* Fixed typo on class documentation of TextUtils
* corrected comma placement in documentation
* Fixed typos in comments
* fix-issue-4424 (#4445)
Co-authored-by: Pratham2305
* fix edit categories ui (#4414)
Co-authored-by: Pratham2305
* Fix doom version issue (#4463)
* Update db version
* DBOpenHelper version update
* fix :Back Pressed Event not work in Explore tab when user not login (#4404)
* fix :Back Pressed Event not work in Explore tab
* minor changes
* fix :Upload count or number of contribution does not get updated when media is successful uploaded (#4399)
* * fix:Number of Contributions not updated
* Add javadocs
* minor changes
* made minor changes
* String was nonsense and untranslatible, fixed (#4466)
* Ability to show captions and descriptions in all entered languages (#4355)
* implement Ability to show captions and descriptions in all entered languages
*Add Javadoc
* handle Back event of fragment(mediaDetailFragment)
* fix minor bugs
* add internationalization
* revert previous changes
* fix visibility bug
* resolve conflict
* Fixes #4437 - Changed indentation on files with 2 spaces to 4 spaces (#4462)
* Edited Project.xml to make indent size 4
* Changed files with 2 space indentation to use 4 space indentation
* Edited Project.xml to make indent size 4
* changed files with 2 space indent to 4 space indent
* fix :Back Pressed Event not work in Explore tab when user not login (#4404)
* fix :Back Pressed Event not work in Explore tab
* minor changes
* fix :Upload count or number of contribution does not get updated when media is successful uploaded (#4399)
* * fix:Number of Contributions not updated
* Add javadocs
* minor changes
* made minor changes
* String was nonsense and untranslatible, fixed (#4466)
* Ability to show captions and descriptions in all entered languages (#4355)
* implement Ability to show captions and descriptions in all entered languages
*Add Javadoc
* handle Back event of fragment(mediaDetailFragment)
* fix minor bugs
* add internationalization
* revert previous changes
* fix visibility bug
* resolve conflict
Co-authored-by: Prince kushwaha <65972015+Prince-kushwaha@users.noreply.github.com>
Co-authored-by: neslihanturan
* Use more understandable strings (#4470)
* Fix #3792 Missing Column Issue (#4468)
* Fix Missing Column Issue
* Fix tests
* Add UploadCategoriesFragment Unit Tests (#4473)
* Panorama (#4467)
* panoramic images fixed
* made requested changes
* Minor refactoring
Co-authored-by: Aditya Srivastava
* Localisation updates from https://translatewiki.net.
* Main activity title is sometimes "Contributions", sometimes "Commons" (#4472)
Fixes #4438 Replace == with equals() in onRestoreInstanceState
* Localisation updates from https://translatewiki.net.
* caption and description copyable (#4481)
* Removed next button in quiz (#4382)
* issues resolved
* modification done
* warning fixed
* issues resolved
* Button added
* don't know function added
* Button added
* modification done
* modification done
* Localisation updates from https://translatewiki.net.
* Added option to show and modify location while uploading (#4475)
* initial commit
* Everything done
* minor modification
* minor modification
* Issues fixed
* minor modifications
* issue fixed
* Issues fixed
* Tutorial removed from log out state (#4479)
* tutorial removed from log out state
* Issue removed
* Update changelog.md
* Versioning for v3.0.2
* Fix #4482 (#4484)
* Fix crash when image resolution is very high (#4483)
* Localisation updates from https://translatewiki.net.
* Add Contributions Fragment Unit Tests (#4490)
* Fix Tests Errors (#4491)
* Add UploadMediaDetailFragment Unit Tests (#4492)
* Localisation updates from https://translatewiki.net.
* Folder Fragment test
* Folder Fragment test done
* 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
* [GSOC] Added Image Fetch (#4449)
* Added basic Fetch
* added permission request
* Folder count rectified
* Loaded thumbnail
* disabled overlay
* Added sha1 function
* Documented the code
* [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 b1b4257f205b022ffaadee9f947357e5fc04c337.
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
* 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
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
Co-authored-by: neslihanturan
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
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
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
Co-authored-by: Josephine Lim
Co-authored-by: Ashish
Co-authored-by: neslihanturan
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
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>
* [GSoC] Show uploaded images differently. (#4464)
* uploaded images shown differently
* Loaded images before query
* Handled exceptions, Made ImageLoader injectable, Document and clean code
* [GSoC] Added Uploaded status table in room database. (#4476)
* added Uploaded status table in room database
* Added unique property, minor refractoring
* Database intigrated
* Database integrated
* Handled result null exception
* Exceptions handled and refractored
* Introduced constants
* moved to sealed class
* No database insert on network error
* queried original image
* documented the code
* Updated uploaded status on upload success
* Image Helper test (#4485)
* [GSoC] Adapter Tests (#4488)
* Added FolderAdapterTest
* Image Adapter Test
* Folder Fragment test
* Folder Fragment test done
* Fragment test complete
* Added Custom Selector View Model Test
* ImageFileLoaderTest
* Update strings.xml
* Custom Selector Activiy test
* Image Loader Test
Co-authored-by: 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
Co-authored-by: Josephine Lim
Co-authored-by: Ashish
Co-authored-by: neslihanturan
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
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>
Co-authored-by: Brigham Byerly <6891883+byerlyb20@users.noreply.github.com>
Co-authored-by: Jamie Brown
Co-authored-by: Prince kushwaha <65972015+Prince-kushwaha@users.noreply.github.com>
Co-authored-by: Nicolas Raoul
Co-authored-by: Ashar
---
.../ui/selector/FolderFragment.kt | 19 +-
.../ui/selector/ImageFragment.kt | 27 ++-
.../customselector/ui/selector/ImageLoader.kt | 6 +-
.../explore/ExploreListRootFragment.java | 2 +-
.../ui/selector/CustomSelectorActivityTest.kt | 96 ++++++++
.../selector/CustomSelectorViewModelTest.kt | 41 ++++
.../ui/selector/FolderFragmentTest.kt | 130 +++++++++++
.../ui/selector/ImageFileLoaderTest.kt | 123 ++++++++++
.../ui/selector/ImageFragmentTest.kt | 135 +++++++++++
.../ui/selector/ImageLoaderTest.kt | 218 ++++++++++++++++++
app/src/test/resources/imageLoaderTestFile | 0
11 files changed, 786 insertions(+), 11 deletions(-)
create mode 100644 app/src/test/kotlin/fr/free/nrw/commons/customselector/ui/selector/CustomSelectorActivityTest.kt
create mode 100644 app/src/test/kotlin/fr/free/nrw/commons/customselector/ui/selector/CustomSelectorViewModelTest.kt
create mode 100644 app/src/test/kotlin/fr/free/nrw/commons/customselector/ui/selector/FolderFragmentTest.kt
create mode 100644 app/src/test/kotlin/fr/free/nrw/commons/customselector/ui/selector/ImageFileLoaderTest.kt
create mode 100644 app/src/test/kotlin/fr/free/nrw/commons/customselector/ui/selector/ImageFragmentTest.kt
create mode 100644 app/src/test/kotlin/fr/free/nrw/commons/customselector/ui/selector/ImageLoaderTest.kt
create mode 100644 app/src/test/resources/imageLoaderTestFile
diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/FolderFragment.kt b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/FolderFragment.kt
index 1d5901c9d0..e43c0798cc 100644
--- a/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/FolderFragment.kt
+++ b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/FolderFragment.kt
@@ -4,9 +4,11 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import android.widget.ProgressBar
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.GridLayoutManager
+import androidx.recyclerview.widget.RecyclerView
import fr.free.nrw.commons.R
import fr.free.nrw.commons.customselector.helper.ImageHelper
import fr.free.nrw.commons.customselector.model.Result
@@ -16,7 +18,6 @@ import fr.free.nrw.commons.customselector.ui.adapter.FolderAdapter
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.view.*
import javax.inject.Inject
@@ -27,6 +28,12 @@ class FolderFragment : CommonsDaggerSupportFragment() {
*/
private var viewModel: CustomSelectorViewModel? = null
+ /**
+ * View Elements
+ */
+ private var selectorRV: RecyclerView? = null
+ private var loader: ProgressBar? = null
+
/**
* View Model Factory.
*/
@@ -75,6 +82,8 @@ class FolderFragment : CommonsDaggerSupportFragment() {
val root = inflater.inflate(R.layout.fragment_custom_selector, container, false)
folderAdapter = FolderAdapter(activity!!, activity as FolderClickListener)
gridLayoutManager = GridLayoutManager(context, columnCount())
+ selectorRV = root.selector_rv
+ loader = root.loader
with(root.selector_rv){
this.layoutManager = gridLayoutManager
setHasFixedSize(true)
@@ -96,9 +105,13 @@ class FolderFragment : CommonsDaggerSupportFragment() {
val folders = ImageHelper.folderListFromImages(result.images)
folderAdapter.init(folders)
folderAdapter.notifyDataSetChanged()
- selector_rv.visibility = View.VISIBLE
+ selectorRV?.let {
+ it.visibility = View.VISIBLE
+ }
+ }
+ loader?.let {
+ it.visibility = if (result.status is CallbackStatus.FETCHING) View.VISIBLE else View.GONE
}
- loader.visibility = if (result.status is CallbackStatus.FETCHING) View.VISIBLE else View.GONE
}
/**
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 a2de0ed294..f1583c54fc 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
@@ -4,9 +4,11 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import android.widget.ProgressBar
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.GridLayoutManager
+import androidx.recyclerview.widget.RecyclerView
import fr.free.nrw.commons.R
import fr.free.nrw.commons.customselector.helper.ImageHelper
import fr.free.nrw.commons.customselector.listeners.ImageSelectListener
@@ -28,7 +30,13 @@ class ImageFragment: CommonsDaggerSupportFragment() {
/**
* View model for images.
*/
- private lateinit var viewModel: CustomSelectorViewModel
+ private var viewModel: CustomSelectorViewModel? = null
+
+ /**
+ * View Elements
+ */
+ private var selectorRV: RecyclerView? = null
+ private var loader: ProgressBar? = null
/**
* View model Factory.
@@ -98,10 +106,13 @@ class ImageFragment: CommonsDaggerSupportFragment() {
this.adapter = imageAdapter
}
- viewModel.result.observe(viewLifecycleOwner, Observer{
+ viewModel?.result?.observe(viewLifecycleOwner, Observer{
handleResult(it)
})
+ selectorRV = root.selector_rv
+ loader = root.loader
+
return root
}
@@ -113,13 +124,19 @@ class ImageFragment: CommonsDaggerSupportFragment() {
val images = result.images
if(images.isNotEmpty()) {
imageAdapter.init(ImageHelper.filterImages(images,bucketId))
- selector_rv.visibility = View.VISIBLE
+ selectorRV?.let{
+ it.visibility = View.VISIBLE
+ }
}
else{
- selector_rv.visibility = View.GONE
+ selectorRV?.let{
+ it.visibility = View.GONE
+ }
}
}
- loader.visibility = if (result.status is CallbackStatus.FETCHING) View.VISIBLE else View.GONE
+ loader?.let {
+ it.visibility = if (result.status is CallbackStatus.FETCHING) View.VISIBLE else View.GONE
+ }
}
/**
diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/ImageLoader.kt b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/ImageLoader.kt
index f2d4d5709f..5680cc7753 100644
--- a/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/ImageLoader.kt
+++ b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/ImageLoader.kt
@@ -19,6 +19,7 @@ import timber.log.Timber
import java.io.IOException
import java.net.UnknownHostException
import java.util.*
+import java.util.concurrent.TimeUnit
import javax.inject.Inject
import kotlin.collections.HashMap
@@ -179,7 +180,8 @@ class ImageLoader @Inject constructor(
return Result.TRUE
} else {
uploadedStatus.lastUpdated?.let {
- if (it.date >= Calendar.getInstance().time.date - INVALIDATE_DAY_COUNT) {
+ val duration = Calendar.getInstance().time.time - it.time
+ if (TimeUnit.MILLISECONDS.toDays(duration) < INVALIDATE_DAY_COUNT) {
return Result.FALSE
}
}
@@ -218,7 +220,7 @@ class ImageLoader @Inject constructor(
}
companion object {
- const val INVALIDATE_DAY_COUNT: Int = 7
+ const val INVALIDATE_DAY_COUNT: Long = 7
}
}
\ No newline at end of file
diff --git a/app/src/main/java/fr/free/nrw/commons/explore/ExploreListRootFragment.java b/app/src/main/java/fr/free/nrw/commons/explore/ExploreListRootFragment.java
index 32b38fea7e..e88f14b558 100644
--- a/app/src/main/java/fr/free/nrw/commons/explore/ExploreListRootFragment.java
+++ b/app/src/main/java/fr/free/nrw/commons/explore/ExploreListRootFragment.java
@@ -40,7 +40,7 @@ public ExploreListRootFragment(Bundle bundle) {
featuredArguments.putString("categoryName", title);
listFragment.setArguments(featuredArguments);
}
-
+
@Nullable
@Override
public View onCreateView(@NonNull final LayoutInflater inflater,
diff --git a/app/src/test/kotlin/fr/free/nrw/commons/customselector/ui/selector/CustomSelectorActivityTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/customselector/ui/selector/CustomSelectorActivityTest.kt
new file mode 100644
index 0000000000..6d55a49e23
--- /dev/null
+++ b/app/src/test/kotlin/fr/free/nrw/commons/customselector/ui/selector/CustomSelectorActivityTest.kt
@@ -0,0 +1,96 @@
+package fr.free.nrw.commons.customselector.ui.selector
+
+import android.net.Uri
+import android.os.Bundle
+import fr.free.nrw.commons.TestCommonsApplication
+import fr.free.nrw.commons.customselector.model.Folder
+import fr.free.nrw.commons.customselector.model.Image
+import org.junit.Before
+import org.junit.Test
+import org.junit.jupiter.api.Assertions.assertNotNull
+import org.junit.runner.RunWith
+import org.mockito.MockitoAnnotations
+import org.robolectric.Robolectric
+import org.robolectric.RobolectricTestRunner
+import org.robolectric.annotation.Config
+
+/**
+ * Custom Selector Activity Test
+ */
+@RunWith(RobolectricTestRunner::class)
+@Config(sdk = [21], application = TestCommonsApplication::class)
+class CustomSelectorActivityTest {
+
+ private lateinit var activity: CustomSelectorActivity
+
+ /**
+ * Set up the tests.
+ */
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ activity = Robolectric.buildActivity(CustomSelectorActivity::class.java)
+ .get()
+ val onCreate = activity.javaClass.getDeclaredMethod("onCreate", Bundle::class.java)
+ onCreate.isAccessible = true
+ onCreate.invoke(activity, null)
+ }
+
+ /**
+ * Test activity not null.
+ */
+ @Test
+ @Throws(Exception::class)
+ fun testActivityNotNull() {
+ assertNotNull(activity)
+ }
+
+ /**
+ * Test changeTitle function.
+ */
+ @Test
+ @Throws(Exception::class)
+ fun testChangeTitle() {
+ val func = activity.javaClass.getDeclaredMethod("changeTitle", String::class.java)
+ func.isAccessible = true
+ func.invoke(activity, "test")
+ }
+
+ /**
+ * Test onFolderClick function.
+ */
+ @Test
+ @Throws(Exception::class)
+ fun testOnFolderClick() {
+ activity.onFolderClick(Folder(1, "test", arrayListOf()));
+ }
+
+ /**
+ * Test selectedImagesChanged function.
+ */
+ @Test
+ @Throws(Exception::class)
+ fun testOnSelectedImagesChanged() {
+ activity.onSelectedImagesChanged(ArrayList())
+ }
+
+ /**
+ * Test onDone function.
+ */
+ @Test
+ @Throws(Exception::class)
+ fun testOnDone() {
+ activity.onDone()
+ activity.onSelectedImagesChanged(ArrayList(arrayListOf(Image(1, "test", Uri.parse("test"), "test", 1))));
+ activity.onDone()
+ }
+
+ /**
+ * Test onBackPressed Function.
+ */
+ @Test
+ @Throws(Exception::class)
+ fun testOnBackPressed() {
+ activity.onBackPressed()
+ }
+}
\ No newline at end of file
diff --git a/app/src/test/kotlin/fr/free/nrw/commons/customselector/ui/selector/CustomSelectorViewModelTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/customselector/ui/selector/CustomSelectorViewModelTest.kt
new file mode 100644
index 0000000000..309392d4dc
--- /dev/null
+++ b/app/src/test/kotlin/fr/free/nrw/commons/customselector/ui/selector/CustomSelectorViewModelTest.kt
@@ -0,0 +1,41 @@
+package fr.free.nrw.commons.customselector.ui.selector
+
+import android.content.Context
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+/**
+ * Custom Selector View Model test.
+ */
+class CustomSelectorViewModelTest {
+
+ private lateinit var viewModel: CustomSelectorViewModel
+
+ @Mock
+ private lateinit var imageFileLoader: ImageFileLoader
+
+ @Mock
+ private lateinit var context: Context
+
+ /**
+ * Set up the test.
+ */
+ @Before
+ fun setUp(){
+ MockitoAnnotations.initMocks(this)
+ viewModel = CustomSelectorViewModel(context, imageFileLoader);
+ }
+
+ /**
+ * Test onCleared();
+ */
+ @Test
+ fun testOnCleared(){
+ val func = viewModel.javaClass.getDeclaredMethod("onCleared")
+ func.isAccessible = true
+ func.invoke(viewModel);
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/test/kotlin/fr/free/nrw/commons/customselector/ui/selector/FolderFragmentTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/customselector/ui/selector/FolderFragmentTest.kt
new file mode 100644
index 0000000000..53094d6f7b
--- /dev/null
+++ b/app/src/test/kotlin/fr/free/nrw/commons/customselector/ui/selector/FolderFragmentTest.kt
@@ -0,0 +1,130 @@
+package fr.free.nrw.commons.customselector.ui.selector
+
+import android.content.Context
+import android.os.Bundle
+import android.os.Looper
+import android.view.LayoutInflater
+import android.view.View
+import fr.free.nrw.commons.customselector.model.Result
+import android.widget.ProgressBar
+import androidx.fragment.app.FragmentManager
+import androidx.fragment.app.FragmentTransaction
+import androidx.recyclerview.widget.RecyclerView
+import com.facebook.drawee.backends.pipeline.Fresco
+import com.facebook.soloader.SoLoader
+import fr.free.nrw.commons.R
+import fr.free.nrw.commons.TestAppAdapter
+import fr.free.nrw.commons.TestCommonsApplication
+import fr.free.nrw.commons.customselector.model.CallbackStatus
+import fr.free.nrw.commons.customselector.ui.adapter.FolderAdapter
+import org.junit.Before
+import org.junit.Test
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+import org.powermock.reflect.Whitebox
+import org.robolectric.Robolectric
+import org.robolectric.RobolectricTestRunner
+import org.robolectric.RuntimeEnvironment
+import org.robolectric.Shadows
+import org.robolectric.annotation.Config
+import org.robolectric.annotation.LooperMode
+import org.wikipedia.AppAdapter
+import java.lang.reflect.Field
+
+/**
+ * Custom Selector Folder Fragment Test.
+ */
+@RunWith(RobolectricTestRunner::class)
+@Config(sdk = [21], application = TestCommonsApplication::class)
+@LooperMode(LooperMode.Mode.PAUSED)
+class FolderFragmentTest {
+
+ private lateinit var fragment: FolderFragment
+ private lateinit var view: View
+ private lateinit var selectorRV : RecyclerView
+ private lateinit var loader : ProgressBar
+ private lateinit var layoutInflater: LayoutInflater
+ private lateinit var context: Context
+ private lateinit var viewModelField:Field
+
+ @Mock
+ private lateinit var adapter: FolderAdapter
+
+ @Mock
+ private lateinit var savedInstanceState: Bundle
+
+ /**
+ * Setup the folder fragment.
+ */
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ context = RuntimeEnvironment.application.applicationContext
+ AppAdapter.set(TestAppAdapter())
+ SoLoader.setInTestMode()
+ Fresco.initialize(context)
+ val activity = Robolectric.buildActivity(CustomSelectorActivity::class.java).create().get()
+
+ fragment = FolderFragment.newInstance()
+ val fragmentManager: FragmentManager = activity.supportFragmentManager
+ val fragmentTransaction: FragmentTransaction = fragmentManager.beginTransaction()
+ fragmentTransaction.add(fragment, null)
+ fragmentTransaction.commit()
+
+ layoutInflater = LayoutInflater.from(activity)
+ view = layoutInflater.inflate(R.layout.fragment_custom_selector, null) as View
+
+ selectorRV = view.findViewById(R.id.selector_rv)
+ loader = view.findViewById(R.id.loader)
+
+ Whitebox.setInternalState(fragment, "folderAdapter", adapter)
+ Whitebox.setInternalState(fragment, "selectorRV", selectorRV )
+ Whitebox.setInternalState(fragment, "loader", loader)
+
+ viewModelField = fragment.javaClass.getDeclaredField("viewModel")
+ viewModelField.isAccessible = true
+ }
+
+ /**
+ * Test onCreateView
+ */
+ @Test
+ @Throws(Exception::class)
+ fun testOnCreateView() {
+ Shadows.shadowOf(Looper.getMainLooper()).idle()
+ viewModelField.set(fragment, null)
+ fragment.onCreateView(layoutInflater, null, savedInstanceState)
+ }
+
+ /**
+ * Test onCreate
+ */
+ @Test
+ @Throws(Exception::class)
+ fun testOnCreate() {
+ Shadows.shadowOf(Looper.getMainLooper()).idle()
+ fragment.onCreate(savedInstanceState)
+ }
+
+ /**
+ * Test columnCount.
+ */
+ @Test
+ fun testColumnCount() {
+ val func = fragment.javaClass.getDeclaredMethod("columnCount")
+ func.isAccessible = true
+ assertEquals(2, func.invoke(fragment))
+ }
+
+ /**
+ * Test handleResult.
+ */
+ @Test
+ fun testHandleResult() {
+ val func = fragment.javaClass.getDeclaredMethod("handleResult", Result::class.java)
+ func.isAccessible = true
+ func.invoke(fragment, Result(CallbackStatus.SUCCESS, arrayListOf()))
+ }
+}
\ No newline at end of file
diff --git a/app/src/test/kotlin/fr/free/nrw/commons/customselector/ui/selector/ImageFileLoaderTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/customselector/ui/selector/ImageFileLoaderTest.kt
new file mode 100644
index 0000000000..e30d47216c
--- /dev/null
+++ b/app/src/test/kotlin/fr/free/nrw/commons/customselector/ui/selector/ImageFileLoaderTest.kt
@@ -0,0 +1,123 @@
+package fr.free.nrw.commons.customselector.ui.selector
+
+import android.content.ContentResolver
+import android.content.Context
+import android.provider.MediaStore
+import com.nhaarman.mockitokotlin2.anyOrNull
+import com.nhaarman.mockitokotlin2.doReturn
+import com.nhaarman.mockitokotlin2.mock
+import com.nhaarman.mockitokotlin2.same
+import fr.free.nrw.commons.TestCommonsApplication
+import fr.free.nrw.commons.customselector.listeners.ImageLoaderListener
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+import org.powermock.reflect.Whitebox
+import org.robolectric.RobolectricTestRunner
+import org.robolectric.annotation.Config
+import org.robolectric.annotation.LooperMode
+import org.robolectric.fakes.RoboCursor
+import java.io.File
+import kotlin.coroutines.CoroutineContext
+
+/**
+ * Custom Selector Image File loader test.
+ */
+@RunWith(RobolectricTestRunner::class)
+@Config(sdk = [21], application = TestCommonsApplication::class)
+@LooperMode(LooperMode.Mode.PAUSED)
+class ImageFileLoaderTest {
+
+ @Mock
+ private lateinit var mockContentResolver: ContentResolver
+
+ @Mock
+ private lateinit var context: Context;
+
+ @Mock
+ private lateinit var imageLoaderListener: ImageLoaderListener
+
+ @Mock
+ private lateinit var coroutineScope: CoroutineScope
+
+ private lateinit var imageCursor: RoboCursor
+ private lateinit var coroutineContext: CoroutineContext
+ private lateinit var projection: List
+ private lateinit var imageFileLoader: ImageFileLoader
+
+ /**
+ * Setup before tests.
+ */
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ coroutineContext = Dispatchers.Main
+ imageCursor = RoboCursor()
+ imageFileLoader = ImageFileLoader(context)
+ projection = listOf(
+ MediaStore.Images.Media._ID,
+ MediaStore.Images.Media.DISPLAY_NAME,
+ MediaStore.Images.Media.DATA,
+ MediaStore.Images.Media.BUCKET_ID,
+ MediaStore.Images.Media.BUCKET_DISPLAY_NAME
+ )
+
+ Whitebox.setInternalState(imageFileLoader, "coroutineContext", coroutineContext)
+ }
+
+ /**
+ * Test loading device images.
+ */
+ @Test
+ fun testLoadDeviceImages() {
+ imageFileLoader.loadDeviceImages(imageLoaderListener, coroutineScope)
+ }
+
+ /**
+ * Test get images from the device function.
+ */
+ @Test
+ fun testGetImages() {
+ val func = imageFileLoader.javaClass.getDeclaredMethod(
+ "getImages",
+ ImageLoaderListener::class.java
+ )
+ func.isAccessible = true
+
+ val image1 = arrayOf(1, "imageLoaderTestFile", "src/test/resources/imageLoaderTestFile", 1, "downloads")
+ val image2 = arrayOf(2, "imageLoaderTestFile", null, 1, "downloads")
+ File("src/test/resources/imageLoaderTestFile").createNewFile()
+
+ imageCursor.setColumnNames(projection)
+ imageCursor.setResults(arrayOf(image1, image2));
+
+ val contentResolver: ContentResolver = mock {
+ on {
+ query(
+ same(MediaStore.Images.Media.EXTERNAL_CONTENT_URI),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull()
+ )
+ } doReturn imageCursor;
+ }
+
+ // test null cursor.
+ `when`(
+ context.contentResolver
+ ).thenReturn(mockContentResolver)
+ func.invoke(imageFileLoader, imageLoaderListener);
+
+ // test demo cursor.
+ `when`(
+ context.contentResolver
+ ).thenReturn(contentResolver)
+ func.invoke(imageFileLoader, imageLoaderListener);
+ }
+}
\ No newline at end of file
diff --git a/app/src/test/kotlin/fr/free/nrw/commons/customselector/ui/selector/ImageFragmentTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/customselector/ui/selector/ImageFragmentTest.kt
new file mode 100644
index 0000000000..9794003c82
--- /dev/null
+++ b/app/src/test/kotlin/fr/free/nrw/commons/customselector/ui/selector/ImageFragmentTest.kt
@@ -0,0 +1,135 @@
+package fr.free.nrw.commons.customselector.ui.selector
+
+import android.content.Context
+import android.os.Bundle
+import android.os.Looper
+import android.view.LayoutInflater
+import android.view.View
+import android.widget.ProgressBar
+import androidx.fragment.app.FragmentManager
+import androidx.fragment.app.FragmentTransaction
+import androidx.recyclerview.widget.RecyclerView
+import com.facebook.drawee.backends.pipeline.Fresco
+import com.facebook.soloader.SoLoader
+import fr.free.nrw.commons.R
+import fr.free.nrw.commons.TestAppAdapter
+import fr.free.nrw.commons.TestCommonsApplication
+import fr.free.nrw.commons.customselector.model.CallbackStatus
+import fr.free.nrw.commons.customselector.model.Image
+import fr.free.nrw.commons.customselector.model.Result
+import fr.free.nrw.commons.customselector.ui.adapter.ImageAdapter
+import org.junit.Before
+import org.junit.Test
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+import org.powermock.reflect.Whitebox
+import org.robolectric.Robolectric
+import org.robolectric.RobolectricTestRunner
+import org.robolectric.RuntimeEnvironment
+import org.robolectric.Shadows
+import org.robolectric.annotation.Config
+import org.robolectric.annotation.LooperMode
+import org.wikipedia.AppAdapter
+import java.lang.reflect.Field
+
+/**
+ * Custom Selector Image Fragment Test.
+ */
+@RunWith(RobolectricTestRunner::class)
+@Config(sdk = [21], application = TestCommonsApplication::class)
+@LooperMode(LooperMode.Mode.PAUSED)
+class ImageFragmentTest {
+
+ private lateinit var fragment: ImageFragment
+ private lateinit var view: View
+ private lateinit var selectorRV : RecyclerView
+ private lateinit var loader : ProgressBar
+ private lateinit var layoutInflater: LayoutInflater
+ private lateinit var context: Context
+ private lateinit var viewModelField: Field
+
+ @Mock
+ private lateinit var image: Image
+
+ @Mock
+ private lateinit var adapter: ImageAdapter
+
+ @Mock
+ private lateinit var savedInstanceState: Bundle
+
+ /**
+ * Setup the image fragment.
+ */
+ @Before
+ fun setUp(){
+ MockitoAnnotations.initMocks(this)
+ context = RuntimeEnvironment.application.applicationContext
+ AppAdapter.set(TestAppAdapter())
+ SoLoader.setInTestMode()
+ Fresco.initialize(context)
+ val activity = Robolectric.buildActivity(CustomSelectorActivity::class.java).create().get()
+
+ fragment = ImageFragment.newInstance(1)
+ val fragmentManager: FragmentManager = activity.supportFragmentManager
+ val fragmentTransaction: FragmentTransaction = fragmentManager.beginTransaction()
+ fragmentTransaction.add(fragment, null)
+ fragmentTransaction.commit()
+
+ layoutInflater = LayoutInflater.from(activity)
+ view = layoutInflater.inflate(R.layout.fragment_custom_selector, null, false) as View
+ selectorRV = view.findViewById(R.id.selector_rv)
+ loader = view.findViewById(R.id.loader)
+
+ Whitebox.setInternalState(fragment, "imageAdapter", adapter)
+ Whitebox.setInternalState(fragment, "selectorRV", selectorRV )
+ Whitebox.setInternalState(fragment, "loader", loader)
+
+ viewModelField = fragment.javaClass.getDeclaredField("viewModel")
+ viewModelField.isAccessible = true
+ }
+
+ /**
+ * Test onCreate
+ */
+ @Test
+ @Throws(Exception::class)
+ fun testOnCreate(){
+ Shadows.shadowOf(Looper.getMainLooper()).idle()
+ fragment.onCreate(savedInstanceState);
+ }
+
+ /**
+ * Test onCreateView
+ */
+ @Test
+ @Throws(Exception::class)
+ fun testOnCreateView() {
+ Shadows.shadowOf(Looper.getMainLooper()).idle()
+ viewModelField.set(fragment, null)
+ fragment.onCreateView(layoutInflater, null, savedInstanceState)
+ }
+
+ /**
+ * Test handleResult.
+ */
+ @Test
+ fun testHandleResult(){
+ val func = fragment.javaClass.getDeclaredMethod("handleResult", Result::class.java)
+ func.isAccessible = true
+ func.invoke(fragment, Result(CallbackStatus.SUCCESS, arrayListOf()))
+ func.invoke(fragment, Result(CallbackStatus.SUCCESS, arrayListOf(image,image)))
+ }
+
+ /**
+ * Test getSpanCount.
+ */
+ @Test
+ fun testGetSpanCount() {
+ val func = fragment.javaClass.getDeclaredMethod("getSpanCount")
+ func.isAccessible = true
+ assertEquals(3, func.invoke(fragment))
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/test/kotlin/fr/free/nrw/commons/customselector/ui/selector/ImageLoaderTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/customselector/ui/selector/ImageLoaderTest.kt
new file mode 100644
index 0000000000..cb7cf3a501
--- /dev/null
+++ b/app/src/test/kotlin/fr/free/nrw/commons/customselector/ui/selector/ImageLoaderTest.kt
@@ -0,0 +1,218 @@
+package fr.free.nrw.commons.customselector.ui.selector
+
+import android.content.ContentResolver
+import android.content.Context
+import android.net.Uri
+import com.nhaarman.mockitokotlin2.any
+import com.nhaarman.mockitokotlin2.whenever
+import fr.free.nrw.commons.TestCommonsApplication
+import fr.free.nrw.commons.customselector.database.UploadedStatus
+import fr.free.nrw.commons.customselector.database.UploadedStatusDao
+import fr.free.nrw.commons.customselector.model.Image
+import fr.free.nrw.commons.customselector.ui.adapter.ImageAdapter
+import fr.free.nrw.commons.filepicker.PickedFiles
+import fr.free.nrw.commons.filepicker.UploadableFile
+import fr.free.nrw.commons.media.MediaClient
+import fr.free.nrw.commons.upload.FileProcessor
+import fr.free.nrw.commons.upload.FileUtilsWrapper
+import io.reactivex.Single
+import junit.framework.Assert
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.*
+import org.powermock.api.mockito.PowerMockito
+import org.powermock.core.classloader.annotations.PrepareForTest
+import org.powermock.modules.junit4.PowerMockRunner
+import org.powermock.reflect.Whitebox
+import org.robolectric.annotation.Config
+import java.io.File
+import java.io.FileInputStream
+import java.lang.Exception
+import java.util.*
+import kotlin.collections.HashMap
+
+/**
+ * Image Loader Test.
+ */
+@RunWith(PowerMockRunner::class)
+@PrepareForTest(PickedFiles::class)
+@Config(sdk = [21], application = TestCommonsApplication::class)
+class ImageLoaderTest {
+
+ @Mock
+ private lateinit var uri:Uri
+
+ @Mock
+ private lateinit var mediaClient: MediaClient
+
+ @Mock
+ private lateinit var single: Single
+
+ @Mock
+ private lateinit var fileProcessor: FileProcessor
+
+ @Mock
+ private lateinit var fileUtilsWrapper: FileUtilsWrapper
+
+ @Mock
+ private lateinit var uploadedStatusDao: UploadedStatusDao
+
+ @Mock
+ private lateinit var holder: ImageAdapter.ImageViewHolder
+
+ @Mock
+ private lateinit var context: Context
+
+ @Mock
+ private lateinit var uploadableFile: UploadableFile
+
+ @Mock
+ private lateinit var inputStream: FileInputStream
+
+ @Mock
+ private lateinit var contentResolver: ContentResolver
+
+ @Mock
+ private lateinit var image: Image;
+
+ private lateinit var imageLoader: ImageLoader;
+ private var mapImageSHA1: HashMap = HashMap()
+ private var mapHolderImage : HashMap = HashMap()
+ private var mapResult: HashMap = HashMap()
+
+ /**
+ * Setup before test.
+ */
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ imageLoader =
+ ImageLoader(mediaClient, fileProcessor, fileUtilsWrapper, uploadedStatusDao, context)
+
+ Whitebox.setInternalState(imageLoader, "mapImageSHA1", mapImageSHA1);
+ Whitebox.setInternalState(imageLoader, "mapHolderImage", mapHolderImage);
+ Whitebox.setInternalState(imageLoader, "mapResult", mapResult);
+ Whitebox.setInternalState(imageLoader, "context", context)
+ }
+
+ /**
+ * Test queryAndSetView.
+ */
+ @Test
+ fun testQueryAndSetView(){
+ // TODO
+ imageLoader.queryAndSetView(holder,image)
+ }
+
+ /**
+ * Test querySha1
+ */
+ @Test
+ fun testQuerySha1() {
+ val func = imageLoader.javaClass.getDeclaredMethod(
+ "querySHA1",
+ String::class.java
+ )
+ func.isAccessible = true
+
+ Mockito.`when`(single.blockingGet()).thenReturn(true)
+ Mockito.`when`(mediaClient.checkFileExistsUsingSha("testSha1")).thenReturn(single)
+ Mockito.`when`(fileUtilsWrapper.getSHA1(any())).thenReturn("testSha1")
+
+ // test without saving in map.
+ func.invoke(imageLoader, "testSha1");
+
+ // test with map save.
+ mapResult["testSha1"] = ImageLoader.Result.FALSE
+ func.invoke(imageLoader, "testSha1");
+ }
+
+ /**
+ * Test getSha1
+ */
+ @Test
+ @Throws (Exception::class)
+ fun testGetSha1() {
+ val func = imageLoader.javaClass.getDeclaredMethod(
+ "getSHA1",
+ Image::class.java
+ )
+ func.isAccessible = true
+
+ PowerMockito.mockStatic(PickedFiles::class.java);
+ BDDMockito.given(PickedFiles.pickedExistingPicture(context, image.uri))
+ .willReturn(UploadableFile(uri, File("ABC")));
+
+ whenever(fileUtilsWrapper.getFileInputStream("ABC")).thenReturn(inputStream)
+ whenever(fileUtilsWrapper.getSHA1(inputStream)).thenReturn("testSha1")
+
+ Assert.assertEquals("testSha1", func.invoke(imageLoader, image));
+ whenever(PickedFiles.pickedExistingPicture(context,Uri.parse("test"))).thenReturn(uploadableFile)
+
+ mapImageSHA1[image] = "testSha2"
+ Assert.assertEquals("testSha2", func.invoke(imageLoader, image));
+ }
+
+ /**
+ * Test insertIntoUploaded Function.
+ */
+ @Test
+ @Throws (Exception::class)
+ fun testInsertIntoUploaded() {
+ val func = imageLoader.javaClass.getDeclaredMethod(
+ "insertIntoUploaded",
+ String::class.java,
+ String::class.java,
+ Boolean::class.java,
+ Boolean::class.java)
+ func.isAccessible = true
+
+ func.invoke(imageLoader, "", "", true, true)
+ }
+
+ /**
+ * Test getImageSha1.
+ */
+ @Test
+ @Throws (Exception::class)
+ fun testGetImageSHA1() {
+ val func = imageLoader.javaClass.getDeclaredMethod(
+ "getImageSHA1",
+ Uri::class.java)
+ func.isAccessible = true
+
+ whenever(contentResolver.openInputStream(uri)).thenReturn(inputStream)
+ whenever(context.contentResolver).thenReturn(contentResolver)
+ whenever(fileUtilsWrapper.getSHA1(inputStream)).thenReturn("testSha1")
+
+ Assert.assertEquals("testSha1", func.invoke(imageLoader,uri))
+ }
+
+ /**
+ * Test getResultFromUploadedStatus.
+ */
+ @Test
+ @Throws (Exception::class)
+ fun testGetResultFromUploadedStatus() {
+ val func = imageLoader.javaClass.getDeclaredMethod(
+ "getResultFromUploadedStatus",
+ UploadedStatus::class.java)
+ func.isAccessible = true
+
+ // test Result.TRUE
+ Assert.assertEquals(ImageLoader.Result.TRUE,
+ func.invoke(imageLoader,
+ UploadedStatus("", "", true, true)))
+
+ // test Result.FALSE
+ Assert.assertEquals(ImageLoader.Result.FALSE,
+ func.invoke(imageLoader,
+ UploadedStatus("", "", false, false, Calendar.getInstance().time)))
+
+ // test Result.INVALID
+ Assert.assertEquals(ImageLoader.Result.INVALID,
+ func.invoke(imageLoader, UploadedStatus("", "", false, false, Date(0))))
+
+ }
+}
\ No newline at end of file
diff --git a/app/src/test/resources/imageLoaderTestFile b/app/src/test/resources/imageLoaderTestFile
new file mode 100644
index 0000000000..e69de29bb2
From 443dcf445ba37c9e6698dbfe23b77db14cf49483 Mon Sep 17 00:00:00 2001
From: Aditya-Srivastav <54016427+4D17Y4@users.noreply.github.com>
Date: Wed, 21 Jul 2021 08:57:25 +0530
Subject: [PATCH 14/28] Image Loader Improvements (#4516)
---
.../customselector/database/UploadedDao.kt | 24 ++-
.../customselector/ui/adapter/ImageAdapter.kt | 6 +-
.../ui/selector/ImageFragment.kt | 8 +
.../customselector/ui/selector/ImageLoader.kt | 185 +++++++++++-------
.../nrw/commons/upload/worker/UploadWorker.kt | 13 +-
5 files changed, 146 insertions(+), 90 deletions(-)
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 d9f2fc55eb..c0282c92c1 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
@@ -50,39 +50,37 @@ abstract class UploadedStatusDao {
/**
* Asynchronous insert into uploaded status table.
*/
- fun insertUploaded(uploadedStatus: UploadedStatus) = runBlocking {
- async {
- uploadedStatus.lastUpdated = Calendar.getInstance().time as Date?
- insert(uploadedStatus)
- }.await()
+ suspend fun insertUploaded(uploadedStatus: UploadedStatus) {
+ uploadedStatus.lastUpdated = Calendar.getInstance().time as Date?
+ insert(uploadedStatus)
}
/**
* Asynchronous delete from uploaded status table.
*/
- fun deleteUploaded(uploadedStatus: UploadedStatus) = runBlocking {
- async { delete(uploadedStatus) }
+ suspend fun deleteUploaded(uploadedStatus: UploadedStatus) {
+ delete(uploadedStatus)
}
/**
* Asynchronous update entry in uploaded status table.
*/
- fun updateUploaded(uploadedStatus: UploadedStatus) = runBlocking {
- async { update(uploadedStatus) }
+ suspend fun updateUploaded(uploadedStatus: UploadedStatus) {
+ update(uploadedStatus)
}
/**
* Asynchronous image sha1 query.
*/
- fun getUploadedFromImageSHA1(imageSHA1: String) = runBlocking {
- async { getFromImageSHA1(imageSHA1) }.await()
+ suspend fun getUploadedFromImageSHA1(imageSHA1: String):UploadedStatus {
+ return getFromImageSHA1(imageSHA1)
}
/**
* Asynchronous modified image sha1 query.
*/
- fun getUploadedFromModifiedImageSHA1(modifiedImageSHA1: String) = runBlocking {
- async { getFromModifiedImageSHA1(modifiedImageSHA1) }.await()
+ suspend fun getUploadedFromModifiedImageSHA1(modifiedImageSHA1: String):UploadedStatus {
+ return getFromModifiedImageSHA1(modifiedImageSHA1)
}
}
\ 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 9029e03bc2..ff41048f04 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
@@ -76,7 +76,7 @@ class ImageAdapter(
else {
holder.itemUnselected();
}
- Glide.with(context).load(image.uri).into(holder.image)
+ Glide.with(context).load(image.uri).thumbnail(0.3f).into(holder.image)
imageLoader.queryAndSetView(holder,image)
holder.itemView.setOnClickListener {
selectOrRemoveImage(holder, position)
@@ -99,8 +99,8 @@ class ImageAdapter(
if(holder.isItemUploaded()){
Toast.makeText(context,"Already Uploaded image", Toast.LENGTH_SHORT).show()
} else {
- selectedImages.add(images[position])
- notifyItemChanged(position, ImageSelectedOrUpdated())
+ selectedImages.add(images[position])
+ notifyItemChanged(position, ImageSelectedOrUpdated())
}
}
imageSelectListener.onSelectedImagesChanged(selectedImages)
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 f1583c54fc..cbb3fc4425 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
@@ -148,4 +148,12 @@ class ImageFragment: CommonsDaggerSupportFragment() {
return 3
// todo change span count depending on the device orientation and other factos.
}
+
+ /**
+ * OnDestroy Cleanup the imageLoader coroutine.
+ */
+ override fun onDestroy() {
+ imageLoader?.cleanUP()
+ super.onDestroy()
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/ImageLoader.kt b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/ImageLoader.kt
index 5680cc7753..a617b2d2a4 100644
--- a/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/ImageLoader.kt
+++ b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/ImageLoader.kt
@@ -11,10 +11,7 @@ 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 kotlinx.coroutines.*
import timber.log.Timber
import java.io.IOException
import java.net.UnknownHostException
@@ -57,9 +54,17 @@ class ImageLoader @Inject constructor(
/**
* Maps to facilitate image query.
*/
- private var mapImageSHA1: HashMap = HashMap()
+ private var mapModifiedImageSHA1: HashMap = HashMap()
private var mapHolderImage : HashMap = HashMap()
private var mapResult: HashMap = HashMap()
+ private var mapImageSHA1: HashMap = HashMap()
+
+ /**
+ * Coroutine Dispatchers and Scope.
+ */
+ private var defaultDispatcher = Dispatchers.Default
+ private var ioDispatcher = Dispatchers.IO
+ private val scope = MainScope()
/**
* Query image and setUp the view.
@@ -72,42 +77,43 @@ class ImageLoader @Inject constructor(
mapHolderImage[holder] = image
holder.itemNotUploaded()
- CoroutineScope(Dispatchers.Main).launch {
+ scope.launch {
- var result : Result = Result.NOTFOUND
- withContext(Dispatchers.Default) {
+ var result: Result = Result.NOTFOUND
+ if (mapHolderImage[holder] != image) {
+ return@launch
+ }
+
+ val imageSHA1 = getImageSHA1(image.uri)
+ val uploadedStatus = getFromUploaded(imageSHA1)
+
+ val sha1 = uploadedStatus?.let {
+ result = getResultFromUploadedStatus(uploadedStatus)
+ uploadedStatus.modifiedImageSHA1
+ } ?: run {
if (mapHolderImage[holder] == image) {
- val imageSHA1 = getImageSHA1(image.uri)
- val uploadedStatus = uploadedStatusDao.getUploadedFromImageSHA1(imageSHA1)
-
- val sha1 = uploadedStatus?.let {
- result = getResultFromUploadedStatus(uploadedStatus)
- uploadedStatus.modifiedImageSHA1
- } ?: run {
- if(mapHolderImage[holder] == image) {
- getSHA1(image)
- } else {
- ""
- }
- }
-
- if (mapHolderImage[holder] == image &&
- result in arrayOf(Result.NOTFOUND, Result.INVALID) &&
- sha1.isNotEmpty()) {
- // Query original image.
- result = querySHA1(imageSHA1)
- if( result is Result.TRUE ) {
- // Original image found.
- insertIntoUploaded(imageSHA1, sha1, result is Result.TRUE, false)
- }
- else {
- // Original image not found, query modified image.
- result = querySHA1(sha1)
- if (result != Result.ERROR) {
- insertIntoUploaded(imageSHA1, sha1, false, result is Result.TRUE)
- }
- }
+ getSHA1(image)
+ } else {
+ ""
+ }
+ }
+
+ if (mapHolderImage[holder] != image) {
+ return@launch
+ }
+
+ if (result in arrayOf(Result.NOTFOUND, Result.INVALID) && sha1.isNotEmpty()) {
+ // Query original image.
+ result = querySHA1(imageSHA1)
+ if (result is Result.TRUE) {
+ // Original image found.
+ insertIntoUploaded(imageSHA1, sha1, result is Result.TRUE, false)
+ } else {
+ // Original image not found, query modified image.
+ result = querySHA1(sha1)
+ if (result != Result.ERROR) {
+ insertIntoUploaded(imageSHA1, sha1, false, result is Result.TRUE)
}
}
}
@@ -122,25 +128,27 @@ class ImageLoader @Inject constructor(
*
* @return Query result.
*/
- private fun querySHA1(SHA1: String): Result {
- mapResult[SHA1]?.let{
- return it
- }
- var result : Result = Result.FALSE
- try {
- if (mediaClient.checkFileExistsUsingSha(SHA1).blockingGet()) {
- mapResult[SHA1] = Result.TRUE
- result = Result.TRUE
+
+ private suspend fun querySHA1(SHA1: String): Result {
+ return withContext(ioDispatcher) {
+ mapResult[SHA1]?.let {
+ return@withContext it
}
- } catch (e: Exception) {
- if (e is UnknownHostException) {
- // Handle no network connection.
- Timber.e(e, "Network Connection Error")
+ var result: Result = Result.FALSE
+ try {
+ if (mediaClient.checkFileExistsUsingSha(SHA1).blockingGet()) {
+ mapResult[SHA1] = Result.TRUE
+ result = Result.TRUE
+ }
+ } catch (e: Exception) {
+ if (e is UnknownHostException) {
+ // Handle no network connection.
+ Timber.e(e, "Network Connection Error")
+ }
+ result = Result.ERROR
+ e.printStackTrace()
}
- result = Result.ERROR
- e.printStackTrace()
- } finally {
- return result
+ result
}
}
@@ -149,27 +157,48 @@ class ImageLoader @Inject constructor(
*
* @return sha1 of the image
*/
- private fun getSHA1(image: Image): String {
- mapImageSHA1[image]?.let{
+ private suspend fun getSHA1(image: Image): String {
+ mapModifiedImageSHA1[image]?.let{
return it
}
val sha1 = generateModifiedSHA1(image);
- mapImageSHA1[image] = sha1;
+ mapModifiedImageSHA1[image] = sha1;
return sha1;
}
+ /**
+ * Get the uploaded status entry from the database.
+ */
+ private suspend fun getFromUploaded(imageSha1:String): UploadedStatus?{
+ return uploadedStatusDao.getUploadedFromImageSHA1(imageSha1)
+ }
+
/**
* Insert into uploaded status table.
*/
- private fun insertIntoUploaded(imageSha1:String, modifiedImageSha1:String, imageResult:Boolean, modifiedImageResult: Boolean){
- uploadedStatusDao.insertUploaded(UploadedStatus(imageSha1, modifiedImageSha1, imageResult, modifiedImageResult))
+ private suspend fun insertIntoUploaded(imageSha1:String, modifiedImageSha1:String, imageResult:Boolean, modifiedImageResult: Boolean){
+ uploadedStatusDao.insertUploaded(
+ UploadedStatus(
+ imageSha1,
+ modifiedImageSha1,
+ imageResult,
+ modifiedImageResult
+ )
+ )
}
/**
* Get image sha1 from uri, used to retrieve the original image sha1.
*/
- private fun getImageSHA1(uri: Uri): String {
- return fileUtilsWrapper.getSHA1(context.contentResolver.openInputStream(uri))
+ private suspend fun getImageSHA1(uri: Uri): String {
+ return withContext(ioDispatcher) {
+ mapImageSHA1[uri]?.let{
+ return@withContext it
+ }
+ val result = fileUtilsWrapper.getSHA1(context.contentResolver.openInputStream(uri))
+ mapImageSHA1[uri] = result
+ result
+ }
}
/**
@@ -194,18 +223,28 @@ class ImageLoader @Inject constructor(
*
* @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
+ private suspend fun generateModifiedSHA1(image: Image) : String {
+ return withContext(defaultDispatcher) {
+ val uploadableFile = PickedFiles.pickedExistingPicture(context, image.uri)
+ val exifInterface: ExifInterface? = try {
+ ExifInterface(uploadableFile.file!!)
+ } catch (e: IOException) {
+ Timber.e(e)
+ null
+ }
+ fileProcessor.redactExifTags(exifInterface, fileProcessor.getExifTagsToRedact())
+ val sha1 =
+ fileUtilsWrapper.getSHA1(fileUtilsWrapper.getFileInputStream(uploadableFile.filePath))
+ uploadableFile.file.delete()
+ sha1
}
- fileProcessor.redactExifTags(exifInterface, fileProcessor.getExifTagsToRedact())
- val sha1 = fileUtilsWrapper.getSHA1(fileUtilsWrapper.getFileInputStream(uploadableFile.filePath))
- uploadableFile.file.delete()
- return sha1
+ }
+
+ /**
+ * CleanUp function.
+ */
+ fun cleanUP() {
+ scope.cancel()
}
/**
diff --git a/app/src/main/java/fr/free/nrw/commons/upload/worker/UploadWorker.kt b/app/src/main/java/fr/free/nrw/commons/upload/worker/UploadWorker.kt
index 8f9bc9504b..d46c5e4ac9 100644
--- a/app/src/main/java/fr/free/nrw/commons/upload/worker/UploadWorker.kt
+++ b/app/src/main/java/fr/free/nrw/commons/upload/worker/UploadWorker.kt
@@ -29,9 +29,11 @@ import fr.free.nrw.commons.upload.UploadClient
import fr.free.nrw.commons.upload.UploadResult
import fr.free.nrw.commons.wikidata.WikidataEditService
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.MainScope
import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import timber.log.Timber
import java.io.IOException
@@ -438,7 +440,16 @@ class UploadWorker(var appContext: Context, workerParams: WorkerParameters) :
contribution.contentUri?.let {
val imageSha1 = fileUtilsWrapper.getSHA1(appContext.contentResolver.openInputStream(it))
val modifiedSha1 = fileUtilsWrapper.getSHA1(fileUtilsWrapper.getFileInputStream(contribution.localUri?.path))
- uploadedStatusDao.insertUploaded(UploadedStatus(imageSha1, modifiedSha1, imageSha1 == modifiedSha1, true));
+ MainScope().launch {
+ uploadedStatusDao.insertUploaded(
+ UploadedStatus(
+ imageSha1,
+ modifiedSha1,
+ imageSha1 == modifiedSha1,
+ true
+ )
+ );
+ }
}
}
From 36a94fea9390fb5326d0979ae59496ee41fa1d9f Mon Sep 17 00:00:00 2001
From: Aditya-Srivastav <54016427+4D17Y4@users.noreply.github.com>
Date: Sat, 24 Jul 2021 14:09:59 +0530
Subject: [PATCH 15/28] ImageLoader Test Updated (#4517)
---
app/build.gradle | 1 +
.../customselector/database/UploadedDao.kt | 38 +---
.../customselector/ui/selector/ImageLoader.kt | 18 +-
.../ui/selector/ImageLoaderTest.kt | 162 +++++++++---------
4 files changed, 96 insertions(+), 123 deletions(-)
diff --git a/app/build.gradle b/app/build.gradle
index 501595fdaa..0ccc38f602 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -93,6 +93,7 @@ dependencies {
testImplementation "org.junit.jupiter:junit-jupiter-api:5.3.1"
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:5.3.1"
testImplementation 'com.facebook.soloader:soloader:0.9.0'
+ testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.4.2"
// Android testing
androidTestImplementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$KOTLIN_VERSION"
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 c0282c92c1..49a1f61c3b 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
@@ -1,9 +1,7 @@
package fr.free.nrw.commons.customselector.database
import androidx.room.*
-import kotlinx.coroutines.runBlocking
import java.util.*
-import kotlinx.coroutines.*
/**
* UploadedStatusDao for Custom Selector.
@@ -29,58 +27,30 @@ abstract class UploadedStatusDao {
@Delete
abstract suspend fun delete(uploadedStatus: UploadedStatus)
- /**
- * Get All entries from the uploaded status table.
- */
- @Query("SELECT * FROM uploaded_table")
- abstract suspend fun getAll() : List
-
/**
* Query uploaded status with image sha1.
*/
@Query("SELECT * FROM uploaded_table WHERE imageSHA1 = (:imageSHA1) ")
- abstract suspend fun getFromImageSHA1(imageSHA1 : String) : UploadedStatus
+ abstract suspend fun getFromImageSHA1(imageSHA1 : String) : UploadedStatus?
/**
* Query uploaded status with modified image sha1.
*/
@Query("SELECT * FROM uploaded_table WHERE modifiedImageSHA1 = (:modifiedImageSHA1) ")
- abstract suspend fun getFromModifiedImageSHA1(modifiedImageSHA1 : String) : UploadedStatus
+ abstract suspend fun getFromModifiedImageSHA1(modifiedImageSHA1 : String) : UploadedStatus?
/**
* Asynchronous insert into uploaded status table.
*/
suspend fun insertUploaded(uploadedStatus: UploadedStatus) {
- uploadedStatus.lastUpdated = Calendar.getInstance().time as Date?
+ uploadedStatus.lastUpdated = Calendar.getInstance().time
insert(uploadedStatus)
}
- /**
- * Asynchronous delete from uploaded status table.
- */
- suspend fun deleteUploaded(uploadedStatus: UploadedStatus) {
- delete(uploadedStatus)
- }
-
- /**
- * Asynchronous update entry in uploaded status table.
- */
- suspend fun updateUploaded(uploadedStatus: UploadedStatus) {
- update(uploadedStatus)
- }
-
/**
* Asynchronous image sha1 query.
*/
- suspend fun getUploadedFromImageSHA1(imageSHA1: String):UploadedStatus {
+ suspend fun getUploadedFromImageSHA1(imageSHA1: String):UploadedStatus? {
return getFromImageSHA1(imageSHA1)
}
-
- /**
- * Asynchronous modified image sha1 query.
- */
- suspend fun getUploadedFromModifiedImageSHA1(modifiedImageSHA1: String):UploadedStatus {
- return getFromModifiedImageSHA1(modifiedImageSHA1)
- }
-
}
\ No newline at end of file
diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/ImageLoader.kt b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/ImageLoader.kt
index a617b2d2a4..3b5254f86d 100644
--- a/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/ImageLoader.kt
+++ b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/ImageLoader.kt
@@ -62,9 +62,9 @@ class ImageLoader @Inject constructor(
/**
* Coroutine Dispatchers and Scope.
*/
- private var defaultDispatcher = Dispatchers.Default
- private var ioDispatcher = Dispatchers.IO
- private val scope = MainScope()
+ private var defaultDispatcher : CoroutineDispatcher = Dispatchers.Default
+ private var ioDispatcher : CoroutineDispatcher = Dispatchers.IO
+ private val scope : CoroutineScope = MainScope()
/**
* Query image and setUp the view.
@@ -129,7 +129,7 @@ class ImageLoader @Inject constructor(
* @return Query result.
*/
- private suspend fun querySHA1(SHA1: String): Result {
+ suspend fun querySHA1(SHA1: String): Result {
return withContext(ioDispatcher) {
mapResult[SHA1]?.let {
return@withContext it
@@ -157,7 +157,7 @@ class ImageLoader @Inject constructor(
*
* @return sha1 of the image
*/
- private suspend fun getSHA1(image: Image): String {
+ suspend fun getSHA1(image: Image): String {
mapModifiedImageSHA1[image]?.let{
return it
}
@@ -169,14 +169,14 @@ class ImageLoader @Inject constructor(
/**
* Get the uploaded status entry from the database.
*/
- private suspend fun getFromUploaded(imageSha1:String): UploadedStatus?{
+ suspend fun getFromUploaded(imageSha1:String): UploadedStatus? {
return uploadedStatusDao.getUploadedFromImageSHA1(imageSha1)
}
/**
* Insert into uploaded status table.
*/
- private suspend fun insertIntoUploaded(imageSha1:String, modifiedImageSha1:String, imageResult:Boolean, modifiedImageResult: Boolean){
+ suspend fun insertIntoUploaded(imageSha1:String, modifiedImageSha1:String, imageResult:Boolean, modifiedImageResult: Boolean){
uploadedStatusDao.insertUploaded(
UploadedStatus(
imageSha1,
@@ -190,7 +190,7 @@ class ImageLoader @Inject constructor(
/**
* Get image sha1 from uri, used to retrieve the original image sha1.
*/
- private suspend fun getImageSHA1(uri: Uri): String {
+ suspend fun getImageSHA1(uri: Uri): String {
return withContext(ioDispatcher) {
mapImageSHA1[uri]?.let{
return@withContext it
@@ -204,7 +204,7 @@ class ImageLoader @Inject constructor(
/**
* Get result data from database.
*/
- private fun getResultFromUploadedStatus(uploadedStatus: UploadedStatus): Result {
+ fun getResultFromUploadedStatus(uploadedStatus: UploadedStatus): Result {
if (uploadedStatus.imageResult || uploadedStatus.modifiedImageResult) {
return Result.TRUE
} else {
diff --git a/app/src/test/kotlin/fr/free/nrw/commons/customselector/ui/selector/ImageLoaderTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/customselector/ui/selector/ImageLoaderTest.kt
index cb7cf3a501..fe26921e5e 100644
--- a/app/src/test/kotlin/fr/free/nrw/commons/customselector/ui/selector/ImageLoaderTest.kt
+++ b/app/src/test/kotlin/fr/free/nrw/commons/customselector/ui/selector/ImageLoaderTest.kt
@@ -3,8 +3,7 @@ package fr.free.nrw.commons.customselector.ui.selector
import android.content.ContentResolver
import android.content.Context
import android.net.Uri
-import com.nhaarman.mockitokotlin2.any
-import com.nhaarman.mockitokotlin2.whenever
+import com.nhaarman.mockitokotlin2.*
import fr.free.nrw.commons.TestCommonsApplication
import fr.free.nrw.commons.customselector.database.UploadedStatus
import fr.free.nrw.commons.customselector.database.UploadedStatusDao
@@ -17,6 +16,10 @@ import fr.free.nrw.commons.upload.FileProcessor
import fr.free.nrw.commons.upload.FileUtilsWrapper
import io.reactivex.Single
import junit.framework.Assert
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.*
+import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -28,7 +31,6 @@ import org.powermock.reflect.Whitebox
import org.robolectric.annotation.Config
import java.io.File
import java.io.FileInputStream
-import java.lang.Exception
import java.util.*
import kotlin.collections.HashMap
@@ -38,6 +40,7 @@ import kotlin.collections.HashMap
@RunWith(PowerMockRunner::class)
@PrepareForTest(PickedFiles::class)
@Config(sdk = [21], application = TestCommonsApplication::class)
+@ExperimentalCoroutinesApi
class ImageLoaderTest {
@Mock
@@ -73,146 +76,145 @@ class ImageLoaderTest {
@Mock
private lateinit var contentResolver: ContentResolver
- @Mock
- private lateinit var image: Image;
+ @ExperimentalCoroutinesApi
+ private val testDispacher = TestCoroutineDispatcher()
private lateinit var imageLoader: ImageLoader;
- private var mapImageSHA1: HashMap = HashMap()
+ private var mapImageSHA1: HashMap = HashMap()
private var mapHolderImage : HashMap = HashMap()
private var mapResult: HashMap = HashMap()
+ private var mapModifiedImageSHA1: HashMap = HashMap()
+ private lateinit var image: Image;
+ private lateinit var uploadedStatus: UploadedStatus;
/**
* Setup before test.
*/
@Before
+ @ExperimentalCoroutinesApi
fun setup() {
+ Dispatchers.setMain(testDispacher)
MockitoAnnotations.initMocks(this)
+
imageLoader =
ImageLoader(mediaClient, fileProcessor, fileUtilsWrapper, uploadedStatusDao, context)
+ uploadedStatus= UploadedStatus(
+ "testSha1",
+ "testSha1",
+ false,
+ false,
+ Calendar.getInstance().time
+ )
+ image = Image(1, "test", uri, "test", 0, "test")
Whitebox.setInternalState(imageLoader, "mapImageSHA1", mapImageSHA1);
Whitebox.setInternalState(imageLoader, "mapHolderImage", mapHolderImage);
+ Whitebox.setInternalState(imageLoader, "mapModifiedImageSHA1", mapModifiedImageSHA1);
Whitebox.setInternalState(imageLoader, "mapResult", mapResult);
Whitebox.setInternalState(imageLoader, "context", context)
+ Whitebox.setInternalState(imageLoader, "ioDispatcher", testDispacher)
+ Whitebox.setInternalState(imageLoader, "defaultDispatcher", testDispacher)
+
+ whenever(contentResolver.openInputStream(uri)).thenReturn(inputStream)
+ whenever(context.contentResolver).thenReturn(contentResolver)
+ whenever(fileUtilsWrapper.getSHA1(inputStream)).thenReturn("testSha1")
}
/**
- * Test queryAndSetView.
+ * Reset Dispatchers.
*/
- @Test
- fun testQueryAndSetView(){
- // TODO
- imageLoader.queryAndSetView(holder,image)
+ @After
+ @ExperimentalCoroutinesApi
+ fun tearDown() {
+ Dispatchers.resetMain()
+ testDispacher.cleanupTestCoroutines()
}
/**
- * Test querySha1
+ * Test queryAndSetView with upload Status as null.
*/
@Test
- fun testQuerySha1() {
- val func = imageLoader.javaClass.getDeclaredMethod(
- "querySHA1",
- String::class.java
- )
- func.isAccessible = true
-
- Mockito.`when`(single.blockingGet()).thenReturn(true)
- Mockito.`when`(mediaClient.checkFileExistsUsingSha("testSha1")).thenReturn(single)
- Mockito.`when`(fileUtilsWrapper.getSHA1(any())).thenReturn("testSha1")
+ fun testQueryAndSetViewUploadedStatusNull() = testDispacher.runBlockingTest {
+ whenever(uploadedStatusDao.getUploadedFromImageSHA1(any())).thenReturn(null)
+ mapModifiedImageSHA1[image] = "testSha1"
+ mapImageSHA1[uri] = "testSha1"
- // test without saving in map.
- func.invoke(imageLoader, "testSha1");
+ mapResult["testSha1"] = ImageLoader.Result.TRUE
+ imageLoader.queryAndSetView(holder, image)
- // test with map save.
mapResult["testSha1"] = ImageLoader.Result.FALSE
- func.invoke(imageLoader, "testSha1");
+ imageLoader.queryAndSetView(holder, image)
}
/**
- * Test getSha1
+ * Test queryAndSetView with upload Status not null (ie retrieved from table)
*/
@Test
- @Throws (Exception::class)
- fun testGetSha1() {
- val func = imageLoader.javaClass.getDeclaredMethod(
- "getSHA1",
- Image::class.java
- )
- func.isAccessible = true
-
- PowerMockito.mockStatic(PickedFiles::class.java);
- BDDMockito.given(PickedFiles.pickedExistingPicture(context, image.uri))
- .willReturn(UploadableFile(uri, File("ABC")));
-
- whenever(fileUtilsWrapper.getFileInputStream("ABC")).thenReturn(inputStream)
- whenever(fileUtilsWrapper.getSHA1(inputStream)).thenReturn("testSha1")
-
- Assert.assertEquals("testSha1", func.invoke(imageLoader, image));
- whenever(PickedFiles.pickedExistingPicture(context,Uri.parse("test"))).thenReturn(uploadableFile)
-
- mapImageSHA1[image] = "testSha2"
- Assert.assertEquals("testSha2", func.invoke(imageLoader, image));
+ fun testQueryAndSetViewUploadedStatusNotNull() = testDispacher.runBlockingTest {
+ whenever(uploadedStatusDao.getUploadedFromImageSHA1(any())).thenReturn(uploadedStatus)
+ imageLoader.queryAndSetView(holder, image)
}
/**
- * Test insertIntoUploaded Function.
+ * Test querySha1
*/
@Test
- @Throws (Exception::class)
- fun testInsertIntoUploaded() {
- val func = imageLoader.javaClass.getDeclaredMethod(
- "insertIntoUploaded",
- String::class.java,
- String::class.java,
- Boolean::class.java,
- Boolean::class.java)
- func.isAccessible = true
+ fun testQuerySha1() = testDispacher.runBlockingTest {
- func.invoke(imageLoader, "", "", true, true)
+ whenever(single.blockingGet()).thenReturn(true)
+ whenever(mediaClient.checkFileExistsUsingSha("testSha1")).thenReturn(single)
+ whenever(fileUtilsWrapper.getSHA1(any())).thenReturn("testSha1")
+
+ imageLoader.querySHA1("testSha1")
}
/**
- * Test getImageSha1.
+ * Test getSha1
*/
@Test
- @Throws (Exception::class)
- fun testGetImageSHA1() {
- val func = imageLoader.javaClass.getDeclaredMethod(
- "getImageSHA1",
- Uri::class.java)
- func.isAccessible = true
+ @ExperimentalCoroutinesApi
+ fun testGetSha1() = testDispacher.runBlockingTest {
- whenever(contentResolver.openInputStream(uri)).thenReturn(inputStream)
- whenever(context.contentResolver).thenReturn(contentResolver)
+ PowerMockito.mockStatic(PickedFiles::class.java)
+ BDDMockito.given(PickedFiles.pickedExistingPicture(context, image.uri))
+ .willReturn(UploadableFile(uri, File("ABC")))
+
+
+ whenever(fileUtilsWrapper.getFileInputStream("ABC")).thenReturn(inputStream)
whenever(fileUtilsWrapper.getSHA1(inputStream)).thenReturn("testSha1")
- Assert.assertEquals("testSha1", func.invoke(imageLoader,uri))
+ Assert.assertEquals("testSha1", imageLoader.getSHA1(image));
+ whenever(PickedFiles.pickedExistingPicture(context, Uri.parse("test"))).thenReturn(
+ uploadableFile
+ )
+
+ mapModifiedImageSHA1[image] = "testSha2"
+ Assert.assertEquals("testSha2", imageLoader.getSHA1(image));
}
/**
* Test getResultFromUploadedStatus.
*/
@Test
- @Throws (Exception::class)
fun testGetResultFromUploadedStatus() {
val func = imageLoader.javaClass.getDeclaredMethod(
"getResultFromUploadedStatus",
UploadedStatus::class.java)
func.isAccessible = true
- // test Result.TRUE
- Assert.assertEquals(ImageLoader.Result.TRUE,
- func.invoke(imageLoader,
- UploadedStatus("", "", true, true)))
-
- // test Result.FALSE
- Assert.assertEquals(ImageLoader.Result.FALSE,
- func.invoke(imageLoader,
- UploadedStatus("", "", false, false, Calendar.getInstance().time)))
-
// test Result.INVALID
+ uploadedStatus.lastUpdated = Date(0);
Assert.assertEquals(ImageLoader.Result.INVALID,
- func.invoke(imageLoader, UploadedStatus("", "", false, false, Date(0))))
+ imageLoader.getResultFromUploadedStatus(uploadedStatus))
+
+ // test Result.TRUE
+ uploadedStatus.imageResult = true;
+ Assert.assertEquals(ImageLoader.Result.TRUE,
+ imageLoader.getResultFromUploadedStatus(uploadedStatus))
+ }
+ @Test
+ fun testCleanUP() {
+ imageLoader.cleanUP()
}
}
\ No newline at end of file
From b62247d6005c995eb45d2f4bf11be831c5b7d885 Mon Sep 17 00:00:00 2001
From: Aditya-Srivastav <54016427+4D17Y4@users.noreply.github.com>
Date: Mon, 26 Jul 2021 16:31:45 +0530
Subject: [PATCH 16/28] [GSoC] Improvement and bug Fixes (#4522)
* Improvement and bug Fixes
* fixed ellipsize
---
.../ui/selector/CustomSelectorActivity.kt | 5 +-
.../res/layout/activity_custom_selector.xml | 14 +--
.../res/layout/custom_selector_toolbar.xml | 85 ++++++++++---------
.../res/layout/item_custom_selector_image.xml | 1 +
app/src/main/res/values/strings.xml | 2 +
5 files changed, 58 insertions(+), 49 deletions(-)
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 099c89a862..ec7855cb4f 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
@@ -3,6 +3,7 @@ package fr.free.nrw.commons.customselector.ui.selector
import android.app.Activity
import android.content.Intent
import android.os.Bundle
+import android.view.View
import android.widget.ImageButton
import android.widget.TextView
import androidx.lifecycle.ViewModelProvider
@@ -96,7 +97,9 @@ class CustomSelectorActivity : BaseActivity(), FolderClickListener, ImageSelectL
*/
override fun onSelectedImagesChanged(selectedImages: ArrayList) {
viewModel.selectedImages.value = selectedImages
- // todo update selected images in view model.
+
+ val done : ImageButton = findViewById(R.id.done)
+ done.visibility = if (selectedImages.isEmpty()) View.INVISIBLE else View.VISIBLE
}
/**
diff --git a/app/src/main/res/layout/activity_custom_selector.xml b/app/src/main/res/layout/activity_custom_selector.xml
index 9587e7c0a0..d96918feee 100644
--- a/app/src/main/res/layout/activity_custom_selector.xml
+++ b/app/src/main/res/layout/activity_custom_selector.xml
@@ -4,16 +4,10 @@
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
-
-
-
+
+
-
+
+
-
-
-
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_custom_selector_image.xml b/app/src/main/res/layout/item_custom_selector_image.xml
index 021f463bc6..f04a719223 100644
--- a/app/src/main/res/layout/item_custom_selector_image.xml
+++ b/app/src/main/res/layout/item_custom_selector_image.xml
@@ -40,6 +40,7 @@
app:layout_constraintDimensionRatio="H,1:1"
android:textSize="11sp"
android:textStyle="bold"
+ android:textColor="@color/black"
android:layout_margin="@dimen/dimen_6"
android:gravity="center|center_vertical"
style="@style/TextAppearance.AppCompat.Small"
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 4e3c8fb040..3d71f9b560 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -648,5 +648,7 @@ Upload your first media by tapping on the add button.
Wiki Loves Monuments is an international photo contest for monuments organised by Wikimedia
Custom Selector
No Images
+ Done
+ Back
From e8f7d0f03b93f253651279b3d1a618432c6d9940 Mon Sep 17 00:00:00 2001
From: Aditya-Srivastav <54016427+4D17Y4@users.noreply.github.com>
Date: Tue, 27 Jul 2021 05:50:49 +0530
Subject: [PATCH 17/28] Saving selector state (#4526)
---
.../listeners/FolderClickListener.kt | 4 +-
.../ui/adapter/FolderAdapter.kt | 2 +-
.../ui/selector/CustomSelectorActivity.kt | 65 ++++++++++++++++---
3 files changed, 57 insertions(+), 14 deletions(-)
diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/listeners/FolderClickListener.kt b/app/src/main/java/fr/free/nrw/commons/customselector/listeners/FolderClickListener.kt
index 15b74c57e7..cb32807f83 100644
--- a/app/src/main/java/fr/free/nrw/commons/customselector/listeners/FolderClickListener.kt
+++ b/app/src/main/java/fr/free/nrw/commons/customselector/listeners/FolderClickListener.kt
@@ -1,7 +1,5 @@
package fr.free.nrw.commons.customselector.listeners
-import fr.free.nrw.commons.customselector.model.Folder
-
interface FolderClickListener {
- fun onFolderClick(folder : Folder)
+ fun onFolderClick(folderId: Long, folderName: String)
}
\ No newline at end of file
diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/ui/adapter/FolderAdapter.kt b/app/src/main/java/fr/free/nrw/commons/customselector/ui/adapter/FolderAdapter.kt
index fb3e497940..67dcc789c5 100644
--- a/app/src/main/java/fr/free/nrw/commons/customselector/ui/adapter/FolderAdapter.kt
+++ b/app/src/main/java/fr/free/nrw/commons/customselector/ui/adapter/FolderAdapter.kt
@@ -49,7 +49,7 @@ class FolderAdapter(
holder.name.text = folder.name
holder.count.text = count.toString()
holder.itemView.setOnClickListener{
- itemClickListener.onFolderClick(folder)
+ itemClickListener.onFolderClick(folder.bucketId, folder.name)
}
//todo load image thumbnail.
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 ec7855cb4f..3b8bee3901 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
@@ -2,26 +2,43 @@ package fr.free.nrw.commons.customselector.ui.selector
import android.app.Activity
import android.content.Intent
+import android.content.SharedPreferences
import android.os.Bundle
import android.view.View
import android.widget.ImageButton
import android.widget.TextView
+import androidx.fragment.app.FragmentManager
import androidx.lifecycle.ViewModelProvider
import fr.free.nrw.commons.R
import fr.free.nrw.commons.customselector.listeners.FolderClickListener
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 {
+class CustomSelectorActivity: BaseActivity(), FolderClickListener, ImageSelectListener, FragmentManager.OnBackStackChangedListener {
/**
* View model.
*/
- private lateinit var viewModel: CustomSelectorViewModel
+ private lateinit var viewModel: CustomSelectorViewModel
+
+ /**
+ * isImageFragmentOpen is true when the image fragment is in view.
+ */
+ private var isImageFragmentOpen = false
+
+ /**
+ * Current ImageFragment attributes.
+ */
+ private var bucketId: Long = 0L
+ private lateinit var bucketName: String
+
+ /**
+ * Pref for saving selector state.
+ */
+ private lateinit var prefs: SharedPreferences
/**
* View Model Factory.
@@ -35,9 +52,17 @@ class CustomSelectorActivity : BaseActivity(), FolderClickListener, ImageSelectL
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_custom_selector)
- viewModel = ViewModelProvider(this,customSelectorViewModelFactory).get(CustomSelectorViewModel::class.java)
+ prefs = applicationContext.getSharedPreferences("CustomSelector", MODE_PRIVATE)
+ viewModel = ViewModelProvider(this, customSelectorViewModelFactory).get(CustomSelectorViewModel::class.java)
setupViews()
+
+ // Open folder if saved in prefs.
+ if(prefs.contains("FolderId")){
+ val lastOpenFolderId: Long = prefs.getLong("FolderId", 0L)
+ val lastOpenFolderName: String? = prefs.getString("FolderName", null)
+ lastOpenFolderName?.let { onFolderClick(lastOpenFolderId, it) }
+ }
}
/**
@@ -49,8 +74,6 @@ class CustomSelectorActivity : BaseActivity(), FolderClickListener, ImageSelectL
.commit()
fetchData()
setUpToolbar()
-
- // todo : open image fragment depending on the last user visit.
}
/**
@@ -63,7 +86,7 @@ class CustomSelectorActivity : BaseActivity(), FolderClickListener, ImageSelectL
/**
* Change the title of the toolbar.
*/
- private fun changeTitle(title:String) {
+ private fun changeTitle(title: String) {
val titleText = findViewById(R.id.title)
if(titleText != null) {
titleText.text = title
@@ -84,12 +107,17 @@ class CustomSelectorActivity : BaseActivity(), FolderClickListener, ImageSelectL
/**
* override on folder click, change the toolbar title on folder click.
*/
- override fun onFolderClick(folder: Folder) {
+ override fun onFolderClick(folderId: Long, folderName: String) {
supportFragmentManager.beginTransaction()
- .add(R.id.fragment_container, ImageFragment.newInstance(folder.bucketId))
+ .add(R.id.fragment_container, ImageFragment.newInstance(folderId))
.addToBackStack(null)
.commit()
- changeTitle(folder.name)
+
+ changeTitle(folderName)
+
+ bucketId = folderId
+ bucketName = folderName
+ isImageFragmentOpen = true
}
/**
@@ -148,4 +176,21 @@ class CustomSelectorActivity : BaseActivity(), FolderClickListener, ImageSelectL
}
}
+ override fun onDestroy() {
+ if(isImageFragmentOpen){
+ prefs.edit().putLong("FolderId", bucketId).putString("FolderName", bucketName).apply()
+ } else {
+ prefs.edit().remove("FolderId").remove("FolderName").apply()
+ }
+ super.onDestroy()
+ }
+
+ /**
+ * Called whenever the contents of the back stack change.
+ */
+ override fun onBackStackChanged() {
+ if(supportFragmentManager.backStackEntryCount == 0) {
+ isImageFragmentOpen = false
+ }
+ }
}
\ No newline at end of file
From d78e6deb4eb5ce7f35f585befe3f06e72da64c36 Mon Sep 17 00:00:00 2001
From: Aditya-Srivastav <54016427+4D17Y4@users.noreply.github.com>
Date: Sun, 8 Aug 2021 18:38:11 +0530
Subject: [PATCH 18/28] [GSoC] Saved Image Fragment Scroll State (#4528)
* Saved Image Fragment Scroll State
* Fix delete image
* Fixed Delete bug
* Changed custom selector icon
---
.../customselector/helper/ImageHelper.kt | 25 ++++----
.../listeners/FolderClickListener.kt | 2 +-
.../ui/adapter/FolderAdapter.kt | 39 ++++++++++---
.../customselector/ui/adapter/ImageAdapter.kt | 54 +++++++++++-------
.../ui/selector/CustomSelectorActivity.kt | 36 ++++++------
.../ui/selector/FolderFragment.kt | 13 ++++-
.../ui/selector/ImageFragment.kt | 57 +++++++++++++++++--
.../customselector/ui/selector/ImageLoader.kt | 14 ++++-
.../res/drawable/ic_custom_image_picker.xml | 3 +
.../layout/fragment_contributions_list.xml | 22 +++----
.../layout/item_custom_selector_folder.xml | 2 -
11 files changed, 185 insertions(+), 82 deletions(-)
create mode 100644 app/src/main/res/drawable/ic_custom_image_picker.xml
diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/helper/ImageHelper.kt b/app/src/main/java/fr/free/nrw/commons/customselector/helper/ImageHelper.kt
index 0a751d47bc..1447cd2d73 100644
--- a/app/src/main/java/fr/free/nrw/commons/customselector/helper/ImageHelper.kt
+++ b/app/src/main/java/fr/free/nrw/commons/customselector/helper/ImageHelper.kt
@@ -1,19 +1,7 @@
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.Image
-import fr.free.nrw.commons.filepicker.Constants
-import timber.log.Timber
-import java.io.*
-import java.math.BigInteger
-import java.security.MessageDigest
-import java.security.NoSuchAlgorithmException
-import kotlin.collections.ArrayList
-import kotlin.collections.HashMap
-import kotlin.collections.LinkedHashMap
-
/**
* Image Helper object, includes all the static functions required by custom selector.
@@ -24,7 +12,7 @@ object ImageHelper {
/**
* Returns the list of folders from given image list.
*/
- fun folderListFromImages(images: List): List {
+ fun folderListFromImages(images: List): ArrayList {
val folderMap: MutableMap = LinkedHashMap()
for (image in images) {
val bucketId = image.bucketId
@@ -61,6 +49,17 @@ object ImageHelper {
return list.indexOf(image)
}
+ /**
+ * getIndex: Returns the index of image in given list.
+ */
+ fun getIndexFromId(list: ArrayList, imageId: Long): Int {
+ for(i in list){
+ if(i.id == imageId)
+ return list.indexOf(i)
+ }
+ return 0;
+ }
+
/**
* Gets the list of indices from the master list.
*/
diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/listeners/FolderClickListener.kt b/app/src/main/java/fr/free/nrw/commons/customselector/listeners/FolderClickListener.kt
index cb32807f83..e016a71ba6 100644
--- a/app/src/main/java/fr/free/nrw/commons/customselector/listeners/FolderClickListener.kt
+++ b/app/src/main/java/fr/free/nrw/commons/customselector/listeners/FolderClickListener.kt
@@ -1,5 +1,5 @@
package fr.free.nrw.commons.customselector.listeners
interface FolderClickListener {
- fun onFolderClick(folderId: Long, folderName: String)
+ fun onFolderClick(folderId: Long, folderName: String, lastItemId: Long)
}
\ No newline at end of file
diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/ui/adapter/FolderAdapter.kt b/app/src/main/java/fr/free/nrw/commons/customselector/ui/adapter/FolderAdapter.kt
index 67dcc789c5..93759bdf48 100644
--- a/app/src/main/java/fr/free/nrw/commons/customselector/ui/adapter/FolderAdapter.kt
+++ b/app/src/main/java/fr/free/nrw/commons/customselector/ui/adapter/FolderAdapter.kt
@@ -11,6 +11,7 @@ import com.bumptech.glide.Glide
import fr.free.nrw.commons.R
import fr.free.nrw.commons.customselector.listeners.FolderClickListener
import fr.free.nrw.commons.customselector.model.Folder
+import fr.free.nrw.commons.customselector.model.Image
class FolderAdapter(
/**
@@ -43,16 +44,38 @@ class FolderAdapter(
*/
override fun onBindViewHolder(holder: FolderViewHolder, position: Int) {
val folder = folders[position]
- val count = folder.images.size
- val previewImage = folder.images[0]
- Glide.with(context).load(previewImage.uri).into(holder.image)
- holder.name.text = folder.name
- holder.count.text = count.toString()
- holder.itemView.setOnClickListener{
- itemClickListener.onFolderClick(folder.bucketId, folder.name)
+ val toBeRemoved = ArrayList()
+
+ for(image in folder.images) {
+ // Remove all the top images that do not exist anymore
+ if(context.contentResolver.getType(image.uri) == null){
+ // File not found
+ toBeRemoved.add(image)
+ } else {
+ break
+ }
}
+ holder.image.setImageDrawable (null)
+ folder.images.removeAll(toBeRemoved)
+ val count = folder.images.size
- //todo load image thumbnail.
+ if(count == 0) {
+ // Folder is empty, remove folder from the adapter.
+ holder.itemView.post{
+ val updatePosition = folders.indexOf(folder)
+ folders.removeAt(updatePosition)
+ notifyItemRemoved(updatePosition)
+ notifyItemRangeChanged(updatePosition, folders.size)
+ }
+ } else {
+ val previewImage = folder.images[0]
+ Glide.with(context).load(previewImage.uri).into(holder.image)
+ holder.name.text = folder.name
+ holder.count.text = count.toString()
+ holder.itemView.setOnClickListener {
+ itemClickListener.onFolderClick(folder.bucketId, folder.name, 0)
+ }
+ }
}
/**
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 ff41048f04..8225ab2dc0 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
@@ -1,9 +1,8 @@
package fr.free.nrw.commons.customselector.ui.adapter
import android.content.Context
-import android.view.ViewGroup
-import fr.free.nrw.commons.R
import android.view.View
+import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import android.widget.Toast
@@ -11,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.R
import fr.free.nrw.commons.customselector.helper.ImageHelper
import fr.free.nrw.commons.customselector.listeners.ImageSelectListener
import fr.free.nrw.commons.customselector.model.Image
@@ -59,7 +59,7 @@ class ImageAdapter(
* Create View holder.
*/
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)
return ImageViewHolder(itemView)
}
@@ -68,36 +68,46 @@ class ImageAdapter(
*/
override fun onBindViewHolder(holder: ImageViewHolder, position: Int) {
val image=images[position]
- 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).thumbnail(0.3f).into(holder.image)
- imageLoader.queryAndSetView(holder,image)
- holder.itemView.setOnClickListener {
- selectOrRemoveImage(holder, position)
+ holder.image.setImageDrawable (null)
+ if (context.contentResolver.getType(image.uri) == null) {
+ // Image does not exist anymore, update adapter.
+ holder.itemView.post {
+ val updatedPosition = images.indexOf(image)
+ images.remove(image)
+ notifyItemRemoved(updatedPosition)
+ notifyItemRangeChanged(updatedPosition, images.size)
+ }
+ } else {
+ 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).thumbnail(0.3f).into(holder.image)
+ imageLoader.queryAndSetView(holder, image)
+ holder.itemView.setOnClickListener {
+ selectOrRemoveImage(holder, position)
+ }
}
}
/**
* Handle click event on an image, update counter on images.
*/
- private fun selectOrRemoveImage(holder:ImageViewHolder, position:Int){
- val clickedIndex = ImageHelper.getIndex(selectedImages,images[position])
+ private fun selectOrRemoveImage(holder: ImageViewHolder, position: Int){
+ val clickedIndex = ImageHelper.getIndex(selectedImages, images[position])
if (clickedIndex != -1) {
selectedImages.removeAt(clickedIndex)
- notifyItemChanged(position,ImageUnselected())
+ notifyItemChanged(position, ImageUnselected())
val indexes = ImageHelper.getIndexList(selectedImages, images)
for (index in indexes) {
notifyItemChanged(index, ImageSelectedOrUpdated())
}
} else {
if(holder.isItemUploaded()){
- Toast.makeText(context,"Already Uploaded image", Toast.LENGTH_SHORT).show()
+ Toast.makeText(context, "Already Uploaded image", Toast.LENGTH_SHORT).show()
} else {
selectedImages.add(images[position])
notifyItemChanged(position, ImageSelectedOrUpdated())
@@ -109,7 +119,7 @@ class ImageAdapter(
/**
* Initialize the data set.
*/
- fun init(newImages:List) {
+ fun init(newImages: List) {
val oldImageList:ArrayList = images
val newImageList:ArrayList = ArrayList(newImages)
val diffResult = DiffUtil.calculateDiff(
@@ -128,6 +138,10 @@ class ImageAdapter(
return images.size
}
+ fun getImageIdAt(position: Int): Long {
+ return images.get(position).id
+ }
+
/**
* Image view holder.
*/
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 3b8bee3901..972c16fc49 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
@@ -7,7 +7,6 @@ import android.os.Bundle
import android.view.View
import android.widget.ImageButton
import android.widget.TextView
-import androidx.fragment.app.FragmentManager
import androidx.lifecycle.ViewModelProvider
import fr.free.nrw.commons.R
import fr.free.nrw.commons.customselector.listeners.FolderClickListener
@@ -17,7 +16,7 @@ import fr.free.nrw.commons.theme.BaseActivity
import java.io.File
import javax.inject.Inject
-class CustomSelectorActivity: BaseActivity(), FolderClickListener, ImageSelectListener, FragmentManager.OnBackStackChangedListener {
+class CustomSelectorActivity: BaseActivity(), FolderClickListener, ImageSelectListener {
/**
* View model.
@@ -58,10 +57,11 @@ class CustomSelectorActivity: BaseActivity(), FolderClickListener, ImageSelectLi
setupViews()
// Open folder if saved in prefs.
- if(prefs.contains("FolderId")){
- val lastOpenFolderId: Long = prefs.getLong("FolderId", 0L)
- val lastOpenFolderName: String? = prefs.getString("FolderName", null)
- lastOpenFolderName?.let { onFolderClick(lastOpenFolderId, it) }
+ if(prefs.contains(FOLDER_ID)){
+ val lastOpenFolderId: Long = prefs.getLong(FOLDER_ID, 0L)
+ val lastOpenFolderName: String? = prefs.getString(FOLDER_NAME, null)
+ val lastItemId: Long = prefs.getLong(ITEM_ID, 0)
+ lastOpenFolderName?.let { onFolderClick(lastOpenFolderId, it, lastItemId) }
}
}
@@ -107,9 +107,9 @@ class CustomSelectorActivity: BaseActivity(), FolderClickListener, ImageSelectLi
/**
* override on folder click, change the toolbar title on folder click.
*/
- override fun onFolderClick(folderId: Long, folderName: String) {
+ override fun onFolderClick(folderId: Long, folderName: String, lastItemId: Long) {
supportFragmentManager.beginTransaction()
- .add(R.id.fragment_container, ImageFragment.newInstance(folderId))
+ .add(R.id.fragment_container, ImageFragment.newInstance(folderId, lastItemId))
.addToBackStack(null)
.commit()
@@ -172,25 +172,27 @@ class CustomSelectorActivity: BaseActivity(), FolderClickListener, ImageSelectLi
super.onBackPressed()
val fragment = supportFragmentManager.findFragmentById(R.id.fragment_container)
if(fragment != null && fragment is FolderFragment){
+ isImageFragmentOpen = false
changeTitle(getString(R.string.custom_selector_title))
}
}
+ /**
+ * On activity destroy
+ * If image fragment is open, overwrite its attributes otherwise discard the values.
+ */
override fun onDestroy() {
if(isImageFragmentOpen){
- prefs.edit().putLong("FolderId", bucketId).putString("FolderName", bucketName).apply()
+ prefs.edit().putLong(FOLDER_ID, bucketId).putString(FOLDER_NAME, bucketName).apply()
} else {
- prefs.edit().remove("FolderId").remove("FolderName").apply()
+ prefs.edit().remove(FOLDER_ID).remove(FOLDER_NAME).apply()
}
super.onDestroy()
}
- /**
- * Called whenever the contents of the back stack change.
- */
- override fun onBackStackChanged() {
- if(supportFragmentManager.backStackEntryCount == 0) {
- isImageFragmentOpen = false
- }
+ companion object {
+ const val FOLDER_ID : String = "FolderId"
+ const val FOLDER_NAME : String = "FolderName"
+ const val ITEM_ID : String = "ItemId"
}
}
\ No newline at end of file
diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/FolderFragment.kt b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/FolderFragment.kt
index e43c0798cc..b1cd8ab371 100644
--- a/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/FolderFragment.kt
+++ b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/FolderFragment.kt
@@ -14,6 +14,7 @@ import fr.free.nrw.commons.customselector.helper.ImageHelper
import fr.free.nrw.commons.customselector.model.Result
import fr.free.nrw.commons.customselector.listeners.FolderClickListener
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.di.CommonsDaggerSupportFragment
import fr.free.nrw.commons.media.MediaClient
@@ -55,6 +56,11 @@ class FolderFragment : CommonsDaggerSupportFragment() {
*/
private lateinit var gridLayoutManager: GridLayoutManager
+ /**
+ * Folder List.
+ */
+ private lateinit var folders : ArrayList
+
/**
* Companion newInstance.
*/
@@ -102,7 +108,7 @@ class FolderFragment : CommonsDaggerSupportFragment() {
*/
private fun handleResult(result: Result) {
if(result.status is CallbackStatus.SUCCESS){
- val folders = ImageHelper.folderListFromImages(result.images)
+ folders = ImageHelper.folderListFromImages(result.images)
folderAdapter.init(folders)
folderAdapter.notifyDataSetChanged()
selectorRV?.let {
@@ -114,6 +120,11 @@ class FolderFragment : CommonsDaggerSupportFragment() {
}
}
+ override fun onResume() {
+ folderAdapter.notifyDataSetChanged()
+ super.onResume()
+ }
+
/**
* Return Column count ie span count for grid view adapter.
*/
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 cbb3fc4425..b575e015b6 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
@@ -1,6 +1,8 @@
package fr.free.nrw.commons.customselector.ui.selector
+import android.net.Uri
import android.os.Bundle
+import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@@ -13,11 +15,15 @@ import fr.free.nrw.commons.R
import fr.free.nrw.commons.customselector.helper.ImageHelper
import fr.free.nrw.commons.customselector.listeners.ImageSelectListener
import fr.free.nrw.commons.customselector.model.CallbackStatus
+import fr.free.nrw.commons.customselector.model.Image
import fr.free.nrw.commons.customselector.model.Result
import fr.free.nrw.commons.customselector.ui.adapter.ImageAdapter
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment
-import kotlinx.android.synthetic.main.fragment_custom_selector.*
+import fr.free.nrw.commons.theme.BaseActivity
import kotlinx.android.synthetic.main.fragment_custom_selector.view.*
+import java.io.File
+import java.io.FileInputStream
+import java.net.URI
import javax.inject.Inject
class ImageFragment: CommonsDaggerSupportFragment() {
@@ -27,13 +33,18 @@ class ImageFragment: CommonsDaggerSupportFragment() {
*/
private var bucketId: Long? = null
+ /**
+ * Last ImageItem Id.
+ */
+ private var lastItemId: Long? = null
+
/**
* View model for images.
*/
private var viewModel: CustomSelectorViewModel? = null
/**
- * View Elements
+ * View Elements.
*/
private var selectorRV: RecyclerView? = null
private var loader: ProgressBar? = null
@@ -67,14 +78,16 @@ class ImageFragment: CommonsDaggerSupportFragment() {
* BucketId args name
*/
const val BUCKET_ID = "BucketId"
+ const val LAST_ITEM_ID = "LastItemId"
/**
* newInstance from bucketId.
*/
- fun newInstance(bucketId: Long): ImageFragment {
+ fun newInstance(bucketId: Long, lastItemId: Long): ImageFragment {
val fragment = ImageFragment()
val args = Bundle()
args.putLong(BUCKET_ID, bucketId)
+ args.putLong(LAST_ITEM_ID, lastItemId)
fragment.arguments = args
return fragment
}
@@ -87,6 +100,7 @@ class ImageFragment: CommonsDaggerSupportFragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
bucketId = arguments?.getLong(BUCKET_ID)
+ lastItemId = arguments?.getLong(LAST_ITEM_ID, 0)
viewModel = ViewModelProvider(requireActivity(),customSelectorViewModelFactory).get(CustomSelectorViewModel::class.java)
}
@@ -116,6 +130,8 @@ class ImageFragment: CommonsDaggerSupportFragment() {
return root
}
+ lateinit var filteredImages: ArrayList;
+
/**
* Handle view model result.
*/
@@ -123,9 +139,14 @@ class ImageFragment: CommonsDaggerSupportFragment() {
if(result.status is CallbackStatus.SUCCESS){
val images = result.images
if(images.isNotEmpty()) {
- imageAdapter.init(ImageHelper.filterImages(images,bucketId))
- selectorRV?.let{
+ filteredImages = ImageHelper.filterImages(images, bucketId)
+ imageAdapter.init(filteredImages)
+ selectorRV?.let {
it.visibility = View.VISIBLE
+ lastItemId?.let { pos ->
+ (it.layoutManager as GridLayoutManager)
+ .scrollToPosition(ImageHelper.getIndexFromId(filteredImages, pos))
+ }
}
}
else{
@@ -149,11 +170,35 @@ class ImageFragment: CommonsDaggerSupportFragment() {
// todo change span count depending on the device orientation and other factos.
}
+ override fun onResume() {
+ imageAdapter.notifyDataSetChanged()
+ super.onResume()
+ }
+
/**
- * OnDestroy Cleanup the imageLoader coroutine.
+ * OnDestroy
+ * Cleanup the imageLoader coroutine.
+ * Save the Image Fragment state.
*/
override fun onDestroy() {
imageLoader?.cleanUP()
+
+ val position = (selectorRV?.layoutManager as GridLayoutManager)
+ .findFirstVisibleItemPosition()
+
+ // Check for empty RecyclerView.
+ if (position != -1) {
+ context?.let { context ->
+ context.getSharedPreferences(
+ "CustomSelector",
+ BaseActivity.MODE_PRIVATE
+ )?.let { prefs ->
+ prefs.edit()?.let { editor ->
+ editor.putLong("ItemId", imageAdapter.getImageIdAt(position))?.apply()
+ }
+ }
+ }
+ }
super.onDestroy()
}
}
\ No newline at end of file
diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/ImageLoader.kt b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/ImageLoader.kt
index 3b5254f86d..73b2f1f79c 100644
--- a/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/ImageLoader.kt
+++ b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/ImageLoader.kt
@@ -13,6 +13,7 @@ import fr.free.nrw.commons.upload.FileProcessor
import fr.free.nrw.commons.upload.FileUtilsWrapper
import kotlinx.coroutines.*
import timber.log.Timber
+import java.io.FileNotFoundException
import java.io.IOException
import java.net.UnknownHostException
import java.util.*
@@ -86,6 +87,8 @@ class ImageLoader @Inject constructor(
}
val imageSHA1 = getImageSHA1(image.uri)
+ if(imageSHA1.isEmpty())
+ return@launch
val uploadedStatus = getFromUploaded(imageSHA1)
val sha1 = uploadedStatus?.let {
@@ -195,9 +198,14 @@ class ImageLoader @Inject constructor(
mapImageSHA1[uri]?.let{
return@withContext it
}
- val result = fileUtilsWrapper.getSHA1(context.contentResolver.openInputStream(uri))
- mapImageSHA1[uri] = result
- result
+ try {
+ val result = fileUtilsWrapper.getSHA1(context.contentResolver.openInputStream(uri))
+ mapImageSHA1[uri] = result
+ result
+ } catch (e: FileNotFoundException){
+ e.printStackTrace()
+ ""
+ }
}
}
diff --git a/app/src/main/res/drawable/ic_custom_image_picker.xml b/app/src/main/res/drawable/ic_custom_image_picker.xml
new file mode 100644
index 0000000000..7dd39280a6
--- /dev/null
+++ b/app/src/main/res/drawable/ic_custom_image_picker.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_contributions_list.xml b/app/src/main/res/layout/fragment_contributions_list.xml
index e9852f49af..923cc83431 100644
--- a/app/src/main/res/layout/fragment_contributions_list.xml
+++ b/app/src/main/res/layout/fragment_contributions_list.xml
@@ -70,17 +70,17 @@
app:srcCompat="@drawable/ic_photo_white_24dp" />
+ android:id="@+id/fab_custom_gallery"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:tint="@color/button_blue"
+ android:visibility="gone"
+ app:backgroundTint="@color/main_background_light"
+ app:useCompatPadding="true"
+ app:elevation="@dimen/tiny_margin"
+ app:fabSize="mini"
+ app:srcCompat="@drawable/ic_custom_image_picker"
+ android:background="@drawable/commons"/>
Date: Mon, 9 Aug 2021 16:32:18 +0530
Subject: [PATCH 19/28] rebase fix
---
app/src/main/res/values/attrs.xml | 1 -
1 file changed, 1 deletion(-)
diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml
index fb61e8d18a..f43772fb55 100644
--- a/app/src/main/res/values/attrs.xml
+++ b/app/src/main/res/values/attrs.xml
@@ -51,7 +51,6 @@
-
From 1b984c8b9b88bbc9922798d086d8d6e91f91c7f6 Mon Sep 17 00:00:00 2001
From: Aditya-Srivastav <54016427+4D17Y4@users.noreply.github.com>
Date: Wed, 14 Jul 2021 16:35:08 +0530
Subject: [PATCH 20/28] [GSoC] Master rebase. (#4505)
* 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 b1b4257f205b022ffaadee9f947357e5fc04c337.
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
* 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
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
Co-authored-by: neslihanturan
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
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 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
* Fixes #3694 Pre-select places as depictions (#4452)
* WikidataEditService: stop automatically adding WikidataPlace as a depiction
When the user initiates the upload process from Nearby and also manually adds the place as a depiction, the depiction is added twice. Since this behavior is invisible to the user, it is being removed in preparation for auto-selecting the place as a depiction on the DepictsFragment screen.
* DepictsFragment: auto-select place as a depiction
Pass the Place reference from UploadActivity to DepictsFragment and select the corresponding DepictedItem. Using the place id, retrieve the corresponding Entity to create and select a DepictedItem.
* UploadRepository: use Place from UploadItem to obtain a DepictedItem
Instead of passing a Place object from UploadActivity to DepictsFragment and then passing the Place object up the chain to obtain and select a DepictedItem, retrieve the Place object directly within UploadRepository
* DepictsFragment: select Place depiction when fragment becomes visible
* UploadDepictsAdapter: make adapter aware of selection state
Update selection state when recycled list items are automatically selected, preventing automatically selected items from appearing as unselected until they are forced to re-bind (i.e. after scrolling)
* DepictsFragment: pre-select place depictions for all UploadItems
If several images are selected and set to different places, pre-select all place depictions to reinforce the intended upload workflow philosophy (i.e. all images in a set are intended to be from/of the same place). See discussion in commons-app/apps-android-commons#3694
* DepictsFragment: scroll to the top every time list is updated
* Typo fixes (#4461)
* Fixed typo on class documentation of TextUtils
* corrected comma placement in documentation
* Fixed typos in comments
* fix-issue-4424 (#4445)
Co-authored-by: Pratham2305
* fix edit categories ui (#4414)
Co-authored-by: Pratham2305
* Fix doom version issue (#4463)
* Update db version
* DBOpenHelper version update
* fix :Back Pressed Event not work in Explore tab when user not login (#4404)
* fix :Back Pressed Event not work in Explore tab
* minor changes
* fix :Upload count or number of contribution does not get updated when media is successful uploaded (#4399)
* * fix:Number of Contributions not updated
* Add javadocs
* minor changes
* made minor changes
* String was nonsense and untranslatible, fixed (#4466)
* Ability to show captions and descriptions in all entered languages (#4355)
* implement Ability to show captions and descriptions in all entered languages
*Add Javadoc
* handle Back event of fragment(mediaDetailFragment)
* fix minor bugs
* add internationalization
* revert previous changes
* fix visibility bug
* resolve conflict
* Fixes #4437 - Changed indentation on files with 2 spaces to 4 spaces (#4462)
* Edited Project.xml to make indent size 4
* Changed files with 2 space indentation to use 4 space indentation
* Edited Project.xml to make indent size 4
* changed files with 2 space indent to 4 space indent
* fix :Back Pressed Event not work in Explore tab when user not login (#4404)
* fix :Back Pressed Event not work in Explore tab
* minor changes
* fix :Upload count or number of contribution does not get updated when media is successful uploaded (#4399)
* * fix:Number of Contributions not updated
* Add javadocs
* minor changes
* made minor changes
* String was nonsense and untranslatible, fixed (#4466)
* Ability to show captions and descriptions in all entered languages (#4355)
* implement Ability to show captions and descriptions in all entered languages
*Add Javadoc
* handle Back event of fragment(mediaDetailFragment)
* fix minor bugs
* add internationalization
* revert previous changes
* fix visibility bug
* resolve conflict
Co-authored-by: Prince kushwaha <65972015+Prince-kushwaha@users.noreply.github.com>
Co-authored-by: neslihanturan
* Use more understandable strings (#4470)
* Fix #3792 Missing Column Issue (#4468)
* Fix Missing Column Issue
* Fix tests
* Add UploadCategoriesFragment Unit Tests (#4473)
* Panorama (#4467)
* panoramic images fixed
* made requested changes
* Minor refactoring
Co-authored-by: Aditya Srivastava
* Localisation updates from https://translatewiki.net.
* Main activity title is sometimes "Contributions", sometimes "Commons" (#4472)
Fixes #4438 Replace == with equals() in onRestoreInstanceState
* Localisation updates from https://translatewiki.net.
* caption and description copyable (#4481)
* Removed next button in quiz (#4382)
* issues resolved
* modification done
* warning fixed
* issues resolved
* Button added
* don't know function added
* Button added
* modification done
* modification done
* Localisation updates from https://translatewiki.net.
* Added option to show and modify location while uploading (#4475)
* initial commit
* Everything done
* minor modification
* minor modification
* Issues fixed
* minor modifications
* issue fixed
* Issues fixed
* Tutorial removed from log out state (#4479)
* tutorial removed from log out state
* Issue removed
* Update changelog.md
* Versioning for v3.0.2
* Fix #4482 (#4484)
* Fix crash when image resolution is very high (#4483)
* Localisation updates from https://translatewiki.net.
* Add Contributions Fragment Unit Tests (#4490)
* Fix Tests Errors (#4491)
* Add UploadMediaDetailFragment Unit Tests (#4492)
* Localisation updates from https://translatewiki.net.
* Localisation updates from https://translatewiki.net.
* Localisation updates from https://translatewiki.net.
* 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
* [GSOC] Added Image Fetch (#4449)
* Added basic Fetch
* added permission request
* Folder count rectified
* Loaded thumbnail
* disabled overlay
* Added sha1 function
* Documented the code
* [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 b1b4257f205b022ffaadee9f947357e5fc04c337.
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
* 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
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
Co-authored-by: neslihanturan
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
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
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
Co-authored-by: Josephine Lim
Co-authored-by: Ashish
Co-authored-by: neslihanturan
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
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>
* [GSoC] Show uploaded images differently. (#4464)
* uploaded images shown differently
* Loaded images before query
* Handled exceptions, Made ImageLoader injectable, Document and clean code
* [GSoC] Added Uploaded status table in room database. (#4476)
* added Uploaded status table in room database
* Added unique property, minor refractoring
* Database intigrated
* Database integrated
* Handled result null exception
* Exceptions handled and refractored
* Introduced constants
* moved to sealed class
* No database insert on network error
* queried original image
* documented the code
* Updated uploaded status on upload success
* Image Helper test (#4485)
* [GSoC] Adapter Tests (#4488)
* Added FolderAdapterTest
* Image Adapter Test
* merge fix
* rebase fix
Co-authored-by: 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
Co-authored-by: Josephine Lim
Co-authored-by: Ashish
Co-authored-by: neslihanturan
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
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>
Co-authored-by: Brigham Byerly <6891883+byerlyb20@users.noreply.github.com>
Co-authored-by: Jamie Brown
Co-authored-by: Prince kushwaha <65972015+Prince-kushwaha@users.noreply.github.com>
Co-authored-by: Nicolas Raoul
Co-authored-by: Ashar
---
app/src/main/res/values/attrs.xml | 1 +
1 file changed, 1 insertion(+)
diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml
index f43772fb55..fb61e8d18a 100644
--- a/app/src/main/res/values/attrs.xml
+++ b/app/src/main/res/values/attrs.xml
@@ -51,6 +51,7 @@
+
From ea29a726a179f87bba10ec54e5ba6fb23f7a2429 Mon Sep 17 00:00:00 2001
From: Aditya-Srivastav <54016427+4D17Y4@users.noreply.github.com>
Date: Tue, 10 Aug 2021 07:31:15 +0530
Subject: [PATCH 21/28] Tests updated (#4538)
---
.../ui/adapter/FolderAdapter.kt | 2 +-
.../customselector/ui/adapter/ImageAdapter.kt | 2 +-
.../ui/selector/ImageFragment.kt | 3 +-
.../ui/adapter/FolderAdapterTest.kt | 22 +++++++++++--
.../ui/adapter/ImageAdapterTest.kt | 28 ++++++++++++++--
.../ui/selector/CustomSelectorActivityTest.kt | 25 +++++++++++++-
.../ui/selector/FolderFragmentTest.kt | 11 +++++++
.../ui/selector/ImageFragmentTest.kt | 33 ++++++++++++++++++-
8 files changed, 116 insertions(+), 10 deletions(-)
diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/ui/adapter/FolderAdapter.kt b/app/src/main/java/fr/free/nrw/commons/customselector/ui/adapter/FolderAdapter.kt
index 93759bdf48..03790a7d87 100644
--- a/app/src/main/java/fr/free/nrw/commons/customselector/ui/adapter/FolderAdapter.kt
+++ b/app/src/main/java/fr/free/nrw/commons/customselector/ui/adapter/FolderAdapter.kt
@@ -69,7 +69,7 @@ class FolderAdapter(
}
} else {
val previewImage = folder.images[0]
- Glide.with(context).load(previewImage.uri).into(holder.image)
+ Glide.with(holder.image).load(previewImage.uri).into(holder.image)
holder.name.text = folder.name
holder.count.text = count.toString()
holder.itemView.setOnClickListener {
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 8225ab2dc0..2dd97f3e3e 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
@@ -85,7 +85,7 @@ class ImageAdapter(
} else {
holder.itemUnselected();
}
- Glide.with(context).load(image.uri).thumbnail(0.3f).into(holder.image)
+ Glide.with(holder.image).load(image.uri).thumbnail(0.3f).into(holder.image)
imageLoader.queryAndSetView(holder, image)
holder.itemView.setOnClickListener {
selectOrRemoveImage(holder, position)
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 b575e015b6..ef49f27e7e 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
@@ -48,6 +48,7 @@ class ImageFragment: CommonsDaggerSupportFragment() {
*/
private var selectorRV: RecyclerView? = null
private var loader: ProgressBar? = null
+ lateinit var filteredImages: ArrayList;
/**
* View model Factory.
@@ -130,8 +131,6 @@ class ImageFragment: CommonsDaggerSupportFragment() {
return root
}
- lateinit var filteredImages: ArrayList;
-
/**
* Handle view model result.
*/
diff --git a/app/src/test/kotlin/fr/free/nrw/commons/customselector/ui/adapter/FolderAdapterTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/customselector/ui/adapter/FolderAdapterTest.kt
index 6a6271b96e..1c2a663f38 100644
--- a/app/src/test/kotlin/fr/free/nrw/commons/customselector/ui/adapter/FolderAdapterTest.kt
+++ b/app/src/test/kotlin/fr/free/nrw/commons/customselector/ui/adapter/FolderAdapterTest.kt
@@ -1,11 +1,14 @@
package fr.free.nrw.commons.customselector.ui.adapter
+import android.content.ContentResolver
import fr.free.nrw.commons.R
import android.content.Context
import android.net.Uri
import android.view.LayoutInflater
import android.view.View
import android.widget.GridLayout
+import com.nhaarman.mockitokotlin2.any
+import com.nhaarman.mockitokotlin2.whenever
import fr.free.nrw.commons.TestCommonsApplication
import fr.free.nrw.commons.customselector.listeners.FolderClickListener
import fr.free.nrw.commons.customselector.model.Folder
@@ -15,7 +18,10 @@ import org.junit.Before
import org.junit.Test
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.runner.RunWith
+import org.mockito.Mock
import org.mockito.Mockito
+import org.mockito.MockitoAnnotations
+import org.powermock.reflect.Whitebox
import org.robolectric.Robolectric
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
@@ -34,13 +40,21 @@ class FolderAdapterTest {
private lateinit var folder: Folder
private lateinit var folderList: ArrayList
+ @Mock
+ private lateinit var context: Context
+
+ @Mock
+ private lateinit var mockContentResolver: ContentResolver
+
@Before
@Throws(Exception::class)
fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
activity = Robolectric.buildActivity(CustomSelectorActivity::class.java).get()
image = Image(1, "image", uri, "abc/abc", 1, "bucket1")
folder = Folder(1, "bucket1", ArrayList(listOf(image)))
- folderList = ArrayList(listOf(folder))
+ folderList = ArrayList(listOf(folder, folder, folder))
folderAdapter = FolderAdapter(activity, activity as FolderClickListener)
}
@@ -57,9 +71,13 @@ class FolderAdapterTest {
*/
@Test
fun onBindViewHolder() {
- folderAdapter.init(folderList)
val inflater = activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
val listItemView: View = inflater.inflate(R.layout.item_custom_selector_folder, null, false)
+
+ whenever(context.contentResolver).thenReturn(mockContentResolver)
+ whenever(mockContentResolver.getType(any())).thenReturn("jpg")
+ Whitebox.setInternalState(folderAdapter, "context", context)
+ folderAdapter.init(folderList)
folderAdapter.onBindViewHolder(FolderAdapter.FolderViewHolder(listItemView), 0)
}
diff --git a/app/src/test/kotlin/fr/free/nrw/commons/customselector/ui/adapter/ImageAdapterTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/customselector/ui/adapter/ImageAdapterTest.kt
index 8de08a2d32..fac24cb326 100644
--- a/app/src/test/kotlin/fr/free/nrw/commons/customselector/ui/adapter/ImageAdapterTest.kt
+++ b/app/src/test/kotlin/fr/free/nrw/commons/customselector/ui/adapter/ImageAdapterTest.kt
@@ -1,9 +1,12 @@
package fr.free.nrw.commons.customselector.ui.adapter
+import android.content.ContentResolver
import android.content.Context
+import android.net.Uri
import android.view.LayoutInflater
import android.view.View
import android.widget.GridLayout
+import com.nhaarman.mockitokotlin2.whenever
import fr.free.nrw.commons.R
import fr.free.nrw.commons.TestCommonsApplication
import fr.free.nrw.commons.customselector.listeners.ImageSelectListener
@@ -15,6 +18,7 @@ import org.junit.Test
import org.junit.jupiter.api.Assertions
import org.junit.runner.RunWith
import org.mockito.*
+import org.powermock.reflect.Whitebox
import org.robolectric.Robolectric
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
@@ -26,18 +30,23 @@ import java.lang.reflect.Field
@RunWith(RobolectricTestRunner::class)
@Config(sdk = [21], application = TestCommonsApplication::class)
class ImageAdapterTest {
- @Mock
- private lateinit var image: Image
@Mock
private lateinit var imageLoader: ImageLoader
@Mock
private lateinit var imageSelectListener: ImageSelectListener
+ @Mock
+ private lateinit var context: Context
+ @Mock
+ private lateinit var mockContentResolver: ContentResolver
private lateinit var activity: CustomSelectorActivity
private lateinit var imageAdapter: ImageAdapter
private lateinit var images : ArrayList
private lateinit var holder: ImageAdapter.ImageViewHolder
private lateinit var selectedImageField: Field
+ private var uri: Uri = Mockito.mock(Uri::class.java)
+ private lateinit var image: Image
+
/**
* Set up variables.
@@ -48,6 +57,7 @@ class ImageAdapterTest {
MockitoAnnotations.initMocks(this)
activity = Robolectric.buildActivity(CustomSelectorActivity::class.java).get()
imageAdapter = ImageAdapter(activity, imageSelectListener, imageLoader)
+ image = Image(1, "image", uri, "abc/abc", 1, "bucket1")
images = ArrayList()
val inflater = activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
@@ -71,6 +81,11 @@ class ImageAdapterTest {
*/
@Test
fun onBindViewHolder() {
+
+ whenever(context.contentResolver).thenReturn(mockContentResolver)
+ whenever(mockContentResolver.getType(uri)).thenReturn("jpg")
+ Whitebox.setInternalState(imageAdapter, "context", context)
+
// Parameters.
images.add(image)
imageAdapter.init(images)
@@ -118,4 +133,13 @@ class ImageAdapterTest {
fun getItemCount() {
Assertions.assertEquals(0, imageAdapter.itemCount)
}
+
+ /**
+ * Test getImageId
+ */
+ @Test
+ fun getImageIdAt() {
+ imageAdapter.init(listOf(image))
+ Assertions.assertEquals(1, imageAdapter.getImageIdAt(0))
+ }
}
\ No newline at end of file
diff --git a/app/src/test/kotlin/fr/free/nrw/commons/customselector/ui/selector/CustomSelectorActivityTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/customselector/ui/selector/CustomSelectorActivityTest.kt
index 6d55a49e23..21007daeb5 100644
--- a/app/src/test/kotlin/fr/free/nrw/commons/customselector/ui/selector/CustomSelectorActivityTest.kt
+++ b/app/src/test/kotlin/fr/free/nrw/commons/customselector/ui/selector/CustomSelectorActivityTest.kt
@@ -2,7 +2,11 @@ package fr.free.nrw.commons.customselector.ui.selector
import android.net.Uri
import android.os.Bundle
+import android.os.Looper
+import android.os.Looper.getMainLooper
+import fr.free.nrw.commons.TestAppAdapter
import fr.free.nrw.commons.TestCommonsApplication
+import fr.free.nrw.commons.contributions.MainActivity
import fr.free.nrw.commons.customselector.model.Folder
import fr.free.nrw.commons.customselector.model.Image
import org.junit.Before
@@ -12,7 +16,11 @@ import org.junit.runner.RunWith
import org.mockito.MockitoAnnotations
import org.robolectric.Robolectric
import org.robolectric.RobolectricTestRunner
+import org.robolectric.Shadows
+import org.robolectric.Shadows.shadowOf
import org.robolectric.annotation.Config
+import org.wikipedia.AppAdapter
+import java.lang.reflect.Method
/**
* Custom Selector Activity Test
@@ -29,6 +37,8 @@ class CustomSelectorActivityTest {
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
+ AppAdapter.set(TestAppAdapter())
+
activity = Robolectric.buildActivity(CustomSelectorActivity::class.java)
.get()
val onCreate = activity.javaClass.getDeclaredMethod("onCreate", Bundle::class.java)
@@ -62,7 +72,7 @@ class CustomSelectorActivityTest {
@Test
@Throws(Exception::class)
fun testOnFolderClick() {
- activity.onFolderClick(Folder(1, "test", arrayListOf()));
+ activity.onFolderClick(1, "test", 0);
}
/**
@@ -93,4 +103,17 @@ class CustomSelectorActivityTest {
fun testOnBackPressed() {
activity.onBackPressed()
}
+
+ /**
+ * Test onDestroy Function.
+ */
+ @Test
+ @Throws(Exception::class)
+ fun testOnDestroy() {
+ val method: Method = CustomSelectorActivity::class.java.getDeclaredMethod(
+ "onDestroy"
+ )
+ method.isAccessible = true
+ method.invoke(activity)
+ }
}
\ No newline at end of file
diff --git a/app/src/test/kotlin/fr/free/nrw/commons/customselector/ui/selector/FolderFragmentTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/customselector/ui/selector/FolderFragmentTest.kt
index 53094d6f7b..c77f243427 100644
--- a/app/src/test/kotlin/fr/free/nrw/commons/customselector/ui/selector/FolderFragmentTest.kt
+++ b/app/src/test/kotlin/fr/free/nrw/commons/customselector/ui/selector/FolderFragmentTest.kt
@@ -127,4 +127,15 @@ class FolderFragmentTest {
func.isAccessible = true
func.invoke(fragment, Result(CallbackStatus.SUCCESS, arrayListOf()))
}
+
+ /**
+ * Test onResume.
+ */
+ @Test
+ fun testOnResume() {
+ val func = fragment.javaClass.getDeclaredMethod("onResume")
+ func.isAccessible = true
+ func.invoke(fragment)
+ }
+
}
\ No newline at end of file
diff --git a/app/src/test/kotlin/fr/free/nrw/commons/customselector/ui/selector/ImageFragmentTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/customselector/ui/selector/ImageFragmentTest.kt
index 9794003c82..10ebcc4e8c 100644
--- a/app/src/test/kotlin/fr/free/nrw/commons/customselector/ui/selector/ImageFragmentTest.kt
+++ b/app/src/test/kotlin/fr/free/nrw/commons/customselector/ui/selector/ImageFragmentTest.kt
@@ -3,14 +3,17 @@ package fr.free.nrw.commons.customselector.ui.selector
import android.content.Context
import android.os.Bundle
import android.os.Looper
+import android.os.Looper.getMainLooper
import android.view.LayoutInflater
import android.view.View
import android.widget.ProgressBar
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentTransaction
+import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.facebook.drawee.backends.pipeline.Fresco
import com.facebook.soloader.SoLoader
+import com.nhaarman.mockitokotlin2.whenever
import fr.free.nrw.commons.R
import fr.free.nrw.commons.TestAppAdapter
import fr.free.nrw.commons.TestCommonsApplication
@@ -29,6 +32,7 @@ import org.robolectric.Robolectric
import org.robolectric.RobolectricTestRunner
import org.robolectric.RuntimeEnvironment
import org.robolectric.Shadows
+import org.robolectric.Shadows.shadowOf
import org.robolectric.annotation.Config
import org.robolectric.annotation.LooperMode
import org.wikipedia.AppAdapter
@@ -50,6 +54,9 @@ class ImageFragmentTest {
private lateinit var context: Context
private lateinit var viewModelField: Field
+ @Mock
+ private lateinit var layoutManager: GridLayoutManager
+
@Mock
private lateinit var image: Image
@@ -71,7 +78,7 @@ class ImageFragmentTest {
Fresco.initialize(context)
val activity = Robolectric.buildActivity(CustomSelectorActivity::class.java).create().get()
- fragment = ImageFragment.newInstance(1)
+ fragment = ImageFragment.newInstance(1,0)
val fragmentManager: FragmentManager = activity.supportFragmentManager
val fragmentTransaction: FragmentTransaction = fragmentManager.beginTransaction()
fragmentTransaction.add(fragment, null)
@@ -132,4 +139,28 @@ class ImageFragmentTest {
assertEquals(3, func.invoke(fragment))
}
+
+ /**
+ * Test onResume.
+ */
+ @Test
+ fun testOnResume() {
+ val func = fragment.javaClass.getDeclaredMethod("onResume")
+ func.isAccessible = true
+ func.invoke(fragment)
+ }
+
+ /**
+ * Test onDestroy.
+ */
+ @Test
+ fun testOnDestroy() {
+ shadowOf(getMainLooper()).idle()
+ selectorRV.layoutManager = layoutManager
+ whenever(layoutManager.findFirstVisibleItemPosition()).thenReturn(1)
+ val func = fragment.javaClass.getDeclaredMethod("onDestroy")
+ func.isAccessible = true
+ func.invoke(fragment)
+ }
+
}
\ No newline at end of file
From 9333e52e632ff4e389cf5c66de643c7bebab74d0 Mon Sep 17 00:00:00 2001
From: Aditya-Srivastav <54016427+4D17Y4@users.noreply.github.com>
Date: Tue, 10 Aug 2021 16:18:16 +0530
Subject: [PATCH 22/28] orientation fixed (#4540)
---
app/src/main/AndroidManifest.xml | 7 +++++--
app/src/main/res/values/strings.xml | 1 +
2 files changed, 6 insertions(+), 2 deletions(-)
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 1c10683275..a2c33778fa 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -110,8 +110,11 @@
-
+
Settings
Sign Up
Featured Images
+ Custom Selector
Category
Peer Review
About
From 2e2b2860f54f46f07d993a2eec7431951cd89867 Mon Sep 17 00:00:00 2001
From: Aditya-Srivastav <54016427+4D17Y4@users.noreply.github.com>
Date: Tue, 10 Aug 2021 17:59:33 +0530
Subject: [PATCH 23/28] refractoring (#4541)
---
.../ContributionsListFragment.java | 3 +
.../customselector/helper/ImageHelper.kt | 6 +-
.../listeners/FolderClickListener.kt | 10 +++
.../listeners/ImageLoaderListener.kt | 14 ++++
.../listeners/ImageSelectListener.kt | 8 +++
.../customselector/model/CallbackStatus.kt | 4 ++
.../commons/customselector/model/Folder.kt | 3 +
.../nrw/commons/customselector/model/Image.kt | 3 +
.../commons/customselector/model/Result.kt | 3 +
.../ui/adapter/FolderAdapter.kt | 6 +-
.../customselector/ui/adapter/ImageAdapter.kt | 3 +
.../ui/selector/CustomSelectorActivity.kt | 3 +
.../ui/selector/CustomSelectorViewModel.kt | 3 +
.../ui/selector/FolderFragment.kt | 3 +
.../ui/selector/ImageFileLoader.kt | 8 ++-
.../commons/di/CommonsApplicationModule.java | 5 ++
.../nrw/commons/filepicker/FilePicker.java | 14 ++++
.../nrw/commons/filepicker/PickedFiles.java | 64 ++++++++++++++++++-
.../free/nrw/commons/upload/UploadItem.java | 5 ++
19 files changed, 157 insertions(+), 11 deletions(-)
diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListFragment.java b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListFragment.java
index d2bceae1fe..0b739e316b 100644
--- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListFragment.java
+++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListFragment.java
@@ -269,6 +269,9 @@ private void setListeners() {
});
}
+ /**
+ * Launch Custom Selector.
+ */
@OnClick(R.id.fab_custom_gallery)
void launchCustomSelector(){
controller.initiateCustomGalleryPickWithPermission(getActivity());
diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/helper/ImageHelper.kt b/app/src/main/java/fr/free/nrw/commons/customselector/helper/ImageHelper.kt
index 1447cd2d73..06ec4c36c8 100644
--- a/app/src/main/java/fr/free/nrw/commons/customselector/helper/ImageHelper.kt
+++ b/app/src/main/java/fr/free/nrw/commons/customselector/helper/ImageHelper.kt
@@ -6,7 +6,6 @@ import fr.free.nrw.commons.customselector.model.Image
/**
* Image Helper object, includes all the static functions required by custom selector.
*/
-
object ImageHelper {
/**
@@ -65,10 +64,7 @@ object ImageHelper {
*/
fun getIndexList(list: ArrayList, masterList: ArrayList): ArrayList {
- /**
- * TODO
- * Can be optimised as masterList is sorted by time.
- */
+ // Can be optimised as masterList is sorted by time.
val indexes = arrayListOf()
for(image in list) {
diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/listeners/FolderClickListener.kt b/app/src/main/java/fr/free/nrw/commons/customselector/listeners/FolderClickListener.kt
index e016a71ba6..bc3bd518dc 100644
--- a/app/src/main/java/fr/free/nrw/commons/customselector/listeners/FolderClickListener.kt
+++ b/app/src/main/java/fr/free/nrw/commons/customselector/listeners/FolderClickListener.kt
@@ -1,5 +1,15 @@
package fr.free.nrw.commons.customselector.listeners
+/**
+ * Custom Selector Folder Click Listener
+ */
interface FolderClickListener {
+
+ /**
+ * onFolderClick
+ * @param folderId : folder id of the folder.
+ * @param folderName : folder name of the folder.
+ * @param lastItemId : last scroll position in the folder.
+ */
fun onFolderClick(folderId: Long, folderName: String, lastItemId: Long)
}
\ No newline at end of file
diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/listeners/ImageLoaderListener.kt b/app/src/main/java/fr/free/nrw/commons/customselector/listeners/ImageLoaderListener.kt
index f8540c90a4..5ba43082d4 100644
--- a/app/src/main/java/fr/free/nrw/commons/customselector/listeners/ImageLoaderListener.kt
+++ b/app/src/main/java/fr/free/nrw/commons/customselector/listeners/ImageLoaderListener.kt
@@ -2,7 +2,21 @@ package fr.free.nrw.commons.customselector.listeners
import fr.free.nrw.commons.customselector.model.Image
+/**
+ * Custom Selector Image Loader Listener
+ * responds to the device image query.
+ */
interface ImageLoaderListener {
+
+ /**
+ * On image loaded
+ * @param images : queried device images.
+ */
fun onImageLoaded(images: ArrayList)
+
+ /**
+ * On failed
+ * @param throwable : throwable exception on failure.
+ */
fun onFailed(throwable: Throwable)
}
\ 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 c29aa21e2e..688c93e649 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
@@ -2,6 +2,14 @@ package fr.free.nrw.commons.customselector.listeners
import fr.free.nrw.commons.customselector.model.Image
+/**
+ * Custom selector Image select listener
+ */
interface ImageSelectListener {
+
+ /**
+ * onSelectedImagesChanged
+ * @param selectedImages : new selected images.
+ */
fun onSelectedImagesChanged(selectedImages: ArrayList)
}
\ No newline at end of file
diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/model/CallbackStatus.kt b/app/src/main/java/fr/free/nrw/commons/customselector/model/CallbackStatus.kt
index 257b39a950..5cdcfb9bf7 100644
--- a/app/src/main/java/fr/free/nrw/commons/customselector/model/CallbackStatus.kt
+++ b/app/src/main/java/fr/free/nrw/commons/customselector/model/CallbackStatus.kt
@@ -1,5 +1,9 @@
package fr.free.nrw.commons.customselector.model
+/**
+ * sealed class Callback Status.
+ * Current status of the device image query.
+ */
sealed class CallbackStatus {
/**
IDLE : The callback is idle , doing nothing.
diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/model/Folder.kt b/app/src/main/java/fr/free/nrw/commons/customselector/model/Folder.kt
index 0ce95ec22d..6857589bdf 100644
--- a/app/src/main/java/fr/free/nrw/commons/customselector/model/Folder.kt
+++ b/app/src/main/java/fr/free/nrw/commons/customselector/model/Folder.kt
@@ -1,5 +1,8 @@
package fr.free.nrw.commons.customselector.model
+/**
+ * Custom selector data class Folder.
+ */
data class Folder(
/**
bucketId : Unique directory id, eg 540528482
diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/model/Image.kt b/app/src/main/java/fr/free/nrw/commons/customselector/model/Image.kt
index d6d296f29d..12e75580db 100644
--- a/app/src/main/java/fr/free/nrw/commons/customselector/model/Image.kt
+++ b/app/src/main/java/fr/free/nrw/commons/customselector/model/Image.kt
@@ -4,6 +4,9 @@ import android.net.Uri
import android.os.Parcel
import android.os.Parcelable
+/**
+ * Custom selector data class Image.
+ */
data class Image(
/**
id : Unique image id, primary key of image in device, eg 104950
diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/model/Result.kt b/app/src/main/java/fr/free/nrw/commons/customselector/model/Result.kt
index 0eb4decbd0..11ed8ef006 100644
--- a/app/src/main/java/fr/free/nrw/commons/customselector/model/Result.kt
+++ b/app/src/main/java/fr/free/nrw/commons/customselector/model/Result.kt
@@ -1,5 +1,8 @@
package fr.free.nrw.commons.customselector.model
+/**
+ * Custom selector data class Result.
+ */
data class Result(
/**
* CallbackStatus : stores the result status
diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/ui/adapter/FolderAdapter.kt b/app/src/main/java/fr/free/nrw/commons/customselector/ui/adapter/FolderAdapter.kt
index 03790a7d87..60d2994917 100644
--- a/app/src/main/java/fr/free/nrw/commons/customselector/ui/adapter/FolderAdapter.kt
+++ b/app/src/main/java/fr/free/nrw/commons/customselector/ui/adapter/FolderAdapter.kt
@@ -13,7 +13,10 @@ import fr.free.nrw.commons.customselector.listeners.FolderClickListener
import fr.free.nrw.commons.customselector.model.Folder
import fr.free.nrw.commons.customselector.model.Image
-class FolderAdapter(
+/**
+ * Custom selector FolderAdapter.
+ */
+class FolderAdapter(
/**
* Application context.
*/
@@ -91,7 +94,6 @@ class FolderAdapter(
diffResult.dispatchUpdatesTo(this)
}
-
/**
* returns item count.
*/
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 2dd97f3e3e..6712762036 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
@@ -16,6 +16,9 @@ import fr.free.nrw.commons.customselector.listeners.ImageSelectListener
import fr.free.nrw.commons.customselector.model.Image
import fr.free.nrw.commons.customselector.ui.selector.ImageLoader
+/**
+ * Custom selector ImageAdapter.
+ */
class ImageAdapter(
/**
* Application Context.
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 972c16fc49..2192399746 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
@@ -16,6 +16,9 @@ import fr.free.nrw.commons.theme.BaseActivity
import java.io.File
import javax.inject.Inject
+/**
+ * Custom Selector Activity.
+ */
class CustomSelectorActivity: BaseActivity(), FolderClickListener, ImageSelectListener {
/**
diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/CustomSelectorViewModel.kt b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/CustomSelectorViewModel.kt
index 4f56a808b5..cd7858d1b6 100644
--- a/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/CustomSelectorViewModel.kt
+++ b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/CustomSelectorViewModel.kt
@@ -11,6 +11,9 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel
+/**
+ * Custom Selector view model.
+ */
class CustomSelectorViewModel(var context: Context,var imageFileLoader: ImageFileLoader) : ViewModel() {
/**
diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/FolderFragment.kt b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/FolderFragment.kt
index b1cd8ab371..45237e92d4 100644
--- a/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/FolderFragment.kt
+++ b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/FolderFragment.kt
@@ -22,6 +22,9 @@ import fr.free.nrw.commons.upload.FileProcessor
import kotlinx.android.synthetic.main.fragment_custom_selector.view.*
import javax.inject.Inject
+/**
+ * Custom selector folder fragment.
+ */
class FolderFragment : CommonsDaggerSupportFragment() {
/**
diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/ImageFileLoader.kt b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/ImageFileLoader.kt
index 95cb8233ff..12e883a144 100644
--- a/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/ImageFileLoader.kt
+++ b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/ImageFileLoader.kt
@@ -9,6 +9,10 @@ import kotlinx.coroutines.*
import java.io.File
import kotlin.coroutines.CoroutineContext
+/**
+ * Custom Selector Image File Loader.
+ * Loads device images.
+ */
class ImageFileLoader(val context: Context) : CoroutineScope{
/**
@@ -39,7 +43,7 @@ class ImageFileLoader(val context: Context) : CoroutineScope{
/**
- * Load the device images using cursor
+ * Load Device images using cursor
*/
private fun getImages(listener:ImageLoaderListener) {
val cursor = context.contentResolver.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, projection, null, null, MediaStore.Images.Media.DATE_ADDED + " DESC")
@@ -99,7 +103,7 @@ class ImageFileLoader(val context: Context) : CoroutineScope{
//todo Abort loading images.
}
- /**
+ /*
*
* TODO
* Sha1 for image (original image).
diff --git a/app/src/main/java/fr/free/nrw/commons/di/CommonsApplicationModule.java b/app/src/main/java/fr/free/nrw/commons/di/CommonsApplicationModule.java
index 7d9c061ffc..bffda8332c 100644
--- a/app/src/main/java/fr/free/nrw/commons/di/CommonsApplicationModule.java
+++ b/app/src/main/java/fr/free/nrw/commons/di/CommonsApplicationModule.java
@@ -68,6 +68,11 @@ public CommonsApplicationModule(Context applicationContext) {
this.applicationContext = applicationContext;
}
+ /**
+ * Provides ImageFileLoader used to fetch device images.
+ * @param context
+ * @return
+ */
@Provides
public ImageFileLoader providesImageFileLoader(Context context) {
return new ImageFileLoader(context);
diff --git a/app/src/main/java/fr/free/nrw/commons/filepicker/FilePicker.java b/app/src/main/java/fr/free/nrw/commons/filepicker/FilePicker.java
index 6d516abd96..bc43cb1543 100644
--- a/app/src/main/java/fr/free/nrw/commons/filepicker/FilePicker.java
+++ b/app/src/main/java/fr/free/nrw/commons/filepicker/FilePicker.java
@@ -53,6 +53,12 @@ private static Intent createGalleryIntent(@NonNull Context context, int type) {
.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, configuration(context).allowsMultiplePickingInGallery());
}
+ /**
+ * CreateCustomSectorIntent, creates intent for custom selector activity.
+ * @param context
+ * @param type
+ * @return Custom selector intent
+ */
private static Intent createCustomSelectorIntent(@NonNull Context context, int type) {
storeType(context, type);
return new Intent(context, CustomSelectorActivity.class);
@@ -215,6 +221,10 @@ private static void onPictureReturnedFromDocuments(Intent data, Activity activit
}
}
+ /**
+ * onPictureReturnedFromCustomSelector.
+ * Retrieve and forward the images to upload wizard through callback.
+ */
private static void onPictureReturnedFromCustomSelector(Intent data, Activity activity, @NonNull FilePicker.Callbacks callbacks) {
try {
List files = getFilesFromCustomSelector(data, activity);
@@ -225,6 +235,10 @@ private static void onPictureReturnedFromCustomSelector(Intent data, Activity ac
}
}
+ /**
+ * Get files from custom selector
+ * Retrieve and process the selected images from the custom selector.
+ */
private static List getFilesFromCustomSelector(Intent data, Activity activity) throws IOException, SecurityException {
List files = new ArrayList<>();
ArrayList images = data.getParcelableArrayListExtra("Images");
diff --git a/app/src/main/java/fr/free/nrw/commons/filepicker/PickedFiles.java b/app/src/main/java/fr/free/nrw/commons/filepicker/PickedFiles.java
index c5eb101bc3..ea59831731 100644
--- a/app/src/main/java/fr/free/nrw/commons/filepicker/PickedFiles.java
+++ b/app/src/main/java/fr/free/nrw/commons/filepicker/PickedFiles.java
@@ -24,19 +24,38 @@
import timber.log.Timber;
-
+/**
+ * PickedFiles.
+ * Process the upload items.
+ */
public class PickedFiles implements Constants {
+ /**
+ * Get Folder Name
+ * @param context
+ * @return default application folder name.
+ */
private static String getFolderName(@NonNull Context context) {
return FilePicker.configuration(context).getFolderName();
}
+ /**
+ * tempImageDirectory
+ * @param context
+ * @return temporary image directory to copy and perform exif changes.
+ */
private static File tempImageDirectory(@NonNull Context context) {
File privateTempDir = new File(context.getCacheDir(), DEFAULT_FOLDER_NAME);
if (!privateTempDir.exists()) privateTempDir.mkdirs();
return privateTempDir;
}
+ /**
+ * writeToFile
+ * writes inputStream data to the destination file.
+ * @param in input stream of source file.
+ * @param file destination file
+ */
private static void writeToFile(InputStream in, File file) {
try {
OutputStream out = new FileOutputStream(file);
@@ -52,11 +71,24 @@ private static void writeToFile(InputStream in, File file) {
}
}
+ /**
+ * Copy file function.
+ * Copies source file to destination file.
+ * @param src source file
+ * @param dst destination file
+ * @throws IOException (File input stream exception)
+ */
private static void copyFile(File src, File dst) throws IOException {
InputStream in = new FileInputStream(src);
writeToFile(in, dst);
}
+ /**
+ * Copy files in separate thread.
+ * Copies all the uploadable files to the temp image folder on background thread.
+ * @param context
+ * @param filesToCopy uploadable file list to be copied.
+ */
static void copyFilesInSeparateThread(final Context context, final List filesToCopy) {
new Thread(() -> {
List copiedFiles = new ArrayList<>();
@@ -64,7 +96,9 @@ static void copyFilesInSeparateThread(final Context context, final List singleFileList(UploadableFile file) {
List list = new ArrayList<>();
list.add(file);
return list;
}
+ /**
+ * ScanCopiedImages
+ * Scan copied images metadata using media scanner.
+ * @param context
+ * @param copiedImages copied images list.
+ */
static void scanCopiedImages(Context context, List copiedImages) {
String[] paths = new String[copiedImages.size()];
for (int i = 0; i < copiedImages.size(); i++) {
@@ -104,6 +150,12 @@ static void scanCopiedImages(Context context, List copiedImages) {
});
}
+ /**
+ * pickedExistingPicture
+ * convert the image into uploadable file.
+ * @param photoUri Uri of the image.
+ * @return Uploadable file ready for tag redaction.
+ */
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);
File directory = tempImageDirectory(context);
@@ -116,6 +168,9 @@ public static UploadableFile pickedExistingPicture(@NonNull Context context, Uri
return new UploadableFile(photoUri, photoFile);
}
+ /**
+ * getCameraPictureLocation
+ */
static File getCameraPicturesLocation(@NonNull Context context) throws IOException {
File dir = tempImageDirectory(context);
return File.createTempFile(UUID.randomUUID().toString(), ".jpg", dir);
@@ -142,6 +197,11 @@ private static String getMimeType(@NonNull Context context, @NonNull Uri uri) {
return extension;
}
+ /**
+ * GetUriToFile
+ * @param file get uri of file
+ * @return uri of requested file.
+ */
static Uri getUriToFile(@NonNull Context context, @NonNull File file) {
String packageName = context.getApplicationContext().getPackageName();
String authority = packageName + ".provider";
diff --git a/app/src/main/java/fr/free/nrw/commons/upload/UploadItem.java b/app/src/main/java/fr/free/nrw/commons/upload/UploadItem.java
index bed3e3454f..d7a9c9582b 100644
--- a/app/src/main/java/fr/free/nrw/commons/upload/UploadItem.java
+++ b/app/src/main/java/fr/free/nrw/commons/upload/UploadItem.java
@@ -69,6 +69,11 @@ public int getImageQuality() {
return imageQuality.getValue();
}
+ /**
+ * getContentUri.
+ * @return Uri of uploadItem
+ * Uri points to image location or name, eg content://media/external/images/camera/10495 (Android 10)
+ */
public Uri getContentUri() { return contentUri; }
public void setImageQuality(final int imageQuality) {
From 029f1708037b200c4d66f85c6af8c85e071dbd82 Mon Sep 17 00:00:00 2001
From: Aditya-Srivastav <54016427+4D17Y4@users.noreply.github.com>
Date: Thu, 12 Aug 2021 17:19:07 +0530
Subject: [PATCH 24/28] refractoring (#4545)
---
.../commons/customselector/ui/selector/FolderFragment.kt | 4 ++++
.../commons/customselector/ui/selector/ImageFragment.kt | 7 +++++++
.../nrw/commons/customselector/ui/selector/ImageLoader.kt | 7 +++++++
.../main/java/fr/free/nrw/commons/upload/UploadItem.java | 5 +++++
4 files changed, 23 insertions(+)
diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/FolderFragment.kt b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/FolderFragment.kt
index 45237e92d4..456c14831a 100644
--- a/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/FolderFragment.kt
+++ b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/FolderFragment.kt
@@ -123,6 +123,10 @@ class FolderFragment : CommonsDaggerSupportFragment() {
}
}
+ /**
+ * onResume
+ * notifyDataSetChanged, rebuild the holder views to account for deleted images, folders.
+ */
override fun onResume() {
folderAdapter.notifyDataSetChanged()
super.onResume()
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 ef49f27e7e..ef9ae1fceb 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
@@ -26,6 +26,9 @@ import java.io.FileInputStream
import java.net.URI
import javax.inject.Inject
+/**
+ * Custom Selector Image Fragment.
+ */
class ImageFragment: CommonsDaggerSupportFragment() {
/**
@@ -169,6 +172,10 @@ class ImageFragment: CommonsDaggerSupportFragment() {
// todo change span count depending on the device orientation and other factos.
}
+ /**
+ * onResume
+ * notifyDataSetChanged, rebuild the holder views to account for deleted images.
+ */
override fun onResume() {
imageAdapter.notifyDataSetChanged()
super.onResume()
diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/ImageLoader.kt b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/ImageLoader.kt
index 73b2f1f79c..cf45a87c14 100644
--- a/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/ImageLoader.kt
+++ b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/ImageLoader.kt
@@ -266,7 +266,14 @@ class ImageLoader @Inject constructor(
object ERROR : Result()
}
+ /**
+ * Companion Object
+ */
companion object {
+ /**
+ * Invalidate Day count.
+ * False Database Entries are invalid after INVALIDATE_DAY_COUNT and need to be re-queried.
+ */
const val INVALIDATE_DAY_COUNT: Long = 7
}
diff --git a/app/src/main/java/fr/free/nrw/commons/upload/UploadItem.java b/app/src/main/java/fr/free/nrw/commons/upload/UploadItem.java
index d7a9c9582b..1b482717f1 100644
--- a/app/src/main/java/fr/free/nrw/commons/upload/UploadItem.java
+++ b/app/src/main/java/fr/free/nrw/commons/upload/UploadItem.java
@@ -23,6 +23,11 @@ public class UploadItem {
private final String createdTimestampSource;
private final BehaviorSubject imageQuality;
private boolean hasInvalidLocation;
+
+ /**
+ * Uri of uploadItem
+ * Uri points to image location or name, eg content://media/external/images/camera/10495 (Android 10)
+ */
private final Uri contentUri;
From 0ebbc9d1df20883d325fb1546df2cbabdc929534 Mon Sep 17 00:00:00 2001
From: Aditya-Srivastav <54016427+4D17Y4@users.noreply.github.com>
Date: Sat, 14 Aug 2021 06:33:26 +0530
Subject: [PATCH 25/28] [GSoC] Welcome Dialog (#4546)
* Welcome Dialog
* Condition Fix
* Orientation, back button Fix
---
.../customselector/ui/adapter/ImageAdapter.kt | 2 +-
.../ui/selector/CustomSelectorActivity.kt | 25 +++-
.../layout/custom_selector_info_dialog.xml | 133 ++++++++++++++++++
app/src/main/res/values/strings.xml | 5 +
4 files changed, 163 insertions(+), 2 deletions(-)
create mode 100644 app/src/main/res/layout/custom_selector_info_dialog.xml
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 6712762036..ea7505aebf 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
@@ -110,7 +110,7 @@ class ImageAdapter(
}
} else {
if(holder.isItemUploaded()){
- Toast.makeText(context, "Already Uploaded image", Toast.LENGTH_SHORT).show()
+ Toast.makeText(context, R.string.custom_selector_already_uploaded_image_text, Toast.LENGTH_SHORT).show()
} else {
selectedImages.add(images[position])
notifyItemChanged(position, ImageSelectedOrUpdated())
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 2192399746..02b0a8b1a7 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
@@ -1,10 +1,13 @@
package fr.free.nrw.commons.customselector.ui.selector
import android.app.Activity
+import android.app.Dialog
import android.content.Intent
import android.content.SharedPreferences
import android.os.Bundle
import android.view.View
+import android.view.Window
+import android.widget.Button
import android.widget.ImageButton
import android.widget.TextView
import androidx.lifecycle.ViewModelProvider
@@ -16,6 +19,7 @@ import fr.free.nrw.commons.theme.BaseActivity
import java.io.File
import javax.inject.Inject
+
/**
* Custom Selector Activity.
*/
@@ -55,10 +59,18 @@ class CustomSelectorActivity: BaseActivity(), FolderClickListener, ImageSelectLi
setContentView(R.layout.activity_custom_selector)
prefs = applicationContext.getSharedPreferences("CustomSelector", MODE_PRIVATE)
- viewModel = ViewModelProvider(this, customSelectorViewModelFactory).get(CustomSelectorViewModel::class.java)
+ viewModel = ViewModelProvider(this, customSelectorViewModelFactory).get(
+ CustomSelectorViewModel::class.java
+ )
setupViews()
+ if(prefs.getBoolean("customSelectorFirstLaunch", true)) {
+ // show welcome dialog on first launch
+ showWelcomeDialog()
+ prefs.edit().putBoolean("customSelectorFirstLaunch", false).apply()
+ }
+
// Open folder if saved in prefs.
if(prefs.contains(FOLDER_ID)){
val lastOpenFolderId: Long = prefs.getLong(FOLDER_ID, 0L)
@@ -68,6 +80,17 @@ class CustomSelectorActivity: BaseActivity(), FolderClickListener, ImageSelectLi
}
}
+ /**
+ * Show Custom Selector Welcome Dialog.
+ */
+ private fun showWelcomeDialog() {
+ val dialog = Dialog(this)
+ dialog.requestWindowFeature(Window.FEATURE_NO_TITLE)
+ dialog.setContentView(R.layout.custom_selector_info_dialog)
+ (dialog.findViewById(R.id.btn_ok) as Button).setOnClickListener { dialog.dismiss() }
+ dialog.show()
+ }
+
/**
* Set up view, default folder view.
*/
diff --git a/app/src/main/res/layout/custom_selector_info_dialog.xml b/app/src/main/res/layout/custom_selector_info_dialog.xml
new file mode 100644
index 0000000000..a34f247dd3
--- /dev/null
+++ b/app/src/main/res/layout/custom_selector_info_dialog.xml
@@ -0,0 +1,133 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index e43bae7b55..4a04fe47d6 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -651,5 +651,10 @@ Upload your first media by tapping on the add button.
No Images
Done
Back
+ Welcome to Custom Picture Selector
+ This picker shows differently pictures that are already to Commons.
+ Unlike the picture on the left, the picture on the right has the Commons logo indicating it is already uploaded.
+ Awesome
+ This image has already been uploaded to Commons.
From 25792e2165b0bf90a3f2e1307153285603d9b636 Mon Sep 17 00:00:00 2001
From: Aditya-Srivastav <54016427+4D17Y4@users.noreply.github.com>
Date: Wed, 18 Aug 2021 05:22:24 +0530
Subject: [PATCH 26/28] [GSoC] Image preview (#4559)
* Image preview
* refractor
---
.../customselector/listeners/ImageSelectListener.kt | 7 +++++++
.../commons/customselector/ui/adapter/ImageAdapter.kt | 6 ++++++
.../ui/selector/CustomSelectorActivity.kt | 11 +++++++++++
3 files changed, 24 insertions(+)
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 688c93e649..1d7310b1d5 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
@@ -1,5 +1,6 @@
package fr.free.nrw.commons.customselector.listeners
+import android.net.Uri
import fr.free.nrw.commons.customselector.model.Image
/**
@@ -12,4 +13,10 @@ interface ImageSelectListener {
* @param selectedImages : new selected images.
*/
fun onSelectedImagesChanged(selectedImages: ArrayList)
+
+ /**
+ * onLongPress
+ * @param imageUri : uri of image
+ */
+ fun onLongPress(imageUri: Uri)
}
\ 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 ea7505aebf..f3fce1cc0e 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
@@ -93,6 +93,12 @@ class ImageAdapter(
holder.itemView.setOnClickListener {
selectOrRemoveImage(holder, position)
}
+
+ // launch media preview on long click.
+ holder.itemView.setOnLongClickListener {
+ imageSelectListener.onLongPress(image.uri)
+ true
+ }
}
}
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 02b0a8b1a7..8dffe73064 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,6 +4,7 @@ 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
@@ -15,6 +16,7 @@ import fr.free.nrw.commons.R
import fr.free.nrw.commons.customselector.listeners.FolderClickListener
import fr.free.nrw.commons.customselector.listeners.ImageSelectListener
import fr.free.nrw.commons.customselector.model.Image
+import fr.free.nrw.commons.media.ZoomableActivity
import fr.free.nrw.commons.theme.BaseActivity
import java.io.File
import javax.inject.Inject
@@ -156,6 +158,15 @@ class CustomSelectorActivity: BaseActivity(), FolderClickListener, ImageSelectLi
done.visibility = if (selectedImages.isEmpty()) View.INVISIBLE else View.VISIBLE
}
+ /**
+ * onLongPress
+ * @param imageUri : uri of image
+ */
+ override fun onLongPress(imageUri: Uri) {
+ val intent = Intent(this, ZoomableActivity::class.java).setData(imageUri);
+ startActivity(intent)
+ }
+
/**
* OnDone clicked.
* Get the selected images. Remove any non existent file, forward the data to finish selector.
From 50650a169880ff4bc283f24afc6d4e324943b1e9 Mon Sep 17 00:00:00 2001
From: Aditya Srivastava
Date: Wed, 18 Aug 2021 16:40:44 +0530
Subject: [PATCH 27/28] update database version
---
app/src/main/java/fr/free/nrw/commons/data/DBOpenHelper.java | 2 +-
app/src/main/res/values/attrs.xml | 1 -
2 files changed, 1 insertion(+), 2 deletions(-)
diff --git a/app/src/main/java/fr/free/nrw/commons/data/DBOpenHelper.java b/app/src/main/java/fr/free/nrw/commons/data/DBOpenHelper.java
index 315bafe414..f9ba4a5ae3 100644
--- a/app/src/main/java/fr/free/nrw/commons/data/DBOpenHelper.java
+++ b/app/src/main/java/fr/free/nrw/commons/data/DBOpenHelper.java
@@ -13,7 +13,7 @@
public class DBOpenHelper extends SQLiteOpenHelper {
private static final String DATABASE_NAME = "commons.db";
- private static final int DATABASE_VERSION = 17;
+ private static final int DATABASE_VERSION = 18;
public static final String CONTRIBUTIONS_TABLE = "contributions";
private final String DROP_TABLE_STATEMENT="DROP TABLE IF EXISTS %s";
diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml
index fb61e8d18a..6983f67441 100644
--- a/app/src/main/res/values/attrs.xml
+++ b/app/src/main/res/values/attrs.xml
@@ -48,7 +48,6 @@
-
From 3f2d929512619e1d4ded3fa993f6674a61bbc012 Mon Sep 17 00:00:00 2001
From: Aditya Srivastava
Date: Wed, 18 Aug 2021 17:02:36 +0530
Subject: [PATCH 28/28] remove duplicates
---
app/src/main/res/values/attrs.xml | 2 --
1 file changed, 2 deletions(-)
diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml
index d944d2d0bc..f43772fb55 100644
--- a/app/src/main/res/values/attrs.xml
+++ b/app/src/main/res/values/attrs.xml
@@ -48,8 +48,6 @@
-
-