mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-27 04:43:54 +01:00
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
This commit is contained in:
parent
63798c00ca
commit
2dbda3a488
28 changed files with 1092 additions and 73 deletions
|
|
@ -110,6 +110,9 @@
|
||||||
<activity android:name=".quiz.QuizResultActivity"
|
<activity android:name=".quiz.QuizResultActivity"
|
||||||
android:label="@string/result"/>
|
android:label="@string/result"/>
|
||||||
|
|
||||||
|
<activity android:name=".customselector.ui.selector.CustomSelectorActivity"
|
||||||
|
android:label="CustomSelector"/>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".category.CategoryDetailsActivity"
|
android:name=".category.CategoryDetailsActivity"
|
||||||
android:label="@string/title_activity_featured_images"
|
android:label="@string/title_activity_featured_images"
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import static android.view.View.VISIBLE;
|
||||||
import static fr.free.nrw.commons.di.NetworkingModule.NAMED_LANGUAGE_WIKI_PEDIA_WIKI_SITE;
|
import static fr.free.nrw.commons.di.NetworkingModule.NAMED_LANGUAGE_WIKI_PEDIA_WIKI_SITE;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
|
@ -29,13 +30,16 @@ import androidx.recyclerview.widget.RecyclerView.OnItemTouchListener;
|
||||||
import androidx.recyclerview.widget.SimpleItemAnimator;
|
import androidx.recyclerview.widget.SimpleItemAnimator;
|
||||||
import butterknife.BindView;
|
import butterknife.BindView;
|
||||||
import butterknife.ButterKnife;
|
import butterknife.ButterKnife;
|
||||||
|
import butterknife.OnClick;
|
||||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||||
import fr.free.nrw.commons.Media;
|
import fr.free.nrw.commons.Media;
|
||||||
import fr.free.nrw.commons.R;
|
import fr.free.nrw.commons.R;
|
||||||
import fr.free.nrw.commons.Utils;
|
import fr.free.nrw.commons.Utils;
|
||||||
|
import fr.free.nrw.commons.customselector.ui.selector.CustomSelectorActivity;
|
||||||
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
|
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
|
||||||
import fr.free.nrw.commons.utils.DialogUtil;
|
import fr.free.nrw.commons.utils.DialogUtil;
|
||||||
import fr.free.nrw.commons.media.MediaClient;
|
import fr.free.nrw.commons.media.MediaClient;
|
||||||
|
import fr.free.nrw.commons.utils.SystemThemeUtils;
|
||||||
import fr.free.nrw.commons.utils.ViewUtil;
|
import fr.free.nrw.commons.utils.ViewUtil;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
@ -67,6 +71,11 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment impl
|
||||||
TextView noContributionsYet;
|
TextView noContributionsYet;
|
||||||
@BindView(R.id.fab_layout)
|
@BindView(R.id.fab_layout)
|
||||||
LinearLayout fab_layout;
|
LinearLayout fab_layout;
|
||||||
|
@BindView(R.id.fab_custom_gallery)
|
||||||
|
FloatingActionButton fabCustomGallery;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
SystemThemeUtils systemThemeUtils;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
ContributionController controller;
|
ContributionController controller;
|
||||||
|
|
@ -260,6 +269,12 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment impl
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OnClick(R.id.fab_custom_gallery)
|
||||||
|
void launchCustomSelector(){
|
||||||
|
Intent intent = new Intent(getActivity(), CustomSelectorActivity.class);
|
||||||
|
startActivity(intent);
|
||||||
|
}
|
||||||
|
|
||||||
private void animateFAB(final boolean isFabOpen) {
|
private void animateFAB(final boolean isFabOpen) {
|
||||||
this.isFabOpen = !isFabOpen;
|
this.isFabOpen = !isFabOpen;
|
||||||
if (fabPlus.isShown()) {
|
if (fabPlus.isShown()) {
|
||||||
|
|
@ -267,14 +282,18 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment impl
|
||||||
fabPlus.startAnimation(rotate_backward);
|
fabPlus.startAnimation(rotate_backward);
|
||||||
fabCamera.startAnimation(fab_close);
|
fabCamera.startAnimation(fab_close);
|
||||||
fabGallery.startAnimation(fab_close);
|
fabGallery.startAnimation(fab_close);
|
||||||
|
fabCustomGallery.startAnimation(fab_close);
|
||||||
fabCamera.hide();
|
fabCamera.hide();
|
||||||
fabGallery.hide();
|
fabGallery.hide();
|
||||||
|
fabCustomGallery.hide();
|
||||||
} else {
|
} else {
|
||||||
fabPlus.startAnimation(rotate_forward);
|
fabPlus.startAnimation(rotate_forward);
|
||||||
fabCamera.startAnimation(fab_open);
|
fabCamera.startAnimation(fab_open);
|
||||||
fabGallery.startAnimation(fab_open);
|
fabGallery.startAnimation(fab_open);
|
||||||
|
fabCustomGallery.startAnimation(fab_open);
|
||||||
fabCamera.show();
|
fabCamera.show();
|
||||||
fabGallery.show();
|
fabGallery.show();
|
||||||
|
fabCustomGallery.show();
|
||||||
}
|
}
|
||||||
this.isFabOpen = !isFabOpen;
|
this.isFabOpen = !isFabOpen;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
package fr.free.nrw.commons.customselector.listeners
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.customselector.model.Folder
|
||||||
|
|
||||||
|
interface FolderClickListener {
|
||||||
|
fun onFolderClick(folder : Folder)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
package fr.free.nrw.commons.customselector.listeners
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.customselector.model.Image
|
||||||
|
|
||||||
|
interface ImageLoaderListener {
|
||||||
|
fun onImageLoaded(images: ArrayList<Image>)
|
||||||
|
fun onFailed(throwable: Throwable)
|
||||||
|
}
|
||||||
|
|
@ -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<Image>)
|
||||||
|
}
|
||||||
|
|
@ -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()
|
||||||
|
}
|
||||||
|
|
@ -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<Image> = arrayListOf<Image>()
|
||||||
|
|
||||||
|
|
||||||
|
) {
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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<Image> {
|
||||||
|
override fun createFromParcel(parcel: Parcel): Image {
|
||||||
|
return Image(parcel)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun newArray(size: Int): Array<Image?> {
|
||||||
|
return arrayOfNulls(size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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<Image>) {
|
||||||
|
}
|
||||||
|
|
@ -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<FolderAdapter.FolderViewHolder?>(context) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Image Loader for loading images.
|
||||||
|
*/
|
||||||
|
private val imageLoader = ImageLoader()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of folders.
|
||||||
|
*/
|
||||||
|
private var folders: MutableList<Folder> = 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<Folder>) {
|
||||||
|
val oldFolderList: MutableList<Folder> = 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<Folder>,
|
||||||
|
var newFolders: MutableList<Folder>
|
||||||
|
) : 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))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -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<ImageAdapter.ImageViewHolder>(context) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Currently selected images.
|
||||||
|
*/
|
||||||
|
private var selectedImages = arrayListOf<Image>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of all images in adapter.
|
||||||
|
*/
|
||||||
|
private var images: ArrayList<Image> = 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<Image>) {
|
||||||
|
val oldImageList:ArrayList<Image> = images
|
||||||
|
val newImageList:ArrayList<Image> = 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<Image>,
|
||||||
|
var newImageList: ArrayList<Image>
|
||||||
|
) : 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])
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -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<T : RecyclerView.ViewHolder?>(val context: Context): RecyclerView.Adapter<T>() {
|
||||||
|
val inflater: LayoutInflater = LayoutInflater.from(context)
|
||||||
|
}
|
||||||
|
|
@ -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<TextView>(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<Image>) {
|
||||||
|
// 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.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -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<Image>) {
|
||||||
|
result.postValue(Result(CallbackStatus.SUCCESS, images))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailed(throwable: Throwable) {
|
||||||
|
result.postValue(Result(CallbackStatus.SUCCESS, arrayListOf()))
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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<CustomSelectorViewModel: ViewModel?> create(modelClass: Class<CustomSelectorViewModel>) : CustomSelectorViewModel {
|
||||||
|
return CustomSelectorViewModel(context,imageFileLoader) as CustomSelectorViewModel
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -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<Folder>()
|
||||||
|
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.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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<Image> = 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).
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
@ -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.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
package fr.free.nrw.commons.customselector.ui.selector
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Image Loader class, loads images, depending on API results.
|
||||||
|
*/
|
||||||
|
class ImageLoader {
|
||||||
|
}
|
||||||
|
|
@ -8,6 +8,7 @@ import fr.free.nrw.commons.auth.LoginActivity;
|
||||||
import fr.free.nrw.commons.auth.SignupActivity;
|
import fr.free.nrw.commons.auth.SignupActivity;
|
||||||
import fr.free.nrw.commons.category.CategoryDetailsActivity;
|
import fr.free.nrw.commons.category.CategoryDetailsActivity;
|
||||||
import fr.free.nrw.commons.contributions.MainActivity;
|
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.depictions.WikidataItemDetailsActivity;
|
||||||
import fr.free.nrw.commons.explore.SearchActivity;
|
import fr.free.nrw.commons.explore.SearchActivity;
|
||||||
import fr.free.nrw.commons.notification.NotificationActivity;
|
import fr.free.nrw.commons.notification.NotificationActivity;
|
||||||
|
|
@ -34,6 +35,9 @@ public abstract class ActivityBuilderModule {
|
||||||
@ContributesAndroidInjector
|
@ContributesAndroidInjector
|
||||||
abstract MainActivity bindContributionsActivity();
|
abstract MainActivity bindContributionsActivity();
|
||||||
|
|
||||||
|
@ContributesAndroidInjector
|
||||||
|
abstract CustomSelectorActivity bindCustomSelectorActivity();
|
||||||
|
|
||||||
@ContributesAndroidInjector
|
@ContributesAndroidInjector
|
||||||
abstract SettingsActivity bindSettingsActivity();
|
abstract SettingsActivity bindSettingsActivity();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ import fr.free.nrw.commons.R;
|
||||||
import fr.free.nrw.commons.auth.AccountUtil;
|
import fr.free.nrw.commons.auth.AccountUtil;
|
||||||
import fr.free.nrw.commons.auth.SessionManager;
|
import fr.free.nrw.commons.auth.SessionManager;
|
||||||
import fr.free.nrw.commons.contributions.ContributionDao;
|
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.data.DBOpenHelper;
|
||||||
import fr.free.nrw.commons.db.AppDatabase;
|
import fr.free.nrw.commons.db.AppDatabase;
|
||||||
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
||||||
|
|
@ -66,6 +67,11 @@ public class CommonsApplicationModule {
|
||||||
this.applicationContext = applicationContext;
|
this.applicationContext = applicationContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
public ImageFileLoader providesImageFileLoader() {
|
||||||
|
return new ImageFileLoader(this.applicationContext);
|
||||||
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
public Context providesApplicationContext() {
|
public Context providesApplicationContext() {
|
||||||
return this.applicationContext;
|
return this.applicationContext;
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,8 @@ import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsFragment;
|
||||||
import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesFragment;
|
import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesFragment;
|
||||||
import fr.free.nrw.commons.contributions.ContributionsFragment;
|
import fr.free.nrw.commons.contributions.ContributionsFragment;
|
||||||
import fr.free.nrw.commons.contributions.ContributionsListFragment;
|
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.ExploreFragment;
|
||||||
import fr.free.nrw.commons.explore.ExploreListRootFragment;
|
import fr.free.nrw.commons.explore.ExploreListRootFragment;
|
||||||
import fr.free.nrw.commons.explore.categories.media.CategoriesMediaFragment;
|
import fr.free.nrw.commons.explore.categories.media.CategoriesMediaFragment;
|
||||||
|
|
@ -49,6 +51,12 @@ public abstract class FragmentBuilderModule {
|
||||||
@ContributesAndroidInjector
|
@ContributesAndroidInjector
|
||||||
abstract MediaDetailFragment bindMediaDetailFragment();
|
abstract MediaDetailFragment bindMediaDetailFragment();
|
||||||
|
|
||||||
|
@ContributesAndroidInjector
|
||||||
|
abstract FolderFragment bindFolderFragment();
|
||||||
|
|
||||||
|
@ContributesAndroidInjector
|
||||||
|
abstract ImageFragment bindImageFragment();
|
||||||
|
|
||||||
@ContributesAndroidInjector
|
@ContributesAndroidInjector
|
||||||
abstract MediaDetailPagerFragment bindMediaDetailPagerFragment();
|
abstract MediaDetailPagerFragment bindMediaDetailPagerFragment();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,10 +15,11 @@
|
||||||
app:layout_constraintTop_toTopOf="parent"/>
|
app:layout_constraintTop_toTopOf="parent"/>
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
<include
|
<androidx.fragment.app.FragmentContainerView
|
||||||
layout="@layout/fragment_custom_selector"
|
android:id="@+id/fragment_container"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/toolbar_layout"
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
/>
|
app:layout_constraintTop_toBottomOf="@+id/toolbar_layout"/>
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
>
|
>
|
||||||
|
|
||||||
<ImageView
|
<ImageButton
|
||||||
android:id="@+id/back"
|
android:id="@+id/back"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
|
@ -13,6 +13,8 @@
|
||||||
android:layout_centerVertical="true"
|
android:layout_centerVertical="true"
|
||||||
android:background="?attr/selectableItemBackgroundBorderless"
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
android:padding="@dimen/standard_gap"
|
android:padding="@dimen/standard_gap"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
app:srcCompat="?attr/custom_selector_back" />
|
app:srcCompat="?attr/custom_selector_back" />
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -29,7 +31,7 @@
|
||||||
android:text="@string/custom_selector_title"
|
android:text="@string/custom_selector_title"
|
||||||
style="@style/TextAppearance.AppCompat.Widget.ActionBar.Title" />
|
style="@style/TextAppearance.AppCompat.Widget.ActionBar.Title" />
|
||||||
|
|
||||||
<ImageView
|
<ImageButton
|
||||||
android:id="@+id/done"
|
android:id="@+id/done"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
|
@ -37,6 +39,7 @@
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_centerVertical="true"
|
android:layout_centerVertical="true"
|
||||||
android:clickable="true"
|
android:clickable="true"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
android:focusable="true"
|
android:focusable="true"
|
||||||
android:padding="@dimen/standard_gap"
|
android:padding="@dimen/standard_gap"
|
||||||
app:srcCompat="?attr/custom_selector_done"/>
|
app:srcCompat="?attr/custom_selector_done"/>
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,19 @@
|
||||||
app:fabSize="mini"
|
app:fabSize="mini"
|
||||||
app:srcCompat="@drawable/ic_photo_white_24dp" />
|
app:srcCompat="@drawable/ic_photo_white_24dp" />
|
||||||
|
|
||||||
|
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||||
|
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/commons_logo"
|
||||||
|
android:background="@drawable/commons"/>
|
||||||
|
|
||||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||||
android:id="@+id/fab_plus"
|
android:id="@+id/fab_plus"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/selector_rv"
|
android:id="@+id/selector_rv"
|
||||||
|
android:background="?attr/mainBackground"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
android:padding="@dimen/dimen_2"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
<androidx.cardview.widget.CardView
|
<androidx.cardview.widget.CardView
|
||||||
|
|
@ -11,7 +11,6 @@
|
||||||
app:cardCornerRadius="@dimen/dimen_6"
|
app:cardCornerRadius="@dimen/dimen_6"
|
||||||
app:cardElevation="@dimen/dimen_2"
|
app:cardElevation="@dimen/dimen_2"
|
||||||
android:id="@+id/view"
|
android:id="@+id/view"
|
||||||
app:cardUseCompatPadding="true"
|
|
||||||
app:layout_constraintDimensionRatio="H,1:1"
|
app:layout_constraintDimensionRatio="H,1:1"
|
||||||
app:layout_constraintTop_toTopOf="parent">
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
|
|
@ -24,49 +23,47 @@
|
||||||
android:id="@+id/folder_thumbnail"
|
android:id="@+id/folder_thumbnail"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:scaleType="centerCrop"/>
|
android:background="@color/black"
|
||||||
|
android:alpha="0.15"
|
||||||
|
android:scaleType="centerCrop" />
|
||||||
|
|
||||||
<View
|
<View
|
||||||
|
android:id="@+id/album_overlay"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:id="@+id/album_overlay"
|
android:alpha="0.05" />
|
||||||
android:alpha="0.05"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/folder_details"
|
android:id="@+id/folder_details"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
>
|
app:layout_constraintBottom_toBottomOf="parent">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/album_name"
|
android:id="@+id/folder_name"
|
||||||
|
style="@style/TextAppearance.AppCompat.Widget.ActionBar.Title"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
app:layout_constraintLeft_toLeftOf="parent"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
android:layout_margin="@dimen/dimen_6"
|
android:layout_margin="@dimen/dimen_6"
|
||||||
android:padding="@dimen/dimen_6"
|
|
||||||
style="@style/TextAppearance.AppCompat.Widget.ActionBar.Title"
|
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:textSize="16sp"
|
android:ellipsize="end"
|
||||||
|
android:padding="@dimen/dimen_6"
|
||||||
android:singleLine="true"
|
android:singleLine="true"
|
||||||
android:ellipsize="end"/>
|
android:textSize="16sp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintLeft_toLeftOf="parent" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/album_count"
|
android:id="@+id/folder_count"
|
||||||
|
style="@style/TextAppearance.AppCompat.Widget.ActionBar.Title"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
app:layout_constraintRight_toRightOf="parent"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
android:layout_margin="5dp"
|
android:layout_margin="5dp"
|
||||||
android:padding="5dp"
|
|
||||||
android:textColor="@color/white"
|
|
||||||
android:textStyle="bold"
|
|
||||||
android:textSize="16sp"
|
android:textSize="16sp"
|
||||||
/>
|
android:textStyle="bold"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintRight_toRightOf="parent" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
@ -75,7 +72,7 @@
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:visibility="visible"
|
android:visibility="visible"
|
||||||
app:constraint_referenced_ids="folder_details,album_overlay"/>
|
app:constraint_referenced_ids="folder_details,album_overlay" />
|
||||||
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
|
||||||
|
|
@ -3,16 +3,16 @@
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
android:padding="@dimen/dimen_2"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
<androidx.cardview.widget.CardView
|
<androidx.cardview.widget.CardView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
app:layout_constraintDimensionRatio="H,1:1"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
app:cardCornerRadius="@dimen/dimen_6"
|
app:cardCornerRadius="@dimen/dimen_6"
|
||||||
app:cardElevation="@dimen/dimen_2"
|
app:cardElevation="@dimen/dimen_2"
|
||||||
android:id="@+id/view"
|
android:id="@+id/view"
|
||||||
app:cardUseCompatPadding="true"
|
|
||||||
app:layout_constraintDimensionRatio="H,1:1"
|
|
||||||
app:layout_constraintTop_toTopOf="parent">
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
|
@ -29,7 +29,7 @@
|
||||||
android:id="@+id/selected_overlay"
|
android:id="@+id/selected_overlay"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:alpha="0.4"
|
android:alpha="0.25"
|
||||||
android:background="@color/divider_grey"
|
android:background="@color/divider_grey"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
@ -38,18 +38,19 @@
|
||||||
android:layout_width="@dimen/dimen_20"
|
android:layout_width="@dimen/dimen_20"
|
||||||
android:layout_height="@dimen/dimen_20"
|
android:layout_height="@dimen/dimen_20"
|
||||||
app:layout_constraintDimensionRatio="H,1:1"
|
app:layout_constraintDimensionRatio="H,1:1"
|
||||||
android:layout_margin="@dimen/dimen_10"
|
android:textSize="11sp"
|
||||||
android:gravity="center|center_vertical"
|
|
||||||
android:includeFontPadding="false"
|
|
||||||
android:textStyle="bold"
|
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"
|
android:background="@drawable/circle_shape"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"/>
|
app:layout_constraintTop_toTopOf="parent"/>
|
||||||
|
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.Group
|
<androidx.constraintlayout.widget.Group
|
||||||
android:id="@+id/selected_view"
|
android:id="@+id/selected_group"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:visibility="visible"
|
android:visibility="visible"
|
||||||
|
|
@ -66,15 +67,15 @@
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/uploaded_overlay_icon"
|
android:id="@+id/uploaded_overlay_icon"
|
||||||
android:layout_width="@dimen/dimen_140"
|
android:layout_width="@dimen/dimen_72"
|
||||||
android:layout_height="@dimen/dimen_140"
|
android:layout_height="@dimen/dimen_72"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:srcCompat="@drawable/commons"
|
app:srcCompat="@drawable/commons"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.Group
|
<androidx.constraintlayout.widget.Group
|
||||||
android:id="@+id/uploaded"
|
android:id="@+id/uploaded_group"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:visibility="visible"
|
android:visibility="visible"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue